added MaxSize configuration variable

fixed --fast to work robustly without relying on the \Recent flag in
messages
This commit is contained in:
Michael Elkins 2000-12-22 07:14:32 +00:00
parent a8f9af4296
commit 0527181f45
10 changed files with 347 additions and 159 deletions

View File

@ -1,5 +1,55 @@
2000-12-21 Michael Elkins <me@sigipe.org>
* imap.c, isync.h, maildir.c, sync.c:
RFC822.PEEK is obsolete in RFC2060. Use BODY.PEEK[] instead, which does
the same thing
keep track of the uidvalidity so isync can detect if the mailbox on the
server has changed since the last sync.
* NEWS: updated NEWS for 0.3 release
* Makefile.am, isync.spec:
added support for building RPMS
* Makefile.am, isync.1:
added target for creating html version of the man page
documented the imaps: prefix to the Host command
* imap.c, sync.c:
can't assume flag order when fetching a message. just search for the
first `{' to find the message size.
* isync.1, sync.c:
added BUGS section to manpage detailing the fact that we break the
maildir(5) spec by parsing the filename
change message delivery to use the method described in maildir(5)
* configure.in, main.c, sync.c:
use getpass() to get the user's password
unlink the temp file if we are unable to fetch a new message from the
server.
update version to 0.3
* isync.1: fixed typo in man page for --verbose option
* Makefile.am, README, TODO, imap.c, isync.h, list.c:
added generic IMAP list parser and rewrote imap_exec() to handle
arbitrary data instead of hardcoded
* Makefile.am, README, configure.in, main.c:
fixes to compile cleanly under Solaris 2.7
* configure.in, imap.c, isync.1, isync.h, main.c:
added OpenSSL support
* ChangeLog, configure.in, main.c:
config options were not case insensitive
* imap.c, isync.h, maildir.c, main.c, sync.c:
don't fetch deleted messages when expunging

14
NEWS
View File

@ -1,3 +1,17 @@
[0.4]
Added MaxSize configuration option to limit downloading of new messages from
the server to less than some threshold.
More robust --fast option works without using \Recent flags, so the previous
problem with multiple accesses killing these flags is no longer a problem.
RFC2060 obsoleted RFC822.PEEK, use BODY.PEEK[] instead which does the same
job.
Don't need to request UID in a FETCH when doing UID FETCH (RFC2060 states
that its automatically returned).
[0.3]
Fixed to clean up temp maildir files when the fetch of a new message failed.

View File

@ -1,5 +1,5 @@
AC_INIT(isync.h)
AM_INIT_AUTOMAKE(isync,0.3)
AM_INIT_AUTOMAKE(isync,0.4)
AM_PROG_CC_STDC
if test $CC = gcc; then
CFLAGS="$CFLAGS -pipe"

159
imap.c
View File

@ -150,9 +150,13 @@ buffer_gets (buffer_t * b, char **s)
}
static int
parse_fetch (imap_t * imap, list_t * list, message_t * cur)
parse_fetch (imap_t * imap, list_t * list)
{
list_t *tmp;
unsigned int uid = 0;
unsigned int mask = 0;
unsigned int size = 0;
message_t *cur;
if (!is_list (list))
return -1;
@ -165,7 +169,14 @@ parse_fetch (imap_t * imap, list_t * list, message_t * cur)
{
tmp = tmp->next;
if (is_atom (tmp))
cur->uid = atoi (tmp->val);
{
uid = atoi (tmp->val);
if (uid < imap->minuid)
{
/* already saw this message */
return 0;
}
}
else
puts ("Error, unable to parse UID");
}
@ -181,20 +192,17 @@ parse_fetch (imap_t * imap, list_t * list, message_t * cur)
if (is_atom (flags))
{
if (!strcmp ("\\Seen", flags->val))
cur->flags |= D_SEEN;
mask |= D_SEEN;
else if (!strcmp ("\\Flagged", flags->val))
cur->flags |= D_FLAGGED;
mask |= D_FLAGGED;
else if (!strcmp ("\\Deleted", flags->val))
{
cur->flags |= D_DELETED;
imap->deleted++;
}
mask |= D_DELETED;
else if (!strcmp ("\\Answered", flags->val))
cur->flags |= D_ANSWERED;
mask |= D_ANSWERED;
else if (!strcmp ("\\Draft", flags->val))
cur->flags |= D_DRAFT;
mask |= D_DRAFT;
else if (!strcmp ("\\Recent", flags->val))
cur->flags |= D_RECENT;
mask |= D_RECENT;
else
printf ("Warning, unknown flag %s\n",
flags->val);
@ -206,8 +214,26 @@ parse_fetch (imap_t * imap, list_t * list, message_t * cur)
else
puts ("Error, unable to parse FLAGS");
}
else if (!strcmp ("RFC822.SIZE", tmp->val))
{
tmp = tmp->next;
if (is_atom (tmp))
size = atol (tmp->val);
}
}
}
cur = calloc (1, sizeof (message_t));
cur->next = imap->msgs;
imap->msgs = cur;
if (mask & D_DELETED)
imap->deleted++;
cur->uid = uid;
cur->flags = mask;
cur->size = size;
return 0;
}
@ -246,8 +272,6 @@ imap_exec (imap_t * imap, const char *fmt, ...)
char *cmd;
char *arg;
char *arg1;
message_t **cur = 0;
message_t **rec = 0;
va_start (ap, fmt);
vsnprintf (tmp, sizeof (tmp), fmt, ap);
@ -281,22 +305,6 @@ imap_exec (imap_t * imap, const char *fmt, ...)
imap->ns_other = parse_list (cmd, &cmd);
imap->ns_shared = parse_list (cmd, 0);
}
else if (!strcmp ("SEARCH", arg))
{
if (!rec)
{
rec = &imap->recent_msgs;
while (*rec)
rec = &(*rec)->next;
}
/* parse rest of `cmd' */
while ((arg = next_arg (&cmd)))
{
*rec = calloc (1, sizeof (message_t));
(*rec)->uid = atoi (arg);
rec = &(*rec)->next;
}
}
else if (!strcmp ("OK", arg) || !strcmp ("BAD", arg) ||
!strcmp ("NO", arg) || !strcmp ("PREAUTH", arg) ||
!strcmp ("BYE", arg))
@ -313,25 +321,15 @@ imap_exec (imap_t * imap, const char *fmt, ...)
{
list_t *list;
if (!cur)
{
cur = &imap->msgs;
while (*cur)
cur = &(*cur)->next;
}
list = parse_list (cmd, 0);
*cur = calloc (1, sizeof (message_t));
if (parse_fetch (imap, list, *cur))
if (parse_fetch (imap, list))
{
free_list (list);
return -1;
}
free_list (list);
cur = &(*cur)->next;
}
}
else
@ -357,66 +355,20 @@ imap_exec (imap_t * imap, const char *fmt, ...)
/* 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;
}
/* `box' is the config info for the maildrop to sync. `minuid' is the
* minimum UID to consider. in normal mode this will be 1, but in --fast
* mode we only fetch messages newer than the last one seen in the local
* mailbox.
*/
imap_t *
imap_open (config_t * box, int fast)
imap_open (config_t * box, unsigned int minuid)
{
int ret;
imap_t *imap;
int s;
struct sockaddr_in sin;
struct hostent *he;
char *ns_prefix = 0;
char *ns_prefix = "";
#if HAVE_LIBSSL
int use_ssl = 0;
#endif
@ -463,6 +415,7 @@ imap_open (config_t * box, int fast)
imap->buf = calloc (1, sizeof (buffer_t));
imap->buf->sock = imap->sock;
imap->box = box;
imap->minuid = minuid;
#if HAVE_LIBSSL
if (!box->use_imaps)
@ -520,28 +473,18 @@ imap_open (config_t * box, int fast)
{
fputs ("Selecting mailbox... ", stdout);
fflush (stdout);
ret = imap_exec (imap, "SELECT %s%s",
ns_prefix ? ns_prefix : "", box->box);
ret = imap_exec (imap, "SELECT %s%s", ns_prefix, box->box);
if (!ret)
printf ("%d messages, %d recent\n", imap->count, imap->recent);
}
if (!ret)
{
if (fast)
puts ("Reading IMAP mailbox index");
if (imap->count > 0)
{
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);
ret = imap_exec (imap, "UID FETCH %d:* (FLAGS RFC822.SIZE)",
imap->minuid);
}
}

