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)
