# Companion to delayed_expunge for mailboxes. diff -udNr cyrus-imapd-2.3.8/imap/cyr_expire.c cyrus-imapd/imap/cyr_expire.c --- cyrus-imapd-2.3.8/imap/cyr_expire.c 2007-02-05 18:41:46.000000000 +0000 +++ cyrus-imapd/imap/cyr_expire.c 2007-07-23 14:35:25.000000000 +0100 @@ -90,6 +90,18 @@ int verbose; }; +struct delete_node { + struct delete_node *next; + char *name; +}; + +struct delete_rock { + int prefixlen; + time_t delete_mark; + struct delete_node *head; + struct delete_node *tail; +}; + /* * mailbox_expunge() callback to expunge expired articles. */ @@ -266,26 +278,66 @@ return 0; } +int delete(char *name, int matchlen, int maycreate __attribute__((unused)), + void *rock) +{ + struct delete_rock *drock = (struct delete_rock *) rock; + unsigned long timestamp; + char *p = name + drock->prefixlen; + int i; + struct delete_node *node; + + /* Sanity check for 8 hex digits followed by '.' */ + for (i = 0 ; i < 7; i++) { + if (!isxdigit(p[i])) return(0); + } + if (p[8] != '.') return(0); + + timestamp = strtoul(p, NULL, 16); + if ((timestamp == 0) || (timestamp > 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)); - 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); @@ -354,6 +406,45 @@ 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 (erock.verbose) { + fprintf(stderr, + "Removing deleted mailboxes older than %d days\n", + delete_days); + } + + strlcpy(buf, delete_hierachy, sizeof(buf)); + strlcat(buf, ".*", sizeof(buf)); + + drock.prefixlen = strlen(buf) - 1; + 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 (erock.verbose) { + fprintf(stderr, "Removing: %s\n", node->name); + } + r = mboxlist_deletemailbox(node->name, 1, NULL, NULL, 0, 0, 0); + count++; + } + + if (erock.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 -udNr cyrus-imapd-2.3.8/imap/imapd.c cyrus-imapd/imap/imapd.c --- cyrus-imapd-2.3.8/imap/imapd.c 2007-02-05 18:49:55.000000000 +0000 +++ cyrus-imapd/imap/imapd.c 2007-07-23 14:35:25.000000000 +0100 @@ -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 -udNr cyrus-imapd-2.3.8/imap/mboxlist.c cyrus-imapd/imap/mboxlist.c --- cyrus-imapd-2.3.8/imap/mboxlist.c 2007-02-05 18:41:47.000000000 +0000 +++ cyrus-imapd/imap/mboxlist.c 2007-07-23 14:38:41.000000000 +0100 @@ -875,6 +875,93 @@ } /* + * 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); + 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); + } + } + + gettimeofday( &tv, NULL ); + snprintf(newname, sizeof(newname), "%s.%X.%s", + delete_hierachy, tv.tv_sec, name); + + /* 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); + return r; +} + +/* * Delete a mailbox. * Deleting the mailbox user.FOO may only be performed by an admin. * @@ -3261,3 +3348,37 @@ 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; + + if (!defined) { + defined = 1; + delete_hierachy = xstrdup(config_getstring(IMAPOPT_DELETE_HIERACHY)); + if (delete_hierachy) + delete_hierachy_len = strlen(delete_hierachy); + } + + if (!delete_hierachy || !mboxlist_delayed_delete_isenabled()) + return(0); + + return ((!strncmp(mailboxname, delete_hierachy, delete_hierachy_len) && + mailboxname[delete_hierachy_len] == '.') ? 1 : 0); +} diff -udNr cyrus-imapd-2.3.8/imap/mboxlist.h cyrus-imapd/imap/mboxlist.h --- cyrus-imapd-2.3.8/imap/mboxlist.h 2006-11-30 17:11:19.000000000 +0000 +++ cyrus-imapd/imap/mboxlist.h 2007-07-23 14:35:25.000000000 +0100 @@ -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 -udNr cyrus-imapd-2.3.8/lib/imapoptions cyrus-imapd/lib/imapoptions --- cyrus-imapd-2.3.8/lib/imapoptions 2007-02-07 18:58:07.000000000 +0000 +++ cyrus-imapd/lib/imapoptions 2007-07-23 14:35:25.000000000 +0100 @@ -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 -udNr cyrus-imapd-2.3.8/man/cyr_expire.8 cyrus-imapd/man/cyr_expire.8 --- cyrus-imapd-2.3.8/man/cyr_expire.8 2006-11-30 17:11:23.000000000 +0000 +++ cyrus-imapd/man/cyr_expire.8 2007-07-23 14:35:25.000000000 +0100 @@ -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