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