15
isync.1
View File

@ -66,12 +66,6 @@ Causes
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
@ -158,6 +152,15 @@ Defines an alias for the mailbox which can be used as a shortcut on the
command line.
..
.TP
\fBMaxSize\fR \fIbytes\fR
Sets a threshold for the maximum message size (in bytes) for which
.B isync
should fetch from the server. This is useful for weeding out messages with
large attachments. If
.I bytes
is 0, the maximum file size is
.B unlimited.
.TP
\fBRequireSSL\fR \fIyes|no\fR
.B isync
will abort the connection if a TLS/SSL session to the IMAP

17
isync.h
View File

@ -54,6 +54,7 @@ struct config
char *pass;
char *box;
char *alias;
unsigned int max_size;
config_t *next;
#if HAVE_LIBSSL
char *cert_file;
@ -69,7 +70,9 @@ struct mailbox
message_t *msgs;
unsigned int deleted; /* # of deleted messages */
unsigned int uidvalidity;
unsigned int maxuid; /* largest uid we know about */
unsigned int changed:1;
unsigned int maxuidchanged:1;
};
/* message dispositions */
@ -86,6 +89,7 @@ struct message
char *file;
unsigned int uid;
unsigned int flags;
unsigned int size;
message_t *next;
unsigned int processed:1; /* message has already been evaluated */
unsigned int new:1; /* message is in the new/ subdir */
@ -119,6 +123,8 @@ typedef struct
*/
unsigned int deleted; /* # of deleted messages */
unsigned int uidvalidity;
unsigned int maxuid;
unsigned int minuid;
/* NAMESPACE info */
list_t *ns_personal;
list_t *ns_other;
@ -127,9 +133,8 @@ typedef struct
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 */
#define SYNC_EXPUNGE (1<<2) /* don't fetch deleted messages */
#define SYNC_DELETE (1<<0) /* delete local that don't exist on server */
#define SYNC_EXPUNGE (1<<1) /* don't fetch deleted messages */
extern config_t global;
extern unsigned int Tag;
@ -142,19 +147,21 @@ extern SSL_CTX *SSLContext;
char *next_arg (char **);
int sync_mailbox (mailbox_t *, imap_t *, int);
int sync_mailbox (mailbox_t *, imap_t *, int, unsigned 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);
imap_t *imap_open (config_t *, unsigned int);
mailbox_t *maildir_open (const char *, int fast);
int maildir_expunge (mailbox_t *, int);
int maildir_sync (mailbox_t *);
int maildir_set_uidvalidity (mailbox_t *, unsigned int uidvalidity);
message_t * find_msg (message_t * list, unsigned int uid);
/* parse an IMAP list construct */
list_t * parse_list (char *s, char **end);
int is_atom (list_t *list);

