/* dav_db.c -- implementation of per-user DAV database
*
* Copyright (c) 1994-2012 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.
*
*/
#include <config.h>
#include <stdlib.h>
#include <syslog.h>
#include <string.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "assert.h"
#include "caldav_alarm.h"
#include "cyrusdb.h"
#include "dav_db.h"
#include "global.h"
#include "util.h"
#include "xmalloc.h"
#define CMD_CREATE_CAL \
"CREATE TABLE IF NOT EXISTS ical_objs (" \
" rowid INTEGER PRIMARY KEY," \
" creationdate INTEGER," \
" mailbox TEXT NOT NULL," \
" resource TEXT NOT NULL," \
" imap_uid INTEGER," \
" modseq INTEGER," \
" createdmodseq INTEGER," \
" lock_token TEXT," \
" lock_owner TEXT," \
" lock_ownerid TEXT," \
" lock_expire INTEGER," \
" comp_type INTEGER," \
" ical_uid TEXT," \
" organizer TEXT," \
" dtstart TEXT," \
" dtend TEXT," \
" comp_flags INTEGER," \
" sched_tag TEXT," \
" alive INTEGER," \
" UNIQUE( mailbox, imap_uid )," \
" UNIQUE( mailbox, resource ) );" \
"CREATE INDEX IF NOT EXISTS idx_ical_uid ON ical_objs ( ical_uid );"
#define CMD_CREATE_CARD \
"CREATE TABLE IF NOT EXISTS vcard_objs (" \
" rowid INTEGER PRIMARY KEY," \
" creationdate INTEGER," \
" mailbox TEXT NOT NULL," \
" resource TEXT NOT NULL," \
" imap_uid INTEGER," \
" modseq INTEGER," \
" createdmodseq INTEGER," \
" lock_token TEXT," \
" lock_owner TEXT," \
" lock_ownerid TEXT," \
" lock_expire INTEGER," \
" version INTEGER," \
" vcard_uid TEXT," \
" kind INTEGER," \
" fullname TEXT," \
" name TEXT," \
" nickname TEXT," \
" alive INTEGER," \
" UNIQUE( mailbox, imap_uid )," \
" UNIQUE( mailbox, resource ) );" \
"CREATE INDEX IF NOT EXISTS idx_vcard_fn ON vcard_objs ( fullname );" \
"CREATE INDEX IF NOT EXISTS idx_vcard_uid ON vcard_objs ( vcard_uid );"
#define CMD_CREATE_EM \
"CREATE TABLE IF NOT EXISTS vcard_emails (" \
" rowid INTEGER PRIMARY KEY," \
" objid INTEGER," \
" pos INTEGER NOT NULL," /* for sorting */ \
" email TEXT NOT NULL COLLATE NOCASE," \
" ispref INTEGER NOT NULL DEFAULT 0," \
" ispinned INTEGER NOT NULL DEFAULT 0," \
" FOREIGN KEY (objid) REFERENCES vcard_objs (rowid) ON DELETE CASCADE );" \
"CREATE INDEX IF NOT EXISTS idx_vcard_email ON vcard_emails ( email COLLATE NOCASE );"
#define CMD_CREATE_GR \
"CREATE TABLE IF NOT EXISTS vcard_groups (" \
" rowid INTEGER PRIMARY KEY," \
" objid INTEGER," \
" pos INTEGER NOT NULL," /* for sorting */ \
" member_uid TEXT NOT NULL," \
" otheruser TEXT NOT NULL DEFAULT \"\"," \
" FOREIGN KEY (objid) REFERENCES vcard_objs (rowid) ON DELETE CASCADE );"
#define CMD_CREATE_OBJS \
"CREATE TABLE IF NOT EXISTS dav_objs (" \
" rowid INTEGER PRIMARY KEY," \
" creationdate INTEGER," \
" mailbox TEXT NOT NULL," \
" resource TEXT NOT NULL," \
" imap_uid INTEGER," \
" modseq INTEGER," \
" createdmodseq INTEGER," \
" lock_token TEXT," \
" lock_owner TEXT," \
" lock_ownerid TEXT," \
" lock_expire INTEGER," \
" filename TEXT," \
" type TEXT," \
" subtype TEXT," \
" res_uid TEXT," \
" ref_count INTEGER," \
" alive INTEGER," \
" UNIQUE( mailbox, imap_uid )," \
" UNIQUE( mailbox, resource ) );" \
#define CMD_CREATE_CALCACHE \
"CREATE TABLE IF NOT EXISTS ical_jmapcache (" \
" rowid INTEGER NOT NULL," \
" userid TEXT NOT NULL," \
" jmapversion INTEGER NOT NULL," \
" jmapdata TEXT NOT NULL," \
" PRIMARY KEY (rowid, userid)" \
" FOREIGN KEY (rowid) REFERENCES ical_objs (rowid) ON DELETE CASCADE );"
#define CMD_CREATE_CARDCACHE \
"CREATE TABLE IF NOT EXISTS vcard_jmapcache (" \
" rowid INTEGER NOT NULL PRIMARY KEY," \
" jmapversion INTEGER NOT NULL," \
" jmapdata TEXT NOT NULL," \
" FOREIGN KEY (rowid) REFERENCES vcard_objs (rowid) ON DELETE CASCADE );"
#define CMD_CREATE CMD_CREATE_CAL CMD_CREATE_CARD CMD_CREATE_EM CMD_CREATE_GR \
CMD_CREATE_OBJS CMD_CREATE_CALCACHE CMD_CREATE_CARDCACHE
/* leaves these unused columns around, but that's life. A dav_reconstruct
* will fix them */
#define CMD_DBUPGRADEv2 \
"ALTER TABLE ical_objs ADD COLUMN comp_flags INTEGER;" \
"UPDATE ical_objs SET comp_flags = recurring + 2 * transp;"
#define CMD_DBUPGRADEv3 \
"ALTER TABLE ical_objs ADD COLUMN modseq INTEGER;" \
"UPDATE ical_objs SET modseq = 1;" \
"ALTER TABLE vcard_objs ADD COLUMN modseq INTEGER;" \
"UPDATE vcard_objs SET modseq = 1;"
#define CMD_DBUPGRADEv4 \
"ALTER TABLE ical_objs ADD COLUMN alive INTEGER;" \
"UPDATE ical_objs SET alive = 1;" \
"ALTER TABLE vcard_objs ADD COLUMN alive INTEGER;" \
"UPDATE vcard_objs SET alive = 1;"
#define CMD_DBUPGRADEv5 \
"ALTER TABLE vcard_emails ADD COLUMN ispref INTEGER NOT NULL DEFAULT 0;" \
"ALTER TABLE vcard_groups ADD COLUMN otheruser TEXT NOT NULL DEFAULT \"\";"
#define CMD_DBUPGRADEv6 CMD_CREATE_OBJS
#define CMD_DBUPGRADEv7 \
"ALTER TABLE ical_objs ADD COLUMN createdmodseq INTEGER;" \
"UPDATE ical_objs SET createdmodseq = 0;" \
"ALTER TABLE vcard_objs ADD COLUMN createdmodseq INTEGER;" \
"UPDATE vcard_objs SET createdmodseq = 0;" \
"ALTER TABLE dav_objs ADD COLUMN createdmodseq INTEGER;" \
"UPDATE dav_objs SET createdmodseq = 0;"
#define CMD_DBUPGRADEv8 \
"ALTER TABLE vcard_emails ADD COLUMN ispinned INTEGER NOT NULL DEFAULT 0;"
#define CMD_DBUPGRADEv9 CMD_CREATE_CALCACHE CMD_CREATE_CARDCACHE
#define CMD_DBUPGRADEv10 \
"CREATE UNIQUE INDEX IF NOT EXISTS idx_ical_imapuid ON ical_objs ( mailbox, imap_uid );" \
"CREATE UNIQUE INDEX IF NOT EXISTS idx_vcard_imapuid ON vcard_objs ( mailbox, imap_uid );" \
"CREATE UNIQUE INDEX IF NOT EXISTS idx_object_imapuid ON dav_objs ( mailbox, imap_uid );"
struct sqldb_upgrade davdb_upgrade[] = {
{ 2, CMD_DBUPGRADEv2, NULL },
{ 3, CMD_DBUPGRADEv3, NULL },
{ 4, CMD_DBUPGRADEv4, NULL },
{ 5, CMD_DBUPGRADEv5, NULL },
{ 6, CMD_DBUPGRADEv6, NULL },
{ 7, CMD_DBUPGRADEv7, NULL },
{ 8, CMD_DBUPGRADEv8, NULL },
{ 9, CMD_DBUPGRADEv9, NULL },
{ 10, CMD_DBUPGRADEv10, NULL },
{ 0, NULL, NULL }
};
#define DB_VERSION 10
static int in_reconstruct = 0;
EXPORTED sqldb_t *dav_open_userid(const char *userid)
{
sqldb_t *db = NULL;
struct buf fname = BUF_INITIALIZER;
dav_getpath_byuserid(&fname, userid);
if (in_reconstruct) buf_printf(&fname, ".NEW");
db = sqldb_open(buf_cstring(&fname), CMD_CREATE, DB_VERSION, davdb_upgrade,
config_getduration(IMAPOPT_DAV_LOCK_TIMEOUT, 's') * 1000);
buf_free(&fname);
return db;
}
EXPORTED sqldb_t *dav_open_mailbox(struct mailbox *mailbox)
{
sqldb_t *db = NULL;
struct buf fname = BUF_INITIALIZER;
dav_getpath(&fname, mailbox);
if (in_reconstruct) buf_printf(&fname, ".NEW");
db = sqldb_open(buf_cstring(&fname), CMD_CREATE, DB_VERSION, davdb_upgrade,
config_getduration(IMAPOPT_DAV_LOCK_TIMEOUT, 's') * 1000);
buf_free(&fname);
return db;
}
EXPORTED int dav_attach_userid(sqldb_t *db, const char *userid)
{
struct buf fname = BUF_INITIALIZER;
dav_getpath_byuserid(&fname, userid);
int r = sqldb_attach(db, buf_cstring(&fname));
buf_free(&fname);
return r;
}
EXPORTED int dav_attach_mailbox(sqldb_t *db, struct mailbox *mailbox)
{
struct buf fname = BUF_INITIALIZER;
dav_getpath(&fname, mailbox);
int r = sqldb_attach(db, buf_cstring(&fname));
buf_free(&fname);
return r;
}
/*
* mboxlist_usermboxtree() callback function to create DAV DB entries for a mailbox
*/
static int _dav_reconstruct_mb(const mbentry_t *mbentry,
void *rock
#ifndef WITH_JMAP
__attribute__((unused))
#endif
)
{
#ifdef WITH_JMAP
const char *userid = (const char *) rock;
struct buf attrib = BUF_INITIALIZER;
#endif
int (*addproc)(struct mailbox *) = NULL;
int r = 0;
signals_poll();
switch (mbentry->mbtype) {
#ifdef WITH_DAV
case MBTYPE_CALENDAR:
case MBTYPE_COLLECTION:
case MBTYPE_ADDRESSBOOK:
addproc = &mailbox_add_dav;
break;
#endif
#ifdef WITH_JMAP
case MBTYPE_SUBMISSION:
addproc = &mailbox_add_email_alarms;
break;
case MBTYPE_EMAIL:
r = annotatemore_lookup(mbentry->name, "/specialuse", userid, &attrib);
if (!r && buf_len(&attrib)) {
strarray_t *specialuse =
strarray_split(buf_cstring(&attrib), NULL, 0);
if (strarray_find(specialuse, "\\Snoozed", 0) >= 0) {
addproc = &mailbox_add_email_alarms;
}
strarray_free(specialuse);
}
buf_free(&attrib);
break;
#endif
}
if (addproc) {
struct mailbox *mailbox = NULL;
/* Open/lock header */
r = mailbox_open_iwl(mbentry->name, &mailbox);
// needs to be writable to remove bogus lastalarm data
if (!r) r = addproc(mailbox);
mailbox_close(&mailbox);
}
return r;
}
static void run_audit_tool(const char *tool, const char *srcdb, const char *dstdb)
{
pid_t pid = fork();
if (pid < 0)
return;
if (pid == 0) {
/* child */
execl(tool, tool, srcdb, dstdb, (void *)NULL);
exit(-1);
}
int status;
while (waitpid(pid, &status, 0) < 0);
}
EXPORTED int dav_reconstruct_user(const char *userid, const char *audit_tool)
{
syslog(LOG_NOTICE, "dav_reconstruct_user: %s", userid);
struct buf fname = BUF_INITIALIZER;
dav_getpath_byuserid(&fname, userid);
struct buf newfname = BUF_INITIALIZER;
dav_getpath_byuserid(&newfname, userid);
buf_printf(&newfname, ".NEW");
/* XXX - this still means that alarms can go missing if this
* task is interrupted, but we can't afford to keep the
* alarm database locked for the entire time, it's a single
* blocking database over the entire server */
caldav_alarm_delete_user(userid);
in_reconstruct = 1;
sqldb_t *userdb = dav_open_userid(userid);
sqldb_begin(userdb, "reconstruct");
int r = mboxlist_usermboxtree(userid, NULL,
_dav_reconstruct_mb, (void *) userid, 0);
if (r)
sqldb_rollback(userdb, "reconstruct");
else
sqldb_commit(userdb, "reconstruct");
sqldb_close(&userdb);
in_reconstruct = 0;
/* this actually works before close according to the internets */
if (r) {
syslog(LOG_ERR, "dav_reconstruct_user: %s FAILED %s", userid, error_message(r));
if (audit_tool) {
printf("Not auditing %s, reconstruct failed %s\n", userid, error_message(r));
}
unlink(buf_cstring(&newfname));
}
else {
syslog(LOG_NOTICE, "dav_reconstruct_user: %s SUCCEEDED", userid);
if (audit_tool) {
run_audit_tool(audit_tool, buf_cstring(&fname), buf_cstring(&newfname));
unlink(buf_cstring(&newfname));
}
else {
rename(buf_cstring(&newfname), buf_cstring(&fname));
}
}
buf_free(&newfname);
buf_free(&fname);
return 0;
}