config.c 12 KB

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