1 /*
2  * Copyright (c) 1994-2008 Carnegie Mellon University.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in
13  *    the documentation and/or other materials provided with the
14  *    distribution.
15  *
16  * 3. The name "Carnegie Mellon University" must not be used to
17  *    endorse or promote products derived from this software without
18  *    prior written permission. For permission or any legal
19  *    details, please contact
20  *      Carnegie Mellon University
21  *      Center for Technology Transfer and Enterprise Creation
22  *      4615 Forbes Avenue
23  *      Suite 302
24  *      Pittsburgh, PA  15213
25  *      (412) 268-7393, fax: (412) 268-7395
26  *      innovation@andrew.cmu.edu
27  *
28  * 4. Redistributions of any form whatsoever must retain the following
29  *    acknowledgment:
30  *    "This product includes software developed by Computing Services
31  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
32  *
33  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
34  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
35  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
36  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
37  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
38  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
39  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
40  */
41 
42 #include <config.h>
43 
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <sysexits.h>
48 #include <syslog.h>
49 #include <ctype.h>
50 #include <sys/stat.h>
51 #include <sys/types.h>
52 #include <fcntl.h>
53 #ifdef HAVE_UNISTD_H
54 #include <unistd.h>
55 #endif
56 #if HAVE_DIRENT_H
57 # include <dirent.h>
58 #else
59 # define dirent direct
60 # if HAVE_SYS_NDIR_H
61 #  include <sys/ndir.h>
62 # endif
63 # if HAVE_SYS_DIR_H
64 #  include <sys/dir.h>
65 # endif
66 # if HAVE_NDIR_H
67 #  include <ndir.h>
68 # endif
69 #endif
70 
71 #include "assert.h"
72 #include "xmalloc.h"
73 #include "global.h"
74 #include "util.h"
75 #include "cyrusdb.h"
76 
77 /* generated headers are not necessarily in current directory */
78 #include "imap/imap_err.h"
79 
80 #include "duplicate.h"
81 
82 #define DEBUG 0
83 
84 #define DB (config_duplicate_db)
85 
86 static struct db *dupdb = NULL;
87 static int duplicate_dbopen = 0;
88 
89 /* must be called after cyrus_init */
duplicate_init(const char * fname)90 EXPORTED int duplicate_init(const char *fname)
91 {
92     int r = 0;
93     char *tofree = NULL;
94 
95     if (!fname)
96         fname = config_getstring(IMAPOPT_DUPLICATE_DB_PATH);
97 
98     /* create db file name */
99     if (!fname) {
100         tofree = strconcat(config_dir, FNAME_DELIVERDB, (char *)NULL);
101         fname = tofree;
102     }
103 
104     r = cyrusdb_open(DB, fname, CYRUSDB_CREATE, &dupdb);
105     if (r != 0) {
106         syslog(LOG_ERR, "DBERROR: opening %s: %s", fname,
107                cyrusdb_strerror(r));
108         goto out;
109     }
110     duplicate_dbopen = 1;
111 
112 out:
113     free(tofree);
114 
115     return r;
116 }
117 
make_key(struct buf * key,const duplicate_key_t * dkey)118 static int make_key(struct buf *key, const duplicate_key_t *dkey)
119 {
120     if (!dkey ||
121         !dkey->id ||
122         !dkey->to ||
123         !dkey->date)
124         return IMAP_INTERNAL;
125 
126     buf_reset(key);
127     buf_appendmap(key, dkey->id, strlen(dkey->id)+1);
128     buf_appendmap(key, dkey->to, strlen(dkey->to)+1);
129     buf_appendmap(key, dkey->date, strlen(dkey->date)+1);
130     /* We have three concatenated values now, all parts ending with '\0' */
131 
132     return 0;
133 }
134 
split_key(const char * key,int keylen,duplicate_key_t * dkey)135 static int split_key(const char *key, int keylen, duplicate_key_t *dkey)
136 {
137 #define MAXFIELDS 3
138     const char *fields[MAXFIELDS];
139     int n = 0;
140     const char *p;
141 
142     /* check the key as a whole is nul-terminated */
143     if (key[keylen-1] != '\0')
144         return IMAP_INTERNAL;
145 
146     /* find the \0 field boundaries */
147     for (p = key ; p < (key+keylen) ; p += strlen(p)+1) {
148         if (n == MAXFIELDS)
149             return IMAP_INTERNAL;
150         fields[n++] = p;
151     }
152 
153     if (n != 3)
154         return IMAP_INTERNAL;
155     dkey->id = fields[0];
156     dkey->to = fields[1];
157     dkey->date = fields[2];
158 
159     return 0;
160 #undef MAXFIELDS
161 }
162 
duplicate_check(const duplicate_key_t * dkey)163 EXPORTED time_t duplicate_check(const duplicate_key_t *dkey)
164 {
165     struct buf key = BUF_INITIALIZER;
166     int r;
167     const char *data = NULL;
168     size_t len = 0;
169     time_t mark = 0;
170 
171     if (!duplicate_dbopen) return 0;
172 
173     r = make_key(&key, dkey);
174     if (r) return 0;
175 
176     do {
177         r = cyrusdb_fetch(dupdb, key.s, key.len,
178                       &data, &len, NULL);
179     } while (r == CYRUSDB_AGAIN);
180 
181     if (!r && data) {
182         assert((len == sizeof(time_t)) ||
183                (len == sizeof(time_t) + sizeof(unsigned long)));
184 
185         /* found the record */
186         memcpy(&mark, data, sizeof(time_t));
187     } else if (r != CYRUSDB_OK) {
188         if (r != CYRUSDB_NOTFOUND) {
189             syslog(LOG_ERR, "duplicate_check: error looking up %s/%s/%s: %s",
190                    dkey->id, dkey->to, dkey->date,
191                    cyrusdb_strerror(r));
192         }
193         mark = 0;
194     }
195 
196 #if DEBUG
197     syslog(LOG_DEBUG, "duplicate_check: %-40s %-20s %-40s %ld",
198            dkey->id, dkey->to, dkey->date, mark);
199 #endif
200 
201     buf_free(&key);
202     return mark;
203 }
204 
duplicate_log(const duplicate_key_t * dkey,const char * action)205 EXPORTED void duplicate_log(const duplicate_key_t *dkey, const char *action)
206 {
207     assert(dkey->date != NULL);
208     syslog(LOG_INFO, "dupelim: eliminated duplicate message to %s id %s date %s (%s)",
209       dkey->to, dkey->id, dkey->date, action);
210     if (config_auditlog)
211         syslog(LOG_NOTICE, "auditlog: duplicate sessionid=<%s> action=<%s> message-id=%s user=<%s> date=<%s>",
212                session_id(), action, dkey->id, dkey->to, dkey->date);
213 }
214 
duplicate_mark(const duplicate_key_t * dkey,time_t mark,unsigned long uid)215 EXPORTED void duplicate_mark(const duplicate_key_t *dkey, time_t mark, unsigned long uid)
216 {
217     struct buf key = BUF_INITIALIZER;
218     char data[100];
219     int r;
220 
221     if (!duplicate_dbopen) return;
222 
223     r = make_key(&key, dkey);
224     if (r) return;
225 
226     memcpy(data, &mark, sizeof(mark));
227     memcpy(data + sizeof(mark), &uid, sizeof(uid));
228 
229     do {
230         r = cyrusdb_store(dupdb, key.s, key.len,
231                       data, sizeof(mark)+sizeof(uid), NULL);
232     } while (r == CYRUSDB_AGAIN);
233 
234 #if DEBUG
235     syslog(LOG_DEBUG, "duplicate_mark: %-40s %-20s %-40s %ld %lu",
236            dkey->id, dkey->to, dkey->date, mark, uid);
237 #endif
238     buf_free(&key);
239 }
240 
241 struct findrock {
242     duplicate_find_proc_t proc;
243     void *rock;
244 };
245 
find_cb(void * rock,const char * key,size_t keylen,const char * data,size_t datalen)246 static int find_cb(void *rock, const char *key, size_t keylen,
247                    const char *data, size_t datalen)
248 {
249     struct findrock *frock = (struct findrock *) rock;
250     duplicate_key_t dkey = DUPLICATE_INITIALIZER;
251     time_t mark;
252     unsigned long uid = 0;
253     int r;
254 
255     r = split_key(key, keylen, &dkey);
256     if (r) return 0;    /* ignore broken records */
257 
258     /* make sure its a mailbox */
259     if (dkey.to[0] == '.') return 0;
260 
261     /* grab the mark and uid */
262     memcpy(&mark, data, sizeof(time_t));
263     if (datalen > (int) sizeof(mark))
264         memcpy(&uid, data + sizeof(mark), sizeof(unsigned long));
265 
266     r = (*frock->proc)(&dkey, mark, uid, frock->rock);
267 
268     return r;
269 }
270 
duplicate_find(const char * msgid,duplicate_find_proc_t proc,void * rock)271 EXPORTED int duplicate_find(const char *msgid,
272                    duplicate_find_proc_t proc,
273                    void *rock)
274 {
275     struct findrock frock;
276 
277     if (!msgid) msgid = "";
278 
279     frock.proc = proc;
280     frock.rock = rock;
281 
282     /* check each entry in our database */
283     cyrusdb_foreach(dupdb, msgid, strlen(msgid), NULL, find_cb, &frock, NULL);
284 
285     return 0;
286 }
287 
288 struct prunerock {
289     struct db *db;
290     time_t expmark; /* default expmark, if not overridden by table entry */
291     struct hash_table *expire_table;
292     int count;
293     int deletions;
294 };
295 
prune_p(void * rock,const char * key,size_t keylen,const char * data,size_t datalen)296 static int prune_p(void *rock,
297                    const char *key, size_t keylen,
298                    const char *data, size_t datalen __attribute__((unused)))
299 {
300     struct prunerock *prock = (struct prunerock *) rock;
301     time_t mark, *expmark = NULL;
302     duplicate_key_t dkey = DUPLICATE_INITIALIZER;
303     int r;
304 
305     prock->count++;
306 
307     r = split_key(key, keylen, &dkey);
308     if (r) return 1;    /* broken record, want to prune it */
309 
310     /* grab the rcpt, make sure its a mailbox and lookup its expire time */
311     if (prock->expire_table && dkey.to[0] && dkey.to[0] != '.') {
312         expmark = (time_t *) hash_lookup(dkey.to, prock->expire_table);
313     }
314 
315     /* grab the mark */
316     memcpy(&mark, data, sizeof(time_t));
317 
318     /* check if we should prune this entry */
319     return (mark < (expmark ? *expmark : prock->expmark));
320 }
321 
prune_cb(void * rock,const char * id,size_t idlen,const char * data,size_t datalen)322 static int prune_cb(void *rock, const char *id, size_t idlen,
323                     const char *data __attribute__((unused)),
324                     size_t datalen __attribute__((unused)))
325 {
326     struct prunerock *prock = (struct prunerock *) rock;
327     int r;
328 
329     prock->deletions++;
330 
331     do {
332         r = cyrusdb_delete(prock->db, id, idlen, NULL, 0);
333     } while (r == CYRUSDB_AGAIN);
334 
335 
336     return 0;
337 }
338 
duplicate_prune(int seconds,struct hash_table * expire_table)339 EXPORTED int duplicate_prune(int seconds, struct hash_table *expire_table)
340 {
341     struct prunerock prock;
342 
343     if (seconds < 0) fatal("must specify positive number of seconds", EX_USAGE);
344 
345     prock.count = prock.deletions = 0;
346     prock.expmark = time(NULL) - seconds;
347     prock.expire_table = expire_table;
348     syslog(LOG_NOTICE, "duplicate_prune: pruning back %0.2f days",
349            ((double)seconds/86400));
350 
351     /* check each entry in our database */
352     prock.db = dupdb;
353     cyrusdb_foreach(dupdb, "", 0, &prune_p, &prune_cb, &prock, NULL);
354 
355     syslog(LOG_NOTICE, "duplicate_prune: purged %d out of %d entries",
356            prock.deletions, prock.count);
357 
358     return 0;
359 }
360 
361 struct dumprock {
362     FILE *f;
363     int count;
364 };
365 
dump_cb(void * rock,const char * key,size_t keylen,const char * data,size_t datalen)366 static int dump_cb(void *rock,
367                    const char *key, size_t keylen,
368                    const char *data, size_t datalen)
369 {
370     struct dumprock *drock = (struct dumprock *) rock;
371     time_t mark;
372     duplicate_key_t dkey = DUPLICATE_INITIALIZER;
373     char *freeme = NULL;
374     int r;
375     int idlen, i;
376     unsigned long uid = 0;
377 
378     assert((datalen == sizeof(time_t)) ||
379            (datalen == sizeof(time_t) + sizeof(unsigned long)));
380 
381     drock->count++;
382 
383     memcpy(&mark, data, sizeof(time_t));
384     if (datalen > (int) sizeof(mark))
385         memcpy(&uid, data + sizeof(mark), sizeof(unsigned long));
386 
387     r = split_key(key, keylen, &dkey);
388     if (r) goto out;
389     idlen = strlen(dkey.id);
390 
391     for (i = 0; i < idlen; i++) {
392         if (!isprint((unsigned char) dkey.id[i])) break;
393     }
394 
395     if (i != idlen) {
396         /* change to hexadecimal */
397         freeme = xmalloc(idlen * 2 + 1);
398         bin_to_hex(dkey.id, idlen, freeme, BH_UPPER);
399         dkey.id = freeme;
400     }
401 
402     fprintf(drock->f, "id: %-40s\tto: %-20s\tat: %ld\tuid: %lu\n",
403             dkey.id, dkey.to, (long) mark, uid);
404 
405 out:
406     if (freeme) free(freeme);
407 
408     return 0;
409 }
410 
duplicate_dump(FILE * f)411 EXPORTED int duplicate_dump(FILE *f)
412 {
413     struct dumprock drock;
414 
415     drock.f = f;
416     drock.count = 0;
417 
418     /* check each entry in our database */
419     cyrusdb_foreach(dupdb, "", 0, NULL, &dump_cb, &drock, NULL);
420 
421     return drock.count;
422 }
423 
duplicate_done(void)424 EXPORTED int duplicate_done(void)
425 {
426     int r = 0;
427 
428     if (duplicate_dbopen) {
429         r = cyrusdb_close(dupdb);
430         if (r) {
431             syslog(LOG_ERR, "DBERROR: error closing deliverdb: %s",
432                    cyrusdb_strerror(r));
433         }
434         duplicate_dbopen = 0;
435     }
436 
437     return r;
438 }
439