/* cyr_expire.c -- Program to expire deliver.db entries and messages
*
* Copyright (c) 1994-2017 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
NOTES:
* Precedence of configuration in `cyr_expire`:
Highest Lowest
|----------------------------------|
Annotation -> Command-line -> Config
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sysexits.h>
#include <syslog.h>
#include <signal.h>
#include <errno.h>
#include <stdbool.h>
#include <libgen.h>
#include <sasl/sasl.h>
#include "annotate.h"
#include "duplicate.h"
#include "global.h"
#include "hash.h"
#include "libcyr_cfg.h"
#include "mboxevent.h"
#include "mboxlist.h"
#include "conversations.h"
#include "util.h"
#include "xmalloc.h"
#include "strarray.h"
/* generated headers are not necessarily in current directory */
#include "imap/imap_err.h"
#define SECS_IN_A_MIN 60
#define SECS_IN_AN_HR (60 * SECS_IN_A_MIN)
#define SECS_IN_A_DAY (24 * SECS_IN_AN_HR)
/* global state */
static volatile sig_atomic_t sigquit = 0;
static int verbose = 0;
static const char *progname = NULL;
static struct namespace expire_namespace; /* current namespace */
/* command line arguments */
struct arguments {
int archive_seconds;
int delete_seconds;
int expire_seconds;
int expunge_seconds;
int do_cid_expire;
/* bools */
bool do_expunge;
bool do_userflags;
bool skip_annotate;
const char *altconfig;
const char *mbox_prefix;
const char *userid;
char *freeme; /* for mbox_prefix */
};
struct archive_rock {
time_t archive_mark;
unsigned long messages_archived;
bool skip_annotate;
};
struct expire_rock {
struct hash_table table;
time_t expire_mark;
time_t expunge_mark;
time_t tombstone_mark;
unsigned long mailboxes_seen;
unsigned long messages_seen;
unsigned long messages_expired;
unsigned long messages_expunged;
unsigned long userflags_expunged;
bit32 userflags[MAX_USER_FLAGS/32];
bool do_userflags;
bool skip_annotate;
};
struct conversations_rock {
struct hash_table seen;
time_t expire_mark;
unsigned long databases_seen;
unsigned long msgids_seen;
unsigned long msgids_expired;
};
struct delete_rock {
time_t delete_mark;
strarray_t to_delete;
bool skip_annotate;
};
/* The global context */
struct cyr_expire_ctx {
struct arguments args;
struct archive_rock arock;
struct conversations_rock crock;
struct delete_rock drock;
struct expire_rock erock;
};
static const struct cyr_expire_ctx zero_ctx;
static void sighandler(int sig);
/* verbosep - a wrapper to print if the 'verbose' option is
turned on.
*/
__attribute__((format (printf, 1, 2)))
static inline void verbosep(const char *fmt, ...)
{
va_list params;
if (!verbose)
return;
va_start(params, fmt);
vfprintf(stderr, fmt, params);
va_end(params);
fputc('\n', stderr);
}
static void cyr_expire_init(const char *progname, struct cyr_expire_ctx *ctx)
{
struct sigaction action;
/* Initialise signal handlers */
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = sighandler;
if (sigaction(SIGQUIT, &action, NULL) < 0)
fatal("unable to install signal handler for SIGQUIT", EX_TEMPFAIL);
if (sigaction(SIGINT, &action, NULL) < 0)
fatal("unable to install signal handler for SIGINT", EX_TEMPFAIL);
if (sigaction(SIGTERM, &action, NULL) < 0)
fatal("unable to install signal handler for SIGTERM", EX_TEMPFAIL);
construct_hash_table(&ctx->erock.table, 10000, 1);
strarray_init(&ctx->drock.to_delete);
construct_hash_table(&ctx->crock.seen, 100, 1);
cyrus_init(ctx->args.altconfig, progname, 0, 0);
global_sasl_init(1, 0, NULL);
ctx->erock.do_userflags = ctx->args.do_userflags;
/* TODO: Ideally all the functions should just use the skip_annotate from
* args. But that would require a change in the callback signatures.
* So retaining it as is for now.
*/
ctx->arock.skip_annotate = ctx->args.skip_annotate;
ctx->erock.skip_annotate = ctx->args.skip_annotate;
ctx->drock.skip_annotate = ctx->args.skip_annotate;
}
static void cyr_expire_cleanup(struct cyr_expire_ctx *ctx)
{
if (ctx->args.freeme) free(ctx->args.freeme);
free_hash_table(&ctx->erock.table, free);
free_hash_table(&ctx->crock.seen, NULL);
strarray_fini(&ctx->drock.to_delete);
duplicate_done();
sasl_done();
cyrus_done();
}
static void usage(void)
{
fprintf(stderr, "Usage: %s [OPTIONS] {mailbox|users}\n", progname);
fprintf(stderr, "Expire messages and duplicate delivery database entries.\n");
fprintf(stderr, "\n");
fprintf(stderr, "-a skip annotation lookup\n");
fprintf(stderr, "-c do not expire conversations\n");
fprintf(stderr, "-h print this help and exit\n");
fprintf(stderr, "-p <mailbox-prefix> specify prefix for mailboxes\n");
fprintf(stderr, "-t remove user flags which are not used\n");
fprintf(stderr, "-u <user-id> specify user id for mailbox lookup\n");
fprintf(stderr, "-v enable verbose output\n");
fprintf(stderr, "-x do not expunge messages\n");
fprintf(stderr, "-C <config-file> use <config-file> instead of config from imapd.conf\n");
fprintf(stderr, "-A <archive-duration> \n");
fprintf(stderr, "-D <delete-duration> \n");
fprintf(stderr, "-E <expire-duration> \n");
fprintf(stderr, "-X <expunge-duration> \n");
fprintf(stderr, "\n");
exit(EX_USAGE);
}
/*
* Parse a non-negative duration string as seconds.
*
* Convert "23.5m" to fractional days. Accepts the suffixes "d" (day),
* (day), "h" (hour), "m" (minute) and "s" (second). If no suffix, assume
* days.
* Returns 1 if successful and *secondsp is filled in, or 0 if the suffix
* is unknown or on error.
*/
static int parse_duration(const char *s, int *secondsp)
{
char *end = NULL;
double val;
int multiplier = SECS_IN_A_DAY; /* default is days */
/* no negative or empty numbers please */
if (!*s || *s == '-')
return 0;
val = strtod(s, &end);
/* Allow 'd', 'h', 'm' and 's' as end, else return error. */
if (*end) {
if (end[1]) return 0; /* trailing extra junk */
switch (*end) {
case 'd':
/* already the default */
break;
case 'h':
multiplier = SECS_IN_AN_HR;
break;
case 'm':
multiplier = SECS_IN_A_MIN;
break;
case 's':
multiplier = 1;
break;
default:
return 0;
}
}
*secondsp = multiplier * val;
return 1;
}
/*
* Given an annotation, reads it, and converts it into 'seconds',
* using `parse_duration`.
*
* On Success: Returns 1
* On Failure: Returns 0
*/
static int get_annotation_value(const char *mboxname,
const char *annot_entry,
int *secondsp, bool iterate)
{
struct buf attrib = BUF_INITIALIZER;
int ret = 0;
/* mboxname needs to be copied since `mboxname_make_parent`
* runs a strrchr() on it.
*/
char *buf = xstrdup(mboxname);
/*
* Mailboxes inherit /vendo/cmu/cyrus-imapd/{expire, archive, delete},
* so we need to iterate all the way up to "" (server entry).
*/
do {
buf_free(&attrib);
ret = annotatemore_lookup(buf, annot_entry, "", &attrib);
if (ret || /* error */
attrib.s) /* found an entry */
break;
} while (mboxname_make_parent(buf) && iterate);
if (attrib.s && parse_duration(attrib.s, secondsp))
ret = 1;
else
ret = 0;
buf_free(&attrib);
free(buf);
syslog(LOG_DEBUG, "get_annotation_value: ret(%d):secondsp(%d)\n", ret, *secondsp);
return ret;
}
static int expunge_userflags(struct mailbox *mailbox, struct expire_rock *erock)
{
unsigned int i;
int r;
for (i = 0; i < MAX_USER_FLAGS; i++) {
if (erock->userflags[i/32] & 1<<(i&31))
continue;
if (!mailbox->flagname[i])
continue;
verbosep("Expunging userflag %u (%s) from %s\n",
i, mailbox->flagname[i], mailbox->name);
r = mailbox_remove_user_flag(mailbox, i);
if (r) return r;
erock->userflags_expunged++;
}
return 0;
}
/*
* mailbox_expunge() callback to *only* count userflags.
*/
static unsigned userflag_cb(struct mailbox *mailbox __attribute__((unused)),
const struct index_record *record,
void *rock)
{
struct expire_rock *erock = (struct expire_rock *) rock;
unsigned int i;
/* record which user flags are set */
for (i = 0; i < (MAX_USER_FLAGS/32); i++)
erock->userflags[i] |= record->user_flags[i];
return 0; /* always keep the message */
}
static int archive(const mbentry_t *mbentry, void *rock)
{
struct archive_rock *arock = (struct archive_rock *) rock;
struct mailbox *mailbox = NULL;
int archive_seconds = -1;
if (sigquit)
return 1;
if (mbentry->mbtype & MBTYPE_DELETED)
goto done;
if (mbentry->mbtype & MBTYPE_REMOTE)
goto done;
if (mailbox_open_iwl(mbentry->name, &mailbox))
goto done;
/* check /vendor/cmu/cyrus-imapd/archive */
if (!arock->skip_annotate &&
get_annotation_value(mbentry->name, IMAP_ANNOT_NS "archive",
&archive_seconds, false)) {
arock->archive_mark = archive_seconds ?
time(0) - archive_seconds : 0;
}
/* The default callback for mailbox_archive() is mailbox_should_archive()
* in imap/mailbox.c. This one takes the arock->archive_mark as the
* callback data.
*/
mailbox_archive(mailbox, NULL, &arock->archive_mark, ITER_SKIP_EXPUNGED);
done:
mailbox_close(&mailbox);
/* move on to the next mailbox regardless of errors */
return 0;
}
/*
* mailbox_expunge() callback to expunge expired articles.
*/
static unsigned expire_cb(struct mailbox *mailbox __attribute__((unused)),
const struct index_record *record,
void *rock)
{
struct expire_rock *erock = (struct expire_rock *) rock;
unsigned int i;
/* otherwise, we're expiring messages by sent date */
if (record->gmtime < erock->expire_mark) {
erock->messages_expired++;
return 1;
}
/* record which user flags are set */
for (i = 0; i < (MAX_USER_FLAGS/32); i++)
erock->userflags[i] |= record->user_flags[i];
return 0;
}
/*
* callback function to:
* - expire messages from mailboxes,
* - build a hash table of mailboxes in which we expired messages,
* - and perform a cleanup of expunged messages
*/
static int expire(const mbentry_t *mbentry, void *rock)
{
struct expire_rock *erock = (struct expire_rock *) rock;
int r;
struct mailbox *mailbox = NULL;
unsigned numexpunged = 0;
int expire_seconds = 0;
int did_expunge = 0;
if (sigquit) {
/* don't care if we leak some memory, we are shutting down */
return 1;
}
/* Skip remote mailboxes */
if (mbentry->mbtype & MBTYPE_REMOTE)
goto done;
/* clean up deleted entries after 7 days */
if (mbentry->mbtype & MBTYPE_DELETED) {
if (mbentry->mtime < erock->tombstone_mark) {
verbosep("Removing stale tombstone for %s\n", mbentry->name);
syslog(LOG_NOTICE, "Removing stale tombstone for %s", mbentry->name);
mboxlist_delete(mbentry->name);
}
goto done;
}
memset(erock->userflags, 0, sizeof(erock->userflags));
r = mailbox_open_iwl(mbentry->name, &mailbox);
if (r) {
/* mailbox corrupt/nonexistent -- skip it */
syslog(LOG_WARNING, "unable to open mailbox %s: %s",
mbentry->name, error_message(r));
goto done;
}
/* see if we need to expire messages.
* since mailboxes inherit /vendor/cmu/cyrus-imapd/expire,
* we need to iterate all the way up to "" (server entry)
*/
if (!erock->skip_annotate &&
get_annotation_value(mbentry->name, IMAP_ANNOT_NS "expire",
&expire_seconds, true)) {
/* add mailbox to table */
erock->expire_mark = expire_seconds ?
time(0) - expire_seconds : 0 /* never */ ;
hash_insert(mbentry->name,
xmemdup(&erock->expire_mark, sizeof(erock->expire_mark)),
&erock->table);
if (expire_seconds) {
verbosep("expiring messages in %s older than %0.2f days\n",
mbentry->name,
((double)expire_seconds/SECS_IN_A_DAY));
r = mailbox_expunge(mailbox, expire_cb, erock, NULL,
EVENT_MESSAGE_EXPIRE);
if (r)
syslog(LOG_ERR, "failed to expire old messages: %s", mbentry->name);
did_expunge = 1;
}
}
if (!did_expunge && erock->do_userflags) {
r = mailbox_expunge(mailbox, userflag_cb, erock, NULL,
EVENT_MESSAGE_EXPIRE);
if (r)
syslog(LOG_ERR, "failed to scan user flags for %s: %s",
mbentry->name, error_message(r));
}
erock->messages_seen += mailbox->i.num_records;
if (erock->do_userflags)
expunge_userflags(mailbox, erock);
verbosep("cleaning up expunged messages in %s\n", mbentry->name);
r = mailbox_expunge_cleanup(mailbox, erock->expunge_mark, &numexpunged);
erock->messages_expunged += numexpunged;
erock->mailboxes_seen++;
if (r) {
syslog(LOG_WARNING, "failure expiring %s: %s", mbentry->name, error_message(r));
annotate_state_abort(&mailbox->annot_state);
}
done:
mailbox_close(&mailbox);
/* Even if we had a problem with one mailbox, continue with the others */
return 0;
}
static int delete(const mbentry_t *mbentry, void *rock)
{
struct delete_rock *drock = (struct delete_rock *) rock;
time_t timestamp;
int delete_seconds = -1;
if (sigquit)
return 1;
if (mbentry->mbtype & MBTYPE_DELETED)
goto done;
if (mbentry->mbtype & MBTYPE_REMOTE)
goto done;
/* check if this is a mailbox we want to examine */
if (!mboxname_isdeletedmailbox(mbentry->name, ×tamp))
goto done;
/* check /vendor/cmu/cyrus-imapd/delete */
if (!drock->skip_annotate &&
get_annotation_value(mbentry->name, IMAP_ANNOT_NS "delete",
&delete_seconds, false)) {
drock->delete_mark = delete_seconds ?
time(0) - delete_seconds: 0;
}
if ((timestamp == 0) || (timestamp > drock->delete_mark))
goto done;
verbosep("Cleaning up %s\n", mbentry->name);
/* Add this mailbox to list of mailboxes to delete */
strarray_append(&drock->to_delete, mbentry->name);
done:
/* Even if we had a problem with one mailbox, continue with the others */
return 0;
}
static int expire_conversations(const mbentry_t *mbentry, void *rock)
{
struct conversations_rock *crock = (struct conversations_rock *)rock;
struct conversations_state *state = NULL;
unsigned int nseen = 0, ndeleted = 0;
char *filename = NULL;
if (sigquit)
return 1;
if (mbentry->mbtype & MBTYPE_DELETED)
goto done;
if (mbentry->mbtype & MBTYPE_REMOTE)
goto done;
if (mboxname_isdeletedmailbox(mbentry->name, NULL))
goto done;
filename = conversations_getmboxpath(mbentry->name);
if (!filename)
goto done;
if (hash_lookup(filename, &crock->seen))
goto done;
verbosep("Pruning conversations from db %s\n", filename);
if (!conversations_open_mbox(mbentry->name, 0/*shared*/, &state)) {
conversations_prune(state, crock->expire_mark, &nseen, &ndeleted);
conversations_commit(&state);
}
hash_insert(filename, (void *)1, &crock->seen);
crock->databases_seen++;
crock->msgids_seen += nseen;
crock->msgids_expired += ndeleted;
done:
free(filename);
return 0;
}
static void sighandler(int sig __attribute((unused)))
{
sigquit = 1;
return;
}
static int do_archive(struct cyr_expire_ctx *ctx)
{
if (ctx->args.archive_seconds >= 0) {
syslog(LOG_DEBUG, ">> do_archive: archive_seconds(%d) >= 0\n",
ctx->args.archive_seconds);
ctx->arock.archive_mark = time(0) - ctx->args.archive_seconds;
if (ctx->args.userid)
mboxlist_usermboxtree(ctx->args.userid, NULL, archive,
&ctx->arock, MBOXTREE_DELETED);
else
mboxlist_allmbox(ctx->args.mbox_prefix, archive, &ctx->arock, 0);
}
return 0;
}
static int do_expunge(struct cyr_expire_ctx *ctx)
{
if (ctx->args.do_expunge && (ctx->args.expunge_seconds >= 0 ||
ctx->args.expire_seconds ||
ctx->erock.do_userflags)) {
/* XXX: better way to determine a size for this table? */
/* expire messages from mailboxes,
* build a hash table of mailboxes in which we expired messages,
* and perform a cleanup of expunged messages
*/
if (ctx->args.expunge_seconds < 0) {
ctx->erock.expunge_mark = 0;
} else {
ctx->erock.expunge_mark = time(0) - ctx->args.expunge_seconds;
verbosep("Expunging deleted messages in mailboxes older than %0.2f days\n",
((double)ctx->args.expunge_seconds/SECS_IN_A_DAY));
}
/* XXX _ a control for this too? */
ctx->erock.tombstone_mark = time(0) - SECS_IN_A_DAY*7;
if (ctx->args.userid)
mboxlist_usermboxtree(ctx->args.userid, NULL, expire,
&ctx->erock, MBOXTREE_DELETED|MBOXTREE_TOMBSTONES);
else
mboxlist_allmbox(ctx->args.mbox_prefix, expire, &ctx->erock,
MBOXTREE_TOMBSTONES);
syslog(LOG_NOTICE, "Expired %lu and expunged %lu out of %lu "
"messages from %lu mailboxes",
ctx->erock.messages_expired,
ctx->erock.messages_expunged,
ctx->erock.messages_seen,
ctx->erock.mailboxes_seen);
verbosep("\nExpired %lu and expunged %lu out of %lu "
"messages from %lu mailboxes\n",
ctx->erock.messages_expired,
ctx->erock.messages_expunged,
ctx->erock.messages_seen,
ctx->erock.mailboxes_seen);
if (ctx->erock.do_userflags) {
syslog(LOG_NOTICE, "Expunged %lu user flags",
ctx->erock.userflags_expunged);
verbosep("Expunged %lu user flags\n",
ctx->erock.userflags_expunged);
}
}
return 0;
}
static int do_cid_expire(struct cyr_expire_ctx *ctx)
{
if (ctx->args.do_cid_expire) {
int cid_expire_seconds;
cid_expire_seconds = config_getduration(IMAPOPT_CONVERSATIONS_EXPIRE_AFTER, 'd');
ctx->crock.expire_mark = time(0) - cid_expire_seconds;
verbosep("Removing conversation entries older than %0.2f days\n",
(double)(cid_expire_seconds/SECS_IN_A_DAY));
if (ctx->args.userid)
mboxlist_usermboxtree(ctx->args.userid, NULL, expire_conversations,
&ctx->crock, MBOXTREE_DELETED);
else
mboxlist_allmbox(ctx->args.mbox_prefix, expire_conversations,
&ctx->crock, 0);
syslog(LOG_NOTICE, "Expired %lu entries of %lu entries seen "
"in %lu conversation databases",
ctx->crock.msgids_expired,
ctx->crock.msgids_seen,
ctx->crock.databases_seen);
verbosep("Expired %lu entries of %lu entries seen "
"in %lu conversation databases\n",
ctx->crock.msgids_expired,
ctx->crock.msgids_seen,
ctx->crock.databases_seen);
}
return 0;
}
static int do_delete(struct cyr_expire_ctx *ctx)
{
int ret = 0;
if ((ctx->args.delete_seconds >= 0) &&
mboxlist_delayed_delete_isenabled() &&
config_getstring(IMAPOPT_DELETEDPREFIX)) {
int count = 0;
int i;
verbosep("Removing deleted mailboxes older than %0.2f days\n",
((double)ctx->args.delete_seconds/SECS_IN_A_DAY));
ctx->drock.delete_mark = time(0) - ctx->args.delete_seconds;
if (ctx->args.userid)
mboxlist_usermboxtree(ctx->args.userid, NULL, delete,
&ctx->drock, MBOXTREE_DELETED|MBOXTREE_INTERMEDIATES);
else
mboxlist_allmbox(ctx->args.mbox_prefix, delete, &ctx->drock, MBOXTREE_INTERMEDIATES);
for (i = 0 ; i < ctx->drock.to_delete.count ; i++) {
char *name = ctx->drock.to_delete.data[i];
if (sigquit)
return ret; /* return from here, will quit in main. */
verbosep("Removing: %s\n", name);
ret = mboxlist_deletemailboxlock(name, 1, NULL, NULL, NULL,
MBOXLIST_DELETE_KEEP_INTERMEDIARIES);
/* XXX: Ignoring the return from mboxlist_deletemailbox() ??? */
count++;
}
verbosep("Removed %d deleted mailboxes\n", count);
syslog(LOG_NOTICE, "Removed %d deleted mailboxes", count);
}
return ret;
}
static int do_duplicate_prune(struct cyr_expire_ctx *ctx)
{
int ret = 0;
if (ctx->args.expire_seconds > 0)
ret = duplicate_prune(ctx->args.expire_seconds, &ctx->erock.table);
return ret;
}
static int parse_args(int argc, char *argv[], struct arguments *args)
{
extern char *optarg;
int opt;
args->archive_seconds = -1;
args->delete_seconds = -1;
args->expire_seconds = -1;
args->expunge_seconds = -1;
args->do_expunge = true;
args->do_cid_expire = -1;
while ((opt = getopt(argc, argv, "C:D:E:X:A:p:u:vaxtch")) != EOF) {
switch (opt) {
case 'A':
if (!parse_duration(optarg, &args->archive_seconds)) usage();
break;
case 'C':
args->altconfig = optarg;
break;
case 'D':
if (!parse_duration(optarg, &args->delete_seconds))
usage();
break;
case 'E':
if (!parse_duration(optarg, &args->expire_seconds))
usage();
break;
case 'X':
if (!parse_duration(optarg, &args->expunge_seconds))
usage();
break;
case 'a':
args->skip_annotate = true;
break;
case 'c':
args->do_cid_expire = 0;
break;
case 'p':
if (args->userid) usage();
args->mbox_prefix = optarg;
break;
case 't':
args->do_userflags = true;
break;
case 'u':
if (args->mbox_prefix) usage();
args->userid = optarg;
break;
case 'v':
verbose++;
break;
case 'x':
args->do_expunge = false;
break;
case 'h':
default:
usage();
break;
}
}
if (args->archive_seconds == -1 &&
args->delete_seconds == -1 &&
args->expire_seconds == -1 &&
args->expunge_seconds == -1 &&
!args->do_userflags) {
/* TODO: Print a more useful error message here. */
fprintf(stderr, "Missing arguments.\n");
usage();
return -EINVAL;
}
return 0;
}
int main(int argc, char *argv[])
{
int r = 0;
struct cyr_expire_ctx ctx = zero_ctx;
progname = basename(argv[0]);
if (parse_args(argc, argv, &ctx.args) != 0)
exit(EXIT_FAILURE);
cyr_expire_init(progname, &ctx);
/* do_cid_expire defaults to whatever IMAP options are set */
if (ctx.args.do_cid_expire < 0)
ctx.args.do_cid_expire = config_getswitch(IMAPOPT_CONVERSATIONS);
/* Set namespace -- force standard (internal) */
if ((r = mboxname_init_namespace(&expire_namespace, 1)) != 0) {
syslog(LOG_ERR, "%s", error_message(r));
fatal(error_message(r), EX_CONFIG);
}
mboxevent_setnamespace(&expire_namespace);
/* now that we have a namespace, convert mbox_prefix to internal ns */
if (ctx.args.mbox_prefix) {
char *intname = mboxname_from_external(ctx.args.mbox_prefix,
&expire_namespace, NULL);
ctx.args.mbox_prefix = ctx.args.freeme = intname;
}
if (duplicate_init(NULL) != 0) {
fprintf(stderr,
"cyr_expire: unable to init duplicate delivery database\n");
exit(1);
}
r = do_archive(&ctx);
if (sigquit)
goto finish;
r = do_expunge(&ctx);
if (sigquit)
goto finish;
r = do_cid_expire(&ctx);
if (sigquit)
goto finish;
r = do_delete(&ctx);
if (sigquit)
goto finish;
/* purge deliver.db entries of expired messages */
r = do_duplicate_prune(&ctx);
finish:
cyr_expire_cleanup(&ctx);
exit(r);
}