1 /* webdav_db.c -- implementation of per-user WebDAV database
2  *
3  * Copyright (c) 1994-2015 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 
44 #include <config.h>
45 
46 #ifdef WITH_DAV
47 
48 #include <syslog.h>
49 #include <string.h>
50 
51 #include "webdav_db.h"
52 #include "cyrusdb.h"
53 #include "httpd.h"
54 #include "http_dav.h"
55 #include "libconfig.h"
56 #include "util.h"
57 #include "xstrlcat.h"
58 #include "xmalloc.h"
59 
60 
61 enum {
62     STMT_SELRSRC,
63     STMT_SELUID,
64     STMT_SELMBOX,
65     STMT_INSERT,
66     STMT_UPDATE,
67     STMT_DELETE,
68     STMT_DELMBOX,
69     STMT_BEGIN,
70     STMT_COMMIT,
71     STMT_ROLLBACK
72 };
73 
74 #define NUM_STMT 10
75 
76 struct webdav_db {
77     sqldb_t *db;                        /* DB handle */
78     char *userid;
79     struct buf mailbox;                 /* buffers for copies of column text */
80     struct buf resource;
81     struct buf lock_token;
82     struct buf lock_owner;
83     struct buf lock_ownerid;
84     struct buf filename;
85     struct buf type;
86     struct buf subtype;
87     struct buf res_uid;
88     unsigned ref_count;
89 };
90 
91 static int webdav_initialized = 0;
92 
done_cb(void * rock)93 static void done_cb(void *rock __attribute__((unused))) {
94     webdav_done();
95 }
96 
init_internal()97 static void init_internal() {
98     if (!webdav_initialized) {
99         webdav_init();
100         cyrus_modules_add(done_cb, NULL);
101     }
102 }
103 
webdav_init(void)104 EXPORTED int webdav_init(void)
105 {
106     int r = sqldb_init();
107     if (!r) webdav_initialized = 1;
108     return r;
109 }
110 
111 
webdav_done(void)112 EXPORTED int webdav_done(void)
113 {
114     int r = sqldb_done();
115     if (!r) webdav_initialized = 0;
116     return r;
117 }
118 
119 /* Open DAV DB corresponding to userid */
webdav_open_userid(const char * userid)120 EXPORTED struct webdav_db *webdav_open_userid(const char *userid)
121 {
122     struct webdav_db *webdavdb = NULL;
123 
124     init_internal();
125 
126     sqldb_t *db = dav_open_userid(userid);
127     if (!db) return NULL;
128 
129     webdavdb = xzmalloc(sizeof(struct webdav_db));
130     webdavdb->userid = xstrdup(userid);
131     webdavdb->db = db;
132 
133     return webdavdb;
134 }
135 
136 /* Open DAV DB corresponding to mailbox */
webdav_open_mailbox(struct mailbox * mailbox)137 EXPORTED struct webdav_db *webdav_open_mailbox(struct mailbox *mailbox)
138 {
139     struct webdav_db *webdavdb = NULL;
140     char *userid = mboxname_to_userid(mailbox->name);
141 
142     init_internal();
143 
144     if (userid) {
145         webdavdb = webdav_open_userid(userid);
146         free(userid);
147         return webdavdb;
148     }
149 
150     sqldb_t *db = dav_open_mailbox(mailbox);
151     if (!db) return NULL;
152 
153     webdavdb = xzmalloc(sizeof(struct webdav_db));
154     webdavdb->db = db;
155 
156     return webdavdb;
157 }
158 
159 /* Close DAV DB */
webdav_close(struct webdav_db * webdavdb)160 EXPORTED int webdav_close(struct webdav_db *webdavdb)
161 {
162     int r = 0;
163 
164     if (!webdavdb) return 0;
165 
166     buf_free(&webdavdb->mailbox);
167     buf_free(&webdavdb->resource);
168     buf_free(&webdavdb->lock_token);
169     buf_free(&webdavdb->lock_owner);
170     buf_free(&webdavdb->lock_ownerid);
171     buf_free(&webdavdb->filename);
172     buf_free(&webdavdb->type);
173     buf_free(&webdavdb->subtype);
174     buf_free(&webdavdb->res_uid);
175 
176     r = sqldb_close(&webdavdb->db);
177 
178     free(webdavdb->userid);
179     free(webdavdb);
180 
181     return r;
182 }
183 
webdav_begin(struct webdav_db * webdavdb)184 EXPORTED int webdav_begin(struct webdav_db *webdavdb)
185 {
186     return sqldb_begin(webdavdb->db, "webdav");
187 }
188 
webdav_commit(struct webdav_db * webdavdb)189 EXPORTED int webdav_commit(struct webdav_db *webdavdb)
190 {
191     return sqldb_commit(webdavdb->db, "webdav");
192 }
193 
webdav_abort(struct webdav_db * webdavdb)194 EXPORTED int webdav_abort(struct webdav_db *webdavdb)
195 {
196     return sqldb_rollback(webdavdb->db, "webdav");
197 }
198 
199 
200 struct read_rock {
201     struct webdav_db *db;
202     struct webdav_data *wdata;
203     int tombstones;
204     webdav_cb_t *cb;
205     void *rock;
206 };
207 
column_text_to_buf(const char * text,struct buf * buf)208 static const char *column_text_to_buf(const char *text, struct buf *buf)
209 {
210     if (text) {
211         buf_setcstr(buf, text);
212         text = buf_cstring(buf);
213     }
214 
215     return text;
216 }
217 
read_cb(sqlite3_stmt * stmt,void * rock)218 static int read_cb(sqlite3_stmt *stmt, void *rock)
219 {
220     struct read_rock *rrock = (struct read_rock *) rock;
221     struct webdav_db *db = rrock->db;
222     struct webdav_data *wdata = rrock->wdata;
223     int r = 0;
224 
225     memset(wdata, 0, sizeof(struct webdav_data));
226 
227     wdata->dav.alive = sqlite3_column_int(stmt, 14);
228     wdata->dav.modseq = sqlite3_column_int64(stmt, 15);
229     wdata->dav.createdmodseq = sqlite3_column_int64(stmt, 16);
230     if (!rrock->tombstones && !wdata->dav.alive)
231         return 0;
232 
233     wdata->dav.rowid = sqlite3_column_int(stmt, 0);
234     wdata->dav.creationdate = sqlite3_column_int(stmt, 1);
235     wdata->dav.imap_uid = sqlite3_column_int(stmt, 4);
236     wdata->dav.lock_expire = sqlite3_column_int(stmt, 8);
237     wdata->ref_count = sqlite3_column_int(stmt, 13);
238 
239     if (rrock->cb) {
240         /* We can use the column data directly for the callback */
241         wdata->dav.mailbox = (const char *) sqlite3_column_text(stmt, 2);
242         wdata->dav.resource = (const char *) sqlite3_column_text(stmt, 3);
243         wdata->dav.lock_token = (const char *) sqlite3_column_text(stmt, 5);
244         wdata->dav.lock_owner = (const char *) sqlite3_column_text(stmt, 6);
245         wdata->dav.lock_ownerid = (const char *) sqlite3_column_text(stmt, 7);
246         wdata->filename = (const char *) sqlite3_column_text(stmt, 9);
247         wdata->type = (const char *) sqlite3_column_text(stmt, 10);
248         wdata->subtype = (const char *) sqlite3_column_text(stmt, 11);
249         wdata->res_uid = (const char *) sqlite3_column_text(stmt, 12);
250         r = rrock->cb(rrock->rock, wdata);
251     }
252     else {
253         /* For single row SELECTs like webdav_read(),
254          * we need to make a copy of the column data before
255          * it gets flushed by sqlite3_step() or sqlite3_reset() */
256         wdata->dav.mailbox =
257             column_text_to_buf((const char *) sqlite3_column_text(stmt, 2),
258                                &db->mailbox);
259         wdata->dav.resource =
260             column_text_to_buf((const char *) sqlite3_column_text(stmt, 3),
261                                &db->resource);
262         wdata->dav.lock_token =
263             column_text_to_buf((const char *) sqlite3_column_text(stmt, 5),
264                                &db->lock_token);
265         wdata->dav.lock_owner =
266             column_text_to_buf((const char *) sqlite3_column_text(stmt, 6),
267                                &db->lock_owner);
268         wdata->dav.lock_ownerid =
269             column_text_to_buf((const char *) sqlite3_column_text(stmt, 7),
270                                &db->lock_ownerid);
271         wdata->filename =
272             column_text_to_buf((const char *) sqlite3_column_text(stmt, 9),
273                                &db->filename);
274         wdata->type =
275             column_text_to_buf((const char *) sqlite3_column_text(stmt, 10),
276                                &db->type);
277         wdata->subtype =
278             column_text_to_buf((const char *) sqlite3_column_text(stmt, 11),
279                                &db->subtype);
280         wdata->res_uid =
281             column_text_to_buf((const char *) sqlite3_column_text(stmt, 12),
282                                &db->res_uid);
283     }
284 
285     return r;
286 }
287 
288 #define CMD_GETFIELDS                                                   \
289     "SELECT rowid, creationdate, mailbox, resource, imap_uid,"          \
290     "  lock_token, lock_owner, lock_ownerid, lock_expire,"              \
291     "  filename, type, subtype, res_uid, ref_count, alive,"             \
292     "  modseq, createdmodseq"                                           \
293     " FROM dav_objs"                                                    \
294 
295 
296 #define CMD_SELRSRC CMD_GETFIELDS                                       \
297     " WHERE mailbox = :mailbox AND resource = :resource;"
298 
webdav_lookup_resource(struct webdav_db * webdavdb,const char * mailbox,const char * resource,struct webdav_data ** result,int tombstones)299 EXPORTED int webdav_lookup_resource(struct webdav_db *webdavdb,
300                                     const char *mailbox, const char *resource,
301                                     struct webdav_data **result,
302                                     int tombstones)
303 {
304     struct sqldb_bindval bval[] = {
305         { ":mailbox",  SQLITE_TEXT, { .s = mailbox       } },
306         { ":resource", SQLITE_TEXT, { .s = resource      } },
307         { NULL,        SQLITE_NULL, { .s = NULL          } } };
308     static struct webdav_data wdata;
309     struct read_rock rrock = { webdavdb, &wdata, tombstones, NULL, NULL };
310     int r;
311 
312     *result = memset(&wdata, 0, sizeof(struct webdav_data));
313 
314     r = sqldb_exec(webdavdb->db, CMD_SELRSRC, bval, &read_cb, &rrock);
315     if (!r && !wdata.dav.rowid) r = CYRUSDB_NOTFOUND;
316 
317     return r;
318 }
319 
320 
321 #define CMD_SELIMAPUID CMD_GETFIELDS \
322     " WHERE mailbox = :mailbox AND imap_uid = :imap_uid;"
323 
webdav_lookup_imapuid(struct webdav_db * webdavdb,const char * mailbox,int imap_uid,struct webdav_data ** result,int tombstones)324 EXPORTED int webdav_lookup_imapuid(struct webdav_db *webdavdb,
325                                     const char *mailbox, int imap_uid,
326                                     struct webdav_data **result,
327                                     int tombstones)
328 {
329     struct sqldb_bindval bval[] = {
330         { ":mailbox",  SQLITE_TEXT,    { .s = mailbox       } },
331         { ":imap_uid", SQLITE_INTEGER, { .i = imap_uid      } },
332         { NULL,        SQLITE_NULL,    { .s = NULL          } } };
333     static struct webdav_data wdata;
334     struct read_rock rrock = { webdavdb, &wdata, tombstones, NULL, NULL };
335     int r;
336 
337     *result = memset(&wdata, 0, sizeof(struct webdav_data));
338 
339     r = sqldb_exec(webdavdb->db, CMD_SELIMAPUID, bval, &read_cb, &rrock);
340     if (!r && !wdata.dav.rowid) r = CYRUSDB_NOTFOUND;
341 
342     wdata.dav.mailbox = mailbox;
343     wdata.dav.imap_uid = imap_uid;
344 
345     return r;
346 }
347 
348 
349 #define CMD_SELUID CMD_GETFIELDS                                        \
350     " WHERE res_uid = :res_uid AND alive = 1;"
351 
webdav_lookup_uid(struct webdav_db * webdavdb,const char * res_uid,struct webdav_data ** result)352 EXPORTED int webdav_lookup_uid(struct webdav_db *webdavdb, const char *res_uid,
353                                struct webdav_data **result)
354 {
355     struct sqldb_bindval bval[] = {
356         { ":res_uid",    SQLITE_TEXT, { .s = res_uid             } },
357         { NULL,          SQLITE_NULL, { .s = NULL                } } };
358     static struct webdav_data wdata;
359     struct read_rock rrock = { webdavdb, &wdata, 0, NULL, NULL };
360     int r;
361 
362     *result = memset(&wdata, 0, sizeof(struct webdav_data));
363 
364     r = sqldb_exec(webdavdb->db, CMD_SELUID, bval, &read_cb, &rrock);
365     if (!r && !wdata.dav.rowid) r = CYRUSDB_NOTFOUND;
366 
367     return r;
368 }
369 
370 
371 #define CMD_SELMBOX CMD_GETFIELDS                                       \
372     " WHERE mailbox = :mailbox AND alive = 1;"
373 
webdav_foreach(struct webdav_db * webdavdb,const char * mailbox,int (* cb)(void * rock,struct webdav_data * data),void * rock)374 EXPORTED int webdav_foreach(struct webdav_db *webdavdb, const char *mailbox,
375                             int (*cb)(void *rock, struct webdav_data *data),
376                             void *rock)
377 {
378     struct sqldb_bindval bval[] = {
379         { ":mailbox", SQLITE_TEXT, { .s = mailbox } },
380         { NULL,       SQLITE_NULL, { .s = NULL    } } };
381     struct webdav_data wdata;
382     struct read_rock rrock = { webdavdb, &wdata, 0, cb, rock };
383 
384     return sqldb_exec(webdavdb->db, CMD_SELMBOX, bval, &read_cb, &rrock);
385 }
386 
387 
388 #define CMD_INSERT                                                      \
389     "INSERT INTO dav_objs ("                                            \
390     "  creationdate, mailbox, resource, imap_uid, modseq,"              \
391     "  createdmodseq,"                                                  \
392     "  lock_token, lock_owner, lock_ownerid, lock_expire,"              \
393     "  filename, type, subtype, res_uid, ref_count, alive )"            \
394     " VALUES ("                                                         \
395     "  :creationdate, :mailbox, :resource, :imap_uid, :modseq,"         \
396     "  :createdmodseq,"                                                 \
397     "  :lock_token, :lock_owner, :lock_ownerid, :lock_expire,"          \
398     "  :filename, :type, :subtype, :res_uid, :ref_count, :alive );"
399 
400 #define CMD_UPDATE                      \
401     "UPDATE dav_objs SET"               \
402     "  imap_uid     = :imap_uid,"       \
403     "  modseq       = :modseq,"         \
404     "  createdmodseq = :createdmodseq," \
405     "  lock_token   = :lock_token,"     \
406     "  lock_owner   = :lock_owner,"     \
407     "  lock_ownerid = :lock_ownerid,"   \
408     "  lock_expire  = :lock_expire,"    \
409     "  filename     = :filename,"       \
410     "  type         = :type,"           \
411     "  subtype      = :subtype,"        \
412     "  res_uid      = :res_uid,"        \
413     "  ref_count    = :ref_count,"      \
414     "  alive        = :alive"           \
415     " WHERE rowid = :rowid;"
416 
webdav_write(struct webdav_db * webdavdb,struct webdav_data * wdata)417 EXPORTED int webdav_write(struct webdav_db *webdavdb, struct webdav_data *wdata)
418 {
419     struct sqldb_bindval bval[] = {
420         { ":imap_uid",     SQLITE_INTEGER, { .i = wdata->dav.imap_uid     } },
421         { ":modseq",       SQLITE_INTEGER, { .i = wdata->dav.modseq       } },
422         { ":createdmodseq", SQLITE_INTEGER, { .i = wdata->dav.createdmodseq } },
423         { ":lock_token",   SQLITE_TEXT,    { .s = wdata->dav.lock_token   } },
424         { ":lock_owner",   SQLITE_TEXT,    { .s = wdata->dav.lock_owner   } },
425         { ":lock_ownerid", SQLITE_TEXT,    { .s = wdata->dav.lock_ownerid } },
426         { ":lock_expire",  SQLITE_INTEGER, { .i = wdata->dav.lock_expire  } },
427         { ":filename",     SQLITE_TEXT,    { .s = wdata->filename         } },
428         { ":type",         SQLITE_TEXT,    { .s = wdata->type             } },
429         { ":subtype",      SQLITE_TEXT,    { .s = wdata->subtype          } },
430         { ":res_uid",      SQLITE_TEXT,    { .s = wdata->res_uid          } },
431         { ":ref_count",    SQLITE_INTEGER, { .i = wdata->ref_count        } },
432         { ":alive",        SQLITE_INTEGER, { .i = wdata->dav.alive        } },
433         { NULL,            SQLITE_NULL,    { .s = NULL                    } },
434         { NULL,            SQLITE_NULL,    { .s = NULL                    } },
435         { NULL,            SQLITE_NULL,    { .s = NULL                    } },
436         { NULL,            SQLITE_NULL,    { .s = NULL                    } } };
437     const char *cmd;
438     int r;
439 
440     if (wdata->dav.rowid) {
441         cmd = CMD_UPDATE;
442 
443         bval[13].name = ":rowid";
444         bval[13].type = SQLITE_INTEGER;
445         bval[13].val.i = wdata->dav.rowid;
446     }
447     else {
448         cmd = CMD_INSERT;
449 
450         bval[13].name = ":creationdate";
451         bval[13].type = SQLITE_INTEGER;
452         bval[13].val.i = wdata->dav.creationdate;
453         bval[14].name = ":mailbox";
454         bval[14].type = SQLITE_TEXT;
455         bval[14].val.s = wdata->dav.mailbox;
456         bval[15].name = ":resource";
457         bval[15].type = SQLITE_TEXT;
458         bval[15].val.s = wdata->dav.resource;
459     }
460 
461     r = sqldb_exec(webdavdb->db, cmd, bval, NULL, NULL);
462 
463     return r;
464 }
465 
466 
467 #define CMD_DELETE "DELETE FROM dav_objs WHERE rowid = :rowid;"
468 
webdav_delete(struct webdav_db * webdavdb,unsigned rowid)469 EXPORTED int webdav_delete(struct webdav_db *webdavdb, unsigned rowid)
470 {
471     struct sqldb_bindval bval[] = {
472         { ":rowid", SQLITE_INTEGER, { .i = rowid } },
473         { NULL,     SQLITE_NULL,    { .s = NULL  } } };
474     int r;
475 
476     r = sqldb_exec(webdavdb->db, CMD_DELETE, bval, NULL, NULL);
477 
478     return r;
479 }
480 
481 
482 #define CMD_DELMBOX "DELETE FROM dav_objs WHERE mailbox = :mailbox;"
483 
webdav_delmbox(struct webdav_db * webdavdb,const char * mailbox)484 EXPORTED int webdav_delmbox(struct webdav_db *webdavdb, const char *mailbox)
485 {
486     struct sqldb_bindval bval[] = {
487         { ":mailbox", SQLITE_TEXT, { .s = mailbox } },
488         { NULL,       SQLITE_NULL, { .s = NULL    } } };
489     int r;
490 
491     r = sqldb_exec(webdavdb->db, CMD_DELMBOX, bval, NULL, NULL);
492 
493     return r;
494 }
495 
webdav_get_updates(struct webdav_db * webdavdb,modseq_t oldmodseq,const char * mboxname,int kind,int limit,int (* cb)(void * rock,struct webdav_data * wdata),void * rock)496 EXPORTED int webdav_get_updates(struct webdav_db *webdavdb,
497                                 modseq_t oldmodseq, const char *mboxname,
498                                 int kind __attribute__((unused)), int limit,
499                                 int (*cb)(void *rock, struct webdav_data *wdata),
500                                 void *rock)
501 {
502     struct sqldb_bindval bval[] = {
503         { ":mailbox",      SQLITE_TEXT,    { .s = mboxname  } },
504         { ":modseq",       SQLITE_INTEGER, { .i = oldmodseq } },
505         /* SQLite interprets a negative limit as unbounded. */
506         { ":limit",        SQLITE_INTEGER, { .i = limit > 0 ? limit : -1 } },
507         { NULL,            SQLITE_NULL,    { .s = NULL      } }
508     };
509     static struct webdav_data wdata;
510     struct read_rock rrock = { webdavdb, &wdata, 1 /* tombstones */, cb, rock };
511     struct buf sqlbuf = BUF_INITIALIZER;
512     int r;
513 
514     buf_setcstr(&sqlbuf, CMD_GETFIELDS " WHERE");
515     if (mboxname) buf_appendcstr(&sqlbuf, " mailbox = :mailbox AND");
516     if (!oldmodseq) buf_appendcstr(&sqlbuf, " alive = 1 AND");
517     buf_appendcstr(&sqlbuf, " modseq > :modseq ORDER BY modseq LIMIT :limit;");
518 
519     r = sqldb_exec(webdavdb->db, buf_cstring(&sqlbuf), bval, &read_cb, &rrock);
520     buf_free(&sqlbuf);
521 
522     if (r) {
523         syslog(LOG_ERR, "webdav error %s", error_message(r));
524     }
525     return r;
526 }
527 
528 #else
529 
webdav_init(void)530 EXPORTED int webdav_init(void)
531 {
532     return 0;
533 }
534 
535 
webdav_done(void)536 EXPORTED int webdav_done(void)
537 {
538     return 0;
539 }
540 
541 #endif /* WITH_DAV */
542