View File

@ -6,6 +6,8 @@
User me
#Port 143
#Box INBOX
# don't download messages larger than 200K bytes
MaxSize 200000
###
### work mailbox

174
maildir.c
View File

@ -25,8 +25,38 @@
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include "isync.h"
static int
do_lock (int fd, int flag)
{
struct flock lck;
struct stat sb;
if (fstat (fd, &sb))
{
perror ("fstat");
return -1;
}
memset (&lck, 0, sizeof (lck));
lck.l_type = flag;
lck.l_whence = SEEK_SET;
lck.l_start = 0;
lck.l_len = sb.st_size;
if (fcntl (fd, F_SETLK, &lck))
{
perror ("fcntl");
close (fd);
return -1;
}
return 0;
}
/* 2,<flags> */
static void
parse_info (message_t * m, char *s)
@ -49,6 +79,45 @@ parse_info (message_t * m, char *s)
}
}
static unsigned int
read_uid (const char *path, const char *file)
{
char full[_POSIX_PATH_MAX];
int fd;
int ret;
int len;
char buf[64];
unsigned int uid = 0;
snprintf (full, sizeof (full), "%s/%s", path, file);
fd = open (full, O_RDONLY);
if (fd == -1)
{
if (errno != ENOENT)
{
perror ("open");
return -1;
}
return 0; /* doesn't exist */
}
ret = do_lock (fd, F_RDLCK);
if (!ret)
{
len = read (fd, buf, sizeof (buf) - 1);
if (len == -1)
ret = -1;
else
{
buf[len] = 0;
uid = atol (buf);
}
}
ret |= do_lock (fd, F_UNLCK);
close (fd);
return ret ? ret : uid;
}
/* 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,
@ -66,7 +135,6 @@ maildir_open (const char *path, int fast)
mailbox_t *m;
char *s;
int count = 0;
FILE *fp;
/* check to make sure this looks like a valid maildir box */
snprintf (buf, sizeof (buf), "%s/new", path);
@ -84,16 +152,22 @@ maildir_open (const char *path, int fast)
m = calloc (1, sizeof (mailbox_t));
m->path = strdup (path);
m->uidvalidity = -1;
/* check for the uidvalidity value */
snprintf (buf, sizeof (buf), "%s/isyncuidvalidity", path);
if ((fp = fopen (buf, "r")))
m->uidvalidity = read_uid (path, "isyncuidvalidity");
if (m->uidvalidity == (unsigned int) -1)
{
buf[sizeof (buf) - 1] = 0;
if (fgets (buf, sizeof (buf) - 1, fp))
m->uidvalidity = atol (buf);
fclose (fp);
free (m->path);
free (m);
return NULL;
}
/* load the current maxuid */
if ((m->maxuid = read_uid (path, "isyncmaxuid")) == (unsigned int) -1)
{
free (m->path);
free (m);
return NULL;
}
if (fast)
@ -108,6 +182,8 @@ maildir_open (const char *path, int fast)
d = opendir (buf);
if (!d)
{
free (m->path);
free (m);
perror ("opendir");
return 0;
}
@ -130,10 +206,15 @@ maildir_open (const char *path, int fast)
*/
s = strstr (p->file, "UID");
if (!s)
puts ("warning, no uid for message");
puts ("Warning, no uid for message");
else
{
p->uid = strtol (s + 3, &s, 10);
if (p->uid > m->maxuid)
{
m->maxuid = p->uid;
m->maxuidchanged = 1;
}
if (*s && *s != ':')
{
puts ("warning, unable to parse uid");
@ -183,6 +264,70 @@ maildir_expunge (mailbox_t * mbox, int dead)
return 0;
}
static int
update_maxuid (mailbox_t * mbox)
{
int fd;
char buf[64];
size_t len;
unsigned int uid;
char path[_POSIX_PATH_MAX];
int ret = 0;
snprintf (path, sizeof (path), "%s/isyncmaxuid", mbox->path);
fd = open (path, O_RDWR | O_CREAT, 0600);
if (fd == -1)
{
perror ("open");
return -1;
}
/* lock the file */
if (do_lock (fd, F_WRLCK))
{
close (fd);
return -1;
}
/* read the file again just to make sure it wasn't updated while
* we were doing something else
*/
len = read (fd, buf, sizeof (buf) - 1);
buf[len] = 0;
uid = atol (buf);
if (uid > mbox->maxuid)
{
puts ("Error, maxuid is now higher (fatal)");
ret = -1;
}
if (!ret)
{
/* rewind */
lseek (fd, 0, SEEK_SET);
/* write out the file */
snprintf (buf, sizeof (buf), "%u\n", mbox->maxuid);
len = write (fd, buf, strlen (buf));
if (len == (size_t) - 1)
{
perror ("write");
ret = -1;
}
else
{
ret = ftruncate (fd, len);
if (ret)
perror ("ftruncate");
}
}
ret |= do_lock (fd, F_UNLCK);
ret |= close (fd);
return ret;
}
int
maildir_sync (mailbox_t * mbox)
{
@ -190,6 +335,7 @@ maildir_sync (mailbox_t * mbox)
char path[_POSIX_PATH_MAX];
char oldpath[_POSIX_PATH_MAX];
char *p;
int ret = 0;
if (mbox->changed)
{
@ -219,7 +365,11 @@ maildir_sync (mailbox_t * mbox)
}
}
}
return 0;
if (mbox->maxuidchanged)
ret = update_maxuid (mbox);
return ret;
}
int
@ -242,7 +392,7 @@ maildir_set_uidvalidity (mailbox_t * mbox, unsigned int uidvalidity)
ret = write (fd, buf, strlen (buf));
if (ret == -1)
perror("write");
perror ("write");
else if ((size_t) ret != strlen (buf))
ret = -1;
else
@ -250,7 +400,7 @@ maildir_set_uidvalidity (mailbox_t * mbox, unsigned int uidvalidity)
if (close (fd))
{
perror("close");
perror ("close");
ret = -1;
}

