diff -ur cyrus-imapd-2.3.8.orig/imap/cyr_expire.c cyrus-imapd-2.3.8/imap/cyr_expire.c --- cyrus-imapd-2.3.8.orig/imap/cyr_expire.c 2007-02-05 13:41:46.000000000 -0500 +++ cyrus-imapd-2.3.8/imap/cyr_expire.c 2007-07-23 09:08:38.000000000 -0400 @@ -90,6 +90,20 @@ int verbose; }; +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. */ @@ -266,26 +280,90 @@ 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 *delete_hierachy; if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE); /* zero the expire_rock */ memset(&erock, 0, sizeof(erock)); + memset(&drock, 0, sizeof(drock)); - while ((opt = getopt(argc, argv, "C:E:X:v")) != EOF) { + while ((opt = getopt(argc, argv, "C:D:E:X:v")) != 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); @@ -298,6 +376,7 @@ case 'v': erock.verbose++; + drock.verbose++; break; default: @@ -354,6 +433,44 @@ erock.deleted, erock.messages, erock.mailboxes); } + if (mboxlist_delayed_delete_isenabled() && + (delete_hierachy = config_getstring(IMAPOPT_DELETE_HIERACHY))) { + 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, delete_hierachy, 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); diff -ur cyrus-imapd-2.3.8.orig/imap/imapd.c cyrus-imapd-2.3.8/imap/imapd.c --- cyrus-imapd-2.3.8.orig/imap/imapd.c 2007-02-05 13:49:55.000000000 -0500 +++ cyrus-imapd-2.3.8/imap/imapd.c 2007-07-23 09:08:38.000000000 -0400 @@ -4981,9 +4981,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_delete_hierachy(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); @@ -5063,9 +5073,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_delete_hierachy(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? */ @@ -9263,6 +9284,12 @@ else strlcpy(mboxname, lastname, sizeof(mboxname)); + /* Suppress DELETED hierachy unless admin */ + if (!imapd_userisadmin && + mboxlist_delayed_delete_isenabled() && + mboxlist_in_delete_hierachy(mboxname)) + return; + /* Look it up */ nonexistent = mboxlist_detail(mboxname, &mbtype, NULL, NULL, NULL, NULL, NULL); diff -ur cyrus-imapd-2.3.8.orig/imap/mboxlist.c cyrus-imapd-2.3.8/imap/mboxlist.c --- cyrus-imapd-2.3.8.orig/imap/mboxlist.c 2007-02-05 13:41:47.000000000 -0500 +++ cyrus-imapd-2.3.8/imap/mboxlist.c 2007-07-23 09:11:16.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 *delete_hierachy = config_getstring(IMAPOPT_DELETE_HIERACHY); + 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", + delete_hierachy, 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_delete_hierachy(const char *mailboxname) +{ + static int defined = 0; + static const char *delete_hierachy = NULL; + static int delete_hierachy_len = 0; + int domainlen = 0; + char *p; + + if (!defined) { + defined = 1; + delete_hierachy = config_getstring(IMAPOPT_DELETE_HIERACHY); + if (delete_hierachy) + delete_hierachy_len = strlen(delete_hierachy); + } + + if (!delete_hierachy || !mboxlist_delayed_delete_isenabled()) + return(0); + + if (config_virtdomains && (p = strchr(mailboxname, '!'))) + domainlen = p - mailboxname + 1; + + return ((!strncmp(mailboxname + domainlen, delete_hierachy, delete_hierachy_len) && + mailboxname[domainlen + delete_hierachy_len] == '.') ? 1 : 0); +} diff -ur cyrus-imapd-2.3.8.orig/imap/mboxlist.h cyrus-imapd-2.3.8/imap/mboxlist.h --- cyrus-imapd-2.3.8.orig/imap/mboxlist.h 2006-11-30 12:11:19.000000000 -0500 +++ cyrus-imapd-2.3.8/imap/mboxlist.h 2007-07-23 09:08:38.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_delete_hierachy(const char *mailboxname); #endif diff -ur cyrus-imapd-2.3.8.orig/lib/imapoptions cyrus-imapd-2.3.8/lib/imapoptions --- cyrus-imapd-2.3.8.orig/lib/imapoptions 2007-02-07 13:58:07.000000000 -0500 +++ cyrus-imapd-2.3.8/lib/imapoptions 2007-07-23 09:08:38.000000000 -0400 @@ -197,6 +197,16 @@ { "defaultpartition", "default", STRING } /* The partition name used by default for new mailboxes. */ +{ "delete_hierachy", "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 "Delete_hierachy" 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 diff -ur cyrus-imapd-2.3.8.orig/man/cyr_expire.8 cyrus-imapd-2.3.8/man/cyr_expire.8 --- cyrus-imapd-2.3.8.orig/man/cyr_expire.8 2006-11-30 12:11:23.000000000 -0500 +++ cyrus-imapd-2.3.8/man/cyr_expire.8 2007-07-23 09:08:38.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