1 /* seen_db.c -- implementation of seen database using per-user berkeley db
2  *
3  * Copyright (c) 1994-2008 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 #include <config.h>
44 
45 #include <stdlib.h>
46 #include <syslog.h>
47 #include <string.h>
48 #include <sys/types.h>
49 #include <netinet/in.h>
50 #include <errno.h>
51 #ifdef HAVE_UNISTD_H
52 #include <unistd.h>
53 #endif
54 #include <fcntl.h>
55 #include <sys/stat.h>
56 #include <sys/uio.h>
57 #include "cyrusdb.h"
58 #include "map.h"
59 #include "util.h"
60 
61 #include "assert.h"
62 #include "global.h"
63 #include "xmalloc.h"
64 #include "mailbox.h"
65 #include "seen.h"
66 #include "sync_log.h"
67 #include "imparse.h"
68 
69 /* generated headers are not necessarily in current directory */
70 #include "imap/imap_err.h"
71 
72 #define FNAME_SEENSUFFIX ".seen" /* per user seen state extension */
73 #define FNAME_SEEN "/cyrus.seen" /* for legacy seen state */
74 
75 enum {
76     SEEN_VERSION = 1,
77     SEEN_DEBUG = 0
78 };
79 
80 struct seen {
81     char *user;                 /* what user is this for? */
82     struct db *db;
83     struct txn *tid;            /* outstanding txn, if any */
84 };
85 
86 #define DB (config_seenstate_db)
87 
seen_getpath(const char * userid)88 HIDDEN char *seen_getpath(const char *userid)
89 {
90     char *fname = xmalloc(strlen(config_dir) + sizeof(FNAME_DOMAINDIR) +
91                           sizeof(FNAME_USERDIR) + strlen(userid) +
92                           sizeof(FNAME_SEENSUFFIX) + 10);
93     char c, *domain;
94 
95     if (config_virtdomains && (domain = strchr(userid, '@'))) {
96         char d = (char) dir_hash_c(domain+1, config_fulldirhash);
97         *domain = '\0';  /* split user@domain */
98         c = (char) dir_hash_c(userid, config_fulldirhash);
99         sprintf(fname, "%s%s%c/%s%s%c/%s%s", config_dir, FNAME_DOMAINDIR, d,
100                 domain+1, FNAME_USERDIR, c, userid, FNAME_SEENSUFFIX);
101         *domain = '@';  /* reassemble user@domain */
102     }
103     else {
104         c = (char) dir_hash_c(userid, config_fulldirhash);
105         sprintf(fname, "%s%s%c/%s%s", config_dir, FNAME_USERDIR, c, userid,
106                 FNAME_SEENSUFFIX);
107     }
108 
109     return fname;
110 }
111 
seen_open(const char * user,int flags,struct seen ** seendbptr)112 EXPORTED int seen_open(const char *user,
113               int flags,
114               struct seen **seendbptr)
115 {
116     struct seen *seendb = NULL;
117     char *fname = NULL;
118     int dbflags = (flags & SEEN_CREATE) ? CYRUSDB_CREATE : 0;
119     int r;
120 
121     assert(user);
122     assert(*seendbptr == NULL);
123 
124     /* create seendb */
125     seendb = (struct seen *) xmalloc(sizeof(struct seen));
126 
127     if (SEEN_DEBUG) {
128         syslog(LOG_DEBUG, "seen_db: seen_open(%s)", user);
129     }
130 
131     /* open the seendb corresponding to user */
132     fname = seen_getpath(user);
133     if (flags & SEEN_CREATE) cyrus_mkdir(fname, 0755);
134     r = cyrusdb_open(DB, fname, dbflags | CYRUSDB_CONVERT, &seendb->db);
135     if (r) {
136         if (!(flags & SEEN_SILENT)) {
137             int level = (flags & SEEN_CREATE) ? LOG_ERR : LOG_DEBUG;
138             syslog(level, "DBERROR: opening %s: %s", fname,
139                    cyrusdb_strerror(r));
140         }
141         r = IMAP_IOERROR;
142         free(seendb);
143         free(fname);
144         return r;
145     }
146     syslog(LOG_DEBUG, "seen_db: user %s opened %s", user, fname);
147     free(fname);
148 
149     seendb->tid = NULL;
150     seendb->user = xstrdup(user);
151 
152     *seendbptr = seendb;
153     return r;
154 }
155 
156 struct seendata_rock {
157     seenproc_t *f;
158     void *rock;
159 };
160 
seen_freedata(struct seendata * sd)161 EXPORTED void seen_freedata(struct seendata *sd)
162 {
163     free (sd->seenuids);
164 }
165 
parse_data(const char * data,int datalen,struct seendata * sd)166 static void parse_data(const char *data, int datalen, struct seendata *sd)
167 {
168     /* remember that 'data' may not be null terminated ! */
169     const char *dend = data + datalen;
170     char *p;
171     int uidlen;
172     int version;
173 
174     memset(sd, 0, sizeof(struct seendata));
175 
176     version = strtol(data, &p, 10); data = p;
177     assert(version == SEEN_VERSION);
178 
179     sd->lastread = strtol(data, &p, 10); data = p;
180     sd->lastuid = strtoll(data, &p, 10); data = p;
181     sd->lastchange = strtol(data, &p, 10); data = p;
182     while (p < dend && Uisspace(*p)) { p++; } data = p;
183     uidlen = dend - data;
184     sd->seenuids = xmalloc(uidlen + 1);
185     memcpy(sd->seenuids, data, uidlen);
186     sd->seenuids[uidlen] = '\0';
187 }
188 
foreach_proc(void * rock,const char * key,size_t keylen,const char * data,size_t datalen)189 static int foreach_proc(void *rock,
190                  const char *key,
191                  size_t keylen,
192                  const char *data,
193                  size_t datalen)
194 {
195     struct seendata sd = SEENDATA_INITIALIZER;
196     struct seendata_rock *sr = (struct seendata_rock *)rock;
197     char *name = xstrndup(key, keylen);
198     int r;
199 
200     parse_data(data, datalen, &sd);
201 
202     r = (sr->f)(name, &sd, sr->rock);
203 
204     seen_freedata(&sd);
205     free(name);
206 
207     return r;
208 }
209 
seen_foreach(struct seen * seendb,seenproc_t * f,void * rock)210 EXPORTED int seen_foreach(struct seen *seendb, seenproc_t *f, void *rock)
211 {
212     struct seendata_rock sdrock;
213     sdrock.f = f;
214     sdrock.rock = rock;
215     return cyrusdb_foreach(seendb->db, "", 0, NULL, foreach_proc, &sdrock, NULL);
216 }
217 
seen_readit(struct seen * seendb,const char * uniqueid,struct seendata * sd,int rw)218 static int seen_readit(struct seen *seendb, const char *uniqueid,
219                        struct seendata *sd, int rw)
220 {
221     int r;
222     const char *data;
223     size_t datalen;
224 
225     assert(seendb && uniqueid);
226     if (rw || seendb->tid) {
227         r = cyrusdb_fetchlock(seendb->db, uniqueid, strlen(uniqueid),
228                           &data, &datalen, &seendb->tid);
229     } else {
230         r = cyrusdb_fetch(seendb->db, uniqueid, strlen(uniqueid),
231                       &data, &datalen, NULL);
232     }
233     switch (r) {
234     case 0:
235         break;
236     case CYRUSDB_AGAIN:
237         syslog(LOG_DEBUG, "deadlock in seen database for '%s/%s'",
238                seendb->user, uniqueid);
239         return IMAP_AGAIN;
240         break;
241     case CYRUSDB_NOTFOUND:
242         memset(sd, 0, sizeof(struct seendata));
243         sd->seenuids = xstrdup("");
244         return 0;
245         break;
246     default:
247         syslog(LOG_ERR, "DBERROR: error fetching txn %s",
248                cyrusdb_strerror(r));
249         return IMAP_IOERROR;
250         break;
251     }
252 
253     parse_data(data, datalen, sd);
254     if (sd->seenuids[0] && !imparse_issequence(sd->seenuids)) {
255         syslog(LOG_ERR, "DBERROR: invalid sequence <%s> for %s %s - nuking",
256                sd->seenuids, seendb->user, uniqueid);
257         free(sd->seenuids);
258         sd->seenuids = xstrdup("");
259     }
260 
261     return 0;
262 }
263 
seen_read(struct seen * seendb,const char * uniqueid,struct seendata * sd)264 EXPORTED int seen_read(struct seen *seendb, const char *uniqueid, struct seendata *sd)
265 {
266     if (SEEN_DEBUG) {
267         syslog(LOG_DEBUG, "seen_db: seen_read %s (%s)",
268                seendb->user, uniqueid);
269     }
270 
271     return seen_readit(seendb, uniqueid, sd, 0);
272 }
273 
seen_lockread(struct seen * seendb,const char * uniqueid,struct seendata * sd)274 EXPORTED int seen_lockread(struct seen *seendb, const char *uniqueid, struct seendata *sd)
275 {
276     if (SEEN_DEBUG) {
277         syslog(LOG_DEBUG, "seen_db: seen_lockread %s (%s)",
278                seendb->user, uniqueid);
279     }
280 
281     return seen_readit(seendb, uniqueid, sd, 1);
282 }
283 
seen_write(struct seen * seendb,const char * uniqueid,struct seendata * sd)284 EXPORTED int seen_write(struct seen *seendb, const char *uniqueid, struct seendata *sd)
285 {
286     int sz = strlen(sd->seenuids) + 50;
287     char *data = xmalloc(sz);
288     int datalen;
289     int r;
290 
291     assert(seendb && uniqueid);
292 
293     if (SEEN_DEBUG) {
294         syslog(LOG_DEBUG, "seen_db: seen_write %s (%s)",
295                seendb->user, uniqueid);
296     }
297 
298     snprintf(data, sz, "%d " TIME_T_FMT " %u " TIME_T_FMT " %s", SEEN_VERSION,
299             sd->lastread, sd->lastuid,
300             sd->lastchange, sd->seenuids);
301     datalen = strlen(data);
302 
303     r = cyrusdb_store(seendb->db, uniqueid, strlen(uniqueid),
304                   data, datalen, &seendb->tid);
305     switch (r) {
306     case CYRUSDB_OK:
307         break;
308     case CYRUSDB_IOERROR:
309         r = IMAP_AGAIN;
310         break;
311     default:
312         syslog(LOG_ERR, "DBERROR: error updating database: %s",
313                cyrusdb_strerror(r));
314         r = IMAP_IOERROR;
315         break;
316     }
317 
318     free(data);
319 
320     sync_log_seen(seendb->user, uniqueid);
321 
322     return r;
323 }
324 
seen_close(struct seen ** seendbptr)325 EXPORTED int seen_close(struct seen **seendbptr)
326 {
327     struct seen *seendb = *seendbptr;
328     int r;
329 
330     if (!seendb) return 0;
331 
332     if (SEEN_DEBUG) {
333         syslog(LOG_DEBUG, "seen_db: seen_close(%s)", seendb->user);
334     }
335 
336     if (seendb->tid) {
337         if (SEEN_DEBUG) {
338             syslog(LOG_DEBUG, "seen_db: committing changes for %s", seendb->user);
339         }
340         r = cyrusdb_commit(seendb->db, seendb->tid);
341         if (r != CYRUSDB_OK) {
342             syslog(LOG_ERR, "DBERROR: error committing seen txn; "
343                    "seen state lost: %s", cyrusdb_strerror(r));
344         }
345         seendb->tid = NULL;
346     }
347 
348     r = cyrusdb_close(seendb->db);
349     if (r) {
350         syslog(LOG_ERR, "DBERROR: error closing: %s",
351                cyrusdb_strerror(r));
352         r = IMAP_IOERROR;
353     }
354     free(seendb->user);
355     free(seendb);
356 
357     *seendbptr = NULL;
358 
359     return 0;
360 }
361 
seen_create_mailbox(const char * userid,struct mailbox * mailbox)362 HIDDEN int seen_create_mailbox(const char *userid, struct mailbox *mailbox)
363 {
364     if (SEEN_DEBUG) {
365         syslog(LOG_DEBUG, "seen_db: seen_create_mailbox(%s, %s)",
366                userid, mailbox->uniqueid);
367     }
368 
369     /* noop */
370     return 0;
371 }
372 
seen_delete_mailbox(const char * userid,struct mailbox * mailbox)373 EXPORTED int seen_delete_mailbox(const char *userid, struct mailbox *mailbox)
374 {
375     int r;
376     struct seen *seendb = NULL;
377     const char *uniqueid = mailbox->uniqueid;
378 
379     if (SEEN_DEBUG) {
380         syslog(LOG_DEBUG, "seen_db: seen_delete_mailbox(%s, %s)",
381                userid, uniqueid);
382     }
383 
384     /* noop */
385     if (!userid)
386         return 0;
387 
388     r = seen_open(userid, SEEN_SILENT, &seendb);
389     if (!r) r = cyrusdb_delete(seendb->db, uniqueid, strlen(uniqueid),
390                            &seendb->tid, 1);
391     seen_close(&seendb);
392 
393     return r;
394 }
395 
seen_create_user(const char * user)396 int seen_create_user(const char *user)
397 {
398     if (SEEN_DEBUG) {
399         syslog(LOG_DEBUG, "seen_db: seen_create_user(%s)",
400                user);
401     }
402 
403     /* we'll be lazy here and create this when needed */
404     return 0;
405 }
406 
seen_delete_user(const char * user)407 HIDDEN int seen_delete_user(const char *user)
408 {
409     char *fname = seen_getpath(user);
410     int r = 0;
411 
412     if (SEEN_DEBUG) {
413         syslog(LOG_DEBUG, "seen_db: seen_delete_user(%s)",
414                user);
415     }
416 
417     if (unlink(fname) && errno != ENOENT) {
418         syslog(LOG_ERR, "error unlinking %s: %m", fname);
419         r = IMAP_IOERROR;
420     }
421 
422     free(fname);
423     return r;
424 }
425 
seen_rename_user(const char * olduser,const char * newuser)426 HIDDEN int seen_rename_user(const char *olduser, const char *newuser)
427 {
428     char *oldfname = seen_getpath(olduser);
429     char *newfname = seen_getpath(newuser);
430     int r = 0;
431 
432     if (SEEN_DEBUG) {
433         syslog(LOG_DEBUG, "seen_db: seen_rename_user(%s, %s)",
434                olduser, newuser);
435     }
436 
437     cyrus_mkdir(newfname, 0755);
438     if (rename(oldfname, newfname) && errno != ENOENT) {
439         syslog(LOG_ERR, "error renaming %s to %s: %m", oldfname, newfname);
440         r = IMAP_IOERROR;
441     }
442 
443     free(oldfname);
444     free(newfname);
445 
446     return r;
447 }
448 
seen_copy(const char * userid,struct mailbox * oldmailbox,struct mailbox * newmailbox)449 HIDDEN int seen_copy(const char *userid, struct mailbox *oldmailbox,
450               struct mailbox *newmailbox)
451 {
452     if (SEEN_DEBUG) {
453         syslog(LOG_DEBUG, "seen_db: seen_copy %s (%s => %s)",
454                userid ? userid : "", oldmailbox->uniqueid, newmailbox->uniqueid);
455     }
456 
457     if (userid && strcmp(oldmailbox->uniqueid, newmailbox->uniqueid)) {
458         int r;
459         struct seen *seendb = NULL;
460         struct seendata sd = SEENDATA_INITIALIZER;
461 
462         r = seen_open(userid, SEEN_SILENT, &seendb);
463 
464         /* just be silent if it's missing */
465         if (!r) r = seen_lockread(seendb, oldmailbox->uniqueid, &sd);
466         if (!r) r = seen_write(seendb, newmailbox->uniqueid, &sd);
467 
468         seen_close(&seendb);
469         seen_freedata(&sd);
470     }
471 
472     /* noop */
473     return 0;
474 }
475 
seen_done(void)476 EXPORTED int seen_done(void)
477 {
478     if (SEEN_DEBUG) {
479         syslog(LOG_DEBUG, "seen_db: seen_done()");
480     }
481 
482     return 0;
483 }
484 
seen_compare(struct seendata * a,struct seendata * b)485 EXPORTED int seen_compare(struct seendata *a, struct seendata *b)
486 {
487     if (a->lastuid == b->lastuid &&
488         a->lastread == b->lastread &&
489         a->lastchange == b->lastchange &&
490         !strcmp(a->seenuids, b->seenuids))
491         return 1;
492 
493     return 0;
494 }
495 
496 /* Look up the unique id in the new file, if it is there, compare the
497  * last change times, and ensure that the database uses the newer of
498  * the two */
seen_merge_cb(void * rockp,const char * key,size_t keylen,const char * newdata,size_t newlen)499 static int seen_merge_cb(void *rockp,
500                          const char *key, size_t keylen,
501                          const char *newdata, size_t newlen)
502 {
503     int r = 0;
504     struct seen *seendb = (struct seen *)rockp;
505     struct seendata oldsd, newsd;
506     char *uniqueid = xstrndup(key, keylen);
507     int dirty = 0;
508 
509     parse_data(newdata, newlen, &newsd);
510 
511     if (seen_lockread(seendb, uniqueid, &oldsd)) {
512         dirty = 1; /* no record */
513     }
514     else {
515         if (newsd.lastuid > oldsd.lastuid) dirty = 1;
516         if (newsd.lastread > oldsd.lastread) dirty = 1;
517     }
518 
519     if (dirty) {
520         /* write back data from new entry */
521         r = seen_write(seendb, uniqueid, &newsd);
522     }
523 
524     free(uniqueid);
525 
526     return r;
527 }
528 
529 /* we want to merge records from "newfile" into
530  * the already existing "currentfile", but only
531  * if the record in newfile is actually newer
532  * (or doesn't exist in currentfile yet)  */
seen_merge(struct seen * seendb,const char * newfile)533 HIDDEN int seen_merge(struct seen *seendb, const char *newfile)
534 {
535     int r = 0;
536     struct db *newdb = NULL;
537 
538     r = cyrusdb_open(DB, newfile, 0, &newdb);
539     /* if it doesn't exist, there's nothing
540      * to do, so abort without an error */
541     if (r == CYRUSDB_NOTFOUND) return 0;
542 
543     if (!r) r = cyrusdb_foreach(newdb, "", 0, NULL, seen_merge_cb, seendb, NULL);
544 
545     if (newdb) cyrusdb_close(newdb);
546 
547     return r;
548 }
549