1 /* cyr_expire.c -- Program to expire deliver.db entries and messages
2 *
3 * Copyright (c) 1994-2017 Carnegie Mellon University. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
16 *
17 * 3. The name "Carnegie Mellon University" must not be used to
18 * endorse or promote products derived from this software without
19 * prior written permission. For permission or any legal
20 * details, please contact
21 * Carnegie Mellon University
22 * Center for Technology Transfer and Enterprise Creation
23 * 4615 Forbes Avenue
24 * Suite 302
25 * Pittsburgh, PA 15213
26 * (412) 268-7393, fax: (412) 268-7395
27 * innovation@andrew.cmu.edu
28 *
29 * 4. Redistributions of any form whatsoever must retain the following
30 * acknowledgment:
31 * "This product includes software developed by Computing Services
32 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33 *
34 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41 */
42
43 /*
44 NOTES:
45 * Precedence of configuration in `cyr_expire`:
46 Highest Lowest
47 |----------------------------------|
48 Annotation -> Command-line -> Config
49 */
50
51 #include <config.h>
52
53 #ifdef HAVE_UNISTD_H
54 #include <unistd.h>
55 #endif
56 #include <stdlib.h>
57 #include <stdio.h>
58 #include <string.h>
59 #include <fcntl.h>
60 #include <sys/stat.h>
61 #include <sysexits.h>
62 #include <syslog.h>
63 #include <signal.h>
64 #include <errno.h>
65 #include <stdbool.h>
66 #include <libgen.h>
67
68 #include <sasl/sasl.h>
69
70 #include "annotate.h"
71 #include "duplicate.h"
72 #include "global.h"
73 #include "hash.h"
74 #include "libcyr_cfg.h"
75 #include "mboxevent.h"
76 #include "mboxlist.h"
77 #include "conversations.h"
78 #include "util.h"
79 #include "xmalloc.h"
80 #include "strarray.h"
81
82 /* generated headers are not necessarily in current directory */
83 #include "imap/imap_err.h"
84
85 #define SECS_IN_A_MIN 60
86 #define SECS_IN_AN_HR (60 * SECS_IN_A_MIN)
87 #define SECS_IN_A_DAY (24 * SECS_IN_AN_HR)
88
89 /* global state */
90 static volatile sig_atomic_t sigquit = 0;
91 static int verbose = 0;
92 static const char *progname = NULL;
93 static struct namespace expire_namespace; /* current namespace */
94
95 /* command line arguments */
96 struct arguments {
97 int archive_seconds;
98 int delete_seconds;
99 int expire_seconds;
100 int expunge_seconds;
101
102 int do_cid_expire;
103
104 /* bools */
105 bool do_expunge;
106 bool do_userflags;
107 bool skip_annotate;
108
109 const char *altconfig;
110 const char *mbox_prefix;
111 const char *userid;
112 };
113
114 struct archive_rock {
115 time_t archive_mark;
116 unsigned long messages_archived;
117 bool skip_annotate;
118 };
119
120 struct expire_rock {
121 struct hash_table table;
122 time_t expire_mark;
123 time_t expunge_mark;
124 time_t tombstone_mark;
125 unsigned long mailboxes_seen;
126 unsigned long messages_seen;
127 unsigned long messages_expired;
128 unsigned long messages_expunged;
129 unsigned long userflags_expunged;
130 bit32 userflags[MAX_USER_FLAGS/32];
131 bool do_userflags;
132 bool skip_annotate;
133 };
134
135 struct conversations_rock {
136 struct hash_table seen;
137 time_t expire_mark;
138 unsigned long databases_seen;
139 unsigned long msgids_seen;
140 unsigned long msgids_expired;
141 };
142
143 struct delete_rock {
144 time_t delete_mark;
145 strarray_t to_delete;
146 bool skip_annotate;
147 };
148
149 /* The global context */
150 struct cyr_expire_ctx {
151 struct arguments args;
152 struct archive_rock arock;
153 struct conversations_rock crock;
154 struct delete_rock drock;
155 struct expire_rock erock;
156 };
157
158 static const struct cyr_expire_ctx zero_ctx;
159
160 static void sighandler(int sig);
161
162 /* verbosep - a wrapper to print if the 'verbose' option is
163 turned on.
164 */
165 __attribute__((format (printf, 1, 2)))
verbosep(const char * fmt,...)166 static inline void verbosep(const char *fmt, ...)
167 {
168 va_list params;
169
170 if (!verbose)
171 return;
172
173 va_start(params, fmt);
174 vfprintf(stderr, fmt, params);
175 va_end(params);
176 fputc('\n', stderr);
177 }
178
cyr_expire_init(const char * progname,struct cyr_expire_ctx * ctx)179 static void cyr_expire_init(const char *progname, struct cyr_expire_ctx *ctx)
180 {
181 struct sigaction action;
182
183 /* Initialise signal handlers */
184 sigemptyset(&action.sa_mask);
185 action.sa_flags = 0;
186 action.sa_handler = sighandler;
187 if (sigaction(SIGQUIT, &action, NULL) < 0)
188 fatal("unable to install signal handler for SIGQUIT", EX_TEMPFAIL);
189 if (sigaction(SIGINT, &action, NULL) < 0)
190 fatal("unable to install signal handler for SIGINT", EX_TEMPFAIL);
191 if (sigaction(SIGTERM, &action, NULL) < 0)
192 fatal("unable to install signal handler for SIGTERM", EX_TEMPFAIL);
193
194 construct_hash_table(&ctx->erock.table, 10000, 1);
195 strarray_init(&ctx->drock.to_delete);
196 construct_hash_table(&ctx->crock.seen, 100, 1);
197
198 cyrus_init(ctx->args.altconfig, progname, 0, 0);
199 global_sasl_init(1, 0, NULL);
200
201 ctx->erock.do_userflags = ctx->args.do_userflags;
202 /* TODO: Ideally all the functions should just use the skip_annotate from
203 * args. But that would require a change in the callback signatures.
204 * So retaining it as is for now.
205 */
206 ctx->arock.skip_annotate = ctx->args.skip_annotate;
207 ctx->erock.skip_annotate = ctx->args.skip_annotate;
208 ctx->drock.skip_annotate = ctx->args.skip_annotate;
209 }
210
cyr_expire_cleanup(struct cyr_expire_ctx * ctx)211 static void cyr_expire_cleanup(struct cyr_expire_ctx *ctx)
212 {
213 free_hash_table(&ctx->erock.table, free);
214 free_hash_table(&ctx->crock.seen, NULL);
215 strarray_fini(&ctx->drock.to_delete);
216
217 duplicate_done();
218 sasl_done();
219 cyrus_done();
220 }
221
usage(void)222 static void usage(void)
223 {
224 fprintf(stderr, "Usage: %s [OPTIONS] {mailbox|users}\n", progname);
225 fprintf(stderr, "Expire messages and duplicate delivery database entries.\n");
226 fprintf(stderr, "\n");
227
228 fprintf(stderr, "-a skip annotation lookup\n");
229 fprintf(stderr, "-c do not expire conversations\n");
230 fprintf(stderr, "-h print this help and exit\n");
231 fprintf(stderr, "-p <mailbox-prefix> specify prefix for mailboxes\n");
232 fprintf(stderr, "-t remove user flags which are not used\n");
233 fprintf(stderr, "-u <user-id> specify user id for mailbox lookup\n");
234 fprintf(stderr, "-v enable verbose output\n");
235 fprintf(stderr, "-x do not expunge messages\n");
236 fprintf(stderr, "-C <config-file> use <config-file> instead of config from imapd.conf\n");
237 fprintf(stderr, "-A <archive-duration> \n");
238 fprintf(stderr, "-D <delete-duration> \n");
239 fprintf(stderr, "-E <expire-duration> \n");
240 fprintf(stderr, "-X <expunge-duration> \n");
241
242 fprintf(stderr, "\n");
243
244 exit(EX_USAGE);
245 }
246
247 /*
248 * Parse a non-negative duration string as seconds.
249 *
250 * Convert "23.5m" to fractional days. Accepts the suffixes "d" (day),
251 * (day), "h" (hour), "m" (minute) and "s" (second). If no suffix, assume
252 * days.
253 * Returns 1 if successful and *secondsp is filled in, or 0 if the suffix
254 * is unknown or on error.
255 */
parse_duration(const char * s,int * secondsp)256 static int parse_duration(const char *s, int *secondsp)
257 {
258 char *end = NULL;
259 double val;
260 int multiplier = SECS_IN_A_DAY; /* default is days */
261
262 /* no negative or empty numbers please */
263 if (!*s || *s == '-')
264 return 0;
265
266 val = strtod(s, &end);
267 /* Allow 'd', 'h', 'm' and 's' as end, else return error. */
268 if (*end) {
269 if (end[1]) return 0; /* trailing extra junk */
270
271 switch (*end) {
272 case 'd':
273 /* already the default */
274 break;
275 case 'h':
276 multiplier = SECS_IN_AN_HR;
277 break;
278 case 'm':
279 multiplier = SECS_IN_A_MIN;
280 break;
281 case 's':
282 multiplier = 1;
283 break;
284 default:
285 return 0;
286 }
287 }
288
289 *secondsp = multiplier * val;
290
291 return 1;
292 }
293
294 /*
295 * Given an annotation, reads it, and converts it into 'seconds',
296 * using `parse_duration`.
297 *
298 * On Success: Returns 1
299 * On Failure: Returns 0
300 */
get_annotation_value(const char * mboxname,const char * annot_entry,int * secondsp,bool iterate)301 static int get_annotation_value(const char *mboxname,
302 const char *annot_entry,
303 int *secondsp, bool iterate)
304 {
305 struct buf attrib = BUF_INITIALIZER;
306 int ret = 0;
307 /* mboxname needs to be copied since `mboxname_make_parent`
308 * runs a strrchr() on it.
309 */
310 char *buf = xstrdup(mboxname);
311
312 /*
313 * Mailboxes inherit /vendo/cmu/cyrus-imapd/{expire, archive, delete},
314 * so we need to iterate all the way up to "" (server entry).
315 */
316 do {
317 buf_free(&attrib);
318 ret = annotatemore_lookup(buf, annot_entry, "", &attrib);
319 if (ret || /* error */
320 attrib.s) /* found an entry */
321 break;
322 } while (mboxname_make_parent(buf) && iterate);
323
324 if (attrib.s && parse_duration(attrib.s, secondsp))
325 ret = 1;
326 else
327 ret = 0;
328
329 buf_free(&attrib);
330 free(buf);
331
332 syslog(LOG_DEBUG, "get_annotation_value: ret(%d):secondsp(%d)\n", ret, *secondsp);
333 return ret;
334 }
335
expunge_userflags(struct mailbox * mailbox,struct expire_rock * erock)336 static int expunge_userflags(struct mailbox *mailbox, struct expire_rock *erock)
337 {
338 unsigned int i;
339 int r;
340
341 for (i = 0; i < MAX_USER_FLAGS; i++) {
342 if (erock->userflags[i/32] & 1<<(i&31))
343 continue;
344 if (!mailbox->flagname[i])
345 continue;
346 verbosep("Expunging userflag %u (%s) from %s\n",
347 i, mailbox->flagname[i], mailbox->name);
348 r = mailbox_remove_user_flag(mailbox, i);
349 if (r) return r;
350 erock->userflags_expunged++;
351 }
352
353 return 0;
354 }
355
356 /*
357 * mailbox_expunge() callback to *only* count userflags.
358 */
userflag_cb(struct mailbox * mailbox,const struct index_record * record,void * rock)359 static unsigned userflag_cb(struct mailbox *mailbox __attribute__((unused)),
360 const struct index_record *record,
361 void *rock)
362 {
363 struct expire_rock *erock = (struct expire_rock *) rock;
364 unsigned int i;
365
366 /* record which user flags are set */
367 for (i = 0; i < (MAX_USER_FLAGS/32); i++)
368 erock->userflags[i] |= record->user_flags[i];
369
370 return 0; /* always keep the message */
371 }
372
archive(const mbentry_t * mbentry,void * rock)373 static int archive(const mbentry_t *mbentry, void *rock)
374 {
375 struct archive_rock *arock = (struct archive_rock *) rock;
376 struct mailbox *mailbox = NULL;
377 int archive_seconds = -1;
378
379 if (sigquit)
380 return 1;
381
382 if (mbentry->mbtype & MBTYPE_DELETED)
383 goto done;
384
385 if (mbentry->mbtype & MBTYPE_REMOTE)
386 goto done;
387
388 if (mailbox_open_iwl(mbentry->name, &mailbox))
389 goto done;
390
391 /* check /vendor/cmu/cyrus-imapd/archive */
392 if (!arock->skip_annotate &&
393 get_annotation_value(mbentry->name, IMAP_ANNOT_NS "archive",
394 &archive_seconds, false)) {
395 arock->archive_mark = archive_seconds ?
396 time(0) - archive_seconds : 0;
397 }
398
399 /* The default callback for mailbox_archive() is mailbox_should_archive()
400 * in imap/mailbox.c. This one takes the arock->archive_mark as the
401 * callback data.
402 */
403 mailbox_archive(mailbox, NULL, &arock->archive_mark, ITER_SKIP_EXPUNGED);
404
405 done:
406 mailbox_close(&mailbox);
407
408 /* move on to the next mailbox regardless of errors */
409 return 0;
410 }
411
412 /*
413 * mailbox_expunge() callback to expunge expired articles.
414 */
expire_cb(struct mailbox * mailbox,const struct index_record * record,void * rock)415 static unsigned expire_cb(struct mailbox *mailbox __attribute__((unused)),
416 const struct index_record *record,
417 void *rock)
418 {
419 struct expire_rock *erock = (struct expire_rock *) rock;
420 unsigned int i;
421
422 /* otherwise, we're expiring messages by sent date */
423 if (record->gmtime < erock->expire_mark) {
424 erock->messages_expired++;
425 return 1;
426 }
427
428 /* record which user flags are set */
429 for (i = 0; i < (MAX_USER_FLAGS/32); i++)
430 erock->userflags[i] |= record->user_flags[i];
431
432 return 0;
433 }
434
435 /*
436 * callback function to:
437 * - expire messages from mailboxes,
438 * - build a hash table of mailboxes in which we expired messages,
439 * - and perform a cleanup of expunged messages
440 */
expire(const mbentry_t * mbentry,void * rock)441 static int expire(const mbentry_t *mbentry, void *rock)
442 {
443 struct expire_rock *erock = (struct expire_rock *) rock;
444 int r;
445 struct mailbox *mailbox = NULL;
446 unsigned numexpunged = 0;
447 int expire_seconds = 0;
448 int did_expunge = 0;
449
450 if (sigquit) {
451 /* don't care if we leak some memory, we are shutting down */
452 return 1;
453 }
454
455 /* Skip remote mailboxes */
456 if (mbentry->mbtype & MBTYPE_REMOTE)
457 goto done;
458
459 /* clean up deleted entries after 7 days */
460 if (mbentry->mbtype & MBTYPE_DELETED) {
461 if (mbentry->mtime < erock->tombstone_mark) {
462 verbosep("Removing stale tombstone for %s\n", mbentry->name);
463 syslog(LOG_NOTICE, "Removing stale tombstone for %s", mbentry->name);
464 mboxlist_delete(mbentry->name);
465 }
466 goto done;
467 }
468
469 memset(erock->userflags, 0, sizeof(erock->userflags));
470
471 r = mailbox_open_iwl(mbentry->name, &mailbox);
472 if (r) {
473 /* mailbox corrupt/nonexistent -- skip it */
474 syslog(LOG_WARNING, "unable to open mailbox %s: %s",
475 mbentry->name, error_message(r));
476 goto done;
477 }
478
479 /* see if we need to expire messages.
480 * since mailboxes inherit /vendor/cmu/cyrus-imapd/expire,
481 * we need to iterate all the way up to "" (server entry)
482 */
483 if (!erock->skip_annotate &&
484 get_annotation_value(mbentry->name, IMAP_ANNOT_NS "expire",
485 &expire_seconds, true)) {
486 /* add mailbox to table */
487 erock->expire_mark = expire_seconds ?
488 time(0) - expire_seconds : 0 /* never */ ;
489 hash_insert(mbentry->name,
490 xmemdup(&erock->expire_mark, sizeof(erock->expire_mark)),
491 &erock->table);
492
493 if (expire_seconds) {
494 verbosep("expiring messages in %s older than %0.2f days\n",
495 mbentry->name,
496 ((double)expire_seconds/SECS_IN_A_DAY));
497
498 r = mailbox_expunge(mailbox, expire_cb, erock, NULL,
499 EVENT_MESSAGE_EXPIRE);
500 if (r)
501 syslog(LOG_ERR, "failed to expire old messages: %s", mbentry->name);
502 did_expunge = 1;
503 }
504 }
505
506 if (!did_expunge && erock->do_userflags) {
507 r = mailbox_expunge(mailbox, userflag_cb, erock, NULL,
508 EVENT_MESSAGE_EXPIRE);
509 if (r)
510 syslog(LOG_ERR, "failed to scan user flags for %s: %s",
511 mbentry->name, error_message(r));
512 }
513
514 erock->messages_seen += mailbox->i.num_records;
515
516 if (erock->do_userflags)
517 expunge_userflags(mailbox, erock);
518
519 verbosep("cleaning up expunged messages in %s\n", mbentry->name);
520
521 r = mailbox_expunge_cleanup(mailbox, erock->expunge_mark, &numexpunged);
522
523 erock->messages_expunged += numexpunged;
524 erock->mailboxes_seen++;
525
526 if (r) {
527 syslog(LOG_WARNING, "failure expiring %s: %s", mbentry->name, error_message(r));
528 annotate_state_abort(&mailbox->annot_state);
529 }
530
531 done:
532 mailbox_close(&mailbox);
533 /* Even if we had a problem with one mailbox, continue with the others */
534 return 0;
535 }
536
delete(const mbentry_t * mbentry,void * rock)537 static int delete(const mbentry_t *mbentry, void *rock)
538 {
539 struct delete_rock *drock = (struct delete_rock *) rock;
540 time_t timestamp;
541 int delete_seconds = -1;
542
543 if (sigquit)
544 return 1;
545
546 if (mbentry->mbtype & MBTYPE_DELETED)
547 goto done;
548
549 if (mbentry->mbtype & MBTYPE_REMOTE)
550 goto done;
551
552 /* check if this is a mailbox we want to examine */
553 if (!mboxname_isdeletedmailbox(mbentry->name, ×tamp))
554 goto done;
555
556 /* check /vendor/cmu/cyrus-imapd/delete */
557 if (!drock->skip_annotate &&
558 get_annotation_value(mbentry->name, IMAP_ANNOT_NS "delete",
559 &delete_seconds, false)) {
560 drock->delete_mark = delete_seconds ?
561 time(0) - delete_seconds: 0;
562 }
563
564 if ((timestamp == 0) || (timestamp > drock->delete_mark))
565 goto done;
566
567 verbosep("Cleaning up %s\n", mbentry->name);
568
569 /* Add this mailbox to list of mailboxes to delete */
570 strarray_append(&drock->to_delete, mbentry->name);
571
572 done:
573 /* Even if we had a problem with one mailbox, continue with the others */
574 return 0;
575 }
576
expire_conversations(const mbentry_t * mbentry,void * rock)577 static int expire_conversations(const mbentry_t *mbentry, void *rock)
578 {
579 struct conversations_rock *crock = (struct conversations_rock *)rock;
580 struct conversations_state *state = NULL;
581 unsigned int nseen = 0, ndeleted = 0;
582 char *filename = NULL;
583
584 if (sigquit)
585 return 1;
586
587 if (mbentry->mbtype & MBTYPE_DELETED)
588 goto done;
589
590 if (mbentry->mbtype & MBTYPE_REMOTE)
591 goto done;
592
593 if (mboxname_isdeletedmailbox(mbentry->name, NULL))
594 goto done;
595
596 filename = conversations_getmboxpath(mbentry->name);
597 if (!filename)
598 goto done;
599
600 if (hash_lookup(filename, &crock->seen))
601 goto done;
602
603 verbosep("Pruning conversations from db %s\n", filename);
604
605 if (!conversations_open_mbox(mbentry->name, 0/*shared*/, &state)) {
606 conversations_prune(state, crock->expire_mark, &nseen, &ndeleted);
607 conversations_commit(&state);
608 }
609
610 hash_insert(filename, (void *)1, &crock->seen);
611
612 crock->databases_seen++;
613 crock->msgids_seen += nseen;
614 crock->msgids_expired += ndeleted;
615
616 done:
617 free(filename);
618 return 0;
619 }
620
sighandler(int sig __attribute ((unused)))621 static void sighandler(int sig __attribute((unused)))
622 {
623 sigquit = 1;
624 return;
625 }
626
do_archive(struct cyr_expire_ctx * ctx)627 static int do_archive(struct cyr_expire_ctx *ctx)
628 {
629 if (ctx->args.archive_seconds >= 0) {
630 syslog(LOG_DEBUG, ">> do_archive: archive_seconds(%d) >= 0\n",
631 ctx->args.archive_seconds);
632 ctx->arock.archive_mark = time(0) - ctx->args.archive_seconds;
633
634 if (ctx->args.userid)
635 mboxlist_usermboxtree(ctx->args.userid, NULL, archive,
636 &ctx->arock, MBOXTREE_DELETED);
637 else
638 mboxlist_allmbox(ctx->args.mbox_prefix, archive, &ctx->arock, 0);
639 }
640
641 return 0;
642 }
643
do_expunge(struct cyr_expire_ctx * ctx)644 static int do_expunge(struct cyr_expire_ctx *ctx)
645 {
646 if (ctx->args.do_expunge && (ctx->args.expunge_seconds >= 0 ||
647 ctx->args.expire_seconds ||
648 ctx->erock.do_userflags)) {
649 /* XXX: better way to determine a size for this table? */
650
651 /* expire messages from mailboxes,
652 * build a hash table of mailboxes in which we expired messages,
653 * and perform a cleanup of expunged messages
654 */
655 if (ctx->args.expunge_seconds < 0) {
656 ctx->erock.expunge_mark = 0;
657 } else {
658 ctx->erock.expunge_mark = time(0) - ctx->args.expunge_seconds;
659
660 verbosep("Expunging deleted messages in mailboxes older than %0.2f days\n",
661 ((double)ctx->args.expunge_seconds/SECS_IN_A_DAY));
662 }
663
664 /* XXX _ a control for this too? */
665 ctx->erock.tombstone_mark = time(0) - SECS_IN_A_DAY*7;
666
667 if (ctx->args.userid)
668 mboxlist_usermboxtree(ctx->args.userid, NULL, expire,
669 &ctx->erock, MBOXTREE_DELETED|MBOXTREE_TOMBSTONES);
670 else
671 mboxlist_allmbox(ctx->args.mbox_prefix, expire, &ctx->erock,
672 MBOXTREE_TOMBSTONES);
673
674 syslog(LOG_NOTICE, "Expired %lu and expunged %lu out of %lu "
675 "messages from %lu mailboxes",
676 ctx->erock.messages_expired,
677 ctx->erock.messages_expunged,
678 ctx->erock.messages_seen,
679 ctx->erock.mailboxes_seen);
680 verbosep("\nExpired %lu and expunged %lu out of %lu "
681 "messages from %lu mailboxes\n",
682 ctx->erock.messages_expired,
683 ctx->erock.messages_expunged,
684 ctx->erock.messages_seen,
685 ctx->erock.mailboxes_seen);
686
687 if (ctx->erock.do_userflags) {
688 syslog(LOG_NOTICE, "Expunged %lu user flags",
689 ctx->erock.userflags_expunged);
690 verbosep("Expunged %lu user flags\n",
691 ctx->erock.userflags_expunged);
692 }
693
694 }
695
696 return 0;
697 }
698
do_cid_expire(struct cyr_expire_ctx * ctx)699 static int do_cid_expire(struct cyr_expire_ctx *ctx)
700 {
701 if (ctx->args.do_cid_expire) {
702 int cid_expire_seconds;
703
704 cid_expire_seconds = config_getduration(IMAPOPT_CONVERSATIONS_EXPIRE_AFTER, 'd');
705 ctx->crock.expire_mark = time(0) - cid_expire_seconds;
706
707 verbosep("Removing conversation entries older than %0.2f days\n",
708 (double)(cid_expire_seconds/SECS_IN_A_DAY));
709
710 if (ctx->args.userid)
711 mboxlist_usermboxtree(ctx->args.userid, NULL, expire_conversations,
712 &ctx->crock, MBOXTREE_DELETED);
713 else
714 mboxlist_allmbox(ctx->args.mbox_prefix, expire_conversations,
715 &ctx->crock, 0);
716
717 syslog(LOG_NOTICE, "Expired %lu entries of %lu entries seen "
718 "in %lu conversation databases",
719 ctx->crock.msgids_expired,
720 ctx->crock.msgids_seen,
721 ctx->crock.databases_seen);
722 verbosep("Expired %lu entries of %lu entries seen "
723 "in %lu conversation databases\n",
724 ctx->crock.msgids_expired,
725 ctx->crock.msgids_seen,
726 ctx->crock.databases_seen);
727 }
728
729 return 0;
730 }
731
do_delete(struct cyr_expire_ctx * ctx)732 static int do_delete(struct cyr_expire_ctx *ctx)
733 {
734 int ret = 0;
735
736 if ((ctx->args.delete_seconds >= 0) &&
737 mboxlist_delayed_delete_isenabled() &&
738 config_getstring(IMAPOPT_DELETEDPREFIX)) {
739 int count = 0;
740 int i;
741
742 verbosep("Removing deleted mailboxes older than %0.2f days\n",
743 ((double)ctx->args.delete_seconds/SECS_IN_A_DAY));
744
745 ctx->drock.delete_mark = time(0) - ctx->args.delete_seconds;
746
747 if (ctx->args.userid)
748 mboxlist_usermboxtree(ctx->args.userid, NULL, delete,
749 &ctx->drock, MBOXTREE_DELETED);
750 else
751 mboxlist_allmbox(ctx->args.mbox_prefix, delete, &ctx->drock, 0);
752
753 for (i = 0 ; i < ctx->drock.to_delete.count ; i++) {
754 char *name = ctx->drock.to_delete.data[i];
755
756 if (sigquit)
757 return ret; /* return from here, will quit in main. */
758
759 verbosep("Removing: %s\n", name);
760
761 ret = mboxlist_deletemailboxlock(name, 1, NULL, NULL, NULL, 0, 0, 0, 0);
762 /* XXX: Ignoring the return from mboxlist_deletemailbox() ??? */
763 count++;
764 }
765
766 verbosep("Removed %d deleted mailboxes\n", count);
767
768 syslog(LOG_NOTICE, "Removed %d deleted mailboxes", count);
769 }
770
771 return ret;
772 }
773
do_duplicate_prune(struct cyr_expire_ctx * ctx)774 static int do_duplicate_prune(struct cyr_expire_ctx *ctx)
775 {
776 int ret = 0;
777 if (ctx->args.expire_seconds > 0)
778 ret = duplicate_prune(ctx->args.expire_seconds, &ctx->erock.table);
779
780 return ret;
781 }
782
parse_args(int argc,char * argv[],struct arguments * args)783 static int parse_args(int argc, char *argv[], struct arguments *args)
784 {
785 extern char *optarg;
786 int opt;
787
788 args->archive_seconds = -1;
789 args->delete_seconds = -1;
790 args->expire_seconds = -1;
791 args->expunge_seconds = -1;
792 args->do_expunge = true;
793 args->do_cid_expire = -1;
794
795 while ((opt = getopt(argc, argv, "C:D:E:X:A:p:u:vaxtch")) != EOF) {
796 switch (opt) {
797 case 'A':
798 if (!parse_duration(optarg, &args->archive_seconds)) usage();
799 break;
800
801 case 'C':
802 args->altconfig = optarg;
803 break;
804
805 case 'D':
806 if (!parse_duration(optarg, &args->delete_seconds))
807 usage();
808 break;
809
810 case 'E':
811 if (!parse_duration(optarg, &args->expire_seconds))
812 usage();
813 break;
814
815 case 'X':
816 if (!parse_duration(optarg, &args->expunge_seconds))
817 usage();
818 break;
819
820 case 'a':
821 args->skip_annotate = true;
822 break;
823
824 case 'c':
825 args->do_cid_expire = 0;
826 break;
827
828 case 'p':
829 args->mbox_prefix = optarg;
830 break;
831
832 case 't':
833 args->do_userflags = true;
834 break;
835
836 case 'u':
837 args->userid = optarg;
838 break;
839
840 case 'v':
841 verbose++;
842 break;
843
844 case 'x':
845 args->do_expunge = false;
846 break;
847
848 case 'h':
849 default:
850 usage();
851 break;
852 }
853 }
854
855 if (args->archive_seconds == -1 &&
856 args->delete_seconds == -1 &&
857 args->expire_seconds == -1 &&
858 args->expunge_seconds == -1 &&
859 !args->do_userflags) {
860 /* TODO: Print a more useful error message here. */
861 fprintf(stderr, "Missing arguments.\n");
862 usage();
863 return -EINVAL;
864 }
865
866
867 return 0;
868 }
869
main(int argc,char * argv[])870 int main(int argc, char *argv[])
871 {
872 int r = 0;
873 struct cyr_expire_ctx ctx = zero_ctx;
874
875 progname = basename(argv[0]);
876
877 if (parse_args(argc, argv, &ctx.args) != 0)
878 exit(EXIT_FAILURE);
879
880 cyr_expire_init(progname, &ctx);
881
882 /* do_cid_expire defaults to whatever IMAP options are set */
883 if (ctx.args.do_cid_expire < 0)
884 ctx.args.do_cid_expire = config_getswitch(IMAPOPT_CONVERSATIONS);
885
886 /* Set namespace -- force standard (internal) */
887 if ((r = mboxname_init_namespace(&expire_namespace, 1)) != 0) {
888 syslog(LOG_ERR, "%s", error_message(r));
889 fatal(error_message(r), EX_CONFIG);
890 }
891
892 mboxevent_setnamespace(&expire_namespace);
893
894 if (duplicate_init(NULL) != 0) {
895 fprintf(stderr,
896 "cyr_expire: unable to init duplicate delivery database\n");
897 exit(1);
898 }
899
900 r = do_archive(&ctx);
901
902 if (sigquit)
903 goto finish;
904
905 r = do_expunge(&ctx);
906
907 if (sigquit)
908 goto finish;
909
910 r = do_cid_expire(&ctx);
911
912 if (sigquit)
913 goto finish;
914
915 r = do_delete(&ctx);
916
917 if (sigquit)
918 goto finish;
919
920 /* purge deliver.db entries of expired messages */
921 r = do_duplicate_prune(&ctx);
922
923 finish:
924 cyr_expire_cleanup(&ctx);
925 exit(r);
926 }
927