diff --git a/imap/imapd.c b/imap/imapd.c index de67b46..4f50a8d 100644 --- a/imap/imapd.c +++ b/imap/imapd.c @@ -169,6 +169,9 @@ 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 */ static SSL *tls_conn = NULL; @@ -1966,6 +1969,38 @@ void cmdloop() (havepartition ? arg3.s : NULL)); /* snmp_increment(XFER_COUNT, 1);*/ } + else if (!strcmp(cmd.s, "Xlist")) { + struct listargs listargs; + + memset(&listargs, 0, sizeof(struct listargs)); + listargs.opts = LIST_CHILDREN|LIST_XLIST; +#ifdef ENABLE_LISTEXT + /* Check for and parse LISTEXT options */ + c = prot_getc(imapd_in); + if (c == '(') { + c = getlistopts(tag.s, &listargs.opts); + if (c == EOF) { + eatline(imapd_in, c); + continue; + } + } + else + prot_ungetc(c, imapd_in); +#endif /* ENABLE_LISTEXT */ + if (imapd_magicplus) listargs.opts += LIST_SUBSCRIBED; + + c = getastring(imapd_in, imapd_out, &arg1); + if (c != ' ') goto missingargs; + c = getastring(imapd_in, imapd_out, &arg2); + if (c == '\r') c = prot_getc(imapd_in); + if (c != '\n') goto extraargs; + + listargs.ref = arg1.s; + listargs.pat = arg2.s; + cmd_list(tag.s, &listargs); + + snmp_increment(LIST_COUNT, 1); + } else goto badcmd; break; @@ -9432,6 +9467,167 @@ static int mailboxdata(char *name, return 0; } +struct xlist_rock { + const char *mboxname; + const char *sep; +}; + +static void xlist_check(const char *key, const char *val, void *rock) +{ + struct xlist_rock *r = (struct xlist_rock *)rock; + char *flag; + + if (strncmp(key, "xlist-", 6)) + return; + + if (strcmp(val, r->mboxname)) + return; + + flag = xstrdup(key + 6); + lcase(flag); + flag[0] = toupper((unsigned char)flag[0]); + prot_printf(imapd_out, "%s\\%s", r->sep, flag); + free(flag); + + 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]; + int inboxlen; + + (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, "INBOX", + imapd_userid, inboxname); + inboxlen = strlen(inboxname); + + /* inbox */ + 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); + } + + 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); + } +} + /* * Issue a LIST or LSUB untagged response */ @@ -9446,6 +9642,7 @@ static void mstringdata(char *cmd, char *name, int matchlen, int maycreate, int lastnamehassub = 0; int c, mbtype; char mboxname[MAX_MAILBOX_BUFFER]; + char internal_name[MAX_MAILBOX_PATH+1]; /* We have to reset the sawuser flag before each list command. * Handle it as a dirty hack. @@ -9483,8 +9680,19 @@ static void mstringdata(char *cmd, char *name, int matchlen, int maycreate, prot_printf(imapd_out, "%s%s", nonexistent ? " " : "", lastnamehassub ? "\\HasChildren" : "\\HasNoChildren"); } + + (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, lastname, + imapd_userid, mboxname); + (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, mboxname, + imapd_userid, internal_name); + if (config_getswitch(IMAPOPT_SPECIALUSEALWAYS) || listopts & LIST_XLIST) { + char sep[2] = " "; + xlist_flags(internal_name, sep); + } + + prot_printf(imapd_out, ") \"%c\" ", imapd_namespace.hier_sep); - + (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, lastname, imapd_userid, mboxname); printstring(mboxname); @@ -9563,6 +9771,16 @@ static void mstringdata(char *cmd, char *name, int matchlen, int maycreate, if (listopts & LIST_CHILDREN) prot_printf(imapd_out, " \\HasChildren"); } + + (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, name, + imapd_userid, mboxname); + (*imapd_namespace.mboxname_tointernal)(&imapd_namespace, mboxname, + imapd_userid, internal_name); + if (config_getswitch(IMAPOPT_SPECIALUSEALWAYS) || listopts & LIST_XLIST) { + char sep[2] = " "; + xlist_flags(internal_name, sep); + } + prot_printf(imapd_out, ") \"%c\" ", imapd_namespace.hier_sep); (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, name, @@ -9579,6 +9797,7 @@ static void mstringdata(char *cmd, char *name, int matchlen, int maycreate, static int listdata(char *name, int matchlen, int maycreate, void *rock) { struct listargs *listargs = (struct listargs *) rock; + char *cmd; if (name && listargs->scan) { /* SCAN mailbox for content */ @@ -9660,8 +9879,15 @@ static int listdata(char *name, int matchlen, int maycreate, void *rock) } } - mstringdata(((listargs->opts & LIST_LSUB) ? "LSUB" : "LIST"), - name, matchlen, maycreate, listargs->opts); + if (listargs->opts & LIST_LSUB) { + cmd = "LSUB"; + } else if (listargs->opts & LIST_XLIST) { + cmd = "XLIST"; + } else { + cmd = "LIST"; + } + + mstringdata(cmd, name, matchlen, maycreate, listargs->opts); return 0; } diff --git a/imap/imapd.h b/imap/imapd.h index 6535369..515ede5 100644 --- a/imap/imapd.h +++ b/imap/imapd.h @@ -242,7 +242,8 @@ enum { LIST_EXT = (1<<1), LIST_SUBSCRIBED = (1<<2), LIST_CHILDREN = (1<<3), - LIST_REMOTE = (1<<4) + LIST_REMOTE = (1<<4), + LIST_XLIST = (1<<5), }; extern struct protstream *imapd_out, *imapd_in; diff --git a/imap/user.c b/imap/user.c index 81dc383..2f70cf8 100644 --- a/imap/user.c +++ b/imap/user.c @@ -82,6 +82,8 @@ #include "quota.h" #include "xmalloc.h" +#define FNAME_XLISTSUFFIX ".xlist" + #if 0 static int user_deleteacl(char *name, int matchlen, int maycreate, void* rock) { @@ -481,3 +483,28 @@ int user_deletequotaroots(const char *user) return r; } + +/* 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 f8f04fd..d17f124 100644 --- a/imap/user.h +++ b/imap/user.h @@ -70,4 +70,7 @@ int user_copyquotaroot(char *oldname, char *newname); /* Delete all quotaroots for 'user' */ int user_deletequotaroots(const char *user); +/* find the xlist file for user */ +char *user_hash_xlist(const char *user); + #endif diff --git a/imap/version.h b/imap/version.h index ad8cd0a..055de22 100644 --- a/imap/version.h +++ b/imap/version.h @@ -69,7 +69,7 @@ enum { "NO_ATOMIC_RENAME UNSELECT " \ "CHILDREN MULTIAPPEND BINARY " \ "SORT SORT=MODSEQ THREAD=ORDEREDSUBJECT THREAD=REFERENCES " \ - "ANNOTATEMORE CATENATE CONDSTORE SCAN" + "ANNOTATEMORE CATENATE CONDSTORE SCAN XLIST" /* Values for ID processing */ diff --git a/lib/imapoptions b/lib/imapoptions index 7beb647..8bd9a10 100644 --- a/lib/imapoptions +++ b/lib/imapoptions @@ -1063,6 +1063,11 @@ product version in the capabilities */ successfully authenticate. Otherwise lmtpd returns permanent failures (causing the mail to bounce immediately). */ +{ "specialusealways", 0, SWITCH } +/* If enabled, this option causes LIST and LSUB output to always include + the XLIST "special-use" flags */ + + { "sql_database", NULL, STRING } /* Name of the database which contains the cyrusdb table(s). */ diff --git a/perl/imap/IMAP.xs b/perl/imap/IMAP.xs index c3e3d97..38583ea 100644 --- a/perl/imap/IMAP.xs +++ b/perl/imap/IMAP.xs @@ -124,10 +124,10 @@ void imclient_xs_cb(struct imclient *client, void *prock, SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv("-client", 0))); - rv = newSVsv(&sv_undef); + rv = newSVsv(&PL_sv_undef); sv_setref_pv(rv, NULL, (void *) rock->client); XPUSHs(rv); - if (rock->prock != &sv_undef) { + if (rock->prock != &PL_sv_undef) { XPUSHs(sv_2mortal(newSVpv("-rock", 0))); XPUSHs(sv_mortalcopy(rock->prock)); } @@ -392,7 +392,7 @@ CODE: ST(0) = sv_newmortal(); if(client->authenticated) { - ST(0) = &sv_no; + ST(0) = &PL_sv_no; return; } @@ -414,10 +414,10 @@ CODE: rc = imclient_authenticate(client->imclient, mechlist, service, user, minssf, maxssf); if (rc) - ST(0) = &sv_no; + ST(0) = &PL_sv_no; else { client->authenticated = 1; - ST(0) = &sv_yes; + ST(0) = &PL_sv_yes; } int @@ -449,12 +449,12 @@ CODE: #ifdef HAVE_SSL rc = imclient_starttls(client->imclient, tls_cert_file, tls_key_file, CAfile, CApath); if (rc) - ST(0) = &sv_no; + ST(0) = &PL_sv_no; else { - ST(0) = &sv_yes; + ST(0) = &PL_sv_yes; } #else - ST(0) = &sv_no; + ST(0) = &PL_sv_no; #endif /* HAVE_SSL */ void @@ -514,7 +514,7 @@ PPCODE: (val = hv_fetch(cb, "Rock", 4, 0))) prock = *val; else - prock = &sv_undef; + prock = &PL_sv_undef; /* * build our internal rock, which is used by our internal * callback handler to invoke the Perl callback @@ -525,7 +525,7 @@ PPCODE: rock = (struct xsccb *) safemalloc(sizeof *rock); /* bump refcounts on these so they don't go away */ rock->pcb = SvREFCNT_inc(pcb); - if (!prock) prock = &sv_undef; + if (!prock) prock = &PL_sv_undef; rock->prock = SvREFCNT_inc(prock); rock->client = client; rock->autofree = 0; @@ -652,9 +652,9 @@ PPCODE: EXTEND(SP, 1); pcb = av_shift(av); if (strcmp(SvPV(pcb, arg), "OK") == 0) - PUSHs(&sv_yes); + PUSHs(&PL_sv_yes); else - PUSHs(&sv_no); + PUSHs(&PL_sv_no); pcb = perl_get_sv("@", TRUE); sv_setsv(pcb, av_shift(av)); if (av_len(av) != -1) { @@ -687,9 +687,9 @@ PPCODE: EXTEND(SP, 2); PUSHs(sv_2mortal(newSViv(fd))); if (writep) - PUSHs(&sv_yes); + PUSHs(&PL_sv_yes); else - PUSHs(&sv_no); + PUSHs(&PL_sv_no); void imclient_fromURL(client,url)