1 /* caldav_db.c -- implementation of per-user CalDAV database
2  *
3  * Copyright (c) 1994-2012 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 #include <sysexits.h>
47 #include <syslog.h>
48 #include <string.h>
49 
50 #include <libical/ical.h>
51 
52 #include "caldav_alarm.h"
53 #include "caldav_db.h"
54 #include "cyrusdb.h"
55 #include "httpd.h"
56 #include "http_dav.h"
57 #include "ical_support.h"
58 #include "libconfig.h"
59 #include "mboxname.h"
60 #include "util.h"
61 #include "xstrlcat.h"
62 #include "xmalloc.h"
63 
64 /* generated headers are not necessarily in current directory */
65 #include "imap/imap_err.h"
66 
67 struct caldav_db {
68     sqldb_t *db;                        /* DB handle */
69     char *sched_inbox;                  /* DB owner's scheduling Inbox */
70     struct buf mailbox;                 /* buffers for copies of column text */
71     struct buf resource;
72     struct buf lock_token;
73     struct buf lock_owner;
74     struct buf lock_ownerid;
75     struct buf ical_uid;
76     struct buf organizer;
77     struct buf dtstart;
78     struct buf dtend;
79     struct buf sched_tag;
80     struct buf jmapdata;
81 };
82 
83 
84 static struct namespace caldav_namespace;
85 EXPORTED time_t caldav_epoch = -1;
86 EXPORTED time_t caldav_eternity = -1;
87 
88 static int caldav_initialized = 0;
89 
done_cb(void * rock)90 static void done_cb(void *rock __attribute__((unused))) {
91     caldav_done();
92 }
93 
init_internal()94 static void init_internal() {
95     if (!caldav_initialized) {
96         caldav_init();
97         cyrus_modules_add(done_cb, NULL);
98     }
99 }
100 
caldav_init(void)101 EXPORTED int caldav_init(void)
102 {
103     int r;
104     struct icaltimetype date;
105 
106     /* Set namespace -- force standard (internal) */
107     if ((r = mboxname_init_namespace(&caldav_namespace, 1))) {
108         syslog(LOG_ERR, "%s", error_message(r));
109         fatal(error_message(r), EX_CONFIG);
110     }
111 
112     /* Get min date-time */
113     date = icaltime_from_string(config_getstring(IMAPOPT_CALDAV_MINDATETIME));
114     if (!icaltime_is_null_time(date)) {
115         caldav_epoch = icaltime_as_timet_with_zone(date, NULL);
116     }
117     if (caldav_epoch == -1) caldav_epoch = INT_MIN;
118 
119     /* Get max date-time */
120     date = icaltime_from_string(config_getstring(IMAPOPT_CALDAV_MAXDATETIME));
121     if (!icaltime_is_null_time(date)) {
122         caldav_eternity = icaltime_as_timet_with_zone(date, NULL);
123     }
124     if (caldav_eternity == -1) caldav_eternity = INT_MAX;
125 
126     r = sqldb_init();
127     caldav_alarm_init();
128 
129     if (!r) caldav_initialized = 1;
130     return r;
131 }
132 
133 
caldav_done(void)134 EXPORTED int caldav_done(void)
135 {
136     int r;
137     caldav_alarm_done();
138     r = sqldb_done();
139     if (!r) caldav_initialized = 0;
140     return r;
141 }
142 
caldav_open_userid(const char * userid)143 EXPORTED struct caldav_db *caldav_open_userid(const char *userid)
144 {
145     struct caldav_db *caldavdb = NULL;
146 
147     init_internal();
148 
149     sqldb_t *db = dav_open_userid(userid);
150     if (!db) return NULL;
151 
152     caldavdb = xzmalloc(sizeof(struct caldav_db));
153     caldavdb->db = db;
154 
155     /* Construct mbox name corresponding to userid's scheduling Inbox */
156     caldavdb->sched_inbox = caldav_mboxname(userid, SCHED_INBOX);
157 
158     return caldavdb;
159 }
160 
161 /* Open DAV DB corresponding to userid */
caldav_open_mailbox(struct mailbox * mailbox)162 EXPORTED struct caldav_db *caldav_open_mailbox(struct mailbox *mailbox)
163 {
164     struct caldav_db *caldavdb = NULL;
165     char *userid = mboxname_to_userid(mailbox->name);
166 
167     init_internal();
168 
169     if (userid) {
170         caldavdb = caldav_open_userid(userid);
171         free(userid);
172         return caldavdb;
173     }
174 
175     sqldb_t *db = dav_open_mailbox(mailbox);
176     if (!db) return NULL;
177 
178     caldavdb = xzmalloc(sizeof(struct caldav_db));
179     caldavdb->db = db;
180 
181     return caldavdb;
182 }
183 
184 /* Close DAV DB */
caldav_close(struct caldav_db * caldavdb)185 EXPORTED int caldav_close(struct caldav_db *caldavdb)
186 {
187     int r = 0;
188 
189     if (!caldavdb) return 0;
190 
191     free(caldavdb->sched_inbox);
192     buf_free(&caldavdb->mailbox);
193     buf_free(&caldavdb->resource);
194     buf_free(&caldavdb->lock_token);
195     buf_free(&caldavdb->lock_owner);
196     buf_free(&caldavdb->lock_ownerid);
197     buf_free(&caldavdb->ical_uid);
198     buf_free(&caldavdb->organizer);
199     buf_free(&caldavdb->dtstart);
200     buf_free(&caldavdb->dtend);
201     buf_free(&caldavdb->sched_tag);
202     buf_free(&caldavdb->jmapdata);
203 
204     r = sqldb_close(&caldavdb->db);
205 
206     free(caldavdb);
207 
208     return r;
209 }
210 
caldav_begin(struct caldav_db * caldavdb)211 EXPORTED int caldav_begin(struct caldav_db *caldavdb)
212 {
213     return sqldb_begin(caldavdb->db, "caldav");
214 }
215 
caldav_commit(struct caldav_db * caldavdb)216 EXPORTED int caldav_commit(struct caldav_db *caldavdb)
217 {
218     return sqldb_commit(caldavdb->db, "caldav");
219 }
220 
caldav_abort(struct caldav_db * caldavdb)221 EXPORTED int caldav_abort(struct caldav_db *caldavdb)
222 {
223     return sqldb_rollback(caldavdb->db, "caldav");
224 }
225 
226 #define RROCK_FLAG_TOMBSTONES (1<<0)
227 struct read_rock {
228     struct caldav_db *db;
229     struct caldav_data *cdata;
230     int flags;
231     caldav_cb_t *cb;
232     void *rock;
233 };
234 
column_text_to_buf(const char * text,struct buf * buf)235 static const char *column_text_to_buf(const char *text, struct buf *buf)
236 {
237     if (text) {
238         buf_setcstr(buf, text);
239         text = buf_cstring(buf);
240     }
241 
242     return text;
243 }
244 
_num_to_comp_flags(struct comp_flags * flags,unsigned num)245 static void _num_to_comp_flags(struct comp_flags *flags, unsigned num)
246 {
247     flags->recurring =  num & 1;
248     flags->transp    = (num >> 1) & 1;
249     flags->status    = (num >> 2) & 3;
250     flags->tzbyref   = (num >> 4) & 1;
251     flags->mattach   = (num >> 5) & 1;
252     flags->shared    = (num >> 6) & 1;
253 }
254 
_comp_flags_to_num(struct comp_flags * flags)255 static unsigned _comp_flags_to_num(struct comp_flags *flags)
256 {
257    return (flags->recurring & 1)
258        + ((flags->transp    & 1) << 1)
259        + ((flags->status    & 3) << 2)
260        + ((flags->tzbyref   & 1) << 4)
261        + ((flags->mattach   & 1) << 5)
262        + ((flags->shared    & 1) << 6);
263 }
264 
265 #define CMD_READFIELDS                                                  \
266     "SELECT rowid, creationdate, mailbox, resource, imap_uid,"          \
267     "  lock_token, lock_owner, lock_ownerid, lock_expire,"              \
268     "  comp_type, ical_uid, organizer, dtstart, dtend,"                 \
269     "  comp_flags, sched_tag, alive, modseq, createdmodseq,"            \
270     "  NULL, NULL"                                                      \
271     " FROM ical_objs"                                                   \
272 
273 #define CMD_READFIELDS_JMAP                                             \
274     "SELECT ical_objs.rowid, creationdate, mailbox, resource, imap_uid,"\
275     "  lock_token, lock_owner, lock_ownerid, lock_expire,"              \
276     "  comp_type, ical_uid, organizer, dtstart, dtend,"                 \
277     "  comp_flags, sched_tag, alive, modseq, createdmodseq,"            \
278     "  jmapversion, jmapdata"                                           \
279     " FROM ical_objs LEFT JOIN ical_jmapcache"                          \
280     " ON (ical_objs.rowid = ical_jmapcache.rowid AND ical_jmapcache.userid = :asuserid)"
281 
read_cb(sqlite3_stmt * stmt,void * rock)282 static int read_cb(sqlite3_stmt *stmt, void *rock)
283 {
284     struct read_rock *rrock = (struct read_rock *) rock;
285     struct caldav_db *db = rrock->db;
286     struct caldav_data *cdata = rrock->cdata;
287     int r = 0;
288 
289     memset(cdata, 0, sizeof(struct caldav_data));
290 
291     cdata->dav.alive = sqlite3_column_int(stmt, 16);
292     cdata->dav.modseq = sqlite3_column_int64(stmt, 17);
293     cdata->dav.createdmodseq = sqlite3_column_int64(stmt, 18);
294     if (!(rrock->flags & RROCK_FLAG_TOMBSTONES) && !cdata->dav.alive)
295         return 0;
296 
297     cdata->dav.rowid = sqlite3_column_int(stmt, 0);
298     cdata->dav.creationdate = sqlite3_column_int(stmt, 1);
299     cdata->dav.imap_uid = sqlite3_column_int(stmt, 4);
300     cdata->dav.lock_expire = sqlite3_column_int(stmt, 8);
301     cdata->comp_type = sqlite3_column_int(stmt, 9);
302     _num_to_comp_flags(&cdata->comp_flags, sqlite3_column_int(stmt, 14));
303     cdata->jmapversion = sqlite3_column_int(stmt, 19);
304 
305     if (rrock->cb) {
306         /* We can use the column data directly for the callback */
307         cdata->dav.mailbox = (const char *) sqlite3_column_text(stmt, 2);
308         cdata->dav.resource = (const char *) sqlite3_column_text(stmt, 3);
309         cdata->dav.lock_token = (const char *) sqlite3_column_text(stmt, 5);
310         cdata->dav.lock_owner = (const char *) sqlite3_column_text(stmt, 6);
311         cdata->dav.lock_ownerid = (const char *) sqlite3_column_text(stmt, 7);
312         cdata->ical_uid = (const char *) sqlite3_column_text(stmt, 10);
313         cdata->organizer = (const char *) sqlite3_column_text(stmt, 11);
314         cdata->dtstart = (const char *) sqlite3_column_text(stmt, 12);
315         cdata->dtend = (const char *) sqlite3_column_text(stmt, 13);
316         cdata->sched_tag = (const char *) sqlite3_column_text(stmt, 15);
317         cdata->jmapdata = (const char *) sqlite3_column_text(stmt, 20);
318         r = rrock->cb(rrock->rock, cdata);
319     }
320     else {
321         /* For single row SELECTs like caldav_read(),
322          * we need to make a copy of the column data before
323          * it gets flushed by sqlite3_step() or sqlite3_reset() */
324         cdata->dav.mailbox =
325             column_text_to_buf((const char *) sqlite3_column_text(stmt, 2),
326                                &db->mailbox);
327         cdata->dav.resource =
328             column_text_to_buf((const char *) sqlite3_column_text(stmt, 3),
329                                &db->resource);
330         cdata->dav.lock_token =
331             column_text_to_buf((const char *) sqlite3_column_text(stmt, 5),
332                                &db->lock_token);
333         cdata->dav.lock_owner =
334             column_text_to_buf((const char *) sqlite3_column_text(stmt, 6),
335                                &db->lock_owner);
336         cdata->dav.lock_ownerid =
337             column_text_to_buf((const char *) sqlite3_column_text(stmt, 7),
338                                &db->lock_ownerid);
339         cdata->ical_uid =
340             column_text_to_buf((const char *) sqlite3_column_text(stmt, 10),
341                                &db->ical_uid);
342         cdata->organizer =
343             column_text_to_buf((const char *) sqlite3_column_text(stmt, 11),
344                                &db->organizer);
345         cdata->dtstart =
346             column_text_to_buf((const char *) sqlite3_column_text(stmt, 12),
347                                &db->dtstart);
348         cdata->dtend =
349             column_text_to_buf((const char *) sqlite3_column_text(stmt, 13),
350                                &db->dtend);
351         cdata->sched_tag =
352             column_text_to_buf((const char *) sqlite3_column_text(stmt, 15),
353                                &db->sched_tag);
354         cdata->jmapdata =
355             column_text_to_buf((const char *) sqlite3_column_text(stmt, 20),
356                                &db->jmapdata);
357     }
358 
359     return r;
360 }
361 
362 
363 #define CMD_SELRSRC CMD_READFIELDS \
364     " WHERE mailbox = :mailbox AND resource = :resource;"
365 
caldav_lookup_resource(struct caldav_db * caldavdb,const char * mailbox,const char * resource,struct caldav_data ** result,int tombstones)366 EXPORTED int caldav_lookup_resource(struct caldav_db *caldavdb,
367                            const char *mailbox, const char *resource,
368                            struct caldav_data **result,
369                            int tombstones)
370 {
371     struct sqldb_bindval bval[] = {
372         { ":mailbox",  SQLITE_TEXT, { .s = mailbox       } },
373         { ":resource", SQLITE_TEXT, { .s = resource      } },
374         { NULL,        SQLITE_NULL, { .s = NULL          } } };
375     static struct caldav_data cdata;
376     struct read_rock rrock = { caldavdb, &cdata, tombstones, NULL, NULL };
377     int r;
378 
379     *result = memset(&cdata, 0, sizeof(struct caldav_data));
380 
381     r = sqldb_exec(caldavdb->db, CMD_SELRSRC, bval, &read_cb, &rrock);
382     if (!r && !cdata.dav.rowid) r = CYRUSDB_NOTFOUND;
383 
384     /* always add the mailbox and resource, so error responses don't
385      * crash out */
386     cdata.dav.mailbox = mailbox;
387     cdata.dav.resource = resource;
388 
389     return r;
390 }
391 
392 #define CMD_SELIMAPUID CMD_READFIELDS \
393     " WHERE mailbox = :mailbox AND imap_uid = :imap_uid;"
394 
caldav_lookup_imapuid(struct caldav_db * caldavdb,const char * mailbox,int imap_uid,struct caldav_data ** result,int tombstones)395 EXPORTED int caldav_lookup_imapuid(struct caldav_db *caldavdb,
396                            const char *mailbox, int imap_uid,
397                            struct caldav_data **result,
398                            int tombstones)
399 {
400     struct sqldb_bindval bval[] = {
401         { ":mailbox",  SQLITE_TEXT,    { .s = mailbox       } },
402         { ":imap_uid", SQLITE_INTEGER, { .i = imap_uid      } },
403         { NULL,        SQLITE_NULL,    { .s = NULL          } } };
404     static struct caldav_data cdata;
405     struct read_rock rrock = { caldavdb, &cdata, tombstones, NULL, NULL };
406     int r;
407 
408     *result = memset(&cdata, 0, sizeof(struct caldav_data));
409 
410     r = sqldb_exec(caldavdb->db, CMD_SELIMAPUID, bval, &read_cb, &rrock);
411     if (!r && !cdata.dav.rowid) r = CYRUSDB_NOTFOUND;
412 
413     cdata.dav.mailbox = mailbox;
414     cdata.dav.imap_uid = imap_uid;
415 
416     return r;
417 }
418 
419 
420 #define CMD_SELUID CMD_READFIELDS \
421     " WHERE ical_uid = :ical_uid AND mailbox != :inbox AND alive = 1;"
422 
caldav_lookup_uid(struct caldav_db * caldavdb,const char * ical_uid,struct caldav_data ** result)423 EXPORTED int caldav_lookup_uid(struct caldav_db *caldavdb, const char *ical_uid,
424                                struct caldav_data **result)
425 {
426     struct sqldb_bindval bval[] = {
427         { ":ical_uid", SQLITE_TEXT, { .s = ical_uid              } },
428         { ":inbox",    SQLITE_TEXT, { .s = caldavdb->sched_inbox } },
429         { NULL,        SQLITE_NULL, { .s = NULL                  } } };
430     static struct caldav_data cdata;
431     struct read_rock rrock = { caldavdb, &cdata, 0, NULL, NULL };
432     int r;
433 
434     /* XXX - ability to pass through the tombstones flag */
435 
436     *result = memset(&cdata, 0, sizeof(struct caldav_data));
437 
438     r = sqldb_exec(caldavdb->db, CMD_SELUID, bval, &read_cb, &rrock);
439     if (!r && !cdata.dav.rowid) r = CYRUSDB_NOTFOUND;
440 
441     return r;
442 }
443 
444 
445 #define CMD_SELMBOX CMD_READFIELDS \
446     " WHERE mailbox = :mailbox AND alive = 1;"
447 
448 #define CMD_SELALIVE CMD_READFIELDS \
449     " WHERE alive = 1;"
450 
caldav_foreach(struct caldav_db * caldavdb,const char * mailbox,caldav_cb_t * cb,void * rock)451 EXPORTED int caldav_foreach(struct caldav_db *caldavdb, const char *mailbox,
452                             caldav_cb_t *cb, void *rock)
453 {
454     struct sqldb_bindval bval[] = {
455         { ":mailbox", SQLITE_TEXT, { .s = mailbox } },
456         { NULL,       SQLITE_NULL, { .s = NULL    } } };
457     struct caldav_data cdata;
458     struct read_rock rrock = { caldavdb, &cdata, 0, cb, rock };
459 
460     /* XXX - tombstones */
461 
462     if (mailbox) {
463         return sqldb_exec(caldavdb->db, CMD_SELMBOX, bval, &read_cb, &rrock);
464     } else {
465         return sqldb_exec(caldavdb->db, CMD_SELALIVE, bval, &read_cb, &rrock);
466     }
467 }
468 
469 #define CMD_SELRANGE_MBOX CMD_READFIELDS \
470     " WHERE dtend > :after AND dtstart < :before " \
471     " AND mailbox = :mailbox AND alive = 1 "
472 
473 #define CMD_SELRANGE CMD_READFIELDS \
474     " WHERE dtend > :after AND dtstart < :before " \
475     " AND alive = 1 "
476 
caldav_foreach_timerange(struct caldav_db * caldavdb,const char * mailbox,time_t after,time_t before,enum caldav_sort * sort,size_t nsort,caldav_cb_t * cb,void * rock)477 EXPORTED int caldav_foreach_timerange(struct caldav_db *caldavdb,
478                                       const char *mailbox,
479                                       time_t after, time_t before,
480                                       enum caldav_sort* sort, size_t nsort,
481                                       caldav_cb_t *cb, void *rock)
482 {
483     struct sqldb_bindval bval[] = {
484         { ":after",     SQLITE_TEXT, { .s = NULL    } },
485         { ":before",   SQLITE_TEXT, { .s = NULL    } },
486         { ":mailbox", SQLITE_TEXT, { .s = mailbox } },
487         { NULL,       SQLITE_NULL, { .s = NULL    } } };
488     struct caldav_data cdata;
489     struct read_rock rrock = { caldavdb, &cdata, 0, cb, rock };
490     icaltimetype dtafter, dtbefore;
491     icaltimezone *utc = icaltimezone_get_utc_timezone();
492 
493     dtafter = icaltime_from_timet_with_zone(after, 0, utc);
494     dtbefore= icaltime_from_timet_with_zone(before, 0, utc);
495 
496     bval[0].val.s = icaltime_as_ical_string(dtafter);
497     bval[1].val.s = icaltime_as_ical_string(dtbefore);
498 
499     /* XXX - if 'before' defines the zero second of a day, a full-day
500      * event starting on that day matches. That's not entirely correct,
501      * since 'before' is defined to be exclusive. */
502 
503     /* XXX - tombstones */
504 
505     struct buf stmt = BUF_INITIALIZER;
506     buf_setcstr(&stmt, mailbox ? CMD_SELRANGE_MBOX : CMD_SELRANGE);
507     if (nsort) {
508         buf_appendcstr(&stmt, " ORDER BY ");
509         size_t i;
510         for (i = 0; i < nsort; i++) {
511             if (i) buf_appendcstr(&stmt, ", ");
512             switch (sort[i] & ~CAL_SORT_DESC) {
513                 case CAL_SORT_UID:
514                     buf_appendcstr(&stmt, "ical_uid");
515                     break;
516                 case CAL_SORT_START:
517                     buf_appendcstr(&stmt, "dtstart");
518                     break;
519                 case CAL_SORT_MAILBOX:
520                     buf_appendcstr(&stmt, "mailbox");
521                     break;
522                 default:
523                     continue;
524             }
525             buf_appendcstr(&stmt, sort[i] & CAL_SORT_DESC ? " DESC" : " ASC");
526         }
527     }
528     buf_putc(&stmt, ';');
529 
530     int r = sqldb_exec(caldavdb->db, buf_cstring(&stmt), bval, &read_cb, &rrock);
531     buf_free(&stmt);
532     return r;
533 }
534 
535 
536 #define CMD_INSERT                                                      \
537     "INSERT INTO ical_objs ("                                           \
538     "  alive, mailbox, resource, creationdate, imap_uid, modseq,"       \
539     "  createdmodseq,"                                                  \
540     "  lock_token, lock_owner, lock_ownerid, lock_expire,"              \
541     "  comp_type, ical_uid, organizer, dtstart, dtend,"                 \
542     "  comp_flags, sched_tag )"                                         \
543     " VALUES ("                                                         \
544     "  :alive, :mailbox, :resource, :creationdate, :imap_uid, :modseq," \
545     "  :createdmodseq,"                                                 \
546     "  :lock_token, :lock_owner, :lock_ownerid, :lock_expire,"          \
547     "  :comp_type, :ical_uid, :organizer, :dtstart, :dtend,"            \
548     "  :comp_flags, :sched_tag );"
549 
550 #define CMD_UPDATE                      \
551     "UPDATE ical_objs SET"              \
552     "  alive        = :alive,"          \
553     "  creationdate = :creationdate,"   \
554     "  imap_uid     = :imap_uid,"       \
555     "  modseq       = :modseq,"         \
556     "  createdmodseq = :createdmodseq,"  \
557     "  lock_token   = :lock_token,"     \
558     "  lock_owner   = :lock_owner,"     \
559     "  lock_ownerid = :lock_ownerid,"   \
560     "  lock_expire  = :lock_expire,"    \
561     "  comp_type    = :comp_type,"      \
562     "  ical_uid     = :ical_uid,"       \
563     "  organizer    = :organizer,"      \
564     "  dtstart      = :dtstart,"        \
565     "  dtend        = :dtend,"          \
566     "  comp_flags   = :comp_flags,"     \
567     "  sched_tag    = :sched_tag"       \
568     " WHERE rowid = :rowid;"
569 
570 #define CMD_DELETE_JMAPCACHE "DELETE FROM ical_jmapcache WHERE rowid = :rowid"
571 
caldav_write(struct caldav_db * caldavdb,struct caldav_data * cdata)572 EXPORTED int caldav_write(struct caldav_db *caldavdb, struct caldav_data *cdata)
573 {
574     unsigned comp_flags = _comp_flags_to_num(&cdata->comp_flags);
575     struct sqldb_bindval bval[] = {
576         { ":rowid",        SQLITE_INTEGER, { .i = cdata->dav.rowid        } },
577         { ":alive",        SQLITE_INTEGER, { .i = cdata->dav.alive        } },
578         { ":mailbox",      SQLITE_TEXT,    { .s = cdata->dav.mailbox      } },
579         { ":resource",     SQLITE_TEXT,    { .s = cdata->dav.resource     } },
580         { ":creationdate", SQLITE_INTEGER, { .i = cdata->dav.creationdate } },
581         { ":imap_uid",     SQLITE_INTEGER, { .i = cdata->dav.imap_uid     } },
582         { ":modseq",       SQLITE_INTEGER, { .i = cdata->dav.modseq       } },
583         { ":createdmodseq", SQLITE_INTEGER, { .i = cdata->dav.createdmodseq } },
584         { ":lock_token",   SQLITE_TEXT,    { .s = cdata->dav.lock_token   } },
585         { ":lock_owner",   SQLITE_TEXT,    { .s = cdata->dav.lock_owner   } },
586         { ":lock_ownerid", SQLITE_TEXT,    { .s = cdata->dav.lock_ownerid } },
587         { ":lock_expire",  SQLITE_INTEGER, { .i = cdata->dav.lock_expire  } },
588         { ":comp_type",    SQLITE_INTEGER, { .i = cdata->comp_type        } },
589         { ":ical_uid",     SQLITE_TEXT,    { .s = cdata->ical_uid         } },
590         { ":organizer",    SQLITE_TEXT,    { .s = cdata->organizer        } },
591         { ":dtstart",      SQLITE_TEXT,    { .s = cdata->dtstart          } },
592         { ":dtend",        SQLITE_TEXT,    { .s = cdata->dtend            } },
593         { ":sched_tag",    SQLITE_TEXT,    { .s = cdata->sched_tag        } },
594         { ":comp_flags",   SQLITE_INTEGER, { .i = comp_flags              } },
595         { NULL,            SQLITE_NULL,    { .s = NULL                    } } };
596 
597     if (cdata->dav.rowid) {
598         int r = sqldb_exec(caldavdb->db, CMD_DELETE_JMAPCACHE, bval, NULL, NULL);
599         if (r) return r;
600         r = sqldb_exec(caldavdb->db, CMD_UPDATE, bval, NULL, NULL);
601         if (r) return r;
602     }
603     else {
604         int r = sqldb_exec(caldavdb->db, CMD_INSERT, bval, NULL, NULL);
605         if (r) return r;
606         cdata->dav.rowid = sqldb_lastid(caldavdb->db);
607     }
608 
609     return 0;
610 }
611 
612 
613 #define CMD_DELETE "DELETE FROM ical_objs WHERE rowid = :rowid;"
614 
caldav_delete(struct caldav_db * caldavdb,unsigned rowid)615 EXPORTED int caldav_delete(struct caldav_db *caldavdb, unsigned rowid)
616 {
617     struct sqldb_bindval bval[] = {
618         { ":rowid", SQLITE_INTEGER, { .i = rowid } },
619         { NULL,     SQLITE_NULL,    { .s = NULL  } } };
620     int r;
621 
622     r = sqldb_exec(caldavdb->db, CMD_DELETE, bval, NULL, NULL);
623 
624     return r;
625 }
626 
627 
628 #define CMD_DELMBOX "DELETE FROM ical_objs WHERE mailbox = :mailbox;"
629 
caldav_delmbox(struct caldav_db * caldavdb,const char * mailbox)630 EXPORTED int caldav_delmbox(struct caldav_db *caldavdb, const char *mailbox)
631 {
632     struct sqldb_bindval bval[] = {
633         { ":mailbox", SQLITE_TEXT, { .s = mailbox } },
634         { NULL,       SQLITE_NULL, { .s = NULL    } } };
635     int r;
636 
637     r = sqldb_exec(caldavdb->db, CMD_DELMBOX, bval, NULL, NULL);
638 
639     return r;
640 }
641 
caldav_get_updates(struct caldav_db * caldavdb,modseq_t oldmodseq,const char * mboxname,int kind,int limit,int (* cb)(void * rock,struct caldav_data * cdata),void * rock)642 EXPORTED int caldav_get_updates(struct caldav_db *caldavdb,
643                                 modseq_t oldmodseq, const char *mboxname,
644                                 int kind, int limit,
645                                 int (*cb)(void *rock, struct caldav_data *cdata),
646                                 void *rock)
647 {
648     struct sqldb_bindval bval[] = {
649         { ":mailbox",      SQLITE_TEXT,    { .s = mboxname  } },
650         { ":modseq",       SQLITE_INTEGER, { .i = oldmodseq } },
651         { ":comp_type",    SQLITE_INTEGER, { .i = kind      } },
652         /* SQLite interprets a negative limit as unbounded. */
653         { ":limit",        SQLITE_INTEGER, { .i = limit > 0 ? limit : -1 } },
654         { NULL,            SQLITE_NULL,    { .s = NULL      } }
655     };
656     static struct caldav_data cdata;
657     struct read_rock rrock =
658         { caldavdb, &cdata, RROCK_FLAG_TOMBSTONES, cb, rock };
659     struct buf sqlbuf = BUF_INITIALIZER;
660     int r;
661 
662     buf_setcstr(&sqlbuf, CMD_READFIELDS " WHERE");
663     if (mboxname) buf_appendcstr(&sqlbuf, " mailbox = :mailbox AND");
664     if (kind >= 0) {
665         /* Use a negative value to signal that we accept ALL components types */
666         buf_appendcstr(&sqlbuf, " comp_type = :comp_type AND");
667     }
668     if (!oldmodseq) buf_appendcstr(&sqlbuf, " alive = 1 AND");
669     buf_appendcstr(&sqlbuf, " modseq > :modseq ORDER BY modseq LIMIT :limit;");
670 
671     r = sqldb_exec(caldavdb->db, buf_cstring(&sqlbuf), bval, &read_cb, &rrock);
672     buf_free(&sqlbuf);
673 
674     if (r) {
675         syslog(LOG_ERR, "caldav error %s", error_message(r));
676     }
677     return r;
678 }
679 
680 
check_mattach_cb(icalcomponent * comp,void * rock)681 static void check_mattach_cb(icalcomponent *comp, void *rock)
682 {
683     int *mattach = (int *) rock;
684 
685     /* Check for managed attachment */
686     if (!*mattach) {
687         icalproperty *prop;
688 
689         for (prop = icalcomponent_get_first_property(comp, ICAL_ATTACH_PROPERTY);
690              prop;
691              prop = icalcomponent_get_next_property(comp, ICAL_ATTACH_PROPERTY)) {
692 
693             if (icalproperty_get_managedid_parameter(prop)) *mattach = 1;
694         }
695     }
696 }
697 
caldav_writeentry(struct caldav_db * caldavdb,struct caldav_data * cdata,icalcomponent * ical)698 EXPORTED int caldav_writeentry(struct caldav_db *caldavdb, struct caldav_data *cdata,
699                                icalcomponent *ical)
700 {
701     icalcomponent *comp = icalcomponent_get_first_real_component(ical);
702     icalcomponent_kind kind;
703     icalproperty *prop;
704     unsigned mykind = 0, recurring = 0, transp = 0, status = 0, mattach = 0;
705     struct icalperiodtype span;
706 
707     /* Get iCalendar UID */
708     cdata->ical_uid = icalcomponent_get_uid(comp);
709 
710     /* Get component type and optional status */
711     kind = icalcomponent_isa(comp);
712     switch (kind) {
713     case ICAL_VEVENT_COMPONENT:
714         mykind = CAL_COMP_VEVENT;
715         switch (icalcomponent_get_status(comp)) {
716         case ICAL_STATUS_CANCELLED: status = CAL_STATUS_CANCELED; break;
717         case ICAL_STATUS_TENTATIVE: status = CAL_STATUS_TENTATIVE; break;
718         default: status = CAL_STATUS_BUSY; break;
719         }
720         break;
721     case ICAL_VTODO_COMPONENT: mykind = CAL_COMP_VTODO; break;
722     case ICAL_VJOURNAL_COMPONENT: mykind = CAL_COMP_VJOURNAL; break;
723     case ICAL_VFREEBUSY_COMPONENT: mykind = CAL_COMP_VFREEBUSY; break;
724     case ICAL_VAVAILABILITY_COMPONENT: mykind = CAL_COMP_VAVAILABILITY; break;
725     case ICAL_VPOLL_COMPONENT: mykind = CAL_COMP_VPOLL; break;
726     default: break;
727     }
728     cdata->comp_type = mykind;
729     cdata->comp_flags.status = status;
730 
731     cdata->organizer = NULL;
732 
733     /* Get organizer */
734     prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
735     if (prop) {
736         cdata->organizer = icalproperty_get_organizer(prop);
737         if (cdata->organizer && !strncasecmp(cdata->organizer, "mailto:", 7))
738             cdata->organizer += 7;
739     }
740     /* maybe it's only on a sub event */
741     icalcomponent *nextcomp;
742     while (!cdata->organizer &&
743            (nextcomp = icalcomponent_get_next_component(ical, kind))) {
744         prop = icalcomponent_get_first_property(nextcomp, ICAL_ORGANIZER_PROPERTY);
745         if (prop) {
746             cdata->organizer = icalproperty_get_organizer(prop);
747             if (cdata->organizer && !strncasecmp(cdata->organizer, "mailto:", 7))
748                 cdata->organizer += 7;
749         }
750     }
751 
752     /* Get transparency */
753     prop = icalcomponent_get_first_property(comp, ICAL_TRANSP_PROPERTY);
754     if (prop) {
755         icalvalue *transp_val = icalproperty_get_value(prop);
756 
757         switch (icalvalue_get_transp(transp_val)) {
758         case ICAL_TRANSP_TRANSPARENT:
759         case ICAL_TRANSP_TRANSPARENTNOCONFLICT:
760             transp = 1;
761             break;
762 
763         default:
764             transp = 0;
765             break;
766         }
767     }
768     cdata->comp_flags.transp = transp;
769 
770     /* Get span of component set and check for managed attachments */
771     span = icalrecurrenceset_get_utc_timespan(ical, kind, NULL, &recurring,
772                                               &check_mattach_cb, &mattach);
773 
774     cdata->dtstart = icaltime_as_ical_string(span.start);
775     cdata->dtend = icaltime_as_ical_string(span.end);
776     cdata->comp_flags.recurring = recurring;
777     cdata->comp_flags.mattach = mattach;
778 
779     return caldav_write(caldavdb, cdata);
780 }
781 
782 
caldav_mboxname(const char * userid,const char * name)783 EXPORTED char *caldav_mboxname(const char *userid, const char *name)
784 {
785     struct buf boxbuf = BUF_INITIALIZER;
786     char *res = NULL;
787 
788     buf_setcstr(&boxbuf, config_getstring(IMAPOPT_CALENDARPREFIX));
789 
790     if (name) {
791         size_t len = strcspn(name, "/");
792         buf_putc(&boxbuf, '.');
793         buf_appendmap(&boxbuf, name, len);
794     }
795 
796     res = mboxname_user_mbox(userid, buf_cstring(&boxbuf));
797 
798     buf_free(&boxbuf);
799 
800     return res;
801 }
802 
caldav_get_events(struct caldav_db * caldavdb,const char * asuserid,const char * mailbox,const char * ical_uid,caldav_cb_t * cb,void * rock)803 EXPORTED int caldav_get_events(struct caldav_db *caldavdb, const char *asuserid,
804                                const char *mailbox, const char *ical_uid,
805                                caldav_cb_t *cb, void *rock)
806 {
807     struct sqldb_bindval bval[] = {
808         { ":mailbox",  SQLITE_TEXT, { .s = mailbox } },
809         { ":ical_uid", SQLITE_TEXT, { .s = ical_uid } },
810         { ":asuserid", SQLITE_TEXT, { .s = asuserid } },
811         { NULL,        SQLITE_NULL, { .s = NULL    } } };
812     struct caldav_data cdata;
813     struct read_rock rrock = { caldavdb, &cdata, 0, cb, rock };
814     struct buf sqlbuf = BUF_INITIALIZER;
815 
816     buf_setcstr(&sqlbuf, CMD_READFIELDS_JMAP);
817     buf_appendcstr(&sqlbuf, " WHERE alive = 1");
818     if (mailbox)
819         buf_appendcstr(&sqlbuf, " AND mailbox = :mailbox");
820     if (ical_uid)
821         buf_appendcstr(&sqlbuf, " AND ical_uid = :ical_uid");
822     buf_appendcstr(&sqlbuf, " ORDER BY mailbox, imap_uid;");
823 
824     /* XXX - tombstones */
825 
826     int r = sqldb_exec(caldavdb->db, buf_cstring(&sqlbuf), bval, &read_cb, &rrock);
827     buf_free(&sqlbuf);
828 
829     if (r) {
830         syslog(LOG_ERR, "caldav error %s", error_message(r));
831         /* XXX - free memory */
832     }
833 
834     return r;
835 }
836 
837 #define CMD_DELETE_JMAPCACHE_USER "DELETE FROM ical_jmapcache WHERE rowid = :rowid AND userid = :userid"
838 #define CMD_INSERT_JMAPCACHE_USER                                           \
839     "INSERT INTO ical_jmapcache ( rowid, userid, jmapversion, jmapdata )"   \
840     " VALUES ( :rowid, :userid, :jmapversion, :jmapdata );"
841 
caldav_write_jmapcache(struct caldav_db * caldavdb,int rowid,const char * userid,int version,const char * data)842 EXPORTED int caldav_write_jmapcache(struct caldav_db *caldavdb, int rowid, const char *userid, int version, const char *data)
843 {
844     struct sqldb_bindval bval[] = {
845         { ":rowid",        SQLITE_INTEGER, { .i = rowid  } },
846         { ":userid",       SQLITE_TEXT,    { .s = userid } },
847         { ":jmapversion",  SQLITE_INTEGER, { .i = version } },
848         { ":jmapdata",     SQLITE_TEXT,    { .s = data   } },
849         { NULL,            SQLITE_NULL,    { .s = NULL   } } };
850     int r;
851 
852     /* clean up existing records if any */
853     r = sqldb_exec(caldavdb->db, CMD_DELETE_JMAPCACHE_USER, bval, NULL, NULL);
854     if (r) return r;
855 
856     /* insert the cache record */
857     return sqldb_exec(caldavdb->db, CMD_INSERT_JMAPCACHE_USER, bval, NULL, NULL);
858 }
859 
860 struct shareacls_rock {
861     const char *userid;
862     char *principalname;
863     char *principalacl;
864     char *newprincipalacl;
865     char *outboxname;
866     char *outboxacl;
867     char *newoutboxacl;
868     hash_table user_access;
869 };
870 
871 #define CALSHARE_WANTSCHED 1
872 #define CALSHARE_HAVESCHED 2
873 #define CALSHARE_WANTPRIN 4
874 #define CALSHARE_HAVEPRIN 8
875 
_add_shareacls(const mbentry_t * mbentry,void * rock)876 static int _add_shareacls(const mbentry_t *mbentry, void *rock)
877 {
878     struct shareacls_rock *share = rock;
879 
880     char *acl = xstrdup(mbentry->acl);
881 
882     int isprincipal = !strcmp(mbentry->name, share->principalname);
883     int isoutbox = !strcmp(mbentry->name, share->outboxname);
884 
885     if (isprincipal) {
886         share->principalacl = xstrdup(acl);
887         share->newprincipalacl = xstrdup(acl);
888     }
889 
890     if (isoutbox) {
891         share->outboxacl = xstrdup(acl);
892         share->newoutboxacl = xstrdup(acl);
893     }
894 
895     char *userid;
896     char *nextid = NULL;
897     for (userid = acl; userid; userid = nextid) {
898         char *rightstr;
899         int access;
900 
901         rightstr = strchr(userid, '\t');
902         if (!rightstr) break;
903         *rightstr++ = '\0';
904 
905         nextid = strchr(rightstr, '\t');
906         if (!nextid) break;
907         *nextid++ = '\0';
908 
909         /* skip system users and owner */
910         if (is_system_user(userid)) continue;
911         if (!strcmp(userid, share->userid)) continue;
912 
913         cyrus_acl_strtomask(rightstr, &access);
914 
915         uintptr_t have = (uintptr_t)hash_lookup(userid, &share->user_access);
916         uintptr_t set = have;
917 
918         // if it's the principal, we have each user with principal read access
919         if (isprincipal) {
920             if ((access & DACL_READ) == DACL_READ)
921                 set |= CALSHARE_HAVEPRIN;
922         }
923         // if it's the Outbox, we have each user with reply ability
924         else if (isoutbox) {
925             if ((access & (DACL_INVITE|DACL_REPLY)) == (DACL_INVITE|DACL_REPLY))
926                 set |= CALSHARE_HAVESCHED;
927         }
928         // and if they can see anything else, then we NEED the above!
929         else {
930             if (access & ACL_READ)
931                 set |= CALSHARE_WANTPRIN;
932             if (access & ACL_INSERT)
933                 set |= CALSHARE_WANTSCHED;
934         }
935 
936         if (set != have) hash_insert(userid, (void *)set, &share->user_access);
937     }
938 
939     free(acl);
940     return 0;
941 }
942 
_update_acls(const char * userid,void * data,void * rock)943 static void _update_acls(const char *userid, void *data, void *rock)
944 {
945     struct shareacls_rock *share = rock;
946     uintptr_t aclstatus = (uintptr_t)data;
947 
948     if ((aclstatus & CALSHARE_WANTSCHED) && !(aclstatus & CALSHARE_HAVESCHED)) {
949         cyrus_acl_set(&share->newoutboxacl, userid, ACL_MODE_ADD, (DACL_INVITE|DACL_REPLY), NULL, NULL);
950     }
951 
952     if (!(aclstatus & CALSHARE_WANTSCHED) && (aclstatus & CALSHARE_HAVESCHED)) {
953         cyrus_acl_set(&share->newoutboxacl, userid, ACL_MODE_REMOVE, (DACL_INVITE|DACL_REPLY), NULL, NULL);
954     }
955 
956     if ((aclstatus & CALSHARE_WANTPRIN) && !(aclstatus & CALSHARE_HAVEPRIN)) {
957         cyrus_acl_set(&share->newprincipalacl, userid, ACL_MODE_ADD, DACL_READ, NULL, NULL);
958     }
959 
960     if (!(aclstatus & CALSHARE_WANTPRIN) && (aclstatus & CALSHARE_HAVEPRIN)) {
961         cyrus_acl_set(&share->newprincipalacl, userid, ACL_MODE_REMOVE, DACL_READ, NULL, NULL);
962     }
963 }
964 
965 /* update the share acls.  We do this by:
966  * 1) iterating all the calendars for this user, looking at all the ACLs and
967  *    tracking for each user mentioned, whether they have or need principal
968  *    access or scheduling access.
969  * 2) when we see the inbox and outbox, clone the ACLs.
970  * 3) iterate all seen users, and decide whether we need to change the ACLs
971  *    for either of those mailboxes.
972  */
caldav_update_shareacls(const char * userid)973 EXPORTED int caldav_update_shareacls(const char *userid)
974 {
975     struct shareacls_rock rock = {
976         userid,
977         NULL, NULL, NULL,
978         NULL, NULL, NULL,
979         HASH_TABLE_INITIALIZER
980     };
981     construct_hash_table(&rock.user_access, 10, 0);
982     rock.principalname = caldav_mboxname(userid, NULL);
983     rock.outboxname = caldav_mboxname(userid, SCHED_OUTBOX);
984 
985     // find out what the values should be
986     int r = mboxlist_mboxtree(rock.principalname, _add_shareacls, &rock, 0);
987 
988     // did we find the ACLs?  If not, bail now!
989     if (!rock.principalacl || !rock.outboxacl) {
990         r = IMAP_MAILBOX_NONEXISTENT;
991         goto done;
992     }
993 
994     // change the ACLs as required
995     hash_enumerate(&rock.user_access, _update_acls, &rock);
996 
997     if (strcmp(rock.principalacl, rock.newprincipalacl)) {
998         r = mboxlist_updateacl_raw(rock.principalname, rock.newprincipalacl);
999         if (r) goto done;
1000     }
1001 
1002     if (strcmp(rock.outboxacl, rock.newoutboxacl)) {
1003         r = mboxlist_updateacl_raw(rock.outboxname, rock.newoutboxacl);
1004         if (r) goto done;
1005     }
1006 
1007 done:
1008     free(rock.principalname);
1009     free(rock.principalacl);
1010     free(rock.newprincipalacl);
1011     free(rock.outboxname);
1012     free(rock.outboxacl);
1013     free(rock.newoutboxacl);
1014     free_hash_table(&rock.user_access, NULL);
1015 
1016     return r;
1017 }
1018 
1019 
caldav_comp_type_as_string(unsigned comp_type)1020 EXPORTED const char *caldav_comp_type_as_string(unsigned comp_type)
1021 {
1022     switch (comp_type) {
1023         /* "Real" components */
1024         case CAL_COMP_VEVENT:
1025             return "VEVENT";
1026         case CAL_COMP_VTODO:
1027             return "VTODO";
1028         case CAL_COMP_VJOURNAL:
1029             return "VJOURNAL";
1030         case CAL_COMP_VFREEBUSY:
1031             return "VFREEBUSY";
1032         case CAL_COMP_VAVAILABILITY:
1033             return "VAVAILABILITY";
1034         case CAL_COMP_VPOLL:
1035             return "VPOLL";
1036         /* Other components */
1037         case CAL_COMP_VALARM:
1038             return "VALARM";
1039         case CAL_COMP_VTIMEZONE:
1040             return "VTIMEZONE";
1041         case CAL_COMP_VCALENDAR:
1042             return "VCALENDAR";
1043         default:
1044             return NULL;
1045     }
1046 }
1047 
1048