config.c 13 KB

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