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