config.c 14 KB

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