19
main.c
View File

@ -95,6 +95,7 @@ config_defaults (config_t * conf)
conf->port = global.port;
conf->box = global.box;
conf->host = global.host;
conf->max_size = global.max_size;
#if HAVE_LIBSSL
conf->require_ssl = global.require_ssl;
conf->use_imaps = global.use_imaps;
@ -206,6 +207,13 @@ load_config (char *where)
if (*cur)
(*cur)->alias = strdup (p);
}
else if (!strncasecmp ("maxsize", buf, 7))
{
if (*cur)
(*cur)->max_size = atol (p);
else
global.max_size = atol (p);
}
#if HAVE_LIBSSL
else if (!strncasecmp ("CertificateFile", buf, 15))
{
@ -285,6 +293,7 @@ main (int argc, char **argv)
global.port = 143;
global.box = "INBOX";
global.user = strdup (pw->pw_name);
global.max_size = 100000;
#if HAVE_LIBSSL
/* this will probably annoy people, but its the best default just in
* case people forget to turn it on
@ -380,16 +389,14 @@ main (int argc, char **argv)
exit (1);
}
imap = imap_open (box, fast);
imap = imap_open (box, fast ? mail->maxuid + 1 : 1);
if (!imap)
exit (1);
puts ("Synchronizing");
i = 0;
i |= (fast) ? SYNC_FAST : 0;
i |= (delete) ? SYNC_DELETE : 0;
i |= (expunge) ? SYNC_EXPUNGE : 0;
if (sync_mailbox (mail, imap, i))
i = delete ? SYNC_DELETE : 0;
i |= expunge ? SYNC_EXPUNGE : 0;
if (sync_mailbox (mail, imap, i, box->max_size))
exit (1);
if (!fast)

54
sync.c
View File

@ -31,7 +31,7 @@
static unsigned int MaildirCount = 0;
static message_t *
message_t *
find_msg (message_t * list, unsigned int uid)
{
for (; list; list = list->next)
@ -41,7 +41,7 @@ find_msg (message_t * list, unsigned int uid)
}
int
sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags)
sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags, unsigned int max_size)
{
message_t *cur;
message_t *tmp;
@ -69,12 +69,21 @@ sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags)
return -1;
}
if (mbox->maxuid == (unsigned int) -1 || imap->maxuid > mbox->maxuid)
{
mbox->maxuid = imap->maxuid;
mbox->maxuidchanged = 1;
}
/* if we are --fast mode, the mailbox wont have been loaded, so
* this next step is skipped.
*/
for (cur = mbox->msgs; cur; cur = cur->next)
{
tmp = find_msg (imap->msgs, cur->uid);
if (!tmp)
{
printf ("warning, uid %d doesn't exist on server\n", cur->uid);
printf ("Warning, uid %d doesn't exist on server\n", cur->uid);
if (flags & SYNC_DELETE)
{
cur->flags |= D_DELETED;
@ -85,25 +94,22 @@ sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags)
}
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)))
{
/* check if local flags are different from server flags.
* ignore \Recent and \Draft
*/
if (cur->flags != (tmp->flags & ~(D_RECENT | D_DRAFT)))
{
/* set local flags that don't exist on the server */
if (!(tmp->flags & D_DELETED) && (cur->flags & D_DELETED))
imap->deleted++;
imap_set_flags (imap, cur->uid, cur->flags & ~tmp->flags);
/* set local flags that don't exist on the server */
if (!(tmp->flags & D_DELETED) && (cur->flags & D_DELETED))
imap->deleted++;
imap_set_flags (imap, cur->uid, cur->flags & ~tmp->flags);
/* update local flags */
if((cur->flags & D_DELETED) == 0 && (tmp->flags & D_DELETED))
mbox->deleted++;
cur->flags |= (tmp->flags & ~(D_RECENT | D_DRAFT));
cur->changed = 1;
mbox->changed = 1;
}
/* update local flags */
if((cur->flags & D_DELETED) == 0 && (tmp->flags & D_DELETED))
mbox->deleted++;
cur->flags |= (tmp->flags & ~(D_RECENT | D_DRAFT));
cur->changed = 1;
mbox->changed = 1;
}
}
@ -124,6 +130,13 @@ sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags)
continue;
}
if (max_size && cur->size > max_size)
{
printf ("Warning, message skipped because it is too big (%u)\n",
cur->size);
continue;
}
for (;;)
{
/* create new file */
@ -150,7 +163,6 @@ sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags)
(cur->flags & D_ANSWERED) ? "R" : "",
(cur->flags & D_SEEN) ? "S" : "",
(cur->flags & D_DELETED) ? "T" : "");
}
/* give some visual feedback that something is happening */