diff --git a/.cvsignore b/.cvsignore index 8ed23b5..ad6c4ed 100644 --- a/.cvsignore +++ b/.cvsignore @@ -7,12 +7,16 @@ build-stamp config.h config.h.in config.cache +config.guess config.log config.status +config.sub configure configure.lineno configure-stamp isync.spec isync-*.tar.gz patch-stamp +stamp-h +stamp-h.in stamp-h1 diff --git a/Makefile.am b/Makefile.am index f0e4e0d..aca30bd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,6 @@ SUBDIRS = src -man_MANS = isync.1 -EXTRA_DIST = debian isyncrc.sample isync.spec $(man_MANS) +bin_SCRIPTS = get-cert +EXTRA_DIST = debian isync.spec $(bin_SCRIPTS) log: @perl -p -e "s/^(\\S+)\\s+(\\S.+\\S)\\s+(\\S+)\\s*\$$/\$$1:'\$$2 <\$$3>'\\n/" < ../CVSROOT/accounts > .usermap @@ -16,6 +16,9 @@ deb-clean: distdir distclean: deb-clean +dist-hook: + find $(distdir)/debian \( -name CVS -o -name .cvsignore -o -name .#\*# -o -type l \) -print0 | xargs -0r rm -rf + rpm: make dist cp $(PACKAGE)-$(VERSION).tar.gz /usr/src/rpm/SOURCES diff --git a/NEWS b/NEWS index 02f717d..6ba06b7 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,27 @@ +[1.0.0] + +Essentially a rewrite. Synchronization state storage concept, configuration +and command line changed entirely. +But you needn't to worry about the upgrade, as a fully automated migration +path is provided, even for users of isync 0.7 and below. +Still, you should re-read the manual to be able to take full advantage of the +new features: + +The supported mailbox types can be freely paired. +A possible application of this is using a local IMAP server to access +mailboxes that are not natively supported yet. + +Message deletions (expunges) are now propagated both ways, so there is no need +for using mutt with maildir_trash any more. + +Additional trash options added. + +`OneToOne' replaced by something more flexible. + +Partial support for IMAP pipelining (streaming, parallelization) added. +Makes flag change propagation much faster - this affects every message that +becomes Seen/Read. + [0.9] Added Tunnel directive to allow the user to specify a shell command to run diff --git a/README b/README index 9bfa082..da64072 100644 --- a/README +++ b/README @@ -4,52 +4,69 @@ | \__ \ |_| | | | | (__ |_|___/\__, |_| |_|\___| |___/ -isync - IMAP4 to maildir mailbox synchronization program +isync/mbsync - free (GPL) mailbox synchronization program http://isync.sf.net/ -Author: Michael Elkins -Current maintainer: Oswald Buddenhagen +See AUTHORS for contact information. -``isync'' is a command line application which synchronizes a local -maildir-style mailbox with a remote IMAP4 mailbox, suitable for use in -IMAP-disconnected mode. Multiple copies of the remote IMAP4 mailbox can be -maintained, and all flags are synchronized. +``mbsync'' is a command line application which synchronizes mailboxes; +currently Maildir and IMAP4 mailboxes are supported. New messages, message +deletions and flag changes can be propagated both ways. +``mbsync'' is suitable for use in IMAP-disconnected mode. -* Features: +Synchronization is based on unique message identifiers (UIDs), so no +identification conflicts can occur (as opposed to some other mail +synchronizers). +Synchronization state is kept in one local text file per mailbox pair; +multiple replicas of a mailbox can be maintained. - * Fast mode for fetching new mail only - * Supports imaps: (port 993) TLS/SSL connections - * Supports STARTTLS (RFC2595) for confidentiality - * Supports NAMESPACE (RFC2342) - * Supports CRAM-MD5 (RFC2095) for authentication +isync is the project name, while mbsync is the current executable name; this +change was necessary because of massive changes in the user interface. An +isync executable still exists; it is a compatibility wrapper around mbsync. + +* Features + + * Fine-grained selection of synchronization operations to perform + * Synchronizes single mailboxes or entire mailbox collections + * Partial mirrors possible: keep only the latest messages locally + * Trash functionality: backup messages before removing them + * IMAP features: + * Supports TLS/SSL via imaps: (port 993) and STARTTLS (RFC2595) + * Supports CRAM-MD5 (RFC2195) for authentication + * Supports NAMESPACE (RFC2342) for simplified configuration + * Pipelining for maximum speed (currently only partially implemented) * Compatibility - ``isync'' has been tested with the following IMAP servers: + isync should work fairly well with any IMAP4 compliant server; + particularily efficient with those that support the UIDPLUS and LITERAL+ + extensions. - * Microsoft Exchange 2000 IMAP4rev1 server version 6.0.4417.0 - * Courier-IMAP 1.2.3 - * WU-IMAP 2000 - * Domino IMAP4 Server Release 5.0.8 + Courier 1.4.3 is known to be buggy, version 1.7.3 works fine. + + c-client (UW-IMAP, Pine) is mostly fine, but tends to change UIDVALIDITY + pretty often when used with unix/mbox mailboxes, making isync refuse + synchronization. + The "cure" is to simply copy the new UIDVALIDITY from the affected + mailbox to mbsync's state file. This is a Bad Hack (TM), but it works - + use at your own risk (if the UIDVALIDITY change was genuine, this will + delete all messages in the affected mailbox - not that this ever + happened to me). * Platforms - ``isync'' has successfully been compiled on: - - * Linux - * Solaris 2.7 - * OpenBSD 2.8 - * FreeBSD 4.3 + At some point, ``isync'' has successfully run on: + Linux, Solaris 2.7, OpenBSD 2.8, FreeBSD 4.3, Cygwin * Requirements - OpenSSL for TLS/SSL support (optional) + OpenSSL for TLS/SSL support (optional) -* INSTALLING +* Installation - ./configure - make install + ./configure + make install -* HELP +* Help - Please see the man page for complete documentation. + Please see the man page for complete documentation. diff --git a/TODO b/TODO index 8654916..6c8c7a3 100644 --- a/TODO +++ b/TODO @@ -1,39 +1,51 @@ -change of UIDVALIDITY shouldn't be considered fatal for the imap connection. -maybe the error handling needs to be cleaned up in general. +make SSL certificate validation more automatic. -don't require maildir_trash. currently MaxMessages gets into the way of -simply removing it; that is fixable with some shuffling, though. +add asynchronous operation to remote mailbox drivers. this is actually +what prevents us from simply using c-client and thus becoming mailsync. -refactor mailbox support. create proper mailbox drivers; handle imap and -maildir (and anything else) symmetrically; decouple UID->message mapping -from sync database - should use the same UID storing schemes as c-client -(pine, uw-imap) does, at least optionally, i think. +handle custom flags (keywords). -add asynchrounous operation to remote mailbox drivers. this is actually -what prevents us from simply using c-client for the previous point and -thus simply becoming mailsync. +fix maildir_{open_store,list} to handle partial names (last char not slash). -store message flags in sync database, so _un_setting them will be properly -synced as well. +add a way to automatically create and sync subfolders. -handle custom imap flags. currently, isync just fails horribly if it -encounters some. +could store TUID even when UIDPLUS is supported. would avoid duplicated +messages after abort before new UID arrives. -add options for fine-grained control of syncing operations (--new, --delete & ---flags) and direction (--push & --pull). +decouple TUID search from append. that's a prerequisite for usable +MULTIAPPEND, and is generally good for async. should be way faster, too, +as it saves repeated mailbox rescans with single-file formats. -add support for syncing with other: and shared: via NAMESPACE +use MULTIAPPEND and FETCH with multiple messages. -isync ignores asynchronous notifications (untagged responses), so mail -arriving during a fetch will not be fetched in the current run any more. +create dummies describing MIME structure of messages bigger than MaxSize. +flagging the dummy would fetch the real message. possibly remove --renew. -add a way to automatically create and sync IMAP subfolders. +don't SELECT boxes unless really needed; in particular not for appending, +and in write-only mode not before changes are made. -make the command line take precedence over the config file. +possibly request message attributes on a per-message basis from the drivers. +considerations: +- record non-existing UID ranges in the sync database, so IMAP FETCHes needn't + to exclude anyway non-existing messages explicitly. +- when detect unborn pairs and orphaned messages being gone? implied by expunge: + with trashing, by local driver, or of messages we deleted in this run. the + remaining cases could be handled by automatic periodical cleanup passes, an + explicit --cleanup action, or be implied by one of the other actions. +- the benefit of this is questionable, as fine-grained requests will result + in sending huge amounts of data, and upstream is often way slower than + downstream. -possibly timestamp mails with remote arrival date. +maildir: possibly timestamp mails with remote arrival date. -possibly recover from UIDVALIDITY change by resyncing according to message -IDs - this is a pretty common condition with uw-imap. +maybe throw out the ctx->recent stuff - it's used only for one info message. possibly use ^[[1m to highlight error messages. + +consider alternative trash implementation: trash only messages we delete, +and trash before marking them deleted in the mailbox. downside: all other +programs have to do the same. and what if the deleted flag is unset? + +items out of scope of purely UID based approach: +- detect message moves between folders +- recovering from UIDVALIDITY change (uw-imap does this a lot) diff --git a/configure.in b/configure.in index 48f68d2..e31a05f 100644 --- a/configure.in +++ b/configure.in @@ -1,31 +1,32 @@ AC_INIT(src/isync.h) AM_CONFIG_HEADER(config.h) -AM_INIT_AUTOMAKE(isync, 0.9.2) +AM_INIT_AUTOMAKE(isync, 1.0.0alpha) AM_MAINTAINER_MODE AM_PROG_CC_STDC if test "$GCC" = yes; then - CFLAGS="$CFLAGS -pipe -W -Wall -Wshadow -Wmissing-prototypes" + CFLAGS="$CFLAGS -pipe -W -Wall -Wshadow -Wstrict-prototypes" fi -AC_CHECK_FUNCS(getopt_long) +AC_CHECK_FUNCS(vasprintf) -AC_CHECK_LIB(socket, socket) -AC_CHECK_LIB(nsl, inet_ntoa) +AC_CHECK_LIB(socket, socket, [SOCK_LIBS="-lsocket"]) +AC_CHECK_LIB(nsl, inet_ntoa, [SOCK_LIBS="$SOCK_LIBS -lnsl"]) +AC_SUBST(SOCK_LIBS) ssl=false AC_ARG_WITH(ssl, - [ --with-ssl=DIR yes/no/OpenSSL installation root [detect]], + AS_HELP_STRING([--with-ssl=DIR], [yes/no/OpenSSL installation root [detect]]), [ob_cv_with_ssl=$withval]) if test "x$ob_cv_with_ssl" != xno; then if test -d "$ob_cv_with_ssl/lib"; then - CPFLAGS="$CPPFLAGS -I$ob_cv_with_ssl/include" - LDFLAGS="$LDFLAGS -L$ob_cv_with_ssl/lib" + CPFLAGS="$CPPFLAGS -I$ob_cv_with_ssl/include" + LDFLAGS="$LDFLAGS -L$ob_cv_with_ssl/lib" fi AC_CHECK_LIB(crypto, ERR_error_string, [cryptolib=" -lcrypto"]) AC_CHECK_LIB(ssl, SSL_library_init, [ - LIBS="-lssl$cryptolib $LIBS" + SSL_LIBS="-lssl$cryptolib" AC_DEFINE(HAVE_LIBSSL, 1, [Define if you want SSL support]) ssl=true ],[ @@ -34,19 +35,29 @@ if test "x$ob_cv_with_ssl" != xno; then fi ]) fi +AC_SUBST(SSL_LIBS) -AC_CACHE_CHECK(for db_create in -ldb, ac_cv_db_db_create, - [ac_cv_db_dbcreate=no - AC_TRY_LINK([#include ], - [db_create();],[ac_cv_db_db_create=yes])]) -if test $ac_cv_db_db_create=yes; then - LIBS="$LIBS -ldb" -else - AC_MSG_ERROR([Berkley DB not found. -You must install libdb including the respective development files/headers.]) +AC_CACHE_CHECK([for Berkley DB 4.2], ac_cv_berkdb4, + [ac_cv_berkdb4=no + AC_TRY_LINK([#include ], + [DB *db; + db->truncate(db, 0, 0, 0); + db->open(db, 0, "foo", "foo", DB_HASH, DB_CREATE, 0)], + [ac_cv_berkdb4=yes])]) +if test "x$ac_cv_berkdb4" = xno; then + AC_MSG_ERROR([Berkley DB 4.2 not found. +You must install libdb4.2 including the respective development files/headers.]) fi -AC_OUTPUT(Makefile src/Makefile isync.spec) +AC_ARG_ENABLE(compat, + AS_HELP_STRING([--disable-compat], [don't include isync compatibility wrapper [no]]), + [ob_cv_enable_compat=$enableval]) +if test "x$ob_cv_enable_compat" != xno; then + AC_CHECK_FUNCS(getopt_long) +fi +AM_CONDITIONAL(with_compat, test "x$ob_cv_enable_compat" != xno) + +AC_OUTPUT(Makefile src/Makefile src/compat/Makefile isync.spec) if $ssl; then AC_MSG_RESULT([ diff --git a/debian/NEWS b/debian/NEWS deleted file mode 100644 index d166bea..0000000 --- a/debian/NEWS +++ /dev/null @@ -1,16 +0,0 @@ -isync (0.8-1) unstable; urgency=low - - IMPORTANT upgrade note: - - This version includes a change to the way the UID for each message is - stored in the local mailbox. You need to remove all the messages in your - local folder if you were previously using another version of isync or else - you will end up with duplicate messages on your IMAP server. - - A suggested upgrade procedure is to use isync version 0.7 to synchronize - any local changes in isync-managed mailboxes with your IMAP server, and - then remove the contents of the local mailboxes, before upgrading to this - version. Then run isync again to pull down the mail again. You must do - this manually, the Debian package will not do this for you. - - -- Joey Hess Tue, 29 Oct 2002 13:50:40 -0500 diff --git a/debian/README.Debian b/debian/README.Debian deleted file mode 100644 index af3c5e2..0000000 --- a/debian/README.Debian +++ /dev/null @@ -1,22 +0,0 @@ -A note from isync's web site: - -To use this command effectively, you need a mail client that sets the T -(trashed) flag when it deletes a message from a maildir mailbox, instead of -just removing it altogether. Currently, only Mutt 1.3.27 supports this. Without -such a client, isync will refetch the locally deleted messages from the server -since they will never get expunged. Be sure to put - -set maildir_trash - -in your ~/.muttrc when using Mutt. - -isync can be integrated into Mutt fairly easily with a few hooks: - - folder-hook ~A bind index $ - folder-hook +maildir 'macro index $ "!isync -e maildir\n"' - -where maildir is the name of the local mailbox (or its alias). This works well -so long as you are not modifying the IMAP mailbox outside of Mutt. However, if -you are using another mail program simultaneously Mutt will have the wrong idea -of the local mailbox flags and messages will start disappearing from its index -display (don't worry, they are still on disk). diff --git a/debian/config b/debian/config deleted file mode 100644 index 2475356..0000000 --- a/debian/config +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -set -e -. /usr/share/debconf/confmodule -if [ "$1" = "configure" -a ! -z "$2" ] && \ - dpkg --compare-versions "$2" lt 0.8; then - db_input critical isync/upgrade_0.8 || true - db_go || true -fi diff --git a/debian/control b/debian/control index 70a8841..8ce4878 100644 --- a/debian/control +++ b/debian/control @@ -4,21 +4,26 @@ Priority: optional Maintainer: Nicolas Boullis Uploaders: Nicolas Boullis , Theodore Y. Ts'o Standards-Version: 3.6.1 -Build-Depends: libssl-dev, debhelper (>= 4.1.16), dpkg-dev (>= 1.9.0), libdb4.0-dev, dpatch +Build-Depends: libssl-dev, debhelper (>= 4.1.16), dpkg-dev (>= 1.9.0), libdb4.2-dev, dpatch Package: isync Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Suggests: mutt -Description: Synchronize a local maildir with a remote IMAP4 mailbox - A command line application which synchronizes a local maildir-style - mailbox with a remote IMAP4 mailbox, suitable for use in disconnected - mode. Multiple copies of the remote IMAP4 mailbox can be maintained, - and all flags and messages are synchronized. +Description: Synchronize Maildir and IMAP4 mailboxes + A command line application which synchronizes mailboxes; currently + Maildir and IMAP4 mailboxes are supported. + New messages, message deletions and flag changes can be propagated both ways. + It is useful for working in disconnected mode, such as on a laptop or with a + non-permanent internet collection (dIMAP). . Features: - * Fast mode for fetching new mail only - * Supports imaps: (port 993) TLS/SSL connections - * Supports STARTTLS (RFC2595) for confidentiality - * Supports NAMESPACE (RFC2342) - * Supports CRAM-MD5 (RFC2095) for authentication + * Fine-grained selection of synchronization operations to perform + * Synchronizes single mailboxes or entire mailbox collections + * Partial mirrors possible: keep only the latest messages locally + * Trash functionality: backup messages before removing them + * IMAP features: + * Supports TLS/SSL via imaps: (port 993) and STARTTLS (RFC2595) + * Supports CRAM-MD5 (RFC2195) for authentication + * Supports NAMESPACE (RFC2342) for simplified configuration + * Pipelining for maximum speed (currently only partially implemented) diff --git a/debian/patches/00list b/debian/patches/00list index 4d4c8af..e69de29 100644 --- a/debian/patches/00list +++ b/debian/patches/00list @@ -1 +0,0 @@ -20-cleanup diff --git a/debian/patches/20-cleanup.dpatch b/debian/patches/20-cleanup.dpatch deleted file mode 100755 index 7a7264b..0000000 --- a/debian/patches/20-cleanup.dpatch +++ /dev/null @@ -1,146 +0,0 @@ -#! /bin/sh -e -## 20-cleanup.dpatch by Theodore Ts'o -## -## DP: Make sure the database store and the imap database is closed -## DP: if isync is aborted. - -[ -f debian/patches/00patch-opts ] && . debian/patches/00patch-opts -patch_opts="${patch_opts:--f --no-backup-if-mismatch}" - -if [ $# -ne 1 ]; then - echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" - exit 1 -fi -case "$1" in - -patch) patch $patch_opts -p1 < $0;; - -unpatch) patch $patch_opts -p1 -R < $0;; - *) - echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" - exit 1;; -esac - -exit 0 -@DPATCH@ - -Problem description: - ->> If isync dies in the middle of synchronization, or the network ->> connection breaks while it is synchronizing a mailbox, new messages ->> which are downloaded from the IMAP server do not have their UID saved ->> to the maildir directory. This is REALLY, REALLY BAD, because it ->> means that on the next isync, the downloaded messages are re-uploaded ->> to the imap server, resulting in duplicate messages in the IMAP store. ->> ->> This takes means the network download takes longer, and if the network ->> connection is unrealible, it means it's more likely the the IMAP ->> connection will break, resulting in more duplicate messages being ->> uploaded to the servers. (The first time, 14 messages were uploaded ->> to the server. The second time I re-isynced, 65 messages were ->> uploaded to the server, resulting in some 79 duplicate messages that I ->> had to manually weed out. Grr, grr, grr, grr.) - -Problem solution: - -Actually, I managed to figure out the solution a while ago, and got -hung up trying to figure out the right way to submit the patches back -to upstream (there's no mailing list that I can find; so do you just -communicate directly with the developers). Anyway, I got busy and I -never had a chance to send the patches a while ago. - -This patch is not the best, but it does seem to work. Perhaps a -better approach would be to use the more advanced API's available with -berkdb, so you can actually force a sync to the db/dbm files after -the mail message has been downloaded. Fundamentally, that's the -problem. The id has been added to the db file, but the changes don't -get forced out to disk, so in the case of an abnormal termination of -the program, the id's never get written to disk. - -The patch enclosed below solves the problem by establishing a signal -handler, which cleans up in the case of the user typing ^C (after the -network connection has gone away, say because your GSM phone's GPRS -connection has gotten flakey, for example). However, it doesn't solve -the problem in case of an abrupt system crash. In order to address -that problem, the overall program interfaces would have to be changed -to use the newer berkdb interfaces directly, but that would mean -dropping compatibility with the ancient dbm interface. Personally, I -don't think that to be any great loss, but the changes would be much -more invasive, and would require agreement with the upstream -maintainer that this is the right way to go. - -Also, for bonus points, perhaps there should be an inactivity timer so -that isync can automatically figure out when the network connection -has gone away, and can do a clean shutdown and exit automatically, -instead of requiring the user to type ^C. - - - Ted - - -Patched files: src/main.c -=================================================================== -RCS file: isync-0.9.2/src/RCS/main.c,v -retrieving revision 1.3 -diff -u -r1.3 isync-0.9.2/src/main.c ---- isync-0.9.2/src/main.c 2004/01/10 01:13:38 1.3 -+++ isync-0.9.2/src/main.c 2004/01/10 01:14:34 -@@ -35,6 +35,7 @@ - #include - #include - #include -+#include - - int Quiet; - -@@ -92,6 +93,22 @@ - unsigned int Tag = 0; - char Hostname[256]; - int Verbose = 0; -+mailbox_t *CleanupMail = 0; -+imap_t *CleanupImap = 0; -+int CleanupValid = 0; -+ -+static void signal_exit(int sig) -+{ -+ info("Abort received\n"); -+ if (CleanupValid) { -+ info("Aborting, cleaning up\n"); -+ if (CleanupMail) -+ maildir_close (CleanupMail); -+ if (CleanupImap) -+ imap_close (CleanupImap); -+ } -+ exit (1); -+} - - static void - version (void) -@@ -319,6 +336,10 @@ - usage (1); - } - -+ signal(SIGTERM, signal_exit); -+ signal(SIGHUP, signal_exit); -+ signal(SIGINT, signal_exit); -+ - gethostname (Hostname, sizeof (Hostname)); - - load_config (config, &o2o); -@@ -410,6 +431,9 @@ - ret = 1; - break; - } -+ CleanupValid = 1; -+ CleanupMail = mail; -+ CleanupImap = imap; - - info ("Synchronizing\n"); - i = (delete || box->delete) ? SYNC_DELETE : 0; -@@ -460,6 +484,8 @@ - - } while (0); - -+ CleanupValid = 0; -+ - /* we never sync the same mailbox twice, so close it now */ - if (mail) - maildir_close (mail); - diff --git a/debian/patches/30-async-imap.dpatch b/debian/patches/30-async-imap.dpatch deleted file mode 100755 index 12fcd10..0000000 --- a/debian/patches/30-async-imap.dpatch +++ /dev/null @@ -1,458 +0,0 @@ -#!/bin/sh -e -## 30-aysnc-imap.dpatch by Theodore Y. Ts'o -## -## DP: Add the beginnings of asynchronous IMAP support. So far, we only -## DP: support asynchronous flag setting, since that's the easist. -## DP: Eventually we need to support asynchronous message fetches and -## DP: uploads. - -if [ $# -ne 1 ]; then - echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" - exit 1 -fi - -[ -f debian/patches/00patch-opts ] && . debian/patches/00patch-opts -patch_opts="${patch_opts:--f --no-backup-if-mismatch}" - -case "$1" in - -patch) patch $patch_opts -p1 < $0;; - -unpatch) patch $patch_opts -p1 -R < $0;; - *) - echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" - exit 1;; -esac - -exit 0 - -@DPATCH@ -diff -urNad /usr/projects/isync/SF-cvs/isync/src/imap.c isync/src/imap.c ---- /usr/projects/isync/SF-cvs/isync/src/imap.c 2004-01-15 14:24:40.000000000 -0500 -+++ isync/src/imap.c 2004-01-15 20:36:15.000000000 -0500 -@@ -3,6 +3,7 @@ - * isync - IMAP4 to maildir mailbox synchronizer - * Copyright (C) 2000-2002 Michael R. Elkins - * Copyright (C) 2002-2003 Oswald Buddenhagen -+ * Copyright (C) 2004 Theodore Ts'o - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by -@@ -35,13 +36,33 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #if HAVE_LIBSSL - # include - #endif - -+struct imap_cmd { -+ unsigned int tag; -+ char *cmd; -+ int flags; -+ int response; -+ struct imap_cmd *next; -+ int (*complete_fn) (imap_t *imap, struct imap_cmd * cmd); -+}; -+ -+#define IMAP_FLAG_DONE 0x0001 -+ -+static struct imap_cmd *in_progress = NULL; -+static int num_in_progress = 0; -+int max_in_progress_high = 50; -+int max_in_progress_low = 10; -+ -+static struct imap_cmd *get_cmd_result(imap_t *imap); -+ - const char *Flags[] = { - "\\Seen", - "\\Answered", -@@ -199,6 +220,22 @@ - return write (sock->fd, buf, len); - } - -+static int -+socket_pending(Socket_t *sock) -+{ -+ int num = -1; -+ -+ if (ioctl(sock->fd, FIONREAD, &num) < 0) -+ return -1; -+ if (num > 0) -+ return num; -+#if HAVE_LIBSSL -+ if (sock->use_ssl) -+ return SSL_pending (sock->ssl); -+#endif -+ return 0; -+} -+ - static void - socket_perror (const char *func, Socket_t *sock, int ret) - { -@@ -301,16 +338,20 @@ - } - - static int --parse_fetch (imap_t * imap, list_t * list) -+parse_fetch (imap_t * imap, char *cmd) - { -- list_t *tmp; -+ list_t *tmp, *list; - unsigned int uid = 0; - unsigned int mask = 0; - unsigned int size = 0; - message_t *cur; - -- if (!is_list (list)) -+ list = parse_list (cmd, 0); -+ -+ if (!is_list (list)) { -+ free_list(list); - return -1; -+ } - - for (tmp = list->child; tmp; tmp = tmp->next) - { -@@ -325,6 +366,7 @@ - if (uid < imap->minuid) - { - /* already saw this message */ -+ free_list(list); - return 0; - } - else if (uid > imap->maxuid) -@@ -387,6 +429,7 @@ - cur->flags = mask; - cur->size = size; - -+ free_list(list); - return 0; - } - -@@ -415,39 +458,121 @@ - } - } - --static int --imap_exec (imap_t * imap, const char *fmt, ...) -+static void print_imap_command(const char *cmd, FILE *f) -+{ -+ if (strncmp(cmd, "LOGIN", 5)) -+ fputs(cmd, f); -+ else -+ fputs("LOGIN USERNAME PASSWORD", f); -+} -+ -+static struct imap_cmd *issue_imap_cmd(imap_t *imap, -+ const char *fmt, ...) - { - va_list ap; -- char tmp[256]; -- char buf[256]; -- char *cmd; -- char *arg; -- char *arg1; -- config_t *box; -+ char tmp[1024]; -+ char buf[1024]; -+ struct imap_cmd *cmd; - int n; - -+ cmd = malloc(sizeof(struct imap_cmd)); -+ if (!cmd) -+ return NULL; -+ -+ cmd->tag = ++Tag; -+ cmd->flags = 0; -+ cmd->response = 0; -+ cmd->complete_fn = 0; -+ - va_start (ap, fmt); - vsnprintf (tmp, sizeof (tmp), fmt, ap); - va_end (ap); - -- snprintf (buf, sizeof (buf), "%d %s\r\n", ++Tag, tmp); -+ cmd->cmd = malloc(strlen(tmp)+1); -+ if (cmd->cmd) -+ strcpy(cmd->cmd, tmp); -+ -+ snprintf (buf, sizeof (buf), "%d %s\r\n", cmd->tag, tmp); - if (Verbose) { -- printf (">>> %s", buf); -+ if (num_in_progress) -+ printf("(%d in progress) ", num_in_progress); -+ printf(">>> %d ", cmd->tag); -+ print_imap_command(tmp, stdout); -+ fputc('\n', stdout); - fflush (stdout); - } - n = socket_write (imap->sock, buf, strlen (buf)); - if (n <= 0) - { - socket_perror ("write", imap->sock, n); -- return -1; -+ free(cmd); -+ return NULL; - } -+ cmd->next = in_progress; -+ in_progress = cmd; -+ num_in_progress++; -+ if ((num_in_progress > max_in_progress_high) || -+ socket_pending(imap->sock)) { -+ while ((num_in_progress > max_in_progress_low) || -+ socket_pending(imap->sock)) { -+ if (Verbose && socket_pending(imap->sock)) -+ printf("(Socket input pending)\n"); -+ get_cmd_result(imap); -+ } -+ } -+ return cmd; -+} -+ -+static struct imap_cmd *find_imap_cmd(unsigned int tag) -+{ -+ struct imap_cmd *cmd, *prev; -+ -+ for (prev=NULL, cmd=in_progress; cmd; cmd = cmd->next) { -+ if (tag == cmd->tag) { -+ return cmd; -+ } -+ prev = cmd; -+ } -+ return NULL; -+} -+ -+static void dequeue_imap_cmd(unsigned int tag) -+{ -+ struct imap_cmd *cmd, *prev; -+ -+ for (prev=NULL, cmd=in_progress; cmd; cmd = cmd->next) { -+ if (tag != cmd->tag) { -+ prev = cmd; -+ continue; -+ } -+ if (prev) -+ prev->next = cmd->next; -+ else -+ in_progress = cmd->next; -+ cmd->next = 0; -+ if (cmd->cmd) -+ free(cmd->cmd); -+ cmd->cmd = 0; -+ free(cmd); -+ break; -+ } -+} -+ -+static struct imap_cmd *get_cmd_result(imap_t *imap) -+{ -+ char *cmd; -+ char *arg; -+ char *arg1; -+ config_t *box; -+ int n; -+ unsigned int tag; -+ struct imap_cmd *cmdp; - - for (;;) - { - next: - if (buffer_gets (imap->buf, &cmd)) -- return -1; -+ return NULL; - - arg = next_arg (&cmd); - if (*arg == '*') -@@ -456,7 +581,7 @@ - if (!arg) - { - fprintf (stderr, "IMAP error: unable to parse untagged response\n"); -- return -1; -+ return NULL; - } - - if (!strcmp ("NAMESPACE", arg)) -@@ -528,23 +653,14 @@ - imap->recent = atoi (arg); - else if (!strcmp ("FETCH", arg1)) - { -- list_t *list; -- -- list = parse_list (cmd, 0); -- -- if (parse_fetch (imap, list)) -- { -- free_list (list); -- return -1; -- } -- -- free_list (list); -+ if (parse_fetch (imap, cmd)) -+ return NULL; - } - } - else - { - fprintf (stderr, "IMAP error: unable to parse untagged response\n"); -- return -1; -+ return NULL; - } - } - #if HAVE_LIBSSL -@@ -555,7 +671,7 @@ - if (!imap->cram) - { - fprintf (stderr, "IMAP error, not doing CRAM-MD5 authentication\n"); -- return -1; -+ return NULL; - } - resp = cram (cmd, imap->box->user, imap->box->pass); - -@@ -568,34 +684,94 @@ - if (n <= 0) - { - socket_perror ("write", imap->sock, n); -- return -1; -+ return NULL; - } - n = socket_write (imap->sock, "\r\n", 2); - if (n <= 0) - { - socket_perror ("write", imap->sock, n); -- return -1; -+ return NULL; - } - imap->cram = 0; - } - #endif -- else if ((size_t) atol (arg) != Tag) -- { -- fprintf (stderr, "IMAP error: wrong tag\n"); -- return -1; -- } -- else -- { -- arg = next_arg (&cmd); -- parse_response_code (imap, cmd); -- if (!strcmp ("OK", arg)) -- return 0; -- return -1; -+ else { -+ tag = (unsigned int) atol (arg); -+ cmdp = find_imap_cmd(tag); -+ if (!cmdp) { -+ fprintf(stderr, "IMAP error: sent unknown tag: %u\n", -+ tag); -+ return NULL; -+ } -+ arg = next_arg (&cmd); -+ if (strncmp("OK", arg, 2)) { -+ if (cmdp->cmd) { -+ fputc('\'', stderr); -+ print_imap_command(cmdp->cmd, stderr); -+ fputc('\'', stderr); -+ } else -+ fprintf(stderr, "tag %u", tag); -+ fprintf(stderr, " returned an error (%s): %s\n", -+ arg, cmd ? cmd : ""); -+ cmdp->response = -1; -+ } -+ parse_response_code (imap, cmd); -+ num_in_progress--; -+ cmdp->flags |= IMAP_FLAG_DONE; -+ if (Verbose) -+ printf("Tag %u completed with response %d\n", -+ cmdp->tag, cmdp->response); -+ return cmdp; - } - } - /* not reached */ - } - -+static void flush_imap_cmds(imap_t *imap) -+{ -+ struct imap_cmd *cmdp; -+ -+ while (num_in_progress) { -+ if (in_progress && in_progress->flags & IMAP_FLAG_DONE) { -+ dequeue_imap_cmd(in_progress->tag); -+ continue; -+ } -+ cmdp = get_cmd_result(imap); -+ if (!cmdp) -+ printf("Error trying to flush pending imap cmds\n"); -+ } -+} -+ -+static int -+imap_exec (imap_t * imap, const char *fmt, ...) -+{ -+ va_list ap; -+ char tmp[1024]; -+ struct imap_cmd *cmdp, *waitp; -+ int result; -+ -+ va_start (ap, fmt); -+ vsnprintf (tmp, sizeof (tmp), fmt, ap); -+ va_end (ap); -+ -+ cmdp = issue_imap_cmd(imap, "%s", tmp); -+ if (!cmdp) -+ return -1; -+ -+ if (cmdp->flags & IMAP_FLAG_DONE) -+ return cmdp->response; -+ -+ do { -+ waitp = get_cmd_result(imap); -+ } while (waitp->tag != cmdp->tag); -+ -+ result = cmdp->response; -+ dequeue_imap_cmd(cmdp->tag); -+ -+ return cmdp->response; -+ -+} -+ - #ifdef HAVE_LIBSSL - static int - start_tls (imap_t *imap, config_t * cfg) -@@ -1039,6 +1215,7 @@ - size_t n; - char buf[1024]; - -+ flush_imap_cmds(imap); - send_server (imap->sock, "UID FETCH %d BODY.PEEK[]", uid); - - for (;;) -@@ -1160,7 +1337,9 @@ - (buf[0] != 0) ? " " : "", Flags[i]); - } - -- return imap_exec (imap, "UID STORE %d +FLAGS.SILENT (%s)", uid, buf); -+ if (issue_imap_cmd(imap, "UID STORE %d +FLAGS.SILENT (%s)", uid, buf)) -+ return 0; -+ return -1; - } - - int -@@ -1249,6 +1428,7 @@ - strcat (flagstr,") "); - } - -+ flush_imap_cmds(imap); - send_server (imap->sock, "APPEND %s%s %s{%d}", - imap->prefix, imap->box->box, flagstr, len + extra); - -@@ -1341,6 +1521,7 @@ - } - - /* didn't receive an APPENDUID */ -+ flush_imap_cmds(imap); - send_server (imap->sock, - "UID SEARCH HEADER X-TUID %08lx%05lx%04x", - tv.tv_sec, tv.tv_usec, pid); diff --git a/debian/po/POTFILES.in b/debian/po/POTFILES.in deleted file mode 100644 index cef83a3..0000000 --- a/debian/po/POTFILES.in +++ /dev/null @@ -1 +0,0 @@ -[type: gettext/rfc822deb] templates diff --git a/debian/po/fr.po b/debian/po/fr.po deleted file mode 100644 index 6c37225..0000000 --- a/debian/po/fr.po +++ /dev/null @@ -1,74 +0,0 @@ -# -# Translators, if you are not familiar with the PO format, gettext -# documentation is worth reading, especially sections dedicated to -# this format, e.g. by running: -# info -n '(gettext)PO Files' -# info -n '(gettext)Header Entry' -# -# Some information specific to po-debconf are available at -# /usr/share/doc/po-debconf/README-trans -# or http://www.debian.org/intl/l10n/po-debconf/README-trans -# -# Developers do not need to manually edit POT or PO files. -# -msgid "" -msgstr "" -"Project-Id-Version: isync 0.9.1-1\n" -"POT-Creation-Date: 2003-10-14 21:55+0200\n" -"PO-Revision-Date: 2003-10-27 11:52+0100\n" -"Last-Translator: Christian Perrier \n" -"Language-Team: French \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=iso-8859-15\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Description -#: ../templates:4 -msgid "Abort isync upgrade" -msgstr "Interrompre la mise à jour d'isync" - -#. Description -#: ../templates:4 -msgid "" -"You are upgrading from an older version of isync that stored the UID of each " -"message in a way that is not compatable with the new version. You need to " -"remove all the messages in local folders downloaded with the old version of " -"isync. Otherwise isync will get confused and upload duplicate messages to " -"the IMAP server." -msgstr "" -"Vous mettez isync à jour à partir d'une version qui utilise une méthode de " -"stockage des identifiants des messages incompatible avec la nouvelle " -"version. Il faut supprimer des répertoires locaux tous les messages " -"téléchargés avec l'ancienne version ; sinon, isync fonctionnera " -"incorrectement et enverra au serveur IMAP des doublons des messages." - -#. Description -#: ../templates:4 -msgid "" -"A suggested upgrade procedure is to use the isync version 0.7 to synchronize " -"any local changes in isync-managed mailboxes with your IMAP server (if there " -"are any local changes to synchronise), and then remove the contents of the " -"local mailboxes, before upgrading to version 0.8 or above. Then run isync " -"again to pull down the mail again. You must do this manually; the Debian " -"package will not do this for you." -msgstr "" -"La méthode suggérée pour la mise à jour est la suivante : en utilisant " -"la version 0.7 d'isync, synchronisez avec le serveur IMAP les éventuelles modifications locales " -"des boîtes aux lettres gérées par isync ; puis " -"supprimez le contenu des boîtes aux lettres locales. Ensuite, effectuez la " -"mise à jour vers une version supérieure ou égale à 0.8. Enfin, utilisez à " -"nouveau isync pour récupérer les courriels. Vous devez effectuer cette " -"opération vous-même : le paquet Debian ne la fera pas automatiquement." - -#. Description -#: ../templates:4 -msgid "" -"If you want, the upgrade of isync can be aborted to let you deal with this " -"issue. Or you can just suspend the upgrade or switch to a different virtual " -"console to take care of it. Do not continue past this point before manually " -"resolving this issue!" -msgstr "" -"Si vous le souhaitez, la mise à jour d'isync peut être interrompue pour vous " -"permettre d'effectuer cette opération. Vous pouvez également basculer vers " -"une autre console virtuelle pour vous en occuper, puis reprendre la mise à jour. Ne continuez pas " -"sans faire cette correction." diff --git a/debian/po/templates.pot b/debian/po/templates.pot deleted file mode 100644 index 6cd275a..0000000 --- a/debian/po/templates.pot +++ /dev/null @@ -1,60 +0,0 @@ -# -# Translators, if you are not familiar with the PO format, gettext -# documentation is worth reading, especially sections dedicated to -# this format, e.g. by running: -# info -n '(gettext)PO Files' -# info -n '(gettext)Header Entry' -# -# Some information specific to po-debconf are available at -# /usr/share/doc/po-debconf/README-trans -# or http://www.debian.org/intl/l10n/po-debconf/README-trans -# -# Developers do not need to manually edit POT or PO files. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2003-10-14 21:55+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" -"Content-Transfer-Encoding: 8bit\n" - -#. Description -#: ../templates:4 -msgid "Abort isync upgrade" -msgstr "" - -#. Description -#: ../templates:4 -msgid "" -"You are upgrading from an older version of isync that stored the UID of each " -"message in a way that is not compatable with the new version. You need to " -"remove all the messages in local folders downloaded with the old version of " -"isync. Otherwise isync will get confused and upload duplicate messages to " -"the IMAP server." -msgstr "" - -#. Description -#: ../templates:4 -msgid "" -"A suggested upgrade procedure is to use the isync version 0.7 to synchronize " -"any local changes in isync-managed mailboxes with your IMAP server (if there " -"are any local changes to synchronise), and then remove the contents of the " -"local mailboxes, before upgrading to version 0.8 or above. Then run isync " -"again to pull down the mail again. You must do this manually; the Debian " -"package will not do this for you." -msgstr "" - -#. Description -#: ../templates:4 -msgid "" -"If you want, the upgrade of isync can be aborted to let you deal with this " -"issue. Or you can just suspend the upgrade or switch to a different virtual " -"console to take care of it. Do not continue past this point before manually " -"resolving this issue!" -msgstr "" diff --git a/debian/preinst b/debian/preinst deleted file mode 100644 index ca956a8..0000000 --- a/debian/preinst +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -set -e - -if [ "$1" = "upgrade" ] && dpkg --compare-versions "$2" lt "0.8"; then - # Do not do debconf stuff if debconf is not there. - # I don't want to have to pre-depend on debconf. - if [ -e /usr/share/debconf/confmodule ]; then - . /usr/share/debconf/confmodule - db_get isync/upgrade_0.8 - if [ "$RET" = true ]; then - echo "Aborting isync upgrade at your request so you can manually resolve upgrade issue." >&2 - exit 1 - fi - else - echo "WARNING: Read NEWS.Debian file about manual upgrade issues from isync 0.7." >&2 - fi -fi - -#DEBHELPER# diff --git a/debian/rules b/debian/rules index acbd715..20b97d9 100755 --- a/debian/rules +++ b/debian/rules @@ -41,9 +41,8 @@ binary-arch: build install dh_testroot dh_installchangelogs ChangeLog dh_installdocs AUTHORS NEWS README TODO - dh_installexamples isyncrc.sample + dh_installexamples src/mbsyncrc.sample src/compat/isyncrc.sample dh_installman - dh_installdebconf dh_strip dh_compress dh_fixperms diff --git a/debian/templates b/debian/templates deleted file mode 100644 index c1dbe20..0000000 --- a/debian/templates +++ /dev/null @@ -1,21 +0,0 @@ -Template: isync/upgrade_0.8 -Type: boolean -Default: false -_Description: Abort isync upgrade - You are upgrading from an older version of isync that stored the UID of - each message in a way that is not compatable with the new version. You - need to remove all the messages in local folders downloaded with the old - version of isync. Otherwise isync will get confused and upload duplicate - messages to the IMAP server. - . - A suggested upgrade procedure is to use the isync version 0.7 to - synchronize any local changes in isync-managed mailboxes with your IMAP - server (if there are any local changes to synchronise), and then remove - the contents of the local mailboxes, before upgrading to version 0.8 or - above. Then run isync again to pull down the mail again. You must do this - manually; the Debian package will not do this for you. - . - If you want, the upgrade of isync can be aborted to let you deal with this - issue. Or you can just suspend the upgrade or switch to a different - virtual console to take care of it. Do not continue past this point before - manually resolving this issue! diff --git a/isync.spec.in b/isync.spec.in index 9f4d39d..08ce8cd 100644 --- a/isync.spec.in +++ b/isync.spec.in @@ -10,16 +10,16 @@ Packager: Oswald Buddenhagen BuildRoot: /var/tmp/%{name}-buildroot %description -isync is a command line utility for synchronizing a remote IMAP mailbox with a -local maildir-style mailbox. This is useful for working in disconnected mode, -such as on a laptop. Modifications made locally and remotely are synchronized -so that no message status flags are lost. +isync is a command line utility which synchronizes mailboxes; currently +Maildir and IMAP4 mailboxes are supported. +New messages, message deletions and flag changes can be propagated both ways. +It is useful for working in disconnected mode, such as on a laptop or with a +non-permanent internet collection (dIMAP). %prep %setup %build ./configure --prefix=/usr -make RPM_OPT_FLAGS="$RPM_OPT_FLAGS" %install make DESTDIR=$RPM_BUILD_ROOT install @@ -28,6 +28,11 @@ make DESTDIR=$RPM_BUILD_ROOT install rm -rf $RPM_BUILD_ROOT %files -%doc AUTHORS COPYING README TODO ChangeLog isyncrc.sample +%doc AUTHORS COPYING NEWS README TODO ChangeLog src/mbsyncrc.sample src/compat/isyncrc.sample /usr/bin/isync +/usr/bin/mbsync +/usr/bin/mdconvert +/usr/bin/get-cert /usr/man/man1/isync.1.gz +/usr/man/man1/mbsync.1.gz +/usr/man/man1/mdconvert.1.gz diff --git a/src/.cvsignore b/src/.cvsignore index 5630809..d6cbd59 100644 --- a/src/.cvsignore +++ b/src/.cvsignore @@ -1,4 +1,5 @@ .deps Makefile Makefile.in -isync +mbsync +mdconvert diff --git a/src/Makefile.am b/src/Makefile.am index 8c24394..02d52bb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,16 @@ -bin_PROGRAMS = isync -isync_SOURCES = main.c imap.c sync.c maildir.c list.c cram.c config.c dotlock.c -noinst_HEADERS = isync.h dotlock.h -INCLUDES=$(RPM_OPT_FLAGS) -DISTCLEANFILES = *~ +if with_compat +compat_dir = compat +endif +SUBDIRS = $(compat_dir) + +bin_PROGRAMS = mbsync mdconvert + +mbsync_SOURCES = main.c sync.c config.c util.c drv_imap.c drv_maildir.c +mbsync_LDADD = -ldb $(SSL_LIBS) $(SOCK_LIBS) +noinst_HEADERS = isync.h + +mdconvert_SOURCES = mdconvert.c +mdconvert_LDADD = -ldb + +man_MANS = mbsync.1 mdconvert.1 +EXTRA_DIST = mbsyncrc.sample $(man_MANS) diff --git a/src/compat/.cvsignore b/src/compat/.cvsignore new file mode 100644 index 0000000..5630809 --- /dev/null +++ b/src/compat/.cvsignore @@ -0,0 +1,4 @@ +.deps +Makefile +Makefile.in +isync diff --git a/src/compat/Makefile.am b/src/compat/Makefile.am new file mode 100644 index 0000000..e988fc7 --- /dev/null +++ b/src/compat/Makefile.am @@ -0,0 +1,8 @@ +bin_PROGRAMS = isync + +isync_SOURCES = main.c config.c convert.c util.c +isync_LDADD = -ldb +noinst_HEADERS = isync.h + +man_MANS = isync.1 +EXTRA_DIST = isyncrc.sample $(man_MANS) diff --git a/src/compat/config.c b/src/compat/config.c new file mode 100644 index 0000000..659485b --- /dev/null +++ b/src/compat/config.c @@ -0,0 +1,443 @@ +/* + * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer + * Copyright (C) 2000-2002 Michael R. Elkins + * Copyright (C) 2002-2004 Oswald Buddenhagen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "isync.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static char * +my_strndup( const char *s, size_t nchars ) +{ + char *r = nfmalloc( sizeof(char) * (nchars + 1) ); + memcpy( r, s, nchars ); + r[nchars] = 0; + return r; +} + +char * +expand_strdup( const char *s ) +{ + struct passwd *pw; + const char *p, *q; + char *r; + + if (*s == '~') { + s++; + if (!*s) { + p = 0; + q = Home; + } else if (*s == '/') { + p = s + 1; + q = Home; + } else { + if ((p = strchr( s, '/' ))) { + r = my_strndup( s, (int)(p - s) ); + pw = getpwnam( r ); + free( r ); + p++; + } else + pw = getpwnam( s ); + if (!pw) + return 0; + q = pw->pw_dir; + } + nfasprintf( &r, "%s/%s", q, p ? p : "" ); + return r; + } else if (*s != '/' && xmaildir) { + nfasprintf( &r, "%s/%s", xmaildir, s ); + return r; + } else + return nfstrdup( s ); +} + +static int +is_true( const char *val ) +{ + return + !strcasecmp( val, "yes" ) || + !strcasecmp( val, "true" ) || + !strcasecmp( val, "on" ) || + !strcmp( val, "1" ); +} + +void +load_config( const char *path, config_t ***stor ) +{ + config_t **sstor, *cfg; + FILE *fp; + char *p, *cmd, *val; + int line = 0; + char buf[1024]; + + if (!(fp = fopen( path, "r" ))) { + if (errno != ENOENT) + perror( "fopen" ); + return; + } + if (!Quiet && !Debug && !Verbose) + printf( "Reading configuration file %s\n", path ); + buf[sizeof(buf) - 1] = 0; + cfg = &global; + while (fgets( buf, sizeof(buf) - 1, fp )) { + p = buf; + cmd = next_arg( &p ); + val = next_arg( &p ); + line++; + if (!cmd || *cmd == '#') + continue; + if (!val) { + fprintf( stderr, "%s:%d: parameter missing\n", path, line ); + continue; + } + if (!strcasecmp( "Mailbox", cmd )) { + if (o2o) + break; + cfg = **stor = nfmalloc( sizeof(config_t) ); + *stor = &cfg->next; + memcpy( cfg, &global, sizeof(config_t) ); + /* not expanded at this point */ + cfg->path = nfstrdup( val ); + } else if (!strcasecmp( "OneToOne", cmd )) { + if (boxes) { + forbid: + fprintf( stderr, + "%s:%d: keyword '%s' allowed only in global section\n", + path, line, cmd ); + continue; + } + o2o = is_true( val ); + } else if (!strcasecmp( "Maildir", cmd )) { + if (boxes) + goto forbid; + maildir = nfstrdup( val ); + xmaildir = expand_strdup( val ); + } else if (!strcasecmp( "Folder", cmd )) { + if (boxes) + goto forbid; + folder = nfstrdup( val ); + } else if (!strcasecmp( "Inbox", cmd )) { + if (boxes) + goto forbid; + inbox = nfstrdup( val ); + } else if (!strcasecmp( "Host", cmd )) { + if (!memcmp( "imaps:", val, 6 )) { + val += 6; + cfg->use_imaps = 1; + cfg->port = 993; + cfg->use_sslv2 = 1; + cfg->use_sslv3 = 1; + } + cfg->host = nfstrdup( val ); + } else if (!strcasecmp( "User", cmd )) + cfg->user = nfstrdup( val ); + else if (!strcasecmp( "Pass", cmd )) + cfg->pass = nfstrdup( val ); + else if (!strcasecmp ( "Port", cmd )) + cfg->port = atoi( val ); + else if (!strcasecmp ( "Box", cmd )) + cfg->box = nfstrdup( val ); + else if (!strcasecmp ( "Alias", cmd )) { + if (!boxes) { + fprintf( stderr, + "%s:%d: keyword 'Alias' allowed only in mailbox specification\n", + path, line ); + continue; + } + cfg->alias = nfstrdup( val ); + } else if (!strcasecmp( "MaxSize", cmd )) + cfg->max_size = atol( val ); + else if (!strcasecmp ( "MaxMessages", cmd )) + cfg->max_messages = atol( val ); + else if (!strcasecmp ( "UseNamespace", cmd )) + cfg->use_namespace = is_true( val ); + else if (!strcasecmp ( "CopyDeletedTo", cmd )) + cfg->copy_deleted_to = nfstrdup( val ); + else if (!strcasecmp ( "Tunnel", cmd )) + cfg->tunnel = nfstrdup( val ); + else if (!strcasecmp ( "Expunge", cmd )) + cfg->expunge = is_true( val ); + else if (!strcasecmp( "Delete", cmd )) + cfg->delete = is_true( val ); + else if (!strcasecmp( "CertificateFile", cmd )) + cfg->cert_file = expand_strdup( val ); + else if (!strcasecmp( "RequireSSL", cmd )) + cfg->require_ssl = is_true( val ); + else if (!strcasecmp( "UseSSLv2", cmd )) + cfg->use_sslv2 = is_true( val ); + else if (!strcasecmp( "UseSSLv3", cmd )) + cfg->use_sslv3 = is_true( val ); + else if (!strcasecmp( "UseTLSv1", cmd )) + cfg->use_tlsv1 = is_true( val ); + else if (!strcasecmp( "RequireCRAM", cmd )) + cfg->require_cram = is_true( val ); + else if (buf[0]) + fprintf( stderr, "%s:%d: unknown keyword '%s'\n", path, line, cmd ); + } + fclose( fp ); + if (o2o) { + if (!global.host && !global.tunnel) { + fprintf( stderr, "Neither Host nor Tunnel given to OneToOne. Aborting.\n" ); + exit( 1 ); + } + } else + for (sstor = &boxes; (cfg = *sstor); ) { + if (!cfg->host && !cfg->tunnel) { + fprintf( stderr, "Mailbox '%s' has neither Host nor Tunnel. Skipping.\n", + cfg->alias ? cfg->alias : cfg->path ); + if (&cfg->next == *stor) + *stor = sstor; + *sstor = cfg->next; + continue; + } + sstor = &cfg->next; + } +} + +static const char * +tb( int on ) +{ + return on ? "yes" : "no"; +} + +static void +write_imap_server( FILE *fp, config_t *cfg ) +{ + config_t *pbox; + char *p, *p2; + int hl, a1, a2, a3, a4; + char buf[128]; + static int tunnels; + + if (cfg->tunnel) { + nfasprintf( (char **)&cfg->server_name, "tunnel%d", ++tunnels ); + fprintf( fp, "IMAPAccount %s\nTunnel \"%s\"\n", + cfg->server_name, cfg->tunnel ); + } else { + if (sscanf( cfg->host, "%d.%d.%d.%d", &a1, &a2, &a3, &a4 ) == 4) + cfg->server_name = nfstrdup( cfg->host ); + else { + p = strrchr( cfg->host, '.' ); + if (!p) + hl = nfsnprintf( buf, sizeof(buf), "%s", cfg->host ); + else { + hl = nfsnprintf( buf, sizeof(buf), "%.*s", p - cfg->host, cfg->host ); + p2 = strrchr( buf, '.' ); + if (p2) + hl = sprintf( buf, "%s", p2 + 1 ); + } + if (boxes) /* !o2o */ + for (pbox = boxes; pbox != cfg; pbox = pbox->next) + if (!memcmp( pbox->server_name, buf, hl + 1 )) { + nfasprintf( (char **)&cfg->server_name, "%s-%d", buf, ++pbox->servers ); + goto gotsrv; + } + cfg->server_name = nfstrdup( buf ); + cfg->servers = 1; + gotsrv: ; + } + fprintf( fp, "IMAPAccount %s\n", cfg->server_name ); + if (cfg->use_imaps) + fprintf( fp, "Host imaps:%s\n", cfg->host ); + else + fprintf( fp, "Host %s\n", cfg->host ); + fprintf( fp, "Port %d\n", cfg->port ); + } + if (cfg->user) + fprintf( fp, "User %s\n", cfg->user ); + if (cfg->pass) + fprintf( fp, "Pass \"%s\"\n", cfg->pass ); + fprintf( fp, "RequireCRAM %s\nRequireSSL %s\n" + "UseSSLv2 %s\nUseSSLv3 %s\nUseTLSv1 %s\n", + tb(cfg->require_cram), tb(cfg->require_ssl), + tb(cfg->use_sslv2), tb(cfg->use_sslv3), tb(cfg->use_tlsv1) ); + if ((cfg->use_imaps || cfg->use_sslv2 || cfg->use_sslv3 || cfg->use_tlsv1) && + cfg->cert_file) + fprintf( fp, "CertificateFile %s\n", cfg->cert_file ); + fputc( '\n', fp ); +} + +static void +write_imap_store( FILE *fp, config_t *cfg ) +{ + if (cfg->stores > 1) + nfasprintf( (char **)&cfg->store_name, "%s-%d", cfg->server_name, cfg->stores ); + else + cfg->store_name = cfg->server_name; + fprintf( fp, "IMAPStore %s\nAccount %s\n", + cfg->store_name, cfg->server_name ); + if (*folder) + fprintf( fp, "Path \"%s\"\n", folder ); + else + fprintf( fp, "UseNamespace %s\n", tb(cfg->use_namespace) ); + if (inbox) + fprintf( fp, "MapInbox \"%s\"\n", inbox ); + if (cfg->copy_deleted_to) + fprintf( fp, "Trash \"%s\"\n", cfg->copy_deleted_to ); + fputc( '\n', fp ); +} + +static void +write_channel_parm( FILE *fp, config_t *cfg ) +{ + if (cfg->max_size) + fprintf( fp, "MaxSize %d\n", cfg->max_size ); + if (cfg->max_messages) + fprintf( fp, "MaxMessages %d\n", cfg->max_messages ); + if (!cfg->delete) + fputs( "Sync New ReNew Flags\n", fp ); + if (cfg->expunge) + fputs( "Expunge Both\n", fp ); + fputc( '\n', fp ); +} + +static int +mstrcmp( const char *s1, const char *s2 ) +{ + if (s1 == s2) + return 0; + if (!s1 || !s2) + return 1; + return strcmp( s1, s2 ); +} + +void +write_config( int fd ) +{ + FILE *fp; + const char *cn, *scn; + config_t *box, *sbox, *pbox; + + if (!(fp = fdopen( fd, "w" ))) { + perror( "fdopen" ); + return; + } + + fprintf( fp, "SyncState *\n\nMaildirStore local\nPath \"%s/\"\nAltMap %s\n\n", maildir, tb( altmap > 0 ) ); + if (o2o) { + write_imap_server( fp, &global ); + write_imap_store( fp, &global ); + fprintf( fp, "Channel o2o\nMaster :%s:\nSlave :local:\nPattern %%\n", global.store_name ); + write_channel_parm( fp, &global ); + } else { + for (box = boxes; box; box = box->next) { + for (pbox = boxes; pbox != box; pbox = pbox->next) { + if (box->tunnel) { + if (mstrcmp( pbox->tunnel, box->tunnel )) + continue; + } else { + if (mstrcmp( pbox->host, box->host ) || + pbox->use_imaps != box->use_imaps || + pbox->port != box->port) + continue; + } + if (mstrcmp( pbox->user, box->user ) || + mstrcmp( pbox->pass, box->pass )) /* nonsense */ + continue; + if ((box->use_imaps || box->use_sslv2 || + box->use_sslv3 || box->use_tlsv1) && + mstrcmp( pbox->cert_file, box->cert_file )) /* nonsense */ + continue; + if (pbox->use_imaps != box->use_imaps || + pbox->use_sslv2 != box->use_sslv2 || + pbox->use_sslv3 != box->use_sslv3 || + pbox->use_tlsv1 != box->use_tlsv1) + continue; + box->server_name = pbox->server_name; + for (sbox = boxes; sbox != box; sbox = sbox->next) { + if (sbox->server_name != box->server_name || + mstrcmp( sbox->copy_deleted_to, box->copy_deleted_to ) || + (!*folder && sbox->use_namespace != box->use_namespace)) + continue; + box->store_name = sbox->store_name; + goto gotall; + } + box->stores = ++pbox->stores; + goto gotsrv; + } + write_imap_server( fp, box ); + box->stores = 1; + gotsrv: + write_imap_store( fp, box ); + gotall: + if (box->alias) + cn = box->alias; + else { + cn = strrchr( box->path, '/' ); + if (cn) + cn++; + else + cn = box->path; + } + for (sbox = boxes; sbox != box; sbox = sbox->next) { + if (sbox->alias) + scn = sbox->alias; + else { + scn = strrchr( sbox->path, '/' ); + if (scn) + scn++; + else + scn = sbox->path; + } + if (mstrcmp( cn, scn )) + continue; + nfasprintf( (char **)&box->channel_name, "%s-%d", cn, ++sbox->channels ); + goto gotchan; + } + box->channels = 1; + box->channel_name = cn; + gotchan: + fprintf( fp, "Channel %s\nMaster :%s:%s\nSlave :local:%s\n", + box->channel_name, box->store_name, box->box, box->path ); + write_channel_parm( fp, box ); + } + + } + + fclose( fp ); +} + +config_t * +find_box( const char *s ) +{ + config_t *p; + char *t; + + for (p = boxes; p; p = p->next) { + if (!strcmp( s, p->path ) || (p->alias && !strcmp( s, p->alias ))) + return p; + /* check to see if the full pathname was specified on the + * command line. + */ + t = expand_strdup( p->path ); + if (!strcmp( s, t )) { + free( t ); + return p; + } + free( t ); + } + return 0; +} diff --git a/src/compat/convert.c b/src/compat/convert.c new file mode 100644 index 0000000..b8f584c --- /dev/null +++ b/src/compat/convert.c @@ -0,0 +1,259 @@ +/* + * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer + * Copyright (C) 2000-2002 Michael R. Elkins + * Copyright (C) 2002-2004 Oswald Buddenhagen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "isync.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static const char *subdirs[] = { "cur", "new", "tmp" }; + +static const char Flags[] = { 'D', 'F', 'R', 'S', 'T' }; + +static int +parse_info( const char *s ) +{ + unsigned i; + int flags; + + flags = 0; + if (s && *(s + 1) == '2' && *(s + 2) == ',') + for (s += 3, i = 0; i < as(Flags); i++) + if (strchr( s, Flags[i] )) + flags |= (1 << i); + return flags; +} + +typedef struct { + int uid, flags; +} msg_t; + +static int +compare_uids( const void *l, const void *r ) +{ + return ((msg_t *)l)->uid - ((msg_t *)r)->uid; +} + +static DBT key, value; +static struct flock lck; + +void +convert( config_t *box ) +{ + DIR *d; + struct dirent *e; + char *s, *p, *mboxdir; + FILE *fp; + msg_t *msgs; + DB *db; + int i, ret, fd, uidval, maxuid, bl, uid, rmsgs, nmsgs, uv[2]; + unsigned u; + struct stat sb; + char buf[_POSIX_PATH_MAX], diumname[_POSIX_PATH_MAX], + uvname[_POSIX_PATH_MAX], sname[_POSIX_PATH_MAX], + iuvname[_POSIX_PATH_MAX], imuname[_POSIX_PATH_MAX], + ilname[_POSIX_PATH_MAX], iumname[_POSIX_PATH_MAX]; + + mboxdir = expand_strdup( box->path ); + nfsnprintf( iuvname, sizeof(iuvname), "%s/isyncuidvalidity", mboxdir ); + nfsnprintf( diumname, sizeof(iumname), "%s/.isyncuidmap.db", mboxdir ); + nfsnprintf( uvname, sizeof(uvname), "%s/.uidvalidity", mboxdir ); + if (stat( iuvname, &sb )) { + if (!stat( diumname, &sb )) + altmap++; + else if (!stat( uvname, &sb )) + altmap--; + err1: + free( mboxdir ); + return; + } + for (i = 0; i < 3; i++) { + nfsnprintf( buf, sizeof(buf), "%s/%s", mboxdir, subdirs[i] ); + if (stat( buf, &sb )) { + fprintf( stderr, "ERROR: stat %s: %s (errno %d)\n", buf, + strerror(errno), errno ); + fprintf( stderr, + "ERROR: %s does not appear to be a valid maildir style mailbox\n", + mboxdir ); + goto err1; + } + } + nfsnprintf( iumname, sizeof(iumname), "%s/isyncuidmap.db", mboxdir ); + nfsnprintf( imuname, sizeof(imuname), "%s/isyncmaxuid", mboxdir ); + nfsnprintf( ilname, sizeof(ilname), "%s/isynclock", mboxdir ); + nfsnprintf( sname, sizeof(sname), "%s/.mbsyncstate", mboxdir ); + + if ((fd = open( ilname, O_WRONLY|O_CREAT, 0600 )) < 0) { + perror( ilname ); + goto err1; + } +#if SEEK_SET != 0 + lck.l_whence = SEEK_SET; +#endif +#if F_WRLCK != 0 + lck.l_type = F_WRLCK; +#endif + if (fcntl( fd, F_SETLKW, &lck )) { + perror( ilname ); + err2: + close( fd ); + goto err1; + } + + if (!(fp = fopen( iuvname, "r" ))) { + perror( iuvname ); + goto err2; + } + fscanf( fp, "%d", &uidval ); + fclose( fp ); + if (!(fp = fopen( imuname, "r" ))) { + perror( imuname ); + goto err2; + } + fscanf( fp, "%d", &maxuid ); + fclose( fp ); + + if (!stat( iumname, &sb )) { + if (db_create( &db, 0, 0 )) { + fputs( "dbcreate failed\n", stderr ); + goto err2; + } + if (db->open( db, 0, iumname, 0, DB_HASH, 0, 0 )) { + fputs( "cannot open db\n", stderr ); + db->close( db, 0 ); + goto err2; + } + altmap++; + } else { + db = 0; + altmap--; + } + + msgs = 0; + rmsgs = 0; + nmsgs = 0; + for (i = 0; i < 2; i++) { + bl = nfsnprintf( buf, sizeof(buf), "%s/%s/", mboxdir, subdirs[i] ); + if (!(d = opendir( buf ))) { + perror( "opendir" ); + err4: + if (msgs) + free( msgs ); + if (db) + db->close( db, 0 ); + goto err2; + } + while ((e = readdir( d ))) { + if (*e->d_name == '.') + continue; + s = strchr( e->d_name, ':' ); + if (db) { + key.data = e->d_name; + key.size = s ? (size_t)(s - e->d_name) : strlen( e->d_name ); + if ((ret = db->get( db, 0, &key, &value, 0 ))) { + if (ret != DB_NOTFOUND) + db->err( db, ret, "Maildir error: db->get()" ); + continue; + } + uid = *(int *)value.data; + } else if ((p = strstr( e->d_name, ",U=" ))) + uid = atoi( p + 3 ); + else + continue; + if (nmsgs == rmsgs) { + rmsgs = rmsgs * 2 + 100; + msgs = nfrealloc( msgs, rmsgs * sizeof(msg_t) ); + } + msgs[nmsgs].uid = uid; + msgs[nmsgs++].flags = parse_info( s ); + } + closedir( d ); + } + + qsort( msgs, nmsgs, sizeof(msg_t), compare_uids ); + + if (!(fp = fopen( sname, "w" ))) { + perror( sname ); + goto err4; + } + if (box->max_messages) { + if (!nmsgs) + i = maxuid; + else { + i = nmsgs - box->max_messages; + if (i < 0) + i = 0; + i = msgs[i].uid - 1; + } + } else + i = 0; + fprintf( fp, "%d:%d %d:%d:%d\n", uidval, maxuid, uidval, i, maxuid ); + for (i = 0; i < nmsgs; i++) { + fprintf( fp, "%d %d ", msgs[i].uid, msgs[i].uid ); + for (u = 0; u < as(Flags); u++) + if (msgs[i].flags & (1 << u)) + fputc( Flags[u], fp ); + fputc( '\n', fp ); + } + fclose( fp ); + + if (db) { + key.data = (void *)"UIDVALIDITY"; + key.size = 11; + uv[0] = uidval; + uv[1] = maxuid; + value.data = uv; + value.size = sizeof(uv); + if ((ret = db->put( db, 0, &key, &value, 0 ))) { + db->err( db, ret, "Maildir error: db->put()" ); + goto err4; + } + db->close( db, 0 ); + rename( iumname, diumname ); + } else { + if (!(fp = fopen( uvname, "w" ))) { + perror( uvname ); + goto err4; + } + fprintf( fp, "%d\n%d\n", uidval, maxuid ); + fclose( fp ); + } + + unlink( iuvname ); + unlink( imuname ); + + close( fd ); + unlink( ilname ); + + if (msgs) + free( msgs ); + free( mboxdir ); + return; +} diff --git a/isync.1 b/src/compat/isync.1 similarity index 58% rename from isync.1 rename to src/compat/isync.1 index 0d1f723..56285c8 100644 --- a/isync.1 +++ b/src/compat/isync.1 @@ -1,6 +1,7 @@ .ig -\" isync - IMAP4 to maildir mailbox synchronizer +\" isync - mbsync wrapper: IMAP4 to Maildir mailbox synchronizer \" Copyright (C) 2000-2002 Michael R. Elkins +\" Copyright (C) 2002-2004 Oswald Buddenhagen \" \" This program is free software; you can redistribute it and/or modify \" it under the terms of the GNU General Public License as published by @@ -16,26 +17,27 @@ \" along with this program; if not, write to the Free Software \" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA \" -\" As a special exception, isync may be linked with the OpenSSL library, -\" despite that library's more restrictive license. .. -.TH isync 1 "2002 Dec 22" +.TH isync 1 "2004 Mar 27" .. .SH NAME -isync - synchronize IMAP4 and maildir mailboxes +isync - synchronize IMAP4 and Maildir mailboxes .. .SH SYNOPSIS -\fBisync\fR [\fIoptions...\fR] \fImailbox\fR [\fImailbox ...\fR] -.br -\fBisync\fR [\fIoptions...\fR] \fI-a\fR -.br -\fBisync\fR [\fIoptions...\fR] \fI-l\fR +\fBisync\fR [\fIoptions\fR ...] {\fImailbox\fR ...|\fI-a\fR|\fI-l\fR} .. .SH DESCRIPTION -\fBisync\fR is a command line application which synchronizes a local -maildir-style mailbox with a remote IMAP4 mailbox, suitable for use in -IMAP-disconnected mode. Multiple copies of the remote IMAP4 mailbox can +\fBisync\fR is a command line application which synchronizes local +Maildir mailboxes with remote IMAP4 mailboxes, suitable for use in +IMAP-disconnected mode. Multiple copies of the remote IMAP4 mailboxes can be maintained, and all flags are synchronized. +.br +\fBisync\fR is only a wrapper binary around \fBmbsync\fR to simplify upgrades. +It will automatically migrate the UID mapping from previous versions of +\fBisync\fR (even before 0.8) to the new format, and transparently call +\fBmbsync\fR. If you were using \fBisync\fR version 0.8 or 0.9.x you might +want to use \fBmdconvert\fR to convert the mailboxes to the more efficient +\fBnative\fR UID storage scheme after migrating them. .. .SH OPTIONS .TP @@ -60,7 +62,7 @@ Synchronize all mailboxes (either specified in ~/.isyncrc or determined by the Don't synchronize anything, but list all mailboxes and exit. .TP \fB-L\fR, \fB--create-local\fR -Automatically create the local maildir-style mailbox if it doesn't already +Automatically create the local Maildir mailbox if it doesn't already exist. .TP \fB-R\fR, \fB--create-remote\fR @@ -71,25 +73,23 @@ Automatically create any mailboxes if they don't already exist. This is simply a combination of -L and -R. .TP \fB-d\fR, \fB--delete\fR -Causes \fBisync\fR to delete messages from the local maildir mailbox -which do not exist on the IMAP server. By default, \fIdead\fR messages -are \fBnot\fR deleted. +Causes \fBisync\fR to propagate message deletions. +By default, \fIdead\fR messages are \fBnot\fR deleted. .TP \fB-e\fR, \fB--expunge\fR -Causes \fBisync\fR to permanently remove all messages marked for deletion -in both the local maildir mailbox and the remote IMAP mailbox. By default, -\fIdeleted\fR messages are \fBnot\fR expunged. +Causes \fBisync\fR to permanently remove all messages marked for deletion. +By default, \fIdeleted\fR messages are \fBnot\fR expunged. .TP \fB-f\fR, \fB--fast\fR -Causes \fBisync\fR to skip the step of synchronzing message flags between the -local maildir mailbox and the IMAP mailbox. Only new messages existing on the -server will be fetched into the local mailbox. +Only fetch new messages existing on the server into the local mailbox. +Message deletions and flag changes will not be propagated. .TP \fB-h\fR, \fB--help\fR Displays a summary of command line options .TP \fB-p\fR, \fB--port\fR \fIport\fR -Specifies the port on the IMAP server to connect to (default: 143) +Specifies the port on the IMAP server to connect to (default: 143 for imap, +993 for imaps) .TP \fB-q\fR, \fB--quiet\fR Suppress informational messages. @@ -99,7 +99,7 @@ If specified twice, suppress warning messages as well. Specifies the name of the remote IMAP mailbox to synchronize with (Default: INBOX) .TP -\fB-s\fR, \fB--host\fR \fB[\fRimaps:\fB]\fR\fIhost\fR +\fB-s\fR, \fB--host\fR [\fBimaps:\fR]\fIhost\fR Specifies the hostname of the IMAP server .TP \fB-u\fR, \fB--user\fR \fIuser\fR @@ -116,32 +116,49 @@ Displays \fBisync\fR version information. .TP \fB-V\fR, \fB--verbose\fR Enables \fIverbose\fR mode, which displays the IMAP4 network traffic. +.TP +\fB-D\fR, \fB--debug\fR +Enable printing of \fIdebug\fR messages. +.TP +\fB-w\fR, \fB--write\fR +Don't run \fBmbsync\fR, but instead write a permanent config file for it. +The UID mappings of all configured mailboxes will be migrated. +Note that some command line options that would affect an actual sync operation +will be incorporated into the new config file as well. +The name of the new config file is determined by replacing the last occurrence +of "isync" with "mbsync", or appending ".mbsync" if "isync" was not found. +.TP +\fB-W\fR, \fB--writeto\fR \fIfile\fR +Like \fB-w\fR, but use the specified name for the new config file. .. .SH CONFIGURATION -\fBisync\fR reads \fI~/.isyncrc\fR to load default configuration data. -Each line of the configuration file consists of a command. +\fBisync\fR by default reads \fI~/.isyncrc\fR to load configuration data. +Each non-empty line of the configuration file that does not start with a +hash mark consists of a command. The following commands are understood: .TP \fBMailbox\fR \fIpath\fR -Defines a local maildir mailbox. All configuration commands following this +Defines a local Maildir mailbox. All configuration commands following this line, up until the next \fIMailbox\fR command, apply to this mailbox only. .. .TP -\fBHost\fR \fB[\fRimaps:\fB]\fR\fIname\fR +\fBHost\fR [\fBimaps:\fR]\fIname\fR Defines the DNS name or IP address of the IMAP server. If the hostname is -prefixed with \fIimaps:\fR the connection is assumed to be a SSL connection +prefixed with \fBimaps:\fR the connection is assumed to be a SSL connection to port 993 (though you can change this by placing a \fBPort\fR command -\fBafter\fR the \fBHost\fR command. Note that some servers support SSL on -the default port 143. \fBisync\fR will always attempt to use SSL if available. +\fBafter\fR the \fBHost\fR command). +Note that modern servers support SSL on the default port 143. +\fBisync\fR will always attempt to use SSL if available. .. .TP \fBPort\fR \fIport\fR -Defines the TCP port number on the IMAP server to use (Default: 143) +Defines the TCP port number of the IMAP server (Default: 143 for imap, +993 for imaps) .. .TP \fBBox\fR \fImailbox\fR Defines the name of the remote IMAP mailbox associated with the local -maildir mailbox (Default: INBOX) +Maildir mailbox (Default: INBOX) .. .TP \fBUser\fR \fIusername\fR @@ -161,19 +178,18 @@ command line. .. .TP \fBCopyDeletedTo\fR \fImailbox\fR -Specifies the remote IMAP mailbox to copy deleted messages prior to +Specifies the remote IMAP mailbox to copy deleted messages to prior to expunging (Default: none). .. .TP \fBDelete\fR \fIyes\fR|\fIno\fR -Specifies whether messages in the local copy of the mailbox which don't -exist on the server are automatically deleted. (Default: no). +Specifies whether message deletions are propagated. (Default: no). \fBNOTE:\fR The \fI-d\fR command line option overrides this setting when set to \fIno\fR. .. .TP \fBExpunge\fR \fIyes\fR|\fIno\fR -Specifies whether deleted messages are expunged by default (Default: no). +Specifies whether deleted messages are expunged. (Default: no). \fBNOTE:\fR The \fI-e\fR command line option overrides this setting when set to \fIno\fR. .. @@ -181,7 +197,7 @@ set to \fIno\fR. \fBMailDir\fR \fIdirectory\fR Specifies the location of your local mailboxes if a relative path is specified in a \fIMailbox\fR command (Default: \fI~\fR). -\fBNOTE:\fR This directive is only meaningful in the \fIglobal\fR +\fBNOTE:\fR This directive is allowed only in the \fIglobal\fR section (see below). .. .TP @@ -190,28 +206,28 @@ Specifies the location of your IMAP mailboxes specified in \fIBox\fR commands (Default: \fI""\fR). \fBNOTE:\fR You \fBmust\fR append the hierarchy delimiter (usually a slash) to this specification. -\fBNOTE 2:\fR This directive is only meaningful in the \fIglobal\fR +\fBNOTE 2:\fR This directive is allowed only in the \fIglobal\fR section (see below). .. .TP \fBMaxMessages\fR \fIcount\fR -Sets the number of messages \fBisync\fR should keep in a mailbox. -This is useful for mailboxes where you keep a complete archive on the -server, but want to mirror only the last messages (for instance, for mailing -lists.) -The messages that were the first to arrive in the mailbox (independent of the -actual date of the message) will automatically be deleted if you -pass \fBisync\fR the delete (-d, --delete) flag. -Messages that are flagged (marked as important) will not be automatically -deleted. -If \fIcount\fR is 0, the maximum number of messages is \fBunlimited\fR -(Default: 0). +Sets the number of messages \fBisync\fR should keep in the local copy of a +mailbox. +This is useful for mailboxes where you keep a complete archive on the server, +but want to mirror only the last messages (for instance, for mailing lists). +The messages that were the first to arrive in the mailbox (independently of the +actual date of the message) will be deleted first. +Messages that are flagged (marked as important) and recent messages will not be +automatically deleted. +If \fIcount\fR is 0, the maximum number of messages is \fBunlimited\fR. +(Default: 0) .. .TP \fBMaxSize\fR \fIbytes\fR -Sets a threshold for the maximum message size (in bytes) for which \fBisync\fR -should transfer over the wire. This is useful for weeding out messages with -large attachments. If \fIbytes\fR is 0, the maximum file size is \fBunlimited\fR. +Messages larger than that many bytes will not be transferred over the wire. +This is useful for weeding out messages with large attachments. +If \fIbytes\fR is 0, the maximum file size is \fBunlimited\fR. +(Default: 0) .. .TP \fBTunnel\fR \fIcommand\fR @@ -220,19 +236,20 @@ socket. This allows you to run an IMAP session over an SSH tunnel, for example. .TP \fBUseNamespace\fR \fIyes\fR|\fIno\fR -Selects whether \fBisync\fR should select mailboxes using the namespace given -by the NAMESPACE command. This is useful with broken IMAP servers. (Default: -\fIyes\fR) +Selects whether the server's first "personal" NAMESPACE should be prefixed to +mailbox names. Disabling this makes sense for some broken IMAP servers. +This option is meaningless if a \fIFolder\fR was specified. +(Default: \fIyes\fR) .. .TP \fBRequireCRAM\fR \fIyes\fR|\fIno\fR -If set to \fIyes\fR, \fBisync\fR will require that the server accept CRAM-MD5 -intead of PLAIN to authenticate the user. +If set to \fIyes\fR, \fBisync\fR will abort the connection if no CRAM-MD5 +authentication is possible. (Default: \fIno\fR) .. .TP \fBRequireSSL\fR \fIyes\fR|\fIno\fR -\fBisync\fR will abort the connection if a TLS/SSL session to the IMAP -server can not be established. (Default: \fIyes\fR) +\fBisync\fR will abort the connection if a TLS/SSL session cannot be +established with the IMAP server. (Default: \fIyes\fR) .. .TP \fBCertificateFile\fR \fIpath\fR @@ -258,7 +275,7 @@ Should \fBisync\fR use TLSv1 for communication with the IMAP server over SSL? \fBisync\fR will ignore any \fIMailbox\fR specifications and instead pick up all mailboxes from the local \fIMailDir\fR and remote \fIFolder\fR and map them 1:1 onto each other according to their names. -\fBNOTE:\fR This directive is only meaningful in the \fIglobal\fR +\fBNOTE:\fR This directive is allowed only in the \fIglobal\fR section (see below). .. .TP @@ -278,63 +295,20 @@ the first \fBMailbox\fR command, and then leave out the \fBUser\fR command in the sections for each mailbox. \fBisync\fR will then use the global value by default. .. -.SH MAIL USER AGENT INTERACTION -To use \fBisync\fR effectively, you need a mail client that sets the T -(trashed) flag when it deletes a message from a maildir mailbox, instead of -just removing it altogether. Without such a client, \fBisync\fR will refetch the -locally deleted messages from the server since they will never get expunged. -Mutt (starting with version 1.3.27) is known to support this. Be sure to put -.IP "" 2 -set maildir_trash -.PP -in your ~/.muttrc when using Mutt. -.br -\fBisync\fR can be integrated into Mutt fairly easily with a few hooks: -.IP "" 2 -.nf -folder-hook ~A bind index $ -.br -folder-hook +\fImdir\fR 'macro index $ "!isync -e \fImdir\fR\\n"' -.fi -.PP -where \fImdir\fR is the name of the local mailbox (or its \fIalias\fR). -This works well so long as you are not modifying the IMAP mailbox outside of -Mutt. However, if you are using another mail program simultaneously Mutt -will have the wrong idea of the local mailbox flags and messages will start -disappearing from its index display (don't worry, they are still on disk). -.. .SH FILES .TP .B ~/.isyncrc Default configuration file .. .SH BUGS -No support for IMAP subfolders. -.P -\fBisync\fR does not use NFS-safe locking. It will correctly prevent -concurrent synchronization of a mailbox on the same host, but not across NFS. -.P -When synchronizing multiple mailboxes on the same IMAP server, it is not -possible to select different SSL options for each mailbox. Only the options -from the first mailbox are applied since the SSL session is reused. -.P -If new mail arrives in the IMAP mailbox after \fBisync\fR -has retrieved the initial message list, the new mail will not be fetched -until the next time \fBisync\fR is invoked. -.P -It is currently impossible to unset the \\Flagged attribute of a message -once it is set. It has to be manually unset everywhere since isync -doesn't have enough information to know which was the last status of the -message. -.P -The ndbm database created for each mailbox is not portable across different -architectures. It currently stores the UID in host byte order. -.P The configuration file takes precedence over command line options. .br Use -c /dev/null to work around. +.P +See the \fBINHERENT PROBLEMS\fR section in the \fBmbsync\fR man page, too. +.. .SH SEE ALSO -mutt(1), maildir(5) +mbsync(1), mdconvert(1), mutt(1), maildir(5) .P Up to date information on \fBisync\fR can be found at http://isync.sf.net/ .. diff --git a/src/compat/isync.h b/src/compat/isync.h new file mode 100644 index 0000000..e54888b --- /dev/null +++ b/src/compat/isync.h @@ -0,0 +1,100 @@ +/* + * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer + * Copyright (C) 2000-2002 Michael R. Elkins + * Copyright (C) 2002-2004 Oswald Buddenhagen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define _GNU_SOURCE + +#include + +#include +#include + +#define as(ar) (sizeof(ar)/sizeof(ar[0])) + +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) +# define ATTR_UNUSED __attribute__((unused)) +# define ATTR_NORETURN __attribute__((noreturn)) +# define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var))) +#else +# define ATTR_UNUSED +# define ATTR_NORETURN +# define ATTR_PRINTFLIKE(fmt,var) +#endif + +typedef struct config { + struct config *next; + + const char *server_name; + int servers; + char *host; + int port; + char *user; + char *pass; + char *tunnel; + unsigned int require_cram:1; + unsigned int require_ssl:1; + unsigned int use_imaps:1; + unsigned int use_sslv2:1; + unsigned int use_sslv3:1; + unsigned int use_tlsv1:1; + char *cert_file; + + const char *store_name; + int stores; + char *copy_deleted_to; + unsigned int use_namespace:1; + + const char *channel_name; + int channels; + const char *alias; + const char *box; + const char *path; /* path relative to .maildir, or absolute path */ + int max_size; + unsigned int max_messages; + unsigned int expunge:1; + unsigned int delete:1; +} config_t; + +extern int Quiet, Verbose, Debug; + +extern const char *Home; + +extern config_t global, *boxes; + +extern const char *maildir, *xmaildir, *folder, *inbox; +extern int o2o, altmap; + +/* config.c */ +void load_config( const char *, config_t *** ); +void write_config( int ); +char *expand_strdup( const char * ); +config_t *find_box( const char * ); + +/* convert.c */ +void convert( config_t * ); + +/* util.c */ +char *next_arg( char ** ); +void *nfmalloc( size_t sz ); +void *nfrealloc( void *mem, size_t sz ); +char *nfstrdup( const char *str ); +int nfvasprintf( char **str, const char *fmt, va_list va ); +int nfasprintf( char **str, const char *fmt, ... ); +int nfsnprintf( char *buf, int blen, const char *fmt, ... ); +void ATTR_NORETURN oob( void ); diff --git a/isyncrc.sample b/src/compat/isyncrc.sample similarity index 100% rename from isyncrc.sample rename to src/compat/isyncrc.sample diff --git a/src/compat/main.c b/src/compat/main.c new file mode 100644 index 0000000..8a7006e --- /dev/null +++ b/src/compat/main.c @@ -0,0 +1,431 @@ +/* + * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer + * Copyright (C) 2000-2002 Michael R. Elkins + * Copyright (C) 2002-2004 Oswald Buddenhagen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "isync.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_GETOPT_LONG +# define _GNU_SOURCE +# include +struct option Opts[] = { + {"write", 0, NULL, 'w' }, + {"writeto", 0, NULL, 'W' }, + {"all", 0, NULL, 'a' }, + {"list", 0, NULL, 'l'}, + {"config", 1, NULL, 'c'}, + {"create", 0, NULL, 'C'}, + {"create-local", 0, NULL, 'L'}, + {"create-remote", 0, NULL, 'R'}, + {"delete", 0, NULL, 'd'}, + {"expunge", 0, NULL, 'e'}, + {"fast", 0, NULL, 'f'}, + {"help", 0, NULL, 'h'}, + {"remote", 1, NULL, 'r'}, + {"folder", 1, NULL, 'F'}, + {"maildir", 1, NULL, 'M'}, + {"one-to-one", 0, NULL, '1'}, + {"inbox", 1, NULL, 'I'}, + {"host", 1, NULL, 's'}, + {"port", 1, NULL, 'p'}, + {"debug", 0, NULL, 'D'}, + {"quiet", 0, NULL, 'q'}, + {"user", 1, NULL, 'u'}, + {"version", 0, NULL, 'v'}, + {"verbose", 0, NULL, 'V'}, + {0, 0, 0, 0} +}; +#endif + +static void +version( void ) +{ + puts( PACKAGE " " VERSION ); + exit( 0 ); +} + +static void +usage( int code ) +{ + fputs( +PACKAGE " " VERSION " - mbsync wrapper: IMAP4 to maildir synchronizer\n" +"Copyright (C) 2000-2002 Michael R. Elkins \n" +"Copyright (C) 2002-2004 Oswald Buddenhagen \n" +"usage:\n" +" " PACKAGE " [ flags ] mailbox [mailbox ...]\n" +" " PACKAGE " [ flags ] -a\n" +" " PACKAGE " [ flags ] -l\n" +" -a, --all synchronize all defined mailboxes\n" +" -l, --list list all defined mailboxes and exit\n" +" -L, --create-local create local maildir mailbox if nonexistent\n" +" -R, --create-remote create remote imap mailbox if nonexistent\n" +" -C, --create create both local and remote mailboxes if nonexistent\n" +" -d, --delete delete local msgs that don't exist on the server\n" +" -e, --expunge expunge deleted messages\n" +" -f, --fast only fetch new messages\n" +" -r, --remote BOX remote mailbox\n" +" -F, --folder DIR remote IMAP folder containing mailboxes\n" +" -M, --maildir DIR local directory containing mailboxes\n" +" -1, --one-to-one map every IMAP /box to /box\n" +" -I, --inbox BOX map IMAP INBOX to /BOX (exception to -1)\n" +" -s, --host HOST IMAP server address\n" +" -p, --port PORT server IMAP port\n" +" -u, --user USER IMAP user name\n" +" -c, --config CONFIG read an alternate config file (default: ~/.isyncrc)\n" +" -D, --debug print debugging messages\n" +" -V, --verbose verbose mode (display network traffic)\n" +" -q, --quiet don't display progress info\n" +" -v, --version display version\n" +" -h, --help display this help message\n\n" +"Note that this is a wrapper binary only; the \"real\" isync is named \"mbsync\".\n" +"Options to permanently transform your old isync configuration:\n" +" -w, --write write permanent mbsync configuration\n" +" -W, --writeto FILE write permanent mbsync configuration to FILE\n", + code ? stderr : stdout ); + exit( code ); +} + +static const char * +strrstr( const char *h, const char *n ) +{ + char *p = strstr( h, n ); + if (!p) + return 0; + do { + h = p; + p = strstr( h + 1, n ); + } while (p); + return h; +} + +static void +add_arg( char ***args, const char *arg ) +{ + int nu = 0; + if (*args) + for (; (*args)[nu]; nu++); + *args = nfrealloc( *args, sizeof(char *) * (nu + 2)); + (*args)[nu] = nfstrdup( arg ); + (*args)[nu + 1] = 0; +} + +#define OP_EXPUNGE (1<<0) +#define OP_DELETE (1<<1) +#define OP_FAST (1<<2) +#define OP_CREATE_REMOTE (1<<3) +#define OP_CREATE_LOCAL (1<<4) + +int Quiet, Verbose, Debug; +config_t global, *boxes; +const char *maildir, *xmaildir, *folder, *inbox; +int o2o, altmap; + +const char *Home; + +int +main( int argc, char **argv ) +{ + config_t *box, **stor; + char *config = 0, *outconfig = 0, **args; + int i, pl, fd, mod, all, list, ops, writeout; + struct stat st; + char path1[_POSIX_PATH_MAX], path2[_POSIX_PATH_MAX]; + + if (!(Home = getenv("HOME"))) { + fputs( "Fatal: $HOME not set\n", stderr ); + return 1; + } + + /* defaults */ + /* XXX the precedence is borked: + it's defaults < cmdline < file instead of defaults < file < cmdline */ + global.port = 143; + global.box = "INBOX"; + global.use_namespace = 1; + global.require_ssl = 1; + global.use_tlsv1 = 1; + folder = ""; + maildir = "~"; + xmaildir = Home; + +#define FLAGS "wW:alCLRc:defhp:qu:r:F:M:1I:s:vVD" + + mod = all = list = ops = writeout = Quiet = Verbose = Debug = 0; +#if HAVE_GETOPT_LONG + while ((i = getopt_long( argc, argv, FLAGS, Opts, NULL )) != -1) +#else + while ((i = getopt( argc, argv, FLAGS )) != -1) +#endif + { + switch (i) { + case 'W': + outconfig = optarg; + /* plopp */ + case 'w': + writeout = 1; + break; + case 'l': + list = 1; + /* plopp */ + case 'a': + all = 1; + break; + case '1': + o2o = 1; + mod = 1; + break; + case 'C': + ops |= OP_CREATE_REMOTE|OP_CREATE_LOCAL; + break; + case 'L': + ops |= OP_CREATE_LOCAL; + break; + case 'R': + ops |= OP_CREATE_REMOTE; + break; + case 'c': + config = optarg; + break; + case 'd': + ops |= OP_DELETE; + break; + case 'e': + ops |= OP_EXPUNGE; + break; + case 'f': + ops |= OP_FAST; + break; + case 'p': + global.port = atoi( optarg ); + mod = 1; + break; + case 'r': + global.box = optarg; + mod = 1; + break; + case 'F': + folder = optarg; + mod = 1; + break; + case 'M': + maildir = optarg; + mod = 1; + break; + case 'I': + inbox = optarg; + mod = 1; + break; + case 's': +#if HAVE_LIBSSL + if (!strncasecmp( "imaps:", optarg, 6 )) { + global.use_imaps = 1; + global.port = 993; + global.use_sslv2 = 1; + global.use_sslv3 = 1; + optarg += 6; + } +#endif + global.host = optarg; + mod = 1; + break; + case 'u': + global.user = optarg; + mod = 1; + break; + case 'D': + Debug = 1; + break; + case 'V': + Verbose = 1; + break; + case 'q': + Quiet++; + break; + case 'v': + version(); + case 'h': + usage( 0 ); + default: + usage( 1 ); + } + } + + if (config) { + if (*config != '/') { + if (!getcwd( path1, sizeof(path1) )) { + fprintf( stderr, "Can't obtain working directory\n" ); + return 1; + } + pl = strlen( path1 ); + nfsnprintf( path1 + pl, sizeof(path1) - pl, "/%s", config ); + config = path1; + } + } else { + nfsnprintf( path1, sizeof(path1), "%s/.isyncrc", Home ); + config = path1; + } + stor = &boxes; + load_config( config, &stor ); + + if (!all && !o2o) + for (i = optind; argv[i]; i++) + if (!(box = find_box( argv[i] ))) { + box = nfmalloc( sizeof(config_t) ); + memcpy( box, &global, sizeof(config_t) ); + box->path = argv[i]; + *stor = box; + stor = &box->next; + mod = 1; + } + + if (writeout) { + all = 1; + if (mod) + fprintf( stderr, + "Warning: command line switches that influence the resulting config file\n" + "have been supplied.\n" ); + } else { + if (!argv[optind] && !all) { + fprintf( stderr, "No mailbox specified. Try isync -h\n" ); + return 1; + } + } + + if (all) { + if (o2o) { + DIR * dir; + struct dirent *de; + + if (!(dir = opendir( xmaildir ))) { + fprintf( stderr, "%s: %s\n", xmaildir, strerror(errno) ); + return 1; + } + while ((de = readdir( dir ))) { + if (*de->d_name == '.') + continue; + nfsnprintf( path1, sizeof(path1), "%s/%s/cur", xmaildir, de->d_name ); + if (stat( path1, &st ) || !S_ISDIR( st.st_mode )) + continue; + global.path = de->d_name; + global.box = (inbox && !strcmp( inbox, global.path )) ? + "INBOX" : global.path; + convert( &global ); + } + closedir( dir ); + } else + for (box = boxes; box; box = box->next) + convert( box ); + } else { + for (i = optind; argv[i]; i++) + if (o2o) { + global.path = argv[i]; + global.box = + (inbox && !strcmp( global.path, inbox )) ? + "INBOX" : global.path; + convert( &global ); + } else + convert( find_box( argv[i] ) ); + } + + if (writeout) { + if (!outconfig) { + const char *p = strrchr( config, '/' ); + if (!p) + p = config; + p = strrstr( p, "isync" ); + if (!p) + nfsnprintf( path2, sizeof(path2), "%s.mbsync", config ); + else + nfsnprintf( path2, sizeof(path2), "%.*smb%s", p - config, config, p + 1 ); + outconfig = path2; + } + if ((fd = creat( outconfig, 0666 )) < 0) { + fprintf( stderr, "Error: cannot write new config %s: %s\n", outconfig, strerror(errno) ); + return 1; + } + } else { + strcpy( path2, "/tmp/mbsyncrcXXXXXX" ); + if ((fd = mkstemp( path2 )) < 0) { + fprintf( stderr, "Can't create temp file\n" ); + return 1; + } + } + write_config( fd ); + + if (writeout) + return 0; + args = 0; + add_arg( &args, "mbsync" ); + if (Verbose) + add_arg( &args, "-V" ); + if (Debug) + add_arg( &args, "-D" ); + for (; Quiet; Quiet--) + add_arg( &args, "-q" ); + add_arg( &args, "-cT" ); + add_arg( &args, path2 ); + if (ops & OP_FAST) + add_arg( &args, "-Ln" ); + else if (!(ops & OP_DELETE)) + add_arg( &args, "-nNf" ); + if (ops & OP_CREATE_REMOTE) + add_arg( &args, "-Cm" ); + if (ops & OP_CREATE_LOCAL) + add_arg( &args, "-Cs" ); + if (ops & OP_EXPUNGE) + add_arg( &args, "-X" ); + if (list) + add_arg( &args, "-l" ); + if (o2o) { + if (all) + add_arg( &args, "o2o" ); + else { + char buf[1024]; + strcpy( buf, "o2o:" ); + strcat( buf, argv[optind] ); + while (argv[++optind]) { + strcat( buf, "," ); + strcat( buf, argv[optind] ); + } + add_arg( &args, buf ); + } + } else { + if (all) + add_arg( &args, "-a" ); + else + for (; argv[optind]; optind++) + add_arg( &args, find_box( argv[optind] )->channel_name ); + } + execvp( args[0], args ); + perror( args[0] ); + return 1; +} diff --git a/src/compat/util.c b/src/compat/util.c new file mode 100644 index 0000000..9651ee5 --- /dev/null +++ b/src/compat/util.c @@ -0,0 +1,156 @@ +/* + * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer + * Copyright (C) 2000-2002 Michael R. Elkins + * Copyright (C) 2002-2004 Oswald Buddenhagen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "isync.h" + +#include +#include +#include +#include +#include +#include + +char * +next_arg( char **s ) +{ + char *ret; + + if (!s || !*s) + return 0; + while (isspace( (unsigned char) **s )) + (*s)++; + if (!**s) { + *s = 0; + return 0; + } + if (**s == '"') { + ++*s; + ret = *s; + *s = strchr( *s, '"' ); + } else { + ret = *s; + while (**s && !isspace( (unsigned char) **s )) + (*s)++; + } + if (*s) { + if (**s) + *(*s)++ = 0; + if (!**s) + *s = 0; + } + return ret; +} + +#ifndef HAVE_VASPRINTF +static int +vasprintf( char **strp, const char *fmt, va_list ap ) +{ + int len; + char tmp[1024]; + + if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap) ) < 0) + *strp = 0; + else if ((*strp = malloc( len + 1 ))) { + if (len >= sizeof(tmp)) + vsprintf( *strp, fmt, ap ); + else + memcpy( *strp, tmp, len + 1 ); + } + return len; +} +#endif + +void +oob( void ) +{ + fputs( "Fatal: buffer too small. Please report a bug.\n", stderr ); + abort(); +} + +int +nfsnprintf( char *buf, int blen, const char *fmt, ... ) +{ + int ret; + va_list va; + + va_start( va, fmt ); + if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen) + oob(); + va_end( va ); + return ret; +} + +static void ATTR_NORETURN +oom( void ) +{ + fputs( "Fatal: Out of memory\n", stderr ); + abort(); +} + +void * +nfmalloc( size_t sz ) +{ + void *ret; + + if (!(ret = malloc( sz ))) + oom(); + return ret; +} + +void * +nfrealloc( void *mem, size_t sz ) +{ + char *ret; + + if (!(ret = realloc( mem, sz )) && sz) + oom(); + return ret; +} + +char * +nfstrdup( const char *str ) +{ + char *ret; + + if (!(ret = strdup( str ))) + oom(); + return ret; +} + +int +nfvasprintf( char **str, const char *fmt, va_list va ) +{ + int ret = vasprintf( str, fmt, va ); + if (!*str) + oom(); + return ret; +} + +int +nfasprintf( char **str, const char *fmt, ... ) +{ + int ret; + va_list va; + + va_start( va, fmt ); + ret = nfvasprintf( str, fmt, va ); + va_end( va ); + return ret; +} diff --git a/src/config.c b/src/config.c index f391d5d..4ea8321 100644 --- a/src/config.c +++ b/src/config.c @@ -1,8 +1,7 @@ -/* $Id$ - * - * isync - IMAP4 to maildir mailbox synchronizer +/* + * mbsync - mailbox synchronizer * Copyright (C) 2000-2002 Michael R. Elkins - * Copyright (C) 2002-2003 Oswald Buddenhagen + * Copyright (C) 2002-2004 Oswald Buddenhagen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,7 +17,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * - * As a special exception, isync may be linked with the OpenSSL library, + * As a special exception, mbsync may be linked with the OpenSSL library, * despite that library's more restrictive license. */ @@ -33,254 +32,411 @@ #include #include -config_t *boxes = 0; +store_conf_t *stores; +channel_conf_t *channels; +group_conf_t *groups; +int global_mops, global_sops; +char *global_sync_state; -/* set defaults from the global configuration section */ -static void -config_defaults (config_t * conf) +int +parse_bool( conffile_t *cfile ) { - memcpy (conf, &global, sizeof (config_t)); + if (!strcasecmp( cfile->val, "yes" ) || + !strcasecmp( cfile->val, "true" ) || + !strcasecmp( cfile->val, "on" ) || + !strcmp( cfile->val, "1" )) + return 1; + if (strcasecmp( cfile->val, "no" ) && + strcasecmp( cfile->val, "false" ) && + strcasecmp( cfile->val, "off" ) && + strcmp( cfile->val, "0" )) + fprintf( stderr, "%s:%d: invalid boolean value '%s'\n", + cfile->file, cfile->line, cfile->val ); + return 0; } -static char * -my_strndup (const char *s, size_t nchars) +int +parse_int( conffile_t *cfile ) { - char *r = malloc (sizeof (char) * (nchars + 1)); - if (r) - { - memcpy (r, s, nchars); - r[nchars] = 0; - } - return r; -} - -char * -expand_strdup (const char *s) -{ - char path[_POSIX_PATH_MAX]; - struct passwd *pw; - const char *p; - - if (*s == '~') - { - s++; - if (*s == '/') - { - /* current user */ - pw = getpwuid (getuid ()); - p = s + 1; + char *p; + int ret; + + ret = strtol( cfile->val, &p, 10 ); + if (*p) { + fprintf( stderr, "%s:%d: invalid integer value '%s'\n", + cfile->file, cfile->line, cfile->val ); + return 0; } - else - { - char *user; + return ret; +} - p = strchr (s, '/'); - if (p) - { - user = my_strndup (s, (int)(p - s)); +int +parse_size( conffile_t *cfile ) +{ + char *p; + int ret; + + ret = strtol (cfile->val, &p, 10); + if (*p == 'k' || *p == 'K') + ret *= 1024, p++; + else if (*p == 'm' || *p == 'M') + ret *= 1024 * 1024, p++; + if (*p == 'b' || *p == 'B') p++; - } - else - user = strdup (s); - pw = getpwnam (user); - free (user); + if (*p) { + fprintf (stderr, "%s:%d: invalid size '%s'\n", + cfile->file, cfile->line, cfile->val); + return 0; } - if (!pw) - return 0; - snprintf (path, sizeof (path), "%s/%s", pw->pw_dir, p ? p : ""); - s = path; - } - else if (*s != '/') - { - snprintf (path, sizeof (path), "%s/%s", - global.maildir ? global.maildir : "", s); - s = path; - } - return strdup (s); + return ret; } -static int -is_true (const char *val) +static int +getopt_helper( conffile_t *cfile, int *cops, int *mops, int *sops, char **sync_state ) { - return - !strcasecmp (val, "yes") || - !strcasecmp (val, "true") || - !strcasecmp (val, "on") || - !strcmp (val, "1"); + char *arg; + + if (!strcasecmp( "Sync", cfile->cmd )) { + arg = cfile->val; + do + if (!strcasecmp( "Push", arg )) + *cops |= XOP_PUSH; + else if (!strcasecmp( "Pull", arg )) + *cops |= XOP_PULL; + else if (!strcasecmp( "ReNew", arg )) + *cops |= OP_RENEW; + else if (!strcasecmp( "New", arg )) + *cops |= OP_NEW; + else if (!strcasecmp( "Delete", arg )) + *cops |= OP_DELETE; + else if (!strcasecmp( "Flags", arg )) + *cops |= OP_FLAGS; + else if (!strcasecmp( "PullReNew", arg )) + *sops |= OP_RENEW; + else if (!strcasecmp( "PullNew", arg )) + *sops |= OP_NEW; + else if (!strcasecmp( "PullDelete", arg )) + *sops |= OP_DELETE; + else if (!strcasecmp( "PullFlags", arg )) + *sops |= OP_FLAGS; + else if (!strcasecmp( "PushReNew", arg )) + *mops |= OP_RENEW; + else if (!strcasecmp( "PushNew", arg )) + *mops |= OP_NEW; + else if (!strcasecmp( "PushDelete", arg )) + *mops |= OP_DELETE; + else if (!strcasecmp( "PushFlags", arg )) + *mops |= OP_FLAGS; + else if (!strcasecmp( "All", arg ) || !strcasecmp( "Full", arg )) + *cops |= XOP_PULL|XOP_PUSH; + else if (strcasecmp( "None", arg ) && strcasecmp( "Noop", arg )) + fprintf( stderr, "%s:%d: invalid Sync arg '%s'\n", + cfile->file, cfile->line, arg ); + while ((arg = next_arg( &cfile->rest ))); + *mops |= XOP_HAVE_TYPE; + } else if (!strcasecmp( "Expunge", cfile->cmd )) { + arg = cfile->val; + do + if (!strcasecmp( "Both", arg )) + *cops |= OP_EXPUNGE; + else if (!strcasecmp( "Master", arg )) + *mops |= OP_EXPUNGE; + else if (!strcasecmp( "Slave", arg )) + *sops |= OP_EXPUNGE; + else if (strcasecmp( "None", arg )) + fprintf( stderr, "%s:%d: invalid Expunge arg '%s'\n", + cfile->file, cfile->line, arg ); + while ((arg = next_arg( &cfile->rest ))); + *mops |= XOP_HAVE_EXPUNGE; + } else if (!strcasecmp( "Create", cfile->cmd )) { + arg = cfile->val; + do + if (!strcasecmp( "Both", arg )) + *cops |= OP_CREATE; + else if (!strcasecmp( "Master", arg )) + *mops |= OP_CREATE; + else if (!strcasecmp( "Slave", arg )) + *sops |= OP_CREATE; + else if (strcasecmp( "None", arg )) + fprintf( stderr, "%s:%d: invalid Create arg '%s'\n", + cfile->file, cfile->line, arg ); + while ((arg = next_arg( &cfile->rest ))); + *mops |= XOP_HAVE_CREATE; + } else if (!strcasecmp( "SyncState", cfile->cmd )) + *sync_state = expand_strdup( cfile->val ); + else + return 0; + return 1; +} + +int +getcline( conffile_t *cfile ) +{ + char *p; + + while (fgets( cfile->buf, cfile->bufl, cfile->fp )) { + cfile->line++; + p = cfile->buf; + if (!(cfile->cmd = next_arg( &p ))) + return 1; + if (*cfile->cmd == '#') + continue; + if (!(cfile->val = next_arg( &p ))) { + fprintf( stderr, "%s:%d: parameter missing\n", + cfile->file, cfile->line ); + continue; + } + cfile->rest = p; + return 1; + } + return 0; +} + +/* XXX - this does not detect None conflicts ... */ +int +merge_ops( int cops, int *mops, int *sops ) +{ + int aops; + + aops = *mops | *sops; + if (*mops & XOP_HAVE_TYPE) { + if (aops & OP_MASK_TYPE) { + if (aops & cops & OP_MASK_TYPE) { + cfl: + fprintf( stderr, "Conflicting Sync args specified.\n" ); + return 1; + } + *mops |= cops & OP_MASK_TYPE; + *sops |= cops & OP_MASK_TYPE; + if (cops & XOP_PULL) { + if (*sops & OP_MASK_TYPE) + goto cfl; + *sops |= OP_MASK_TYPE; + } + if (cops & XOP_PUSH) { + if (*mops & OP_MASK_TYPE) + goto cfl; + *mops |= OP_MASK_TYPE; + } + } else if (cops & (OP_MASK_TYPE|XOP_MASK_DIR)) { + if (!(cops & OP_MASK_TYPE)) + cops |= OP_MASK_TYPE; + else if (!(cops & XOP_MASK_DIR)) + cops |= XOP_PULL|XOP_PUSH; + if (cops & XOP_PULL) + *sops |= cops & OP_MASK_TYPE; + if (cops & XOP_PUSH) + *mops |= cops & OP_MASK_TYPE; + } + } + if (*mops & XOP_HAVE_EXPUNGE) { + if (aops & cops & OP_EXPUNGE) { + fprintf( stderr, "Conflicting Expunge args specified.\n" ); + return 1; + } + *mops |= cops & OP_EXPUNGE; + *sops |= cops & OP_EXPUNGE; + } + if (*mops & XOP_HAVE_CREATE) { + if (aops & cops & OP_CREATE) { + fprintf( stderr, "Conflicting Create args specified.\n" ); + return 1; + } + *mops |= cops & OP_CREATE; + *sops |= cops & OP_CREATE; + } + return 0; +} + +int +load_config( const char *where, int pseudo ) +{ + conffile_t cfile; + store_conf_t *store, **storeapp = &stores, **sptarg; + channel_conf_t *channel, **channelapp = &channels; + group_conf_t *group, **groupapp = &groups; + string_list_t *chanlist, **chanlistapp; + char *arg, *p, **ntarg; + int err, len, cops, gcops, max_size; + char path[_POSIX_PATH_MAX]; + char buf[1024]; + + if (!where) { + nfsnprintf( path, sizeof(path), "%s/." EXE "rc", Home ); + cfile.file = path; + } else + cfile.file = where; + + if (!pseudo) + info( "Reading configuration file %s\n", cfile.file ); + + if (!(cfile.fp = fopen( cfile.file, "r" ))) { + perror( "Cannot open config file" ); + return 1; + } + buf[sizeof(buf) - 1] = 0; + cfile.buf = buf; + cfile.bufl = sizeof(buf) - 1; + cfile.line = 0; + + gcops = err = 0; + reloop: + while (getcline( &cfile )) { + if (!cfile.cmd) + continue; + if (imap_driver.parse_store( &cfile, &store, &err ) || + maildir_driver.parse_store( &cfile, &store, &err )) + { + if (store) { + if (!store->path) + store->path = ""; + *storeapp = store; + storeapp = &store->next; + *storeapp = 0; + } + } + else if (!strcasecmp( "Channel", cfile.cmd )) + { + channel = nfcalloc( sizeof(*channel) ); + channel->name = nfstrdup( cfile.val ); + cops = 0; + max_size = -1; + while (getcline( &cfile ) && cfile.cmd) { + if (!strcasecmp( "MaxSize", cfile.cmd )) + max_size = parse_size( &cfile ); + else if (!strcasecmp( "MaxMessages", cfile.cmd )) + channel->max_messages = parse_int( &cfile ); + else if (!strcasecmp( "Pattern", cfile.cmd ) || + !strcasecmp( "Patterns", cfile.cmd )) + { + arg = cfile.val; + do + add_string_list( &channel->patterns, arg ); + while ((arg = next_arg( &cfile.rest ))); + } + else if (!strcasecmp( "Master", cfile.cmd )) { + sptarg = &channel->master; + ntarg = &channel->master_name; + goto linkst; + } else if (!strcasecmp( "Slave", cfile.cmd )) { + sptarg = &channel->slave; + ntarg = &channel->slave_name; + linkst: + if (*cfile.val != ':' || !(p = strchr( cfile.val + 1, ':' ))) { + fprintf( stderr, "%s:%d: malformed mailbox spec\n", + cfile.file, cfile.line ); + err = 1; + continue; + } + *p = 0; + for (store = stores; store; store = store->next) + if (!strcmp( store->name, cfile.val + 1 )) { + memset( sptarg, 0, sizeof(*sptarg) ); + *sptarg = store; + goto stpcom; + } + fprintf( stderr, "%s:%d: unknown store '%s'\n", + cfile.file, cfile.line, cfile.val + 1 ); + err = 1; + continue; + stpcom: + if (*++p) + *ntarg = nfstrdup( p ); + } else if (!getopt_helper( &cfile, &cops, &channel->mops, &channel->sops, &channel->sync_state )) { + fprintf( stderr, "%s:%d: unknown keyword '%s'\n", + cfile.file, cfile.line, cfile.cmd ); + err = 1; + } + } + if (!channel->master) { + fprintf( stderr, "channel '%s' refers to no master store\n", channel->name ); + err = 1; + } else if (!channel->slave) { + fprintf( stderr, "channel '%s' refers to no slave store\n", channel->name ); + err = 1; + } else if (merge_ops( cops, &channel->mops, &channel->sops )) + err = 1; + else { + if (max_size >= 0) + channel->master->max_size = channel->slave->max_size = max_size; + *channelapp = channel; + channelapp = &channel->next; + } + } + else if (!strcasecmp( "Group", cfile.cmd )) + { + group = nfmalloc( sizeof(*group) ); + group->name = nfstrdup( cfile.val ); + *groupapp = group; + groupapp = &group->next; + *groupapp = 0; + chanlistapp = &group->channels; + *chanlistapp = 0; + p = cfile.rest; + while ((arg = next_arg( &p ))) { + addone: + len = strlen( arg ); + chanlist = nfmalloc( sizeof(*chanlist) + len ); + memcpy( chanlist->string, arg, len + 1 ); + *chanlistapp = chanlist; + chanlistapp = &chanlist->next; + *chanlistapp = 0; + } + while (getcline( &cfile )) { + if (!cfile.cmd) + goto reloop; + if (!strcasecmp( "Channel", cfile.cmd ) || + !strcasecmp( "Channels", cfile.cmd )) + { + p = cfile.rest; + arg = cfile.val; + goto addone; + } + else + { + fprintf( stderr, "%s:%d: unknown keyword '%s'\n", + cfile.file, cfile.line, cfile.cmd ); + err = 1; + } + } + break; + } + else if (!getopt_helper( &cfile, &gcops, &global_mops, &global_sops, &global_sync_state )) + { + fprintf( stderr, "%s:%d: unknown section keyword '%s'\n", + cfile.file, cfile.line, cfile.cmd ); + err = 1; + while (getcline( &cfile )) + if (!cfile.cmd) + goto reloop; + break; + } + } + fclose (cfile.fp); + err |= merge_ops( gcops, &global_mops, &global_sops ); + if (!global_sync_state) + global_sync_state = expand_strdup( "~/." EXE "/" ); + if (!err && pseudo) + unlink( where ); + return err; } void -load_config (const char *where, int *o2o) +parse_generic_store( store_conf_t *store, conffile_t *cfg, int *err ) { - char path[_POSIX_PATH_MAX]; - char buf[1024]; - struct passwd *pw; - config_t **stor = &boxes, *cfg; - int line = 0; - FILE *fp; - char *p, *cmd, *val; - - if (!where) - { - pw = getpwuid (getuid ()); - snprintf (path, sizeof (path), "%s/.isyncrc", pw->pw_dir); - where = path; - } - - info ("Reading configuration file %s\n", where); - - fp = fopen (where, "r"); - if (!fp) - { - if (errno != ENOENT) - perror ("fopen"); - return; - } - buf[sizeof buf - 1] = 0; - cfg = &global; - while ((fgets (buf, sizeof (buf) - 1, fp))) - { - p = buf; - cmd = next_arg (&p); - val = next_arg (&p); - line++; - if (!cmd || *cmd == '#') - continue; - if (!val) { - fprintf (stderr, "%s:%d: parameter missing\n", path, line); - continue; + if (!strcasecmp( "Trash", cfg->cmd )) + store->trash = nfstrdup( cfg->val ); + else if (!strcasecmp( "TrashRemoteNew", cfg->cmd )) + store->trash_remote_new = parse_bool( cfg ); + else if (!strcasecmp( "TrashNewOnly", cfg->cmd )) + store->trash_only_new = parse_bool( cfg ); + else if (!strcasecmp( "MaxSize", cfg->cmd )) + store->max_size = parse_size( cfg ); + else if (!strcasecmp( "MapInbox", cfg->cmd )) + store->map_inbox = nfstrdup( cfg->val ); + else { + fprintf( stderr, "%s:%d: unknown keyword '%s'\n", + cfg->file, cfg->line, cfg->cmd ); + *err = 1; } - if (!strcasecmp ("mailbox", cmd)) - { - if (*o2o) - break; - cfg = *stor = malloc (sizeof (config_t)); - stor = &cfg->next; - config_defaults (cfg); - /* not expanded at this point */ - cfg->path = strdup (val); - } - else if (!strcasecmp ("OneToOne", cmd)) - { - if (boxes) { - forbid: - fprintf (stderr, - "%s:%d: keyword '%s' allowed only in global section\n", - path, line, cmd); - continue; - } - *o2o = is_true (val); - } - else if (!strcasecmp ("maildir", cmd)) - { - if (boxes) - goto forbid; - /* this only affects the global setting */ - global.maildir = expand_strdup (val); - } - else if (!strcasecmp ("folder", cmd)) - { - if (boxes) - goto forbid; - /* this only affects the global setting */ - global.folder = strdup (val); - } - else if (!strcasecmp ("inbox", cmd)) - { - if (boxes) - goto forbid; - /* this only affects the global setting */ - global.inbox = strdup (val); - } - else if (!strcasecmp ("host", cmd)) - { -#if HAVE_LIBSSL - if (!strncasecmp ("imaps:", val, 6)) - { - val += 6; - cfg->use_imaps = 1; - cfg->port = 993; - cfg->use_sslv2 = 1; - cfg->use_sslv3 = 1; - } -#endif - cfg->host = strdup (val); - } - else if (!strcasecmp ("user", cmd)) - cfg->user = strdup (val); - else if (!strcasecmp ("pass", cmd)) - cfg->pass = strdup (val); - else if (!strcasecmp ("port", cmd)) - cfg->port = atoi (val); - else if (!strcasecmp ("box", cmd)) - cfg->box = strdup (val); - else if (!strcasecmp ("alias", cmd)) - { - if (!boxes) { - fprintf (stderr, - "%s:%d: keyword 'alias' allowed only in mailbox specification\n", - path, line); - continue; - } - cfg->alias = strdup (val); - } - else if (!strcasecmp ("maxsize", cmd)) - cfg->max_size = atol (val); - else if (!strcasecmp ("MaxMessages", cmd)) - cfg->max_messages = atol (val); - else if (!strcasecmp ("UseNamespace", cmd)) - cfg->use_namespace = is_true (val); - else if (!strcasecmp ("CopyDeletedTo", cmd)) - cfg->copy_deleted_to = strdup (val); - else if (!strcasecmp ("Tunnel", cmd)) - cfg->tunnel = strdup (val); - else if (!strcasecmp ("Expunge", cmd)) - cfg->expunge = is_true (val); - else if (!strcasecmp ("Delete", cmd)) - cfg->delete = is_true (val); -#if HAVE_LIBSSL - else if (!strcasecmp ("CertificateFile", cmd)) - cfg->cert_file = expand_strdup (val); - else if (!strcasecmp ("RequireSSL", cmd)) - cfg->require_ssl = is_true (val); - else if (!strcasecmp ("UseSSLv2", cmd)) - cfg->use_sslv2 = is_true (val); - else if (!strcasecmp ("UseSSLv3", cmd)) - cfg->use_sslv3 = is_true (val); - else if (!strcasecmp ("UseTLSv1", cmd)) - cfg->use_tlsv1 = is_true (val); - else if (!strcasecmp ("RequireCRAM", cmd)) - cfg->require_cram = is_true (val); -#endif - else if (buf[0]) - fprintf (stderr, "%s:%d: unknown keyword '%s'\n", path, line, cmd); - } - fclose (fp); -} - -config_t * -find_box (const char *s) -{ - config_t *p = boxes; - - for (; p; p = p->next) - { - if (!strcmp (s, p->path) || (p->alias && !strcmp (s, p->alias))) - return p; - else - { - /* check to see if the full pathname was specified on the - * command line. - */ - char *t = expand_strdup (p->path); - - if (!strcmp (s, t)) - { - free (t); - return p; - } - free (t); - } - } - return 0; } diff --git a/src/cram.c b/src/cram.c deleted file mode 100644 index 663899a..0000000 --- a/src/cram.c +++ /dev/null @@ -1,89 +0,0 @@ -/* $Id$ - * - * isync - IMAP4 to maildir mailbox synchronizer - * Copyright (C) 2000-2002 Michael R. Elkins - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * As a special exception, isync may be linked with the OpenSSL library, - * despite that library's more restrictive license. - */ - -#include - -#if HAVE_LIBSSL - -#include "isync.h" - -#include -#include -#include - -#define ENCODED_SIZE(n) (4*((n+2)/3)) - -static char -hexchar (unsigned int b) -{ - if (b < 10) - return '0' + b; - return 'a' + (b - 10); -} - -char * -cram (const char *challenge, const char *user, const char *pass) -{ - HMAC_CTX hmac; - char hash[16]; - char hex[33]; - int i; - unsigned int hashlen = sizeof (hash); - char buf[256]; - int len = strlen (challenge); - char *response = calloc (1, 1 + len); - char *final; - - /* response will always be smaller than challenge because we are - * decoding. - */ - len = EVP_DecodeBlock ((unsigned char *) response, (unsigned char *) challenge, strlen (challenge)); - - HMAC_Init (&hmac, (unsigned char *) pass, strlen (pass), EVP_md5 ()); - HMAC_Update (&hmac, (unsigned char *) response, strlen(response)); - HMAC_Final (&hmac, (unsigned char *) hash, &hashlen); - - assert (hashlen == sizeof (hash)); - - free (response); - - hex[32] = 0; - for (i = 0; i < 16; i++) - { - hex[2 * i] = hexchar ((hash[i] >> 4) & 0xf); - hex[2 * i + 1] = hexchar (hash[i] & 0xf); - } - - snprintf (buf, sizeof (buf), "%s %s", user, hex); - - len = strlen (buf); - len = ENCODED_SIZE (len) + 1; - final = malloc (len); - final[len - 1] = 0; - - assert (EVP_EncodeBlock ((unsigned char *) final, (unsigned char *) buf, strlen (buf)) == len - 1); - - return final; -} - -#endif diff --git a/src/dotlock.c b/src/dotlock.c deleted file mode 100644 index b2a50b9..0000000 --- a/src/dotlock.c +++ /dev/null @@ -1,102 +0,0 @@ -/* $Id$ - * - * isync - IMAP4 to maildir mailbox synchronizer - * Copyright (C) 2002 Michael R. Elkins - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * As a special exception, isync may be linked with the OpenSSL library, - * despite that library's more restrictive license. - */ - -/* - * this file contains routines to establish a mutex using a `dotlock' file - */ - -#include "dotlock.h" - -#include -#include -#include -#include -#if TESTING -#include -#endif - -int dotlock_lock (const char *path, int *fd) -{ - struct flock lck; - - *fd = open (path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); - if (*fd == -1) - return -1; - memset (&lck, 0, sizeof(lck)); -#if SEEK_SET != 0 - lck.l_whence = SEEK_SET; -#endif -#if F_WRLCK != 0 - lck.l_type = F_WRLCK; -#endif - if (fcntl (*fd, F_SETLK, &lck)) - { - close (*fd); - *fd = -1; - return -1; - } - return 0; -} - -int dotlock_unlock (int *fd) -{ - int r = 0; - struct flock lck; - - if (*fd != -1) - { - memset (&lck, 0, sizeof(lck)); -#if SEEK_SET != 0 - lck.l_whence = SEEK_SET; -#endif -#if F_UNLCK != 0 - lck.l_type = F_UNLCK; -#endif - if (fcntl (*fd, F_SETLK, &lck)) - r = -1; - close (*fd); - *fd = -1; - } - return r; -} - -#if TESTING -int main (void) -{ - int fd; - - if (dotlock_lock ("./lock", &fd)) - { - perror ("dotlock_lock"); - goto done; - } - puts ("sleeping for 5 seconds"); - sleep(5); - if (dotlock_unlock (&fd)) - { - perror ("dotlock_unlock"); - } -done: - exit (0); -} -#endif diff --git a/src/dotlock.h b/src/dotlock.h deleted file mode 100644 index ba953b8..0000000 --- a/src/dotlock.h +++ /dev/null @@ -1,25 +0,0 @@ -/* $Id$ - * - * isync - IMAP4 to maildir mailbox synchronizer - * Copyright (C) 2002 Michael R. Elkins - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * As a special exception, isync may be linked with the OpenSSL library, - * despite that library's more restrictive license. - */ - -int dotlock_lock (const char *, int *); -int dotlock_unlock (int *); diff --git a/src/drv_imap.c b/src/drv_imap.c new file mode 100644 index 0000000..613b1f4 --- /dev/null +++ b/src/drv_imap.c @@ -0,0 +1,1817 @@ +/* + * mbsync - mailbox synchronizer + * Copyright (C) 2000-2002 Michael R. Elkins + * Copyright (C) 2002-2004 Oswald Buddenhagen + * Copyright (C) 2004 Theodore Y. Ts'o + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * As a special exception, mbsync may be linked with the OpenSSL library, + * despite that library's more restrictive license. + */ + +#include "isync.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if HAVE_LIBSSL +# include +# include +# include +#endif + +typedef struct imap_server_conf { + struct imap_server_conf *next; + char *name; + char *tunnel; + char *host; + int port; + char *user; + char *pass; +#if HAVE_LIBSSL + char *cert_file; + unsigned use_imaps:1; + unsigned require_ssl:1; + unsigned use_sslv2:1; + unsigned use_sslv3:1; + unsigned use_tlsv1:1; + unsigned require_cram:1; +#endif +} imap_server_conf_t; + +typedef struct imap_store_conf { + store_conf_t gen; + imap_server_conf_t *server; + unsigned use_namespace:1; +} imap_store_conf_t; + +typedef struct imap_message { + message_t gen; +/* int seq; will be needed when expunges are tracked */ +} imap_message_t; + +#define NIL (void*)0x1 +#define LIST (void*)0x2 + +typedef struct _list { + struct _list *next, *child; + char *val; + int len; +} list_t; + +typedef struct { + int fd; +#if HAVE_LIBSSL + SSL *ssl; + unsigned int use_ssl:1; +#endif +} Socket_t; + +typedef struct { + Socket_t sock; + int bytes; + int offset; + char buf[1024]; +} buffer_t; + +struct imap_cmd; +#define max_in_progress 50 /* make this configurable? */ + +typedef struct imap { + int uidnext; /* from SELECT responses */ + list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */ + string_list_t *boxes; /* LIST results */ + message_t **msgapp; /* FETCH results */ + unsigned caps, rcaps; /* CAPABILITY results */ + /* command queue */ + int nexttag, num_in_progress, literal_pending; + struct imap_cmd *in_progress, **in_progress_append; +#if HAVE_LIBSSL + SSL_CTX *SSLContext; +#endif + buffer_t buf; /* this is BIG, so put it last */ +} imap_t; + +typedef struct imap_store { + store_t gen; + imap_t *imap; + const char *prefix; + unsigned /*currentnc:1,*/ trashnc:1; +} imap_store_t; + +struct imap_cmd_cb { + int (*cont)( imap_store_t *ctx, struct imap_cmd *cmd, const char *prompt ); + void (*done)( imap_store_t *ctx, struct imap_cmd *cmd, int response); + void *ctx; + char *data; + int dlen; + int uid; + unsigned create:1; +}; + +struct imap_cmd { + struct imap_cmd *next; + struct imap_cmd_cb cb; + char *cmd; + int tag; +}; + +#define CAP(cap) (imap->caps & (1 << (cap))) + +enum CAPABILITY { + NOLOGIN = 0, + UIDPLUS, + LITERALPLUS, + NAMESPACE, +#if HAVE_LIBSSL + CRAM, + STARTTLS, +#endif +}; + +static const char *cap_list[] = { + "LOGINDISABLED", + "UIDPLUS", + "LITERAL+", + "NAMESPACE", +#if HAVE_LIBSSL + "AUTH=CRAM-MD5", + "STARTTLS", +#endif +}; + +#define RESP_OK 0 +#define RESP_NO 1 +#define RESP_BAD 2 + +static int get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ); + + +static const char *Flags[] = { + "\\Draft", + "\\Flagged", + "\\Answered", + "\\Seen", + "\\Deleted", +}; + +#if HAVE_LIBSSL + +/* this gets called when a certificate is to be verified */ +static int +verify_cert( SSL *ssl ) +{ + X509 *cert; + int err; + char buf[256]; + int ret = -1; + BIO *bio; + + cert = SSL_get_peer_certificate( ssl ); + if (!cert) { + fprintf( stderr, "Error, no server certificate\n" ); + return -1; + } + + err = SSL_get_verify_result( ssl ); + if (err == X509_V_OK) + return 0; + + fprintf( stderr, "Error, can't verify certificate: %s (%d)\n", + X509_verify_cert_error_string(err), err ); + + X509_NAME_oneline( X509_get_subject_name( cert ), buf, sizeof(buf) ); + info( "\nSubject: %s\n", buf ); + X509_NAME_oneline( X509_get_issuer_name( cert ), buf, sizeof(buf) ); + info( "Issuer: %s\n", buf ); + bio = BIO_new( BIO_s_mem() ); + ASN1_TIME_print( bio, X509_get_notBefore( cert ) ); + memset( buf, 0, sizeof(buf) ); + BIO_read( bio, buf, sizeof(buf) - 1 ); + info( "Valid from: %s\n", buf ); + ASN1_TIME_print( bio, X509_get_notAfter( cert ) ); + memset( buf, 0, sizeof(buf) ); + BIO_read( bio, buf, sizeof(buf) - 1 ); + BIO_free( bio ); + info( " to: %s\n", buf ); + + fputs( "\n*** WARNING *** There is no way to verify this certificate. It is\n" + " possible that a hostile attacker has replaced the\n" + " server certificate. Continue at your own risk!\n" + "\nAccept this certificate anyway? [no]: ", stderr ); + if (fgets( buf, sizeof(buf), stdin ) && (buf[0] == 'y' || buf[0] == 'Y')) { + ret = 0; + fprintf( stderr, "\n*** Fine, but don't say I didn't warn you!\n\n" ); + } + return ret; +} + +static int +init_ssl_ctx( imap_store_t *ctx ) +{ + imap_t *imap = ctx->imap; + imap_store_conf_t *conf = (imap_store_conf_t *)ctx->gen.conf; + imap_server_conf_t *srvc = conf->server; + SSL_METHOD *method; + int options = 0; + + if (srvc->use_tlsv1 && !srvc->use_sslv2 && !srvc->use_sslv3) + method = TLSv1_client_method(); + else + method = SSLv23_client_method(); + imap->SSLContext = SSL_CTX_new( method ); + + if (!srvc->cert_file) { + fprintf( stderr, "Error, CertificateFile not defined\n" ); + return -1; + } else if (access( srvc->cert_file, R_OK )) + warn( "*** Warning: can't read CertificateFile, so can't verify server certificates\n" ); + else if (!SSL_CTX_load_verify_locations( imap->SSLContext, srvc->cert_file, NULL )) { + fprintf( stderr, "Error, SSL_CTX_load_verify_locations: %s\n", + ERR_error_string( ERR_get_error(), 0 ) ); + return -1; + } + + if (!srvc->use_sslv2) + options |= SSL_OP_NO_SSLv2; + if (!srvc->use_sslv3) + options |= SSL_OP_NO_SSLv3; + if (!srvc->use_tlsv1) + options |= SSL_OP_NO_TLSv1; + + SSL_CTX_set_options( imap->SSLContext, options ); + + /* we check the result of the verification after SSL_connect() */ + SSL_CTX_set_verify( imap->SSLContext, SSL_VERIFY_NONE, 0 ); + return 0; +} +#endif /* HAVE_LIBSSL */ + +static void +socket_perror( const char *func, Socket_t *sock, int ret ) +{ +#if HAVE_LIBSSL + int err; + + if (sock->use_ssl) { + switch ((err = SSL_get_error( sock->ssl, ret ))) { + case SSL_ERROR_SYSCALL: + case SSL_ERROR_SSL: + if ((err = ERR_get_error()) == 0) { + if (ret == 0) + fprintf( stderr, "SSL_%s:got EOF\n", func ); + else + fprintf( stderr, "SSL_%s:%d:%s\n", func, errno, strerror(errno) ); + } else + fprintf( stderr, "SSL_%s:%d:%s\n", func, err, ERR_error_string( err, 0 ) ); + return; + default: + fprintf( stderr, "SSL_%s:%d:unhandled SSL error\n", func, err ); + break; + } + return; + } +#else + (void)sock; +#endif + if (ret < 0) + perror( func ); + else + fprintf( stderr, "%s: unexpected EOF\n", func ); +} + +static int +socket_read( Socket_t *sock, char *buf, int len ) +{ + int n = +#if HAVE_LIBSSL + sock->use_ssl ? SSL_read( sock->ssl, buf, len ) : +#endif + read( sock->fd, buf, len ); + if (n <= 0) { + socket_perror( "read", sock, n ); + close( sock->fd ); + sock->fd = -1; + } + return n; +} + +static int +socket_write( Socket_t *sock, char *buf, int len ) +{ + int n = +#if HAVE_LIBSSL + sock->use_ssl ? SSL_write( sock->ssl, buf, len ) : +#endif + write( sock->fd, buf, len ); + if (n != len) { + socket_perror( "write", sock, n ); + close( sock->fd ); + sock->fd = -1; + } + return n; +} + +static int +socket_pending( Socket_t *sock ) +{ + int num = -1; + + if (ioctl( sock->fd, FIONREAD, &num ) < 0) + return -1; + if (num > 0) + return num; +#if HAVE_LIBSSL + if (sock->use_ssl) + return SSL_pending( sock->ssl ); +#endif + return 0; +} + +/* simple line buffering */ +static int +buffer_gets( buffer_t * b, char **s ) +{ + int n; + int start = b->offset; + + *s = b->buf + start; + + for (;;) { + /* make sure we have enough data to read the \r\n sequence */ + if (b->offset + 1 >= b->bytes) { + if (start) { + /* shift down used bytes */ + *s = b->buf; + + assert( start <= b->bytes ); + n = b->bytes - start; + + if (n) + memcpy( b->buf, b->buf + start, n ); + b->offset -= start; + b->bytes = n; + start = 0; + } + + n = socket_read( &b->sock, b->buf + b->bytes, + sizeof(b->buf) - b->bytes ); + + if (n <= 0) + return -1; + + b->bytes += n; + } + + if (b->buf[b->offset] == '\r') { + assert( b->offset + 1 < b->bytes ); + if (b->buf[b->offset + 1] == '\n') { + b->buf[b->offset] = 0; /* terminate the string */ + b->offset += 2; /* next line */ + if (Verbose) + puts( *s ); + return 0; + } + } + + b->offset++; + } + /* not reached */ +} + +static struct imap_cmd * +v_issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb, + const char *fmt, va_list ap ) +{ + imap_t *imap = ctx->imap; + struct imap_cmd *cmd; + int n, bufl; + char buf[1024]; + + cmd = nfmalloc( sizeof(struct imap_cmd) ); + nfvasprintf( &cmd->cmd, fmt, ap ); + cmd->tag = ++imap->nexttag; + + if (cb) + cmd->cb = *cb; + else + memset( &cmd->cb, 0, sizeof(cmd->cb) ); + + while (imap->literal_pending) + get_cmd_result( ctx, 0 ); + + bufl = nfsnprintf( buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ? + "%d %s{%d+}\r\n" : "%d %s{%d}\r\n" : "%d %s\r\n", + cmd->tag, cmd->cmd, cmd->cb.dlen ); + if (Verbose) { + if (imap->num_in_progress) + printf( "(%d in progress) ", imap->num_in_progress ); + if (memcmp( cmd->cmd, "LOGIN", 5 )) + printf( ">>> %s", buf ); + else + printf( ">>> %d LOGIN \n", cmd->tag ); + } + if (socket_write( &imap->buf.sock, buf, bufl ) != bufl) { + free( cmd->cmd ); + free( cmd ); + if (cb && cb->data) + free( cb->data ); + return NULL; + } + if (cmd->cb.data) { + if (CAP(LITERALPLUS)) { + n = socket_write( &imap->buf.sock, cmd->cb.data, cmd->cb.dlen ); + free( cmd->cb.data ); + if (n != cmd->cb.dlen || + (n = socket_write( &imap->buf.sock, "\r\n", 2 )) != 2) + { + free( cmd->cmd ); + free( cmd ); + return NULL; + } + cmd->cb.data = 0; + } else + imap->literal_pending = 1; + } else if (cmd->cb.cont) + imap->literal_pending = 1; + cmd->next = 0; + *imap->in_progress_append = cmd; + imap->in_progress_append = &cmd->next; + imap->num_in_progress++; + return cmd; +} + +static struct imap_cmd * +issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... ) +{ + struct imap_cmd *ret; + va_list ap; + + va_start( ap, fmt ); + ret = v_issue_imap_cmd( ctx, cb, fmt, ap ); + va_end( ap ); + return ret; +} + +static struct imap_cmd * +issue_imap_cmd_w( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... ) +{ + imap_t *imap = ctx->imap; + struct imap_cmd *ret; + va_list ap; + + va_start( ap, fmt ); + ret = v_issue_imap_cmd( ctx, cb, fmt, ap ); + va_end( ap ); + while (imap->num_in_progress > max_in_progress || + socket_pending( &imap->buf.sock )) + get_cmd_result( ctx, 0 ); + return ret; +} + +static int +imap_exec( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... ) +{ + va_list ap; + struct imap_cmd *cmdp; + + va_start( ap, fmt ); + cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap ); + va_end( ap ); + if (!cmdp) + return RESP_BAD; + + return get_cmd_result( ctx, cmdp ); +} + +static int +imap_exec_b( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... ) +{ + va_list ap; + struct imap_cmd *cmdp; + + va_start( ap, fmt ); + cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap ); + va_end( ap ); + if (!cmdp) + return DRV_STORE_BAD; + + switch (get_cmd_result( ctx, cmdp )) { + case RESP_BAD: return DRV_STORE_BAD; + case RESP_NO: return DRV_BOX_BAD; + default: return DRV_OK; + } +} + +static int +imap_exec_m( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... ) +{ + va_list ap; + struct imap_cmd *cmdp; + + va_start( ap, fmt ); + cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap ); + va_end( ap ); + if (!cmdp) + return DRV_STORE_BAD; + + switch (get_cmd_result( ctx, cmdp )) { + case RESP_BAD: return DRV_STORE_BAD; + case RESP_NO: return DRV_MSG_BAD; + default: return DRV_OK; + } +} + +/* +static void +drain_imap_replies( imap_t *imap ) +{ + while (imap->num_in_progress) + get_cmd_result( imap, 0 ); +} +*/ + +static int +is_atom( list_t *list ) +{ + return list && list->val && list->val != NIL && list->val != LIST; +} + +static int +is_list( list_t *list ) +{ + return list && list->val == LIST; +} + +static void +free_list( list_t *list ) +{ + list_t *tmp; + + for (; list; list = tmp) { + tmp = list->next; + if (is_list( list )) + free_list( list->child ); + else if (is_atom( list )) + free( list->val ); + free( list ); + } +} + +static int +parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level ) +{ + list_t *cur; + char *s = *sp, *p; + int n, bytes; + + for (;;) { + while (isspace( (unsigned char)*s )) + s++; + if (level && *s == ')') { + s++; + break; + } + *curp = cur = nfmalloc( sizeof(*cur) ); + curp = &cur->next; + cur->val = 0; /* for clean bail */ + if (*s == '(') { + /* sublist */ + s++; + cur->val = LIST; + if (parse_imap_list_l( imap, &s, &cur->child, level + 1 )) + goto bail; + } else if (imap && *s == '{') { + /* literal */ + bytes = cur->len = strtol( s + 1, &s, 10 ); + if (*s != '}') + goto bail; + + s = cur->val = nfmalloc( cur->len ); + + /* dump whats left over in the input buffer */ + n = imap->buf.bytes - imap->buf.offset; + + if (n > bytes) + /* the entire message fit in the buffer */ + n = bytes; + + memcpy( s, imap->buf.buf + imap->buf.offset, n ); + s += n; + bytes -= n; + + /* mark that we used part of the buffer */ + imap->buf.offset += n; + + /* now read the rest of the message */ + while (bytes > 0) { + if ((n = socket_read (&imap->buf.sock, s, bytes)) <= 0) + goto bail; + s += n; + bytes -= n; + } + + if (buffer_gets( &imap->buf, &s )) + goto bail; + } else if (*s == '"') { + /* quoted string */ + s++; + p = s; + for (; *s != '"'; s++) + if (!*s) + goto bail; + cur->len = s - p; + s++; + cur->val = nfmalloc( cur->len + 1 ); + memcpy( cur->val, p, cur->len ); + cur->val[cur->len] = 0; + } else { + /* atom */ + p = s; + for (; *s && !isspace( (unsigned char)*s ); s++) + if (level && *s == ')') + break; + cur->len = s - p; + if (cur->len == 3 && !memcmp ("NIL", p, 3)) + cur->val = NIL; + else { + cur->val = nfmalloc( cur->len + 1 ); + memcpy( cur->val, p, cur->len ); + cur->val[cur->len] = 0; + } + } + + if (!level) + break; + if (!*s) + goto bail; + } + *sp = s; + *curp = 0; + return 0; + + bail: + *curp = 0; + return -1; +} + +static list_t * +parse_imap_list( imap_t *imap, char **sp ) +{ + list_t *head; + + if (!parse_imap_list_l( imap, sp, &head, 0 )) + return head; + free_list( head ); + return NULL; +} + +static list_t * +parse_list( char **sp ) +{ + return parse_imap_list( 0, sp ); +} + +static int +parse_fetch( imap_t *imap, char *cmd ) /* move this down */ +{ + list_t *tmp, *list, *flags; + char *body = 0; + imap_message_t *cur; + msg_data_t *msgdata; + struct imap_cmd *cmdp; + int uid = 0, mask = 0, status = 0, size = 0; + unsigned i; + + list = parse_imap_list( imap, &cmd ); + + if (!is_list( list )) { + fprintf( stderr, "IMAP error: bogus FETCH response\n" ); + free_list( list ); + return -1; + } + + for (tmp = list->child; tmp; tmp = tmp->next) { + if (is_atom( tmp )) { + if (!strcmp( "UID", tmp->val )) { + tmp = tmp->next; + if (is_atom( tmp )) + uid = atoi( tmp->val ); + else + fprintf( stderr, "IMAP error: unable to parse UID\n" ); + } else if (!strcmp( "FLAGS", tmp->val )) { + tmp = tmp->next; + if (is_list( tmp )) { + for (flags = tmp->child; flags; flags = flags->next) { + if (is_atom( flags )) { + if (!strcmp( "\\Recent", flags->val )) { + status |= M_RECENT; + goto flagok; + } + for (i = 0; i < as(Flags); i++) + if (!strcmp( Flags[i], flags->val )) { + mask |= 1 << i; + goto flagok; + } + fprintf( stderr, "IMAP warning: unknown flag %s\n", flags->val ); + flagok: ; + } else + fprintf( stderr, "IMAP error: unable to parse FLAGS list\n" ); + } + status |= M_FLAGS; + } else + fprintf( stderr, "IMAP error: unable to parse FLAGS\n" ); + } else if (!strcmp( "RFC822.SIZE", tmp->val )) { + tmp = tmp->next; + if (is_atom( tmp )) + size = atoi( tmp->val ); + else + fprintf( stderr, "IMAP error: unable to parse SIZE\n" ); + } else if (!strcmp( "BODY[]", tmp->val )) { + tmp = tmp->next; + if (is_atom( tmp )) { + body = tmp->val; + tmp->val = 0; /* don't free together with list */ + size = tmp->len; + } else + fprintf( stderr, "IMAP error: unable to parse BODY[]\n" ); + } + } + } + + if (body) { + for (cmdp = imap->in_progress; cmdp; cmdp = cmdp->next) + if (cmdp->cb.uid == uid) + goto gotuid; + fprintf( stderr, "IMAP error: unexpected FETCH response (UID %d)\n", uid ); + free_list( list ); + return -1; + gotuid: + msgdata = (msg_data_t *)cmdp->cb.ctx; + msgdata->data = body; + msgdata->len = size; + msgdata->crlf = 1; + if (status & M_FLAGS) + msgdata->flags = mask; + } else if (uid) { /* ignore async flag updates for now */ + /* XXX this will need sorting for out-of-order (multiple queries) */ + cur = nfcalloc( sizeof(*cur) ); + *imap->msgapp = &cur->gen; + imap->msgapp = &cur->gen.next; + cur->gen.next = 0; + cur->gen.uid = uid; + cur->gen.flags = mask; + cur->gen.status = status; + cur->gen.size = size; + } + + free_list( list ); + return 0; +} + +static void +parse_capability( imap_t *imap, char *cmd ) +{ + char *arg; + unsigned i; + + imap->caps = 0x80000000; + while ((arg = next_arg( &cmd ))) + for (i = 0; i < as(cap_list); i++) + if (!strcmp( cap_list[i], arg )) + imap->caps |= 1 << i; + imap->rcaps = imap->caps; +} + +static int +parse_response_code( imap_store_t *ctx, struct imap_cmd_cb *cb, char *s ) +{ + imap_t *imap = ctx->imap; + char *arg, *p; + + if (*s != '[') + return RESP_OK; /* no response code */ + s++; + if (!(p = strchr( s, ']' ))) { + fprintf( stderr, "IMAP error: malformed response code\n" ); + return RESP_BAD; + } + *p++ = 0; + arg = next_arg( &s ); + if (!strcmp( "UIDVALIDITY", arg )) { + if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg ))) { + fprintf( stderr, "IMAP error: malformed UIDVALIDITY status\n" ); + return RESP_BAD; + } + } else if (!strcmp( "UIDNEXT", arg )) { + if (!(arg = next_arg( &s )) || !(imap->uidnext = atoi( arg ))) { + fprintf( stderr, "IMAP error: malformed NEXTUID status\n" ); + return RESP_BAD; + } + } else if (!strcmp( "CAPABILITY", arg )) { + parse_capability( imap, s ); + } else if (!strcmp( "ALERT", arg )) { + /* RFC2060 says that these messages MUST be displayed + * to the user + */ + for (; isspace( (unsigned char)*p ); p++); + fprintf( stderr, "*** IMAP ALERT *** %s\n", p ); + } else if (cb && cb->ctx && !strcmp( "APPENDUID", arg )) { + if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg )) || + !(arg = next_arg( &s )) || !(*(int *)cb->ctx = atoi( arg ))) + { + fprintf( stderr, "IMAP error: malformed APPENDUID status\n" ); + return RESP_BAD; + } + } + return RESP_OK; +} + +static void +parse_search( imap_t *imap, char *cmd ) +{ + char *arg; + struct imap_cmd *cmdp; + int uid; + + arg = next_arg( &cmd ); + if (!arg || !(uid = atoi( arg ))) { + fprintf( stderr, "IMAP error: malformed SEARCH response\n" ); + return; + } + + /* Find the first command that expects a UID - this is guaranteed + * to come in-order, as there are no other means to identify which + * SEARCH response belongs to which request. + */ + for (cmdp = imap->in_progress; cmdp; cmdp = cmdp->next) + if (cmdp->cb.uid == -1) { + *(int *)cmdp->cb.ctx = uid; + return; + } + fprintf( stderr, "IMAP error: unexpected SEARCH response (UID %u)\n", uid ); +} + +static void +parse_list_rsp( imap_store_t *ctx, char *cmd ) +{ + imap_t *imap = ctx->imap; + char *arg; + list_t *list, *lp; + int l; + + list = parse_list( &cmd ); + if (list->val == LIST) + for (lp = list->child; lp; lp = lp->next) + if (is_atom( lp ) && !strcasecmp( lp->val, "\\NoSelect" )) { + free_list( list ); + return; + } + free_list( list ); + (void) next_arg( &cmd ); /* skip delimiter */ + arg = next_arg( &cmd ); + l = strlen( ctx->gen.conf->path ); + if (memcmp( arg, ctx->gen.conf->path, l )) + return; + arg += l; + if (!memcmp( arg + strlen( arg ) - 5, ".lock", 5 )) /* workaround broken servers */ + return; + add_string_list( &imap->boxes, arg ); +} + +static int +get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) +{ + imap_t *imap = ctx->imap; + struct imap_cmd *cmdp, **pcmdp, *ncmdp; + char *cmd, *arg, *arg1, *p; + int n, resp, resp2, tag; + + for (;;) { + if (buffer_gets( &imap->buf, &cmd )) + return RESP_BAD; + + arg = next_arg( &cmd ); + if (*arg == '*') { + arg = next_arg( &cmd ); + if (!arg) { + fprintf( stderr, "IMAP error: unable to parse untagged response\n" ); + return RESP_BAD; + } + + if (!strcmp( "NAMESPACE", arg )) { + imap->ns_personal = parse_list( &cmd ); + imap->ns_other = parse_list( &cmd ); + imap->ns_shared = parse_list( &cmd ); + } else if (!strcmp( "OK", arg ) || !strcmp( "BAD", arg ) || + !strcmp( "NO", arg ) || !strcmp( "BYE", arg )) { + if ((resp = parse_response_code( ctx, 0, cmd )) != RESP_OK) + return resp; + } else if (!strcmp( "CAPABILITY", arg )) + parse_capability( imap, cmd ); + else if (!strcmp( "LIST", arg )) + parse_list_rsp( ctx, cmd ); + else if (!strcmp( "SEARCH", arg )) + parse_search( imap, cmd ); + else if ((arg1 = next_arg( &cmd ))) { + if (!strcmp( "EXISTS", arg1 )) + ctx->gen.count = atoi( arg ); + else if (!strcmp( "RECENT", arg1 )) + ctx->gen.recent = atoi( arg ); + else if(!strcmp ( "FETCH", arg1 )) { + if (parse_fetch( imap, cmd )) + return RESP_BAD; + } + } else { + fprintf( stderr, "IMAP error: unable to parse untagged response\n" ); + return RESP_BAD; + } + } else if (!imap->in_progress) { + fprintf( stderr, "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : "" ); + return RESP_BAD; + } else if (*arg == '+') { + /* This can happen only with the last command underway, as + it enforces a round-trip. */ + cmdp = (struct imap_cmd *)((char *)imap->in_progress_append - + offsetof(struct imap_cmd, next)); + if (cmdp->cb.data) { + n = socket_write( &imap->buf.sock, cmdp->cb.data, cmdp->cb.dlen ); + free( cmdp->cb.data ); + cmdp->cb.data = 0; + if (n != (int)cmdp->cb.dlen) + return RESP_BAD; + } else if (cmdp->cb.cont) { + if (cmdp->cb.cont( ctx, cmdp, cmd )) + return RESP_BAD; + } else { + fprintf( stderr, "IMAP error: unexpected command continuation request\n" ); + return RESP_BAD; + } + if (socket_write( &imap->buf.sock, "\r\n", 2 ) != 2) + return RESP_BAD; + if (!cmdp->cb.cont) + imap->literal_pending = 0; + if (!tcmd) + return DRV_OK; + } else { + tag = atoi( arg ); + for (pcmdp = &imap->in_progress; (cmdp = *pcmdp); pcmdp = &cmdp->next) + if (cmdp->tag == tag) + goto gottag; + fprintf( stderr, "IMAP error: unexpected tag %s\n", arg ); + return RESP_BAD; + gottag: + if (!(*pcmdp = cmdp->next)) + imap->in_progress_append = pcmdp; + imap->num_in_progress--; + if (cmdp->cb.cont || cmdp->cb.data) + imap->literal_pending = 0; + arg = next_arg( &cmd ); + if (!strcmp( "OK", arg )) + resp = DRV_OK; + else { + if (!strcmp( "NO", arg )) { + if (cmdp->cb.create && cmd && !memcmp( cmd, "[TRYCREATE]", 11 )) { /* SELECT, APPEND or UID COPY */ + p = strchr( cmdp->cmd, '"' ); + if (!issue_imap_cmd( ctx, 0, "CREATE %.*s", strchr( p + 1, '"' ) - p + 1, p )) { + resp = RESP_BAD; + goto normal; + } + /* not waiting here violates the spec, but a server that does not + grok this nonetheless violates it too. */ + cmdp->cb.create = 0; + if (!(ncmdp = issue_imap_cmd( ctx, &cmdp->cb, "%s", cmdp->cmd ))) { + resp = RESP_BAD; + goto normal; + } + free( cmdp->cmd ); + free( cmdp ); + if (!tcmd) + return 0; /* ignored */ + if (cmdp == tcmd) + tcmd = ncmdp; + continue; + } + resp = RESP_NO; + } else /*if (!strcmp( "BAD", arg ))*/ + resp = RESP_BAD; + fprintf( stderr, "IMAP command '%s' returned an error: %s %s\n", + memcmp (cmdp->cmd, "LOGIN", 5) ? + cmdp->cmd : "LOGIN ", + arg, cmd ? cmd : ""); + } + if ((resp2 = parse_response_code( ctx, cmdp->cb.ctx, cmd )) > resp) + resp = resp2; + normal: + if (cmdp->cb.done) + cmdp->cb.done( ctx, cmdp, resp ); + if (cmdp->cb.data) + free( cmdp->cb.data ); + free( cmdp->cmd ); + free( cmdp ); + if (!tcmd || tcmd == cmdp) + return resp; + } + } + /* not reached */ +} + +static void +imap_close_server( imap_store_t *ictx ) +{ + imap_t *imap = ictx->imap; + + if (imap->buf.sock.fd != -1) { + imap_exec( ictx, 0, "LOGOUT" ); + close( imap->buf.sock.fd ); + } +#ifdef HAVE_LIBSSL + if (imap->SSLContext) + SSL_CTX_free( imap->SSLContext ); +#endif + free_list( imap->ns_personal ); + free_list( imap->ns_other ); + free_list( imap->ns_shared ); + free( imap ); +} + +static void +imap_close_store( store_t *ctx ) +{ + imap_close_server( (imap_store_t *)ctx ); + free_generic_messages( ctx->msgs ); + free( ctx ); +} + +#ifdef HAVE_LIBSSL +static int +start_tls( imap_store_t *ctx ) +{ + imap_t *imap = ctx->imap; + int ret; + static int ssl_inited; + + if (!ssl_inited) { + SSL_library_init(); + SSL_load_error_strings(); + ssl_inited = 1; + } + + if (init_ssl_ctx( ctx )) + return 1; + + imap->buf.sock.ssl = SSL_new( imap->SSLContext ); + SSL_set_fd( imap->buf.sock.ssl, imap->buf.sock.fd ); + if ((ret = SSL_connect( imap->buf.sock.ssl )) <= 0) { + socket_perror( "connect", &imap->buf.sock, ret ); + return 1; + } + + /* verify the server certificate */ + if (verify_cert( imap->buf.sock.ssl )) + return 1; + + imap->buf.sock.use_ssl = 1; + info( "Connection is now encrypted\n" ); + return 0; +} + +#define ENCODED_SIZE(n) (4*((n+2)/3)) + +static char +hexchar( unsigned int b ) +{ + if (b < 10) + return '0' + b; + return 'a' + (b - 10); +} + +/* XXX merge into do_cram_auth? */ +static char * +cram( const char *challenge, const char *user, const char *pass ) +{ + HMAC_CTX hmac; + char hash[16]; + char hex[33]; + int i; + unsigned int hashlen = sizeof(hash); + char buf[256]; + int len = strlen( challenge ); + char *response = nfcalloc( 1 + len ); + char *final; + + /* response will always be smaller than challenge because we are + * decoding. + */ + len = EVP_DecodeBlock( (unsigned char *)response, (unsigned char *)challenge, strlen( challenge ) ); + + HMAC_Init( &hmac, (unsigned char *) pass, strlen( pass ), EVP_md5() ); + HMAC_Update( &hmac, (unsigned char *)response, strlen( response ) ); + HMAC_Final( &hmac, (unsigned char *)hash, &hashlen ); + + assert( hashlen == sizeof(hash) ); + + free( response ); + + hex[32] = 0; + for (i = 0; i < 16; i++) { + hex[2 * i] = hexchar( (hash[i] >> 4) & 0xf ); + hex[2 * i + 1] = hexchar( hash[i] & 0xf ); + } + + nfsnprintf( buf, sizeof(buf), "%s %s", user, hex ); + + len = strlen( buf ); + len = ENCODED_SIZE( len ) + 1; + final = nfmalloc( len ); + final[len - 1] = 0; + + assert( EVP_EncodeBlock( (unsigned char *)final, (unsigned char *)buf, strlen( buf ) ) == len - 1 ); + + return final; +} + +static int +do_cram_auth (imap_store_t *ctx, struct imap_cmd *cmdp, const char *prompt) +{ + imap_t *imap = ctx->imap; + imap_server_conf_t *srvc = ((imap_store_conf_t *)ctx->gen.conf)->server; + char *resp; + int n, l; + + resp = cram( prompt, srvc->user, srvc->pass ); + + if (Verbose) + printf( ">+> %s\n", resp ); + l = strlen( resp ); + n = socket_write( &imap->buf.sock, resp, l ); + free( resp ); + if (n != l) + return -1; + if (socket_write( &imap->buf.sock, "\r\n", 2 ) != 2) + return -1; + cmdp->cb.cont = 0; + return 0; +} +#endif + +static store_t * +imap_open_store( store_conf_t *conf, store_t *oldctx ) +{ + imap_store_conf_t *cfg = (imap_store_conf_t *)conf; + imap_server_conf_t *srvc = cfg->server; + imap_store_t *ctx = (imap_store_t *)oldctx; + imap_t *imap; + char *arg, *rsp; + struct hostent *he; + struct sockaddr_in addr; + int s, a[2], preauth; +#if HAVE_LIBSSL + int use_ssl; +#endif + + if (ctx) { + if (((imap_store_conf_t *)(ctx->gen.conf))->server == cfg->server) { + ctx->gen.conf = conf; + imap = ctx->imap; + goto final; + } + imap_close_server( ctx ); + } else + ctx = nfcalloc( sizeof(*ctx) ); + + ctx->gen.conf = conf; + ctx->imap = imap = nfcalloc( sizeof(*imap) ); + imap->buf.sock.fd = -1; + imap->in_progress_append = &imap->in_progress; + + /* open connection to IMAP server */ +#if HAVE_LIBSSL + use_ssl = 0; +#endif + + if (srvc->tunnel) { + info( "Starting tunnel '%s'... ", srvc->tunnel ); + + if (socketpair( PF_UNIX, SOCK_STREAM, 0, a )) { + perror( "socketpair" ); + exit( 1 ); + } + + if (fork() == 0) { + if (dup2( a[0], 0 ) == -1 || dup2( a[0], 1 ) == -1) + _exit( 127 ); + close( a[0] ); + close( a[1] ); + execl( "/bin/sh", "sh", "-c", srvc->tunnel, 0 ); + _exit( 127 ); + } + + close (a[0]); + + imap->buf.sock.fd = a[1]; + + info( "ok\n" ); + } else { + memset( &addr, 0, sizeof(addr) ); + addr.sin_port = htons( srvc->port ); + addr.sin_family = AF_INET; + + info( "Resolving %s... ", srvc->host ); + he = gethostbyname( srvc->host ); + if (!he) { + perror( "gethostbyname" ); + goto bail; + } + info( "ok\n" ); + + addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]); + + s = socket( PF_INET, SOCK_STREAM, 0 ); + + info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) ); + if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) { + close( s ); + perror( "connect" ); + goto bail; + } + info( "ok\n" ); + + imap->buf.sock.fd = s; + +#if HAVE_LIBSSL + if (srvc->use_imaps) { + if (start_tls( ctx )) + goto bail; + use_ssl = 1; + } +#endif + } + + /* read the greeting string */ + if (buffer_gets( &imap->buf, &rsp )) { + fprintf( stderr, "IMAP error: no greeting response\n" ); + goto bail; + } + arg = next_arg( &rsp ); + if (!arg || *arg != '*' || (arg = next_arg( &rsp )) == NULL) { + fprintf( stderr, "IMAP error: invalid greeting response\n" ); + goto bail; + } + preauth = 0; + if (!strcmp( "PREAUTH", arg )) + preauth = 1; + else if (strcmp( "OK", arg ) != 0) { + fprintf( stderr, "IMAP error: unknown greeting response\n" ); + goto bail; + } + parse_response_code( ctx, 0, rsp ); + if (!imap->caps && imap_exec( ctx, 0, "CAPABILITY" ) != RESP_OK) + goto bail; + + if (!preauth) { +#if HAVE_LIBSSL + if (!srvc->use_imaps && (srvc->use_sslv2 || srvc->use_sslv3 || srvc->use_tlsv1)) { + /* always try to select SSL support if available */ + if (CAP(STARTTLS)) { + if (imap_exec( ctx, 0, "STARTTLS" ) != RESP_OK) + goto bail; + if (start_tls( ctx )) + goto bail; + use_ssl = 1; + + if (imap_exec( ctx, 0, "CAPABILITY" ) != RESP_OK) + goto bail; + } else { + if (srvc->require_ssl) { + fprintf( stderr, "IMAP error: SSL support not available\n" ); + goto bail; + } else + warn( "IMAP warning: SSL support not available\n" ); + } + } +#endif + + info ("Logging in...\n"); + if (!srvc->user) { + fprintf( stderr, "Skipping server %s, no user\n", srvc->host ); + goto bail; + } + if (!srvc->pass) { + char prompt[80]; + sprintf( prompt, "Password (%s@%s): ", srvc->user, srvc->host ); + arg = getpass( prompt ); + if (!arg) { + perror( "getpass" ); + exit( 1 ); + } + if (!*arg) { + fprintf( stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host ); + goto bail; + } + /* + * getpass() returns a pointer to a static buffer. make a copy + * for long term storage. + */ + srvc->pass = nfstrdup( arg ); + } +#if HAVE_LIBSSL + if (CAP(CRAM)) { + struct imap_cmd_cb cb; + + info( "Authenticating with CRAM-MD5\n" ); + memset( &cb, 0, sizeof(cb) ); + cb.cont = do_cram_auth; + if (imap_exec( ctx, &cb, "AUTHENTICATE CRAM-MD5" ) != RESP_OK) + goto bail; + } else if (srvc->require_cram) { + fprintf( stderr, "IMAP error: CRAM-MD5 authentication is not supported by server\n" ); + goto bail; + } else +#endif + { + if (CAP(NOLOGIN)) { + fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host ); + goto bail; + } +#if HAVE_LIBSSL + if (!use_ssl) +#endif + warn( "*** IMAP Warning *** Password is being sent in the clear\n" ); + if (imap_exec( ctx, 0, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) { + fprintf( stderr, "IMAP error: LOGIN failed\n" ); + goto bail; + } + } + } /* !preauth */ + + final: + ctx->prefix = ""; + if (*conf->path) + ctx->prefix = conf->path; + else if (cfg->use_namespace && CAP(NAMESPACE)) { + /* get NAMESPACE info */ + if (imap_exec( ctx, 0, "NAMESPACE" ) != RESP_OK) + goto bail; + /* XXX for now assume personal namespace */ + if (is_list( imap->ns_personal ) && + is_list( imap->ns_personal->child ) && + is_atom( imap->ns_personal->child->child )) + ctx->prefix = imap->ns_personal->child->child->val; + } + ctx->trashnc = 1; + return (store_t *)ctx; + + bail: + imap_close_store( &ctx->gen ); + return 0; +} + +static void +imap_prepare( store_t *gctx, int opts ) +{ + free_generic_messages( gctx->msgs ); + gctx->msgs = 0; + gctx->opts = opts; +} + +static int +imap_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs ) +{ + imap_store_t *ctx = (imap_store_t *)gctx; + imap_t *imap = ctx->imap; + const char *prefix; + int ret, i, j, bl; + struct imap_cmd_cb cb; + char buf[1000]; + + + if (!strcmp( gctx->name, "INBOX" )) { +// imap->currentnc = 0; + prefix = ""; + } else { +// imap->currentnc = 1; /* could use LIST results for that */ + prefix = ctx->prefix; + } + + memset( &cb, 0, sizeof(cb) ); + cb.create = gctx->opts & OPEN_CREATE; + if ((ret = imap_exec_b( ctx, &cb, "SELECT \"%s%s\"", prefix, gctx->name )) != DRV_OK) + goto bail; + + if (gctx->count) { + imap->msgapp = &gctx->msgs; + sort_ints( excs, nexcs ); + for (i = 0; i < nexcs; ) { + for (bl = 0; i < nexcs && bl < 960; i++) { + if (bl) + buf[bl++] = ','; + bl += sprintf( buf + bl, "%d", excs[i] ); + j = i; + for (; i + 1 < nexcs && excs[i + 1] == excs[i] + 1; i++); + if (i != j) + bl += sprintf( buf + bl, ":%d", excs[i] ); + } + if ((ret = imap_exec_b( ctx, 0, "UID FETCH %s (UID%s%s)", buf, + (gctx->opts & OPEN_FLAGS) ? " FLAGS" : "", + (gctx->opts & OPEN_SIZE) ? " RFC822.SIZE" : "" )) != DRV_OK) + goto bail; + } + if (maxuid == INT_MAX) + maxuid = imap->uidnext ? imap->uidnext - 1 : 1000000000; + if (maxuid >= minuid && + (ret = imap_exec_b( ctx, 0, "UID FETCH %d:%d (UID%s%s)", minuid, maxuid, + (gctx->opts & OPEN_FLAGS) ? " FLAGS" : "", + (gctx->opts & OPEN_SIZE) ? " RFC822.SIZE" : "" )) != DRV_OK) + goto bail; + } + + ret = DRV_OK; + + bail: + if (excs) + free( excs ); + return ret; +} + +static int +imap_fetch_msg( store_t *ctx, message_t *msg, msg_data_t *data ) +{ + struct imap_cmd_cb cb; + + memset( &cb, 0, sizeof(cb) ); + cb.uid = msg->uid; + cb.ctx = data; + return imap_exec_m( (imap_store_t *)ctx, &cb, "UID FETCH %d (%sBODY.PEEK[])", + msg->uid, (msg->status & M_FLAGS) ? "" : "FLAGS " ); +} + +static int +imap_make_flags( int flags, char *buf ) +{ + const char *s; + unsigned i, d; + + for (i = d = 0; i < as(Flags); i++) + if (flags & (1 << i)) { + buf[d++] = ' '; + for (s = Flags[i]; *s; s++) + buf[d++] = *s; + } + buf[0] = '('; + buf[d++] = ')'; + return d; +} + +static int +imap_flags_helper( imap_store_t *ctx, int uid, char what, int flags) +{ + char buf[256]; + + buf[imap_make_flags( flags, buf )] = 0; + return issue_imap_cmd_w( ctx, 0, "UID STORE %d %cFLAGS.SILENT %s", uid, what, buf ) ? DRV_OK : DRV_STORE_BAD; +} + +static int +imap_set_flags( store_t *gctx, message_t *msg, int uid, int add, int del ) +{ + imap_store_t *ctx = (imap_store_t *)gctx; + int ret; + + if (msg) { + uid = msg->uid; + add &= ~msg->flags; + del &= msg->flags; + msg->flags |= add; + msg->flags &= ~del; + } + if ((!add || (ret = imap_flags_helper( ctx, uid, '+', add )) == DRV_OK) && + (!del || (ret = imap_flags_helper( ctx, uid, '-', del )) == DRV_OK)) + return DRV_OK; + return ret; +} + +static int +imap_close( store_t *ctx ) +{ + return imap_exec_b( (imap_store_t *)ctx, 0, "CLOSE" ); +} + +static int +imap_trash_msg( store_t *gctx, message_t *msg ) +{ + imap_store_t *ctx = (imap_store_t *)gctx; + struct imap_cmd_cb cb; + + memset( &cb, 0, sizeof(cb) ); + cb.create = 1; + return imap_exec_m( ctx, &cb, "UID COPY %d \"%s%s\"", + msg->uid, ctx->prefix, gctx->conf->trash ); +} + +#define TUIDL 8 + +static int +imap_store_msg( store_t *gctx, msg_data_t *data, int *uid ) +{ + imap_store_t *ctx = (imap_store_t *)gctx; + imap_t *imap = ctx->imap; + struct imap_cmd_cb cb; + char *fmap, *buf; + const char *prefix, *box; + int ret, i, j, d, len, extra, nocr; + int start, sbreak = 0, ebreak = 0; + char flagstr[128], tuid[TUIDL * 2 + 1]; + + memset( &cb, 0, sizeof(cb) ); + + fmap = data->data; + len = data->len; + nocr = !data->crlf; + extra = 0, i = 0; + if (!CAP(UIDPLUS) && uid) { + nloop: + start = i; + while (i < len) + if (fmap[i++] == '\n') { + extra += nocr; + if (i - 2 + nocr == start) { + sbreak = ebreak = i - 2 + nocr; + goto mktid; + } + if (!memcmp( fmap + start, "X-TUID: ", 8 )) { + extra -= (ebreak = i) - (sbreak = start) + nocr; + goto mktid; + } + goto nloop; + } + /* invalid message */ + free( fmap ); + return DRV_MSG_BAD; + mktid: + for (j = 0; j < TUIDL; j++) + sprintf( tuid + j * 2, "%02x", arc4_getbyte() ); + extra += 8 + TUIDL * 2 + 2; + } + if (nocr) + for (; i < len; i++) + if (fmap[i] == '\n') + extra++; + + cb.dlen = len + extra; + buf = cb.data = nfmalloc( cb.dlen ); + i = 0; + if (!CAP(UIDPLUS) && uid) { + if (nocr) { + for (; i < sbreak; i++) + if (fmap[i] == '\n') { + *buf++ = '\r'; + *buf++ = '\n'; + } else + *buf++ = fmap[i]; + } else { + memcpy( buf, fmap, sbreak ); + buf += sbreak; + } + memcpy( buf, "X-TUID: ", 8 ); + buf += 8; + memcpy( buf, tuid, TUIDL * 2 ); + buf += TUIDL * 2; + *buf++ = '\r'; + *buf++ = '\n'; + i = ebreak; + } + if (nocr) { + for (; i < len; i++) + if (fmap[i] == '\n') { + *buf++ = '\r'; + *buf++ = '\n'; + } else + *buf++ = fmap[i]; + } else + memcpy( buf, fmap + i, len - i ); + + free( fmap ); + + d = 0; + if (data->flags) { + d = imap_make_flags( data->flags, flagstr ); + flagstr[d++] = ' '; + } + flagstr[d] = 0; + + if (!uid) { + box = gctx->conf->trash; + prefix = ctx->prefix; + cb.create = 1; + if (ctx->trashnc) + imap->caps = imap->rcaps & ~(1 << LITERALPLUS); + } else { + box = gctx->name; + prefix = !strcmp( box, "INBOX" ) ? "" : ctx->prefix; + cb.create = gctx->opts & OPEN_CREATE; + /*if (ctx->currentnc) + imap->caps = imap->rcaps & ~(1 << LITERALPLUS);*/ + } + cb.ctx = uid; + ret = imap_exec_m( ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr ); + imap->caps = imap->rcaps; + if (ret != DRV_OK) + return ret; + if (!uid) + ctx->trashnc = 0; + else { + /*ctx->currentnc = 0;*/ + gctx->count++; + } + + if (CAP(UIDPLUS) || !uid) + return DRV_OK; + + /* Didn't receive an APPENDUID */ + cb.uid = -1; /* we're looking for a UID */ + cb.data = 0; /* reset; ctx still set */ + return imap_exec_m( ctx, &cb, "UID SEARCH HEADER X-TUID %s", tuid ); +} + +static int +imap_list( store_t *gctx, string_list_t **retb ) +{ + imap_store_t *ctx = (imap_store_t *)gctx; + imap_t *imap = ctx->imap; + int ret; + + imap->boxes = 0; + if ((ret = imap_exec_b( ctx, 0, "LIST \"\" \"%s%%\"", ctx->prefix )) != DRV_OK) + return ret; + *retb = imap->boxes; + return DRV_OK; +} + +static int +imap_check( store_t *gctx ) +{ + (void) gctx; + /* flush queue here */ + return DRV_OK; +} + +imap_server_conf_t *servers, **serverapp = &servers; + +static int +imap_parse_store( conffile_t *cfg, store_conf_t **storep, int *err ) +{ + imap_store_conf_t *store; + imap_server_conf_t *server, *srv, sserver; + + if (!strcasecmp( "IMAPAccount", cfg->cmd )) { + server = nfcalloc( sizeof(*server) ); + server->name = nfstrdup( cfg->val ); + *serverapp = server; + serverapp = &server->next; + store = 0; + } else if (!strcasecmp( "IMAPStore", cfg->cmd )) { + store = nfcalloc( sizeof(*store) ); + store->gen.driver = &imap_driver; + store->gen.name = nfstrdup( cfg->val ); + store->use_namespace = 1; + memset( &sserver, 0, sizeof(sserver) ); + server = &sserver; + } else + return 0; + +#if HAVE_LIBSSL + /* this will probably annoy people, but its the best default just in + * case people forget to turn it on + */ + server->require_ssl = 1; + server->use_tlsv1 = 1; +#endif + + while (getcline( cfg ) && cfg->cmd) { + if (!strcasecmp( "Account", cfg->cmd )) { + for (srv = servers; srv; srv = srv->next) + if (srv->name && !strcmp( srv->name, cfg->val )) + goto gotsrv; + fprintf( stderr, "%s:%d: unknown IMAP account '%s'\n", + cfg->file, cfg->line, cfg->val ); + *err = 1; + continue; + gotsrv: + store->server = srv; + } else if (!strcasecmp( "Host", cfg->cmd )) { +#if HAVE_LIBSSL + if (!memcmp( "imaps:", cfg->val, 6 )) { + cfg->val += 6; + server->use_imaps = 1; + server->use_sslv2 = 1; + server->use_sslv3 = 1; + if (!server->port) + server->port = 993; + } else +#endif + { + if (!memcmp( "imap:", cfg->val, 5 )) + cfg->val += 5; + if (!server->port) + server->port = 143; + } + if (!memcmp( "//", cfg->val, 2 )) + cfg->val += 2; + server->host = nfstrdup( cfg->val ); + } + else if (!strcasecmp( "User", cfg->cmd )) + server->user = nfstrdup( cfg->val ); + else if (!strcasecmp( "Pass", cfg->cmd )) + server->pass = nfstrdup( cfg->val ); + else if (!strcasecmp( "Port", cfg->cmd )) + server->port = parse_int( cfg ); +#if HAVE_LIBSSL + else if (!strcasecmp( "CertificateFile", cfg->cmd )) + server->cert_file = expand_strdup( cfg->val ); + else if (!strcasecmp( "RequireSSL", cfg->cmd )) + server->require_ssl = parse_bool( cfg ); + else if (!strcasecmp( "UseSSLv2", cfg->cmd )) + server->use_sslv2 = parse_bool( cfg ); + else if (!strcasecmp( "UseSSLv3", cfg->cmd )) + server->use_sslv3 = parse_bool( cfg ); + else if (!strcasecmp( "UseTLSv1", cfg->cmd )) + server->use_tlsv1 = parse_bool( cfg ); + else if (!strcasecmp( "RequireCRAM", cfg->cmd )) + server->require_cram = parse_bool( cfg ); +#endif + else if (!strcasecmp( "Tunnel", cfg->cmd )) + server->tunnel = nfstrdup( cfg->val ); + else if (store) { + if (!strcasecmp( "UseNamespace", cfg->cmd )) + store->use_namespace = parse_bool( cfg ); + else if (!strcasecmp( "Path", cfg->cmd )) + store->gen.path = nfstrdup( cfg->val ); + else + parse_generic_store( &store->gen, cfg, err ); + } else { + fprintf( stderr, "%s:%d: unknown keyword '%s'\n", + cfg->file, cfg->line, cfg->cmd ); + *err = 1; + } + } + if (!store || !store->server) { + if (!server->tunnel && !server->host) { + if (store) + fprintf( stderr, "IMAP store '%s' has incomplete connection details\n", store->gen.name ); + else + fprintf( stderr, "IMAP account '%s' has incomplete connection details\n", server->name ); + *err = 1; + /* leaking server/store */ + *storep = 0; + return 1; + } + } + *storep = &store->gen; + if (store && !store->server) { + store->server = nfmalloc( sizeof(sserver) ); + memcpy( store->server, &sserver, sizeof(sserver) ); + } + return 1; +} + +struct driver imap_driver = { + imap_parse_store, + imap_open_store, + imap_close_store, + imap_list, + imap_prepare, + imap_select, + imap_fetch_msg, + imap_store_msg, + imap_set_flags, + imap_trash_msg, + imap_check, + imap_close +}; diff --git a/src/drv_maildir.c b/src/drv_maildir.c new file mode 100644 index 0000000..33260da --- /dev/null +++ b/src/drv_maildir.c @@ -0,0 +1,1153 @@ +/* + * mbsync - mailbox synchronizer + * Copyright (C) 2000-2002 Michael R. Elkins + * Copyright (C) 2002-2004 Oswald Buddenhagen + * Copyright (C) 2004 Theodore Y. Ts'o + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * As a special exception, mbsync may be linked with the OpenSSL library, + * despite that library's more restrictive license. + */ + +#include "isync.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USE_DB 1 + +#ifdef USE_DB +#include +#endif /* USE_DB */ + +typedef struct maildir_store_conf { + store_conf_t gen; + char *inbox; +#ifdef USE_DB + int alt_map; +#endif /* USE_DB */ +} maildir_store_conf_t; + +typedef struct maildir_message { + message_t gen; + char *base; +} maildir_message_t; + +typedef struct maildir_store { + store_t gen; + int uvfd, uvok, nuid; + int minuid, maxuid, nexcs, *excs; +#ifdef USE_DB + DB *db; +#endif /* USE_DB */ +} maildir_store_t; + +#ifdef USE_DB +static DBT key, value; /* no need to be reentrant, and this saves lots of memset()s */ +#endif /* USE_DB */ +static struct flock lck; + +static int MaildirCount; + +static const char Flags[] = { 'D', 'F', 'R', 'S', 'T' }; + +static unsigned char +maildir_parse_flags( const char *base ) +{ + const char *s; + unsigned i; + unsigned char flags; + + flags = 0; + if ((s = strstr( base, ":2," ))) + for (s += 3, i = 0; i < as(Flags); i++) + if (strchr( s, Flags[i] )) + flags |= (1 << i); + return flags; +} + +static void maildir_close_store( store_t *gctx ); + +static store_t * +maildir_open_store( store_conf_t *conf, store_t *oldctx ) +{ + maildir_store_t *ctx; + struct stat st; + + if (oldctx) + maildir_close_store( oldctx ); + if (stat( conf->path, &st ) || !S_ISDIR(st.st_mode)) { + fprintf( stderr, "Maildir error: cannot open store %s\n", conf->path ); + return 0; + } + ctx = nfcalloc( sizeof(*ctx) ); + ctx->gen.conf = conf; + ctx->uvfd = -1; + return &ctx->gen; +} + +static void +free_maildir_messages( message_t *msg ) +{ + message_t *tmsg; + + for (; (tmsg = msg); msg = tmsg) { + tmsg = msg->next; + free( ((maildir_message_t *)msg)->base ); + free( msg ); + } +} + +static void +maildir_cleanup( store_t *gctx ) +{ + maildir_store_t *ctx = (maildir_store_t *)gctx; + + free_maildir_messages( gctx->msgs ); +#ifdef USE_DB + if (ctx->db) + ctx->db->close( ctx->db, 0 ); +#endif /* USE_DB */ + if (gctx->path) + free( gctx->path ); + if (ctx->excs) + free( ctx->excs ); + if (ctx->uvfd >= 0) + close( ctx->uvfd ); +} + +static void +maildir_close_store( store_t *gctx ) +{ + maildir_cleanup( gctx ); + free( gctx ); +} + +static int +maildir_list( store_t *gctx, string_list_t **retb ) +{ + DIR *dir; + struct dirent *de; + + if (!(dir = opendir( gctx->conf->path ))) { + fprintf( stderr, "%s: %s\n", gctx->conf->path, strerror(errno) ); + return DRV_STORE_BAD; + } + *retb = 0; + while ((de = readdir( dir ))) { + struct stat st; + char buf[PATH_MAX]; + + if (*de->d_name == '.') + continue; + nfsnprintf( buf, sizeof(buf), "%s%s/cur", gctx->conf->path, de->d_name ); + if (stat( buf, &st ) || !S_ISDIR(st.st_mode)) + continue; + add_string_list( retb, de->d_name ); + } + closedir (dir); + + return DRV_OK; +} + +static const char *subdirs[] = { "cur", "new", "tmp" }; + +typedef struct { + char *base; + int size; + unsigned uid:31, recent:1; +} msg_t; + +typedef struct { + msg_t *ents; + int nents, nalloc; +} msglist_t; + +static void +maildir_free_scan( msglist_t *msglist ) +{ + int i; + + if (msglist->ents) { + for (i = 0; i < msglist->nents; i++) + if (msglist->ents[i].base) + free( msglist->ents[i].base ); + free( msglist->ents ); + } +} + +#define _24_HOURS (3600 * 24) + +static int +maildir_validate( const char *prefix, const char *box, int create ) +{ + DIR *dirp; + struct dirent *entry; + time_t now; + int i, bl; + struct stat st; + char buf[_POSIX_PATH_MAX]; + + bl = nfsnprintf( buf, sizeof(buf) - 4, "%s%s/", prefix, box ); + if (stat( buf, &st )) { + if (errno == ENOENT) { + if (create) { + if (mkdir( buf, 0700 )) { + fprintf( stderr, "Maildir error: mkdir %s: %s (errno %d)\n", + buf, strerror(errno), errno ); + return DRV_STORE_BAD; + } + for (i = 0; i < 3; i++) { + memcpy( buf + bl, subdirs[i], 4 ); + if (mkdir( buf, 0700 )) { + fprintf( stderr, "Maildir error: mkdir %s: %s (errno %d)\n", + buf, strerror(errno), errno ); + return DRV_BOX_BAD; + } + } + } else { + fprintf( stderr, "Maildir error: mailbox '%s' does not exist\n", buf ); + return DRV_BOX_BAD; + } + } else { + fprintf( stderr, "Maildir error: stat %s: %s (errno %d)\n", + buf, strerror(errno), errno ); + return DRV_BOX_BAD; + } + } else { + for (i = 0; i < 3; i++) { + memcpy( buf + bl, subdirs[i], 4 ); + if (stat( buf, &st ) || !S_ISDIR(st.st_mode)) { + fprintf( stderr, "Maildir error: '%s' is no valid mailbox\n", buf ); + return DRV_BOX_BAD; + } + } + memcpy( buf + bl, "tmp/", 5 ); + bl += 4; + if (!(dirp = opendir( buf ))) { + fprintf( stderr, "Maildir error: opendir: %s: %s (errno %d)\n", + buf, strerror(errno), errno ); + return DRV_BOX_BAD; + } + time( &now ); + while ((entry = readdir( dirp ))) { + nfsnprintf( buf + bl, sizeof(buf) - bl, "%s", entry->d_name ); + if (stat( buf, &st )) + fprintf( stderr, "Maildir error: stat: %s: %s (errno %d)\n", + buf, strerror(errno), errno ); + else if (S_ISREG(st.st_mode) && now - st.st_ctime >= _24_HOURS) { + /* this should happen infrequently enough that it won't be + * bothersome to the user to display when it occurs. + */ + info( "Maildir notice: removing stale file %s\n", buf ); + if (unlink( buf )) + fprintf( stderr, "Maildir error: unlink: %s: %s (errno %d)\n", + buf, strerror(errno), errno ); + } + } + closedir( dirp ); + } + return DRV_OK; +} + +static int +maildir_uidval_lock( maildir_store_t *ctx ) +{ + int n; + char buf[128]; + + /* This is legacy only */ + if (flock( ctx->uvfd, LOCK_EX ) < 0) { + fprintf( stderr, "Maildir error: cannot flock UIDVALIDITY.\n" ); + return DRV_BOX_BAD; + } + /* This (theoretically) works over NFS. Let's hope nobody else did + the same in the opposite order, as we'd deadlock then. */ +#if SEEK_SET != 0 + lck.l_whence = SEEK_SET; +#endif + lck.l_type = F_WRLCK; + if (fcntl( ctx->uvfd, F_SETLKW, &lck )) { + fprintf( stderr, "Maildir error: cannot fcntl lock UIDVALIDITY.\n" ); + return DRV_BOX_BAD; + } + lseek( ctx->uvfd, 0, SEEK_SET ); + if ((n = read( ctx->uvfd, buf, sizeof(buf) )) <= 0 || + (buf[n] = 0, sscanf( buf, "%d\n%d", &ctx->gen.uidvalidity, &ctx->nuid ) != 2)) { + info( "Maildir notice: cannot read UIDVALIDITY, creating new.\n" ); + ctx->gen.uidvalidity = time( 0 ); + ctx->nuid = 0; + ctx->uvok = 0; + } else + ctx->uvok = 1; + return DRV_OK; +} + +static void +maildir_uidval_unlock( maildir_store_t *ctx ) +{ + lck.l_type = F_UNLCK; + fcntl( ctx->uvfd, F_SETLK, &lck ); + /* This is legacy only */ + flock( ctx->uvfd, LOCK_UN ); +} + +static int +maildir_obtain_uid( maildir_store_t *ctx, int *uid ) +{ + int n; + char buf[128]; + + *uid = ++ctx->nuid; + n = sprintf( buf, "%d\n%d\n", ctx->gen.uidvalidity, ctx->nuid ); + lseek( ctx->uvfd, 0, SEEK_SET ); + if (write( ctx->uvfd, buf, n ) != n || ftruncate( ctx->uvfd, n )) { + fprintf( stderr, "Maildir error: cannot write UIDVALIDITY.\n" ); + return DRV_BOX_BAD; + } + return DRV_OK; +} + +#ifdef USE_DB +static void +make_key( DBT *tkey, char *name ) +{ + char *u = strpbrk( name, ":," ); + tkey->data = name; + tkey->size = u ? (size_t)(u - name) : strlen( name ); +} + +static int +maildir_set_uid( maildir_store_t *ctx, const char *name, int *uid ) +{ + int ret, uv[2]; + + *uid = ++ctx->nuid; + key.data = (void *)"UIDVALIDITY"; + key.size = 11; + uv[0] = ctx->gen.uidvalidity; + uv[1] = ctx->nuid; + value.data = uv; + value.size = sizeof(uv); + if ((ret = ctx->db->put( ctx->db, 0, &key, &value, 0 ))) { + tbork: + ctx->db->err( ctx->db, ret, "Maildir error: db->put()" ); + bork: + ctx->db->close( ctx->db, 0 ); + ctx->db = 0; + return DRV_BOX_BAD; + } + make_key( &key, (char *)name ); + value.data = uid; + value.size = sizeof(*uid); + if ((ret = ctx->db->put( ctx->db, 0, &key, &value, 0 ))) + goto tbork; + if ((ret = ctx->db->sync( ctx->db, 0 ))) { + ctx->db->err( ctx->db, ret, "Maildir error: db->sync()" ); + goto bork; + } + return DRV_OK; +} +#endif /* USE_DB */ + +static int +maildir_compare( const void *l, const void *r ) +{ + msg_t *lm = (msg_t *)l, *rm = (msg_t *)r; + char *ldot, *rdot, *ldot2, *rdot2, *lseq, *rseq; + int ret, llen, rlen; + + if ((ret = lm->uid - rm->uid)) + return ret; + + /* No UID, so sort by arrival date. We should not do this, but we rely + on the suggested unique file name scheme - we have no choice. */ + /* The first field are always the seconds. Alphabetical sort should be + faster than numeric. */ + if (!(ldot = strchr( lm->base, '.' )) || !(rdot = strchr( rm->base, '.' ))) + goto stronly; /* Should never happen ... */ + llen = ldot - lm->base, rlen = rdot - rm->base; + /* The shorter number is smaller. Really. This won't trigger with any + mail created after Sep 9 2001 anyway. */ + if ((ret = llen - rlen)) + return ret; + if ((ret = memcmp( lm->base, rm->base, llen ))) + return ret; + + ldot++, rdot++; + + if ((llen = strtol( ldot, &ldot2, 10 ))) { + if (!(rlen = strtol( rdot, &rdot2, 10 ))) + goto stronly; /* Comparing apples to oranges ... */ + /* Classical PID specs */ + if ((ret = llen - rlen)) { + retpid: + /* Handle PID wraparound. This works only on systems + where PIDs are not reused too fast */ + if (ret > 20000 || ret < -20000) + ret = -ret; + return ret; + } + return (*ldot2 != '_' ? 0 : atoi( ldot2 + 1 )) - + (*rdot2 != '_' ? 0 : atoi( rdot2 + 1 )); + } + + if (!(ldot2 = strchr( ldot, '.' )) || !(rdot2 = strchr( rdot, '.' ))) + goto stronly; /* Should never happen ... */ + llen = ldot2 - ldot, rlen = rdot2 - rdot; + + if (((lseq = memchr( ldot, '#', llen )) && (rseq = memchr( rdot, '#', rlen ))) || + ((lseq = memchr( ldot, 'M', llen )) && (rseq = memchr( rdot, 'M', rlen )))) + return atoi( lseq + 1 ) - atoi( rseq + 1 ); + + if ((lseq = memchr( ldot, 'P', llen )) && (rseq = memchr( rdot, 'P', rlen ))) { + if ((ret = atoi( lseq + 1 ) - atoi( rseq + 1 ))) + goto retpid; + if ((lseq = memchr( ldot, 'Q', llen )) && (rseq = memchr( rdot, 'Q', rlen ))) + return atoi( lseq + 1 ) - atoi( rseq + 1 ); + } + + stronly: + /* Fall-back, so the sort order is defined at all */ + return strcmp( lm->base, rm->base ); +} + +static int +maildir_scan( maildir_store_t *ctx, msglist_t *msglist ) +{ + DIR *d; + struct dirent *e; + const char *u, *ru; +#ifdef USE_DB + DB *tdb; + DBC *dbc; +#endif /* USE_DB */ + msg_t *entry; + int i, j, uid, bl, ml, fnl, ret; + struct stat st; + char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX]; + +#ifdef USE_DB + if (!ctx->db) +#endif /* USE_DB */ + if ((ret = maildir_uidval_lock( ctx )) != DRV_OK) + return ret; + + again: + msglist->ents = 0; + msglist->nents = msglist->nalloc = 0; + ctx->gen.count = ctx->gen.recent = 0; + if (ctx->uvok || ctx->maxuid == INT_MAX) { +#ifdef USE_DB + if (ctx->db) { + if (db_create( &tdb, 0, 0 )) { + fputs( "Maildir error: db_create() failed\n", stderr ); + return DRV_BOX_BAD; + } + if (tdb->open( tdb, 0, 0, 0, DB_HASH, DB_CREATE, 0 )) { + fputs( "Maildir error: tdb->open() failed\n", stderr ); + tdb->close( tdb, 0 ); + return DRV_BOX_BAD; + } + } +#endif /* USE_DB */ + bl = nfsnprintf( buf, sizeof(buf) - 4, "%s/", ctx->gen.path ); + for (i = 0; i < 2; i++) { + memcpy( buf + bl, subdirs[i], 4 ); + if (!(d = opendir( buf ))) { + perror( buf ); +#ifdef USE_DB + if (!ctx->db) +#endif /* USE_DB */ + maildir_uidval_unlock( ctx ); +#ifdef USE_DB + bork: +#endif /* USE_DB */ + maildir_free_scan( msglist ); +#ifdef USE_DB + if (ctx->db) + tdb->close( tdb, 0 ); +#endif /* USE_DB */ + return DRV_BOX_BAD; + } + while ((e = readdir( d ))) { + if (*e->d_name == '.') + continue; + ctx->gen.count++; + ctx->gen.recent += i; +#ifdef USE_DB + if (ctx->db) { + make_key( &key, e->d_name ); + if ((ret = ctx->db->get( ctx->db, 0, &key, &value, 0 ))) { + if (ret != DB_NOTFOUND) { + ctx->db->err( ctx->db, ret, "Maildir error: db->get()" ); + ctx->db->close( ctx->db, 0 ); + ctx->db = 0; + goto bork; + } + uid = INT_MAX; + } else { + value.size = 0; + if ((ret = tdb->put( tdb, 0, &key, &value, 0 ))) { + tdb->err( tdb, ret, "Maildir error: tdb->put()" ); + goto bork; + } + uid = *(int *)value.data; + } + } else +#endif /* USE_DB */ + { + uid = (ctx->uvok && (u = strstr( e->d_name, ",U=" ))) ? atoi( u + 3 ) : 0; + if (!uid) + uid = INT_MAX; + } + if (uid <= ctx->maxuid) { + if (uid < ctx->minuid) { + for (j = 0; j < ctx->nexcs; j++) + if (ctx->excs[j] == uid) + goto oke; + continue; + oke: ; + } + if (msglist->nalloc == msglist->nents) { + msglist->nalloc = msglist->nalloc * 2 + 100; + msglist->ents = nfrealloc( msglist->ents, msglist->nalloc * sizeof(msg_t) ); + } + entry = &msglist->ents[msglist->nents++]; + entry->base = nfstrdup( e->d_name ); + entry->uid = uid; + entry->recent = i; + entry->size = 0; + } + } + closedir( d ); + } +#ifdef USE_DB + if (ctx->db) { + if ((ret = ctx->db->cursor( ctx->db, 0, &dbc, 0 ))) + ctx->db->err( ctx->db, ret, "Maildir error: db->cursor()" ); + else { + for (;;) { + if ((ret = dbc->c_get( dbc, &key, &value, DB_NEXT ))) { + if (ret != DB_NOTFOUND) + ctx->db->err( ctx->db, ret, "Maildir error: db->c_get()" ); + break; + } + if ((key.size != 11 || memcmp( key.data, "UIDVALIDITY", 11 )) && + (ret = tdb->get( tdb, 0, &key, &value, 0 ))) { + if (ret != DB_NOTFOUND) { + tdb->err( tdb, ret, "Maildir error: tdb->get()" ); + break; + } + if ((ret = dbc->c_del( dbc, 0 ))) { + ctx->db->err( ctx->db, ret, "Maildir error: db->c_del()" ); + break; + } + } + } + dbc->c_close( dbc ); + } + tdb->close( tdb, 0 ); + } +#endif /* USE_DB */ + qsort( msglist->ents, msglist->nents, sizeof(msg_t), maildir_compare ); + for (uid = i = 0; i < msglist->nents; i++) { + entry = &msglist->ents[i]; + if (entry->uid != INT_MAX) { + if (uid == entry->uid) { + info( "Maildir notice: duplicate UID; changing UIDVALIDITY.\n" ); + ctx->gen.uidvalidity = time( 0 ); + ctx->nuid = 0; + ctx->uvok = 0; +#ifdef USE_DB + if (ctx->db) + ctx->db->truncate( ctx->db, 0, 0 /* &u_int32_t_dummy */, 0 ); +#endif /* USE_DB */ + maildir_free_scan( msglist ); + goto again; + } + uid = entry->uid; + if (ctx->gen.opts & OPEN_SIZE) + nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%s", subdirs[entry->recent], entry->base ); +#ifdef USE_DB + } else if (ctx->db) { + if ((ret = maildir_set_uid( ctx, entry->base, &uid )) != DRV_OK) { + maildir_free_scan( msglist ); + return ret; + } + entry->uid = uid; + if (ctx->gen.opts & OPEN_SIZE) + nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%s", subdirs[entry->recent], entry->base ); +#endif /* USE_DB */ + } else { + if ((ret = maildir_obtain_uid( ctx, &uid )) != DRV_OK) { + maildir_free_scan( msglist ); + return ret; + } + entry->uid = uid; + if ((u = strstr( entry->base, ",U=" ))) + for (ru = u + 3; isdigit( (unsigned char)*ru ); ru++); + else + u = ru = strchr( entry->base, ':' ); + if (u) + ml = u - entry->base; + else + ru = "", ml = INT_MAX; + fnl = nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%.*s,U=%d%s", subdirs[entry->recent], ml, entry->base, uid, ru ) + 1 - 4; + memcpy( nbuf, buf, bl + 4 ); + nfsnprintf( nbuf + bl + 4, sizeof(nbuf) - bl - 4, "%s", entry->base ); + if (rename( nbuf, buf )) { + if (errno != ENOENT) { + perror( buf ); + maildir_uidval_unlock( ctx ); + maildir_free_scan( msglist ); + return DRV_BOX_BAD; + } + maildir_free_scan( msglist ); + goto again; + } + free( entry->base ); + entry->base = nfmalloc( fnl ); + memcpy( entry->base, buf + bl + 4, fnl ); + } + if (ctx->gen.opts & OPEN_SIZE) { + if (stat( buf, &st )) { + maildir_free_scan( msglist ); + goto again; + } + entry->size = st.st_size; + } + } + } +#ifdef USE_DB + if (!ctx->db) +#endif /* ! USE_DB */ + maildir_uidval_unlock( ctx ); + return DRV_OK; +} + +static void +maildir_init_msg( maildir_store_t *ctx, maildir_message_t *msg, msg_t *entry ) +{ + msg->base = entry->base; + entry->base = 0; /* prevent deletion */ + msg->gen.size = entry->size; + if (entry->recent) + msg->gen.status |= M_RECENT; + if (ctx->gen.opts & OPEN_FLAGS) { + msg->gen.status |= M_FLAGS; + msg->gen.flags = maildir_parse_flags( msg->base ); + } else + msg->gen.flags = 0; +} + +static void +maildir_app_msg( maildir_store_t *ctx, message_t ***msgapp, msg_t *entry ) +{ + maildir_message_t *msg = nfmalloc( sizeof(*msg) ); + msg->gen.next = **msgapp; + **msgapp = &msg->gen; + *msgapp = &msg->gen.next; + msg->gen.uid = entry->uid; + msg->gen.status = 0; + maildir_init_msg( ctx, msg, entry ); +} + +static void +maildir_prepare( store_t *gctx, int opts ) +{ + maildir_store_t *ctx = (maildir_store_t *)gctx; + + maildir_cleanup( gctx ); + gctx->msgs = 0; + ctx->uvfd = -1; +#ifdef USE_DB + ctx->db = 0; +#endif /* USE_DB */ + if (!strcmp( gctx->name, "INBOX" )) + gctx->path = nfstrdup( ((maildir_store_conf_t *)gctx->conf)->inbox ); + else + nfasprintf( &gctx->path, "%s%s", gctx->conf->path, gctx->name ); + if (opts & OPEN_SETFLAGS) + opts |= OPEN_OLD; + if (opts & OPEN_EXPUNGE) + opts |= OPEN_OLD|OPEN_NEW|OPEN_FLAGS; + gctx->opts = opts; +} + +static int +maildir_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs ) +{ + maildir_store_t *ctx = (maildir_store_t *)gctx; + message_t **msgapp; + msglist_t msglist; + int i; +#ifdef USE_DB + int ret; +#endif /* USE_DB */ + char uvpath[_POSIX_PATH_MAX]; + + ctx->minuid = minuid; + ctx->maxuid = maxuid; + ctx->excs = nfrealloc( excs, nexcs * sizeof(int) ); + ctx->nexcs = nexcs; + + if (maildir_validate( gctx->path, "", ctx->gen.opts & OPEN_CREATE ) != DRV_OK) + return DRV_BOX_BAD; + + nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", gctx->path ); +#ifndef USE_DB + if ((ctx->uvfd = open( uvpath, O_RDWR|O_CREAT, 0600 )) < 0) { + perror( uvpath ); + return DRV_BOX_BAD; + } +#else + if ((ctx->uvfd = open( uvpath, O_RDWR, 0600 )) < 0) { + nfsnprintf( uvpath, sizeof(uvpath), "%s/.isyncuidmap.db", gctx->path ); + if ((ctx->uvfd = open( uvpath, O_RDWR, 0600 )) < 0) { + if (((maildir_store_conf_t *)gctx->conf)->alt_map) { + if ((ctx->uvfd = open( uvpath, O_RDWR|O_CREAT, 0600 )) >= 0) + goto dbok; + } else { + nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", gctx->path ); + if ((ctx->uvfd = open( uvpath, O_RDWR|O_CREAT, 0600 )) >= 0) + goto fnok; + } + perror( uvpath ); + return DRV_BOX_BAD; + } + dbok: +#if SEEK_SET != 0 + lck.l_whence = SEEK_SET; +#endif + lck.l_type = F_WRLCK; + if (fcntl( ctx->uvfd, F_SETLKW, &lck )) { + perror( uvpath ); + bork: + close( ctx->uvfd ); + ctx->uvfd = -1; + return DRV_BOX_BAD; + } + if (db_create( &ctx->db, 0, 0 )) { + fputs( "Maildir error: db_create() failed\n", stderr ); + goto bork; + } + if ((ret = ctx->db->open( ctx->db, 0, uvpath, 0, DB_HASH, DB_CREATE, 0 ))) { + ctx->db->err( ctx->db, ret, "Maildir error: db->open(%s)", uvpath ); + dbork: + ctx->db->close( ctx->db, 0 ); + goto bork; + } + key.data = (void *)"UIDVALIDITY"; + key.size = 11; + if ((ret = ctx->db->get( ctx->db, 0, &key, &value, 0 ))) { + if (ret != DB_NOTFOUND) { + ctx->db->err( ctx->db, ret, "Maildir error: db->get()" ); + goto dbork; + } + info( "Maildir notice: cannot read UIDVALIDITY, creating new.\n" ); + ctx->gen.uidvalidity = time( 0 ); + ctx->nuid = 0; + ctx->uvok = 0; + } else { + ctx->gen.uidvalidity = ((int *)value.data)[0]; + ctx->nuid = ((int *)value.data)[1]; + ctx->uvok = 1; + } + } + fnok: +#endif /* USE_DB */ + + if (maildir_scan( ctx, &msglist ) != DRV_OK) + return DRV_BOX_BAD; + msgapp = &ctx->gen.msgs; + for (i = 0; i < msglist.nents; i++) + maildir_app_msg( ctx, &msgapp, msglist.ents + i ); + maildir_free_scan( &msglist ); + + return DRV_OK; +} + +static int +maildir_rescan( maildir_store_t *ctx ) +{ + message_t **msgapp; + maildir_message_t *msg; + msglist_t msglist; + int i; + + if (maildir_scan( ctx, &msglist ) != DRV_OK) + return DRV_BOX_BAD; + ctx->gen.recent = 0; + for (msgapp = &ctx->gen.msgs, i = 0; + (msg = (maildir_message_t *)*msgapp) || i < msglist.nents; ) + { + if (!msg || msglist.ents[i].uid < msg->gen.uid) { + debug( "ignoring new message %d\n", msglist.ents[i].uid ); + /* maildir_app_msg( ctx, &msgapp, msglist.ents + i ); */ + i++; + } else if (i >= msglist.nents || msglist.ents[i].uid > msg->gen.uid) { + debug( "purging deleted message %d\n", msg->gen.uid ); + msg->gen.status = M_DEAD; + msgapp = &msg->gen.next; + } else { + debug( "updating message %d\n", msg->gen.uid ); + msg->gen.status &= ~(M_FLAGS|M_RECENT); + free( msg->base ); + maildir_init_msg( ctx, msg, msglist.ents + i ); + i++, msgapp = &msg->gen.next; + } + } + maildir_free_scan( &msglist ); + return DRV_OK; +} + +static int +maildir_again( maildir_store_t *ctx, maildir_message_t *msg, const char *fn ) +{ + int ret; + + if (errno != ENOENT) { + perror( fn ); + return DRV_BOX_BAD; + } + if ((ret = maildir_rescan( ctx )) != DRV_OK) + return ret; + return (msg->gen.status & M_DEAD) ? DRV_OK : DRV_MSG_BAD; +} + +static int +maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data ) +{ + maildir_store_t *ctx = (maildir_store_t *)gctx; + maildir_message_t *msg = (maildir_message_t *)gmsg; + int fd, ret; + struct stat st; + char buf[_POSIX_PATH_MAX]; + + for (;;) { + nfsnprintf( buf, sizeof(buf), "%s/%s/%s", gctx->path, subdirs[gmsg->status & M_RECENT], msg->base ); + if ((fd = open( buf, O_RDONLY )) >= 0) + break; + if ((ret = maildir_again( ctx, msg, buf )) != DRV_OK) + return ret; + } + fstat( fd, &st ); + data->crlf = 0; + data->len = st.st_size; + data->data = nfmalloc( data->len ); + if (read( fd, data->data, data->len ) != data->len) { + perror( buf ); + close( fd ); + return DRV_MSG_BAD; + } + close( fd ); + if (!(gmsg->status & M_FLAGS)) + data->flags = maildir_parse_flags( msg->base ); + return DRV_OK; +} + +static int +maildir_make_flags( int flags, char *buf ) +{ + unsigned i, d; + + buf[0] = ':'; + buf[1] = '2'; + buf[2] = ','; + for (d = 3, i = 0; i < as(Flags); i++) + if (flags & (1 << i)) + buf[d++] = Flags[i]; + buf[d] = 0; + return d; +} + +static int +maildir_store_msg( store_t *gctx, msg_data_t *data, int *uid ) +{ + maildir_store_t *ctx = (maildir_store_t *)gctx; + const char *prefix, *box; + int ret, fd, bl; + char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX], fbuf[NUM_FLAGS + 3], base[128]; + + bl = nfsnprintf( base, sizeof(base), "%ld.%d_%d.%s", time( 0 ), Pid, ++MaildirCount, Hostname ); + if (uid) { +#ifdef USE_DB + if (ctx->db) { + if ((ret = maildir_set_uid( ctx, base, uid )) != DRV_OK) { + free( data->data ); + return ret; + } + } else +#endif /* USE_DB */ + { + if ((ret = maildir_uidval_lock( ctx )) != DRV_OK || + (ret = maildir_obtain_uid( ctx, uid )) != DRV_OK) + return ret; + maildir_uidval_unlock( ctx ); + nfsnprintf( base + bl, sizeof(base) - bl, ",U=%d", *uid ); + } + prefix = gctx->path; + box = ""; + } else { + prefix = gctx->conf->path; + box = gctx->conf->trash; + } + + maildir_make_flags( data->flags, fbuf ); + nfsnprintf( buf, sizeof(buf), "%s%s/tmp/%s%s", prefix, box, base, fbuf ); + if ((fd = open( buf, O_WRONLY|O_CREAT|O_EXCL, 0600 )) < 0) { + if (errno != ENOENT) { + perror( buf ); + free( data->data ); + return DRV_BOX_BAD; + } + if ((ret = maildir_validate( gctx->conf->path, gctx->conf->trash, gctx->opts & OPEN_CREATE )) != DRV_OK) { + free( data->data ); + return ret; + } + if ((fd = open( buf, O_WRONLY|O_CREAT|O_EXCL, 0600 )) < 0) { + perror( buf ); + free( data->data ); + return DRV_BOX_BAD; + } + } + strip_cr( data ); + ret = write( fd, data->data, data->len ); + free( data->data ); + if (ret != data->len) { + if (ret < 0) + perror( buf ); + else + fprintf( stderr, "Maildir error: %s: partial write\n", buf ); + close( fd ); + return DRV_BOX_BAD; + } + close( fd ); + nfsnprintf( nbuf, sizeof(nbuf), "%s%s/new/%s%s", prefix, box, base, fbuf ); + if (rename( buf, nbuf )) { + perror( nbuf ); + return DRV_BOX_BAD; + } + if (uid) + gctx->count++; + return DRV_OK; +} + +static int +maildir_set_flags( store_t *gctx, message_t *gmsg, int uid, int add, int del ) +{ + maildir_store_t *ctx = (maildir_store_t *)gctx; + maildir_message_t *msg = (maildir_message_t *)gmsg; + char *s, *p; + unsigned i; + int j, ret, ol, fl, bbl, bl, tl; + char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX]; + + (void) uid; + bbl = nfsnprintf( buf, sizeof(buf), "%s/", gctx->path ); + for (;;) { + bl = bbl + nfsnprintf( buf + bbl, sizeof(buf) - bbl, "%s/", subdirs[gmsg->status & M_RECENT] ); + ol = strlen( msg->base ); + if ((int)sizeof(buf) - bl < ol + 3 + NUM_FLAGS) + oob(); + memcpy( buf + bl, msg->base, ol + 1 ); + memcpy( nbuf, buf, bl + ol + 1 ); + if ((s = strstr( nbuf + bl, ":2," ))) { + s += 3; + fl = ol - (s - (nbuf + bl)); + for (i = 0; i < as(Flags); i++) { + if ((p = strchr( s, Flags[i] ))) { + if (del & (1 << i)) { + memcpy( p, p + 1, fl - (p - s) ); + fl--; + } + } else if (add & (1 << i)) { + for (j = 0; j < fl && Flags[i] > s[j]; j++); + fl++; + memmove( s + j + 1, s + j, fl - j ); + s[j] = Flags[i]; + } + } + tl = ol + 3 + fl; + } else { + tl = ol + maildir_make_flags( msg->gen.flags, nbuf + bl + ol ); + } + if (!rename( buf, nbuf )) + break; + if ((ret = maildir_again( ctx, msg, buf )) != DRV_OK) + return ret; + } + free( msg->base ); + msg->base = nfmalloc( tl + 1 ); + memcpy( msg->base, nbuf + bl, tl + 1 ); + msg->gen.flags |= add; + msg->gen.flags &= ~del; + + return DRV_OK; +} + +#ifdef USE_DB +static int +maildir_purge_msg( maildir_store_t *ctx, const char *name ) +{ + int ret; + + make_key( &key, (char *)name ); + if ((ret = ctx->db->del( ctx->db, 0, &key, 0 ))) { + ctx->db->err( ctx->db, ret, "Maildir error: db->del()" ); + ctx->db->close( ctx->db, 0 ); + ctx->db = 0; + return DRV_BOX_BAD; + } + return DRV_OK; +} +#endif /* USE_DB */ + +static int +maildir_trash_msg( store_t *gctx, message_t *gmsg ) +{ + maildir_store_t *ctx = (maildir_store_t *)gctx; + maildir_message_t *msg = (maildir_message_t *)gmsg; + char *s; + int ret; + struct stat st; + char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX]; + + for (;;) { + nfsnprintf( buf, sizeof(buf), "%s/%s/%s", gctx->path, subdirs[gmsg->status & M_RECENT], msg->base ); + s = strstr( msg->base, ":2," ); + nfsnprintf( nbuf, sizeof(nbuf), "%s%s/new/%ld.%d_%d.%s%s", gctx->conf->path, gctx->conf->trash, + time( 0 ), Pid, ++MaildirCount, Hostname, s ? s : "" ); + if (!rename( buf, nbuf )) + break; + if (!stat( buf, &st )) { + if ((ret = maildir_validate( gctx->conf->path, gctx->conf->trash, 1 )) != DRV_OK) + return ret; + if (!rename( buf, nbuf )) + break; + perror( nbuf ); + return DRV_BOX_BAD; + } + if ((ret = maildir_again( ctx, msg, buf )) != DRV_OK) + return ret; + } + gmsg->status |= M_DEAD; + gctx->count--; + +#ifdef USE_DB + if (ctx->db) + return maildir_purge_msg( ctx, msg->base ); +#endif /* USE_DB */ + return DRV_OK; +} + +static int +maildir_close( store_t *gctx ) +{ +#ifdef USE_DB + maildir_store_t *ctx = (maildir_store_t *)gctx; +#endif /* USE_DB */ + message_t *msg; + int basel, retry, ret; + char buf[_POSIX_PATH_MAX]; + + for (;;) { + retry = 0; + basel = nfsnprintf( buf, sizeof(buf), "%s/", gctx->path ); + for (msg = gctx->msgs; msg; msg = msg->next) + if (!(msg->status & M_DEAD) && (msg->flags & F_DELETED)) { + nfsnprintf( buf + basel, sizeof(buf) - basel, "%s/%s", subdirs[msg->status & M_RECENT], ((maildir_message_t *)msg)->base ); + if (unlink( buf )) { + if (errno == ENOENT) + retry = 1; + else + perror( buf ); + } else { + msg->status |= M_DEAD; + gctx->count--; +#ifdef USE_DB + if (ctx->db && (ret = maildir_purge_msg( ctx, ((maildir_message_t *)msg)->base )) != DRV_OK) + return ret; +#endif /* USE_DB */ + } + } + if (!retry) + return DRV_OK; + if ((ret = maildir_rescan( (maildir_store_t *)gctx )) != DRV_OK) + return ret; + } +} + +static int +maildir_check( store_t *gctx ) +{ + (void) gctx; + return DRV_OK; +} + +static int +maildir_parse_store( conffile_t *cfg, store_conf_t **storep, int *err ) +{ + maildir_store_conf_t *store; + + if (strcasecmp( "MaildirStore", cfg->cmd )) + return 0; + store = nfcalloc( sizeof(*store) ); + store->gen.driver = &maildir_driver; + store->gen.name = nfstrdup( cfg->val ); + + while (getcline( cfg ) && cfg->cmd) + if (!strcasecmp( "Inbox", cfg->cmd )) + store->inbox = expand_strdup( cfg->val ); + else if (!strcasecmp( "Path", cfg->cmd )) + store->gen.path = expand_strdup( cfg->val ); +#ifdef USE_DB + else if (!strcasecmp( "AltMap", cfg->cmd )) + store->alt_map = parse_bool( cfg ); +#endif /* USE_DB */ + else + parse_generic_store( &store->gen, cfg, err ); + if (!store->inbox) + store->inbox = expand_strdup( "~/Maildir" ); + *storep = &store->gen; + return 1; +} + +struct driver maildir_driver = { + maildir_parse_store, + maildir_open_store, + maildir_close_store, + maildir_list, + maildir_prepare, + maildir_select, + maildir_fetch_msg, + maildir_store_msg, + maildir_set_flags, + maildir_trash_msg, + maildir_check, + maildir_close +}; diff --git a/src/imap.c b/src/imap.c deleted file mode 100644 index 62fcaf6..0000000 --- a/src/imap.c +++ /dev/null @@ -1,1413 +0,0 @@ -/* $Id$ - * - * isync - IMAP4 to maildir mailbox synchronizer - * Copyright (C) 2000-2002 Michael R. Elkins - * Copyright (C) 2002-2004 Oswald Buddenhagen - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * As a special exception, isync may be linked with the OpenSSL library, - * despite that library's more restrictive license. - */ - -#include "isync.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if HAVE_LIBSSL -# include -#endif - -#define as(ar) (sizeof(ar)/sizeof(ar[0])) - -#define CAP(cap) (imap->caps & (1 << (cap))) - -static int Tag; - -const char *Flags[] = { - "\\Seen", - "\\Answered", - "\\Deleted", - "\\Flagged", - "\\Recent", - "\\Draft" -}; - -void -free_message (message_t * msg) -{ - message_t *tmp; - - while (msg) - { - tmp = msg; - msg = msg->next; - if (tmp->file) - free (tmp->file); - free (tmp); - } -} - -#if HAVE_LIBSSL - -#define MAX_DEPTH 1 - -SSL_CTX *SSLContext = 0; - -/* this gets called when a certificate is to be verified */ -static int -verify_cert (SSL * ssl) -{ - X509 *cert; - int err; - char buf[256]; - int ret = -1; - BIO *bio; - - cert = SSL_get_peer_certificate (ssl); - if (!cert) - { - fprintf (stderr, "Error, no server certificate\n"); - return -1; - } - - err = SSL_get_verify_result (ssl); - if (err == X509_V_OK) - return 0; - - fprintf (stderr, "Error, can't verify certificate: %s (%d)\n", - X509_verify_cert_error_string (err), err); - - X509_NAME_oneline (X509_get_subject_name (cert), buf, sizeof (buf)); - info ("\nSubject: %s\n", buf); - X509_NAME_oneline (X509_get_issuer_name (cert), buf, sizeof (buf)); - info ("Issuer: %s\n", buf); - bio = BIO_new (BIO_s_mem ()); - ASN1_TIME_print (bio, X509_get_notBefore (cert)); - memset (buf, 0, sizeof (buf)); - BIO_read (bio, buf, sizeof (buf) - 1); - info ("Valid from: %s\n", buf); - ASN1_TIME_print (bio, X509_get_notAfter (cert)); - memset (buf, 0, sizeof (buf)); - BIO_read (bio, buf, sizeof (buf) - 1); - BIO_free (bio); - info (" to: %s\n", buf); - - fprintf (stderr, - "\n*** WARNING *** There is no way to verify this certificate. It is\n" - " possible that a hostile attacker has replaced the\n" - " server certificate. Continue at your own risk!\n" - "\nAccept this certificate anyway? [no]: "); - if (fgets (buf, sizeof (buf), stdin) && (buf[0] == 'y' || buf[0] == 'Y')) - { - ret = 0; - fprintf (stderr, "\n*** Fine, but don't say I didn't warn you!\n\n"); - } - return ret; -} - -static int -init_ssl (config_t * conf) -{ - SSL_METHOD *method; - int options = 0; - - if (!conf->cert_file) - { - fprintf (stderr, "Error, CertificateFile not defined\n"); - return -1; - } - SSL_library_init (); - SSL_load_error_strings (); - - if (conf->use_tlsv1 && !conf->use_sslv2 && !conf->use_sslv3) - method = TLSv1_client_method (); - else - method = SSLv23_client_method (); - - SSLContext = SSL_CTX_new (method); - - if (access (conf->cert_file, R_OK)) - { - if (errno != ENOENT) - { - perror ("access"); - return -1; - } - warn ("*** Warning: CertificateFile doesn't exist, can't verify server certificates\n"); - } - else - if (!SSL_CTX_load_verify_locations - (SSLContext, conf->cert_file, NULL)) - { - fprintf (stderr, "Error, SSL_CTX_load_verify_locations: %s\n", - ERR_error_string (ERR_get_error (), 0)); - return -1; - } - - if (!conf->use_sslv2) - options |= SSL_OP_NO_SSLv2; - if (!conf->use_sslv3) - options |= SSL_OP_NO_SSLv3; - if (!conf->use_tlsv1) - options |= SSL_OP_NO_TLSv1; - - SSL_CTX_set_options (SSLContext, options); - - /* we check the result of the verification after SSL_connect() */ - SSL_CTX_set_verify (SSLContext, SSL_VERIFY_NONE, 0); - return 0; -} -#endif /* HAVE_LIBSSL */ - -static int -socket_read (Socket_t * sock, char *buf, size_t len) -{ -#if HAVE_LIBSSL - if (sock->use_ssl) - return SSL_read (sock->ssl, buf, len); -#endif - return read (sock->fd, buf, len); -} - -static int -socket_write (Socket_t * sock, char *buf, size_t len) -{ -#if HAVE_LIBSSL - if (sock->use_ssl) - return SSL_write (sock->ssl, buf, len); -#endif - return write (sock->fd, buf, len); -} - -static void -socket_perror (const char *func, Socket_t *sock, int ret) -{ -#if HAVE_LIBSSL - int err; - - if (sock->use_ssl) - { - switch ((err = SSL_get_error (sock->ssl, ret))) - { - case SSL_ERROR_SYSCALL: - case SSL_ERROR_SSL: - if ((err = ERR_get_error ()) == 0) - { - if (ret == 0) - fprintf (stderr, "SSL_%s:got EOF\n", func); - else - fprintf (stderr, "SSL_%s:%d:%s\n", func, - errno, strerror (errno)); - } - else - fprintf (stderr, "SSL_%s:%d:%s\n", func, err, - ERR_error_string (err, 0)); - return; - default: - fprintf (stderr, "SSL_%s:%d:unhandled SSL error\n", func, err); - break; - } - return; - } -#else - (void) sock; -#endif - if (ret) - perror (func); - else - fprintf (stderr, "%s: unexpected EOF\n", func); -} - -/* simple line buffering */ -static int -buffer_gets (buffer_t * b, char **s) -{ - int n; - int start = b->offset; - - *s = b->buf + start; - - for (;;) - { - /* make sure we have enough data to read the \r\n sequence */ - if (b->offset + 1 >= b->bytes) - { - if (start != 0) - { - /* shift down used bytes */ - *s = b->buf; - - assert (start <= b->bytes); - n = b->bytes - start; - - if (n) - memmove (b->buf, b->buf + start, n); - b->offset -= start; - b->bytes = n; - start = 0; - } - - n = - socket_read (b->sock, b->buf + b->bytes, - sizeof (b->buf) - b->bytes); - - if (n <= 0) - { - socket_perror ("read", b->sock, n); - return -1; - } - - b->bytes += n; - } - - if (b->buf[b->offset] == '\r') - { - assert (b->offset + 1 < b->bytes); - if (b->buf[b->offset + 1] == '\n') - { - b->buf[b->offset] = 0; /* terminate the string */ - b->offset += 2; /* next line */ - if (Verbose) { - puts (*s); - fflush (stdout); - } - return 0; - } - } - - b->offset++; - } - /* not reached */ -} - -static int -parse_fetch (imap_t *imap, char *cmd) -{ - list_t *tmp, *list, *flags; - unsigned int uid = 0; - unsigned int mask = 0; - unsigned int size = 0; - message_t *cur; - - list = parse_list (cmd, 0); - - if (!is_list (list)) { - free_list (list); - return -1; - } - - for (tmp = list->child; tmp; tmp = tmp->next) - { - if (is_atom (tmp)) - { - if (!strcmp ("UID", tmp->val)) - { - tmp = tmp->next; - if (is_atom (tmp)) - { - uid = atoi (tmp->val); - if (uid < imap->minuid) - { - /* already saw this message */ - free_list (list); - return 0; - } - else if (uid > imap->maxuid) - imap->maxuid = uid; - } - else - fprintf (stderr, "IMAP error: unable to parse UID\n"); - } - else if (!strcmp ("FLAGS", tmp->val)) - { - tmp = tmp->next; - if (is_list (tmp)) - { - for (flags = tmp->child; flags; flags = flags->next) - { - if (is_atom (flags)) - { - if (!strcmp ("\\Seen", flags->val)) - mask |= D_SEEN; - else if (!strcmp ("\\Flagged", flags->val)) - mask |= D_FLAGGED; - else if (!strcmp ("\\Deleted", flags->val)) - mask |= D_DELETED; - else if (!strcmp ("\\Answered", flags->val)) - mask |= D_ANSWERED; - else if (!strcmp ("\\Draft", flags->val)) - mask |= D_DRAFT; - else if (!strcmp ("\\Recent", flags->val)) - mask |= D_RECENT; - else - fprintf (stderr, "IMAP error: unknown flag %s\n", - flags->val); - } - else - fprintf (stderr, "IMAP error: unable to parse FLAGS list\n"); - } - } - else - fprintf (stderr, "IMAP error: unable to parse FLAGS\n"); - } - else if (!strcmp ("RFC822.SIZE", tmp->val)) - { - tmp = tmp->next; - if (is_atom (tmp)) - size = atol (tmp->val); - } - } - } - - cur = calloc (1, sizeof (message_t)); - cur->next = imap->msgs; - imap->msgs = cur; - - if (mask & D_DELETED) - imap->deleted++; - - cur->uid = uid; - cur->flags = mask; - cur->size = size; - - free_list (list); - return 0; -} - -/* Keep this in sync with enum CAPABILITY */ -const char *cap_list[] = { - "LOGINDISABLED", - "UIDPLUS", - "NAMESPACE", -#if HAVE_LIBSSL - "AUTH=CRAM-MD5", - "STARTTLS", -#endif -}; - -static void -parse_capability (imap_t *imap, char *cmd) -{ - char *arg; - unsigned i; - - imap->caps = 0x80000000; - while ((arg = next_arg (&cmd))) - for (i = 0; i < as(cap_list); i++) - if (!strcmp (cap_list[i], arg)) - imap->caps |= 1 << i; -} - -static void -parse_response_code (imap_t * imap, char *s) -{ - char *arg, *p; - - if (*s != '[') - return; /* no response code */ - s++; - if (!(p = strchr (s, ']'))) - { - fprintf (stderr, "IMAP error: malformed response code\n"); - return; - } - *p++ = 0; - - arg = next_arg (&s); - - if (!strcmp ("UIDVALIDITY", arg)) - { - arg = next_arg (&s); - imap->uidvalidity = atol (arg); - } - else if (!strcmp ("CAPABILITY", arg)) - { - parse_capability (imap, s); - } - else if (!strcmp ("ALERT", arg)) - { - /* RFC2060 says that these messages MUST be displayed - * to the user - */ - for (; isspace ((unsigned char)*p); p++); - fprintf (stderr, "*** IMAP ALERT *** %s\n", p); - } -} - -static int -imap_exec (imap_t * imap, const char *fmt, ...) -{ - va_list ap; - char tmp[256]; - char buf[256]; - char *cmd; - char *arg; - char *arg1; - config_t *box; - int n; - - va_start (ap, fmt); - vsnprintf (tmp, sizeof (tmp), fmt, ap); - va_end (ap); - - snprintf (buf, sizeof (buf), "%d %s\r\n", ++Tag, tmp); - if (Verbose) { - printf (">>> %s", buf); - fflush (stdout); - } - n = socket_write (imap->sock, buf, strlen (buf)); - if (n <= 0) - { - socket_perror ("write", imap->sock, n); - return -1; - } - - for (;;) - { - next: - if (buffer_gets (imap->buf, &cmd)) - return -1; - - arg = next_arg (&cmd); - if (*arg == '*') - { - arg = next_arg (&cmd); - if (!arg) - { - fprintf (stderr, "IMAP error: unable to parse untagged response\n"); - return -1; - } - - if (!strcmp ("NAMESPACE", arg)) - { - imap->ns_personal = parse_list (cmd, &cmd); - imap->ns_other = parse_list (cmd, &cmd); - imap->ns_shared = parse_list (cmd, 0); - } - else if (!strcmp ("OK", arg) || !strcmp ("BAD", arg) || - !strcmp ("NO", arg) || !strcmp ("BYE", arg)) - { - parse_response_code (imap, cmd); - } - else if (!strcmp ("CAPABILITY", arg)) - { - parse_capability (imap, cmd); - } - else if (!strcmp ("LIST", arg)) - { - list_t *list, *lp; - int l; - - list = parse_list (cmd, &cmd); - if (list->val == LIST) - for (lp = list->child; lp; lp = lp->next) - if (is_atom (lp) && - !strcasecmp (lp->val, "\\NoSelect")) - { - free_list (list); - goto next; - } - free_list (list); - (void) next_arg (&cmd); /* skip delimiter */ - arg = next_arg (&cmd); - l = strlen (global.folder); - if (memcmp (arg, global.folder, l)) - goto next; - arg += l; - if (!memcmp (arg + strlen (arg) - 5, ".lock", 5)) - goto next; - for (box = boxes; box; box = box->next) - if (!strcmp (box->box, arg)) - goto next; - box = malloc (sizeof (config_t)); - memcpy (box, &global, sizeof (config_t)); - box->path = strdup (arg); - box->box = box->path; - box->next = boxes; - boxes = box; - } - else if ((arg1 = next_arg (&cmd))) - { - if (!strcmp ("EXISTS", arg1)) - imap->count = atoi (arg); - else if (!strcmp ("RECENT", arg1)) - imap->recent = atoi (arg); - else if (!strcmp ("FETCH", arg1)) - { - if (parse_fetch (imap, cmd)) - return -1; - } - } - else - { - fprintf (stderr, "IMAP error: unable to parse untagged response\n"); - return -1; - } - } -#if HAVE_LIBSSL - else if (*arg == '+') - { - char *resp; - - if (!imap->cram) - { - fprintf (stderr, "IMAP error, not doing CRAM-MD5 authentication\n"); - return -1; - } - resp = cram (cmd, imap->box->user, imap->box->pass); - - if (Verbose) { - printf (">+> %s\n", resp); - fflush (stdout); - } - n = socket_write (imap->sock, resp, strlen (resp)); - free (resp); - if (n <= 0) - { - socket_perror ("write", imap->sock, n); - return -1; - } - n = socket_write (imap->sock, "\r\n", 2); - if (n <= 0) - { - socket_perror ("write", imap->sock, n); - return -1; - } - imap->cram = 0; - } -#endif - else if (atoi (arg) != Tag) - { - fprintf (stderr, "IMAP error: wrong tag\n"); - return -1; - } - else - { - arg = next_arg (&cmd); - parse_response_code (imap, cmd); - if (!strcmp ("OK", arg)) - return 0; - return -1; - } - } - /* not reached */ -} - -#ifdef HAVE_LIBSSL -static int -start_tls (imap_t *imap, config_t * cfg) -{ - int ret; - - /* initialize SSL */ - if (init_ssl (cfg)) - return 1; - - imap->sock->ssl = SSL_new (SSLContext); - SSL_set_fd (imap->sock->ssl, imap->sock->fd); - if ((ret = SSL_connect (imap->sock->ssl)) <= 0) - { - socket_perror ("connect", imap->sock, ret); - return 1; - } - - /* verify the server certificate */ - if (verify_cert (imap->sock->ssl)) - return 1; - - imap->sock->use_ssl = 1; - puts ("SSL support enabled"); - return 0; -} -#endif - -imap_t * -imap_connect (config_t * cfg) -{ - int s; - struct sockaddr_in addr; - struct hostent *he; - imap_t *imap; - char *arg, *rsp; - int preauth; -#if HAVE_LIBSSL - int use_ssl; -#endif - int a[2]; - - imap = calloc (1, sizeof (imap_t)); - imap->box = cfg; - imap->sock = calloc (1, sizeof (Socket_t)); - imap->buf = calloc (1, sizeof (buffer_t)); - imap->buf->sock = imap->sock; - imap->sock->fd = -1; - - /* open connection to IMAP server */ - - if (cfg->tunnel) - { - info ("Starting tunnel '%s'...", cfg->tunnel); - fflush (stdout); - - if (socketpair (PF_UNIX, SOCK_STREAM, 0, a)) - { - perror ("socketpair"); - exit (1); - } - - if (fork () == 0) - { - if (dup2 (a[0], 0) == -1 || dup2 (a[0], 1) == -1) - { - _exit (127); - } - close (a[0]); - close (a[1]); - execl ("/bin/sh", "sh", "-c", cfg->tunnel, 0); - _exit (127); - } - - close (a[0]); - - imap->sock->fd = a[1]; - - info ("ok\n"); - } - else - { - memset (&addr, 0, sizeof (addr)); - addr.sin_port = htons (cfg->port); - addr.sin_family = AF_INET; - - info ("Resolving %s... ", cfg->host); - fflush (stdout); - he = gethostbyname (cfg->host); - if (!he) - { - perror ("gethostbyname"); - goto bail; - } - info ("ok\n"); - - addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]); - - s = socket (PF_INET, SOCK_STREAM, 0); - - info ("Connecting to %s:%hu... ", inet_ntoa (addr.sin_addr), - ntohs (addr.sin_port)); - fflush (stdout); - if (connect (s, (struct sockaddr *) &addr, sizeof (addr))) - { - close (s); - perror ("connect"); - goto bail; - } - info ("ok\n"); - - imap->sock->fd = s; - } - -#if HAVE_LIBSSL - use_ssl = 0; - if (cfg->use_imaps) { - if (start_tls (imap, cfg)) - goto bail; - use_ssl = 1; - } -#endif - - /* read the greeting string */ - if (buffer_gets (imap->buf, &rsp)) - { - fprintf (stderr, "IMAP error: no greeting response\n"); - goto bail; - } - arg = next_arg (&rsp); - if (!arg || *arg != '*' || (arg = next_arg (&rsp)) == NULL) - { - fprintf (stderr, "IMAP error: invalid greeting response\n"); - goto bail; - } - preauth = 0; - if (!strcmp ("PREAUTH", arg)) - preauth = 1; - else if (strcmp ("OK", arg) != 0) - { - fprintf (stderr, "IMAP error: unknown greeting response\n"); - goto bail; - } - parse_response_code (imap, rsp); - if (!imap->caps && imap_exec (imap, "CAPABILITY")) - goto bail; - - if (!preauth) - { - -#if HAVE_LIBSSL - if (!cfg->use_imaps) - { - if (cfg->use_sslv2 || cfg->use_sslv3 || cfg->use_tlsv1) - { - /* always try to select SSL support if available */ - if (CAP(STARTTLS)) - { - if (imap_exec (imap, "STARTTLS")) - goto bail; - if (start_tls (imap, cfg)) - goto bail; - use_ssl = 1; - - if (imap_exec (imap, "CAPABILITY")) - goto bail; - } - else - { - if (cfg->require_ssl) - { - fprintf (stderr, "IMAP error: SSL support not available\n"); - goto bail; - } - else - warn ("IMAP warning: SSL support not available\n"); - } - } - } -#endif - - info ("Logging in...\n"); - - if (!cfg->pass) - { - /* - * if we don't have a global password set, prompt the user for - * it now. - */ - if (!global.pass) - { - char prompt[80]; - sprintf(prompt, "Password (mailbox %s@%s/%s): ", - cfg->user, cfg->host, cfg->box); - global.pass = getpass (prompt); - if (!global.pass) - { - perror ("getpass"); - exit (1); - } - if (!*global.pass) - { - fprintf (stderr, "Skipping %s, no password\n", cfg->path); - global.pass = NULL; /* force retry */ - goto bail; - } - /* - * getpass() returns a pointer to a static buffer. make a copy - * for long term storage. - */ - global.pass = strdup (global.pass); - } - cfg->pass = strdup (global.pass); - } - -#if HAVE_LIBSSL - if (CAP(CRAM)) - { - info ("Authenticating with CRAM-MD5\n"); - imap->cram = 1; - if (imap_exec (imap, "AUTHENTICATE CRAM-MD5")) - goto bail; - } - else if (imap->box->require_cram) - { - fprintf (stderr, "IMAP error: CRAM-MD5 authentication is not supported by server\n"); - goto bail; - } - else -#endif - { - if (CAP(NOLOGIN)) - { - fprintf (stderr, "Skipping %s, server forbids LOGIN\n", cfg->path); - goto bail; - } -#if HAVE_LIBSSL - if (!use_ssl) -#endif - warn ("*** IMAP Warning *** Password is being sent in the clear\n"); - if (imap_exec (imap, "LOGIN \"%s\" \"%s\"", cfg->user, cfg->pass)) - { - fprintf (stderr, "IMAP error: LOGIN failed\n"); - goto bail; - } - } - } /* !preauth */ - - /* get NAMESPACE info */ - if (!global.folder && cfg->use_namespace && CAP(NAMESPACE)) - { - if (imap_exec (imap, "NAMESPACE")) - goto bail; - } - return imap; - - bail: - imap_close (imap); - return 0; -} - -static int -mstrcmp (const char *s1, const char *s2) -{ - if (s1 == s2) - return 0; - if (!s1 || !s2) - return 1; - return strcmp (s1, s2); -} - -/* `box' is the config info for the maildrop to sync. `minuid' is the - * minimum UID to consider. in normal mode this will be 1, but in --fast - * mode we only fetch messages newer than the last one seen in the local - * mailbox. - */ -imap_t * -imap_open (config_t * box, unsigned int minuid, imap_t * imap, int imap_flags) -{ - if (imap) - { - /* determine whether or not we can reuse the existing session */ - if (mstrcmp (box->tunnel, imap->box->tunnel) || - mstrcmp (box->host, imap->box->host) || - mstrcmp (box->user, imap->box->user) || - box->port != imap->box->port -#if HAVE_LIBSSL - /* ensure that security requirements are met */ - || (box->require_ssl ^ imap->box->require_ssl) - || (box->require_cram ^ imap->box->require_cram) -#endif - ) - { - /* can't reuse */ - imap_close (imap); - } - else - { - /* reset mailbox-specific state info */ - imap->box = box; - imap->recent = 0; - imap->deleted = 0; - imap->count = 0; - imap->maxuid = 0; - free_message (imap->msgs); - imap->msgs = 0; - goto gotimap; - } - } - if (!(imap = imap_connect (box))) - return 0; - gotimap: - - if (global.folder) - imap->prefix = !strcmp (box->box, "INBOX") ? "" : global.folder; - else - { - imap->prefix = ""; - /* XXX for now assume personal namespace */ - if (imap->box->use_namespace && - is_list (imap->ns_personal) && - is_list (imap->ns_personal->child) && - is_atom (imap->ns_personal->child->child)) - imap->prefix = imap->ns_personal->child->child->val; - } - - info ("Selecting IMAP mailbox... "); - fflush (stdout); - if (imap_exec (imap, "SELECT \"%s%s\"", imap->prefix, box->box)) { - if (imap_flags & IMAP_CREATE) { - if (imap_exec (imap, "CREATE \"%s%s\"", imap->prefix, box->box)) - goto bail; - if (imap_exec (imap, "SELECT \"%s%s\"", imap->prefix, box->box)) - goto bail; - } else - goto bail; - } - info ("%d messages, %d recent\n", imap->count, imap->recent); - - info ("Reading IMAP mailbox index\n"); - imap->minuid = minuid; - if (imap->count > 0) - { - if (imap_exec (imap, "UID FETCH %d:* (FLAGS%s)", minuid, - (imap_flags & IMAP_GET_SIZE) ? " RFC822.SIZE" : "")) - goto bail; - } - - return imap; - - bail: - imap_close (imap); - return 0; -} - -void -imap_close (imap_t * imap) -{ - if (imap) - { - if (imap->sock->fd != -1) - { - imap_exec (imap, "LOGOUT"); - close (imap->sock->fd); - } - free (imap->sock); - free (imap->buf); - free_message (imap->msgs); - memset (imap, 0xff, sizeof (imap_t)); - free (imap); - } -} - -/* write a buffer stripping all \r bytes */ -static int -write_strip (int fd, char *buf, size_t len) -{ - size_t start = 0; - size_t end = 0; - ssize_t n; - - while (start < len) - { - while (end < len && buf[end] != '\r') - end++; - n = write (fd, buf + start, end - start); - if (n == -1) - { - perror ("write"); - return -1; - } - else if ((size_t) n != end - start) - { - /* short write, try again */ - start += n; - } - else - { - /* write complete */ - end++; - start = end; - } - } - return 0; -} - -static int -send_server (Socket_t * sock, const char *fmt, ...) -{ - char buf[128]; - char cmd[128]; - va_list ap; - int n; - - va_start (ap, fmt); - vsnprintf (buf, sizeof (buf), fmt, ap); - va_end (ap); - - snprintf (cmd, sizeof (cmd), "%d %s\r\n", ++Tag, buf); - if (Verbose) { - printf (">>> %s", cmd); - fflush (stdout); - } - n = socket_write (sock, cmd, strlen (cmd)); - if (n <= 0) - { - socket_perror ("write", sock, n); - return -1; - } - - return 0; -} - -int -imap_fetch_message (imap_t * imap, unsigned int uid, int fd) -{ - char *cmd; - char *arg; - size_t bytes; - size_t n; - char buf[1024]; - - send_server (imap->sock, "UID FETCH %d BODY.PEEK[]", uid); - - for (;;) - { - if (buffer_gets (imap->buf, &cmd)) - return -1; - if (*cmd == '*') - { - /* need to figure out how long the message is - * * FETCH (RFC822 {} - */ - - next_arg (&cmd); /* * */ - next_arg (&cmd); /* */ - arg = next_arg (&cmd); /* FETCH */ - - if (strcasecmp ("FETCH", arg) != 0) - { - /* this is likely an untagged response, such as when new - * mail arrives in the middle of the session. just skip - * it for now. - * - * eg., - * "* 4000 EXISTS" - * "* 2 RECENT" - * - */ - info ("IMAP info: skipping untagged response: %s\n", arg); - continue; - } - - while ((arg = next_arg (&cmd)) && *arg != '{') - ; - if (!arg) - { - fprintf (stderr, "IMAP error: parse error getting size\n"); - return -1; - } - bytes = strtol (arg + 1, 0, 10); - - /* dump whats left over in the input buffer */ - n = imap->buf->bytes - imap->buf->offset; - - if (n > bytes) - { - /* the entire message fit in the buffer */ - n = bytes; - } - - /* ick. we have to strip out the \r\n line endings, so - * i can't just dump the raw bytes to disk. - */ - if (write_strip (fd, imap->buf->buf + imap->buf->offset, n)) - { - /* write failed, message is not delivered */ - return -1; - } - - bytes -= n; - - /* mark that we used part of the buffer */ - imap->buf->offset += n; - - /* now read the rest of the message */ - while (bytes > 0) - { - n = bytes; - if (n > sizeof (buf)) - n = sizeof (buf); - n = socket_read (imap->sock, buf, n); - if (n > 0) - { - if (write_strip (fd, buf, n)) - { - /* write failed */ - return -1; - } - bytes -= n; - } - else - { - socket_perror ("read", imap->sock, n); - return -1; - } - } - - buffer_gets (imap->buf, &cmd); - } - else - { - arg = next_arg (&cmd); - if (!arg || atoi (arg) != Tag) - { - fprintf (stderr, "IMAP error: wrong tag\n"); - return -1; - } - arg = next_arg (&cmd); - if (!strcmp ("OK", arg)) - return 0; - return -1; - } - } - /* not reached */ -} - -/* add flags to existing flags */ -int -imap_set_flags (imap_t * imap, unsigned int uid, unsigned int flags) -{ - char buf[256]; - int i; - - buf[0] = 0; - for (i = 0; i < D_MAX; i++) - { - if (flags & (1 << i)) - snprintf (buf + strlen (buf), - sizeof (buf) - strlen (buf), "%s%s", - (buf[0] != 0) ? " " : "", Flags[i]); - } - - return imap_exec (imap, "UID STORE %d +FLAGS.SILENT (%s)", uid, buf); -} - -int -imap_expunge (imap_t * imap) -{ - return imap_exec (imap, "EXPUNGE"); -} - -int -imap_copy_message (imap_t * imap, unsigned int uid, const char *mailbox) -{ - return imap_exec (imap, "UID COPY %u \"%s%s\"", uid, imap->prefix, - mailbox); -} - -int -imap_append_message (imap_t * imap, int fd, message_t * msg) -{ - char *fmap; - int extra, uid, tuidl = 0; - char flagstr[128], tuid[128]; - char *s; - size_t i; - size_t start; - size_t len, sbreak = 0, ebreak = 0; - char *arg; - struct timeval tv; - pid_t pid = getpid(); - - len = msg->size; - /* ugh, we need to count the number of newlines */ - fmap = (char *)mmap (0, len, PROT_READ, MAP_PRIVATE, fd, 0); - if (!fmap) - { - perror ("mmap"); - return -1; - } - - extra = 0, i = 0; - if (!CAP(UIDPLUS)) - { - nloop: - start = i; - while (i < len) - if (fmap[i++] == '\n') - { - extra++; - if (i - 1 == start) - { - sbreak = ebreak = i - 1; - goto mktid; - } - if (!memcmp (fmap + start, "X-TUID: ", 8)) - { - extra -= (ebreak = i) - (sbreak = start) + 1; - goto mktid; - } - goto nloop; - } - /* invalid mesasge */ - goto bail; - mktid: - gettimeofday (&tv, 0); - tuidl = sprintf (tuid, "X-TUID: %08lx%05lx%04x\r\n", - tv.tv_sec, tv.tv_usec, pid); - extra += tuidl; - } - for (; i < len; i++) - if (fmap[i] == '\n') - extra++; - - flagstr[0] = 0; - if (msg->flags) - { - if (msg->flags & D_DELETED) - strcat (flagstr," \\Deleted"); - if (msg->flags & D_ANSWERED) - strcat (flagstr," \\Answered"); - if (msg->flags & D_SEEN) - strcat (flagstr," \\Seen"); - if (msg->flags & D_FLAGGED) - strcat (flagstr," \\Flagged"); - if (msg->flags & D_DRAFT) - strcat (flagstr," \\Draft"); - flagstr[0] = '('; - strcat (flagstr,") "); - } - - send_server (imap->sock, "APPEND %s%s %s{%d}", - imap->prefix, imap->box->box, flagstr, len + extra); - - if (buffer_gets (imap->buf, &s)) - goto bail; - - if (*s != '+') - { - fprintf (stderr, "IMAP error: expected `+' from server (aborting)\n"); - goto bail; - } - - i = 0; - if (!CAP(UIDPLUS)) - { - n1loop: - start = i; - while (i < sbreak) - if (fmap[i++] == '\n') - { - socket_write (imap->sock, fmap + start, i - 1 - start); - socket_write (imap->sock, "\r\n", 2); - goto n1loop; - } - socket_write (imap->sock, tuid, tuidl); - i = ebreak; - } - n2loop: - start = i; - while (i < len) - if (fmap[i++] == '\n') - { - socket_write (imap->sock, fmap + start, i - 1 - start); - socket_write (imap->sock, "\r\n", 2); - goto n2loop; - } - socket_write (imap->sock, fmap + start, len - start); - socket_write (imap->sock, "\r\n", 2); - - munmap (fmap, len); - - for (;;) - { - if (buffer_gets (imap->buf, &s)) - return -1; - - arg = next_arg (&s); - if (*arg == '*') - { - /* XXX just ignore it for now */ - } - else if (atoi (arg) != Tag) - { - fprintf (stderr, "IMAP error: wrong tag\n"); - return -1; - } - else - { - arg = next_arg (&s); - if (strcmp (arg, "OK")) - return -1; - arg = next_arg (&s); - if (*arg != '[') - break; - arg++; - if (strcasecmp ("APPENDUID", arg)) - { - fprintf (stderr, "IMAP error: expected APPENDUID\n"); - break; - } - arg = next_arg (&s); - if (!arg) - break; - if (atoi (arg) != (int) imap->uidvalidity) - { - fprintf (stderr, "IMAP error: UIDVALIDITY doesn't match APPENDUID\n"); - return -1; - } - arg = next_arg (&s); - if (!arg) - break; - uid = strtol (arg, &s, 10); - if (*s != ']') - { - /* parse error */ - break; - } - return uid; - } - } - - /* didn't receive an APPENDUID */ - send_server (imap->sock, - "UID SEARCH HEADER X-TUID %08lx%05lx%04x", - tv.tv_sec, tv.tv_usec, pid); - uid = 0; - for (;;) - { - if (buffer_gets (imap->buf, &s)) - return -1; - - arg = next_arg (&s); - if (*arg == '*') - { - arg = next_arg (&s); - if (!strcmp (arg, "SEARCH")) - { - arg = next_arg (&s); - if (!arg) - fprintf (stderr, "IMAP error: incomplete SEARCH response\n"); - else - uid = atoi (arg); - } - } - else if (atoi (arg) != (int) Tag) - { - fprintf (stderr, "IMAP error: wrong tag\n"); - return -1; - } - else - { - arg = next_arg (&s); - if (strcmp (arg, "OK")) - return -1; - return uid; - } - } - - return 0; - - bail: - munmap (fmap, len); - return -1; -} - -int -imap_list (imap_t * imap) -{ - return imap_exec (imap, "LIST \"\" \"%s*\"", global.folder); -} - diff --git a/src/isync.h b/src/isync.h index a58f77a..097a5cb 100644 --- a/src/isync.h +++ b/src/isync.h @@ -1,6 +1,5 @@ -/* $Id$ - * - * isync - IMAP4 to maildir mailbox synchronizer +/* + * mbsync - mailbox synchronizer * Copyright (C) 2000-2002 Michael R. Elkins * Copyright (C) 2002-2004 Oswald Buddenhagen * @@ -18,219 +17,233 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * - * As a special exception, isync may be linked with the OpenSSL library, + * As a special exception, mbsync may be linked with the OpenSSL library, * despite that library's more restrictive license. */ +#define _GNU_SOURCE + #include #include +#include -#include +#define as(ar) (sizeof(ar)/sizeof(ar[0])) -#if HAVE_LIBSSL -# include +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) +# define ATTR_UNUSED __attribute__((unused)) +# define ATTR_NORETURN __attribute__((noreturn)) +# define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var))) +#else +# define ATTR_UNUSED +# define ATTR_NORETURN +# define ATTR_PRINTFLIKE(fmt,var) #endif -typedef struct -{ - int fd; -#if HAVE_LIBSSL - SSL *ssl; - unsigned int use_ssl:1; -#endif -} Socket_t; +#define EXE "mbsync" -typedef struct -{ - Socket_t *sock; - int bytes; - int offset; - char buf[1024]; -} -buffer_t; +typedef struct { + const char *file; + FILE *fp; + char *buf; + int bufl; + int line; + char *cmd, *val, *rest; +} conffile_t; -typedef struct config config_t; -typedef struct mailbox mailbox_t; -typedef struct message message_t; +#define OP_NEW (1<<0) +#define OP_RENEW (1<<1) +#define OP_DELETE (1<<2) +#define OP_FLAGS (1<<3) +#define OP_MASK_TYPE (OP_NEW|OP_RENEW|OP_DELETE|OP_FLAGS) /* asserted in the target ops */ +#define OP_EXPUNGE (1<<4) +#define OP_CREATE (1<<5) +#define XOP_PUSH (1<<6) +#define XOP_PULL (1<<7) +#define XOP_MASK_DIR (XOP_PUSH|XOP_PULL) +#define XOP_HAVE_TYPE (1<<8) +#define XOP_HAVE_EXPUNGE (1<<9) +#define XOP_HAVE_CREATE (1<<10) -struct config -{ - char *maildir; - char *path; /* path relative to .maildir, or absolute path */ - char *host; - int port; - char *user; - char *pass; - char *folder; - char *box; - char *inbox; - char *alias; - char *copy_deleted_to; - char *tunnel; - unsigned int max_messages; - off_t max_size; - config_t *next; -#if HAVE_LIBSSL - char *cert_file; - unsigned int use_imaps:1; - unsigned int require_ssl:1; - unsigned int use_sslv2:1; - unsigned int use_sslv3:1; - unsigned int use_tlsv1:1; - unsigned int require_cram:1; -#endif - unsigned int use_namespace:1; - unsigned int expunge:1; - unsigned int delete:1; - unsigned int wanted:1; +typedef struct driver driver_t; + +typedef struct store_conf { + struct store_conf *next; + char *name; + driver_t *driver; + const char *path; /* should this be here? its interpretation is driver-specific */ + char *map_inbox; + char *trash; + unsigned max_size; /* off_t is overkill */ + unsigned trash_remote_new:1, trash_only_new:1; +} store_conf_t; + +typedef struct string_list { + struct string_list *next; + char string[1]; +} string_list_t; + +typedef struct channel_conf { + struct channel_conf *next; + char *name; + store_conf_t *master, *slave; + char *master_name, *slave_name; + char *sync_state; + string_list_t *patterns; + int mops, sops; + unsigned max_messages; /* for slave only */ +} channel_conf_t; + +typedef struct group_conf { + struct group_conf *next; + char *name; + string_list_t *channels; +} group_conf_t; + +/* For message->flags */ +/* Keep the mailbox driver flag definitions in sync! */ +/* The order is according to alphabetical maildir flag sort */ +#define F_DRAFT (1<<0) /* Draft */ +#define F_FLAGGED (1<<1) /* Flagged */ +#define F_ANSWERED (1<<2) /* Replied */ +#define F_SEEN (1<<3) /* Seen */ +#define F_DELETED (1<<4) /* Trashed */ +#define NUM_FLAGS 5 + +/* For message->status */ +#define M_RECENT (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */ +#define M_DEAD (1<<1) /* expunged */ +#define M_FLAGS (1<<2) /* flags fetched */ +#define M_PROCESSED (1<<3) /* registered in pair */ +#define M_NOT_SYNCED (1<<4) /* not in remote mailbox, yet */ +#define M_EXPIRED (1<<5) /* kicked out by MaxMessages */ + +typedef struct message { + struct message *next; + /* string_list_t *keywords; */ + size_t size; /* zero implies "not fetched" */ + int uid; + unsigned char flags, status; +} message_t; + +/* For opts, both in store and driver_t->select() */ +#define OPEN_OLD (1<<0) +#define OPEN_NEW (1<<1) +#define OPEN_FLAGS (1<<2) +#define OPEN_SIZE (1<<3) +#define OPEN_CREATE (1<<4) +#define OPEN_EXPUNGE (1<<5) +#define OPEN_SETFLAGS (1<<6) +#define OPEN_APPEND (1<<7) + +typedef struct store { + store_conf_t *conf; /* foreign */ + + /* currently open mailbox */ + const char *name; /* foreign! maybe preset? */ + char *path; /* own */ + message_t *msgs; /* own */ + int uidvalidity; + unsigned char opts; /* maybe preset? */ + /* note that the following do _not_ reflect stats from msgs, but mailbox totals */ + int count; /* # of messages */ + int recent; /* # of recent messages - don't trust this beyond the initial read */ +} store_t; + +typedef struct { + char *data; + int len; + unsigned char flags; + unsigned char crlf:1; +} msg_data_t; + +#define DRV_OK 0 +#define DRV_MSG_BAD -1 +#define DRV_BOX_BAD -2 +#define DRV_STORE_BAD -3 + +struct driver { + int (*parse_store)( conffile_t *cfg, store_conf_t **storep, int *err ); + store_t *(*open_store)( store_conf_t *conf, store_t *oldctx ); + void (*close_store)( store_t *ctx ); + int (*list)( store_t *ctx, string_list_t **boxes ); + void (*prepare)( store_t *ctx, int opts ); + int (*select)( store_t *ctx, int minuid, int maxuid, int *excs, int nexcs ); + int (*fetch_msg)( store_t *ctx, message_t *msg, msg_data_t *data ); + int (*store_msg)( store_t *ctx, msg_data_t *data, int *uid ); /* if uid is null, store to trash */ + int (*set_flags)( store_t *ctx, message_t *msg, int uid, int add, int del ); /* msg can be null, therefore uid as a fallback */ + int (*trash_msg)( store_t *ctx, message_t *msg ); /* This may expunge the original message immediately, but it needn't to */ + int (*check)( store_t *ctx ); /* IMAP-style: flush */ + int (*close)( store_t *ctx ); /* IMAP-style: expunge inclusive */ }; -/* struct representing local mailbox file */ -struct mailbox -{ - DB *db; - char *path; - message_t *msgs; - int lockfd; - unsigned int deleted; /* # of deleted messages */ - unsigned int uidvalidity; - unsigned int maxuid; /* largest uid we know about */ - unsigned int uidseen : 1; /* flag indicating whether or not we saw a - valid value for UIDVALIDITY */ -}; -/* message dispositions */ -#define D_SEEN (1<<0) -#define D_ANSWERED (1<<1) -#define D_DELETED (1<<2) -#define D_FLAGGED (1<<3) -#define D_RECENT (1<<4) -#define D_DRAFT (1<<5) -#define D_MAX 6 +/* main.c */ -struct message -{ - char *file; - unsigned int uid; - unsigned int flags; - size_t size; - message_t *next; - unsigned int processed:1; /* message has already been evaluated */ - unsigned int new:1; /* message is in the new/ subdir */ - unsigned int dead:1; /* message doesn't exist on the server */ - unsigned int wanted:1; /* when using MaxMessages, keep this message */ -}; - -/* struct used for parsing IMAP lists */ -typedef struct _list list_t; - -#define NIL (void*)0x1 -#define LIST (void*)0x2 - -struct _list -{ - char *val; - list_t *next; - list_t *child; -}; - -/* imap connection info */ -typedef struct -{ - Socket_t *sock; - unsigned int count; /* # of msgs */ - unsigned int recent; /* # of recent messages */ - buffer_t *buf; /* input buffer for reading server output */ - message_t *msgs; /* list of messages on the server */ - config_t *box; /* mailbox to open */ - char *prefix; /* namespace prefix */ - unsigned int deleted; /* # of deleted messages */ - unsigned int uidvalidity; - unsigned int maxuid; - unsigned int minuid; - /* NAMESPACE info */ - list_t *ns_personal; - list_t *ns_other; - list_t *ns_shared; - unsigned int caps; -#if HAVE_LIBSSL - unsigned int cram:1; -#endif -} -imap_t; - -/* Keep in sync with cap_list */ -enum CAPABILITY { - NOLOGIN, - UIDPLUS, - NAMESPACE, -#if HAVE_LIBSSL - CRAM, - STARTTLS, -#endif -}; - -/* flags for sync_mailbox */ -#define SYNC_DELETE (1<<0) /* delete local that don't exist on server */ -#define SYNC_EXPUNGE (1<<1) /* don't fetch deleted messages */ - -/* flags for maildir_open */ -#define OPEN_FAST (1<<0) /* fast open - don't parse */ -#define OPEN_CREATE (1<<1) /* create mailbox if nonexistent */ - -/* flags for imap_open */ -#define IMAP_CREATE (1<<0) /* Create remote mailboxes if necessary */ -#define IMAP_GET_SIZE (1<<1) /* Request the RFC 822 SIZE */ - - -extern config_t global; -extern config_t *boxes; +extern int Pid; extern char Hostname[256]; -extern int Verbose, Quiet; +extern const char *Home; -extern void info (const char *, ...); -extern void infoc (char); -extern void warn (const char *, ...); -#if HAVE_LIBSSL -extern SSL_CTX *SSLContext; +/* util.c */ -char *cram (const char *, const char *, const char *); -#endif +extern int Verbose, Quiet, Debug; -char *next_arg (char **); +void debug( const char *, ... ); +void info( const char *, ... ); +void infoc( char ); +void warn( const char *, ... ); -int sync_mailbox (mailbox_t *, imap_t *, int, unsigned int, unsigned int); +char *next_arg( char ** ); -void load_config (const char *, int *); -char * expand_strdup (const char *s); -config_t *find_box (const char *); +void add_string_list( string_list_t **list, const char *str ); +void free_string_list( string_list_t *list ); -void imap_close (imap_t *); -int imap_copy_message (imap_t * imap, unsigned int uid, const char *mailbox); -int imap_fetch_message (imap_t *, unsigned int, int); -int imap_set_flags (imap_t *, unsigned int, unsigned int); -int imap_expunge (imap_t *); -imap_t *imap_connect (config_t *); -imap_t *imap_open (config_t *, unsigned int, imap_t *, int); -int imap_append_message (imap_t *, int, message_t *); -int imap_list (imap_t *); +void free_generic_messages( message_t * ); -mailbox_t *maildir_open (const char *, int flags); -int maildir_expunge (mailbox_t *, int); -int maildir_set_uidvalidity (mailbox_t *, unsigned int uidvalidity); -void maildir_close (mailbox_t *); -int maildir_update_maxuid (mailbox_t * mbox); +void strip_cr( msg_data_t *msgdata ); -message_t * find_msg (message_t * list, unsigned int uid); -void free_message (message_t *); +void *nfmalloc( size_t sz ); +void *nfcalloc( size_t sz ); +void *nfrealloc( void *mem, size_t sz ); +char *nfstrdup( const char *str ); +int nfvasprintf( char **str, const char *fmt, va_list va ); +int nfasprintf( char **str, const char *fmt, ... ); +int nfsnprintf( char *buf, int blen, const char *fmt, ... ); +void ATTR_NORETURN oob( void ); -/* parse an IMAP list construct */ -list_t * parse_list (char *s, char **end); -int is_atom (list_t *list); -int is_list (list_t *list); -int is_nil (list_t *list); -void free_list (list_t *list); +char *expand_strdup( const char *s ); -#define strfcpy(a,b,c) {strncpy(a,b,c);(a)[c-1]=0;} +void sort_ints( int *arr, int len ); + +void arc4_init( void ); +unsigned char arc4_getbyte( void ); + +/* sync.c */ + +#define SYNC_OK 0 +#define SYNC_FAIL 1 +#define SYNC_MASTER_BAD 2 +#define SYNC_SLAVE_BAD 3 + +int sync_boxes( store_t *, const char *, + store_t *, const char *, + channel_conf_t * ); + +/* config.c */ + +extern channel_conf_t *channels; +extern group_conf_t *groups; +extern int global_mops, global_sops; +extern char *global_sync_state; + +int parse_bool( conffile_t *cfile ); +int parse_int( conffile_t *cfile ); +int parse_size( conffile_t *cfile ); +int getcline( conffile_t *cfile ); +int merge_ops( int cops, int *mops, int *sops ); +int load_config( const char *filename, int pseudo ); +void parse_generic_store( store_conf_t *store, conffile_t *cfg, int *err ); + +/* drv_*.c */ +extern driver_t maildir_driver, imap_driver; diff --git a/src/list.c b/src/list.c deleted file mode 100644 index 8886710..0000000 --- a/src/list.c +++ /dev/null @@ -1,179 +0,0 @@ -/* $Id$ - * - * isync - IMAP4 to maildir mailbox synchronizer - * Copyright (C) 2000-2002 Michael R. Elkins - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * As a special exception, isync may be linked with the OpenSSL library, - * despite that library's more restrictive license. - */ - -#include "isync.h" - -#include -#include -#include - -static char * -skip_string (char *s) -{ - while (*s && *s != '"') - s++; - return s; -} - -list_t * -parse_list (char *s, char **end) -{ - int level = 1; - list_t *cur; - list_t **list; - char *b; - - cur = calloc (1, sizeof (list_t)); - while (isspace ((unsigned char) *s)) - s++; - if (*s == '(') - { - /* start of list. find the end of the list */ - s++; - b = s; /* save beginning */ - cur->val = LIST; - while (*s) - { - if (*s == '(') - { - level++; - } - else if (*s == ')') - { - level--; - if (level == 0) - break; - } - else if (*s == '"') - { - s = skip_string (s + 1); - if (!*s) - { - /* parse error */ - free (cur); - return NULL; - } - } - s++; - } - if (level != 0) - { - free (cur); /* parse error */ - return NULL; - } - *s++ = 0; - - list = &cur->child; - while (*b) - { - *list = parse_list (b, &b); - if (*list == NULL) - { - /* parse error */ - free (cur); - return NULL; - } - while (*list) - list = &(*list)->next; - } - } - else if (*s == '"') - { - /* quoted string */ - s++; - cur->val = s; - s = skip_string (s); - if (!*s) - { - /* parse error */ - free (cur); - return NULL; - } - *s++ = 0; - cur->val = strdup (cur->val); - } - else - { - /* atom */ - cur->val = s; - while (*s && !isspace ((unsigned char) *s)) - s++; - if (*s) - *s++ = 0; - if (strcmp ("NIL", cur->val)) - cur->val = strdup (cur->val); - else - cur->val = NIL; - } - if (end) - *end = s; - return cur; -} - -int -is_atom (list_t * list) -{ - return (list && list->val && list->val != NIL && list->val != LIST); -} - -int -is_list (list_t * list) -{ - return (list && list->val == LIST); -} - -int -is_nil (list_t * list) -{ - return (list && list->val == NIL); -} - -void -free_list (list_t * list) -{ - list_t *tmp; - - while (list) - { - tmp = list; - list = list->next; - if (is_list (tmp)) - free_list (tmp->child); - else if (is_atom (tmp)) - free (tmp->val); - free (tmp); - } -} - -#if TEST -int -main (int argc, char **argv) -{ - char buf[256]; - list_t *list; - - strcpy (buf, - "((compound list) atom NIL \"string with a (\" (another list))"); - list = parse_list (buf, 0); -} -#endif diff --git a/src/maildir.c b/src/maildir.c deleted file mode 100644 index 19cef25..0000000 --- a/src/maildir.c +++ /dev/null @@ -1,459 +0,0 @@ -/* $Id$ - * - * isync - IMAP4 to maildir mailbox synchronizer - * Copyright (C) 2000-2002 Michael R. Elkins - * Copyright (C) 2002-2003 Oswald Buddenhagen - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * As a special exception, isync may be linked with the OpenSSL library, - * despite that library's more restrictive license. - */ - -#include "isync.h" -#include "dotlock.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* 2, */ -static void -parse_info (message_t * m, char *s) -{ - if (*s == '2' && *(s + 1) == ',') - { - s += 2; - while (*s) - { - if (*s == 'F') - m->flags |= D_FLAGGED; - else if (*s == 'R') - m->flags |= D_ANSWERED; - else if (*s == 'T') - m->flags |= D_DELETED; - else if (*s == 'S') - m->flags |= D_SEEN; - s++; - } - } -} - -/* - * There are three possible results of this function: - * >1 uid was already seen - * 0 uid was not yet seen - * -1 unable to read uid because of some other error - */ - -static int -read_uid (const char *path, const char *file, unsigned int *uid /* out */) -{ - char full[_POSIX_PATH_MAX]; - int fd; - int ret = -1; - int len; - char buf[64], *ptr; - - snprintf (full, sizeof (full), "%s/%s", path, file); - fd = open (full, O_RDONLY); - if (fd == -1) - { - if (errno != ENOENT) - { - perror (full); - return -1; - } - return 0; /* doesn't exist */ - } - len = read (fd, buf, sizeof (buf) - 1); - if (len == -1) - perror ("read"); - else - { - buf[len] = 0; - errno = 0; - *uid = strtoul (buf, &ptr, 10); - if (errno) - perror ("strtoul"); - else if (ptr && *ptr == '\n') - ret = 1; - /* else invalid value */ - } - close (fd); - return ret; -} - -/* - * open a maildir mailbox. - * if OPEN_FAST is set, we just check to make - * sure its a valid mailbox and don't actually parse it. any IMAP messages - * with the \Recent flag set are guaranteed not to be in the mailbox yet, - * so we can save a lot of time when the user just wants to fetch new messages - * without syncing the flags. - * if OPEN_CREATE is set, we create the mailbox if it doesn't already exist. - */ -mailbox_t * -maildir_open (const char *path, int flags) -{ - char buf[_POSIX_PATH_MAX]; - DIR *d; - struct dirent *e; - message_t **cur; - message_t *p; - mailbox_t *m; - char *s; - int count = 0; - struct stat sb; - const char *subdirs[] = { "cur", "new", "tmp" }; - int i, ret; - DBT key, value; - - m = calloc (1, sizeof (mailbox_t)); - m->lockfd = -1; - /* filename expansion happens here, not in the config parser */ - m->path = expand_strdup (path); - - if (stat (m->path, &sb)) - { - if (errno == ENOENT && (flags & OPEN_CREATE)) - { - if (mkdir (m->path, S_IRUSR | S_IWUSR | S_IXUSR)) - { - fprintf (stderr, "ERROR: mkdir %s: %s (errno %d)\n", - m->path, strerror (errno), errno); - goto err; - } - - for (i = 0; i < 3; i++) - { - snprintf (buf, sizeof (buf), "%s/%s", m->path, subdirs[i]); - if (mkdir (buf, S_IRUSR | S_IWUSR | S_IXUSR)) - { - fprintf (stderr, "ERROR: mkdir %s: %s (errno %d)\n", - buf, strerror (errno), errno); - goto err; - } - } - - } - else - { - fprintf (stderr, "ERROR: stat %s: %s (errno %d)\n", m->path, - strerror (errno), errno); - goto err; - } - } - else - { - /* check to make sure this looks like a valid maildir box */ - for (i = 0; i < 3; i++) - { - snprintf (buf, sizeof (buf), "%s/%s", m->path, subdirs[i]); - if (stat (buf, &sb)) - { - fprintf (stderr, "ERROR: stat %s: %s (errno %d)\n", buf, - strerror (errno), errno); - fprintf (stderr, - "ERROR: %s does not appear to be a valid maildir style mailbox\n", - m->path); - goto err; - } - } - } - - /* - * we need a mutex on the maildir because of the state files that isync - * uses. - */ - snprintf (buf, sizeof (buf), "%s/isynclock", m->path); - if (dotlock_lock (buf, &m->lockfd)) - goto err; - - /* check for the uidvalidity value */ - i = read_uid (m->path, "isyncuidvalidity", &m->uidvalidity); - if (i == -1) - goto err; - else if (i > 0) - m->uidseen = 1; - - /* load the current maxuid */ - if (read_uid (m->path, "isyncmaxuid", &m->maxuid) == -1) - goto err; - - snprintf (buf, sizeof (buf), "%s/isyncuidmap.db", m->path); - if (db_create (&m->db, 0, 0)) { - fputs ("dbcreate failed\n", stderr); - goto err; - } - if ((ret = m->db->set_pagesize (m->db, 4096)) != 0 || - (ret = m->db->set_h_ffactor (m->db, 40)) != 0 || - (ret = m->db->set_h_nelem (m->db, 1)) != 0) { - fputs ("Error configuring database\n", stderr); - goto err; - } - m->db->open (m->db, buf, 0, DB_HASH, DB_CREATE, S_IRUSR | S_IWUSR); - if (m->db == NULL) - { - fputs ("ERROR: unable to open UID db\n", stderr); - goto err; - } - - if (flags & OPEN_FAST) - return m; - - cur = &m->msgs; - for (; count < 2; count++) - { - /* read the msgs from the new subdir */ - snprintf (buf, sizeof (buf), "%s/%s", m->path, - (count == 0) ? "new" : "cur"); - d = opendir (buf); - if (!d) - { - perror ("opendir"); - goto err; - } - while ((e = readdir (d))) - { - if (*e->d_name == '.') - continue; /* skip dot-files */ - *cur = calloc (1, sizeof (message_t)); - p = *cur; - p->file = strdup (e->d_name); - p->uid = -1; - p->flags = 0; - p->new = (count == 0); - - /* determine the UID for this message. The basename (sans - * flags) is used as the key in the db - */ - memset (&key, 0, sizeof(key)); - memset (&value, 0, sizeof(value)); - key.data = p->file; - s = strchr (p->file, ':'); - key.size = s ? (size_t) (s - p->file) : strlen (p->file); - ret = m->db->get (m->db, 0, &key, &value, 0); - if (ret == DB_NOTFOUND) { - /* Every locally generated message triggers this ... */ - /*warn ("Warning: no UID for message %.*s\n", - key.size, p->file);*/ - } else if (ret) { - fprintf (stderr, "Unexpected error (%d) from db_get(%.*s)\n", - ret, key.size, p->file); - } else if (ret == 0) { - p->uid = *((int *) value.data); - if (p->uid > m->maxuid) - m->maxuid = p->uid; - } - if (s) - parse_info (p, s + 1); - if (p->flags & D_DELETED) - m->deleted++; - cur = &p->next; - } - closedir (d); - } - return m; - - err: - if (m->db) - m->db->close (m->db, 0); - dotlock_unlock (&m->lockfd); - free (m->path); - free (m); - return NULL; -} - -/* permanently remove messages from a maildir mailbox. if `dead' is nonzero, - * we only remove the messags marked dead. - */ -int -maildir_expunge (mailbox_t * mbox, int dead) -{ - message_t **cur = &mbox->msgs; - message_t *tmp; - char *s; - DBT key; - char path[_POSIX_PATH_MAX]; - - while (*cur) - { - if ((dead == 0 && (*cur)->flags & D_DELETED) || - (dead && (*cur)->dead)) - { - tmp = *cur; - snprintf (path, sizeof (path), "%s/%s/%s", - mbox->path, tmp->new ? "new" : "cur", tmp->file); - if (unlink (path)) - perror (path); - /* remove the message from the UID map */ - memset (&key, 0, sizeof(key)); - key.data = tmp->file; - s = strchr (tmp->file, ':'); - key.size = s ? (size_t) (s - tmp->file) : strlen (key.data); - mbox->db->del (mbox->db, 0, &key, 0); - mbox->db->sync (mbox->db, 0); - *cur = (*cur)->next; - free (tmp->file); - free (tmp); - } - else - cur = &(*cur)->next; - } - return 0; -} - -int -maildir_update_maxuid (mailbox_t * mbox) -{ - int fd; - char buf[64]; - size_t len; - char path[_POSIX_PATH_MAX]; - int ret = 0; - - snprintf (path, sizeof (path), "%s/isyncmaxuid", mbox->path); - fd = open (path, O_WRONLY | O_CREAT, 0600); - if (fd == -1) - { - perror ("open"); - return -1; - } - - /* write out the file */ - snprintf (buf, sizeof (buf), "%u\n", mbox->maxuid); - len = write (fd, buf, strlen (buf)); - if (len == (size_t) - 1) - { - perror ("write"); - ret = -1; - } - - if (close (fd)) - ret = -1; - - return ret; -} - -#define _24_HOURS (3600 * 24) - -static void -maildir_clean_tmp (const char *mbox) -{ - char path[_POSIX_PATH_MAX]; - DIR *dirp; - struct dirent *entry; - struct stat st; - time_t now; - - snprintf (path, sizeof (path), "%s/tmp", mbox); - dirp = opendir (path); - if (dirp == NULL) - { - fprintf (stderr, "maildir_clean_tmp: opendir: %s: %s (errno %d)\n", - path, strerror (errno), errno); - return; - } - /* assuming this scan will take less than a second, we only need to - * check the time once before the following loop. - */ - time (&now); - while ((entry = readdir (dirp))) - { - snprintf (path, sizeof (path), "%s/tmp/%s", mbox, entry->d_name); - if (stat (path, &st)) - fprintf (stderr, "maildir_clean_tmp: stat: %s: %s (errno %d)\n", - path, strerror (errno), errno); - else if (S_ISREG (st.st_mode) && now - st.st_ctime >= _24_HOURS) - { - /* this should happen infrequently enough that it won't be - * bothersome to the user to display when it occurs. - */ - info ("Notice: removing stale file %s\n", path); - if (unlink (path)) - fprintf (stderr, - "maildir_clean_tmp: unlink: %s: %s (errno %d)\n", - path, strerror (errno), errno); - } - } - closedir(dirp); -} - -void -maildir_close (mailbox_t * mbox) -{ - if (mbox->db) - mbox->db->close (mbox->db, 0); - - /* release the mutex on the mailbox */ - dotlock_unlock (&mbox->lockfd); - - /* per the maildir(5) specification, delivery agents are supposed to - * set a 24-hour timer on items placed in the `tmp' directory. - */ - maildir_clean_tmp (mbox->path); - - free (mbox->path); - free_message (mbox->msgs); - memset (mbox, 0xff, sizeof (mailbox_t)); - free (mbox); -} - -int -maildir_set_uidvalidity (mailbox_t * mbox, unsigned int uidvalidity) -{ - char path[_POSIX_PATH_MAX]; - char buf[16]; - int fd; - int ret; - - snprintf (path, sizeof (path), "%s/isyncuidvalidity", mbox->path); - fd = open (path, O_WRONLY | O_CREAT | O_EXCL, 0600); - if (fd == -1) - { - perror ("open"); - return -1; - } - snprintf (buf, sizeof (buf), "%u\n", uidvalidity); - - ret = write (fd, buf, strlen (buf)); - - if (ret == -1) - perror ("write"); - else if ((size_t) ret != strlen (buf)) - ret = -1; - else - ret = 0; - - if (close (fd)) - { - perror ("close"); - ret = -1; - } - - if (ret) - if (unlink (path)) - perror ("unlink"); - - return (ret); -} diff --git a/src/main.c b/src/main.c index 17a0fe6..c2cc6a9 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,5 @@ -/* $Id$ - * - * isync - IMAP4 to maildir mailbox synchronizer +/* + * mbsync - mailbox synchronizer * Copyright (C) 2000-2002 Michael R. Elkins * Copyright (C) 2002-2004 Oswald Buddenhagen * @@ -18,473 +17,597 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * - * As a special exception, isync may be linked with the OpenSSL library, + * As a special exception, mbsync may be linked with the OpenSSL library, * despite that library's more restrictive license. */ #include "isync.h" -#include -#include #include #include -#include -#include -#include -#include -#include #include -#include -#include -int Quiet; - -void -info (const char *msg, ...) -{ - va_list va; - - if (!Quiet) - { - va_start (va, msg); - vprintf (msg, va); - va_end (va); - } -} - -void -infoc (char c) -{ - if (!Quiet) - putchar (c); -} - -void -warn (const char *msg, ...) -{ - va_list va; - - if (Quiet < 2) - { - va_start (va, msg); - vfprintf (stderr, msg, va); - va_end (va); - } -} - -#if HAVE_GETOPT_LONG -# define _GNU_SOURCE -# include -struct option Opts[] = { - {"all", 0, NULL, 'a'}, - {"list", 0, NULL, 'l'}, - {"config", 1, NULL, 'c'}, - {"create", 0, NULL, 'C'}, - {"create-local", 0, NULL, 'L'}, - {"create-remote", 0, NULL, 'R'}, - {"delete", 0, NULL, 'd'}, - {"expunge", 0, NULL, 'e'}, - {"fast", 0, NULL, 'f'}, - {"help", 0, NULL, 'h'}, - {"remote", 1, NULL, 'r'}, - {"folder", 1, NULL, 'F'}, - {"maildir", 1, NULL, 'M'}, - {"one-to-one", 0, NULL, '1'}, - {"inbox", 1, NULL, 'I'}, - {"host", 1, NULL, 's'}, - {"port", 1, NULL, 'p'}, - {"quiet", 0, NULL, 'q'}, - {"user", 1, NULL, 'u'}, - {"version", 0, NULL, 'v'}, - {"verbose", 0, NULL, 'V'}, - {0, 0, 0, 0} -}; -#endif - -config_t global; -char Hostname[256]; -int Verbose = 0; +int Pid; /* for maildir and imap */ +char Hostname[256]; /* for maildir */ +const char *Home; /* for config */ static void -version (void) +version( void ) { - puts (PACKAGE " " VERSION); - exit (0); + puts( PACKAGE " " VERSION ); + exit( 0 ); } static void -usage (int code) +usage( int code ) { - fputs ( -PACKAGE " " VERSION " IMAP4 to maildir synchronizer\n" + fputs( +PACKAGE " " VERSION " - mailbox synchronizer\n" "Copyright (C) 2000-2002 Michael R. Elkins \n" "Copyright (C) 2002-2004 Oswald Buddenhagen \n" +"Copyright (C) 2004 Theodore Ts'o \n" "usage:\n" -" " PACKAGE " [ flags ] mailbox [mailbox ...]\n" -" " PACKAGE " [ flags ] -a\n" -" " PACKAGE " [ flags ] -l\n" -" -a, --all synchronize all defined mailboxes\n" -" -l, --list list all defined mailboxes and exit\n" -" -L, --create-local create local maildir mailbox if nonexistent\n" -" -R, --create-remote create remote imap mailbox if nonexistent\n" -" -C, --create create both local and remote mailboxes if nonexistent\n" -" -d, --delete delete local msgs that don't exist on the server\n" -" -e, --expunge expunge deleted messages\n" -" -f, --fast only fetch new messages\n" -" -r, --remote BOX remote mailbox\n" -" -F, --folder DIR remote IMAP folder containing mailboxes\n" -" -M, --maildir DIR local directory containing mailboxes\n" -" -1, --one-to-one map every IMAP /box to /box\n" -" -I, --inbox BOX map IMAP INBOX to /BOX (exception to -1)\n" -" -s, --host HOST IMAP server address\n" -" -p, --port PORT server IMAP port\n" -" -u, --user USER IMAP user name\n" -" -c, --config CONFIG read an alternate config file (default: ~/.isyncrc)\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" +" -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 master to slave\n" +" -H, --push propagate from slave to master\n" +" -C, --create create mailboxes if nonexistent\n" +" -X, --expunge expunge deleted messages\n" +" -c, --config CONFIG read an alternate config file (default: ~/." EXE "rc)\n" +" -D, --debug print debugging messages\n" " -V, --verbose verbose mode (display network traffic)\n" " -q, --quiet don't display progress info\n" " -v, --version display version\n" " -h, --help display this help message\n" -"Compile time options:\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 and --expunge can be suffixed with -master/-slave. Read the man page.\n" +"\nSupported mailbox formats are: IMAP4rev1, Maildir\n" +"\nCompile time options:\n" #if HAVE_LIBSSL " +HAVE_LIBSSL\n" #else " -HAVE_LIBSSL\n" #endif - , code ? stderr : stdout); - exit (code); + , code ? stderr : stdout ); + exit( code ); } -char * -next_arg (char **s) +static int +matches( const char *t, const char *p ) { - char *ret; + for (;;) { + if (!*p) + return !*t; + if (*p == '*') { + p++; + do { + if (matches( t, p )) + return 1; + } while (*t++); + return 0; + } else if (*p == '%') { + p++; + do { + if (*t == '.' || *t == '/') /* this is "somewhat" hacky ... */ + return 0; + if (matches( t, p )) + return 1; + } while (*t++); + return 0; + } else { + if (*p != *t) + return 0; + p++, t++; + } + } +} - if (!s) - return 0; - if (!*s) - return 0; - while (isspace ((unsigned char) **s)) - (*s)++; - if (!**s) - { - *s = 0; - return 0; - } - if (**s == '"') - { - ++*s; - ret = *s; - *s = strchr (*s, '"'); - } - else - { - ret = *s; - while (**s && !isspace ((unsigned char) **s)) - (*s)++; - } - if (*s) - { - if (**s) - *(*s)++ = 0; - if (!**s) - *s = 0; - } - return ret; +static string_list_t * +filter_boxes( string_list_t *boxes, string_list_t *patterns ) +{ + string_list_t *nboxes = 0, *cpat; + const char *ps; + int not, fnot; + + for (; boxes; boxes = boxes->next) { + fnot = 1; + for (cpat = patterns; cpat; cpat = cpat->next) { + ps = cpat->string; + if (*ps == '!') { + ps++; + not = 1; + } else + not = 0; + if (matches( boxes->string, ps )) { + fnot = not; + break; + } + } + if (!fnot) + add_string_list( &nboxes, boxes->string ); + } + return nboxes; +} + +static void +merge_actions( channel_conf_t *chan, int mops, int sops, int have, int mask, int def ) +{ + if (mops & have) { + chan->mops &= ~mask; + chan->mops |= mops & mask; + chan->sops &= ~mask; + chan->sops |= sops & mask; + } else if (!(chan->mops & have)) { + if (global_mops & have) { + chan->mops |= global_mops & mask; + chan->sops |= global_sops & mask; + } else { + chan->mops |= def; + chan->sops |= def; + } + } } int -main (int argc, char **argv) +main( int argc, char **argv ) { - int i; - int ret; - config_t *box = 0; - mailbox_t *mail = 0; - imap_t *imap = 0; - int expunge = 0; /* by default, don't delete anything */ - int fast = 0; - int delete = 0; - char *config = 0; - struct passwd *pw; - int all = 0; - int list = 0; - int o2o = 0; - int mbox_open_mode = 0; - int imap_flags = 0; + channel_conf_t *chan; + store_conf_t *mconf, *sconf; + group_conf_t *group; + driver_t *mdriver, *sdriver; + store_t *mctx, *sctx; + string_list_t *umboxes, *usboxes, *mboxes, *sboxes, *mbox, *sbox, **mboxp, **sboxp, *cboxes, *chanptr; + char *config = 0, *channame, *boxlist, *opt, *ochar; + int all = 0, list = 0, cops = 0, mops = 0, sops = 0, gumboxes, gusboxes; + int oind, ret, op, multiple, pseudo = 0; - pw = getpwuid (getuid ()); + 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(); - /* defaults */ - memset (&global, 0, sizeof (global)); - /* XXX the precedence is borked: - it's defaults < cmdline < file instead of defaults < file < cmdline */ - global.port = 143; - global.box = "INBOX"; - global.folder = ""; - global.user = strdup (pw->pw_name); - global.maildir = strdup (pw->pw_dir); - global.use_namespace = 1; -#if HAVE_LIBSSL - /* this will probably annoy people, but its the best default just in - * case people forget to turn it on - */ - global.require_ssl = 1; - global.use_tlsv1 = 1; -#endif - -#define FLAGS "alCLRc:defhp:qu:r:F:M:1I:s:vV" - -#if HAVE_GETOPT_LONG - while ((i = getopt_long (argc, argv, FLAGS, Opts, NULL)) != -1) -#else - while ((i = getopt (argc, argv, FLAGS)) != -1) -#endif - { - switch (i) - { - case 'l': - list = 1; - /* plopp */ - case 'a': - all = 1; - break; - case '1': - o2o = 1; - break; - case 'C': - mbox_open_mode |= OPEN_CREATE; - imap_flags |= IMAP_CREATE; - break; - case 'L': - mbox_open_mode |= OPEN_CREATE; - break; - case 'R': - imap_flags |= IMAP_CREATE; - break; - case 'c': - config = optarg; - break; - case 'd': - delete = 1; - break; - case 'e': - expunge = 1; - break; - case 'f': - mbox_open_mode |= OPEN_FAST; - fast = 1; - break; - case 'p': - global.port = atoi (optarg); - break; - case 'q': - Quiet++; - Verbose = 0; - break; - case 'r': - global.box = optarg; - break; - case 'F': - global.folder = optarg; - break; - case 'M': - global.maildir = optarg; - break; - case 'I': - global.inbox = optarg; - break; - case 's': -#if HAVE_LIBSSL - if (!strncasecmp ("imaps:", optarg, 6)) - { - global.use_imaps = 1; - global.port = 993; - global.use_sslv2 = 1; - global.use_sslv3 = 1; - optarg += 6; + for (oind = 1, ochar = 0; oind < argc; ) { + if (!ochar || !*ochar) { + if (argv[oind][0] != '-') + break; + if (argv[oind][1] == '-') { + opt = argv[oind++] + 2; + if (!*opt) + break; + if (!strcmp( opt, "config" )) { + if (oind >= argc) { + fprintf( stderr, "--config requires an argument.\n" ); + return 1; + } + config = argv[oind++]; + } else if (!memcmp( opt, "config=", 7 )) + config = opt + 7; + else if (!strcmp( opt, "all" )) + all = 1; + else if (!strcmp( opt, "list" )) + list = 1; + else if (!strcmp( opt, "help" )) + usage( 0 ); + else if (!strcmp( opt, "version" )) + version(); + else if (!strcmp( opt, "quiet" )) + Quiet++; + else if (!strcmp( opt, "verbose" )) { + Verbose = 1; + if (!Quiet) + Quiet = 1; + } else if (!strcmp( opt, "debug" )) { + Debug = 1; + if (!Quiet) + Quiet = 1; + } else if (!strcmp( opt, "pull" )) + cops |= XOP_PULL, mops |= XOP_HAVE_TYPE; + else if (!strcmp( opt, "push" )) + cops |= XOP_PUSH, mops |= XOP_HAVE_TYPE; + else if (!memcmp( opt, "create", 6 )) { + opt += 6; + op = OP_CREATE|XOP_HAVE_CREATE; + lcop: + if (!*opt) + cops |= op; + else if (!strcmp( opt, "-master" )) + mops |= op, ochar++; + else if (!strcmp( opt, "-slave" )) + sops |= op, ochar++; + else + goto badopt; + mops |= op & (XOP_HAVE_CREATE|XOP_HAVE_EXPUNGE); + } else if (!memcmp( opt, "expunge", 7 )) { + opt += 7; + op = OP_EXPUNGE|XOP_HAVE_EXPUNGE; + goto lcop; + } else if (!strcmp( opt, "no-expunge" )) + mops |= XOP_HAVE_EXPUNGE; + else if (!strcmp( opt, "no-create" )) + mops |= XOP_HAVE_CREATE; + else if (!strcmp( opt, "full" )) + mops |= XOP_HAVE_TYPE|XOP_PULL|XOP_PUSH; + else if (!strcmp( opt, "noop" )) + mops |= XOP_HAVE_TYPE; + else if (!memcmp( opt, "pull", 4 )) { + op = XOP_PULL; + lcac: + opt += 4; + if (!*opt) + cops |= op; + else if (*opt == '-') { + opt++; + goto rlcac; + } else + goto badopt; + } else if (!memcmp( opt, "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: + fprintf( stderr, "Unknown option '%s'\n", argv[oind - 1] ); + return 1; + } + switch (op & XOP_MASK_DIR) { + case XOP_PULL: sops |= op & OP_MASK_TYPE; break; + case XOP_PUSH: mops |= op & OP_MASK_TYPE; break; + default: cops |= op; break; + } + mops |= XOP_HAVE_TYPE; + } + continue; + } + ochar = argv[oind++] + 1; + if (!*ochar) { + fprintf( stderr, "Invalid option '-'\n" ); + return 1; + } } -#endif - global.host = optarg; - break; - case 'u': - global.user = optarg; - break; - case 'V': - Verbose = 1; - break; - case 'v': - version (); - case 'h': - usage (0); - default: - usage (1); - } - } - - if (!argv[optind] && !all) - { - fprintf (stderr, "No mailbox specified. Try isync -h\n"); - return 1; - } - - gethostname (Hostname, sizeof (Hostname)); - - load_config (config, &o2o); - - if (all && o2o) - { - DIR *dir; - struct dirent *de; - - if (global.inbox) { - boxes = malloc (sizeof (config_t)); - memcpy (boxes, &global, sizeof (config_t)); - boxes->box = "INBOX"; - boxes->path = global.inbox; - } - - if (!(dir = opendir (global.maildir))) { - fprintf (stderr, "%s: %s\n", global.maildir, strerror(errno)); - return 1; - } - while ((de = readdir (dir))) { - struct stat st; - char buf[PATH_MAX]; - - if (*de->d_name == '.') - continue; - if (global.inbox && !strcmp (global.inbox, de->d_name)) - continue; - snprintf (buf, sizeof(buf), "%s/%s/cur", global.maildir, de->d_name); - if (stat (buf, &st) || !S_ISDIR (st.st_mode)) - continue; - box = malloc (sizeof (config_t)); - memcpy (box, &global, sizeof (config_t)); - box->path = strdup (de->d_name); - box->box = box->path; - box->next = boxes; - boxes = box; - } - closedir (dir); - - imap = imap_connect (&global); - if (!imap) - return 1; - if (imap_list (imap)) - return 1; - } - if (list) - { - for (box = boxes; box; box = box->next) - puts (box->path); - return 0; - } - ret = 0; - for (box = boxes; (all && box) || (!all && argv[optind]); optind++) - { - if (!all) - { - if (o2o || NULL == (box = find_box (argv[optind]))) - { - /* if enough info is given on the command line, don't worry if - * the mailbox isn't defined. - */ - if (!global.host) - { - fprintf (stderr, "%s: no such mailbox\n", argv[optind]); - /* continue is ok here because we are not handling the - * `all' case. - */ - continue; - } - global.path = argv[optind]; - box = &global; - if (o2o) - global.box = - (global.inbox && !strcmp (global.path, global.inbox)) ? - "INBOX" : global.path; - } - } - - do { - info ("Mailbox %s\n", box->path); - mail = maildir_open (box->path, mbox_open_mode); - if (!mail) - { - fprintf (stderr, "%s: unable to open mailbox\n", box->path); - ret = 1; - break; - } - - if (box->max_size) - imap_flags |= IMAP_GET_SIZE; - imap = imap_open (box, fast ? mail->maxuid + 1 : 1, imap, imap_flags); - if (!imap) - { - fprintf (stderr, "%s: skipping mailbox due to IMAP error\n", - box->path); - ret = 1; - break; - } - - info ("Synchronizing\n"); - i = (delete || box->delete) ? SYNC_DELETE : 0; - i |= (expunge || box->expunge) ? SYNC_EXPUNGE : 0; - if (sync_mailbox (mail, imap, i, box->max_size, box->max_messages)) - { - imap_close (imap); /* Just to be safe. Don't really know - * what the problem was. - */ - imap = NULL; /* context no longer valid */ - ret = 1; - break; - } - - if (!fast) - { - if ((expunge || box->expunge) && - (imap->deleted || mail->deleted)) - { - /* remove messages marked for deletion */ - info ("Expunging %d messages from server\n", imap->deleted); - if (imap_expunge (imap)) - { - imap_close (imap); - imap = NULL; - ret = 1; + switch (*ochar++) { + case 'a': + all = 1; break; - } - info ("Expunging %d messages from local mailbox\n", - mail->deleted); - if (maildir_expunge (mail, 0)) { - ret = 1; + case 'l': + list = 1; break; - } - } - /* remove messages deleted from server. this can safely be an - * `else' clause since dead messages are marked as deleted by - * sync_mailbox. - */ - else if (delete) { - if (maildir_expunge (mail, 1)) { - ret = 1; + case 'c': + if (*ochar == 'T') { + ochar++; + pseudo = 1; + } + if (oind >= argc) { + fprintf( stderr, "-c requires an argument.\n" ); + return 1; + } + config = argv[oind++]; break; - } + case 'C': + op = OP_CREATE|XOP_HAVE_CREATE; + cop: + if (*ochar == 'm') + mops |= op, ochar++; + else if (*ochar == 's') + sops |= op, ochar++; + else if (*ochar == '-') + ochar++; + else + cops |= op; + mops |= op & (XOP_HAVE_CREATE|XOP_HAVE_EXPUNGE); + break; + case 'X': + op = OP_EXPUNGE|XOP_HAVE_EXPUNGE; + goto cop; + case 'F': + cops |= XOP_PULL|XOP_PUSH; + case '0': + mops |= 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: sops |= op & OP_MASK_TYPE; break; + case XOP_PUSH: mops |= op & OP_MASK_TYPE; break; + default: cops |= op; break; + } + else + cops |= op; + mops |= XOP_HAVE_TYPE; + break; + case 'L': + op = XOP_PULL; + goto cac; + case 'H': + op = XOP_PUSH; + goto cac; + case 'q': + Quiet++; + break; + case 'V': + Verbose = 1; + if (!Quiet) + Quiet = 1; + break; + case 'D': + Debug = 1; + if (!Quiet) + Quiet = 1; + break; + case 'v': + version(); + case 'h': + usage( 0 ); + default: + fprintf( stderr, "Unknown option '-%c'\n", *(ochar - 1) ); + return 1; } - } + } - } while (0); + if (merge_ops( cops, &mops, &sops )) + return 1; - /* we never sync the same mailbox twice, so close it now */ - if (mail) - maildir_close (mail); + if (load_config( config, pseudo )) + return 1; - /* the imap connection is not closed so we can keep the connection - * open, and there is no IMAP command for un-SELECT-ing a mailbox. - */ + if (!all && !argv[oind]) { + fputs( "No channel specified. Try '" EXE " -h'\n", stderr ); + return 1; + } + if (!channels) { + fputs( "No channels defined. Try 'man " EXE "'\n", stderr ); + return 1; + } + + ret = 0; + chan = channels; + chanptr = 0; + mctx = sctx = 0; + mconf = sconf = 0; /* make-gcc-happy */ + mdriver = sdriver = 0; /* make-gcc-happy */ + gumboxes = gusboxes = 0; + umboxes = usboxes = 0; if (all) - box = box->next; - } - /* gracefully close connection to the IMAP server */ - imap_close (imap); - return ret; + multiple = channels->next != 0; + else if (argv[oind + 1]) + multiple = 1; + else { + multiple = 0; + for (group = groups; group; group = group->next) + if (!strcmp( group->name, argv[oind] )) { + multiple = 1; + break; + } + } + for (;;) { + boxlist = 0; + if (!all) { + if (chanptr) + channame = chanptr->string; + else { + for (group = groups; group; group = group->next) + if (!strcmp( group->name, argv[oind] )) { + chanptr = group->channels; + channame = chanptr->string; + goto gotgrp; + } + channame = argv[oind]; + gotgrp: ; + } + if ((boxlist = strchr( channame, ':' ))) + *boxlist++ = 0; + for (chan = channels; chan; chan = chan->next) + if (!strcmp( chan->name, channame )) + goto gotchan; + fprintf( stderr, "No channel or group named '%s' defined.\n", channame ); + ret = 1; + goto gotnone; + gotchan: ; + } + merge_actions( chan, mops, sops, XOP_HAVE_TYPE, OP_MASK_TYPE, OP_MASK_TYPE ); + merge_actions( chan, mops, sops, XOP_HAVE_CREATE, OP_CREATE, 0 ); + merge_actions( chan, mops, sops, XOP_HAVE_EXPUNGE, OP_EXPUNGE, 0 ); + + mboxes = sboxes = cboxes = 0; + /* possible todo: handle master <-> slave swaps */ + if (mctx) { + if (mconf == chan->master) + goto gotmctx; + free_string_list( umboxes ); + umboxes = 0; + gumboxes = 0; + if (mconf->driver != chan->master->driver) { + mdriver->close_store( mctx ); + mctx = 0; + } + } + mconf = chan->master; + mdriver = mconf->driver; + if (!(mctx = mdriver->open_store( chan->master, mctx ))) { + ret = 1; + goto next; + } + gotmctx: + if (sctx) { + if (sconf == chan->slave) + goto gotsctx; + free_string_list( usboxes ); + usboxes = 0; + gusboxes = 0; + if (sconf->driver != chan->slave->driver) { + sdriver->close_store( sctx ); + sctx = 0; + } + } + sconf = chan->slave; + sdriver = sconf->driver; + if (!(sctx = sdriver->open_store( chan->slave, sctx ))) { + ret = 1; + goto next; + } + gotsctx: + info( "Channel %s\n", chan->name ); + if (list && multiple) + printf( "%s:\n", chan->name ); + if (boxlist) { + for (boxlist = strtok( boxlist, ",\n" ); boxlist; boxlist = strtok( 0, ",\n" )) + if (list) + puts( boxlist ); + else + switch (sync_boxes( mctx, boxlist, sctx, boxlist, chan )) { + case SYNC_MASTER_BAD: goto screwm; + case SYNC_SLAVE_BAD: goto screws; + case SYNC_FAIL: ret = 1; + } + } else if (chan->patterns) { + if (!gumboxes) { + if (mdriver->list( mctx, &umboxes ) != DRV_OK) { + screwm: + mdriver->close_store( mctx ); + free_string_list( umboxes ); + umboxes = 0; + gumboxes = 0; + mctx = 0; + ret = 1; + goto next; + } else { + gumboxes = 1; + if (mctx->conf->map_inbox) + add_string_list( &umboxes, mctx->conf->map_inbox ); + } + } + if (!gusboxes) { + if (sdriver->list( sctx, &usboxes ) != DRV_OK) { + screws: + sdriver->close_store( sctx ); + free_string_list( usboxes ); + usboxes = 0; + gusboxes = 0; + sctx = 0; + ret = 1; + goto next; + } else { + gusboxes = 1; + if (sctx->conf->map_inbox) + add_string_list( &usboxes, sctx->conf->map_inbox ); + } + } + mboxes = filter_boxes( umboxes, chan->patterns ); + sboxes = filter_boxes( usboxes, chan->patterns ); + for (mboxp = &mboxes; (mbox = *mboxp); ) { + for (sboxp = &sboxes; (sbox = *sboxp); sboxp = &sbox->next) + if (!strcmp( sbox->string, mbox->string )) { + *sboxp = sbox->next; + free( sbox ); + *mboxp = mbox->next; + mbox->next = cboxes; + cboxes = mbox; + goto gotdupe; + } + mboxp = &mbox->next; + gotdupe: ; + } + for (mbox = cboxes; mbox; mbox = mbox->next) + if (list) + puts( mbox->string ); + else + switch (sync_boxes( mctx, mbox->string, sctx, mbox->string, chan )) { + case SYNC_MASTER_BAD: goto screwm; + case SYNC_SLAVE_BAD: goto screws; + case SYNC_FAIL: ret = 1; + } + if ((chan->sops & OP_MASK_TYPE) && (chan->sops & OP_CREATE)) { + for (mbox = mboxes; mbox; mbox = mbox->next) + if (list) + puts( mbox->string ); + else + switch (sync_boxes( mctx, mbox->string, sctx, mbox->string, chan )) { + case SYNC_MASTER_BAD: goto screwm; + case SYNC_SLAVE_BAD: goto screws; + case SYNC_FAIL: ret = 1; + } + } + if ((chan->mops & OP_MASK_TYPE) && (chan->mops & OP_CREATE)) { + for (mbox = sboxes; mbox; mbox = mbox->next) + if (list) + puts( mbox->string ); + else + switch (sync_boxes( mctx, mbox->string, sctx, mbox->string, chan )) { + case SYNC_MASTER_BAD: goto screwm; + case SYNC_SLAVE_BAD: goto screws; + case SYNC_FAIL: ret = 1; + } + } + } else + if (list) + printf( "%s <=> %s\n", chan->master_name, chan->slave_name ); + else + switch (sync_boxes( mctx, chan->master_name, sctx, chan->slave_name, chan )) { + case SYNC_MASTER_BAD: goto screwm; + case SYNC_SLAVE_BAD: goto screws; + case SYNC_FAIL: ret = 1; + } + + next: + free_string_list( cboxes ); + free_string_list( mboxes ); + free_string_list( sboxes ); + if (all) { + if (!(chan = chan->next)) + break; + } else { + if (chanptr && (chanptr = chanptr->next)) + continue; + gotnone: + if (!argv[++oind]) + break; + } + } + free_string_list( usboxes ); + if (sctx) + sdriver->close_store( sctx ); + free_string_list( umboxes ); + if (mctx) + mdriver->close_store( mctx ); + + return ret; } diff --git a/src/mbsync.1 b/src/mbsync.1 new file mode 100644 index 0000000..2b44bc2 --- /dev/null +++ b/src/mbsync.1 @@ -0,0 +1,471 @@ +.ig +\" mbsync - mailbox synchronizer +\" Copyright (C) 2000-2002 Michael R. Elkins +\" Copyright (C) 2002-2004 Oswald Buddenhagen +\" Copyright (C) 2004 Theodore Y. Ts'o +\" +\" This program is free software; you can redistribute it and/or modify +\" it under the terms of the GNU General Public License as published by +\" the Free Software Foundation; either version 2 of the License, or +\" (at your option) any later version. +\" +\" This program is distributed in the hope that it will be useful, +\" but WITHOUT ANY WARRANTY; without even the implied warranty of +\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +\" GNU General Public License for more details. +\" +\" You should have received a copy of the GNU General Public License +\" along with this program; if not, write to the Free Software +\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +\" +\" As a special exception, mbsync may be linked with the OpenSSL library, +\" despite that library's more restrictive license. +.. +.TH mbsync 1 "2004 Mar 27" +.. +.SH NAME +mbsync - synchronize IMAP4 and Maildir mailboxes +.. +.SH SYNOPSIS +\fBmbsync\fR [\fIoptions\fR ...] {{\fIchannel\fR[\fB:\fIbox\fR[{\fB,\fR|\fB\\n\fR}...]]|\fIgroup\fR} ...|\fB-a\fR} +.. +.SH DESCRIPTION +\fBmbsync\fR is a command line application which synchronizes mailboxes; +currently Maildir and IMAP4 mailboxes are supported. +New messages, message deletions and flag changes can be propagated both ways; +the operation set can be selected in a fine-grained manner. +.br +Synchronization is based on unique message identifiers (UIDs), so no +identification conflicts can occur (as opposed to some other mail synchronizers). +OTOH, \fBmbsync\fR is susceptible to UID validity changes (that \fIshould\fR +never happen, but see "Compatibility" in the README). +Synchronization state is kept in one local text file per mailbox pair; +multiple replicas of a mailbox can be maintained. +.. +.SH OPTIONS +.TP +\fB-c\fR, \fB--config\fR \fIfile\fR +Read configuration from \fIfile\fR. +By default, the configuration is read from ~/.mbsyncrc. +.TP +\fB-a\fR, \fB--all\fR +Select all configured channels. Any channel/group specifications on the command +line are ignored. +.TP +\fB-l\fR, \fB--list\fR +Don't synchronize anything, but list all mailboxes in the selected channels +and exit. +.TP +\fB-C\fR[\fBm\fR][\fBs\fR], \fB--create\fR[\fB-master\fR|\fB-slave\fR] +Override any \fBCreate\fR options from the config file. See below. +.TP +\fB-X\fR[\fBm\fR][\fBs\fR], \fB--expunge\fR[\fB-master\fR|\fB-slave\fR] +Override any \fBExpunge\fR options from the config file. See below. +.TP +{\fB-n\fR|\fB-N\fR|\fB-d\fR|\fB-f\fR|\fB-0\fR|\fB-F\fR},\ + {\fB--new\fR|\fB--renew\fR|\fB--delete\fR|\fB--flags\fR|\fB--noop\fR|\fB--full\fR} +.TP +\r{\fB-L\fR|\fB-H\fR}[\fBn\fR][\fBN\fR][\fBd\fR][\fBf\fR],\ + {\fB--pull\fR|\fB--push\fR}[\fB-new\fR|\fB-renew\fR|\fB-delete\fR|\fB-flags\fR] +Override any \fBSync\fR options from the config file. See below. +.TP +\fB-h\fR, \fB--help\fR +Display a summary of command line options. +.TP +\fB-v\fR, \fB--version\fR +Display version information. +.TP +\fB-V\fR, \fB--verbose\fR +Enable \fIverbose\fR mode, which displays the IMAP4 network traffic. +.TP +\fB-D\fR, \fB--debug\fR +Enable printing \fIdebug\fR information. +.TP +\fB-q\fR, \fB--quiet\fR +Suppress informational messages. +If specified twice, suppress warning messages as well. +.. +.SH CONFIGURATION +The configuration file is mandatory; \fBmbsync\fR will not run without it. +Lines starting with a hash mark (\fB#\fR) are comments and are ignored entirely. +Configuration items are keywords followed by one or more arguments; +arguments containing spaces must be enclosed in double quotes (\fB"\fR). +All keywords (including those used as arguments) are case-insensitive. +There are a few global options, the rest applies to particular sections. +Sections are started by a section keyword and are terminated by an empty line +or end of file. +Every section defines an object with a an identifier unique within that +object class. +.P +There are two basic object classes: Stores and Channels. A Store defines +a collection of mailboxes; basically a folder, either local or remote. +A Channel connects two Stores, describing the way the two are synchronized. +.br +There are two auxiliary object classes: Accounts and Groups. An Account +describes the connection part of remote Stores, so a server connection can be +shared between multiple Stores. A Group aggregates multiple Channels to +save typing on the command line. +.. +.SS All Stores +These options can be used in all supported Store types. +.br +In this context, the term "remote" describes the second Store within a Channel, +and not necessarily a remote server. +.br +The special mailbox \fBINBOX\fR exists in every Store; its physical location +in the file system is Store type specific. +.. +.TP +\fBPath\fR \fIpath\fR +The location of the Store in the (server's) file system. +If this is no absolute path, the reference point is Store type specific. +This string is prepended to the mailbox names addressed in this Store, +but is not considered part of them; this is important for \fBPatterns\fR +in the Channels section. +Note that you \fBmust\fR append a slash if you want to specify an entire +directory. +(Default: \fI""\fR) +.. +.TP +\fBMaxSize\fR \fIsize\fR[\fBk\fR|\fBm\fR][\fBb\fR] +Messages larger than that will not be propagated into this Store. +This is useful for weeding out messages with large attachments. +\fBK\fR and \fBM\fR can be appended to the size to specify KiBytes resp. +MeBytes instead of bytes. \fBB\fR is accepted but superflous. +If \fIsize\fR is 0, the maximum message size is \fBunlimited\fR. +(Default: \fI0\fR) +.. +.TP +\fBMapInbox\fR \fImailbox\fR +Create a virtual mailbox (relative to \fBPath\fR), which is backed by +the \fBINBOX\fR. Makes sense in conjunction with \fBPatterns\fR in the +Channels section. +.. +.TP +\fBTrash\fR \fImailbox\fR +Specifies a mailbox (relative to \fBPath\fR) to copy deleted messages to +prior to expunging. See \fBINHERENT PROBLEMS\fR below. +(Default: none) +.. +.TP +\fBTrashNewOnly\fR \fIyes\fR|\fIno\fR +When trashing, copy only not yet propagated messages. This makes sense if the +remote Store has a \fBTrash\fR as well (with \fBTrashNewOnly\fR \fIno\fR). +(Default: \fIno\fR) +.. +.TP +\fBTrashRemoteNew\fR \fIyes\fR|\fIno\fR +When expunging the remote Store, copy not yet propagated messages to this +Store's \fBTrash\fR. When using this, the remote Store does not need an own +\fBTrash\fR at all, yet all messages are archived. +(Default: \fIno\fR) +.. +.SS Maildir Stores +The reference point for relative \fBPath\fRs is $HOME. +.P +As \fBmbsync\fR needs UIDs, but no standardized UID storage scheme exists for +Maildir, \fBmbsync\fR supports two schemes, each with its pros and cons. +.br +The \fBnative\fR scheme is stolen from the latest Maildir patches to \fBc-client\fR +and is therefore compatible with \fBpine\fR. The UID validity is stored in a +file named .uidvalidity; the UIDs are encoded in the file names of the messages. +.br +The \fBalternative\fR scheme is based on the UID mapping used by \fBisync\fR +versions 0.8 and 0.9.x. The invariant parts of the file names of the messages +are used as keys into a Berkeley database named .isyncuidmap.db, which holds +the UID validity as well. +.br +The \fBnative\fR scheme is faster, more space efficient, endianess independent +and "human readable", but will be disrupted if a message is copied from another +mailbox without getting a new file name; this would result in duplicated UIDs +sooner or later, which in turn results in a UID validity change, making +synchronization fail. +The \fBalternative\fR scheme would fail if a MUA changed a message's file name +in a part \fBmbsync\fR considers invariant; this would be interpreted as a +message deletion and a new message, resulting in unnecessary traffic. +.br +\fBMutt\fR is known to work fine with both schemes. +.br +Use \fBmdconvert\fR to convert mailboxes from one scheme to the other. +.. +.TP +\fBMaildirStore\fR \fIname\fR +Define the Maildir Store \fIname\fR, opening a section for its parameters. +.. +.TP +\fBAltMap\fR \fIyes\fR|\fIno\fR +Use the \fBalternative\fR UID storage scheme for mailboxes in this Store. +This does not affect mailboxes that do already have a UID storage scheme; +use \fBmdconvert\fR to change it. +(Default: \fIno\fR) +.. +.TP +\fBInbox\fR \fIpath\fR +The location of the \fBINBOX\fR. This is \fInot\fR relative to \fBPath\fR. +(Default: \fI~/Maildir\fR) +.. +.SS IMAP4 Accounts +.TP +\fBIMAPAccount\fR \fIname\fR +Define the IMAP4 Account \fIname\fR, opening a section for its parameters. +.. +.TP +\fBHost\fR [\fBimaps:\fR]\fIhost\fR +Specify the DNS name or IP address of the IMAP server. If \fIhost\fR is +prefixed with \fBimaps:\fR the connection is assumed to be an SSL connection +to port 993. +Note that modern servers support SSL on the default port 143 via the +STARTTLS extension, which will be used automatically by default. +.. +.TP +\fBPort\fR \fIport\fR +Specify the TCP port number of the IMAP server. (Default: 143 for imap, +993 for imaps) +.. +.TP +\fBUser\fR \fIusername\fR +Specify the login name on the IMAP server. (Default: current local user) +.. +.TP +\fBPass\fR \fIpassword\fR +Specify the password for \fIusername\fR on the IMAP server. +Note that this option is \fBNOT\fR required. +If no password is specified in the configuration file, \fBmbsync\fR +will prompt you for it. +.. +.TP +\fBTunnel\fR \fIcommand\fR +Specify a command to run to establish a connection rather than opening a TCP +socket. This allows you to run an IMAP session over an SSH tunnel, for +example. This makes most other IMAPAccount options superflous. +.. +.TP +\fBRequireCRAM\fR \fIyes\fR|\fIno\fR +If set to \fIyes\fR, \fBmbsync\fR will abort the connection if no CRAM-MD5 +authentication is possible. (Default: \fIno\fR) +.. +.TP +\fBRequireSSL\fR \fIyes\fR|\fIno\fR +\fBmbsync\fR will abort the connection if a TLS/SSL session cannot be +established with the IMAP server. (Default: \fIyes\fR) +.. +.TP +\fBCertificateFile\fR \fIpath\fR +File containing X.509 CA certificates used to verify server identities. +This option is \fImandatory\fR if SSL is used. See \fBSSL CERTIFICATES\fR below. +.. +.TP +\fBUseSSLv2\fR \fIyes\fR|\fIno\fR +Use SSLv2 for communication with the IMAP server over SSL? +(Default: \fIyes\fR if an imaps \fBHost\fR is used, otherwise \fIno\fR) +.. +.TP +\fBUseSSLv3\fR \fIyes\fR|\fIno\fR +Use SSLv3 for communication with the IMAP server over SSL? +(Default: \fIyes\fR if an imaps \fBHost\fR is used, otherwise \fIno\fR) +.. +.TP +\fBUseTLSv1\fR \fIyes\fR|\fIno\fR +Use TLSv1 for communication with the IMAP server over SSL? +(Default: \fIyes\fR) +.. +.SS IMAP Stores +The reference point for relative \fBPath\fRs is whatever the server likes it +to be; probably the user's $HOME or $HOME/Mail on that server. The location +of \fBINBOX\fR is up to the server as well and is usually irrelevant. +.TP +\fBIMAPStore\fR \fIname\fR +Define the IMAP4 Store \fIname\fR, opening a section for its parameters. +.. +.TP +\fBAccount\fR \fIaccount\fR +Specify which IMAP4 Account to use. Instead of defining an Account and +referencing it here, it is also possible to specify all the Account options +directly in the Store's section - this makes sense if an Account is used for +one Store only anyway. +.. +.TP +\fBUseNamespace\fR \fIyes\fR|\fIno\fR +Selects whether the server's first "personal" NAMESPACE should be prefixed to +mailbox names. Disabling this makes sense for some broken IMAP servers. +This option is meaningless if a \fBPath\fR was specified. +(Default: \fIyes\fR) +.. +.SS Channels +.TP +\fBChannel\fR \fIname\fR +Define the Channel \fIname\fR, opening a section for its parameters. +.. +.TP +{\fBMaster\fR|\fBSlave\fR} \fB:\fIstore\fB:\fR[\fImailbox\fR] +Specify the Master resp. Slave Store to be connected by this Channel. +If \fImailbox\fR is omitted, \fBINBOX\fR is assumed. +The \fImailbox\fR specification can be overridden by \fBPatterns\fR, which +in turn can be overridden by a mailbox list in a Channel reference (a Group +specification or the command line). +.. +.TP +\fBPattern\fR[\fBs\fR] [\fB!\fR]\fIpattern\fR ... +Instead of synchronizing only one mailbox pair, synchronize all mailboxes +that match the \fIpattern\fR(s). The mailbox names are the same on both +Master and Slave. Patterns are IMAP4 patterns, i.e., \fB*\fR matches anything +and \fB%\fR matches anything up to the next hierarchy delimiter. Prepending +\fB!\fR to a pattern makes it an exclusion. Multiple patterns can be specified +(either by supplying multiple arguments or by using \fBPattern\fR multiple +times); later matches take precedence. +Example: "\fBPatterns\fR\ \fI%\ !Trash\fR" +.. +.TP +\fBMaxSize\fR \fIsize\fR[\fBk\fR|\fBm\fR][\fBb\fR] +Analogous to the homonymous option in the Stores section, but applies equally +to Master and Slave. Note that this actually modifies the Stores, so take care +not to provide conflicting settings if you use the Stores in multiple Channels. +.. +.TP +\fBMaxMessages\fR \fIcount\fR +Sets the maximum number of messages to keep in each Slave mailbox. +This is useful for mailboxes where you keep a complete archive on the server, +but want to mirror only the last messages (for instance, for mailing lists). +The messages that were the first to arrive in the mailbox (independently of +the actual date of the message) will be deleted first. +Messages that are flagged (marked as important) and recent messages will not +be automatically deleted. +If \fIcount\fR is 0, the maximum number of messages is \fBunlimited\fR +(Default: \fI0\fR). +.. +.TP +\fBSync\fR {\fINone\fR|[\fIPull\fR] [\fIPush\fR] [\fINew\fR] [\fIReNew\fR] [\fIDelete\fR] [\fIFlags\fR]|\fIFull\fR} +Select the synchronization operation(s) to perform: +.br +\fBPull\fR - propagate changes from Master to Slave. +.br +\fBPush\fR - propagate changes from Slave to Master. +.br +\fBNew\fR - propagate newly appeared messages. +.br +\fBReNew\fR - previously refused messages are re-evaluated for propagation. +Useful after flagging affected messages in the source Store or enlarging +MaxSize in the destination Store. +.br +\fBDelete\fR - propagate message deletions. This applies only to messages that +are actually gone, i.e., were expunged. The affected messages in the remote +Store are marked as deleted only, i.e., they won't be really deleted until +that Store is expunged. +.br +\fBFlags\fR - propagate flag changes. Note that Deleted/Trashed is a flag as +well; this is particularily interesting if you use \fBmutt\fR with the +maildir_trash option. +.br +\fBAll\fR (\fB--full\fR on the command line) - all of the above. +This is the global default. +.br +\fBNone\fR (\fB--noop\fR on the command line) - don't propagate anything. +Useful if you want to expunge only. +.IP +\fBPull\fR and \fBPush\fR are direction flags, while \fBNew\fR, \fBReNew\fR, +\fBDelete\fR and \fBFlags\fR are type flags. The two flag classes make up a +two-dimensional matrix (a table). Its cells are the individual actions to +perform. There are two styles of asserting the cells: +.br +In the first style, the flags select entire rows/colums in the matrix. Only +the cells which are selected both horizontally and vertically are asserted. +Specifying no flags from a class is like specifying all flags from this class. +For example, "\fBSync\fR\ \fBPull\fR\ \fBNew\fR\ \fBFlags\fR" will propagate +new messages and flag changes from the Master to the Slave, +"\fBSync\fR\ \fBNew\fR\ \fBDelete\fR" will propagate message arrivals and +deletions both ways, and "\fBSync\fR\ \fBPush\fR" will propagate all changes +from the Slave to the Master. +.br +In the second style, direction flags are concatenated with type flags; every +compound flag immediately asserts a cell in the matrix. In addition to at least +one compound flag, the individual flags can be used as well, but as opposed to +the first style, they immediately assert all cells in their respective +row/column. For example, +"\fBSync\fR\ \fBPullNew\fR\ \fBPullDelete\fR\ \fBPush\fR" will propagate +message arrivals and deletions from the Master to the Slave and any changes +from the Slave to the Master. +Note that it is not allowed to assert a cell in two ways, e.g. +"\fBSync\fR\ \fBPullNew\fR\ \fBPull\fR" and +"\fBSync\fR\ \fBPullNew\fR\ \fBDelete\fR\ \fBPush\fR" induce error messages. +.. +.TP +\fBCreate\fR {\fINone\fR|\fIMaster\fR|\fISlave\fR|\fIBoth\fR} +Automatically create missing mailboxes [on the Master/Slave]. +Otherwise print an error message and skip that mailbox pair if a mailbox +does not exist. +(Global default: \fINone\fR) +.. +.TP +\fBExpunge\fR {\fINone\fR|\fIMaster\fR|\fISlave\fR|\fIBoth\fR} +Permanently remove all messages [on the Master/Slave] marked for deletion. +(Global default: \fINone\fR) +.. +.P +\fBSync\fR, \fBCreate\fR and \fBExpunge\fR can be used outside any section for +a global effect. The global settings are overridden by Channel-specific options, +which in turn are overridden by command line switches. +.. +.TP +\fBSyncState\fR {\fB*\fR|\fIpath\fR} +Set the location of this Channel's synchronization state files. \fB*\fR means +that the state should be saved in a file named .mbsyncstate in the +Slave mailbox itself; this has the advantage that you needn't to care for the +state file if you delete the mailbox, but it works only with Maildir mailboxes, +obviously. Otherwise this is interpreted as a string to prepend to the Slave +mailbox name to make up a complete path. +.br +This option can be used outside any section for a global effect. In this case +the appended string is made up according to the pattern +\fB:\fImaster\fB:\fImaster-box\fB_:\fIslave\fB:\fIslave-box\fR. +.br +(Global default: \fI~/.mbsync/\fR). +.. +.SS Groups +.TP +\fBGroup\fR \fIname\fR [\fIchannel\fR[\fB:\fIbox\fR[\fB,\fR...]]] ... +Define the Group \fIname\fR, opening a section for its parameters. +Note that even though Groups have an own namespace, they will "hide" Channels +with the same name on the command line. +.br +One or more Channels can be specified on the same line. +.br +If you supply one or more \fIbox\fRes to a \fIchannel\fR, they will be used +instead of what is specified in the Channel. The same can be done on the command +line, except that there newlines can be used as mailbox name separators as well. +.. +.TP +\fBChannel\fR[\fBs\fR] \fIchannel\fR[\fB:\fIbox\fR[\fB,\fR...]] ... +Add the specified channels to the group. This option can be specified multiple +times within a Group. +.. +.SH SSL CERTIFICATES +[to be done] +.. +.SH INHERENT PROBLEMS +Changes done after \fBmbsync\fR has retrieved the message list will not be +synchronised until the next time \fBmbsync\fR is invoked. +.P +Using \fBTrash\fR on IMAP Stores bears a race condition: messages will be +lost if they are marked as deleted after the message list was retrieved but +before the mailbox is expunged. There is no risk as long as the IMAP mailbox +is not simultaneously accessed by \fBmbsync\fR and another mail client. +.. +.SH FILES +.TP +.B ~/.mbsyncrc +Default configuration file +.TP +.B ~/.mbsync/ +Directory containing synchronization state files +.. +.SH SEE ALSO +mdconvert(1), isync(1), mutt(1), maildir(5) +.P +Up to date information on \fBmbsync\fR can be found at http://isync.sf.net/ +.. +.SH AUTHOR +Written by Michael R. Elkins , +.br +rewritten and maintained by Oswald Buddenhagen , +.br +contributions by Theodore Y. Ts'o . diff --git a/src/mbsyncrc.sample b/src/mbsyncrc.sample new file mode 100644 index 0000000..fe0adb6 --- /dev/null +++ b/src/mbsyncrc.sample @@ -0,0 +1,82 @@ +# Global configuration section +# Values here are used as defaults for any following Channel section that +# doesn't specify them. +Expunge None +Create Both + +MaildirStore local +Path ~/Mail/ +Trash Trash + + +IMAPStore work +Host work.host.com +Pass xxxxxxxx +CertificateFile /etc/ssl/certs/ca-certificates.crt + +Channel work +Master :work: +Slave :local:work +Expunge Slave +Sync PullNew Push + + +IMAPStore personal +Host host.play.com +Port 6789 +RequireSSL no + +Channel personal +Master :personal: +Slave :local:personal +Expunge Both +MaxMessages 150 +MaxSize 200k + +IMAPStore remote +Tunnel "ssh -q host.remote.com /usr/sbin/imapd" + +Channel remote +Master :remote: +Slave :local:remote + + +Group boxes +Channels work personal remote + + +IMAPStore st1 +Host st1.domain.com +RequireCRAM yes +CertificateFile ~/.st1-certificate.crt + +IMAPStore st2 +Host imap.another-domain.com +Path non-standard/ +RequireSSL no +UseTLSv1 no + +Channel rst +Master :st1:somebox +Slave :st2: + + +IMAPAccount server +Host imaps:foo.bar.com +CertificateFile ~/.server-certificate.crt + +IMAPStore server +Account server +MapInbox inbox +Trash ~/trash +TrashRemoteNew yes + +MaildirStore mirror +Path ~/Maildir/ + +Channel o2o +Master :server: +Slave :mirror: +Patterns % + +Group partial o2o:inbox,sent-mail,foobar diff --git a/src/mdconvert.1 b/src/mdconvert.1 new file mode 100644 index 0000000..c915be3 --- /dev/null +++ b/src/mdconvert.1 @@ -0,0 +1,51 @@ +.ig +\" mdconvert - Maildir mailbox UID storage scheme converter +\" Copyright (C) 2004 Oswald Buddenhagen +\" +\" This program is free software; you can redistribute it and/or modify +\" it under the terms of the GNU General Public License as published by +\" the Free Software Foundation; either version 2 of the License, or +\" (at your option) any later version. +\" +\" This program is distributed in the hope that it will be useful, +\" but WITHOUT ANY WARRANTY; without even the implied warranty of +\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +\" GNU General Public License for more details. +\" +\" You should have received a copy of the GNU General Public License +\" along with this program; if not, write to the Free Software +\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +.. +.TH mdconvert 1 "2004 Mar 27" +.. +.SH NAME +mdconvert - Maildir mailbox UID storage scheme converter +.. +.SH SYNOPSIS +\fBmdconvert\fR [\fIoptions\fR ...] \fImailbox\fR ... +.. +.SH DESCRIPTION +\fBmdconvert\fR converts Maildir mailboxes between the two UID storage schemes +supported by \fBmbsync\fR. See \fBmbsync\fR's manual page for details on these +schemes. +.. +.SH OPTIONS +.TP +\fB-a\fR, \fB--alt\fR +Convert to the \fBalternative\fR (Berkeley DB based) UID storage scheme. +.TP +\fB-n\fR, \fB--native\fR +Convert to the \fBnative\fR (file name based) UID storage scheme. +This is the default. +.TP +\fB-h\fR, \fB--help\fR +Displays a summary of command line options. +.TP +\fB-v\fR, \fB--version\fR +Displays version information. +.. +.SH SEE ALSO +mbsync(1) +.. +.SH AUTHOR +Written and maintained by Oswald Buddenhagen . diff --git a/src/mdconvert.c b/src/mdconvert.c new file mode 100644 index 0000000..f2c60a1 --- /dev/null +++ b/src/mdconvert.c @@ -0,0 +1,256 @@ +/* + * mdconvert - Maildir UID scheme converter + * Copyright (C) 2004 Oswald Buddenhagen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define EXE "mdconvert" + +static int +nfsnprintf( char *buf, int blen, const char *fmt, ... ) +{ + int ret; + va_list va; + + va_start( va, fmt ); + if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen) { + fputs( "Fatal: buffer too small. Please report a bug.\n", stderr ); + abort(); + } + va_end( va ); + return ret; +} + +static const char *subdirs[] = { "cur", "new" }; +static struct flock lck; +static DBT key, value; + +static inline int +convert( const char *box, int altmap ) +{ + DB *db; + DIR *d; + struct dirent *e; + const char *u, *ru; + char *p, *s, *dpath, *spath, *dbpath; + int i, n, ret, sfd, dfd, bl, ml, uv[2], uid; + struct stat st; + char buf[_POSIX_PATH_MAX], buf2[_POSIX_PATH_MAX]; + char umpath[_POSIX_PATH_MAX], uvpath[_POSIX_PATH_MAX], tdpath[_POSIX_PATH_MAX]; + + if (stat( box, &st ) || !S_ISDIR(st.st_mode)) { + fprintf( stderr, "'%s' is no Maildir mailbox.\n", box ); + return 1; + } + + nfsnprintf( umpath, sizeof(umpath), "%s/.isyncuidmap.db", box ); + nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", box ); + if (altmap) + dpath = umpath, spath = uvpath, dbpath = tdpath; + else + spath = umpath, dpath = uvpath, dbpath = umpath; + nfsnprintf( tdpath, sizeof(tdpath), "%s.tmp", dpath ); + if ((sfd = open( spath, O_RDWR )) < 0) { + if (errno != ENOENT) + perror( spath ); + return 1; + } + if (fcntl( sfd, F_SETLKW, &lck )) { + perror( spath ); + goto sbork; + } + if ((dfd = open( tdpath, O_RDWR|O_CREAT, 0600 )) < 0) { + perror( tdpath ); + goto sbork; + } + if (db_create( &db, 0, 0 )) { + fputs( "Error: db_create() failed\n", stderr ); + goto tbork; + } + if ((ret = db->open( db, 0, dbpath, 0, DB_HASH, DB_CREATE, 0 ))) { + db->err( db, ret, "Error: db->open(%s)", dbpath ); + dbork: + db->close( db, 0 ); + tbork: + unlink( tdpath ); + close( dfd ); + sbork: + close( sfd ); + return 1; + } + key.data = (void *)"UIDVALIDITY"; + key.size = 11; + if (altmap) { + if ((n = read( sfd, buf, sizeof(buf) )) <= 0 || + (buf[n] = 0, sscanf( buf, "%d\n%d", &uv[0], &uv[1] ) != 2)) + { + fprintf( stderr, "Error: cannot read UIDVALIDITY of '%s'.\n", box ); + goto dbork; + } + value.data = uv; + value.size = sizeof(uv); + if ((ret = db->put( db, 0, &key, &value, 0 ))) { + db->err( db, ret, "Error: cannot write UIDVALIDITY for '%s'", box ); + goto dbork; + } + } else { + if ((ret = db->get( db, 0, &key, &value, 0 ))) { + db->err( db, ret, "Error: cannot read UIDVALIDITY of '%s'", box ); + goto dbork; + } + n = sprintf( buf, "%d\n%d\n", ((int *)value.data)[0], ((int *)value.data)[1] ); + if (write( dfd, buf, n ) != n) { + fprintf( stderr, "Error: cannot write UIDVALIDITY for '%s'.\n", box ); + goto dbork; + } + } + + again: + for (i = 0; i < 2; i++) { + bl = nfsnprintf( buf, sizeof(buf), "%s/%s/", box, subdirs[i] ); + if (!(d = opendir( buf ))) { + perror( "opendir" ); + goto dbork; + } + while ((e = readdir( d ))) { + if (*e->d_name == '.') + continue; + nfsnprintf( buf + bl, sizeof(buf) - bl, "%s", e->d_name ); + memcpy( buf2, buf, bl ); + p = strstr( e->d_name, ",U=" ); + if (p) + for (u = p, ru = p + 3; isdigit( (unsigned char)*ru ); ru++); + else + u = ru = strchr( e->d_name, ':' ); + if (u) + ml = u - e->d_name; + else + ru = "", ml = INT_MAX; + if (altmap) { + if (!p) + continue; + key.data = e->d_name; + key.size = (size_t)(strchr( e->d_name, ',' ) - e->d_name); + uid = atoi( p + 3 ); + value.data = &uid; + value.size = sizeof(uid); + if ((ret = db->put( db, 0, &key, &value, 0 ))) { + db->err( db, ret, "Error: cannot write UID for '%s'", box ); + goto ebork; + } + nfsnprintf( buf2 + bl, sizeof(buf2) - bl, "%.*s%s", ml, e->d_name, ru ); + } else { + s = strpbrk( e->d_name, ",:" ); + key.data = e->d_name; + key.size = s ? (size_t)(s - e->d_name) : strlen( e->d_name ); + if ((ret = db->get( db, 0, &key, &value, 0 ))) { + if (ret != DB_NOTFOUND) { + db->err( db, ret, "Error: cannot read UID for '%s'", box ); + goto ebork; + } + continue; + } + uid = *(int *)value.data; + nfsnprintf( buf2 + bl, sizeof(buf2) - bl, "%.*s,U=%d%s", ml, e->d_name, uid, ru ); + } + if (rename( buf, buf2 )) { + if (errno == ENOENT) + goto again; + perror( buf ); + ebork: + closedir( d ); + goto dbork; + } + + } + closedir( d ); + } + + db->close( db, 0 ); + close( dfd ); + if (rename( tdpath, dpath )) { + perror( dpath ); + return 1; + } + if (unlink( spath )) + perror( spath ); + close( sfd ); + return 0; +} + +int +main( int argc, char **argv ) +{ + int oint, ret, altmap = 0; + + for (oint = 1; oint < argc; oint++) { + if (!strcmp( argv[oint], "-h" ) || !strcmp( argv[oint], "--help" )) { + puts( +"Usage: " EXE " [-a] mailbox...\n" +" -a, --alt convert to alternative (DB based) UID scheme\n" +" -n, --native convert to native (file name based) UID scheme (default)\n" +" -h, --help show this help message\n" +" -v, --version display version" + ); + return 0; + } else if (!strcmp( argv[oint], "-v" ) || !strcmp( argv[oint], "--version" )) { + puts( EXE " " VERSION " - Maildir UID scheme converter" ); + return 0; + } else if (!strcmp( argv[oint], "-a" ) || !strcmp( argv[oint], "--alt" )) { + altmap = 1; + } else if (!strcmp( argv[oint], "-n" ) || !strcmp( argv[oint], "--native" )) { + altmap = 0; + } else if (!strcmp( argv[oint], "--" )) { + oint++; + break; + } else if (argv[oint][0] == '-') { + fprintf( stderr, "Unrecognized option '%s'. Try " EXE " -h\n", argv[oint] ); + return 1; + } else + break; + } + if (oint == argc) { + fprintf( stderr, "Mailbox specification missing. Try " EXE " -h\n" ); + return 1; + } +#if SEEK_SET != 0 + lck.l_whence = SEEK_SET; +#endif +#if F_WRLCK != 0 + lck.l_type = F_WRLCK; +#endif + ret = 0; + for (; oint < argc; oint++) + ret |= convert( argv[oint], altmap ); + return ret; +} + diff --git a/src/sync.c b/src/sync.c index 2ee00b2..77f2b03 100644 --- a/src/sync.c +++ b/src/sync.c @@ -1,8 +1,7 @@ -/* $Id$ - * - * isync - IMAP4 to maildir mailbox synchronizer +/* + * mbsync - mailbox synchronizer * Copyright (C) 2000-2002 Michael R. Elkins - * Copyright (C) 2002-2003 Oswald Buddenhagen + * Copyright (C) 2002-2004 Oswald Buddenhagen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,7 +17,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * - * As a special exception, isync may be linked with the OpenSSL library, + * As a special exception, mbsync may be linked with the OpenSSL library, * despite that library's more restrictive license. */ @@ -34,367 +33,940 @@ #include #include -static unsigned int MaildirCount = 0; +static const char Flags[] = { 'D', 'F', 'R', 'S', 'T' }; -message_t * -find_msg (message_t * list, unsigned int uid) +static int +parse_flags( const char *buf ) { - for (; list; list = list->next) - if (list->uid == uid) - return list; - return 0; + unsigned flags, i, d; + + for (flags = i = d = 0; i < as(Flags); i++) + if (buf[d] == Flags[i]) { + flags |= (1 << i); + d++; + } + return flags; } static int -set_uid (DB * db, const char *f, unsigned int uid) +make_flags( int flags, char *buf ) { - char *s; - DBT key, val; - int ret; + unsigned i, d; - memset (&key, 0, sizeof(key)); - memset (&val, 0, sizeof(val)); - key.data = (void *) f; - s = strchr (f, ':'); - key.size = s ? (size_t) (s - f) : strlen (f); - val.data = (void *) &uid; - val.size = sizeof (uid); - ret = db->put (db, 0, &key, &val, 0); - if (ret < 0) - fprintf (stderr, "Unexpected error (%d) from db_put(%.*s, %d)\n", - ret, key.size, f, uid); - db->sync(db, 0); - return 0; + for (i = d = 0; i < as(Flags); i++) + if (flags & (1 << i)) + buf[d++] = Flags[i]; + buf[d] = 0; + return d; +} + +static void +makeopts( int dops, store_conf_t *dconf, int *dopts, + store_conf_t *sconf, int *sopts ) +{ + if (dops & (OP_DELETE|OP_FLAGS)) { + *dopts |= OPEN_SETFLAGS; + *sopts |= OPEN_OLD; + if (dops & OP_FLAGS) + *sopts |= OPEN_FLAGS; + } + if (dops & (OP_NEW|OP_RENEW)) { + *dopts |= OPEN_APPEND; + if (dops & OP_RENEW) + *sopts |= OPEN_OLD; + if (dops & OP_NEW) + *sopts |= OPEN_NEW; + if (dops & OP_EXPUNGE) + *sopts |= OPEN_FLAGS; + if (dconf->max_size) + *sopts |= OPEN_SIZE; + } + if (dops & OP_EXPUNGE) { + *dopts |= OPEN_EXPUNGE; + if (dconf->trash) { + if (!dconf->trash_only_new) + *dopts |= OPEN_OLD; + *dopts |= OPEN_NEW|OPEN_FLAGS; + } else if (sconf->trash && sconf->trash_remote_new) + *dopts |= OPEN_NEW|OPEN_FLAGS; + } + if (dops & OP_CREATE) + *dopts |= OPEN_CREATE; +} + +static void +dump_box( store_t *ctx ) +{ + message_t *msg; + char fbuf[16]; /* enlarge when support for keywords is added */ + + if (Debug) + for (msg = ctx->msgs; msg; msg = msg->next) { + make_flags( msg->flags, fbuf ); + printf( " message %d, %s, %d\n", msg->uid, fbuf, msg->size ); + } +} + +static message_t * +findmsg( store_t *ctx, int uid, message_t **nmsg, const char *who ) +{ + message_t *msg; + + if (uid > 0) { + if (*nmsg && (*nmsg)->uid == uid) { + debug( " %s came in sequence\n", who ); + msg = *nmsg; + found: + *nmsg = msg->next; + if (!(msg->status & M_DEAD)) { + msg->status |= M_PROCESSED; + return msg; + } + debug( " ... but it vanished under our feet!\n" ); + } else { + for (msg = ctx->msgs; msg; msg = msg->next) + if (msg->uid == uid) { + debug( " %s came out of sequence\n", who ); + goto found; + } + debug( " %s not present\n", who ); + } + } else + debug( " no %s expected\n", who ); + return 0; +} + +#define S_DEAD (1<<0) +#define S_EXPIRED (1<<1) +#define S_DEL_MASTER (1<<2) +#define S_DEL_SLAVE (1<<3) +#define S_EXP_SLAVE (1<<4) + +typedef struct sync_rec { + struct sync_rec *next; + /* string_list_t *keywords; */ + int muid, suid; + unsigned char flags, status; +} sync_rec_t; + + +#define EX_OK 0 +#define EX_FAIL 1 +#define EX_STORE_BAD 2 +#define EX_RSTORE_BAD 3 + +static int +expunge( store_t *ctx, store_t *rctx ) +{ + driver_t *driver = ctx->conf->driver, *rdriver = rctx->conf->driver; + message_t *msg; + msg_data_t msgdata; + + for (msg = ctx->msgs; msg; msg = msg->next) + if (msg->flags & F_DELETED) { + if (ctx->conf->trash) { + if (!ctx->conf->trash_only_new || (msg->status & M_NOT_SYNCED)) { + debug( " trashing message %d\n", msg->uid ); + switch (driver->trash_msg( ctx, msg )) { + case DRV_STORE_BAD: return EX_STORE_BAD; + default: return EX_FAIL; + case DRV_OK: break; + } + } else + debug( " not trashing message %d - not new\n", msg->uid ); + } else if (rctx->conf->trash && rctx->conf->trash_remote_new) { + if (msg->status & M_NOT_SYNCED) { + if (!rctx->conf->max_size || msg->size <= rctx->conf->max_size) { + debug( " remote trashing message %d\n", msg->uid ); + msgdata.flags = msg->flags; + switch (driver->fetch_msg( ctx, msg, &msgdata )) { + case DRV_STORE_BAD: return EX_STORE_BAD; + default: return EX_FAIL; + case DRV_OK: break; + } + switch (rdriver->store_msg( rctx, &msgdata, 0 )) { + case DRV_STORE_BAD: return EX_RSTORE_BAD; + default: return EX_FAIL; + case DRV_OK: break; + } + } else + debug( " not remote trashing message %d - too big\n", msg->uid ); + } else + debug( " not remote trashing message %d - not new\n", msg->uid ); + } + } + + switch (driver->close( ctx )) { + case DRV_STORE_BAD: return EX_STORE_BAD; + default: return EX_FAIL; + case DRV_OK: return EX_OK;; + } +} + +/* cases: + a) both non-null + b) only master null + b.1) muid 0 + b.2) muid -1 + b.3) master not scanned + b.4) master gone + c) only slave null + c.1) suid 0 + c.2) suid -1 + c.3) slave not scanned + c.4) slave gone + d) both null + d.1) both gone + d.2) muid 0, slave not scanned + d.3) muid -1, slave not scanned + d.4) master gone, slave not scanned + d.5) muid 0, slave gone + d.6) muid -1, slave gone + d.7) suid 0, master not scanned + d.8) suid -1, master not scanned + d.9) slave gone, master not scanned + d.10) suid 0, master gone + d.11) suid -1, master gone + impossible cases: both muid & suid 0 or -1, both not scanned +*/ +static int +sync_old( int tops, store_t *sctx, store_t *tctx, store_conf_t *tconf, FILE *jfp, int pull, + unsigned char *nflags, sync_rec_t *srec, message_t *smsg, message_t *tmsg, int dels, int delt ) +{ + driver_t *tdriver = tctx->conf->driver, *sdriver = sctx->conf->driver; + int uid, tuid, unex; + unsigned char sflags, aflags, dflags, rflags; + msg_data_t msgdata; + + /* excludes (push) c.3) d.2) d.3) d.4) / (pull) b.3) d.7) d.8) d.9) */ + tuid = pull ? srec->suid : srec->muid; + if (!tuid) { + /* b.1) / c.1) */ + debug( pull ? " no more slave\n" : " no more master\n" ); + } else if (dels) { + /* c.4) d.9) / b.4) d.4) */ + debug( pull ? " master vanished\n" : " slave vanished\n" ); + if (tmsg && tmsg->flags != *nflags) + info( "Info: conflicting changes in (%d,%d)\n", srec->muid, srec->suid ); + if (tops & OP_DELETE) { + debug( pull ? " -> pulling delete\n" : " -> pushing delete\n" ); + switch (tdriver->set_flags( tctx, tmsg, tuid, F_DELETED, 0 )) { + case DRV_STORE_BAD: return pull ? SYNC_SLAVE_BAD : SYNC_MASTER_BAD; + case DRV_BOX_BAD: return SYNC_FAIL; + default: /* ok */ break; + case DRV_OK: + if (pull) { + fprintf( jfp, "< %d %d 0\n", srec->muid, srec->suid ); + srec->muid = 0; + } else { + fprintf( jfp, "> %d %d 0\n", srec->muid, srec->suid ); + srec->suid = 0; + } + } + } + } else if (!smsg) + /* c.1) c.2) d.7) d.8) / b.1) b.2) d.2) d.3) */ + ; + else if (tuid < 0) { + /* b.2) / c.2) */ + debug( pull ? " no slave yet\n" : " no master yet\n" ); + if (tops & OP_RENEW) { + if ((tops & OP_EXPUNGE) && (smsg->flags & F_DELETED)) { + debug( pull ? " -> not pulling - would be expunged anyway\n" : " -> not pushing - would be expunged anyway\n" ); + smsg->status |= M_NOT_SYNCED; + } else { + if ((smsg->flags & F_FLAGGED) || !tconf->max_size || smsg->size <= tconf->max_size) { + debug( pull ? " -> pulling it\n" : " -> pushing it\n" ); + msgdata.flags = smsg->flags; + switch (sdriver->fetch_msg( sctx, smsg, &msgdata )) { + case DRV_STORE_BAD: return pull ? SYNC_MASTER_BAD : SYNC_SLAVE_BAD; + case DRV_BOX_BAD: return SYNC_FAIL; + default: /* ok */ smsg->status |= M_NOT_SYNCED; break; + case DRV_OK: + smsg->flags = msgdata.flags; + switch (tdriver->store_msg( tctx, &msgdata, &uid )) { + case DRV_STORE_BAD: return pull ? SYNC_SLAVE_BAD : SYNC_MASTER_BAD; + default: return SYNC_FAIL; + case DRV_OK: + if (pull) { + srec->suid = uid; + fprintf( jfp, "> %d -1 %d\n", srec->muid, srec->suid ); + } else { + srec->muid = uid; + fprintf( jfp, "< -1 %d %d\n", srec->suid, srec->muid ); + } + *nflags = smsg->flags; + } + } + } else { + debug( pull ? " -> not pulling - still too big\n" : " -> not pushing - still too big\n" ); + smsg->status |= M_NOT_SYNCED; + } + } + } else + smsg->status |= M_NOT_SYNCED; + } else if (!delt) { + /* a) & b.3) / c.3) */ + debug( pull ? " may pull\n" : " may push\n" ); + if (tops & OP_FLAGS) { + debug( pull ? " -> pulling flags\n" : " -> pushing flags\n" ); + sflags = smsg->flags; + aflags = sflags & ~*nflags; + dflags = ~sflags & *nflags; + unex = 0; + if (srec->status & S_EXPIRED) { + if (!pull) { + if (sflags & F_DELETED) { + if (!(sflags & F_FLAGGED)) + aflags &= ~F_DELETED; + } else + unex = 1; + } else { + if ((sflags & F_FLAGGED) && !(sflags & F_DELETED)) { + unex = 1; + dflags |= F_DELETED; + } + } + } + rflags = (*nflags | aflags) & ~dflags; + if ((tops & OP_EXPUNGE) && (rflags & F_DELETED) && + (!tctx->conf->trash || tctx->conf->trash_only_new)) + { + aflags &= F_DELETED; + dflags = 0; + } + switch (tdriver->set_flags( tctx, tmsg, tuid, aflags, dflags )) { + case DRV_STORE_BAD: return pull ? SYNC_SLAVE_BAD : SYNC_MASTER_BAD; + case DRV_BOX_BAD: return SYNC_FAIL; + default: /* ok */ break; + case DRV_OK: + *nflags = rflags; + if (unex) { + debug( "unexpiring pair(%d,%d)\n", srec->muid, srec->suid ); + /* log last, so deletion can't be misinterpreted! */ + fprintf( jfp, "~ %d %d 0\n", srec->muid, srec->suid ); + srec->status &= ~S_EXPIRED; + } + } + } + } /* else b.4) / c.4) */ + return SYNC_OK; +} + +static int +sync_new( int tops, store_t *sctx, store_t *tctx, store_conf_t *tconf, FILE *jfp, sync_rec_t ***srecadd, int pull, int *smaxuid ) +{ + driver_t *tdriver = tctx->conf->driver, *sdriver = sctx->conf->driver; + sync_rec_t *srec; + message_t *msg; + int nmsgs, uid; + msg_data_t msgdata; + + for (nmsgs = 0, msg = sctx->msgs; msg; msg = msg->next) + if (!(msg->status & M_PROCESSED)) { + if (tops & OP_NEW) { + debug( pull ? "new message %d on master\n" : "new message %d on slave\n", msg->uid ); + if ((tops & OP_EXPUNGE) && (msg->flags & F_DELETED)) { + debug( pull ? " not pulling - would be expunged anyway\n" : " not pushing - would be expunged anyway\n" ); + msg->status |= M_NOT_SYNCED; + } else { + if ((msg->flags & F_FLAGGED) || !tconf->max_size || msg->size <= tconf->max_size) { + debug( pull ? " pulling it\n" : " pushing it\n" ); + if (!nmsgs) + info( pull ? "Pulling new messages..." : "Pushing new messages..." ); + else + infoc( '.' ); + nmsgs++; + msgdata.flags = msg->flags; + switch (sdriver->fetch_msg( sctx, msg, &msgdata )) { + case DRV_STORE_BAD: return pull ? SYNC_MASTER_BAD : SYNC_SLAVE_BAD; + case DRV_BOX_BAD: return SYNC_FAIL; + case DRV_MSG_BAD: /* ok */ msg->status |= M_NOT_SYNCED; continue; + } + msg->flags = msgdata.flags; + switch (tdriver->store_msg( tctx, &msgdata, &uid )) { + case DRV_STORE_BAD: return pull ? SYNC_SLAVE_BAD : SYNC_MASTER_BAD; + default: return SYNC_FAIL; + case DRV_OK: break; + } + } else { + debug( pull ? " not pulling - too big\n" : " not pushing - too big\n" ); + msg->status |= M_NOT_SYNCED; + uid = -1; + } + srec = nfmalloc( sizeof(*srec) ); + if (pull) { + srec->muid = msg->uid; + srec->suid = uid; + } else { + srec->muid = uid; + srec->suid = msg->uid; + } + srec->flags = msg->flags; + srec->status = 0; + srec->next = 0; + **srecadd = srec; + *srecadd = &srec->next; + fprintf( jfp, "+ %d %d %u\n", srec->muid, srec->suid, srec->flags ); + if (*smaxuid < msg->uid) { + *smaxuid = msg->uid; + fprintf( jfp, pull ? "( %d\n" : ") %d\n", msg->uid ); + } + } + } else + msg->status |= M_NOT_SYNCED; + } + if (nmsgs) + info( " %d messages\n", nmsgs ); + return SYNC_OK; +} + +static char * +clean_strdup( const char *s ) +{ + char *cs; + int i; + + cs = nfstrdup( s ); + for (i = 0; cs[i]; i++) + if (cs[i] == '/') + cs[i] = '!'; + return cs; } int -sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags, - unsigned int max_size, unsigned int max_msgs) +sync_boxes( store_t *mctx, const char *mname, + store_t *sctx, const char *sname, + channel_conf_t *chan ) { - message_t *cur; - message_t *tmp; - char path[_POSIX_PATH_MAX]; - char newpath[_POSIX_PATH_MAX]; - char suffix[_POSIX_PATH_MAX]; - char *p; - int fd; - int ret; - int fetched = 0; - int upload = 0; - unsigned int msg_count; + driver_t *mdriver = mctx->conf->driver, *sdriver = sctx->conf->driver; + message_t *mmsg, *smsg, *nmmsg, *nsmsg; + sync_rec_t *recs, *srec, **srecadd, *nsrec; + char *dname, *jname, *nname, *lname, *s, *cmname, *csname; + FILE *dfp, *jfp, *nfp; + int mopts, sopts; + int nom, nos, delm, dels, mex, sex; + int muidval, suidval, smaxxuid, mmaxuid, smaxuid, minwuid, maxwuid; + int t1, t2, t3; + int lfd, ret, line, todel, delt, i, *mexcs, nmexcs, rmexcs; + unsigned char nflags; + struct stat st; + struct flock lck; + char fbuf[16]; /* enlarge when support for keywords is added */ + char buf[64]; - if (mbox->uidseen) - { - if (mbox->uidvalidity != imap->uidvalidity) - { - /* if the UIDVALIDITY value has changed, it means all our - * local UIDs are invalid, so we can't sync. - */ - fprintf (stderr, - "ERROR: UIDVALIDITY of '%s' changed on server\n", - imap->box->box); - return -1; + ret = SYNC_OK; + recs = 0, srecadd = &recs; + + nmmsg = nsmsg = 0; + + mctx->uidvalidity = sctx->uidvalidity = 0; + mopts = sopts = 0; + makeopts( chan->sops, chan->slave, &sopts, chan->master, &mopts ); + makeopts( chan->mops, chan->master, &mopts, chan->slave, &sopts ); + if ((chan->sops & (OP_NEW|OP_RENEW)) && chan->max_messages) + sopts |= OPEN_OLD|OPEN_NEW|OPEN_FLAGS; + if (!mname || (mctx->conf->map_inbox && !strcmp( mctx->conf->map_inbox, mname ))) + mname = "INBOX"; + mctx->name = mname; + mdriver->prepare( mctx, mopts ); + if (!sname || (sctx->conf->map_inbox && !strcmp( sctx->conf->map_inbox, sname ))) + sname = "INBOX"; + sctx->name = sname; + sdriver->prepare( sctx, sopts ); + + if (!strcmp( chan->sync_state ? chan->sync_state : global_sync_state, "*" )) { + if (!sctx->path) { + fprintf( stderr, "Error: store '%s' does not support in-box sync state\n", chan->slave->name ); + return SYNC_SLAVE_BAD; + } + nfasprintf( &dname, "%s/." EXE "state", sctx->path ); + } else { + csname = clean_strdup( sname ); + if (chan->sync_state) + nfasprintf( &dname, "%s%s", chan->sync_state, csname ); + else { + cmname = clean_strdup( mname ); + nfasprintf( &dname, "%s:%s:%s_:%s:%s", global_sync_state, + chan->master->name, cmname, chan->slave->name, csname ); + free( cmname ); + } + free( csname ); } - } - else if (maildir_set_uidvalidity (mbox, imap->uidvalidity)) - { - fputs ("ERROR: unable to store UIDVALIDITY\n", stderr); - return -1; - } - - if (mbox->maxuid == 0 || imap->maxuid > mbox->maxuid) - { - mbox->maxuid = imap->maxuid; - } - - /* if we are --fast mode, the mailbox wont have been loaded, so - * this next step is skipped. - */ - for (cur = mbox->msgs; cur; cur = cur->next) - { - tmp = find_msg (imap->msgs, cur->uid); - if (!tmp) - { - /* if this message wasn't fetched from the server, attempt to - * upload it - */ - if (cur->uid == (unsigned int) -1) - { - struct stat sb; - - if ((cur->flags & D_DELETED) && (flags & SYNC_EXPUNGE)) - { - /* - * This message is marked as deleted and we are - * expunging. Don't upload to the server. - */ - continue; + nfasprintf( &jname, "%s.journal", dname ); + nfasprintf( &nname, "%s.new", dname ); + nfasprintf( &lname, "%s.lock", dname ); + muidval = suidval = smaxxuid = mmaxuid = smaxuid = 0; + memset( &lck, 0, sizeof(lck) ); +#if SEEK_SET != 0 + lck.l_whence = SEEK_SET; +#endif +#if F_WRLCK != 0 + lck.l_type = F_WRLCK; +#endif + if ((lfd = open( lname, O_WRONLY|O_CREAT, 0666 )) < 0) { + if (errno != ENOENT) { + lferr: + fprintf( stderr, "Error: cannot create lock file %s: %s\n", lname, strerror(errno) ); + ret = SYNC_FAIL; + goto bail2; } - - if (!upload) - info ("Uploading messages"); - infoc ('.'); - fflush (stdout); - upload++; - - /* upload the message if its not too big */ - snprintf (path, sizeof (path), "%s/%s/%s", mbox->path, - cur->new ? "new" : "cur", cur->file); - if (stat (path, &sb)) - { - perror (path); - continue; /* not fatal */ - } - if (imap->box->max_size > 0 - && sb.st_size > imap->box->max_size) - { - info ("Local message %s is too large (%lu), skipping...\n", - cur->file, (unsigned long) sb.st_size); - continue; - } - fd = open (path, O_RDONLY); - if (fd == -1) - { - /* This can happen if the message was simply deleted (ok) - or the flags changed (not ok - maildir sucks). */ - fprintf (stderr, "Error, unable to open %s: %s (errno %d)\n", - path, strerror (errno), errno); - continue; - } - - cur->size = sb.st_size; - cur->uid = imap_append_message (imap, fd, cur); - if (cur->uid != (unsigned int) -1) { - /* update the db */ - set_uid (mbox->db, cur->file, cur->uid); - if (!cur->uid) - warn ("Warning: no UID for new messge %s\n", cur->file); - else if (cur->uid > mbox->maxuid) - mbox->maxuid = cur->uid; - } - - close (fd); - } - /* - * message used to exist on server but no longer does (we know - * this beacause it has a UID associated with it). - */ - else if (flags & SYNC_DELETE) - { - cur->flags |= D_DELETED; - cur->dead = 1; - mbox->deleted++; - } - /* if the user doesn't want local msgs deleted when they don't - * exist on the server, warn that such messages exist. - */ - else - info ("Local message %u doesn't exist on server\n", cur->uid); - continue; + goto skiprd; } - tmp->processed = 1; + if (fcntl( lfd, F_SETLK, &lck )) { + lckerr: + fprintf( stderr, "Error: channel :%s:%s-:%s:%s is locked\n", + chan->master->name, mname, chan->slave->name, sname ); + ret = SYNC_FAIL; + goto bail1; + } + if ((dfp = fopen( dname, "r" ))) { + debug( "reading sync state %s ...\n", dname ); + if (fscanf( dfp, "%d:%d %d:%d:%d\n", &muidval, &mmaxuid, &suidval, &smaxxuid, &smaxuid) != 5) { + fprintf( stderr, "Error: invalid sync state header in %s\n", dname ); + fclose( dfp ); + ret = SYNC_FAIL; + goto bail; + } + line = 1; + while (fgets( buf, sizeof(buf), dfp )) { + line++; + fbuf[0] = 0; + if (sscanf( buf, "%d %d %15s\n", &t1, &t2, fbuf ) < 2) { + fprintf( stderr, "Error: invalid sync state entry at %s:%d\n", dname, line ); + fclose( dfp ); + ret = SYNC_FAIL; + goto bail; + } + srec = nfmalloc( sizeof(*srec) ); + srec->muid = t1; + srec->suid = t2; + s = fbuf; + if (*s == 'X') { + s++; + srec->status = S_EXPIRED; + } else + srec->status = 0; + srec->flags = parse_flags( s ); + debug( " entry (%d,%d,%u,%s)\n", srec->muid, srec->suid, srec->flags, srec->status & S_EXPIRED ? "X" : "" ); + srec->next = 0; + *srecadd = srec; + srecadd = &srec->next; + } + fclose( dfp ); + } else { + if (errno != ENOENT) { + fprintf( stderr, "Error: cannot read sync state %s\n", dname ); + ret = SYNC_FAIL; + goto bail; + } + } + if ((jfp = fopen( jname, "r" ))) { + if (!stat( nname, &st )) { + debug( "recovering journal ...\n" ); + line = 0; + srec = recs; + while (fgets( buf, sizeof(buf), jfp )) { + line++; + if (buf[0] == '^') + srec = recs; + else { + if (buf[0] == '(' || buf[0] == ')' ? + (sscanf( buf + 2, "%d\n", &t1 ) != 1) : + buf[0] == '-' || buf[0] == '|' ? + (sscanf( buf + 2, "%d %d\n", &t1, &t2 ) != 2) : + (sscanf( buf + 2, "%d %d %d\n", &t1, &t2, &t3 ) != 3)) + { + fprintf( stderr, "Error: malformed journal entry at %s:%d\n", jname, line ); + fclose( jfp ); + ret = SYNC_FAIL; + goto bail; + } + if (buf[0] == '(') + mmaxuid = t1; + else if (buf[0] == ')') + smaxuid = t1; + else if (buf[0] == '|') { + muidval = t1; + suidval = t2; + } else if (buf[0] == '+') { + srec = nfmalloc( sizeof(*srec) ); + srec->muid = t1; + srec->suid = t2; + srec->flags = t3; + debug( " new entry(%d,%d,%u)\n", t1, t2, t3 ); + srec->status = 0; + srec->next = 0; + *srecadd = srec; + srecadd = &srec->next; + } else { + for (; srec; srec = srec->next) + if (srec->muid == t1 && srec->suid == t2) + goto syncfnd; + fprintf( stderr, "Error: journal entry at %s:%d refers to non-existing sync state entry\n", jname, line ); + fclose( jfp ); + ret = SYNC_FAIL; + goto bail; + syncfnd: + debug( " entry(%d,%d,%u) ", srec->muid, srec->suid, srec->flags ); + switch (buf[0]) { + case '-': + debug( "killed\n" ); + srec->status = S_DEAD; + break; + case '<': + debug( "master now %d\n", t3 ); + srec->muid = t3; + break; + case '>': + debug( "slave now %d\n", t3 ); + srec->suid = t3; + break; + case '*': + debug( "flags now %d\n", t3 ); + srec->flags = t3; + break; + case '~': + debug( "expired now %d\n", t3 ); + if (t3) { + if (smaxxuid < t2) + smaxxuid = t2; + srec->status |= S_EXPIRED; + } else + srec->status &= ~S_EXPIRED; + break; + default: + fprintf( stderr, "Error: unrecognized journal entry at %s:%d\n", jname, line ); + fclose( jfp ); + ret = SYNC_FAIL; + goto bail; + } + } + } + } + } + fclose( jfp ); + } else { + if (errno != ENOENT) { + fprintf( stderr, "Error: cannot read journal %s\n", jname ); + ret = SYNC_FAIL; + goto bail; + } + } + skiprd: - /* if the message is deleted, and CopyDeletedTo is set, and we - * are expunging, make a copy of the message now. - */ - if (((cur->flags | tmp->flags) & D_DELETED) != 0 && - (flags & SYNC_EXPUNGE) && imap->box->copy_deleted_to) - { - if (imap_copy_message (imap, cur->uid, - imap->box->copy_deleted_to)) - { - fprintf (stderr, - "ERROR: unable to copy deleted message to \"%s\"\n", - imap->box->copy_deleted_to); - return -1; - } + if (sctx->opts & OPEN_NEW) + maxwuid = INT_MAX; + else if (sctx->opts & OPEN_OLD) { + maxwuid = 0; + for (srec = recs; srec; srec = srec->next) + if (!(srec->status & S_DEAD) && srec->suid > maxwuid) + maxwuid = srec->suid; + } else + maxwuid = 0; + info( "Selecting slave... " ); + debug( "selecting slave [1,%d]\n", maxwuid ); + switch (sdriver->select( sctx, (sctx->opts & OPEN_OLD) ? 1 : smaxuid + 1, maxwuid, 0, 0 )) { + case DRV_STORE_BAD: ret = SYNC_SLAVE_BAD; goto bail; + case DRV_BOX_BAD: ret = SYNC_FAIL; goto bail; + } + info( "%d messages, %d recent\n", sctx->count, sctx->recent ); + dump_box( sctx ); + + if (suidval && suidval != sctx->uidvalidity) { + fprintf( stderr, "Error: UIDVALIDITY of slave changed\n" ); + ret = SYNC_FAIL; + goto bail; } - /* check if local flags are different from server flags. - * ignore \Recent and \Draft - */ - if (cur->flags != (tmp->flags & ~(D_RECENT | D_DRAFT))) - { - /* set local flags that don't exist on the server */ - if (!(tmp->flags & D_DELETED) && (cur->flags & D_DELETED)) - imap->deleted++; - - imap_set_flags (imap, cur->uid, cur->flags & ~tmp->flags); - - /* update local flags */ - if ((cur->flags & D_DELETED) == 0 && (tmp->flags & D_DELETED)) - mbox->deleted++; - cur->flags |= (tmp->flags & ~(D_RECENT | D_DRAFT)); - - /* don't bother renaming the file if we are just going to - * remove it later. - */ - if ((cur->flags & D_DELETED) == 0 || (flags & SYNC_EXPUNGE) == 0) - { - size_t sl; - - /* generate old path */ - snprintf (path, sizeof (path), "%s/%s/%s", - mbox->path, cur->new ? "new" : "cur", cur->file); - - /* generate new path */ - strcpy (newpath, path); - p = strchr (newpath, ':'); - sl = p ? (size_t)(p - newpath) : strlen (newpath); - snprintf (newpath + sl, sizeof (newpath) - sl, ":2,%s%s%s%s", - (cur->flags & D_FLAGGED) ? "F" : "", - (cur->flags & D_ANSWERED) ? "R" : "", - (cur->flags & D_SEEN) ? "S" : "", - (cur->flags & D_DELETED) ? "T" : ""); - - if (rename (path, newpath)) - { - perror ("Warning: cannot set flags on message"); - } - else - { - /* update the filename in the msg struct */ - p = strrchr (newpath, '/'); - free (cur->file); - cur->file = strdup (p + 1); - } - } + s = strrchr( dname, '/' ); + *s = 0; + mkdir( dname, 0700 ); + *s = '/'; + if (lfd < 0) { + if ((lfd = open( lname, O_WRONLY|O_CREAT, 0666 )) < 0) + goto lferr; + if (fcntl( lfd, F_SETLK, &lck )) + goto lckerr; } - } - - if (upload) - info (" %d messages.\n", upload); - - if (max_msgs == 0) - max_msgs = UINT_MAX; - else - { - /* expire messages in excess of the max-count for this mailbox. - * flagged mails are considered sacrosant and not deleted. - * we have already done the upload to the server, so messing with - * the flags variable do not have remote side effects. - */ - for (cur = imap->msgs, msg_count = 0; - cur && msg_count < max_msgs; cur = cur->next, msg_count++) - { - tmp = find_msg (mbox->msgs, cur->uid); - if (tmp) - tmp->wanted = 1; + if (!(nfp = fopen( nname, "w" ))) { + fprintf( stderr, "Error: cannot write new sync state %s\n", nname ); + ret = SYNC_FAIL; + goto bail; } - for (cur = mbox->msgs; cur; cur = cur->next) - { - if (!cur->wanted && !(cur->flags & D_FLAGGED)) - { - cur->flags |= D_DELETED; - cur->dead = 1; - mbox->deleted++; - } + if (!(jfp = fopen( jname, "a" ))) { + fprintf( stderr, "Error: cannot write journal %s\n", jname ); + fclose( nfp ); + ret = SYNC_FAIL; + goto bail; } - } + setlinebuf( jfp ); - for (cur = imap->msgs, msg_count = 0; - cur && msg_count < max_msgs; cur = cur->next, msg_count++) - { - if (!cur->processed) - { - /* new message on server */ + mexcs = 0; + nmexcs = rmexcs = 0; + minwuid = INT_MAX; + if (smaxxuid) { + debug( "preparing master selection - max expired slave uid is %d\n", smaxxuid ); + for (srec = recs; srec; srec = srec->next) { + if (srec->status & S_DEAD) + continue; + if (srec->status & S_EXPIRED) { + if (!srec->suid || ((sctx->opts & OPEN_OLD) && !findmsg( sctx, srec->suid, &nsmsg, "slave" ))) + srec->status |= S_EXP_SLAVE; + else if (minwuid > srec->muid) + minwuid = srec->muid; + } else if (smaxxuid < srec->suid && minwuid > srec->muid) + minwuid = srec->muid; + } + debug( " min non-orphaned master uid is %d\n", minwuid ); + fprintf( jfp, "^\n" ); /* if any S_EXP_SLAVE */ + for (srec = recs; srec; srec = srec->next) { + if (srec->status & S_DEAD) + continue; + if (srec->status & S_EXP_SLAVE) { + if (minwuid > srec->muid && mmaxuid >= srec->muid) { + debug( " -> killing (%d,%d)\n", srec->muid, srec->suid ); + srec->status = S_DEAD; + fprintf( jfp, "- %d %d\n", srec->muid, srec->suid ); + } else if (srec->suid) { + debug( " -> orphaning (%d,[%d])\n", srec->muid, srec->suid ); + fprintf( jfp, "> %d %d 0\n", srec->muid, srec->suid ); + srec->suid = 0; + } + } else if (minwuid > srec->muid) { + if (srec->suid < 0) { + if (mmaxuid >= srec->muid) { + debug( " -> killing (%d,%d)\n", srec->muid, srec->suid ); + srec->status = S_DEAD; + fprintf( jfp, "- %d %d\n", srec->muid, srec->suid ); + } + } else if (srec->muid > 0 && srec->suid && (mctx->opts & OPEN_OLD) && + (!(mctx->opts & OPEN_NEW) || mmaxuid >= srec->muid)) { + if (nmexcs == rmexcs) { + rmexcs = rmexcs * 2 + 100; + mexcs = nfrealloc( mexcs, rmexcs * sizeof(int) ); + } + mexcs[nmexcs++] = srec->muid; + } + } + } + debug( " exception list is:" ); + for (i = 0; i < nmexcs; i++) + debug( " %d", mexcs[i] ); + debug( "\n" ); + } else if (mctx->opts & OPEN_OLD) + minwuid = 1; + if (mctx->opts & OPEN_NEW) { + if (minwuid > mmaxuid + 1) + minwuid = mmaxuid + 1; + maxwuid = INT_MAX; + } else if (mctx->opts & OPEN_OLD) { + maxwuid = 0; + for (srec = recs; srec; srec = srec->next) + if (!(srec->status & S_DEAD) && srec->muid > maxwuid) + maxwuid = srec->muid; + } else + maxwuid = 0; + info( "Selecting master... " ); + debug( "selecting master [%d,%d]\n", minwuid, maxwuid ); + switch (mdriver->select( mctx, minwuid, maxwuid, mexcs, nmexcs )) { + case DRV_STORE_BAD: ret = SYNC_MASTER_BAD; goto finish; + case DRV_BOX_BAD: ret = SYNC_FAIL; goto finish; + } + info( "%d messages, %d recent\n", mctx->count, mctx->recent ); + dump_box( mctx ); - if ((flags & SYNC_EXPUNGE) && (cur->flags & D_DELETED)) - { - /* this message has been marked for deletion and - * we are currently expunging a mailbox. don't - * bother downloading this message - */ - continue; - } + if (muidval && muidval != mctx->uidvalidity) { + fprintf( stderr, "Error: UIDVALIDITY of master changed\n" ); + ret = SYNC_FAIL; + goto finish; + } - if (max_size && cur->size > max_size) - { - info ("Remote message %u skipped because it is too big (%u)\n", - cur->uid, cur->size); - continue; - } + if (!muidval || !suidval) { + muidval = mctx->uidvalidity; + suidval = sctx->uidvalidity; + fprintf( jfp, "| %d %d\n", muidval, suidval ); + } - /* construct the flags part of the file name. */ + info( "Synchronizing\n" ); + debug( "synchronizing old entries\n" ); + fprintf( jfp, "^\n" ); + for (srec = recs; srec; srec = srec->next) { + if (srec->status & S_DEAD) + continue; + debug( "pair (%d,%d)\n", srec->muid, srec->suid ); + mmsg = findmsg( mctx, srec->muid, &nmmsg, "master" ); + smsg = (srec->status & S_EXP_SLAVE) ? 0 : findmsg( sctx, srec->suid, &nsmsg, "slave" ); + nom = !mmsg && (mctx->opts & OPEN_OLD); + nos = !smsg && (sctx->opts & OPEN_OLD); + if (nom && nos) { + debug( " vanished\n" ); + /* d.1) d.5) d.6) d.10) d.11) */ + srec->status = S_DEAD; + fprintf( jfp, "- %d %d\n", srec->muid, srec->suid ); + } else { + delm = nom && (srec->muid > 0); + dels = nos && (srec->suid > 0); + nflags = srec->flags; - *suffix = 0; - if (cur->flags & ~(D_RECENT | D_DRAFT)) - { - snprintf (suffix, sizeof (suffix), ":2,%s%s%s%s", - (cur->flags & D_FLAGGED) ? "F" : "", - (cur->flags & D_ANSWERED) ? "R" : "", - (cur->flags & D_SEEN) ? "S" : "", - (cur->flags & D_DELETED) ? "T" : ""); - } + if ((ret = sync_old( chan->mops, sctx, mctx, chan->master, jfp, 0, &nflags, srec, smsg, mmsg, dels, delm )) != SYNC_OK || + (ret = sync_old( chan->sops, mctx, sctx, chan->slave, jfp, 1, &nflags, srec, mmsg, smsg, delm, dels )) != SYNC_OK) + goto finish; - for (;;) - { - /* create new file */ - snprintf (path, sizeof (path), "%s/tmp/%ld_%d.%d.%s%s", - mbox->path, time (0), MaildirCount++, getpid (), - Hostname, suffix); + if (srec->flags != nflags) { + debug( " updating flags (%u -> %u)\n", srec->flags, nflags ); + srec->flags = nflags; + fprintf( jfp, "* %d %d %u\n", srec->muid, srec->suid, nflags ); + } + if (mmsg && (mmsg->flags & F_DELETED)) + srec->status |= S_DEL_MASTER; + if (smsg && (smsg->flags & F_DELETED)) + srec->status |= S_DEL_SLAVE; + } + } - if ((fd = open (path, O_WRONLY | O_CREAT | O_EXCL, 0600)) > 0) - break; - if (errno != EEXIST) - { - perror (path); - break; + debug( "synchronizing new entries\n" ); + if ((ret = sync_new( chan->mops, sctx, mctx, chan->master, jfp, &srecadd, 0, &smaxuid )) != SYNC_OK || + (ret = sync_new( chan->sops, mctx, sctx, chan->slave, jfp, &srecadd, 1, &mmaxuid )) != SYNC_OK) + goto finish; + + if ((chan->sops & (OP_NEW|OP_RENEW)) && chan->max_messages) { + debug( "expiring excessive entries\n" ); + todel = sctx->count - chan->max_messages; + for (smsg = sctx->msgs; smsg && todel > 0; smsg = smsg->next) + if (!(smsg->status & M_DEAD) && (smsg->flags & F_DELETED)) + todel--; + delt = 0; + for (smsg = sctx->msgs; smsg && todel > 0; smsg = smsg->next) { + if ((smsg->status & M_DEAD) || (smsg->flags & F_DELETED)) + continue; + if ((smsg->flags & F_FLAGGED) || (smsg->status & M_NOT_SYNCED)) /* add M_DESYNCED? */ + todel--; + else if (!(smsg->status & M_RECENT)) { + smsg->status |= M_EXPIRED; + delt++; + todel--; + } + } + if (delt) { + fprintf( jfp, "^\n" ); + for (srec = recs; srec; srec = srec->next) { + if (srec->status & (S_DEAD|S_EXPIRED)) + continue; + smsg = findmsg( sctx, srec->suid, &nsmsg, "slave" ); + if (smsg && (smsg->status & M_EXPIRED)) { + debug( " expiring pair(%d,%d)\n", srec->muid, srec->suid ); + /* log first, so deletion can't be misinterpreted! */ + fprintf( jfp, "~ %d %d 1\n", srec->muid, srec->suid ); + if (smaxxuid < srec->suid) + smaxxuid = srec->suid; + srec->status |= S_EXPIRED; + switch (sdriver->set_flags( sctx, smsg, 0, F_DELETED, 0 )) { + case DRV_STORE_BAD: ret = SYNC_SLAVE_BAD; goto finish; + case DRV_BOX_BAD: ret = SYNC_FAIL; goto finish; + default: /* ok */ break; + case DRV_OK: srec->status |= S_DEL_SLAVE; + } + } + } + } + } + + /* Doing CLOSE here instead of EXPUNGE above saves network traffic. + But it costs more server power for single-file formats. And it + makes disk-full/quota-exceeded more probable. */ + mex = sex = 0; + if (chan->mops & OP_EXPUNGE) { + info( "Expunging master\n" ); + debug( "expunging master\n" ); + switch (expunge( mctx, sctx )) { + case EX_STORE_BAD: ret = SYNC_MASTER_BAD; goto finish; + case EX_RSTORE_BAD: ret = SYNC_SLAVE_BAD; goto finish; + default: ret = SYNC_FAIL; break; + case EX_OK: mex = 1; + } + } + if (chan->sops & OP_EXPUNGE) { + info( "Expunging slave\n" ); + debug( "expunging slave\n" ); + switch (expunge( sctx, mctx )) { + case EX_STORE_BAD: ret = SYNC_SLAVE_BAD; goto finish; + case EX_RSTORE_BAD: ret = SYNC_MASTER_BAD; goto finish; + default: ret = SYNC_FAIL; break; + case EX_OK: mex = 1; + } + } + if (mex || sex) { + /* This cleanup is not strictly necessary, as the next full sync + would throw out the dead entries anyway. But ... */ + + minwuid = INT_MAX; + if (smaxxuid) { + debug( "preparing entry purge - max expired slave uid is %d\n", smaxxuid ); + for (srec = recs; srec; srec = srec->next) { + if (srec->status & S_DEAD) + continue; + if (!((srec->suid <= 0 || ((srec->status & S_DEL_SLAVE) && sex)) && + (srec->muid <= 0 || ((srec->status & S_DEL_MASTER) && mex) || (srec->status & S_EXPIRED))) && + smaxxuid < srec->suid && minwuid > srec->muid) + minwuid = srec->muid; + } + debug( " min non-orphaned master uid is %d\n", minwuid ); } - sleep (2); - } - - if (fd < 0) - continue; - - /* give some visual feedback that something is happening */ - if (!fetched) - info ("Fetching new messages"); - infoc ('.'); - fflush (stdout); - fetched++; - - ret = imap_fetch_message (imap, cur->uid, fd); - - if (fsync (fd)) - { - perror ("fsync"); - close (fd); - } - else if (close (fd)) - perror ("close"); - else if (!ret) - { - p = strrchr (path, '/'); - - snprintf (newpath, sizeof (newpath), "%s/%s%s", mbox->path, - (cur->flags & D_SEEN) ? "cur" : "new", p); /* hack: ignore \recent, use !\seen instead */ - - /* its ok if this fails, the next time we sync the message - * will get pulled down - */ - if (link (path, newpath)) - perror ("link"); - else - { - /* update the db with the UID mapping for this file */ - set_uid (mbox->db, p + 1, cur->uid); - if (cur->uid > mbox->maxuid) - mbox->maxuid = cur->uid; + fprintf( jfp, "^\n" ); + for (srec = recs; srec; srec = srec->next) { + if (srec->status & S_DEAD) + continue; + if (srec->suid <= 0 || ((srec->status & S_DEL_SLAVE) && sex)) { + if (srec->muid <= 0 || ((srec->status & S_DEL_MASTER) && mex)) { + debug( " -> killing (%d,%d)\n", srec->muid, srec->suid ); + srec->status = S_DEAD; + fprintf( jfp, "- %d %d\n", srec->muid, srec->suid ); + } else if (srec->status & S_EXPIRED) { + if (mmaxuid >= srec->muid && minwuid > srec->muid) { + debug( " -> killing (%d,%d)\n", srec->muid, srec->suid ); + srec->status = S_DEAD; + fprintf( jfp, "- %d %d\n", srec->muid, srec->suid ); + } else if (srec->suid) { + debug( " -> orphaning (%d,[%d])\n", srec->muid, srec->suid ); + fprintf( jfp, "> %d %d 0\n", srec->muid, srec->suid ); + srec->suid = 0; + } + } + } } - } - - /* always remove the temp file */ - unlink (path); } - } - if (fetched) - info (" %d messages\n", fetched); + finish: + fprintf( nfp, "%d:%d %d:%d:%d\n", muidval, mmaxuid, suidval, smaxxuid, smaxuid ); + for (srec = recs; srec; srec = srec->next) { + if (srec->status & S_DEAD) + continue; + make_flags( srec->flags, fbuf ); + fprintf( nfp, "%d %d %s%s\n", srec->muid, srec->suid, + srec->status & S_EXPIRED ? "X" : "", fbuf ); + } - if (maildir_update_maxuid (mbox)) - return -1; + fclose( nfp ); + fclose( jfp ); + /* order is important! */ + rename( nname, dname ); + unlink( jname ); - return 0; + bail: + for (srec = recs; srec; srec = nsrec) { + nsrec = srec->next; + free( srec ); + } + unlink( lname ); + bail1: + close( lfd ); + bail2: + free( lname ); + free( nname ); + free( jname ); + free( dname ); + return ret; } + diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..989a03f --- /dev/null +++ b/src/util.c @@ -0,0 +1,389 @@ +/* + * mbsync - mailbox synchronizer + * Copyright (C) 2000-2002 Michael R. Elkins + * Copyright (C) 2002-2004 Oswald Buddenhagen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * As a special exception, mbsync may be linked with the OpenSSL library, + * despite that library's more restrictive license. + */ + +#include "isync.h" + +#include +#include +#include +#include +#include +#include +#include + +int Verbose, Quiet, Debug; + +void +debug( const char *msg, ... ) +{ + va_list va; + + if (Debug) { + va_start( va, msg ); + vprintf( msg, va ); + va_end( va ); + } +} + +void +info( const char *msg, ... ) +{ + va_list va; + + if (!Quiet) { + va_start( va, msg ); + vprintf( msg, va ); + va_end( va ); + fflush( stdout ); + } +} + +void +infoc( char c ) +{ + if (!Quiet) { + putchar( c ); + fflush( stdout ); + } +} + +void +warn( const char *msg, ... ) +{ + va_list va; + + if (Quiet < 2) { + va_start( va, msg ); + vfprintf( stderr, msg, va ); + va_end( va ); + } +} + +char * +next_arg( char **s ) +{ + char *ret; + + if (!s || !*s) + return 0; + while (isspace( (unsigned char) **s )) + (*s)++; + if (!**s) { + *s = 0; + return 0; + } + if (**s == '"') { + ++*s; + ret = *s; + *s = strchr( *s, '"' ); + } else { + ret = *s; + while (**s && !isspace( (unsigned char) **s )) + (*s)++; + } + if (*s) { + if (**s) + *(*s)++ = 0; + if (!**s) + *s = 0; + } + return ret; +} + +void +add_string_list( string_list_t **list, const char *str ) +{ + string_list_t *elem; + int len; + + len = strlen( str ); + elem = nfmalloc( sizeof(*elem) + len ); + elem->next = *list; + *list = elem; + memcpy( elem->string, str, len + 1 ); +} + +void +free_string_list( string_list_t *list ) +{ + string_list_t *tlist; + + for (; list; list = tlist) { + tlist = list->next; + free( list ); + } +} + +void +free_generic_messages( message_t *msgs ) +{ + message_t *tmsg; + + for (; msgs; msgs = tmsg) { + tmsg = msgs->next; + free( msgs ); + } +} + +void +strip_cr( msg_data_t *msgdata ) +{ + int i, o; + + if (msgdata->crlf) { + for (i = o = 0; i < msgdata->len; i++) + if (msgdata->data[i] != '\r') + msgdata->data[o++] = msgdata->data[i]; + msgdata->len = o; + msgdata->crlf = 0; + } +} + +#ifndef HAVE_VASPRINTF +static int +vasprintf( char **strp, const char *fmt, va_list ap ) +{ + int len; + char tmp[1024]; + + if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap) ) < 0) + *strp = 0; + else if ((*strp = malloc( len + 1 ))) { + if (len >= sizeof(tmp)) + vsprintf( *strp, fmt, ap ); + else + memcpy( *strp, tmp, len + 1 ); + } + return len; +} +#endif + +void +oob( void ) +{ + fputs( "Fatal: buffer too small. Please report a bug.\n", stderr ); + abort(); +} + +int +nfsnprintf( char *buf, int blen, const char *fmt, ... ) +{ + int ret; + va_list va; + + va_start( va, fmt ); + if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen) + oob(); + va_end( va ); + return ret; +} + +static void ATTR_NORETURN +oom( void ) +{ + fputs( "Fatal: Out of memory\n", stderr ); + abort(); +} + +void * +nfmalloc( size_t sz ) +{ + void *ret; + + if (!(ret = malloc( sz ))) + oom(); + return ret; +} + +void * +nfcalloc( size_t sz ) +{ + void *ret; + + if (!(ret = calloc( sz, 1 ))) + oom(); + return ret; +} + +void * +nfrealloc( void *mem, size_t sz ) +{ + char *ret; + + if (!(ret = realloc( mem, sz )) && sz) + oom(); + return ret; +} + +char * +nfstrdup( const char *str ) +{ + char *ret; + + if (!(ret = strdup( str ))) + oom(); + return ret; +} + +int +nfvasprintf( char **str, const char *fmt, va_list va ) +{ + int ret = vasprintf( str, fmt, va ); + if (!*str) + oom(); + return ret; +} + +int +nfasprintf( char **str, const char *fmt, ... ) +{ + int ret; + va_list va; + + va_start( va, fmt ); + ret = nfvasprintf( str, fmt, va ); + va_end( va ); + return ret; +} + +/* +static struct passwd * +cur_user( void ) +{ + char *p; + struct passwd *pw; + uid_t uid; + + uid = getuid(); + if ((!(p = getenv("LOGNAME")) || !(pw = getpwnam( p )) || pw->pw_uid != uid) && + (!(p = getenv("USER")) || !(pw = getpwnam( p )) || pw->pw_uid != uid) && + !(pw = getpwuid( uid ))) + { + fputs ("Cannot determinate current user\n", stderr); + return 0; + } + return pw; +} +*/ + +static char * +my_strndup( const char *s, size_t nchars ) +{ + char *r = nfmalloc( nchars + 1 ); + memcpy( r, s, nchars ); + r[nchars] = 0; + return r; +} + +char * +expand_strdup( const char *s ) +{ + struct passwd *pw; + const char *p, *q; + char *r; + + if (*s == '~') { + s++; + if (!*s) { + p = 0; + q = Home; + } else if (*s == '/') { + p = s; + q = Home; + } else { + if ((p = strchr( s, '/' ))) { + r = my_strndup( s, (int)(p - s) ); + pw = getpwnam( r ); + free( r ); + } else + pw = getpwnam( s ); + if (!pw) + return 0; + q = pw->pw_dir; + } + nfasprintf( &r, "%s%s", q, p ? p : "" ); + return r; + } else + return nfstrdup( s ); +} + +static int +compare_ints( const void *l, const void *r ) +{ + return *(int *)l - *(int *)r; +} + +void +sort_ints( int *arr, int len ) +{ + qsort( arr, len, sizeof(int), compare_ints ); +} + + +static struct { + unsigned char i, j, s[256]; +} rs; + +void +arc4_init( void ) +{ + int i, fd; + unsigned char j, si, dat[128]; + + if ((fd = open( "/dev/urandom", O_RDONLY )) < 0 && (fd = open( "/dev/random", O_RDONLY )) < 0) { + fprintf( stderr, "Fatal: no random number source available.\n" ); + exit( 3 ); + } + if (read( fd, dat, 128 ) != 128) { + fprintf( stderr, "Fatal: cannot read random number source.\n" ); + exit( 3 ); + } + close( fd ); + + for (i = 0; i < 256; i++) + rs.s[i] = i; + for (i = j = 0; i < 256; i++) { + si = rs.s[i]; + j += si + dat[i & 127]; + rs.s[i] = rs.s[j]; + rs.s[j] = si; + } + rs.i = rs.j = 0; + + for (i = 0; i < 256; i++) + arc4_getbyte(); +} + +unsigned char +arc4_getbyte( void ) +{ + unsigned char si, sj; + + rs.i++; + si = rs.s[rs.i]; + rs.j += si; + sj = rs.s[rs.j]; + rs.s[rs.i] = sj; + rs.s[rs.j] = si; + return rs.s[(si + sj) & 0xff]; +}