/* webdav_db.c -- implementation of per-user WebDAV database
*
* Copyright (c) 1994-2015 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>
#ifdef WITH_DAV
#include <syslog.h>
#include <string.h>
#include "webdav_db.h"
#include "cyrusdb.h"
#include "httpd.h"
#include "http_dav.h"
#include "libconfig.h"
#include "util.h"
#include "xstrlcat.h"
#include "xmalloc.h"
enum {
STMT_SELRSRC,
STMT_SELUID,
STMT_SELMBOX,
STMT_INSERT,
STMT_UPDATE,
STMT_DELETE,
STMT_DELMBOX,
STMT_BEGIN,
STMT_COMMIT,
STMT_ROLLBACK
};
#define NUM_STMT 10
struct webdav_db {
sqldb_t *db; /* DB handle */
char *userid;
struct buf mailbox; /* buffers for copies of column text */
struct buf resource;
struct buf lock_token;
struct buf lock_owner;
struct buf lock_ownerid;
struct buf filename;
struct buf type;
struct buf subtype;
struct buf res_uid;
unsigned ref_count;
};
static int webdav_initialized = 0;
static void done_cb(void *rock __attribute__((unused))) {
webdav_done();
}
static void init_internal() {
if (!webdav_initialized) {
webdav_init();
cyrus_modules_add(done_cb, NULL);
}
}
EXPORTED int webdav_init(void)
{
int r = sqldb_init();
if (!r) webdav_initialized = 1;
return r;
}
EXPORTED int webdav_done(void)
{
int r = sqldb_done();
if (!r) webdav_initialized = 0;
return r;
}
/* Open DAV DB corresponding to userid */
EXPORTED struct webdav_db *webdav_open_userid(const char *userid)
{
struct webdav_db *webdavdb = NULL;
init_internal();
sqldb_t *db = dav_open_userid(userid);
if (!db) return NULL;
webdavdb = xzmalloc(sizeof(struct webdav_db));
webdavdb->userid = xstrdup(userid);
webdavdb->db = db;
return webdavdb;
}
/* Open DAV DB corresponding to mailbox */
EXPORTED struct webdav_db *webdav_open_mailbox(struct mailbox *mailbox)
{
struct webdav_db *webdavdb = NULL;
char *userid = mboxname_to_userid(mailbox->name);
init_internal();
if (userid) {
webdavdb = webdav_open_userid(userid);
free(userid);
return webdavdb;
}
sqldb_t *db = dav_open_mailbox(mailbox);
if (!db) return NULL;
webdavdb = xzmalloc(sizeof(struct webdav_db));
webdavdb->db = db;
return webdavdb;
}
/* Close DAV DB */
EXPORTED int webdav_close(struct webdav_db *webdavdb)
{
int r = 0;
if (!webdavdb) return 0;
buf_free(&webdavdb->mailbox);
buf_free(&webdavdb->resource);
buf_free(&webdavdb->lock_token);
buf_free(&webdavdb->lock_owner);
buf_free(&webdavdb->lock_ownerid);
buf_free(&webdavdb->filename);
buf_free(&webdavdb->type);
buf_free(&webdavdb->subtype);
buf_free(&webdavdb->res_uid);
r = sqldb_close(&webdavdb->db);
free(webdavdb->userid);
free(webdavdb);
return r;
}
EXPORTED int webdav_begin(struct webdav_db *webdavdb)
{
return sqldb_begin(webdavdb->db, "webdav");
}
EXPORTED int webdav_commit(struct webdav_db *webdavdb)
{
return sqldb_commit(webdavdb->db, "webdav");
}
EXPORTED int webdav_abort(struct webdav_db *webdavdb)
{
return sqldb_rollback(webdavdb->db, "webdav");
}
struct read_rock {
struct webdav_db *db;
struct webdav_data *wdata;
int tombstones;
webdav_cb_t *cb;
void *rock;
};
static const char *column_text_to_buf(const char *text, struct buf *buf)
{
if (text) {
buf_setcstr(buf, text);
text = buf_cstring(buf);
}
return text;
}
static int read_cb(sqlite3_stmt *stmt, void *rock)
{
struct read_rock *rrock = (struct read_rock *) rock;
struct webdav_db *db = rrock->db;
struct webdav_data *wdata = rrock->wdata;
int r = 0;
memset(wdata, 0, sizeof(struct webdav_data));
wdata->dav.alive = sqlite3_column_int(stmt, 14);
wdata->dav.modseq = sqlite3_column_int64(stmt, 15);
wdata->dav.createdmodseq = sqlite3_column_int64(stmt, 16);
if (!rrock->tombstones && !wdata->dav.alive)
return 0;
wdata->dav.rowid = sqlite3_column_int(stmt, 0);
wdata->dav.creationdate = sqlite3_column_int(stmt, 1);
wdata->dav.imap_uid = sqlite3_column_int(stmt, 4);
wdata->dav.lock_expire = sqlite3_column_int(stmt, 8);
wdata->ref_count = sqlite3_column_int(stmt, 13);
if (rrock->cb) {
/* We can use the column data directly for the callback */
wdata->dav.mailbox = (const char *) sqlite3_column_text(stmt, 2);
wdata->dav.resource = (const char *) sqlite3_column_text(stmt, 3);
wdata->dav.lock_token = (const char *) sqlite3_column_text(stmt, 5);
wdata->dav.lock_owner = (const char *) sqlite3_column_text(stmt, 6);
wdata->dav.lock_ownerid = (const char *) sqlite3_column_text(stmt, 7);
wdata->filename = (const char *) sqlite3_column_text(stmt, 9);
wdata->type = (const char *) sqlite3_column_text(stmt, 10);
wdata->subtype = (const char *) sqlite3_column_text(stmt, 11);
wdata->res_uid = (const char *) sqlite3_column_text(stmt, 12);
r = rrock->cb(rrock->rock, wdata);
}
else {
/* For single row SELECTs like webdav_read(),
* we need to make a copy of the column data before
* it gets flushed by sqlite3_step() or sqlite3_reset() */
wdata->dav.mailbox =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 2),
&db->mailbox);
wdata->dav.resource =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 3),
&db->resource);
wdata->dav.lock_token =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 5),
&db->lock_token);
wdata->dav.lock_owner =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 6),
&db->lock_owner);
wdata->dav.lock_ownerid =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 7),
&db->lock_ownerid);
wdata->filename =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 9),
&db->filename);
wdata->type =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 10),
&db->type);
wdata->subtype =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 11),
&db->subtype);
wdata->res_uid =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 12),
&db->res_uid);
}
return r;
}
#define CMD_GETFIELDS \
"SELECT rowid, creationdate, mailbox, resource, imap_uid," \
" lock_token, lock_owner, lock_ownerid, lock_expire," \
" filename, type, subtype, res_uid, ref_count, alive," \
" modseq, createdmodseq" \
" FROM dav_objs" \
#define CMD_SELRSRC CMD_GETFIELDS \
" WHERE mailbox = :mailbox AND resource = :resource;"
EXPORTED int webdav_lookup_resource(struct webdav_db *webdavdb,
const char *mailbox, const char *resource,
struct webdav_data **result,
int tombstones)
{
struct sqldb_bindval bval[] = {
{ ":mailbox", SQLITE_TEXT, { .s = mailbox } },
{ ":resource", SQLITE_TEXT, { .s = resource } },
{ NULL, SQLITE_NULL, { .s = NULL } } };
static struct webdav_data wdata;
struct read_rock rrock = { webdavdb, &wdata, tombstones, NULL, NULL };
int r;
*result = memset(&wdata, 0, sizeof(struct webdav_data));
r = sqldb_exec(webdavdb->db, CMD_SELRSRC, bval, &read_cb, &rrock);
if (!r && !wdata.dav.rowid) r = CYRUSDB_NOTFOUND;
return r;
}
#define CMD_SELIMAPUID CMD_GETFIELDS \
" WHERE mailbox = :mailbox AND imap_uid = :imap_uid;"
EXPORTED int webdav_lookup_imapuid(struct webdav_db *webdavdb,
const char *mailbox, int imap_uid,
struct webdav_data **result,
int tombstones)
{
struct sqldb_bindval bval[] = {
{ ":mailbox", SQLITE_TEXT, { .s = mailbox } },
{ ":imap_uid", SQLITE_INTEGER, { .i = imap_uid } },
{ NULL, SQLITE_NULL, { .s = NULL } } };
static struct webdav_data wdata;
struct read_rock rrock = { webdavdb, &wdata, tombstones, NULL, NULL };
int r;
*result = memset(&wdata, 0, sizeof(struct webdav_data));
r = sqldb_exec(webdavdb->db, CMD_SELIMAPUID, bval, &read_cb, &rrock);
if (!r && !wdata.dav.rowid) r = CYRUSDB_NOTFOUND;
wdata.dav.mailbox = mailbox;
wdata.dav.imap_uid = imap_uid;
return r;
}
#define CMD_SELUID CMD_GETFIELDS \
" WHERE res_uid = :res_uid AND alive = 1;"
EXPORTED int webdav_lookup_uid(struct webdav_db *webdavdb, const char *res_uid,
struct webdav_data **result)
{
struct sqldb_bindval bval[] = {
{ ":res_uid", SQLITE_TEXT, { .s = res_uid } },
{ NULL, SQLITE_NULL, { .s = NULL } } };
static struct webdav_data wdata;
struct read_rock rrock = { webdavdb, &wdata, 0, NULL, NULL };
int r;
*result = memset(&wdata, 0, sizeof(struct webdav_data));
r = sqldb_exec(webdavdb->db, CMD_SELUID, bval, &read_cb, &rrock);
if (!r && !wdata.dav.rowid) r = CYRUSDB_NOTFOUND;
return r;
}
#define CMD_SELMBOX CMD_GETFIELDS \
" WHERE mailbox = :mailbox AND alive = 1;"
EXPORTED int webdav_foreach(struct webdav_db *webdavdb, const char *mailbox,
int (*cb)(void *rock, struct webdav_data *data),
void *rock)
{
struct sqldb_bindval bval[] = {
{ ":mailbox", SQLITE_TEXT, { .s = mailbox } },
{ NULL, SQLITE_NULL, { .s = NULL } } };
struct webdav_data wdata;
struct read_rock rrock = { webdavdb, &wdata, 0, cb, rock };
return sqldb_exec(webdavdb->db, CMD_SELMBOX, bval, &read_cb, &rrock);
}
#define CMD_INSERT \
"INSERT INTO dav_objs (" \
" creationdate, mailbox, resource, imap_uid, modseq," \
" createdmodseq," \
" lock_token, lock_owner, lock_ownerid, lock_expire," \
" filename, type, subtype, res_uid, ref_count, alive )" \
" VALUES (" \
" :creationdate, :mailbox, :resource, :imap_uid, :modseq," \
" :createdmodseq," \
" :lock_token, :lock_owner, :lock_ownerid, :lock_expire," \
" :filename, :type, :subtype, :res_uid, :ref_count, :alive );"
#define CMD_UPDATE \
"UPDATE dav_objs SET" \
" imap_uid = :imap_uid," \
" modseq = :modseq," \
" createdmodseq = :createdmodseq," \
" lock_token = :lock_token," \
" lock_owner = :lock_owner," \
" lock_ownerid = :lock_ownerid," \
" lock_expire = :lock_expire," \
" filename = :filename," \
" type = :type," \
" subtype = :subtype," \
" res_uid = :res_uid," \
" ref_count = :ref_count," \
" alive = :alive" \
" WHERE rowid = :rowid;"
EXPORTED int webdav_write(struct webdav_db *webdavdb, struct webdav_data *wdata)
{
struct sqldb_bindval bval[] = {
{ ":imap_uid", SQLITE_INTEGER, { .i = wdata->dav.imap_uid } },
{ ":modseq", SQLITE_INTEGER, { .i = wdata->dav.modseq } },
{ ":createdmodseq", SQLITE_INTEGER, { .i = wdata->dav.createdmodseq } },
{ ":lock_token", SQLITE_TEXT, { .s = wdata->dav.lock_token } },
{ ":lock_owner", SQLITE_TEXT, { .s = wdata->dav.lock_owner } },
{ ":lock_ownerid", SQLITE_TEXT, { .s = wdata->dav.lock_ownerid } },
{ ":lock_expire", SQLITE_INTEGER, { .i = wdata->dav.lock_expire } },
{ ":filename", SQLITE_TEXT, { .s = wdata->filename } },
{ ":type", SQLITE_TEXT, { .s = wdata->type } },
{ ":subtype", SQLITE_TEXT, { .s = wdata->subtype } },
{ ":res_uid", SQLITE_TEXT, { .s = wdata->res_uid } },
{ ":ref_count", SQLITE_INTEGER, { .i = wdata->ref_count } },
{ ":alive", SQLITE_INTEGER, { .i = wdata->dav.alive } },
{ NULL, SQLITE_NULL, { .s = NULL } },
{ NULL, SQLITE_NULL, { .s = NULL } },
{ NULL, SQLITE_NULL, { .s = NULL } },
{ NULL, SQLITE_NULL, { .s = NULL } } };
const char *cmd;
int r;
if (wdata->dav.rowid) {
cmd = CMD_UPDATE;
bval[13].name = ":rowid";
bval[13].type = SQLITE_INTEGER;
bval[13].val.i = wdata->dav.rowid;
}
else {
cmd = CMD_INSERT;
bval[13].name = ":creationdate";
bval[13].type = SQLITE_INTEGER;
bval[13].val.i = wdata->dav.creationdate;
bval[14].name = ":mailbox";
bval[14].type = SQLITE_TEXT;
bval[14].val.s = wdata->dav.mailbox;
bval[15].name = ":resource";
bval[15].type = SQLITE_TEXT;
bval[15].val.s = wdata->dav.resource;
}
r = sqldb_exec(webdavdb->db, cmd, bval, NULL, NULL);
return r;
}
#define CMD_DELETE "DELETE FROM dav_objs WHERE rowid = :rowid;"
EXPORTED int webdav_delete(struct webdav_db *webdavdb, unsigned rowid)
{
struct sqldb_bindval bval[] = {
{ ":rowid", SQLITE_INTEGER, { .i = rowid } },
{ NULL, SQLITE_NULL, { .s = NULL } } };
int r;
r = sqldb_exec(webdavdb->db, CMD_DELETE, bval, NULL, NULL);
return r;
}
#define CMD_DELMBOX "DELETE FROM dav_objs WHERE mailbox = :mailbox;"
HIDDEN int webdav_delmbox(struct webdav_db *webdavdb, const char *mailbox)
{
struct sqldb_bindval bval[] = {
{ ":mailbox", SQLITE_TEXT, { .s = mailbox } },
{ NULL, SQLITE_NULL, { .s = NULL } } };
int r;
r = sqldb_exec(webdavdb->db, CMD_DELMBOX, bval, NULL, NULL);
return r;
}
EXPORTED int webdav_get_updates(struct webdav_db *webdavdb,
modseq_t oldmodseq, const char *mboxname,
int kind __attribute__((unused)), int limit,
int (*cb)(void *rock, struct webdav_data *wdata),
void *rock)
{
struct sqldb_bindval bval[] = {
{ ":mailbox", SQLITE_TEXT, { .s = mboxname } },
{ ":modseq", SQLITE_INTEGER, { .i = oldmodseq } },
/* SQLite interprets a negative limit as unbounded. */
{ ":limit", SQLITE_INTEGER, { .i = limit > 0 ? limit : -1 } },
{ NULL, SQLITE_NULL, { .s = NULL } }
};
static struct webdav_data wdata;
struct read_rock rrock = { webdavdb, &wdata, 1 /* tombstones */, cb, rock };
struct buf sqlbuf = BUF_INITIALIZER;
int r;
buf_setcstr(&sqlbuf, CMD_GETFIELDS " WHERE");
if (mboxname) buf_appendcstr(&sqlbuf, " mailbox = :mailbox AND");
if (!oldmodseq) buf_appendcstr(&sqlbuf, " alive = 1 AND");
buf_appendcstr(&sqlbuf, " modseq > :modseq ORDER BY modseq LIMIT :limit;");
r = sqldb_exec(webdavdb->db, buf_cstring(&sqlbuf), bval, &read_cb, &rrock);
buf_free(&sqlbuf);
if (r) {
syslog(LOG_ERR, "webdav error %s", error_message(r));
}
return r;
}
#else
EXPORTED int webdav_init(void)
{
return 0;
}
EXPORTED int webdav_done(void)
{
return 0;
}
#endif /* WITH_DAV */