initial import

This commit is contained in:
Michael Elkins 2000-12-20 21:41:21 +00:00
commit f47d0d7c11
14 changed files with 1640 additions and 0 deletions

1
AUTHORS Normal file
View File

@ -0,0 +1 @@
Michael R. Elkins <me@mutt.org>

0
ChangeLog Normal file
View File

4
Makefile.am Normal file
View File

@ -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)

3
NEWS Normal file
View File

@ -0,0 +1,3 @@
[0.1]
Initial release.

28
README Normal file
View File

@ -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.

1
TODO Normal file
View File

@ -0,0 +1 @@
add upload support to mirror local msgs on the server

9
configure.in Normal file
View File

@ -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)

531
imap.c Normal file
View File

@ -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");
}

184
isync.1 Normal file
View File

@ -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.

111
isync.h Normal file
View File

@ -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 *);

28
isyncrc.sample Normal file
View File

@ -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

208
maildir.c Normal file
View File

@ -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;
}

398
main.c Normal file
View File

@ -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);
}

134
sync.c Normal file
View File

@ -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;
}