commit f47d0d7c110b0ec61bd2e6ed1d0cdee753d1edd5
Author: Michael Elkins <me@mutt.org>
Date: Wed, 20 Dec 2000 21:41:21 +0000
initial import
Diffstat:
A | AUTHORS | | | 1 | + |
A | ChangeLog | | | 0 | |
A | Makefile.am | | | 4 | ++++ |
A | NEWS | | | 3 | +++ |
A | README | | | 28 | ++++++++++++++++++++++++++++ |
A | TODO | | | 1 | + |
A | configure.in | | | 9 | +++++++++ |
A | imap.c | | | 531 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | isync.1 | | | 184 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | isync.h | | | 111 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | isyncrc.sample | | | 28 | ++++++++++++++++++++++++++++ |
A | maildir.c | | | 208 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | main.c | | | 398 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | sync.c | | | 134 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
14 files changed, 1640 insertions(+), 0 deletions(-)
diff --git a/AUTHORS b/AUTHORS
@@ -0,0 +1 @@
+Michael R. Elkins <me@mutt.org>
diff --git a/ChangeLog b/ChangeLog
diff --git a/Makefile.am b/Makefile.am
@@ -0,0 +1,4 @@
+bin_PROGRAMS=isync
+isync_SOURCES=main.c imap.c sync.c maildir.c
+man_MANS=isync.1
+EXTRA_DIST=sample.isyncrc $(man_MANS)
diff --git a/NEWS b/NEWS
@@ -0,0 +1,3 @@
+[0.1]
+
+Initial release.
diff --git a/README b/README
@@ -0,0 +1,28 @@
+ _
+(_)___ _ _ _ __ ___
+| / __| | | | '_ \ / __|
+| \__ \ |_| | | | | (__
+|_|___/\__, |_| |_|\___|
+ |___/
+isync - IMAP4 to maildir mailbox synchronization program
+http://www.sigpipe.org/isync/
+
+Author: Michael Elkins <me@mutt.org>
+
+``isync'' is a command line application which synchronizes a local
+maildir-style mailbox with a remote IMAP4 mailbox, suitable for use in
+IMAP-disconnected mode. Multiple copies of the remote IMAP4 mailbox can be
+maintained, and all flags are synchronized.
+
+``isync'' has been tested with the following IMAP servers:
+
+ Microsoft Exchange 2000 IMAP4rev1 server version 6.0.4417.0
+
+* INSTALLING
+
+ ./configure
+ make install
+
+* HELP
+
+ Please see the man page for complete documentation.
diff --git a/TODO b/TODO
@@ -0,0 +1 @@
+add upload support to mirror local msgs on the server
diff --git a/configure.in b/configure.in
@@ -0,0 +1,9 @@
+AC_INIT(isync.h)
+AM_INIT_AUTOMAKE(isync,0.1)
+AM_PROG_CC_STDC
+if test $CC = gcc; then
+ CFLAGS="$CFLAGS -pipe"
+fi
+AC_CHECK_FUNCS(getopt_long)
+CFLAGS="$CFLAGS -W -Wall -pedantic -Wmissing-prototypes -Wmissing-declarations"
+AC_OUTPUT(Makefile)
diff --git a/imap.c b/imap.c
@@ -0,0 +1,531 @@
+/* isync - IMAP4 to maildir mailbox synchronizer
+ * Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <assert.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include "isync.h"
+
+const char *Flags[] = {
+ "\\Seen",
+ "\\Answered",
+ "\\Deleted",
+ "\\Flagged",
+ "\\Recent",
+ "\\Draft"
+};
+
+/* simple line buffering */
+static int
+buffer_gets (buffer_t * b, char **s)
+{
+ int n;
+ int start = b->offset;
+
+ *s = b->buf + start;
+
+ for (;;)
+ {
+ if (b->offset + 2 > b->bytes)
+ {
+ /* shift down used bytes */
+ *s = b->buf;
+
+ assert (start <= b->bytes);
+ n = b->bytes - start;
+
+ if (n)
+ memmove (b->buf, b->buf + start, n);
+ b->offset = n;
+ start = 0;
+
+ n = read (b->fd, b->buf + b->offset, sizeof (b->buf) - b->offset);
+ if (n <= 0)
+ {
+ if (n == -1)
+ perror ("read");
+ else
+ puts ("EOF");
+ return -1;
+ }
+ b->bytes = b->offset + n;
+
+// printf ("buffer_gets:read %d bytes\n", n);
+ }
+
+ if (b->buf[b->offset] == '\r')
+ {
+ if (b->buf[b->offset + 1] == '\n')
+ {
+ b->buf[b->offset] = 0; /* terminate the string */
+ b->offset += 2; /* next line */
+ return 0;
+ }
+ }
+
+ b->offset++;
+ }
+ /* not reached */
+}
+
+static int
+imap_exec (imap_t * imap, const char *fmt, ...)
+{
+ va_list ap;
+ char tmp[256];
+ char buf[256];
+ char *cmd;
+ char *arg;
+ char *arg1;
+ message_t **cur = 0;
+ message_t **rec = 0;
+
+ va_start (ap, fmt);
+ vsnprintf (tmp, sizeof (tmp), fmt, ap);
+ va_end (ap);
+
+ snprintf (buf, sizeof (buf), "%d %s\r\n", ++Tag, tmp);
+ if (Verbose)
+ fputs (buf, stdout);
+ write (imap->fd, buf, strlen (buf));
+
+ for (;;)
+ {
+ if (buffer_gets (imap->buf, &cmd))
+ return -1;
+ if (Verbose)
+ puts (cmd);
+
+ arg = next_arg (&cmd);
+ if (*arg == '*')
+ {
+ arg = next_arg (&cmd);
+ arg1 = next_arg (&cmd);
+
+ if (arg1 && !strcmp ("EXISTS", arg1))
+ imap->count = atoi (arg);
+ else if (arg1 && !strcmp ("RECENT", arg1))
+ imap->recent = atoi (arg);
+ else if (!strcmp ("SEARCH", arg))
+ {
+ if (!rec)
+ {
+ rec = &imap->msgs;
+ while (*rec)
+ rec = &(*rec)->next;
+ }
+ while ((arg = next_arg (&cmd)))
+ {
+ *rec = calloc (1, sizeof (message_t));
+ (*rec)->uid = atoi (arg);
+ rec = &(*rec)->next;
+ }
+ }
+ else if (arg1 && !strcmp ("FETCH", arg1))
+ {
+ if (!cur)
+ {
+ cur = &imap->msgs;
+ while (*cur)
+ cur = &(*cur)->next;
+ }
+
+ /* new message
+ * * <N> FETCH (UID <uid> FLAGS (...))
+ */
+ arg = next_arg (&cmd); /* (UID */
+ arg = next_arg (&cmd); /* <uid> */
+ *cur = calloc (1, sizeof (message_t));
+ (*cur)->uid = atoi (arg);
+
+ arg = next_arg (&cmd); /* FLAGS */
+ if (!arg || strcmp ("FLAGS", arg))
+ {
+ printf ("FETCH parse error: expected FLAGS at %s\n", arg);
+ return -1;
+ }
+
+ /* if we need to parse additional info, we should keep
+ * a copy of this `arg' pointer
+ */
+
+ cmd++;
+ arg = strchr (cmd, ')');
+ if (!arg)
+ {
+ puts ("FETCH parse error");
+ return -1;
+ }
+ *arg = 0;
+
+ /* parse message flags */
+ while ((arg = next_arg (&cmd)))
+ {
+ if (!strcmp ("\\Seen", arg))
+ (*cur)->flags |= D_SEEN;
+ else if (!strcmp ("\\Flagged", arg))
+ (*cur)->flags |= D_FLAGGED;
+ else if (!strcmp ("\\Deleted", arg))
+ (*cur)->flags |= D_DELETED;
+ else if (!strcmp ("\\Answered", arg))
+ (*cur)->flags |= D_ANSWERED;
+ else if (!strcmp ("\\Draft", arg))
+ (*cur)->flags |= D_DRAFT;
+ else if (!strcmp ("\\Recent", arg))
+ (*cur)->flags |= D_RECENT;
+ else
+ printf ("warning, unknown flag %s\n", arg);
+ }
+
+ cur = &(*cur)->next;
+ }
+ }
+ else if ((size_t) atol (arg) != Tag)
+ {
+ puts ("wrong tag");
+ return -1;
+ }
+ else
+ {
+ arg = next_arg (&cmd);
+ if (!strcmp ("OK", arg))
+ return 0;
+ puts ("IMAP command failed");
+ return -1;
+ }
+ }
+ /* not reached */
+}
+
+static int
+fetch_recent_flags (imap_t * imap)
+{
+ char buf[1024];
+ message_t **cur = &imap->recent_msgs;
+ message_t *tmp;
+ unsigned int start = -1;
+ unsigned int last = -1;
+ int ret = 0;
+
+ buf[0] = 0;
+ while (*cur)
+ {
+ tmp = *cur;
+
+ if (last == (unsigned int) -1)
+ {
+ /* init */
+ start = tmp->uid;
+ last = tmp->uid;
+ }
+ else if (tmp->uid == last + 1)
+ last++;
+ else
+ {
+ /* out of sequence */
+ if (start == last)
+ ret = imap_exec (imap, "UID FETCH %d (UID FLAGS)", start);
+ else
+ ret =
+ imap_exec (imap, "UID FETCH %d:%d (UID FLAGS)", start,
+ last);
+ start = tmp->uid;
+ last = tmp->uid;
+ }
+ free (tmp);
+ *cur = (*cur)->next;
+ }
+
+ if (start != (unsigned int) -1)
+ {
+ if (start == last)
+ ret = imap_exec (imap, "UID FETCH %d (UID FLAGS)", start);
+ else
+ ret =
+ imap_exec (imap, "UID FETCH %d:%d (UID FLAGS)", start, last);
+ }
+
+ return ret;
+}
+
+imap_t *
+imap_open (config_t * box, int fast)
+{
+ int ret;
+ imap_t *imap;
+ int s;
+ struct sockaddr_in sin;
+ struct hostent *he;
+
+ /* open connection to IMAP server */
+
+ memset (&sin, 0, sizeof (sin));
+ sin.sin_port = htons (box->port);
+ sin.sin_family = AF_INET;
+
+ printf ("Resolving %s... ", box->host);
+ fflush (stdout);
+ he = gethostbyname (box->host);
+ if (!he)
+ {
+ perror ("gethostbyname");
+ return 0;
+ }
+ puts ("ok");
+
+ sin.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
+
+ s = socket (PF_INET, SOCK_STREAM, 0);
+
+ printf ("Connecting to %s:%hu... ", inet_ntoa (sin.sin_addr),
+ ntohs (sin.sin_port));
+ fflush (stdout);
+ if (connect (s, (struct sockaddr *) &sin, sizeof (sin)))
+ {
+ perror ("connect");
+ exit (1);
+ }
+ puts ("ok");
+
+ imap = calloc (1, sizeof (imap_t));
+ imap->fd = s;
+ //imap->state = imap_state_init;
+ imap->buf = calloc (1, sizeof (buffer_t));
+ imap->buf->fd = s;
+ imap->box = box;
+
+ puts ("Logging in...");
+ ret = imap_exec (imap, "LOGIN %s %s", box->user, box->pass);
+ if (!ret)
+ {
+ fputs ("Selecting mailbox... ", stdout);
+ fflush (stdout);
+ ret = imap_exec (imap, "SELECT %s", box->box);
+ if (!ret)
+ printf ("%d messages, %d recent\n", imap->count, imap->recent);
+ }
+
+ if (!ret)
+ {
+ if (fast)
+ {
+ if (imap->recent > 0)
+ {
+ puts ("Fetching info for recent messages");
+ ret = imap_exec (imap, "UID SEARCH RECENT");
+ if (!ret)
+ ret = fetch_recent_flags (imap);
+ }
+ }
+ else if (imap->count > 0)
+ {
+ puts ("Reading IMAP mailbox index");
+ ret = imap_exec (imap, "FETCH 1:%d (UID FLAGS)", imap->count);
+ }
+ }
+
+ if (ret)
+ {
+ imap_exec (imap, "LOGOUT");
+ close (s);
+ free (imap->buf);
+ free (imap);
+ imap = 0;
+ }
+
+ return imap;
+}
+
+void
+imap_close (imap_t * imap)
+{
+ puts ("Closing IMAP connection");
+ imap_exec (imap, "LOGOUT");
+}
+
+/* write a buffer stripping all \r bytes */
+static int
+write_strip (int fd, char *buf, size_t len)
+{
+ size_t start = 0;
+ size_t end = 0;
+
+ while (start < len)
+ {
+ while (end < len && buf[end] != '\r')
+ end++;
+ write (fd, buf + start, end - start);
+ end++;
+ start = end;
+ }
+ return 0;
+}
+
+static void
+send_server (int fd, const char *fmt, ...)
+{
+ char buf[128];
+ char cmd[128];
+ va_list ap;
+
+ va_start (ap, fmt);
+ vsnprintf (buf, sizeof (buf), fmt, ap);
+ va_end (ap);
+
+ snprintf (cmd, sizeof (cmd), "%d %s\r\n", ++Tag, buf);
+ write (fd, cmd, strlen (cmd));
+
+ if (Verbose)
+ fputs (cmd, stdout);
+}
+
+int
+imap_fetch_message (imap_t * imap, unsigned int uid, int fd)
+{
+ char *cmd;
+ char *arg;
+ size_t bytes;
+ size_t n;
+ char buf[1024];
+
+ send_server (imap->fd, "UID FETCH %d RFC822.PEEK", uid);
+
+ for (;;)
+ {
+ if (buffer_gets (imap->buf, &cmd))
+ return -1;
+
+ if (Verbose)
+ puts (cmd);
+
+ if (*cmd == '*')
+ {
+ /* need to figure out how long the message is
+ * * <msgno> FETCH (RFC822 {<size>}
+ */
+
+ next_arg (&cmd); /* * */
+ next_arg (&cmd); /* <msgno> */
+ next_arg (&cmd); /* FETCH */
+ next_arg (&cmd); /* (RFC822 */
+ arg = next_arg (&cmd);
+ if (*arg != '{')
+ {
+ puts ("parse error getting size");
+ return -1;
+ }
+ bytes = strtol (arg + 1, 0, 10);
+// printf ("receiving %d byte message\n", bytes);
+
+ /* dump whats left over in the input buffer */
+ n = imap->buf->bytes - imap->buf->offset;
+
+ if (n > bytes)
+ {
+ /* the entire message fit in the buffer */
+ n = bytes;
+ }
+
+ /* ick. we have to strip out the \r\n line endings, so
+ * i can't just dump the raw bytes to disk.
+ */
+ write_strip (fd, imap->buf->buf + imap->buf->offset, n);
+
+ bytes -= n;
+
+// printf ("wrote %d buffered bytes\n", n);
+
+ /* mark that we used part of the buffer */
+ imap->buf->offset += n;
+
+ /* now read the rest of the message */
+ while (bytes > 0)
+ {
+ n = bytes;
+ if (n > sizeof (buf))
+ n = sizeof (buf);
+ n = read (imap->fd, buf, n);
+ if (n > 0)
+ {
+// printf("imap_fetch_message:%d:read %d bytes\n", __LINE__, n);
+ write_strip (fd, buf, n);
+ bytes -= n;
+ }
+ else
+ {
+ if (n == (size_t) - 1)
+ perror ("read");
+ else
+ puts ("EOF");
+ return -1;
+ }
+ }
+
+// puts ("finished fetching msg");
+
+ buffer_gets (imap->buf, &cmd);
+ if (Verbose)
+ puts (cmd); /* last part of line */
+ }
+ else
+ {
+ arg = next_arg (&cmd);
+ if (!arg || (size_t) atoi (arg) != Tag)
+ {
+ puts ("wrong tag");
+ return -1;
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/* add flags to existing flags */
+int
+imap_set_flags (imap_t * imap, unsigned int uid, unsigned int flags)
+{
+ char buf[256];
+ int i;
+
+ buf[0] = 0;
+ for (i = 0; i < D_MAX; i++)
+ {
+ if (flags & (1 << i))
+ snprintf (buf + strlen (buf),
+ sizeof (buf) - strlen (buf), "%s%s",
+ (buf[0] != 0) ? " " : "", Flags[i]);
+ }
+
+ return imap_exec (imap, "UID STORE %d +FLAGS.SILENT (%s)", uid, buf);
+}
+
+int
+imap_expunge (imap_t * imap)
+{
+ return imap_exec (imap, "EXPUNGE");
+}
diff --git a/isync.1 b/isync.1
@@ -0,0 +1,184 @@
+.ig
+\" isync - IMAP4 to maildir mailbox synchronizer
+\" Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
+\"
+\" This program is free software; you can redistribute it and/or modify
+\" it under the terms of the GNU General Public License as published by
+\" the Free Software Foundation; either version 2 of the License, or
+\" (at your option) any later version.
+\"
+\" This program is distributed in the hope that it will be useful,
+\" but WITHOUT ANY WARRANTY; without even the implied warranty of
+\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+\" GNU General Public License for more details.
+\"
+\" You should have received a copy of the GNU General Public License
+\" along with this program; if not, write to the Free Software
+\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+..
+.TH isync 1 "2000 Dec 20"
+..
+.SH NAME
+isync - synchronize IMAP4 and maildir mailboxes
+..
+.SH SYNOPSIS
+.B isync
+[
+.I options...
+]
+.I file
+..
+.SH DESCRIPTION
+.B isync
+is a command line application which synchronizes a local maildir-style
+mailbox with a remote IMAP4 mailbox, suitable for use in IMAP-disconnected
+mode. Multiple copies of the remote IMAP4 mailbox can be maintained, and
+all flags are synchronized.
+..
+.SH OPTIONS
+.TP
+\fB-c\fR, \fB--config\fR \fIfile\fR
+Read configuration from
+.I file
+By default, configuration is read from ~/.isyncrc if it exists.
+.TP
+.B -d, --delete
+Causes
+.B isync
+to delete messages from the local maildir mailbox which do not exist on the
+IMAP server. By default,
+.I dead
+messages are
+.B not
+deleted.
+.TP
+.B -e, --expunge
+Causes
+.B isync
+to permanently remove all messages marked for deletion in both the local
+maildir mailbox and the remote IMAP mailbox. By default, messages are
+.B not
+expunged.
+.TP
+.B -f, --fast
+Causes
+.B isync
+to skip the step of synchronzing message flags between the local maildir
+mailbox and the IMAP mailbox. Only new messages existing on the server will
+be fetched into the local mailbox.
+.B NOTE:
+This command works by checking the \\Recent flag on messages in the IMAP
+mailbox. If you access the IMAP mailbox from multiple locations, the
+\\Recent flag will be lost between sessions, so you must do a full
+synchronization to fetch the messages which do not exist in the local
+mailbox.
+.TP
+.B -h, --help
+Displays a summary of command line options
+.TP
+\fB-p\fR, \fB--port\fR \fIport\fR
+Specifies the port on the IMAP server to connect to (default: 143)
+.TP
+\fB-r\fR, \fB--remote\fR \fIbox\fR
+Specifies the name of the remote IMAP mailbox to synchronize with
+(Default: INBOX)
+.TP
+\fB-s\fR, \fB--host\fR \fIhost\fR
+.P
+Specifies the hostname of the IMAP server
+.TP
+\fB-u\fR, \fB--user\fR \fIuser\fR
+Specifies the login name to access the IMAP server (default: $USER)
+.TP
+.B -v, --version
+Displays
+.B isync
+version information
+.TP
+.B -V, --verbose
+Enables
+.I verbose
+mode, which disables the IMAP network traffic.
+..
+.SH CONFIGURATION
+.B isync
+reads
+.I ~/.isyncrc
+to load default configuration data. Each line of the configuration file
+consists of a command. The following commands are understood:
+.TP
+\fBMailbox\fR \fIpath\fR
+Defines a local maildir mailbox. All configuration commands following this
+line, up until the next
+.I Mailbox
+command, apply to this mailbox only.
+..
+.TP
+\fBHost\fR \fIname\fR
+Defines the DNS name or IP address of the IMAP server
+..
+.TP
+\fBPort\fR \fIport\fR
+Defines the TCP port number on the IMAP server to use (Default: 143)
+..
+.TP
+\fBBox\fR \fImailbox\fR
+Defines the name of the remote IMAP mailbox associated with the local
+maildir mailbox (Default: INBOX)
+..
+.TP
+\fBUser\fR \fIusername\fR
+Defines the login name on the IMAP server (Default: current user)
+..
+.TP
+\fBPass\fR \fIpassword\fR
+Defines the password for
+.I username
+on the IMAP server. Note that this option is
+.B NOT
+required. If no password is specified in the configuration file,
+.B isync
+will prompt you for it.
+..
+.TP
+\fBAlias\fR \fIstring\fR
+Defines an alias for the mailbox which can be used as a shortcut on the
+command line.
+.P
+Configuration commands that appear prior to the first
+.B Mailbox
+command are considered to be
+.I global
+options which are used as defaults when those specific options are not
+specifically set for a defined Mailbox. For example, if you use the same
+login name for several IMAP servers, you can put a
+.B User
+command before the first
+.B Mailbox
+command, and then leave out the
+.B User
+command in the sections for each mailbox.
+.B isync
+will then use the global value by default.
+..
+.SH FILES
+.TP
+.B ~/.isyncrc
+Default configuration file
+..
+.SH SEE ALSO
+mutt(1), maildir(5)
+.P
+Up to date information on
+.B isync
+can be found at
+http://www.sigpipe.org/isync/.
+..
+.SH AUTHOR
+Written by Michael R. Elkins <me@mutt.org>.
+..
+.SH BUGS
+SSL is currently not used when connecting to the IMAP server. A future
+version of
+.B isync
+is expected to support this.
diff --git a/isync.h b/isync.h
@@ -0,0 +1,111 @@
+/* isync - IMAP4 to maildir mailbox synchronizer
+ * Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <stdarg.h>
+
+typedef struct
+{
+ int fd;
+ char buf[1024];
+ int bytes;
+ int offset;
+}
+buffer_t;
+
+typedef struct config config_t;
+typedef struct mailbox mailbox_t;
+typedef struct message message_t;
+
+struct config
+{
+ char *path;
+ char *host;
+ int port;
+ char *user;
+ char *pass;
+ char *box;
+ char *alias;
+ config_t *next;
+};
+
+/* struct representing local mailbox file */
+struct mailbox
+{
+ char *path;
+ message_t *msgs;
+ unsigned int changed:1;
+};
+
+/* message dispositions */
+#define D_SEEN (1<<0)
+#define D_ANSWERED (1<<1)
+#define D_DELETED (1<<2)
+#define D_FLAGGED (1<<3)
+#define D_RECENT (1<<4)
+#define D_DRAFT (1<<5)
+#define D_MAX 6
+
+struct message
+{
+ char *file;
+ unsigned int uid;
+ unsigned int flags;
+ message_t *next;
+ unsigned int processed:1; /* message has already been evaluated */
+ unsigned int new:1; /* message is in the new/ subdir */
+ unsigned int changed:1; /* flags changed */
+ unsigned int dead:1; /* message doesn't exist on the server */
+};
+
+/* imap connection info */
+typedef struct
+{
+ int fd; /* server socket */
+ unsigned int count; /* # of msgs */
+ unsigned int recent; /* # of recent messages */
+ buffer_t *buf; /* input buffer for reading server output */
+ message_t *msgs; /* list of messages on the server */
+ config_t *box; /* mailbox to open */
+ message_t *recent_msgs; /* list of recent messages - only contains
+ * UID to be used in a FETCH FLAGS command
+ */
+}
+imap_t;
+
+/* flags for sync_mailbox */
+#define SYNC_FAST (1<<0) /* don't sync flags, only fetch new msgs */
+#define SYNC_DELETE (1<<1) /* delete local that don't exist on server */
+
+extern config_t global;
+extern unsigned int Tag;
+extern char Hostname[256];
+extern int Verbose;
+
+char *next_arg (char **);
+
+int sync_mailbox (mailbox_t *, imap_t *, int);
+
+void imap_close (imap_t *);
+int imap_fetch_message (imap_t *, unsigned int, int);
+int imap_set_flags (imap_t *, unsigned int, unsigned int);
+int imap_expunge (imap_t *);
+imap_t *imap_open (config_t *, int);
+
+mailbox_t *maildir_open (const char *, int fast);
+int maildir_expunge (mailbox_t *, int);
+int maildir_sync (mailbox_t *);
diff --git a/isyncrc.sample b/isyncrc.sample
@@ -0,0 +1,28 @@
+# Global configuration section
+# Values here are used as defaults for any following Mailbox section that
+# doesn't specify it.
+
+# my default username, if different from the local username
+User me
+#Port 143
+#Box INBOX
+
+###
+### work mailbox
+###
+
+Mailbox /home/me/Mail/work
+Host work.host.com
+Pass xxxxxxxx
+# define a shortcut so I can just use "isync work" from the command line
+Alias work
+
+###
+### personal mailbox
+###
+
+Mailbox /home/me/Mail/personal
+Host host.play.com
+# use a non-default port for this connection
+Port 6789
+Alias personal
diff --git a/maildir.c b/maildir.c
@@ -0,0 +1,208 @@
+/* isync - IMAP4 to maildir mailbox synchronizer
+ * Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "isync.h"
+
+/* 2,<flags> */
+static void
+parse_info (message_t * m, char *s)
+{
+ if (*s == '2' && *(s + 1) == ',')
+ {
+ s += 2;
+ while (*s)
+ {
+ if (*s == 'F')
+ m->flags |= D_FLAGGED;
+ else if (*s == 'R')
+ m->flags |= D_ANSWERED;
+ else if (*s == 'T')
+ m->flags |= D_DELETED;
+ else if (*s == 'S')
+ m->flags |= D_SEEN;
+ s++;
+ }
+ }
+}
+
+/* open a maildir mailbox. if `fast' is nonzero, we just check to make
+ * sure its a valid mailbox and don't actually parse it. any IMAP messages
+ * with the \Recent flag set are guaranteed not to be in the mailbox yet,
+ * so we can save a lot of time when the user just wants to fetch new messages
+ * without syncing the flags.
+ */
+mailbox_t *
+maildir_open (const char *path, int fast)
+{
+ char buf[_POSIX_PATH_MAX];
+ DIR *d;
+ struct dirent *e;
+ message_t **cur;
+ message_t *p;
+ mailbox_t *m;
+ char *s;
+ int count = 0;
+
+ /* check to make sure this looks like a valid maildir box */
+ snprintf (buf, sizeof (buf), "%s/new", path);
+ if (access (buf, F_OK))
+ {
+ perror ("access");
+ return 0;
+ }
+ snprintf (buf, sizeof (buf), "%s/cur", path);
+ if (access (buf, F_OK))
+ {
+ perror ("access");
+ return 0;
+ }
+ m = calloc (1, sizeof (mailbox_t));
+ m->path = strdup (path);
+
+ if (fast)
+ return m;
+
+ cur = &m->msgs;
+ for (; count < 2; count++)
+ {
+ /* read the msgs from the new subdir */
+ snprintf (buf, sizeof (buf), "%s/%s", path,
+ (count == 0) ? "new" : "cur");
+ d = opendir (buf);
+ if (!d)
+ {
+ perror ("opendir");
+ return 0;
+ }
+ while ((e = readdir (d)))
+ {
+ if (*e->d_name == '.')
+ continue; /* skip dot-files */
+ *cur = calloc (1, sizeof (message_t));
+ p = *cur;
+ p->file = strdup (e->d_name);
+ p->uid = -1;
+ p->flags = (count == 1) ? D_SEEN : 0;
+ p->new = (count == 0);
+
+ /* filename format is something like:
+ * <unique-prefix>.UID<n>:2,<flags>
+ * This is completely non-standard, but in order for mail
+ * clients to understand the flags, we have to use the
+ * standard :info as described by the qmail spec
+ */
+ s = strstr (p->file, "UID");
+ if (!s)
+ puts ("warning, no uid for message");
+ else
+ {
+ p->uid = strtol (s + 3, &s, 10);
+ if (*s && *s != ':')
+ {
+ puts ("warning, unable to parse uid");
+ p->uid = -1; /* reset */
+ }
+ }
+
+ s = strchr (p->file, ':');
+ if (s)
+ parse_info (p, s + 1);
+ cur = &p->next;
+ }
+ closedir (d);
+ }
+ return m;
+}
+
+/* permanently remove messages from a maildir mailbox. if `dead' is nonzero,
+ * we only remove the messags marked dead.
+ */
+int
+maildir_expunge (mailbox_t * mbox, int dead)
+{
+ message_t **cur = &mbox->msgs;
+ message_t *tmp;
+ char path[_POSIX_PATH_MAX];
+
+ while (*cur)
+ {
+ if ((dead == 0 && (*cur)->flags & D_DELETED) ||
+ (dead && (*cur)->dead))
+ {
+ tmp = *cur;
+ *cur = (*cur)->next;
+ snprintf (path, sizeof (path), "%s/%s/%s",
+ mbox->path, tmp->new ? "new" : "cur", tmp->file);
+ if (unlink (path))
+ perror ("unlink");
+ free (tmp->file);
+ free (tmp);
+ }
+ else
+ cur = &(*cur)->next;
+ }
+ return 0;
+}
+
+int
+maildir_sync (mailbox_t * mbox)
+{
+ message_t *cur = mbox->msgs;
+ char path[_POSIX_PATH_MAX];
+ char oldpath[_POSIX_PATH_MAX];
+ char *p;
+
+ if (mbox->changed)
+ {
+ for (; cur; cur = cur->next)
+ {
+ if (cur->changed)
+ {
+ /* generate old path */
+ snprintf (oldpath, sizeof (oldpath), "%s/%s/%s",
+ mbox->path, cur->new ? "new" : "cur", cur->file);
+
+ /* truncate old flags (if present) */
+ p = strchr (cur->file, ':');
+ if (p)
+ *p = 0;
+
+ p = strrchr (cur->file, '/');
+
+ /* generate new path */
+ snprintf (path, sizeof (path), "%s/%s%s:2,%s%s%s%s",
+ mbox->path, (cur->flags & D_SEEN) ? "cur" : "new",
+ cur->file, (cur->flags & D_FLAGGED) ? "F" : "",
+ (cur->flags & D_ANSWERED) ? "R" : "",
+ (cur->flags & D_SEEN) ? "S" : "",
+ (cur->flags & D_DELETED) ? "T" : "");
+
+ if (rename (oldpath, path))
+ perror ("rename");
+ }
+ }
+ }
+ return 0;
+}
diff --git a/main.c b/main.c
@@ -0,0 +1,398 @@
+/* isync - IMAP4 to maildir mailbox synchronizer
+ * Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <termios.h>
+#include "isync.h"
+
+#if HAVE_GETOPT_LONG
+#define _GNU_SOURCE
+#include <getopt.h>
+
+struct option Opts[] = {
+ {"config", 1, NULL, 'c'},
+ {"delete", 0, NULL, 'd'},
+ {"expunge", 0, NULL, 'e'},
+ {"fast", 0, NULL, 'f'},
+ {"help", 0, NULL, 'h'},
+ {"remote", 1, NULL, 'r'},
+ {"host", 1, NULL, 's'},
+ {"port", 1, NULL, 'p'},
+ {"user", 1, NULL, 'u'},
+ {"version", 0, NULL, 'v'},
+ {"verbose", 0, NULL, 'V'},
+ {0, 0, 0, 0}
+};
+#endif
+
+config_t global;
+unsigned int Tag = 0;
+static config_t *box = 0;
+char Hostname[256];
+int Verbose = 0;
+
+static void
+version (void)
+{
+ printf ("%s %s\n", PACKAGE, VERSION);
+ exit (0);
+}
+
+static void
+usage (void)
+{
+ printf ("%s %s IMAP4 to maildir synchronizer\n", PACKAGE, VERSION);
+ puts ("Copyright (C) 2000 Michael R. Elkins <me@mutt.org>");
+ printf ("usage: %s [ flags ] mailbox\n", PACKAGE);
+ puts
+ (" -c, --config CONFIG read an alternate config file (default: ~/.isyncrc)");
+ puts
+ (" -d, --delete delete local msgs that don't exist on the server");
+ puts
+ (" -e, --expunge expunge deleted messages from the server");
+ puts (" -f, --fast only fetch new messages");
+ puts (" -h, --help display this help message");
+ puts (" -p, --port PORT server IMAP port");
+ puts (" -r, --remote BOX remote mailbox");
+ puts (" -s, --host HOST IMAP server address");
+ puts (" -u, --user USER IMAP user name");
+ puts (" -v, --version display version");
+ puts
+ (" -V, --verbose verbose mode (display network traffic)");
+ exit (0);
+}
+
+static char *
+enter_password (void)
+{
+ struct termios t;
+ char pass[32];
+
+ tcgetattr (0, &t);
+ t.c_lflag &= ~ECHO;
+ tcsetattr (0, TCSANOW, &t);
+ printf ("Password: ");
+ fflush (stdout);
+ pass[sizeof (pass) - 1] = 0;
+ fgets (pass, sizeof (pass) - 1, stdin);
+ if (pass[0])
+ pass[strlen (pass) - 1] = 0; /* kill newline */
+ t.c_lflag |= ECHO;
+ tcsetattr (0, TCSANOW, &t);
+ puts ("");
+ return strdup (pass);
+}
+
+static void
+load_config (char *where)
+{
+ char path[_POSIX_PATH_MAX];
+ char buf[1024];
+ struct passwd *pw;
+ config_t **cur = &box;
+ char *p;
+ int line = 0;
+ FILE *fp;
+
+ if (!where)
+ {
+ pw = getpwuid (getuid ());
+ snprintf (path, sizeof (path), "%s/.isyncrc", pw->pw_dir);
+ where = path;
+ }
+ printf ("Reading %s\n", where);
+
+ fp = fopen (where, "r");
+ if (!fp)
+ {
+ if (errno != ENOENT)
+ {
+ perror ("fopen");
+ return;
+ }
+ }
+ while ((fgets (buf, sizeof (buf) - 1, fp)))
+ {
+ if (buf[0])
+ buf[strlen (buf) - 1] = 0;
+ line++;
+ if (buf[0] == '#')
+ continue;
+ p = buf;
+ while (*p && !isspace (*p))
+ p++;
+ while (isspace (*p))
+ p++;
+ if (!strncmp ("mailbox", buf, 7))
+ {
+ if (*cur)
+ cur = &(*cur)->next;
+ *cur = calloc (1, sizeof (config_t));
+ (*cur)->path = strdup (p);
+ }
+ else if (!strncmp ("host", buf, 4))
+ {
+ if (*cur)
+ (*cur)->host = strdup (p);
+ else
+ global.host = strdup (p);
+ }
+ else if (!strncmp ("user", buf, 4))
+ {
+ if (*cur)
+ (*cur)->user = strdup (p);
+ else
+ global.user = strdup (p);
+ }
+ else if (!strncmp ("pass", buf, 4))
+ {
+ if (*cur)
+ (*cur)->pass = strdup (p);
+ else
+ global.pass = strdup (p);
+ }
+ else if (!strncmp ("port", buf, 4))
+ {
+ if (*cur)
+ (*cur)->port = atoi (p);
+ else
+ global.port = atoi (p);
+ }
+ else if (!strncmp ("box", buf, 3))
+ {
+ if (*cur)
+ (*cur)->box = strdup (p);
+ else
+ global.box = strdup (p);
+ }
+ else if (!strncmp ("alias", buf, 5))
+ {
+ if (*cur)
+ (*cur)->alias = strdup (p);
+ }
+ else if (buf[0])
+ printf ("%s:%d:unknown command:%s", path, line, buf);
+ }
+ fclose (fp);
+}
+
+static config_t *
+find_box (const char *s)
+{
+ config_t *p = box;
+
+ for (; p; p = p->next)
+ if (!strcmp (s, p->path) || (p->alias && !strcmp (s, p->alias)))
+ return p;
+ return 0;
+}
+
+char *
+next_arg (char **s)
+{
+ char *ret;
+
+ if (!s)
+ return 0;
+ if (!*s)
+ return 0;
+ while (isspace (**s))
+ (*s)++;
+ if (!**s)
+ {
+ *s = 0;
+ return 0;
+ }
+ ret = *s;
+ while (**s && !isspace (**s))
+ (*s)++;
+ if (**s)
+ *(*s)++ = 0;
+ if (!**s)
+ *s = 0;
+ return ret;
+}
+
+int
+main (int argc, char **argv)
+{
+ int i;
+ config_t *box;
+ mailbox_t *mail;
+ imap_t *imap;
+ int expunge = 0; /* by default, don't delete anything */
+ int fast = 0;
+ int delete = 0;
+ char *config = 0;
+ struct passwd *pw;
+
+ pw = getpwuid (getuid ());
+
+ /* defaults */
+ memset (&global, 0, sizeof (global));
+ global.port = 143;
+ global.box = "INBOX";
+ global.user = strdup (pw->pw_name);
+
+#if HAVE_GETOPT_LONG
+ while ((i = getopt_long (argc, argv, "defhp:u:r:s:vV", Opts, NULL)) != -1)
+#else
+ while ((i = getopt (argc, argv, "defhp:u:r:s:vV")) != -1)
+#endif
+ {
+ switch (i)
+ {
+ case 'c':
+ config = optarg;
+ break;
+ case 'd':
+ delete = 1;
+ break;
+ case 'e':
+ expunge = 1;
+ break;
+ case 'f':
+ fast = 1;
+ break;
+ case 'p':
+ global.port = atoi (optarg);
+ break;
+ case 'r':
+ global.box = optarg;
+ break;
+ case 's':
+ global.host = optarg;
+ break;
+ case 'u':
+ free (global.user);
+ global.user = optarg;
+ break;
+ case 'V':
+ Verbose = 1;
+ break;
+ case 'v':
+ version ();
+ default:
+ usage ();
+ }
+ }
+
+ if (!argv[optind])
+ {
+ puts ("No box specified");
+ usage ();
+ }
+
+ gethostname (Hostname, sizeof (Hostname));
+
+ load_config (config);
+
+ box = find_box (argv[optind]);
+ if (!box)
+ {
+ /* if enough info is given on the command line, don't worry if
+ * the mailbox isn't defined.
+ */
+ if (!global.host)
+ {
+ puts ("No such mailbox");
+ exit (1);
+ }
+ global.path = argv[optind];
+ box = &global;
+ }
+
+ /* fill in missing info with defaults */
+ if (!box->pass)
+ {
+ if (!global.pass)
+ {
+ box->pass = enter_password ();
+ if (!box->pass)
+ {
+ puts ("Aborting, no password");
+ exit (1);
+ }
+ }
+ else
+ box->pass = global.pass;
+ }
+ if (!box->user)
+ box->user = global.user;
+ if (!box->port)
+ box->port = global.port;
+ if (!box->host)
+ box->host = global.host;
+ if (!box->box)
+ box->box = global.box;
+
+ printf ("Reading %s\n", box->path);
+ mail = maildir_open (box->path, fast);
+ if (!mail)
+ {
+ puts ("Unable to load mailbox");
+ exit (1);
+ }
+
+ imap = imap_open (box, fast);
+ if (!imap)
+ exit (1);
+
+ puts ("Synchronizing");
+ i = 0;
+ i |= (fast) ? SYNC_FAST : 0;
+ i |= (delete) ? SYNC_DELETE : 0;
+ if (sync_mailbox (mail, imap, i))
+ exit (1);
+
+ if (!fast)
+ {
+ if (expunge)
+ {
+ /* remove messages marked for deletion */
+ puts ("Expunging messages");
+ if (imap_expunge (imap))
+ exit (1);
+ if (maildir_expunge (mail, 0))
+ exit (1);
+ }
+ /* remove messages deleted from server. this can safely be an
+ * `else' clause since dead messages are marked as deleted by
+ * sync_mailbox.
+ */
+ else if (delete)
+ maildir_expunge (mail, 1);
+
+ /* write changed flags back to the mailbox */
+ printf ("Committing changes to %s\n", mail->path);
+ if (maildir_sync (mail))
+ exit (1);
+ }
+
+ /* gracefully close connection to the IMAP server */
+ imap_close (imap);
+
+ exit (0);
+}
diff --git a/sync.c b/sync.c
@@ -0,0 +1,134 @@
+/* isync - IMAP4 to maildir mailbox synchronizer
+ * Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <stdio.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <fcntl.h>
+#include <string.h>
+#include "isync.h"
+
+static unsigned int MaildirCount = 0;
+
+static message_t *
+find_msg (message_t * list, unsigned int uid)
+{
+ for (; list; list = list->next)
+ if (list->uid == uid)
+ return list;
+ return 0;
+}
+
+int
+sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags)
+{
+ message_t *cur;
+ message_t *tmp;
+ char path[_POSIX_PATH_MAX];
+ char newpath[_POSIX_PATH_MAX];
+ char *p;
+ int fd;
+
+ for (cur = mbox->msgs; cur; cur = cur->next)
+ {
+ tmp = find_msg (imap->msgs, cur->uid);
+ if (!tmp)
+ {
+ printf ("warning, uid %d doesn't exist on server\n", cur->uid);
+ if (flags & SYNC_DELETE)
+ {
+ cur->flags |= D_DELETED;
+ cur->dead = 1;
+ }
+ continue;
+ }
+ tmp->processed = 1;
+
+ if (!(flags & SYNC_FAST))
+ {
+ /* check if local flags are different from server flags.
+ * ignore \Recent and \Draft
+ */
+ if (cur->flags != (tmp->flags & ~(D_RECENT | D_DRAFT)))
+ {
+ /* set local flags that don't exist on the server */
+ imap_set_flags (imap, cur->uid, cur->flags & ~tmp->flags);
+
+ /* update local flags */
+ cur->flags |= (tmp->flags & ~(D_RECENT | D_DRAFT));
+ cur->changed = 1;
+ mbox->changed = 1;
+ }
+ }
+ }
+
+ fputs ("Fetching new messages", stdout);
+ fflush (stdout);
+ for (cur = imap->msgs; cur; cur = cur->next)
+ {
+ if (!cur->processed)
+ {
+ /* new message on server */
+ fputs (".", stdout);
+ fflush (stdout);
+
+ /* create new file */
+ snprintf (path, sizeof (path), "%s/tmp/%s.%ld_%d.%d.UID%d",
+ mbox->path, Hostname, time (0), MaildirCount++,
+ getpid (), cur->uid);
+
+ if (cur->flags)
+ {
+ /* append flags */
+ snprintf (path + strlen (path), sizeof (path) - strlen (path),
+ ":2,%s%s%s%s",
+ (cur->flags & D_FLAGGED) ? "F" : "",
+ (cur->flags & D_ANSWERED) ? "R" : "",
+ (cur->flags & D_SEEN) ? "S" : "",
+ (cur->flags & D_DELETED) ? "T" : "");
+ }
+
+// printf("creating %s\n", path);
+ fd = open (path, O_WRONLY | O_CREAT | O_EXCL, 0600);
+ if (fd < 0)
+ {
+ perror ("open");
+ continue;
+ }
+
+ imap_fetch_message (imap, cur->uid, fd);
+
+ close (fd);
+
+ p = strrchr (path, '/');
+
+ snprintf (newpath, sizeof (newpath), "%s/%s%s", mbox->path,
+ (cur->flags & D_SEEN) ? "cur" : "new", p);
+
+// printf ("moving %s to %s\n", path, newpath);
+
+ if (rename (path, newpath))
+ perror ("rename");
+ }
+ }
+ puts ("");
+
+ return 0;
+}