commit 4f17ae1c3dc738cd4da5c61c2d5e0789cb8315b5 Author: Oswald Buddenhagen <o...@kde.org> Date: Sat Aug 11 18:34:46 2012 +0200
add support for hierarchical mailboxes configure.in | 2 +- src/drv_imap.c | 142 +++++++++++++++++++++++++++++++++------------ src/drv_maildir.c | 139 ++++++++++++++++++++++++++++++++++---------- src/isync.h | 13 ++++- src/main.c | 11 +++- src/mbsync.1 | 13 ++++ src/util.c | 36 +++++++++++ 7 files changed, 283 insertions(+), 73 deletions(-) diff --git a/configure.in b/configure.in index b16eb5d..3e074bf 100644 --- a/configure.in +++ b/configure.in @@ -12,7 +12,7 @@ fi CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE" AC_CHECK_HEADERS(sys/poll.h sys/select.h) -AC_CHECK_FUNCS(vasprintf) +AC_CHECK_FUNCS(vasprintf memrchr) AC_CHECK_LIB(socket, socket, [SOCK_LIBS="-lsocket"]) AC_CHECK_LIB(nsl, inet_ntoa, [SOCK_LIBS="$SOCK_LIBS -lnsl"]) diff --git a/src/drv_imap.c b/src/drv_imap.c index 0c15507..692afe5 100644 --- a/src/drv_imap.c +++ b/src/drv_imap.c @@ -50,6 +50,7 @@ typedef struct imap_store_conf { store_conf_t gen; imap_server_conf_t *server; unsigned use_namespace:1; + char delimiter; } imap_store_conf_t; typedef struct imap_message { @@ -82,6 +83,7 @@ typedef struct imap_store { /* trash folder's existence is not confirmed yet */ enum { TrashUnknown, TrashChecking, TrashKnown } trashnc; unsigned got_namespace:1; + char delimiter; /* hierarchy delimiter */ list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */ message_t **msgapp; /* FETCH results */ unsigned caps; /* CAPABILITY results */ @@ -808,21 +810,63 @@ parse_list_rsp( imap_store_t *ctx, char *cmd ) return; } free_list( list ); - (void) next_arg( &cmd ); /* skip delimiter */ arg = next_arg( &cmd ); - l = strlen( ctx->gen.conf->path ); - if (memcmp( arg, ctx->gen.conf->path, l )) - return; - arg += l; - if (l && !strcmp( arg, "INBOX" )) { - warn( "IMAP warning: ignoring INBOX in %s\n", ctx->gen.conf->path ); - return; + if (!ctx->delimiter) + ctx->delimiter = *arg; + arg = next_arg( &cmd ); + if (memcmp( arg, "INBOX", 5 ) || (arg[5] && arg[5] != ctx->delimiter)) { + l = strlen( ctx->gen.conf->path ); + if (memcmp( arg, ctx->gen.conf->path, l )) + return; + arg += l; + if (!memcmp( arg, "INBOX", 5 ) && (!arg[5] || arg[5] == ctx->delimiter)) { + if (!arg[5]) + warn( "IMAP warning: ignoring INBOX in %s\n", ctx->gen.conf->path ); + return; + } } if (!memcmp( arg + strlen( arg ) - 5, ".lock", 5 )) /* workaround broken servers */ return; + if (map_name( arg, ctx->delimiter, '/') < 0) { + warn( "IMAP warning: ignoring mailbox %s (reserved character '/' in name)\n", arg ); + return; + } add_string_list( &ctx->gen.boxes, arg ); } +static int +prepare_name( char *buf, const imap_store_t *ctx, const char *prefix, const char *name ) +{ + int pl; + + nfsnprintf( buf, 1024, "%s%n%s", prefix, &pl, name ); + switch (map_name( buf + pl, '/', ctx->delimiter )) { + case -1: + error( "IMAP error: mailbox name %s contains server's hierarchy delimiter\n", buf + pl ); + return -1; + case -2: + error( "IMAP error: server's hierarchy delimiter not known\n" ); + return -1; + default: + return 0; + } +} + +static int +prepare_box( char *buf, const imap_store_t *ctx ) +{ + const char *name = ctx->gen.name; + + return prepare_name( buf, ctx, + (!memcmp( name, "INBOX", 5 ) && (!name[5] || name[5] == '/')) ? "" : ctx->prefix, name ); +} + +static int +prepare_trash( char *buf, const imap_store_t *ctx ) +{ + return prepare_name( buf, ctx, ctx->prefix, ctx->gen.conf->trash ); +} + struct imap_cmd_trycreate { struct imap_cmd gen; struct imap_cmd *orig_cmd; @@ -1157,6 +1201,7 @@ imap_open_store( store_conf_t *conf, ctx->gen.boxes = 0; ctx->gen.listed = 0; ctx->gen.conf = conf; + ctx->delimiter = 0; ctx->callbacks.imap_open = cb; ctx->callback_aux = aux; set_bad_callback( &ctx->gen, (void (*)(void *))imap_open_store_bail, ctx ); @@ -1367,10 +1412,9 @@ imap_open_store_namespace( imap_store_t *ctx ) { imap_store_conf_t *cfg = (imap_store_conf_t *)ctx->gen.conf; - ctx->prefix = ""; - if (*cfg->gen.path) - ctx->prefix = cfg->gen.path; - else if (cfg->use_namespace && CAP(NAMESPACE)) { + ctx->prefix = cfg->gen.path; + ctx->delimiter = cfg->delimiter; + if (((!*ctx->prefix && cfg->use_namespace) || !cfg->delimiter) && CAP(NAMESPACE)) { /* get NAMESPACE info */ if (!ctx->got_namespace) imap_exec( ctx, 0, imap_open_store_namespace_p2, "NAMESPACE" ); @@ -1395,11 +1439,20 @@ imap_open_store_namespace_p2( imap_store_t *ctx, struct imap_cmd *cmd ATTR_UNUSE static void imap_open_store_namespace2( imap_store_t *ctx ) { - /* XXX for now assume personal namespace */ - if (is_list( ctx->ns_personal ) && - is_list( ctx->ns_personal->child ) && - is_atom( ctx->ns_personal->child->child )) - ctx->prefix = ctx->ns_personal->child->child->val; + imap_store_conf_t *cfg = (imap_store_conf_t *)ctx->gen.conf; + list_t *nsp, *nsp_1st, *nsp_1st_ns, *nsp_1st_dl; + + /* XXX for now assume 1st personal namespace */ + if (is_list( (nsp = ctx->ns_personal) ) && + is_list( (nsp_1st = nsp->child) ) && + is_atom( (nsp_1st_ns = nsp_1st->child) ) && + is_atom( (nsp_1st_dl = nsp_1st_ns->next) )) + { + if (!*ctx->prefix && cfg->use_namespace) + ctx->prefix = nsp_1st_ns->val; + if (!ctx->delimiter) + ctx->delimiter = *nsp_1st_dl->val; + } imap_open_store_finalize( ctx ); } @@ -1446,15 +1499,14 @@ imap_select( store_t *gctx, int create, { imap_store_t *ctx = (imap_store_t *)gctx; struct imap_cmd_simple *cmd; - const char *prefix; + char buf[1024]; free_generic_messages( gctx->msgs ); gctx->msgs = 0; - if (!strcmp( gctx->name, "INBOX" )) { - prefix = ""; - } else { - prefix = ctx->prefix; + if (prepare_box( buf, ctx ) < 0) { + cb( DRV_BOX_BAD, aux ); + return; } ctx->gen.uidnext = 0; @@ -1463,7 +1515,7 @@ imap_select( store_t *gctx, int create, cmd->gen.param.create = create; cmd->gen.param.trycreate = 1; imap_exec( ctx, &cmd->gen, imap_done_simple_box, - "SELECT \"%s%s\"", prefix, gctx->name ); + "SELECT \"%s\"", buf ); } /******************* imap_load *******************/ @@ -1636,13 +1688,17 @@ imap_trash_msg( store_t *gctx, message_t *msg, { imap_store_t *ctx = (imap_store_t *)gctx; struct imap_cmd_simple *cmd; + char buf[1024]; INIT_IMAP_CMD(imap_cmd_simple, cmd, cb, aux) cmd->gen.param.create = 1; cmd->gen.param.to_trash = 1; + if (prepare_trash( buf, ctx ) < 0) { + cb( DRV_BOX_BAD, aux ); + return; + } imap_exec( ctx, &cmd->gen, imap_done_simple_msg, - "UID COPY %d \"%s%s\"", - msg->uid, ctx->prefix, gctx->conf->trash ); + "UID COPY %d \"%s\"", msg->uid, buf ); } /******************* imap_store_msg *******************/ @@ -1655,9 +1711,8 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash, { imap_store_t *ctx = (imap_store_t *)gctx; struct imap_cmd_out_uid *cmd; - const char *prefix, *box; int d; - char flagstr[128]; + char flagstr[128], buf[1024]; d = 0; if (data->flags) { @@ -1672,16 +1727,20 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash, cmd->out_uid = -2; if (to_trash) { - box = gctx->conf->trash; - prefix = ctx->prefix; cmd->gen.param.create = 1; cmd->gen.param.to_trash = 1; + if (prepare_trash( buf, ctx ) < 0) { + cb( DRV_BOX_BAD, -1, aux ); + return; + } } else { - box = gctx->name; - prefix = !strcmp( box, "INBOX" ) ? "" : ctx->prefix; + if (prepare_box( buf, ctx ) < 0) { + cb( DRV_BOX_BAD, -1, aux ); + return; + } } imap_exec( ctx, &cmd->gen, imap_store_msg_p2, - "APPEND \"%s%s\" %s", prefix, box, flagstr ); + "APPEND \"%s\" %s", buf, flagstr ); } static void @@ -1710,15 +1769,20 @@ imap_find_new_msgs( store_t *gctx, /******************* imap_list *******************/ static void -imap_list( store_t *gctx, +imap_list( store_t *gctx, int flags, void (*cb)( int sts, void *aux ), void *aux ) { imap_store_t *ctx = (imap_store_t *)gctx; - struct imap_cmd_simple *cmd; - - INIT_IMAP_CMD(imap_cmd_simple, cmd, cb, aux) - imap_exec( ctx, &cmd->gen, imap_done_simple_box, - "LIST \"\" \"%s%%\"", ctx->prefix ); + struct imap_cmd_refcounted_state *sts = imap_refcounted_new_state( cb, aux ); + + if (((flags & LIST_PATH) && + imap_exec( ctx, imap_refcounted_new_cmd( sts ), imap_refcounted_done_box, + "LIST \"\" \"%s*\"", ctx->prefix ) < 0) || + ((flags & LIST_INBOX) && (!(flags & LIST_PATH) || *ctx->prefix) && + imap_exec( ctx, imap_refcounted_new_cmd( sts ), imap_refcounted_done_box, + "LIST \"\" INBOX*" ) < 0)) + {} + imap_refcounted_done( sts ); } /******************* imap_cancel *******************/ @@ -1853,6 +1917,8 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep, int *err ) store->use_namespace = parse_bool( cfg ); else if (!strcasecmp( "Path", cfg->cmd )) store->gen.path = nfstrdup( cfg->val ); + else if (!strcasecmp( "PathDelimiter", cfg->cmd )) + store->delimiter = *cfg->val; else parse_generic_store( &store->gen, cfg, err ); continue; diff --git a/src/drv_maildir.c b/src/drv_maildir.c index d5e740d..74d7b9c 100644 --- a/src/drv_maildir.c +++ b/src/drv_maildir.c @@ -94,6 +94,29 @@ maildir_parse_flags( const char *base ) return flags; } +static char * +maildir_join_path( const char *prefix, const char *box ) +{ + char *out, *p; + int pl, bl, n; + char c; + + pl = strlen( prefix ); + for (bl = 0, n = 0; (c = box[bl]); bl++) + if (c == '/') + n++; + out = nfmalloc( pl + bl + n + 1 ); + memcpy( out, prefix, pl ); + p = out + pl; + while ((c = *box++)) { + *p++ = c; + if (c == '/') + *p++ = '.'; + } + *p = 0; + return out; +} + static void maildir_open_store( store_conf_t *conf, void (*cb)( store_t *ctx, void *aux ), void *aux ) @@ -109,7 +132,8 @@ maildir_open_store( store_conf_t *conf, ctx = nfcalloc( sizeof(*ctx) ); ctx->gen.conf = conf; ctx->uvfd = -1; - nfasprintf( &ctx->trash, "%s%s", conf->path, conf->trash ); + if (conf->trash) + ctx->trash = maildir_join_path( conf->path, conf->trash ); cb( &ctx->gen, aux ); } @@ -168,40 +192,87 @@ maildir_invoke_bad_callback( store_t *ctx ) ctx->bad_callback( ctx->bad_callback_aux ); } -static void -maildir_list( store_t *gctx, - void (*cb)( int sts, void *aux ), void *aux ) +static int maildir_list_part( store_t *gctx, int doInbox, int *flags ); + +static int +maildir_list_recurse( store_t *gctx, int isBox, int *flags, const char *inbox, + char *path, int pathLen, char *name, int nameLen ) { DIR *dir; + int pl, nl; struct dirent *de; + struct stat st; - if (!(dir = opendir( gctx->conf->path ))) { - sys_error( "Maildir error: cannot list %s", gctx->conf->path ); - maildir_invoke_bad_callback( gctx ); - cb( DRV_CANCELED, aux ); - return; + if (isBox) { + nfsnprintf( path + pathLen, _POSIX_PATH_MAX - pathLen, "/cur" ); + if (stat( path, &st ) || !S_ISDIR(st.st_mode)) + return 0; + path[pathLen] = 0; + add_string_list( &gctx->boxes, name ); + name[nameLen++] = '/'; + } + if (!(dir = opendir( path ))) { + sys_error( "Maildir error: cannot list %s", path ); + return -1; } while ((de = readdir( dir ))) { - const char *inbox = ((maildir_store_conf_t *)gctx->conf)->inbox; - int bl, isibx; - struct stat st; - char buf[PATH_MAX]; - - if (*de->d_name == '.') - continue; - bl = nfsnprintf( buf, sizeof(buf), "%s%s/cur", gctx->conf->path, de->d_name ); - if (stat( buf, &st ) || !S_ISDIR(st.st_mode)) - continue; - isibx = !memcmp( buf, inbox, bl - 4 ) && !inbox[bl - 4]; - if (!isibx && !strcmp( de->d_name, "INBOX" )) { - warn( "Maildir warning: ignoring INBOX in %s\n", gctx->conf->path ); - continue; + const char *ent = de->d_name; + pl = pathLen + nfsnprintf( path + pathLen, _POSIX_PATH_MAX - pathLen, "%s", ent ); + if (inbox && !memcmp( path, inbox, pl ) && !inbox[pl]) { + if (maildir_list_part( gctx, 1, flags ) < 0) + return -1; + } else { + if (!memcmp( ent, "INBOX", 6 )) { + path[pathLen] = 0; + warn( "Maildir warning: ignoring INBOX in %s\n", path ); + continue; + } + if (*ent == '.') { + if (!isBox) + continue; + ent++; + } else { + if (isBox) + continue; + } + nl = nameLen + nfsnprintf( name + nameLen, _POSIX_PATH_MAX - nameLen, "%s", ent ); + if (maildir_list_recurse( gctx, 1, flags, inbox, path, pl, name, nl ) < 0) + return -1; } - add_string_list( &gctx->boxes, isibx ? "INBOX" : de->d_name ); } closedir (dir); + return 0; +} - cb( DRV_OK, aux ); +static int +maildir_list_part( store_t *gctx, int doInbox, int *flags ) +{ + int pl, nl; + const char *inbox = ((maildir_store_conf_t *)gctx->conf)->inbox; + char path[_POSIX_PATH_MAX], name[_POSIX_PATH_MAX]; + + if (doInbox) { + *flags &= ~LIST_INBOX; + pl = nfsnprintf( path, _POSIX_PATH_MAX, "%s", inbox ); + nl = nfsnprintf( name, _POSIX_PATH_MAX, "INBOX" ); + return maildir_list_recurse( gctx, 1, flags, 0, path, pl, name, nl ); + } else { + pl = nfsnprintf( path, _POSIX_PATH_MAX, "%s", gctx->conf->path ); + return maildir_list_recurse( gctx, 0, flags, inbox, path, pl, name, 0 ); + } +} + +static void +maildir_list( store_t *gctx, int flags, + void (*cb)( int sts, void *aux ), void *aux ) +{ + if (((flags & LIST_PATH) && maildir_list_part( gctx, 0, &flags ) < 0) || + ((flags & LIST_INBOX) && maildir_list_part( gctx, 1, &flags ) < 0)) { + maildir_invoke_bad_callback( gctx ); + cb( DRV_CANCELED, aux ); + } else { + cb( DRV_OK, aux ); + } } static const char *subdirs[] = { "cur", "new", "tmp" }; @@ -237,8 +308,9 @@ maildir_validate( const char *box, int create, maildir_store_t *ctx ) { DIR *dirp; struct dirent *entry; + char *p; time_t now; - int i, bl; + int i, bl, ret; struct stat st; char buf[_POSIX_PATH_MAX]; @@ -246,6 +318,13 @@ maildir_validate( const char *box, int create, maildir_store_t *ctx ) if (stat( buf, &st )) { if (errno == ENOENT) { if (create) { + p = memrchr( buf, '/', bl - 1 ); + if (*(p + 1) == '.') { + *p = 0; + if ((ret = maildir_validate( buf, 1, ctx )) != DRV_OK) + return ret; + *p = '/'; + } if (mkdir( buf, 0700 )) { sys_error( "Maildir error: cannot create mailbox '%s'", buf ); maildir_invoke_bad_callback( &ctx->gen ); @@ -822,10 +901,10 @@ maildir_select( store_t *gctx, int create, #ifdef USE_DB ctx->db = 0; #endif /* USE_DB */ - if (!strcmp( gctx->name, "INBOX" )) - gctx->path = nfstrdup( ((maildir_store_conf_t *)gctx->conf)->inbox ); - else - nfasprintf( &gctx->path, "%s%s", gctx->conf->path, gctx->name ); + gctx->path = + (!memcmp( gctx->name, "INBOX", 5 ) && (!gctx->name[5] || gctx->name[5] == '/')) ? + maildir_join_path( ((maildir_store_conf_t *)gctx->conf)->inbox, gctx->name + 5 ) : + maildir_join_path( gctx->conf->path, gctx->name ); if ((ret = maildir_validate( gctx->path, create, ctx )) != DRV_OK) { cb( ret, aux ); diff --git a/src/isync.h b/src/isync.h index 9b23e50..28b3b8e 100644 --- a/src/isync.h +++ b/src/isync.h @@ -259,6 +259,9 @@ typedef struct { */ #define DRV_CRLF 1 +#define LIST_PATH 1 +#define LIST_INBOX 2 + struct driver { int flags; @@ -283,8 +286,8 @@ struct driver { * Pending commands will have their callbacks synchronously invoked with DRV_CANCELED. */ void (*cancel_store)( store_t *ctx ); - /* List the mailboxes in this store. */ - void (*list)( store_t *ctx, + /* List the mailboxes in this store. Flags are ORed LIST_* values. */ + void (*list)( store_t *ctx, int flags, void (*cb)( int sts, void *aux ), void *aux ); /* Invoked before select(), this informs the driver which operations (OP_*) @@ -415,6 +418,10 @@ void free_string_list( string_list_t *list ); void free_generic_messages( message_t * ); +#ifndef HAVE_MEMRCHR +void *memrchr( const void *s, int c, size_t n ); +#endif + void *nfmalloc( size_t sz ); void *nfcalloc( size_t sz ); void *nfrealloc( void *mem, size_t sz ); @@ -426,6 +433,8 @@ void ATTR_NORETURN oob( void ); char *expand_strdup( const char *s ); +int map_name( char *arg, char in, char out ); + void sort_ints( int *arr, int len ); void arc4_init( void ); diff --git a/src/main.c b/src/main.c index a636ab1..d3d0302 100644 --- a/src/main.c +++ b/src/main.c @@ -129,7 +129,7 @@ matches( const char *t, const char *p ) } else if (*p == '%') { p++; do { - if (*t == '.' || *t == '/') /* this is "somewhat" hacky ... */ + if (*t == '/') return 0; if (matches( t, p )) return 1; @@ -690,6 +690,8 @@ static void store_opened( store_t *ctx, void *aux ) { MVARS(aux) + string_list_t *cpat; + int flags; if (!ctx) { mvars->ret = mvars->skip = 1; @@ -699,8 +701,13 @@ store_opened( store_t *ctx, void *aux ) } mvars->ctx[t] = ctx; if (!mvars->skip && !mvars->boxlist && mvars->chan->patterns && !ctx->listed) { + for (flags = 0, cpat = mvars->chan->patterns; cpat; cpat = cpat->next) { + const char *pat = cpat->string; + if (*pat != '!') + flags |= (!memcmp( pat, "INBOX", 5 ) && (!pat[5] || pat[5] == '/')) ? LIST_INBOX : LIST_PATH; + } set_bad_callback( ctx, store_bad, AUX ); - mvars->drv[t]->list( ctx, store_listed, AUX ); + mvars->drv[t]->list( ctx, flags, store_listed, AUX ); } else { mvars->state[t] = ST_OPEN; sync_chans( mvars, E_OPEN ); diff --git a/src/mbsync.1 b/src/mbsync.1 index 03178f3..5d41e1a 100644 --- a/src/mbsync.1 +++ b/src/mbsync.1 @@ -105,6 +105,13 @@ There are two auxiliary object classes: Accounts and Groups. An Account describes the connection part of remote Stores, so a server connection can be shared between multiple Stores. A Group aggregates multiple Channels to save typing on the command line. +.P +File system locations (in particular, \fBPath\fR and \fBInbox\fR) use the +Store's internal path separators, which may be slashes, periods, etc., or +even combinations thereof. +.br +Mailbox names, OTOH, always use canonical path separators, which are +Unix-like forward slashes. .. .SS All Stores These options can be used in all supported Store types. @@ -140,6 +147,7 @@ If \fIsize\fR is 0, the maximum message size is \fBunlimited\fR. Create a virtual mailbox (relative to \fBPath\fR), which is backed by the \fBINBOX\fR. Makes sense in conjunction with \fBPatterns\fR in the Channels section. +This virtual mailbox does not support subfolders. .. .TP \fBTrash\fR \fImailbox\fR @@ -306,6 +314,11 @@ mailbox names. Disabling this makes sense for some broken IMAP servers. This option is meaningless if a \fBPath\fR was specified. (Default: \fIyes\fR) .. +.TP +\fBPathDelimiter\fR \fIdelim\fR +Specify the server's hierarchy delimiter character. +(Default: taken from the server's first "personal" NAMESPACE) +.. .SS Channels .TP \fBChannel\fR \fIname\fR diff --git a/src/util.c b/src/util.c index d3f1638..c6a4a00 100644 --- a/src/util.c +++ b/src/util.c @@ -229,6 +229,19 @@ vasprintf( char **strp, const char *fmt, va_list ap ) } #endif +#ifndef HAVE_MEMRCHR +void * +memrchr( const void *s, int c, size_t n ) +{ + u_char *b = (u_char *)s, *e = b + n; + + while (--e >= b) + if (*e == c) + return (void *)e; + return 0; +} +#endif + void oob( void ) { @@ -378,6 +391,29 @@ expand_strdup( const char *s ) return nfstrdup( s ); } +/* Return value: 0 = ok, -1 = out found in arg, -2 = in found in arg but no out specified */ +int +map_name( char *arg, char in, char out ) +{ + int l, k; + + if (!in || in == out) + return 0; + for (l = 0; arg[l]; l++) + if (arg[l] == in) { + if (!out) + return -2; + arg[l] = out; + } else if (arg[l] == out) { + /* restore original name for printing error message */ + for (k = 0; k < l; k++) + if (arg[k] == out) + arg[k] = in; + return -1; + } + return 0; +} + static int compare_ints( const void *l, const void *r ) { ------------------------------------------------------------------------------ Live Security Virtual Conference Exclusive live event will cover all the ways today's security and threat landscape has changed and how IT managers can respond. Discussions will include endpoint security, mobile security and the latest in malware threats. http://www.accelacomm.com/jaw/sfrnl04242012/114/50122263/ _______________________________________________ isync-devel mailing list isync-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/isync-devel