// SPDX-FileCopyrightText: 2000-2002 Michael R. Elkins // SPDX-FileCopyrightText: 2002-2022 Oswald Buddenhagen // SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception /* * mbsync - mailbox synchronizer */ #include "main_p.h" #include #include #include #ifdef __linux__ # include #endif static void ATTR_NORETURN version( void ) { puts( PACKAGE " " VERSION ); exit( 0 ); } static void ATTR_NORETURN usage( int code ) { fputs( PACKAGE " " VERSION " - mailbox synchronizer\n" "Copyright (C) 2000-2002 Michael R. Elkins \n" "Copyright (C) 2002-2022 Oswald Buddenhagen \n" "Copyright (C) 2004 Theodore Ts'o \n" "usage:\n" " " EXE " [flags] {{channel[:box,...]|group} ...|-a}\n" " -a, --all operate on all defined channels\n" " -l, --list list mailboxes instead of syncing them\n" " -ls, --list-stores raw listing of stores' mailboxes\n" " -n, --new propagate new messages\n" " -d, --delete propagate message deletions\n" " -f, --flags propagate message flag changes\n" " -N, --renew propagate previously not propagated new messages\n" " -L, --pull propagate from far to near side\n" " -H, --push propagate from near to far side\n" " -C, --create propagate creations of mailboxes\n" " -R, --remove propagate deletions of mailboxes\n" " -X, --expunge expunge deleted messages\n" " -c, --config CONFIG read an alternate config file (default: ~/." EXE "rc)\n" " -D, --debug debugging modes (see manual)\n" " -V, --verbose display what is happening\n" " -q, --quiet don't display progress counters\n" " -v, --version display version\n" " -h, --help display this help message\n" "\nIf neither --pull nor --push are specified, both are active.\n" "If neither --new, --delete, --flags nor --renew are specified, all are active.\n" "Direction and operation can be concatenated like --pull-new, etc.\n" "--create, --remove, and --expunge can be suffixed with -far/-near.\n" "See the man page for details.\n" "\nSupported mailbox formats are: IMAP4rev1, Maildir\n" "\nCompile time options:\n" #ifdef HAVE_LIBSSL " +HAVE_LIBSSL" #else " -HAVE_LIBSSL" #endif #ifdef HAVE_LIBSASL " +HAVE_LIBSASL" #else " -HAVE_LIBSASL" #endif #ifdef HAVE_LIBZ " +HAVE_LIBZ" #else " -HAVE_LIBZ" #endif #ifdef USE_DB " +USE_DB" #else " -USE_DB" #endif #ifdef HAVE_IPV6 " +HAVE_IPV6\n" #else " -HAVE_IPV6\n" #endif , code ? stderr : stdout ); exit( code ); } #ifdef __linux__ static void ATTR_NORETURN crashHandler( int n ) { int dpid; char pbuf[10], pabuf[20]; close( 0 ); open( "/dev/tty", O_RDWR ); dup2( 0, 1 ); dup2( 0, 2 ); error( "*** " EXE " caught signal %d. Starting debugger ...\n", n ); #ifdef PR_SET_PTRACER int pip[2]; if (pipe( pip ) < 0) { perror( "pipe()" ); exit( 3 ); } #endif switch ((dpid = fork())) { case -1: perror( "fork()" ); break; case 0: #ifdef PR_SET_PTRACER close( pip[1] ); read( pip[0], pbuf, 1 ); close( pip[0] ); #endif sprintf( pbuf, "%d", Pid ); sprintf( pabuf, "/proc/%d/exe", Pid ); execlp( "gdb", "gdb", pabuf, pbuf, (char *)0 ); perror( "execlp()" ); _exit( 1 ); default: #ifdef PR_SET_PTRACER prctl( PR_SET_PTRACER, (ulong)dpid ); close( pip[1] ); close( pip[0] ); #endif waitpid( dpid, NULL, 0 ); break; } exit( 3 ); } #endif int main( int argc, char **argv ) { core_vars_t mvars[1]; char *config = NULL, *opt, *ochar; int oind, cops = 0, op, ms_warn = 0; tzset(); gethostname( Hostname, sizeof(Hostname) ); if ((ochar = strchr( Hostname, '.' ))) *ochar = 0; Pid = getpid(); if (!(Home = getenv("HOME"))) { fputs( "Fatal: $HOME not set\n", stderr ); return 1; } arc4_init(); memset( mvars, 0, sizeof(*mvars) ); for (oind = 1, ochar = NULL; ; ) { if (!ochar || !*ochar) { if (oind >= argc) break; if (argv[oind][0] != '-') break; if (argv[oind][1] == '-') { opt = argv[oind++] + 2; if (!*opt) break; if (!strcmp( opt, "config" )) { if (oind >= argc) { error( "--config requires an argument.\n" ); return 1; } config = argv[oind++]; } else if (starts_with( opt, -1, "config=", 7 )) { config = opt + 7; } else if (!strcmp( opt, "all" )) { mvars->all = 1; } else if (!strcmp( opt, "list" )) { mvars->list = 1; } else if (!strcmp( opt, "list-stores" )) { mvars->list_stores = 1; } else if (!strcmp( opt, "help" )) { usage( 0 ); } else if (!strcmp( opt, "version" )) { version(); } else if (!strcmp( opt, "quiet" )) { if (Verbosity > VERYQUIET) Verbosity--; } else if (!strcmp( opt, "verbose" )) { Verbosity = VERBOSE; } else if (starts_with( opt, -1, "debug", 5 )) { opt += 5; if (!*opt) op = DEBUG_ALL; else if (!strcmp( opt, "-crash" )) op = DEBUG_CRASH; else if (!strcmp( opt, "-driver" )) op = DEBUG_DRV; else if (!strcmp( opt, "-driver-all" )) op = DEBUG_DRV | DEBUG_DRV_ALL; else if (!strcmp( opt, "-maildir" )) op = DEBUG_MAILDIR; else if (!strcmp( opt, "-main" )) op = DEBUG_MAIN; else if (!strcmp( opt, "-net" )) op = DEBUG_NET; else if (!strcmp( opt, "-net-all" )) op = DEBUG_NET | DEBUG_NET_ALL; else if (!strcmp( opt, "-sync" )) op = DEBUG_SYNC; else goto badopt; DFlags |= op; } else if (!strcmp( opt, "pull" )) { cops |= XOP_PULL, mvars->ops[F] |= XOP_HAVE_TYPE; } else if (!strcmp( opt, "push" )) { cops |= XOP_PUSH, mvars->ops[F] |= XOP_HAVE_TYPE; } else if (starts_with( opt, -1, "create", 6 )) { opt += 6; op = OP_CREATE|XOP_HAVE_CREATE; lcop: if (!*opt) cops |= op; else if (!strcmp( opt, "-far" )) mvars->ops[F] |= op; else if (!strcmp( opt, "-master" )) // Pre-1.4 legacy mvars->ops[F] |= op, ms_warn = 1; else if (!strcmp( opt, "-near" )) mvars->ops[N] |= op; else if (!strcmp( opt, "-slave" )) // Pre-1.4 legacy mvars->ops[N] |= op, ms_warn = 1; else goto badopt; mvars->ops[F] |= op & (XOP_HAVE_CREATE | XOP_HAVE_REMOVE | XOP_HAVE_EXPUNGE); } else if (starts_with( opt, -1, "remove", 6 )) { opt += 6; op = OP_REMOVE|XOP_HAVE_REMOVE; goto lcop; } else if (starts_with( opt, -1, "expunge", 7 )) { opt += 7; op = OP_EXPUNGE|XOP_HAVE_EXPUNGE; goto lcop; } else if (!strcmp( opt, "no-expunge" )) { mvars->ops[F] |= XOP_HAVE_EXPUNGE; } else if (!strcmp( opt, "no-create" )) { mvars->ops[F] |= XOP_HAVE_CREATE; } else if (!strcmp( opt, "no-remove" )) { mvars->ops[F] |= XOP_HAVE_REMOVE; } else if (!strcmp( opt, "full" )) { mvars->ops[F] |= XOP_HAVE_TYPE | XOP_PULL | XOP_PUSH; } else if (!strcmp( opt, "noop" )) { mvars->ops[F] |= XOP_HAVE_TYPE; } else if (starts_with( opt, -1, "pull", 4 )) { op = XOP_PULL; lcac: opt += 4; if (!*opt) { cops |= op; } else if (*opt == '-') { opt++; goto rlcac; } else { goto badopt; } } else if (starts_with( opt, -1, "push", 4 )) { op = XOP_PUSH; goto lcac; } else { op = 0; rlcac: if (!strcmp( opt, "new" )) { op |= OP_NEW; } else if (!strcmp( opt, "renew" )) { op |= OP_RENEW; } else if (!strcmp( opt, "delete" )) { op |= OP_DELETE; } else if (!strcmp( opt, "flags" )) { op |= OP_FLAGS; } else { badopt: error( "Unknown option '%s'\n", argv[oind - 1] ); return 1; } switch (op & XOP_MASK_DIR) { case XOP_PULL: mvars->ops[N] |= op & OP_MASK_TYPE; break; case XOP_PUSH: mvars->ops[F] |= op & OP_MASK_TYPE; break; default: cops |= op; break; } mvars->ops[F] |= XOP_HAVE_TYPE; } continue; } ochar = argv[oind++] + 1; if (!*ochar) { error( "Invalid option '-'\n" ); return 1; } } switch (*ochar++) { case 'a': mvars->all = 1; break; case 'l': if (*ochar == 's') mvars->list_stores = 1, ochar++; else mvars->list = 1; break; case 'c': if (oind >= argc) { error( "-c requires an argument.\n" ); return 1; } config = argv[oind++]; break; case 'C': op = OP_CREATE|XOP_HAVE_CREATE; cop: if (*ochar == 'f') mvars->ops[F] |= op, ochar++; else if (*ochar == 'm') // Pre-1.4 legacy mvars->ops[F] |= op, ms_warn = 1, ochar++; else if (*ochar == 'n') mvars->ops[N] |= op, ochar++; else if (*ochar == 's') // Pre-1.4 legacy mvars->ops[N] |= op, ms_warn = 1, ochar++; else if (*ochar == '-') ochar++; else cops |= op; mvars->ops[F] |= op & (XOP_HAVE_CREATE | XOP_HAVE_REMOVE | XOP_HAVE_EXPUNGE); break; case 'R': op = OP_REMOVE|XOP_HAVE_REMOVE; goto cop; case 'X': op = OP_EXPUNGE|XOP_HAVE_EXPUNGE; goto cop; case 'F': cops |= XOP_PULL|XOP_PUSH; FALLTHROUGH case '0': mvars->ops[F] |= XOP_HAVE_TYPE; break; case 'n': case 'd': case 'f': case 'N': --ochar; op = 0; cac: for (;; ochar++) { if (*ochar == 'n') op |= OP_NEW; else if (*ochar == 'd') op |= OP_DELETE; else if (*ochar == 'f') op |= OP_FLAGS; else if (*ochar == 'N') op |= OP_RENEW; else break; } if (op & OP_MASK_TYPE) { switch (op & XOP_MASK_DIR) { case XOP_PULL: mvars->ops[N] |= op & OP_MASK_TYPE; break; case XOP_PUSH: mvars->ops[F] |= op & OP_MASK_TYPE; break; default: cops |= op; break; } } else { cops |= op; } mvars->ops[F] |= XOP_HAVE_TYPE; break; case 'L': op = XOP_PULL; goto cac; case 'H': op = XOP_PUSH; goto cac; case 'q': if (Verbosity > VERYQUIET) Verbosity--; break; case 'V': Verbosity = VERBOSE; break; case 'D': for (op = 0; *ochar; ochar++) { switch (*ochar) { case 'C': op |= DEBUG_CRASH; break; case 'd': op |= DEBUG_DRV; break; case 'D': op |= DEBUG_DRV | DEBUG_DRV_ALL; break; case 'm': op |= DEBUG_MAILDIR; break; case 'M': op |= DEBUG_MAIN; break; case 'n': op |= DEBUG_NET; break; case 'N': op |= DEBUG_NET | DEBUG_NET_ALL; break; case 's': op |= DEBUG_SYNC; break; default: error( "Unknown -D flag '%c'\n", *ochar ); return 1; } } if (!op) op = DEBUG_ALL; DFlags |= op; break; case 'T': for (; *ochar; ) { switch (*ochar++) { case 'a': DFlags |= FORCEASYNC; break; case 'j': DFlags |= KEEPJOURNAL; JLimit = strtol( ochar, &ochar, 10 ); break; case 'z': DFlags |= ZERODELAY; break; default: error( "Unknown -T flag '%c'\n", *(ochar - 1) ); return 1; } } break; case 'v': version(); case 'h': usage( 0 ); default: error( "Unknown option '-%c'\n", *(ochar - 1) ); return 1; } } if (ms_warn) warn( "Notice: -master/-slave/m/s suffixes are deprecated; use -far/-near/f/n instead.\n" ); if (DFlags & DEBUG_ANY) { Verbosity = VERBOSE; fputs( PACKAGE " " VERSION " called with:", stdout ); for (op = 1; op < argc; op++) printf( " '%s'", argv[op] ); puts( "" ); } else if (Verbosity >= TERSE && isatty( 1 )) { DFlags |= PROGRESS; } #ifdef __linux__ if (DFlags & DEBUG_CRASH) { signal( SIGSEGV, crashHandler ); signal( SIGBUS, crashHandler ); signal( SIGILL, crashHandler ); } #endif if (merge_ops( cops, mvars->ops, NULL )) return 1; if (load_config( config )) return 1; if (mvars->list_stores) list_stores( mvars, argv + oind ); else sync_chans( mvars, argv + oind ); return mvars->ret; }