Index: cyrus-imapd-2.3.9/imap/cyr_expire.c =================================================================== --- cyrus-imapd-2.3.9.orig/imap/cyr_expire.c 2007-08-16 21:42:14.000000000 -0400 +++ cyrus-imapd-2.3.9/imap/cyr_expire.c 2007-08-16 21:46:47.000000000 -0400 @@ -91,6 +91,20 @@ int skip_annotate; }; +struct delete_node { + struct delete_node *next; + char *name; +}; + +struct delete_rock { + char prefix[100]; + int prefixlen; + time_t delete_mark; + struct delete_node *head; + struct delete_node *tail; + int verbose; +}; + /* * mailbox_expunge() callback to expunge expired articles. */ @@ -273,14 +287,72 @@ return 0; } +int delete(char *name, int matchlen, int maycreate __attribute__((unused)), + void *rock) +{ + struct delete_rock *drock = (struct delete_rock *) rock; + char fnamebuf[MAX_MAILBOX_PATH+1]; + struct stat sbuf; + char *p; + int i, r, domainlen = 0; + struct delete_node *node; + int mbtype; + char *path, *mpath; + + if (config_virtdomains && (p = strchr(name, '!'))) + domainlen = p - name + 1; + + /* check if this is a mailbox we want to examine */ + if (strncmp(name+domainlen, drock->prefix, drock->prefixlen)) + return 0; + + /* Skip remote mailboxes */ + r = mboxlist_detail(name, &mbtype, &path, &mpath, NULL, NULL, NULL); + if (r) { + if (drock->verbose) { + printf("error looking up %s: %s\n", name, error_message(r)); + } + return 1; + } + if (mbtype & MBTYPE_REMOTE) return 0; + + /* check that the header is older than the number of days we care about */ + if (mpath && + (config_metapartition_files & + IMAP_ENUM_METAPARTITION_FILES_HEADER)) { + strlcpy(fnamebuf, mpath, sizeof(fnamebuf)); + } else { + strlcpy(fnamebuf, path, sizeof(fnamebuf)); + } + strlcat(fnamebuf, FNAME_HEADER, sizeof(fnamebuf)); + if (stat(fnamebuf, &sbuf)) return 0; + if ((sbuf.st_mtime == 0) || (sbuf.st_mtime > drock->delete_mark)) + return(0); + + /* Add this mailbox to list of mailboxes to delete */ + node = xmalloc(sizeof(struct delete_node)); + node->next = NULL; + node->name = xstrdup(name); + + if (drock->tail) { + drock->tail->next = node; + drock->tail = node; + } else { + drock->head = drock->tail = node; + } + return(0); +} + int main(int argc, char *argv[]) { extern char *optarg; - int opt, r = 0, expire_days = 0, expunge_days = 0; + int opt, r = 0, expire_days = 0, expunge_days = 0, delete_days = 0; char *alt_config = NULL; char buf[100]; struct hash_table expire_table; struct expire_rock erock; + struct delete_rock drock; + const char *deleteprefix; if ((geteuid()) == 0 && (become_cyrus() != 0)) { fatal("must run as the Cyrus user", EC_USAGE); @@ -288,13 +360,19 @@ /* zero the expire_rock */ memset(&erock, 0, sizeof(erock)); + memset(&drock, 0, sizeof(drock)); - while ((opt = getopt(argc, argv, "C:E:X:va")) != EOF) { + while ((opt = getopt(argc, argv, "C:D:E:X:va")) != EOF) { switch (opt) { case 'C': /* alt config file */ alt_config = optarg; break; + case 'D': + if (delete_days) usage(); + delete_days = atoi(optarg); + break; + case 'E': if (expire_days) usage(); expire_days = atoi(optarg); @@ -307,6 +385,7 @@ case 'v': erock.verbose++; + drock.verbose++; break; case 'a': @@ -367,6 +446,44 @@ erock.deleted, erock.messages, erock.mailboxes); } + if (mboxlist_delayed_delete_isenabled() && + (deleteprefix = config_getstring(IMAPOPT_DELETEPREFIX))) { + struct delete_node *node; + int count = 0; + + if (drock.verbose) { + fprintf(stderr, + "Removing deleted mailboxes older than %d days\n", + delete_days); + } + + strlcpy(drock.prefix, deleteprefix, sizeof(drock.prefix)); + strlcat(drock.prefix, ".", sizeof(drock.prefix)); + drock.prefixlen = strlen(drock.prefix); + drock.delete_mark = time(0) - (delete_days * 60 * 60 * 24); + drock.head = NULL; + drock.tail = NULL; + + mboxlist_findall(NULL, buf, 1, 0, 0, &delete, &drock); + + for (node = drock.head ; node ; node = node->next) { + if (drock.verbose) { + fprintf(stderr, "Removing: %s\n", node->name); + } + r = mboxlist_deletemailbox(node->name, 1, NULL, NULL, 0, 0, 0); + count++; + } + + if (drock.verbose) { + if (count != 1) { + fprintf(stderr, "Removed %d deleted mailboxes\n", count); + } else { + fprintf(stderr, "Removed 1 deleted mailbox\n"); + } + } + syslog(LOG_NOTICE, "Removed %d deleted mailboxes", count); + } + /* purge deliver.db entries of expired messages */ r = duplicate_prune(expire_days, &expire_table); Index: cyrus-imapd-2.3.9/imap/imapd.c =================================================================== --- cyrus-imapd-2.3.9.orig/imap/imapd.c 2007-08-16 21:42:58.000000000 -0400 +++ cyrus-imapd-2.3.9/imap/imapd.c 2007-08-16 21:45:58.000000000 -0400 @@ -5016,9 +5016,19 @@ { int r; - r = mboxlist_deletemailbox(name, imapd_userisadmin, - imapd_userid, imapd_authstate, - 0, 0, 0); + if (!mboxlist_delayed_delete_isenabled()) { + r = mboxlist_deletemailbox(name, imapd_userisadmin, + imapd_userid, imapd_authstate, + 0, 0, 0); + } else if (imapd_userisadmin && mboxlist_in_deleteprefix(name)) { + r = mboxlist_deletemailbox(name, imapd_userisadmin, + imapd_userid, imapd_authstate, + 0, 0, 0); + } else { + r = mboxlist_delayed_deletemailbox(name, imapd_userisadmin, + imapd_userid, imapd_authstate, + 0, 0, 0); + } if (!r) sync_log_mailbox(name); @@ -5098,9 +5108,20 @@ if (config_virtdomains && (p = strchr(mailboxname, '!'))) domainlen = p - mailboxname + 1; - r = mboxlist_deletemailbox(mailboxname, imapd_userisadmin, - imapd_userid, imapd_authstate, 1-force, - localonly, 0); + if (localonly || !mboxlist_delayed_delete_isenabled()) { + r = mboxlist_deletemailbox(mailboxname, imapd_userisadmin, + imapd_userid, imapd_authstate, + 1-force, localonly, 0); + } else if (imapd_userisadmin && + mboxlist_in_deleteprefix(mailboxname)) { + r = mboxlist_deletemailbox(mailboxname, imapd_userisadmin, + imapd_userid, imapd_authstate, + 0 /* checkacl */, localonly, 0); + } else { + r = mboxlist_delayed_deletemailbox(mailboxname, imapd_userisadmin, + imapd_userid, imapd_authstate, + 1-force, localonly, 0); + } } /* was it a top-level user mailbox? */ @@ -9323,6 +9344,12 @@ else strlcpy(mboxname, lastname, sizeof(mboxname)); + /* Suppress DELETED hierachy unless admin */ + if (!imapd_userisadmin && + mboxlist_delayed_delete_isenabled() && + mboxlist_in_deleteprefix(mboxname)) + return; + /* Look it up */ nonexistent = mboxlist_detail(mboxname, &mbtype, NULL, NULL, NULL, NULL, NULL); Index: cyrus-imapd-2.3.9/imap/mboxlist.c =================================================================== --- cyrus-imapd-2.3.9.orig/imap/mboxlist.c 2007-08-16 21:42:14.000000000 -0400 +++ cyrus-imapd-2.3.9/imap/mboxlist.c 2007-08-16 21:46:47.000000000 -0400 @@ -82,6 +82,7 @@ #include "mboxlist.h" #include "quota.h" +#include "sync_log.h" #define DB config_mboxlist_db #define SUBDB config_subscription_db @@ -875,6 +876,103 @@ } /* + * Delayed Delete a mailbox: translate delete into rename + * + * XXX local_only? + */ +int +mboxlist_delayed_deletemailbox(const char *name, int isadmin, char *userid, + struct auth_state *auth_state, int checkacl, + int local_only, int force) +{ + char newname[MAX_MAILBOX_PATH+1]; + char *path, *mpath; + char *acl; + char *partition; + int r; + long access; + struct mailbox mailbox; + int deletequotaroot = 0; + struct txn *tid = NULL; + int isremote = 0; + int mbtype; + const char *p; + const char *deleteprefix = config_getstring(IMAPOPT_DELETEPREFIX); + int domainlen = 0; + struct timeval tv; + + if(!isadmin && force) return IMAP_PERMISSION_DENIED; + + /* Check for request to delete a user: + user. with no dots after it */ + if ((p = mboxname_isusermailbox(name, 1))) { + /* Can't DELETE INBOX (your own inbox) */ + if (userid) { + int len = config_virtdomains ? + strcspn(userid, "@") : strlen(userid); + if ((len == strlen(p)) && !strncmp(p, userid, len)) { + return(IMAP_MAILBOX_NOTSUPPORTED); + } + } + + /* Only admins may delete user */ + if (!isadmin) return(IMAP_PERMISSION_DENIED); + } + + do { + r = mboxlist_mylookup(name, &mbtype, + &path, &mpath, &partition, &acl, NULL, 1); + } while (r == IMAP_AGAIN); + + if (r) return(r); + + isremote = mbtype & MBTYPE_REMOTE; + + /* are we reserved? (but for remote mailboxes this is okay, since + * we don't touch their data files at all) */ + if(!isremote && (mbtype & MBTYPE_RESERVE) && !force) { + return(IMAP_MAILBOX_RESERVED); + } + + /* check if user has Delete right (we've already excluded non-admins + * from deleting a user mailbox) */ + if (checkacl) { + access = cyrus_acl_myrights(auth_state, acl); + if(!(access & ACL_DELETEMBOX)) { + /* User has admin rights over their own mailbox namespace */ + if (mboxname_userownsmailbox(userid, name) && + (config_implicitrights & ACL_ADMIN)) { + isadmin = 1; + } + + /* Lie about error if privacy demands */ + r = (isadmin || (access & ACL_LOOKUP)) ? + IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT; + return(r); + } + } + + if (config_virtdomains && (p = strchr(name, '!'))) + domainlen = p - name + 1; + + gettimeofday( &tv, NULL ); + + if (domainlen && domainlen < sizeof(newname)) + strncpy(newname, name, domainlen); + snprintf(newname+domainlen, sizeof(newname)-domainlen, "%s.%s.%X", + deleteprefix, name+domainlen, tv.tv_sec); + + /* Get mboxlist_renamemailbox to do the hard work. No ACL checks needed */ + r = mboxlist_renamemailbox((char *)name, newname, partition, + 1 /* isadmin */, userid, + auth_state, force); + + /* don't forget to log the rename! */ + sync_log_mailbox_double((char *)name, newname); + return r; +} + +/* * Delete a mailbox. * Deleting the mailbox user.FOO may only be performed by an admin. * @@ -3261,3 +3359,42 @@ return DB->abort(mbdb, tid); } + +int +mboxlist_delayed_delete_isenabled(void) +{ + static int defined = 0; + static enum enum_value config_delete_mode; + + if (!defined) { + defined = 1; + config_delete_mode = config_getenum(IMAPOPT_DELETE_MODE); + } + + return(config_delete_mode == IMAP_ENUM_DELETE_MODE_DELAYED); +} + +int mboxlist_in_deleteprefix(const char *mailboxname) +{ + static int defined = 0; + static const char *deleteprefix = NULL; + static int deleteprefix_len = 0; + int domainlen = 0; + char *p; + + if (!defined) { + defined = 1; + deleteprefix = config_getstring(IMAPOPT_DELETEPREFIX); + if (deleteprefix) + deleteprefix_len = strlen(deleteprefix); + } + + if (!deleteprefix || !mboxlist_delayed_delete_isenabled()) + return(0); + + if (config_virtdomains && (p = strchr(mailboxname, '!'))) + domainlen = p - mailboxname + 1; + + return ((!strncmp(mailboxname + domainlen, deleteprefix, deleteprefix_len) && + mailboxname[domainlen + deleteprefix_len] == '.') ? 1 : 0); +} Index: cyrus-imapd-2.3.9/imap/mboxlist.h =================================================================== --- cyrus-imapd-2.3.9.orig/imap/mboxlist.h 2007-08-16 21:42:14.000000000 -0400 +++ cyrus-imapd-2.3.9/imap/mboxlist.h 2007-08-16 21:45:58.000000000 -0400 @@ -118,6 +118,12 @@ struct auth_state *auth_state, int localonly, int forceuser, int dbonly); +/* delated delete */ +/* Translate delete into rename */ +int +mboxlist_delayed_deletemailbox(const char *name, int isadmin, char *userid, + struct auth_state *auth_state, int checkacl, + int local_only, int force); /* Delete a mailbox. */ /* setting local_only disables any communication with the mupdate server * and deletes the mailbox from the filesystem regardless of if it is @@ -204,4 +210,6 @@ int mboxlist_commit(struct txn *tid); int mboxlist_abort(struct txn *tid); +int mboxlist_delayed_delete_isenabled(void); +int mboxlist_in_deleteprefix(const char *mailboxname); #endif Index: cyrus-imapd-2.3.9/imap/mboxname.c =================================================================== --- cyrus-imapd-2.3.9.orig/imap/mboxname.c 2007-08-16 21:42:14.000000000 -0400 +++ cyrus-imapd-2.3.9/imap/mboxname.c 2007-08-16 21:46:47.000000000 -0400 @@ -599,11 +599,25 @@ char *mboxname_isusermailbox(const char *name, int isinbox) { const char *p; + const char *start = name; + const char *deleteprefix = config_getstring(IMAPOPT_DELETEPREFIX); + const char sep = config_getswitch(IMAPOPT_UNIXHIERARCHYSEP) ? '/' : '.'; + int len = strlen(deleteprefix); + int isdel = 0; + + /* step past the domain part */ + if (config_virtdomains && (p = strchr(start, '!'))) + start = p + 1; + + /* step past any deleted bit */ + if (mboxlist_delayed_delete_isenabled() && strlen(start) > len && !strncmp(start, deleteprefix, len) && start[len] == sep) { + start += len + 1; + isdel = 1; /* there's an additional sep + hextimestamp on isdel folders */ + } - if (((!strncmp(name, "user.", 5) && (p = name+5)) || - ((p = strstr(name, "!user.")) && (p += 6))) && - (!isinbox || !strchr(p, '.'))) - return (char*) p; + if (strlen(start) > 5 && !strncmp(start, "user", 4) && start[4] == sep && + (!isinbox || !strchr(start+5, sep)) || (isdel && (p = strchr(start+5, sep)) && !strchr(p+1, sep))) + return (char*) start+5; else return NULL; } Index: cyrus-imapd-2.3.9/lib/imapoptions =================================================================== --- cyrus-imapd-2.3.9.orig/lib/imapoptions 2007-08-16 21:42:58.000000000 -0400 +++ cyrus-imapd-2.3.9/lib/imapoptions 2007-08-16 21:47:21.000000000 -0400 @@ -201,6 +201,16 @@ { "defaultpartition", "default", STRING } /* The partition name used by default for new mailboxes. */ +{ "deleteprefix", "DELETED", STRING } +/* Location for deleted mailboxes, if "delete_mode" set to be "delayed" */ + +{ "delete_mode", "immediate", ENUM("immediate", "delayed") } +/* The manner in which mailboxes are deleted. "Immediate" mode is the + default behavior in which mailboxes are removed immediately. In + "Delayed" mode, mailboxes are renamed to a special hiearchy defined + by the "deleteprefix" option to be removed later by cyr_expire. +*/ + { "deleteright", "c", STRING } /* Deprecated - only used for backwards compatibility with existing installations. Lists the old RFC 2086 right which was used to Index: cyrus-imapd-2.3.9/man/cyr_expire.8 =================================================================== --- cyrus-imapd-2.3.9.orig/man/cyr_expire.8 2007-08-16 21:42:14.000000000 -0400 +++ cyrus-imapd-2.3.9/man/cyr_expire.8 2007-08-16 21:44:30.000000000 -0400 @@ -48,6 +48,9 @@ .B \-C .I config-file ] +[ +.BI \-D " delete-days" +] .BI \-E " expire-days" [ .BI \-X " expunge-days" @@ -84,6 +87,11 @@ .BI \-C " config-file" Read configuration options from \fIconfig-file\fR. .TP +\fB\-D \fIdelete-days\fR +Remove previously deleted mailboxes older than \fIdelete-days\fR +(when using the "delayed" delete mode). The default is 0 (zero) +days, which will delete \fBall\fR previously deleted mailboxes. +.TP \fB\-E \fIexpire-days\fR Prune the duplicate database of entries older than \fIexpire-days\fR. This value is only used for entries which do not have a corresponding