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