config.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. /*
  2. * mbsync - mailbox synchronizer
  3. * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
  4. * Copyright (C) 2002-2006,2011 Oswald Buddenhagen <ossi@users.sf.net>
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. * As a special exception, mbsync may be linked with the OpenSSL library,
  20. * despite that library's more restrictive license.
  21. */
  22. #include "isync.h"
  23. #include <unistd.h>
  24. #include <limits.h>
  25. #include <pwd.h>
  26. #include <sys/types.h>
  27. #include <string.h>
  28. #include <stdlib.h>
  29. #include <stdio.h>
  30. driver_t *drivers[N_DRIVERS] = { &maildir_driver, &imap_driver };
  31. store_conf_t *stores;
  32. channel_conf_t *channels;
  33. group_conf_t *groups;
  34. int global_ops[2];
  35. char *global_sync_state;
  36. int
  37. parse_bool( conffile_t *cfile )
  38. {
  39. if (!strcasecmp( cfile->val, "yes" ) ||
  40. !strcasecmp( cfile->val, "true" ) ||
  41. !strcasecmp( cfile->val, "on" ) ||
  42. !strcmp( cfile->val, "1" ))
  43. return 1;
  44. if (strcasecmp( cfile->val, "no" ) &&
  45. strcasecmp( cfile->val, "false" ) &&
  46. strcasecmp( cfile->val, "off" ) &&
  47. strcmp( cfile->val, "0" ))
  48. error( "%s:%d: invalid boolean value '%s'\n",
  49. cfile->file, cfile->line, cfile->val );
  50. return 0;
  51. }
  52. int
  53. parse_int( conffile_t *cfile )
  54. {
  55. char *p;
  56. int ret;
  57. ret = strtol( cfile->val, &p, 10 );
  58. if (*p) {
  59. error( "%s:%d: invalid integer value '%s'\n",
  60. cfile->file, cfile->line, cfile->val );
  61. return 0;
  62. }
  63. return ret;
  64. }
  65. int
  66. parse_size( conffile_t *cfile )
  67. {
  68. char *p;
  69. int ret;
  70. ret = strtol (cfile->val, &p, 10);
  71. if (*p == 'k' || *p == 'K')
  72. ret *= 1024, p++;
  73. else if (*p == 'm' || *p == 'M')
  74. ret *= 1024 * 1024, p++;
  75. if (*p == 'b' || *p == 'B')
  76. p++;
  77. if (*p) {
  78. fprintf (stderr, "%s:%d: invalid size '%s'\n",
  79. cfile->file, cfile->line, cfile->val);
  80. return 0;
  81. }
  82. return ret;
  83. }
  84. static int
  85. getopt_helper( conffile_t *cfile, int *cops, int ops[], char **sync_state )
  86. {
  87. char *arg;
  88. if (!strcasecmp( "Sync", cfile->cmd )) {
  89. arg = cfile->val;
  90. do
  91. if (!strcasecmp( "Push", arg ))
  92. *cops |= XOP_PUSH;
  93. else if (!strcasecmp( "Pull", arg ))
  94. *cops |= XOP_PULL;
  95. else if (!strcasecmp( "ReNew", arg ))
  96. *cops |= OP_RENEW;
  97. else if (!strcasecmp( "New", arg ))
  98. *cops |= OP_NEW;
  99. else if (!strcasecmp( "Delete", arg ))
  100. *cops |= OP_DELETE;
  101. else if (!strcasecmp( "Flags", arg ))
  102. *cops |= OP_FLAGS;
  103. else if (!strcasecmp( "PullReNew", arg ))
  104. ops[S] |= OP_RENEW;
  105. else if (!strcasecmp( "PullNew", arg ))
  106. ops[S] |= OP_NEW;
  107. else if (!strcasecmp( "PullDelete", arg ))
  108. ops[S] |= OP_DELETE;
  109. else if (!strcasecmp( "PullFlags", arg ))
  110. ops[S] |= OP_FLAGS;
  111. else if (!strcasecmp( "PushReNew", arg ))
  112. ops[M] |= OP_RENEW;
  113. else if (!strcasecmp( "PushNew", arg ))
  114. ops[M] |= OP_NEW;
  115. else if (!strcasecmp( "PushDelete", arg ))
  116. ops[M] |= OP_DELETE;
  117. else if (!strcasecmp( "PushFlags", arg ))
  118. ops[M] |= OP_FLAGS;
  119. else if (!strcasecmp( "All", arg ) || !strcasecmp( "Full", arg ))
  120. *cops |= XOP_PULL|XOP_PUSH;
  121. else if (strcasecmp( "None", arg ) && strcasecmp( "Noop", arg ))
  122. error( "%s:%d: invalid Sync arg '%s'\n",
  123. cfile->file, cfile->line, arg );
  124. while ((arg = next_arg( &cfile->rest )));
  125. ops[M] |= XOP_HAVE_TYPE;
  126. } else if (!strcasecmp( "Expunge", cfile->cmd )) {
  127. arg = cfile->val;
  128. do
  129. if (!strcasecmp( "Both", arg ))
  130. *cops |= OP_EXPUNGE;
  131. else if (!strcasecmp( "Master", arg ))
  132. ops[M] |= OP_EXPUNGE;
  133. else if (!strcasecmp( "Slave", arg ))
  134. ops[S] |= OP_EXPUNGE;
  135. else if (strcasecmp( "None", arg ))
  136. error( "%s:%d: invalid Expunge arg '%s'\n",
  137. cfile->file, cfile->line, arg );
  138. while ((arg = next_arg( &cfile->rest )));
  139. ops[M] |= XOP_HAVE_EXPUNGE;
  140. } else if (!strcasecmp( "Create", cfile->cmd )) {
  141. arg = cfile->val;
  142. do
  143. if (!strcasecmp( "Both", arg ))
  144. *cops |= OP_CREATE;
  145. else if (!strcasecmp( "Master", arg ))
  146. ops[M] |= OP_CREATE;
  147. else if (!strcasecmp( "Slave", arg ))
  148. ops[S] |= OP_CREATE;
  149. else if (strcasecmp( "None", arg ))
  150. error( "%s:%d: invalid Create arg '%s'\n",
  151. cfile->file, cfile->line, arg );
  152. while ((arg = next_arg( &cfile->rest )));
  153. ops[M] |= XOP_HAVE_CREATE;
  154. } else if (!strcasecmp( "SyncState", cfile->cmd ))
  155. *sync_state = expand_strdup( cfile->val );
  156. else
  157. return 0;
  158. return 1;
  159. }
  160. int
  161. getcline( conffile_t *cfile )
  162. {
  163. char *p;
  164. while (fgets( cfile->buf, cfile->bufl, cfile->fp )) {
  165. cfile->line++;
  166. p = cfile->buf;
  167. if (!(cfile->cmd = next_arg( &p )))
  168. return 1;
  169. if (*cfile->cmd == '#')
  170. continue;
  171. if (!(cfile->val = next_arg( &p ))) {
  172. error( "%s:%d: parameter missing\n", cfile->file, cfile->line );
  173. continue;
  174. }
  175. cfile->rest = p;
  176. return 1;
  177. }
  178. return 0;
  179. }
  180. /* XXX - this does not detect None conflicts ... */
  181. int
  182. merge_ops( int cops, int ops[] )
  183. {
  184. int aops;
  185. aops = ops[M] | ops[S];
  186. if (ops[M] & XOP_HAVE_TYPE) {
  187. if (aops & OP_MASK_TYPE) {
  188. if (aops & cops & OP_MASK_TYPE) {
  189. cfl:
  190. error( "Conflicting Sync args specified.\n" );
  191. return 1;
  192. }
  193. ops[M] |= cops & OP_MASK_TYPE;
  194. ops[S] |= cops & OP_MASK_TYPE;
  195. if (cops & XOP_PULL) {
  196. if (ops[S] & OP_MASK_TYPE)
  197. goto cfl;
  198. ops[S] |= OP_MASK_TYPE;
  199. }
  200. if (cops & XOP_PUSH) {
  201. if (ops[M] & OP_MASK_TYPE)
  202. goto cfl;
  203. ops[M] |= OP_MASK_TYPE;
  204. }
  205. } else if (cops & (OP_MASK_TYPE|XOP_MASK_DIR)) {
  206. if (!(cops & OP_MASK_TYPE))
  207. cops |= OP_MASK_TYPE;
  208. else if (!(cops & XOP_MASK_DIR))
  209. cops |= XOP_PULL|XOP_PUSH;
  210. if (cops & XOP_PULL)
  211. ops[S] |= cops & OP_MASK_TYPE;
  212. if (cops & XOP_PUSH)
  213. ops[M] |= cops & OP_MASK_TYPE;
  214. }
  215. }
  216. if (ops[M] & XOP_HAVE_EXPUNGE) {
  217. if (aops & cops & OP_EXPUNGE) {
  218. error( "Conflicting Expunge args specified.\n" );
  219. return 1;
  220. }
  221. ops[M] |= cops & OP_EXPUNGE;
  222. ops[S] |= cops & OP_EXPUNGE;
  223. }
  224. if (ops[M] & XOP_HAVE_CREATE) {
  225. if (aops & cops & OP_CREATE) {
  226. error( "Conflicting Create args specified.\n" );
  227. return 1;
  228. }
  229. ops[M] |= cops & OP_CREATE;
  230. ops[S] |= cops & OP_CREATE;
  231. }
  232. return 0;
  233. }
  234. int
  235. load_config( const char *where, int pseudo )
  236. {
  237. conffile_t cfile;
  238. store_conf_t *store, **storeapp = &stores;
  239. channel_conf_t *channel, **channelapp = &channels;
  240. group_conf_t *group, **groupapp = &groups;
  241. string_list_t *chanlist, **chanlistapp;
  242. char *arg, *p;
  243. int err, len, cops, gcops, max_size, ms, i;
  244. char path[_POSIX_PATH_MAX];
  245. char buf[1024];
  246. if (!where) {
  247. nfsnprintf( path, sizeof(path), "%s/." EXE "rc", Home );
  248. cfile.file = path;
  249. } else
  250. cfile.file = where;
  251. if (!pseudo)
  252. info( "Reading configuration file %s\n", cfile.file );
  253. if (!(cfile.fp = fopen( cfile.file, "r" ))) {
  254. sys_error( "Cannot open config file '%s'", cfile.file );
  255. return 1;
  256. }
  257. buf[sizeof(buf) - 1] = 0;
  258. cfile.buf = buf;
  259. cfile.bufl = sizeof(buf) - 1;
  260. cfile.line = 0;
  261. gcops = err = 0;
  262. reloop:
  263. while (getcline( &cfile )) {
  264. if (!cfile.cmd)
  265. continue;
  266. for (i = 0; i < N_DRIVERS; i++)
  267. if (drivers[i]->parse_store( &cfile, &store, &err )) {
  268. if (store) {
  269. if (!store->path)
  270. store->path = "";
  271. *storeapp = store;
  272. storeapp = &store->next;
  273. *storeapp = 0;
  274. }
  275. goto reloop;
  276. }
  277. if (!strcasecmp( "Channel", cfile.cmd ))
  278. {
  279. channel = nfcalloc( sizeof(*channel) );
  280. channel->name = nfstrdup( cfile.val );
  281. cops = 0;
  282. max_size = -1;
  283. while (getcline( &cfile ) && cfile.cmd) {
  284. if (!strcasecmp( "MaxSize", cfile.cmd ))
  285. max_size = parse_size( &cfile );
  286. else if (!strcasecmp( "MaxMessages", cfile.cmd ))
  287. channel->max_messages = parse_int( &cfile );
  288. else if (!strcasecmp( "Pattern", cfile.cmd ) ||
  289. !strcasecmp( "Patterns", cfile.cmd ))
  290. {
  291. arg = cfile.val;
  292. do
  293. add_string_list( &channel->patterns, arg );
  294. while ((arg = next_arg( &cfile.rest )));
  295. }
  296. else if (!strcasecmp( "Master", cfile.cmd )) {
  297. ms = M;
  298. goto linkst;
  299. } else if (!strcasecmp( "Slave", cfile.cmd )) {
  300. ms = S;
  301. linkst:
  302. if (*cfile.val != ':' || !(p = strchr( cfile.val + 1, ':' ))) {
  303. error( "%s:%d: malformed mailbox spec\n",
  304. cfile.file, cfile.line );
  305. err = 1;
  306. continue;
  307. }
  308. *p = 0;
  309. for (store = stores; store; store = store->next)
  310. if (!strcmp( store->name, cfile.val + 1 )) {
  311. channel->stores[ms] = store;
  312. goto stpcom;
  313. }
  314. error( "%s:%d: unknown store '%s'\n",
  315. cfile.file, cfile.line, cfile.val + 1 );
  316. err = 1;
  317. continue;
  318. stpcom:
  319. if (*++p)
  320. channel->boxes[ms] = nfstrdup( p );
  321. } else if (!getopt_helper( &cfile, &cops, channel->ops, &channel->sync_state )) {
  322. error( "%s:%d: unknown keyword '%s'\n", cfile.file, cfile.line, cfile.cmd );
  323. err = 1;
  324. }
  325. }
  326. if (!channel->stores[M]) {
  327. error( "channel '%s' refers to no master store\n", channel->name );
  328. err = 1;
  329. } else if (!channel->stores[S]) {
  330. error( "channel '%s' refers to no slave store\n", channel->name );
  331. err = 1;
  332. } else if (merge_ops( cops, channel->ops ))
  333. err = 1;
  334. else {
  335. if (max_size >= 0)
  336. channel->stores[M]->max_size = channel->stores[S]->max_size = max_size;
  337. *channelapp = channel;
  338. channelapp = &channel->next;
  339. }
  340. }
  341. else if (!strcasecmp( "Group", cfile.cmd ))
  342. {
  343. group = nfmalloc( sizeof(*group) );
  344. group->name = nfstrdup( cfile.val );
  345. *groupapp = group;
  346. groupapp = &group->next;
  347. *groupapp = 0;
  348. chanlistapp = &group->channels;
  349. *chanlistapp = 0;
  350. p = cfile.rest;
  351. while ((arg = next_arg( &p ))) {
  352. addone:
  353. len = strlen( arg );
  354. chanlist = nfmalloc( sizeof(*chanlist) + len );
  355. memcpy( chanlist->string, arg, len + 1 );
  356. *chanlistapp = chanlist;
  357. chanlistapp = &chanlist->next;
  358. *chanlistapp = 0;
  359. }
  360. while (getcline( &cfile )) {
  361. if (!cfile.cmd)
  362. goto reloop;
  363. if (!strcasecmp( "Channel", cfile.cmd ) ||
  364. !strcasecmp( "Channels", cfile.cmd ))
  365. {
  366. p = cfile.rest;
  367. arg = cfile.val;
  368. goto addone;
  369. }
  370. else
  371. {
  372. error( "%s:%d: unknown keyword '%s'\n",
  373. cfile.file, cfile.line, cfile.cmd );
  374. err = 1;
  375. }
  376. }
  377. break;
  378. }
  379. else if (!getopt_helper( &cfile, &gcops, global_ops, &global_sync_state ))
  380. {
  381. error( "%s:%d: unknown section keyword '%s'\n",
  382. cfile.file, cfile.line, cfile.cmd );
  383. err = 1;
  384. while (getcline( &cfile ))
  385. if (!cfile.cmd)
  386. goto reloop;
  387. break;
  388. }
  389. }
  390. fclose (cfile.fp);
  391. err |= merge_ops( gcops, global_ops );
  392. if (!global_sync_state)
  393. global_sync_state = expand_strdup( "~/." EXE "/" );
  394. if (!err && pseudo)
  395. unlink( where );
  396. return err;
  397. }
  398. void
  399. parse_generic_store( store_conf_t *store, conffile_t *cfg, int *err )
  400. {
  401. if (!strcasecmp( "Trash", cfg->cmd ))
  402. store->trash = nfstrdup( cfg->val );
  403. else if (!strcasecmp( "TrashRemoteNew", cfg->cmd ))
  404. store->trash_remote_new = parse_bool( cfg );
  405. else if (!strcasecmp( "TrashNewOnly", cfg->cmd ))
  406. store->trash_only_new = parse_bool( cfg );
  407. else if (!strcasecmp( "MaxSize", cfg->cmd ))
  408. store->max_size = parse_size( cfg );
  409. else if (!strcasecmp( "MapInbox", cfg->cmd ))
  410. store->map_inbox = nfstrdup( cfg->val );
  411. else if (!strcasecmp( "Flatten", cfg->cmd )) {
  412. int sl = strlen( cfg->val );
  413. if (sl != 1) {
  414. error( "%s:%d: malformed flattened hierarchy delimiter\n", cfg->file, cfg->line );
  415. *err = 1;
  416. } else if (cfg->val[0] == '/') {
  417. error( "%s:%d: flattened hierarchy delimiter cannot be the canonical delimiter '/'\n", cfg->file, cfg->line );
  418. *err = 1;
  419. } else {
  420. store->flat_delim = cfg->val[0];
  421. }
  422. } else {
  423. error( "%s:%d: unknown keyword '%s'\n", cfg->file, cfg->line, cfg->cmd );
  424. *err = 1;
  425. }
  426. }