main.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. /*
  2. * mbsync - mailbox synchronizer
  3. * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
  4. * Copyright (C) 2002-2006 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 <stdlib.h>
  25. #include <unistd.h>
  26. #include <string.h>
  27. #include <fcntl.h>
  28. #include <signal.h>
  29. #include <sys/wait.h>
  30. int Pid; /* for maildir and imap */
  31. char Hostname[256]; /* for maildir */
  32. const char *Home; /* for config */
  33. static void
  34. version( void )
  35. {
  36. puts( PACKAGE " " VERSION );
  37. exit( 0 );
  38. }
  39. static void
  40. usage( int code )
  41. {
  42. fputs(
  43. PACKAGE " " VERSION " - mailbox synchronizer\n"
  44. "Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>\n"
  45. "Copyright (C) 2002-2006 Oswald Buddenhagen <ossi@users.sf.net>\n"
  46. "Copyright (C) 2004 Theodore Ts'o <tytso@mit.edu>\n"
  47. "usage:\n"
  48. " " EXE " [flags] {{channel[:box,...]|group} ...|-a}\n"
  49. " -a, --all operate on all defined channels\n"
  50. " -l, --list list mailboxes instead of syncing them\n"
  51. " -n, --new propagate new messages\n"
  52. " -d, --delete propagate message deletions\n"
  53. " -f, --flags propagate message flag changes\n"
  54. " -N, --renew propagate previously not propagated new messages\n"
  55. " -L, --pull propagate from master to slave\n"
  56. " -H, --push propagate from slave to master\n"
  57. " -C, --create create mailboxes if nonexistent\n"
  58. " -X, --expunge expunge deleted messages\n"
  59. " -c, --config CONFIG read an alternate config file (default: ~/." EXE "rc)\n"
  60. " -D, --debug print debugging messages\n"
  61. " -V, --verbose verbose mode (display network traffic)\n"
  62. " -q, --quiet don't display progress info\n"
  63. " -v, --version display version\n"
  64. " -h, --help display this help message\n"
  65. "\nIf neither --pull nor --push are specified, both are active.\n"
  66. "If neither --new, --delete, --flags nor --renew are specified, all are active.\n"
  67. "Direction and operation can be concatenated like --pull-new, etc.\n"
  68. "--create and --expunge can be suffixed with -master/-slave. Read the man page.\n"
  69. "\nSupported mailbox formats are: IMAP4rev1, Maildir\n"
  70. "\nCompile time options:\n"
  71. #if HAVE_LIBSSL
  72. " +HAVE_LIBSSL\n"
  73. #else
  74. " -HAVE_LIBSSL\n"
  75. #endif
  76. , code ? stderr : stdout );
  77. exit( code );
  78. }
  79. static void
  80. crashHandler( int n )
  81. {
  82. int dpid;
  83. char pbuf[10], pabuf[20];
  84. close( 0 );
  85. open( "/dev/tty", O_RDWR );
  86. dup2( 0, 1 );
  87. dup2( 0, 2 );
  88. error( "*** " EXE " caught signal %d. Starting debugger ...\n", n );
  89. switch ((dpid = fork ())) {
  90. case -1:
  91. perror( "fork()" );
  92. break;
  93. case 0:
  94. sprintf( pbuf, "%d", Pid );
  95. sprintf( pabuf, "/proc/%d/exe", Pid );
  96. execlp( "gdb", "gdb", pabuf, pbuf, (char *)0 );
  97. perror( "execlp()" );
  98. _exit( 1 );
  99. default:
  100. waitpid( dpid, 0, 0 );
  101. break;
  102. }
  103. exit( 3 );
  104. }
  105. static int
  106. matches( const char *t, const char *p )
  107. {
  108. for (;;) {
  109. if (!*p)
  110. return !*t;
  111. if (*p == '*') {
  112. p++;
  113. do {
  114. if (matches( t, p ))
  115. return 1;
  116. } while (*t++);
  117. return 0;
  118. } else if (*p == '%') {
  119. p++;
  120. do {
  121. if (*t == '.' || *t == '/') /* this is "somewhat" hacky ... */
  122. return 0;
  123. if (matches( t, p ))
  124. return 1;
  125. } while (*t++);
  126. return 0;
  127. } else {
  128. if (*p != *t)
  129. return 0;
  130. p++, t++;
  131. }
  132. }
  133. }
  134. static string_list_t *
  135. filter_boxes( string_list_t *boxes, string_list_t *patterns )
  136. {
  137. string_list_t *nboxes = 0, *cpat;
  138. const char *ps;
  139. int not, fnot;
  140. for (; boxes; boxes = boxes->next) {
  141. fnot = 1;
  142. for (cpat = patterns; cpat; cpat = cpat->next) {
  143. ps = cpat->string;
  144. if (*ps == '!') {
  145. ps++;
  146. not = 1;
  147. } else
  148. not = 0;
  149. if (matches( boxes->string, ps )) {
  150. fnot = not;
  151. break;
  152. }
  153. }
  154. if (!fnot)
  155. add_string_list( &nboxes, boxes->string );
  156. }
  157. return nboxes;
  158. }
  159. static void
  160. merge_actions( channel_conf_t *chan, int ops[], int have, int mask, int def )
  161. {
  162. if (ops[M] & have) {
  163. chan->ops[M] &= ~mask;
  164. chan->ops[M] |= ops[M] & mask;
  165. chan->ops[S] &= ~mask;
  166. chan->ops[S] |= ops[S] & mask;
  167. } else if (!(chan->ops[M] & have)) {
  168. if (global_ops[M] & have) {
  169. chan->ops[M] |= global_ops[M] & mask;
  170. chan->ops[S] |= global_ops[S] & mask;
  171. } else {
  172. chan->ops[M] |= def;
  173. chan->ops[S] |= def;
  174. }
  175. }
  176. }
  177. int
  178. main( int argc, char **argv )
  179. {
  180. channel_conf_t *chan;
  181. group_conf_t *group;
  182. driver_t *driver[2];
  183. store_t *ctx[2];
  184. string_list_t *boxes[2], *mbox, *sbox, **mboxp, **sboxp, *cboxes, *chanptr;
  185. char *config = 0, *channame, *boxlist, *boxp, *opt, *ochar;
  186. const char *names[2];
  187. int all = 0, list = 0, cops = 0, ops[2] = { 0, 0 };
  188. int oind, ret, op, multiple, pseudo = 0, t;
  189. gethostname( Hostname, sizeof(Hostname) );
  190. if ((ochar = strchr( Hostname, '.' )))
  191. *ochar = 0;
  192. Pid = getpid();
  193. if (!(Home = getenv("HOME"))) {
  194. fputs( "Fatal: $HOME not set\n", stderr );
  195. return 1;
  196. }
  197. Ontty = isatty( 1 ) && isatty( 2 );
  198. arc4_init();
  199. for (oind = 1, ochar = 0; oind < argc; ) {
  200. if (!ochar || !*ochar) {
  201. if (argv[oind][0] != '-')
  202. break;
  203. if (argv[oind][1] == '-') {
  204. opt = argv[oind++] + 2;
  205. if (!*opt)
  206. break;
  207. if (!strcmp( opt, "config" )) {
  208. if (oind >= argc) {
  209. error( "--config requires an argument.\n" );
  210. return 1;
  211. }
  212. config = argv[oind++];
  213. } else if (!memcmp( opt, "config=", 7 ))
  214. config = opt + 7;
  215. else if (!strcmp( opt, "all" ))
  216. all = 1;
  217. else if (!strcmp( opt, "list" ))
  218. list = 1;
  219. else if (!strcmp( opt, "help" ))
  220. usage( 0 );
  221. else if (!strcmp( opt, "version" ))
  222. version();
  223. else if (!strcmp( opt, "quiet" )) {
  224. if (DFlags & QUIET)
  225. DFlags |= VERYQUIET;
  226. else
  227. DFlags |= QUIET;
  228. } else if (!strcmp( opt, "verbose" ))
  229. DFlags |= VERBOSE | QUIET;
  230. else if (!strcmp( opt, "debug" ))
  231. DFlags |= DEBUG | QUIET;
  232. else if (!strcmp( opt, "pull" ))
  233. cops |= XOP_PULL, ops[M] |= XOP_HAVE_TYPE;
  234. else if (!strcmp( opt, "push" ))
  235. cops |= XOP_PUSH, ops[M] |= XOP_HAVE_TYPE;
  236. else if (!memcmp( opt, "create", 6 )) {
  237. opt += 6;
  238. op = OP_CREATE|XOP_HAVE_CREATE;
  239. lcop:
  240. if (!*opt)
  241. cops |= op;
  242. else if (!strcmp( opt, "-master" ))
  243. ops[M] |= op, ochar++;
  244. else if (!strcmp( opt, "-slave" ))
  245. ops[S] |= op, ochar++;
  246. else
  247. goto badopt;
  248. ops[M] |= op & (XOP_HAVE_CREATE|XOP_HAVE_EXPUNGE);
  249. } else if (!memcmp( opt, "expunge", 7 )) {
  250. opt += 7;
  251. op = OP_EXPUNGE|XOP_HAVE_EXPUNGE;
  252. goto lcop;
  253. } else if (!strcmp( opt, "no-expunge" ))
  254. ops[M] |= XOP_HAVE_EXPUNGE;
  255. else if (!strcmp( opt, "no-create" ))
  256. ops[M] |= XOP_HAVE_CREATE;
  257. else if (!strcmp( opt, "full" ))
  258. ops[M] |= XOP_HAVE_TYPE|XOP_PULL|XOP_PUSH;
  259. else if (!strcmp( opt, "noop" ))
  260. ops[M] |= XOP_HAVE_TYPE;
  261. else if (!memcmp( opt, "pull", 4 )) {
  262. op = XOP_PULL;
  263. lcac:
  264. opt += 4;
  265. if (!*opt)
  266. cops |= op;
  267. else if (*opt == '-') {
  268. opt++;
  269. goto rlcac;
  270. } else
  271. goto badopt;
  272. } else if (!memcmp( opt, "push", 4 )) {
  273. op = XOP_PUSH;
  274. goto lcac;
  275. } else {
  276. op = 0;
  277. rlcac:
  278. if (!strcmp( opt, "new" ))
  279. op |= OP_NEW;
  280. else if (!strcmp( opt, "renew" ))
  281. op |= OP_RENEW;
  282. else if (!strcmp( opt, "delete" ))
  283. op |= OP_DELETE;
  284. else if (!strcmp( opt, "flags" ))
  285. op |= OP_FLAGS;
  286. else {
  287. badopt:
  288. error( "Unknown option '%s'\n", argv[oind - 1] );
  289. return 1;
  290. }
  291. switch (op & XOP_MASK_DIR) {
  292. case XOP_PULL: ops[S] |= op & OP_MASK_TYPE; break;
  293. case XOP_PUSH: ops[M] |= op & OP_MASK_TYPE; break;
  294. default: cops |= op; break;
  295. }
  296. ops[M] |= XOP_HAVE_TYPE;
  297. }
  298. continue;
  299. }
  300. ochar = argv[oind++] + 1;
  301. if (!*ochar) {
  302. error( "Invalid option '-'\n" );
  303. return 1;
  304. }
  305. }
  306. switch (*ochar++) {
  307. case 'a':
  308. all = 1;
  309. break;
  310. case 'l':
  311. list = 1;
  312. break;
  313. case 'c':
  314. if (*ochar == 'T') {
  315. ochar++;
  316. pseudo = 1;
  317. }
  318. if (oind >= argc) {
  319. error( "-c requires an argument.\n" );
  320. return 1;
  321. }
  322. config = argv[oind++];
  323. break;
  324. case 'C':
  325. op = OP_CREATE|XOP_HAVE_CREATE;
  326. cop:
  327. if (*ochar == 'm')
  328. ops[M] |= op, ochar++;
  329. else if (*ochar == 's')
  330. ops[S] |= op, ochar++;
  331. else if (*ochar == '-')
  332. ochar++;
  333. else
  334. cops |= op;
  335. ops[M] |= op & (XOP_HAVE_CREATE|XOP_HAVE_EXPUNGE);
  336. break;
  337. case 'X':
  338. op = OP_EXPUNGE|XOP_HAVE_EXPUNGE;
  339. goto cop;
  340. case 'F':
  341. cops |= XOP_PULL|XOP_PUSH;
  342. case '0':
  343. ops[M] |= XOP_HAVE_TYPE;
  344. break;
  345. case 'n':
  346. case 'd':
  347. case 'f':
  348. case 'N':
  349. --ochar;
  350. op = 0;
  351. cac:
  352. for (;; ochar++) {
  353. if (*ochar == 'n')
  354. op |= OP_NEW;
  355. else if (*ochar == 'd')
  356. op |= OP_DELETE;
  357. else if (*ochar == 'f')
  358. op |= OP_FLAGS;
  359. else if (*ochar == 'N')
  360. op |= OP_RENEW;
  361. else
  362. break;
  363. }
  364. if (op & OP_MASK_TYPE)
  365. switch (op & XOP_MASK_DIR) {
  366. case XOP_PULL: ops[S] |= op & OP_MASK_TYPE; break;
  367. case XOP_PUSH: ops[M] |= op & OP_MASK_TYPE; break;
  368. default: cops |= op; break;
  369. }
  370. else
  371. cops |= op;
  372. ops[M] |= XOP_HAVE_TYPE;
  373. break;
  374. case 'L':
  375. op = XOP_PULL;
  376. goto cac;
  377. case 'H':
  378. op = XOP_PUSH;
  379. goto cac;
  380. case 'q':
  381. if (DFlags & QUIET)
  382. DFlags |= VERYQUIET;
  383. else
  384. DFlags |= QUIET;
  385. break;
  386. case 'V':
  387. DFlags |= VERBOSE | QUIET;
  388. break;
  389. case 'D':
  390. DFlags |= DEBUG | QUIET;
  391. break;
  392. case 'J':
  393. DFlags |= KEEPJOURNAL;
  394. break;
  395. case 'v':
  396. version();
  397. case 'h':
  398. usage( 0 );
  399. default:
  400. error( "Unknown option '-%c'\n", *(ochar - 1) );
  401. return 1;
  402. }
  403. }
  404. if (DFlags & DEBUG) {
  405. signal( SIGSEGV, crashHandler );
  406. signal( SIGBUS, crashHandler );
  407. signal( SIGILL, crashHandler );
  408. }
  409. if (merge_ops( cops, ops ))
  410. return 1;
  411. if (load_config( config, pseudo ))
  412. return 1;
  413. if (!all && !argv[oind]) {
  414. fputs( "No channel specified. Try '" EXE " -h'\n", stderr );
  415. return 1;
  416. }
  417. if (!channels) {
  418. fputs( "No channels defined. Try 'man " EXE "'\n", stderr );
  419. return 1;
  420. }
  421. ret = 0;
  422. chan = channels;
  423. chanptr = 0;
  424. if (all)
  425. multiple = channels->next != 0;
  426. else if (argv[oind + 1])
  427. multiple = 1;
  428. else {
  429. multiple = 0;
  430. for (group = groups; group; group = group->next)
  431. if (!strcmp( group->name, argv[oind] )) {
  432. multiple = 1;
  433. break;
  434. }
  435. }
  436. for (;;) {
  437. boxlist = 0;
  438. if (!all) {
  439. if (chanptr)
  440. channame = chanptr->string;
  441. else {
  442. for (group = groups; group; group = group->next)
  443. if (!strcmp( group->name, argv[oind] )) {
  444. chanptr = group->channels;
  445. channame = chanptr->string;
  446. goto gotgrp;
  447. }
  448. channame = argv[oind];
  449. gotgrp: ;
  450. }
  451. if ((boxlist = strchr( channame, ':' )))
  452. *boxlist++ = 0;
  453. for (chan = channels; chan; chan = chan->next)
  454. if (!strcmp( chan->name, channame ))
  455. goto gotchan;
  456. error( "No channel or group named '%s' defined.\n", channame );
  457. ret = 1;
  458. goto gotnone;
  459. gotchan: ;
  460. }
  461. merge_actions( chan, ops, XOP_HAVE_TYPE, OP_MASK_TYPE, OP_MASK_TYPE );
  462. merge_actions( chan, ops, XOP_HAVE_CREATE, OP_CREATE, 0 );
  463. merge_actions( chan, ops, XOP_HAVE_EXPUNGE, OP_EXPUNGE, 0 );
  464. info( "Channel %s\n", chan->name );
  465. boxes[M] = boxes[S] = cboxes = 0;
  466. for (t = 0; t < 2; t++) {
  467. driver[t] = chan->stores[t]->driver;
  468. ctx[t] = driver[t]->own_store( chan->stores[t] );
  469. }
  470. for (t = 0; t < 2; t++)
  471. if (!ctx[t]) {
  472. info( "Opening %s %s...\n", str_ms[t], chan->stores[t]->name );
  473. if (!(ctx[t] = driver[t]->open_store( chan->stores[t] ))) {
  474. ret = 1;
  475. goto next;
  476. }
  477. }
  478. if (boxlist)
  479. boxp = boxlist;
  480. else if (chan->patterns) {
  481. for (t = 0; t < 2; t++) {
  482. if (!ctx[t]->listed) {
  483. if (driver[t]->list( ctx[t] ) != DRV_OK) {
  484. screwt:
  485. driver[t]->cancel_store( ctx[t] );
  486. ctx[t] = 0;
  487. ret = 1;
  488. goto next;
  489. } else if (ctx[t]->conf->map_inbox)
  490. add_string_list( &ctx[t]->boxes, ctx[t]->conf->map_inbox );
  491. }
  492. boxes[t] = filter_boxes( ctx[t]->boxes, chan->patterns );
  493. }
  494. for (mboxp = &boxes[M]; (mbox = *mboxp); ) {
  495. for (sboxp = &boxes[S]; (sbox = *sboxp); sboxp = &sbox->next)
  496. if (!strcmp( sbox->string, mbox->string )) {
  497. *sboxp = sbox->next;
  498. free( sbox );
  499. *mboxp = mbox->next;
  500. mbox->next = cboxes;
  501. cboxes = mbox;
  502. goto gotdupe;
  503. }
  504. mboxp = &mbox->next;
  505. gotdupe: ;
  506. }
  507. }
  508. if (list && multiple)
  509. printf( "%s:\n", chan->name );
  510. if (boxlist) {
  511. while ((names[S] = strsep( &boxp, ",\n" ))) {
  512. if (list)
  513. puts( names[S] );
  514. else {
  515. names[M] = names[S];
  516. switch (sync_boxes( ctx, names, chan )) {
  517. case SYNC_BAD(M): t = M; goto screwt;
  518. case SYNC_BAD(S): t = S; goto screwt;
  519. case SYNC_FAIL: ret = 1;
  520. }
  521. }
  522. }
  523. } else if (chan->patterns) {
  524. for (mbox = cboxes; mbox; mbox = mbox->next)
  525. if (list)
  526. puts( mbox->string );
  527. else {
  528. names[M] = names[S] = mbox->string;
  529. switch (sync_boxes( ctx, names, chan )) {
  530. case SYNC_BAD(M): t = M; goto screwt;
  531. case SYNC_BAD(S): t = S; goto screwt;
  532. case SYNC_FAIL: ret = 1;
  533. }
  534. }
  535. for (t = 0; t < 2; t++)
  536. if ((chan->ops[1-t] & OP_MASK_TYPE) && (chan->ops[1-t] & OP_CREATE)) {
  537. for (mbox = boxes[t]; mbox; mbox = mbox->next)
  538. if (list)
  539. puts( mbox->string );
  540. else {
  541. names[M] = names[S] = mbox->string;
  542. switch (sync_boxes( ctx, names, chan )) {
  543. case SYNC_BAD(M): t = M; goto screwt;
  544. case SYNC_BAD(S): t = S; goto screwt;
  545. case SYNC_FAIL: ret = 1;
  546. }
  547. }
  548. }
  549. } else
  550. if (list)
  551. printf( "%s <=> %s\n", chan->boxes[M], chan->boxes[S] );
  552. else
  553. switch (sync_boxes( ctx, chan->boxes, chan )) {
  554. case SYNC_BAD(M): t = M; goto screwt;
  555. case SYNC_BAD(S): t = S; goto screwt;
  556. case SYNC_FAIL: ret = 1;
  557. }
  558. next:
  559. free_string_list( cboxes );
  560. free_string_list( boxes[M] );
  561. free_string_list( boxes[S] );
  562. if (ctx[M])
  563. driver[M]->disown_store( ctx[M] );
  564. if (ctx[S])
  565. driver[S]->disown_store( ctx[S] );
  566. if (all) {
  567. if (!(chan = chan->next))
  568. break;
  569. } else {
  570. if (chanptr && (chanptr = chanptr->next))
  571. continue;
  572. gotnone:
  573. if (!argv[++oind])
  574. break;
  575. }
  576. }
  577. for (t = 0; t < N_DRIVERS; t++)
  578. drivers[t]->cleanup();
  579. return ret;
  580. }