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