From ba7650c9b748b5180a65469d164fbce0f6a09ae8 Mon Sep 17 00:00:00 2001 From: Michael Elkins Date: Thu, 21 Dec 2000 10:24:53 +0000 Subject: [PATCH] added generic IMAP list parser and rewrote imap_exec() to handle arbitrary data instead of hardcoded --- Makefile.am | 2 +- README | 13 +++- TODO | 2 + imap.c | 180 ++++++++++++++++++++++++++++++++++------------------ isync.h | 23 +++++++ list.c | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 330 insertions(+), 65 deletions(-) create mode 100644 list.c diff --git a/Makefile.am b/Makefile.am index 5608ccf..36f75ee 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,5 @@ bin_PROGRAMS=isync -isync_SOURCES=main.c imap.c sync.c maildir.c isync.h +isync_SOURCES=main.c imap.c sync.c maildir.c isync.h list.c man_MANS=isync.1 EXTRA_DIST=sample.isyncrc $(man_MANS) diff --git a/README b/README index 9d5c94c..13f2071 100644 --- a/README +++ b/README @@ -16,9 +16,10 @@ maintained, and all flags are synchronized. * Features: - * Supports imaps: (port 993) TLS/SSL connections - * Supports STARTTLS * Fast mode for fetching new mail only + * Supports imaps: (port 993) TLS/SSL connections + * Supports STARTTLS (RFC2595) + * Supports NAMESPACE (RFC2342) * Compatibility @@ -26,6 +27,14 @@ maintained, and all flags are synchronized. * Microsoft Exchange 2000 IMAP4rev1 server version 6.0.4417.0 +* Platforms + + ``isync'' has successfully be compiled under: + + * Linux 2.2.18 + * Solaris 2.7 + * OpenBSD 2.8 + * Requirements OpenSSL for TLS/SSL support (optional) diff --git a/TODO b/TODO index 295df15..6366c7a 100644 --- a/TODO +++ b/TODO @@ -1 +1,3 @@ add upload support to mirror local msgs on the server + +add support for syncing with other: and shared: via NAMESPACE diff --git a/imap.c b/imap.c index e05c59f..18ee0c6 100644 --- a/imap.c +++ b/imap.c @@ -149,6 +149,67 @@ buffer_gets (buffer_t * b, char **s) /* not reached */ } +static int +parse_fetch (imap_t * imap, list_t * list, message_t *cur) +{ + list_t *tmp; + + if (!is_list (list)) + return -1; + + for (tmp = list->child; tmp; tmp = tmp->next) + { + if (is_atom (tmp)) + { + if (!strcmp ("UID", tmp->val)) + { + tmp = tmp->next; + if (is_atom (tmp)) + cur->uid = atoi (tmp->val); + else + puts ("Error, unable to parse UID"); + } + else if (!strcmp ("FLAGS", tmp->val)) + { + tmp = tmp->next; + if (is_list (tmp)) + { + list_t *flags = tmp->child; + + for (; flags; flags = flags->next) + { + if (is_atom (flags)) + { + if (!strcmp ("\\Seen", flags->val)) + cur->flags |= D_SEEN; + else if (!strcmp ("\\Flagged", flags->val)) + cur->flags |= D_FLAGGED; + else if (!strcmp ("\\Deleted", flags->val)) + { + cur->flags |= D_DELETED; + imap->deleted++; + } + else if (!strcmp ("\\Answered", flags->val)) + cur->flags |= D_ANSWERED; + else if (!strcmp ("\\Draft", flags->val)) + cur->flags |= D_DRAFT; + else if (!strcmp ("\\Recent", flags->val)) + cur->flags |= D_RECENT; + else + printf ("Warning, unknown flag %s\n",flags->val); + } + else + puts ("Error, unable to parse FLAGS list"); + } + } + else + puts ("Error, unable to parse FLAGS"); + } + } + } + return 0; +} + static int imap_exec (imap_t * imap, const char *fmt, ...) { @@ -181,12 +242,18 @@ imap_exec (imap_t * imap, const char *fmt, ...) if (*arg == '*') { arg = next_arg (&cmd); - arg1 = next_arg (&cmd); + if (!arg) + { + puts ("Error, unable to parse untagged command"); + return -1; + } - if (arg1 && !strcmp ("EXISTS", arg1)) - imap->count = atoi (arg); - else if (arg1 && !strcmp ("RECENT", arg1)) - imap->recent = atoi (arg); + if (!strcmp ("NAMESPACE", arg)) + { + imap->ns_personal = parse_list (cmd, &cmd); + imap->ns_other = parse_list (cmd, &cmd); + imap->ns_shared = parse_list (cmd, 0); + } else if (!strcmp ("SEARCH", arg)) { if (!rec) @@ -195,10 +262,6 @@ imap_exec (imap_t * imap, const char *fmt, ...) while (*rec) rec = &(*rec)->next; } - /* need to add arg1 */ - *rec = calloc (1, sizeof (message_t)); - (*rec)->uid = atoi (arg1); - rec = &(*rec)->next; /* parse rest of `cmd' */ while ((arg = next_arg (&cmd))) { @@ -207,66 +270,41 @@ imap_exec (imap_t * imap, const char *fmt, ...) rec = &(*rec)->next; } } - else if (arg1 && !strcmp ("FETCH", arg1)) + else if ((arg1 = next_arg (&cmd))) { - if (!cur) - { - cur = &imap->msgs; - while (*cur) - cur = &(*cur)->next; - } - - /* new message - * * FETCH (UID FLAGS (...)) - */ - arg = next_arg (&cmd); /* (UID */ - arg = next_arg (&cmd); /* */ - *cur = calloc (1, sizeof (message_t)); - (*cur)->uid = atoi (arg); - - arg = next_arg (&cmd); /* FLAGS */ - if (!arg || strcmp ("FLAGS", arg)) + if (!strcmp ("EXISTS", arg1)) + imap->count = atoi (arg); + else if (!strcmp ("RECENT", arg1)) + imap->recent = atoi (arg); + else if (!strcmp ("FETCH", arg1)) { - printf ("FETCH parse error: expected FLAGS at %s\n", arg); - return -1; - } + list_t *list; - /* if we need to parse additional info, we should keep - * a copy of this `arg' pointer - */ + if (!cur) + { + cur = &imap->msgs; + while (*cur) + cur = &(*cur)->next; + } - cmd++; - arg = strchr (cmd, ')'); - if (!arg) - { - puts ("FETCH parse error"); - return -1; - } - *arg = 0; + list = parse_list (cmd, 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 = calloc (1, sizeof(message_t)); + if (parse_fetch (imap, list, *cur)) { - (*cur)->flags |= D_DELETED; - imap->deleted++; + free_list (list); + return -1; } - 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; + free_list (list); + + cur = &(*cur)->next; + } + } + else + { + puts ("Error, unable to parse untagged command"); + return -1; } } else if ((size_t) atol (arg) != Tag) @@ -344,6 +382,7 @@ imap_open (config_t * box, int fast) int s; struct sockaddr_in sin; struct hostent *he; + char *ns_prefix = 0; #if HAVE_LIBSSL int use_ssl = 0; #endif @@ -427,11 +466,28 @@ imap_open (config_t * box, int fast) puts ("Logging in..."); ret = imap_exec (imap, "LOGIN %s %s", box->user, box->pass); + + if (!ret) + { + /* get NAMESPACE info */ + if (!imap_exec (imap, "NAMESPACE")) + { + /* XXX for now assume personal namespace */ + if (is_list (imap->ns_personal) && + is_list(imap->ns_personal->child) && + is_atom(imap->ns_personal->child->child)) + { + ns_prefix = imap->ns_personal->child->child->val; + } + } + } + if (!ret) { fputs ("Selecting mailbox... ", stdout); fflush (stdout); - ret = imap_exec (imap, "SELECT %s", box->box); + ret = imap_exec (imap, "SELECT %s%s", + ns_prefix ? ns_prefix : "", box->box); if (!ret) printf ("%d messages, %d recent\n", imap->count, imap->recent); } diff --git a/isync.h b/isync.h index 8c92044..52f36ef 100644 --- a/isync.h +++ b/isync.h @@ -92,6 +92,18 @@ struct message unsigned int dead:1; /* message doesn't exist on the server */ }; +/* struct used for parsing IMAP lists */ +typedef struct _list list_t; + +#define NIL (void*)0x1 +#define LIST (void*)0x2 + +struct _list { + char *val; + list_t *next; + list_t *child; +}; + /* imap connection info */ typedef struct { @@ -105,6 +117,10 @@ typedef struct * UID to be used in a FETCH FLAGS command */ unsigned int deleted; /* # of deleted messages */ + /* NAMESPACE info */ + list_t *ns_personal; + list_t *ns_other; + list_t *ns_shared; } imap_t; @@ -135,3 +151,10 @@ 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 *); + +/* parse an IMAP list construct */ +list_t * parse_list (char *s, char **end); +int is_atom (list_t *list); +int is_list (list_t *list); +int is_nil (list_t *list); +void free_list (list_t *list); diff --git a/list.c b/list.c new file mode 100644 index 0000000..61abaed --- /dev/null +++ b/list.c @@ -0,0 +1,175 @@ +/* $Id$ + * + * isync - IMAP4 to maildir mailbox synchronizer + * Copyright (C) 2000 Michael R. Elkins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include "isync.h" + +static char * +skip_string (char *s) +{ + while (*s && *s != '"') + s++; + return s; +} + +list_t * +parse_list (char *s, char **end) +{ + int level = 1; + list_t *cur; + list_t **list; + char *b; + + cur = calloc (1, sizeof (list_t)); + while (isspace ((unsigned char) *s)) + s++; + if (*s == '(') + { + /* start of list. find the end of the list */ + s++; + b = s; /* save beginning */ + cur->val = LIST; + while (*s) + { + if (*s == '(') + { + level++; + } + else if (*s == ')') + { + level--; + if (level == 0) + break; + } + else if (*s == '"') + { + s = skip_string (s + 1); + if (!*s) + { + /* parse error */ + free (cur); + return NULL; + } + } + s++; + } + if (level != 0) + { + free (cur); /* parse error */ + return NULL; + } + *s++ = 0; + + list = &cur->child; + while (*b) + { + *list = parse_list (b, &b); + if (*list == NULL) + { + /* parse error */ + free (cur); + return NULL; + } + while (*list) + list = &(*list)->next; + } + } + else if (*s == '"') + { + /* quoted string */ + s++; + cur->val = s; + s = skip_string (s); + if (!*s) + { + /* parse error */ + free (cur); + return NULL; + } + *s++ = 0; + cur->val = strdup (cur->val); + } + else + { + /* atom */ + cur->val = s; + while (*s && !isspace ((unsigned char) *s)) + s++; + if (*s) + *s++ = 0; + if (strcmp ("NIL", cur->val)) + cur->val = strdup (cur->val); + else + cur->val = NIL; + } + if (end) + *end = s; + return cur; +} + +int +is_atom (list_t * list) +{ + return (list && list->val && list->val != NIL && list->val != LIST); +} + +int +is_list (list_t * list) +{ + return (list && list->val == LIST); +} + +int +is_nil (list_t * list) +{ + return (list && list->val == NIL); +} + +void +free_list (list_t * list) +{ + list_t *tmp; + + while (list) + { + tmp = list; + list = list->next; + if (is_list (list)) + free_list (tmp->child); + else if (is_atom (tmp)) + free (tmp->val); + free (tmp); + } +} + +#if TEST +int +main (int argc, char **argv) +{ + char buf[256]; + list_t *list; + + strcpy (buf, + "((compound list) atom NIL \"string with a (\" (another list))"); + list = parse_list (buf, 0); +} +#endif