config.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  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 <assert.h>
  24. #include <unistd.h>
  25. #include <limits.h>
  26. #include <pwd.h>
  27. #include <sys/types.h>
  28. #include <ctype.h>
  29. #include <string.h>
  30. #include <stdlib.h>
  31. #include <stdio.h>
  32. driver_t *drivers[N_DRIVERS] = { &maildir_driver, &imap_driver };
  33. store_conf_t *stores;
  34. channel_conf_t *channels;
  35. group_conf_t *groups;
  36. int global_ops[2];
  37. char *global_sync_state;
  38. int FSyncLevel = FSYNC_NORMAL;
  39. #define ARG_OPTIONAL 0
  40. #define ARG_REQUIRED 1
  41. static char *
  42. get_arg( conffile_t *cfile, int required, int *comment )
  43. {
  44. char *ret, *p, *t;
  45. int quoted;
  46. char c;
  47. p = cfile->rest;
  48. assert( p );
  49. while ((c = *p) && isspace( (unsigned char) c ))
  50. p++;
  51. if (!c || c == '#') {
  52. if (comment)
  53. *comment = (c == '#');
  54. if (required) {
  55. error( "%s:%d: parameter missing\n", cfile->file, cfile->line );
  56. cfile->err = 1;
  57. }
  58. ret = 0;
  59. } else {
  60. for (quoted = 0, ret = t = p; c; c = *p) {
  61. p++;
  62. if (c == '"')
  63. quoted ^= 1;
  64. else if (!quoted && isspace( (unsigned char) c ))
  65. break;
  66. else
  67. *t++ = c;
  68. }
  69. *t = 0;
  70. if (quoted) {
  71. error( "%s:%d: missing closing quote\n", cfile->file, cfile->line );
  72. cfile->err = 1;
  73. ret = 0;
  74. }
  75. }
  76. cfile->rest = p;
  77. return ret;
  78. }
  79. int
  80. parse_bool( conffile_t *cfile )
  81. {
  82. if (!strcasecmp( cfile->val, "yes" ) ||
  83. !strcasecmp( cfile->val, "true" ) ||
  84. !strcasecmp( cfile->val, "on" ) ||
  85. !strcmp( cfile->val, "1" ))
  86. return 1;
  87. if (strcasecmp( cfile->val, "no" ) &&
  88. strcasecmp( cfile->val, "false" ) &&
  89. strcasecmp( cfile->val, "off" ) &&
  90. strcmp( cfile->val, "0" )) {
  91. error( "%s:%d: invalid boolean value '%s'\n",
  92. cfile->file, cfile->line, cfile->val );
  93. cfile->err = 1;
  94. }
  95. return 0;
  96. }
  97. int
  98. parse_int( conffile_t *cfile )
  99. {
  100. char *p;
  101. int ret;
  102. ret = strtol( cfile->val, &p, 10 );
  103. if (*p) {
  104. error( "%s:%d: invalid integer value '%s'\n",
  105. cfile->file, cfile->line, cfile->val );
  106. cfile->err = 1;
  107. return 0;
  108. }
  109. return ret;
  110. }
  111. int
  112. parse_size( conffile_t *cfile )
  113. {
  114. char *p;
  115. int ret;
  116. ret = strtol (cfile->val, &p, 10);
  117. if (*p == 'k' || *p == 'K')
  118. ret *= 1024, p++;
  119. else if (*p == 'm' || *p == 'M')
  120. ret *= 1024 * 1024, p++;
  121. if (*p == 'b' || *p == 'B')
  122. p++;
  123. if (*p) {
  124. fprintf (stderr, "%s:%d: invalid size '%s'\n",
  125. cfile->file, cfile->line, cfile->val);
  126. cfile->err = 1;
  127. return 0;
  128. }
  129. return ret;
  130. }
  131. static int
  132. getopt_helper( conffile_t *cfile, int *cops, int ops[], char **sync_state )
  133. {
  134. char *arg;
  135. if (!strcasecmp( "Sync", cfile->cmd )) {
  136. arg = cfile->val;
  137. do
  138. if (!strcasecmp( "Push", arg ))
  139. *cops |= XOP_PUSH;
  140. else if (!strcasecmp( "Pull", arg ))
  141. *cops |= XOP_PULL;
  142. else if (!strcasecmp( "ReNew", arg ))
  143. *cops |= OP_RENEW;
  144. else if (!strcasecmp( "New", arg ))
  145. *cops |= OP_NEW;
  146. else if (!strcasecmp( "Delete", arg ))
  147. *cops |= OP_DELETE;
  148. else if (!strcasecmp( "Flags", arg ))
  149. *cops |= OP_FLAGS;
  150. else if (!strcasecmp( "PullReNew", arg ))
  151. ops[S] |= OP_RENEW;
  152. else if (!strcasecmp( "PullNew", arg ))
  153. ops[S] |= OP_NEW;
  154. else if (!strcasecmp( "PullDelete", arg ))
  155. ops[S] |= OP_DELETE;
  156. else if (!strcasecmp( "PullFlags", arg ))
  157. ops[S] |= OP_FLAGS;
  158. else if (!strcasecmp( "PushReNew", arg ))
  159. ops[M] |= OP_RENEW;
  160. else if (!strcasecmp( "PushNew", arg ))
  161. ops[M] |= OP_NEW;
  162. else if (!strcasecmp( "PushDelete", arg ))
  163. ops[M] |= OP_DELETE;
  164. else if (!strcasecmp( "PushFlags", arg ))
  165. ops[M] |= OP_FLAGS;
  166. else if (!strcasecmp( "All", arg ) || !strcasecmp( "Full", arg ))
  167. *cops |= XOP_PULL|XOP_PUSH;
  168. else if (strcasecmp( "None", arg ) && strcasecmp( "Noop", arg )) {
  169. error( "%s:%d: invalid Sync arg '%s'\n",
  170. cfile->file, cfile->line, arg );
  171. cfile->err = 1;
  172. }
  173. while ((arg = get_arg( cfile, ARG_OPTIONAL, 0 )));
  174. ops[M] |= XOP_HAVE_TYPE;
  175. } else if (!strcasecmp( "Expunge", cfile->cmd )) {
  176. arg = cfile->val;
  177. do
  178. if (!strcasecmp( "Both", arg ))
  179. *cops |= OP_EXPUNGE;
  180. else if (!strcasecmp( "Master", arg ))
  181. ops[M] |= OP_EXPUNGE;
  182. else if (!strcasecmp( "Slave", arg ))
  183. ops[S] |= OP_EXPUNGE;
  184. else if (strcasecmp( "None", arg )) {
  185. error( "%s:%d: invalid Expunge arg '%s'\n",
  186. cfile->file, cfile->line, arg );
  187. cfile->err = 1;
  188. }
  189. while ((arg = get_arg( cfile, ARG_OPTIONAL, 0 )));
  190. ops[M] |= XOP_HAVE_EXPUNGE;
  191. } else if (!strcasecmp( "Create", cfile->cmd )) {
  192. arg = cfile->val;
  193. do
  194. if (!strcasecmp( "Both", arg ))
  195. *cops |= OP_CREATE;
  196. else if (!strcasecmp( "Master", arg ))
  197. ops[M] |= OP_CREATE;
  198. else if (!strcasecmp( "Slave", arg ))
  199. ops[S] |= OP_CREATE;
  200. else if (strcasecmp( "None", arg )) {
  201. error( "%s:%d: invalid Create arg '%s'\n",
  202. cfile->file, cfile->line, arg );
  203. cfile->err = 1;
  204. }
  205. while ((arg = get_arg( cfile, ARG_OPTIONAL, 0 )));
  206. ops[M] |= XOP_HAVE_CREATE;
  207. } else if (!strcasecmp( "SyncState", cfile->cmd ))
  208. *sync_state = expand_strdup( cfile->val );
  209. else
  210. return 0;
  211. return 1;
  212. }
  213. int
  214. getcline( conffile_t *cfile )
  215. {
  216. int comment;
  217. while (fgets( cfile->buf, cfile->bufl, cfile->fp )) {
  218. cfile->line++;
  219. cfile->rest = cfile->buf;
  220. if (!(cfile->cmd = get_arg( cfile, ARG_OPTIONAL, &comment ))) {
  221. if (comment)
  222. continue;
  223. return 1;
  224. }
  225. if (!(cfile->val = get_arg( cfile, ARG_REQUIRED, 0 )))
  226. continue;
  227. return 1;
  228. }
  229. return 0;
  230. }
  231. /* XXX - this does not detect None conflicts ... */
  232. int
  233. merge_ops( int cops, int ops[] )
  234. {
  235. int aops;
  236. aops = ops[M] | ops[S];
  237. if (ops[M] & XOP_HAVE_TYPE) {
  238. if (aops & OP_MASK_TYPE) {
  239. if (aops & cops & OP_MASK_TYPE) {
  240. cfl:
  241. error( "Conflicting Sync args specified.\n" );
  242. return 1;
  243. }
  244. ops[M] |= cops & OP_MASK_TYPE;
  245. ops[S] |= cops & OP_MASK_TYPE;
  246. if (cops & XOP_PULL) {
  247. if (ops[S] & OP_MASK_TYPE)
  248. goto cfl;
  249. ops[S] |= OP_MASK_TYPE;
  250. }
  251. if (cops & XOP_PUSH) {
  252. if (ops[M] & OP_MASK_TYPE)
  253. goto cfl;
  254. ops[M] |= OP_MASK_TYPE;
  255. }
  256. } else if (cops & (OP_MASK_TYPE|XOP_MASK_DIR)) {
  257. if (!(cops & OP_MASK_TYPE))
  258. cops |= OP_MASK_TYPE;
  259. else if (!(cops & XOP_MASK_DIR))
  260. cops |= XOP_PULL|XOP_PUSH;
  261. if (cops & XOP_PULL)
  262. ops[S] |= cops & OP_MASK_TYPE;
  263. if (cops & XOP_PUSH)
  264. ops[M] |= cops & OP_MASK_TYPE;
  265. }
  266. }
  267. if (ops[M] & XOP_HAVE_EXPUNGE) {
  268. if (aops & cops & OP_EXPUNGE) {
  269. error( "Conflicting Expunge args specified.\n" );
  270. return 1;
  271. }
  272. ops[M] |= cops & OP_EXPUNGE;
  273. ops[S] |= cops & OP_EXPUNGE;
  274. }
  275. if (ops[M] & XOP_HAVE_CREATE) {
  276. if (aops & cops & OP_CREATE) {
  277. error( "Conflicting Create args specified.\n" );
  278. return 1;
  279. }
  280. ops[M] |= cops & OP_CREATE;
  281. ops[S] |= cops & OP_CREATE;
  282. }
  283. return 0;
  284. }
  285. int
  286. load_config( const char *where, int pseudo )
  287. {
  288. conffile_t cfile;
  289. store_conf_t *store, **storeapp = &stores;
  290. channel_conf_t *channel, **channelapp = &channels;
  291. group_conf_t *group, **groupapp = &groups;
  292. string_list_t *chanlist, **chanlistapp;
  293. char *arg, *p;
  294. int len, cops, gcops, max_size, ms, i;
  295. char path[_POSIX_PATH_MAX];
  296. char buf[1024];
  297. if (!where) {
  298. nfsnprintf( path, sizeof(path), "%s/." EXE "rc", Home );
  299. cfile.file = path;
  300. } else
  301. cfile.file = where;
  302. if (!pseudo)
  303. info( "Reading configuration file %s\n", cfile.file );
  304. if (!(cfile.fp = fopen( cfile.file, "r" ))) {
  305. sys_error( "Cannot open config file '%s'", cfile.file );
  306. return 1;
  307. }
  308. buf[sizeof(buf) - 1] = 0;
  309. cfile.buf = buf;
  310. cfile.bufl = sizeof(buf) - 1;
  311. cfile.line = 0;
  312. cfile.err = 0;
  313. gcops = 0;
  314. reloop:
  315. while (getcline( &cfile )) {
  316. if (!cfile.cmd)
  317. continue;
  318. for (i = 0; i < N_DRIVERS; i++)
  319. if (drivers[i]->parse_store( &cfile, &store )) {
  320. if (store) {
  321. if (!store->path)
  322. store->path = "";
  323. *storeapp = store;
  324. storeapp = &store->next;
  325. *storeapp = 0;
  326. }
  327. goto reloop;
  328. }
  329. if (!strcasecmp( "Channel", cfile.cmd ))
  330. {
  331. channel = nfcalloc( sizeof(*channel) );
  332. channel->name = nfstrdup( cfile.val );
  333. cops = 0;
  334. max_size = -1;
  335. while (getcline( &cfile ) && cfile.cmd) {
  336. if (!strcasecmp( "MaxSize", cfile.cmd ))
  337. max_size = parse_size( &cfile );
  338. else if (!strcasecmp( "MaxMessages", cfile.cmd ))
  339. channel->max_messages = parse_int( &cfile );
  340. else if (!strcasecmp( "Pattern", cfile.cmd ) ||
  341. !strcasecmp( "Patterns", cfile.cmd ))
  342. {
  343. arg = cfile.val;
  344. do
  345. add_string_list( &channel->patterns, arg );
  346. while ((arg = get_arg( &cfile, ARG_OPTIONAL, 0 )));
  347. }
  348. else if (!strcasecmp( "Master", cfile.cmd )) {
  349. ms = M;
  350. goto linkst;
  351. } else if (!strcasecmp( "Slave", cfile.cmd )) {
  352. ms = S;
  353. linkst:
  354. if (*cfile.val != ':' || !(p = strchr( cfile.val + 1, ':' ))) {
  355. error( "%s:%d: malformed mailbox spec\n",
  356. cfile.file, cfile.line );
  357. cfile.err = 1;
  358. continue;
  359. }
  360. *p = 0;
  361. for (store = stores; store; store = store->next)
  362. if (!strcmp( store->name, cfile.val + 1 )) {
  363. channel->stores[ms] = store;
  364. goto stpcom;
  365. }
  366. error( "%s:%d: unknown store '%s'\n",
  367. cfile.file, cfile.line, cfile.val + 1 );
  368. cfile.err = 1;
  369. continue;
  370. stpcom:
  371. if (*++p)
  372. channel->boxes[ms] = nfstrdup( p );
  373. } else if (!getopt_helper( &cfile, &cops, channel->ops, &channel->sync_state )) {
  374. error( "%s:%d: unknown keyword '%s'\n", cfile.file, cfile.line, cfile.cmd );
  375. cfile.err = 1;
  376. }
  377. }
  378. if (!channel->stores[M]) {
  379. error( "channel '%s' refers to no master store\n", channel->name );
  380. cfile.err = 1;
  381. } else if (!channel->stores[S]) {
  382. error( "channel '%s' refers to no slave store\n", channel->name );
  383. cfile.err = 1;
  384. } else if (merge_ops( cops, channel->ops ))
  385. cfile.err = 1;
  386. else {
  387. if (max_size >= 0)
  388. channel->stores[M]->max_size = channel->stores[S]->max_size = max_size;
  389. *channelapp = channel;
  390. channelapp = &channel->next;
  391. }
  392. }
  393. else if (!strcasecmp( "Group", cfile.cmd ))
  394. {
  395. group = nfmalloc( sizeof(*group) );
  396. group->name = nfstrdup( cfile.val );
  397. *groupapp = group;
  398. groupapp = &group->next;
  399. *groupapp = 0;
  400. chanlistapp = &group->channels;
  401. *chanlistapp = 0;
  402. while ((arg = get_arg( &cfile, ARG_OPTIONAL, 0 ))) {
  403. addone:
  404. len = strlen( arg );
  405. chanlist = nfmalloc( sizeof(*chanlist) + len );
  406. memcpy( chanlist->string, arg, len + 1 );
  407. *chanlistapp = chanlist;
  408. chanlistapp = &chanlist->next;
  409. *chanlistapp = 0;
  410. }
  411. while (getcline( &cfile )) {
  412. if (!cfile.cmd)
  413. goto reloop;
  414. if (!strcasecmp( "Channel", cfile.cmd ) ||
  415. !strcasecmp( "Channels", cfile.cmd ))
  416. {
  417. arg = cfile.val;
  418. goto addone;
  419. }
  420. else
  421. {
  422. error( "%s:%d: unknown keyword '%s'\n",
  423. cfile.file, cfile.line, cfile.cmd );
  424. cfile.err = 1;
  425. }
  426. }
  427. break;
  428. }
  429. else if (!strcasecmp( "FSync", cfile.cmd ))
  430. {
  431. arg = cfile.val;
  432. if (!strcasecmp( "None", arg ))
  433. FSyncLevel = FSYNC_NONE;
  434. else if (!strcasecmp( "Normal", arg ))
  435. FSyncLevel = FSYNC_NORMAL;
  436. else if (!strcasecmp( "Thorough", arg ))
  437. FSyncLevel = FSYNC_THOROUGH;
  438. }
  439. else if (!getopt_helper( &cfile, &gcops, global_ops, &global_sync_state ))
  440. {
  441. error( "%s:%d: unknown section keyword '%s'\n",
  442. cfile.file, cfile.line, cfile.cmd );
  443. cfile.err = 1;
  444. while (getcline( &cfile ))
  445. if (!cfile.cmd)
  446. goto reloop;
  447. break;
  448. }
  449. }
  450. fclose (cfile.fp);
  451. cfile.err |= merge_ops( gcops, global_ops );
  452. if (!global_sync_state)
  453. global_sync_state = expand_strdup( "~/." EXE "/" );
  454. if (!cfile.err && pseudo)
  455. unlink( where );
  456. return cfile.err;
  457. }
  458. void
  459. parse_generic_store( store_conf_t *store, conffile_t *cfg )
  460. {
  461. if (!strcasecmp( "Trash", cfg->cmd ))
  462. store->trash = nfstrdup( cfg->val );
  463. else if (!strcasecmp( "TrashRemoteNew", cfg->cmd ))
  464. store->trash_remote_new = parse_bool( cfg );
  465. else if (!strcasecmp( "TrashNewOnly", cfg->cmd ))
  466. store->trash_only_new = parse_bool( cfg );
  467. else if (!strcasecmp( "MaxSize", cfg->cmd ))
  468. store->max_size = parse_size( cfg );
  469. else if (!strcasecmp( "MapInbox", cfg->cmd ))
  470. store->map_inbox = nfstrdup( cfg->val );
  471. else if (!strcasecmp( "Flatten", cfg->cmd )) {
  472. int sl = strlen( cfg->val );
  473. if (sl != 1) {
  474. error( "%s:%d: malformed flattened hierarchy delimiter\n", cfg->file, cfg->line );
  475. cfg->err = 1;
  476. } else if (cfg->val[0] == '/') {
  477. error( "%s:%d: flattened hierarchy delimiter cannot be the canonical delimiter '/'\n", cfg->file, cfg->line );
  478. cfg->err = 1;
  479. } else {
  480. store->flat_delim = cfg->val[0];
  481. }
  482. } else {
  483. error( "%s:%d: unknown keyword '%s'\n", cfg->file, cfg->line, cfg->cmd );
  484. cfg->err = 1;
  485. }
  486. }