diff --git a/imap/imapd.c b/imap/imapd.c index fa6402b..6de4cde 100644 --- a/imap/imapd.c +++ b/imap/imapd.c @@ -171,6 +171,8 @@ static int imapd_starttls_done = 0; /* have we done a successful starttls? */ static void *imapd_tls_comp = NULL; /* TLS compression method, if any */ static int imapd_compress_done = 0; /* have we done a successful compress? */ const char *plaintextloginalert = NULL; +struct keyvalue **xlist_attrs = NULL; /* per user xlist special use conf */ +unsigned long num_xlist_attrs = 0; #ifdef HAVE_SSL /* our tls connection, if any */ @@ -738,6 +740,16 @@ static void imapd_reset(void) imapd_starttls_done = 0; plaintextloginalert = NULL; + if (xlist_attrs) { + /* Cleanup the xlist attr(s) */ + while (num_xlist_attrs) { + struct keyvalue *cur_xlistattr = xlist_attrs[--num_xlist_attrs]; + free(cur_xlistattr); + } + free(xlist_attrs); + xlist_attrs = NULL; + } + if(saslprops.iplocalport) { free(saslprops.iplocalport); saslprops.iplocalport = NULL; @@ -1095,6 +1107,15 @@ void fatal(const char *s, int code) free(stage); } + if (xlist_attrs) { + /* Cleanup the xlist attr(s) */ + while (num_xlist_attrs) { + struct keyvalue *cur_xlistattr = xlist_attrs[--num_xlist_attrs]; + free(cur_xlistattr); + } + free(xlist_attrs); + } + syslog(LOG_ERR, "Fatal error: %s", s); shut_down(code); } @@ -9817,6 +9838,103 @@ static void xlist_check(const char *key, const char *val, void *rock) r->sep = " "; } +static void xlist_read_config() +{ + unsigned GROWSIZE = 4096; + char *filename = user_hash_xlist(imapd_userid); + FILE *infile = NULL; + int lineno = 0; + char *buf = NULL; + char *p, *q, *key; + char internal_mailboxname[MAX_MAILBOX_BUFFER]; + unsigned bufsize = GROWSIZE, len = 0; + unsigned long numalloc = 1; + xlist_attrs = xmalloc(numalloc * sizeof(struct keyvalue *)); + num_xlist_attrs = 0; + struct keyvalue *curr_attr = NULL; + + infile = fopen(filename, "r"); + free(filename); + if (!infile) { + /* can't read file, then xlist_attrs is ready to go */ + return; + } + buf = xmalloc(bufsize); + + /* read lines of the config file */ + while (fgets(buf+len, bufsize-len, infile)) { + if (buf[len]) { + len = strlen(buf); + if (buf[len-1] == '\n') { + /* end of line */ + buf[--len] = '\0'; + } else if (!feof(infile) && len == bufsize-1) { + /* line is longer than the buffer */ + bufsize += GROWSIZE; + buf = xrealloc(buf, bufsize); + continue; + } + } + len = 0; + + /* remove leading whitespace */ + for (p = buf; *p && Uisspace(*p); p++); + + /* skip comments */ + if (!*p || *p == '#') continue; + + /* go over the key */ + key = p; + while (*p && (Uisalnum(*p) || *p == '-' || *p == '_')) { + if (Uisupper(*p)) *p = tolower((unsigned char) *p); + p++; + } + if (*p != ':') { + /* keys must end with :, ignoring wrong line */ + continue; + } + *p++ = '\0'; + + /* remove leading whitespace for the value */ + while (*p && Uisspace(*p)) p++; + + /* remove trailing whitespace in the value */ + for (q = p + strlen(p) - 1; q > p && Uisspace(*q); q--) { + *q = '\0'; + } + + if (!*p) { + /* empty key, ignoring wrong line */ + continue; + } + + /* set curr_attr */ + if (!curr_attr) { + curr_attr = *xlist_attrs = xzmalloc(sizeof(struct keyvalue)); + num_xlist_attrs++; + } else { + /* allocate more memory if needed */ + if (num_xlist_attrs == numalloc) { + numalloc *= 2; + xlist_attrs = xrealloc(xlist_attrs, + numalloc * sizeof(struct keyvalue *)); + } + curr_attr = xlist_attrs[num_xlist_attrs] = xzmalloc(sizeof(struct keyvalue)); + num_xlist_attrs++; + } + + /* we need to convert the key (mboxname) to internal, which + * is the way it will be compared */ + (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, p, + imapd_userid, internal_mailboxname); + curr_attr->key = xstrdup(key); + curr_attr->value = xstrdup(internal_mailboxname); + } + + fclose(infile); + free(buf); +} + static void xlist_flags(const char *mboxname, char *sep) { char inboxname[MAX_MAILBOX_PATH+1]; @@ -9826,23 +9944,34 @@ static void xlist_flags(const char *mboxname, char *sep) imapd_userid, inboxname); inboxlen = strlen(inboxname); - /* doesn't match inbox, not xlistable */ - if (strncmp(mboxname, inboxname, inboxlen)) - return; - /* inbox */ - if (mboxname[inboxlen] == '\0') { + if (!strncmp(mboxname, inboxname, inboxlen) && mboxname[inboxlen] == '\0') { prot_printf(imapd_out, "%s\\Inbox", sep); + return; + } + + struct xlist_rock rock; + rock.sep = sep; + + /* Go over the global xlist special-use attrs */ + if (!strncmp(mboxname, inboxname, inboxlen) && mboxname[inboxlen] == '.') { + rock.mboxname = mboxname + inboxlen + 1; + config_foreachoverflowstring(xlist_check, &rock); } - /* subdir */ - else if (mboxname[inboxlen] == '.') { - struct xlist_rock rock; - rock.sep = sep; - rock.mboxname = mboxname + inboxlen + 1; - config_foreachoverflowstring(xlist_check, &rock); + + rock.mboxname = mboxname; + + /* check per user xlist special-use attributes, reading it from file + * the first time (lazy loading) */ + if (!xlist_attrs) { + xlist_read_config(); + } + + /* go over the per user xlist special-use attrs */ + unsigned long i = 0; + for(i = 0; i < num_xlist_attrs; i++) { + xlist_check(xlist_attrs[i]->key, xlist_attrs[i]->value, &rock); } - /* otherwise it's actually another user who matches for - * the substr. Ok to just print nothing */ } /* Print LIST or LSUB untagged response */ diff --git a/imap/user.c b/imap/user.c index c811cf9..f3157f8 100644 --- a/imap/user.c +++ b/imap/user.c @@ -84,6 +84,7 @@ #include "sync_log.h" #define FNAME_SUBSSUFFIX ".sub" +#define FNAME_XLISTSUFFIX ".xlist" #if 0 static int user_deleteacl(char *name, int matchlen, int maycreate, void* rock) @@ -510,4 +511,29 @@ char *user_hash_subs(const char *userid) return fname; } +/* hash the userid to a file containing the subscriptions for that user */ +char *user_hash_xlist(const char *userid) +{ + char *fname = xmalloc(strlen(config_dir) + sizeof(FNAME_DOMAINDIR) + + sizeof(FNAME_USERDIR) + strlen(userid) + + sizeof(FNAME_XLISTSUFFIX) + 10); + char c, *domain; + + if (config_virtdomains && (domain = strchr(userid, '@'))) { + char d = (char) dir_hash_c(domain+1, config_fulldirhash); + *domain = '\0'; /* split user@domain */ + c = (char) dir_hash_c(userid, config_fulldirhash); + sprintf(fname, "%s%s%c/%s%s%c/%s%s", config_dir, FNAME_DOMAINDIR, d, + domain+1, FNAME_USERDIR, c, userid, FNAME_XLISTSUFFIX); + *domain = '@'; /* replace '@' */ + } + else { + c = (char) dir_hash_c(userid, config_fulldirhash); + sprintf(fname, "%s%s%c/%s%s", config_dir, FNAME_USERDIR, c, userid, + FNAME_XLISTSUFFIX); + } + + return fname; +} + diff --git a/imap/user.h b/imap/user.h index 3733659..1cfc29e 100644 --- a/imap/user.h +++ b/imap/user.h @@ -76,4 +76,7 @@ int user_deletequotaroots(const char *user); /* find the subscriptions file for user */ char *user_hash_subs(const char *user); +/* find the xlist file for user */ +char *user_hash_xlist(const char *user); + #endif