/* carddav_db.c -- implementation of per-user CardDAV database
*
* Copyright (c) 1994-2013 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 <syslog.h>
#include <string.h>
#include "append.h"
#include "carddav_db.h"
#include "cyrusdb.h"
#include "httpd.h"
#include "http_dav.h"
#include "libconfig.h"
#include "times.h"
#include "util.h"
#include "xstrlcat.h"
#include "xmalloc.h"
#define NUM_BUFS 10
struct carddav_db {
sqldb_t *db; /* DB handle */
struct buf bufs[NUM_BUFS]; /* buffers for copies of column text */
char *userid;
};
EXPORTED int carddav_init(void)
{
return sqldb_init();
}
EXPORTED int carddav_done(void)
{
return sqldb_done();
}
EXPORTED struct carddav_db *carddav_open_userid(const char *userid)
{
struct carddav_db *carddavdb = NULL;
sqldb_t *db = dav_open_userid(userid);
if (!db) return NULL;
carddavdb = xzmalloc(sizeof(struct carddav_db));
carddavdb->userid = xstrdup(userid);
carddavdb->db = db;
return carddavdb;
}
EXPORTED struct carddav_db *carddav_open_mailbox(struct mailbox *mailbox)
{
struct carddav_db *carddavdb = NULL;
char *userid = mboxname_to_userid(mailbox->name);
if (userid) {
carddavdb = carddav_open_userid(userid);
free(userid);
return carddavdb;
}
sqldb_t *db = dav_open_mailbox(mailbox);
if (!db) return NULL;
carddavdb = xzmalloc(sizeof(struct carddav_db));
carddavdb->userid = xstrdup(userid);
carddavdb->db = db;
return carddavdb;
}
/* Close DAV DB */
EXPORTED int carddav_close(struct carddav_db *carddavdb)
{
int i, r = 0;
if (!carddavdb) return 0;
for (i = 0; i < NUM_BUFS; i++) {
buf_free(&carddavdb->bufs[i]);
}
r = sqldb_close(&carddavdb->db);
free(carddavdb->userid);
free(carddavdb);
return r;
}
EXPORTED int carddav_begin(struct carddav_db *carddavdb)
{
return sqldb_begin(carddavdb->db, "carddav");
}
EXPORTED int carddav_commit(struct carddav_db *carddavdb)
{
return sqldb_commit(carddavdb->db, "carddav");
}
EXPORTED int carddav_abort(struct carddav_db *carddavdb)
{
return sqldb_rollback(carddavdb->db, "carddav");
}
struct read_rock {
struct carddav_db *db;
struct carddav_data *cdata;
int tombstones;
carddav_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;
}
#define CMD_GETFIELDS \
"SELECT rowid, creationdate, mailbox, resource, imap_uid," \
" lock_token, lock_owner, lock_ownerid, lock_expire," \
" version, vcard_uid, kind, fullname, name, nickname, alive" \
" FROM vcard_objs"
static int read_cb(sqlite3_stmt *stmt, void *rock)
{
struct read_rock *rrock = (struct read_rock *) rock;
struct carddav_db *db = rrock->db;
struct carddav_data *cdata = rrock->cdata;
int r = 0;
memset(cdata, 0, sizeof(struct carddav_data));
cdata->dav.alive = sqlite3_column_int(stmt, 15);
if (!rrock->tombstones && !cdata->dav.alive)
return 0;
cdata->dav.rowid = sqlite3_column_int(stmt, 0);
cdata->dav.creationdate = sqlite3_column_int(stmt, 1);
cdata->dav.imap_uid = sqlite3_column_int(stmt, 4);
cdata->dav.lock_expire = sqlite3_column_int(stmt, 8);
cdata->version = sqlite3_column_int(stmt, 9);
cdata->kind = sqlite3_column_int(stmt, 11);
if (rrock->cb) {
/* We can use the column data directly for the callback */
cdata->dav.mailbox = (const char *) sqlite3_column_text(stmt, 2);
cdata->dav.resource = (const char *) sqlite3_column_text(stmt, 3);
cdata->dav.lock_token = (const char *) sqlite3_column_text(stmt, 5);
cdata->dav.lock_owner = (const char *) sqlite3_column_text(stmt, 6);
cdata->dav.lock_ownerid = (const char *) sqlite3_column_text(stmt, 7);
cdata->vcard_uid = (const char *) sqlite3_column_text(stmt, 10);
cdata->fullname = (const char *) sqlite3_column_text(stmt, 12);
cdata->name = (const char *) sqlite3_column_text(stmt, 13);
cdata->nickname = (const char *) sqlite3_column_text(stmt, 14);
r = rrock->cb(rrock->rock, cdata);
}
else {
/* For single row SELECTs like carddav_read(),
* we need to make a copy of the column data before
* it gets flushed by sqlite3_step() or sqlite3_reset() */
cdata->dav.mailbox =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 2),
&db->bufs[0]);
cdata->dav.resource =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 3),
&db->bufs[1]);
cdata->dav.lock_token =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 5),
&db->bufs[2]);
cdata->dav.lock_owner =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 6),
&db->bufs[3]);
cdata->dav.lock_ownerid =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 7),
&db->bufs[4]);
cdata->vcard_uid =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 10),
&db->bufs[5]);
cdata->fullname =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 12),
&db->bufs[6]);
cdata->name =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 13),
&db->bufs[7]);
cdata->nickname =
column_text_to_buf((const char *) sqlite3_column_text(stmt, 14),
&db->bufs[8]);
}
return r;
}
#define CMD_SELRSRC CMD_GETFIELDS \
" WHERE mailbox = :mailbox AND resource = :resource;"
EXPORTED int carddav_lookup_resource(struct carddav_db *carddavdb,
const char *mailbox, const char *resource,
struct carddav_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 carddav_data cdata;
struct read_rock rrock = { carddavdb, &cdata, tombstones, NULL, NULL };
int r;
*result = memset(&cdata, 0, sizeof(struct carddav_data));
r = sqldb_exec(carddavdb->db, CMD_SELRSRC, bval, &read_cb, &rrock);
if (!r && !cdata.dav.rowid) r = CYRUSDB_NOTFOUND;
/* always mailbox and resource so error paths don't fail */
cdata.dav.mailbox = mailbox;
cdata.dav.resource = resource;
return r;
}
#define CMD_SELUID CMD_GETFIELDS \
" WHERE vcard_uid = :vcard_uid AND alive = 1;"
EXPORTED int carddav_lookup_uid(struct carddav_db *carddavdb, const char *vcard_uid,
struct carddav_data **result)
{
struct sqldb_bindval bval[] = {
{ ":vcard_uid", SQLITE_TEXT, { .s = vcard_uid } },
{ NULL, SQLITE_NULL, { .s = NULL } } };
static struct carddav_data cdata;
struct read_rock rrock = { carddavdb, &cdata, 0, NULL, NULL };
int r;
*result = memset(&cdata, 0, sizeof(struct carddav_data));
r = sqldb_exec(carddavdb->db, CMD_SELUID, bval, &read_cb, &rrock);
if (!r && !cdata.dav.rowid) r = CYRUSDB_NOTFOUND;
return r;
}
#define CMD_SELMBOX CMD_GETFIELDS \
" WHERE mailbox = :mailbox AND alive = 1;"
EXPORTED int carddav_foreach(struct carddav_db *carddavdb, const char *mailbox,
int (*cb)(void *rock, struct carddav_data *data),
void *rock)
{
struct sqldb_bindval bval[] = {
{ ":mailbox", SQLITE_TEXT, { .s = mailbox } },
{ NULL, SQLITE_NULL, { .s = NULL } } };
struct carddav_data cdata;
struct read_rock rrock = { carddavdb, &cdata, 0, cb, rock };
return sqldb_exec(carddavdb->db, CMD_SELMBOX, bval, &read_cb, &rrock);
}
#define CMD_GETUID_GROUPS \
"SELECT GO.vcard_uid FROM vcard_objs GO" \
" JOIN vcard_groups G" \
" WHERE G.objid = GO.rowid" \
" AND G.member_uid = :uid AND GO.alive = 1;"
static int addarray_cb(sqlite3_stmt *stmt, void *rock)
{
strarray_t *array = (strarray_t *)rock;
const char *value = (const char *)sqlite3_column_text(stmt, 0);
if (value) strarray_add(array, value);
return 0;
}
EXPORTED strarray_t *carddav_getuid_groups(struct carddav_db *carddavdb, const char *uid)
{
struct sqldb_bindval bval[] = {
{ ":uid", SQLITE_TEXT, { .s = uid } },
{ NULL, SQLITE_NULL, { .s = NULL } }
};
strarray_t *groups;
int r;
groups = strarray_new();
r = sqldb_exec(carddavdb->db, CMD_GETUID_GROUPS, bval, &addarray_cb, groups);
if (r) {
/* XXX syslog */
}
return groups;
}
#define CMD_GETEMAIL_EXISTS \
"SELECT E.rowid " \
" FROM vcard_emails E JOIN vcard_objs CO" \
" WHERE E.objid = CO.rowid AND E.email = :email AND CO.alive = 1" \
" LIMIT 1"
#define CMD_GETEMAIL_GROUPS \
"SELECT GO.vcard_uid FROM vcard_objs GO" \
" JOIN vcard_groups G JOIN vcard_objs CO JOIN vcard_emails E" \
" WHERE E.objid = CO.rowid AND CO.vcard_uid = G.member_uid" \
" AND G.objid = GO.rowid AND E.email = :email" \
" AND GO.alive = 1 AND CO.alive = 1;"
#define CMD_GETEMAIL2UIDS \
"SELECT DISTINCT vcard_uid " \
" FROM vcard_objs CO JOIN vcard_emails E" \
" WHERE E.objid = CO.rowid AND CO.alive = 1" \
" AND E.email = :email AND CO.mailbox = :mailbox;"
#define CMD_GETUID2GROUPS \
"SELECT DISTINCT GO.fullname" \
" FROM vcard_objs GO JOIN vcard_groups G" \
" WHERE G.objid = GO.rowid AND GO.alive = 1" \
" AND G.member_uid = :member_uid AND G.otheruser = :otheruser" \
" AND GO.mailbox = :mailbox;"
static int emailexists_cb(sqlite3_stmt *stmt, void *rock)
{
int *exists = (int *)rock;
if (sqlite3_column_int(stmt, 0))
*exists = 1;
return 0;
}
EXPORTED strarray_t *carddav_getemail(struct carddav_db *carddavdb, const char *email)
{
struct sqldb_bindval bval[] = {
{ ":email", SQLITE_TEXT, { .s = email } },
{ NULL, SQLITE_NULL, { .s = NULL } }
};
int exists = 0;
strarray_t *groups;
int r;
r = sqldb_exec(carddavdb->db, CMD_GETEMAIL_EXISTS, bval, &emailexists_cb, &exists);
if (r) {
/* XXX syslog */
return NULL;
}
if (!exists)
return NULL;
groups = strarray_new();
r = sqldb_exec(carddavdb->db, CMD_GETEMAIL_GROUPS, bval, &addarray_cb, groups);
if (r) {
/* XXX syslog */
}
return groups;
}
EXPORTED strarray_t *carddav_getemail2uids(struct carddav_db *carddavdb, const char *email,
const char *mboxname)
{
struct sqldb_bindval bval[] = {
{ ":email", SQLITE_TEXT, { .s = email } },
{ ":mailbox", SQLITE_TEXT, { .s = mboxname } },
{ NULL, SQLITE_NULL, { .s = NULL } }
};
strarray_t *uids = strarray_new();
sqldb_exec(carddavdb->db, CMD_GETEMAIL2UIDS, bval, &addarray_cb, uids);
return uids;
}
EXPORTED strarray_t *carddav_getuid2groups(struct carddav_db *carddavdb, const char *member_uid,
const char *mboxname, const char *otheruser)
{
struct sqldb_bindval bval[] = {
{ ":member_uid", SQLITE_TEXT, { .s = member_uid } },
{ ":mailbox", SQLITE_TEXT, { .s = mboxname } },
{ ":otheruser", SQLITE_TEXT, { .s = otheruser } },
{ NULL, SQLITE_NULL, { .s = NULL } }
};
strarray_t *groups = strarray_new();
sqldb_exec(carddavdb->db, CMD_GETUID2GROUPS, bval, &addarray_cb, groups);
return groups;
}
#define CMD_GETGROUP_EXISTS \
"SELECT rowid " \
" FROM vcard_objs" \
" WHERE mailbox = :mailbox AND kind = :kind AND vcard_uid = :group AND alive = 1;"
#define CMD_GETGROUP_MEMBERS \
"SELECT E.email FROM vcard_emails E" \
" JOIN vcard_objs CO JOIN vcard_groups G JOIN vcard_objs GO" \
" WHERE E.objid = CO.rowid AND CO.vcard_uid = G.member_uid AND G.objid = GO.rowid" \
" AND E.pos = 0 AND GO.mailbox = :mailbox AND GO.vcard_uid = :group" \
" AND GO.alive = 1 AND CO.alive = 1;"
static int groupexists_cb(sqlite3_stmt *stmt, void *rock)
{
int *exists = (int *)rock;
if (sqlite3_column_int(stmt, 0))
*exists = 1;
return 0;
}
static int groupmembers_cb(sqlite3_stmt *stmt, void *rock)
{
strarray_t *array = (strarray_t *)rock;
strarray_add(array, (const char *)sqlite3_column_text(stmt, 0));
return 0;
}
EXPORTED strarray_t *carddav_getgroup(struct carddav_db *carddavdb, const char *mailbox, const char *group)
{
struct sqldb_bindval bval[] = {
{ ":mailbox", SQLITE_TEXT, { .s = mailbox } },
{ ":group", SQLITE_TEXT, { .s = group } },
{ ":kind", SQLITE_INTEGER, { .i = CARDDAV_KIND_GROUP } },
{ NULL, SQLITE_NULL, { .s = NULL } }
};
int exists = 0;
strarray_t *members;
int r;
r = sqldb_exec(carddavdb->db, CMD_GETGROUP_EXISTS, bval, &groupexists_cb, &exists);
if (r) {
/* XXX syslog */
return NULL;
}
if (!exists)
return NULL;
members = strarray_new();
r = sqldb_exec(carddavdb->db, CMD_GETGROUP_MEMBERS, bval, &groupmembers_cb, members);
if (r) {
/* XXX syslog */
}
return members;
}
#define CMD_DELETE_EMAIL "DELETE FROM vcard_emails WHERE objid = :objid"
#define CMD_INSERT_EMAIL \
"INSERT INTO vcard_emails ( objid, pos, email, ispref )" \
" VALUES ( :objid, :pos, :email, :ispref );"
static int carddav_write_emails(struct carddav_db *carddavdb, int rowid, const strarray_t *emails)
{
struct sqldb_bindval bval[] = {
{ ":objid", SQLITE_INTEGER, { .i = rowid } },
{ ":pos", SQLITE_INTEGER, { .i = 0 } },
{ ":email", SQLITE_TEXT, { .s = NULL } },
{ ":ispref", SQLITE_INTEGER, { .i = 0 } },
{ NULL, SQLITE_NULL, { .s = NULL } } };
int r;
int i;
/* clean up existing records if any */
r = sqldb_exec(carddavdb->db, CMD_DELETE_EMAIL, bval, NULL, NULL);
if (r) return r;
for (i = 0; i < strarray_size(emails)/2; i++) {
const char *pref = strarray_safenth(emails, i*2+1);
bval[1].val.i = i;
bval[2].val.s = strarray_safenth(emails, i*2);
bval[3].val.i = *pref ? 1 : 0;
r = sqldb_exec(carddavdb->db, CMD_INSERT_EMAIL, bval, NULL, NULL);
if (r) return r;
}
return 0;
}
#define CMD_DELETE_GROUP "DELETE FROM vcard_groups WHERE objid = :objid"
#define CMD_INSERT_GROUP \
"INSERT INTO vcard_groups ( objid, pos, member_uid, otheruser )" \
" VALUES ( :objid, :pos, :member_uid, :otheruser );"
static int carddav_write_groups(struct carddav_db *carddavdb, int rowid, const strarray_t *member_uids)
{
struct sqldb_bindval bval[] = {
{ ":objid", SQLITE_INTEGER, { .i = rowid } },
{ ":pos", SQLITE_INTEGER, { .i = 0 } },
{ ":member_uid", SQLITE_TEXT, { .s = NULL } },
{ ":otheruser", SQLITE_TEXT, { .s = NULL } },
{ NULL, SQLITE_NULL, { .s = NULL } } };
int r;
int i;
/* remove any existing first */
r = sqldb_exec(carddavdb->db, CMD_DELETE_GROUP, bval, NULL, NULL);
if (r) return r;
for (i = 0; i < strarray_size(member_uids)/2; i++) {
bval[1].val.i = i;
bval[2].val.s = strarray_safenth(member_uids, 2*i);
bval[3].val.s = strarray_safenth(member_uids, 2*i+1);
r = sqldb_exec(carddavdb->db, CMD_INSERT_GROUP, bval, NULL, NULL);
if (r) return r;
}
return 0;
}
#define CMD_INSERT \
"INSERT INTO vcard_objs (" \
" alive, creationdate, mailbox, resource, imap_uid, modseq," \
" lock_token, lock_owner, lock_ownerid, lock_expire," \
" version, vcard_uid, kind, fullname, name, nickname)" \
" VALUES (" \
" :alive, :creationdate, :mailbox, :resource, :imap_uid, :modseq," \
" :lock_token, :lock_owner, :lock_ownerid, :lock_expire," \
" :version, :vcard_uid, :kind, :fullname, :name, :nickname );"
#define CMD_UPDATE \
"UPDATE vcard_objs SET" \
" alive = :alive," \
" creationdate = :creationdate," \
" imap_uid = :imap_uid," \
" modseq = :modseq," \
" lock_token = :lock_token," \
" lock_owner = :lock_owner," \
" lock_ownerid = :lock_ownerid," \
" lock_expire = :lock_expire," \
" version = :version," \
" vcard_uid = :vcard_uid," \
" kind = :kind," \
" fullname = :fullname," \
" name = :name," \
" nickname = :nickname" \
" WHERE rowid = :rowid;"
EXPORTED int carddav_write(struct carddav_db *carddavdb, struct carddav_data *cdata)
{
struct sqldb_bindval bval[] = {
{ ":rowid", SQLITE_INTEGER, { .i = cdata->dav.rowid } },
{ ":alive", SQLITE_INTEGER, { .i = cdata->dav.alive } },
{ ":creationdate", SQLITE_INTEGER, { .i = cdata->dav.creationdate } },
{ ":mailbox", SQLITE_TEXT, { .s = cdata->dav.mailbox } },
{ ":resource", SQLITE_TEXT, { .s = cdata->dav.resource } },
{ ":imap_uid", SQLITE_INTEGER, { .i = cdata->dav.imap_uid } },
{ ":modseq", SQLITE_INTEGER, { .i = cdata->dav.modseq } },
{ ":lock_token", SQLITE_TEXT, { .s = cdata->dav.lock_token } },
{ ":lock_owner", SQLITE_TEXT, { .s = cdata->dav.lock_owner } },
{ ":lock_ownerid", SQLITE_TEXT, { .s = cdata->dav.lock_ownerid } },
{ ":lock_expire", SQLITE_INTEGER, { .i = cdata->dav.lock_expire } },
{ ":version", SQLITE_INTEGER, { .i = cdata->version } },
{ ":vcard_uid", SQLITE_TEXT, { .s = cdata->vcard_uid } },
{ ":kind", SQLITE_INTEGER, { .i = cdata->kind } },
{ ":fullname", SQLITE_TEXT, { .s = cdata->fullname } },
{ ":name", SQLITE_TEXT, { .s = cdata->name } },
{ ":nickname", SQLITE_TEXT, { .s = cdata->nickname } },
{ NULL, SQLITE_NULL, { .s = NULL } } };
if (cdata->dav.rowid) {
int r = sqldb_exec(carddavdb->db, CMD_UPDATE, bval, NULL, NULL);
if (r) return r;
}
else {
int r = sqldb_exec(carddavdb->db, CMD_INSERT, bval, NULL, NULL);
if (r) return r;
cdata->dav.rowid = sqldb_lastid(carddavdb->db);
}
return 0;
}
#define CMD_DELETE "DELETE FROM vcard_objs WHERE rowid = :rowid;"
EXPORTED int carddav_delete(struct carddav_db *carddavdb, unsigned rowid)
{
struct sqldb_bindval bval[] = {
{ ":rowid", SQLITE_INTEGER, { .i = rowid } },
{ NULL, SQLITE_NULL, { .s = NULL } } };
int r;
r = sqldb_exec(carddavdb->db, CMD_DELETE, bval, NULL, NULL);
if (r) return r;
return 0;
}
#define CMD_DELMBOX "DELETE FROM vcard_objs WHERE mailbox = :mailbox;"
EXPORTED int carddav_delmbox(struct carddav_db *carddavdb, const char *mailbox)
{
struct sqldb_bindval bval[] = {
{ ":mailbox", SQLITE_TEXT, { .s = mailbox } },
{ NULL, SQLITE_NULL, { .s = NULL } } };
int r;
r = sqldb_exec(carddavdb->db, CMD_DELMBOX, bval, NULL, NULL);
if (r) return r;
return 0;
}
EXPORTED int carddav_get_cards(struct carddav_db *carddavdb,
const char *abookname,
const char *vcard_uid, int kind,
int (*cb)(void *rock,
struct carddav_data *cdata),
void *rock)
{
struct sqldb_bindval bval[] = {
{ ":kind", SQLITE_INTEGER, { .i = kind } },
{ ":mailbox", SQLITE_TEXT, { .s = abookname } },
{ ":vcard_uid", SQLITE_TEXT, { .s = vcard_uid } },
{ NULL, SQLITE_NULL, { .s = NULL } }
};
static struct carddav_data cdata;
struct read_rock rrock = { carddavdb, &cdata, 0, cb, rock };
struct buf sqlbuf = BUF_INITIALIZER;
buf_setcstr(&sqlbuf, CMD_GETFIELDS);
buf_appendcstr(&sqlbuf, " WHERE alive = 1 AND kind = :kind");
if (abookname)
buf_appendcstr(&sqlbuf, " AND mailbox = :mailbox");
if (vcard_uid)
buf_appendcstr(&sqlbuf, " AND vcard_uid = :vcard_uid");
buf_appendcstr(&sqlbuf, " ORDER BY mailbox, imap_uid;");
int r = sqldb_exec(carddavdb->db, buf_cstring(&sqlbuf), bval, &read_cb, &rrock);
buf_free(&sqlbuf);
if (r) {
syslog(LOG_ERR, "caldav error %s", error_message(r));
/* XXX - free memory */
}
return r;
}
#define CMD_GETUPDATES CMD_GETFIELDS \
" WHERE kind = :kind AND modseq > :modseq;"
#define CMD_GETUPDATES_MBOX CMD_GETFIELDS \
" WHERE mailbox = :mailbox AND kind = :kind AND modseq > :modseq;"
EXPORTED int carddav_get_updates(struct carddav_db *carddavdb,
modseq_t oldmodseq, const char *mboxname, int kind,
int (*cb)(void *rock,
struct carddav_data *cdata),
void *rock)
{
struct sqldb_bindval bval[] = {
{ ":mailbox", SQLITE_TEXT, { .s = mboxname } },
{ ":modseq", SQLITE_INTEGER, { .i = oldmodseq } },
{ ":kind", SQLITE_INTEGER, { .i = kind } },
{ NULL, SQLITE_NULL, { .s = NULL } }
};
static struct carddav_data cdata;
struct read_rock rrock = { carddavdb, &cdata, 0, cb, rock };
int r;
if (mboxname)
r = sqldb_exec(carddavdb->db, CMD_GETUPDATES_MBOX, bval, &read_cb, &rrock);
else
r = sqldb_exec(carddavdb->db, CMD_GETUPDATES, bval, &read_cb, &rrock);
if (r) {
syslog(LOG_ERR, "caldav error %s", error_message(r));
/* XXX - free memory */
}
return r;
}
EXPORTED int carddav_writecard(struct carddav_db *carddavdb, struct carddav_data *cdata,
struct vparse_card *vcard)
{
struct vparse_entry *ventry;
strarray_t emails = STRARRAY_INITIALIZER;
strarray_t member_uids = STRARRAY_INITIALIZER;
for (ventry = vcard->properties; ventry; ventry = ventry->next) {
const char *name = ventry->name;
const char *propval = ventry->v.value;
if (!name) continue;
if (!propval) continue;
if (!strcmp(name, "uid")) {
cdata->vcard_uid = propval;
}
else if (!strcmp(name, "n")) {
cdata->name = propval;
}
else if (!strcmp(name, "fn")) {
cdata->fullname = propval;
}
else if (!strcmp(name, "nickname")) {
cdata->nickname = propval;
}
else if (!strcmp(name, "email")) {
/* XXX - insert if primary */
int ispref = 0;
struct vparse_param *param;
for (param = ventry->params; param; param = param->next) {
if (!strcasecmp(param->name, "type") && !strcasecmp(param->value, "pref"))
ispref = 1;
}
strarray_append(&emails, propval);
strarray_append(&emails, ispref ? "1" : "");
}
else if (!strcmp(name, "x-addressbookserver-member")) {
if (strncmp(propval, "urn:uuid:", 9)) continue;
strarray_append(&member_uids, propval+9);
strarray_append(&member_uids, "");
}
else if (!strcmp(name, "x-fm-otheraccount-member")) {
if (strncmp(propval, "urn:uuid:", 9)) continue;
struct vparse_param *param = vparse_get_param(ventry, "userid");
if (!param) continue;
strarray_append(&member_uids, propval+9);
strarray_append(&member_uids, param->value);
}
else if (!strcmp(name, "x-addressbookserver-kind")) {
if (!strcasecmp(propval, "group"))
cdata->kind = CARDDAV_KIND_GROUP;
/* default case is CARDDAV_KIND_CONTACT */
}
}
int r = carddav_write(carddavdb, cdata);
if (!r) r = carddav_write_emails(carddavdb, cdata->dav.rowid, &emails);
if (!r) r = carddav_write_groups(carddavdb, cdata->dav.rowid, &member_uids);
strarray_fini(&emails);
strarray_fini(&member_uids);
return r;
}
EXPORTED int carddav_store(struct mailbox *mailbox, struct vparse_card *vcard,
const char *resource,
strarray_t *flags, struct entryattlist *annots,
const char *userid, struct auth_state *authstate,
int ignorequota)
{
int r = 0;
FILE *f = NULL;
struct stagemsg *stage;
char *header;
quota_t qdiffs[QUOTA_NUMRESOURCES] = QUOTA_DIFFS_DONTCARE_INITIALIZER;
struct appendstate as;
time_t now = time(0);
char *freeme = NULL;
char datestr[80];
/* Prepare to stage the message */
if (!(f = append_newstage(mailbox->name, now, 0, &stage))) {
syslog(LOG_ERR, "append_newstage(%s) failed", mailbox->name);
return -1;
}
/* set the REVision time */
time_to_iso8601(now, datestr, sizeof(datestr), 0);
vparse_replace_entry(vcard, NULL, "REV", datestr);
/* Create header for resource */
const char *uid = vparse_stringval(vcard, "uid");
const char *fullname = vparse_stringval(vcard, "fn");
if (!resource) resource = freeme = strconcat(uid, ".vcf", (char *)NULL);
struct buf buf = BUF_INITIALIZER;
vparse_tobuf(vcard, &buf);
char *mbuserid = mboxname_to_userid(mailbox->name);
time_to_rfc822(now, datestr, sizeof(datestr));
/* XXX This needs to be done via an LDAP/DB lookup */
header = charset_encode_mimeheader(mbuserid, 0);
fprintf(f, "From: %s <>\r\n", header);
free(header);
header = charset_encode_mimeheader(fullname, 0);
fprintf(f, "Subject: %s\r\n", header);
free(header);
fprintf(f, "Date: %s\r\n", datestr);
if (strchr(uid, '@'))
fprintf(f, "Message-ID: <%s>\r\n", uid);
else
fprintf(f, "Message-ID: <%s@%s>\r\n", uid, config_servername);
fprintf(f, "Content-Type: text/vcard; charset=utf-8\r\n");
fprintf(f, "Content-Length: %u\r\n", (unsigned)buf_len(&buf));
fprintf(f, "Content-Disposition: inline; filename=\"%s\"\r\n", resource);
/* XXX Check domain of data and use appropriate CTE */
fprintf(f, "MIME-Version: 1.0\r\n");
fprintf(f, "\r\n");
/* Write the vCard data to the file */
fprintf(f, "%s", buf_cstring(&buf));
buf_free(&buf);
qdiffs[QUOTA_STORAGE] = ftell(f);
qdiffs[QUOTA_MESSAGE] = 1;
fclose(f);
if ((r = append_setup_mbox(&as, mailbox, userid, authstate, 0,
ignorequota ? NULL : qdiffs, 0, 0,
EVENT_MESSAGE_NEW|EVENT_CALENDAR))) {
syslog(LOG_ERR, "append_setup(%s) failed: %s",
mailbox->name, error_message(r));
goto done;
}
struct body *body = NULL;
r = append_fromstage(&as, &body, stage, now, flags, 0, annots);
if (body) {
message_free_body(body);
free(body);
}
if (r) {
syslog(LOG_ERR, "append_fromstage() failed");
append_abort(&as);
goto done;
}
/* Commit the append to the calendar mailbox */
r = append_commit(&as);
if (r) {
syslog(LOG_ERR, "append_commit() failed");
goto done;
}
done:
append_removestage(stage);
free(freeme);
free(mbuserid);
return r;
}
EXPORTED int carddav_remove(struct mailbox *mailbox,
uint32_t olduid, int isreplace)
{
int userflag;
int r = mailbox_user_flag(mailbox, DFLAG_UNBIND, &userflag, 1);
struct index_record oldrecord;
if (!r) r = mailbox_find_index_record(mailbox, olduid, &oldrecord);
if (!r && !(oldrecord.system_flags & FLAG_EXPUNGED)) {
if (isreplace) oldrecord.user_flags[userflag/32] |= 1<<(userflag&31);
oldrecord.system_flags |= FLAG_EXPUNGED;
r = mailbox_rewrite_index_record(mailbox, &oldrecord);
}
if (r) {
syslog(LOG_ERR, "expunging record (%s) failed: %s",
mailbox->name, error_message(r));
}
return r;
}
EXPORTED char *carddav_mboxname(const char *userid, const char *name)
{
struct buf boxbuf = BUF_INITIALIZER;
char *res = NULL;
buf_setcstr(&boxbuf, config_getstring(IMAPOPT_ADDRESSBOOKPREFIX));
if (name) {
size_t len = strcspn(name, "/");
buf_putc(&boxbuf, '.');
buf_appendmap(&boxbuf, name, len);
}
res = mboxname_user_mbox(userid, buf_cstring(&boxbuf));
buf_free(&boxbuf);
return res;
}