├── debian ├── compat ├── dirs ├── docs ├── watch ├── .gitignore ├── generate-deb ├── copyright ├── rules ├── control └── changelog ├── src ├── compat │ ├── .gitignore │ ├── Makefile.am │ ├── isyncrc.sample │ ├── isync.h │ ├── util.c │ ├── convert.c │ ├── main.c │ ├── isync.1 │ └── config.c ├── .gitignore ├── Makefile.am ├── mbsyncrc.sample ├── mdconvert.1 ├── mdconvert.c ├── util.c ├── config.c ├── run-tests.pl ├── isync.h └── socket.c ├── autogen.sh ├── .gitignore ├── AUTHORS ├── isync.spec.in ├── acinclude.m4 ├── get-cert ├── Makefile.am ├── README ├── TODO ├── configure.in ├── NEWS └── COPYING /debian/compat: -------------------------------------------------------------------------------- 1 | 4 2 | -------------------------------------------------------------------------------- /debian/dirs: -------------------------------------------------------------------------------- 1 | usr/bin 2 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | NEWS 2 | README 3 | TODO 4 | -------------------------------------------------------------------------------- /src/compat/.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | Makefile 3 | Makefile.in 4 | isync 5 | *.o 6 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | version=3 2 | http://sf.net/isync/ isync-(.*)\.tar\.gz debian uupdate 3 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | Makefile 3 | Makefile.in 4 | mbsync 5 | mdconvert 6 | tmp 7 | *.o 8 | -------------------------------------------------------------------------------- /debian/.gitignore: -------------------------------------------------------------------------------- 1 | files 2 | isync 3 | isync.debhelper.log 4 | isync.postrm.debhelper 5 | isync.substvars 6 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | set -e -v 3 | make -f Makefile.am log 4 | aclocal 5 | autoheader 6 | automake --add-missing 7 | autoconf 8 | -------------------------------------------------------------------------------- /src/compat/Makefile.am: -------------------------------------------------------------------------------- 1 | bin_PROGRAMS = isync 2 | 3 | isync_SOURCES = main.c config.c convert.c util.c 4 | isync_LDADD = -ldb 5 | noinst_HEADERS = isync.h 6 | 7 | man_MANS = isync.1 8 | 9 | exampledir = $(docdir)/examples 10 | example_DATA = isyncrc.sample 11 | 12 | EXTRA_DIST = $(example_DATA) $(man_MANS) 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .autoconf_trace 2 | ChangeLog 3 | INSTALL 4 | Makefile 5 | Makefile.in 6 | autom4te.cache 7 | aclocal.m4 8 | build-stamp 9 | config.h 10 | config.h.in 11 | config.cache 12 | config.guess 13 | config.log 14 | config.status 15 | config.sub 16 | configure 17 | configure.lineno 18 | configure-stamp 19 | depcomp 20 | install-sh 21 | isync.spec 22 | isync-*.tar.gz 23 | missing 24 | patch-stamp 25 | stamp-h 26 | stamp-h.in 27 | stamp-h1 28 | *~ 29 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | if with_compat 2 | compat_dir = compat 3 | endif 4 | SUBDIRS = $(compat_dir) 5 | 6 | bin_PROGRAMS = mbsync mdconvert 7 | 8 | mbsync_SOURCES = main.c sync.c config.c util.c socket.c drv_imap.c drv_maildir.c 9 | mbsync_LDADD = -ldb $(SSL_LIBS) $(SOCK_LIBS) 10 | noinst_HEADERS = isync.h 11 | 12 | mdconvert_SOURCES = mdconvert.c 13 | mdconvert_LDADD = -ldb 14 | 15 | man_MANS = mbsync.1 mdconvert.1 16 | 17 | exampledir = $(docdir)/examples 18 | example_DATA = mbsyncrc.sample 19 | 20 | EXTRA_DIST = run-tests.pl $(example_DATA) $(man_MANS) 21 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Oswald Buddenhagen 2 | * Contributor, current maintainer 3 | 4 | Theodore Ts'o 5 | * Contributor, Debian package co-maintainer 6 | 7 | Nicolas Boullis 8 | * Debian package maintainer and minor upstream contributions 9 | 10 | Michael Elkins 11 | * Original author 12 | 13 | Send questions and bug reports to the isync-devel@lists.sourceforge.net 14 | mailing list. 15 | 16 | _DON'T_ report bugs to Michael, not even in a CC: - he is not actively 17 | involved in isync development any more. 18 | -------------------------------------------------------------------------------- /isync.spec.in: -------------------------------------------------------------------------------- 1 | Summary: Utility to synchronize IMAP mailboxes with local maildir folders 2 | Name: isync 3 | Version: @VERSION@ 4 | Release: 1 5 | License: GPL 6 | Group: Applications/Internet 7 | Source: @PACKAGE@-@VERSION@.tar.gz 8 | URL: http://@PACKAGE@.sf.net/ 9 | Packager: Oswald Buddenhagen 10 | BuildRoot: /var/tmp/%{name}-buildroot 11 | 12 | %description 13 | isync is a command line utility which synchronizes mailboxes; currently 14 | Maildir and IMAP4 mailboxes are supported. 15 | New messages, message deletions and flag changes can be propagated both ways. 16 | It is useful for working in disconnected mode, such as on a laptop or with a 17 | non-permanent internet collection (dIMAP). 18 | 19 | %prep 20 | %setup 21 | %build 22 | %configure 23 | 24 | %install 25 | rm -rf $RPM_BUILD_ROOT 26 | make DESTDIR=$RPM_BUILD_ROOT install 27 | rm -rf $RPM_BUILD_ROOT%{_docdir}/%{name} 28 | 29 | %clean 30 | rm -rf $RPM_BUILD_ROOT 31 | 32 | %files 33 | %doc AUTHORS COPYING NEWS README TODO ChangeLog src/mbsyncrc.sample src/compat/isyncrc.sample 34 | %{_bindir}/* 35 | %{_mandir}/man1/* 36 | -------------------------------------------------------------------------------- /debian/generate-deb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Intended to be run from the root of the isync source tree in the repository. 4 | # 5 | VERSION=`dpkg-parsechangelog | sed -ne 's/^Version: \(.*\)-[^-]\+$/\1/p'` 6 | OLDVERSION=$VERSION 7 | 8 | if echo $VERSION | grep +cvsXXXXXXXX; then 9 | DATE=`date +%Y%m%d` 10 | VERSION=`echo $VERSION | sed -e s/+cvsXXXXXXXX/+cvs${DATE}/` 11 | else 12 | if [ ! -f ../isync_$VERSION.orig.tar.gz ]; then 13 | echo isync_$VERSION.orig.tar.gz must be found in the parent directory. 14 | exit 1 15 | fi 16 | fi 17 | rm -rf ../isync-$VERSION 18 | 19 | fakeroot ./debian/rules clean 20 | cp -rl . ../isync-$VERSION 21 | cd ../isync-$VERSION 22 | if [ "$OLDVERSION" != "$VERSION" ]; then 23 | sed -e s/+cvsXXXXXXXX/+cvs${DATE}/ < debian/changelog > debian/changelog.new 24 | mv debian/changelog.new debian/changelog 25 | fi 26 | find . -name .git -print0 | xargs -0r rm -rf 27 | find . -name .gitignore -print0 | xargs -0r rm 28 | find . -type l -print0 | xargs -0r rm 29 | find . -name .#\*# -print0 | xargs -0r rm 30 | aclocal 31 | autoheader 32 | automake --add-missing --copy 33 | autoconf 34 | if [ -n "$DOSIGN" ]; then 35 | SIGNOPTS= 36 | else 37 | SIGNOPTS="-us -uc" 38 | fi 39 | dpkg-buildpackage -rfakeroot $SIGNOPTS 40 | -------------------------------------------------------------------------------- /src/compat/isyncrc.sample: -------------------------------------------------------------------------------- 1 | # Global configuration section 2 | # Values here are used as defaults for any following Mailbox section that 3 | # doesn't specify it. 4 | 5 | # SSL server certificate file 6 | CertificateFile /etc/ssl/certs/ca-certificates.crt 7 | 8 | # by default, expunge deleted messages (same as -e on command line) 9 | Expunge yes 10 | 11 | # by default delete messages in the local mailbox which no longer exist 12 | # on the server 13 | Delete yes 14 | 15 | # copy deleted messages to the IMAP "Trash" folder 16 | CopyDeletedTo "Trash" 17 | 18 | # my default username, if different from the local username 19 | User me 20 | #Port 143 21 | #Box INBOX 22 | # don't download messages larger than 200K bytes 23 | MaxSize 200000 24 | 25 | ### 26 | ### work mailbox 27 | ### 28 | 29 | Mailbox /home/me/Mail/work 30 | Host work.host.com 31 | Pass xxxxxxxx 32 | # define a shortcut so I can just use "isync work" from the command line 33 | Alias work 34 | # don't auto expunge messages in this box (overridden by -e on command line) 35 | Expunge no 36 | 37 | ### 38 | ### personal mailbox 39 | ### 40 | 41 | Mailbox /home/me/Mail/personal 42 | Host host.play.com 43 | # use a non-default port for this connection 44 | Port 6789 45 | Alias personal 46 | 47 | 48 | ### 49 | ### Remote mailbox over a SSH tunnel 50 | ### 51 | 52 | Mailbox /home/me/Mail/remote 53 | Host host.remote.com 54 | Tunnel "ssh -q host.remote.com /usr/sbin/imapd" 55 | Alias remote 56 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | This package was debianized by Tommi Virtanen and is now 2 | maintained by Nicolas Boullis . 3 | 4 | It was downloaded from http://isync.sourceforge.net/ 5 | 6 | Upstream Author: Michael R. Elkins , 7 | Oswald Buddenhagen 8 | 9 | Copyright: 10 | 11 | * isync - IMAP4 to maildir mailbox synchronizer 12 | * Copyright (C) 2000-2002 Michael R. Elkins 13 | * Copyright (C) 2002-2006 Oswald Buddenhagen 14 | * 15 | * This program is free software; you can redistribute it and/or modify 16 | * it under the terms of the GNU General Public License as published by 17 | * the Free Software Foundation; either version 2 of the License, or 18 | * (at your option) any later version. 19 | * 20 | * This program is distributed in the hope that it will be useful, 21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | * GNU General Public License for more details. 24 | * 25 | * You should have received a copy of the GNU General Public License 26 | * along with this program. If not, see . 27 | * 28 | * As a special exception, isync may be linked with the OpenSSL library, 29 | * despite that library's more restrictive license. 30 | 31 | On Debian systems, the complete text of the GNU General Public 32 | License can be found in /usr/share/common-licenses/GPL 33 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | CFLAGS = -Wall -g 4 | ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) 5 | CFLAGS += -O0 6 | else 7 | CFLAGS += -O2 8 | endif 9 | 10 | DEB_HOST_GNU_TYPE := $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) 11 | DEB_BUILD_GNU_TYPE := $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) 12 | 13 | build: build-stamp 14 | build-stamp: 15 | dh_testdir 16 | ./configure --build=$(DEB_BUILD_GNU_TYPE) --host=$(DEB_HOST_GNU_TYPE) --prefix=/usr --mandir=/usr/share/man 17 | $(MAKE) CFLAGS="$(CFLAGS)" 18 | touch build-stamp 19 | 20 | clean: 21 | dh_testdir 22 | dh_testroot 23 | rm -f build-stamp 24 | [ ! -f Makefile ] || $(MAKE) distclean 25 | dh_clean Makefile config.log config.status 26 | 27 | install: build 28 | dh_testdir 29 | dh_testroot 30 | dh_clean -k 31 | dh_installdirs usr/bin usr/share/man/man1 32 | $(MAKE) DESTDIR=$(CURDIR)/debian/isync install 33 | rm -r $(CURDIR)/debian/isync/usr/share/doc 34 | mv $(CURDIR)/debian/isync/usr/bin/get-cert $(CURDIR)/debian/isync/usr/bin/mbsync-get-cert 35 | 36 | binary-indep: build install 37 | 38 | binary-arch: build install 39 | dh_testdir 40 | dh_testroot 41 | dh_installchangelogs ChangeLog 42 | dh_installdocs AUTHORS NEWS README TODO 43 | dh_installexamples src/mbsyncrc.sample src/compat/isyncrc.sample 44 | dh_installman 45 | dh_strip 46 | dh_compress 47 | dh_fixperms 48 | dh_installdeb 49 | dh_shlibdeps 50 | dh_gencontrol 51 | dh_md5sums 52 | dh_builddeb 53 | 54 | binary: binary-indep binary-arch 55 | .PHONY: build clean binary-indep binary-arch binary install 56 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: isync 2 | Section: mail 3 | Priority: optional 4 | Maintainer: Nicolas Boullis 5 | Uploaders: Theodore Y. Ts'o 6 | Standards-Version: 3.7.3 7 | Build-Depends: libssl-dev (>= 0.9.8), debhelper (>= 4.1.16), dpkg-dev (>= 1.9.0), libdb-dev 8 | 9 | Package: isync 10 | Architecture: any 11 | Depends: ${shlibs:Depends}, ${misc:Depends} 12 | Suggests: mutt 13 | Description: Synchronize Maildir and IMAP4 mailboxes 14 | A command line application which synchronizes mailboxes; currently 15 | Maildir and IMAP4 mailboxes are supported. 16 | New messages, message deletions and flag changes can be propagated both ways. 17 | It is useful for working in disconnected mode, such as on a laptop or with a 18 | non-permanent internet collection (dIMAP). 19 | . 20 | The main application was much improved in version 1.0. Those 21 | improvements lead to interface changes and the application being 22 | renamed to mbsync. The application isync is now only a wrapper to 23 | keep compatibility with earlier versions. 24 | . 25 | Features: 26 | * Fine-grained selection of synchronization operations to perform 27 | * Synchronizes single mailboxes or entire mailbox collections 28 | * Partial mirrors possible: keep only the latest messages locally 29 | * Trash functionality: backup messages before removing them 30 | * IMAP features: 31 | * Supports TLS/SSL via imaps: (port 993) and STARTTLS (RFC2595) 32 | * Supports CRAM-MD5 (RFC2195) for authentication 33 | * Supports NAMESPACE (RFC2342) for simplified configuration 34 | * Pipelining for maximum speed 35 | -------------------------------------------------------------------------------- /src/mbsyncrc.sample: -------------------------------------------------------------------------------- 1 | # Global configuration section 2 | # Values here are used as defaults for any following Channel section that 3 | # doesn't specify them. 4 | Expunge None 5 | Create Both 6 | 7 | MaildirStore local 8 | Path ~/Mail/ 9 | Trash Trash 10 | 11 | 12 | IMAPStore work 13 | Host work.host.com 14 | Pass xxxxxxxx 15 | CertificateFile /etc/ssl/certs/ca-certificates.crt 16 | 17 | Channel work 18 | Master :work: 19 | Slave :local:work 20 | Expunge Slave 21 | Sync PullNew Push 22 | 23 | 24 | IMAPStore personal 25 | Host host.play.com 26 | Port 6789 27 | RequireSSL no 28 | 29 | Channel personal 30 | Master :personal: 31 | Slave :local:personal 32 | Expunge Both 33 | MaxMessages 150 34 | MaxSize 200k 35 | 36 | IMAPStore remote 37 | Tunnel "ssh -q host.remote.com /usr/sbin/imapd" 38 | 39 | Channel remote 40 | Master :remote: 41 | Slave :local:remote 42 | 43 | 44 | Group boxes 45 | Channels work personal remote 46 | 47 | 48 | IMAPStore st1 49 | Host st1.domain.com 50 | RequireCRAM yes 51 | CertificateFile ~/.st1-certificate.crt 52 | 53 | IMAPStore st2 54 | Host imap.another-domain.com 55 | Path non-standard/ 56 | RequireSSL no 57 | UseTLSv1 no 58 | 59 | Channel rst 60 | Master :st1:somebox 61 | Slave :st2: 62 | 63 | 64 | IMAPAccount server 65 | Host imaps:foo.bar.com 66 | CertificateFile ~/.server-certificate.crt 67 | 68 | IMAPStore server 69 | Account server 70 | MapInbox inbox 71 | Trash ~/trash 72 | TrashRemoteNew yes 73 | 74 | MaildirStore mirror 75 | Path ~/Maildir/ 76 | 77 | Channel o2o 78 | Master :server: 79 | Slave :mirror: 80 | Patterns % 81 | 82 | Group partial o2o:inbox,sent-mail,foobar 83 | -------------------------------------------------------------------------------- /acinclude.m4: -------------------------------------------------------------------------------- 1 | # Add --enable-maintainer-mode option to configure. 2 | # From Jim Meyering 3 | # Change it to enable maintainer mode by default by Nicolas Boullis. 4 | 5 | # Copyright 1996, 1998, 2000, 2001, 2002 Free Software Foundation, Inc. 6 | # Copyright 2004 Nicolas Boullis. 7 | 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2, or (at your option) 11 | # any later version. 12 | 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software Foundation, 20 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 21 | 22 | AC_DEFUN([AM_MAINTAINER_MODE], 23 | [AC_MSG_CHECKING([whether to enable maintainer-specific portions of Makefiles]) 24 | dnl maintainer-mode is enabled by default 25 | AC_ARG_ENABLE(maintainer-mode, 26 | [ --disable-maintainer-mode disable make rules and dependencies not useful 27 | (and sometimes confusing) to the casual installer], 28 | USE_MAINTAINER_MODE=$enableval, 29 | USE_MAINTAINER_MODE=yes) 30 | AC_MSG_RESULT([$USE_MAINTAINER_MODE]) 31 | AM_CONDITIONAL(MAINTAINER_MODE, [test $USE_MAINTAINER_MODE = yes]) 32 | MAINT=$MAINTAINER_MODE_TRUE 33 | AC_SUBST(MAINT)dnl 34 | ] 35 | ) 36 | -------------------------------------------------------------------------------- /src/mdconvert.1: -------------------------------------------------------------------------------- 1 | .ig 2 | \" mdconvert - Maildir mailbox UID storage scheme converter 3 | \" Copyright (C) 2004 Oswald Buddenhagen 4 | \" 5 | \" This program is free software; you can redistribute it and/or modify 6 | \" it under the terms of the GNU General Public License as published by 7 | \" the Free Software Foundation; either version 2 of the License, or 8 | \" (at your option) any later version. 9 | \" 10 | \" This program is distributed in the hope that it will be useful, 11 | \" but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | \" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | \" GNU General Public License for more details. 14 | \" 15 | \" You should have received a copy of the GNU General Public License 16 | \" along with this program. If not, see . 17 | .. 18 | .TH mdconvert 1 "2004 Mar 27" 19 | .. 20 | .SH NAME 21 | mdconvert - Maildir mailbox UID storage scheme converter 22 | .. 23 | .SH SYNOPSIS 24 | \fBmdconvert\fR [\fIoptions\fR ...] \fImailbox\fR ... 25 | .. 26 | .SH DESCRIPTION 27 | \fBmdconvert\fR converts Maildir mailboxes between the two UID storage schemes 28 | supported by \fBmbsync\fR. See \fBmbsync\fR's manual page for details on these 29 | schemes. 30 | .. 31 | .SH OPTIONS 32 | .TP 33 | \fB-a\fR, \fB--alt\fR 34 | Convert to the \fBalternative\fR (Berkeley DB based) UID storage scheme. 35 | .TP 36 | \fB-n\fR, \fB--native\fR 37 | Convert to the \fBnative\fR (file name based) UID storage scheme. 38 | This is the default. 39 | .TP 40 | \fB-h\fR, \fB--help\fR 41 | Displays a summary of command line options. 42 | .TP 43 | \fB-v\fR, \fB--version\fR 44 | Displays version information. 45 | .. 46 | .SH SEE ALSO 47 | mbsync(1) 48 | .. 49 | .SH AUTHOR 50 | Written and maintained by Oswald Buddenhagen . 51 | -------------------------------------------------------------------------------- /get-cert: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script will extract the necessary certificate from the IMAP server 4 | # It assumes that an attacker isn't trying to spoof you when you connect 5 | # to the IMAP server! You're better off downloading the certificate 6 | # from a trusted source. 7 | # 8 | # Copyright (C) 2003 Theodore Ts'o 9 | # This program is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software Foundation, 21 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 22 | # 23 | 24 | if [ $# != 1 ]; then 25 | echo "Usage: $0 " >&2 26 | exit 1 27 | fi 28 | 29 | HOST=$1 30 | 31 | seed=`date '+%s'` 32 | try=0 33 | while :; do 34 | TMPDIR=/tmp/get-cert.$$.$seed 35 | mkdir $TMPDIR 2> /dev/null && break 36 | if [ $try = 1000 ]; then 37 | echo "Cannot create temporary directory." >&2 38 | exit 1 39 | fi 40 | try=`expr $try + 1` 41 | seed=`expr \( \( $seed \* 1103515245 \) + 12345 \) % 2147483648` 42 | done 43 | 44 | TMPFILE=$TMPDIR/get-cert 45 | ERRFILE=$TMPDIR/get-cert-err 46 | CERTFILE=$TMPDIR/cert 47 | 48 | echo QUIT | openssl s_client -connect $HOST:993 -showcerts \ 49 | > $TMPFILE 2> $ERRFILE 50 | sed -e '1,/^-----BEGIN CERTIFICATE-----/d' \ 51 | -e '/^-----END CERTIFICATE-----/,$d' < $TMPFILE > $CERTFILE 52 | 53 | if test -s $CERTFILE ; then 54 | echo -----BEGIN CERTIFICATE----- 55 | cat $CERTFILE 56 | echo -----END CERTIFICATE----- 57 | else 58 | echo "Couldn't retrieve certificate. openssl reported the following errors:" 59 | cat $ERRFILE 60 | fi 61 | 62 | rm -r $TMPDIR 63 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = src 2 | bin_SCRIPTS = get-cert 3 | EXTRA_DIST = debian isync.spec $(bin_SCRIPTS) 4 | 5 | LOG_PL = \ 6 | use POSIX qw(strftime); \ 7 | use Date::Parse; \ 8 | use Text::Wrap; \ 9 | $$Text::Wrap::columns = 72; \ 10 | while (defined($$_ = <>)) { \ 11 | /^commit / or die "commit missing: $$_"; \ 12 | <> =~ /^log size (\d+)$$/ or die "wrong size"; \ 13 | $$len = $$1; \ 14 | read(STDIN, $$log, $$len) == $$len or die "unexpected EOF"; \ 15 | $$log =~ s/^Author: ([^>]+>)\nDate: (\d{4}-\d\d-\d\d \d\d:\d\d:\d\d [-+]\d{4})\n(.*)$$/$$3/s or die "unexpected log format"; \ 16 | $$author = $$1; $$date = str2time($$2); \ 17 | scalar(<>); \ 18 | @files = (); \ 19 | $$pfx = ""; \ 20 | while (defined($$l = <>) and $$l ne "\n") { \ 21 | chomp $$l; \ 22 | next if ($$l =~ m,^(ChangeLog$$|NEWS$$|TODO$$|debian/),); \ 23 | if (!@files) { \ 24 | $$pfx = $$l; \ 25 | $$pfx =~ s,/?[^/]+$$,,; \ 26 | } else { \ 27 | while (length($$pfx)) { \ 28 | $$l =~ m,^\Q$$pfx/\E, and last; \ 29 | $$pfx =~ s,/?[^/]+$$,,; \ 30 | } \ 31 | } \ 32 | push @files, $$l; \ 33 | } \ 34 | next if (!@files); \ 35 | print strftime("%F %H:%M", gmtime($$date))." ".$$author."\n\n"; \ 36 | if (@files > 1 and ($$len = length($$pfx))) { \ 37 | @efiles = (); \ 38 | for $$f (@files) { push @efiles, substr($$f, $$len + 1); } \ 39 | $$fstr = $$pfx."/: "; \ 40 | } else { \ 41 | @efiles = @files; \ 42 | $$fstr = ""; \ 43 | } \ 44 | print wrap("\t* ", "\t ", $$fstr.join(", ", @efiles).":")."\n"; \ 45 | $$log =~ s, +$$,,gm; \ 46 | $$log =~ s,^ ,\t,gm; \ 47 | print $$log."\n"; \ 48 | } 49 | 50 | $(srcdir)/ChangeLog: log 51 | log: 52 | @test -z "$(srcdir)" || cd $(srcdir) && \ 53 | ( ! test -d .git || \ 54 | git log --date=iso --log-size --name-only | \ 55 | perl -e '$(LOG_PL)' > ChangeLog ) 56 | 57 | deb: deb-clean 58 | CFLAGS= fakeroot debian/rules binary 59 | 60 | deb-clean: 61 | dh_clean -Xsrc/ 62 | fakeroot debian/rules clean 63 | 64 | dist-hook: 65 | find $(distdir)/debian \( -name .#\*# -o -type l \) -print0 | xargs -0r rm -rf 66 | -cd $(distdir)/debian && test -f .gitignore && rm -rf `cat .gitignore` .gitignore 67 | 68 | dist-sign: dist 69 | gpg -b -a $(PACKAGE)-$(VERSION).tar.gz 70 | 71 | rpm: dist 72 | CFLAGS="-O2 -mtune=core2" rpmbuild --clean -ta $(PACKAGE)-$(VERSION).tar.gz 73 | 74 | rpm-ia32: dist 75 | CFLAGS="-O2 -m32 -march=i686" rpmbuild --target i686-unknown-linux --clean -ta $(PACKAGE)-$(VERSION).tar.gz 76 | 77 | doc_DATA = README TODO NEWS ChangeLog AUTHORS 78 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | _ 2 | (_)___ _ _ _ __ ___ 3 | | / __| | | | '_ \ / __| 4 | | \__ \ |_| | | | | (__ 5 | |_|___/\__, |_| |_|\___| 6 | |___/ 7 | isync/mbsync - free (GPL) mailbox synchronization program 8 | http://isync.sf.net/ 9 | 10 | See AUTHORS for contact information. 11 | 12 | ``mbsync'' is a command line application which synchronizes mailboxes; 13 | currently Maildir and IMAP4 mailboxes are supported. New messages, message 14 | deletions and flag changes can be propagated both ways. 15 | ``mbsync'' is suitable for use in IMAP-disconnected mode. 16 | 17 | Synchronization is based on unique message identifiers (UIDs), so no 18 | identification conflicts can occur (as opposed to some other mail 19 | synchronizers). 20 | Synchronization state is kept in one local text file per mailbox pair; 21 | multiple replicas of a mailbox can be maintained. 22 | 23 | isync is the project name, while mbsync is the current executable name; this 24 | change was necessary because of massive changes in the user interface. An 25 | isync executable still exists; it is a compatibility wrapper around mbsync. 26 | 27 | * Features 28 | 29 | * Fine-grained selection of synchronization operations to perform 30 | * Synchronizes single mailboxes or entire mailbox collections 31 | * Partial mirrors possible: keep only the latest messages locally 32 | * Trash functionality: backup messages before removing them 33 | * IMAP features: 34 | * Supports TLS/SSL via imaps: (port 993) and STARTTLS (RFC2595) 35 | * Supports CRAM-MD5 (RFC2195) for authentication 36 | * Supports NAMESPACE (RFC2342) for simplified configuration 37 | * Pipelining for maximum speed 38 | 39 | * Compatibility 40 | 41 | isync should work fairly well with any IMAP4 compliant server; 42 | particularily efficient with those that support the UIDPLUS and LITERAL+ 43 | extensions. 44 | 45 | Courier 1.4.3 is known to be buggy, version 1.7.3 works fine. 46 | 47 | c-client (UW-IMAP, Pine) is mostly fine, but versions less than 2004a.352 48 | tend to change UIDVALIDITY pretty often when used with unix/mbox mailboxes, 49 | making isync refuse synchronization. 50 | The "cure" is to simply copy the new UIDVALIDITY from the affected 51 | mailbox to mbsync's state file. This is a Bad Hack (TM), but it works - 52 | use at your own risk (if the UIDVALIDITY change was genuine, this will 53 | delete all messages in the affected mailbox - not that this ever 54 | happened to me). 55 | 56 | * Platforms 57 | 58 | At some point, ``isync'' has successfully run on: 59 | Linux, Solaris 2.7, OpenBSD 2.8, FreeBSD 4.3. 60 | 61 | Note that Cygwin cannot be reasonably supported due to restrictions 62 | of the Windows file system. 63 | 64 | * Requirements 65 | 66 | Berkley DB 4.2+ 67 | OpenSSL for TLS/SSL support (optional) 68 | 69 | * Installation 70 | 71 | ./configure 72 | make install 73 | 74 | * Help 75 | 76 | Please see the man page for complete documentation. 77 | -------------------------------------------------------------------------------- /src/compat/isync.h: -------------------------------------------------------------------------------- 1 | /* 2 | * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2004 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | #define as(ar) (sizeof(ar)/sizeof(ar[0])) 26 | 27 | #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) 28 | # define ATTR_UNUSED __attribute__((unused)) 29 | # define ATTR_NORETURN __attribute__((noreturn)) 30 | # define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var))) 31 | #else 32 | # define ATTR_UNUSED 33 | # define ATTR_NORETURN 34 | # define ATTR_PRINTFLIKE(fmt,var) 35 | #endif 36 | 37 | typedef struct config { 38 | struct config *next; 39 | 40 | const char *server_name; 41 | const char *old_server_name; 42 | int servers; 43 | int old_servers; 44 | char *host; 45 | int port; 46 | char *user; 47 | char *pass; 48 | char *tunnel; 49 | unsigned int require_cram:1; 50 | unsigned int require_ssl:1; 51 | unsigned int use_imaps:1; 52 | unsigned int use_sslv2:1; 53 | unsigned int use_sslv3:1; 54 | unsigned int use_tlsv1:1; 55 | char *cert_file; 56 | 57 | const char *store_name; 58 | int stores; 59 | char *copy_deleted_to; 60 | unsigned int use_namespace:1; 61 | 62 | const char *channel_name; 63 | int channels; 64 | const char *alias; 65 | const char *box; 66 | const char *path; /* path relative to .maildir, or absolute path */ 67 | int max_size; 68 | unsigned int max_messages; 69 | unsigned int expunge:1; 70 | unsigned int delete:1; 71 | } config_t; 72 | 73 | extern int Quiet, Verbose, Debug; 74 | 75 | extern const char *Home; 76 | extern int HomeLen; 77 | 78 | extern config_t global, *boxes; 79 | 80 | extern const char *maildir, *xmaildir, *folder, *inbox; 81 | extern int o2o, altmap, delete, expunge; 82 | 83 | /* config.c */ 84 | void load_config( const char *, config_t *** ); 85 | void write_config( int ); 86 | char *expand_strdup( const char * ); 87 | config_t *find_box( const char * ); 88 | 89 | /* convert.c */ 90 | void convert( config_t * ); 91 | 92 | /* util.c */ 93 | char *next_arg( char ** ); 94 | void *nfmalloc( size_t sz ); 95 | void *nfrealloc( void *mem, size_t sz ); 96 | char *nfstrdup( const char *str ); 97 | int nfvasprintf( char **str, const char *fmt, va_list va ); 98 | int nfasprintf( char **str, const char *fmt, ... ); 99 | int nfsnprintf( char *buf, int blen, const char *fmt, ... ); 100 | void sys_error( const char *, ... ); 101 | void ATTR_NORETURN oob( void ); 102 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | find out why mutt's message size calc is confused. 2 | 3 | f{,data}sync() usage could be optimized by batching the calls. 4 | 5 | add some marker about message being already [remotely] trashed. 6 | real transactions would be certainly not particularly useful ... 7 | 8 | check whether disappearing (M_DEAD) messages (due to maildir rescans) are 9 | properly accounted for by the syncing code. 10 | 11 | make sync_chans() aware of servers, so a bad server (e.g., wrong password) 12 | won't cause the same error message for every attached store. 13 | 14 | make SSL (connect) timeouts produce a bit more than "Unidentified socket error". 15 | 16 | network timeout handling in general would be a good idea. 17 | 18 | unify maildir locking between the two UID storage schemes. 19 | re-opening the db may be expensive, so keep it open. 20 | but keeping lock for too long (e.g., big message downloads) may block other 21 | clients. auto-release lock after 500 ms? 22 | 23 | kill the concept of an INBOX, it is a relic from single-channel operation. 24 | if somebody needs it, he can have two stores with different Paths. the path 25 | can name a single (in-)box (curr. broken with maildir). an empty box name 26 | actually means empty, so the IMAP mailbox should use INBOX for Path (can't 27 | make that the default, as it would mess up the NAMESPACE). 28 | 29 | add daemon mode. primary goal: keep imap password in memory. 30 | also: idling mode. 31 | 32 | parallel fetching of multiple mailboxes. 33 | 34 | set_flags: 35 | - imap: grouping commands for efficiency 36 | - callback should get the flags actually affected. but then, why could flag 37 | changes fail at all? 38 | 39 | add streaming from fetching to storing. 40 | 41 | handle custom flags (keywords). 42 | 43 | handle google IMAP extensions. 44 | 45 | use MULTIAPPEND and FETCH with multiple messages. 46 | 47 | create dummies describing MIME structure of messages bigger than MaxSize. 48 | flagging the dummy would fetch the real message. possibly remove --renew. 49 | note that all interaction needs to happen on the slave side probably. 50 | 51 | don't SELECT boxes unless really needed; in particular not for appending, 52 | and in write-only mode not before changes are made. 53 | problem: UIDVALIDITY change detection is delayed, significantly complicating 54 | matters. 55 | 56 | possibly request message attributes on a per-message basis from the drivers. 57 | considerations: 58 | - record non-existing UID ranges in the sync database, so IMAP FETCHes needn't 59 | to exclude anyway non-existing messages explicitly. 60 | - when detect unborn pairs and orphaned messages being gone? implied by expunge: 61 | with trashing, by local driver, or of messages we deleted in this run. the 62 | remaining cases could be handled by automatic periodical cleanup passes, an 63 | explicit --cleanup action, or be implied by one of the other actions. 64 | - the benefit of this is questionable, as fine-grained requests will result 65 | in sending huge amounts of data, and upstream is often way slower than 66 | downstream. 67 | 68 | maildir: possibly timestamp mails with remote arrival date. 69 | 70 | maybe throw out the ctx->recent stuff - it's used only for one info message. 71 | 72 | possibly use ^[[1m to highlight error messages. 73 | 74 | consider alternative trash implementation: trash only messages we delete, 75 | and trash before marking them deleted in the mailbox. downside: all other 76 | programs have to do the same. and what if the deleted flag is unset? 77 | 78 | items out of scope of purely UID based approach: 79 | - detect message moves between folders 80 | - recovering from UIDVALIDITY change (uw-imap < 2004.352 does this a lot) 81 | -------------------------------------------------------------------------------- /src/compat/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2004 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "isync.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | void 30 | sys_error( const char *msg, ... ) 31 | { 32 | va_list va; 33 | char buf[1024]; 34 | 35 | va_start( va, msg ); 36 | if ((unsigned)vsnprintf( buf, sizeof(buf), msg, va ) >= sizeof(buf)) 37 | oob(); 38 | va_end( va ); 39 | perror( buf ); 40 | } 41 | 42 | char * 43 | next_arg( char **s ) 44 | { 45 | char *ret; 46 | 47 | if (!s || !*s) 48 | return 0; 49 | while (isspace( (unsigned char) **s )) 50 | (*s)++; 51 | if (!**s) { 52 | *s = 0; 53 | return 0; 54 | } 55 | if (**s == '"') { 56 | ++*s; 57 | ret = *s; 58 | *s = strchr( *s, '"' ); 59 | } else { 60 | ret = *s; 61 | while (**s && !isspace( (unsigned char) **s )) 62 | (*s)++; 63 | } 64 | if (*s) { 65 | if (**s) 66 | *(*s)++ = 0; 67 | if (!**s) 68 | *s = 0; 69 | } 70 | return ret; 71 | } 72 | 73 | #ifndef HAVE_VASPRINTF 74 | static int 75 | vasprintf( char **strp, const char *fmt, va_list ap ) 76 | { 77 | int len; 78 | char tmp[1024]; 79 | 80 | if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap )) < 0 || !(*strp = malloc( len + 1 ))) 81 | return -1; 82 | if (len >= (int)sizeof(tmp)) 83 | vsprintf( *strp, fmt, ap ); 84 | else 85 | memcpy( *strp, tmp, len + 1 ); 86 | return len; 87 | } 88 | #endif 89 | 90 | void 91 | oob( void ) 92 | { 93 | fputs( "Fatal: buffer too small. Please report a bug.\n", stderr ); 94 | abort(); 95 | } 96 | 97 | int 98 | nfsnprintf( char *buf, int blen, const char *fmt, ... ) 99 | { 100 | int ret; 101 | va_list va; 102 | 103 | va_start( va, fmt ); 104 | if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen) 105 | oob(); 106 | va_end( va ); 107 | return ret; 108 | } 109 | 110 | static void ATTR_NORETURN 111 | oom( void ) 112 | { 113 | fputs( "Fatal: Out of memory\n", stderr ); 114 | abort(); 115 | } 116 | 117 | void * 118 | nfmalloc( size_t sz ) 119 | { 120 | void *ret; 121 | 122 | if (!(ret = malloc( sz ))) 123 | oom(); 124 | return ret; 125 | } 126 | 127 | void * 128 | nfrealloc( void *mem, size_t sz ) 129 | { 130 | char *ret; 131 | 132 | if (!(ret = realloc( mem, sz )) && sz) 133 | oom(); 134 | return ret; 135 | } 136 | 137 | char * 138 | nfstrdup( const char *str ) 139 | { 140 | char *ret; 141 | 142 | if (!(ret = strdup( str ))) 143 | oom(); 144 | return ret; 145 | } 146 | 147 | int 148 | nfvasprintf( char **str, const char *fmt, va_list va ) 149 | { 150 | int ret = vasprintf( str, fmt, va ); 151 | if (ret < 0) 152 | oom(); 153 | return ret; 154 | } 155 | 156 | int 157 | nfasprintf( char **str, const char *fmt, ... ) 158 | { 159 | int ret; 160 | va_list va; 161 | 162 | va_start( va, fmt ); 163 | ret = nfvasprintf( str, fmt, va ); 164 | va_end( va ); 165 | return ret; 166 | } 167 | -------------------------------------------------------------------------------- /configure.in: -------------------------------------------------------------------------------- 1 | AC_INIT(src/isync.h) 2 | AM_CONFIG_HEADER(config.h) 3 | AM_INIT_AUTOMAKE(isync, 1.1.0) 4 | 5 | AM_MAINTAINER_MODE 6 | 7 | AM_PROG_CC_STDC 8 | if test "$GCC" = yes; then 9 | CFLAGS="$CFLAGS -pipe -W -Wall -Wshadow -Wstrict-prototypes -ansi -pedantic -Wno-overlength-strings" 10 | fi 11 | 12 | CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE" 13 | 14 | AC_CHECK_HEADERS(sys/poll.h sys/select.h) 15 | AC_CHECK_FUNCS(vasprintf memrchr) 16 | 17 | AC_CHECK_LIB(socket, socket, [SOCK_LIBS="-lsocket"]) 18 | AC_CHECK_LIB(nsl, inet_ntoa, [SOCK_LIBS="$SOCK_LIBS -lnsl"]) 19 | AC_SUBST(SOCK_LIBS) 20 | 21 | m4_ifdef([AS_HELP_STRING], , [m4_define([AS_HELP_STRING], m4_defn([AC_HELP_STRING]))]) 22 | 23 | have_ssl_paths= 24 | AC_ARG_WITH(ssl, 25 | AS_HELP_STRING([--with-ssl[=PATH]], [where to look for SSL [detect]]), 26 | [ob_cv_with_ssl=$withval]) 27 | if test "x$ob_cv_with_ssl" != xno; then 28 | case $ob_cv_with_ssl in 29 | ""|yes) 30 | dnl Detect the pkg-config tool, as it may have extra info about the openssl 31 | dnl installation we can use. I *believe* this is what we are expected to do 32 | dnl on really recent Redhat Linux hosts. 33 | AC_PATH_PROG(PKGCONFIG, pkg-config, no, $PATH:/usr/bin:/usr/local/bin) 34 | if test "$PKGCONFIG" != "no" ; then 35 | AC_MSG_CHECKING([OpenSSL presence with pkg-config]) 36 | if $PKGCONFIG --exists openssl; then 37 | SSL_LIBS=`$PKGCONFIG --libs-only-l openssl` 38 | SSL_LDFLAGS=`$PKGCONFIG --libs-only-L openssl` 39 | SSL_CPPFLAGS=`$PKGCONFIG --cflags-only-I openssl` 40 | have_ssl_paths=yes 41 | AC_MSG_RESULT([found]) 42 | else 43 | AC_MSG_RESULT([not found]) 44 | fi 45 | fi 46 | ;; 47 | *) 48 | SSL_LDFLAGS=-L$ob_cv_with_ssl/lib$libsuff 49 | SSL_CPPFLAGS=-I$ob_cv_with_ssl/include 50 | ;; 51 | esac 52 | if test -z "$have_ssl_paths"; then 53 | sav_LDFLAGS=$LDFLAGS 54 | LDFLAGS="$LDFLAGS $SSL_LDFLAGS" 55 | AC_CHECK_LIB(dl, dlopen, [LIBDL=-ldl]) 56 | AC_CHECK_LIB(crypto, CRYPTO_lock, [LIBCRYPTO=-lcrypto]) 57 | AC_CHECK_LIB(ssl, SSL_connect, 58 | [SSL_LIBS="-lssl $LIBCRYPTO $LIBDL" have_ssl_paths=yes]) 59 | LDFLAGS=$sav_LDFLAGS 60 | fi 61 | 62 | sav_CPPFLAGS=$CPPFLAGS 63 | CPPFLAGS="$CPPFLAGS $SSL_CPPFLAGS" 64 | AC_CHECK_HEADER(openssl/ssl.h, , [have_ssl_paths=]) 65 | CPPFLAGS=$sav_CPPFLAGS 66 | 67 | if test -z "$have_ssl_paths"; then 68 | if test -n "$ob_cv_with_ssl"; then 69 | AC_MSG_ERROR([OpenSSL libs and/or includes were not found where specified]) 70 | fi 71 | else 72 | AC_DEFINE(HAVE_LIBSSL, 1, [if you have the OpenSSL libraries]) 73 | CPPFLAGS="$CPPFLAGS $SSL_CPPFLAGS" 74 | LDFLAGS="$LDFLAGS $SSL_LDFLAGS" 75 | fi 76 | fi 77 | AC_SUBST(SSL_LIBS) 78 | 79 | AC_CACHE_CHECK([for Berkley DB 4.2], ac_cv_berkdb4, 80 | [ac_cv_berkdb4=no 81 | AC_TRY_LINK([#include ], 82 | [DB *db; 83 | db->truncate(db, 0, 0, 0); 84 | db->open(db, 0, "foo", "foo", DB_HASH, DB_CREATE, 0)], 85 | [ac_cv_berkdb4=yes])]) 86 | if test "x$ac_cv_berkdb4" = xno; then 87 | AC_MSG_ERROR([Berkley DB 4.2 not found. 88 | You must install libdb4.2 including the respective development files/headers.]) 89 | fi 90 | 91 | AC_ARG_ENABLE(compat, 92 | AS_HELP_STRING([--disable-compat], [don't include isync compatibility wrapper [no]]), 93 | [ob_cv_enable_compat=$enableval]) 94 | if test "x$ob_cv_enable_compat" != xno; then 95 | AC_CHECK_FUNCS(getopt_long) 96 | fi 97 | AM_CONDITIONAL(with_compat, test "x$ob_cv_enable_compat" != xno) 98 | 99 | AC_OUTPUT(Makefile src/Makefile src/compat/Makefile isync.spec) 100 | 101 | if test -n "$have_ssl_paths"; then 102 | AC_MSG_RESULT([ 103 | Using SSL 104 | ]) 105 | else 106 | AC_MSG_RESULT([ 107 | Not using SSL 108 | ]) 109 | fi 110 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | [1.1.0] 2 | 3 | Support for hierarchical mailboxes in Patterns. 4 | 5 | Full support for IMAP pipelining (streaming, parallelization) added. 6 | This is considerably faster especially with high-latency networks. 7 | 8 | Faster and hopefully more reliable support for IMAP servers without the 9 | UIDPLUS extension (e.g., M$ Exchange). 10 | 11 | More automatic handling of SSL certificates. 12 | 13 | Data safety in case of system crashes is improved. 14 | 15 | [1.0.0] 16 | 17 | Essentially a rewrite. Synchronization state storage concept, configuration 18 | and command line changed entirely. 19 | But you needn't to worry about the upgrade, as a fully automated migration 20 | path is provided, even for users of isync 0.7 and below. 21 | Still, you should re-read the manual to be able to take full advantage of the 22 | new features: 23 | 24 | The supported mailbox types can be freely paired. 25 | A possible application of this is using a local IMAP server to access 26 | mailboxes that are not natively supported yet. 27 | 28 | Message deletions (expunges) are now propagated both ways, so there is no need 29 | for using mutt with maildir_trash any more. 30 | 31 | Additional trash options added. 32 | 33 | `OneToOne' replaced by something more flexible. 34 | 35 | Partial support for IMAP pipelining (streaming, parallelization) added. 36 | Makes flag change propagation much faster - this affects every message that 37 | becomes Seen/Read. 38 | 39 | [0.9] 40 | 41 | Added Tunnel directive to allow the user to specify a shell command to run 42 | to set up an IMAP connection in place of a TCP socket (eg., to run over 43 | an SSH session). 44 | 45 | Added PREAUTH support (useful mostly in conjunction with Tunnel). 46 | 47 | Messages marked deleted are not uploaded when we are going to expunge. 48 | 49 | Locally generated messages are not re-fetched after uploading even if the 50 | UIDPLUS extension is not supported by the server. 51 | 52 | Added `OneToOne' configuration option: ignore any Mailbox specifications 53 | and instead pick up all mailboxes from the local MailDir and remote Folder 54 | and map them 1:1 onto each other according to their names. 55 | 56 | -C now creates both local and remote boxes; -L and -R create only local/remote. 57 | 58 | --quiet is now really quiet. 59 | 60 | [0.8] 61 | 62 | !!! IMPORTANT !!! 63 | 64 | In order to fix the problem where messages copied from one mailbox to 65 | another were not uploaded to the new mailbox, the way Isync stores the UID 66 | for each message needed to be changed. As a result, you _MUST_ delete all 67 | the messages in the local maildir box before using this version. Otherwise 68 | it will upload every message to the server thinking its a new mail. 69 | 70 | [0.7] 71 | 72 | Added `MaxMessages' configuration option to allow tracking of only the most 73 | recently added message in the local mailbox. 74 | 75 | Added --create (-C) command line option to force creation of the local 76 | maildir-style mailbox if it doesn't already exist. 77 | 78 | [0.6] 79 | 80 | Added `Delete' configuration option to correspond to the -d command line 81 | option. 82 | 83 | Added -a (--all) command line option to synchronize all mailboxes. 84 | 85 | [0.5] 86 | 87 | Updated SSL support. 88 | 89 | Added CRAM authentication support. 90 | 91 | Added MailDir configuration option to specify the default location of local 92 | mailboxes when relative paths are used. 93 | 94 | Added support for uploading local messages to the IMAP server. 95 | 96 | Added CopyDeletedTo configuration option to cause isync to move deleted 97 | messages to a particular mailbox on the server when they are expunged. 98 | 99 | [0.4] 100 | 101 | Added MaxSize configuration option to limit downloading of new messages from 102 | the server to less than some threshold. 103 | 104 | More robust --fast option works without using \Recent flags, so the previous 105 | problem with multiple accesses killing these flags is no longer a problem. 106 | 107 | RFC2060 obsoleted RFC822.PEEK, use BODY.PEEK[] instead which does the same 108 | job. 109 | 110 | Don't need to request UID in a FETCH when doing UID FETCH (RFC2060 states 111 | that its automatically returned). 112 | 113 | [0.3] 114 | 115 | Fixed to clean up temp maildir files when the fetch of a new message failed. 116 | 117 | Fixed to not assume order of the flags returned by "UID FETCH" 118 | 119 | Added support for building RPMs. 120 | 121 | [0.2] 122 | 123 | SSL support added. 124 | 125 | [0.1] 126 | 127 | Initial release. 128 | -------------------------------------------------------------------------------- /src/compat/convert.c: -------------------------------------------------------------------------------- 1 | /* 2 | * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2004 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "isync.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | 34 | static const char *subdirs[] = { "cur", "new", "tmp" }; 35 | 36 | static const char Flags[] = { 'D', 'F', 'R', 'S', 'T' }; 37 | 38 | static int 39 | parse_info( const char *s ) 40 | { 41 | unsigned i; 42 | int flags; 43 | 44 | flags = 0; 45 | if (s && *(s + 1) == '2' && *(s + 2) == ',') 46 | for (s += 3, i = 0; i < as(Flags); i++) 47 | if (strchr( s, Flags[i] )) 48 | flags |= (1 << i); 49 | return flags; 50 | } 51 | 52 | typedef struct { 53 | int uid, flags; 54 | } msg_t; 55 | 56 | static int 57 | compare_uids( const void *l, const void *r ) 58 | { 59 | return ((msg_t *)l)->uid - ((msg_t *)r)->uid; 60 | } 61 | 62 | static DBT key, value; 63 | static struct flock lck; 64 | 65 | void 66 | convert( config_t *box ) 67 | { 68 | DIR *d; 69 | struct dirent *e; 70 | char *s, *p, *mboxdir; 71 | FILE *fp; 72 | msg_t *msgs; 73 | DB *db; 74 | int i, ret, fd, uidval, maxuid, uid, rmsgs, nmsgs, uv[2]; 75 | unsigned u; 76 | struct stat sb; 77 | char buf[_POSIX_PATH_MAX], diumname[_POSIX_PATH_MAX], 78 | uvname[_POSIX_PATH_MAX], sname[_POSIX_PATH_MAX], 79 | iuvname[_POSIX_PATH_MAX], imuname[_POSIX_PATH_MAX], 80 | ilname[_POSIX_PATH_MAX], iumname[_POSIX_PATH_MAX]; 81 | 82 | mboxdir = expand_strdup( box->path ); 83 | nfsnprintf( iuvname, sizeof(iuvname), "%s/isyncuidvalidity", mboxdir ); 84 | nfsnprintf( diumname, sizeof(iumname), "%s/.isyncuidmap.db", mboxdir ); 85 | nfsnprintf( uvname, sizeof(uvname), "%s/.uidvalidity", mboxdir ); 86 | if (stat( iuvname, &sb )) { 87 | if (!stat( diumname, &sb )) 88 | altmap++; 89 | else if (!stat( uvname, &sb )) 90 | altmap--; 91 | err1: 92 | free( mboxdir ); 93 | return; 94 | } 95 | for (i = 0; i < 3; i++) { 96 | nfsnprintf( buf, sizeof(buf), "%s/%s", mboxdir, subdirs[i] ); 97 | if (stat( buf, &sb )) { 98 | sys_error( "ERROR: cannot access %s", buf ); 99 | fprintf( stderr, 100 | "ERROR: '%s' does not appear to be a valid maildir style mailbox\n", 101 | mboxdir ); 102 | goto err1; 103 | } 104 | } 105 | nfsnprintf( iumname, sizeof(iumname), "%s/isyncuidmap.db", mboxdir ); 106 | nfsnprintf( imuname, sizeof(imuname), "%s/isyncmaxuid", mboxdir ); 107 | nfsnprintf( ilname, sizeof(ilname), "%s/isynclock", mboxdir ); 108 | nfsnprintf( sname, sizeof(sname), "%s/.mbsyncstate", mboxdir ); 109 | 110 | if ((fd = open( ilname, O_WRONLY|O_CREAT, 0600 )) < 0) { 111 | sys_error( "Cannot create %s", ilname ); 112 | goto err1; 113 | } 114 | #if SEEK_SET != 0 115 | lck.l_whence = SEEK_SET; 116 | #endif 117 | #if F_WRLCK != 0 118 | lck.l_type = F_WRLCK; 119 | #endif 120 | if (fcntl( fd, F_SETLKW, &lck )) { 121 | sys_error( "Cannot lock %s", ilname ); 122 | err2: 123 | close( fd ); 124 | goto err1; 125 | } 126 | 127 | if (!(fp = fopen( iuvname, "r" ))) { 128 | sys_error( "Cannot open %s", iuvname ); 129 | goto err2; 130 | } 131 | fscanf( fp, "%d", &uidval ); 132 | fclose( fp ); 133 | if (!(fp = fopen( imuname, "r" ))) { 134 | sys_error( "Cannot open %s", imuname ); 135 | goto err2; 136 | } 137 | fscanf( fp, "%d", &maxuid ); 138 | fclose( fp ); 139 | 140 | if (!stat( iumname, &sb )) { 141 | if (db_create( &db, 0, 0 )) { 142 | fputs( "dbcreate failed\n", stderr ); 143 | goto err2; 144 | } 145 | if ((db->open)( db, 0, iumname, 0, DB_HASH, 0, 0 )) { 146 | fputs( "cannot open db\n", stderr ); 147 | db->close( db, 0 ); 148 | goto err2; 149 | } 150 | altmap++; 151 | } else { 152 | db = 0; 153 | altmap--; 154 | } 155 | 156 | msgs = 0; 157 | rmsgs = 0; 158 | nmsgs = 0; 159 | for (i = 0; i < 2; i++) { 160 | nfsnprintf( buf, sizeof(buf), "%s/%s/", mboxdir, subdirs[i] ); 161 | if (!(d = opendir( buf ))) { 162 | sys_error( "Cannot list %s", buf ); 163 | err4: 164 | free( msgs ); 165 | if (db) 166 | db->close( db, 0 ); 167 | goto err2; 168 | } 169 | while ((e = readdir( d ))) { 170 | if (*e->d_name == '.') 171 | continue; 172 | s = strchr( e->d_name, ':' ); 173 | if (db) { 174 | key.data = e->d_name; 175 | key.size = s ? (size_t)(s - e->d_name) : strlen( e->d_name ); 176 | if ((ret = db->get( db, 0, &key, &value, 0 ))) { 177 | if (ret != DB_NOTFOUND) 178 | db->err( db, ret, "Maildir error: db->get()" ); 179 | continue; 180 | } 181 | uid = *(int *)value.data; 182 | } else if ((p = strstr( e->d_name, ",U=" ))) 183 | uid = atoi( p + 3 ); 184 | else 185 | continue; 186 | if (nmsgs == rmsgs) { 187 | rmsgs = rmsgs * 2 + 100; 188 | msgs = nfrealloc( msgs, rmsgs * sizeof(msg_t) ); 189 | } 190 | msgs[nmsgs].uid = uid; 191 | msgs[nmsgs++].flags = parse_info( s ); 192 | } 193 | closedir( d ); 194 | } 195 | 196 | qsort( msgs, nmsgs, sizeof(msg_t), compare_uids ); 197 | 198 | if (!(fp = fopen( sname, "w" ))) { 199 | sys_error( "Cannot create %s", sname ); 200 | goto err4; 201 | } 202 | if (box->max_messages) { 203 | if (!nmsgs) 204 | i = maxuid; 205 | else { 206 | i = nmsgs - box->max_messages; 207 | if (i < 0) 208 | i = 0; 209 | i = msgs[i].uid - 1; 210 | } 211 | } else 212 | i = 0; 213 | fprintf( fp, "%d:%d %d:%d:%d\n", uidval, maxuid, uidval, i, maxuid ); 214 | for (i = 0; i < nmsgs; i++) { 215 | fprintf( fp, "%d %d ", msgs[i].uid, msgs[i].uid ); 216 | for (u = 0; u < as(Flags); u++) 217 | if (msgs[i].flags & (1 << u)) 218 | fputc( Flags[u], fp ); 219 | fputc( '\n', fp ); 220 | } 221 | fclose( fp ); 222 | 223 | if (db) { 224 | key.data = (void *)"UIDVALIDITY"; 225 | key.size = 11; 226 | uv[0] = uidval; 227 | uv[1] = maxuid; 228 | value.data = uv; 229 | value.size = sizeof(uv); 230 | if ((ret = db->put( db, 0, &key, &value, 0 ))) { 231 | db->err( db, ret, "Maildir error: db->put()" ); 232 | goto err4; 233 | } 234 | db->close( db, 0 ); 235 | rename( iumname, diumname ); 236 | } else { 237 | if (!(fp = fopen( uvname, "w" ))) { 238 | sys_error( "Cannot create %s", uvname ); 239 | goto err4; 240 | } 241 | fprintf( fp, "%d\n%d\n", uidval, maxuid ); 242 | fclose( fp ); 243 | } 244 | 245 | unlink( iuvname ); 246 | unlink( imuname ); 247 | 248 | close( fd ); 249 | unlink( ilname ); 250 | 251 | free( msgs ); 252 | free( mboxdir ); 253 | return; 254 | } 255 | -------------------------------------------------------------------------------- /src/mdconvert.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mdconvert - Maildir UID scheme converter 3 | * Copyright (C) 2004 Oswald Buddenhagen 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | 35 | #define EXE "mdconvert" 36 | 37 | #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) 38 | # define ATTR_NORETURN __attribute__((noreturn)) 39 | # define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var))) 40 | #else 41 | # define ATTR_NORETURN 42 | # define ATTR_PRINTFLIKE(fmt,var) 43 | #endif 44 | 45 | static void ATTR_NORETURN 46 | oob( void ) 47 | { 48 | fputs( "Fatal: buffer too small. Please report a bug.\n", stderr ); 49 | abort(); 50 | } 51 | 52 | static void ATTR_PRINTFLIKE(1, 2) 53 | sys_error( const char *msg, ... ) 54 | { 55 | va_list va; 56 | char buf[1024]; 57 | 58 | va_start( va, msg ); 59 | if ((unsigned)vsnprintf( buf, sizeof(buf), msg, va ) >= sizeof(buf)) 60 | oob(); 61 | va_end( va ); 62 | perror( buf ); 63 | } 64 | 65 | static int ATTR_PRINTFLIKE(3, 4) 66 | nfsnprintf( char *buf, int blen, const char *fmt, ... ) 67 | { 68 | int ret; 69 | va_list va; 70 | 71 | va_start( va, fmt ); 72 | if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen) 73 | oob(); 74 | va_end( va ); 75 | return ret; 76 | } 77 | 78 | static const char *subdirs[] = { "cur", "new" }; 79 | static struct flock lck; 80 | static DBT key, value; 81 | 82 | static int 83 | convert( const char *box, int altmap ) 84 | { 85 | DB *db; 86 | DIR *d; 87 | struct dirent *e; 88 | const char *u, *ru; 89 | char *p, *s, *dpath, *spath, *dbpath; 90 | int i, n, ret, sfd, dfd, bl, ml, uv[2], uid; 91 | struct stat st; 92 | char buf[_POSIX_PATH_MAX], buf2[_POSIX_PATH_MAX]; 93 | char umpath[_POSIX_PATH_MAX], uvpath[_POSIX_PATH_MAX], tdpath[_POSIX_PATH_MAX]; 94 | 95 | if (stat( box, &st ) || !S_ISDIR(st.st_mode)) { 96 | fprintf( stderr, "'%s' is no Maildir mailbox.\n", box ); 97 | return 1; 98 | } 99 | 100 | nfsnprintf( umpath, sizeof(umpath), "%s/.isyncuidmap.db", box ); 101 | nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", box ); 102 | if (altmap) 103 | dpath = umpath, spath = uvpath, dbpath = tdpath; 104 | else 105 | spath = umpath, dpath = uvpath, dbpath = umpath; 106 | nfsnprintf( tdpath, sizeof(tdpath), "%s.tmp", dpath ); 107 | if ((sfd = open( spath, O_RDWR )) < 0) { 108 | if (errno != ENOENT) 109 | sys_error( "Cannot open %s", spath ); 110 | return 1; 111 | } 112 | if (fcntl( sfd, F_SETLKW, &lck )) { 113 | sys_error( "Cannot lock %s", spath ); 114 | goto sbork; 115 | } 116 | if ((dfd = open( tdpath, O_RDWR|O_CREAT, 0600 )) < 0) { 117 | sys_error( "Cannot create %s", tdpath ); 118 | goto sbork; 119 | } 120 | if (db_create( &db, 0, 0 )) { 121 | fputs( "Error: db_create() failed\n", stderr ); 122 | goto tbork; 123 | } 124 | if ((ret = (db->open)( db, 0, dbpath, 0, DB_HASH, altmap ? DB_CREATE|DB_TRUNCATE : 0, 0 ))) { 125 | db->err( db, ret, "Error: db->open(%s)", dbpath ); 126 | dbork: 127 | db->close( db, 0 ); 128 | tbork: 129 | unlink( tdpath ); 130 | close( dfd ); 131 | sbork: 132 | close( sfd ); 133 | return 1; 134 | } 135 | key.data = (void *)"UIDVALIDITY"; 136 | key.size = 11; 137 | if (altmap) { 138 | if ((n = read( sfd, buf, sizeof(buf) )) <= 0 || 139 | (buf[n] = 0, sscanf( buf, "%d\n%d", &uv[0], &uv[1] ) != 2)) 140 | { 141 | fprintf( stderr, "Error: cannot read UIDVALIDITY of '%s'.\n", box ); 142 | goto dbork; 143 | } 144 | value.data = uv; 145 | value.size = sizeof(uv); 146 | if ((ret = db->put( db, 0, &key, &value, 0 ))) { 147 | db->err( db, ret, "Error: cannot write UIDVALIDITY for '%s'", box ); 148 | goto dbork; 149 | } 150 | } else { 151 | if ((ret = db->get( db, 0, &key, &value, 0 ))) { 152 | db->err( db, ret, "Error: cannot read UIDVALIDITY of '%s'", box ); 153 | goto dbork; 154 | } 155 | n = sprintf( buf, "%d\n%d\n", ((int *)value.data)[0], ((int *)value.data)[1] ); 156 | if (write( dfd, buf, n ) != n) { 157 | fprintf( stderr, "Error: cannot write UIDVALIDITY for '%s'.\n", box ); 158 | goto dbork; 159 | } 160 | } 161 | 162 | again: 163 | for (i = 0; i < 2; i++) { 164 | bl = nfsnprintf( buf, sizeof(buf), "%s/%s/", box, subdirs[i] ); 165 | if (!(d = opendir( buf ))) { 166 | sys_error( "Cannot list %s", buf ); 167 | goto dbork; 168 | } 169 | while ((e = readdir( d ))) { 170 | if (*e->d_name == '.') 171 | continue; 172 | nfsnprintf( buf + bl, sizeof(buf) - bl, "%s", e->d_name ); 173 | memcpy( buf2, buf, bl ); 174 | p = strstr( e->d_name, ",U=" ); 175 | if (p) 176 | for (u = p, ru = p + 3; isdigit( (unsigned char)*ru ); ru++); 177 | else 178 | u = ru = strchr( e->d_name, ':' ); 179 | if (u) 180 | ml = u - e->d_name; 181 | else 182 | ru = "", ml = sizeof(buf); 183 | if (altmap) { 184 | if (!p) 185 | continue; 186 | key.data = e->d_name; 187 | key.size = (size_t)(strchr( e->d_name, ',' ) - e->d_name); 188 | uid = atoi( p + 3 ); 189 | value.data = &uid; 190 | value.size = sizeof(uid); 191 | if ((ret = db->put( db, 0, &key, &value, 0 ))) { 192 | db->err( db, ret, "Error: cannot write UID for '%s'", box ); 193 | goto ebork; 194 | } 195 | nfsnprintf( buf2 + bl, sizeof(buf2) - bl, "%.*s%s", ml, e->d_name, ru ); 196 | } else { 197 | s = strpbrk( e->d_name, ",:" ); 198 | key.data = e->d_name; 199 | key.size = s ? (size_t)(s - e->d_name) : strlen( e->d_name ); 200 | if ((ret = db->get( db, 0, &key, &value, 0 ))) { 201 | if (ret != DB_NOTFOUND) { 202 | db->err( db, ret, "Error: cannot read UID for '%s'", box ); 203 | goto ebork; 204 | } 205 | continue; 206 | } 207 | uid = *(int *)value.data; 208 | nfsnprintf( buf2 + bl, sizeof(buf2) - bl, "%.*s,U=%d%s", ml, e->d_name, uid, ru ); 209 | } 210 | if (rename( buf, buf2 )) { 211 | if (errno == ENOENT) 212 | goto again; 213 | sys_error( "Cannot rename %s to %s", buf, buf2 ); 214 | ebork: 215 | closedir( d ); 216 | goto dbork; 217 | } 218 | 219 | } 220 | closedir( d ); 221 | } 222 | 223 | db->close( db, 0 ); 224 | close( dfd ); 225 | if (rename( tdpath, dpath )) { 226 | sys_error( "Cannot rename %s to %s", tdpath, dpath ); 227 | return 1; 228 | } 229 | if (unlink( spath )) 230 | sys_error( "Cannot remove %s", spath ); 231 | close( sfd ); 232 | return 0; 233 | } 234 | 235 | int 236 | main( int argc, char **argv ) 237 | { 238 | int oint, ret, altmap = 0; 239 | 240 | for (oint = 1; oint < argc; oint++) { 241 | if (!strcmp( argv[oint], "-h" ) || !strcmp( argv[oint], "--help" )) { 242 | puts( 243 | "Usage: " EXE " [-a] mailbox...\n" 244 | " -a, --alt convert to alternative (DB based) UID scheme\n" 245 | " -n, --native convert to native (file name based) UID scheme (default)\n" 246 | " -h, --help show this help message\n" 247 | " -v, --version display version" 248 | ); 249 | return 0; 250 | } else if (!strcmp( argv[oint], "-v" ) || !strcmp( argv[oint], "--version" )) { 251 | puts( EXE " " VERSION " - Maildir UID scheme converter" ); 252 | return 0; 253 | } else if (!strcmp( argv[oint], "-a" ) || !strcmp( argv[oint], "--alt" )) { 254 | altmap = 1; 255 | } else if (!strcmp( argv[oint], "-n" ) || !strcmp( argv[oint], "--native" )) { 256 | altmap = 0; 257 | } else if (!strcmp( argv[oint], "--" )) { 258 | oint++; 259 | break; 260 | } else if (argv[oint][0] == '-') { 261 | fprintf( stderr, "Unrecognized option '%s'. Try " EXE " -h\n", argv[oint] ); 262 | return 1; 263 | } else 264 | break; 265 | } 266 | if (oint == argc) { 267 | fprintf( stderr, "Mailbox specification missing. Try " EXE " -h\n" ); 268 | return 1; 269 | } 270 | #if SEEK_SET != 0 271 | lck.l_whence = SEEK_SET; 272 | #endif 273 | #if F_WRLCK != 0 274 | lck.l_type = F_WRLCK; 275 | #endif 276 | ret = 0; 277 | for (; oint < argc; oint++) 278 | ret |= convert( argv[oint], altmap ); 279 | return ret; 280 | } 281 | 282 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | isync (1.0.4-2.1) unstable; urgency=low 2 | 3 | * Non-maintainer upload. 4 | * Drop debconf note that deals with a pre-Etch transition. 5 | Closes: #492194 6 | 7 | -- Christian Perrier Sat, 25 Oct 2008 08:40:52 +0200 8 | 9 | isync (1.0.4-2) unstable; urgency=low 10 | 11 | * Change the libdb4.4-dev build-dependency to libdb-dev. Thanks Luk for 12 | pointing this. (Closes: #499165) 13 | 14 | -- Nicolas Boullis Wed, 17 Sep 2008 23:58:58 +0200 15 | 16 | isync (1.0.4-1) unstable; urgency=low 17 | 18 | * The second "thanks Christian" release. 19 | * New upstream release. 20 | - Accept empty "* SEARCH" response. (Closes: #413336) 21 | - Quote user name in generated config. (Closes: #456783) 22 | * Explain the isync->mbsync change in the package description. 23 | (Closes: #430648) 24 | * Fix the debian/watch file that lacked the version and action fields. 25 | * Disable the upstream "deb-clean" stuff in the top-level Makefile, as 26 | in breaks cleaning the build directory. 27 | * Bump Standards-Version to 3.7.3. (No change required.) 28 | 29 | -- Nicolas Boullis Sat, 03 May 2008 01:42:55 +0200 30 | 31 | isync (1.0.3-3.1) unstable; urgency=low 32 | 33 | * Non-maintainer upload to fix pending l10n issues. 34 | * Debconf translations: 35 | - Portuguese. Closes: #418283 36 | - Italian. Closes: #418246 37 | - Dutch. Closes: #422244 38 | - Spanish. Closes: #426184 39 | - Finnish. Closes: #468214 40 | - Galician. Closes: #470529 41 | * [Lintian] Do not include debian revision in the build dependency for 42 | libssl-dev 43 | * [Lintian] No longer ignore errors from "make distclean" 44 | 45 | -- Christian Perrier Wed, 12 Mar 2008 07:24:01 +0100 46 | 47 | isync (1.0.3-3) unstable; urgency=low 48 | 49 | * The "thanks Christian" release. 50 | * Update German debconf templates translation. Thanks to Erik Schanze 51 | (for the translation) and Christian Perrier (for forwarding the 52 | translation). (Closes: #407615) 53 | 54 | -- Nicolas Boullis Mon, 5 Feb 2007 00:17:15 +0100 55 | 56 | isync (1.0.3-2.1) unstable; urgency=low 57 | 58 | * Non-maintainer upload with maintainer's permission 59 | * Debconf templates translations: 60 | - French updated by me 61 | - Brazilian Portuguese translation added 62 | - Czech translation added. Closes: #403473 63 | - Russian translation added. Closes: #403510 64 | - Vietnamese translation added 65 | - Norwegian Bokmål translation added. Closes: #403523 66 | 67 | -- Christian Perrier Sun, 17 Dec 2006 15:31:04 +0100 68 | 69 | isync (1.0.3-2) unstable; urgency=low 70 | 71 | * Back to unstable, with permission from Steve Langasek. (Message-ID: 72 | <20061121015225.GF28035@borges.dodds.net>) 73 | * Rewrite the debconf note, thanks to the debian-l10n-english team (and 74 | especially MJ Ray). 75 | * Also add some information about the new version into NEWS.Debian. 76 | * Remove the information about the need to set the T (trashed) flag from 77 | README.Debian. 78 | * Also install the isyncrc.sample sample configuration file. 79 | * Bump Standards-Version to 3.7.2. (No change required.) 80 | 81 | -- Nicolas Boullis Tue, 5 Dec 2006 00:34:54 +0100 82 | 83 | isync (1.0.3-1) experimental; urgency=low 84 | 85 | * New upstream release. (Closes: #315423) 86 | - Isync now supports breaking and linking threads. (Closes: #177280) 87 | - It also supports unflagging messages. (Closes: #111286) 88 | - IMAP commands are sent asynchronously. (Closes: #226222) 89 | * Kill the old debconf question about upgrades from pre-0.8 versions. 90 | * Use the (now obsolete) swedish and portugese translations anyway. 91 | (Closes: #337771, #378891) 92 | * New debconf note that warns about upgrades from pre-1.0 versions. 93 | * Add a build dependency on po-debconf. 94 | 95 | -- Nicolas Boullis Sun, 19 Nov 2006 15:04:31 +0100 96 | 97 | isync (0.9.2-4) unstable; urgency=low 98 | 99 | * Add Czech debconf translation, thanks to Martin Šín. (Closes: #317571) 100 | * Build with the newest libssl-dev. 101 | * Load the debconf library in postinst to ensure that everything works 102 | as expected, thanks to lintian for noticing the problem and to 103 | Josselin Mouette for pointing to the right doc. 104 | * Fix a bashism in the config script, thanks to lintian. 105 | * Update the postal address of the FSF in the copyright file. 106 | * Bump Standards-Version to 3.6.2. (No change required.) 107 | 108 | -- Nicolas Boullis Mon, 10 Oct 2005 01:37:50 +0200 109 | 110 | isync (0.9.2-3) unstable; urgency=low 111 | 112 | * Bump build-dependency from libdb4.0-dev to libdb4.2-dev, thanks to 113 | Andreas Jochens. (Closes: #280268) 114 | 115 | -- Nicolas Boullis Tue, 9 Nov 2004 18:21:12 +0100 116 | 117 | isync (0.9.2-2) unstable; urgency=low 118 | 119 | * Add german debconf templates translation, thanks to Erik Schanze. 120 | (Closes: #267675) 121 | 122 | -- Nicolas Boullis Tue, 24 Aug 2004 00:32:32 +0200 123 | 124 | isync (0.9.2-1) unstable; urgency=low 125 | 126 | * New upstream release. 127 | - Password prompt now includes the mailbox/server. (Closes: #92893) 128 | * Backported from CVS: 129 | - A few prinf converted to info (disabled with -q). 130 | - A few other printf converted to warn (disabled with -q -q) to be 131 | able to disable the warning when SSL is not available. 132 | (Closes: #228086) 133 | - Update the manpage accordingly (about -q). 134 | - Improve the manpage (about using isync with mutt). 135 | * Add Theodore Y. Ts'o as a co-maintainter. 136 | 137 | -- Nicolas Boullis Tue, 13 Apr 2004 02:12:42 +0200 138 | 139 | isync (0.9.1-4) unstable; urgency=low 140 | 141 | * The "Why do I keep adding such stupid bugs?" release. 142 | * Remove the extra parenthesis that caused UID FETCH syntax errors, 143 | thanks to Niels den Otter for pointing the bug and giving the 144 | solution. (Closes: #224803) 145 | * Use configure's --build and --host options to prevent wrong 146 | optimizations (such as building for sparc64 rather than for sparc). 147 | 148 | -- Nicolas Boullis Wed, 7 Jan 2004 01:06:53 +0100 149 | 150 | isync (0.9.1-3) unstable; urgency=low 151 | 152 | * Do not segfault when using both tunneled end non-tunneled connections, 153 | thanks to Nik A. Melchior for reporting and for his patch. 154 | (Closes: #220667) 155 | * Save uid of messages when interrupted, thanks to Theodore Y. Ts'o for 156 | reporting and for his patch. (Closes: #220346) 157 | * Do not get the sizes of the messages if unneeded (if MaxSize=0). 158 | 159 | -- Nicolas Boullis Thu, 18 Dec 2003 00:55:04 +0100 160 | 161 | isync (0.9.1-2) unstable; urgency=low 162 | 163 | * Add french debconf templates translation, thanks to Christian 164 | Perrier. (Closes: #218118) 165 | 166 | -- Nicolas Boullis Mon, 3 Nov 2003 18:50:56 +0100 167 | 168 | isync (0.9.1-1) unstable; urgency=low 169 | 170 | * New maintainer. (Closes: #180050) 171 | * New upstream release. 172 | - With the new option -R, isync is now able to create non-existent 173 | remote mailboxes. (Closes: #170388) 174 | * Update debian/copyright to match the current copyright: 175 | - Add Oswald Buddenhagen as copyright owner. 176 | - Add special exception for OpenSSL. 177 | * Add support for noopt in $DEB_BUILD_OPTIONS in debian/rules. 178 | * Switch to po-debconf. 179 | * Remove sample.isyncrc from debian/docs: no need to have it both as a 180 | doc and as an example. 181 | * Move package from section non-US/main (?) to mail. (Closes: #154216) 182 | * Update versionned build-dependency on debhelper to >= 4.1.16. 183 | * Bump Standards-Version to 3.6.1. (No change required.) 184 | 185 | -- Nicolas Boullis Tue, 14 Oct 2003 22:02:20 +0200 186 | 187 | isync (0.8-4) unstable; urgency=low 188 | 189 | * Orphaned the package, as I no longer use it. 190 | 191 | -- Joey Hess Thu, 6 Feb 2003 15:46:38 -0500 192 | 193 | isync (0.8-3) unstable; urgency=low 194 | 195 | * New upstream maintainer; updated copyright file web site address, and 196 | watch file. NB: new upstream has not made any new releases yet. 197 | 198 | -- Joey Hess Sat, 1 Feb 2003 16:03:49 -0500 199 | 200 | isync (0.8-2) unstable; urgency=low 201 | 202 | * Only reset debconf question if user chooses to abort upgrade. 203 | Closes: #167363 204 | * Don't open lock files O_EXCL. As seen in upstream cvs. 205 | * Description and build-deps updates. 206 | * Added README.Debian with notes on mutt integration. 207 | 208 | -- Joey Hess Fri, 1 Nov 2002 18:02:44 -0500 209 | 210 | isync (0.8-1) unstable; urgency=low 211 | 212 | * New upstream release. Closes: #134080 213 | 214 | **WARNING** 215 | You need to remove all the messages in your local folder if you were 216 | previously using another version of isync or else you will end up with 217 | duplicate messages on your IMAP server. 218 | 219 | * Has better support for uploading locally added messages. Closes: #120272 220 | * Added a debconf queston with some info about this that lets you abort the 221 | upgrade. 222 | * Added NEWS.Debian with same info. 223 | * New maintainer. 224 | * Removed upstream debianization stuff. 225 | * Updated copyright file. 226 | * Updated to current policy throughout. 227 | * Added uscan watch file. 228 | * Updated build-deps. 229 | * Now that isync needs berkeley databases, go with db4, so I don't have to 230 | transition from db3 later. 231 | * Fix fd leak (forgot to close tmp dir in maildir). Closes: #150762 232 | 233 | -- Joey Hess Tue, 29 Oct 2002 17:02:14 -0500 234 | 235 | isync (0.7-1) unstable; urgency=low 236 | 237 | * New upstream version (Closes: #121312, #92051). 238 | * Rumors say this might fix bugs #102255 and #120272, 239 | but I have no test setup right now, so I'm leaving them open. 240 | * Updated standards-version. 241 | 242 | -- Tommi Virtanen Sat, 5 Jan 2002 16:13:35 +0200 243 | 244 | isync (0.5-1) unstable; urgency=low 245 | 246 | * New upstream version (Closes: #98642). 247 | * Install sample.isyncrc too (Closes: #90464). 248 | 249 | -- Tommi Virtanen Sat, 23 Jun 2001 01:19:07 +0300 250 | 251 | isync (0.4-1) unstable; urgency=low 252 | 253 | * Initial Release. 254 | 255 | -- Tommi Virtanen Sat, 10 Mar 2001 18:43:35 +0200 256 | -------------------------------------------------------------------------------- /src/compat/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2004 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "isync.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #ifdef HAVE_GETOPT_LONG 36 | # include 37 | struct option Opts[] = { 38 | {"write", 0, NULL, 'w' }, 39 | {"writeto", 0, NULL, 'W' }, 40 | {"all", 0, NULL, 'a' }, 41 | {"list", 0, NULL, 'l'}, 42 | {"config", 1, NULL, 'c'}, 43 | {"create", 0, NULL, 'C'}, 44 | {"create-local", 0, NULL, 'L'}, 45 | {"create-remote", 0, NULL, 'R'}, 46 | {"delete", 0, NULL, 'd'}, 47 | {"expunge", 0, NULL, 'e'}, 48 | {"fast", 0, NULL, 'f'}, 49 | {"help", 0, NULL, 'h'}, 50 | {"remote", 1, NULL, 'r'}, 51 | {"folder", 1, NULL, 'F'}, 52 | {"maildir", 1, NULL, 'M'}, 53 | {"one-to-one", 0, NULL, '1'}, 54 | {"inbox", 1, NULL, 'I'}, 55 | {"host", 1, NULL, 's'}, 56 | {"port", 1, NULL, 'p'}, 57 | {"debug", 0, NULL, 'D'}, 58 | {"quiet", 0, NULL, 'q'}, 59 | {"user", 1, NULL, 'u'}, 60 | {"pass", 1, NULL, 'P'}, 61 | {"version", 0, NULL, 'v'}, 62 | {"verbose", 0, NULL, 'V'}, 63 | {0, 0, 0, 0} 64 | }; 65 | #endif 66 | 67 | static void 68 | version( void ) 69 | { 70 | puts( PACKAGE " " VERSION ); 71 | exit( 0 ); 72 | } 73 | 74 | static void 75 | usage( int code ) 76 | { 77 | fputs( 78 | PACKAGE " " VERSION " - mbsync wrapper: IMAP4 to maildir synchronizer\n" 79 | "Copyright (C) 2000-2002 Michael R. Elkins \n" 80 | "Copyright (C) 2002-2006,2008,2010-2012 Oswald Buddenhagen \n" 81 | "Copyright (C) 2004 Theodore Ts'o \n" 82 | "usage:\n" 83 | " " PACKAGE " [ flags ] mailbox [mailbox ...]\n" 84 | " " PACKAGE " [ flags ] -a\n" 85 | " " PACKAGE " [ flags ] -l\n" 86 | " -a, --all synchronize all defined mailboxes\n" 87 | " -l, --list list all defined mailboxes and exit\n" 88 | " -L, --create-local create local maildir mailbox if nonexistent\n" 89 | " -R, --create-remote create remote imap mailbox if nonexistent\n" 90 | " -C, --create create both local and remote mailboxes if nonexistent\n" 91 | " -d, --delete delete local msgs that don't exist on the server\n" 92 | " -e, --expunge expunge deleted messages\n" 93 | " -f, --fast only fetch new messages\n" 94 | " -r, --remote BOX remote mailbox\n" 95 | " -F, --folder DIR remote IMAP folder containing mailboxes\n" 96 | " -M, --maildir DIR local directory containing mailboxes\n" 97 | " -1, --one-to-one map every IMAP /box to /box\n" 98 | " -I, --inbox BOX map IMAP INBOX to /BOX (exception to -1)\n" 99 | " -s, --host HOST IMAP server address\n" 100 | " -p, --port PORT server IMAP port\n" 101 | " -u, --user USER IMAP user name\n" 102 | " -P, --pass PASSWORD IMAP password\n" 103 | " -c, --config CONFIG read an alternate config file (default: ~/.isyncrc)\n" 104 | " -D, --debug print debugging messages\n" 105 | " -V, --verbose verbose mode (display network traffic)\n" 106 | " -q, --quiet don't display progress info\n" 107 | " -v, --version display version\n" 108 | " -h, --help display this help message\n\n" 109 | "Note that this is a wrapper binary only; the \"real\" isync is named \"mbsync\".\n" 110 | "Options to permanently transform your old isync configuration:\n" 111 | " -w, --write write permanent mbsync configuration\n" 112 | " -W, --writeto FILE write permanent mbsync configuration to FILE\n", 113 | code ? stderr : stdout ); 114 | exit( code ); 115 | } 116 | 117 | static const char * 118 | strrstr( const char *h, const char *n ) 119 | { 120 | char *p = strstr( h, n ); 121 | if (!p) 122 | return 0; 123 | do { 124 | h = p; 125 | p = strstr( h + 1, n ); 126 | } while (p); 127 | return h; 128 | } 129 | 130 | static void 131 | add_arg( char ***args, const char *arg ) 132 | { 133 | int nu = 0; 134 | if (*args) 135 | for (; (*args)[nu]; nu++); 136 | *args = nfrealloc( *args, sizeof(char *) * (nu + 2)); 137 | (*args)[nu] = nfstrdup( arg ); 138 | (*args)[nu + 1] = 0; 139 | } 140 | 141 | #define OP_FAST (1<<2) 142 | #define OP_CREATE_REMOTE (1<<3) 143 | #define OP_CREATE_LOCAL (1<<4) 144 | 145 | int Quiet, Verbose, Debug; 146 | config_t global, *boxes; 147 | const char *maildir, *xmaildir, *folder, *inbox; 148 | int o2o, altmap, delete, expunge; 149 | 150 | const char *Home; 151 | int HomeLen; 152 | 153 | int 154 | main( int argc, char **argv ) 155 | { 156 | config_t *box, **stor; 157 | char *config = 0, *outconfig = 0, **args; 158 | int i, pl, fd, mod, all, list, ops, writeout; 159 | struct stat st; 160 | char path1[_POSIX_PATH_MAX], path2[_POSIX_PATH_MAX]; 161 | 162 | if (!(Home = getenv("HOME"))) { 163 | fputs( "Fatal: $HOME not set\n", stderr ); 164 | return 1; 165 | } 166 | HomeLen = strlen( Home ); 167 | 168 | /* defaults */ 169 | /* XXX the precedence is borked: 170 | it's defaults < cmdline < file instead of defaults < file < cmdline */ 171 | #ifdef BSD 172 | global.user = getenv( "USER" ); 173 | #else 174 | global.user = getenv( "LOGNAME" ); 175 | #endif 176 | global.port = 143; 177 | global.box = "INBOX"; 178 | global.use_namespace = 1; 179 | global.require_ssl = 1; 180 | global.use_tlsv1 = 1; 181 | folder = ""; 182 | maildir = "~"; 183 | xmaildir = Home; 184 | 185 | #define FLAGS "wW:alCLRc:defhp:qu:P:r:F:M:1I:s:vVD" 186 | 187 | mod = all = list = ops = writeout = Quiet = Verbose = Debug = 0; 188 | #ifdef HAVE_GETOPT_LONG 189 | while ((i = getopt_long( argc, argv, FLAGS, Opts, NULL )) != -1) 190 | #else 191 | while ((i = getopt( argc, argv, FLAGS )) != -1) 192 | #endif 193 | { 194 | switch (i) { 195 | case 'W': 196 | outconfig = optarg; 197 | /* plopp */ 198 | case 'w': 199 | writeout = 1; 200 | break; 201 | case 'l': 202 | list = 1; 203 | /* plopp */ 204 | case 'a': 205 | all = 1; 206 | break; 207 | case '1': 208 | o2o = 1; 209 | mod = 1; 210 | break; 211 | case 'C': 212 | ops |= OP_CREATE_REMOTE|OP_CREATE_LOCAL; 213 | break; 214 | case 'L': 215 | ops |= OP_CREATE_LOCAL; 216 | break; 217 | case 'R': 218 | ops |= OP_CREATE_REMOTE; 219 | break; 220 | case 'c': 221 | config = optarg; 222 | break; 223 | case 'd': 224 | delete = 1; 225 | break; 226 | case 'e': 227 | expunge = 1; 228 | break; 229 | case 'f': 230 | ops |= OP_FAST; 231 | break; 232 | case 'p': 233 | global.port = atoi( optarg ); 234 | mod = 1; 235 | break; 236 | case 'r': 237 | global.box = optarg; 238 | mod = 1; 239 | break; 240 | case 'F': 241 | folder = optarg; 242 | mod = 1; 243 | break; 244 | case 'M': 245 | maildir = optarg; 246 | mod = 1; 247 | break; 248 | case 'I': 249 | inbox = optarg; 250 | mod = 1; 251 | break; 252 | case 's': 253 | #ifdef HAVE_LIBSSL 254 | if (!strncasecmp( "imaps:", optarg, 6 )) { 255 | global.use_imaps = 1; 256 | global.port = 993; 257 | global.use_sslv2 = 1; 258 | global.use_sslv3 = 1; 259 | optarg += 6; 260 | } 261 | #endif 262 | global.host = optarg; 263 | mod = 1; 264 | break; 265 | case 'u': 266 | global.user = optarg; 267 | mod = 1; 268 | break; 269 | case 'P': 270 | global.pass = optarg; 271 | mod = 1; 272 | break; 273 | case 'D': 274 | Debug = 1; 275 | break; 276 | case 'V': 277 | Verbose++; 278 | break; 279 | case 'q': 280 | Quiet++; 281 | break; 282 | case 'v': 283 | version(); 284 | case 'h': 285 | usage( 0 ); 286 | default: 287 | usage( 1 ); 288 | } 289 | } 290 | 291 | if (config) { 292 | if (*config != '/') { 293 | if (!getcwd( path1, sizeof(path1) )) { 294 | fprintf( stderr, "Can't obtain working directory\n" ); 295 | return 1; 296 | } 297 | pl = strlen( path1 ); 298 | nfsnprintf( path1 + pl, sizeof(path1) - pl, "/%s", config ); 299 | config = path1; 300 | } 301 | } else { 302 | nfsnprintf( path1, sizeof(path1), "%s/.isyncrc", Home ); 303 | config = path1; 304 | } 305 | stor = &boxes; 306 | load_config( config, &stor ); 307 | 308 | if (!all && !o2o) 309 | for (i = optind; argv[i]; i++) 310 | if (!(box = find_box( argv[i] ))) { 311 | box = nfmalloc( sizeof(config_t) ); 312 | memcpy( box, &global, sizeof(config_t) ); 313 | box->path = argv[i]; 314 | *stor = box; 315 | stor = &box->next; 316 | mod = 1; 317 | } 318 | 319 | if (writeout) { 320 | all = 1; 321 | if (mod) 322 | fprintf( stderr, 323 | "Warning: command line switches that influence the resulting config file\n" 324 | "have been supplied.\n" ); 325 | } else { 326 | if (!argv[optind] && !all) { 327 | fprintf( stderr, "No mailbox specified. Try isync -h\n" ); 328 | return 1; 329 | } 330 | } 331 | 332 | if (all) { 333 | if (o2o) { 334 | DIR * dir; 335 | struct dirent *de; 336 | 337 | if (!(dir = opendir( xmaildir ))) { 338 | sys_error( "Cannot list '%s'", xmaildir ); 339 | return 1; 340 | } 341 | while ((de = readdir( dir ))) { 342 | if (*de->d_name == '.') 343 | continue; 344 | nfsnprintf( path2, sizeof(path2), "%s/%s/cur", xmaildir, de->d_name ); 345 | if (stat( path2, &st ) || !S_ISDIR( st.st_mode )) 346 | continue; 347 | global.path = de->d_name; 348 | global.box = (inbox && !strcmp( inbox, global.path )) ? 349 | "INBOX" : global.path; 350 | convert( &global ); 351 | } 352 | closedir( dir ); 353 | } else 354 | for (box = boxes; box; box = box->next) 355 | convert( box ); 356 | } else { 357 | for (i = optind; argv[i]; i++) 358 | if (o2o) { 359 | global.path = argv[i]; 360 | global.box = 361 | (inbox && !strcmp( global.path, inbox )) ? 362 | "INBOX" : global.path; 363 | convert( &global ); 364 | } else 365 | convert( find_box( argv[i] ) ); 366 | } 367 | 368 | if (writeout) { 369 | if (!outconfig) { 370 | const char *p = strrchr( config, '/' ); 371 | if (!p) 372 | p = config; 373 | p = strrstr( p, "isync" ); 374 | if (!p) 375 | nfsnprintf( path2, sizeof(path2), "%s.mbsync", config ); 376 | else 377 | nfsnprintf( path2, sizeof(path2), "%.*smb%s", p - config, config, p + 1 ); 378 | outconfig = path2; 379 | } 380 | if ((fd = creat( outconfig, 0666 )) < 0) { 381 | sys_error( "Error: cannot create config file '%s'", outconfig ); 382 | return 1; 383 | } 384 | } else { 385 | strcpy( path2, "/tmp/mbsyncrcXXXXXX" ); 386 | if ((fd = mkstemp( path2 )) < 0) { 387 | sys_error( "Error: cannot create temporary config file" ); 388 | return 1; 389 | } 390 | } 391 | write_config( fd ); 392 | 393 | if (writeout) 394 | return 0; 395 | args = 0; 396 | add_arg( &args, "mbsync" ); 397 | while (--Verbose >= 0) 398 | add_arg( &args, "-V" ); 399 | if (Debug) 400 | add_arg( &args, "-D" ); 401 | for (; Quiet; Quiet--) 402 | add_arg( &args, "-q" ); 403 | add_arg( &args, "-cT" ); 404 | add_arg( &args, path2 ); 405 | if (ops & OP_FAST) 406 | add_arg( &args, "-Ln" ); 407 | if (ops & OP_CREATE_REMOTE) 408 | add_arg( &args, "-Cm" ); 409 | if (ops & OP_CREATE_LOCAL) 410 | add_arg( &args, "-Cs" ); 411 | if (list) 412 | add_arg( &args, "-lC" ); 413 | if (o2o) { 414 | if (all) 415 | add_arg( &args, "o2o" ); 416 | else { 417 | char buf[1024]; 418 | strcpy( buf, "o2o:" ); 419 | strcat( buf, argv[optind] ); 420 | while (argv[++optind]) { 421 | strcat( buf, "," ); 422 | strcat( buf, argv[optind] ); 423 | } 424 | add_arg( &args, buf ); 425 | } 426 | } else { 427 | if (all) 428 | add_arg( &args, "-a" ); 429 | else 430 | for (; argv[optind]; optind++) 431 | add_arg( &args, find_box( argv[optind] )->channel_name ); 432 | } 433 | execvp( args[0], args ); 434 | sys_error( "Cannot execute %s", args[0] ); 435 | return 1; 436 | } 437 | -------------------------------------------------------------------------------- /src/compat/isync.1: -------------------------------------------------------------------------------- 1 | .ig 2 | \" isync - mbsync wrapper: IMAP4 to Maildir mailbox synchronizer 3 | \" Copyright (C) 2000-2002 Michael R. Elkins 4 | \" Copyright (C) 2002-2004 Oswald Buddenhagen 5 | \" 6 | \" This program is free software; you can redistribute it and/or modify 7 | \" it under the terms of the GNU General Public License as published by 8 | \" the Free Software Foundation; either version 2 of the License, or 9 | \" (at your option) any later version. 10 | \" 11 | \" This program is distributed in the hope that it will be useful, 12 | \" but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | \" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | \" GNU General Public License for more details. 15 | \" 16 | \" You should have received a copy of the GNU General Public License 17 | \" along with this program. If not, see . 18 | \" 19 | .. 20 | .TH isync 1 "2010 Feb 7" 21 | .. 22 | .SH NAME 23 | isync - synchronize IMAP4 and Maildir mailboxes 24 | .. 25 | .SH SYNOPSIS 26 | \fBisync\fR [\fIoptions\fR ...] {\fImailbox\fR ...|\fI-a\fR|\fI-l\fR} 27 | .. 28 | .SH DESCRIPTION 29 | \fBisync\fR is a command line application which synchronizes local 30 | Maildir mailboxes with remote IMAP4 mailboxes, suitable for use in 31 | IMAP-disconnected mode. Multiple copies of the remote IMAP4 mailboxes can 32 | be maintained, and all flags are synchronized. 33 | .br 34 | \fBisync\fR is only a wrapper binary around \fBmbsync\fR to simplify upgrades. 35 | It will automatically migrate the UID mapping from previous versions of 36 | \fBisync\fR (even before 0.8) to the new format, and transparently call 37 | \fBmbsync\fR. If you were using \fBisync\fR version 0.8 or 0.9.x you might 38 | want to use \fBmdconvert\fR to convert the mailboxes to the more efficient 39 | \fBnative\fR UID storage scheme after migrating them. 40 | .. 41 | .SH OPTIONS 42 | .TP 43 | \fB-c\fR, \fB--config\fR \fIfile\fR 44 | Read configuration from \fIfile\fR. 45 | By default, the configuration is read from ~/.isyncrc if it exists. 46 | .TP 47 | \fB-1\fR, \fB--one-to-one\fR 48 | Instead of using the mailbox specifications in ~/.isyncrc, isync will pick up 49 | all mailboxes from the local directory and remote folder and map them 1:1 50 | onto each other according to their names. 51 | .TP 52 | \fB-I\fR, \fB--inbox\fR \fImailbox\fR 53 | Exception to the 1:1 mapping created by -1: the special IMAP mailbox \fIINBOX\fR 54 | is mapped to the local \fImailbox\fR (relative to the maildir). 55 | .TP 56 | \fB-a\fR, \fB--all\fR 57 | Synchronize all mailboxes (either specified in ~/.isyncrc or determined by the 58 | 1:1 mapping). 59 | .TP 60 | \fB-l\fR, \fB--list\fR 61 | Don't synchronize anything, but list all mailboxes and exit. 62 | .TP 63 | \fB-L\fR, \fB--create-local\fR 64 | Automatically create the local Maildir mailbox if it doesn't already 65 | exist. 66 | .TP 67 | \fB-R\fR, \fB--create-remote\fR 68 | Automatically create the remote IMAP mailbox if it doesn't already exist. 69 | .TP 70 | \fB-C\fR, \fB--create\fR 71 | Automatically create any mailboxes if they don't already exist. 72 | This is simply a combination of -L and -R. 73 | .TP 74 | \fB-d\fR, \fB--delete\fR 75 | Causes \fBisync\fR to propagate message deletions. 76 | By default, \fIdead\fR messages are \fBnot\fR deleted. 77 | .TP 78 | \fB-e\fR, \fB--expunge\fR 79 | Causes \fBisync\fR to permanently remove all messages marked for deletion. 80 | By default, \fIdeleted\fR messages are \fBnot\fR expunged. 81 | .TP 82 | \fB-f\fR, \fB--fast\fR 83 | Only fetch new messages existing on the server into the local mailbox. 84 | Message deletions and flag changes will not be propagated. 85 | .TP 86 | \fB-h\fR, \fB--help\fR 87 | Displays a summary of command line options 88 | .TP 89 | \fB-p\fR, \fB--port\fR \fIport\fR 90 | Specifies the port on the IMAP server to connect to (default: 143 for imap, 91 | 993 for imaps) 92 | .TP 93 | \fB-q\fR, \fB--quiet\fR 94 | Suppress informational messages. 95 | If specified twice, suppress warning messages as well. 96 | .TP 97 | \fB-r\fR, \fB--remote\fR \fIbox\fR 98 | Specifies the name of the remote IMAP mailbox to synchronize with 99 | (Default: INBOX) 100 | .TP 101 | \fB-s\fR, \fB--host\fR [\fBimaps:\fR]\fIhost\fR 102 | Specifies the hostname of the IMAP server 103 | .TP 104 | \fB-u\fR, \fB--user\fR \fIuser\fR 105 | Specifies the login name to access the IMAP server (default: $USER) 106 | .TP 107 | \fB-P\fR, \fB--pass\fR \fIpassword\fR 108 | Specifies the password to access the IMAP server (prompted for by default) 109 | .TP 110 | \fB-M\fR, \fB--maildir\fR \fIdir\fR 111 | Specifies the location for your local mailboxes. 112 | .TP 113 | \fB-F\fR, \fB--folder\fR \fIfolder\fR/ 114 | Specifies the location for your remote mailboxes. 115 | .TP 116 | \fB-v\fR, \fB--version\fR 117 | Displays \fBisync\fR version information. 118 | .TP 119 | \fB-V\fR, \fB--verbose\fR 120 | Enables \fIverbose\fR mode, which displays the IMAP4 network traffic. 121 | .TP 122 | \fB-D\fR, \fB--debug\fR 123 | Enable printing of \fIdebug\fR messages. 124 | .TP 125 | \fB-w\fR, \fB--write\fR 126 | Don't run \fBmbsync\fR, but instead write a permanent config file for it. 127 | The UID mappings of all configured mailboxes will be migrated. 128 | Note that most command line options that would affect an actual sync operation 129 | will be incorporated into the new config file as well; exceptions are 130 | --fast and --create[-remote|-local]. 131 | The name of the new config file is determined by replacing the last occurrence 132 | of "isync" with "mbsync", or appending ".mbsync" if "isync" was not found. 133 | .TP 134 | \fB-W\fR, \fB--writeto\fR \fIfile\fR 135 | Like \fB-w\fR, but use the specified name for the new config file. 136 | .. 137 | .SH CONFIGURATION 138 | \fBisync\fR by default reads \fI~/.isyncrc\fR to load configuration data. 139 | Each non-empty line of the configuration file that does not start with a 140 | hash mark consists of a command. 141 | The following commands are understood: 142 | .TP 143 | \fBMailbox\fR \fIpath\fR 144 | Defines a local Maildir mailbox. All configuration commands following this 145 | line, up until the next \fIMailbox\fR command, apply to this mailbox only. 146 | .. 147 | .TP 148 | \fBHost\fR [\fBimaps:\fR]\fIname\fR 149 | Defines the DNS name or IP address of the IMAP server. If the hostname is 150 | prefixed with \fBimaps:\fR the connection is assumed to be a SSL connection 151 | to port 993 (though you can change this by placing a \fBPort\fR command 152 | \fBafter\fR the \fBHost\fR command). 153 | Note that modern servers support SSL on the default port 143. 154 | \fBisync\fR will always attempt to use SSL if available. 155 | .. 156 | .TP 157 | \fBPort\fR \fIport\fR 158 | Defines the TCP port number of the IMAP server (Default: 143 for imap, 159 | 993 for imaps) 160 | .. 161 | .TP 162 | \fBBox\fR \fImailbox\fR 163 | Defines the name of the remote IMAP mailbox associated with the local 164 | Maildir mailbox (Default: INBOX) 165 | .. 166 | .TP 167 | \fBUser\fR \fIusername\fR 168 | Defines the login name on the IMAP server (Default: current user) 169 | .. 170 | .TP 171 | \fBPass\fR \fIpassword\fR 172 | Defines the password for \fIusername\fR on the IMAP server. 173 | Note that this option is \fBNOT\fR required. 174 | If no password is specified in the configuration file, \fBisync\fR 175 | will prompt you for it. 176 | .. 177 | .TP 178 | \fBAlias\fR \fIstring\fR 179 | Defines an alias for the mailbox which can be used as a shortcut on the 180 | command line. 181 | .. 182 | .TP 183 | \fBCopyDeletedTo\fR \fImailbox\fR 184 | Specifies the remote IMAP mailbox to copy deleted messages to prior to 185 | expunging (Default: none). 186 | .. 187 | .TP 188 | \fBDelete\fR \fIyes\fR|\fIno\fR 189 | Specifies whether message deletions are propagated. (Default: no). 190 | \fBNOTE:\fR The \fI-d\fR command line option overrides this setting when 191 | set to \fIno\fR. 192 | .. 193 | .TP 194 | \fBExpunge\fR \fIyes\fR|\fIno\fR 195 | Specifies whether deleted messages are expunged. (Default: no). 196 | \fBNOTE:\fR The \fI-e\fR command line option overrides this setting when 197 | set to \fIno\fR. 198 | .. 199 | .TP 200 | \fBMailDir\fR \fIdirectory\fR 201 | Specifies the location of your local mailboxes if a relative path is 202 | specified in a \fIMailbox\fR command (Default: \fI~\fR). 203 | \fBNOTE:\fR This directive is allowed only in the \fIglobal\fR 204 | section (see below). 205 | .. 206 | .TP 207 | \fBFolder\fR \fIdirectory\fR/ 208 | Specifies the location of your IMAP mailboxes 209 | specified in \fIBox\fR commands (Default: \fI""\fR). 210 | \fBNOTE:\fR You \fBmust\fR append the hierarchy delimiter (usually 211 | a slash) to this specification. 212 | \fBNOTE 2:\fR This directive is allowed only in the \fIglobal\fR 213 | section (see below). 214 | .. 215 | .TP 216 | \fBMaxMessages\fR \fIcount\fR 217 | Sets the number of messages \fBisync\fR should keep in the local copy of a 218 | mailbox. 219 | This is useful for mailboxes where you keep a complete archive on the server, 220 | but want to mirror only the last messages (for instance, for mailing lists). 221 | The messages that were the first to arrive in the mailbox (independently of the 222 | actual date of the message) will be deleted first. 223 | Messages that are flagged (marked as important) and recent messages will not be 224 | automatically deleted. 225 | If \fIcount\fR is 0, the maximum number of messages is \fBunlimited\fR. 226 | (Default: 0) 227 | .. 228 | .TP 229 | \fBMaxSize\fR \fIbytes\fR 230 | Messages larger than that many bytes will not be transferred over the wire. 231 | This is useful for weeding out messages with large attachments. 232 | If \fIbytes\fR is 0, the maximum file size is \fBunlimited\fR. 233 | (Default: 0) 234 | .. 235 | .TP 236 | \fBTunnel\fR \fIcommand\fR 237 | Specify a command to run to establish a connection rather than opening a TCP 238 | socket. This allows you to run an IMAP session over an SSH tunnel, for 239 | example. 240 | .TP 241 | \fBUseNamespace\fR \fIyes\fR|\fIno\fR 242 | Selects whether the server's first "personal" NAMESPACE should be prefixed to 243 | mailbox names. Disabling this makes sense for some broken IMAP servers. 244 | This option is meaningless if a \fIFolder\fR was specified. 245 | (Default: \fIyes\fR) 246 | .. 247 | .TP 248 | \fBRequireCRAM\fR \fIyes\fR|\fIno\fR 249 | If set to \fIyes\fR, \fBisync\fR will abort the connection if no CRAM-MD5 250 | authentication is possible. (Default: \fIno\fR) 251 | .. 252 | .TP 253 | \fBRequireSSL\fR \fIyes\fR|\fIno\fR 254 | \fBisync\fR will abort the connection if a TLS/SSL session cannot be 255 | established with the IMAP server. (Default: \fIyes\fR) 256 | .. 257 | .TP 258 | \fBCertificateFile\fR \fIpath\fR 259 | File containing X.509 CA certificates used to verify server identities. 260 | .. 261 | .TP 262 | \fBUseSSLv2\fR \fIyes\fR|\fIno\fR 263 | Should \fBisync\fR use SSLv2 for communication with the IMAP server over SSL? 264 | (Default: \fIyes\fR if the imaps port is used, otherwise \fIno\fR) 265 | .. 266 | .TP 267 | \fBUseSSLv3\fR \fIyes\fR|\fIno\fR 268 | Should \fBisync\fR use SSLv3 for communication with the IMAP server over SSL? 269 | (Default: \fIyes\fR if the imaps port is used, otherwise \fIno\fR) 270 | .. 271 | .TP 272 | \fBUseTLSv1\fR \fIyes\fR|\fIno\fR 273 | Should \fBisync\fR use TLSv1 for communication with the IMAP server over SSL? 274 | (Default: \fIyes\fR) 275 | .. 276 | .TP 277 | \fBOneToOne\fR 278 | \fBisync\fR will ignore any \fIMailbox\fR specifications and instead pick up 279 | all mailboxes from the local \fIMailDir\fR and remote \fIFolder\fR and map 280 | them 1:1 onto each other according to their names. 281 | \fBNOTE:\fR This directive is allowed only in the \fIglobal\fR 282 | section (see below). 283 | .. 284 | .TP 285 | \fBInbox\fR \fImailbox\fR 286 | Exception to the OneToOne mapping: the special IMAP mailbox \fIINBOX\fR 287 | is mapped to the local \fImailbox\fR (relative to the \fIMailDir\fR). 288 | \fBNOTE:\fR This directive is only meaningful in the \fIglobal\fR 289 | section (see below). 290 | .. 291 | .P 292 | Configuration commands that appear prior to the first \fBMailbox\fR 293 | command are considered to be \fIglobal\fR 294 | options which are used as defaults when those specific options are not 295 | specifically set for a defined Mailbox. For example, if you use the same 296 | login name for several IMAP servers, you can put a \fBUser\fR command before 297 | the first \fBMailbox\fR command, and then leave out the \fBUser\fR command 298 | in the sections for each mailbox. 299 | \fBisync\fR will then use the global value by default. 300 | .. 301 | .SH FILES 302 | .TP 303 | .B ~/.isyncrc 304 | Default configuration file 305 | .. 306 | .SH BUGS 307 | The configuration file takes precedence over command line options. 308 | .br 309 | Use -c /dev/null to work around. 310 | .P 311 | See the \fBINHERENT PROBLEMS\fR section in the \fBmbsync\fR man page, too. 312 | .. 313 | .SH SEE ALSO 314 | mbsync(1), mdconvert(1), mutt(1), maildir(5) 315 | .P 316 | Up to date information on \fBisync\fR can be found at http://isync.sf.net/ 317 | .. 318 | .SH AUTHORS 319 | Originally written by Michael R. Elkins, 320 | currently maintained by Oswald Buddenhagen. 321 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mbsync - mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2006,2011,2012 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | * As a special exception, mbsync may be linked with the OpenSSL library, 20 | * despite that library's more restrictive license. 21 | */ 22 | 23 | #include "isync.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | int DFlags; 33 | static int need_nl; 34 | 35 | void 36 | flushn( void ) 37 | { 38 | if (need_nl) { 39 | putchar( '\n' ); 40 | fflush( stdout ); 41 | need_nl = 0; 42 | } 43 | } 44 | 45 | static void 46 | printn( const char *msg, va_list va ) 47 | { 48 | if (*msg == '\v') 49 | msg++; 50 | else 51 | flushn(); 52 | vprintf( msg, va ); 53 | fflush( stdout ); 54 | } 55 | 56 | void 57 | debug( const char *msg, ... ) 58 | { 59 | va_list va; 60 | 61 | if (DFlags & DEBUG) { 62 | va_start( va, msg ); 63 | vprintf( msg, va ); 64 | va_end( va ); 65 | fflush( stdout ); 66 | need_nl = 0; 67 | } 68 | } 69 | 70 | void 71 | debugn( const char *msg, ... ) 72 | { 73 | va_list va; 74 | 75 | if (DFlags & DEBUG) { 76 | va_start( va, msg ); 77 | vprintf( msg, va ); 78 | va_end( va ); 79 | fflush( stdout ); 80 | need_nl = 1; 81 | } 82 | } 83 | 84 | void 85 | info( const char *msg, ... ) 86 | { 87 | va_list va; 88 | 89 | if (!(DFlags & QUIET)) { 90 | va_start( va, msg ); 91 | printn( msg, va ); 92 | va_end( va ); 93 | need_nl = 0; 94 | } 95 | } 96 | 97 | void 98 | infon( const char *msg, ... ) 99 | { 100 | va_list va; 101 | 102 | if (!(DFlags & QUIET)) { 103 | va_start( va, msg ); 104 | printn( msg, va ); 105 | va_end( va ); 106 | need_nl = 1; 107 | } 108 | } 109 | 110 | void 111 | warn( const char *msg, ... ) 112 | { 113 | va_list va; 114 | 115 | if (!(DFlags & VERYQUIET)) { 116 | flushn(); 117 | va_start( va, msg ); 118 | vfprintf( stderr, msg, va ); 119 | va_end( va ); 120 | } 121 | } 122 | 123 | void 124 | error( const char *msg, ... ) 125 | { 126 | va_list va; 127 | 128 | flushn(); 129 | va_start( va, msg ); 130 | vfprintf( stderr, msg, va ); 131 | va_end( va ); 132 | } 133 | 134 | void 135 | sys_error( const char *msg, ... ) 136 | { 137 | va_list va; 138 | char buf[1024]; 139 | 140 | flushn(); 141 | va_start( va, msg ); 142 | if ((unsigned)vsnprintf( buf, sizeof(buf), msg, va ) >= sizeof(buf)) 143 | oob(); 144 | va_end( va ); 145 | perror( buf ); 146 | } 147 | 148 | void 149 | add_string_list( string_list_t **list, const char *str ) 150 | { 151 | string_list_t *elem; 152 | int len; 153 | 154 | len = strlen( str ); 155 | elem = nfmalloc( sizeof(*elem) + len ); 156 | elem->next = *list; 157 | *list = elem; 158 | memcpy( elem->string, str, len + 1 ); 159 | } 160 | 161 | void 162 | free_string_list( string_list_t *list ) 163 | { 164 | string_list_t *tlist; 165 | 166 | for (; list; list = tlist) { 167 | tlist = list->next; 168 | free( list ); 169 | } 170 | } 171 | 172 | void 173 | free_generic_messages( message_t *msgs ) 174 | { 175 | message_t *tmsg; 176 | 177 | for (; msgs; msgs = tmsg) { 178 | tmsg = msgs->next; 179 | free( msgs ); 180 | } 181 | } 182 | 183 | #ifndef HAVE_VASPRINTF 184 | static int 185 | vasprintf( char **strp, const char *fmt, va_list ap ) 186 | { 187 | int len; 188 | char tmp[1024]; 189 | 190 | if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap )) < 0 || !(*strp = malloc( len + 1 ))) 191 | return -1; 192 | if (len >= (int)sizeof(tmp)) 193 | vsprintf( *strp, fmt, ap ); 194 | else 195 | memcpy( *strp, tmp, len + 1 ); 196 | return len; 197 | } 198 | #endif 199 | 200 | #ifndef HAVE_MEMRCHR 201 | void * 202 | memrchr( const void *s, int c, size_t n ) 203 | { 204 | u_char *b = (u_char *)s, *e = b + n; 205 | 206 | while (--e >= b) 207 | if (*e == c) 208 | return (void *)e; 209 | return 0; 210 | } 211 | #endif 212 | 213 | void 214 | oob( void ) 215 | { 216 | fputs( "Fatal: buffer too small. Please report a bug.\n", stderr ); 217 | abort(); 218 | } 219 | 220 | int 221 | nfsnprintf( char *buf, int blen, const char *fmt, ... ) 222 | { 223 | int ret; 224 | va_list va; 225 | 226 | va_start( va, fmt ); 227 | if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen) 228 | oob(); 229 | va_end( va ); 230 | return ret; 231 | } 232 | 233 | static void ATTR_NORETURN 234 | oom( void ) 235 | { 236 | fputs( "Fatal: Out of memory\n", stderr ); 237 | abort(); 238 | } 239 | 240 | void * 241 | nfmalloc( size_t sz ) 242 | { 243 | void *ret; 244 | 245 | if (!(ret = malloc( sz ))) 246 | oom(); 247 | return ret; 248 | } 249 | 250 | void * 251 | nfcalloc( size_t sz ) 252 | { 253 | void *ret; 254 | 255 | if (!(ret = calloc( sz, 1 ))) 256 | oom(); 257 | return ret; 258 | } 259 | 260 | void * 261 | nfrealloc( void *mem, size_t sz ) 262 | { 263 | char *ret; 264 | 265 | if (!(ret = realloc( mem, sz )) && sz) 266 | oom(); 267 | return ret; 268 | } 269 | 270 | char * 271 | nfstrdup( const char *str ) 272 | { 273 | char *ret; 274 | 275 | if (!(ret = strdup( str ))) 276 | oom(); 277 | return ret; 278 | } 279 | 280 | int 281 | nfvasprintf( char **str, const char *fmt, va_list va ) 282 | { 283 | int ret = vasprintf( str, fmt, va ); 284 | if (ret < 0) 285 | oom(); 286 | return ret; 287 | } 288 | 289 | int 290 | nfasprintf( char **str, const char *fmt, ... ) 291 | { 292 | int ret; 293 | va_list va; 294 | 295 | va_start( va, fmt ); 296 | ret = nfvasprintf( str, fmt, va ); 297 | va_end( va ); 298 | return ret; 299 | } 300 | 301 | /* 302 | static struct passwd * 303 | cur_user( void ) 304 | { 305 | char *p; 306 | struct passwd *pw; 307 | uid_t uid; 308 | 309 | uid = getuid(); 310 | if ((!(p = getenv("LOGNAME")) || !(pw = getpwnam( p )) || pw->pw_uid != uid) && 311 | (!(p = getenv("USER")) || !(pw = getpwnam( p )) || pw->pw_uid != uid) && 312 | !(pw = getpwuid( uid ))) 313 | { 314 | fputs ("Cannot determinate current user\n", stderr); 315 | return 0; 316 | } 317 | return pw; 318 | } 319 | */ 320 | 321 | static char * 322 | my_strndup( const char *s, size_t nchars ) 323 | { 324 | char *r = nfmalloc( nchars + 1 ); 325 | memcpy( r, s, nchars ); 326 | r[nchars] = 0; 327 | return r; 328 | } 329 | 330 | char * 331 | expand_strdup( const char *s ) 332 | { 333 | struct passwd *pw; 334 | const char *p, *q; 335 | char *r; 336 | 337 | if (*s == '~') { 338 | s++; 339 | if (!*s) { 340 | p = 0; 341 | q = Home; 342 | } else if (*s == '/') { 343 | p = s; 344 | q = Home; 345 | } else { 346 | if ((p = strchr( s, '/' ))) { 347 | r = my_strndup( s, (int)(p - s) ); 348 | pw = getpwnam( r ); 349 | free( r ); 350 | } else 351 | pw = getpwnam( s ); 352 | if (!pw) 353 | return 0; 354 | q = pw->pw_dir; 355 | } 356 | nfasprintf( &r, "%s%s", q, p ? p : "" ); 357 | return r; 358 | } else 359 | return nfstrdup( s ); 360 | } 361 | 362 | /* Return value: 0 = ok, -1 = out found in arg, -2 = in found in arg but no out specified */ 363 | int 364 | map_name( char *arg, char in, char out ) 365 | { 366 | int l, k; 367 | 368 | if (!in || in == out) 369 | return 0; 370 | for (l = 0; arg[l]; l++) 371 | if (arg[l] == in) { 372 | if (!out) 373 | return -2; 374 | arg[l] = out; 375 | } else if (arg[l] == out) { 376 | /* restore original name for printing error message */ 377 | for (k = 0; k < l; k++) 378 | if (arg[k] == out) 379 | arg[k] = in; 380 | return -1; 381 | } 382 | return 0; 383 | } 384 | 385 | static int 386 | compare_ints( const void *l, const void *r ) 387 | { 388 | return *(int *)l - *(int *)r; 389 | } 390 | 391 | void 392 | sort_ints( int *arr, int len ) 393 | { 394 | qsort( arr, len, sizeof(int), compare_ints ); 395 | } 396 | 397 | 398 | static struct { 399 | unsigned char i, j, s[256]; 400 | } rs; 401 | 402 | void 403 | arc4_init( void ) 404 | { 405 | int i, fd; 406 | unsigned char j, si, dat[128]; 407 | 408 | if ((fd = open( "/dev/urandom", O_RDONLY )) < 0 && (fd = open( "/dev/random", O_RDONLY )) < 0) { 409 | error( "Fatal: no random number source available.\n" ); 410 | exit( 3 ); 411 | } 412 | if (read( fd, dat, 128 ) != 128) { 413 | error( "Fatal: cannot read random number source.\n" ); 414 | exit( 3 ); 415 | } 416 | close( fd ); 417 | 418 | for (i = 0; i < 256; i++) 419 | rs.s[i] = i; 420 | for (i = j = 0; i < 256; i++) { 421 | si = rs.s[i]; 422 | j += si + dat[i & 127]; 423 | rs.s[i] = rs.s[j]; 424 | rs.s[j] = si; 425 | } 426 | rs.i = rs.j = 0; 427 | 428 | for (i = 0; i < 256; i++) 429 | arc4_getbyte(); 430 | } 431 | 432 | unsigned char 433 | arc4_getbyte( void ) 434 | { 435 | unsigned char si, sj; 436 | 437 | rs.i++; 438 | si = rs.s[rs.i]; 439 | rs.j += si; 440 | sj = rs.s[rs.j]; 441 | rs.s[rs.i] = sj; 442 | rs.s[rs.j] = si; 443 | return rs.s[(si + sj) & 0xff]; 444 | } 445 | 446 | static const unsigned char prime_deltas[] = { 447 | 0, 0, 1, 3, 1, 5, 3, 3, 1, 9, 7, 5, 3, 9, 25, 3, 448 | 1, 21, 3, 21, 7, 15, 9, 5, 3, 29, 15, 0, 0, 0, 0, 0 449 | }; 450 | 451 | int 452 | bucketsForSize( int size ) 453 | { 454 | int base = 4, bits = 2; 455 | 456 | for (;;) { 457 | int prime = base + prime_deltas[bits]; 458 | if (prime >= size) 459 | return prime; 460 | base <<= 1; 461 | bits++; 462 | } 463 | } 464 | 465 | #ifdef HAVE_SYS_POLL_H 466 | static struct pollfd *pollfds; 467 | #else 468 | # ifdef HAVE_SYS_SELECT_H 469 | # include 470 | # endif 471 | # define pollfds fdparms 472 | #endif 473 | static struct { 474 | void (*cb)( int what, void *aux ); 475 | void *aux; 476 | #ifndef HAVE_SYS_POLL_H 477 | int fd, events; 478 | #endif 479 | int faked; 480 | } *fdparms; 481 | static int npolls, rpolls, changed; 482 | 483 | static int 484 | find_fd( int fd ) 485 | { 486 | int n; 487 | 488 | for (n = 0; n < npolls; n++) 489 | if (pollfds[n].fd == fd) 490 | return n; 491 | return -1; 492 | } 493 | 494 | void 495 | add_fd( int fd, void (*cb)( int events, void *aux ), void *aux ) 496 | { 497 | int n; 498 | 499 | assert( find_fd( fd ) < 0 ); 500 | n = npolls++; 501 | if (rpolls < npolls) { 502 | rpolls = npolls; 503 | #ifdef HAVE_SYS_POLL_H 504 | pollfds = nfrealloc(pollfds, npolls * sizeof(*pollfds)); 505 | #endif 506 | fdparms = nfrealloc(fdparms, npolls * sizeof(*fdparms)); 507 | } 508 | pollfds[n].fd = fd; 509 | pollfds[n].events = 0; /* POLLERR & POLLHUP implicit */ 510 | fdparms[n].faked = 0; 511 | fdparms[n].cb = cb; 512 | fdparms[n].aux = aux; 513 | changed = 1; 514 | } 515 | 516 | void 517 | conf_fd( int fd, int and_events, int or_events ) 518 | { 519 | int n = find_fd( fd ); 520 | assert( n >= 0 ); 521 | pollfds[n].events = (pollfds[n].events & and_events) | or_events; 522 | } 523 | 524 | void 525 | fake_fd( int fd, int events ) 526 | { 527 | int n = find_fd( fd ); 528 | assert( n >= 0 ); 529 | fdparms[n].faked |= events; 530 | } 531 | 532 | void 533 | del_fd( int fd ) 534 | { 535 | int n = find_fd( fd ); 536 | assert( n >= 0 ); 537 | npolls--; 538 | #ifdef HAVE_SYS_POLL_H 539 | memmove(pollfds + n, pollfds + n + 1, (npolls - n) * sizeof(*pollfds)); 540 | #endif 541 | memmove(fdparms + n, fdparms + n + 1, (npolls - n) * sizeof(*fdparms)); 542 | changed = 1; 543 | } 544 | 545 | #define shifted_bit(in, from, to) \ 546 | (((unsigned)(in) & from) \ 547 | / (from > to ? from / to : 1) \ 548 | * (to > from ? to / from : 1)) 549 | 550 | static void 551 | event_wait( void ) 552 | { 553 | int m, n; 554 | 555 | #ifdef HAVE_SYS_POLL_H 556 | int timeout = -1; 557 | for (n = 0; n < npolls; n++) 558 | if (fdparms[n].faked) { 559 | timeout = 0; 560 | break; 561 | } 562 | if (poll( pollfds, npolls, timeout ) < 0) { 563 | perror( "poll() failed in event loop" ); 564 | abort(); 565 | } 566 | for (n = 0; n < npolls; n++) 567 | if ((m = pollfds[n].revents | fdparms[n].faked)) { 568 | assert( !(m & POLLNVAL) ); 569 | fdparms[n].faked = 0; 570 | fdparms[n].cb( m | shifted_bit( m, POLLHUP, POLLIN ), fdparms[n].aux ); 571 | if (changed) { 572 | changed = 0; 573 | break; 574 | } 575 | } 576 | #else 577 | struct timeval *timeout = 0; 578 | static struct timeval null_tv; 579 | fd_set rfds, wfds, efds; 580 | int fd; 581 | 582 | FD_ZERO( &rfds ); 583 | FD_ZERO( &wfds ); 584 | FD_ZERO( &efds ); 585 | m = -1; 586 | for (n = 0; n < npolls; n++) { 587 | if (fdparms[n].faked) 588 | timeout = &null_tv; 589 | fd = fdparms[n].fd; 590 | if (fdparms[n].events & POLLIN) 591 | FD_SET( fd, &rfds ); 592 | if (fdparms[n].events & POLLOUT) 593 | FD_SET( fd, &wfds ); 594 | FD_SET( fd, &efds ); 595 | if (fd > m) 596 | m = fd; 597 | } 598 | if (select( m + 1, &rfds, &wfds, &efds, timeout ) < 0) { 599 | perror( "select() failed in event loop" ); 600 | abort(); 601 | } 602 | for (n = 0; n < npolls; n++) { 603 | fd = fdparms[n].fd; 604 | m = fdparms[n].faked; 605 | if (FD_ISSET( fd, &rfds )) 606 | m |= POLLIN; 607 | if (FD_ISSET( fd, &wfds )) 608 | m |= POLLOUT; 609 | if (FD_ISSET( fd, &efds )) 610 | m |= POLLERR; 611 | if (m) { 612 | fdparms[n].faked = 0; 613 | fdparms[n].cb( m, fdparms[n].aux ); 614 | if (changed) { 615 | changed = 0; 616 | break; 617 | } 618 | } 619 | } 620 | #endif 621 | } 622 | 623 | void 624 | main_loop( void ) 625 | { 626 | while (npolls) 627 | event_wait(); 628 | } 629 | -------------------------------------------------------------------------------- /src/compat/config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2004 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "isync.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | static int local_home, local_root; 32 | 33 | static char * 34 | my_strndup( const char *s, size_t nchars ) 35 | { 36 | char *r = nfmalloc( sizeof(char) * (nchars + 1) ); 37 | memcpy( r, s, nchars ); 38 | r[nchars] = 0; 39 | return r; 40 | } 41 | 42 | char * 43 | expand_strdup( const char *s ) 44 | { 45 | struct passwd *pw; 46 | const char *p, *q; 47 | char *r; 48 | 49 | if (*s == '~') { 50 | s++; 51 | if (!*s) { 52 | p = 0; 53 | q = Home; 54 | } else if (*s == '/') { 55 | p = s + 1; 56 | q = Home; 57 | } else { 58 | if ((p = strchr( s, '/' ))) { 59 | r = my_strndup( s, (int)(p - s) ); 60 | pw = getpwnam( r ); 61 | free( r ); 62 | p++; 63 | } else 64 | pw = getpwnam( s ); 65 | if (!pw) 66 | return 0; 67 | q = pw->pw_dir; 68 | } 69 | nfasprintf( &r, "%s/%s", q, p ? p : "" ); 70 | return r; 71 | } else if (*s != '/' && xmaildir) { 72 | nfasprintf( &r, "%s/%s", xmaildir, s ); 73 | return r; 74 | } else 75 | return nfstrdup( s ); 76 | } 77 | 78 | static int 79 | is_true( const char *val ) 80 | { 81 | return 82 | !strcasecmp( val, "yes" ) || 83 | !strcasecmp( val, "true" ) || 84 | !strcasecmp( val, "on" ) || 85 | !strcmp( val, "1" ); 86 | } 87 | 88 | void 89 | load_config( const char *path, config_t ***stor ) 90 | { 91 | config_t **sstor, *cfg; 92 | FILE *fp; 93 | char *p, *cmd, *val; 94 | int line = 0; 95 | char buf[1024]; 96 | 97 | if (!(fp = fopen( path, "r" ))) { 98 | if (errno != ENOENT) 99 | sys_error( "Cannot read config file '%s'", path ); 100 | return; 101 | } 102 | if (!Quiet && !Debug && !Verbose) 103 | printf( "Reading configuration file %s\n", path ); 104 | buf[sizeof(buf) - 1] = 0; 105 | cfg = &global; 106 | while (fgets( buf, sizeof(buf) - 1, fp )) { 107 | p = buf; 108 | cmd = next_arg( &p ); 109 | val = next_arg( &p ); 110 | line++; 111 | if (!cmd || *cmd == '#') 112 | continue; 113 | if (!val) { 114 | fprintf( stderr, "%s:%d: parameter missing\n", path, line ); 115 | continue; 116 | } 117 | if (!strcasecmp( "Mailbox", cmd )) { 118 | if (o2o) 119 | break; 120 | cfg = **stor = nfmalloc( sizeof(config_t) ); 121 | *stor = &cfg->next; 122 | memcpy( cfg, &global, sizeof(config_t) ); 123 | if (val[0] == '~' && val[1] == '/') { 124 | val += 2; 125 | local_home = 1; 126 | } else if (!memcmp( val, Home, HomeLen ) && val[HomeLen] == '/') { 127 | val += HomeLen + 1; 128 | local_home = 1; 129 | } else if (val[0] == '/') 130 | local_root = 1; 131 | else 132 | local_home = 1; 133 | /* not expanded at this point */ 134 | cfg->path = nfstrdup( val ); 135 | } else if (!strcasecmp( "OneToOne", cmd )) { 136 | if (boxes) { 137 | forbid: 138 | fprintf( stderr, 139 | "%s:%d: keyword '%s' allowed only in global section\n", 140 | path, line, cmd ); 141 | continue; 142 | } 143 | o2o = is_true( val ); 144 | } else if (!strcasecmp( "Maildir", cmd )) { 145 | if (boxes) 146 | goto forbid; 147 | maildir = nfstrdup( val ); 148 | xmaildir = expand_strdup( val ); 149 | } else if (!strcasecmp( "Folder", cmd )) { 150 | if (boxes) 151 | goto forbid; 152 | folder = nfstrdup( val ); 153 | } else if (!strcasecmp( "Inbox", cmd )) { 154 | if (boxes) 155 | goto forbid; 156 | inbox = nfstrdup( val ); 157 | } else if (!strcasecmp( "Host", cmd )) { 158 | if (!memcmp( "imaps:", val, 6 )) { 159 | val += 6; 160 | cfg->use_imaps = 1; 161 | cfg->port = 993; 162 | cfg->use_sslv2 = 1; 163 | cfg->use_sslv3 = 1; 164 | } 165 | cfg->host = nfstrdup( val ); 166 | } else if (!strcasecmp( "User", cmd )) 167 | cfg->user = nfstrdup( val ); 168 | else if (!strcasecmp( "Pass", cmd )) 169 | cfg->pass = nfstrdup( val ); 170 | else if (!strcasecmp ( "Port", cmd )) 171 | cfg->port = atoi( val ); 172 | else if (!strcasecmp ( "Box", cmd )) 173 | cfg->box = nfstrdup( val ); 174 | else if (!strcasecmp ( "Alias", cmd )) { 175 | if (!boxes) { 176 | fprintf( stderr, 177 | "%s:%d: keyword 'Alias' allowed only in mailbox specification\n", 178 | path, line ); 179 | continue; 180 | } 181 | cfg->alias = nfstrdup( val ); 182 | } else if (!strcasecmp( "MaxSize", cmd )) 183 | cfg->max_size = atol( val ); 184 | else if (!strcasecmp ( "MaxMessages", cmd )) 185 | cfg->max_messages = atol( val ); 186 | else if (!strcasecmp ( "UseNamespace", cmd )) 187 | cfg->use_namespace = is_true( val ); 188 | else if (!strcasecmp ( "CopyDeletedTo", cmd )) 189 | cfg->copy_deleted_to = nfstrdup( val ); 190 | else if (!strcasecmp ( "Tunnel", cmd )) 191 | cfg->tunnel = nfstrdup( val ); 192 | else if (!strcasecmp ( "Expunge", cmd )) 193 | cfg->expunge = is_true( val ); 194 | else if (!strcasecmp( "Delete", cmd )) 195 | cfg->delete = is_true( val ); 196 | else if (!strcasecmp( "CertificateFile", cmd )) 197 | cfg->cert_file = expand_strdup( val ); 198 | else if (!strcasecmp( "RequireSSL", cmd )) 199 | cfg->require_ssl = is_true( val ); 200 | else if (!strcasecmp( "UseSSLv2", cmd )) 201 | cfg->use_sslv2 = is_true( val ); 202 | else if (!strcasecmp( "UseSSLv3", cmd )) 203 | cfg->use_sslv3 = is_true( val ); 204 | else if (!strcasecmp( "UseTLSv1", cmd )) 205 | cfg->use_tlsv1 = is_true( val ); 206 | else if (!strcasecmp( "RequireCRAM", cmd )) 207 | cfg->require_cram = is_true( val ); 208 | else if (buf[0]) 209 | fprintf( stderr, "%s:%d: unknown keyword '%s'\n", path, line, cmd ); 210 | } 211 | fclose( fp ); 212 | if (o2o) { 213 | if (!global.host && !global.tunnel) { 214 | fprintf( stderr, "Neither Host nor Tunnel given to OneToOne. Aborting.\n" ); 215 | exit( 1 ); 216 | } 217 | } else 218 | for (sstor = &boxes; (cfg = *sstor); ) { 219 | if (!cfg->host && !cfg->tunnel) { 220 | fprintf( stderr, "Mailbox '%s' has neither Host nor Tunnel. Skipping.\n", 221 | cfg->alias ? cfg->alias : cfg->path ); 222 | if (&cfg->next == *stor) 223 | *stor = sstor; 224 | *sstor = cfg->next; 225 | continue; 226 | } 227 | sstor = &cfg->next; 228 | } 229 | } 230 | 231 | static const char * 232 | tb( int on ) 233 | { 234 | return on ? "yes" : "no"; 235 | } 236 | 237 | static void 238 | write_imap_server( FILE *fp, config_t *cfg ) 239 | { 240 | config_t *pbox; 241 | char *p, *p2; 242 | int hl, a1, a2, a3, a4; 243 | char buf[128], ubuf[64]; 244 | static int tunnels; 245 | 246 | if (cfg->tunnel) 247 | nfasprintf( (char **)&cfg->old_server_name, "tunnel%d", ++tunnels ); 248 | else if (cfg->host) { 249 | if (sscanf( cfg->host, "%d.%d.%d.%d", &a1, &a2, &a3, &a4 ) == 4) 250 | hl = nfsnprintf( buf, sizeof(buf), "%s", cfg->host ); 251 | else { 252 | /* XXX this does not avoid clashes. add port? */ 253 | p = strrchr( cfg->host, '.' ); 254 | if (!p) 255 | hl = nfsnprintf( buf, sizeof(buf), "%s", cfg->host ); 256 | else { 257 | hl = nfsnprintf( buf, sizeof(buf), "%.*s", p - cfg->host, cfg->host ); 258 | p2 = strrchr( buf, '.' ); 259 | if (p2) 260 | hl = sprintf( buf, "%s", p2 + 1 ); 261 | } 262 | } 263 | if (boxes) /* !o2o */ 264 | for (pbox = boxes; pbox != cfg; pbox = pbox->next) 265 | if (!memcmp( pbox->server_name, buf, hl + 1 )) { 266 | nfasprintf( (char **)&cfg->old_server_name, "%s-%d", buf, ++pbox->old_servers ); 267 | goto gotsrv; 268 | } 269 | cfg->old_server_name = nfstrdup( buf ); 270 | cfg->old_servers = 1; 271 | gotsrv: ; 272 | } else { 273 | fprintf( stderr, "ERROR: Neither host nor tunnel specified for mailbox %s.\n", cfg->path ); 274 | exit( 1 ); 275 | } 276 | 277 | if (cfg->user) 278 | nfsnprintf( ubuf, sizeof(ubuf), "%s@", cfg->user ); 279 | else 280 | ubuf[0] = 0; 281 | if (!cfg->host) 282 | hl = nfsnprintf( buf, sizeof(buf), "%stunnel", ubuf ); 283 | else { 284 | if (cfg->port != (cfg->use_imaps ? 993 : 143)) 285 | hl = nfsnprintf( buf, sizeof(buf), "%s%s_%d", ubuf, cfg->host, cfg->port ); 286 | else 287 | hl = nfsnprintf( buf, sizeof(buf), "%s%s", ubuf, cfg->host ); 288 | } 289 | if (boxes) /* !o2o */ 290 | for (pbox = boxes; pbox != cfg; pbox = pbox->next) 291 | if (!memcmp( pbox->server_name, buf, hl + 1 )) { 292 | nfasprintf( (char **)&cfg->server_name, "%s-%d", buf, ++pbox->servers ); 293 | goto ngotsrv; 294 | } 295 | cfg->server_name = nfstrdup( buf ); 296 | cfg->servers = 1; 297 | ngotsrv: ; 298 | 299 | fprintf( fp, "IMAPAccount %s\n", cfg->server_name ); 300 | if (cfg->tunnel) 301 | fprintf( fp, "Tunnel \"%s\"\n", cfg->tunnel ); 302 | else { 303 | if (cfg->use_imaps) 304 | fprintf( fp, "Host imaps:%s\n", cfg->host ); 305 | else 306 | fprintf( fp, "Host %s\n", cfg->host ); 307 | fprintf( fp, "Port %d\n", cfg->port ); 308 | } 309 | if (cfg->user) 310 | fprintf( fp, "User \"%s\"\n", cfg->user ); 311 | if (cfg->pass) 312 | fprintf( fp, "Pass \"%s\"\n", cfg->pass ); 313 | fprintf( fp, "RequireCRAM %s\nRequireSSL %s\n" 314 | "UseSSLv2 %s\nUseSSLv3 %s\nUseTLSv1 %s\n", 315 | tb(cfg->require_cram), tb(cfg->require_ssl), 316 | tb(cfg->use_sslv2), tb(cfg->use_sslv3), tb(cfg->use_tlsv1) ); 317 | if ((cfg->use_imaps || cfg->use_sslv2 || cfg->use_sslv3 || cfg->use_tlsv1) && 318 | cfg->cert_file) 319 | fprintf( fp, "CertificateFile %s\n", cfg->cert_file ); 320 | fputc( '\n', fp ); 321 | } 322 | 323 | static void 324 | write_imap_store( FILE *fp, config_t *cfg ) 325 | { 326 | if (cfg->stores > 1) 327 | nfasprintf( (char **)&cfg->store_name, "%s-%d", cfg->old_server_name, cfg->stores ); 328 | else 329 | cfg->store_name = cfg->old_server_name; 330 | fprintf( fp, "IMAPStore %s\nAccount %s\n", 331 | cfg->store_name, cfg->server_name ); 332 | if (*folder) 333 | fprintf( fp, "Path \"%s\"\n", folder ); 334 | else 335 | fprintf( fp, "UseNamespace %s\n", tb(cfg->use_namespace) ); 336 | if (inbox) 337 | fprintf( fp, "MapInbox \"%s\"\n", inbox ); 338 | if (cfg->copy_deleted_to) 339 | fprintf( fp, "Trash \"%s\"\n", cfg->copy_deleted_to ); 340 | fputc( '\n', fp ); 341 | } 342 | 343 | static void 344 | write_channel_parm( FILE *fp, config_t *cfg ) 345 | { 346 | if (cfg->max_size) 347 | fprintf( fp, "MaxSize %d\n", cfg->max_size ); 348 | if (cfg->max_messages) 349 | fprintf( fp, "MaxMessages %d\n", cfg->max_messages ); 350 | if (!cfg->delete && !delete) 351 | fputs( "Sync New ReNew Flags\n", fp ); 352 | if (cfg->expunge || expunge) 353 | fputs( "Expunge Both\n", fp ); 354 | fputc( '\n', fp ); 355 | } 356 | 357 | static int 358 | mstrcmp( const char *s1, const char *s2 ) 359 | { 360 | if (s1 == s2) 361 | return 0; 362 | if (!s1 || !s2) 363 | return 1; 364 | return strcmp( s1, s2 ); 365 | } 366 | 367 | void 368 | write_config( int fd ) 369 | { 370 | FILE *fp; 371 | const char *cn, *scn; 372 | config_t *box, *sbox, *pbox; 373 | 374 | if (!(fp = fdopen( fd, "w" ))) { 375 | perror( "fdopen" ); 376 | return; 377 | } 378 | 379 | fprintf( fp, "SyncState *\n\n" ); 380 | if (local_home || o2o) 381 | fprintf( fp, "MaildirStore local\nPath \"%s/\"\nInbox \"%s/INBOX\"\nAltMap %s\n\n", 382 | maildir, maildir, tb( altmap > 0 ) ); 383 | if (local_root) 384 | fprintf( fp, "MaildirStore local_root\nPath /\nAltMap %s\n\n", tb( altmap > 0 ) ); 385 | if (o2o) { 386 | write_imap_server( fp, &global ); 387 | write_imap_store( fp, &global ); 388 | fprintf( fp, "Channel o2o\nMaster :%s:\nSlave :local:\nPattern %%\n", global.store_name ); 389 | write_channel_parm( fp, &global ); 390 | } else { 391 | for (box = boxes; box; box = box->next) { 392 | for (pbox = boxes; pbox != box; pbox = pbox->next) { 393 | if (box->tunnel) { 394 | if (mstrcmp( pbox->tunnel, box->tunnel )) 395 | continue; 396 | } else { 397 | if (mstrcmp( pbox->host, box->host ) || 398 | pbox->use_imaps != box->use_imaps || 399 | pbox->port != box->port) 400 | continue; 401 | } 402 | if (mstrcmp( pbox->user, box->user ) || 403 | mstrcmp( pbox->pass, box->pass )) /* nonsense */ 404 | continue; 405 | if ((box->use_imaps || box->use_sslv2 || 406 | box->use_sslv3 || box->use_tlsv1) && 407 | mstrcmp( pbox->cert_file, box->cert_file )) /* nonsense */ 408 | continue; 409 | if (pbox->use_imaps != box->use_imaps || 410 | pbox->use_sslv2 != box->use_sslv2 || 411 | pbox->use_sslv3 != box->use_sslv3 || 412 | pbox->use_tlsv1 != box->use_tlsv1) 413 | continue; 414 | box->server_name = pbox->server_name; 415 | for (sbox = boxes; sbox != box; sbox = sbox->next) { 416 | if (sbox->server_name != box->server_name || 417 | mstrcmp( sbox->copy_deleted_to, box->copy_deleted_to ) || 418 | (!*folder && sbox->use_namespace != box->use_namespace)) 419 | continue; 420 | box->store_name = sbox->store_name; 421 | goto gotall; 422 | } 423 | box->stores = ++pbox->stores; 424 | goto gotsrv; 425 | } 426 | write_imap_server( fp, box ); 427 | box->stores = 1; 428 | gotsrv: 429 | write_imap_store( fp, box ); 430 | gotall: 431 | if (box->alias) 432 | cn = box->alias; 433 | else { 434 | cn = strrchr( box->path, '/' ); 435 | if (cn) 436 | cn++; 437 | else 438 | cn = box->path; 439 | } 440 | for (sbox = boxes; sbox != box; sbox = sbox->next) { 441 | if (sbox->alias) 442 | scn = sbox->alias; 443 | else { 444 | scn = strrchr( sbox->path, '/' ); 445 | if (scn) 446 | scn++; 447 | else 448 | scn = sbox->path; 449 | } 450 | if (mstrcmp( cn, scn )) 451 | continue; 452 | nfasprintf( (char **)&box->channel_name, "%s-%d", cn, ++sbox->channels ); 453 | goto gotchan; 454 | } 455 | box->channels = 1; 456 | box->channel_name = cn; 457 | gotchan: 458 | if (box->path[0] == '/') 459 | fprintf( fp, "Channel %s\nMaster :%s:\"%s\"\nSlave :local_root:\"%s\"\n", 460 | box->channel_name, box->store_name, box->box, box->path + 1 ); 461 | else 462 | fprintf( fp, "Channel %s\nMaster :%s:\"%s\"\nSlave :local:\"%s\"\n", 463 | box->channel_name, box->store_name, box->box, box->path ); 464 | write_channel_parm( fp, box ); 465 | } 466 | 467 | } 468 | 469 | fclose( fp ); 470 | } 471 | 472 | config_t * 473 | find_box( const char *s ) 474 | { 475 | config_t *p; 476 | char *t; 477 | 478 | if (!memcmp( s, Home, HomeLen ) && s[HomeLen] == '/') 479 | s += HomeLen + 1; 480 | for (p = boxes; p; p = p->next) { 481 | if (!strcmp( s, p->path ) || (p->alias && !strcmp( s, p->alias ))) 482 | return p; 483 | /* check to see if the full pathname was specified on the 484 | * command line. 485 | */ 486 | t = expand_strdup( p->path ); 487 | if (!strcmp( s, t )) { 488 | free( t ); 489 | return p; 490 | } 491 | free( t ); 492 | } 493 | return 0; 494 | } 495 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mbsync - mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2006,2011 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | * As a special exception, mbsync may be linked with the OpenSSL library, 20 | * despite that library's more restrictive license. 21 | */ 22 | 23 | #include "isync.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | driver_t *drivers[N_DRIVERS] = { &maildir_driver, &imap_driver }; 36 | 37 | store_conf_t *stores; 38 | channel_conf_t *channels; 39 | group_conf_t *groups; 40 | int global_ops[2]; 41 | char *global_sync_state; 42 | int FSyncLevel = FSYNC_NORMAL; 43 | 44 | #define ARG_OPTIONAL 0 45 | #define ARG_REQUIRED 1 46 | 47 | static char * 48 | get_arg( conffile_t *cfile, int required, int *comment ) 49 | { 50 | char *ret, *p, *t; 51 | int quoted; 52 | char c; 53 | 54 | p = cfile->rest; 55 | assert( p ); 56 | while ((c = *p) && isspace( (unsigned char) c )) 57 | p++; 58 | if (!c || c == '#') { 59 | if (comment) 60 | *comment = (c == '#'); 61 | if (required) { 62 | error( "%s:%d: parameter missing\n", cfile->file, cfile->line ); 63 | cfile->err = 1; 64 | } 65 | ret = 0; 66 | } else { 67 | for (quoted = 0, ret = t = p; c; c = *p) { 68 | p++; 69 | if (c == '"') 70 | quoted ^= 1; 71 | else if (!quoted && isspace( (unsigned char) c )) 72 | break; 73 | else 74 | *t++ = c; 75 | } 76 | *t = 0; 77 | if (quoted) { 78 | error( "%s:%d: missing closing quote\n", cfile->file, cfile->line ); 79 | cfile->err = 1; 80 | ret = 0; 81 | } 82 | } 83 | cfile->rest = p; 84 | return ret; 85 | } 86 | 87 | int 88 | parse_bool( conffile_t *cfile ) 89 | { 90 | if (!strcasecmp( cfile->val, "yes" ) || 91 | !strcasecmp( cfile->val, "true" ) || 92 | !strcasecmp( cfile->val, "on" ) || 93 | !strcmp( cfile->val, "1" )) 94 | return 1; 95 | if (strcasecmp( cfile->val, "no" ) && 96 | strcasecmp( cfile->val, "false" ) && 97 | strcasecmp( cfile->val, "off" ) && 98 | strcmp( cfile->val, "0" )) { 99 | error( "%s:%d: invalid boolean value '%s'\n", 100 | cfile->file, cfile->line, cfile->val ); 101 | cfile->err = 1; 102 | } 103 | return 0; 104 | } 105 | 106 | int 107 | parse_int( conffile_t *cfile ) 108 | { 109 | char *p; 110 | int ret; 111 | 112 | ret = strtol( cfile->val, &p, 10 ); 113 | if (*p) { 114 | error( "%s:%d: invalid integer value '%s'\n", 115 | cfile->file, cfile->line, cfile->val ); 116 | cfile->err = 1; 117 | return 0; 118 | } 119 | return ret; 120 | } 121 | 122 | int 123 | parse_size( conffile_t *cfile ) 124 | { 125 | char *p; 126 | int ret; 127 | 128 | ret = strtol (cfile->val, &p, 10); 129 | if (*p == 'k' || *p == 'K') 130 | ret *= 1024, p++; 131 | else if (*p == 'm' || *p == 'M') 132 | ret *= 1024 * 1024, p++; 133 | if (*p == 'b' || *p == 'B') 134 | p++; 135 | if (*p) { 136 | fprintf (stderr, "%s:%d: invalid size '%s'\n", 137 | cfile->file, cfile->line, cfile->val); 138 | cfile->err = 1; 139 | return 0; 140 | } 141 | return ret; 142 | } 143 | 144 | static int 145 | getopt_helper( conffile_t *cfile, int *cops, int ops[], char **sync_state ) 146 | { 147 | char *arg; 148 | 149 | if (!strcasecmp( "Sync", cfile->cmd )) { 150 | arg = cfile->val; 151 | do 152 | if (!strcasecmp( "Push", arg )) 153 | *cops |= XOP_PUSH; 154 | else if (!strcasecmp( "Pull", arg )) 155 | *cops |= XOP_PULL; 156 | else if (!strcasecmp( "ReNew", arg )) 157 | *cops |= OP_RENEW; 158 | else if (!strcasecmp( "New", arg )) 159 | *cops |= OP_NEW; 160 | else if (!strcasecmp( "Delete", arg )) 161 | *cops |= OP_DELETE; 162 | else if (!strcasecmp( "Flags", arg )) 163 | *cops |= OP_FLAGS; 164 | else if (!strcasecmp( "PullReNew", arg )) 165 | ops[S] |= OP_RENEW; 166 | else if (!strcasecmp( "PullNew", arg )) 167 | ops[S] |= OP_NEW; 168 | else if (!strcasecmp( "PullDelete", arg )) 169 | ops[S] |= OP_DELETE; 170 | else if (!strcasecmp( "PullFlags", arg )) 171 | ops[S] |= OP_FLAGS; 172 | else if (!strcasecmp( "PushReNew", arg )) 173 | ops[M] |= OP_RENEW; 174 | else if (!strcasecmp( "PushNew", arg )) 175 | ops[M] |= OP_NEW; 176 | else if (!strcasecmp( "PushDelete", arg )) 177 | ops[M] |= OP_DELETE; 178 | else if (!strcasecmp( "PushFlags", arg )) 179 | ops[M] |= OP_FLAGS; 180 | else if (!strcasecmp( "All", arg ) || !strcasecmp( "Full", arg )) 181 | *cops |= XOP_PULL|XOP_PUSH; 182 | else if (strcasecmp( "None", arg ) && strcasecmp( "Noop", arg )) { 183 | error( "%s:%d: invalid Sync arg '%s'\n", 184 | cfile->file, cfile->line, arg ); 185 | cfile->err = 1; 186 | } 187 | while ((arg = get_arg( cfile, ARG_OPTIONAL, 0 ))); 188 | ops[M] |= XOP_HAVE_TYPE; 189 | } else if (!strcasecmp( "Expunge", cfile->cmd )) { 190 | arg = cfile->val; 191 | do 192 | if (!strcasecmp( "Both", arg )) 193 | *cops |= OP_EXPUNGE; 194 | else if (!strcasecmp( "Master", arg )) 195 | ops[M] |= OP_EXPUNGE; 196 | else if (!strcasecmp( "Slave", arg )) 197 | ops[S] |= OP_EXPUNGE; 198 | else if (strcasecmp( "None", arg )) { 199 | error( "%s:%d: invalid Expunge arg '%s'\n", 200 | cfile->file, cfile->line, arg ); 201 | cfile->err = 1; 202 | } 203 | while ((arg = get_arg( cfile, ARG_OPTIONAL, 0 ))); 204 | ops[M] |= XOP_HAVE_EXPUNGE; 205 | } else if (!strcasecmp( "Create", cfile->cmd )) { 206 | arg = cfile->val; 207 | do 208 | if (!strcasecmp( "Both", arg )) 209 | *cops |= OP_CREATE; 210 | else if (!strcasecmp( "Master", arg )) 211 | ops[M] |= OP_CREATE; 212 | else if (!strcasecmp( "Slave", arg )) 213 | ops[S] |= OP_CREATE; 214 | else if (strcasecmp( "None", arg )) { 215 | error( "%s:%d: invalid Create arg '%s'\n", 216 | cfile->file, cfile->line, arg ); 217 | cfile->err = 1; 218 | } 219 | while ((arg = get_arg( cfile, ARG_OPTIONAL, 0 ))); 220 | ops[M] |= XOP_HAVE_CREATE; 221 | } else if (!strcasecmp( "SyncState", cfile->cmd )) 222 | *sync_state = expand_strdup( cfile->val ); 223 | else 224 | return 0; 225 | return 1; 226 | } 227 | 228 | int 229 | getcline( conffile_t *cfile ) 230 | { 231 | int comment; 232 | 233 | while (fgets( cfile->buf, cfile->bufl, cfile->fp )) { 234 | cfile->line++; 235 | cfile->rest = cfile->buf; 236 | if (!(cfile->cmd = get_arg( cfile, ARG_OPTIONAL, &comment ))) { 237 | if (comment) 238 | continue; 239 | return 1; 240 | } 241 | if (!(cfile->val = get_arg( cfile, ARG_REQUIRED, 0 ))) 242 | continue; 243 | return 1; 244 | } 245 | return 0; 246 | } 247 | 248 | /* XXX - this does not detect None conflicts ... */ 249 | int 250 | merge_ops( int cops, int ops[] ) 251 | { 252 | int aops; 253 | 254 | aops = ops[M] | ops[S]; 255 | if (ops[M] & XOP_HAVE_TYPE) { 256 | if (aops & OP_MASK_TYPE) { 257 | if (aops & cops & OP_MASK_TYPE) { 258 | cfl: 259 | error( "Conflicting Sync args specified.\n" ); 260 | return 1; 261 | } 262 | ops[M] |= cops & OP_MASK_TYPE; 263 | ops[S] |= cops & OP_MASK_TYPE; 264 | if (cops & XOP_PULL) { 265 | if (ops[S] & OP_MASK_TYPE) 266 | goto cfl; 267 | ops[S] |= OP_MASK_TYPE; 268 | } 269 | if (cops & XOP_PUSH) { 270 | if (ops[M] & OP_MASK_TYPE) 271 | goto cfl; 272 | ops[M] |= OP_MASK_TYPE; 273 | } 274 | } else if (cops & (OP_MASK_TYPE|XOP_MASK_DIR)) { 275 | if (!(cops & OP_MASK_TYPE)) 276 | cops |= OP_MASK_TYPE; 277 | else if (!(cops & XOP_MASK_DIR)) 278 | cops |= XOP_PULL|XOP_PUSH; 279 | if (cops & XOP_PULL) 280 | ops[S] |= cops & OP_MASK_TYPE; 281 | if (cops & XOP_PUSH) 282 | ops[M] |= cops & OP_MASK_TYPE; 283 | } 284 | } 285 | if (ops[M] & XOP_HAVE_EXPUNGE) { 286 | if (aops & cops & OP_EXPUNGE) { 287 | error( "Conflicting Expunge args specified.\n" ); 288 | return 1; 289 | } 290 | ops[M] |= cops & OP_EXPUNGE; 291 | ops[S] |= cops & OP_EXPUNGE; 292 | } 293 | if (ops[M] & XOP_HAVE_CREATE) { 294 | if (aops & cops & OP_CREATE) { 295 | error( "Conflicting Create args specified.\n" ); 296 | return 1; 297 | } 298 | ops[M] |= cops & OP_CREATE; 299 | ops[S] |= cops & OP_CREATE; 300 | } 301 | return 0; 302 | } 303 | 304 | int 305 | load_config( const char *where, int pseudo ) 306 | { 307 | conffile_t cfile; 308 | store_conf_t *store, **storeapp = &stores; 309 | channel_conf_t *channel, **channelapp = &channels; 310 | group_conf_t *group, **groupapp = &groups; 311 | string_list_t *chanlist, **chanlistapp; 312 | char *arg, *p; 313 | int len, cops, gcops, max_size, ms, i; 314 | char path[_POSIX_PATH_MAX]; 315 | char buf[1024]; 316 | 317 | if (!where) { 318 | nfsnprintf( path, sizeof(path), "%s/." EXE "rc", Home ); 319 | cfile.file = path; 320 | } else 321 | cfile.file = where; 322 | 323 | if (!pseudo) 324 | info( "Reading configuration file %s\n", cfile.file ); 325 | 326 | if (!(cfile.fp = fopen( cfile.file, "r" ))) { 327 | sys_error( "Cannot open config file '%s'", cfile.file ); 328 | return 1; 329 | } 330 | buf[sizeof(buf) - 1] = 0; 331 | cfile.buf = buf; 332 | cfile.bufl = sizeof(buf) - 1; 333 | cfile.line = 0; 334 | cfile.err = 0; 335 | 336 | gcops = 0; 337 | reloop: 338 | while (getcline( &cfile )) { 339 | if (!cfile.cmd) 340 | continue; 341 | for (i = 0; i < N_DRIVERS; i++) 342 | if (drivers[i]->parse_store( &cfile, &store )) { 343 | if (store) { 344 | if (!store->path) 345 | store->path = ""; 346 | *storeapp = store; 347 | storeapp = &store->next; 348 | *storeapp = 0; 349 | } 350 | goto reloop; 351 | } 352 | if (!strcasecmp( "Channel", cfile.cmd )) 353 | { 354 | channel = nfcalloc( sizeof(*channel) ); 355 | channel->name = nfstrdup( cfile.val ); 356 | cops = 0; 357 | max_size = -1; 358 | while (getcline( &cfile ) && cfile.cmd) { 359 | if (!strcasecmp( "MaxSize", cfile.cmd )) 360 | max_size = parse_size( &cfile ); 361 | else if (!strcasecmp( "MaxMessages", cfile.cmd )) 362 | channel->max_messages = parse_int( &cfile ); 363 | else if (!strcasecmp( "Pattern", cfile.cmd ) || 364 | !strcasecmp( "Patterns", cfile.cmd )) 365 | { 366 | arg = cfile.val; 367 | do 368 | add_string_list( &channel->patterns, arg ); 369 | while ((arg = get_arg( &cfile, ARG_OPTIONAL, 0 ))); 370 | } 371 | else if (!strcasecmp( "Master", cfile.cmd )) { 372 | ms = M; 373 | goto linkst; 374 | } else if (!strcasecmp( "Slave", cfile.cmd )) { 375 | ms = S; 376 | linkst: 377 | if (*cfile.val != ':' || !(p = strchr( cfile.val + 1, ':' ))) { 378 | error( "%s:%d: malformed mailbox spec\n", 379 | cfile.file, cfile.line ); 380 | cfile.err = 1; 381 | continue; 382 | } 383 | *p = 0; 384 | for (store = stores; store; store = store->next) 385 | if (!strcmp( store->name, cfile.val + 1 )) { 386 | channel->stores[ms] = store; 387 | goto stpcom; 388 | } 389 | error( "%s:%d: unknown store '%s'\n", 390 | cfile.file, cfile.line, cfile.val + 1 ); 391 | cfile.err = 1; 392 | continue; 393 | stpcom: 394 | if (*++p) 395 | channel->boxes[ms] = nfstrdup( p ); 396 | } else if (!getopt_helper( &cfile, &cops, channel->ops, &channel->sync_state )) { 397 | error( "%s:%d: unknown keyword '%s'\n", cfile.file, cfile.line, cfile.cmd ); 398 | cfile.err = 1; 399 | } 400 | } 401 | if (!channel->stores[M]) { 402 | error( "channel '%s' refers to no master store\n", channel->name ); 403 | cfile.err = 1; 404 | } else if (!channel->stores[S]) { 405 | error( "channel '%s' refers to no slave store\n", channel->name ); 406 | cfile.err = 1; 407 | } else if (merge_ops( cops, channel->ops )) 408 | cfile.err = 1; 409 | else { 410 | if (max_size >= 0) 411 | channel->stores[M]->max_size = channel->stores[S]->max_size = max_size; 412 | *channelapp = channel; 413 | channelapp = &channel->next; 414 | } 415 | } 416 | else if (!strcasecmp( "Group", cfile.cmd )) 417 | { 418 | group = nfmalloc( sizeof(*group) ); 419 | group->name = nfstrdup( cfile.val ); 420 | *groupapp = group; 421 | groupapp = &group->next; 422 | *groupapp = 0; 423 | chanlistapp = &group->channels; 424 | *chanlistapp = 0; 425 | while ((arg = get_arg( &cfile, ARG_OPTIONAL, 0 ))) { 426 | addone: 427 | len = strlen( arg ); 428 | chanlist = nfmalloc( sizeof(*chanlist) + len ); 429 | memcpy( chanlist->string, arg, len + 1 ); 430 | *chanlistapp = chanlist; 431 | chanlistapp = &chanlist->next; 432 | *chanlistapp = 0; 433 | } 434 | while (getcline( &cfile )) { 435 | if (!cfile.cmd) 436 | goto reloop; 437 | if (!strcasecmp( "Channel", cfile.cmd ) || 438 | !strcasecmp( "Channels", cfile.cmd )) 439 | { 440 | arg = cfile.val; 441 | goto addone; 442 | } 443 | else 444 | { 445 | error( "%s:%d: unknown keyword '%s'\n", 446 | cfile.file, cfile.line, cfile.cmd ); 447 | cfile.err = 1; 448 | } 449 | } 450 | break; 451 | } 452 | else if (!strcasecmp( "FSync", cfile.cmd )) 453 | { 454 | arg = cfile.val; 455 | if (!strcasecmp( "None", arg )) 456 | FSyncLevel = FSYNC_NONE; 457 | else if (!strcasecmp( "Normal", arg )) 458 | FSyncLevel = FSYNC_NORMAL; 459 | else if (!strcasecmp( "Thorough", arg )) 460 | FSyncLevel = FSYNC_THOROUGH; 461 | } 462 | else if (!getopt_helper( &cfile, &gcops, global_ops, &global_sync_state )) 463 | { 464 | error( "%s:%d: unknown section keyword '%s'\n", 465 | cfile.file, cfile.line, cfile.cmd ); 466 | cfile.err = 1; 467 | while (getcline( &cfile )) 468 | if (!cfile.cmd) 469 | goto reloop; 470 | break; 471 | } 472 | } 473 | fclose (cfile.fp); 474 | cfile.err |= merge_ops( gcops, global_ops ); 475 | if (!global_sync_state) 476 | global_sync_state = expand_strdup( "~/." EXE "/" ); 477 | if (!cfile.err && pseudo) 478 | unlink( where ); 479 | return cfile.err; 480 | } 481 | 482 | void 483 | parse_generic_store( store_conf_t *store, conffile_t *cfg ) 484 | { 485 | if (!strcasecmp( "Trash", cfg->cmd )) 486 | store->trash = nfstrdup( cfg->val ); 487 | else if (!strcasecmp( "TrashRemoteNew", cfg->cmd )) 488 | store->trash_remote_new = parse_bool( cfg ); 489 | else if (!strcasecmp( "TrashNewOnly", cfg->cmd )) 490 | store->trash_only_new = parse_bool( cfg ); 491 | else if (!strcasecmp( "MaxSize", cfg->cmd )) 492 | store->max_size = parse_size( cfg ); 493 | else if (!strcasecmp( "MapInbox", cfg->cmd )) 494 | store->map_inbox = nfstrdup( cfg->val ); 495 | else if (!strcasecmp( "Flatten", cfg->cmd )) { 496 | int sl = strlen( cfg->val ); 497 | if (sl != 1) { 498 | error( "%s:%d: malformed flattened hierarchy delimiter\n", cfg->file, cfg->line ); 499 | cfg->err = 1; 500 | } else if (cfg->val[0] == '/') { 501 | error( "%s:%d: flattened hierarchy delimiter cannot be the canonical delimiter '/'\n", cfg->file, cfg->line ); 502 | cfg->err = 1; 503 | } else { 504 | store->flat_delim = cfg->val[0]; 505 | } 506 | } else { 507 | error( "%s:%d: unknown keyword '%s'\n", cfg->file, cfg->line, cfg->cmd ); 508 | cfg->err = 1; 509 | } 510 | } 511 | -------------------------------------------------------------------------------- /src/run-tests.pl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/perl -w 2 | # 3 | # Copyright (C) 2006 Oswald Buddenhagen 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | use strict; 20 | use File::Path; 21 | 22 | -d "tmp" or mkdir "tmp"; 23 | chdir "tmp" or die "Cannot enter temp direcory.\n"; 24 | 25 | sub show($$@); 26 | sub test($$); 27 | 28 | ################################################################################ 29 | 30 | # generic syncing tests 31 | my @x01 = ( 32 | [ 8, 33 | 1, 1, "F", 2, 2, "", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "F", 7, 7, "FT", 9, 0, "" ], 34 | [ 8, 35 | 1, 1, "", 2, 2, "F", 3, 3, "F", 4, 4, "", 5, 5, "", 7, 7, "", 8, 8, "", 10, 0, "" ], 36 | [ 8, 0, 0, 37 | 1, 1, "", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "", 6, 6, "", 7, 7, "", 8, 8, "" ], 38 | ); 39 | 40 | #show("01", "01", "", "", ""); 41 | my @X01 = ( 42 | [ "", "", "" ], 43 | [ 10, 44 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "FT", 7, 7, "FT", 9, 9, "", 10, 10, "" ], 45 | [ 10, 46 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 7, 7, "FT", 8, 8, "T", 9, 10, "", 10, 9, "" ], 47 | [ 9, 0, 9, 48 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 0, "", 7, 7, "FT", 0, 8, "", 10, 9, "", 9, 10, "" ], 49 | ); 50 | test(\@x01, \@X01); 51 | 52 | #show("01", "02", "", "", "Expunge Both\n"); 53 | my @X02 = ( 54 | [ "", "", "Expunge Both\n" ], 55 | [ 10, 56 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 9, 9, "", 10, 10, "" ], 57 | [ 10, 58 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 9, 10, "", 10, 9, "" ], 59 | [ 9, 0, 9, 60 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 10, 9, "", 9, 10, "" ], 61 | ); 62 | test(\@x01, \@X02); 63 | 64 | #show("01", "03", "", "", "Expunge Slave\n"); 65 | my @X03 = ( 66 | [ "", "", "Expunge Slave\n" ], 67 | [ 10, 68 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "FT", 7, 7, "FT", 9, 9, "", 10, 10, "" ], 69 | [ 10, 70 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 9, 10, "", 10, 9, "" ], 71 | [ 9, 0, 9, 72 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 0, "T", 6, 0, "", 7, 0, "T", 10, 9, "", 9, 10, "" ], 73 | ); 74 | test(\@x01, \@X03); 75 | 76 | #show("01", "04", "", "", "Sync Pull\n"); 77 | my @X04 = ( 78 | [ "", "", "Sync Pull\n" ], 79 | [ 9, 80 | 1, 1, "F", 2, 2, "", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "F", 7, 7, "FT", 9, 9, "" ], 81 | [ 9, 82 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 7, 7, "FT", 8, 8, "T", 9, 9, "", 10, 0, "" ], 83 | [ 9, 0, 0, 84 | 1, 1, "F", 2, 2, "", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "", 7, 7, "FT", 0, 8, "", 9, 9, "" ], 85 | ); 86 | test(\@x01, \@X04); 87 | 88 | #show("01", "05", "", "", "Sync Flags\n"); 89 | my @X05 = ( 90 | [ "", "", "Sync Flags\n" ], 91 | [ 8, 92 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "F", 7, 7, "FT", 9, 0, "" ], 93 | [ 8, 94 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 7, 7, "FT", 8, 8, "", 10, 0, "" ], 95 | [ 8, 0, 0, 96 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "", 7, 7, "FT", 8, 8, "" ], 97 | ); 98 | test(\@x01, \@X05); 99 | 100 | #show("01", "06", "", "", "Sync Delete\n"); 101 | my @X06 = ( 102 | [ "", "", "Sync Delete\n" ], 103 | [ 8, 104 | 1, 1, "F", 2, 2, "", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "FT", 7, 7, "FT", 9, 0, "" ], 105 | [ 8, 106 | 1, 1, "", 2, 2, "F", 3, 3, "F", 4, 4, "", 5, 5, "", 7, 7, "", 8, 8, "T", 10, 0, "" ], 107 | [ 8, 0, 0, 108 | 1, 1, "", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "", 6, 0, "", 7, 7, "", 0, 8, "" ], 109 | ); 110 | test(\@x01, \@X06); 111 | 112 | #show("01", "07", "", "", "Sync New\n"); 113 | my @X07 = ( 114 | [ "", "", "Sync New\n" ], 115 | [ 10, 116 | 1, 1, "F", 2, 2, "", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "F", 7, 7, "FT", 9, 9, "", 10, 10, "" ], 117 | [ 10, 118 | 1, 1, "", 2, 2, "F", 3, 3, "F", 4, 4, "", 5, 5, "", 7, 7, "", 8, 8, "", 9, 10, "", 10, 9, "" ], 119 | [ 9, 0, 9, 120 | 1, 1, "", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "", 6, 6, "", 7, 7, "", 8, 8, "", 10, 9, "", 9, 10, "" ], 121 | ); 122 | test(\@x01, \@X07); 123 | 124 | #show("01", "08", "", "", "Sync PushFlags PullDelete\n"); 125 | my @X08 = ( 126 | [ "", "", "Sync PushFlags PullDelete\n" ], 127 | [ 8, 128 | 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "F", 7, 7, "FT", 9, 0, "" ], 129 | [ 8, 130 | 1, 1, "", 2, 2, "F", 3, 3, "F", 4, 4, "", 5, 5, "", 7, 7, "", 8, 8, "T", 10, 0, "" ], 131 | [ 8, 0, 0, 132 | 1, 1, "", 2, 2, "F", 3, 3, "F", 4, 4, "", 5, 5, "", 6, 6, "", 7, 7, "", 0, 8, "" ], 133 | ); 134 | test(\@x01, \@X08); 135 | 136 | # size restriction tests 137 | 138 | my @x10 = ( 139 | [ 0, 140 | 1, 0, "", 2, 0, "*" ], 141 | [ 0, 142 | 3, 0, "*" ], 143 | [ 0, 0, 0, 144 | ], 145 | ); 146 | 147 | #show("10", "11", "MaxSize 1k\n", "MaxSize 1k\n", ""); 148 | my @X11 = ( 149 | [ "MaxSize 1k\n", "MaxSize 1k\n", "" ], 150 | [ 2, 151 | 1, 1, "", 2, 2, "*" ], 152 | [ 2, 153 | 3, 1, "*", 1, 2, "" ], 154 | [ 2, 0, 1, 155 | -1, 1, "", 1, 2, "", 2, -1, "" ], 156 | ); 157 | test(\@x10, \@X11); 158 | 159 | my @x20 = @X11[1,2,3]; 160 | 161 | #show("20", "11", "MaxSize 1k\n", "MaxSize 1k\n", ""); # sic! - 11 162 | test(\@x20, \@X11); 163 | 164 | #show("20", "22", "", "MaxSize 1k\n", ""); 165 | my @X22 = ( 166 | [ "", "MaxSize 1k\n", "" ], 167 | [ 3, 168 | 1, 1, "", 2, 2, "*", 3, 3, "*" ], 169 | [ 2, 170 | 3, 1, "*", 1, 2, "" ], 171 | [ 2, 0, 1, 172 | 3, 1, "", 1, 2, "", 2, -1, "" ], 173 | ); 174 | test(\@x20, \@X22); 175 | 176 | # expiration tests 177 | 178 | my @x30 = ( 179 | [ 0, 180 | 1, 0, "F", 2, 0, "", 3, 0, "", 4, 0, "", 5, 0, "" ], 181 | [ 0, 182 | ], 183 | [ 0, 0, 0, 184 | ], 185 | ); 186 | 187 | #show("30", "31", "", "", "MaxMessages 3\n"); 188 | my @X31 = ( 189 | [ "", "", "MaxMessages 3\n" ], 190 | [ 5, 191 | 1, 1, "F", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "" ], 192 | [ 5, 193 | 1, 1, "F", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "" ], 194 | [ 5, 0, 0, 195 | 1, 1, "F", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "" ], 196 | ); 197 | test(\@x30, \@X31); 198 | 199 | my @x40 = @X31[1,2,3]; 200 | 201 | #show("40", "41", "", "", "MaxMessages 3\nExpunge Both\n"); 202 | my @X41 = ( 203 | [ "", "", "MaxMessages 3\nExpunge Both\n" ], 204 | [ 5, 205 | 1, 1, "F", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "" ], 206 | [ 5, 207 | 1, 1, "F", 3, 3, "", 4, 4, "", 5, 5, "" ], 208 | [ 5, 2, 0, 209 | 1, 1, "F", 3, 3, "", 4, 4, "", 5, 5, "" ], 210 | ); 211 | test(\@x40, \@X41); 212 | 213 | my @x50 = ( 214 | [ 5, 215 | 1, 1, "F", 2, 2, "F", 3, 3, "", 4, 4, "", 5, 5, "" ], 216 | [ 5, 217 | 1, 1, " ", 2, 2, "T", 3, 3, "", 4, 4, "", 5, 5, "" ], 218 | [ 5, 2, 0, 219 | 1, 1, "F", 2, 2, "X", 3, 3, "", 4, 4, "", 5, 5, "" ], 220 | ); 221 | 222 | #show("50", "51", "", "", "MaxMessages 3\nExpunge Both\n"); 223 | my @X51 = ( 224 | [ "", "", "MaxMessages 3\nExpunge Both\n" ], 225 | [ 5, 226 | 1, 1, "", 2, 2, "F", 3, 3, "", 4, 4, "", 5, 5, "" ], 227 | [ 5, 228 | 2, 2, "F", 3, 3, "", 4, 4, "", 5, 5, "" ], 229 | [ 5, 2, 0, 230 | 2, 2, "F", 3, 3, "", 4, 4, "", 5, 5, "" ], 231 | ); 232 | test(\@x50, \@X51); 233 | 234 | 235 | ################################################################################ 236 | 237 | chdir ".."; 238 | rmdir "tmp"; 239 | print "OK.\n"; 240 | exit 0; 241 | 242 | 243 | sub qm($) 244 | { 245 | shift; 246 | s/\\/\\\\/g; 247 | s/\"/\\"/g; 248 | s/\"/\\"/g; 249 | s/\n/\\n/g; 250 | return $_; 251 | } 252 | 253 | # $global, $master, $slave 254 | sub writecfg($$$) 255 | { 256 | open(FILE, ">", ".mbsyncrc") or 257 | die "Cannot open .mbsyncrc.\n"; 258 | print FILE 259 | "FSync None 260 | 261 | MaildirStore master 262 | Path ./ 263 | Inbox ./master 264 | ".shift()." 265 | MaildirStore slave 266 | Path ./ 267 | Inbox ./slave 268 | ".shift()." 269 | Channel test 270 | Master :master: 271 | Slave :slave: 272 | SyncState * 273 | ".shift(); 274 | close FILE; 275 | } 276 | 277 | sub killcfg() 278 | { 279 | unlink ".mbsyncrc"; 280 | } 281 | 282 | # $options 283 | sub runsync($) 284 | { 285 | # open FILE, "valgrind -q --log-fd=3 ../mbsync ".shift()." -c .mbsyncrc test 3>&2 2>&1 |"; 286 | open FILE, "../mbsync -D -Z ".shift()." -c .mbsyncrc test 2>&1 |"; 287 | my @out = ; 288 | close FILE or push(@out, $! ? "*** error closing mbsync: $!\n" : "*** mbsync exited with signal ".($?&127).", code ".($?>>8)."\n"); 289 | return $?, @out; 290 | } 291 | 292 | 293 | # $path 294 | sub readbox($) 295 | { 296 | my $bn = shift; 297 | 298 | (-d $bn) or 299 | die "No mailbox '$bn'.\n"; 300 | (-d $bn."/tmp" and -d $bn."/new" and -d $bn."/cur") or 301 | die "Invalid mailbox '$bn'.\n"; 302 | open(FILE, "<", $bn."/.uidvalidity") or die "Cannot read UID validity of mailbox '$bn'.\n"; 303 | my $dummy = ; 304 | chomp(my $mu = ); 305 | close FILE; 306 | my %ms = (); 307 | for my $d ("cur", "new") { 308 | opendir(DIR, $bn."/".$d) or next; 309 | for my $f (grep(!/^\.\.?$/, readdir(DIR))) { 310 | my ($uid, $flg, $num); 311 | if ($f =~ /^\d+\.\d+_\d+\.[-[:alnum:]]+,U=(\d+):2,(.*)$/) { 312 | ($uid, $flg) = ($1, $2); 313 | } elsif ($f =~ /^\d+\.\d+_(\d+)\.[-[:alnum:]]+:2,(.*)$/) { 314 | ($uid, $flg) = (0, $2); 315 | } else { 316 | print STDERR "unrecognided file name '$f' in '$bn'.\n"; 317 | exit 1; 318 | } 319 | open(FILE, "<", $bn."/".$d."/".$f) or die "Cannot read message '$f' in '$bn'.\n"; 320 | my $sz = 0; 321 | while () { 322 | /^Subject: (\d+)$/ && ($num = $1); 323 | $sz += length($_); 324 | } 325 | close FILE; 326 | if (!defined($num)) { 327 | print STDERR "message '$f' in '$bn' has no identifier.\n"; 328 | exit 1; 329 | } 330 | @{ $ms{$num} } = ($uid, $flg.($sz>1000?"*":"")); 331 | } 332 | } 333 | return ($mu, %ms); 334 | } 335 | 336 | # $boxname 337 | sub showbox($) 338 | { 339 | my ($bn) = @_; 340 | 341 | my ($mu, %ms) = readbox($bn); 342 | print " [ $mu,\n "; 343 | my $frst = 1; 344 | for my $num (sort {my ($ca, $cb) = ($ms{$a}[0], $ms{$b}[0]); ($ca?$ca:$a+1000) <=> ($cb?$cb:$b+1000)} keys %ms) { 345 | if ($frst) { 346 | $frst = 0; 347 | } else { 348 | print ", "; 349 | } 350 | print "$num, $ms{$num}[0], \"$ms{$num}[1]\""; 351 | } 352 | print " ],\n"; 353 | } 354 | 355 | # $filename 356 | sub showstate($) 357 | { 358 | my ($fn) = @_; 359 | 360 | if (!open(FILE, "<", $fn)) { 361 | print STDERR " Cannot read sync state $fn: $!\n"; 362 | return; 363 | } 364 | $_ = ; 365 | if (!defined $_) { 366 | print STDERR " Missing sync state header.\n"; 367 | close FILE; 368 | return; 369 | } 370 | if (!/^1:(\d+):0 1:(\d+):(\d+):0\n$/) { 371 | print STDERR " Malformed sync state header '$_'.\n"; 372 | close FILE; 373 | return; 374 | } 375 | print " [ $1, $2, $3,\n "; 376 | my $frst = 1; 377 | for () { 378 | if ($frst) { 379 | $frst = 0; 380 | } else { 381 | print ", "; 382 | } 383 | if (!/^(-?\d+) (-?\d+) (.*)\n$/) { 384 | print "??, ??, \"??\""; 385 | } else { 386 | print "$1, $2, \"$3\""; 387 | } 388 | } 389 | print " ],\n"; 390 | close FILE; 391 | } 392 | 393 | # $filename 394 | sub showchan($) 395 | { 396 | my ($fn) = @_; 397 | 398 | showbox("master"); 399 | showbox("slave"); 400 | showstate($fn); 401 | } 402 | 403 | sub show($$@) 404 | { 405 | my ($sx, $tx, @sfx) = @_; 406 | my @sp; 407 | eval "\@sp = \@x$sx"; 408 | mkchan($sp[0], $sp[1], @{ $sp[2] }); 409 | print "my \@x$sx = (\n"; 410 | showchan("slave/.mbsyncstate"); 411 | print ");\n"; 412 | &writecfg(@sfx); 413 | runsync(""); 414 | killcfg(); 415 | print "my \@X$tx = (\n"; 416 | print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ],\n"; 417 | showchan("slave/.mbsyncstate"); 418 | print ");\n"; 419 | print "test(\\\@x$sx, \\\@X$tx);\n\n"; 420 | rmtree "slave"; 421 | rmtree "master"; 422 | } 423 | 424 | # $boxname, $maxuid, @msgs 425 | sub mkbox($$@) 426 | { 427 | my ($bn, $mu, @ms) = @_; 428 | 429 | rmtree($bn); 430 | (mkdir($bn) and mkdir($bn."/tmp") and mkdir($bn."/new") and mkdir($bn."/cur")) or 431 | die "Cannot create mailbox $bn.\n"; 432 | open(FILE, ">", $bn."/.uidvalidity") or die "Cannot create UID validity for mailbox $bn.\n"; 433 | print FILE "1\n$mu\n"; 434 | close FILE; 435 | while (@ms) { 436 | my ($num, $uid, $flg) = (shift @ms, shift @ms, shift @ms); 437 | if ($uid) { 438 | $uid = ",U=".$uid; 439 | } else { 440 | $uid = ""; 441 | } 442 | my $big = $flg =~ s/\*//; 443 | open(FILE, ">", $bn."/cur/0.1_".$num.".local".$uid.":2,".$flg) or 444 | die "Cannot create message $num in mailbox $bn.\n"; 445 | print FILE "From: foo\nTo: bar\nDate: Thu, 1 Jan 1970 00:00:00 +0000\nSubject: $num\n\n".(("A"x50)."\n")x($big*30); 446 | close FILE; 447 | } 448 | } 449 | 450 | # \@master, \@slave, @syncstate 451 | sub mkchan($$@) 452 | { 453 | my ($m, $s, @t) = @_; 454 | &mkbox("master", @{ $m }); 455 | &mkbox("slave", @{ $s }); 456 | open(FILE, ">", "slave/.mbsyncstate") or 457 | die "Cannot create sync state.\n"; 458 | print FILE "1:".shift(@t)." 1:".shift(@t).":".shift(@t)."\n"; 459 | while (@t) { 460 | print FILE shift(@t)." ".shift(@t)." ".shift(@t)."\n"; 461 | } 462 | close FILE; 463 | } 464 | 465 | # $config, $boxname, $maxuid, @msgs 466 | sub ckbox($$$@) 467 | { 468 | my ($bn, $MU, @MS) = @_; 469 | 470 | my ($mu, %ms) = readbox($bn); 471 | if ($mu != $MU) { 472 | print STDERR "MAXUID mismatch for '$bn'.\n"; 473 | return 1; 474 | } 475 | while (@MS) { 476 | my ($num, $uid, $flg) = (shift @MS, shift @MS, shift @MS); 477 | if (!defined $ms{$num}) { 478 | print STDERR "No message $bn:$num.\n"; 479 | return 1; 480 | } 481 | if ($ms{$num}[0] ne $uid) { 482 | print STDERR "UID mismatch for $bn:$num.\n"; 483 | return 1; 484 | } 485 | if ($ms{$num}[1] ne $flg) { 486 | print STDERR "Flag mismatch for $bn:$num.\n"; 487 | return 1; 488 | } 489 | delete $ms{$num}; 490 | } 491 | if (%ms) { 492 | print STDERR "Excess messages in '$bn': ".join(", ", sort({$a <=> $b } keys(%ms))).".\n"; 493 | return 1; 494 | } 495 | return 0; 496 | } 497 | 498 | # $filename, @syncstate 499 | sub ckstate($@) 500 | { 501 | my ($fn, @T) = @_; 502 | open(FILE, "<", $fn) or die "Cannot read sync state $fn.\n"; 503 | my $l = ; 504 | chomp(my @ls = ); 505 | close FILE; 506 | if (!defined $l) { 507 | print STDERR "Sync state header missing.\n"; 508 | return 1; 509 | } 510 | chomp($l); 511 | my $xl = "1:".shift(@T).":0 1:".shift(@T).":".shift(@T).":0"; 512 | if ($l ne $xl) { 513 | print STDERR "Sync state header mismatch: '$l' instead of '$xl'.\n"; 514 | return 1; 515 | } else { 516 | for $l (@ls) { 517 | $xl = shift(@T)." ".shift(@T)." ".shift(@T); 518 | if ($l ne $xl) { 519 | print STDERR "Sync state entry mismatch: '$l' instead of '$xl'.\n"; 520 | return 1; 521 | } 522 | } 523 | } 524 | return 0; 525 | } 526 | 527 | # \@master, \@slave, @syncstate 528 | sub ckchan($$@) 529 | { 530 | my ($M, $S, @T) = @_; 531 | my $rslt = ckstate("slave/.mbsyncstate.new", @T); 532 | $rslt |= &ckbox("master", @{ $M }); 533 | $rslt |= &ckbox("slave", @{ $S }); 534 | return $rslt; 535 | } 536 | 537 | sub printbox($$@) 538 | { 539 | my ($bn, $mu, @ms) = @_; 540 | 541 | print " [ $mu,\n "; 542 | my $frst = 1; 543 | while (@ms) { 544 | if ($frst) { 545 | $frst = 0; 546 | } else { 547 | print ", "; 548 | } 549 | print shift(@ms).", ".shift(@ms).", \"".shift(@ms)."\""; 550 | } 551 | print " ],\n"; 552 | } 553 | 554 | # @syncstate 555 | sub printstate(@) 556 | { 557 | my (@t) = @_; 558 | 559 | print " [ ".shift(@t).", ".shift(@t).", ".shift(@t).",\n "; 560 | my $frst = 1; 561 | while (@t) { 562 | if ($frst) { 563 | $frst = 0; 564 | } else { 565 | print ", "; 566 | } 567 | print shift(@t).", ".shift(@t).", \"".shift(@t)."\""; 568 | } 569 | print " ],\n"; 570 | close FILE; 571 | } 572 | 573 | # \@master, \@slave, @syncstate 574 | sub printchan($$@) 575 | { 576 | my ($m, $s, @t) = @_; 577 | 578 | &printbox("master", @{ $m }); 579 | &printbox("slave", @{ $s }); 580 | printstate(@t); 581 | } 582 | 583 | sub test($$) 584 | { 585 | my ($sx, $tx) = @_; 586 | 587 | mkchan($$sx[0], $$sx[1], @{ $$sx[2] }); 588 | &writecfg(@{ $$tx[0] }); 589 | my ($xc, @ret) = runsync("-J"); 590 | if ($xc) { 591 | print "Input:\n"; 592 | printchan($$sx[0], $$sx[1], @{ $$sx[2] }); 593 | print "Options:\n"; 594 | print " [ ".join(", ", map('"'.qm($_).'"', @{ $$tx[0] }))." ]\n"; 595 | print "Expected result:\n"; 596 | printchan($$tx[1], $$tx[2], @{ $$tx[3] }); 597 | print "Debug output:\n"; 598 | print @ret; 599 | exit 1; 600 | } 601 | if (ckchan($$tx[1], $$tx[2], @{ $$tx[3] })) { 602 | print "Input:\n"; 603 | printchan($$sx[0], $$sx[1], @{ $$sx[2] }); 604 | print "Options:\n"; 605 | print " [ ".join(", ", map('"'.qm($_).'"', @{ $$tx[0] }))." ]\n"; 606 | print "Expected result:\n"; 607 | printchan($$tx[1], $$tx[2], @{ $$tx[3] }); 608 | print "Actual result:\n"; 609 | showchan("slave/.mbsyncstate.new"); 610 | print "Debug output:\n"; 611 | print @ret; 612 | exit 1; 613 | } 614 | open(FILE, "<", "slave/.mbsyncstate.journal") or 615 | die "Cannot read journal.\n"; 616 | my @nj = ; 617 | close FILE; 618 | @ret = runsync(""); 619 | killcfg(); 620 | if (ckstate("slave/.mbsyncstate", @{ $$tx[3] })) { 621 | print "Options:\n"; 622 | print " [ ".join(", ", map('"'.qm($_).'"', @{ $$tx[0] }))." ]\n"; 623 | print "Old State:\n"; 624 | printstate(@{ $$sx[2] }); 625 | print "Journal:\n".join("", @nj)."\n"; 626 | print "Expected New State:\n"; 627 | printstate(@{ $$tx[3] }); 628 | print "New State:\n"; 629 | showstate("slave/.mbsyncstate"); 630 | print "Debug output:\n"; 631 | print @ret; 632 | exit 1; 633 | } 634 | rmtree "slave"; 635 | rmtree "master"; 636 | } 637 | -------------------------------------------------------------------------------- /src/isync.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mbsync - mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2006,2010-2012 Oswald Buddenhagen 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | * As a special exception, mbsync may be linked with the OpenSSL library, 20 | * despite that library's more restrictive license. 21 | */ 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #define as(ar) (sizeof(ar)/sizeof(ar[0])) 30 | 31 | #define __stringify(x) #x 32 | #define stringify(x) __stringify(x) 33 | 34 | #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) 35 | # define ATTR_UNUSED __attribute__((unused)) 36 | # define ATTR_NORETURN __attribute__((noreturn)) 37 | # define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var))) 38 | #else 39 | # define ATTR_UNUSED 40 | # define ATTR_NORETURN 41 | # define ATTR_PRINTFLIKE(fmt,var) 42 | #endif 43 | 44 | #ifdef __GNUC__ 45 | # define INLINE __inline__ 46 | #else 47 | # define INLINE 48 | #endif 49 | 50 | #define EXE "mbsync" 51 | 52 | typedef struct ssl_st SSL; 53 | typedef struct ssl_ctx_st SSL_CTX; 54 | typedef struct x509_store_st X509_STORE; 55 | 56 | typedef struct server_conf { 57 | char *tunnel; 58 | char *host; 59 | int port; 60 | #ifdef HAVE_LIBSSL 61 | char *cert_file; 62 | unsigned use_imaps:1; 63 | unsigned use_sslv2:1; 64 | unsigned use_sslv3:1; 65 | unsigned use_tlsv1:1; 66 | 67 | /* these are actually variables and are leaked at the end */ 68 | SSL_CTX *SSLContext; 69 | X509_STORE *cert_store; 70 | #endif 71 | } server_conf_t; 72 | 73 | typedef struct buff_chunk { 74 | struct buff_chunk *next; 75 | char *data; 76 | int len; 77 | char buf[1]; 78 | } buff_chunk_t; 79 | 80 | typedef struct { 81 | /* connection */ 82 | int fd; 83 | int state; 84 | const server_conf_t *conf; /* needed during connect */ 85 | char *name; 86 | #ifdef HAVE_LIBSSL 87 | SSL *ssl; 88 | #endif 89 | 90 | void (*bad_callback)( void *aux ); /* async fail while sending or listening */ 91 | void (*read_callback)( void *aux ); /* data available for reading */ 92 | int (*write_callback)( void *aux ); /* all *queued* data was sent */ 93 | union { 94 | void (*connect)( int ok, void *aux ); 95 | void (*starttls)( int ok, void *aux ); 96 | } callbacks; 97 | void *callback_aux; 98 | 99 | /* writing */ 100 | buff_chunk_t *write_buf, **write_buf_append; /* buffer head & tail */ 101 | int write_offset; /* offset into buffer head */ 102 | 103 | /* reading */ 104 | int offset; /* start of filled bytes in buffer */ 105 | int bytes; /* number of filled bytes in buffer */ 106 | int scanoff; /* offset to continue scanning for newline at, relative to 'offset' */ 107 | char buf[100000]; 108 | } conn_t; 109 | 110 | typedef struct { 111 | const char *file; 112 | FILE *fp; 113 | char *buf; 114 | int bufl; 115 | int line; 116 | int err; 117 | char *cmd, *val, *rest; 118 | } conffile_t; 119 | 120 | #define OP_NEW (1<<0) 121 | #define OP_RENEW (1<<1) 122 | #define OP_DELETE (1<<2) 123 | #define OP_FLAGS (1<<3) 124 | #define OP_MASK_TYPE (OP_NEW|OP_RENEW|OP_DELETE|OP_FLAGS) /* asserted in the target ops */ 125 | #define OP_EXPUNGE (1<<4) 126 | #define OP_CREATE (1<<5) 127 | #define XOP_PUSH (1<<6) 128 | #define XOP_PULL (1<<7) 129 | #define XOP_MASK_DIR (XOP_PUSH|XOP_PULL) 130 | #define XOP_HAVE_TYPE (1<<8) 131 | #define XOP_HAVE_EXPUNGE (1<<9) 132 | #define XOP_HAVE_CREATE (1<<10) 133 | 134 | typedef struct driver driver_t; 135 | 136 | typedef struct store_conf { 137 | struct store_conf *next; 138 | char *name; 139 | driver_t *driver; 140 | const char *path; /* should this be here? its interpretation is driver-specific */ 141 | const char *map_inbox; 142 | const char *trash; 143 | unsigned max_size; /* off_t is overkill */ 144 | unsigned trash_remote_new:1, trash_only_new:1; 145 | char flat_delim; 146 | } store_conf_t; 147 | 148 | typedef struct string_list { 149 | struct string_list *next; 150 | char string[1]; 151 | } string_list_t; 152 | 153 | #define M 0 /* master */ 154 | #define S 1 /* slave */ 155 | 156 | typedef struct channel_conf { 157 | struct channel_conf *next; 158 | const char *name; 159 | store_conf_t *stores[2]; 160 | const char *boxes[2]; 161 | char *sync_state; 162 | string_list_t *patterns; 163 | int ops[2]; 164 | unsigned max_messages; /* for slave only */ 165 | } channel_conf_t; 166 | 167 | typedef struct group_conf { 168 | struct group_conf *next; 169 | const char *name; 170 | string_list_t *channels; 171 | } group_conf_t; 172 | 173 | /* For message->flags */ 174 | /* Keep the mailbox driver flag definitions in sync! */ 175 | /* The order is according to alphabetical maildir flag sort */ 176 | #define F_DRAFT (1<<0) /* Draft */ 177 | #define F_FLAGGED (1<<1) /* Flagged */ 178 | #define F_ANSWERED (1<<2) /* Replied */ 179 | #define F_SEEN (1<<3) /* Seen */ 180 | #define F_DELETED (1<<4) /* Trashed */ 181 | #define NUM_FLAGS 5 182 | 183 | /* For message->status */ 184 | #define M_RECENT (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */ 185 | #define M_DEAD (1<<1) /* expunged */ 186 | #define M_FLAGS (1<<2) /* flags fetched */ 187 | 188 | #define TUIDL 12 189 | 190 | typedef struct message { 191 | struct message *next; 192 | struct sync_rec *srec; 193 | /* string_list_t *keywords; */ 194 | size_t size; /* zero implies "not fetched" */ 195 | int uid; 196 | unsigned char flags, status; 197 | char tuid[TUIDL]; 198 | } message_t; 199 | 200 | /* For opts, both in store and driver_t->select() */ 201 | #define OPEN_OLD (1<<0) 202 | #define OPEN_NEW (1<<1) 203 | #define OPEN_FLAGS (1<<2) 204 | #define OPEN_SIZE (1<<3) 205 | #define OPEN_EXPUNGE (1<<5) 206 | #define OPEN_SETFLAGS (1<<6) 207 | #define OPEN_APPEND (1<<7) 208 | #define OPEN_FIND (1<<8) 209 | 210 | typedef struct store { 211 | struct store *next; 212 | store_conf_t *conf; /* foreign */ 213 | string_list_t *boxes; /* _list results - own */ 214 | unsigned listed:1; /* was _list already run? */ 215 | 216 | void (*bad_callback)( void *aux ); 217 | void *bad_callback_aux; 218 | 219 | /* currently open mailbox */ 220 | const char *orig_name; /* foreign! maybe preset? */ 221 | char *name; /* foreign! maybe preset? */ 222 | char *path; /* own */ 223 | message_t *msgs; /* own */ 224 | int uidvalidity; 225 | int uidnext; /* from SELECT responses */ 226 | unsigned opts; /* maybe preset? */ 227 | /* note that the following do _not_ reflect stats from msgs, but mailbox totals */ 228 | int count; /* # of messages */ 229 | int recent; /* # of recent messages - don't trust this beyond the initial read */ 230 | } store_t; 231 | 232 | /* When the callback is invoked (at most once per store), the store is fubar; 233 | * call the driver's cancel_store() to dispose of it. */ 234 | static INLINE void 235 | set_bad_callback( store_t *ctx, void (*cb)( void *aux ), void *aux ) 236 | { 237 | ctx->bad_callback = cb; 238 | ctx->bad_callback_aux = aux; 239 | } 240 | 241 | typedef struct { 242 | char *data; 243 | int len; 244 | unsigned char flags; 245 | } msg_data_t; 246 | 247 | #define DRV_OK 0 248 | /* Message went missing, or mailbox is full, etc. */ 249 | #define DRV_MSG_BAD 1 250 | /* Something is wrong with the current mailbox - probably it is somehow inaccessible. */ 251 | #define DRV_BOX_BAD 2 252 | /* The command has been cancel()ed or cancel_store()d. */ 253 | #define DRV_CANCELED 3 254 | 255 | /* All memory belongs to the driver's user, unless stated otherwise. */ 256 | 257 | /* 258 | This flag says that the driver CAN store messages with CRLFs, 259 | not that it must. The lack of it OTOH implies that it CANNOT, 260 | and as CRLF is the canonical format, we convert. 261 | */ 262 | #define DRV_CRLF 1 263 | 264 | #define LIST_PATH 1 265 | #define LIST_INBOX 2 266 | 267 | struct driver { 268 | int flags; 269 | 270 | /* Parse configuration. */ 271 | int (*parse_store)( conffile_t *cfg, store_conf_t **storep ); 272 | 273 | /* Close remaining server connections. All stores must be disowned first. */ 274 | void (*cleanup)( void ); 275 | 276 | /* Open a store with the given configuration. This may recycle existing 277 | * server connections. Upon failure, a null store is passed to the callback. */ 278 | void (*open_store)( store_conf_t *conf, 279 | void (*cb)( store_t *ctx, void *aux ), void *aux ); 280 | 281 | /* Mark the store as available for recycling. Server connection may be kept alive. */ 282 | void (*disown_store)( store_t *ctx ); 283 | 284 | /* Try to recycle a store with the given configuration. */ 285 | store_t *(*own_store)( store_conf_t *conf ); 286 | 287 | /* Discard the store after a bad_callback. The server connections will be closed. 288 | * Pending commands will have their callbacks synchronously invoked with DRV_CANCELED. */ 289 | void (*cancel_store)( store_t *ctx ); 290 | 291 | /* List the mailboxes in this store. Flags are ORed LIST_* values. */ 292 | void (*list)( store_t *ctx, int flags, 293 | void (*cb)( int sts, void *aux ), void *aux ); 294 | 295 | /* Invoked before select(), this informs the driver which operations (OP_*) 296 | * will be performed on the mailbox. The driver may extend the set by implicitly 297 | * needed or available operations. */ 298 | void (*prepare_opts)( store_t *ctx, int opts ); 299 | 300 | /* Open the mailbox ctx->name. Optionally create missing boxes. 301 | * As a side effect, this should resolve ctx->path if applicable. */ 302 | void (*select)( store_t *ctx, int create, 303 | void (*cb)( int sts, void *aux ), void *aux ); 304 | 305 | /* Load the message attributes needed to perform the requested operations. 306 | * Consider only messages with UIDs between minuid and maxuid (inclusive) 307 | * and those named in the excs array (smaller than minuid). 308 | * The driver takes ownership of the excs array. Messages below newuid do not need 309 | * to have the TUID populated even if OPEN_FIND is set. */ 310 | void (*load)( store_t *ctx, int minuid, int maxuid, int newuid, int *excs, int nexcs, 311 | void (*cb)( int sts, void *aux ), void *aux ); 312 | 313 | /* Fetch the contents and flags of the given message from the current mailbox. */ 314 | void (*fetch_msg)( store_t *ctx, message_t *msg, msg_data_t *data, 315 | void (*cb)( int sts, void *aux ), void *aux ); 316 | 317 | /* Store the given message to either the current mailbox or the trash folder. 318 | * If the new copy's UID can be immediately determined, return it, otherwise -1. */ 319 | void (*store_msg)( store_t *ctx, msg_data_t *data, int to_trash, 320 | void (*cb)( int sts, int uid, void *aux ), void *aux ); 321 | 322 | /* Index the messages which have newly appeared in the mailbox, including their 323 | * temporary UID headers. This is needed if store_msg() does not guarantee returning 324 | * a UID; otherwise the driver needs to implement only the OPEN_FIND flag. */ 325 | void (*find_new_msgs)( store_t *ctx, 326 | void (*cb)( int sts, void *aux ), void *aux ); 327 | 328 | /* Add/remove the named flags to/from the given message. The message may be either 329 | * a pre-fetched one (in which case the in-memory representation is updated), 330 | * or it may be identifed by UID only. The operation may be delayed until commit() 331 | * is called. */ 332 | void (*set_flags)( store_t *ctx, message_t *msg, int uid, int add, int del, /* msg can be null, therefore uid as a fallback */ 333 | void (*cb)( int sts, void *aux ), void *aux ); 334 | 335 | /* Move the given message from the current mailbox to the trash folder. 336 | * This may expunge the original message immediately, but it needn't to. */ 337 | void (*trash_msg)( store_t *ctx, message_t *msg, /* This may expunge the original message immediately, but it needn't to */ 338 | void (*cb)( int sts, void *aux ), void *aux ); 339 | 340 | /* Expunge deleted messages from the current mailbox and close it. 341 | * There is no need to explicitly close a mailbox if no expunge is needed. */ 342 | void (*close)( store_t *ctx, /* IMAP-style: expunge inclusive */ 343 | void (*cb)( int sts, void *aux ), void *aux ); 344 | 345 | /* Cancel queued commands which are not in flight yet; they will have their 346 | * callbacks invoked with DRV_CANCELED. Afterwards, wait for the completion of 347 | * the in-flight commands. If the store is canceled before this command completes, 348 | * the callback will *not* be invoked. */ 349 | void (*cancel)( store_t *ctx, 350 | void (*cb)( void *aux ), void *aux ); 351 | 352 | /* Commit any pending set_flags() commands. */ 353 | void (*commit)( store_t *ctx ); 354 | }; 355 | 356 | 357 | /* main.c */ 358 | 359 | extern int Pid; 360 | extern char Hostname[256]; 361 | extern const char *Home; 362 | 363 | 364 | /* socket.c */ 365 | 366 | /* call this before doing anything with the socket */ 367 | static INLINE void socket_init( conn_t *conn, 368 | const server_conf_t *conf, 369 | void (*bad_callback)( void *aux ), 370 | void (*read_callback)( void *aux ), 371 | int (*write_callback)( void *aux ), 372 | void *aux ) 373 | { 374 | conn->conf = conf; 375 | conn->bad_callback = bad_callback; 376 | conn->read_callback = read_callback; 377 | conn->write_callback = write_callback; 378 | conn->callback_aux = aux; 379 | conn->fd = -1; 380 | conn->name = 0; 381 | conn->write_buf_append = &conn->write_buf; 382 | } 383 | void socket_connect( conn_t *conn, void (*cb)( int ok, void *aux ) ); 384 | void socket_start_tls(conn_t *conn, void (*cb)( int ok, void *aux ) ); 385 | void socket_close( conn_t *sock ); 386 | int socket_read( conn_t *sock, char *buf, int len ); /* never waits */ 387 | char *socket_read_line( conn_t *sock ); /* don't free return value; never waits */ 388 | typedef enum { KeepOwn = 0, GiveOwn } ownership_t; 389 | int socket_write( conn_t *sock, char *buf, int len, ownership_t takeOwn ); 390 | 391 | void cram( const char *challenge, const char *user, const char *pass, 392 | char **_final, int *_finallen ); 393 | 394 | 395 | /* util.c */ 396 | 397 | #define DEBUG 1 398 | #define VERBOSE 2 399 | #define XVERBOSE 4 400 | #define QUIET 8 401 | #define VERYQUIET 16 402 | #define KEEPJOURNAL 32 403 | #define ZERODELAY 64 404 | 405 | extern int DFlags; 406 | 407 | void ATTR_PRINTFLIKE(1, 2) debug( const char *, ... ); 408 | void ATTR_PRINTFLIKE(1, 2) debugn( const char *, ... ); 409 | void ATTR_PRINTFLIKE(1, 2) info( const char *, ... ); 410 | void ATTR_PRINTFLIKE(1, 2) infon( const char *, ... ); 411 | void ATTR_PRINTFLIKE(1, 2) warn( const char *, ... ); 412 | void ATTR_PRINTFLIKE(1, 2) error( const char *, ... ); 413 | void ATTR_PRINTFLIKE(1, 2) sys_error( const char *, ... ); 414 | void flushn( void ); 415 | 416 | void add_string_list( string_list_t **list, const char *str ); 417 | void free_string_list( string_list_t *list ); 418 | 419 | void free_generic_messages( message_t * ); 420 | 421 | #ifndef HAVE_MEMRCHR 422 | void *memrchr( const void *s, int c, size_t n ); 423 | #endif 424 | 425 | void *nfmalloc( size_t sz ); 426 | void *nfcalloc( size_t sz ); 427 | void *nfrealloc( void *mem, size_t sz ); 428 | char *nfstrdup( const char *str ); 429 | int nfvasprintf( char **str, const char *fmt, va_list va ); 430 | int ATTR_PRINTFLIKE(2, 3) nfasprintf( char **str, const char *fmt, ... ); 431 | int ATTR_PRINTFLIKE(3, 4) nfsnprintf( char *buf, int blen, const char *fmt, ... ); 432 | void ATTR_NORETURN oob( void ); 433 | 434 | char *expand_strdup( const char *s ); 435 | 436 | int map_name( char *arg, char in, char out ); 437 | 438 | void sort_ints( int *arr, int len ); 439 | 440 | void arc4_init( void ); 441 | unsigned char arc4_getbyte( void ); 442 | 443 | int bucketsForSize( int size ); 444 | 445 | #ifdef HAVE_SYS_POLL_H 446 | # include 447 | #else 448 | # define POLLIN 1 449 | # define POLLOUT 4 450 | # define POLLERR 8 451 | #endif 452 | 453 | void add_fd( int fd, void (*cb)( int events, void *aux ), void *aux ); 454 | void conf_fd( int fd, int and_events, int or_events ); 455 | void fake_fd( int fd, int events ); 456 | void del_fd( int fd ); 457 | void main_loop( void ); 458 | 459 | /* sync.c */ 460 | 461 | extern const char *str_ms[2], *str_hl[2]; 462 | 463 | #define SYNC_OK 0 /* assumed to be 0 */ 464 | #define SYNC_FAIL 1 465 | #define SYNC_FAIL_ALL 2 466 | #define SYNC_BAD(ms) (4<<(ms)) 467 | #define SYNC_NOGOOD 16 /* internal */ 468 | #define SYNC_CANCELED 32 /* internal */ 469 | 470 | /* All passed pointers must stay alive until cb is called. */ 471 | void sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan, 472 | void (*cb)( int sts, void *aux ), void *aux ); 473 | 474 | /* config.c */ 475 | 476 | #define N_DRIVERS 2 477 | extern driver_t *drivers[N_DRIVERS]; 478 | 479 | extern channel_conf_t *channels; 480 | extern group_conf_t *groups; 481 | extern int global_ops[2]; 482 | extern char *global_sync_state; 483 | 484 | #define FSYNC_NONE 0 485 | #define FSYNC_NORMAL 1 486 | #define FSYNC_THOROUGH 2 487 | 488 | extern int FSyncLevel; 489 | 490 | int parse_bool( conffile_t *cfile ); 491 | int parse_int( conffile_t *cfile ); 492 | int parse_size( conffile_t *cfile ); 493 | int getcline( conffile_t *cfile ); 494 | int merge_ops( int cops, int ops[] ); 495 | int load_config( const char *filename, int pseudo ); 496 | void parse_generic_store( store_conf_t *store, conffile_t *cfg ); 497 | 498 | /* drv_*.c */ 499 | extern driver_t maildir_driver, imap_driver; 500 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /src/socket.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mbsync - mailbox synchronizer 3 | * Copyright (C) 2000-2002 Michael R. Elkins 4 | * Copyright (C) 2002-2006,2008,2010,2011 Oswald Buddenhagen 5 | * Copyright (C) 2004 Theodore Y. Ts'o 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | * 20 | * As a special exception, mbsync may be linked with the OpenSSL library, 21 | * despite that library's more restrictive license. 22 | */ 23 | 24 | /* This must come before isync.h to avoid our #define S messing up 25 | * blowfish.h on MacOS X. */ 26 | #include 27 | #ifdef HAVE_LIBSSL 28 | # include 29 | # include 30 | # include 31 | #endif 32 | 33 | #include "isync.h" 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | enum { 50 | SCK_CONNECTING, 51 | #ifdef HAVE_LIBSSL 52 | SCK_STARTTLS, 53 | #endif 54 | SCK_READY 55 | }; 56 | 57 | static void 58 | socket_fail( conn_t *conn ) 59 | { 60 | conn->bad_callback( conn->callback_aux ); 61 | } 62 | 63 | #ifdef HAVE_LIBSSL 64 | static int 65 | ssl_return( const char *func, conn_t *conn, int ret ) 66 | { 67 | int err; 68 | 69 | switch ((err = SSL_get_error( conn->ssl, ret ))) { 70 | case SSL_ERROR_NONE: 71 | return ret; 72 | case SSL_ERROR_WANT_WRITE: 73 | conf_fd( conn->fd, POLLIN, POLLOUT ); 74 | /* fallthrough */ 75 | case SSL_ERROR_WANT_READ: 76 | return 0; 77 | case SSL_ERROR_SYSCALL: 78 | case SSL_ERROR_SSL: 79 | if (!(err = ERR_get_error())) { 80 | if (ret == 0) 81 | error( "Socket error: secure %s %s: unexpected EOF\n", func, conn->name ); 82 | else 83 | sys_error( "Socket error: secure %s %s", func, conn->name ); 84 | } else { 85 | error( "Socket error: secure %s %s: %s\n", func, conn->name, ERR_error_string( err, 0 ) ); 86 | } 87 | break; 88 | default: 89 | error( "Socket error: secure %s %s: unhandled SSL error %d\n", func, conn->name, err ); 90 | break; 91 | } 92 | if (conn->state == SCK_STARTTLS) 93 | conn->callbacks.starttls( 0, conn->callback_aux ); 94 | else 95 | socket_fail( conn ); 96 | return -1; 97 | } 98 | 99 | /* Some of this code is inspired by / lifted from mutt. */ 100 | 101 | static int 102 | compare_certificates( X509 *cert, X509 *peercert, 103 | unsigned char *peermd, unsigned peermdlen ) 104 | { 105 | unsigned char md[EVP_MAX_MD_SIZE]; 106 | unsigned mdlen; 107 | 108 | /* Avoid CPU-intensive digest calculation if the certificates are 109 | * not even remotely equal. */ 110 | if (X509_subject_name_cmp( cert, peercert ) || 111 | X509_issuer_name_cmp( cert, peercert )) 112 | return -1; 113 | 114 | if (!X509_digest( cert, EVP_sha1(), md, &mdlen ) || 115 | peermdlen != mdlen || memcmp( peermd, md, mdlen )) 116 | return -1; 117 | 118 | return 0; 119 | } 120 | 121 | #if OPENSSL_VERSION_NUMBER >= 0x00904000L 122 | #define READ_X509_KEY(fp, key) PEM_read_X509( fp, key, 0, 0 ) 123 | #else 124 | #define READ_X509_KEY(fp, key) PEM_read_X509( fp, key, 0 ) 125 | #endif 126 | 127 | /* this gets called when a certificate is to be verified */ 128 | static int 129 | verify_cert( const server_conf_t *conf, conn_t *sock ) 130 | { 131 | server_conf_t *mconf = (server_conf_t *)conf; 132 | SSL *ssl = sock->ssl; 133 | X509 *cert, *lcert; 134 | BIO *bio; 135 | FILE *fp; 136 | int err; 137 | unsigned n, i; 138 | X509_STORE_CTX xsc; 139 | char buf[256]; 140 | unsigned char md[EVP_MAX_MD_SIZE]; 141 | 142 | cert = SSL_get_peer_certificate( ssl ); 143 | if (!cert) { 144 | error( "Error, no server certificate\n" ); 145 | return -1; 146 | } 147 | 148 | while (conf->cert_file) { /* while() instead of if() so break works */ 149 | if (X509_cmp_current_time( X509_get_notBefore( cert )) >= 0) { 150 | error( "Server certificate is not yet valid\n" ); 151 | break; 152 | } 153 | if (X509_cmp_current_time( X509_get_notAfter( cert )) <= 0) { 154 | error( "Server certificate has expired\n" ); 155 | break; 156 | } 157 | if (!X509_digest( cert, EVP_sha1(), md, &n )) { 158 | error( "*** Unable to calculate digest\n" ); 159 | break; 160 | } 161 | if (!(fp = fopen( conf->cert_file, "rt" ))) { 162 | sys_error( "Unable to load CertificateFile '%s'", conf->cert_file ); 163 | return -1; 164 | } 165 | err = -1; 166 | for (lcert = 0; READ_X509_KEY( fp, &lcert ); ) 167 | if (!(err = compare_certificates( lcert, cert, md, n ))) 168 | break; 169 | X509_free( lcert ); 170 | fclose( fp ); 171 | if (!err) 172 | return 0; 173 | break; 174 | } 175 | 176 | if (!mconf->cert_store) { 177 | if (!(mconf->cert_store = X509_STORE_new())) { 178 | error( "Error creating certificate store\n" ); 179 | return -1; 180 | } 181 | if (!X509_STORE_set_default_paths( mconf->cert_store )) 182 | warn( "Error while loading default certificate files: %s\n", 183 | ERR_error_string( ERR_get_error(), 0 ) ); 184 | if (!conf->cert_file) { 185 | info( "Note: CertificateFile not defined\n" ); 186 | } else if (!X509_STORE_load_locations( mconf->cert_store, conf->cert_file, 0 )) { 187 | error( "Error while loading certificate file '%s': %s\n", 188 | conf->cert_file, ERR_error_string( ERR_get_error(), 0 ) ); 189 | return -1; 190 | } 191 | } 192 | 193 | X509_STORE_CTX_init( &xsc, mconf->cert_store, cert, 0 ); 194 | err = X509_verify_cert( &xsc ) > 0 ? 0 : X509_STORE_CTX_get_error( &xsc ); 195 | X509_STORE_CTX_cleanup( &xsc ); 196 | if (!err) 197 | return 0; 198 | error( "Error, cannot verify certificate: %s (%d)\n", 199 | X509_verify_cert_error_string( err ), err ); 200 | 201 | X509_NAME_oneline( X509_get_subject_name( cert ), buf, sizeof(buf) ); 202 | info( "\nSubject: %s\n", buf ); 203 | X509_NAME_oneline( X509_get_issuer_name( cert ), buf, sizeof(buf) ); 204 | info( "Issuer: %s\n", buf ); 205 | bio = BIO_new( BIO_s_mem() ); 206 | ASN1_TIME_print( bio, X509_get_notBefore( cert ) ); 207 | memset( buf, 0, sizeof(buf) ); 208 | BIO_read( bio, buf, sizeof(buf) - 1 ); 209 | info( "Valid from: %s\n", buf ); 210 | ASN1_TIME_print( bio, X509_get_notAfter( cert ) ); 211 | memset( buf, 0, sizeof(buf) ); 212 | BIO_read( bio, buf, sizeof(buf) - 1 ); 213 | BIO_free( bio ); 214 | info( " to: %s\n", buf ); 215 | if (!X509_digest( cert, EVP_md5(), md, &n )) { 216 | error( "*** Unable to calculate fingerprint\n" ); 217 | } else { 218 | info( "Fingerprint: " ); 219 | for (i = 0; i < n; i += 2) 220 | info( "%02X%02X ", md[i], md[i + 1] ); 221 | info( "\n" ); 222 | } 223 | 224 | fputs( "\nAccept certificate? [y/N]: ", stderr ); 225 | if (fgets( buf, sizeof(buf), stdin ) && (buf[0] == 'y' || buf[0] == 'Y')) 226 | return 0; 227 | return -1; 228 | } 229 | 230 | static int 231 | init_ssl_ctx( const server_conf_t *conf ) 232 | { 233 | server_conf_t *mconf = (server_conf_t *)conf; 234 | const SSL_METHOD *method; 235 | int options = 0; 236 | 237 | if (conf->use_tlsv1 && !conf->use_sslv2 && !conf->use_sslv3) 238 | method = TLSv1_client_method(); 239 | else 240 | method = SSLv23_client_method(); 241 | mconf->SSLContext = SSL_CTX_new( method ); 242 | 243 | if (!conf->use_sslv2) 244 | options |= SSL_OP_NO_SSLv2; 245 | if (!conf->use_sslv3) 246 | options |= SSL_OP_NO_SSLv3; 247 | if (!conf->use_tlsv1) 248 | options |= SSL_OP_NO_TLSv1; 249 | 250 | SSL_CTX_set_options( mconf->SSLContext, options ); 251 | 252 | /* we check the result of the verification after SSL_connect() */ 253 | SSL_CTX_set_verify( mconf->SSLContext, SSL_VERIFY_NONE, 0 ); 254 | return 0; 255 | } 256 | 257 | static void start_tls_p2( conn_t * ); 258 | static void start_tls_p3( conn_t *, int ); 259 | 260 | void 261 | socket_start_tls( conn_t *conn, void (*cb)( int ok, void *aux ) ) 262 | { 263 | static int ssl_inited; 264 | 265 | conn->callbacks.starttls = cb; 266 | 267 | if (!ssl_inited) { 268 | SSL_library_init(); 269 | SSL_load_error_strings(); 270 | ssl_inited = 1; 271 | } 272 | 273 | if (!conn->conf->SSLContext && init_ssl_ctx( conn->conf )) { 274 | start_tls_p3( conn, 0 ); 275 | return; 276 | } 277 | 278 | conn->ssl = SSL_new( ((server_conf_t *)conn->conf)->SSLContext ); 279 | SSL_set_fd( conn->ssl, conn->fd ); 280 | SSL_set_mode( conn->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER ); 281 | conn->state = SCK_STARTTLS; 282 | start_tls_p2( conn ); 283 | } 284 | 285 | static void 286 | start_tls_p2( conn_t *conn ) 287 | { 288 | switch (ssl_return( "connect to", conn, SSL_connect( conn->ssl ) )) { 289 | case -1: 290 | start_tls_p3( conn, 0 ); 291 | break; 292 | case 0: 293 | break; 294 | default: 295 | /* verify the server certificate */ 296 | if (verify_cert( conn->conf, conn )) { 297 | start_tls_p3( conn, 0 ); 298 | } else { 299 | info( "Connection is now encrypted\n" ); 300 | start_tls_p3( conn, 1 ); 301 | } 302 | break; 303 | } 304 | } 305 | 306 | static void start_tls_p3( conn_t *conn, int ok ) 307 | { 308 | conn->state = SCK_READY; 309 | conn->callbacks.starttls( ok, conn->callback_aux ); 310 | } 311 | 312 | #endif /* HAVE_LIBSSL */ 313 | 314 | static void socket_fd_cb( int, void * ); 315 | 316 | static void socket_connected2( conn_t * ); 317 | static void socket_connect_bail( conn_t * ); 318 | 319 | static void 320 | socket_close_internal( conn_t *sock ) 321 | { 322 | del_fd( sock->fd ); 323 | close( sock->fd ); 324 | sock->fd = -1; 325 | } 326 | 327 | void 328 | socket_connect( conn_t *sock, void (*cb)( int ok, void *aux ) ) 329 | { 330 | const server_conf_t *conf = sock->conf; 331 | struct hostent *he; 332 | struct sockaddr_in addr; 333 | int s, a[2]; 334 | 335 | sock->callbacks.connect = cb; 336 | 337 | /* open connection to IMAP server */ 338 | if (conf->tunnel) { 339 | nfasprintf( &sock->name, "tunnel '%s'", conf->tunnel ); 340 | infon( "Starting %s... ", sock->name ); 341 | 342 | if (socketpair( PF_UNIX, SOCK_STREAM, 0, a )) { 343 | perror( "socketpair" ); 344 | exit( 1 ); 345 | } 346 | 347 | if (fork() == 0) { 348 | if (dup2( a[0], 0 ) == -1 || dup2( a[0], 1 ) == -1) 349 | _exit( 127 ); 350 | close( a[0] ); 351 | close( a[1] ); 352 | execl( "/bin/sh", "sh", "-c", conf->tunnel, (char *)0 ); 353 | _exit( 127 ); 354 | } 355 | 356 | close( a[0] ); 357 | sock->fd = a[1]; 358 | 359 | fcntl( a[1], F_SETFL, O_NONBLOCK ); 360 | add_fd( a[1], socket_fd_cb, sock ); 361 | 362 | } else { 363 | memset( &addr, 0, sizeof(addr) ); 364 | addr.sin_port = conf->port ? htons( conf->port ) : 365 | #ifdef HAVE_LIBSSL 366 | conf->use_imaps ? htons( 993 ) : 367 | #endif 368 | htons( 143 ); 369 | addr.sin_family = AF_INET; 370 | 371 | infon( "Resolving %s... ", conf->host ); 372 | he = gethostbyname( conf->host ); 373 | if (!he) { 374 | error( "IMAP error: Cannot resolve server '%s'\n", conf->host ); 375 | goto bail; 376 | } 377 | info( "\vok\n" ); 378 | 379 | addr.sin_addr.s_addr = *((int *)he->h_addr_list[0]); 380 | 381 | s = socket( PF_INET, SOCK_STREAM, 0 ); 382 | if (s < 0) { 383 | perror( "socket" ); 384 | exit( 1 ); 385 | } 386 | sock->fd = s; 387 | fcntl( s, F_SETFL, O_NONBLOCK ); 388 | add_fd( s, socket_fd_cb, sock ); 389 | 390 | nfasprintf( &sock->name, "%s (%s:%hu)", 391 | conf->host, inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) ); 392 | infon( "Connecting to %s... ", sock->name ); 393 | if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) { 394 | if (errno != EINPROGRESS) { 395 | sys_error( "Cannot connect to %s", sock->name ); 396 | socket_close_internal( sock ); 397 | goto bail; 398 | } 399 | conf_fd( s, 0, POLLOUT ); 400 | sock->state = SCK_CONNECTING; 401 | info( "\v\n" ); 402 | return; 403 | } 404 | 405 | } 406 | info( "\vok\n" ); 407 | socket_connected2( sock ); 408 | return; 409 | 410 | bail: 411 | socket_connect_bail( sock ); 412 | } 413 | 414 | static void 415 | socket_connected( conn_t *conn ) 416 | { 417 | int soerr; 418 | socklen_t selen = sizeof(soerr); 419 | 420 | if (getsockopt( conn->fd, SOL_SOCKET, SO_ERROR, &soerr, &selen )) { 421 | perror( "getsockopt" ); 422 | exit( 1 ); 423 | } 424 | if (soerr) { 425 | errno = soerr; 426 | sys_error( "Cannot connect to %s", conn->name ); 427 | socket_close_internal( conn ); 428 | socket_connect_bail( conn ); 429 | return; 430 | } 431 | socket_connected2( conn ); 432 | } 433 | 434 | static void 435 | socket_connected2( conn_t *conn ) 436 | { 437 | conf_fd( conn->fd, 0, POLLIN ); 438 | conn->state = SCK_READY; 439 | conn->callbacks.connect( 1, conn->callback_aux ); 440 | } 441 | 442 | static void 443 | socket_connect_bail( conn_t *conn ) 444 | { 445 | free( conn->name ); 446 | conn->name = 0; 447 | conn->callbacks.connect( 0, conn->callback_aux ); 448 | } 449 | 450 | static void dispose_chunk( conn_t *conn ); 451 | 452 | void 453 | socket_close( conn_t *sock ) 454 | { 455 | if (sock->fd >= 0) 456 | socket_close_internal( sock ); 457 | free( sock->name ); 458 | sock->name = 0; 459 | #ifdef HAVE_LIBSSL 460 | if (sock->ssl) { 461 | SSL_free( sock->ssl ); 462 | sock->ssl = 0; 463 | } 464 | #endif 465 | while (sock->write_buf) 466 | dispose_chunk( sock ); 467 | } 468 | 469 | static void 470 | socket_fill( conn_t *sock ) 471 | { 472 | char *buf; 473 | int n = sock->offset + sock->bytes; 474 | int len = sizeof(sock->buf) - n; 475 | if (!len) { 476 | error( "Socket error: receive buffer full. Probably protocol error.\n" ); 477 | socket_fail( sock ); 478 | return; 479 | } 480 | assert( sock->fd >= 0 ); 481 | buf = sock->buf + n; 482 | #ifdef HAVE_LIBSSL 483 | if (sock->ssl) { 484 | if ((n = ssl_return( "read from", sock, SSL_read( sock->ssl, buf, len ) )) <= 0) 485 | return; 486 | if (n == len && SSL_pending( sock->ssl )) 487 | fake_fd( sock->fd, POLLIN ); 488 | } else 489 | #endif 490 | { 491 | if ((n = read( sock->fd, buf, len )) < 0) { 492 | sys_error( "Socket error: read from %s", sock->name ); 493 | socket_fail( sock ); 494 | return; 495 | } else if (!n) { 496 | error( "Socket error: read from %s: unexpected EOF\n", sock->name ); 497 | socket_fail( sock ); 498 | return; 499 | } 500 | } 501 | sock->bytes += n; 502 | sock->read_callback( sock->callback_aux ); 503 | } 504 | 505 | int 506 | socket_read( conn_t *conn, char *buf, int len ) 507 | { 508 | int n = conn->bytes; 509 | if (n > len) 510 | n = len; 511 | memcpy( buf, conn->buf + conn->offset, n ); 512 | if (!(conn->bytes -= n)) 513 | conn->offset = 0; 514 | else 515 | conn->offset += n; 516 | return n; 517 | } 518 | 519 | char * 520 | socket_read_line( conn_t *b ) 521 | { 522 | char *p, *s; 523 | int n; 524 | 525 | s = b->buf + b->offset; 526 | p = memchr( s + b->scanoff, '\n', b->bytes - b->scanoff ); 527 | if (!p) { 528 | b->scanoff = b->bytes; 529 | if (b->offset + b->bytes == sizeof(b->buf)) { 530 | memmove( b->buf, b->buf + b->offset, b->bytes ); 531 | b->offset = 0; 532 | } 533 | return 0; 534 | } 535 | n = p + 1 - s; 536 | b->offset += n; 537 | b->bytes -= n; 538 | b->scanoff = 0; 539 | if (p != s && p[-1] == '\r') 540 | p--; 541 | *p = 0; 542 | if (DFlags & VERBOSE) { 543 | puts( s ); 544 | fflush( stdout ); 545 | } 546 | return s; 547 | } 548 | 549 | static int 550 | do_write( conn_t *sock, char *buf, int len ) 551 | { 552 | int n; 553 | 554 | assert( sock->fd >= 0 ); 555 | #ifdef HAVE_LIBSSL 556 | if (sock->ssl) 557 | return ssl_return( "write to", sock, SSL_write( sock->ssl, buf, len ) ); 558 | #endif 559 | n = write( sock->fd, buf, len ); 560 | if (n < 0) { 561 | if (errno != EAGAIN && errno != EWOULDBLOCK) { 562 | sys_error( "Socket error: write to %s", sock->name ); 563 | socket_fail( sock ); 564 | } else { 565 | n = 0; 566 | conf_fd( sock->fd, POLLIN, POLLOUT ); 567 | } 568 | } else if (n != len) { 569 | conf_fd( sock->fd, POLLIN, POLLOUT ); 570 | } 571 | return n; 572 | } 573 | 574 | static void 575 | dispose_chunk( conn_t *conn ) 576 | { 577 | buff_chunk_t *bc = conn->write_buf; 578 | if (!(conn->write_buf = bc->next)) 579 | conn->write_buf_append = &conn->write_buf; 580 | if (bc->data != bc->buf) 581 | free( bc->data ); 582 | free( bc ); 583 | } 584 | 585 | static int 586 | do_queued_write( conn_t *conn ) 587 | { 588 | buff_chunk_t *bc; 589 | 590 | if (!conn->write_buf) 591 | return 0; 592 | 593 | while ((bc = conn->write_buf)) { 594 | int n, len = bc->len - conn->write_offset; 595 | if ((n = do_write( conn, bc->data + conn->write_offset, len )) < 0) 596 | return -1; 597 | if (n != len) { 598 | conn->write_offset += n; 599 | return 0; 600 | } 601 | conn->write_offset = 0; 602 | dispose_chunk( conn ); 603 | } 604 | #ifdef HAVE_LIBSSL 605 | if (conn->ssl && SSL_pending( conn->ssl )) 606 | fake_fd( conn->fd, POLLIN ); 607 | #endif 608 | return conn->write_callback( conn->callback_aux ); 609 | } 610 | 611 | static void 612 | do_append( conn_t *conn, char *buf, int len, ownership_t takeOwn ) 613 | { 614 | buff_chunk_t *bc; 615 | 616 | if (takeOwn == GiveOwn) { 617 | bc = nfmalloc( offsetof(buff_chunk_t, buf) ); 618 | bc->data = buf; 619 | } else { 620 | bc = nfmalloc( offsetof(buff_chunk_t, buf) + len ); 621 | bc->data = bc->buf; 622 | memcpy( bc->data, buf, len ); 623 | } 624 | bc->len = len; 625 | bc->next = 0; 626 | *conn->write_buf_append = bc; 627 | conn->write_buf_append = &bc->next; 628 | } 629 | 630 | int 631 | socket_write( conn_t *conn, char *buf, int len, ownership_t takeOwn ) 632 | { 633 | if (conn->write_buf) { 634 | do_append( conn, buf, len, takeOwn ); 635 | return len; 636 | } else { 637 | int n = do_write( conn, buf, len ); 638 | if (n != len && n >= 0) { 639 | conn->write_offset = n; 640 | do_append( conn, buf, len, takeOwn ); 641 | } else if (takeOwn) { 642 | free( buf ); 643 | } 644 | return n; 645 | } 646 | } 647 | 648 | static void 649 | socket_fd_cb( int events, void *aux ) 650 | { 651 | conn_t *conn = (conn_t *)aux; 652 | 653 | if (events & POLLERR) { 654 | error( "Unidentified socket error from %s.\n", conn->name ); 655 | socket_fail( conn ); 656 | return; 657 | } 658 | 659 | if (conn->state == SCK_CONNECTING) { 660 | socket_connected( conn ); 661 | return; 662 | } 663 | 664 | if (events & POLLOUT) 665 | conf_fd( conn->fd, POLLIN, 0 ); 666 | 667 | #ifdef HAVE_LIBSSL 668 | if (conn->state == SCK_STARTTLS) { 669 | start_tls_p2( conn ); 670 | return; 671 | } 672 | if (conn->ssl) { 673 | if (do_queued_write( conn ) < 0) 674 | return; 675 | socket_fill( conn ); 676 | return; 677 | } 678 | #endif 679 | 680 | if ((events & POLLOUT) && do_queued_write( conn ) < 0) 681 | return; 682 | if (events & POLLIN) 683 | socket_fill( conn ); 684 | } 685 | 686 | #ifdef HAVE_LIBSSL 687 | /* this isn't strictly socket code, but let's have all OpenSSL use in one file. */ 688 | 689 | #define ENCODED_SIZE(n) (4*((n+2)/3)) 690 | 691 | static char 692 | hexchar( unsigned int b ) 693 | { 694 | if (b < 10) 695 | return '0' + b; 696 | return 'a' + (b - 10); 697 | } 698 | 699 | void 700 | cram( const char *challenge, const char *user, const char *pass, char **_final, int *_finallen ) 701 | { 702 | unsigned char *response, *final; 703 | unsigned hashlen; 704 | int i, clen, rlen, blen, flen, olen; 705 | unsigned char hash[16]; 706 | char buf[256], hex[33]; 707 | HMAC_CTX hmac; 708 | 709 | HMAC_Init( &hmac, (unsigned char *)pass, strlen( pass ), EVP_md5() ); 710 | 711 | clen = strlen( challenge ); 712 | /* response will always be smaller than challenge because we are decoding. */ 713 | response = nfcalloc( 1 + clen ); 714 | rlen = EVP_DecodeBlock( response, (unsigned char *)challenge, clen ); 715 | HMAC_Update( &hmac, response, rlen ); 716 | free( response ); 717 | 718 | hashlen = sizeof(hash); 719 | HMAC_Final( &hmac, hash, &hashlen ); 720 | assert( hashlen == sizeof(hash) ); 721 | 722 | hex[32] = 0; 723 | for (i = 0; i < 16; i++) { 724 | hex[2 * i] = hexchar( (hash[i] >> 4) & 0xf ); 725 | hex[2 * i + 1] = hexchar( hash[i] & 0xf ); 726 | } 727 | 728 | blen = nfsnprintf( buf, sizeof(buf), "%s %s", user, hex ); 729 | 730 | flen = ENCODED_SIZE( blen ); 731 | final = nfmalloc( flen + 1 ); 732 | final[flen] = 0; 733 | olen = EVP_EncodeBlock( (unsigned char *)final, (unsigned char *)buf, blen ); 734 | assert( olen == flen ); 735 | 736 | *_final = (char *)final; 737 | *_finallen = flen; 738 | } 739 | #endif 740 | --------------------------------------------------------------------------------