1 /* http_caldav.c -- Routines for handling CalDAV collections in httpd
2  *
3  * Copyright (c) 1994-2011 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  * TODO:
45  *
46  *   - Make proxying more robust.  Currently depends on calendar collections
47  *     residing on same server as user's INBOX.  Doesn't handle global/shared
48  *     calendars.
49  *   - Support COPY/MOVE on collections
50  *   - Add more required properties?
51  *   - calendar-query REPORT (handle timezone, timezone-id)
52  *   - free-busy-query REPORT (check ACL and transp on all calendars)
53  *   - sync-collection REPORT - need to handle Depth infinity?
54  */
55 
56 #include <config.h>
57 
58 #include <syslog.h>
59 
60 #include <libical/ical.h>
61 #include <libxml/tree.h>
62 #include <libxml/uri.h>
63 #include <sys/types.h>
64 
65 #include "acl.h"
66 #include "append.h"
67 #include "caldav_db.h"
68 #include "charset.h"
69 #include "exitcodes.h"
70 #include "global.h"
71 #include "hash.h"
72 #include "httpd.h"
73 #include "http_caldav.h"
74 #include "http_caldav_sched.h"
75 #include "http_dav.h"
76 #include "http_proxy.h"
77 #include "index.h"
78 #include "ical_support.h"
79 #include "jcal.h"
80 #include "xcal.h"
81 #include "map.h"
82 #include "mailbox.h"
83 #include "mboxlist.h"
84 #include "message.h"
85 #include "message_guid.h"
86 #include "proxy.h"
87 #include "times.h"
88 #include "spool.h"
89 #include "strhash.h"
90 #include "stristr.h"
91 #include "tok.h"
92 #include "util.h"
93 #include "version.h"
94 #include "webdav_db.h"
95 #include "xmalloc.h"
96 #include "xml_support.h"
97 #include "xstrlcat.h"
98 #include "xstrlcpy.h"
99 #include "xstrnchr.h"
100 #include "zoneinfo_db.h"
101 
102 /* generated headers are not necessarily in current directory */
103 #include "imap/http_err.h"
104 #include "imap/imap_err.h"
105 
106 #define TZ_STRIP (1<<9)
107 
108 
109 #ifdef HAVE_RSCALE
110 #include <unicode/uversion.h>
111 
rscale_cmp(const void * a,const void * b)112 static int rscale_cmp(const void *a, const void *b)
113 {
114     /* Convert to uppercase since that's what we prefer to output */
115     return strcmp(ucase(*((char **) a)), ucase(*((char **) b)));
116 }
117 #endif /* HAVE_RSCALE */
118 
119 
120 static struct caldav_db *auth_caldavdb = NULL;
121 static time_t compile_time;
122 static struct buf ical_prodid_buf = BUF_INITIALIZER;
123 
124 unsigned config_allowsched = IMAP_ENUM_CALDAV_ALLOWSCHEDULING_OFF;
125 const char *ical_prodid = NULL;
126 icaltimezone *utc_zone = NULL;
127 struct strlist *cua_domains = NULL;
128 icalarray *rscale_calendars = NULL;
129 
130 struct partial_comp_t {
131     icalcomponent_kind kind;
132     arrayu64_t props;
133     struct partial_comp_t *sibling;
134     struct partial_comp_t *child;
135 };
136 
137 static struct partial_caldata_t {
138     unsigned expand : 1;
139     struct icalperiodtype range;
140     struct partial_comp_t *comp;
141 } partial_caldata;
142 
143 static int meth_options_cal(struct transaction_t *txn, void *params);
144 static int meth_get_head_cal(struct transaction_t *txn, void *params);
145 static int meth_get_head_fb(struct transaction_t *txn, void *params);
146 
147 static void my_caldav_init(struct buf *serverinfo);
148 static void my_caldav_auth(const char *userid);
149 static void my_caldav_reset(void);
150 static void my_caldav_shutdown(void);
151 
152 static int caldav_parse_path(const char *path,
153                              struct request_target_t *tgt, const char **errstr);
154 
155 static int caldav_check_precond(struct transaction_t *txn,
156                                 struct meth_params *params,
157                                 struct mailbox *mailbox, const void *data,
158                                 const char *etag, time_t lastmod);
159 
160 static int caldav_acl(struct transaction_t *txn, xmlNodePtr priv, int *rights);
161 static int caldav_copy(struct transaction_t *txn, void *obj,
162                        struct mailbox *dest_mbox, const char *dest_rsrc,
163                        void *destdb, unsigned flags);
164 static int caldav_delete_cal(struct transaction_t *txn,
165                              struct mailbox *mailbox,
166                              struct index_record *record, void *data);
167 static int caldav_get(struct transaction_t *txn, struct mailbox *mailbox,
168                       struct index_record *record, void *data, void **obj);
169 static int caldav_post(struct transaction_t *txn);
170 static int caldav_patch(struct transaction_t *txn, void *obj);
171 static int caldav_put(struct transaction_t *txn, void *obj,
172                       struct mailbox *mailbox, const char *resource,
173                       void *destdb, unsigned flags);
174 static int caldav_import(struct transaction_t *txn, void *obj,
175                          struct mailbox *mailbox, void *destdb,
176                          xmlNodePtr root, xmlNsPtr *ns, unsigned flags);
177 
178 static int propfind_getcontenttype(const xmlChar *name, xmlNsPtr ns,
179                                    struct propfind_ctx *fctx,
180                                    xmlNodePtr prop, xmlNodePtr resp,
181                                    struct propstat propstat[], void *rock);
182 static int propfind_restype(const xmlChar *name, xmlNsPtr ns,
183                             struct propfind_ctx *fctx,
184                             xmlNodePtr prop, xmlNodePtr resp,
185                             struct propstat propstat[], void *rock);
186 static int propfind_scheduser(const xmlChar *name, xmlNsPtr ns,
187                             struct propfind_ctx *fctx,
188                             xmlNodePtr prop, xmlNodePtr resp,
189                             struct propstat propstat[], void *rock);
190 static int propfind_caldata(const xmlChar *name, xmlNsPtr ns,
191                             struct propfind_ctx *fctx,
192                             xmlNodePtr prop, xmlNodePtr resp,
193                             struct propstat propstat[], void *rock);
194 static int propfind_calcompset(const xmlChar *name, xmlNsPtr ns,
195                                struct propfind_ctx *fctx,
196                                xmlNodePtr prop, xmlNodePtr resp,
197                                struct propstat propstat[], void *rock);
198 static int proppatch_calcompset(xmlNodePtr prop, unsigned set,
199                                 struct proppatch_ctx *pctx,
200                                 struct propstat propstat[], void *rock);
201 static int propfind_suppcaldata(const xmlChar *name, xmlNsPtr ns,
202                                 struct propfind_ctx *fctx,
203                                 xmlNodePtr prop, xmlNodePtr resp,
204                                 struct propstat propstat[], void *rock);
205 static int propfind_maxsize(const xmlChar *name, xmlNsPtr ns,
206                             struct propfind_ctx *fctx,
207                             xmlNodePtr prop, xmlNodePtr resp,
208                             struct propstat propstat[], void *rock);
209 static int propfind_minmaxdate(const xmlChar *name, xmlNsPtr ns,
210                                struct propfind_ctx *fctx,
211                                xmlNodePtr prop, xmlNodePtr resp,
212                                struct propstat propstat[], void *rock);
213 static int propfind_scheddefault(const xmlChar *name, xmlNsPtr ns,
214                                  struct propfind_ctx *fctx,
215                                  xmlNodePtr prop, xmlNodePtr resp,
216                                  struct propstat propstat[], void *rock);
217 static int propfind_schedtag(const xmlChar *name, xmlNsPtr ns,
218                              struct propfind_ctx *fctx,
219                              xmlNodePtr prop, xmlNodePtr resp,
220                              struct propstat propstat[], void *rock);
221 static int propfind_caltransp(const xmlChar *name, xmlNsPtr ns,
222                               struct propfind_ctx *fctx,
223                               xmlNodePtr prop, xmlNodePtr resp,
224                               struct propstat propstat[], void *rock);
225 static int proppatch_caltransp(xmlNodePtr prop, unsigned set,
226                                struct proppatch_ctx *pctx,
227                                struct propstat propstat[], void *rock);
228 static int propfind_timezone(const xmlChar *name, xmlNsPtr ns,
229                              struct propfind_ctx *fctx,
230                              xmlNodePtr prop, xmlNodePtr resp,
231                              struct propstat propstat[], void *rock);
232 static int proppatch_timezone(xmlNodePtr prop, unsigned set,
233                               struct proppatch_ctx *pctx,
234                               struct propstat propstat[], void *rock);
235 static int propfind_availability(const xmlChar *name, xmlNsPtr ns,
236                                  struct propfind_ctx *fctx,
237                                  xmlNodePtr prop, xmlNodePtr resp,
238                                  struct propstat propstat[], void *rock);
239 static int proppatch_availability(xmlNodePtr prop, unsigned set,
240                                   struct proppatch_ctx *pctx,
241                                   struct propstat propstat[], void *rock);
242 static int propfind_tzservset(const xmlChar *name, xmlNsPtr ns,
243                               struct propfind_ctx *fctx,
244                               xmlNodePtr prop, xmlNodePtr resp,
245                               struct propstat propstat[], void *rock);
246 static int propfind_tzid(const xmlChar *name, xmlNsPtr ns,
247                          struct propfind_ctx *fctx,
248                          xmlNodePtr prop, xmlNodePtr resp,
249                          struct propstat propstat[], void *rock);
250 static int proppatch_tzid(xmlNodePtr prop, unsigned set,
251                           struct proppatch_ctx *pctx,
252                           struct propstat propstat[], void *rock);
253 static int propfind_rscaleset(const xmlChar *name, xmlNsPtr ns,
254                               struct propfind_ctx *fctx,
255                               xmlNodePtr prop, xmlNodePtr resp,
256                               struct propstat propstat[], void *rock);
257 static int propfind_sharingmodes(const xmlChar *name, xmlNsPtr ns,
258                                  struct propfind_ctx *fctx,
259                                  xmlNodePtr prop, xmlNodePtr resp,
260                                  struct propstat propstat[], void *rock);
261 
262 static void strip_vtimezones(icalcomponent *ical);
263 
264 static int report_cal_query(struct transaction_t *txn,
265                             struct meth_params *rparams,
266                             xmlNodePtr inroot, struct propfind_ctx *fctx);
267 static int report_fb_query(struct transaction_t *txn,
268                            struct meth_params *rparams,
269                            xmlNodePtr inroot, struct propfind_ctx *fctx);
270 
271 static const char *begin_icalendar(struct buf *buf);
272 static void end_icalendar(struct buf *buf);
273 
274 #define ICALENDAR_CONTENT_TYPE "text/calendar; charset=utf-8"
275 
276 static struct mime_type_t caldav_mime_types[] = {
277     /* First item MUST be the default type and storage format */
278     { ICALENDAR_CONTENT_TYPE, "2.0", "ics",
279       (struct buf* (*)(void *)) &my_icalcomponent_as_ical_string,
280       (void * (*)(const struct buf*)) &ical_string_as_icalcomponent,
281       (void (*)(void *)) &icalcomponent_free, &begin_icalendar, &end_icalendar
282     },
283     { "application/calendar+xml; charset=utf-8", NULL, "xcs",
284       (struct buf* (*)(void *)) &icalcomponent_as_xcal_string,
285       (void * (*)(const struct buf*)) &xcal_string_as_icalcomponent,
286       NULL, &begin_xcal, &end_xcal
287     },
288     { "application/calendar+json; charset=utf-8", NULL, "jcs",
289       (struct buf* (*)(void *)) &icalcomponent_as_jcal_string,
290       (void * (*)(const struct buf*)) &jcal_string_as_icalcomponent,
291       NULL, &begin_jcal, &end_jcal
292     },
293     { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
294 };
295 
296 static struct patch_doc_t caldav_patch_docs[] = {
297 #ifdef HAVE_VPATCH
298     { ICALENDAR_CONTENT_TYPE "; component=VPATCH; optinfo=\"PATCH-VERSION:1\"",
299       &caldav_patch },
300 #endif
301     { NULL, &caldav_patch /* silence compiler when !HAVE_VPATCH */}
302 };
303 
304 /* Array of supported REPORTs */
305 static const struct report_type_t caldav_reports[] = {
306 
307     /* WebDAV Versioning (RFC 3253) REPORTs */
308     { "expand-property", NS_DAV, "multistatus", &report_expand_prop,
309       DACL_READ, 0 },
310 
311     /* WebDAV ACL (RFC 3744) REPORTs */
312     { "acl-principal-prop-set", NS_DAV, "multistatus", &report_acl_prin_prop,
313       DACL_ADMIN, REPORT_NEED_MBOX | REPORT_DEPTH_ZERO },
314 
315     /* WebDAV Sync (RFC 6578) REPORTs */
316     { "sync-collection", NS_DAV, "multistatus", &report_sync_col,
317       DACL_READ, REPORT_NEED_MBOX | REPORT_NEED_PROPS },
318 
319     /* CalDAV (RFC 4791) REPORTs */
320     { "calendar-query", NS_CALDAV, "multistatus", &report_cal_query,
321       DACL_READ, REPORT_NEED_MBOX | REPORT_ALLOW_PROPS },
322     { "calendar-multiget", NS_CALDAV, "multistatus", &report_multiget,
323       DACL_READ, REPORT_NEED_MBOX | REPORT_ALLOW_PROPS },
324     { "free-busy-query", NS_CALDAV, NULL, &report_fb_query,
325       DACL_READFB, REPORT_NEED_MBOX },
326 
327     { NULL, 0, NULL, NULL, 0, 0 }
328 };
329 
330 /* Array of known "live" properties */
331 static const struct prop_entry caldav_props[] = {
332 
333     /* WebDAV (RFC 4918) properties */
334     { "creationdate", NS_DAV,
335       PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
336       propfind_creationdate, NULL, NULL },
337     { "displayname", NS_DAV,
338       PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
339       propfind_fromdb, proppatch_todb, NULL },
340     { "getcontentlanguage", NS_DAV, PROP_ALLPROP | PROP_RESOURCE,
341       propfind_fromhdr, NULL, "Content-Language" },
342     { "getcontentlength", NS_DAV,
343       PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
344       propfind_getlength, NULL, NULL },
345     { "getcontenttype", NS_DAV,
346       PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
347       propfind_getcontenttype, NULL, "Content-Type" },
348     { "getetag", NS_DAV, PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
349       propfind_getetag, NULL, NULL },
350     { "getlastmodified", NS_DAV,
351       PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
352       propfind_getlastmod, NULL, NULL },
353     { "lockdiscovery", NS_DAV, PROP_ALLPROP | PROP_RESOURCE,
354       propfind_lockdisc, NULL, NULL },
355     { "resourcetype", NS_DAV,
356       PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
357       propfind_restype, proppatch_restype, "calendar" },
358     { "supportedlock", NS_DAV, PROP_ALLPROP | PROP_RESOURCE,
359       propfind_suplock, NULL, NULL },
360 
361     /* WebDAV Versioning (RFC 3253) properties */
362     { "supported-report-set", NS_DAV, PROP_COLLECTION,
363       propfind_reportset, NULL, (void *) caldav_reports },
364 
365     /* WebDAV ACL (RFC 3744) properties */
366     { "owner", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
367       propfind_owner, NULL, NULL },
368     { "group", NS_DAV, 0, NULL, NULL, NULL },
369     { "supported-privilege-set", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
370       propfind_supprivset, NULL, NULL },
371     { "current-user-privilege-set", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
372       propfind_curprivset, NULL, NULL },
373     { "acl", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
374       propfind_acl, NULL, NULL },
375     { "acl-restrictions", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
376       propfind_aclrestrict, NULL, NULL },
377     { "inherited-acl-set", NS_DAV, 0, NULL, NULL, NULL },
378     { "principal-collection-set", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
379       propfind_princolset, NULL, NULL },
380 
381     /* WebDAV Quota (RFC 4331) properties */
382     { "quota-available-bytes", NS_DAV, PROP_COLLECTION,
383       propfind_quota, NULL, NULL },
384     { "quota-used-bytes", NS_DAV, PROP_COLLECTION,
385       propfind_quota, NULL, NULL },
386 
387     /* WebDAV Current Principal (RFC 5397) properties */
388     { "current-user-principal", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
389       propfind_curprin, NULL, NULL },
390 
391     /* WebDAV POST (RFC 5995) properties */
392     { "add-member", NS_DAV, PROP_COLLECTION,
393       propfind_addmember, NULL, NULL },
394 
395     /* WebDAV Sync (RFC 6578) properties */
396     { "sync-token", NS_DAV, PROP_COLLECTION,
397       propfind_sync_token, NULL, SYNC_TOKEN_URL_SCHEME },
398 
399     /* WebDAV Sharing (draft-pot-webdav-resource-sharing) properties */
400     { "share-access", NS_DAV, PROP_COLLECTION,
401       propfind_shareaccess, NULL, NULL },
402     { "invite", NS_DAV, PROP_COLLECTION,
403       propfind_invite, NULL, NULL },
404     { "sharer-resource-uri", NS_DAV, PROP_COLLECTION,
405       propfind_sharedurl, NULL, NULL },
406 
407     /* Backwards compatibility with Apple calendar sharing clients */
408     { "invite", NS_CS, PROP_COLLECTION,
409       propfind_invite, NULL, "calendarserver-sharing" },
410     { "allowed-sharing-modes", NS_CS, PROP_COLLECTION,
411       propfind_sharingmodes, NULL, NULL },
412     { "shared-url", NS_CS, PROP_COLLECTION,
413       propfind_sharedurl, NULL, "calendarserver-sharing" },
414 
415     /* CalDAV (RFC 4791) properties */
416     { "calendar-data", NS_CALDAV, PROP_RESOURCE | PROP_PRESCREEN | PROP_CLEANUP,
417       propfind_caldata, NULL, &partial_caldata },
418     { "schedule-user-address", NS_CYRUS, PROP_RESOURCE,
419       propfind_scheduser, NULL, NULL },
420     { "calendar-description", NS_CALDAV, PROP_COLLECTION,
421       propfind_fromdb, proppatch_todb, NULL },
422     { "calendar-timezone", NS_CALDAV, PROP_COLLECTION | PROP_PRESCREEN,
423       propfind_timezone, proppatch_timezone, NULL },
424     { "supported-calendar-component-set", NS_CALDAV, PROP_COLLECTION,
425       propfind_calcompset, proppatch_calcompset, NULL },
426     { "supported-calendar-data", NS_CALDAV, PROP_COLLECTION,
427       propfind_suppcaldata, NULL, NULL },
428     { "max-resource-size", NS_CALDAV, PROP_COLLECTION,
429       propfind_maxsize, NULL, NULL },
430     { "min-date-time", NS_CALDAV, PROP_COLLECTION,
431       propfind_minmaxdate, NULL, &caldav_epoch },
432     { "max-date-time", NS_CALDAV, PROP_COLLECTION,
433       propfind_minmaxdate, NULL, &caldav_eternity },
434     { "max-instances", NS_CALDAV, 0, NULL, NULL, NULL },
435     { "max-attendees-per-instance", NS_CALDAV, 0, NULL, NULL, NULL },
436 
437     /* CalDAV Scheduling (RFC 6638) properties */
438     { "schedule-tag", NS_CALDAV, PROP_RESOURCE,
439       propfind_schedtag, NULL, NULL },
440     { "schedule-default-calendar-URL", NS_CALDAV, PROP_COLLECTION,
441       propfind_scheddefault, NULL, NULL },
442     { "schedule-calendar-transp", NS_CALDAV, PROP_COLLECTION,
443       propfind_caltransp, proppatch_caltransp, NULL },
444 
445     /* Calendar Availability (RFC 7953) properties */
446     { "calendar-availability", NS_CALDAV, PROP_COLLECTION | PROP_PRESCREEN,
447       propfind_availability, proppatch_availability, NULL },
448 
449     /* Backwards compatibility with Apple VAVAILABILITY clients */
450     { "calendar-availability", NS_CS, PROP_COLLECTION | PROP_PRESCREEN,
451       propfind_availability, proppatch_availability, NULL },
452 
453     /* Time Zones by Reference (RFC 7809) properties */
454     { "timezone-service-set", NS_CALDAV, PROP_COLLECTION,
455       propfind_tzservset, NULL, NULL },
456     { "calendar-timezone-id", NS_CALDAV, PROP_COLLECTION,
457       propfind_tzid, proppatch_tzid, NULL },
458 
459     /* RSCALE (RFC 7529) properties */
460     { "supported-rscale-set", NS_CALDAV, PROP_COLLECTION,
461       propfind_rscaleset, NULL, NULL },
462 
463     /* CalDAV Extensions (draft-daboo-caldav-extensions) properties */
464     { "supported-calendar-component-sets", NS_CALDAV, PROP_COLLECTION,
465       propfind_calcompset, NULL, NULL },
466 
467     /* Apple Calendar Server properties */
468     { "getctag", NS_CS, PROP_ALLPROP | PROP_COLLECTION,
469       propfind_sync_token, NULL, "" },
470 
471     /* Apple Mobile Me properties */
472     { "bulk-requests", NS_MECOM, PROP_COLLECTION,
473       propfind_bulkrequests, NULL, NULL },
474 
475     /* Apple Push Notifications Service properties */
476     { "push-transports", NS_CS, PROP_COLLECTION,
477       propfind_push_transports, NULL, (void *) MBTYPE_CALENDAR },
478     { "pushkey", NS_CS, PROP_COLLECTION,
479       propfind_pushkey, NULL, NULL },
480 
481     { NULL, 0, 0, NULL, NULL, NULL }
482 };
483 
484 
485 static struct meth_params caldav_params = {
486     caldav_mime_types,
487     &caldav_parse_path,
488     &caldav_check_precond,
489     { (db_open_proc_t) &caldav_open_mailbox,
490       (db_close_proc_t) &caldav_close,
491       (db_proc_t) &caldav_begin,
492       (db_proc_t) &caldav_commit,
493       (db_proc_t) &caldav_abort,
494       (db_lookup_proc_t) &caldav_lookup_resource,
495       (db_foreach_proc_t) &caldav_foreach,
496       (db_write_proc_t) &caldav_write,
497       (db_delete_proc_t) &caldav_delete },
498     &caldav_acl,
499     { CALDAV_UID_CONFLICT, &caldav_copy },
500     &caldav_delete_cal,
501     &caldav_get,
502     { CALDAV_LOCATION_OK, MBTYPE_CALENDAR },
503     caldav_patch_docs,
504     { POST_ADDMEMBER | POST_SHARE | POST_BULK, &caldav_post, &caldav_import },
505     { CALDAV_SUPP_DATA, &caldav_put },
506     { 0, caldav_props },                        /* Allow infinite depth */
507     caldav_reports
508 };
509 
510 
511 /* Namespace for CalDAV collections */
512 struct namespace_t namespace_calendar = {
513     URL_NS_CALENDAR, 0, "/dav/calendars", "/.well-known/caldav",
514     http_allow_noauth_get, /*authschemes*/0,
515     MBTYPE_CALENDAR,
516     (ALLOW_READ | ALLOW_POST | ALLOW_WRITE | ALLOW_DELETE |
517 #ifdef HAVE_VPATCH
518      ALLOW_PATCH |
519 #endif
520 #ifdef HAVE_VAVAILABILITY
521      ALLOW_CAL_AVAIL |
522 #endif
523      ALLOW_DAV | ALLOW_PROPPATCH | ALLOW_MKCOL | ALLOW_ACL | ALLOW_CAL ),
524     &my_caldav_init, &my_caldav_auth, my_caldav_reset, &my_caldav_shutdown,
525     &dav_premethod, /*bearer*/NULL,
526     {
527         { &meth_acl,            &caldav_params },       /* ACL          */
528         { NULL,                 NULL },                 /* BIND         */
529         { &meth_copy_move,      &caldav_params },       /* COPY         */
530         { &meth_delete,         &caldav_params },       /* DELETE       */
531         { &meth_get_head_cal,   NULL },                 /* GET          */
532         { &meth_get_head_cal,   NULL },                 /* HEAD         */
533         { &meth_lock,           &caldav_params },       /* LOCK         */
534         { &meth_mkcol,          &caldav_params },       /* MKCALENDAR   */
535         { &meth_mkcol,          &caldav_params },       /* MKCOL        */
536         { &meth_copy_move,      &caldav_params },       /* MOVE         */
537         { &meth_options_cal,    &caldav_parse_path },   /* OPTIONS      */
538         { &meth_patch,          &caldav_params },       /* PATCH        */
539         { &meth_post,           &caldav_params },       /* POST         */
540         { &meth_propfind,       &caldav_params },       /* PROPFIND     */
541         { &meth_proppatch,      &caldav_params },       /* PROPPATCH    */
542         { &meth_put,            &caldav_params },       /* PUT          */
543         { &meth_report,         &caldav_params },       /* REPORT       */
544         { &meth_trace,          &caldav_parse_path },   /* TRACE        */
545         { NULL,                 NULL },                 /* UNBIND       */
546         { &meth_unlock,         &caldav_params }        /* UNLOCK       */
547     }
548 };
549 
550 
551 /* Namespace for Freebusy Read URL */
552 struct namespace_t namespace_freebusy = {
553     URL_NS_FREEBUSY, 0, "/freebusy", NULL,
554     http_allow_noauth_get, /*authschemes*/0,
555     MBTYPE_CALENDAR,
556     ALLOW_READ,
557     NULL, NULL, NULL, NULL, NULL, NULL,
558     {
559         { NULL,                 NULL },                 /* ACL          */
560         { NULL,                 NULL },                 /* BIND         */
561         { NULL,                 NULL },                 /* COPY         */
562         { NULL,                 NULL },                 /* DELETE       */
563         { &meth_get_head_fb,    NULL },                 /* GET          */
564         { &meth_get_head_fb,    NULL },                 /* HEAD         */
565         { NULL,                 NULL },                 /* LOCK         */
566         { NULL,                 NULL },                 /* MKCALENDAR   */
567         { NULL,                 NULL },                 /* MKCOL        */
568         { NULL,                 NULL },                 /* MOVE         */
569         { &meth_options,        &caldav_parse_path },   /* OPTIONS      */
570         { NULL,                 NULL },                 /* PATCH        */
571         { NULL,                 NULL },                 /* POST         */
572         { NULL,                 NULL },                 /* PROPFIND     */
573         { NULL,                 NULL },                 /* PROPPATCH    */
574         { NULL,                 NULL },                 /* PUT          */
575         { NULL,                 NULL },                 /* REPORT       */
576         { &meth_trace,          &caldav_parse_path },   /* TRACE        */
577         { NULL,                 NULL },                 /* UNBIND       */
578         { NULL,                 NULL }                  /* UNLOCK       */
579     }
580 };
581 
582 
583 static const struct cal_comp_t {
584     const char *name;
585     unsigned long type;
586 } cal_comps[] = {
587     { "VEVENT",         CAL_COMP_VEVENT },
588     { "VTODO",          CAL_COMP_VTODO },
589     { "VJOURNAL",       CAL_COMP_VJOURNAL },
590     { "VFREEBUSY",      CAL_COMP_VFREEBUSY },
591 #ifdef HAVE_VAVAILABILITY
592     { "VAVAILABILITY",  CAL_COMP_VAVAILABILITY },
593 #endif
594 #ifdef HAVE_VPOLL
595     { "VPOLL",          CAL_COMP_VPOLL },
596 #endif
597 //    { "VTIMEZONE",    CAL_COMP_VTIMEZONE },
598 //    { "VALARM",               CAL_COMP_VALARM },
599     { NULL, 0 }
600 };
601 
602 
my_caldav_init(struct buf * serverinfo)603 static void my_caldav_init(struct buf *serverinfo)
604 {
605     const char *domains;
606     char *domain;
607     tok_t tok;
608 
609     buf_printf(serverinfo, " SQLite/%s", sqlite3_libversion());
610     buf_printf(serverinfo, " LibiCal/%s", ICAL_VERSION);
611 #ifdef HAVE_RSCALE
612     if ((rscale_calendars = icalrecurrencetype_rscale_supported_calendars())) {
613         icalarray_sort(rscale_calendars, &rscale_cmp);
614 
615         buf_printf(serverinfo, " ICU4C/%s", U_ICU_VERSION);
616     }
617 #endif
618     buf_printf(serverinfo, " Jansson/%s", JANSSON_VERSION);
619 
620     namespace_calendar.enabled =
621         config_httpmodules & IMAP_ENUM_HTTPMODULES_CALDAV;
622 
623     if (!namespace_calendar.enabled) return;
624 
625     if (!config_getstring(IMAPOPT_CALENDARPREFIX)) {
626         fatal("Required 'calendarprefix' option is not set", EC_CONFIG);
627     }
628 
629 #ifdef HAVE_IANA_PARAMS
630     config_allowsched = config_getenum(IMAPOPT_CALDAV_ALLOWSCHEDULING);
631     if (config_allowsched) {
632         namespace_calendar.allow |= ALLOW_CAL_SCHED;
633 
634         /* Always treat unknown parameters as IANA */
635         ical_set_unknown_token_handling_setting(ICAL_ASSUME_IANA_TOKEN);
636     }
637 
638     if (config_getswitch(IMAPOPT_CALDAV_ALLOWATTACH))
639         namespace_calendar.allow |= ALLOW_CAL_ATTACH;
640 
641 #endif /* HAVE_IANA_PARAMS */
642 
643 #ifdef HAVE_TZ_BY_REF
644     if (namespace_tzdist.enabled) {
645         /* Tell libical to use our builtin TZ */
646         /* XXX  MUST be done before any use of libical, e.g caldav_init() */
647         char zonedir[MAX_MAILBOX_PATH+1];
648 
649         snprintf(zonedir, MAX_MAILBOX_PATH, "%s%s",
650                  config_dir, FNAME_ZONEINFODIR);
651         set_zone_directory(zonedir);
652         icaltimezone_set_tzid_prefix("");
653         icaltimezone_set_builtin_tzdata(1);
654 
655         namespace_calendar.allow |= ALLOW_CAL_NOTZ;
656     }
657 #endif
658 
659     caldav_init();
660     webdav_init();
661 
662     namespace_principal.enabled = 1;
663     /* Apple clients check principal resources for these DAV tokens */
664     namespace_principal.allow |= namespace_calendar.allow &
665         (ALLOW_CAL | ALLOW_CAL_AVAIL | ALLOW_CAL_SCHED | ALLOW_CAL_ATTACH);
666 
667     namespace_freebusy.enabled =
668         config_httpmodules & IMAP_ENUM_HTTPMODULES_FREEBUSY;
669 
670     compile_time = calc_compile_time(__TIME__, __DATE__);
671 
672     buf_printf(&ical_prodid_buf,
673                "-//CyrusIMAP.org/Cyrus %s//EN", cyrus_version());
674     ical_prodid = buf_cstring(&ical_prodid_buf);
675 
676     /* Create an array of calendar-user-adddress-set domains */
677     domains = config_getstring(IMAPOPT_CALENDAR_USER_ADDRESS_SET);
678     if (!domains) domains = config_defdomain;
679     if (!domains) domains = config_servername;
680 
681     tok_init(&tok, domains, " \t", TOK_TRIMLEFT|TOK_TRIMRIGHT);
682     while ((domain = tok_next(&tok))) appendstrlist(&cua_domains, domain);
683     tok_fini(&tok);
684 
685     utc_zone = icaltimezone_get_utc_timezone();
686 }
687 
_create_mailbox(const char * userid,const char * mailboxname,int type,int useracl,int anyoneacl,const char * displayname)688 static int _create_mailbox(const char *userid, const char *mailboxname,
689                            int type, int useracl, int anyoneacl,
690                            const char *displayname)
691 {
692     int r = 0;
693     char rights[100];
694     struct mailbox *mailbox = NULL;
695 
696     r = mboxlist_lookup(mailboxname, NULL, NULL);
697     if (!r) return 0;
698     if (r != IMAP_MAILBOX_NONEXISTENT) return r;
699 
700     /* Create locally */
701     r = mboxlist_createmailbox(mailboxname, type,
702                                NULL, 0,
703                                userid, httpd_authstate,
704                                0, 0, 0, 0, displayname ? &mailbox : NULL);
705     if (!r && displayname) {
706         annotate_state_t *astate = NULL;
707 
708         r = mailbox_get_annotate_state(mailbox, 0, &astate);
709         if (!r) {
710             const char *annot = DAV_ANNOT_NS "<" XML_NS_DAV ">displayname";
711             struct buf value = BUF_INITIALIZER;
712 
713             buf_init_ro_cstr(&value, displayname);
714             r = annotate_state_writemask(astate, annot, userid, &value);
715         }
716 
717         mailbox_close(&mailbox);
718     }
719     if (!r && useracl) {
720         cyrus_acl_masktostr(useracl, rights);
721         r = mboxlist_setacl(&httpd_namespace, mailboxname, userid, rights,
722                             1, httpd_userid, httpd_authstate);
723     }
724     if (!r && anyoneacl) {
725         cyrus_acl_masktostr(anyoneacl, rights);
726         r = mboxlist_setacl(&httpd_namespace, mailboxname, "anyone", rights,
727                             1, httpd_userid, httpd_authstate);
728     }
729 
730     if (r) syslog(LOG_ERR, "IOERROR: failed to create %s (%s)",
731                   mailboxname, error_message(r));
732     return r;
733 }
734 
caldav_create_defaultcalendars(const char * userid)735 int caldav_create_defaultcalendars(const char *userid)
736 {
737     int r;
738     char *mailboxname;
739     struct buf acl = BUF_INITIALIZER;
740 
741     /* calendar-home-set */
742     mailboxname = caldav_mboxname(userid, NULL);
743     r = mboxlist_lookup(mailboxname, NULL, NULL);
744     if (r == IMAP_MAILBOX_NONEXISTENT) {
745         /* Find location of INBOX */
746         char *inboxname = mboxname_user_mbox(userid, NULL);
747         mbentry_t *mbentry = NULL;
748 
749         r = http_mlookup(inboxname, &mbentry, NULL);
750         free(inboxname);
751         if (r == IMAP_MAILBOX_NONEXISTENT) r = IMAP_INVALID_USER;
752         if (!r && mbentry->server) {
753             proxy_findserver(mbentry->server, &http_protocol, httpd_userid,
754                              &backend_cached, NULL, NULL, httpd_in);
755             mboxlist_entry_free(&mbentry);
756             free(mailboxname);
757             return r;
758         }
759         mboxlist_entry_free(&mbentry);
760 
761         if (!r) r = _create_mailbox(userid, mailboxname, MBTYPE_CALENDAR,
762                                     ACL_ALL | DACL_READFB, DACL_READFB, NULL);
763     }
764 
765     free(mailboxname);
766     if (r) goto done;
767 
768     if (config_getswitch(IMAPOPT_CALDAV_CREATE_DEFAULT)) {
769         /* Default calendar */
770         mailboxname = caldav_mboxname(userid, SCHED_DEFAULT);
771         r = _create_mailbox(userid, mailboxname, MBTYPE_CALENDAR,
772                             ACL_ALL | DACL_READFB, DACL_READFB, "personal");
773         free(mailboxname);
774         if (r) goto done;
775     }
776 
777     if (config_getswitch(IMAPOPT_CALDAV_CREATE_SCHED) &&
778         namespace_calendar.allow & ALLOW_CAL_SCHED) {
779         /* Scheduling Inbox */
780         mailboxname = caldav_mboxname(userid, SCHED_INBOX);
781         r = _create_mailbox(userid, mailboxname, MBTYPE_CALENDAR,
782                             ACL_ALL | DACL_SCHED, DACL_SCHED, NULL);
783         free(mailboxname);
784         if (r) goto done;
785 
786         /* Scheduling Outbox */
787         mailboxname = caldav_mboxname(userid, SCHED_OUTBOX);
788         r = _create_mailbox(userid, mailboxname, MBTYPE_CALENDAR,
789                             ACL_ALL | DACL_SCHED, 0, NULL);
790         free(mailboxname);
791         if (r) goto done;
792     }
793 
794     if (config_getswitch(IMAPOPT_CALDAV_CREATE_ATTACH) &&
795         namespace_calendar.allow & ALLOW_CAL_ATTACH) {
796         /* Managed Attachment Collection */
797         mailboxname = caldav_mboxname(userid, MANAGED_ATTACH);
798         r = _create_mailbox(userid, mailboxname, MBTYPE_COLLECTION,
799                             ACL_ALL, ACL_READ, NULL);
800         free(mailboxname);
801         if (r) goto done;
802     }
803 
804   done:
805     buf_free(&acl);
806     return r;
807 }
808 
my_caldav_auth(const char * userid)809 static void my_caldav_auth(const char *userid)
810 {
811     int r;
812 
813     if (httpd_userisadmin ||
814         global_authisa(httpd_authstate, IMAPOPT_PROXYSERVERS)) {
815         /* admin or proxy from frontend - won't have DAV database */
816         return;
817     }
818     if (config_mupdate_server && !config_getstring(IMAPOPT_PROXYSERVERS)) {
819         /* proxy-only server - won't have DAV database */
820         return;
821     }
822     else {
823         /* Open CalDAV DB for 'userid' */
824         my_caldav_reset();
825         auth_caldavdb = caldav_open_userid(userid);
826         if (!auth_caldavdb) fatal("Unable to open CalDAV DB", EC_IOERR);
827     }
828 
829     /* Auto-provision calendars for 'userid' */
830     r = caldav_create_defaultcalendars(userid);
831     if (r) {
832         syslog(LOG_ERR, "could not autoprovision calendars for userid %s: %s",
833                 userid, error_message(r));
834     }
835 }
836 
my_caldav_reset(void)837 static void my_caldav_reset(void)
838 {
839     if (auth_caldavdb) caldav_close(auth_caldavdb);
840     auth_caldavdb = NULL;
841 }
842 
my_caldav_shutdown(void)843 static void my_caldav_shutdown(void)
844 {
845     if (rscale_calendars) icalarray_free(rscale_calendars);
846     rscale_calendars = NULL;
847 
848     buf_free(&ical_prodid_buf);
849 
850     freestrlist(cua_domains);
851     cua_domains = NULL;
852 
853     my_caldav_reset();
854     webdav_done();
855     caldav_done();
856 }
857 
858 
859 /* Parse request-target path in CalDAV namespace */
caldav_parse_path(const char * path,struct request_target_t * tgt,const char ** errstr)860 static int caldav_parse_path(const char *path,
861                              struct request_target_t *tgt, const char **errstr)
862 {
863     int r;
864 
865     r = calcarddav_parse_path(path, tgt,
866                               config_getstring(IMAPOPT_CALENDARPREFIX),
867                               errstr);
868     if (r) return r;
869 
870     /* Set proper Allow bits based on collection */
871     if (tgt->namespace && tgt->namespace->id == URL_NS_FREEBUSY) {
872         /* Read-only collections */
873         tgt->allow = ALLOW_READ;
874     }
875     else if (!tgt->collection) {
876         /* Allow POST to cal-home-set (share reply) */
877         tgt->allow |= ALLOW_POST;
878     }
879     else if (!strncmp(tgt->collection, MANAGED_ATTACH, strlen(MANAGED_ATTACH))) {
880         /* Read-only non-calendar collection */
881         tgt->allow = ALLOW_READ;
882 
883         tgt->flags = TGT_MANAGED_ATTACH;
884     }
885     else if (!strncmp(tgt->collection, SCHED_INBOX, strlen(SCHED_INBOX))) {
886         /* Can only read and DELETE resources from this collection */
887         tgt->allow &= ALLOW_READ_MASK;
888 
889         if (tgt->resource) tgt->allow |= ALLOW_DELETE;
890 
891         tgt->flags = TGT_SCHED_INBOX;
892     }
893     else if (!strncmp(tgt->collection, SCHED_OUTBOX, strlen(SCHED_OUTBOX))){
894         /* Can only POST to this collection (free/busy request) */
895         tgt->allow &= ALLOW_READ_MASK;
896 
897         if (!tgt->resource) tgt->allow |= ALLOW_POST;
898 
899         tgt->flags = TGT_SCHED_OUTBOX;
900     }
901     else if (tgt->resource) {
902         /* Resource in regular calendar collection (POST for managed attach) */
903         tgt->allow |= (namespace_calendar.allow & ALLOW_PATCH) | ALLOW_POST;
904     }
905 
906     return 0;
907 }
908 
909 
910 /* Check headers for any preconditions */
caldav_check_precond(struct transaction_t * txn,struct meth_params * params,struct mailbox * mailbox,const void * data,const char * etag,time_t lastmod)911 static int caldav_check_precond(struct transaction_t *txn,
912                                 struct meth_params *params,
913                                 struct mailbox *mailbox, const void *data,
914                                 const char *etag, time_t lastmod)
915 {
916     const struct caldav_data *cdata = (const struct caldav_data *) data;
917     const char *stag = cdata && cdata->organizer ? cdata->sched_tag : NULL;
918     const char **hdr;
919     int precond;
920 
921     /* Do normal WebDAV/HTTP checks (primarily for lock-token via If header) */
922     precond = dav_check_precond(txn, params, mailbox, data, etag, lastmod);
923     if (precond == HTTP_PRECOND_FAILED &&
924         cdata->comp_flags.tzbyref && !cdata->organizer && cdata->sched_tag) {
925         /* Resource has just had VTIMEZONEs stripped -
926            check if conditional matches previous ETag */
927 
928         precond = check_precond(txn, cdata->sched_tag, lastmod);
929     }
930     if (!(precond == HTTP_OK || precond == HTTP_PARTIAL)) return precond;
931 
932     /* Per RFC 6638, check Schedule-Tag */
933     if ((hdr = spool_getheader(txn->req_hdrs, "If-Schedule-Tag-Match"))) {
934         /* Special case for Apple 'If-Schedule-Tag-Match:' with no value
935          * and also no schedule tag on the record - let that match */
936         if (cdata && !stag && !hdr[0][0]) return precond;
937         if (etagcmp(hdr[0], stag)) return HTTP_PRECOND_FAILED;
938     }
939 
940     if (txn->meth == METH_GET || txn->meth == METH_HEAD) {
941         /* Fill in Schedule-Tag for successful GET/HEAD */
942         txn->resp_body.stag = stag;
943     }
944 
945     return precond;
946 }
947 
948 
caldav_acl(struct transaction_t * txn,xmlNodePtr priv,int * rights)949 static int caldav_acl(struct transaction_t *txn, xmlNodePtr priv, int *rights)
950 {
951     if (!xmlStrcmp(priv->ns->href, BAD_CAST XML_NS_CALDAV)) {
952         /* CalDAV privileges */
953         switch (txn->req_tgt.flags) {
954         case TGT_SCHED_INBOX:
955             if (!xmlStrcmp(priv->name, BAD_CAST "schedule-deliver"))
956                 *rights |= DACL_SCHED;
957             else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-deliver-invite"))
958                 *rights |= DACL_INVITE;
959             else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-deliver-reply"))
960                 *rights |= DACL_REPLY;
961             else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-query-freebusy"))
962                 *rights |= DACL_SCHEDFB;
963             else {
964                 /* DAV:not-supported-privilege */
965                 txn->error.precond = DAV_SUPP_PRIV;
966             }
967             break;
968         case TGT_SCHED_OUTBOX:
969             if (!xmlStrcmp(priv->name, BAD_CAST "schedule-send"))
970                 *rights |= DACL_SCHED;
971             else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-send-invite"))
972                 *rights |= DACL_INVITE;
973             else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-send-reply"))
974                 *rights |= DACL_REPLY;
975             else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-send-freebusy"))
976                 *rights |= DACL_SCHEDFB;
977             else {
978                 /* DAV:not-supported-privilege */
979                 txn->error.precond = DAV_SUPP_PRIV;
980             }
981             break;
982         default:
983             if (!xmlStrcmp(priv->name, BAD_CAST "read-free-busy"))
984                 *rights |= DACL_READFB;
985             else {
986                 /* DAV:not-supported-privilege */
987                 txn->error.precond = DAV_SUPP_PRIV;
988             }
989             break;
990         }
991 
992         /* Done processing this priv */
993         return 1;
994     }
995     else if (!xmlStrcmp(priv->ns->href, BAD_CAST XML_NS_DAV)) {
996         /* WebDAV privileges */
997         if (!xmlStrcmp(priv->name, BAD_CAST "all")) {
998             switch (txn->req_tgt.flags) {
999             case TGT_SCHED_INBOX:
1000                 /* DAV:all aggregates CALDAV:schedule-deliver */
1001                 *rights |= DACL_SCHED;
1002                 break;
1003             case TGT_SCHED_OUTBOX:
1004                 /* DAV:all aggregates CALDAV:schedule-send */
1005                 *rights |= DACL_SCHED;
1006                 break;
1007             default:
1008                 /* DAV:all aggregates CALDAV:read-free-busy */
1009                 *rights |= DACL_READFB;
1010                 break;
1011             }
1012         }
1013         else if (!xmlStrcmp(priv->name, BAD_CAST "read")) {
1014             switch (txn->req_tgt.flags) {
1015             case TGT_SCHED_INBOX:
1016             case TGT_SCHED_OUTBOX:
1017                 break;
1018             default:
1019                 /* DAV:read aggregates CALDAV:read-free-busy */
1020                 *rights |= DACL_READFB;
1021                 break;
1022             }
1023         }
1024     }
1025 
1026     /* Process this priv in meth_acl() */
1027     return 0;
1028 }
1029 
_scheduling_enabled(struct transaction_t * txn,const struct mailbox * mailbox)1030 static int _scheduling_enabled(struct transaction_t *txn,
1031                                const struct mailbox *mailbox)
1032 {
1033     if (!(namespace_calendar.allow & ALLOW_CAL_SCHED)) return 0;
1034 
1035     const char *entry = DAV_ANNOT_NS "<" XML_NS_CYRUS ">scheduling-enabled";
1036     struct buf buf = BUF_INITIALIZER;
1037     int is_enabled = 1;
1038 
1039     annotatemore_lookupmask(mailbox->name, entry, httpd_userid, &buf);
1040     /* legacy */
1041     if (!strcasecmp(buf_cstring(&buf), "no"))
1042         is_enabled = 0;
1043     if (!strcasecmp(buf_cstring(&buf), "F"))
1044         is_enabled = 0;
1045 
1046     const char **hdr = spool_getheader(txn->req_hdrs, "Scheduling-Enabled");
1047     if (hdr && !strcasecmp(hdr[0], "F"))
1048         is_enabled = 0;
1049 
1050     buf_free(&buf);
1051     return is_enabled;
1052 }
1053 
1054 /* Perform a COPY/MOVE request
1055  *
1056  * preconditions:
1057  *   CALDAV:supported-calendar-data
1058  *   CALDAV:valid-calendar-data
1059  *   CALDAV:valid-calendar-object-resource
1060  *   CALDAV:supported-calendar-component
1061  *   CALDAV:no-uid-conflict (DAV:href)
1062  *   CALDAV:calendar-collection-location-ok
1063  *   CALDAV:max-resource-size
1064  *   CALDAV:min-date-time
1065  *   CALDAV:max-date-time
1066  *   CALDAV:max-instances
1067  *   CALDAV:max-attendees-per-instance
1068  */
caldav_copy(struct transaction_t * txn,void * obj,struct mailbox * dest_mbox,const char * dest_rsrc,void * destdb,unsigned flags)1069 static int caldav_copy(struct transaction_t *txn, void *obj,
1070                        struct mailbox *dest_mbox, const char *dest_rsrc,
1071                        void *destdb, unsigned flags)
1072 {
1073     int r;
1074     struct caldav_db *db = (struct caldav_db *)destdb;
1075 
1076     icalcomponent *comp, *ical = (icalcomponent *) obj;
1077     const char *organizer = NULL;
1078     icalproperty *prop;
1079 
1080     if (!ical) {
1081         txn->error.precond = CALDAV_VALID_DATA;
1082         return HTTP_FORBIDDEN;
1083     }
1084 
1085     if (_scheduling_enabled(txn, dest_mbox)) {
1086         comp = icalcomponent_get_first_real_component(ical);
1087         prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
1088         if (prop) organizer = icalproperty_get_organizer(prop);
1089         if (organizer) flags |= NEW_STAG;
1090     }
1091 
1092     /* Store source resource at destination */
1093     /* XXX - set calendar-user-address based on original message? */
1094     r = caldav_store_resource(txn, ical, dest_mbox, dest_rsrc, db, flags, NULL);
1095 
1096     return r;
1097 }
1098 
1099 
1100 static void decrement_refcount(const char *managed_id,
1101                                struct mailbox *attachments,
1102                                struct webdav_db *webdavdb);
1103 static struct webdav_data *increment_refcount(const char *managed_id,
1104                                               struct webdav_db *webdavdb);
1105 
1106 enum {
1107     REFCNT_DEC  = -1,
1108     REFCNT_HOLD = 0,
1109     REFCNT_INC  = 1
1110 };
1111 
update_refcount(const char * mid,short * op,struct mailbox * attachments)1112 static void update_refcount(const char *mid, short *op,
1113                             struct mailbox *attachments)
1114 {
1115     switch (*op) {
1116     case REFCNT_DEC:
1117         decrement_refcount(mid, attachments, attachments->local_webdav);
1118         break;
1119 
1120     case REFCNT_INC:
1121         increment_refcount(mid, attachments->local_webdav);
1122         break;
1123     }
1124 }
1125 
1126 /* Check an iCal object to see if managed attachments are being manipulated */
manage_attachments(struct transaction_t * txn,struct mailbox * mailbox,icalcomponent * ical,struct caldav_data * cdata,icalcomponent ** oldical,char ** schedule_address)1127 static int manage_attachments(struct transaction_t *txn,
1128                               struct mailbox *mailbox,
1129                               icalcomponent *ical, struct caldav_data *cdata,
1130                               icalcomponent **oldical, char **schedule_address)
1131 {
1132     /* Compare any managed attachments in new and existing resources */
1133     char *mailboxname = NULL;
1134     struct mailbox *attachments = NULL;
1135     struct webdav_db *webdavdb = NULL;
1136     struct hash_table mattach_table = HASH_TABLE_INITIALIZER;
1137     icalcomponent *comp = NULL;
1138     icalcomponent_kind kind;
1139     icalproperty *prop;
1140     icalparameter *param;
1141     const char *mid;
1142     short *op;
1143     int r, ret = 0;
1144 
1145     /* Open attachments collection for writing */
1146     mailboxname = caldav_mboxname(httpd_userid, MANAGED_ATTACH);
1147     r = mailbox_open_iwl(mailboxname, &attachments);
1148     if (r) {
1149         syslog(LOG_ERR, "mailbox_open_iwl(%s) failed: %s",
1150                mailboxname, error_message(r));
1151         ret = HTTP_SERVER_ERROR;
1152     }
1153     else {
1154         /* Open the WebDAV DB corresponding to the attachments collection */
1155         webdavdb = mailbox_open_webdav(attachments);
1156         if (!webdavdb) {
1157             syslog(LOG_ERR, "webdav_open_mailbox(%s) failed",
1158                    attachments->name);
1159             ret = HTTP_SERVER_ERROR;
1160         }
1161     }
1162     free(mailboxname);
1163 
1164     if (ret) return ret;
1165 
1166     /* Create hash table of managed attachments in new resource */
1167     construct_hash_table(&mattach_table, 10, 1);
1168 
1169     if (ical) {
1170         comp = icalcomponent_get_first_real_component(ical);
1171         kind = icalcomponent_isa(comp);
1172     }
1173 
1174     for (; comp;
1175          comp = icalcomponent_get_next_component(ical, kind)) {
1176 
1177         for (prop = icalcomponent_get_first_property(comp,
1178                                                      ICAL_ATTACH_PROPERTY);
1179              prop;
1180              prop = icalcomponent_get_next_property(comp,
1181                                                     ICAL_ATTACH_PROPERTY)) {
1182 
1183             icalattach *attach = icalproperty_get_attach(prop);
1184 
1185             if (icalattach_get_is_url(attach)) {
1186                 struct webdav_data *wdata;
1187 
1188                 param = icalproperty_get_managedid_parameter(prop);
1189                 if (!param) continue;
1190 
1191                 /* Find DAV record for the attachment with this managed-id */
1192                 mid = icalparameter_get_managedid(param);
1193                 webdav_lookup_uid(webdavdb, mid, &wdata);
1194 
1195                 if (!wdata->dav.rowid) {
1196                     txn->error.precond = CALDAV_VALID_MANAGEDID;
1197                     ret = HTTP_FORBIDDEN;
1198                     goto done;
1199                 }
1200 
1201                 if (!hash_lookup(mid, &mattach_table)) {
1202                     /* Assume attachment is being added to ical */
1203                     op = xmalloc(sizeof(short));
1204                     *op = REFCNT_INC;
1205                     hash_insert(mid, op, &mattach_table);
1206                 }
1207             }
1208             else {
1209                 /* XXX  Do we want to strip and manage inline attachments? */
1210             }
1211         }
1212     }
1213 
1214     /* Compare existing managed attachments to those in new resource */
1215     if (cdata->comp_flags.mattach) {
1216         struct index_record record;
1217 
1218         syslog(LOG_NOTICE, "LOADING ICAL %u", cdata->dav.imap_uid);
1219 
1220         /* Load message containing the resource and parse iCal data */
1221         r = mailbox_find_index_record(mailbox, cdata->dav.imap_uid, &record);
1222         if (r) {
1223             txn->error.desc = "Failed to read record";
1224             ret = HTTP_SERVER_ERROR;
1225             goto done;
1226         }
1227 
1228         *oldical = record_to_ical(mailbox, &record, schedule_address);
1229         comp = icalcomponent_get_first_real_component(*oldical);
1230         kind = icalcomponent_isa(comp);
1231 
1232         for (; comp;
1233              comp = icalcomponent_get_next_component(*oldical, kind)) {
1234             for (prop = icalcomponent_get_first_property(comp,
1235                                                          ICAL_ATTACH_PROPERTY);
1236                  prop;
1237                  prop = icalcomponent_get_next_property(comp,
1238                                                         ICAL_ATTACH_PROPERTY)) {
1239 
1240                 param = icalproperty_get_managedid_parameter(prop);
1241                 if (!param) continue;
1242 
1243                 mid = icalparameter_get_managedid(param);
1244                 op = hash_lookup(mid, &mattach_table);
1245                 if (!op) {
1246                     /* Attachment removed from ical */
1247                     op = xmalloc(sizeof(short));
1248                     *op = REFCNT_DEC;
1249                     hash_insert(mid, op, &mattach_table);
1250                 }
1251                 else if (*op != REFCNT_DEC) {
1252                     /* Attachment still in ical */
1253                     *op = REFCNT_HOLD;
1254                 }
1255             }
1256         }
1257     }
1258 
1259     /* Update reference counts of attachments in hash table */
1260     hash_enumerate(&mattach_table,
1261                    (void(*)(const char*,void*,void*)) &update_refcount,
1262                    attachments);
1263 
1264   done:
1265     free_hash_table(&mattach_table, free);
1266     mailbox_close(&attachments);
1267 
1268     return ret;
1269 }
1270 
1271 
get_schedule_addresses(struct transaction_t * txn,strarray_t * addresses)1272 static void get_schedule_addresses(struct transaction_t *txn,
1273                                    strarray_t *addresses)
1274 {
1275     struct buf buf = BUF_INITIALIZER;
1276 
1277     /* allow override of schedule-address per-message (FM specific) */
1278     const char **hdr = spool_getheader(txn->req_hdrs, "Schedule-Address");
1279 
1280     if (hdr) {
1281         if (!strncasecmp(hdr[0], "mailto:", 7))
1282             strarray_append(addresses, hdr[0]+7);
1283         else
1284             strarray_append(addresses, hdr[0]);
1285     }
1286     else {
1287         /* find schedule address based on the destination calendar's user */
1288 
1289         /* check calendar-user-address-set for target user */
1290         const char *annotname =
1291             DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-user-address-set";
1292         char *mailboxname = caldav_mboxname(txn->req_tgt.userid, NULL);
1293         int r = annotatemore_lookupmask(mailboxname, annotname,
1294                                         txn->req_tgt.userid, &buf);
1295         free(mailboxname);
1296         if (!r && buf.len > 7 &&
1297             !strncasecmp(buf_cstring(&buf), "mailto:", 7)) {
1298             strarray_append(addresses, buf_cstring(&buf) + 7);
1299         }
1300         else if (strchr(txn->req_tgt.userid, '@')) {
1301             /* userid corresponding to target */
1302             strarray_append(addresses, txn->req_tgt.userid);
1303         }
1304         else {
1305             /* append fully qualified userids */
1306             struct strlist *domains;
1307 
1308             for (domains = cua_domains; domains; domains = domains->next) {
1309                 buf_reset(&buf);
1310                 buf_printf(&buf, "%s@%s", txn->req_tgt.userid, domains->s);
1311 
1312                 strarray_appendm(addresses, buf_release(&buf));
1313             }
1314         }
1315     }
1316 
1317     buf_free(&buf);
1318 }
1319 
1320 
1321 /* Perform scheduling actions for a DELETE request */
caldav_delete_cal(struct transaction_t * txn,struct mailbox * mailbox,struct index_record * record,void * data)1322 static int caldav_delete_cal(struct transaction_t *txn,
1323                              struct mailbox *mailbox,
1324                              struct index_record *record, void *data)
1325 {
1326     struct caldav_data *cdata = (struct caldav_data *) data;
1327     icalcomponent *ical = NULL;
1328     struct buf buf = BUF_INITIALIZER;
1329     char *schedule_address = NULL;
1330     int r = 0;
1331 
1332     /* Only process deletes on regular calendar collections */
1333     if (txn->req_tgt.flags) return 0;
1334 
1335     if ((namespace_calendar.allow & ALLOW_CAL_ATTACH) &&
1336         cdata->comp_flags.mattach) {
1337         r = manage_attachments(txn, mailbox, NULL,
1338                                cdata, &ical, &schedule_address);
1339         if (r) goto done;
1340     }
1341 
1342     if (cdata->organizer) {
1343         /* Scheduling object resource */
1344         strarray_t schedule_addresses = STRARRAY_INITIALIZER;
1345         const char **hdr;
1346 
1347         /* XXX - check date range? - don't send in the past */
1348 
1349         /* Load message containing the resource and parse iCal data */
1350         if (!ical) ical = record_to_ical(mailbox, record, &schedule_address);
1351 
1352         if (!ical) {
1353             syslog(LOG_ERR,
1354                    "meth_delete: failed to parse iCalendar object %s:%u",
1355                    txn->req_tgt.mbentry->name, record->uid);
1356             return HTTP_SERVER_ERROR;
1357         }
1358 
1359         if (schedule_address) {
1360             strarray_appendm(&schedule_addresses, schedule_address);
1361             schedule_address = NULL;
1362         }
1363         get_schedule_addresses(txn, &schedule_addresses);
1364 
1365         /* XXX - after legacy records are gone, we can strip this and just not send a
1366          * cancellation if deleting a record which was never replied to... */
1367 
1368         char *userid = mboxname_to_userid(txn->req_tgt.mbentry->name);
1369         if (strarray_find_case(&schedule_addresses, cdata->organizer, 0) >= 0) {
1370             /* Organizer scheduling object resource */
1371             schedule_address = xstrdupnull(cdata->organizer);
1372             if (_scheduling_enabled(txn, mailbox))
1373                 sched_request(userid, schedule_address, ical, NULL);
1374         }
1375         else if (!(hdr = spool_getheader(txn->req_hdrs, "Schedule-Reply")) ||
1376                  strcasecmp(hdr[0], "F")) {
1377             /* Attendee scheduling object resource */
1378             schedule_address = xstrdupnull(strarray_nth(&schedule_addresses, 0));
1379             if (_scheduling_enabled(txn, mailbox) && schedule_address)
1380                 sched_reply(userid, schedule_address, ical, NULL);
1381         }
1382 
1383         free(userid);
1384         strarray_fini(&schedule_addresses);
1385     }
1386 
1387   done:
1388     if (ical) icalcomponent_free(ical);
1389     free(schedule_address);
1390     buf_free(&buf);
1391 
1392     return r;
1393 }
1394 
begin_icalendar(struct buf * buf)1395 static const char *begin_icalendar(struct buf *buf)
1396 {
1397     /* Begin iCalendar stream */
1398     buf_setcstr(buf, "BEGIN:VCALENDAR\r\n");
1399     buf_printf(buf, "PRODID:%s\r\n", ical_prodid);
1400     buf_appendcstr(buf, "VERSION:2.0\r\n");
1401 
1402     return "";
1403 }
1404 
end_icalendar(struct buf * buf)1405 static void end_icalendar(struct buf *buf)
1406 {
1407     /* End iCalendar stream */
1408     buf_setcstr(buf, "END:VCALENDAR\r\n");
1409 }
1410 
export_calendar(struct transaction_t * txn)1411 static int export_calendar(struct transaction_t *txn)
1412 {
1413     int ret = 0, r, precond;
1414     struct resp_body_t *resp_body = &txn->resp_body;
1415     struct buf *buf = &resp_body->payload;
1416     struct mailbox *mailbox = NULL;
1417     static char etag[33];
1418     struct hash_table tzid_table;
1419     static const char *displayname_annot =
1420         DAV_ANNOT_NS "<" XML_NS_DAV ">displayname";
1421     struct buf attrib = BUF_INITIALIZER;
1422     const char **hdr, *sep;
1423     struct mime_type_t *mime = NULL;
1424 
1425     /* Check requested MIME type:
1426        1st entry in caldav_mime_types array MUST be default MIME type */
1427     if ((hdr = spool_getheader(txn->req_hdrs, "Accept")))
1428         mime = get_accept_type(hdr, caldav_mime_types);
1429     else mime = caldav_mime_types;
1430     if (!mime) return HTTP_NOT_ACCEPTABLE;
1431 
1432     /* Open mailbox for reading */
1433     r = mailbox_open_irl(txn->req_tgt.mbentry->name, &mailbox);
1434     if (r) {
1435         syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
1436                txn->req_tgt.mbentry->name, error_message(r));
1437         txn->error.desc = error_message(r);
1438         ret = HTTP_SERVER_ERROR;
1439         goto done;
1440     }
1441 
1442     /* Check any preconditions */
1443     sprintf(etag, "%u-%u-%u",
1444             mailbox->i.uidvalidity, mailbox->i.last_uid, mailbox->i.exists);
1445     precond = check_precond(txn, etag, mailbox->index_mtime);
1446 
1447     switch (precond) {
1448     case HTTP_OK:
1449     case HTTP_NOT_MODIFIED:
1450         /* Fill in ETag, Last-Modified, Expires, and Cache-Control */
1451         txn->resp_body.etag = etag;
1452         txn->resp_body.lastmod = mailbox->index_mtime;
1453         txn->resp_body.maxage = 3600;  /* 1 hr */
1454         txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE;  /* don't use stale data */
1455 
1456         if (precond != HTTP_NOT_MODIFIED) break;
1457 
1458         GCC_FALLTHROUGH
1459 
1460     default:
1461         /* We failed a precondition - don't perform the request */
1462         ret = precond;
1463         goto done;
1464     }
1465 
1466     /* Setup for chunked response */
1467     txn->flags.te |= TE_CHUNKED;
1468     txn->flags.vary |= VARY_ACCEPT;
1469     txn->resp_body.type = mime->content_type;
1470 
1471     /* Set filename of resource */
1472     r = annotatemore_lookupmask(mailbox->name, displayname_annot,
1473                                 httpd_userid, &attrib);
1474     /* fall back to last part of mailbox name */
1475     if (r || !attrib.len) buf_setcstr(&attrib, strrchr(mailbox->name, '.') + 1);
1476 
1477     buf_reset(&txn->buf);
1478     buf_printf(&txn->buf, "%s.%s", buf_cstring(&attrib), mime->file_ext);
1479     txn->resp_body.fname = buf_cstring(&txn->buf);
1480 
1481     /* Short-circuit for HEAD request */
1482     if (txn->meth == METH_HEAD) {
1483         response_header(HTTP_OK, txn);
1484         return 0;
1485     }
1486 
1487     /* iCalendar data in response should not be transformed */
1488     txn->flags.cc |= CC_NOTRANSFORM;
1489 
1490     /* Create hash table for TZIDs */
1491     construct_hash_table(&tzid_table, 10, 1);
1492 
1493     /* Begin (converted) iCalendar stream */
1494     sep = mime->begin_stream(buf);
1495     write_body(HTTP_OK, txn, buf_cstring(buf), buf_len(buf));
1496 
1497     struct mailbox_iter *iter =
1498         mailbox_iter_init(mailbox, 0, ITER_SKIP_EXPUNGED|ITER_SKIP_DELETED);
1499 
1500     const message_t *msg;
1501     while ((msg = mailbox_iter_step(iter))) {
1502         const struct index_record *record = msg_record(msg);
1503         icalcomponent *ical;
1504 
1505         /* Map and parse existing iCalendar resource */
1506         ical = record_to_ical(mailbox, record, NULL);
1507 
1508         if (ical) {
1509             icalcomponent *comp;
1510 
1511             for (comp = icalcomponent_get_first_component(ical,
1512                                                           ICAL_ANY_COMPONENT);
1513                  comp;
1514                  comp = icalcomponent_get_next_component(ical,
1515                                                          ICAL_ANY_COMPONENT)) {
1516                 struct buf *cal_str;
1517                 icalcomponent_kind kind = icalcomponent_isa(comp);
1518 
1519                 /* Don't duplicate any TZIDs in our iCalendar */
1520                 if (kind == ICAL_VTIMEZONE_COMPONENT) {
1521                     icalproperty *prop =
1522                         icalcomponent_get_first_property(comp,
1523                                                          ICAL_TZID_PROPERTY);
1524                     const char *tzid = icalproperty_get_tzid(prop);
1525 
1526                     if (hash_lookup(tzid, &tzid_table)) continue;
1527                     else hash_insert(tzid, (void *)0xDEADBEEF, &tzid_table);
1528                 }
1529 
1530                 /* Include this component in our iCalendar */
1531                 if (r++ && *sep) {
1532                     /* Add separator, if necessary */
1533                     buf_reset(buf);
1534                     buf_printf_markup(buf, 0, sep);
1535                     write_body(0, txn, buf_cstring(buf), buf_len(buf));
1536                 }
1537                 cal_str = mime->from_object(comp);
1538                 write_body(0, txn, buf_base(cal_str), buf_len(cal_str));
1539                 buf_destroy(cal_str);
1540             }
1541 
1542             icalcomponent_free(ical);
1543         }
1544     }
1545 
1546     mailbox_iter_done(&iter);
1547 
1548     free_hash_table(&tzid_table, NULL);
1549 
1550     /* End (converted) iCalendar stream */
1551     mime->end_stream(buf);
1552     write_body(0, txn, buf_cstring(buf), buf_len(buf));
1553 
1554     /* End of output */
1555     write_body(0, txn, NULL, 0);
1556 
1557   done:
1558     buf_free(&attrib);
1559     mailbox_close(&mailbox);
1560 
1561     return ret;
1562 }
1563 
1564 
1565 /*
1566  * mboxlist_findall() callback function to list calendars
1567  */
1568 
1569 struct cal_info {
1570     char shortname[MAX_MAILBOX_NAME];
1571     char displayname[MAX_MAILBOX_NAME];
1572     unsigned flags;
1573     unsigned long types;
1574 };
1575 
1576 enum {
1577     CAL_IS_DEFAULT =    (1<<0),
1578     CAL_CAN_DELETE =    (1<<1),
1579     CAL_CAN_ADMIN =     (1<<2),
1580     CAL_IS_PUBLIC =     (1<<3),
1581     CAL_IS_TRANSP =     (1<<4)
1582 };
1583 
1584 struct list_cal_rock {
1585     struct cal_info *cal;
1586     unsigned len;
1587     unsigned alloc;
1588 };
1589 
list_cal_cb(const mbentry_t * mbentry,void * rock)1590 static int list_cal_cb(const mbentry_t *mbentry, void *rock)
1591 {
1592     struct list_cal_rock *lrock = (struct list_cal_rock *) rock;
1593     struct cal_info *cal;
1594     static size_t inboxlen = 0;
1595     static size_t outboxlen = 0;
1596     static size_t defaultlen = 0;
1597     char *shortname;
1598     size_t len;
1599     int r, rights, any_rights = 0;
1600     static const char *displayname_annot =
1601         DAV_ANNOT_NS "<" XML_NS_DAV ">displayname";
1602     static const char *schedtransp_annot =
1603         DAV_ANNOT_NS "<" XML_NS_CALDAV ">schedule-calendar-transp";
1604     static const char *calcompset_annot =
1605         DAV_ANNOT_NS "<" XML_NS_CALDAV ">supported-calendar-component-set";
1606     struct buf displayname = BUF_INITIALIZER, schedtransp = BUF_INITIALIZER;
1607     struct buf calcompset = BUF_INITIALIZER;
1608 
1609     if (!inboxlen) inboxlen = strlen(SCHED_INBOX) - 1;
1610     if (!outboxlen) outboxlen = strlen(SCHED_OUTBOX) - 1;
1611     if (!defaultlen) defaultlen = strlen(SCHED_DEFAULT) - 1;
1612 
1613     /* Make sure its a calendar */
1614     if (mbentry->mbtype != MBTYPE_CALENDAR) goto done;
1615 
1616     /* Make sure its readable */
1617     rights = httpd_myrights(httpd_authstate, mbentry);
1618     if ((rights & DACL_READ) != DACL_READ) goto done;
1619 
1620     /* Don't list scheduling Inbox/Outbox */
1621     shortname = strrchr(mbentry->name, '.') + 1;
1622     len = strlen(shortname);
1623 
1624     if ((len == inboxlen && !strncmp(shortname, SCHED_INBOX, inboxlen)) ||
1625         (len == outboxlen && !strncmp(shortname, SCHED_OUTBOX, outboxlen)))
1626         goto done;
1627 
1628     /* Lookup DAV:displayname */
1629     r = annotatemore_lookupmask(mbentry->name, displayname_annot,
1630                                 httpd_userid, &displayname);
1631     /* fall back to the last part of the mailbox name */
1632     if (r || !displayname.len) buf_setcstr(&displayname, shortname);
1633 
1634     /* Make sure we have room in our array */
1635     if (lrock->len == lrock->alloc) {
1636         lrock->alloc += 100;
1637         lrock->cal = xrealloc(lrock->cal,
1638                               lrock->alloc * sizeof(struct cal_info));
1639     }
1640 
1641     /* Add our calendar to the array */
1642     cal = &lrock->cal[lrock->len];
1643     strlcpy(cal->shortname, shortname, MAX_MAILBOX_NAME);
1644     strlcpy(cal->displayname, buf_cstring(&displayname), MAX_MAILBOX_NAME);
1645     cal->flags = 0;
1646 
1647     /* Is this the default calendar? */
1648     if (len == defaultlen && !strncmp(shortname, SCHED_DEFAULT, defaultlen)) {
1649         cal->flags |= CAL_IS_DEFAULT;
1650     }
1651 
1652     /* Can we delete this calendar? */
1653     else if (rights & DACL_RMCOL) {
1654         cal->flags |= CAL_CAN_DELETE;
1655     }
1656 
1657     /* Can we admin this calendar? */
1658     if (rights & DACL_ADMIN) {
1659         cal->flags |= CAL_CAN_ADMIN;
1660     }
1661 
1662     /* Is this calendar public? */
1663     if (mbentry->acl) {
1664         struct auth_state *auth_anyone = auth_newstate("anyone");
1665 
1666         any_rights = cyrus_acl_myrights(auth_anyone, mbentry->acl);
1667         auth_freestate(auth_anyone);
1668     }
1669     if ((any_rights & DACL_READ) == DACL_READ) {
1670         cal->flags |= CAL_IS_PUBLIC;
1671     }
1672 
1673     /* Is this calendar transparent? */
1674     r = annotatemore_lookupmask(mbentry->name, schedtransp_annot,
1675                                 httpd_userid, &schedtransp);
1676     if (!r && !strcmp(buf_cstring(&schedtransp), "transparent")) {
1677         cal->flags |= CAL_IS_TRANSP;
1678     }
1679     buf_free(&schedtransp);
1680 
1681     /* Which component types are supported? */
1682     r = annotatemore_lookupmask(mbentry->name, calcompset_annot,
1683                                 httpd_userid, &calcompset);
1684     if (!r && buf_len(&calcompset)) {
1685         cal->types = strtoul(buf_cstring(&calcompset), NULL, 10);
1686     }
1687     else {
1688         /* ALL component types */
1689         cal->types = -1;
1690     }
1691     buf_free(&calcompset);
1692 
1693     lrock->len++;
1694 
1695 done:
1696     buf_free(&displayname);
1697 
1698     return 0;
1699 }
1700 
cal_compare(const void * a,const void * b)1701 static int cal_compare(const void *a, const void *b)
1702 {
1703     struct cal_info *c1 = (struct cal_info *) a;
1704     struct cal_info *c2 = (struct cal_info *) b;
1705 
1706     return strcmp(c1->displayname, c2->displayname);
1707 }
1708 
1709 
1710 struct list_tzid_rock {
1711     struct buf *body;
1712     unsigned *level;
1713 };
1714 
list_tzid_cb(const char * tzid,int tzidlen,struct zoneinfo * zi,void * rock)1715 int list_tzid_cb(const char *tzid,
1716                  int tzidlen __attribute__((unused)),
1717                  struct zoneinfo *zi __attribute__((unused)),
1718                  void *rock)
1719 {
1720     struct list_tzid_rock *tzrock = (struct list_tzid_rock *) rock;
1721 
1722     /* Skip Etc and other non-standard zones */
1723     if (strnchr(tzid, '/', tzidlen) && strncmp(tzid, "Etc/", 4)) {
1724         buf_printf_markup(tzrock->body, *tzrock->level,
1725                           "<option>%.*s</option>", tzidlen, tzid);
1726     }
1727 
1728     return 0;
1729 }
1730 
1731 
1732 /* Create a HTML document listing all calendars available to the user */
list_calendars(struct transaction_t * txn)1733 static int list_calendars(struct transaction_t *txn)
1734 {
1735     int ret = 0, precond, rights;
1736     char mboxlist[MAX_MAILBOX_PATH+1];
1737     struct stat sbuf;
1738     time_t lastmod;
1739     const char *etag, *base_path = txn->req_tgt.path;
1740     unsigned level = 0, i;
1741     struct buf *body = &txn->resp_body.payload;
1742     struct list_cal_rock lrock;
1743     const char *proto = NULL;
1744     const char *host = NULL;
1745     const struct cal_comp_t *comp;
1746 #include "imap/http_caldav_js.h"
1747 
1748     /* stat() mailboxes.db for Last-Modified and ETag */
1749     snprintf(mboxlist, MAX_MAILBOX_PATH, "%s%s", config_dir, FNAME_MBOXLIST);
1750     stat(mboxlist, &sbuf);
1751     lastmod = MAX(compile_time, sbuf.st_mtime);
1752     assert(!buf_len(&txn->buf));
1753     buf_printf(&txn->buf, "%ld-%ld-%ld",
1754                compile_time, sbuf.st_mtime, sbuf.st_size);
1755 
1756     /* stat() config file for Last-Modified and ETag */
1757     stat(config_filename, &sbuf);
1758     lastmod = MAX(lastmod, sbuf.st_mtime);
1759     buf_printf(&txn->buf, "-%ld-%ld", sbuf.st_mtime, sbuf.st_size);
1760     etag = buf_cstring(&txn->buf);
1761 
1762     /* Check any preconditions */
1763     precond = check_precond(txn, etag, lastmod);
1764 
1765     switch (precond) {
1766     case HTTP_OK:
1767     case HTTP_NOT_MODIFIED:
1768         /* Fill in ETag, Last-Modified, and Expires */
1769         txn->resp_body.etag = etag;
1770         txn->resp_body.lastmod = lastmod;
1771         txn->flags.cc |= CC_REVALIDATE;
1772 
1773         if (precond != HTTP_NOT_MODIFIED) break;
1774 
1775         GCC_FALLTHROUGH
1776 
1777     default:
1778         /* We failed a precondition - don't perform the request */
1779         ret = precond;
1780         goto done;
1781     }
1782 
1783     /* Setup for chunked response */
1784     txn->flags.te |= TE_CHUNKED;
1785     txn->resp_body.type = "text/html; charset=utf-8";
1786 
1787     /* Short-circuit for HEAD request */
1788     if (txn->meth == METH_HEAD) {
1789         response_header(HTTP_OK, txn);
1790         goto done;
1791     }
1792 
1793     /* Send HTML header */
1794     buf_reset(body);
1795     buf_printf_markup(body, level, HTML_DOCTYPE);
1796     buf_printf_markup(body, level++, "<html>");
1797     buf_printf_markup(body, level++, "<head>");
1798     buf_printf_markup(body, level, "<title>%s</title>", "Available Calendars");
1799     buf_printf_markup(body, level++, "<script type=\"text/javascript\">");
1800     buf_appendcstr(body, "//<![CDATA[\n");
1801     buf_printf(body, (const char *) http_caldav_js,
1802                cyrus_version(), http_caldav_js_len);
1803     buf_appendcstr(body, "//]]>\n");
1804     buf_printf_markup(body, --level, "</script>");
1805     buf_printf_markup(body, level++, "<noscript>");
1806     buf_printf_markup(body, level, "<i>*** %s ***</i>",
1807                       "JavaScript required to create/modify/delete calendars");
1808     buf_printf_markup(body, --level, "</noscript>");
1809     buf_printf_markup(body, --level, "</head>");
1810     buf_printf_markup(body, level++, "<body>");
1811 
1812     write_body(HTTP_OK, txn, buf_cstring(body), buf_len(body));
1813     buf_reset(body);
1814 
1815     /* Check ACL for current user */
1816     rights = httpd_myrights(httpd_authstate, txn->req_tgt.mbentry);
1817 
1818     if (rights & DACL_MKCOL) {
1819         /* Add "create" form */
1820         struct list_tzid_rock tzrock = { body, &level };
1821 
1822         buf_printf_markup(body, level, "<h2>%s</h2>", "Create New Calendar");
1823         buf_printf_markup(body, level++, "<form name='create'>");
1824         buf_printf_markup(body, level++, "<table cellpadding=5>");
1825         buf_printf_markup(body, level++, "<tr>");
1826         buf_printf_markup(body, level, "<td align=right>Name:</td>");
1827         buf_printf_markup(body, level,
1828                           "<td><input name=name size=30 maxlength=40></td>");
1829         buf_printf_markup(body, --level, "</tr>");
1830 
1831         buf_printf_markup(body, level++, "<tr>");
1832         buf_printf_markup(body, level, "<td align=right>Description:</td>");
1833         buf_printf_markup(body, level,
1834                           "<td><input name=desc size=75 maxlength=120></td>");
1835         buf_printf_markup(body, --level, "</tr>");
1836 
1837         buf_printf_markup(body, level++, "<tr>");
1838         buf_printf_markup(body, level, "<td align=right>Components:</td>");
1839         buf_printf_markup(body, level++, "<td>");
1840         for (comp = cal_comps; comp->name; comp++) {
1841             buf_printf_markup(body, level,
1842                               "<input type=checkbox%s name=comp value=%s>%s",
1843                               !strcmp(comp->name, "VEVENT") ? " checked" : "",
1844                               comp->name, comp->name);
1845         }
1846         buf_printf_markup(body, --level, "</td>");
1847         buf_printf_markup(body, --level, "</tr>");
1848 
1849         if (namespace_calendar.allow & ALLOW_CAL_NOTZ) {
1850             buf_printf_markup(body, level++, "<tr>");
1851             buf_printf_markup(body, level, "<td align=right>Time Zone:</td>");
1852             buf_printf_markup(body, level++, "<td>");
1853             buf_printf_markup(body, level++, "<select name=tzid>");
1854             buf_printf_markup(body, level, "<option></option>");
1855             zoneinfo_find(NULL, 1, 0, &list_tzid_cb, &tzrock);
1856             buf_printf_markup(body, --level, "</select>");
1857             buf_printf_markup(body, --level, "</td>");
1858             buf_printf_markup(body, --level, "</tr>");
1859         }
1860 
1861         buf_printf_markup(body, level++, "<tr>");
1862         buf_printf_markup(body, level, "<td></td>");
1863         buf_printf_markup(body, level,
1864                           "<td><br><input type=button value='Create'"
1865                           " onclick=\"createCalendar('%s')\">"
1866                           " <input type=reset></td>",
1867                           base_path);
1868         buf_printf_markup(body, --level, "</tr>");
1869 
1870         buf_printf_markup(body, --level, "</table>");
1871         buf_printf_markup(body, --level, "</form>");
1872 
1873         buf_printf_markup(body, level, "<br><hr><br>");
1874 
1875         write_body(0, txn, buf_cstring(body), buf_len(body));
1876         buf_reset(body);
1877     }
1878 
1879     buf_printf_markup(body, level, "<h2>%s</h2>", "Available Calendars");
1880     buf_printf_markup(body, level++, "<table border cellpadding=5>");
1881 
1882     /* Create base URL for calendars */
1883     http_proto_host(txn->req_hdrs, &proto, &host);
1884     buf_reset(&txn->buf);
1885     buf_printf(&txn->buf, "%s://%s%s", proto, host, txn->req_tgt.path);
1886 
1887     memset(&lrock, 0, sizeof(struct list_cal_rock));
1888     mboxlist_mboxtree(txn->req_tgt.mbentry->name,
1889                       list_cal_cb, &lrock, MBOXTREE_SKIP_ROOT);
1890 
1891     /* Sort calendars by displayname */
1892     qsort(lrock.cal, lrock.len, sizeof(struct cal_info), &cal_compare);
1893 
1894     /* Add available calendars with action items */
1895     for (i = 0; i < lrock.len; i++) {
1896         struct cal_info *cal = &lrock.cal[i];
1897 
1898         /* Send a body chunk once in a while */
1899         if (buf_len(body) > PROT_BUFSIZE) {
1900             write_body(0, txn, buf_cstring(body), buf_len(body));
1901             buf_reset(body);
1902         }
1903 
1904         /* Calendar name */
1905         buf_printf_markup(body, level++, "<tr>");
1906         buf_printf_markup(body, level, "<td>%s%s%s",
1907                           (cal->flags & CAL_IS_DEFAULT) ? "<b>" : "",
1908                           cal->displayname,
1909                           (cal->flags & CAL_IS_DEFAULT) ? "</b>" : "");
1910 
1911         /* Supported components list */
1912         buf_printf_markup(body, level++, "<td>");
1913         buf_printf_markup(body, level++,
1914                           "<select multiple name=comp size=3"
1915                           " onChange=\"compsetCalendar('%s%s', '%s', this.options)\">",
1916                           base_path, cal->shortname, cal->displayname);
1917         for (comp = cal_comps; comp->name; comp++) {
1918             buf_printf_markup(body, level, "<option%s>%s</option>",
1919                               (cal->types & comp->type) ? " selected" : "",
1920                               comp->name);
1921         }
1922         buf_printf_markup(body, --level, "</select>");
1923         buf_printf_markup(body, --level, "</td>");
1924 
1925         /* Subscribe link */
1926         buf_printf_markup(body, level,
1927                           "<td><a href=\"webcal://%s%s%s\">Subscribe</a></td>",
1928                           host, base_path, cal->shortname);
1929 
1930         /* Download link */
1931         buf_printf_markup(body, level, "<td><a href=\"%s%s\">Download</a></td>",
1932                           base_path, cal->shortname);
1933 
1934         /* Delete button */
1935         buf_printf_markup(body, level,
1936                           "<td><input type=button%s value='Delete'"
1937                           " onclick=\"deleteCalendar('%s%s', '%s')\"></td>",
1938                           !(cal->flags & CAL_CAN_DELETE) ? " disabled" : "",
1939                           base_path, cal->shortname, cal->displayname);
1940 
1941         /* Public (shared) checkbox */
1942         buf_printf_markup(body, level,
1943                           "<td><input type=checkbox%s%s name=share"
1944                           " onclick=\"shareCalendar('%s%s', this.checked)\">"
1945                           "Public</td>",
1946                           !(cal->flags & CAL_CAN_ADMIN) ? " disabled" : "",
1947                           (cal->flags & CAL_IS_PUBLIC) ? " checked" : "",
1948                           base_path, cal->shortname);
1949 
1950         /* Transparent checkbox */
1951         buf_printf_markup(body, level,
1952                           "<td><input type=checkbox%s%s name=transp"
1953                           " onclick=\"transpCalendar('%s%s', this.checked)\">"
1954                           "Transparent</td>",
1955                           !(cal->flags & CAL_CAN_ADMIN) ? " disabled" : "",
1956                           (cal->flags & CAL_IS_TRANSP) ? " checked" : "",
1957                           base_path, cal->shortname);
1958 
1959         buf_printf_markup(body, --level, "</tr>");
1960     }
1961 
1962     free(lrock.cal);
1963 
1964     /* Finish list */
1965     buf_printf_markup(body, --level, "</table>");
1966 
1967     /* Finish HTML */
1968     buf_printf_markup(body, --level, "</body>");
1969     buf_printf_markup(body, --level, "</html>");
1970     write_body(0, txn, buf_cstring(body), buf_len(body));
1971 
1972     /* End of output */
1973     write_body(0, txn, NULL, 0);
1974 
1975   done:
1976     return ret;
1977 }
1978 
1979 
1980 /* Parse an RFC3339 date/time per
1981    http://www.calconnect.org/pubdocs/CD0903%20Freebusy%20Read%20URL.pdf */
icaltime_from_rfc3339_string(const char * str)1982 static struct icaltimetype icaltime_from_rfc3339_string(const char *str)
1983 {
1984     struct icaltimetype tt = icaltime_null_time();
1985     size_t size;
1986 
1987     size = strlen(str);
1988 
1989     if (size == 20) {
1990         /* UTC */
1991         if (sscanf(str, "%4u-%02u-%02uT%02u:%02u:%02uZ",
1992                    &tt.year, &tt.month, &tt.day,
1993                    &tt.hour, &tt.minute, &tt.second) < 6) {
1994             goto fail;
1995         }
1996 
1997         tt = icaltime_normalize(tt);
1998     }
1999     else if (size == 25) {
2000         /* TZ offset */
2001         int offset_hour, offset_minute;
2002         char offset_sign;
2003 
2004         if (sscanf(str, "%4u-%02u-%02uT%02u:%02u:%02u%c%02u:%02u",
2005                    &tt.year, &tt.month, &tt.day,
2006                    &tt.hour, &tt.minute, &tt.second,
2007                    &offset_sign, &offset_hour, &offset_minute) < 9) {
2008             goto fail;
2009         }
2010 
2011         if (offset_sign == '-') {
2012             /* negative offset */
2013             offset_hour *= -1;
2014             offset_minute *= -1;
2015         }
2016         else if (offset_sign != '+') {
2017             goto fail;
2018         }
2019 
2020         icaltime_adjust(&tt, 0, -offset_hour, -offset_minute, 0);
2021     }
2022     else {
2023         goto fail;
2024     }
2025 
2026     icaltime_set_utc(&tt, 1);
2027     return tt;
2028 
2029   fail:
2030     return icaltime_null_time();
2031 }
2032 
2033 
2034 struct timezone_rock {
2035     icalcomponent *old;
2036     icalcomponent *new;
2037 };
2038 
add_timezone(icalparameter * param,void * data)2039 static void add_timezone(icalparameter *param, void *data)
2040 {
2041     struct timezone_rock *tzrock = (struct timezone_rock *) data;
2042     const char *tzid = icalparameter_get_tzid(param);
2043 
2044     /* Check if this tz is in our new object */
2045     if (!icalcomponent_get_timezone(tzrock->new, tzid)) {
2046         icalcomponent *vtz = NULL;
2047 
2048         if (tzrock->old) {
2049             /* Fetch tz from old object and add to new */
2050             icaltimezone *tz = icalcomponent_get_timezone(tzrock->old, tzid);
2051             if (tz) vtz = icalcomponent_new_clone(icaltimezone_get_component(tz));
2052         }
2053         else {
2054             /* Fetch tz from our tzdist repository */
2055             struct buf buf = BUF_INITIALIZER;
2056             const char *path;
2057             int fd;
2058 
2059             /* Open and mmap the timezone file */
2060             buf_printf(&buf, "%s%s/%s.ics", config_dir, FNAME_ZONEINFODIR, tzid);
2061             path = buf_cstring(&buf);
2062 
2063             if ((fd = open(path, O_RDONLY)) != -1) {
2064                 struct buf data = BUF_INITIALIZER;
2065                 icalcomponent *ical;
2066 
2067                 buf_init_mmap(&data, 1, fd, path, MAP_UNKNOWN_LEN, NULL);
2068                 ical = ical_string_as_icalcomponent(&data);
2069                 vtz = icalcomponent_get_first_component(ical,
2070                                                         ICAL_VTIMEZONE_COMPONENT);
2071                 icalcomponent_remove_component(ical, vtz);
2072                 icalcomponent_free(ical);
2073                 buf_free(&data);
2074                 close(fd);
2075             }
2076             buf_free(&buf);
2077         }
2078 
2079         if (vtz) icalcomponent_add_component(tzrock->new, vtz);
2080     }
2081 }
2082 
2083 
2084 /* Perform a GET/HEAD request on a CalDAV resource */
caldav_get(struct transaction_t * txn,struct mailbox * mailbox,struct index_record * record,void * data,void ** obj)2085 static int caldav_get(struct transaction_t *txn, struct mailbox *mailbox,
2086                       struct index_record *record, void *data, void **obj)
2087 {
2088     int r, rights;
2089 
2090     if (!(txn->req_tgt.collection || txn->req_tgt.userid))
2091         return HTTP_NO_CONTENT;
2092 
2093     /* Check ACL for current user */
2094     rights = httpd_myrights(httpd_authstate, txn->req_tgt.mbentry);
2095     if ((rights & DACL_READ) != DACL_READ) {
2096         /* DAV:need-privileges */
2097         txn->error.precond = DAV_NEED_PRIVS;
2098         txn->error.resource = txn->req_tgt.path;
2099         txn->error.rights = DACL_READ;
2100         return HTTP_NO_PRIVS;
2101     }
2102 
2103     if (record && record->uid) {
2104         /* GET on a resource */
2105         struct caldav_data *cdata = (struct caldav_data *) data;
2106         unsigned need_tz = 0;
2107         const char **hdr;
2108         icalcomponent *ical;
2109         int ret = HTTP_CONTINUE;
2110 
2111         /* Check for optional CalDAV-Timezones header */
2112         hdr = spool_getheader(txn->req_hdrs, "CalDAV-Timezones");
2113         if (hdr && !strcmp(hdr[0], "T")) need_tz = 1;
2114 
2115         if (cdata->comp_flags.tzbyref) {
2116             if (!cdata->organizer && cdata->sched_tag) {
2117                 /* Resource has just had VTIMEZONEs stripped -
2118                    check if conditional matches previous ETag */
2119 
2120                 if (check_precond(txn, cdata->sched_tag,
2121                                   record->internaldate) == HTTP_NOT_MODIFIED) {
2122                     /* Fill in previous ETag and don't return Last-Modified */
2123                     txn->resp_body.etag = cdata->sched_tag;
2124                     txn->resp_body.lastmod = 0;
2125                     ret = HTTP_NOT_MODIFIED;
2126                 }
2127             }
2128             if (need_tz) {
2129                 /* Add VTIMEZONE components for known TZIDs */
2130                 struct timezone_rock tzrock = { NULL, NULL };
2131                 icalcomponent *comp, *next;
2132                 icalcomponent_kind kind;
2133 
2134                 *obj = ical = record_to_ical(mailbox, record, NULL);
2135                 tzrock.new = ical;
2136 
2137                 comp = icalcomponent_get_first_real_component(ical);
2138                 kind = icalcomponent_isa(comp);
2139                 for (; comp; comp = next) {
2140                     next = icalcomponent_get_next_component(ical, kind);
2141                     icalcomponent_foreach_tzid(comp, &add_timezone, &tzrock);
2142                 }
2143             }
2144         }
2145         else if (!need_tz && (namespace_calendar.allow & ALLOW_CAL_NOTZ)) {
2146             /* Strip known VTIMEZONEs */
2147             struct caldav_db *caldavdb = caldav_open_mailbox(mailbox);
2148             char *userid = NULL;
2149 
2150             mailbox_unlock_index(mailbox, NULL);
2151             r = mailbox_lock_index(mailbox, LOCK_EXCLUSIVE);
2152             if (r) {
2153                 syslog(LOG_ERR, "relock index(%s) failed: %s",
2154                        mailbox->name, error_message(r));
2155                 goto done;
2156             }
2157 
2158             *obj = ical = record_to_ical(mailbox, record, &userid);
2159 
2160             caldav_store_resource(txn, ical, mailbox,
2161                                   cdata->dav.resource, caldavdb,
2162                                   TZ_STRIP | (!cdata->sched_tag ? NEW_STAG : 0),
2163                                   userid);
2164             free(userid);
2165 
2166             /* Fetch the new DAV and index records */
2167             /* NOTE: previous contents of cdata was freed by store_resource */
2168             caldav_lookup_resource(caldavdb, mailbox->name,
2169                                    txn->req_tgt.resource, &cdata, /*tombstones*/0);
2170 
2171             mailbox_find_index_record(mailbox, cdata->dav.imap_uid, record);
2172 
2173             /* Fill in new ETag and Last-Modified */
2174             txn->resp_body.etag = message_guid_encode(&record->guid);
2175             txn->resp_body.lastmod = record->internaldate;
2176 
2177             caldav_close(caldavdb);
2178         }
2179 
2180         /* iCalendar data in response should not be transformed */
2181         txn->flags.cc |= CC_NOTRANSFORM;
2182 
2183       done:
2184         return ret;
2185     }
2186 
2187     if (txn->req_tgt.mbentry->server) {
2188         /* Remote mailbox */
2189         struct backend *be;
2190 
2191         be = proxy_findserver(txn->req_tgt.mbentry->server,
2192                               &http_protocol, httpd_userid,
2193                               &backend_cached, NULL, NULL, httpd_in);
2194         if (!be) return HTTP_UNAVAILABLE;
2195 
2196         return http_pipe_req_resp(be, txn);
2197     }
2198 
2199     /* Local Mailbox */
2200 
2201     if (txn->req_tgt.collection) {
2202         /* Download an entire calendar collection */
2203         return export_calendar(txn);
2204     }
2205     else if (txn->req_tgt.userid) {
2206         /* GET a list of calendars under calendar-home-set */
2207         return list_calendars(txn);
2208     }
2209 
2210     /* Unknown action */
2211     return HTTP_NO_CONTENT;
2212 }
2213 
2214 
2215 /* Perform a GET/HEAD request on a CalDAV/M-Attach resource */
meth_get_head_cal(struct transaction_t * txn,void * gparams)2216 static int meth_get_head_cal(struct transaction_t *txn,
2217                              void *gparams __attribute__((unused)))
2218 {
2219     int r;
2220 
2221     /* Parse the path */
2222     if ((r = caldav_parse_path(txn->req_uri->path,
2223                                &txn->req_tgt, &txn->error.desc))) return r;
2224 
2225     return meth_get_head(txn, (txn->req_tgt.flags == TGT_MANAGED_ATTACH) ?
2226                          &webdav_params : &caldav_params);
2227 }
2228 
2229 /* Decrement reference count on a managed attachment resource */
decrement_refcount(const char * managed_id,struct mailbox * attachments,struct webdav_db * webdavdb)2230 static void decrement_refcount(const char *managed_id,
2231                                struct mailbox *attachments,
2232                                struct webdav_db *webdavdb)
2233 {
2234     int r;
2235     struct webdav_data *wdata;
2236 
2237     /* Find DAV record for the attachment with this managed-id */
2238     webdav_lookup_uid(webdavdb, managed_id, &wdata);
2239 
2240     if (!wdata->dav.rowid) return;
2241 
2242     if (!--wdata->ref_count) {
2243         /* Delete attachment resource */
2244         struct index_record record;
2245 
2246         mailbox_find_index_record(attachments, wdata->dav.imap_uid, &record);
2247         record.system_flags |= FLAG_EXPUNGED;
2248 
2249         r = mailbox_rewrite_index_record(attachments, &record);
2250 
2251         if (r) {
2252             syslog(LOG_ERR, "expunging record (%s) failed: %s",
2253                    attachments->name, error_message(r));
2254         }
2255     }
2256     else {
2257         /* Update reference count on WebDAV record */
2258         r = webdav_write(webdavdb, wdata);
2259 
2260         if (r) {
2261             syslog(LOG_ERR, "updating ref count (%s) failed: %s",
2262                    wdata->dav.resource, error_message(r));
2263         }
2264     }
2265 }
2266 
2267 /* Increment reference count on a managed attachment resource */
increment_refcount(const char * managed_id,struct webdav_db * webdavdb)2268 static struct webdav_data *increment_refcount(const char *managed_id,
2269                                               struct webdav_db *webdavdb)
2270 {
2271     int r;
2272     struct webdav_data *wdata;
2273 
2274     /* Find DAV record for the attachment with this managed-id */
2275     webdav_lookup_uid(webdavdb, managed_id, &wdata);
2276 
2277     if (wdata->dav.rowid) {
2278         /* Update reference count on WebDAV record */
2279         wdata->ref_count++;
2280         r = webdav_write(webdavdb, wdata);
2281 
2282         if (r) {
2283             syslog(LOG_ERR, "updating ref count (%s) failed: %s",
2284                    wdata->dav.resource, error_message(r));
2285         }
2286     }
2287 
2288     return wdata;
2289 }
2290 
2291 /* Manage attachment */
caldav_post_attach(struct transaction_t * txn,int rights)2292 static int caldav_post_attach(struct transaction_t *txn, int rights)
2293 {
2294     int ret = 0, r, precond;
2295     struct resp_body_t *resp_body = &txn->resp_body;
2296     struct strlist *action, *mid, *rid;
2297     struct mime_type_t *mime = NULL;
2298     struct mailbox *calendar = NULL, *attachments = NULL;
2299     struct caldav_db *caldavdb = NULL;
2300     struct caldav_data *cdata;
2301     struct webdav_db *webdavdb = NULL;
2302     struct webdav_data *wdata;
2303     struct index_record record;
2304     char *schedule_address = NULL;
2305     const char *etag = NULL, **hdr;
2306     char *mailboxname = NULL;
2307     time_t lastmod = 0;
2308     icalcomponent *ical = NULL, *comp, *nextc, *master = NULL;
2309     icalcomponent_kind kind;
2310     icalproperty *aprop = NULL, *prop;
2311     icalparameter *param;
2312     unsigned op, return_rep;
2313     strarray_t *rids = NULL;
2314     enum {
2315         ATTACH_ADD,
2316         ATTACH_UPDATE,
2317         ATTACH_REMOVE
2318     };
2319 
2320     if (!(namespace_calendar.allow & ALLOW_CAL_ATTACH)) return HTTP_NOT_ALLOWED;
2321 
2322     /* Check ACL for current user */
2323     if (!(rights & DACL_WRITECONT)) {
2324         /* DAV:need-privileges */
2325         txn->error.precond = DAV_NEED_PRIVS;
2326         txn->error.resource = txn->req_tgt.path;
2327         txn->error.rights = DACL_WRITECONT;
2328         return HTTP_NO_PRIVS;
2329     }
2330 
2331     if ((return_rep = (get_preferences(txn) & PREFER_REP))) {
2332         /* Check requested MIME type:
2333            1st entry in gparams->mime_types array MUST be default MIME type */
2334         if ((hdr = spool_getheader(txn->req_hdrs, "Accept")))
2335             mime = get_accept_type(hdr, caldav_mime_types);
2336         else mime = caldav_mime_types;
2337         if (!mime) return HTTP_NOT_ACCEPTABLE;
2338     }
2339 
2340     /* Fetch and sanity check parameters */
2341     action = hash_lookup("action", &txn->req_qparams);
2342     mid = hash_lookup("managed-id", &txn->req_qparams);
2343     rid = hash_lookup("rid", &txn->req_qparams);
2344 
2345     if (!action || action->next) return HTTP_BAD_REQUEST;
2346     else if (!strcmp(action->s, "attachment-add")) {
2347         op = ATTACH_ADD;
2348         if (mid) return HTTP_BAD_REQUEST;
2349     }
2350     else if (!strcmp(action->s, "attachment-update")) {
2351         op = ATTACH_UPDATE;
2352         if (rid || !mid || mid->next) return HTTP_BAD_REQUEST;
2353     }
2354     else if (!strcmp(action->s, "attachment-remove")) {
2355         op = ATTACH_REMOVE;
2356         if (!mid || mid->next) return HTTP_BAD_REQUEST;
2357     }
2358     else return HTTP_BAD_REQUEST;
2359 
2360     /* Open calendar for writing */
2361     r = mailbox_open_iwl(txn->req_tgt.mbentry->name, &calendar);
2362     if (r) {
2363         syslog(LOG_ERR, "mailbox_open_iwl(%s) failed: %s",
2364                txn->req_tgt.mbentry->name, error_message(r));
2365         txn->error.desc = error_message(r);
2366         ret = HTTP_SERVER_ERROR;
2367         goto done;
2368     }
2369 
2370     /* Open the CalDAV DB corresponding to the calendar */
2371     caldavdb = caldav_open_mailbox(calendar);
2372 
2373     /* Find message UID for the cal resource */
2374     caldav_lookup_resource(caldavdb, txn->req_tgt.mbentry->name,
2375                            txn->req_tgt.resource, &cdata, 0);
2376     if (!cdata->dav.rowid) ret = HTTP_NOT_FOUND;
2377     else if (!cdata->dav.imap_uid) ret = HTTP_CONFLICT;
2378     if (ret) goto done;
2379 
2380     /* Fetch index record for the cal resource */
2381     memset(&record, 0, sizeof(struct index_record));
2382     r = mailbox_find_index_record(calendar, cdata->dav.imap_uid, &record);
2383     if (r) {
2384         txn->error.desc = error_message(r);
2385         ret = HTTP_SERVER_ERROR;
2386         goto done;
2387     }
2388 
2389     etag = message_guid_encode(&record.guid);
2390     lastmod = record.internaldate;
2391 
2392     /* Load and parse message containing the resource */
2393     ical = record_to_ical(calendar, &record, &schedule_address);
2394     comp = icalcomponent_get_first_real_component(ical);
2395     kind = icalcomponent_isa(comp);
2396 
2397     /* Check any preconditions */
2398     precond = caldav_check_precond(txn, &caldav_params,
2399                                    calendar, cdata, etag, lastmod);
2400 
2401     switch (precond) {
2402     case HTTP_OK:
2403         break;
2404 
2405     case HTTP_LOCKED:
2406         txn->error.precond = DAV_NEED_LOCK_TOKEN;
2407         txn->error.resource = txn->req_tgt.path;
2408 
2409         GCC_FALLTHROUGH
2410 
2411     default:
2412         /* We failed a precondition - don't perform the request */
2413         ret = precond;
2414 
2415         if ((precond == HTTP_PRECOND_FAILED) && return_rep) goto return_rep;
2416         else goto done;
2417     }
2418 
2419     /* Open attachments collection for writing */
2420     mailboxname = caldav_mboxname(httpd_userid, MANAGED_ATTACH);
2421     r = mailbox_open_iwl(mailboxname, &attachments);
2422     if (r) {
2423         syslog(LOG_ERR, "mailbox_open_iwl(%s) failed: %s",
2424                mailboxname, error_message(r));
2425         txn->error.desc = error_message(r);
2426         ret = HTTP_SERVER_ERROR;
2427         free(mailboxname);
2428         goto done;
2429     }
2430     free(mailboxname);
2431 
2432     /* Open the WebDAV DB corresponding to the attachments collection */
2433     webdavdb = webdav_open_mailbox(attachments);
2434 
2435     if (mid) {
2436         /* Locate first ATTACH property with this MANAGED-ID */
2437         do {
2438             for (aprop = icalcomponent_get_first_property(comp,
2439                                                           ICAL_ATTACH_PROPERTY);
2440                  aprop;
2441                  aprop = icalcomponent_get_next_property(comp,
2442                                                          ICAL_ATTACH_PROPERTY)) {
2443                 param = icalproperty_get_managedid_parameter(aprop);
2444                 if (param &&
2445                     !strcmp(mid->s, icalparameter_get_managedid(param))) break;
2446             }
2447 
2448             /* Check if this is master component */
2449             if (rid && !master &&
2450                 !icalcomponent_get_first_property(comp,
2451                                                   ICAL_RECURRENCEID_PROPERTY)) {
2452                 master = comp;
2453             }
2454 
2455         } while (!aprop &&
2456                  (comp = icalcomponent_get_next_component(ical, kind)));
2457 
2458         if (!aprop) {
2459             txn->error.precond = CALDAV_VALID_MANAGEDID;
2460             ret = HTTP_BAD_REQUEST;
2461             goto done;
2462         }
2463 
2464         /* Update reference count */
2465         decrement_refcount(mid->s, attachments, webdavdb);
2466     }
2467 
2468     if (op == ATTACH_REMOVE) aprop = NULL;
2469     else {
2470         /* SHA1 of content used as resource UID, resource name, & managed-id */
2471         static char uid[2*MESSAGE_GUID_SIZE+1];
2472         struct message_guid guid;
2473 
2474         /* Read body */
2475         txn->req_body.flags |= BODY_DECODE;
2476         r = http_read_body(httpd_in, httpd_out,
2477                            txn->req_hdrs, &txn->req_body, &txn->error.desc);
2478         if (r) {
2479             txn->flags.conn = CONN_CLOSE;
2480             return r;
2481         }
2482 
2483         /* Make sure we have a body */
2484         if (!buf_len(&txn->req_body.payload)) {
2485             txn->error.desc = "Missing request body";
2486             ret = HTTP_BAD_REQUEST;
2487             goto done;
2488         }
2489 
2490         /* Generate UID of body content */
2491         message_guid_generate(&guid, buf_base(&txn->req_body.payload),
2492                               buf_len(&txn->req_body.payload));
2493         strcpy(uid, message_guid_encode(&guid));
2494 
2495         /* Store the new/updated attachment using WebDAV callback */
2496         ret = webdav_params.put.proc(txn, &txn->req_body.payload,
2497                                      attachments, uid, webdavdb, 0);
2498 
2499         switch (ret) {
2500         case HTTP_CREATED:
2501         case HTTP_NO_CONTENT:
2502             resp_body->cmid = uid;
2503             resp_body->etag = NULL;
2504             break;
2505 
2506         default:
2507             goto done;
2508             break;
2509         }
2510 
2511         /* Update reference count */
2512         wdata = increment_refcount(uid, webdavdb);
2513 
2514         /* Create new ATTACH property */
2515         if (aprop) aprop = icalproperty_new_clone(aprop);
2516         else {
2517             const char *proto = NULL, *host = NULL;
2518             icalattach *attach;
2519 
2520             assert(!buf_len(&txn->buf));
2521             http_proto_host(txn->req_hdrs, &proto, &host);
2522             buf_printf(&txn->buf, "%s://%s%s/%s/%s/%s%s",
2523                        proto, host, namespace_calendar.prefix,
2524                        USER_COLLECTION_PREFIX,
2525                        txn->req_tgt.userid, MANAGED_ATTACH, uid);
2526             attach = icalattach_new_from_url(buf_cstring(&txn->buf));
2527             buf_reset(&txn->buf);
2528 
2529             aprop = icalproperty_new_attach(attach);
2530             icalattach_unref(attach);
2531         }
2532 
2533         /* Update ATTACH parameters - MANAGED-ID, FILENAME, SIZE, FMTTYPE */
2534         param = icalproperty_get_managedid_parameter(aprop);
2535         if (param) icalparameter_set_managedid(param, resp_body->cmid);
2536         else {
2537             param = icalparameter_new_managedid(resp_body->cmid);
2538             icalproperty_add_parameter(aprop, param);
2539         }
2540 
2541         if (wdata->filename) {
2542             param = icalproperty_get_filename_parameter(aprop);
2543             if (param) icalparameter_set_filename(param, wdata->filename);
2544             else {
2545                 param = icalparameter_new_filename(wdata->filename);
2546                 icalproperty_add_parameter(aprop, param);
2547             }
2548         }
2549 
2550         assert(!buf_len(&txn->buf));
2551         buf_printf(&txn->buf, "%tu", buf_len(&txn->req_body.payload));
2552         param = icalproperty_get_size_parameter(aprop);
2553         if (param) icalparameter_set_size(param, buf_cstring(&txn->buf));
2554         else {
2555             param = icalparameter_new_size(buf_cstring(&txn->buf));
2556             icalproperty_add_parameter(aprop, param);
2557         }
2558         buf_reset(&txn->buf);
2559 
2560         if ((hdr = spool_getheader(txn->req_hdrs, "Content-Type"))) {
2561             param = icalproperty_get_first_parameter(aprop,
2562                                                      ICAL_FMTTYPE_PARAMETER);
2563             if (param) icalparameter_set_fmttype(param, *hdr);
2564             else {
2565                 param = icalparameter_new_fmttype(*hdr);
2566                 icalproperty_add_parameter(aprop, param);
2567             }
2568         }
2569     }
2570 
2571     if (rid) {
2572         /* Split list of RECURRENCE-IDs */
2573         rids = strarray_split(rid->s, ",", STRARRAY_TRIM);
2574     }
2575 
2576     /* Process each component */
2577     for (; comp; comp = nextc) {
2578         int idx;
2579 
2580         nextc = icalcomponent_get_next_component(ical, kind);
2581 
2582         if (rid) {
2583             /* Check if RECURRENCE-ID is in our list */
2584             const char *recurid;
2585 
2586             prop = icalcomponent_get_first_property(comp,
2587                                                     ICAL_RECURRENCEID_PROPERTY);
2588             if (prop) recurid = icalproperty_get_value_as_string(prop);
2589             else {
2590                 master = comp;
2591                 recurid = "M";
2592             }
2593 
2594             idx = strarray_find_case(rids, recurid, 0);
2595             if (idx >= 0) {
2596                 /* Remove found recurid from list -
2597                    we will create new overrides for unfound recurids */
2598                 free(strarray_remove(rids, idx));
2599             }
2600             else if (!nextc && strarray_size(rids)) {
2601                 /* Create new overrides */
2602                 struct icaldatetimeperiodtype dtp;
2603                 icalproperty *nextp;
2604 
2605                 master = icalcomponent_new_clone(master);
2606 
2607                 /* Get DTSTART and Remove unwanted recurrence properties */
2608                 for (prop = icalcomponent_get_first_property(master,
2609                                                              ICAL_ANY_PROPERTY);
2610                      prop; prop = nextp) {
2611                     nextp = icalcomponent_get_next_property(master,
2612                                                             ICAL_ANY_PROPERTY);
2613                     switch (icalproperty_isa(prop)) {
2614                     case ICAL_RRULE_PROPERTY:
2615                     case ICAL_RDATE_PROPERTY:
2616                     case ICAL_EXDATE_PROPERTY:
2617                     case ICAL_EXRULE_PROPERTY:
2618                         icalcomponent_remove_property(master, prop);
2619                         icalproperty_free(prop);
2620                         break;
2621 
2622                     case ICAL_DTSTART_PROPERTY:
2623                         dtp = icalproperty_get_datetimeperiod(prop);
2624                         break;
2625 
2626                     default:
2627                         break;
2628                     }
2629                 }
2630 
2631                 /* Get TZID of DTSTART */
2632                 struct icaltimetype dtstart = dtp.time;
2633                 const icaltimezone *tz = icaltime_get_timezone(dtstart);
2634                 const char *tzid = icaltimezone_get_tzid((icaltimezone *) tz);
2635 
2636                 for (idx = 0; idx < strarray_size(rids); idx++) {
2637                     /* Create new component and set DTSTART and RECURRENCE-ID */
2638                     dtstart = icaltime_from_string(strarray_nth(rids, idx));
2639                     if (icaltime_is_null_time(dtstart)) continue;
2640 
2641                     icaltime_set_timezone(&dtstart, tz);
2642 
2643                     comp = icalcomponent_new_clone(master);
2644                     icalcomponent_add_component(ical, comp);
2645                     icalcomponent_set_dtstart(comp, dtstart);
2646 
2647                     prop = icalproperty_new_recurrenceid(dtstart);
2648                     icalcomponent_add_property(comp, prop);
2649                     if (tzid) {
2650                         icalproperty_add_parameter(prop,
2651                                                    icalparameter_new_tzid(tzid));
2652                     }
2653                 }
2654 
2655                 icalcomponent_free(master);
2656                 nextc = icalcomponent_get_next_component(ical, kind);
2657                 rid = NULL;
2658             }
2659             else {
2660                 /* No matching RECURRENCE-ID - Skip this component */
2661                 continue;
2662             }
2663         }
2664 
2665         if (mid) {
2666             /* Remove matching ATTACH property */
2667             for (prop = icalcomponent_get_first_property(comp,
2668                                                          ICAL_ATTACH_PROPERTY);
2669                  prop;
2670                  prop = icalcomponent_get_next_property(comp,
2671                                                         ICAL_ATTACH_PROPERTY)) {
2672                 param = icalproperty_get_managedid_parameter(prop);
2673                 if (param &&
2674                     !strcmp(mid->s, icalparameter_get_managedid(param))) {
2675                     icalcomponent_remove_property(comp, prop);
2676                     icalproperty_free(prop);
2677                     break;
2678                 }
2679             }
2680 
2681             if (!prop) {
2682                 /* No matching ATTACH - Skip this component */
2683                 continue;
2684             }
2685         }
2686 
2687         if (aprop) {
2688             /* Add new/updated ATTACH property */
2689             icalcomponent_add_property(comp, icalproperty_new_clone(aprop));
2690         }
2691     }
2692 
2693     /* Finished with attachment collection */
2694     mailbox_unlock_index(attachments, NULL);
2695 
2696     /* Store updated calendar resource */
2697     ret = caldav_store_resource(txn, ical, calendar, txn->req_tgt.resource,
2698                                 caldavdb, return_rep, schedule_address);
2699 
2700     if (ret == HTTP_NO_CONTENT && return_rep) {
2701         struct buf *data;
2702 
2703         ret = (op == ATTACH_ADD) ? HTTP_CREATED : HTTP_OK;
2704 
2705       return_rep:
2706         /* Convert into requested MIME type */
2707         data = mime->from_object(ical);
2708 
2709         /* Fill in Content-Type, Content-Length */
2710         resp_body->type = mime->content_type;
2711         resp_body->len = buf_len(data);
2712 
2713         /* Fill in Content-Location */
2714         resp_body->loc = txn->req_tgt.path;
2715 
2716         /* Fill in Expires and Cache-Control */
2717         resp_body->maxage = 3600;       /* 1 hr */
2718         txn->flags.cc = CC_MAXAGE
2719             | CC_REVALIDATE             /* don't use stale data */
2720             | CC_NOTRANSFORM;           /* don't alter iCal data */
2721 
2722         /* Output current representation */
2723         write_body(ret, txn, buf_base(data), buf_len(data));
2724 
2725         buf_destroy(data);
2726         ret = 0;
2727     }
2728 
2729   done:
2730     strarray_free(rids);
2731     free(schedule_address);
2732     if (aprop) icalproperty_free(aprop);
2733     if (ical) icalcomponent_free(ical);
2734     if (webdavdb) webdav_close(webdavdb);
2735     if (caldavdb) caldav_close(caldavdb);
2736     mailbox_close(&attachments);
2737     mailbox_close(&calendar);
2738 
2739     return ret;
2740 }
2741 
2742 
2743 /* Perform a busy time request */
caldav_post_outbox(struct transaction_t * txn,int rights)2744 static int caldav_post_outbox(struct transaction_t *txn, int rights)
2745 {
2746     int ret = 0, r;
2747     const char **hdr;
2748     struct mime_type_t *mime = NULL;
2749     icalcomponent *ical = NULL, *comp;
2750     icalcomponent_kind kind = 0;
2751     icalproperty_method meth = 0;
2752     icalproperty *prop = NULL;
2753     const char *uid = NULL, *organizer = NULL;
2754     struct caldav_sched_param sparam;
2755 
2756     /* Check Content-Type */
2757     if ((hdr = spool_getheader(txn->req_hdrs, "Content-Type"))) {
2758         for (mime = caldav_mime_types; mime->content_type; mime++) {
2759             if (is_mediatype(mime->content_type, hdr[0])) break;
2760         }
2761     }
2762     if (!mime || !mime->content_type) {
2763         txn->error.precond = CALDAV_SUPP_DATA;
2764         return HTTP_BAD_REQUEST;
2765     }
2766 
2767     /* Read body */
2768     txn->req_body.flags |= BODY_DECODE;
2769     r = http_read_body(httpd_in, httpd_out,
2770                        txn->req_hdrs, &txn->req_body, &txn->error.desc);
2771     if (r) {
2772         txn->flags.conn = CONN_CLOSE;
2773         return r;
2774     }
2775 
2776     /* Make sure we have a body */
2777     if (!buf_len(&txn->req_body.payload)) {
2778         txn->error.desc = "Missing request body\r\n";
2779         return HTTP_BAD_REQUEST;
2780     }
2781 
2782     /* Parse the iCal data for important properties */
2783     ical = mime->to_object(&txn->req_body.payload);
2784     if (!ical || !icalrestriction_check(ical)) {
2785         txn->error.precond = CALDAV_VALID_DATA;
2786         ret = HTTP_BAD_REQUEST;
2787         goto done;
2788     }
2789 
2790     meth = icalcomponent_get_method(ical);
2791     comp = icalcomponent_get_first_real_component(ical);
2792     if (comp) {
2793         uid = icalcomponent_get_uid(comp);
2794         kind = icalcomponent_isa(comp);
2795         prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
2796     }
2797 
2798     /* Check method preconditions */
2799     if (!meth || !uid || !prop) {
2800         txn->error.precond = CALDAV_VALID_SCHED;
2801         ret = HTTP_BAD_REQUEST;
2802         goto done;
2803     }
2804 
2805     /* Organizer MUST be local to use CalDAV Scheduling */
2806     organizer = icalproperty_get_organizer(prop);
2807     if (!organizer) {
2808         txn->error.precond = CALDAV_VALID_ORGANIZER;
2809         ret = HTTP_FORBIDDEN;
2810         goto done;
2811     }
2812     r = caladdress_lookup(organizer, &sparam, txn->req_tgt.userid);
2813     if (r) {
2814         txn->error.precond = CALDAV_VALID_ORGANIZER;
2815         ret = HTTP_FORBIDDEN;
2816         goto done;
2817     }
2818     if (!sparam.isyou) {
2819         sched_param_free(&sparam);
2820         txn->error.precond = CALDAV_VALID_ORGANIZER;
2821         ret = HTTP_FORBIDDEN;
2822         goto done;
2823     }
2824     sched_param_free(&sparam);
2825 
2826     switch (kind) {
2827     case ICAL_VFREEBUSY_COMPONENT:
2828         if (meth == ICAL_METHOD_REQUEST)
2829             if (!(rights & DACL_SCHEDFB)) {
2830                 /* DAV:need-privileges */
2831                 txn->error.precond = DAV_NEED_PRIVS;
2832                 txn->error.resource = txn->req_tgt.path;
2833                 txn->error.rights = DACL_SCHEDFB;
2834                 ret = HTTP_NO_PRIVS;
2835             }
2836             else ret = sched_busytime_query(txn, mime, ical);
2837         else {
2838             txn->error.precond = CALDAV_VALID_SCHED;
2839             ret = HTTP_BAD_REQUEST;
2840         }
2841         break;
2842 
2843     default:
2844         txn->error.precond = CALDAV_VALID_SCHED;
2845         ret = HTTP_BAD_REQUEST;
2846     }
2847 
2848   done:
2849     if (ical) icalcomponent_free(ical);
2850 
2851     return ret;
2852 }
2853 
2854 
2855 /* Perform a bulk import */
caldav_import(struct transaction_t * txn,void * obj,struct mailbox * mailbox,void * destdb,xmlNodePtr root,xmlNsPtr * ns,unsigned flags)2856 static int caldav_import(struct transaction_t *txn, void *obj,
2857                          struct mailbox *mailbox, void *destdb,
2858                          xmlNodePtr root, xmlNsPtr *ns, unsigned flags)
2859 {
2860     int ret = 0;
2861     icalcomponent *ical = obj, *comp, *next;
2862     icalcomponent_kind kind;
2863     icalproperty *prodid, *version, *calscale;
2864     const char *uid;
2865     struct caldav_db *caldavdb = destdb;
2866     xmlNodePtr resp, propstat, prop, error;
2867     unsigned post_count = 0;
2868 
2869     if (!root) {
2870         /* Validate the iCal data */
2871         if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) {
2872             txn->error.precond = CALDAV_VALID_DATA;
2873             return HTTP_FORBIDDEN;
2874         }
2875         icalrestriction_check(ical);
2876         if ((txn->error.desc = get_icalcomponent_errstr(ical))) {
2877             buf_setcstr(&txn->buf, txn->error.desc);
2878             txn->error.desc = buf_cstring(&txn->buf);
2879             txn->error.precond = CALDAV_VALID_DATA;
2880             return HTTP_FORBIDDEN;
2881         }
2882 
2883         /* Make sure each "real" component has a UID */
2884         for (comp = icalcomponent_get_first_component(ical, ICAL_ANY_COMPONENT);
2885              comp;
2886              comp = icalcomponent_get_next_component(ical, ICAL_ANY_COMPONENT)) {
2887 
2888             icalcomponent_kind kind = icalcomponent_isa(comp);
2889 
2890             if (kind == ICAL_VTIMEZONE_COMPONENT) continue;
2891 
2892             uid = icalcomponent_get_uid(comp);
2893             if (!uid) {
2894                 buf_reset(&txn->buf);
2895                 buf_printf(&txn->buf, "Missing UID property in %s",
2896                            icalcomponent_kind_to_string(kind));
2897                 txn->error.desc = buf_cstring(&txn->buf);
2898                 txn->error.precond = CALDAV_VALID_DATA;
2899                 return HTTP_FORBIDDEN;
2900             }
2901         }
2902 
2903         return 0;
2904     }
2905 
2906     ensure_ns(ns, NS_CALDAV, root, XML_NS_CALDAV, "C");
2907 
2908     size_t len = strlen(txn->req_tgt.path);
2909     txn->req_tgt.resource = txn->req_tgt.path + len;
2910 
2911     prodid = icalcomponent_get_first_property(ical, ICAL_PRODID_PROPERTY);
2912     version = icalcomponent_get_first_property(ical, ICAL_VERSION_PROPERTY);
2913     calscale = icalcomponent_get_first_property(ical, ICAL_CALSCALE_PROPERTY);
2914 
2915     for (comp = icalcomponent_get_first_component(ical, ICAL_ANY_COMPONENT);
2916          comp; comp = next) {
2917         icalcomponent *newical;
2918         struct timezone_rock tzrock;
2919 
2920         next = icalcomponent_get_next_component(ical, ICAL_ANY_COMPONENT);
2921         kind = icalcomponent_isa(comp);
2922         if (kind == ICAL_VTIMEZONE_COMPONENT) continue;
2923 
2924         /* Create new object, making copies of PRODID, VERSION, CALSCALE */
2925         newical = icalcomponent_new(ICAL_VCALENDAR_COMPONENT);
2926         if (prodid)
2927             icalcomponent_add_property(newical, icalproperty_new_clone(prodid));
2928         if (version)
2929             icalcomponent_add_property(newical, icalproperty_new_clone(version));
2930         if (calscale)
2931             icalcomponent_add_property(newical, icalproperty_new_clone(calscale));
2932 
2933         /* Add our component */
2934         icalcomponent_remove_component(ical, comp);
2935         icalcomponent_add_component(newical, comp);
2936 
2937         /* Look for matching UIDs (recurrence set) */
2938 
2939         /* Add required timezone components */
2940         tzrock.old = ical;
2941         tzrock.new = newical;
2942         icalcomponent_foreach_tzid(comp, &add_timezone, &tzrock);
2943 
2944         /* Append a unique resource name to URL and perform a PUT */
2945         uid = icalcomponent_get_uid(comp);
2946         txn->req_tgt.reslen =
2947             snprintf(txn->req_tgt.resource, MAX_MAILBOX_PATH - len,
2948                      "%x-%d-%ld-%u.ics",
2949                      strhash(uid), getpid(), time(0), post_count++);
2950 
2951         ret = caldav_put(txn, newical, mailbox,
2952                          txn->req_tgt.resource, caldavdb, flags);
2953 
2954         resp = xmlNewChild(root, ns[NS_DAV], BAD_CAST "response", NULL);
2955         if (!resp) {
2956             txn->error.desc = "Unable to add response XML element";
2957             return HTTP_SERVER_ERROR;
2958         }
2959 
2960         switch (ret) {
2961         case HTTP_OK:
2962         case HTTP_CREATED:
2963         case HTTP_NO_CONTENT:
2964             xml_add_href(resp, NULL, txn->req_tgt.path);
2965             propstat = xmlNewChild(resp, ns[NS_DAV], BAD_CAST "propstat", NULL);
2966             prop = xmlNewChild(propstat, ns[NS_DAV], BAD_CAST "prop", NULL);
2967 
2968             if (txn->resp_body.etag) {
2969                 xmlNewTextChild(prop, ns[NS_DAV], BAD_CAST "getetag",
2970                                 BAD_CAST txn->resp_body.etag);
2971             }
2972             if (flags & PREFER_REP) {
2973                 xmlNodePtr data = xmlNewChild(prop, ns[NS_CALDAV],
2974                                               BAD_CAST "calendar-data", NULL);
2975                 const char *icalstr = icalcomponent_as_ical_string(newical);
2976                 xmlAddChild(data, xmlNewCDataBlock(root->doc, BAD_CAST icalstr,
2977                                                    strlen(icalstr)));
2978             }
2979             else xmlNewTextChild(prop, ns[NS_CS], BAD_CAST "uid", BAD_CAST uid);
2980 
2981             xmlNewChild(propstat, ns[NS_DAV], BAD_CAST "status",
2982                         BAD_CAST http_statusline(HTTP_OK));
2983             break;
2984 
2985         default:
2986             xml_add_href(resp, NULL, NULL);
2987             xmlNewChild(resp, ns[NS_DAV], BAD_CAST "status",
2988                         BAD_CAST http_statusline(ret));
2989             error = xml_add_error(resp, &txn->error, ns);
2990             xmlNewTextChild(error, ns[NS_CS], BAD_CAST "uid", BAD_CAST uid);
2991             break;
2992         }
2993 
2994         icalcomponent_free(newical);
2995     }
2996 
2997     return 0;
2998 }
2999 
3000 
caldav_post(struct transaction_t * txn)3001 static int caldav_post(struct transaction_t *txn)
3002 {
3003     int ret, rights;
3004 
3005     /* Get rights for current user */
3006     rights = httpd_myrights(httpd_authstate, txn->req_tgt.mbentry);
3007 
3008     if (txn->req_tgt.resource) {
3009         if (txn->req_tgt.flags) {
3010             /* Don't allow POST on resources in special collections */
3011             ret = HTTP_NOT_ALLOWED;
3012         }
3013         else if (txn->req_tgt.mbentry->server) {
3014             /* Remote mailbox */
3015             struct backend *be;
3016 
3017             be = proxy_findserver(txn->req_tgt.mbentry->server,
3018                                   &http_protocol, httpd_userid,
3019                                   &backend_cached, NULL, NULL, httpd_in);
3020             if (!be) ret = HTTP_UNAVAILABLE;
3021             else ret = http_pipe_req_resp(be, txn);
3022         }
3023         else {
3024             /* Local Mailbox */
3025             ret = caldav_post_attach(txn, rights);
3026         }
3027     }
3028     else if (txn->req_tgt.flags == TGT_SCHED_OUTBOX) {
3029         /* POST to schedule-outbox */
3030         ret = caldav_post_outbox(txn, rights);
3031     }
3032     else if (txn->req_tgt.flags) {
3033         /* Don't allow POST to special collections */
3034         ret = HTTP_NOT_ALLOWED;
3035     }
3036     else if (!txn->req_tgt.collection) {
3037         /* POST to calendar-home-set */
3038         ret = notify_post(txn);
3039     }
3040     else {
3041         /* POST to regular calendar collection */
3042         ret = HTTP_CONTINUE;
3043     }
3044 
3045     return ret;
3046 }
3047 
3048 
3049 #ifdef HAVE_VPATCH
3050 enum {
3051     ACTION_UPDATE = 1,
3052     ACTION_DELETE,
3053     ACTION_SETPARAM
3054 };
3055 
3056 enum {
3057     SEGMENT_COMP = 1,
3058     SEGMENT_PROP,
3059     SEGMENT_PARAM
3060 };
3061 
3062 union match_criteria_t {
3063     struct {
3064         char *uid;                /* component UID (optional) */
3065         icaltimetype rid;         /* component RECURRENCE-ID (optional) */
3066     } comp;
3067     struct {
3068         char *param;              /* parameter name (optional) */
3069         char *value;              /* prop/param value (optional) */
3070         unsigned not:1;           /* not equal? */
3071     } prop;
3072 };
3073 
3074 struct path_segment_t {
3075     unsigned type;                    /* Is it comp, prop, or param segment? */
3076     unsigned kind;                    /* libical kind of comp, prop, or param */
3077     union match_criteria_t match;     /* match criteria (depends on 'type') */
3078     unsigned action;                  /* patch action (create,update,setparam)*/
3079     void *data;                       /* patch data (depends on 'action') */
3080 
3081     struct path_segment_t *sibling;
3082     struct path_segment_t *child;
3083 };
3084 
3085 struct patch_data_t {
3086     icalcomponent *patch;             /* component containg patch data */
3087     struct path_segment_t *delete;    /* list of PATCH-DELETE actions */
3088     struct path_segment_t *setparam;  /* list of PATCH-PARAMETER items */
3089 };
3090 
parse_target_path(char * path,struct path_segment_t ** path_seg,unsigned action,void * data,struct error_t * err)3091 static int parse_target_path(char *path, struct path_segment_t **path_seg,
3092                              unsigned action, void *data,
3093                              struct error_t *err)
3094 {
3095     char *p, sep;
3096     struct path_segment_t *tail = NULL, *new;
3097 
3098     for (sep = *path++; sep == '/';) {
3099         p = path + strcspn(path, "[/#");
3100         if ((sep = *p)) *p++ = '\0';
3101 
3102         new = xzmalloc(sizeof(struct path_segment_t));
3103         new->type = SEGMENT_COMP;
3104         new->kind = icalcomponent_string_to_kind(path);
3105         /* Initialize RID as invalid time rather than NULL time
3106            since NULL time is used for empty RID (master component) */
3107         new->match.comp.rid.year = -1;
3108 
3109         if (!*path_seg) *path_seg = new;
3110         else tail->child = new;
3111         tail = new;
3112 
3113         path = p;
3114 
3115         if (sep == '[') {
3116             /* Parse comp-match */
3117             const char *prefix = "UID=";
3118             size_t prefix_len = strlen(prefix);
3119 
3120             if (!(p = strchr(path, ']'))) {
3121                 err->desc = "Badly formatted comp-match";
3122                 return HTTP_BAD_REQUEST;
3123             }
3124 
3125             /* Parse uid-match */
3126             if (!strncmp(path, prefix, prefix_len)) {
3127                 path += prefix_len;
3128                 *p++ = '\0';
3129                 new->match.comp.uid = xstrdup(path);
3130                 sep = *p++;
3131                 path = p;
3132             }
3133 
3134             /* Parse rid-match */
3135             if (sep == '[') {
3136                 prefix = "RID=";
3137                 prefix_len = strlen(prefix);
3138 
3139                 if (strncmp(path, prefix, prefix_len) ||
3140                     !(p = strchr(path, ']'))) {
3141                     err->desc = "Badly formatted rid-match";
3142                     return HTTP_BAD_REQUEST;
3143                 }
3144 
3145                 path += prefix_len;
3146                 *p++ = '\0';
3147                 if (*path && strcmp(path, "M")) {
3148                     new->match.comp.rid = icaltime_from_string(path);
3149                     if (icaltime_is_null_time(new->match.comp.rid)) {
3150                         err->desc = "Invalid recurrence-id";
3151                         return HTTP_BAD_REQUEST;
3152                     }
3153                 }
3154                 else new->match.comp.rid = icaltime_null_time();
3155 
3156                 sep = *p++;
3157                 path = p;
3158             }
3159         }
3160     }
3161 
3162     if (sep == '#' && !*path_seg) {
3163         /* Parse prop-segment */
3164         p = path + strcspn(path, "[;=");
3165         if ((sep = *p)) *p++ = '\0';
3166 
3167         new = xzmalloc(sizeof(struct path_segment_t));
3168         new->type = SEGMENT_PROP;
3169         new->kind = icalproperty_string_to_kind(path);
3170 
3171         if (!*path_seg) *path_seg = new;
3172         else tail->child = new;
3173         tail = new;
3174 
3175         path = p;
3176 
3177         if (sep == '[') {
3178             /* Parse prop-match (MUST start with '=' or '!' or '@') */
3179             if (strspn(path, "=!@") != 1 || !(p = strchr(path, ']'))) {
3180                 err->desc = "Badly formatted prop-match";
3181                 return HTTP_BAD_REQUEST;
3182             }
3183 
3184             *p++ = '\0';
3185             if (*path == '@') {
3186                 /* Parse param-match */
3187                 size_t namelen = strcspn(++path, "!=");
3188                 new->match.prop.param = xstrndup(path, namelen);
3189                 path += namelen;
3190             }
3191 
3192             if (*path) {
3193                 /* Parse prop/param [not]equal value */
3194                 if (*path++ == '!') new->match.prop.not = 1;
3195                 new->match.prop.value = xstrdup(path);
3196             }
3197 
3198             sep = *p++;
3199             path = p;
3200         }
3201 
3202         if (sep == ';') {
3203             /* Parse param-segment */
3204             p = path + strcspn(path, "=");
3205             if ((sep = *p)) *p++ = '\0';
3206 
3207             new = xzmalloc(sizeof(struct path_segment_t));
3208             new->type = SEGMENT_PARAM;
3209             new->kind = icalparameter_string_to_kind(path);
3210 
3211             tail->child = new;
3212             tail = new;
3213 
3214             path = p;
3215         }
3216 
3217         if (sep == '=' && action == ACTION_DELETE) {
3218             /* Parse value-segment */
3219             new->data = xstrdup(path);
3220         }
3221         else if (sep != '\0') {
3222             err->desc = "Invalid separator following prop-segment";
3223             return HTTP_BAD_REQUEST;
3224         }
3225     }
3226     else if (sep != '\0') {
3227         err->desc = "Invalid separator following comp-segment";
3228         return HTTP_BAD_REQUEST;
3229     }
3230 
3231     tail->action = action;
3232     if (!tail->data) tail->data = data;
3233 
3234     return 0;
3235 }
3236 
3237 static void apply_patch(struct path_segment_t *path_seg,
3238                         void *parent, int *num_changes);
3239 
remove_single_value(const char * oldstr,const char * single)3240 static char *remove_single_value(const char *oldstr, const char *single)
3241 {
3242     char *newstr = NULL;
3243     strarray_t *values = strarray_split(oldstr, ",", STRARRAY_TRIM);
3244     int idx = strarray_find(values, single, 0);
3245 
3246     if (idx >= 0) {
3247         /* Found the single value, remove it, and create new string */
3248         strarray_remove(values, idx);
3249         newstr = strarray_join(values, ",");
3250     }
3251     strarray_free(values);
3252 
3253     return newstr;
3254 }
3255 
3256 /* Apply a patch action to a parameter segment */
apply_patch_parameter(struct path_segment_t * path_seg,icalproperty * parent,int * num_changes)3257 static void apply_patch_parameter(struct path_segment_t *path_seg,
3258                                   icalproperty *parent, int *num_changes)
3259 {
3260     icalparameter *param =
3261         icalproperty_get_first_parameter(parent, path_seg->kind);
3262     if (!param) return;
3263 
3264     if (path_seg->action == ACTION_DELETE) {
3265         switch (path_seg->kind) {
3266         case ICAL_MEMBER_PARAMETER:
3267             /* Multi-valued parameter */
3268             if (path_seg->data) {
3269                 /* Check if entire parameter value == single value */
3270                 const char *single = (const char *) path_seg->data;
3271                 const char *param_val = icalparameter_get_value_as_string(param);
3272 
3273                 if (strcmp(param_val, single)) {
3274                     /* Not an exact match, try to remove single value */
3275                     char *newval = remove_single_value(param_val, single);
3276                     if (newval) {
3277                         *num_changes += 1;
3278                         icalparameter_set_member(param, newval);
3279                         free(newval);
3280                     }
3281                     break;
3282                 }
3283             }
3284 
3285             /* Fall through and delete entire parameter */
3286             GCC_FALLTHROUGH
3287 
3288         default:
3289             *num_changes += 1;
3290             icalproperty_remove_parameter_by_ref(parent, param);
3291             break;
3292         }
3293     }
3294 }
3295 
apply_param_match(icalproperty * prop,union match_criteria_t * match)3296 static int apply_param_match(icalproperty *prop, union match_criteria_t *match)
3297 {
3298     icalparameter_kind kind;
3299     icalparameter *param;
3300     int ret = 1;
3301 
3302     /* XXX  Need to handle X- parameters */
3303 
3304     kind = icalparameter_string_to_kind(match->prop.param);
3305     param = icalproperty_get_first_parameter(prop, kind);
3306     if (!param) {
3307         /* property doesn't have this parameter */
3308         ret = match->prop.not;
3309     }
3310     else if (match->prop.value) {
3311         const char *param_val = icalparameter_get_value_as_string(param);
3312 
3313         ret = !strcmp(match->prop.value, param_val);
3314         if (match->prop.not) ret = !ret;  /* invert */
3315     }
3316 
3317     return ret;
3318 }
3319 
3320 /* Apply a patch action to a property segment */
apply_patch_property(struct path_segment_t * path_seg,icalcomponent * parent,int * num_changes)3321 static void apply_patch_property(struct path_segment_t *path_seg,
3322                                  icalcomponent *parent, int *num_changes)
3323 {
3324     icalproperty *prop, *nextprop;
3325     icalparameter *param;
3326 
3327     /* Iterate through each property */
3328     for (prop = icalcomponent_get_first_property(parent, path_seg->kind);
3329          prop; prop = nextprop) {
3330         nextprop = icalcomponent_get_next_property(parent, path_seg->kind);
3331 
3332         /* Check prop-match */
3333         int match = 1;
3334         if (path_seg->match.prop.param) {
3335             /* Check param-match */
3336             match = apply_param_match(prop, &path_seg->match);
3337         }
3338         else if (path_seg->match.prop.value) {
3339             /* Check prop-[not-]equal */
3340             const char *prop_val = icalproperty_get_value_as_string(prop);
3341 
3342             match = !strcmp(path_seg->match.prop.value, prop_val);
3343             if (path_seg->match.prop.not) match = !match;  /* invert */
3344         }
3345         if (!match) continue;
3346 
3347         if (path_seg->child) {
3348             /* Recurse into next segment */
3349             apply_patch(path_seg->child, prop, num_changes);
3350         }
3351         else if (path_seg->action == ACTION_DELETE) {
3352             /* Delete existing property */
3353             switch (path_seg->kind) {
3354             case ICAL_RDATE_PROPERTY:
3355             case ICAL_EXDATE_PROPERTY:
3356             case ICAL_FREEBUSY_PROPERTY:
3357             case ICAL_CATEGORIES_PROPERTY:
3358             case ICAL_RESOURCES_PROPERTY:
3359             case ICAL_ACCEPTRESPONSE_PROPERTY:
3360             case ICAL_POLLPROPERTIES_PROPERTY:
3361                 /* Multi-valued property */
3362                 if (path_seg->data) {
3363                     /* Check if entire property value == single value */
3364                     const char *single = (const char *) path_seg->data;
3365                     const char *propval = icalproperty_get_value_as_string(prop);
3366 
3367                     if (strcmp(propval, single)) {
3368                         /* Not an exact match, try to remove single value */
3369                         char *newval = remove_single_value(propval, single);
3370                         if (newval) {
3371                             *num_changes += 1;
3372                             icalproperty_set_value(prop,
3373                                                    icalvalue_new_string(newval));
3374                             free(newval);
3375                         }
3376                         break;
3377                     }
3378                 }
3379 
3380                 /* Fall through and delete entire property */
3381                 GCC_FALLTHROUGH
3382 
3383             default:
3384                 *num_changes += 1;
3385                 icalcomponent_remove_property(parent, prop);
3386                 icalproperty_free(prop);
3387                 break;
3388             }
3389         }
3390         else if (path_seg->action == ACTION_SETPARAM) {
3391             /* Set parameter(s) from those on PATCH-PARAMETER */
3392             icalproperty *pp_prop = (icalproperty *) path_seg->data;
3393 
3394             *num_changes += 1;
3395             for (param = icalproperty_get_first_parameter(pp_prop,
3396                                                           ICAL_ANY_PARAMETER);
3397                  param;
3398                  param = icalproperty_get_next_parameter(pp_prop,
3399                                                          ICAL_ANY_PARAMETER)) {
3400                 icalproperty_set_parameter(prop, icalparameter_new_clone(param));
3401             }
3402         }
3403     }
3404 }
3405 
create_override(icalcomponent * master,struct icaltime_span * span,void * rock)3406 static void create_override(icalcomponent *master, struct icaltime_span *span,
3407                             void *rock)
3408 {
3409     icalcomponent *new;
3410     icalproperty *prop, *next;
3411     struct icaltimetype dtstart, dtend, now;
3412     const icaltimezone *tz = NULL;
3413     const char *tzid;
3414     int is_date;
3415 
3416     now = icaltime_current_time_with_zone(utc_zone);
3417 
3418     new = icalcomponent_new_clone(master);
3419 
3420     for (prop = icalcomponent_get_first_property(new, ICAL_ANY_PROPERTY);
3421          prop; prop = next) {
3422         next = icalcomponent_get_next_property(new, ICAL_ANY_PROPERTY);
3423 
3424         switch (icalproperty_isa(prop)) {
3425         case ICAL_DTSTART_PROPERTY:
3426             /* Set DTSTART for this recurrence */
3427             dtstart = icalproperty_get_dtstart(prop);
3428             is_date = icaltime_is_date(dtstart);
3429             tz = icaltime_get_timezone(dtstart);
3430 
3431             dtstart = icaltime_from_timet_with_zone(span->start, is_date, tz);
3432             icaltime_set_timezone(&dtstart, tz);
3433             icalproperty_set_dtstart(prop, dtstart);
3434 
3435             /* Add RECURRENCE-ID for this recurrence */
3436             prop = icalproperty_new_recurrenceid(dtstart);
3437             tzid = icaltimezone_get_tzid((icaltimezone *) tz);
3438             if (tzid) {
3439                 icalproperty_add_parameter(prop, icalparameter_new_tzid(tzid));
3440             }
3441             icalcomponent_add_property(new, prop);
3442             break;
3443 
3444         case ICAL_DTEND_PROPERTY:
3445             /* Set DTEND for this recurrence */
3446             dtend = icalproperty_get_dtend(prop);
3447             is_date = icaltime_is_date(dtend);
3448             tz = icaltime_get_timezone(dtend);
3449 
3450             dtend = icaltime_from_timet_with_zone(span->end, is_date, tz);
3451             icaltime_set_timezone(&dtend, tz);
3452             icalproperty_set_dtend(prop, dtend);
3453             break;
3454 
3455         case ICAL_RRULE_PROPERTY:
3456         case ICAL_RDATE_PROPERTY:
3457         case ICAL_EXDATE_PROPERTY:
3458             /* Remove recurrence properties */
3459             icalcomponent_remove_property(new, prop);
3460             icalproperty_free(prop);
3461             break;
3462 
3463         case ICAL_DTSTAMP_PROPERTY:
3464             /* Update DTSTAMP */
3465             icalproperty_set_dtstamp(prop, now);
3466             break;
3467 
3468         case ICAL_CREATED_PROPERTY:
3469             /* Update CREATED */
3470             icalproperty_set_created(prop, now);
3471             break;
3472 
3473         case ICAL_LASTMODIFIED_PROPERTY:
3474             /* Update LASTMODIFIED */
3475             icalproperty_set_lastmodified(prop, now);
3476             break;
3477 
3478         default:
3479             break;
3480         }
3481     }
3482 
3483     *((icalcomponent **) rock) = new;
3484 }
3485 
3486 /* Apply property updates */
apply_property_updates(struct patch_data_t * patch,icalcomponent * parent,int * num_changes)3487 static void apply_property_updates(struct patch_data_t *patch,
3488                                    icalcomponent *parent, int *num_changes)
3489 {
3490     icalproperty *prop = NULL, *nextprop, *newprop;
3491 
3492     for (newprop = icalcomponent_get_first_property(patch->patch,
3493                                                     ICAL_ANY_PROPERTY);
3494          newprop;
3495          newprop = icalcomponent_get_next_property(patch->patch,
3496                                                    ICAL_ANY_PROPERTY)) {
3497         icalproperty_kind kind = icalproperty_isa(newprop);
3498         icalparameter_patchaction action = ICAL_PATCHACTION_BYNAME;
3499         icalparameter *actionp;
3500         union match_criteria_t byparam;
3501 
3502         memset(&byparam, 0, sizeof(union match_criteria_t));
3503         newprop = icalproperty_new_clone(newprop);
3504 
3505         actionp = icalproperty_get_first_parameter(newprop,
3506                                                    ICAL_PATCHACTION_PARAMETER);
3507         if (actionp) {
3508             action = icalparameter_get_patchaction(actionp);
3509             if (action == ICAL_PATCHACTION_X) {
3510                 /* libical treats DQUOTEd BYPARAM as X value */
3511                 const char *byparam_prefix = "BYPARAM@";
3512                 const char *x_val = icalparameter_get_xvalue(actionp);
3513                 if (!strncmp(x_val, byparam_prefix, strlen(byparam_prefix))) {
3514                     /* Parse param-match */
3515                     const char *p = x_val + strlen(byparam_prefix);
3516                     size_t namelen = strcspn(p, "!=");
3517                     byparam.prop.param = xstrndup(p, namelen);
3518                     p += namelen;
3519 
3520                     if (*p) {
3521                         if (*p++ == '!') byparam.prop.not = 1;
3522                         byparam.prop.value = xstrdup(p);
3523                     }
3524                     action = ICAL_PATCHACTION_BYPARAM;
3525                 }
3526             }
3527 
3528             icalproperty_remove_parameter_by_ref(newprop, actionp);
3529             icalparameter_free(actionp);
3530         }
3531 
3532         if (action != ICAL_PATCHACTION_CREATE) {
3533             /* Delete properties matching those being updated */
3534             const char *value = icalproperty_get_value_as_string(newprop);
3535 
3536             for (prop = icalcomponent_get_first_property(parent, kind);
3537                  prop; prop = nextprop) {
3538                 int match = 1;
3539 
3540                 nextprop = icalcomponent_get_next_property(parent, kind);
3541 
3542                 if (action == ICAL_PATCHACTION_BYVALUE) {
3543                     match = !strcmp(value,
3544                                     icalproperty_get_value_as_string(prop));
3545                 }
3546                 else if (action == ICAL_PATCHACTION_BYPARAM) {
3547                     /* Check param-match */
3548                     match = apply_param_match(prop, &byparam);
3549                     free(byparam.prop.param);
3550                     free(byparam.prop.value);
3551                 }
3552                 if (!match) continue;
3553 
3554                 icalcomponent_remove_property(parent, prop);
3555                 icalproperty_free(prop);
3556             }
3557         }
3558 
3559         *num_changes += 1;
3560         icalcomponent_add_property(parent, newprop);
3561     }
3562 }
3563 
3564 /* Apply property updates */
apply_component_updates(struct patch_data_t * patch,icalcomponent * parent,int * num_changes)3565 static void apply_component_updates(struct patch_data_t *patch,
3566                                     icalcomponent *parent, int *num_changes)
3567 {
3568     icalcomponent *comp, *nextcomp, *newcomp;
3569 
3570     for (newcomp = icalcomponent_get_first_component(patch->patch,
3571                                                      ICAL_ANY_COMPONENT);
3572          newcomp;
3573          newcomp = icalcomponent_get_next_component(patch->patch,
3574                                                     ICAL_ANY_COMPONENT)){
3575         icalcomponent_kind kind = icalcomponent_isa(newcomp);
3576         const char *uid = icalcomponent_get_uid(newcomp);
3577         icaltimetype rid = icalcomponent_get_recurrenceid(newcomp);
3578 
3579         newcomp = icalcomponent_new_clone(newcomp);
3580 
3581         /* Delete components matching those being updated */
3582         for (comp = icalcomponent_get_first_component(parent, kind);
3583              comp; comp = nextcomp) {
3584 
3585             nextcomp = icalcomponent_get_next_component(parent, kind);
3586 
3587             if (strcmp(uid, icalcomponent_get_uid(comp)) ||
3588                 icaltime_compare(rid, icalcomponent_get_recurrenceid(comp))) {
3589                 /* skip */
3590                 continue;
3591             }
3592 
3593             icalcomponent_remove_component(parent, comp);
3594             icalcomponent_free(comp);
3595         }
3596 
3597         *num_changes += 1;
3598         icalcomponent_add_component(parent, newcomp);
3599     }
3600 }
3601 
3602 /* Apply a patch action to a component segment */
apply_patch_component(struct path_segment_t * path_seg,icalcomponent * parent,int * num_changes)3603 static void apply_patch_component(struct path_segment_t *path_seg,
3604                                  icalcomponent *parent, int *num_changes)
3605 {
3606     icalcomponent *comp, *nextcomp, *master = NULL;
3607 
3608     /* Iterate through each component */
3609     if (path_seg->kind == ICAL_VCALENDAR_COMPONENT)
3610         comp = parent;
3611     else
3612         comp = icalcomponent_get_first_component(parent, path_seg->kind);
3613 
3614     for (; comp; comp = nextcomp) {
3615         nextcomp = icalcomponent_get_next_component(parent, path_seg->kind);
3616 
3617         /* Check comp-match */
3618         if (path_seg->match.comp.uid &&
3619             strcmp(path_seg->match.comp.uid, icalcomponent_get_uid(comp))) {
3620             continue;  /* UID doesn't match */
3621         }
3622 
3623         if (icaltime_is_valid_time(path_seg->match.comp.rid)) {
3624             icaltimetype recurid =
3625                 icalcomponent_get_recurrenceid_with_zone(comp);
3626 
3627             if (icaltime_is_null_time(recurid)) master = comp;
3628             if (icaltime_compare(recurid, path_seg->match.comp.rid)) {
3629                 if (!nextcomp && master) {
3630                     /* Possibly add an override recurrence.
3631                        Set start and end to coincide with recurrence */
3632                     icalcomponent *override = NULL;
3633                     struct icaltimetype start = path_seg->match.comp.rid;
3634                     struct icaltimetype end =
3635                         icaltime_add(start, icalcomponent_get_duration(master));
3636                     icalcomponent_foreach_recurrence(master, start, end,
3637                                                      &create_override,
3638                                                      &override);
3639                     if (!override) break;  /* Can't override - done */
3640 
3641                     /* Act on new overridden component */
3642                     icalcomponent_add_component(parent, override);
3643                     comp = override;
3644                 }
3645                 else continue;  /* RECURRENCE-ID doesn't match */
3646             }
3647         }
3648 
3649         if (path_seg->child) {
3650             /* Recurse into next segment */
3651             apply_patch(path_seg->child, comp, num_changes);
3652         }
3653         else if (path_seg->action == ACTION_DELETE) {
3654             /* Delete existing component */
3655             *num_changes += 1;
3656             icalcomponent_remove_component(parent, comp);
3657             icalcomponent_free(comp);
3658         }
3659         else if (path_seg->action == ACTION_UPDATE) {
3660             /* Patch existing component */
3661             struct patch_data_t *patch = (struct patch_data_t *) path_seg->data;
3662 
3663             /* Process all PATCH-DELETEs first */
3664             for (path_seg = patch->delete;
3665                  path_seg; path_seg = path_seg->child) {
3666                 apply_patch(path_seg, comp, num_changes);
3667             }
3668 
3669             /* Process all PATCH-SETPARAMETERs second */
3670             for (path_seg = patch->setparam;
3671                  path_seg; path_seg = path_seg->child) {
3672                 apply_patch(path_seg, comp, num_changes);
3673             }
3674 
3675             /* Process all components updates third */
3676             apply_component_updates(patch, comp, num_changes);
3677 
3678             /* Process all property updates last */
3679             apply_property_updates(patch, comp, num_changes);
3680         }
3681     }
3682 }
3683 
3684 /* Apply a patch action to a target segment */
apply_patch(struct path_segment_t * path_seg,void * parent,int * num_changes)3685 static void apply_patch(struct path_segment_t *path_seg,
3686                         void *parent, int *num_changes)
3687 {
3688     switch (path_seg->type) {
3689     case SEGMENT_COMP:
3690         apply_patch_component(path_seg, parent, num_changes);
3691         break;
3692 
3693     case SEGMENT_PROP:
3694         apply_patch_property(path_seg, parent, num_changes);
3695         break;
3696 
3697     case SEGMENT_PARAM:
3698         apply_patch_parameter(path_seg, parent, num_changes);
3699         break;
3700     }
3701 }
3702 
path_segment_free(struct path_segment_t * path_seg)3703 static void path_segment_free(struct path_segment_t *path_seg)
3704 {
3705     struct path_segment_t *next;
3706 
3707     for (; path_seg; path_seg = next) {
3708         next = path_seg->child;
3709 
3710         switch (path_seg->type) {
3711         case SEGMENT_COMP:
3712             free(path_seg->match.comp.uid);
3713             break;
3714 
3715         case SEGMENT_PROP:
3716             free(path_seg->match.prop.param);
3717             free(path_seg->match.prop.value);
3718             break;
3719 
3720         case SEGMENT_PARAM:
3721             break;
3722         }
3723 
3724         free(path_seg);
3725     }
3726 }
3727 
3728 
3729 /* Perform a PATCH request
3730  *
3731  * preconditions:
3732  */
caldav_patch(struct transaction_t * txn,void * obj)3733 static int caldav_patch(struct transaction_t *txn, void *obj)
3734 {
3735     icalcomponent *ical = (icalcomponent *) obj;
3736     icalcomponent *pdoc, *vpatch, *patch;
3737     icalproperty *prop;
3738     int num_changes = 0;
3739     int ret = 0;
3740 
3741     /* Validate the iCal patch */
3742     pdoc = ical_string_as_icalcomponent(&txn->req_body.payload);
3743     if (!pdoc || (icalcomponent_isa(pdoc) != ICAL_VCALENDAR_COMPONENT)) {
3744         txn->error.desc = "Missing VCALENDAR";
3745         txn->error.precond = CALDAV_VALID_DATA;
3746         ret = HTTP_BAD_REQUEST;
3747     }
3748     else if (!icalrestriction_check(pdoc) || icalcomponent_count_errors(pdoc)) {
3749         if ((txn->error.desc = get_icalcomponent_errstr(pdoc)) ||
3750             (txn->error.desc =
3751              get_icalcomponent_errstr(icalcomponent_get_first_real_component(pdoc)))) {
3752             buf_setcstr(&txn->buf, txn->error.desc);
3753             txn->error.desc = buf_cstring(&txn->buf);
3754         }
3755         txn->error.precond = CALDAV_VALID_DATA;
3756         ret = HTTP_BAD_REQUEST;
3757     }
3758     else if (!(vpatch = icalcomponent_get_first_real_component(pdoc)) ||
3759              icalcomponent_isa(vpatch) != ICAL_VPATCH_COMPONENT) {
3760         txn->error.desc = "Missing VPATCH";
3761         txn->error.precond = CALDAV_VALID_DATA;
3762         ret = HTTP_BAD_REQUEST;
3763     }
3764     else if ((prop =
3765               icalcomponent_get_first_property(vpatch,
3766                                                ICAL_PATCHVERSION_PROPERTY)) &&
3767              strcmp(icalproperty_get_patchversion(prop), "1")) {
3768         txn->error.desc = "Unsupported PATCH-VERSION";
3769         txn->error.precond = CALDAV_SUPP_DATA;
3770         ret = HTTP_BAD_REQUEST;
3771     }
3772 
3773     if (ret) goto done;
3774 
3775     /* Process each patch sub-component */
3776     for (patch = icalcomponent_get_first_component(vpatch, ICAL_ANY_COMPONENT);
3777          patch;
3778          patch = icalcomponent_get_next_component(vpatch, ICAL_ANY_COMPONENT)) {
3779 
3780         if (icalcomponent_isa(patch) != ICAL_XPATCH_COMPONENT) {
3781             /* Unknown patch action */
3782             txn->error.precond = CALDAV_SUPP_COMP;
3783             ret = HTTP_BAD_REQUEST;
3784             goto done;
3785         }
3786 
3787         prop = icalcomponent_get_first_property(patch,
3788                                                 ICAL_PATCHTARGET_PROPERTY);
3789         if (!prop) {
3790             txn->error.desc = "Missing TARGET";
3791             txn->error.precond = CALDAV_VALID_DATA;
3792             ret = HTTP_BAD_REQUEST;
3793             goto done;
3794         }
3795 
3796         /* Parse PATCH-TARGET */
3797         char *path = xstrdup(icalproperty_get_patchtarget(prop));
3798         struct path_segment_t *target = NULL, *next;
3799         struct patch_data_t patch_data = { patch, NULL, NULL };
3800 
3801         icalcomponent_remove_property(patch, prop);
3802         icalproperty_free(prop);
3803 
3804         ret = parse_target_path(path, &target,
3805                                 ACTION_UPDATE, &patch_data, &txn->error);
3806         free(path);
3807         if (!ret) {
3808             if (!target || target->type != SEGMENT_COMP ||
3809                 target->kind != ICAL_VCALENDAR_COMPONENT ||
3810                 target->match.comp.uid) {
3811                 txn->error.desc = "Initial segment of PATCH-TARGET"
3812                     " MUST be an unmatched VCALENDAR";
3813                 ret = HTTP_BAD_REQUEST;
3814             }
3815         }
3816 
3817         if (!ret) {
3818             /* Parse and remove all PATCH-DELETEs and PATCH-PARAMETERs */
3819             icalproperty *nextprop;
3820             for (prop =
3821                      icalcomponent_get_first_property(patch, ICAL_ANY_PROPERTY);
3822                  !ret && prop; prop = nextprop) {
3823 
3824                 icalproperty_kind kind = icalproperty_isa(prop);
3825                 struct path_segment_t *ppath = NULL;
3826 
3827                 nextprop =
3828                     icalcomponent_get_next_property(patch, ICAL_ANY_PROPERTY);
3829 
3830                 if (kind == ICAL_PATCHDELETE_PROPERTY) {
3831                     path = xstrdup(icalproperty_get_patchdelete(prop));
3832 
3833                     icalcomponent_remove_property(patch, prop);
3834                     icalproperty_free(prop);
3835 
3836                     ret = parse_target_path(path, &ppath,
3837                                             ACTION_DELETE, NULL, &txn->error);
3838                     free(path);
3839                     if (!ret) {
3840                         if (!ppath ||
3841                             (ppath->type == SEGMENT_COMP &&
3842                              ppath->kind == ICAL_VCALENDAR_COMPONENT)) {
3843                             txn->error.desc = "Initial segment of PATCH-DELETE"
3844                                 " MUST NOT be VCALENDAR";
3845                             ret = HTTP_BAD_REQUEST;
3846                         }
3847                         else {
3848                             /* Add this delete path to our list */
3849                             ppath->sibling = patch_data.delete;
3850                             patch_data.delete = ppath;
3851                         }
3852                     }
3853                 }
3854                 else if (kind == ICAL_PATCHPARAMETER_PROPERTY) {
3855                     path = xstrdup(icalproperty_get_patchparameter(prop));
3856 
3857                     icalcomponent_remove_property(patch, prop);
3858 
3859                     ret = parse_target_path(path, &ppath,
3860                                             ACTION_SETPARAM, prop, &txn->error);
3861                     free(path);
3862                     if (!ret) {
3863                         if (!ppath || ppath->type != SEGMENT_PROP) {
3864                             txn->error.desc =
3865                                 "Initial segment of PATCH-PARAMETER"
3866                                 " MUST be a property";
3867                             ret = HTTP_BAD_REQUEST;
3868                         }
3869                         else {
3870                             /* Add this setparam path to our list */
3871                             ppath->sibling = patch_data.setparam;
3872                             patch_data.setparam = ppath;
3873                         }
3874                     }
3875                 }
3876             }
3877         }
3878 
3879         /* Apply this patch to the target component */
3880         if (!ret) apply_patch(target, ical, &num_changes);
3881 
3882         /* Cleanup target paths */
3883         path_segment_free(target);
3884         for (target = patch_data.delete; target; target = next) {
3885             next = target->sibling;
3886             if (target->data) free(target->data);
3887             path_segment_free(target);
3888         }
3889         for (target = patch_data.setparam; target; target = next) {
3890             next = target->sibling;
3891             if (target->data) icalproperty_free(target->data);
3892             path_segment_free(target);
3893         }
3894 
3895         if (ret) goto done;
3896     }
3897 
3898   done:
3899     icalcomponent_free(pdoc);
3900 
3901     if (ret) return ret;
3902 
3903     /* If no changes are made,
3904        return HTTP_NO_CONTENT to suppress storing of resource */
3905     return (!num_changes ? HTTP_NO_CONTENT : 0);
3906 }
3907 #else
caldav_patch(struct transaction_t * txn,void * obj)3908 static int caldav_patch(struct transaction_t *txn __attribute__((unused)),
3909                         void *obj __attribute__((unused)))
3910 
3911 {
3912     fatal("caldav_patch() called, but no VPATCH", EC_SOFTWARE);
3913 }
3914 #endif /* HAVE_VPATCH */
3915 
3916 
3917 /* Perform a PUT request
3918  *
3919  * preconditions:
3920  *   CALDAV:valid-calendar-data
3921  *   CALDAV:valid-calendar-object-resource
3922  *   CALDAV:supported-calendar-component
3923  *   CALDAV:no-uid-conflict (DAV:href)
3924  *   CALDAV:max-resource-size
3925  *   CALDAV:min-date-time
3926  *   CALDAV:max-date-time
3927  *   CALDAV:max-instances
3928  *   CALDAV:max-attendees-per-instance
3929  */
caldav_put(struct transaction_t * txn,void * obj,struct mailbox * mailbox,const char * resource,void * destdb,unsigned flags)3930 static int caldav_put(struct transaction_t *txn, void *obj,
3931                       struct mailbox *mailbox, const char *resource,
3932                       void *destdb, unsigned flags)
3933 {
3934     int ret = 0;
3935     struct caldav_db *db = (struct caldav_db *)destdb;
3936     icalcomponent *ical = (icalcomponent *)obj;
3937     icalcomponent *oldical = NULL;
3938     icalcomponent *comp, *nextcomp;
3939     icalcomponent_kind kind;
3940     icalproperty *prop, *rrule = NULL;
3941     const char *uid, *organizer = NULL;
3942     char *schedule_address = NULL;
3943     struct buf buf = BUF_INITIALIZER;
3944     struct caldav_data *cdata;
3945 
3946     /* Validate the iCal data */
3947     if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) {
3948         txn->error.precond = CALDAV_VALID_DATA;
3949         ret = HTTP_FORBIDDEN;
3950         goto done;
3951     }
3952 
3953     icalrestriction_check(ical);
3954     if ((txn->error.desc = get_icalcomponent_errstr(ical))) {
3955         buf_setcstr(&txn->buf, txn->error.desc);
3956         txn->error.desc = buf_cstring(&txn->buf);
3957         txn->error.precond = CALDAV_VALID_DATA;
3958         ret = HTTP_FORBIDDEN;
3959         goto done;
3960     }
3961 
3962     comp = icalcomponent_get_first_real_component(ical);
3963     if (rscale_calendars) {
3964         /* Grab RRULE to check RSCALE */
3965         rrule = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY);
3966     }
3967 
3968     /* Make sure iCal UIDs [and ORGANIZERs] in all components are the same */
3969     kind = icalcomponent_isa(comp);
3970     uid = icalcomponent_get_uid(comp);
3971     if (!uid) {
3972         txn->error.precond = CALDAV_VALID_OBJECT;
3973         ret = HTTP_FORBIDDEN;
3974         goto done;
3975     }
3976     prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
3977     if (prop) organizer = icalproperty_get_organizer(prop);
3978     while ((nextcomp =
3979             icalcomponent_get_next_component(ical, kind))) {
3980         const char *nextuid = icalcomponent_get_uid(nextcomp);
3981 
3982         if (!nextuid || strcmp(uid, nextuid)) {
3983             txn->error.precond = CALDAV_VALID_OBJECT;
3984             ret = HTTP_FORBIDDEN;
3985             goto done;
3986         }
3987 
3988         const char *nextorg = NULL;
3989 
3990         prop = icalcomponent_get_first_property(nextcomp,
3991                                                 ICAL_ORGANIZER_PROPERTY);
3992         if (prop) nextorg = icalproperty_get_organizer(prop);
3993         /* if no toplevel organizer, use the one from here */
3994         if (!organizer && nextorg) organizer = nextorg;
3995         if (nextorg && strcmp(organizer, nextorg)) {
3996             txn->error.precond = CALDAV_SAME_ORGANIZER;
3997             ret = HTTP_FORBIDDEN;
3998             goto done;
3999         }
4000 
4001         if (rscale_calendars && !rrule) {
4002             /* Grab RRULE to check RSCALE */
4003             rrule = icalcomponent_get_first_property(nextcomp,
4004                                                      ICAL_RRULE_PROPERTY);
4005         }
4006     }
4007 
4008 #ifdef HAVE_RSCALE
4009     /* Make sure we support the provided RSCALE in an RRULE */
4010     if (rrule && rscale_calendars) {
4011         struct icalrecurrencetype rt = icalproperty_get_rrule(rrule);
4012 
4013         if (rt.rscale && *rt.rscale) {
4014             /* Perform binary search on sorted icalarray */
4015             unsigned found = 0, start = 0, end = rscale_calendars->num_elements;
4016 
4017             ucase((char *) rt.rscale);
4018             while (!found && start < end) {
4019                 unsigned mid = start + (end - start) / 2;
4020                 const char **rscale =
4021                     icalarray_element_at(rscale_calendars, mid);
4022                 int r = strcmp(rt.rscale, *rscale);
4023 
4024                 if (r == 0) found = 1;
4025                 else if (r < 0) end = mid;
4026                 else start = mid + 1;
4027             }
4028 
4029             if (!found) {
4030                 txn->error.precond = CALDAV_SUPP_RSCALE;
4031                 ret = HTTP_FORBIDDEN;
4032                 goto done;
4033             }
4034         }
4035     }
4036 #endif /* HAVE_RSCALE */
4037 
4038     /* Check for changed UID */
4039     caldav_lookup_resource(db, mailbox->name, resource, &cdata, 0);
4040     if (cdata->dav.imap_uid && strcmpsafe(cdata->ical_uid, uid)) {
4041         ret = HTTP_FORBIDDEN;
4042     }
4043     else {
4044         /* Check for duplicate iCalendar UID */
4045         caldav_lookup_uid(db, uid, &cdata);
4046         if (cdata->dav.imap_uid && (strcmp(cdata->dav.mailbox, mailbox->name) ||
4047                                     strcmp(cdata->dav.resource, resource))) {
4048             ret = HTTP_FORBIDDEN;
4049         }
4050     }
4051     if (ret) {
4052         /* CALDAV:no-uid-conflict */
4053         char *owner = mboxname_to_userid(cdata->dav.mailbox);
4054 
4055         txn->error.precond = CALDAV_UID_CONFLICT;
4056         buf_reset(&txn->buf);
4057         buf_printf(&txn->buf, "%s/%s/%s/%s/%s",
4058                    namespace_calendar.prefix, USER_COLLECTION_PREFIX, owner,
4059                    strrchr(cdata->dav.mailbox, '.')+1, cdata->dav.resource);
4060         txn->error.resource = buf_cstring(&txn->buf);
4061         free(owner);
4062         ret = HTTP_FORBIDDEN;
4063         goto done;
4064     }
4065 
4066     if (namespace_calendar.allow & ALLOW_CAL_ATTACH) {
4067         ret = manage_attachments(txn, mailbox, ical,
4068                                  cdata, &oldical, &schedule_address);
4069         if (ret) goto done;
4070     }
4071 
4072     switch (kind) {
4073     case ICAL_VEVENT_COMPONENT:
4074     case ICAL_VTODO_COMPONENT:
4075     case ICAL_VPOLL_COMPONENT:
4076         if (organizer) {
4077             /* Scheduling object resource */
4078             strarray_t schedule_addresses = STRARRAY_INITIALIZER;
4079             int r;
4080 
4081             syslog(LOG_DEBUG,
4082                    "caldav_put: organizer: %s", organizer);
4083 
4084             if (!strncasecmp(organizer, "mailto:", 7)) organizer += 7;
4085 
4086             if (cdata->organizer && !spool_getheader(txn->req_hdrs, "Allow-Organizer-Change")) {
4087                 /* Don't allow ORGANIZER to be changed */
4088                 if (strcmp(cdata->organizer, organizer)) {
4089                     txn->error.desc = "Can not change organizer address";
4090                     ret = HTTP_FORBIDDEN;
4091                 }
4092             }
4093 
4094             /* existing record? */
4095             if (cdata->dav.imap_uid && !oldical) {
4096                 /* Update existing object */
4097                 struct index_record record;
4098 
4099                 syslog(LOG_NOTICE, "LOADING ICAL %u", cdata->dav.imap_uid);
4100 
4101                 /* Load message containing the resource and parse iCal data */
4102                 r = mailbox_find_index_record(mailbox,
4103                                               cdata->dav.imap_uid, &record);
4104                 if (r) {
4105                     txn->error.desc = "Failed to read record \r\n";
4106                     ret = HTTP_SERVER_ERROR;
4107                     goto done;
4108                 }
4109 
4110                 oldical = record_to_ical(mailbox, &record, &schedule_address);
4111             }
4112 
4113             if (schedule_address) {
4114                 strarray_appendm(&schedule_addresses, schedule_address);
4115                 schedule_address = NULL;
4116             }
4117             get_schedule_addresses(txn, &schedule_addresses);
4118 
4119             char *userid = mboxname_to_userid(txn->req_tgt.mbentry->name);
4120             if (strarray_find_case(&schedule_addresses, organizer, 0) >= 0) {
4121                 /* Organizer scheduling object resource */
4122                 if (ret) {
4123                     txn->error.precond = CALDAV_ALLOWED_ORG_CHANGE;
4124                 }
4125                 else {
4126                     schedule_address = xstrdupnull(organizer);
4127                     if (_scheduling_enabled(txn, mailbox))
4128                         sched_request(userid, schedule_address, oldical, ical);
4129                 }
4130             }
4131             else {
4132                 /* Attendee scheduling object resource */
4133                 if (ret) {
4134                     txn->error.precond = CALDAV_ALLOWED_ATT_CHANGE;
4135                 }
4136 #if 0
4137                 else if (!oldical) {
4138                     /* Can't reply to a non-existent invitation */
4139                     /* XXX  But what about invites over iMIP? */
4140                     txn->error.desc = "Can not reply to non-existent resource";
4141                     ret = HTTP_FORBIDDEN;
4142                 }
4143 #endif
4144                 else {
4145                     schedule_address = xstrdupnull(strarray_nth(&schedule_addresses, 0));
4146                     if (_scheduling_enabled(txn, mailbox) && schedule_address)
4147                         sched_reply(userid, schedule_address, oldical, ical);
4148                 }
4149             }
4150             free(userid);
4151             strarray_fini(&schedule_addresses);
4152 
4153             if (ret) goto done;
4154 
4155             flags |= NEW_STAG;
4156         }
4157         break;
4158 
4159     case ICAL_VJOURNAL_COMPONENT:
4160     case ICAL_VFREEBUSY_COMPONENT:
4161     case ICAL_VAVAILABILITY_COMPONENT:
4162         /* Nothing else to do */
4163         break;
4164 
4165     default:
4166         txn->error.precond = CALDAV_SUPP_COMP;
4167         ret = HTTP_FORBIDDEN;
4168         goto done;
4169     }
4170 
4171     /* Store resource at target */
4172     if (!ret) {
4173         ret = caldav_store_resource(txn, ical, mailbox,
4174                                     resource, db, flags, schedule_address);
4175     }
4176 
4177   done:
4178     if (oldical) icalcomponent_free(oldical);
4179     free(schedule_address);
4180     buf_free(&buf);
4181 
4182     return ret;
4183 }
4184 
4185 
4186 struct comp_filter {
4187     unsigned comp_type;
4188     xmlChar *name;              /* need this for X- components */
4189     unsigned depth;
4190     icalcomponent_kind kind;
4191     unsigned allof       : 1;
4192     unsigned not_defined : 1;
4193     struct icalperiodtype *range;
4194     struct prop_filter *prop;
4195     struct comp_filter *comp;
4196     struct comp_filter *next;
4197 };
4198 
4199 struct calquery_filter {
4200     unsigned flags;             /* mask of flags controlling filter */
4201     unsigned comp_types;        /* mask of "real" component types in filter */
4202     icaltimezone *tz;
4203     struct comp_filter *comp;
4204 };
4205 
4206 /* Bitmask of calquery flags */
4207 enum {
4208     PARSE_ICAL = (1<<0)
4209 };
4210 
is_valid_timerange(const struct icaltimetype start,const struct icaltimetype end)4211 static int is_valid_timerange(const struct icaltimetype start,
4212                               const struct icaltimetype end)
4213 {
4214     return (icaltime_is_valid_time(start) && icaltime_is_valid_time(end) &&
4215             !icaltime_is_date(start) && !icaltime_is_date(end) &&
4216             (icaltime_is_utc(start) || start.zone) &&
4217             (icaltime_is_utc(end) || end.zone));
4218 }
4219 
parse_timerange(xmlNodePtr node,struct icalperiodtype ** range,struct error_t * error)4220 static void parse_timerange(xmlNodePtr node,
4221                             struct icalperiodtype **range, struct error_t *error)
4222 {
4223     xmlChar *attr;
4224     int count = 0;
4225 
4226     *range = xzmalloc(sizeof(struct icalperiodtype));
4227 
4228     attr = xmlGetProp(node, BAD_CAST "start");
4229     if (attr) {
4230         count++;
4231         (*range)->start = icaltime_from_string((char *) attr);
4232         xmlFree(attr);
4233     }
4234     else {
4235         (*range)->start =
4236             icaltime_from_timet_with_zone(caldav_epoch, 0, utc_zone);
4237     }
4238 
4239     attr = xmlGetProp(node, BAD_CAST "end");
4240     if (attr) {
4241         count++;
4242         (*range)->end = icaltime_from_string((char *) attr);
4243         xmlFree(attr);
4244     }
4245     else {
4246         (*range)->end =
4247             icaltime_from_timet_with_zone(caldav_eternity, 0, utc_zone);
4248     }
4249 
4250     if (!count || !is_valid_timerange((*range)->start, (*range)->end)) {
4251         error->precond = CALDAV_VALID_FILTER;
4252         error->desc = "Invalid time-range";
4253         error->node = xmlCopyNode(node->parent, 1);
4254     }
4255 }
4256 
cal_parse_propfilter(xmlNodePtr node,struct prop_filter * prop,struct error_t * error)4257 static void cal_parse_propfilter(xmlNodePtr node, struct prop_filter *prop,
4258                                  struct error_t *error)
4259 {
4260     if (!xmlStrcmp(node->name, BAD_CAST "time-range")) {
4261         if (prop->other) {
4262             error->precond = CALDAV_SUPP_FILTER;
4263             error->desc = "Multiple time-range";
4264             error->node = xmlCopyNode(node->parent, 1);
4265         }
4266         else {
4267             struct icalperiodtype *range = NULL;
4268             icalvalue_kind kind =
4269                 icalproperty_kind_to_value_kind(prop->kind);
4270 
4271             switch (kind) {
4272             case ICAL_DATE_VALUE:
4273             case ICAL_DATETIME_VALUE:
4274             case ICAL_DATETIMEPERIOD_VALUE:
4275             case ICAL_PERIOD_VALUE:
4276                 parse_timerange(node, &range, error);
4277                 prop->other = range;
4278                 break;
4279 
4280             default:
4281                 error->precond = CALDAV_SUPP_FILTER;
4282                 error->desc = "Property does not support time-range";
4283                 error->node = xmlCopyNode(node->parent, 1);
4284                 break;
4285             }
4286         }
4287     }
4288     else {
4289         error->precond = CALDAV_SUPP_FILTER;
4290         error->desc = "Unsupported element in prop-filter";
4291         error->node = xmlCopyNode(node->parent, 1);
4292     }
4293 }
4294 
4295 /* This handles calendar-query-extended per draft-daboo-caldav-extensions */
parse_compfilter(xmlNodePtr root,unsigned depth,struct comp_filter ** comp,unsigned * flags,unsigned * comp_types,struct error_t * error)4296 static void parse_compfilter(xmlNodePtr root, unsigned depth,
4297                              struct comp_filter **comp, unsigned *flags,
4298                              unsigned *comp_types, struct error_t *error)
4299 {
4300     xmlChar *attr;
4301     xmlNodePtr node;
4302     struct filter_profile_t profile =
4303         { 1 /* allof */, COLLATION_ASCII,
4304           CALDAV_SUPP_FILTER, CALDAV_SUPP_COLLATION,
4305           &icalproperty_string_to_kind, ICAL_NO_PROPERTY,
4306           &icalparameter_string_to_kind, ICAL_NO_PARAMETER,
4307           &cal_parse_propfilter };
4308 
4309     /* Parse elements of comp-filter */
4310     attr = xmlGetProp(root, BAD_CAST "name");
4311     if (!attr) {
4312         error->precond = CALDAV_SUPP_FILTER;
4313         error->desc = "Missing 'name' attribute";
4314         error->node = xmlCopyNode(root, 2);
4315     }
4316     else {
4317         icalcomponent_kind kind;
4318 
4319         if (!xmlStrcmp(attr, BAD_CAST "*")) kind = ICAL_ANY_COMPONENT;
4320         else kind = icalcomponent_string_to_kind((const char *) attr);
4321 
4322         *comp = xzmalloc(sizeof(struct comp_filter));
4323         (*comp)->name = attr;
4324         (*comp)->depth = depth;
4325         (*comp)->kind = kind;
4326         (*comp)->allof = 1;
4327 
4328         if (kind == ICAL_NO_COMPONENT) {
4329             error->precond = CALDAV_SUPP_FILTER;
4330             error->desc = "Unsupported component";
4331             error->node = xmlCopyNode(root, 2);
4332         }
4333         else {
4334             switch (depth) {
4335             case 0:
4336                 /* VCALENDAR */
4337                 if (kind != ICAL_VCALENDAR_COMPONENT) {
4338                     /* All other components MUST be a decendent of VCALENDAR */
4339                     error->precond = CALDAV_VALID_FILTER;
4340                     error->desc = "VCALENDAR must be toplevel component";
4341                 }
4342                 break;
4343 
4344             case 1:
4345                 /* Child of VCALENDAR */
4346                 switch (kind) {
4347                 case ICAL_VCALENDAR_COMPONENT:
4348                     /* VCALENDAR MUST only appear at toplevel */
4349                     error->precond = CALDAV_VALID_FILTER;
4350                     error->desc = "VCALENDAR can only be toplevel component";
4351                     break;
4352                 case ICAL_VEVENT_COMPONENT:
4353                     (*comp)->comp_type = CAL_COMP_VEVENT;
4354                     break;
4355                 case ICAL_VTODO_COMPONENT:
4356                     (*comp)->comp_type = CAL_COMP_VTODO;
4357                     break;
4358                 case ICAL_VJOURNAL_COMPONENT:
4359                     (*comp)->comp_type = CAL_COMP_VJOURNAL;
4360                     break;
4361                 case ICAL_VFREEBUSY_COMPONENT:
4362                     (*comp)->comp_type = CAL_COMP_VFREEBUSY;
4363                     break;
4364                 case ICAL_VAVAILABILITY_COMPONENT:
4365                     (*comp)->comp_type = CAL_COMP_VAVAILABILITY;
4366                     break;
4367                 case ICAL_VPOLL_COMPONENT:
4368                     (*comp)->comp_type = CAL_COMP_VPOLL;
4369                     break;
4370                 default:
4371                     *flags |= PARSE_ICAL;
4372                     break;
4373                 }
4374 
4375                 *comp_types |= (*comp)->comp_type;
4376                 break;
4377 
4378             default:
4379                 /* [Great*] grandchild of VCALENDAR */
4380                 if (kind == ICAL_VCALENDAR_COMPONENT) {
4381                     /* VCALENDAR MUST only appear at toplevel */
4382                     error->precond = CALDAV_VALID_FILTER;
4383                     error->desc = "VCALENDAR can only be toplevel component";
4384                 }
4385                 else *flags |= PARSE_ICAL;
4386                 break;
4387             }
4388         }
4389 
4390         if (!error->precond) {
4391             attr = xmlGetProp(root, BAD_CAST "test");
4392             if (attr) {
4393                 if (!xmlStrcmp(attr, BAD_CAST "anyof")) (*comp)->allof = 0;
4394                 else if (xmlStrcmp(attr, BAD_CAST "allof")) {
4395                     error->precond = CALDAV_SUPP_FILTER;
4396                     error->desc = "Unsupported test";
4397                     error->node = xmlCopyNode(root, 2);
4398                 }
4399                 xmlFree(attr);
4400             }
4401         }
4402     }
4403 
4404     for (node = xmlFirstElementChild(root); node && !error->precond;
4405          node = xmlNextElementSibling(node)) {
4406 
4407         if ((*comp)->not_defined) {
4408             error->precond = CALDAV_SUPP_FILTER;
4409             error->desc = DAV_FILTER_ISNOTDEF_ERR;
4410             error->node = xmlCopyNode(root, 1);
4411         }
4412         else if (!xmlStrcmp(node->name, BAD_CAST "is-not-defined")) {
4413             if ((*comp)->range || (*comp)->prop || (*comp)->comp) {
4414                 error->precond = CALDAV_SUPP_FILTER;
4415                 error->desc = DAV_FILTER_ISNOTDEF_ERR;
4416                 error->node = xmlCopyNode(root, 1);
4417             }
4418             else {
4419                 *flags |= PARSE_ICAL;
4420                 (*comp)->not_defined = 1;
4421                 if ((*comp)->comp_type) {
4422                     *comp_types &= ~(*comp)->comp_type;
4423                     (*comp)->comp_type = 0;
4424                 }
4425             }
4426         }
4427         else if (!xmlStrcmp(node->name, BAD_CAST "time-range")) {
4428             if ((*comp)->range) {
4429                 error->precond = CALDAV_SUPP_FILTER;
4430                 error->desc = "Multiple time-range";
4431                 error->node = xmlCopyNode(root, 1);
4432             }
4433             else {
4434                 switch ((*comp)->kind) {
4435                 case ICAL_ANY_COMPONENT:
4436                 case ICAL_VEVENT_COMPONENT:
4437                 case ICAL_VTODO_COMPONENT:
4438                 case ICAL_VJOURNAL_COMPONENT:
4439                 case ICAL_VFREEBUSY_COMPONENT:
4440                 case ICAL_VAVAILABILITY_COMPONENT:
4441                 case ICAL_VPOLL_COMPONENT:
4442                     parse_timerange(node, &(*comp)->range, error);
4443                     break;
4444 
4445                 default:
4446                     error->precond = CALDAV_SUPP_FILTER;
4447                     error->desc = "time-range unsupported for this component";
4448                     error->node = xmlCopyNode(root, 1);
4449                     break;
4450                 }
4451             }
4452 
4453             if (depth != 1) *flags |= PARSE_ICAL;
4454         }
4455         else if (!xmlStrcmp(node->name, BAD_CAST "prop-filter")) {
4456             struct prop_filter *prop = NULL;
4457 
4458             *flags |= PARSE_ICAL;
4459 
4460             dav_parse_propfilter(node, &prop, &profile, error);
4461             if (prop) {
4462                 if ((*comp)->prop) prop->next = (*comp)->prop;
4463                 (*comp)->prop = prop;
4464                 if (prop->match) {
4465                     if (prop->other || prop->match->next) {
4466                         error->precond = CALDAV_SUPP_FILTER;
4467                         error->desc = prop->match->next ? "Multiple text-match" :
4468                             "time-range can NOT be combined with text-match";
4469                         error->node = xmlCopyNode(node, 1);
4470                     }
4471                 }
4472             }
4473         }
4474         else if (!xmlStrcmp(node->name, BAD_CAST "comp-filter")) {
4475             struct comp_filter *subcomp = NULL;
4476 
4477             parse_compfilter(node, depth + 1, &subcomp,
4478                              flags, comp_types, error);
4479             if (subcomp) {
4480                 if ((*comp)->comp) subcomp->next = (*comp)->comp;
4481                 (*comp)->comp = subcomp;
4482             }
4483         }
4484         else {
4485             error->precond = CALDAV_SUPP_FILTER;
4486             error->desc = "Unsupported element in comp-filter";
4487             error->node = xmlCopyNode(root, 1);
4488         }
4489     }
4490 }
4491 
parse_calfilter(xmlNodePtr root,struct calquery_filter * filter,struct error_t * error)4492 static int parse_calfilter(xmlNodePtr root, struct calquery_filter *filter,
4493                            struct error_t *error)
4494 {
4495     xmlNodePtr node;
4496 
4497     /* Parse elements of filter */
4498     node = xmlFirstElementChild(root);
4499     if (node && !xmlStrcmp(node->name, BAD_CAST "comp-filter")) {
4500         parse_compfilter(node, 0, &filter->comp,
4501                          &filter->flags, &filter->comp_types, error);
4502     }
4503     else {
4504         error->precond = CALDAV_VALID_FILTER;
4505         error->desc = "missing comp-filter element";
4506     }
4507 
4508     return error->precond ? HTTP_FORBIDDEN : 0;
4509 }
4510 
4511 
apply_paramfilter(struct param_filter * paramfilter,icalproperty * prop)4512 static int apply_paramfilter(struct param_filter *paramfilter,
4513                              icalproperty *prop)
4514 {
4515     int pass = 1;
4516     icalparameter *param =
4517         icalproperty_get_first_parameter(prop, paramfilter->kind);
4518 
4519     if (paramfilter->kind == ICAL_X_PARAMETER) {
4520         /* Find the first X- parameter with matching name */
4521         for (; param && strcmp((const char *) paramfilter->name,
4522                                icalparameter_get_xname(param));
4523              param = icalproperty_get_next_parameter(prop, paramfilter->kind));
4524     }
4525 
4526     if (!param) return paramfilter->not_defined;
4527     if (paramfilter->not_defined) return 0;
4528     if (!paramfilter->match) return 1;
4529 
4530     /* Test each instance of this parameter (logical OR) */
4531     do {
4532         const char *text;
4533 
4534         if (!pass && (paramfilter->kind == ICAL_X_PARAMETER) &&
4535             strcmp((const char *) paramfilter->name,
4536                    icalparameter_get_xname(param))) {
4537             /* Skip X- parameter if name doesn't match */
4538             continue;
4539         }
4540 
4541         text = icalparameter_get_iana_value(param);
4542         pass = dav_apply_textmatch(BAD_CAST text, paramfilter->match);
4543 
4544     } while (!pass &&
4545              (param = icalproperty_get_next_parameter(prop, paramfilter->kind)));
4546 
4547     return pass;
4548 }
4549 
apply_prop_timerange(struct icalperiodtype * range,icalproperty * prop)4550 static int apply_prop_timerange(struct icalperiodtype *range, icalproperty *prop)
4551 {
4552     icalvalue *value = icalproperty_get_value(prop);
4553     struct icalperiodtype period = icalperiodtype_null_period();
4554     icalparameter *param;
4555 
4556     switch (icalvalue_isa(value)) {
4557     case ICAL_DATE_VALUE:
4558         period.start = icalvalue_get_date(value);
4559         period.start.is_date = 0;  /* MUST be DATE-TIME */
4560         break;
4561 
4562     case ICAL_DATETIME_VALUE:
4563         period.start = icalvalue_get_datetime(value);
4564         break;
4565 
4566     case ICAL_PERIOD_VALUE:
4567         period = icalvalue_get_period(value);
4568         break;
4569 
4570     default:
4571         /* Should never get here */
4572         break;
4573     }
4574 
4575     /* Set the timezone, if any, on the start time */
4576     if ((param = icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER))) {
4577         const char *tzid = icalparameter_get_tzid(param);
4578         icaltimezone *tz = NULL;
4579         icalcomponent *comp;
4580 
4581         for (comp = icalproperty_get_parent(prop); comp;
4582              comp = icalcomponent_get_parent(comp)) {
4583             tz = icalcomponent_get_timezone(comp, tzid);
4584             if (tz) break;
4585         }
4586 
4587         if (!tz) tz = icaltimezone_get_builtin_timezone_from_tzid(tzid);
4588 
4589         if (tz) period.start = icaltime_set_timezone(&period.start, tz);
4590     }
4591 
4592     if (!icaldurationtype_is_null_duration(period.duration)) {
4593         /* Calculate end time from duration */
4594         period.end = icaltime_add(period.start, period.duration);
4595     }
4596     else if (icaltime_is_null_time(period.end)) period.end = period.start;
4597 
4598     /* Convert to UTC for comparison with range */
4599     period.start = icaltime_convert_to_zone(period.start, utc_zone);
4600     period.end = icaltime_convert_to_zone(period.end, utc_zone);
4601 
4602     if (icaltime_compare(period.start, range->end) >= 0 ||
4603         icaltime_compare(period.end, range->start) <= 0) {
4604         /* Starts later or ends earlier than range */
4605         return 0;
4606     }
4607 
4608     return 1;
4609 }
4610 
apply_propfilter(struct prop_filter * propfilter,icalcomponent * comp)4611 static int apply_propfilter(struct prop_filter *propfilter, icalcomponent *comp)
4612 {
4613     int pass = 1;
4614     icalproperty *prop =
4615         icalcomponent_get_first_property(comp, propfilter->kind);
4616 
4617     if (propfilter->kind == ICAL_X_PROPERTY) {
4618         /* Find the first X- property with matching name */
4619         for (; prop && strcmp((const char *) propfilter->name,
4620                               icalproperty_get_property_name(prop));
4621              prop = icalcomponent_get_next_property(comp, propfilter->kind));
4622     }
4623 
4624     if (!prop) return propfilter->not_defined;
4625     if (propfilter->not_defined) return 0;
4626     if (!(propfilter->other || propfilter->match || propfilter->param)) return 1;
4627 
4628     /* Test each instance of this property (logical OR) */
4629     do {
4630         struct param_filter *paramfilter;
4631 
4632         if (!pass && (propfilter->kind == ICAL_X_PROPERTY) &&
4633             strcmp((const char *) propfilter->name,
4634                    icalproperty_get_property_name(prop))) {
4635             /* Skip X- property if name doesn't match */
4636             continue;
4637         }
4638 
4639         pass = propfilter->allof;
4640 
4641         if (propfilter->other) {
4642             pass = apply_prop_timerange(propfilter->other, prop);
4643         }
4644         else if (propfilter->match) {
4645             const char *text = icalproperty_get_value_as_string(prop);
4646 
4647             pass = dav_apply_textmatch(BAD_CAST text, propfilter->match);
4648         }
4649 
4650         /* Apply each param-filter, breaking if allof fails or anyof succeeds */
4651         for (paramfilter = propfilter->param;
4652              paramfilter && (pass == propfilter->allof);
4653              paramfilter = paramfilter->next) {
4654 
4655             pass = apply_paramfilter(paramfilter, prop);
4656         }
4657 
4658     } while (!pass &&
4659              (prop = icalcomponent_get_next_property(comp, propfilter->kind)));
4660 
4661     return pass;
4662 }
4663 
in_range(icalcomponent * comp,struct icaltime_span * span,void * rock)4664 static void in_range(icalcomponent *comp __attribute__((unused)),
4665                      struct icaltime_span *span __attribute__((unused)),
4666                      void *rock)
4667 {
4668     int *pass = (int *) rock;
4669 
4670     *pass = 1;
4671 }
4672 
apply_comp_timerange(struct comp_filter * compfilter,icalcomponent * comp,struct caldav_data * cdata,struct propfind_ctx * fctx)4673 static int apply_comp_timerange(struct comp_filter *compfilter,
4674                                 icalcomponent *comp, struct caldav_data *cdata,
4675                                 struct propfind_ctx *fctx)
4676 {
4677     struct icalperiodtype *range = compfilter->range;
4678     struct icaltimetype dtstart;
4679     struct icaltimetype dtend;
4680     int pass = 0;
4681 
4682     if (compfilter->depth == 1) {
4683         /* Use period from cdata */
4684         dtstart = icaltime_from_string(cdata->dtstart);
4685         dtend = icaltime_from_string(cdata->dtend);
4686 
4687         if (icaltime_compare(dtstart, range->end) >= 0 ||
4688             icaltime_compare(dtend, range->start) <= 0) {
4689             /* All occurrences start later or end earlier than range */
4690             return 0;
4691         }
4692         if (compfilter->kind == ICAL_VAVAILABILITY_COMPONENT) {
4693             /* Don't try to expand VAVAILABILITY, just mark it as in range */
4694             return 1;
4695         }
4696         if (cdata->comp_flags.recurring) {
4697             if (!(compfilter->prop || compfilter->comp) &&
4698                 (icaltime_compare(dtstart, range->start) >= 0 ||
4699                  icaltime_compare(dtend, range->end) <= 0)) {
4700                 /* An occurrence (possibly override) starts or ends within range
4701                    and we don't need to do further filtering of the comp */
4702                 return 1;
4703             }
4704 
4705             /* Load message containing the resource and parse iCal data */
4706             if (!comp) {
4707                 if (!fctx->msg_buf.len) {
4708                     mailbox_map_record(fctx->mailbox,
4709                                        fctx->record, &fctx->msg_buf);
4710                 }
4711                 if (fctx->msg_buf.len && !fctx->obj) {
4712                     fctx->obj =
4713                         icalparser_parse_string(buf_cstring(&fctx->msg_buf) +
4714                                                 fctx->record->header_size);
4715                 }
4716                 if (!fctx->obj) return 0;
4717                 comp = icalcomponent_get_first_component(fctx->obj,
4718                                                          compfilter->kind);
4719             }
4720         }
4721         else {
4722             /* Non-recurring component overlaps range */
4723             return 1;
4724         }
4725     }
4726 
4727     /* Process component */
4728     if (!comp) return 0;
4729 
4730     icalcomponent_foreach_recurrence(comp, range->start, range->end,
4731                                      in_range, &pass);
4732 
4733     return pass;
4734 }
4735 
4736 /* See if the current resource matches the specified filter.
4737  * Returns 1 if match, 0 otherwise.
4738  */
apply_compfilter(struct comp_filter * compfilter,icalcomponent * ical,struct caldav_data * cdata,struct propfind_ctx * fctx)4739 static int apply_compfilter(struct comp_filter *compfilter, icalcomponent *ical,
4740                             struct caldav_data *cdata, struct propfind_ctx *fctx)
4741 {
4742     int pass = 0;
4743     icalcomponent *comp = NULL;
4744 
4745     if (compfilter->comp_type &&
4746         (compfilter->comp_type != cdata->comp_type)) return 0;
4747     if (ical) {
4748         if (compfilter->kind == ICAL_VCALENDAR_COMPONENT) comp = ical;
4749         else comp = icalcomponent_get_first_component(ical, compfilter->kind);
4750     }
4751 
4752     /* XXX  Do we need to handle X- components?
4753        It doesn't appear that libical currently deals with them.
4754     */
4755 
4756     if (compfilter->not_defined) return (comp == NULL);
4757 
4758     if (!(compfilter->range || compfilter->prop || compfilter->comp)) return 1;
4759 
4760     /* Test each instance of this component (logical OR) */
4761     do {
4762         struct prop_filter *propfilter;
4763         struct comp_filter *subfilter;
4764 
4765         pass = compfilter->allof;
4766 
4767         if (compfilter->range) {
4768             pass = apply_comp_timerange(compfilter, comp, cdata, fctx);
4769         }
4770 
4771         /* Apply each prop-filter, breaking if allof fails or anyof succeeds */
4772         for (propfilter = compfilter->prop;
4773              propfilter && (pass == compfilter->allof);
4774              propfilter = propfilter->next) {
4775 
4776             pass = apply_propfilter(propfilter, comp);
4777         }
4778 
4779         /* Apply each comp-filter, breaking if allof fails or anyof succeeds */
4780         for (subfilter = compfilter->comp;
4781              subfilter && (pass == compfilter->allof);
4782              subfilter = subfilter->next) {
4783 
4784             pass = apply_compfilter(subfilter, comp, cdata, fctx);
4785         }
4786 
4787     } while (!pass &&
4788              (comp = icalcomponent_get_next_component(ical, compfilter->kind)));
4789 
4790     return pass;
4791 }
4792 
4793 /* See if the current resource matches the specified filter.
4794  * Returns 1 if match, 0 otherwise.
4795  */
apply_calfilter(struct propfind_ctx * fctx,void * data)4796 static int apply_calfilter(struct propfind_ctx *fctx, void *data)
4797 {
4798     struct calquery_filter *calfilter =
4799         (struct calquery_filter *) fctx->filter_crit;
4800     struct caldav_data *cdata = (struct caldav_data *) data;
4801     icalcomponent *ical = fctx->obj;
4802 
4803     if (calfilter->comp_types) {
4804         /* Check if we can short-circuit based on
4805            comp-filter(s) vs component type of resource */
4806         if (!(cdata->comp_type & calfilter->comp_types)) return 0;
4807         if (calfilter->comp->allof &&
4808             (cdata->comp_type & CAL_COMP_REAL) !=
4809             (calfilter->comp_types & CAL_COMP_REAL)) {
4810             return 0;
4811         }
4812     }
4813 
4814     if ((calfilter->flags & PARSE_ICAL) || cdata->comp_flags.recurring) {
4815         /* Load message containing the resource and parse iCal data */
4816         if (!ical) {
4817             if (!fctx->msg_buf.len)
4818                 mailbox_map_record(fctx->mailbox, fctx->record, &fctx->msg_buf);
4819             if (!fctx->msg_buf.len) return 0;
4820 
4821             ical = fctx->obj =
4822                 icalparser_parse_string(buf_cstring(&fctx->msg_buf) +
4823                                         fctx->record->header_size);
4824         }
4825         if (!ical) return 0;
4826     }
4827 
4828     return apply_compfilter(calfilter->comp, ical, cdata, fctx);
4829 }
4830 
4831 
free_compfilter(struct comp_filter * comp)4832 static void free_compfilter(struct comp_filter *comp)
4833 {
4834     struct comp_filter *subcomp, *nextc;
4835     struct prop_filter *prop, *nextp;
4836 
4837     if (!comp) return;
4838 
4839     xmlFree(comp->name);
4840     if (comp->range) free(comp->range);
4841 
4842     for (prop = comp->prop; prop; prop = nextp) {
4843         nextp = prop->next;
4844 
4845         if (prop->other) free(prop->other);
4846         dav_free_propfilter(prop);
4847     }
4848     for (subcomp = comp->comp; subcomp; subcomp = nextc) {
4849         nextc = subcomp->next;
4850 
4851         free_compfilter(subcomp);
4852     }
4853 
4854     free(comp);
4855 }
4856 
4857 
4858 /* dav_foreach() callback to find props on a CalDAV resource
4859  *
4860  * This function will strip any known VTIMEZONEs from the existing resource
4861  * and store as a new resource before returning properties
4862  */
caldav_propfind_by_resource(void * rock,void * data)4863 static int caldav_propfind_by_resource(void *rock, void *data)
4864 {
4865     struct propfind_ctx *fctx = (struct propfind_ctx *) rock;
4866     struct caldav_data *cdata = (struct caldav_data *) data;
4867 
4868     if (sqlite3_libversion_number() < 3003008) {
4869         /* Can't write to a table while a SELECT is active */
4870         goto done;
4871     }
4872 
4873     if (cdata->dav.imap_uid && !cdata->comp_flags.tzbyref) {
4874         struct index_record record;
4875         int r;
4876 
4877         /* Relock index for writing */
4878         if (!mailbox_index_islocked(fctx->mailbox, 1)) {
4879             mailbox_unlock_index(fctx->mailbox, NULL);
4880             r = mailbox_lock_index(fctx->mailbox, LOCK_EXCLUSIVE);
4881             if (r) {
4882                 syslog(LOG_ERR, "relock index(%s) failed: %s",
4883                        fctx->mailbox->name, error_message(r));
4884                 goto done;
4885             }
4886         }
4887 
4888         if (!fctx->record) {
4889             /* Fetch index record for the resource */
4890             r = mailbox_find_index_record(fctx->mailbox,
4891                                           cdata->dav.imap_uid, &record);
4892             /* XXX  Check errors */
4893 
4894             fctx->record = r ? NULL : &record;
4895         }
4896 
4897         if (fctx->record) {
4898             char *schedule_address = NULL;
4899             icalcomponent *ical =
4900                 record_to_ical(fctx->mailbox, fctx->record, &schedule_address);
4901             struct transaction_t txn;
4902 
4903             if (!ical) {
4904                 syslog(LOG_NOTICE,
4905                        "Unable to parse iCal %s:%u prior to stripping TZ",
4906                        fctx->mailbox->name, fctx->record->uid);
4907                 free(schedule_address);
4908                 goto done;
4909             }
4910 
4911             memset(&txn, 0, sizeof(struct transaction_t));
4912             txn.req_hdrs = spool_new_hdrcache();
4913 
4914             caldav_store_resource(&txn, ical, fctx->mailbox,
4915                                   cdata->dav.resource, fctx->davdb,
4916                                   TZ_STRIP | (!cdata->sched_tag ? NEW_STAG : 0),
4917                                   schedule_address);
4918             spool_free_hdrcache(txn.req_hdrs);
4919             buf_free(&txn.buf);
4920             free(schedule_address);
4921 
4922             icalcomponent_free(ical);
4923 
4924             caldav_lookup_resource(fctx->davdb, fctx->mailbox->name,
4925                                    cdata->dav.resource, &cdata, 0);
4926             fctx->record = NULL;
4927         }
4928     }
4929 
4930   done:
4931     return propfind_by_resource(rock, data);
4932 }
4933 
4934 
4935 /* Callback to fetch DAV:getcontenttype */
propfind_getcontenttype(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)4936 static int propfind_getcontenttype(const xmlChar *name, xmlNsPtr ns,
4937                                    struct propfind_ctx *fctx,
4938                                    xmlNodePtr prop __attribute__((unused)),
4939                                    xmlNodePtr resp __attribute__((unused)),
4940                                    struct propstat propstat[],
4941                                    void *rock __attribute__((unused)))
4942 {
4943     buf_setcstr(&fctx->buf, ICALENDAR_CONTENT_TYPE);
4944 
4945     if (fctx->data) {
4946         struct caldav_data *cdata = (struct caldav_data *) fctx->data;
4947         const char *comp = NULL;
4948 
4949         switch (cdata->comp_type) {
4950         case CAL_COMP_VEVENT: comp = "VEVENT"; break;
4951         case CAL_COMP_VTODO: comp = "VTODO"; break;
4952         case CAL_COMP_VJOURNAL: comp = "VJOURNAL"; break;
4953         case CAL_COMP_VFREEBUSY: comp = "VFREEBUSY"; break;
4954         case CAL_COMP_VAVAILABILITY: comp = "VAVAILABILITY"; break;
4955         case CAL_COMP_VPOLL: comp = "VPOLL"; break;
4956         }
4957 
4958         if (comp) buf_printf(&fctx->buf, "; component=%s", comp);
4959     }
4960 
4961     xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
4962                  name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
4963 
4964     return 0;
4965 }
4966 
4967 
4968 /* Callback to fetch DAV:resourcetype */
propfind_restype(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)4969 static int propfind_restype(const xmlChar *name, xmlNsPtr ns,
4970                             struct propfind_ctx *fctx,
4971                             xmlNodePtr prop __attribute__((unused)),
4972                             xmlNodePtr resp,
4973                             struct propstat propstat[],
4974                             void *rock)
4975 {
4976     xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
4977                                    &propstat[PROPSTAT_OK], name, ns, NULL, 0);
4978 
4979     if (!fctx->record) {
4980         xmlNewChild(node, NULL, BAD_CAST "collection", NULL);
4981 
4982         if (fctx->req_tgt->collection &&
4983             fctx->mbentry->mbtype == MBTYPE_CALENDAR) {
4984             ensure_ns(fctx->ns, NS_CALDAV,
4985                       resp ? resp->parent : node->parent, XML_NS_CALDAV, "C");
4986             if (!strcmp(fctx->req_tgt->collection, SCHED_INBOX)) {
4987                 xmlNewChild(node, fctx->ns[NS_CALDAV],
4988                             BAD_CAST "schedule-inbox", NULL);
4989             }
4990             else if (!strcmp(fctx->req_tgt->collection, SCHED_OUTBOX)) {
4991                 xmlNewChild(node, fctx->ns[NS_CALDAV],
4992                             BAD_CAST "schedule-outbox", NULL);
4993             }
4994             else {
4995                 xmlNewChild(node, fctx->ns[NS_CALDAV],
4996                             BAD_CAST "calendar", NULL);
4997                 if (rock) {
4998                     /* Called from PROPFIND - include "shared[-owner]" type */
4999                     xml_add_shareaccess(fctx, resp, node, 1 /* legacy */);
5000                 }
5001             }
5002         }
5003     }
5004 
5005     return 0;
5006 }
5007 
5008 #define PROP_NOVALUE (1<<31)
5009 
parse_partial_comp(xmlNodePtr node)5010 static struct partial_comp_t *parse_partial_comp(xmlNodePtr node)
5011 {
5012     xmlChar *prop;
5013     struct partial_comp_t *pcomp;
5014 
5015     prop = xmlGetProp(node, BAD_CAST "name");
5016     if (!prop) return NULL;
5017 
5018     pcomp = xzmalloc(sizeof(struct partial_comp_t));
5019     pcomp->kind = icalcomponent_string_to_kind((char *) prop);
5020     xmlFree(prop);
5021 
5022     for (node = xmlFirstElementChild(node); node;
5023          node = xmlNextElementSibling(node)) {
5024         if (!xmlStrcmp(node->name, BAD_CAST "allprop")) {
5025             arrayu64_add(&pcomp->props, ICAL_ANY_PROPERTY);
5026         }
5027         else if (!xmlStrcmp(node->name, BAD_CAST "prop")) {
5028             uint64_t kind = ICAL_NO_PROPERTY;
5029 
5030             prop = xmlGetProp(node, BAD_CAST "name");
5031             if (prop) {
5032                 kind = icalproperty_string_to_kind((char *) prop);
5033                 xmlFree(prop);
5034 
5035                 prop = xmlGetProp(node, BAD_CAST "novalue");
5036                 if (prop) {
5037                     if (!xmlStrcmp(prop, BAD_CAST "yes")) {
5038                         /* Set highest order bit to encode "novalue" */
5039                         kind |= PROP_NOVALUE;
5040                     }
5041                     xmlFree(prop);
5042                 }
5043             }
5044             arrayu64_add(&pcomp->props, kind);
5045         }
5046         else if (!xmlStrcmp(node->name, BAD_CAST "allcomp")) {
5047             pcomp->child = xzmalloc(sizeof(struct partial_comp_t));
5048             pcomp->child->kind = ICAL_ANY_COMPONENT;
5049             break;
5050         }
5051         else if (!xmlStrcmp(node->name, BAD_CAST "comp")) {
5052             struct partial_comp_t *child = parse_partial_comp(node);
5053             child->sibling = pcomp->child;
5054             pcomp->child = child;
5055         }
5056     }
5057 
5058     return pcomp;
5059 }
5060 
prune_properties(icalcomponent * parent,struct partial_comp_t * pcomp)5061 static void prune_properties(icalcomponent *parent,
5062                              struct partial_comp_t *pcomp)
5063 {
5064     icalcomponent *comp, *next;
5065     int n, size = arrayu64_size(&pcomp->props);
5066 
5067     if (!size || arrayu64_nth(&pcomp->props, 0) != ICAL_ANY_PROPERTY) {
5068         /* Strip unwanted properties from component */
5069         icalproperty *prop, *nextprop;
5070 
5071         for (prop = icalcomponent_get_first_property(parent, ICAL_ANY_PROPERTY);
5072              prop; prop = nextprop) {
5073             nextprop =
5074                 icalcomponent_get_next_property(parent, ICAL_ANY_PROPERTY);
5075 
5076             uint64_t kind;
5077 
5078             for (n = 0; n < size; n++) {
5079                 kind = arrayu64_nth(&pcomp->props, n);
5080                 /* Highest order bit encodes "novalue" */
5081                 if ((kind & ~PROP_NOVALUE) == icalproperty_isa(prop)) break;
5082             }
5083 
5084             if (n >= size) {
5085                 /* Don't want this property, remove it */
5086                 icalcomponent_remove_property(parent, prop);
5087                 icalproperty_free(prop);
5088             }
5089             else if (kind & PROP_NOVALUE) {
5090                 /* Don't want value for this property, remove it */
5091                 /* XXX  This requires libical (<= 2.x) to be compiled with
5092                    ICAL_ALLOW_EMPTY_PROPERTIES=true
5093                    otherwise icalproperty_as_ical_string() will output
5094                    "ERROR: No Value" as the property value */
5095                 icalproperty_set_value(prop, icalvalue_new(ICAL_NO_VALUE));
5096             }
5097         }
5098     }
5099 
5100     if (pcomp->child && pcomp->child->kind == ICAL_ANY_COMPONENT) return;
5101 
5102     /* Strip unwanted components from component */
5103     for (comp = icalcomponent_get_first_component(parent, ICAL_ANY_COMPONENT);
5104          comp; comp = next) {
5105         icalcomponent_kind kind = icalcomponent_isa(comp);
5106         struct partial_comp_t *child;
5107 
5108         next = icalcomponent_get_next_component(parent, ICAL_ANY_COMPONENT);
5109 
5110         for (child = pcomp->child; child; child = child->sibling) {
5111             if (child->kind == kind) break;
5112         }
5113         if (child) prune_properties(comp, child);
5114         else {
5115             icalcomponent_remove_component(parent, comp);
5116             icalcomponent_free(comp);
5117         }
5118     }
5119 }
5120 
expand_cb(icalcomponent * comp,icaltimetype start,icaltimetype end,void * rock)5121 static int expand_cb(icalcomponent *comp,
5122                      icaltimetype start, icaltimetype end, void *rock)
5123 {
5124     icalcomponent *ical = icalcomponent_get_parent(comp);
5125     icalcomponent *expanded_ical = (icalcomponent *) rock;
5126     icalproperty *prop, *nextprop, *recurid = NULL;
5127     struct icaldatetimeperiodtype dtp;
5128     icaltimetype dtstart;
5129 
5130     start = icaltime_convert_to_zone(start, utc_zone);
5131     end = icaltime_convert_to_zone(end, utc_zone);
5132 
5133     /* Fetch/set/remove interesting properties */
5134     for (prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY);
5135          prop; prop = nextprop) {
5136         nextprop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY);
5137 
5138         switch (icalproperty_isa(prop)) {
5139         case ICAL_DTSTART_PROPERTY:
5140             /* Fetch exiting DTSTART (might be master) */
5141             dtp = icalproperty_get_datetimeperiod(prop);
5142             dtstart = icaltime_convert_to_zone(dtp.time, utc_zone);
5143 
5144             /* Set DTSTART to be for this occurrence (in UTC) */
5145             icalproperty_set_dtstart(prop, start);
5146             icalproperty_remove_parameter_by_kind(prop, ICAL_TZID_PARAMETER);
5147             break;
5148 
5149         case ICAL_DTEND_PROPERTY:
5150             /* Set DTEND to be for this occurrence (in UTC) */
5151             icalproperty_set_dtend(prop, end);
5152             icalproperty_remove_parameter_by_kind(prop, ICAL_TZID_PARAMETER);
5153             break;
5154 
5155         case ICAL_DURATION_PROPERTY:
5156             /* Set DURATION to be for this occurrence */
5157             icalproperty_set_duration(prop, icaltime_subtract(end, start));
5158             break;
5159 
5160         case ICAL_RECURRENCEID_PROPERTY:
5161             /* Reset RECURRENCE-ID of this override to UTC */
5162             dtp = icalproperty_get_datetimeperiod(prop);
5163             dtp.time = icaltime_convert_to_zone(dtp.time, utc_zone);
5164             icalproperty_set_recurrenceid(prop, dtp.time);
5165             icalproperty_remove_parameter_by_kind(prop, ICAL_TZID_PARAMETER);
5166             recurid = prop;
5167 
5168             /* Remove component from existing ical (we can use it as-is)  */
5169             icalcomponent_remove_component(ical, comp);
5170             break;
5171 
5172         case ICAL_RRULE_PROPERTY:
5173         case ICAL_RDATE_PROPERTY:
5174         case ICAL_EXRULE_PROPERTY:
5175         case ICAL_EXDATE_PROPERTY:
5176             /* We don't want any recurrence rule properties */
5177             icalcomponent_remove_property(comp, prop);
5178             icalproperty_free(prop);
5179             break;
5180 
5181         default:
5182             break;
5183         }
5184     }
5185 
5186     if (!recurid) {
5187         /* Clone the master component */
5188         comp = icalcomponent_new_clone(comp);
5189         if (icaltime_compare(start, dtstart)) {
5190             /* Not the first instance - set RECURRENCE-ID */
5191             icalcomponent_set_recurrenceid(comp, start);
5192         }
5193     }
5194 
5195     /* Append the component to expanded ical */
5196     icalcomponent_add_component(expanded_ical, comp);
5197 
5198     return 1;
5199 }
5200 
5201 /* Expand recurrences of ical in range.
5202    NOTE: expand_cb() is destructive of ical as it builds expanded_ical */
expand_caldata(icalcomponent ** ical,struct icalperiodtype range)5203 static icalcomponent *expand_caldata(icalcomponent **ical,
5204                                      struct icalperiodtype range)
5205 {
5206     icalcomponent *expanded_ical =
5207         icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT,
5208                             icalproperty_new_version("2.0"),
5209                             icalproperty_new_prodid(ical_prodid),
5210                             0);
5211 
5212     /* Copy over any CALSCALE property */
5213     icalproperty *prop =
5214         icalcomponent_get_first_property(*ical, ICAL_CALSCALE_PROPERTY);
5215     if (prop)
5216         icalcomponent_add_property(expanded_ical, icalproperty_new_clone(prop));
5217 
5218     icalcomponent_myforeach(*ical, range, NULL, expand_cb, expanded_ical);
5219     icalcomponent_free(*ical);
5220     *ical = expanded_ical;
5221 
5222     return *ical;
5223 }
5224 
limit_caldata(icalcomponent * ical,struct icalperiodtype * limit)5225 static void limit_caldata(icalcomponent *ical, struct icalperiodtype *limit)
5226 {
5227     icaltime_span limitspan;
5228     icaltimetype dtstart, dtend, recurid;
5229     struct icaldurationtype dtduration = icaldurationtype_null_duration();
5230     icalcomponent *comp, *nextcomp;
5231     icalcomponent_kind kind;
5232 
5233     limitspan.start = icaltime_as_timet_with_zone(limit->start, utc_zone);
5234     limitspan.end = icaltime_as_timet_with_zone(limit->end, utc_zone);
5235 
5236     comp = icalcomponent_get_first_real_component(ical);
5237     kind = icalcomponent_isa(comp);
5238 
5239     /* Find master component */
5240     for (; comp; comp = icalcomponent_get_next_component(ical, kind)) {
5241 
5242         recurid = icalcomponent_get_recurrenceid_with_zone(comp);
5243 
5244         if (icaltime_is_null_time(recurid)) {
5245             /* Calculate duration of master component */
5246             dtstart = icalcomponent_get_dtstart(comp);
5247             dtend = icalcomponent_get_dtend(comp);
5248             dtduration = icaltime_subtract(dtend, dtstart);
5249             break;
5250         }
5251     }
5252 
5253     /* Check spans of each override against our limit span */
5254     for (comp = icalcomponent_get_first_component(ical, kind);
5255          comp; comp = nextcomp) {
5256 
5257         icaltime_span recurspan;
5258 
5259         nextcomp = icalcomponent_get_next_component(ical, kind);
5260         recurid = icalcomponent_get_recurrenceid_with_zone(comp);
5261 
5262         /* Skip master component */
5263         if (icaltime_is_null_time(recurid)) continue;
5264 
5265         /* Check span of override */
5266         dtstart = icalcomponent_get_dtstart(comp);
5267         dtend = icalcomponent_get_dtend(comp);
5268         recurspan = icaltime_span_new(dtstart, dtend, 1);
5269         if (icaltime_span_overlaps(&recurspan, &limitspan)) continue;
5270 
5271         /* Check span of original occurrence */
5272         dtstart = recurid;
5273         dtend = icaltime_add(dtstart, dtduration);
5274         recurspan = icaltime_span_new(dtstart, dtend, 1);
5275         if (icaltime_span_overlaps(&recurspan, &limitspan)) continue;
5276 
5277         /* Remove this component (doesn't overlap limit range) */
5278         icalcomponent_remove_component(ical, comp);
5279         icalcomponent_free(comp);
5280     }
5281 }
5282 
5283 /* Callback to prescreen/fetch CALDAV:calendar-data */
propfind_scheduser(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5284 static int propfind_scheduser(const xmlChar *name, xmlNsPtr ns,
5285                             struct propfind_ctx *fctx,
5286                             xmlNodePtr prop __attribute__((unused)),
5287                             xmlNodePtr resp __attribute__((unused)),
5288                             struct propstat propstat[],
5289                             void *rock __attribute__((unused)))
5290 {
5291     struct buf buf = BUF_INITIALIZER;
5292     int rc = HTTP_NOT_FOUND;
5293 
5294     if (propstat && fctx->mailbox && fctx->record) {
5295         message_t *m = message_new_from_record(fctx->mailbox, fctx->record);
5296 
5297         message_get_field(m, "x-schedule-user-address",
5298                           MESSAGE_DECODED|MESSAGE_TRIM, &buf);
5299 
5300         message_unref(&m);
5301     }
5302 
5303     if (buf.len) {
5304         rc = 0;
5305         xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5306                                        name, ns, NULL, 0);
5307         buf_reset(&fctx->buf);
5308         const char *address = buf_cstring(&buf);
5309         if (!strncasecmp(address, "mailto:", 7)) address += 7;
5310         buf_printf(&fctx->buf, "mailto:%s", address);
5311         xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
5312     }
5313 
5314     buf_free(&buf);
5315 
5316     return rc;
5317 }
5318 
5319 /* Callback to prescreen/fetch CALDAV:calendar-data */
propfind_caldata(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5320 static int propfind_caldata(const xmlChar *name, xmlNsPtr ns,
5321                             struct propfind_ctx *fctx,
5322                             xmlNodePtr prop,
5323                             xmlNodePtr resp __attribute__((unused)),
5324                             struct propstat propstat[],
5325                             void *rock)
5326 {
5327     struct partial_caldata_t *partial = (struct partial_caldata_t *) rock;
5328     struct caldav_data *cdata = (struct caldav_data *) fctx->data;
5329     static unsigned need_tz = 0;
5330     const char *data = NULL;
5331     size_t datalen = 0;
5332     int r = 0;
5333 
5334     if (propstat) {
5335         icalcomponent *ical = NULL;
5336 
5337         if (!fctx->record) return HTTP_NOT_FOUND;
5338 
5339         if (!fctx->msg_buf.len)
5340             mailbox_map_record(fctx->mailbox, fctx->record, &fctx->msg_buf);
5341         if (!fctx->msg_buf.len) return HTTP_SERVER_ERROR;
5342 
5343         data = fctx->msg_buf.s + fctx->record->header_size;
5344         datalen = fctx->record->size - fctx->record->header_size;
5345 
5346         if (need_tz) {
5347             if (cdata->comp_flags.tzbyref) {
5348                 /* Add VTIMEZONE components for known TZIDs */
5349                 struct timezone_rock tzrock = { NULL, NULL };
5350                 icalcomponent *comp, *next;
5351                 icalcomponent_kind kind;
5352 
5353                 if (!fctx->obj) fctx->obj = icalparser_parse_string(data);
5354                 ical = fctx->obj;
5355                 tzrock.new = ical;
5356 
5357                 comp = icalcomponent_get_first_real_component(ical);
5358                 kind = icalcomponent_isa(comp);
5359                 for (; comp; comp = next) {
5360                     next = icalcomponent_get_next_component(ical, kind);
5361                     icalcomponent_foreach_tzid(comp, &add_timezone, &tzrock);
5362                 }
5363             }
5364         }
5365         else if (!cdata->comp_flags.tzbyref &&
5366                  (namespace_calendar.allow & ALLOW_CAL_NOTZ)) {
5367             /* Strip all VTIMEZONE components for known TZIDs */
5368             if (!fctx->obj) fctx->obj = icalparser_parse_string(data);
5369             ical = fctx->obj;
5370 
5371             strip_vtimezones(ical);
5372         }
5373 
5374         if (!icaltime_is_null_time(partial->range.start)) {
5375             /* Expand/limit recurrence set */
5376             if (!fctx->obj) fctx->obj = icalparser_parse_string(data);
5377             ical = fctx->obj;
5378 
5379             if (partial->expand) {
5380                 fctx->obj = expand_caldata(&ical, partial->range);
5381             }
5382             else limit_caldata(ical, &partial->range);
5383         }
5384 
5385         if (partial->comp) {
5386             /* Limit returned properties */
5387             if (!fctx->obj) fctx->obj = icalparser_parse_string(data);
5388             ical = fctx->obj;
5389             prune_properties(ical, partial->comp);
5390         }
5391 
5392         if (ical) {
5393             /* Create iCalendar data from new ical component */
5394             data = icalcomponent_as_ical_string(ical);
5395             datalen = strlen(data);
5396         }
5397     }
5398     else if (prop) {
5399         /* Prescreen "property" request - read partial/expand children */
5400         xmlNodePtr node;
5401 
5402         /* Check for optional CalDAV-Timezones header */
5403         const char **hdr =
5404             spool_getheader(fctx->txn->req_hdrs, "CalDAV-Timezones");
5405         if (hdr && !strcmp(hdr[0], "T")) need_tz = 1;
5406         else need_tz = 0;
5407 
5408         /* Initialize expand to be "empty" */
5409         partial->range.start = icaltime_null_time();
5410         partial->range.end = icaltime_null_time();
5411         partial->comp = NULL;
5412 
5413         /* Check for and parse child elements of CALDAV:calendar-data */
5414         for (node = xmlFirstElementChild(prop); node;
5415              node = xmlNextElementSibling(node)) {
5416             xmlChar *prop;
5417 
5418             if (!xmlStrcmp(node->name, BAD_CAST "expand") ||
5419                 !xmlStrcmp(node->name, BAD_CAST "limit-recurrence-set")) {
5420                 partial->expand = (node->name[0] == 'e');
5421                 prop = xmlGetProp(node, BAD_CAST "start");
5422                 if (!prop) return (*fctx->ret = HTTP_BAD_REQUEST);
5423                 partial->range.start = icaltime_from_string((char *) prop);
5424                 xmlFree(prop);
5425 
5426                 prop = xmlGetProp(node, BAD_CAST "end");
5427                 if (!prop) return (*fctx->ret = HTTP_BAD_REQUEST);
5428                 partial->range.end = icaltime_from_string((char *) prop);
5429                 xmlFree(prop);
5430             }
5431             else if (!xmlStrcmp(node->name, BAD_CAST "comp")) {
5432                 partial->comp = parse_partial_comp(node);
5433                 if (!partial->comp ||
5434                     partial->comp->kind != ICAL_VCALENDAR_COMPONENT) {
5435                     return (*fctx->ret = HTTP_BAD_REQUEST);
5436                 }
5437             }
5438             else if (!xmlStrcmp(node->name, BAD_CAST "limit-freebusy-set")) {
5439                 syslog(LOG_NOTICE,
5440                        "Client attempted to use CALDAV:limit-freebusy-set");
5441                 return (*fctx->ret = HTTP_NOT_IMPLEMENTED);
5442             }
5443         }
5444 
5445         if (namespace_calendar.allow & ALLOW_CAL_NOTZ) {
5446             /* We want to strip known VTIMEZONEs */
5447             fctx->proc_by_resource = &caldav_propfind_by_resource;
5448         }
5449     }
5450     else {
5451         /* Cleanup "property" request - free partial component structure */
5452         struct partial_comp_t *pcomp, *child, *sibling;
5453 
5454         for (pcomp = partial->comp; pcomp; pcomp = child) {
5455             child = pcomp->child;
5456 
5457             do {
5458                 sibling = pcomp->sibling;
5459                 arrayu64_fini(&pcomp->props);
5460                 free(pcomp);
5461             } while ((pcomp = sibling));
5462         }
5463 
5464         return 0;
5465     }
5466 
5467     r = propfind_getdata(name, ns, fctx, prop, propstat, caldav_mime_types,
5468                          CALDAV_SUPP_DATA, data, datalen);
5469 
5470     return r;
5471 }
5472 
5473 
5474 /* Callback to fetch CALDAV:calendar-home-set,
5475  * CALDAV:schedule-inbox-URL, CALDAV:schedule-outbox-URL,
5476  * and CALDAV:schedule-default-calendar-URL
5477  */
propfind_calurl(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5478 int propfind_calurl(const xmlChar *name, xmlNsPtr ns,
5479                     struct propfind_ctx *fctx,
5480                     xmlNodePtr prop,
5481                     xmlNodePtr resp __attribute__((unused)),
5482                     struct propstat propstat[], void *rock)
5483 {
5484     const char *cal = (const char *) rock;
5485     xmlNodePtr node;
5486     /* NOTE: calbuf needs to stay in scope until 'cal' is finished with */
5487     struct buf calbuf = BUF_INITIALIZER;
5488     int ret = HTTP_NOT_FOUND; /* error condition if we bail early */
5489 
5490     if (!(namespace_calendar.enabled && httpd_userid))
5491         goto done;
5492 
5493     if (cal) {
5494         /* named calendars are only used for scheduling */
5495         if (!(namespace_calendar.allow & ALLOW_CAL_SCHED))
5496             goto done;
5497 
5498         /* check for renamed calendars - property on the homeset */
5499         const char *annotname = NULL;
5500         if (!strcmp(cal, SCHED_DEFAULT)) {
5501             annotname =
5502                 DAV_ANNOT_NS "<" XML_NS_CALDAV ">schedule-default-calendar";
5503         }
5504         else if (!strcmp(cal, SCHED_INBOX))
5505             annotname = DAV_ANNOT_NS "<" XML_NS_CALDAV ">schedule-inbox";
5506         else if (!strcmp(cal, SCHED_OUTBOX))
5507             annotname = DAV_ANNOT_NS "<" XML_NS_CALDAV ">schedule-outbox";
5508 
5509         if (annotname) {
5510             char *mailboxname = caldav_mboxname(httpd_userid, NULL);
5511             int r = annotatemore_lookupmask(mailboxname, annotname,
5512                                             httpd_userid, &calbuf);
5513             free(mailboxname);
5514             if (!r && calbuf.len) {
5515                 buf_putc(&calbuf, '/');
5516                 cal = buf_cstring(&calbuf);
5517             }
5518         }
5519     }
5520 
5521     node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5522                         name, ns, NULL, 0);
5523 
5524     buf_reset(&fctx->buf);
5525     if (strchr(httpd_userid, '@') || !httpd_extradomain) {
5526         buf_printf(&fctx->buf, "%s/%s/%s/", namespace_calendar.prefix,
5527                    USER_COLLECTION_PREFIX, httpd_userid);
5528     }
5529     else {
5530         buf_printf(&fctx->buf, "%s/%s/%s@%s/", namespace_calendar.prefix,
5531                    USER_COLLECTION_PREFIX, httpd_userid, httpd_extradomain);
5532     }
5533     if (cal) buf_appendcstr(&fctx->buf, cal);
5534 
5535     if ((fctx->mode == PROPFIND_EXPAND) && xmlFirstElementChild(prop)) {
5536         /* Return properties for this URL */
5537         expand_property(prop, fctx, &namespace_calendar, buf_cstring(&fctx->buf),
5538                         &caldav_parse_path, caldav_props, node, cal ? 1 : 0);
5539     }
5540     else {
5541         /* Return just the URL */
5542         xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
5543     }
5544 
5545     ret = 0;
5546 done:
5547     buf_free(&calbuf);
5548 
5549     return ret;
5550 }
5551 
5552 
5553 /* Callback to fetch CALDAV:schedule-default-calendar-URL */
propfind_scheddefault(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5554 static int propfind_scheddefault(const xmlChar *name, xmlNsPtr ns,
5555                                  struct propfind_ctx *fctx,
5556                                  xmlNodePtr prop, xmlNodePtr resp,
5557                                  struct propstat propstat[],
5558                                  void *rock __attribute__((unused)))
5559 {
5560     /* Only defined on CALDAV:schedule-inbox-URL */
5561     if (fctx->req_tgt->flags != TGT_SCHED_INBOX) return HTTP_NOT_FOUND;
5562 
5563     return propfind_calurl(name, ns, fctx,
5564                            prop, resp, propstat, SCHED_DEFAULT);
5565 }
5566 
5567 
5568 /* Callback to fetch CALDAV:supported-calendar-component-set[s] */
propfind_calcompset(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5569 static int propfind_calcompset(const xmlChar *name, xmlNsPtr ns,
5570                                struct propfind_ctx *fctx,
5571                                xmlNodePtr prop __attribute__((unused)),
5572                                xmlNodePtr resp __attribute__((unused)),
5573                                struct propstat propstat[],
5574                                void *rock __attribute__((unused)))
5575 {
5576     struct buf attrib = BUF_INITIALIZER;
5577     const char *prop_annot =
5578         DAV_ANNOT_NS "<" XML_NS_CALDAV ">supported-calendar-component-set";
5579     unsigned long types = 0;
5580     xmlNodePtr set, node;
5581     const struct cal_comp_t *comp;
5582     int r = 0;
5583 
5584     if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
5585 
5586     r = annotatemore_lookupmask(fctx->mbentry->name, prop_annot,
5587                                 httpd_userid, &attrib);
5588     if (r) return HTTP_SERVER_ERROR;
5589 
5590     if (attrib.len) {
5591         types = strtoul(buf_cstring(&attrib), NULL, 10);
5592     }
5593     else {
5594         types = -1;  /* ALL components types */
5595 
5596         /* Apple clients don't like VPOLL */
5597         types &= ~CAL_COMP_VPOLL;
5598     }
5599 
5600     buf_free(&attrib);
5601 
5602     if (!types) return HTTP_NOT_FOUND;
5603 
5604     set = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5605                        name, ns, NULL, 0);
5606     if (!resp) {
5607         ensure_ns(fctx->ns, NS_CALDAV, set->parent, XML_NS_CALDAV, "C");
5608         xmlSetNs(set, fctx->ns[NS_CALDAV]);
5609     }
5610 
5611     /* Create "comp" elements from the stored bitmask */
5612     for (comp = cal_comps; comp->name; comp++) {
5613         if (types & comp->type) {
5614             node = xmlNewChild(set, fctx->ns[NS_CALDAV],
5615                                BAD_CAST "comp", NULL);
5616             xmlNewProp(node, BAD_CAST "name", BAD_CAST comp->name);
5617         }
5618     }
5619 
5620     return 0;
5621 }
5622 
5623 
5624 /* Callback to write supported-calendar-component-set property */
proppatch_calcompset(xmlNodePtr prop,unsigned set,struct proppatch_ctx * pctx,struct propstat propstat[],void * rock)5625 static int proppatch_calcompset(xmlNodePtr prop, unsigned set,
5626                                 struct proppatch_ctx *pctx,
5627                                 struct propstat propstat[],
5628                                 void *rock __attribute__((unused)))
5629 {
5630     unsigned precond = 0, force = 0;
5631     xmlChar *attr = xmlGetProp(prop, BAD_CAST "force");
5632 
5633     /* Check if we want to force changing of a protected property.
5634        This is mainly for our list_calendars() JavaScript client. */
5635     if (attr) {
5636         if (!xmlStrcmp(attr, BAD_CAST "yes") &&
5637             mboxname_userownsmailbox(httpd_userid, pctx->mailbox->name)) {
5638             force = 1;
5639         }
5640         xmlFree(attr);
5641     }
5642 
5643     if (set && (force || (pctx->txn->meth != METH_PROPPATCH))) {
5644         /* "Writeable" for MKCOL/MKCALENDAR only */
5645         xmlNodePtr cur;
5646         unsigned long types = 0;
5647 
5648         /* Work through the given list of components */
5649         for (cur = prop->children; cur; cur = cur->next) {
5650             xmlChar *name;
5651             const struct cal_comp_t *comp;
5652 
5653             /* Make sure its a "comp" element with a "name" */
5654             if (cur->type != XML_ELEMENT_NODE) continue;
5655             if (xmlStrcmp(cur->name, BAD_CAST "comp") ||
5656                 !(name = xmlGetProp(cur, BAD_CAST "name"))) break;
5657 
5658             /* Make sure we have a valid component type */
5659             for (comp = cal_comps;
5660                  comp->name && xmlStrcmp(name, BAD_CAST comp->name); comp++);
5661             xmlFree(name);
5662 
5663             if (comp->name) types |= comp->type;   /* found match in our list */
5664             else break;                            /* no match - invalid type */
5665         }
5666 
5667         if (!cur) {
5668             /* All component types are valid */
5669             char typestr[(sizeof(unsigned long) * 8) / 3 + 1];
5670             sprintf(typestr, "%lu", types);
5671 
5672             proppatch_todb(prop, set, pctx, propstat, (void *) typestr);
5673 
5674             return 0;
5675         }
5676 
5677         /* Invalid component type */
5678         precond = CALDAV_SUPP_COMP;
5679     }
5680     else {
5681         /* Protected property */
5682         precond = DAV_PROT_PROP;
5683     }
5684 
5685     xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV], &propstat[PROPSTAT_FORBID],
5686                  prop->name, prop->ns, NULL, precond);
5687 
5688     *pctx->ret = HTTP_FORBIDDEN;
5689 
5690     return 0;
5691 }
5692 
5693 /* Callback to fetch CALDAV:supported-calendar-data */
propfind_suppcaldata(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5694 static int propfind_suppcaldata(const xmlChar *name, xmlNsPtr ns,
5695                                 struct propfind_ctx *fctx,
5696                                 xmlNodePtr prop __attribute__((unused)),
5697                                 xmlNodePtr resp __attribute__((unused)),
5698                                 struct propstat propstat[],
5699                                 void *rock __attribute__((unused)))
5700 {
5701     xmlNodePtr node;
5702     struct mime_type_t *mime;
5703 
5704     if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
5705 
5706     node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5707                         name, ns, NULL, 0);
5708 
5709     for (mime = caldav_mime_types; mime->content_type; mime++) {
5710         xmlNodePtr type = xmlNewChild(node, fctx->ns[NS_CALDAV],
5711                                       BAD_CAST "calendar-data", NULL);
5712 
5713         /* Trim any charset from content-type */
5714         buf_reset(&fctx->buf);
5715         buf_printf(&fctx->buf, "%.*s",
5716                    (int) strcspn(mime->content_type, ";"), mime->content_type);
5717 
5718         xmlNewProp(type, BAD_CAST "content-type",
5719                    BAD_CAST buf_cstring(&fctx->buf));
5720 
5721         if (mime->version)
5722             xmlNewProp(type, BAD_CAST "version", BAD_CAST mime->version);
5723     }
5724 
5725     return 0;
5726 }
5727 
5728 
5729 /* Callback to fetch CALDAV:max-resource-size */
propfind_maxsize(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5730 static int propfind_maxsize(const xmlChar *name, xmlNsPtr ns,
5731                             struct propfind_ctx *fctx,
5732                             xmlNodePtr prop __attribute__((unused)),
5733                             xmlNodePtr resp __attribute__((unused)),
5734                             struct propstat propstat[],
5735                             void *rock __attribute__((unused)))
5736 {
5737     static int maxsize = 0;
5738 
5739     if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
5740 
5741     if (!maxsize) {
5742         maxsize = config_getint(IMAPOPT_MAXMESSAGESIZE);
5743         if (!maxsize) maxsize = INT_MAX;
5744     }
5745 
5746     buf_reset(&fctx->buf);
5747     buf_printf(&fctx->buf, "%d", maxsize);
5748     xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5749                  name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
5750 
5751     return 0;
5752 }
5753 
5754 
5755 /* Callback to fetch CALDAV:min/max-date-time */
propfind_minmaxdate(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5756 static int propfind_minmaxdate(const xmlChar *name, xmlNsPtr ns,
5757                                struct propfind_ctx *fctx,
5758                                xmlNodePtr prop __attribute__((unused)),
5759                                xmlNodePtr resp __attribute__((unused)),
5760                                struct propstat propstat[],
5761                                void *rock)
5762 {
5763     struct icaltimetype date;
5764 
5765     if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
5766 
5767     date = icaltime_from_timet_with_zone(*((time_t *) rock), 0, utc_zone);
5768 
5769     xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5770                  name, ns, BAD_CAST icaltime_as_ical_string(date), 0);
5771 
5772     return 0;
5773 }
5774 
5775 
5776 /* Callback to fetch CALDAV:schedule-tag */
propfind_schedtag(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5777 static int propfind_schedtag(const xmlChar *name, xmlNsPtr ns,
5778                              struct propfind_ctx *fctx,
5779                              xmlNodePtr prop __attribute__((unused)),
5780                              xmlNodePtr resp __attribute__((unused)),
5781                              struct propstat propstat[],
5782                              void *rock __attribute__((unused)))
5783 {
5784     struct caldav_data *cdata = (struct caldav_data *) fctx->data;
5785 
5786     if (!cdata->organizer || !cdata->sched_tag) return HTTP_NOT_FOUND;
5787 
5788     /* add DQUOTEs */
5789     buf_reset(&fctx->buf);
5790     buf_printf(&fctx->buf, "\"%s\"", cdata->sched_tag);
5791 
5792     xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5793                  name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
5794 
5795     return 0;
5796 }
5797 
5798 
5799 /* Callback to fetch CALDAV:calendar-user-address-set */
propfind_caluseraddr(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5800 int propfind_caluseraddr(const xmlChar *name, xmlNsPtr ns,
5801                          struct propfind_ctx *fctx,
5802                          xmlNodePtr prop __attribute__((unused)),
5803                          xmlNodePtr resp __attribute__((unused)),
5804                          struct propstat propstat[],
5805                          void *rock __attribute__((unused)))
5806 {
5807     xmlNodePtr node;
5808     struct strlist *domains;
5809 
5810     if (!(namespace_calendar.enabled && fctx->req_tgt->userid))
5811         return HTTP_NOT_FOUND;
5812 
5813     node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5814                         name, ns, NULL, 0);
5815 
5816     const char *annotname =
5817         DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-user-address-set";
5818     char *mailboxname = caldav_mboxname(fctx->req_tgt->userid, NULL);
5819     buf_reset(&fctx->buf);
5820     int r = annotatemore_lookupmask(mailboxname, annotname,
5821                                     fctx->req_tgt->userid, &fctx->buf);
5822     free(mailboxname);
5823     if (!r && fctx->buf.len) {
5824         xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
5825         return 0;
5826     }
5827 
5828     /* XXX  This needs to be done via an LDAP/DB lookup */
5829     if (strchr(fctx->req_tgt->userid, '@')) {
5830         buf_reset(&fctx->buf);
5831         buf_printf(&fctx->buf, "mailto:%s", fctx->req_tgt->userid);
5832         xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
5833         return 0;
5834     }
5835 
5836     if (httpd_extradomain) {
5837         buf_reset(&fctx->buf);
5838         buf_printf(&fctx->buf, "mailto:%s@%s",
5839                    fctx->req_tgt->userid, httpd_extradomain);
5840         xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
5841         return 0;
5842     }
5843 
5844     for (domains = cua_domains; domains; domains = domains->next) {
5845         buf_reset(&fctx->buf);
5846         buf_printf(&fctx->buf, "mailto:%s@%s",
5847                    fctx->req_tgt->userid, domains->s);
5848 
5849         xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
5850     }
5851 
5852     return 0;
5853 }
5854 
5855 /* Callback to fetch CALDAV:calendar-user-type */
propfind_calusertype(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5856 int propfind_calusertype(const xmlChar *name, xmlNsPtr ns,
5857                          struct propfind_ctx *fctx,
5858                          xmlNodePtr prop __attribute__((unused)),
5859                          xmlNodePtr resp __attribute__((unused)),
5860                          struct propstat propstat[],
5861                          void *rock __attribute__((unused)))
5862 {
5863     const char *type = fctx->req_tgt->userid ? "INDIVIDUAL" : NULL;
5864 
5865     if (!namespace_calendar.enabled || !type) return HTTP_NOT_FOUND;
5866 
5867     xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5868                  name, ns, BAD_CAST type, 0);
5869 
5870     return 0;
5871 }
5872 
5873 
5874 /* Callback to fetch CALDAV:schedule-calendar-transp */
propfind_caltransp(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5875 static int propfind_caltransp(const xmlChar *name, xmlNsPtr ns,
5876                               struct propfind_ctx *fctx,
5877                               xmlNodePtr prop __attribute__((unused)),
5878                               xmlNodePtr resp __attribute__((unused)),
5879                               struct propstat propstat[],
5880                               void *rock __attribute__((unused)))
5881 {
5882     struct buf attrib = BUF_INITIALIZER;
5883     const char *prop_annot =
5884         DAV_ANNOT_NS "<" XML_NS_CALDAV ">schedule-calendar-transp";
5885     xmlNodePtr node;
5886     int r = 0;
5887 
5888     if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
5889 
5890     r = annotatemore_lookupmask(fctx->mailbox->name, prop_annot,
5891                                 httpd_userid, &attrib);
5892 
5893     if (r) return HTTP_SERVER_ERROR;
5894     if (!attrib.len) return HTTP_NOT_FOUND;
5895 
5896     node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5897                         name, ns, NULL, 0);
5898     xmlNewChild(node, fctx->ns[NS_CALDAV], BAD_CAST buf_cstring(&attrib), NULL);
5899 
5900     buf_free(&attrib);
5901 
5902     return 0;
5903 }
5904 
5905 
5906 /* Callback to write schedule-calendar-transp property */
proppatch_caltransp(xmlNodePtr prop,unsigned set,struct proppatch_ctx * pctx,struct propstat propstat[],void * rock)5907 static int proppatch_caltransp(xmlNodePtr prop, unsigned set,
5908                                struct proppatch_ctx *pctx,
5909                                struct propstat propstat[],
5910                                void *rock __attribute__((unused)))
5911 {
5912     if (pctx->txn->req_tgt.collection && !pctx->txn->req_tgt.resource) {
5913         const xmlChar *value = NULL;
5914 
5915         if (set) {
5916             xmlNodePtr cur;
5917 
5918             /* Find the value */
5919             for (cur = prop->children; cur; cur = cur->next) {
5920 
5921                 /* Make sure its a value we understand */
5922                 if (cur->type != XML_ELEMENT_NODE) continue;
5923                 if (!xmlStrcmp(cur->name, BAD_CAST "opaque") ||
5924                     !xmlStrcmp(cur->name, BAD_CAST "transparent")) {
5925                     value = cur->name;
5926                     break;
5927                 }
5928                 else {
5929                     /* Unknown value */
5930                     xml_add_prop(HTTP_CONFLICT, pctx->ns[NS_DAV],
5931                                  &propstat[PROPSTAT_CONFLICT],
5932                                  prop->name, prop->ns, NULL, 0);
5933 
5934                     *pctx->ret = HTTP_FORBIDDEN;
5935 
5936                     return 0;
5937                 }
5938             }
5939         }
5940 
5941         proppatch_todb(prop, set, pctx, propstat, (void *) value);
5942     }
5943     else {
5944         xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
5945                      &propstat[PROPSTAT_FORBID],
5946                      prop->name, prop->ns, NULL, 0);
5947 
5948         *pctx->ret = HTTP_FORBIDDEN;
5949     }
5950 
5951     return 0;
5952 }
5953 
5954 
5955 /* Callback to prescreen/fetch CALDAV:calendar-timezone */
propfind_timezone(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5956 static int propfind_timezone(const xmlChar *name, xmlNsPtr ns,
5957                              struct propfind_ctx *fctx,
5958                              xmlNodePtr prop,
5959                              xmlNodePtr resp __attribute__((unused)),
5960                              struct propstat propstat[],
5961                              void *rock __attribute__((unused)))
5962 {
5963     struct buf attrib = BUF_INITIALIZER;
5964     const char *data = NULL, *msg_base = NULL;
5965     size_t datalen = 0;
5966     int r = 0;
5967 
5968     if (propstat) {
5969         const char *prop_annot =
5970             DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-timezone";
5971 
5972         if (fctx->mailbox && !fctx->record) {
5973             r = annotatemore_lookupmask(fctx->mailbox->name, prop_annot,
5974                                         httpd_userid, &attrib);
5975         }
5976 
5977         if (r) r = HTTP_SERVER_ERROR;
5978         else if (attrib.len)  {
5979             data = buf_cstring(&attrib);
5980             datalen = attrib.len;
5981         }
5982         else if ((namespace_calendar.allow & ALLOW_CAL_NOTZ)) {
5983             /*  Check for CALDAV:calendar-timezone-id */
5984             prop_annot = DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-timezone-id";
5985 
5986             buf_free(&attrib);
5987 
5988             if (fctx->mailbox)
5989                 r = annotatemore_lookupmask(fctx->mailbox->name, prop_annot,
5990                                             httpd_userid, &attrib);
5991 
5992             if (r) r = HTTP_SERVER_ERROR;
5993             else if (!attrib.len) r = HTTP_NOT_FOUND;
5994             else {
5995                 const char *path;
5996                 int fd;
5997 
5998                 /* Open and mmap the timezone file */
5999                 buf_reset(&fctx->buf);
6000                 buf_printf(&fctx->buf, "%s%s/%s.ics",
6001                            config_dir, FNAME_ZONEINFODIR, buf_cstring(&attrib));
6002 
6003                 path = buf_cstring(&fctx->buf);
6004                 if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR;
6005 
6006                 map_refresh(fd, 1, &msg_base, &datalen,
6007                             MAP_UNKNOWN_LEN, path, NULL);
6008                 close(fd);
6009 
6010                 if (!msg_base) r = HTTP_SERVER_ERROR;
6011 
6012                 data = msg_base;
6013             }
6014         }
6015         else r = HTTP_NOT_FOUND;
6016     }
6017 
6018     if (!r) r = propfind_getdata(name, ns, fctx, prop, propstat,
6019                                  caldav_mime_types, CALDAV_SUPP_DATA,
6020                                  data, datalen);
6021 
6022     if (msg_base) map_free(&msg_base, &datalen);
6023     buf_free(&attrib);
6024 
6025     return r;
6026 }
6027 
6028 
6029 /* Callback to write calendar-timezone property */
proppatch_timezone(xmlNodePtr prop,unsigned set,struct proppatch_ctx * pctx,struct propstat propstat[],void * rock)6030 static int proppatch_timezone(xmlNodePtr prop, unsigned set,
6031                               struct proppatch_ctx *pctx,
6032                               struct propstat propstat[],
6033                               void *rock __attribute__((unused)))
6034 {
6035     if (pctx->txn->req_tgt.collection && !pctx->txn->req_tgt.resource) {
6036         xmlChar *type, *ver = NULL, *freeme = NULL;
6037         const char *tz = NULL;
6038         struct mime_type_t *mime;
6039         unsigned valid = 1;
6040 
6041         type = xmlGetProp(prop, BAD_CAST "content-type");
6042         if (type) ver = xmlGetProp(prop, BAD_CAST "version");
6043 
6044         /* Check/find requested MIME type */
6045         for (mime = caldav_mime_types; type && mime->content_type; mime++) {
6046             if (is_mediatype(mime->content_type, (const char *) type)) {
6047                 if (ver &&
6048                     (!mime->version || xmlStrcmp(ver, BAD_CAST mime->version))) {
6049                     continue;
6050                 }
6051                 break;
6052             }
6053         }
6054 
6055         if (!mime || !mime->content_type) {
6056             xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6057                          &propstat[PROPSTAT_FORBID],
6058                          prop->name, prop->ns, NULL,
6059                          CALDAV_SUPP_DATA);
6060             *pctx->ret = HTTP_FORBIDDEN;
6061             valid = 0;
6062         }
6063         else if (set) {
6064             icalcomponent *ical = NULL;
6065             struct buf buf;
6066 
6067             freeme = xmlNodeGetContent(prop);
6068             tz = (const char *) freeme;
6069 
6070             /* Parse and validate the iCal data */
6071             buf_init_ro_cstr(&buf, tz);
6072             ical = mime->to_object(&buf);
6073             if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) {
6074                 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6075                              &propstat[PROPSTAT_FORBID],
6076                              prop->name, prop->ns, NULL,
6077                              CALDAV_VALID_DATA);
6078                 *pctx->ret = HTTP_FORBIDDEN;
6079                 valid = 0;
6080             }
6081             else if (!icalcomponent_get_first_component(ical,
6082                                                         ICAL_VTIMEZONE_COMPONENT)
6083                      || icalcomponent_get_first_real_component(ical)) {
6084                 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6085                              &propstat[PROPSTAT_FORBID],
6086                              prop->name, prop->ns, NULL,
6087                              CALDAV_VALID_OBJECT);
6088                 *pctx->ret = HTTP_FORBIDDEN;
6089                 valid = 0;
6090             }
6091             else if (mime != caldav_mime_types) {
6092                 tz = icalcomponent_as_ical_string(ical);
6093             }
6094 
6095             if (ical) icalcomponent_free(ical);
6096             buf_free(&buf);
6097         }
6098 
6099         if (valid) {
6100             /* Remove CALDAV:calendar-timezone-id */
6101             const char *prop_annot =
6102                 DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-timezone-id";
6103             annotate_state_t *astate = NULL;
6104             int r;
6105 
6106             buf_reset(&pctx->buf);
6107             r = mailbox_get_annotate_state(pctx->mailbox, 0, &astate);
6108             if (!r) r = annotate_state_writemask(astate, prop_annot,
6109                                                  httpd_userid, &pctx->buf);
6110             if (!r) {
6111                 /* Set CALDAV:calendar-timezone */
6112                 proppatch_todb(prop, set, pctx, propstat, (void *) tz);
6113             }
6114             else {
6115                 xml_add_prop(HTTP_SERVER_ERROR, pctx->ns[NS_DAV],
6116                              &propstat[PROPSTAT_ERROR],
6117                              prop->name, prop->ns, NULL, 0);
6118                 *pctx->ret = HTTP_SERVER_ERROR;
6119             }
6120         }
6121 
6122         if (freeme) xmlFree(freeme);
6123         if (type) xmlFree(type);
6124         if (ver) xmlFree(ver);
6125     }
6126     else {
6127         xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6128                      &propstat[PROPSTAT_FORBID], prop->name, prop->ns, NULL, 0);
6129 
6130         *pctx->ret = HTTP_FORBIDDEN;
6131     }
6132 
6133     return 0;
6134 }
6135 
6136 
6137 /* Callback to prescreen/fetch CALDAV/CS:calendar-availability */
propfind_availability(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)6138 static int propfind_availability(const xmlChar *name, xmlNsPtr ns,
6139                                  struct propfind_ctx *fctx,
6140                                  xmlNodePtr prop,
6141                                  xmlNodePtr resp __attribute__((unused)),
6142                                  struct propstat propstat[],
6143                                  void *rock __attribute__((unused)))
6144 {
6145     struct buf attrib = BUF_INITIALIZER;
6146     const char *data = NULL;
6147     unsigned long datalen = 0;
6148     int r = 0;
6149 
6150     if (propstat) {
6151         buf_reset(&fctx->buf);
6152         buf_printf(&fctx->buf, DAV_ANNOT_NS "<%s>%s",
6153                    (const char *) ns->href, name);
6154 
6155         if (fctx->mailbox && !fctx->record) {
6156             r = annotatemore_lookupmask(fctx->mailbox->name,
6157                                         buf_cstring(&fctx->buf),
6158                                         httpd_userid, &attrib);
6159         }
6160 
6161         if (!attrib.len && xmlStrcmp(ns->href, BAD_CAST XML_NS_CALDAV)) {
6162             /* Check for CALDAV:calendar-availability */
6163             buf_reset(&fctx->buf);
6164             buf_printf(&fctx->buf, DAV_ANNOT_NS "<%s>%s", XML_NS_CALDAV, name);
6165 
6166             if (fctx->mailbox && !fctx->record) {
6167                 r = annotatemore_lookupmask(fctx->mailbox->name,
6168                                             buf_cstring(&fctx->buf),
6169                                             httpd_userid, &attrib);
6170             }
6171         }
6172 
6173         if (r) r = HTTP_SERVER_ERROR;
6174         else if (!attrib.len) r = HTTP_NOT_FOUND;
6175         else {
6176             data = buf_cstring(&attrib);
6177             datalen = attrib.len;
6178         }
6179     }
6180 
6181     if (!r) r = propfind_getdata(name, ns, fctx, prop, propstat,
6182                                  caldav_mime_types, CALDAV_SUPP_DATA,
6183                                  data, datalen);
6184     buf_free(&attrib);
6185 
6186     return r;
6187 }
6188 
6189 
6190 
6191 /* Callback to write calendar-availability property */
proppatch_availability(xmlNodePtr prop,unsigned set,struct proppatch_ctx * pctx,struct propstat propstat[],void * rock)6192 static int proppatch_availability(xmlNodePtr prop, unsigned set,
6193                                   struct proppatch_ctx *pctx,
6194                                   struct propstat propstat[],
6195                                   void *rock __attribute__((unused)))
6196 {
6197     if (config_allowsched && pctx->txn->req_tgt.flags == TGT_SCHED_INBOX) {
6198         const char *avail = NULL;
6199         xmlChar *type, *ver = NULL, *freeme = NULL;
6200         struct mime_type_t *mime;
6201         unsigned valid = 1;
6202 
6203         type = xmlGetProp(prop, BAD_CAST "content-type");
6204         if (type) ver = xmlGetProp(prop, BAD_CAST "version");
6205 
6206         /* Check/find requested MIME type */
6207         for (mime = caldav_mime_types; type && mime->content_type; mime++) {
6208             if (is_mediatype(mime->content_type, (const char *) type)) {
6209                 if (ver &&
6210                     (!mime->version || xmlStrcmp(ver, BAD_CAST mime->version))) {
6211                     continue;
6212                 }
6213                 break;
6214             }
6215         }
6216 
6217         if (!mime || !mime->content_type) {
6218             xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6219                          &propstat[PROPSTAT_FORBID],
6220                          prop->name, prop->ns, NULL,
6221                          CALDAV_SUPP_DATA);
6222             *pctx->ret = HTTP_FORBIDDEN;
6223             valid = 0;
6224         }
6225         else if (set) {
6226             icalcomponent *ical = NULL;
6227             struct buf buf;
6228 
6229             freeme = xmlNodeGetContent(prop);
6230             avail = (const char *) freeme;
6231 
6232             /* Parse and validate the iCal data */
6233             buf_init_ro_cstr(&buf, avail);
6234             ical = mime->to_object(&buf);
6235             if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) {
6236                 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6237                              &propstat[PROPSTAT_FORBID],
6238                              prop->name, prop->ns, NULL,
6239                              CALDAV_VALID_DATA);
6240                 *pctx->ret = HTTP_FORBIDDEN;
6241                 valid = 0;
6242             }
6243             else if (!icalcomponent_get_first_component(ical,
6244                                                         ICAL_VAVAILABILITY_COMPONENT)) {
6245                 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6246                              &propstat[PROPSTAT_FORBID],
6247                              prop->name, prop->ns, NULL,
6248                              CALDAV_VALID_OBJECT);
6249                 *pctx->ret = HTTP_FORBIDDEN;
6250                 valid = 0;
6251             }
6252             else if (mime != caldav_mime_types) {
6253                 avail = icalcomponent_as_ical_string(ical);
6254             }
6255 
6256             if (ical) icalcomponent_free(ical);
6257         }
6258 
6259         if (valid) {
6260             proppatch_todb(prop, set, pctx, propstat, (void *) avail);
6261         }
6262 
6263         if (freeme) xmlFree(freeme);
6264         if (type) xmlFree(type);
6265         if (ver) xmlFree(ver);
6266     }
6267     else {
6268         xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6269                      &propstat[PROPSTAT_FORBID], prop->name, prop->ns, NULL, 0);
6270 
6271         *pctx->ret = HTTP_FORBIDDEN;
6272     }
6273 
6274     return 0;
6275 }
6276 
6277 
6278 /* Callback to fetch CALDAV:timezone-service-set */
propfind_tzservset(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)6279 static int propfind_tzservset(const xmlChar *name, xmlNsPtr ns,
6280                               struct propfind_ctx *fctx,
6281                               xmlNodePtr prop __attribute__((unused)),
6282                               xmlNodePtr resp __attribute__((unused)),
6283                               struct propstat propstat[],
6284                               void *rock __attribute__((unused)))
6285 {
6286     assert(name && ns && fctx && propstat);
6287 
6288 #ifdef HAVE_TZ_BY_REF
6289     if (fctx->req_tgt->resource) return HTTP_NOT_FOUND;
6290 
6291     if (namespace_calendar.allow & ALLOW_CAL_NOTZ) {
6292         xmlNodePtr node;
6293         const char *proto = NULL, *host = NULL;
6294 
6295         node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
6296                             name, ns, NULL, 0);
6297 
6298         http_proto_host(fctx->txn->req_hdrs, &proto, &host);
6299 
6300         buf_reset(&fctx->buf);
6301         buf_printf(&fctx->buf, "%s://%s%s",
6302                    proto, host, namespace_tzdist.prefix);
6303 
6304         xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
6305 
6306         return 0;
6307     }
6308 #endif /* HAVE_TZ_BY_REF */
6309 
6310     return HTTP_NOT_FOUND;
6311 }
6312 
6313 
6314 /* Callback to fetch CALDAV:calendar-timezone-id property */
propfind_tzid(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)6315 static int propfind_tzid(const xmlChar *name, xmlNsPtr ns,
6316                          struct propfind_ctx *fctx,
6317                          xmlNodePtr prop __attribute__((unused)),
6318                          xmlNodePtr resp __attribute__((unused)),
6319                          struct propstat propstat[],
6320                          void *rock __attribute__((unused)))
6321 {
6322     const char *prop_annot =
6323         DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-timezone-id";
6324     struct buf attrib = BUF_INITIALIZER;
6325     const char *value = NULL;
6326     int r = 0;
6327 
6328     if (!(namespace_calendar.allow & ALLOW_CAL_NOTZ) ||
6329         !fctx->req_tgt->collection || fctx->req_tgt->resource)
6330         return HTTP_NOT_FOUND;
6331 
6332     r = annotatemore_lookupmask(fctx->mailbox->name, prop_annot,
6333                                 httpd_userid, &attrib);
6334 
6335     if (r) r = HTTP_SERVER_ERROR;
6336     else if (attrib.len) {
6337         value = buf_cstring(&attrib);
6338     }
6339     else {
6340         /*  Check for CALDAV:calendar-timezone */
6341         prop_annot = DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-timezone";
6342 
6343         if (fctx->mailbox && !fctx->record) {
6344             r = annotatemore_lookupmask(fctx->mailbox->name, prop_annot,
6345                                         httpd_userid, &attrib);
6346         }
6347 
6348         if (r) r = HTTP_SERVER_ERROR;
6349         else if (!attrib.len) r = HTTP_NOT_FOUND;
6350         else {
6351             icalcomponent *ical, *vtz;
6352             icalproperty *tzid;
6353 
6354             ical = icalparser_parse_string(buf_cstring(&attrib));
6355             vtz = icalcomponent_get_first_component(ical,
6356                                                     ICAL_VTIMEZONE_COMPONENT);
6357             tzid = icalcomponent_get_first_property(vtz, ICAL_TZID_PROPERTY);
6358             value = icalmemory_tmp_copy(icalproperty_get_tzid(tzid));
6359             icalcomponent_free(ical);
6360         }
6361     }
6362 
6363     if (!r) xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
6364                          name, ns, BAD_CAST value, 0);
6365 
6366     buf_free(&attrib);
6367 
6368     return r;
6369 }
6370 
6371 
6372 /* Callback to write CALDAV:calendar-timezone-id property */
proppatch_tzid(xmlNodePtr prop,unsigned set,struct proppatch_ctx * pctx,struct propstat propstat[],void * rock)6373 static int proppatch_tzid(xmlNodePtr prop, unsigned set,
6374                           struct proppatch_ctx *pctx,
6375                           struct propstat propstat[],
6376                           void *rock __attribute__((unused)))
6377 {
6378 #ifdef HAVE_TZ_BY_REF
6379     if ((namespace_calendar.allow & ALLOW_CAL_NOTZ) &&
6380         pctx->txn->req_tgt.collection && !pctx->txn->req_tgt.resource) {
6381         xmlChar *freeme = NULL;
6382         const char *tzid = NULL;
6383         unsigned valid = 1;
6384         int r;
6385 
6386         if (set) {
6387             freeme = xmlNodeGetContent(prop);
6388             tzid = (const char *) freeme;
6389 
6390             /* Verify we have tzid record in the database */
6391             r = zoneinfo_lookup(tzid, NULL);
6392             if (r) {
6393                 if (r == CYRUSDB_NOTFOUND) {
6394                     xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6395                                  &propstat[PROPSTAT_FORBID],
6396                                  prop->name, prop->ns, NULL,
6397                                  CALDAV_VALID_TIMEZONE);
6398                 }
6399                 else {
6400                     xml_add_prop(HTTP_SERVER_ERROR, pctx->ns[NS_DAV],
6401                                  &propstat[PROPSTAT_ERROR],
6402                                  prop->name, prop->ns, NULL, 0);
6403                 }
6404                 *pctx->ret = HTTP_FORBIDDEN;
6405                 valid = 0;
6406             }
6407         }
6408 
6409         if (valid) {
6410             /* Remove CALDAV:calendar-timezone */
6411             const char *prop_annot =
6412                 DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-timezone";
6413             annotate_state_t *astate = NULL;
6414 
6415             buf_reset(&pctx->buf);
6416             r = mailbox_get_annotate_state(pctx->mailbox, 0, &astate);
6417             if (!r) r = annotate_state_writemask(astate, prop_annot,
6418                                                  httpd_userid, &pctx->buf);
6419 
6420             if (r) {
6421                 xml_add_prop(HTTP_SERVER_ERROR, pctx->ns[NS_DAV],
6422                              &propstat[PROPSTAT_ERROR],
6423                              prop->name, prop->ns, NULL, 0);
6424                 *pctx->ret = HTTP_SERVER_ERROR;
6425             }
6426             else {
6427                 /* Set CALDAV:calendar-timezone-id */
6428                 proppatch_todb(prop, set, pctx, propstat, (void *) tzid);
6429             }
6430         }
6431 
6432         if (freeme) xmlFree(freeme);
6433 
6434         return 0;
6435     }
6436 #else
6437     (void) set;  /* squash compiler warning */
6438 #endif /* HAVE_TZ_BY_REF */
6439 
6440     xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6441                  &propstat[PROPSTAT_FORBID], prop->name, prop->ns, NULL, 0);
6442 
6443     *pctx->ret = HTTP_FORBIDDEN;
6444 
6445     return 0;
6446 }
6447 
6448 
6449 /* Callback to fetch CALDAV:supported-rscale-set */
propfind_rscaleset(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)6450 static int propfind_rscaleset(const xmlChar *name, xmlNsPtr ns,
6451                               struct propfind_ctx *fctx,
6452                               xmlNodePtr prop __attribute__((unused)),
6453                               xmlNodePtr resp __attribute__((unused)),
6454                               struct propstat propstat[],
6455                               void *rock __attribute__((unused)))
6456 {
6457     assert(name && ns && fctx && propstat);
6458 
6459     if (fctx->req_tgt->resource) return HTTP_NOT_FOUND;
6460 
6461     if (rscale_calendars) {
6462         xmlNodePtr top;
6463         int i, n;
6464 
6465         top = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
6466                            name, ns, NULL, 0);
6467 
6468         for (i = 0, n = rscale_calendars->num_elements; i < n; i++) {
6469             const char **rscale = icalarray_element_at(rscale_calendars, i);
6470 
6471             xmlNewChild(top, fctx->ns[NS_CALDAV],
6472                         BAD_CAST "supported-rscale", BAD_CAST *rscale);
6473         }
6474 
6475         return 0;
6476     }
6477 
6478     return HTTP_NOT_FOUND;
6479 }
6480 
6481 
6482 /* Callback to fetch CS:allowed-sharing-modes */
propfind_sharingmodes(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)6483 static int propfind_sharingmodes(const xmlChar *name, xmlNsPtr ns,
6484                                  struct propfind_ctx *fctx,
6485                                  xmlNodePtr prop __attribute__((unused)),
6486                                  xmlNodePtr resp __attribute__((unused)),
6487                                  struct propstat propstat[],
6488                                  void *rock __attribute__((unused)))
6489 {
6490     xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
6491                                    &propstat[PROPSTAT_OK], name, ns, NULL, 0);
6492 
6493     fctx->flags.cs_sharing = 1;
6494 
6495     if (fctx->req_tgt->collection && !fctx->req_tgt->flags &&
6496         !fctx->req_tgt->resource &&
6497         mboxname_userownsmailbox(fctx->req_tgt->userid, fctx->mbentry->name)) {
6498         xmlNewChild(node, NULL, BAD_CAST "can-be-shared", NULL);
6499 #if 0  /* XXX  this is probably iCloud specific */
6500         xmlNewChild(node, NULL, BAD_CAST "can-be-published", NULL);
6501 #endif
6502     }
6503 
6504     return 0;
6505 }
6506 
6507 
report_cal_query(struct transaction_t * txn,struct meth_params * rparams,xmlNodePtr inroot,struct propfind_ctx * fctx)6508 static int report_cal_query(struct transaction_t *txn,
6509                             struct meth_params *rparams __attribute__((unused)),
6510                             xmlNodePtr inroot, struct propfind_ctx *fctx)
6511 {
6512     int ret = 0;
6513     xmlNodePtr node;
6514     struct calquery_filter calfilter;
6515 
6516     memset(&calfilter, 0, sizeof(struct calquery_filter));
6517 
6518     fctx->filter_crit = &calfilter;
6519     fctx->open_db = (db_open_proc_t) &caldav_open_mailbox;
6520     fctx->close_db = (db_close_proc_t) &caldav_close;
6521     fctx->lookup_resource = (db_lookup_proc_t) &caldav_lookup_resource;
6522     fctx->foreach_resource = (db_foreach_proc_t) &caldav_foreach;
6523     fctx->proc_by_resource = &propfind_by_resource;
6524     fctx->davdb = NULL;
6525 
6526     /* Parse children element of report */
6527     for (node = inroot->children; node; node = node->next) {
6528         if (node->type == XML_ELEMENT_NODE) {
6529             if (!xmlStrcmp(node->name, BAD_CAST "filter")) {
6530                 ret = parse_calfilter(node, &calfilter, &txn->error);
6531                 if (ret) goto done;
6532                 else fctx->filter = apply_calfilter;
6533             }
6534             else if (!xmlStrcmp(node->name, BAD_CAST "timezone")) {
6535                 xmlChar *tzdata = NULL;
6536                 icalcomponent *ical = NULL, *tz = NULL;
6537 
6538                 /* XXX  Need to pass this to query for floating time */
6539                 syslog(LOG_WARNING, "REPORT calendar-query w/timezone");
6540                 tzdata = xmlNodeGetContent(node);
6541                 if (tzdata) {
6542                     ical = icalparser_parse_string((const char *) tzdata);
6543                     xmlFree(tzdata);
6544                 }
6545 
6546                 /* Validate the iCal data */
6547                 if (!ical || !icalrestriction_check(ical) ||
6548                     icalcomponent_get_first_real_component(ical) ||
6549                     !(tz = icalcomponent_get_first_component(ical,
6550                                                              ICAL_VTIMEZONE_COMPONENT))) {
6551                     txn->error.precond = CALDAV_VALID_DATA;
6552                     ret = HTTP_FORBIDDEN;
6553                 }
6554                 else {
6555                     icalcomponent_remove_component(ical, tz);
6556                     calfilter.tz = icaltimezone_new();
6557                     icaltimezone_set_component(calfilter.tz, tz);
6558                 }
6559 
6560                 if (ical) icalcomponent_free(ical);
6561                 if (ret) return ret;
6562             }
6563             else if (!xmlStrcmp(node->name, BAD_CAST "timezone-id")) {
6564                 xmlChar *tzid = NULL;
6565 
6566                 /* XXX  Need to pass this to query for floating time */
6567                 syslog(LOG_WARNING, "REPORT calendar-query w/tzid");
6568                 tzid = xmlNodeGetContent(node);
6569 
6570                 if (tzid) {
6571                     calfilter.tz =
6572                         icaltimezone_get_builtin_timezone_from_tzid((const char *) tzid);
6573                     xmlFree(tzid);
6574 
6575                     if (!calfilter.tz) {
6576                         txn->error.precond = CALDAV_VALID_TIMEZONE;
6577                         return HTTP_FORBIDDEN;
6578                     }
6579                 }
6580             }
6581         }
6582     }
6583 
6584     if (fctx->depth++ > 0) {
6585         /* Calendar collection(s) */
6586         if (txn->req_tgt.collection) {
6587             /* Add response for target calendar collection */
6588             propfind_by_collection(txn->req_tgt.mbentry, fctx);
6589         }
6590         else {
6591             /* Add responses for all contained calendar collections */
6592             mboxlist_mboxtree(txn->req_tgt.mbentry->name,
6593                               propfind_by_collection, fctx,
6594                               MBOXTREE_SKIP_ROOT);
6595 
6596             /* Add responses for all shared calendar collections */
6597             mboxlist_usersubs(txn->req_tgt.userid,
6598                               propfind_by_collection, fctx,
6599                               MBOXTREE_SKIP_PERSONAL);
6600         }
6601 
6602         ret = *fctx->ret;
6603     }
6604 
6605   done:
6606     /* Free filter structure */
6607     if (calfilter.tz) icaltimezone_free(calfilter.tz, 1);
6608     free_compfilter(calfilter.comp);
6609 
6610     if (fctx->davdb) {
6611         caldav_close(fctx->davdb);
6612         fctx->davdb = NULL;
6613     }
6614 
6615     return (ret ? ret : HTTP_MULTI_STATUS);
6616 }
6617 
6618 
is_busytime(icalcomponent * comp)6619 static int is_busytime(icalcomponent *comp)
6620 {
6621     /* Check TRANSP and STATUS per RFC 4791, section 7.10 */
6622     const icalproperty *prop;
6623 
6624     /* Skip transparent events */
6625     prop = icalcomponent_get_first_property(comp, ICAL_TRANSP_PROPERTY);
6626     if (prop && icalproperty_get_transp(prop) == ICAL_TRANSP_TRANSPARENT)
6627         return 0;
6628 
6629     /* Skip cancelled events */
6630     if (icalcomponent_get_status(comp) == ICAL_STATUS_CANCELLED) return 0;
6631 
6632     return 1;
6633 }
6634 
6635 
6636 /* Append a new busytime period to the busytime array */
add_freebusy(struct icaltimetype * recurid,struct icaltimetype * start,struct icaltimetype * end,icalparameter_fbtype fbtype,struct freebusy_filter * fbfilter)6637 static void add_freebusy(struct icaltimetype *recurid,
6638                          struct icaltimetype *start,
6639                          struct icaltimetype *end,
6640                          icalparameter_fbtype fbtype,
6641                          struct freebusy_filter *fbfilter)
6642 {
6643     struct freebusy_array *freebusy = &fbfilter->freebusy;
6644     struct freebusy *newfb;
6645 
6646     /* Grow the array, if necessary */
6647     if (freebusy->len == freebusy->alloc) {
6648         freebusy->alloc += 100;  /* XXX  arbitrary */
6649         freebusy->fb = xrealloc(freebusy->fb,
6650                                 freebusy->alloc * sizeof(struct freebusy));
6651     }
6652 
6653     /* Add new freebusy */
6654     newfb = &freebusy->fb[freebusy->len++];
6655     memset(newfb, 0, sizeof(struct freebusy));
6656 
6657     if (recurid) newfb->recurid = *recurid;
6658 
6659     if (icaltime_is_date(*start)) {
6660         newfb->per.duration = icaltime_subtract(*end, *start);
6661         newfb->per.end = icaltime_null_time();
6662         start->is_date = 0;  /* MUST be DATE-TIME */
6663         newfb->per.start = icaltime_convert_to_zone(*start, utc_zone);
6664     }
6665     else {
6666         newfb->per.duration = icaldurationtype_null_duration();
6667         if (icaltime_compare(fbfilter->end, *end) < 0) {
6668             newfb->per.end = fbfilter->end;
6669         }
6670         else newfb->per.end = *end;
6671 
6672         if (icaltime_compare(fbfilter->start, *start) > 0) {
6673             newfb->per.start = fbfilter->start;
6674         }
6675         else newfb->per.start = *start;
6676     }
6677     newfb->type = fbtype;
6678 }
6679 
6680 
6681 /* Append a new busytime period for recurring comp to the busytime array */
add_freebusy_comp(icalcomponent * comp,icaltimetype start,icaltimetype end,void * rock)6682 static int add_freebusy_comp(icalcomponent *comp,
6683                              icaltimetype start, icaltimetype end,
6684                              void *rock)
6685 {
6686     struct freebusy_filter *fbfilter = (struct freebusy_filter *) rock;
6687     struct icaltimetype recurid;
6688     icalparameter_fbtype fbtype;
6689 
6690     if (!is_busytime(comp)) return 1;
6691 
6692     /* Set start and end times */
6693     start = icaltime_convert_to_zone(start, utc_zone);
6694     end = icaltime_convert_to_zone(end, utc_zone);
6695 
6696     /* Set recurid */
6697     recurid = icalcomponent_get_recurrenceid_with_zone(comp);
6698     if (icaltime_is_null_time(recurid)) recurid = start;
6699     else {
6700         recurid = icaltime_convert_to_zone(recurid, utc_zone);
6701         recurid.is_date = 0;  /* make DATE-TIME for comparison */
6702     }
6703 
6704     /* Set FBTYPE */
6705     switch (icalcomponent_isa(comp)) {
6706     case ICAL_VEVENT_COMPONENT:
6707         fbtype = icalcomponent_get_status(comp) == ICAL_STATUS_TENTATIVE ?
6708             ICAL_FBTYPE_BUSYTENTATIVE : ICAL_FBTYPE_BUSY;
6709         break;
6710 
6711     case ICAL_VFREEBUSY_COMPONENT:
6712         /* XXX  Need to do something better here */
6713         fbtype = ICAL_FBTYPE_BUSY;
6714         break;
6715 
6716 #ifdef HAVE_VAVAILABILITY
6717     case ICAL_VAVAILABILITY_COMPONENT: {
6718         enum icalproperty_busytype busytype = ICAL_BUSYTYPE_BUSYUNAVAILABLE;
6719         icalproperty *prop =
6720             icalcomponent_get_first_property(comp, ICAL_BUSYTYPE_PROPERTY);
6721 
6722         if (prop) busytype = icalproperty_get_busytype(prop);
6723 
6724         switch (busytype) {
6725         case ICAL_BUSYTYPE_BUSYUNAVAILABLE:
6726             fbtype = ICAL_FBTYPE_BUSYUNAVAILABLE;
6727             break;
6728         case ICAL_BUSYTYPE_BUSYTENTATIVE:
6729             fbtype = ICAL_FBTYPE_BUSYTENTATIVE;
6730             break;
6731         default:
6732             fbtype = ICAL_FBTYPE_BUSY;
6733             break;
6734         }
6735         break;
6736     }
6737 
6738     case ICAL_XAVAILABLE_COMPONENT:
6739         fbtype = ICAL_FBTYPE_FREE;
6740         break;
6741 #endif /* HAVE_VAVAILABILITY */
6742 
6743     default:
6744         fbtype = ICAL_FBTYPE_NONE;
6745         break;
6746     }
6747 
6748     add_freebusy(&recurid, &start, &end, fbtype, fbfilter);
6749 
6750     return 1;
6751 }
6752 
6753 
expand_occurrences(icalcomponent * ical,struct freebusy_filter * fbfilter)6754 static void expand_occurrences(icalcomponent *ical,
6755                                struct freebusy_filter *fbfilter)
6756 {
6757     /* Create a span for the given time-range */
6758     struct icalperiodtype rangespan =
6759         { fbfilter->start, fbfilter->end, icaldurationtype_null_duration() };
6760 
6761     icalcomponent_myforeach(ical, rangespan, NULL, add_freebusy_comp, fbfilter);
6762 }
6763 
6764 
6765 /* Append a new vavailability period to the vavail array */
6766 static void
add_vavailability(struct vavailability_array * vavail,icalcomponent * ical)6767 add_vavailability(struct vavailability_array *vavail, icalcomponent *ical)
6768 {
6769     struct vavailability *newav;
6770     icalcomponent *vav;
6771     icalproperty *prop;
6772 
6773     /* Grow the array, if necessary */
6774     if (vavail->len == vavail->alloc) {
6775         vavail->alloc += 10;  /* XXX  arbitrary */
6776         vavail->vav = xrealloc(vavail->vav,
6777                                vavail->alloc * sizeof(struct vavailability));
6778     }
6779 
6780     /* Add new vavailability */
6781     newav = &vavail->vav[vavail->len++];
6782     newav->ical = ical;
6783 
6784     vav = icalcomponent_get_first_real_component(ical);
6785 
6786     /* Set period */
6787     newav->per.start = icalcomponent_get_dtstart(vav);
6788     if (icaltime_is_null_time(newav->per.start)) {
6789         newav->per.start =
6790             icaltime_from_timet_with_zone(caldav_epoch, 0, utc_zone);
6791     }
6792     else {
6793         newav->per.start = icaltime_convert_to_zone(newav->per.start, utc_zone);
6794     }
6795     newav->per.end = icalcomponent_get_dtend(vav);
6796     if (icaltime_is_null_time(newav->per.end)) {
6797         newav->per.end =
6798             icaltime_from_timet_with_zone(caldav_eternity, 0, utc_zone);
6799     }
6800     else {
6801         newav->per.end = icaltime_convert_to_zone(newav->per.end, utc_zone);
6802     }
6803     newav->per.duration = icaldurationtype_null_duration();
6804 
6805     /* Set PRIORITY - 0 (or none) has lower priority than 9 */
6806     prop = icalcomponent_get_first_property(vav, ICAL_PRIORITY_PROPERTY);
6807     if (prop) newav->priority = icalproperty_get_priority(prop);
6808     if (!prop || !newav->priority) newav->priority = 10;
6809 }
6810 
6811 
6812 /* caldav_foreach() callback to find busytime of a resource */
busytime_by_resource(void * rock,void * data)6813 static int busytime_by_resource(void *rock, void *data)
6814 {
6815     struct propfind_ctx *fctx = (struct propfind_ctx *) rock;
6816     struct caldav_data *cdata = (struct caldav_data *) data;
6817     struct freebusy_filter *fbfilter =
6818         (struct freebusy_filter *) fctx->filter_crit;
6819 
6820     keepalive_response(fctx->txn);
6821 
6822     if (!cdata->dav.imap_uid) return 0;
6823 
6824     /* Perform component filtering */
6825     if (!(cdata->comp_type &
6826           (CAL_COMP_VEVENT | CAL_COMP_VFREEBUSY | CAL_COMP_VAVAILABILITY))) {
6827         return 0;
6828     }
6829 
6830     /* Perform time-range filtering */
6831     struct icaltimetype dtstart = icaltime_from_string(cdata->dtstart);
6832     struct icaltimetype dtend = icaltime_from_string(cdata->dtend);
6833 
6834     if (icaltime_compare(dtend, fbfilter->start) <= 0) {
6835         /* Component ends earlier than range */
6836         return 0;
6837     }
6838     if (icaltime_compare(dtstart, fbfilter->end) >= 0) {
6839         /* Component starts later than range */
6840         return 0;
6841     }
6842 
6843     if (cdata->comp_flags.recurring ||
6844         cdata->comp_type == CAL_COMP_VAVAILABILITY) {
6845         /* Need to mmap() and parse iCalendar object */
6846         struct index_record record;
6847         icalcomponent *ical = NULL;
6848         int r;
6849 
6850         /* Fetch index record for the resource */
6851         r = mailbox_find_index_record(fctx->mailbox,
6852                                       cdata->dav.imap_uid, &record);
6853 
6854         if (!r) ical = record_to_ical(fctx->mailbox, &record, NULL);
6855         if (!ical) return 0;
6856 
6857         if (cdata->comp_flags.recurring) {
6858             /* Component is recurring - process each recurrence */
6859             expand_occurrences(ical, fbfilter);
6860 
6861             icalcomponent_free(ical);
6862         }
6863         else {
6864             /* VAVAILABILITY - add to our array for later use */
6865             add_vavailability(&fbfilter->vavail, ical);
6866         }
6867     }
6868     else {
6869         /* Component is non-recurring */
6870         icalparameter_fbtype fbtype;
6871 
6872         if (cdata->comp_flags.transp) {
6873             /* Don't include transparent events in freebusy */
6874             return 0;
6875         }
6876         if (cdata->comp_flags.status == CAL_STATUS_CANCELED) {
6877             /* Don't include canceled events in freebusy */
6878             return 0;
6879         }
6880 
6881         switch (cdata->comp_flags.status) {
6882         case CAL_STATUS_UNAVAILABLE:
6883             fbtype = ICAL_FBTYPE_BUSYUNAVAILABLE; break;
6884 
6885         case CAL_STATUS_TENTATIVE:
6886             fbtype = ICAL_FBTYPE_BUSYTENTATIVE; break;
6887 
6888         default:
6889             fbtype = ICAL_FBTYPE_BUSY; break;
6890         }
6891 
6892         add_freebusy(&dtstart, &dtstart, &dtend, fbtype, fbfilter);
6893     }
6894 
6895     return 0;
6896 }
6897 
6898 
6899 /* mboxlist_findall() callback to find busytime of a collection */
busytime_by_collection(const mbentry_t * mbentry,void * rock)6900 static int busytime_by_collection(const mbentry_t *mbentry, void *rock)
6901 {
6902     const char *mboxname = mbentry->name;
6903     struct propfind_ctx *fctx = (struct propfind_ctx *) rock;
6904     struct freebusy_filter *fbfilter =
6905         (struct freebusy_filter *) fctx->filter_crit;
6906 
6907     if (fbfilter->flags & CHECK_CAL_TRANSP) {
6908         /* Check if the collection is marked as transparent */
6909         struct buf attrib = BUF_INITIALIZER;
6910         const char *prop_annot =
6911             DAV_ANNOT_NS "<" XML_NS_CALDAV ">schedule-calendar-transp";
6912 
6913         if (!annotatemore_lookupmask(mboxname, prop_annot,
6914                                      httpd_userid, &attrib)) {
6915             if (!strcmp(buf_cstring(&attrib), "transparent")) {
6916                 buf_free(&attrib);
6917                 return 0;
6918             }
6919             buf_free(&attrib);
6920         }
6921     }
6922 
6923     return propfind_by_collection(mbentry, rock);
6924 }
6925 
6926 
6927 /* Compare start/end times of freebusy periods -- used for sorting */
compare_freebusy(const void * fb1,const void * fb2)6928 static int compare_freebusy(const void *fb1, const void *fb2)
6929 {
6930     struct freebusy *a = (struct freebusy *) fb1;
6931     struct freebusy *b = (struct freebusy *) fb2;
6932 
6933     int r = icaltime_compare(a->per.start, b->per.start);
6934 
6935     if (r == 0) r = icaltime_compare(a->per.end, b->per.end);
6936 
6937     return r;
6938 }
6939 
6940 
6941 /* Compare type and start/end times of freebusy periods -- used for sorting */
compare_freebusy_with_type(const void * fb1,const void * fb2)6942 static int compare_freebusy_with_type(const void *fb1, const void *fb2)
6943 {
6944     struct freebusy *a = (struct freebusy *) fb1;
6945     struct freebusy *b = (struct freebusy *) fb2;
6946 
6947     int r = a->type - b->type;
6948 
6949     if (r == 0) r = compare_freebusy(fb1, fb2);
6950 
6951     return r;
6952 }
6953 
6954 
6955 /* Compare priority, start/end times, and type of vavail periods for sorting */
compare_vavail(const void * v1,const void * v2)6956 static int compare_vavail(const void *v1, const void *v2)
6957 {
6958     struct vavailability *a = (struct vavailability *) v1;
6959     struct vavailability *b = (struct vavailability *) v2;
6960 
6961     int r = a->priority - b->priority;
6962 
6963     if (r == 0) {
6964         r = icaltime_compare(a->per.start, b->per.start);
6965 
6966         if (r == 0) r = icaltime_compare(a->per.end, b->per.end);
6967     }
6968 
6969     return r;
6970 }
6971 
6972 
combine_vavailability(struct freebusy_filter * fbfilter)6973 static void combine_vavailability(struct freebusy_filter *fbfilter)
6974 {
6975     struct vavailability_array *vavail = &fbfilter->vavail;
6976     struct freebusy_filter availfilter;
6977     struct query_range {
6978         struct icalperiodtype per;
6979         struct query_range *next;
6980     } *ranges, *range, *prev, *next;
6981     unsigned i, j;
6982 
6983     memset(&availfilter, 0, sizeof(struct freebusy_filter));
6984 
6985     /* Sort VAVAILBILITY periods by priority and start time */
6986     qsort(vavail->vav, vavail->len,
6987           sizeof(struct vavailability), compare_vavail);
6988 
6989     /* Maintain a linked list of remaining time ranges
6990      * to be filled in by lower priority VAV components
6991      * starting with the time range in the freebusy query.
6992      * Ranges are removed, clipped, or split as they get filled.
6993      * Quit when no ranges or VAVAILABILITY components remain.
6994      */
6995     ranges = xmalloc(sizeof(struct query_range));
6996     ranges->per.start = fbfilter->start;
6997     ranges->per.end = fbfilter->end;
6998     ranges->next = NULL;
6999 
7000     for (i = 0; i < vavail->len; i++) {
7001         struct vavailability *vav = &vavail->vav[i];
7002         icalcomponent *comp;
7003 
7004         comp = icalcomponent_get_first_component(vav->ical,
7005                                                  ICAL_VAVAILABILITY_COMPONENT);
7006 
7007         for (range = ranges, prev = NULL; range; prev = range, range = next) {
7008             struct icalperiodtype period;
7009 
7010             next = range->next;
7011 
7012             if (icaltime_compare(vav->per.end, range->per.start) <= 0 ||
7013                 icaltime_compare(vav->per.start, range->per.end) >= 0) {
7014                 /* Skip VAVAILABILITY outside our range */
7015                 continue;
7016             }
7017 
7018             /* Set filter range (maximum start time and minimum end time)
7019                and adjust current range as necessary */
7020             if (icaltime_compare(vav->per.start, range->per.start) <= 0) {
7021                 /* VAV starts before range - filter using range start */
7022                 availfilter.start = range->per.start;
7023 
7024                 if (icaltime_compare(vav->per.end, range->per.end) >= 0) {
7025                     /* VAV ends after range - filter using range end */
7026                     availfilter.end = range->per.end;
7027 
7028                     /* Filling entire range - remove it */
7029                     if (prev) prev->next = next;
7030                     else ranges = NULL;
7031 
7032                     free(range);
7033                     range = NULL;
7034                 }
7035                 else {
7036                     /* VAV ends before range - filter using VAV end */
7037                     availfilter.end = vav->per.end;
7038 
7039                     /* Filling front part - adjust start to back remainder */
7040                     range->per.start = vav->per.end;
7041                 }
7042             }
7043             else {
7044                 /* VAV starts after range - filter using VAV start */
7045                 availfilter.start = vav->per.start;
7046 
7047                 if (icaltime_compare(vav->per.end, range->per.end) >= 0) {
7048                     /* VAV ends after range - filter using range end */
7049                     availfilter.end = range->per.end;
7050                 }
7051                 else {
7052                     /* VAV ends before range - filter using VAV end */
7053                     availfilter.end = vav->per.end;
7054 
7055                     /* Splitting range - insert new range for back remainder */
7056                     struct query_range *newr =
7057                         xmalloc(sizeof(struct query_range));
7058                     newr->per.start = vav->per.end;
7059                     newr->per.end = range->per.end;
7060                     newr->next = next;
7061                     range->next = newr;
7062                 }
7063 
7064                 /* Adjust end to front remainder */
7065                 range->per.end = vav->per.start;
7066             }
7067 
7068             /* Expand available time occurrences */
7069             expand_occurrences(comp, &availfilter);
7070 
7071             /* Calculate unavailable periods and add to busytime */
7072             period.start = availfilter.start;
7073             for (j = 0; j < availfilter.freebusy.len; j++) {
7074                 struct freebusy *fb = &availfilter.freebusy.fb[j];
7075 
7076                 /* Ignore overridden instances */
7077                 if (fb->type == ICAL_FBTYPE_NONE) continue;
7078 
7079                 period.end = fb->per.start;
7080                 if (icaltime_compare(period.end, period.start) > 0) {
7081                     add_freebusy_comp(comp, period.start, period.end, fbfilter);
7082                 }
7083                 period.start = fb->per.end;
7084             }
7085             period.end = availfilter.end;
7086             if (icaltime_compare(period.end, period.start) > 0) {
7087                 add_freebusy_comp(comp, period.start, period.end, fbfilter);
7088             }
7089         }
7090 
7091         /* Done with this ical component */
7092         icalcomponent_free(vav->ical);
7093     }
7094 
7095     /* Cleanup the vavailability array */
7096     free(vavail->vav);
7097 
7098     /* Cleanup the availability array */
7099     if (availfilter.freebusy.fb) free(availfilter.freebusy.fb);
7100 
7101     /* Remove any unfilled ranges */
7102     for (; ranges; ranges = next) {
7103         next = ranges->next;
7104         free(ranges);
7105     }
7106 }
7107 
7108 
7109 /* Create an iCalendar object containing busytime of all specified resources */
busytime_query_local(struct transaction_t * txn,struct propfind_ctx * fctx,char mailboxname[],icalproperty_method method,const char * uid,const char * organizer,const char * attendee)7110 icalcomponent *busytime_query_local(struct transaction_t *txn,
7111                                     struct propfind_ctx *fctx,
7112                                     char mailboxname[],
7113                                     icalproperty_method method,
7114                                     const char *uid,
7115                                     const char *organizer,
7116                                     const char *attendee)
7117 {
7118     struct freebusy_filter *fbfilter =
7119         (struct freebusy_filter *) fctx->filter_crit;
7120     struct freebusy_array *freebusy = &fbfilter->freebusy;
7121     struct vavailability_array *vavail = &fbfilter->vavail;
7122     icalcomponent *ical = NULL;
7123     icalcomponent *fbcomp;
7124     icalproperty *prop;
7125     unsigned n;
7126 
7127     fctx->open_db = (db_open_proc_t) &caldav_open_mailbox;
7128     fctx->close_db = (db_close_proc_t) &caldav_close;
7129     fctx->lookup_resource = (db_lookup_proc_t) &caldav_lookup_resource;
7130     fctx->foreach_resource = (db_foreach_proc_t) &caldav_foreach;
7131     fctx->proc_by_resource = &busytime_by_resource;
7132 
7133     /* Gather up all of the busytime and VAVAILABILITY periods */
7134     if (fctx->depth > 0) {
7135         /* Calendar collection(s) */
7136 
7137         if (txn->req_tgt.collection) {
7138             /* Get busytime for target calendar collection */
7139             busytime_by_collection(txn->req_tgt.mbentry, fctx);
7140         }
7141         else {
7142             /* Get busytime for all contained calendar collections */
7143             mboxlist_mboxtree(mailboxname, busytime_by_collection,
7144                               fctx, MBOXTREE_SKIP_ROOT);
7145 
7146             /* XXX  Get busytime for all shared calendar collections? */
7147         }
7148 
7149         if (fctx->davdb) caldav_close(fctx->davdb);
7150     }
7151 
7152     if (*fctx->ret) return NULL;
7153 
7154     if (fbfilter->flags & CHECK_USER_AVAIL) {
7155         /* Check for CALDAV:calendar-availability on user's Inbox */
7156         struct buf attrib = BUF_INITIALIZER;
7157         const char *prop_annot =
7158             DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-availability";
7159         char *userid = mboxname_to_userid(mailboxname);
7160         const char *mboxname = caldav_mboxname(userid, SCHED_INBOX);
7161         if (!annotatemore_lookupmask(mboxname, prop_annot,
7162                                      httpd_userid, &attrib) && attrib.len) {
7163             add_vavailability(vavail,
7164                               icalparser_parse_string(buf_cstring(&attrib)));
7165         }
7166         free(userid);
7167     }
7168 
7169     /* Combine VAVAILABILITY components into busytime */
7170     if (vavail->len) combine_vavailability(fbfilter);
7171 
7172     /* Sort busytime periods by type and start/end times for coalescing */
7173     qsort(freebusy->fb, freebusy->len,
7174           sizeof(struct freebusy), compare_freebusy_with_type);
7175 
7176     /* Coalesce busytime periods of same type into one */
7177     for (n = 0; n + 1 < freebusy->len; n++) {
7178         struct freebusy *fb, *next_fb;
7179         icaltimetype end, next_end;
7180         int isdur, next_isdur;
7181 
7182         fb = &freebusy->fb[n];
7183         next_fb = &freebusy->fb[n+1];
7184 
7185         /* Ignore overridden instances */
7186         if (fb->type == ICAL_FBTYPE_NONE) continue;
7187 
7188         isdur = !icaldurationtype_is_null_duration(fb->per.duration);
7189         end = !isdur ? fb->per.end :
7190             icaltime_add(fb->per.start, fb->per.duration);
7191 
7192         /* Skip periods of different type or that don't overlap */
7193         if ((fb->type != next_fb->type) ||
7194             icaltime_compare(end, next_fb->per.start) < 0) continue;
7195 
7196         /* Coalesce into next busytime */
7197         next_isdur = !icaldurationtype_is_null_duration(next_fb->per.duration);
7198         next_end = !next_isdur ? next_fb->per.end :
7199             icaltime_add(next_fb->per.start, next_fb->per.duration);
7200 
7201         if (icaltime_compare(end, next_end) >= 0) {
7202             /* Current period subsumes next */
7203             next_fb->per.end = fb->per.end;
7204             next_fb->per.duration = fb->per.duration;
7205         }
7206         else if (isdur && next_isdur) {
7207             /* Both periods are durations */
7208             struct icaldurationtype overlap =
7209                 icaltime_subtract(end, next_fb->per.start);
7210 
7211             next_fb->per.duration.days += fb->per.duration.days - overlap.days;
7212         }
7213         else {
7214             /* Need to use explicit period */
7215             next_fb->per.end = next_end;
7216             next_fb->per.duration = icaldurationtype_null_duration();
7217         }
7218 
7219         next_fb->per.start = fb->per.start;
7220 
7221         /* "Remove" the instance
7222            by setting fbtype to NONE (we ignore these later) */
7223         fb->type = ICAL_FBTYPE_NONE;
7224     }
7225 
7226     /* Sort busytime periods by start/end times for addition to VFREEBUSY */
7227     qsort(freebusy->fb, freebusy->len,
7228           sizeof(struct freebusy), compare_freebusy);
7229 
7230     /* Construct iCalendar object with VFREEBUSY component */
7231     ical = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT,
7232                                icalproperty_new_version("2.0"),
7233                                icalproperty_new_prodid(ical_prodid),
7234                                0);
7235 
7236     if (method) icalcomponent_set_method(ical, method);
7237 
7238     fbcomp = icalcomponent_vanew(ICAL_VFREEBUSY_COMPONENT,
7239                                  icalproperty_new_dtstamp(
7240                                      icaltime_from_timet_with_zone(
7241                                          time(0), 0, utc_zone)),
7242                                  icalproperty_new_dtstart(fbfilter->start),
7243                                  icalproperty_new_dtend(fbfilter->end),
7244                                  0);
7245 
7246     icalcomponent_add_component(ical, fbcomp);
7247 
7248     if (uid) icalcomponent_set_uid(fbcomp, uid);
7249     if (organizer) {
7250         prop = icalproperty_new_organizer(organizer);
7251         icalcomponent_add_property(fbcomp, prop);
7252     }
7253     if (attendee) {
7254         prop = icalproperty_new_attendee(attendee);
7255         icalcomponent_add_property(fbcomp, prop);
7256     }
7257 
7258     /* Add busytime periods to VFREEBUSY component */
7259     for (n = 0; n < freebusy->len; n++) {
7260         struct freebusy *fb = &freebusy->fb[n];
7261         icalproperty *busy;
7262 
7263         /* Ignore overridden instances */
7264         if (fb->type == ICAL_FBTYPE_NONE) continue;
7265 
7266         /* Create new FREEBUSY property with FBTYPE and add to component */
7267         busy = icalproperty_new_freebusy(fb->per);
7268         icalproperty_add_parameter(busy, icalparameter_new_fbtype(fb->type));
7269         icalcomponent_add_property(fbcomp, busy);
7270     }
7271 
7272     return ical;
7273 }
7274 
7275 
report_fb_query(struct transaction_t * txn,struct meth_params * rparams,xmlNodePtr inroot,struct propfind_ctx * fctx)7276 static int report_fb_query(struct transaction_t *txn,
7277                            struct meth_params *rparams __attribute__((unused)),
7278                            xmlNodePtr inroot, struct propfind_ctx *fctx)
7279 {
7280     int ret = 0;
7281     const char **hdr;
7282     struct mime_type_t *mime;
7283     struct freebusy_filter fbfilter;
7284     xmlNodePtr node;
7285     icalcomponent *cal;
7286 
7287     /* Can not be run against a resource */
7288     if (txn->req_tgt.resource) return HTTP_FORBIDDEN;
7289 
7290     /* Check requested MIME type:
7291        1st entry in caldav_mime_types array MUST be default MIME type */
7292     if ((hdr = spool_getheader(txn->req_hdrs, "Accept")))
7293         mime = get_accept_type(hdr, caldav_mime_types);
7294     else mime = caldav_mime_types;
7295     if (!mime) return HTTP_NOT_ACCEPTABLE;
7296 
7297     memset(&fbfilter, 0, sizeof(struct freebusy_filter));
7298     fbfilter.start = icaltime_from_timet_with_zone(caldav_epoch, 0, utc_zone);
7299     fbfilter.end = icaltime_from_timet_with_zone(caldav_eternity, 0, utc_zone);
7300     fctx->filter_crit = &fbfilter;
7301 
7302     /* Parse children element of report */
7303     for (node = inroot->children; node; node = node->next) {
7304         if (node->type == XML_ELEMENT_NODE) {
7305             if (!xmlStrcmp(node->name, BAD_CAST "time-range")) {
7306                 xmlChar *start, *end;
7307 
7308                 start = xmlGetProp(node, BAD_CAST "start");
7309                 if (start) {
7310                     fbfilter.start = icaltime_from_string((char *) start);
7311                     xmlFree(start);
7312                 }
7313 
7314                 end = xmlGetProp(node, BAD_CAST "end");
7315                 if (end) {
7316                     fbfilter.end = icaltime_from_string((char *) end);
7317                     xmlFree(end);
7318                 }
7319 
7320                 if (!is_valid_timerange(fbfilter.start, fbfilter.end)) {
7321                     return HTTP_BAD_REQUEST;
7322                 }
7323             }
7324         }
7325     }
7326 
7327     fctx->depth++;
7328     cal = busytime_query_local(txn, fctx, txn->req_tgt.mbentry->name,
7329                                0, NULL, NULL, NULL);
7330 
7331     if (fbfilter.freebusy.fb) free(fbfilter.freebusy.fb);
7332 
7333     if (cal) {
7334         /* Output the iCalendar object as text/calendar */
7335         struct buf *cal_str = mime->from_object(cal);
7336         icalcomponent_free(cal);
7337 
7338         txn->resp_body.type = mime->content_type;
7339 
7340         /* iCalendar data in response should not be transformed */
7341         txn->flags.cc |= CC_NOTRANSFORM;
7342 
7343         write_body(HTTP_OK, txn, buf_base(cal_str), buf_len(cal_str));
7344         buf_destroy(cal_str);
7345     }
7346     else ret = HTTP_NOT_FOUND;
7347 
7348     return ret;
7349 }
7350 
7351 
7352 /* Replace TZID aliases with the actual TZIDs */
replace_tzid_aliases(icalcomponent * ical,struct hash_table * tzid_table)7353 static void replace_tzid_aliases(icalcomponent *ical,
7354                                  struct hash_table *tzid_table)
7355 {
7356     icalproperty *prop;
7357     for (prop = icalcomponent_get_first_property(ical, ICAL_ANY_PROPERTY);
7358          prop;
7359          prop = icalcomponent_get_next_property(ical, ICAL_ANY_PROPERTY)) {
7360         icalparameter *param =
7361             icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER);
7362         if (!param) continue;
7363 
7364         const char *tzid =
7365             hash_lookup(icalparameter_get_tzid(param), tzid_table);
7366         if (tzid) icalparameter_set_tzid(param, tzid);
7367     }
7368 
7369     icalcomponent *comp;
7370     for (comp = icalcomponent_get_first_component(ical, ICAL_ANY_COMPONENT);
7371          comp;
7372          comp = icalcomponent_get_next_component(ical, ICAL_ANY_COMPONENT)) {
7373         replace_tzid_aliases(comp, tzid_table);
7374     }
7375 }
7376 
7377 
7378 /* Strip all VTIMEZONE components for known TZIDs */
strip_vtimezones(icalcomponent * ical)7379 static void strip_vtimezones(icalcomponent *ical)
7380 {
7381     struct hash_table tzid_table;
7382     icalcomponent *vtz, *next;
7383 
7384     /* Create hash table for TZID aliases */
7385     construct_hash_table(&tzid_table, 10, 1);
7386 
7387     for (vtz = icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT);
7388          vtz; vtz = next) {
7389 
7390         next = icalcomponent_get_next_component(ical, ICAL_VTIMEZONE_COMPONENT);
7391 
7392         icalproperty *prop =
7393             icalcomponent_get_first_property(vtz, ICAL_TZID_PROPERTY);
7394         const char *tzid = icalproperty_get_tzid(prop);
7395         struct zoneinfo zi;
7396 
7397         if (!zoneinfo_lookup(tzid, &zi)) {
7398             if (zi.type == ZI_LINK) {
7399                 /* Add this alias to our table */
7400                 hash_insert(tzid, xstrdup(zi.data->s), &tzid_table);
7401             }
7402             freestrlist(zi.data);
7403 
7404             icalcomponent_remove_component(ical, vtz);
7405             icalcomponent_free(vtz);
7406         }
7407     }
7408 
7409     if (hash_numrecords(&tzid_table)) {
7410         /* Replace all TZID aliases with actual TZIDs.
7411            Note: This NEEDS to be done, otherwise looking up the
7412            builtin timezone will fail on a TZID mismatch. */
7413         replace_tzid_aliases(ical, &tzid_table);
7414     }
7415     free_hash_table(&tzid_table, free);
7416 }
7417 
7418 
7419 /* Store the iCal data in the specified calendar/resource */
caldav_store_resource(struct transaction_t * txn,icalcomponent * ical,struct mailbox * mailbox,const char * resource,struct caldav_db * caldavdb,unsigned flags,const char * schedule_address)7420 int caldav_store_resource(struct transaction_t *txn, icalcomponent *ical,
7421                           struct mailbox *mailbox, const char *resource,
7422                           struct caldav_db *caldavdb, unsigned flags,
7423                           const char *schedule_address)
7424 {
7425     int ret;
7426     icalcomponent *comp;
7427     icalcomponent_kind kind;
7428     icalproperty_method meth;
7429     icalproperty *prop;
7430     unsigned mykind = 0, tzbyref = 0;
7431     const char *organizer = NULL;
7432     const char *prop_annot =
7433         DAV_ANNOT_NS "<" XML_NS_CALDAV ">supported-calendar-component-set";
7434     struct buf attrib = BUF_INITIALIZER;
7435     struct caldav_data *cdata;
7436     const char *uid;
7437     struct index_record *oldrecord = NULL, record;
7438     char datestr[80], *mimehdr;
7439     const char *sched_tag;
7440     strarray_t imapflags = STRARRAY_INITIALIZER;
7441 
7442     /* Check for supported component type */
7443     comp = icalcomponent_get_first_real_component(ical);
7444     uid = icalcomponent_get_uid(comp);
7445     kind = icalcomponent_isa(comp);
7446     switch (kind) {
7447     case ICAL_VEVENT_COMPONENT: mykind = CAL_COMP_VEVENT; break;
7448     case ICAL_VTODO_COMPONENT: mykind = CAL_COMP_VTODO; break;
7449     case ICAL_VJOURNAL_COMPONENT: mykind = CAL_COMP_VJOURNAL; break;
7450     case ICAL_VFREEBUSY_COMPONENT: mykind = CAL_COMP_VFREEBUSY; break;
7451     case ICAL_VAVAILABILITY_COMPONENT: mykind = CAL_COMP_VAVAILABILITY; break;
7452 #ifdef HAVE_VPOLL
7453     case ICAL_VPOLL_COMPONENT: mykind = CAL_COMP_VPOLL; break;
7454 #endif
7455     default:
7456         txn->error.precond = CALDAV_SUPP_COMP;
7457         return HTTP_FORBIDDEN;
7458     }
7459 
7460     if (!annotatemore_lookupmask(mailbox->name,
7461                                  prop_annot, httpd_userid, &attrib)
7462         && attrib.len) {
7463         unsigned long supp_comp = strtoul(buf_cstring(&attrib), NULL, 10);
7464 
7465         buf_free(&attrib);
7466 
7467         if (!(mykind & supp_comp)) {
7468             txn->error.precond = CALDAV_SUPP_COMP;
7469             return HTTP_FORBIDDEN;
7470         }
7471     }
7472 
7473     /* Find message UID for the resource, if exists */
7474     caldav_lookup_resource(caldavdb, mailbox->name, resource, &cdata, 0);
7475 
7476     /* does it already exist? */
7477     if (cdata->dav.imap_uid) {
7478         /* Check for change of iCalendar UID */
7479         if (strcmp(cdata->ical_uid, uid)) {
7480             /* CALDAV:no-uid-conflict */
7481             txn->error.precond = CALDAV_UID_CONFLICT;
7482             return HTTP_FORBIDDEN;
7483         }
7484         /* Fetch index record for the resource */
7485         oldrecord = &record;
7486         mailbox_find_index_record(mailbox, cdata->dav.imap_uid, oldrecord);
7487     }
7488 
7489     /* Remove all X-LIC-ERROR properties */
7490     icalcomponent_strip_errors(ical);
7491 
7492     /* Remove all VTIMEZONE components for known TZIDs */
7493     if (namespace_calendar.allow & ALLOW_CAL_NOTZ) {
7494         strip_vtimezones(ical);
7495         tzbyref = 1;
7496     }
7497 
7498     /* If we are just stripping VTIMEZONEs from resource, flag it */
7499     if (flags & TZ_STRIP) strarray_append(&imapflags, DFLAG_UNCHANGED);
7500 
7501     /* Create and cache RFC 5322 header fields for resource */
7502     prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
7503     if (prop) {
7504         organizer = icalproperty_get_organizer(prop);
7505         if (organizer) {
7506             if (!strncasecmp(organizer, "mailto:", 7)) organizer += 7;
7507             assert(!buf_len(&txn->buf));
7508             buf_printf(&txn->buf, "<%s>", organizer);
7509             mimehdr = charset_encode_mimeheader(buf_cstring(&txn->buf),
7510                                                 buf_len(&txn->buf));
7511             spool_replace_header(xstrdup("From"), mimehdr, txn->req_hdrs);
7512             buf_reset(&txn->buf);
7513         }
7514     }
7515 
7516     /* Set Schedule-Tag, if any */
7517     if (flags & NEW_STAG) {
7518         if (oldrecord) sched_tag = message_guid_encode(&oldrecord->guid);
7519         else sched_tag = NULL_ETAG;
7520     }
7521     else if (organizer) sched_tag = cdata->sched_tag;
7522     else sched_tag = cdata->sched_tag = NULL;
7523 
7524     prop = icalcomponent_get_first_property(comp, ICAL_SUMMARY_PROPERTY);
7525     if (prop) {
7526         mimehdr = charset_encode_mimeheader(icalproperty_get_summary(prop), 0);
7527         spool_replace_header(xstrdup("Subject"), mimehdr, txn->req_hdrs);
7528     }
7529     else spool_replace_header(xstrdup("Subject"),
7530                             xstrdup(icalcomponent_kind_to_string(kind)),
7531                             txn->req_hdrs);
7532 
7533     if (schedule_address) {
7534         mimehdr = charset_encode_mimeheader(schedule_address, 0);
7535         spool_replace_header(xstrdup("X-Schedule-User-Address"),
7536                              mimehdr, txn->req_hdrs);
7537     }
7538 
7539     time_to_rfc822(icaltime_as_timet_with_zone(icalcomponent_get_dtstamp(comp),
7540                                                utc_zone),
7541                    datestr, sizeof(datestr));
7542     spool_replace_header(xstrdup("Date"), xstrdup(datestr), txn->req_hdrs);
7543 
7544     buf_reset(&txn->buf);
7545 
7546     /* XXX - validate uid for mime safety? */
7547     if (strchr(uid, '@')) {
7548         buf_printf(&txn->buf, "<%s>", uid);
7549     }
7550     else {
7551         buf_printf(&txn->buf, "<%s@%s>", uid, config_servername);
7552     }
7553     spool_replace_header(xstrdup("Message-ID"),
7554                          buf_release(&txn->buf), txn->req_hdrs);
7555 
7556     buf_setcstr(&txn->buf, ICALENDAR_CONTENT_TYPE);
7557     if ((meth = icalcomponent_get_method(ical)) != ICAL_METHOD_NONE) {
7558         buf_printf(&txn->buf, "; method=%s",
7559                    icalproperty_method_to_string(meth));
7560     }
7561     buf_printf(&txn->buf, "; component=%s", icalcomponent_kind_to_string(kind));
7562     spool_replace_header(xstrdup("Content-Type"),
7563                          buf_release(&txn->buf), txn->req_hdrs);
7564 
7565     buf_printf(&txn->buf, "attachment;\r\n\tfilename=\"%s\"", resource);
7566     if (sched_tag) buf_printf(&txn->buf, ";\r\n\tschedule-tag=%s", sched_tag);
7567     if (tzbyref) buf_printf(&txn->buf, ";\r\n\ttz-by-ref=true");
7568     spool_replace_header(xstrdup("Content-Disposition"),
7569                          buf_release(&txn->buf), txn->req_hdrs);
7570 
7571     spool_remove_header(xstrdup("Content-Description"), txn->req_hdrs);
7572 
7573     /* Store the resource */
7574     ret = dav_store_resource(txn, icalcomponent_as_ical_string(ical), 0,
7575                              mailbox, oldrecord, &imapflags);
7576     strarray_fini(&imapflags);
7577 
7578     switch (ret) {
7579     case HTTP_CREATED:
7580     case HTTP_NO_CONTENT:
7581         if (cdata->organizer) {
7582             if (flags & NEW_STAG) txn->resp_body.stag = sched_tag;
7583 
7584             if (!(flags & PREFER_REP)) {
7585                 /* iCal data has been rewritten - don't return validators */
7586                 txn->resp_body.lastmod = 0;
7587                 txn->resp_body.etag = NULL;
7588             }
7589         }
7590         break;
7591     }
7592 
7593     return ret;
7594 }
7595 
7596 
7597 static struct mime_type_t freebusy_mime_types[] = {
7598     /* First item MUST be the default type */
7599     { ICALENDAR_CONTENT_TYPE, "2.0", "ifb",
7600       (struct buf* (*)(void *)) &my_icalcomponent_as_ical_string,
7601       NULL, NULL, NULL, NULL
7602     },
7603     { "application/calendar+xml; charset=utf-8", NULL, "xfb",
7604       (struct buf* (*)(void *)) &icalcomponent_as_xcal_string,
7605       NULL, NULL, NULL, NULL
7606     },
7607     { "application/calendar+json; charset=utf-8", NULL, "jfb",
7608       (struct buf* (*)(void *)) &icalcomponent_as_jcal_string,
7609       NULL, NULL, NULL, NULL
7610     },
7611     { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
7612 };
7613 
7614 
7615 /* Execute a free/busy query per
7616    http://www.calconnect.org/pubdocs/CD0903%20Freebusy%20Read%20URL.pdf */
meth_get_head_fb(struct transaction_t * txn,void * params)7617 static int meth_get_head_fb(struct transaction_t *txn,
7618                             void *params __attribute__((unused)))
7619 
7620 {
7621     int ret = 0, r, rights;
7622     struct tm *tm;
7623     struct strlist *param;
7624     struct mime_type_t *mime = NULL;
7625     struct propfind_ctx fctx;
7626     struct freebusy_filter fbfilter;
7627     time_t start;
7628     struct icaldurationtype period = icaldurationtype_null_duration();
7629     icalcomponent *cal;
7630 
7631     /* Parse the path */
7632     if ((r = caldav_parse_path(txn->req_uri->path,
7633                                &txn->req_tgt, &txn->error.desc))) return r;
7634 
7635     if (txn->req_tgt.resource ||
7636         !(txn->req_tgt.userid)) {
7637         /* We don't handle GET on a resources or non-calendar collections */
7638         return HTTP_NO_CONTENT;
7639     }
7640 
7641     /* Check ACL for current user */
7642     rights = httpd_myrights(httpd_authstate, txn->req_tgt.mbentry);
7643     if (!(rights & DACL_READFB)) {
7644         /* DAV:need-privileges */
7645         txn->error.precond = DAV_NEED_PRIVS;
7646         txn->error.resource = txn->req_tgt.path;
7647         txn->error.rights = DACL_READFB;
7648         return HTTP_NO_PRIVS;
7649     }
7650 
7651     if (txn->req_tgt.mbentry->server) {
7652         /* Remote mailbox */
7653         struct backend *be;
7654 
7655         be = proxy_findserver(txn->req_tgt.mbentry->server,
7656                               &http_protocol, httpd_userid,
7657                               &backend_cached, NULL, NULL, httpd_in);
7658         if (!be) return HTTP_UNAVAILABLE;
7659 
7660         return http_pipe_req_resp(be, txn);
7661     }
7662 
7663     /* Local Mailbox */
7664 
7665     /* Check/find 'format' */
7666     param = hash_lookup("format", &txn->req_qparams);
7667     if (param) {
7668         if (param->next  /* once only */) return HTTP_BAD_REQUEST;
7669 
7670         for (mime = freebusy_mime_types; mime->content_type; mime++) {
7671             if (is_mediatype(param->s, mime->content_type)) break;
7672         }
7673     }
7674     else mime = freebusy_mime_types;
7675 
7676     if (!mime || !mime->content_type) return HTTP_NOT_ACCEPTABLE;
7677 
7678     memset(&fbfilter, 0, sizeof(struct freebusy_filter));
7679     fbfilter.flags = CHECK_CAL_TRANSP | CHECK_USER_AVAIL;
7680 
7681     /* Check for 'start' */
7682     param = hash_lookup("start", &txn->req_qparams);
7683     if (param) {
7684         if (param->next  /* once only */) return HTTP_BAD_REQUEST;
7685 
7686         fbfilter.start = icaltime_from_rfc3339_string(param->s);
7687         if (icaltime_is_null_time(fbfilter.start)) return HTTP_BAD_REQUEST;
7688 
7689         /* Default to end of given day */
7690         start = icaltime_as_timet_with_zone(fbfilter.start, utc_zone);
7691         tm = localtime(&start);
7692 
7693         period.seconds = 60 - tm->tm_sec;
7694         period.minutes = 59 - tm->tm_min;
7695         period.hours   = 23 - tm->tm_hour;
7696     }
7697     else {
7698         /* Default to start of current day */
7699         start = time(0);
7700         tm = localtime(&start);
7701         tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
7702         fbfilter.start = icaltime_from_timet_with_zone(mktime(tm), 0, utc_zone);
7703 
7704         /* Default to 42 day period */
7705         period.days = 42;
7706     }
7707 
7708     /* Check for 'period' */
7709     param = hash_lookup("period", &txn->req_qparams);
7710     if (param) {
7711         if (param->next  /* once only */ ||
7712             hash_lookup("end", &txn->req_qparams)  /* can't use with 'end' */)
7713             return HTTP_BAD_REQUEST;
7714 
7715         period = icaldurationtype_from_string(param->s);
7716         if (icaldurationtype_is_bad_duration(period)) return HTTP_BAD_REQUEST;
7717     }
7718 
7719     /* Check for 'end' */
7720     param = hash_lookup("end", &txn->req_qparams);
7721     if (param) {
7722         if (param->next  /* once only */) return HTTP_BAD_REQUEST;
7723 
7724         fbfilter.end = icaltime_from_rfc3339_string(param->s);
7725         if (icaltime_is_null_time(fbfilter.end)) return HTTP_BAD_REQUEST;
7726     }
7727     else {
7728         /* Set end based on period */
7729         fbfilter.end = icaltime_add(fbfilter.start, period);
7730     }
7731 
7732 
7733     memset(&fctx, 0, sizeof(struct propfind_ctx));
7734     fctx.txn = txn;
7735     fctx.req_tgt = &txn->req_tgt;
7736     fctx.depth = 2;
7737     fctx.userid = httpd_userid;
7738     fctx.userisadmin = httpd_userisadmin;
7739     fctx.authstate = httpd_authstate;
7740     fctx.reqd_privs = 0;  /* handled by CALDAV:schedule-deliver on Inbox */
7741     fctx.filter_crit = &fbfilter;
7742     fctx.ret = &ret;
7743 
7744     cal = busytime_query_local(txn, &fctx, txn->req_tgt.mbentry->name,
7745                                0, NULL, NULL, NULL);
7746 
7747     if (fbfilter.freebusy.fb) free(fbfilter.freebusy.fb);
7748 
7749     if (cal) {
7750         const char *proto, *host;
7751         icalcomponent *fb;
7752         icalproperty *url;
7753         struct buf *cal_str;
7754 
7755         /* Construct URL */
7756         buf_reset(&txn->buf);
7757         http_proto_host(txn->req_hdrs, &proto, &host);
7758         buf_printf(&txn->buf, "%s://%s%s", proto, host, txn->req_uri->path);
7759         if (URI_QUERY(txn->req_uri))
7760             buf_printf(&txn->buf, "?%s", URI_QUERY(txn->req_uri));
7761 
7762         /* Set URL property */
7763         fb = icalcomponent_get_first_component(cal, ICAL_VFREEBUSY_COMPONENT);
7764         url = icalproperty_new_url(buf_cstring(&txn->buf));
7765         icalcomponent_add_property(fb, url);
7766 
7767         /* Set filename of resource */
7768         buf_reset(&txn->buf);
7769         buf_printf(&txn->buf, "%s.%s",
7770                    txn->req_tgt.userid,
7771                    mime->file_ext);
7772         txn->resp_body.fname = buf_cstring(&txn->buf);
7773 
7774         txn->resp_body.type = mime->content_type;
7775 
7776         /* iCalendar data in response should not be transformed */
7777         txn->flags.cc |= CC_NOTRANSFORM;
7778 
7779         /* Output the iCalendar object */
7780         cal_str = mime->from_object(cal);
7781         icalcomponent_free(cal);
7782 
7783         write_body(HTTP_OK, txn, buf_base(cal_str), buf_len(cal_str));
7784         buf_destroy(cal_str);
7785     }
7786     else ret = HTTP_NOT_FOUND;
7787 
7788     return ret;
7789 }
7790 
7791 
meth_options_cal(struct transaction_t * txn,void * params)7792 static int meth_options_cal(struct transaction_t *txn, void *params)
7793 {
7794     int r;
7795 
7796     /* Parse the path */
7797     if ((r = caldav_parse_path(txn->req_uri->path,
7798                                &txn->req_tgt, &txn->error.desc))) return r;
7799 
7800     if (txn->req_tgt.allow & ALLOW_PATCH) {
7801         /* Add Accept-Patch formats to response */
7802         txn->resp_body.patch = caldav_patch_docs;
7803     }
7804 
7805     return meth_options(txn, params);
7806 }
7807