config.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  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. if (!store->flat_delim)
  336. store->flat_delim = "";
  337. *storeapp = store;
  338. storeapp = &store->next;
  339. *storeapp = 0;
  340. }
  341. goto reloop;
  342. }
  343. if (!strcasecmp( "Channel", cfile.cmd ))
  344. {
  345. channel = nfcalloc( sizeof(*channel) );
  346. channel->name = nfstrdup( cfile.val );
  347. channel->max_messages = global_conf.max_messages;
  348. channel->expire_unread = global_conf.expire_unread;
  349. channel->use_internal_date = global_conf.use_internal_date;
  350. cops = 0;
  351. max_size = -1;
  352. while (getcline( &cfile ) && cfile.cmd) {
  353. if (!strcasecmp( "MaxSize", cfile.cmd ))
  354. max_size = parse_size( &cfile );
  355. else if (!strcasecmp( "Pattern", cfile.cmd ) ||
  356. !strcasecmp( "Patterns", cfile.cmd ))
  357. {
  358. arg = cfile.val;
  359. do
  360. add_string_list( &channel->patterns, arg );
  361. while ((arg = get_arg( &cfile, ARG_OPTIONAL, 0 )));
  362. }
  363. else if (!strcasecmp( "Master", cfile.cmd )) {
  364. ms = M;
  365. goto linkst;
  366. } else if (!strcasecmp( "Slave", cfile.cmd )) {
  367. ms = S;
  368. linkst:
  369. if (*cfile.val != ':' || !(p = strchr( cfile.val + 1, ':' ))) {
  370. error( "%s:%d: malformed mailbox spec\n",
  371. cfile.file, cfile.line );
  372. cfile.err = 1;
  373. continue;
  374. }
  375. *p = 0;
  376. for (store = stores; store; store = store->next)
  377. if (!strcmp( store->name, cfile.val + 1 )) {
  378. channel->stores[ms] = store;
  379. goto stpcom;
  380. }
  381. error( "%s:%d: unknown store '%s'\n",
  382. cfile.file, cfile.line, cfile.val + 1 );
  383. cfile.err = 1;
  384. continue;
  385. stpcom:
  386. if (*++p)
  387. channel->boxes[ms] = nfstrdup( p );
  388. } else if (!getopt_helper( &cfile, &cops, channel )) {
  389. error( "%s:%d: unknown keyword '%s'\n", cfile.file, cfile.line, cfile.cmd );
  390. cfile.err = 1;
  391. }
  392. }
  393. if (!channel->stores[M]) {
  394. error( "channel '%s' refers to no master store\n", channel->name );
  395. cfile.err = 1;
  396. } else if (!channel->stores[S]) {
  397. error( "channel '%s' refers to no slave store\n", channel->name );
  398. cfile.err = 1;
  399. } else if (merge_ops( cops, channel->ops ))
  400. cfile.err = 1;
  401. else {
  402. if (max_size >= 0) {
  403. if (!max_size)
  404. max_size = INT_MAX;
  405. channel->stores[M]->max_size = channel->stores[S]->max_size = max_size;
  406. }
  407. *channelapp = channel;
  408. channelapp = &channel->next;
  409. }
  410. }
  411. else if (!strcasecmp( "Group", cfile.cmd ))
  412. {
  413. group = nfmalloc( sizeof(*group) );
  414. group->name = nfstrdup( cfile.val );
  415. *groupapp = group;
  416. groupapp = &group->next;
  417. *groupapp = 0;
  418. chanlistapp = &group->channels;
  419. *chanlistapp = 0;
  420. while ((arg = get_arg( &cfile, ARG_OPTIONAL, 0 ))) {
  421. addone:
  422. len = strlen( arg );
  423. chanlist = nfmalloc( sizeof(*chanlist) + len );
  424. memcpy( chanlist->string, arg, len + 1 );
  425. *chanlistapp = chanlist;
  426. chanlistapp = &chanlist->next;
  427. *chanlistapp = 0;
  428. }
  429. while (getcline( &cfile )) {
  430. if (!cfile.cmd)
  431. goto reloop;
  432. if (!strcasecmp( "Channel", cfile.cmd ) ||
  433. !strcasecmp( "Channels", cfile.cmd ))
  434. {
  435. arg = cfile.val;
  436. goto addone;
  437. }
  438. else
  439. {
  440. error( "%s:%d: unknown keyword '%s'\n",
  441. cfile.file, cfile.line, cfile.cmd );
  442. cfile.err = 1;
  443. }
  444. }
  445. break;
  446. }
  447. else if (!strcasecmp( "FSync", cfile.cmd ))
  448. {
  449. UseFSync = parse_bool( &cfile );
  450. }
  451. else if (!strcasecmp( "FieldDelimiter", cfile.cmd ))
  452. {
  453. if (strlen( cfile.val ) != 1) {
  454. error( "%s:%d: Field delimiter must be exactly one character long\n", cfile.file, cfile.line );
  455. cfile.err = 1;
  456. } else {
  457. FieldDelimiter = cfile.val[0];
  458. if (!ispunct( FieldDelimiter )) {
  459. error( "%s:%d: Field delimiter must be a punctuation character\n", cfile.file, cfile.line );
  460. cfile.err = 1;
  461. }
  462. }
  463. }
  464. else if (!strcasecmp( "BufferLimit", cfile.cmd ))
  465. {
  466. BufferLimit = parse_size( &cfile );
  467. if (BufferLimit <= 0) {
  468. error( "%s:%d: BufferLimit must be positive\n", cfile.file, cfile.line );
  469. cfile.err = 1;
  470. }
  471. }
  472. else if (!getopt_helper( &cfile, &gcops, &global_conf ))
  473. {
  474. error( "%s:%d: unknown section keyword '%s'\n",
  475. cfile.file, cfile.line, cfile.cmd );
  476. cfile.err = 1;
  477. while (getcline( &cfile ))
  478. if (!cfile.cmd)
  479. goto reloop;
  480. break;
  481. }
  482. }
  483. fclose (cfile.fp);
  484. cfile.err |= merge_ops( gcops, global_conf.ops );
  485. if (!global_conf.sync_state)
  486. global_conf.sync_state = expand_strdup( "~/." EXE "/" );
  487. if (!cfile.err && pseudo)
  488. unlink( where );
  489. return cfile.err;
  490. }