config.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  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. static 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 = NULL;
  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 = NULL;
  72. }
  73. if (quoted) {
  74. error( "%s:%d: missing closing quote\n", cfile->file, cfile->line );
  75. cfile->err = 1;
  76. ret = NULL;
  77. }
  78. }
  79. cfile->rest = p;
  80. return ret;
  81. }
  82. char
  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. uint
  115. parse_size( conffile_t *cfile )
  116. {
  117. char *p;
  118. uint ret;
  119. ret = strtoul( 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, NULL )));
  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, NULL )));
  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, NULL ))) {
  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, NULL )))
  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. uint len, max_size;
  304. int cops, gcops, ms, i;
  305. char path[_POSIX_PATH_MAX];
  306. char buf[1024];
  307. if (!where) {
  308. assert( !pseudo );
  309. nfsnprintf( path, sizeof(path), "%s/." EXE "rc", Home );
  310. cfile.file = path;
  311. } else
  312. cfile.file = where;
  313. if (!pseudo)
  314. info( "Reading configuration file %s\n", cfile.file );
  315. if (!(cfile.fp = fopen( cfile.file, "r" ))) {
  316. sys_error( "Cannot open config file '%s'", cfile.file );
  317. return 1;
  318. }
  319. buf[sizeof(buf) - 1] = 0;
  320. cfile.buf = buf;
  321. cfile.bufl = sizeof(buf) - 1;
  322. cfile.line = 0;
  323. cfile.err = 0;
  324. cfile.rest = NULL;
  325. gcops = 0;
  326. global_conf.expire_unread = -1;
  327. reloop:
  328. while (getcline( &cfile )) {
  329. if (!cfile.cmd)
  330. continue;
  331. for (i = 0; i < N_DRIVERS; i++)
  332. if (drivers[i]->parse_store( &cfile, &store )) {
  333. if (store) {
  334. if (!store->max_size)
  335. store->max_size = UINT_MAX;
  336. if (!store->flat_delim)
  337. store->flat_delim = "";
  338. *storeapp = store;
  339. storeapp = &store->next;
  340. *storeapp = NULL;
  341. }
  342. goto reloop;
  343. }
  344. if (!strcasecmp( "Channel", cfile.cmd ))
  345. {
  346. channel = nfcalloc( sizeof(*channel) );
  347. channel->name = nfstrdup( cfile.val );
  348. channel->max_messages = global_conf.max_messages;
  349. channel->expire_unread = global_conf.expire_unread;
  350. channel->use_internal_date = global_conf.use_internal_date;
  351. cops = 0;
  352. max_size = UINT_MAX;
  353. while (getcline( &cfile ) && cfile.cmd) {
  354. if (!strcasecmp( "MaxSize", cfile.cmd ))
  355. max_size = parse_size( &cfile );
  356. else if (!strcasecmp( "Pattern", cfile.cmd ) ||
  357. !strcasecmp( "Patterns", cfile.cmd ))
  358. {
  359. arg = cfile.val;
  360. do
  361. add_string_list( &channel->patterns, arg );
  362. while ((arg = get_arg( &cfile, ARG_OPTIONAL, NULL )));
  363. }
  364. else if (!strcasecmp( "Master", cfile.cmd )) {
  365. ms = M;
  366. goto linkst;
  367. } else if (!strcasecmp( "Slave", cfile.cmd )) {
  368. ms = S;
  369. linkst:
  370. if (*cfile.val != ':' || !(p = strchr( cfile.val + 1, ':' ))) {
  371. error( "%s:%d: malformed mailbox spec\n",
  372. cfile.file, cfile.line );
  373. cfile.err = 1;
  374. continue;
  375. }
  376. *p = 0;
  377. for (store = stores; store; store = store->next)
  378. if (!strcmp( store->name, cfile.val + 1 )) {
  379. channel->stores[ms] = store;
  380. goto stpcom;
  381. }
  382. error( "%s:%d: unknown store '%s'\n",
  383. cfile.file, cfile.line, cfile.val + 1 );
  384. cfile.err = 1;
  385. continue;
  386. stpcom:
  387. if (*++p)
  388. channel->boxes[ms] = nfstrdup( p );
  389. } else if (!getopt_helper( &cfile, &cops, channel )) {
  390. error( "%s:%d: unknown keyword '%s'\n", cfile.file, cfile.line, cfile.cmd );
  391. cfile.err = 1;
  392. }
  393. }
  394. if (!channel->stores[M]) {
  395. error( "channel '%s' refers to no master store\n", channel->name );
  396. cfile.err = 1;
  397. } else if (!channel->stores[S]) {
  398. error( "channel '%s' refers to no slave store\n", channel->name );
  399. cfile.err = 1;
  400. } else if (merge_ops( cops, channel->ops ))
  401. cfile.err = 1;
  402. else {
  403. if (max_size != UINT_MAX) {
  404. if (!max_size)
  405. max_size = UINT_MAX;
  406. channel->stores[M]->max_size = channel->stores[S]->max_size = max_size;
  407. }
  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 = NULL;
  419. chanlistapp = &group->channels;
  420. *chanlistapp = NULL;
  421. while ((arg = get_arg( &cfile, ARG_OPTIONAL, NULL ))) {
  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 = NULL;
  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. UseFSync = parse_bool( &cfile );
  451. }
  452. else if (!strcasecmp( "FieldDelimiter", cfile.cmd ))
  453. {
  454. if (strlen( cfile.val ) != 1) {
  455. error( "%s:%d: Field delimiter must be exactly one character long\n", cfile.file, cfile.line );
  456. cfile.err = 1;
  457. } else {
  458. FieldDelimiter = cfile.val[0];
  459. if (!ispunct( FieldDelimiter )) {
  460. error( "%s:%d: Field delimiter must be a punctuation character\n", cfile.file, cfile.line );
  461. cfile.err = 1;
  462. }
  463. }
  464. }
  465. else if (!strcasecmp( "BufferLimit", cfile.cmd ))
  466. {
  467. BufferLimit = parse_size( &cfile );
  468. if (!BufferLimit) {
  469. error( "%s:%d: BufferLimit cannot be zero\n", cfile.file, cfile.line );
  470. cfile.err = 1;
  471. }
  472. }
  473. else if (!getopt_helper( &cfile, &gcops, &global_conf ))
  474. {
  475. error( "%s:%d: unknown section keyword '%s'\n",
  476. cfile.file, cfile.line, cfile.cmd );
  477. cfile.err = 1;
  478. while (getcline( &cfile ))
  479. if (!cfile.cmd)
  480. goto reloop;
  481. break;
  482. }
  483. }
  484. fclose (cfile.fp);
  485. cfile.err |= merge_ops( gcops, global_conf.ops );
  486. if (!global_conf.sync_state)
  487. global_conf.sync_state = expand_strdup( "~/." EXE "/" );
  488. if (!cfile.err && pseudo)
  489. unlink( where );
  490. return cfile.err;
  491. }