Codebase list cyrus-imapd / ccb6ca0
New upstream version 2.4.17+caldav~beta10 Ondřej Surý 9 years ago
135 changed file(s) with 12625 addition(s) and 7211 deletion(s). Raw diff Collapse all Expand all
3434 imap/ctl_cyrusdb
3535 imap/ctl_deliver
3636 imap/ctl_mboxlist
37 imap/ctl_zoneinfo
3738 imap/cvt_cyrusdb
3839 imap/cyr_charset
3940 imap/cyr_dbtool
7879 imap/sync_reset
7980 imap/sync_server
8081 imap/tls_prune
82 imap/tz_err.c
83 imap/tz_err.h
8184 imap/unexpunge
8285 imtest/imtest
8386 lib/chartable.c
107110 sieve/sieve_err.h
108111 sieve/sievec
109112 sieve/sieved
113 tools/vzic/vzic
110114 xversion.h
6363 COMPILE_ET = @COMPILE_ET@
6464
6565 PACKAGE = cyrus-imapd
66 VERSION = 2.4.17-caldav-beta9
66 VERSION = 2.4.17-caldav-beta10
6767 GIT_VERSION = $(VERSION).git$(shell date +'%Y%m%d%H%M')
6868
6969 all:: xversion
219219 /* Do we have an rlim_t? */
220220 #undef HAVE_RLIM_T
221221
222 /* Build RSCALE support into httpd? */
223 #undef HAVE_RSCALE
224
222225 /* Define to 1 if you have the <rxposix.h> header file. */
223226 #undef HAVE_RXPOSIX_H
227
228 /* Do we have built-in support for scheduling params? */
229 #undef HAVE_SCHEDULING_PARAMS
224230
225231 /* Define to 1 if you have the `setrlimit' function. */
226232 #undef HAVE_SETRLIMIT
324330
325331 /* Define to 1 if you have the <unistd.h> header file. */
326332 #undef HAVE_UNISTD_H
333
334 /* Build VAVAILABILITY support into httpd? */
335 #undef HAVE_VAVAILABILITY
336
337 /* Build VPOLL support into httpd? */
338 #undef HAVE_VPOLL
327339
328340 /* Define to 1 if you have the `vprintf' function. */
329341 #undef HAVE_VPRINTF
647647 JSON_CFLAGS
648648 DKIM_LIBS
649649 DKIM_CFLAGS
650 ICU_LIBS
651 ICU_CFLAGS
650652 SQLITE3_LIBS
651653 SQLITE3_CFLAGS
652654 ICAL_LIBS
844846 ICAL_LIBS
845847 SQLITE3_CFLAGS
846848 SQLITE3_LIBS
849 ICU_CFLAGS
850 ICU_LIBS
847851 JSON_CFLAGS
848852 JSON_LIBS'
849853
15511555 C compiler flags for SQLITE3, overriding pkg-config
15521556 SQLITE3_LIBS
15531557 linker flags for SQLITE3, overriding pkg-config
1558 ICU_CFLAGS C compiler flags for ICU, overriding pkg-config
1559 ICU_LIBS linker flags for ICU, overriding pkg-config
15541560 JSON_CFLAGS C compiler flags for JSON, overriding pkg-config
15551561 JSON_LIBS linker flags for JSON, overriding pkg-config
15561562
90639069 # Check whether --enable-http was given.
90649070 if test "${enable_http+set}" = set; then :
90659071 enableval=$enable_http; ENABLE_HTTP=$enableval
9072 else
9073 ENABLE_RSS=no ENABLE_DAV=no
90669074 fi
90679075
90689076
94299437
94309438
94319439
9440 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
9441 /* end confdefs.h. */
9442 #include <ical.h>
9443
9444 _ACEOF
9445 if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
9446 $EGREP "ICAL_SCHEDULESTATUS_PARAMETER" >/dev/null 2>&1; then :
9447
9448 $as_echo "#define HAVE_SCHEDULING_PARAMS /**/" >>confdefs.h
9449
9450 fi
9451 rm -f conftest*
9452
9453
9454 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
9455 /* end confdefs.h. */
9456 #include <ical.h>
9457
9458 _ACEOF
9459 if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
9460 $EGREP "ICAL_VAVAILABILITY_COMPONENT" >/dev/null 2>&1; then :
9461
9462 $as_echo "#define HAVE_VAVAILABILITY /**/" >>confdefs.h
9463
9464 fi
9465 rm -f conftest*
9466
9467
9468 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
9469 /* end confdefs.h. */
9470 #include <ical.h>
9471
9472 _ACEOF
9473 if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
9474 $EGREP "ICAL_VPOLL_COMPONENT" >/dev/null 2>&1; then :
9475
9476 $as_echo "#define HAVE_VPOLL /**/" >>confdefs.h
9477
9478 fi
9479 rm -f conftest*
9480
9481
9482 ENABLE_RSCALE=yes
9483
9484 pkg_failed=no
9485 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ICU" >&5
9486 $as_echo_n "checking for ICU... " >&6; }
9487
9488 if test -n "$ICU_CFLAGS"; then
9489 pkg_cv_ICU_CFLAGS="$ICU_CFLAGS"
9490 elif test -n "$PKG_CONFIG"; then
9491 if test -n "$PKG_CONFIG" && \
9492 { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"icu-i18n\""; } >&5
9493 ($PKG_CONFIG --exists --print-errors "icu-i18n") 2>&5
9494 ac_status=$?
9495 $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
9496 test $ac_status = 0; }; then
9497 pkg_cv_ICU_CFLAGS=`$PKG_CONFIG --cflags "icu-i18n" 2>/dev/null`
9498 test "x$?" != "x0" && pkg_failed=yes
9499 else
9500 pkg_failed=yes
9501 fi
9502 else
9503 pkg_failed=untried
9504 fi
9505 if test -n "$ICU_LIBS"; then
9506 pkg_cv_ICU_LIBS="$ICU_LIBS"
9507 elif test -n "$PKG_CONFIG"; then
9508 if test -n "$PKG_CONFIG" && \
9509 { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"icu-i18n\""; } >&5
9510 ($PKG_CONFIG --exists --print-errors "icu-i18n") 2>&5
9511 ac_status=$?
9512 $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
9513 test $ac_status = 0; }; then
9514 pkg_cv_ICU_LIBS=`$PKG_CONFIG --libs "icu-i18n" 2>/dev/null`
9515 test "x$?" != "x0" && pkg_failed=yes
9516 else
9517 pkg_failed=yes
9518 fi
9519 else
9520 pkg_failed=untried
9521 fi
9522
9523
9524
9525 if test $pkg_failed = yes; then
9526 { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
9527 $as_echo "no" >&6; }
9528
9529 if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
9530 _pkg_short_errors_supported=yes
9531 else
9532 _pkg_short_errors_supported=no
9533 fi
9534 if test $_pkg_short_errors_supported = yes; then
9535 ICU_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "icu-i18n" 2>&1`
9536 else
9537 ICU_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "icu-i18n" 2>&1`
9538 fi
9539 # Put the nasty error message in config.log where it belongs
9540 echo "$ICU_PKG_ERRORS" >&5
9541
9542 ENABLE_RSCALE=no
9543 elif test $pkg_failed = untried; then
9544 { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
9545 $as_echo "no" >&6; }
9546 ENABLE_RSCALE=no
9547 else
9548 ICU_CFLAGS=$pkg_cv_ICU_CFLAGS
9549 ICU_LIBS=$pkg_cv_ICU_LIBS
9550 { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
9551 $as_echo "yes" >&6; }
9552
9553 fi
9554 if test "$ENABLE_RSCALE" != no; then
9555
9556
9557 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for icalrecurrencetype_rscale_supported_calendars in -lical" >&5
9558 $as_echo_n "checking for icalrecurrencetype_rscale_supported_calendars in -lical... " >&6; }
9559 if ${ac_cv_lib_ical_icalrecurrencetype_rscale_supported_calendars+:} false; then :
9560 $as_echo_n "(cached) " >&6
9561 else
9562 ac_check_lib_save_LIBS=$LIBS
9563 LIBS="-lical $LIBS"
9564 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
9565 /* end confdefs.h. */
9566
9567 /* Override any GCC internal prototype to avoid an error.
9568 Use char because int might match the return type of a GCC
9569 builtin and then its argument prototype would still apply. */
9570 #ifdef __cplusplus
9571 extern "C"
9572 #endif
9573 char icalrecurrencetype_rscale_supported_calendars ();
9574 int
9575 main ()
9576 {
9577 return icalrecurrencetype_rscale_supported_calendars ();
9578 ;
9579 return 0;
9580 }
9581 _ACEOF
9582 if ac_fn_c_try_link "$LINENO"; then :
9583 ac_cv_lib_ical_icalrecurrencetype_rscale_supported_calendars=yes
9584 else
9585 ac_cv_lib_ical_icalrecurrencetype_rscale_supported_calendars=no
9586 fi
9587 rm -f core conftest.err conftest.$ac_objext \
9588 conftest$ac_exeext conftest.$ac_ext
9589 LIBS=$ac_check_lib_save_LIBS
9590 fi
9591 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ical_icalrecurrencetype_rscale_supported_calendars" >&5
9592 $as_echo "$ac_cv_lib_ical_icalrecurrencetype_rscale_supported_calendars" >&6; }
9593 if test "x$ac_cv_lib_ical_icalrecurrencetype_rscale_supported_calendars" = xyes; then :
9594
9595 $as_echo "#define HAVE_RSCALE /**/" >>confdefs.h
9596
9597 fi
9598
9599 fi
9600
94329601
94339602
94349603 # Check whether --with-opendkim-libdir was given.
96449813 if test "${enable_replication+set}" = set; then :
96459814 enableval=$enable_replication; ENABLE_REPLICATION=$enableval
96469815 if test "$ENABLE_REPLICATION" != no; then
9647 IMAP_PROGS="$IMAP_PROGS sync_client sync_server sync_reset"
9816 IMAP_PROGS="$IMAP_PROGS sync_client sync_reset"
96489817 fi
96499818 fi
96509819
992992 ENABLE_HTTP=no
993993 AC_ARG_ENABLE(http,
994994 [ --enable-http enable HTTP support],
995 ENABLE_HTTP=$enableval)
995 ENABLE_HTTP=$enableval, [ ENABLE_RSS=no ENABLE_DAV=no ])
996996
997997 if test "$ENABLE_HTTP" != no; then
998998 dnl
10291029 AC_SUBST(ICAL_LIBS)
10301030 AC_SUBST(SQLITE3_CFLAGS)
10311031 AC_SUBST(SQLITE3_LIBS)
1032
1033 AC_EGREP_HEADER(ICAL_SCHEDULESTATUS_PARAMETER, ical.h,
1034 AC_DEFINE(HAVE_SCHEDULING_PARAMS,[],
1035 [Do we have built-in support for scheduling params?]))
1036
1037 AC_EGREP_HEADER(ICAL_VAVAILABILITY_COMPONENT, ical.h,
1038 AC_DEFINE(HAVE_VAVAILABILITY,[],
1039 [Build VAVAILABILITY support into httpd?]))
1040
1041 AC_EGREP_HEADER(ICAL_VPOLL_COMPONENT, ical.h,
1042 AC_DEFINE(HAVE_VPOLL,[],
1043 [Build VPOLL support into httpd?]))
1044
1045 ENABLE_RSCALE=yes
1046 PKG_CHECK_MODULES([ICU], [icu-i18n],, ENABLE_RSCALE=no)
1047 if test "$ENABLE_RSCALE" != no; then
1048 AC_SUBST(ICU_CFLAGS)
1049 AC_SUBST(ICU_LIBS)
1050 AC_CHECK_LIB(ical, icalrecurrencetype_rscale_supported_calendars,
1051 AC_DEFINE(HAVE_RSCALE,[], [Build RSCALE support into httpd?]))
1052 fi
10321053
10331054 CYRUS_OPENDKIM_CHK()
10341055 if test "$opendkimlib" = "yes"; then
10721093 [ --enable-replication enable replication support (experimental)],
10731094 ENABLE_REPLICATION=$enableval
10741095 if test "$ENABLE_REPLICATION" != no; then
1075 IMAP_PROGS="$IMAP_PROGS sync_client sync_server sync_reset"
1096 IMAP_PROGS="$IMAP_PROGS sync_client sync_reset"
10761097 fi)
10771098
10781099 AC_SUBST(IMAP_PROGS)
66 <title>Changes to the Cyrus IMAP Server</title>
77 </head>
88 <body>
9
10 <h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta9</h1>
11 <ul>
12 <li>DAV DB changes now occur in the mailbox API which means
13 that replication works for calendars and addressbooks.</li>
14 <li>IMAP XFER is now based on replication.</li>
15 <li>Added support for free/busy query URL to CalDAV.</li>
16 <li>Authentication for GET/HEAD requests is now done on-demand so that
17 free/busy queries and/or calendar subscriptions can be done
18 anonymously (subject to ACL).</li>
19 <li>Added support for VAVAILABILITY, VPOLL, RSCALE to CalDAV based on
20 current drafts (requires libical from git).</li>
21 <li>Updated http_timezone.c to be compliant with current draft.</li>
22 <li>Numerous other CalDAV fixes/enhancements.</li>
23 </ul>
924
1025 <h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta8</h1>
1126 <ul>
188188 <p><i>Note that mailboxes in the calendar hierarchies (those
189189 under <tt>calendarprefix</tt>) <b>SHOULD NOT</b> be accessed with an IMAP
190190 client as doing so will leave a mailbox in a state unsuitable
191 for CalDAV. To this end, calendar mailboxes will not returned by
191 for CalDAV. To this end, calendar mailboxes will not be returned by
192192 Cyrus <tt>imapd</tt> in response to an IMAP client's request for the
193193 available mailbox list, but Cyrus <tt>imapd</tt> will not otherwise
194194 prevent an IMAP client from accessing them.</i></p>
0 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
1 "http://www.w3.org/TR/html4/strict.dtd">
2 <html>
3 <head>
4 <title>Cyrus CalDAV Scheduling Flowchart</title>
5 <meta http-equiv="content-type" content="text/html; charset=UTF-8">
6 </head>
7 <body>
8 <h1>Cyrus CalDAV Scheduling Flowchart</h1>
9
10 <h3 id="caldav_put">caldav_put() - create/modify via HTTP PUT on a
11 resource or POST (add-member) on a calendar</h3>
12 <ol>
13 <li>Check if the new resource is a scheduling resource (contains
14 ORGANIZER property). If not, skip to step 4.</li>
15 <li>Check for (and load) any existing resource.</li>
16 <li>Check if the authenticated user matches ORGANIZER:
17 <ul>
18 <li>If yes:
19 <ul>
20 <li>If only voter (VPOLL) responses changed,
21 goto <a href="#sched_pollstatus">sched_pollstatus()</a>.</li>
22 <li>Otherwise,
23 goto <a href="#sched_request">sched_request()</a>.</li>
24 </ul>
25 <li>Otherwise, goto <a href="#sched_reply">sched_reply()</a>.</li>
26 </ul>
27 <li>Store the new/modified resource.</li>
28 </ol>
29
30 <h3 id="caldav_delete_sched">caldav_delete_sched() - remove via HTTP
31 DELETE on a resource</h3>
32 <ol>
33 <li>Check if the existing resource is a scheduling resource (has
34 Schedule-Tag). If not, we are done.</li>
35 <li>Load the existing resource.</li>
36 <li>Check if the authenticated user matches ORGANIZER. If yes,
37 goto <a href="#sched_request">sched_request()</a>, otherwise
38 goto <a href="#sched_reply">sched_reply()</a>.</li>
39 </ol>
40
41 <h3 id="caldav_post">caldav_post() - busytime query via HTTP POST on
42 Scheduling Outbox</h3>
43 <ol>
44 <li>Check the ACL on the owner's Scheduling Outbox. If the
45 authenticated user doesn't have the DACL_SCHEDFB right, fail.</li>
46 <li><a href="#sched_busytime_query">sched_busytime_query()</a>.</li>
47 </ol>
48
49 <hr>
50
51 <h3 id="sched_pollstatus">sched_pollstatus - perform a voter
52 response update</h3>
53 <ol>
54 <li></li>
55 </ol>
56
57 <hr>
58
59 <h3 id="sched_request">sched_request() - perform an organizer
60 request / attendee status update</h3>
61 <ol>
62 <li>Check the ACL on the owner's Scheduling Outbox. If the
63 authenticated user doesn't have the DACL_INVITE right, fail.</li>
64 <li>If the request includes a resource, then set METHOD:REQUEST,
65 otherwise set METHOD:CANCEL.</li>
66 <li>Create an iTIP message template, copying over any
67 CALSCALE property and VTIMEZONE components.</li>
68 <li>If not an attendee status update and the existing resource is a
69 scheduling resource:
70 Foreach component in the existing resource, add it and
71 its SEQUENCE to our hash table keyed by RECURRENCE-ID (for
72 comparison against new/modified resource).</li>
73 <li>Create a hash table of attendees. This will hold
74 attendee-specific iTIP messages.</li>
75 <li>Foreach component in the new/modified resource:</li>
76 <ol type=a>
77 <li>Lookup (by RECURRENCE-ID) and remove the component from the
78 hash table of existing components.</li>
79 <li>If the component exists compare all of DTSTART, DTEND,
80 DURATION, RRULE, RDATE, EXDATE to those of the new
81 component.</li>
82 <li>If the component is new or changed,
83 then <a href="#process_attendees">process_attendees()</a>.</li>
84 </ol>
85 <li>Foreach remaining component in the hash table of existing
86 components do <a href="#sched_cancel">sched_cancel()</a>.</li>
87 <li>Foreach iTIP message in our hash table of
88 ATTENDEES, <a href="#sched_deliver">sched_deliver()</a> the iTIP
89 message.</li>
90 <li>Foreach component in the new/modified resource update the
91 SCHEDULE-STATUS of each ATTENDEE.</li>
92 </ol>
93
94 <h3 id="process_attendees">process_attendees() - create a suitable
95 iTIP request message for each attendee</h3>
96 <ol>
97 <li>Foreach ATTENDEE in the component, remove the SCHEDULE-STATUS
98 parameter, and set PROPSTAT=NEEDS-ACTION if required.</li>
99 <li>Make a copy of the component and
100 <a href="#clean_component">clean_component()</a>.</li>
101 <li>Foreach ATTENDEE in the cleaned component:</li>
102 <ol type=a>
103 <li>Check the CalDAV Scheduling parameters. If SCHEDULE-AGENT
104 != SERVER, skip to the next attendee.</li>
105 <li>Lookup attendee in our hash table.</li>
106 <li>If it doesn't exist, create a clone of our iTIP template and
107 insert it into our hash table of attendees.</li>
108 <li>Add the component to the attendee's iTIP message.</li>
109 <li>Add the component “number” to our mask of new components
110 appearing in the attendee's iTIP message.</li>
111 </ol>
112 <li>If the component is not the "master", foreach attendee do
113 <a href="#sched_exclude">sched_exclude()</a>.</li>
114 </ol>
115
116 <h3 id="sched_exclude">sched_exclude() - exclude an attendee from a
117 recurrence instance</h3>
118 <ol>
119 <li>If the component did not appear in the attendee's iTIP
120 message, add an EXDATE property (based on the RECURRENCE-ID of
121 the component) to the master component of the attendee's iTIP
122 message.</li>
123 </ol>
124
125 <h3 id="sched_cancel">sched_cancel() - cancel an organizer event/task</h3>
126 <ol>
127 <li>Set STATUS:CANCELLED on the component.</li>
128 <li><a href="#process_attendees">process_attendees()</a>.</li>
129 </ol>
130
131 <hr>
132
133 <h3 id="sched_reply">sched_reply() - perform an attendee reply</h3>
134 <ol>
135 <li>Check the CalDAV Scheduling parameters on ORGANIZER. If
136 SCHEDULE-AGENT != SERVER, we are done.</li>
137 <li>Check the ACL on the owner's Scheduling Outbox. If the
138 authenticated user doesn't have the DACL_REPLY right, fail.</li>
139 <li>Create a new iTIP (METHOD:REPLY) message, copying over any
140 CALSCALE property and VTIMEZONE components.</li>
141 <li>Foreach component in the existing resource:</li>
142 <ol type=a>
143 <li><a href="#trim_attendees">trim_attendees()</a>.</li>
144 <li>Add the trimmed component and the attendee's PARTSTAT to our
145 hash table keyed by RECURRENCE-ID (for comparison against
146 new/modified resource).</li>
147 </ol>
148 <li>Foreach component in the new/modified resource:</li>
149 <ol type=a>
150 <li><a href="#trim_attendees">trim_attendees()</a>.</li>
151 <li>Lookup (by RECURRENCE-ID) and remove the component from the
152 hash table of existing components.</li>
153 <li>If the component exists:</li>
154 <ol type=i>
155 <li>If component is VPOLL, add voter responses to REPLY
156 via <a href="#sched_vpoll_reply">sched_vpoll_reply().</a></li>
157 <li>Otherwise, compare the PARTSTAT of the ATTENDEE to that of
158 the new component.</li>
159 </ol>
160 <li>If the component is new or the PARTSTAT has changed:</li>
161 <ol type=i>
162 <li><a href="#clean_component">clean_component()</a>.</li>
163 <li>Add the component to our iTIP message.</li>
164 <li>Add the component “number” to our mask of new components
165 appearing in our iTIP message.</li>
166 </ol>
167 </ol>
168 <li>Foreach remaining component in the hash table of existing
169 components do <a href="#sched_decline">sched_decline()</a>.</li>
170 <li><a href="#sched_deliver">sched_deliver()</a> our iTIP
171 message.</li>
172 <li>Foreach component in the new/modified resource that appeared
173 in our iTIP message, update the SCHEDULE-STATUS of the ORGANIZER.</li>
174 </ol>
175
176 <h3 id="trim_attendees">trim_attendees() - remove all attendees
177 other than the one replying</h3>
178 <ol>
179 <li>Clone the component and remove all ATTENDEE properties other
180 than the one corresponding to the owner of the calendar.</li>
181 <li>Return the ATTENDEE property of owner, his/her PARTSTAT
182 parameter, and the RECURRENCE-ID of the component.</li>
183 </ol>
184
185 <h3 id="sched_vpoll_reply">sched_vpoll_reply() - add voter
186 responses to VPOLL reply</h3>
187 <ol>
188 <li></li>
189 </ol>
190
191 <h3 id="sched_decline">sched_decline() - decline a recurrence
192 instance for an attendee</h3>
193 <ol>
194 <li>Set PARTSTAT of ATTENDEE to DECLINED.</li>
195 <li><a href="#clean_component">clean_component()</a>.</li>
196 <li>Add the component to our iTIP message.</li>
197 </ol>
198
199 <hr>
200
201 <h3 id="clean_component">clean_component() - sanitize a component
202 for use in an iTIP message</h3>
203 <ol>
204 <li>Update DTSTAMP.</li>
205 <li>Remove any VALARM components.</li>
206 <li>For a reply/decline only, remove scheduling parameters from
207 ORGANIZER.</li>
208 </ol>
209
210 <h3 id="sched_deliver">sched_deliver() - deliver an iTIP message to
211 a recipient</h3>
212 <ol>
213 <li>Lookup the recipient.</li>
214 <li>If local to our server goto
215 <a href="#sched_deliver_local">sched_deliver_local()</a>,
216 otherwise goto
217 <a href="#sched_deliver_remote">sched_deliver_remote()</a>.</li>
218 </ol>
219
220 <hr>
221
222 <h3 id="sched_deliver_local">sched_deliver_local() - deliver an
223 iTIP message to a local user</h3>
224 <ol>
225 <li>Check the ACL on the owner's Scheduling Inbox. If the
226 sender doesn't have the proper right (DACL_INVITE for
227 request/cancel, DACL_REPLY for reply), fail.</li>
228 <li>Search the recipient's calendars for a resource having the
229 specified UID.</li>
230 <li>If the resource doesn't exist:</li>
231 <ol type=a>
232 <li>If the iTIP method is REPLY, fail (we are done).</li>
233 <li>If the iTIP method is CANCEL, ignore it (we are done).</li>
234 <li>Otherwise, create a new (empty) attendee object and target
235 the recipient's Default calendar.</li>
236 </ol>
237 <li>Otherwise, load the existing resource.</li>
238 <li>Update the new/existing resource:</li>
239 <ol type=a>
240 <li>If the iTIP method is CANCEL, set STATUS:CANCELLED on all
241 existing components.</li>
242 <li>If the iTIP method is REPLY, do
243 <a href="#deliver_merge_reply">deliver_merge_reply()</a>.</li>
244 <li>If the iTIP method is REQUEST, do
245 <a href="#deliver_merge_request">deliver_merge_request()</a>.</li>
246 <li>If the iTIP method is POLLSTATUS, do
247 <a href="#deliver_merge_pollstatus">deliver_merge_pollstatus()</a>.</li>
248 </ol>
249 <li>Store the new/updated resource in the recipient's target
250 calendar.</li>
251 <li>Record the delivery status (SCHEDULE-STATUS).</li>
252 <li>If the iTIP message is something other than just a PARTSTAT
253 update from an attendee, store the iTIP message as a new
254 resource in the recipient's Inbox.</li>
255 <li>If the iTIP method is REPLY, send an update other attendees
256 via <a href="#sched_pollstatus">sched_pollstatus()</a> (VPOLL only)
257 or <a href="#sched_request">sched_request()</a>.</li>
258 </ol>
259
260 <h3 id="deliver_merge_reply">deliver_merge_reply() - update
261 an organizer resource with an attendee reply</h3>
262 <ol>
263 <li>Foreach component in the existing resource, add it to our
264 hash table keyed by RECURRENCE-ID (for comparison against
265 iTIP message).</li>
266 <li>Foreach component in the iTIP message:</li>
267 <ol type=a>
268 <li>Lookup (by RECURRENCE-ID) the component from the
269 hash table of existing components.</li>
270 <li>If the component doesn't exist (new recurrence overridden by
271 ATTENDEE) create a new recurring component:</li>
272 <ol type=i>
273 <li>Clone the existing master component.</li>
274 <li>Remove the RRULE property.</li>
275 <li>Add the RECURRENCE-ID from the iTIP message.</li>
276 <li>Replace the DTSTART, DTEND, SEQUENCE properties with those
277 from the iTIP message.</li>
278 <li>Add the new component to our existing resource.</li>
279 </ol>
280 <li>Get the sending ATTENDEE from the iTIP message.</li>
281 <li>Find the matching ATTENDEE in the existing component.</li>
282 <li>If not found (ATTENDEE added themselves to this recurrence),
283 add new ATTENDEE to the component.</li>
284 <li>Set the ATTENDEE PARTSTAT, RSVP, and SCHEDULE-STATUS
285 parameters in the existing component.</li>
286 <li>If the component is VPOLL, update the voter responses in the
287 existing component via
288 <a href="#deliver_merge_vpoll_reply">deliver_merge_vpoll_reply()</a>.</li>
289 </ol>
290 <li>Return the sending ATTENDEE.</li>
291 </ol>
292
293 <h3 id="deliver_merge_vpoll_reply">deliver_merge_vpoll_reply() - update
294 an organizer resource with voter responses</h3>
295 <ol>
296 <li>Foreach sub-component in the existing resource, replace any
297 voter response(s) with those from the reply.</li>
298 </ol>
299
300 <h3 id="deliver_merge_request">deliver_merge_request() -
301 create/update an attendee resource with an organizer request</h3>
302 <ol>
303 <li>Foreach VTIMEZONE component in the existing resource, add it
304 to our hash table keyed by TZID (for comparison against iTIP
305 message).</li>
306 <li>Foreach VTIMEZONE component in the iTIP message:</li>
307 <ol type=a>
308 <li>Lookup (by TZID) the VTIMEZONE component from the hash table of
309 existing components.</li>
310 <li>If the component exists, remove it from the existing
311 object.</li>
312 <li>Add the VTIMEZONE from the iTIP message to our existing
313 object.</li>
314 </ol>
315 <li>Foreach component in the existing resource, add it to our
316 hash table keyed by RECURRENCE-ID (for comparison against
317 iTIP message).</li>
318 <li>Foreach component in the iTIP message:</li>
319 <ol type=a>
320 <li>Clone a new component from the iTIP component.</li>
321 <li>Lookup (by RECURRENCE-ID) the component from the
322 hash table of existing components.</li>
323 <li>If the component exists:</li>
324 <ol type=i>
325 <li>Compare the SEQUENCE of the new component to the existing
326 component to see if it has changed.</li>
327 <li>Copy any COMPLETED, PERCENT-COMPLETE, or TRANSP properties
328 from the existing component to the new component.</li>
329 <li>Copy any ORGANIZER SCHEDULE-STATUS parameter
330 from the existing component to the new component.</li>
331 <li>Remove the existing component from the existing object.</li>
332 </ol>
333 <li>Add the new component to the existing object.</li>
334 </ol>
335 </ol>
336
337 <h3 id="deliver_merge_pollstatus">deliver_merge_pollstatus() -
338 update voter responses on a voter resource</h3>
339 <ol>
340 <li>Foreach sub-component in the existing resource, add it to our
341 hash table keyed by POLL-ITEM-ID (for comparison against
342 iTIP message). The sub-component entry includes a hash table
343 of VOTERs.</li>
344 <li>Foreach sub-component in the iTIP message:</li>
345 <ol type=a>
346 <li>Lookup (by POLL-ITEM-ID) the sub-component from the
347 hash table of existing sub-components.</li>
348 <li>If the component exists, foreach VOTER in the sub-component
349 in the iTIP message:
350 <ol type=i>
351 <li>Lookup VOTER in the hash table of existing
352 sub-component.</li>
353 <li>Add/update VOTER response.</li>
354 </ol>
355 </li>
356 </ol>
357 </ol>
358
359 <hr>
360
361 <h3 id="sched_deliver_remote">sched_deliver_remote() - deliver an
362 iTIP message to a remote user</h3>
363 <ol>
364 <li>If the recipient is local to our Murder, goto
365 <a href="#isched_send">isched_send()</a>, otherwise
366 goto <a href="#imip_send">imip_send()</a>.</li>
367 <li>Retrieve status of iTIP message delivery.</li>
368 </ol>
369
370 <h3 id="isched_send">isched_send() - deliver an iTIP message to a
371 remote user via iSchedule (HTTP)</h3>
372 <ol>
373 </ol>
374
375 <h3 id="imip_send">imip_send() - deliver an iTIP message to a
376 remote user via iMIP (SMTP)</h3>
377 <ol>
378 </ol>
379
380 <hr>
381
382 <h3 id="sched_busytime_query">sched_busytime_query() - perform a
383 busytime query</h3>
384 <ol>
385 </ol>
386
387 <h3 id="busytime_query_local">busytime_query_local() - perform a
388 busytime query on a local user</h3>
389 <ol>
390 </ol>
391
392 <h3 id="busytime_query_remote">busytime_query_remote() - perform a
393 busytime query on a remote user</h3>
394 <ol>
395 </ol>
396
397 </body>
398 </html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:54 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:18 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:54 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:18 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:54 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:18 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:54 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:18 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:54 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:18 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:54 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:18 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:54 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:18 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:55 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:18 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:55 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:18 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:55 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:18 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:55 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:18 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:55 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:18 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:55 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:19 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:55 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:19 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:55 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:19 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:55 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:19 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:55 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:19 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:55 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:19 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:55 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:19 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
372372 should have.</p>
373373
374374 <p style="margin-left:11%;"><b>caldav_allowscheduling:</b>
375 1</p>
376
377 <p style="margin-left:18%;">If enabled, the server will
378 perform calendar scheduling operations.</p>
375 on</p>
376
377 <p style="margin-left:18%;">Enable calendar scheduling
378 operations. If set to &quot;apple&quot;, the server will
379 emulate Apple CalendarServer behavior as closely as
380 possible.</p>
381
382 <p style="margin-left:18%; margin-top: 1em">Allowed values:
383 <i>off</i>, <i>on</i>, <i>apple</i></p>
379384
380385 <p style="margin-left:11%;"><b>caldav_realm:</b>
381386 &lt;none&gt;</p>
394399 be at the toplevel of the shared namespace. A user&rsquo;s
395400 personal calendar hierarchy will be a child of their
396401 Inbox.</p>
402
403
404 <p style="margin-left:11%;"><b>calendar_user_address_set:</b>
405 &lt;none&gt;</p>
406
407 <p style="margin-left:18%;">Space-separated list of domains
408 corresponding to calendar user addresses for which the
409 server is responsible. If not set (the default), the value
410 of the &quot;servername&quot; option will be used.</p>
397411
398412 <p style="margin-left:11%;"><b>carddav_realm:</b>
399413 &lt;none&gt;</p>
12641278
12651279 <p style="margin-left:18%; margin-top: 1em">Allowed values:
12661280 <i>header</i>, <i>index</i>, <i>cache</i>, <i>expunge</i>,
1267 <i>squat</i>, <i>lock</i></p>
1281 <i>squat</i>, <i>lock</i>, <i>dav</i></p>
12681282
12691283 <p style="margin-left:11%;"><b>metapartition-name:</b>
12701284 &lt;none&gt;</p>
19351949 <p style="margin-left:18%;">This is the hostname visible in
19361950 the greeting messages of the POP, IMAP and LMTP daemons. If
19371951 it is unset, then the result returned from gethostname(2) is
1938 used.</p>
1952 used. This is also the value used by murder clusters to
1953 identify the host name. It should be resolvable by DNS to
1954 the correct host, and unique within an active cluster. If
1955 you are using low level replication (e.g. drbd) then it
1956 should be the same on each copy and the DNS name should also
1957 be moved to the new master on failover.</p>
19391958
19401959 <p style="margin-left:11%;"><b>serverinfo:</b> on</p>
19411960
22252244 when authenticating to a sync server. Prefix with a channel
22262245 name to only apply for that channel</p>
22272246
2228 <p style="margin-left:11%;"><b>sync_port:</b> csync</p>
2247 <p style="margin-left:11%;"><b>sync_port:</b>
2248 &lt;none&gt;</p>
22292249
22302250 <p style="margin-left:18%;">Name of the service (or port
2231 number) of the replication service on replica host. The
2232 default is &quot;csync&quot; which is usally port 2005, but
2233 any service name or numeric port can be specified. Prefix
2234 with a channel name to only apply for that channel</p>
2251 number) of the replication service on replica host. Prefix
2252 with a channel name to only apply for that channel. If not
2253 specified, the replication client will first try
2254 &quot;imap&quot; (port 143) to check if imapd supports
2255 replication, otherwise it will default to &quot;csync&quot;
2256 (usually port 2005).</p>
22352257
22362258 <p style="margin-left:11%;"><b>sync_realm:</b>
22372259 &lt;none&gt;</p>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:19 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:19 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:19 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:19 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:19 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:19 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:56 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:57 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:57 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:57 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:57 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:57 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:57 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:20 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:57 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:21 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
4848
4949
5050 <p style="margin-left:11%; margin-top: 1em"><b>sync_client</b>
51 [ <b>&minus;v</b> ] [ <b>&minus;l</b> ] [ <b>&minus;z</b> ]
52 [ <b>&minus;C</b> <i>config-file</i> ] [ <b>&minus;S</b>
53 <i>servername</i> ] <br>
51 [ <b>&minus;v</b> ] [ <b>&minus;l</b> ] [ <b>&minus;L</b> ]
52 [ <b>&minus;z</b> ] [ <b>&minus;C</b> <i>config-file</i> ] [
53 <b>&minus;S</b> <i>servername</i> ] <br>
5454 [ <b>&minus;f</b> <i>input-file</i> ] [ <b>&minus;F</b>
5555 <i>shutdown_file</i> ] [ <b>&minus;w</b>
5656 <i>wait_interval</i> ] <br>
5757 [ <b>&minus;t</b> <i>timeout</i> ] [ <b>&minus;d</b>
5858 <i>delay</i> ] [ <b>&minus;r</b> ] [ <b>&minus;u</b> ] [
5959 <b>&minus;m</b> ] <br>
60 [ <b>&minus;s</b> ] <i>objects</i>...</p>
60 [ <b>&minus;s</b> ] [ <b>&minus;p</b> <i>partition</i> ]
61 <i>objects</i>...</p>
6162
6263 <h2>DESCRIPTION
6364 <a name="DESCRIPTION"></a>
101102
102103
103104 <p>Verbose logging mode.</p></td></tr>
105 <tr valign="top" align="left">
106 <td width="11%"></td>
107 <td width="3%">
108
109
110 <p><b>&minus;L</b></p></td>
111 <td width="8%"></td>
112 <td width="78%">
113
114
115 <p>Perform only local mailbox operations (do not do mupdate
116 operations).</p> </td></tr>
104117 <tr valign="top" align="left">
105118 <td width="11%"></td>
106119 <td width="3%">
258271 <b>sync_client(8).</b></p> </td></tr>
259272 </table>
260273
274 <p style="margin-left:11%;"><b>&minus;p</b>
275 <i>partition</i></p>
276
277 <p style="margin-left:22%;">In mailbox or user replication
278 mode: provides the name of the partition on the replica to
279 which the mailboxes/users should be replicated.</p>
280
261281 <h2>FILES
262282 <a name="FILES"></a>
263283 </h2>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:57 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:21 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
4848
4949 <p style="margin-left:11%; margin-top: 1em"><b>sync_reset</b>
5050 [ <b>&minus;C</b> <i>config-file</i> ] [ <b>&minus;v</b> ] [
51 <b>&minus;f</b> ]</p>
51 <b>&minus;L</b> ] [ <b>&minus;f</b> ]</p>
5252
5353 <h2>DESCRIPTION
5454 <a name="DESCRIPTION"></a>
9191 <td width="3%">
9292
9393
94 <p><b>&minus;L</b></p></td>
95 <td width="8%"></td>
96 <td width="78%">
97
98
99 <p>Perform only local mailbox operations (do not do mupdate
100 operations).</p> </td></tr>
101 <tr valign="top" align="left">
102 <td width="11%"></td>
103 <td width="3%">
104
105
94106 <p><b>&minus;f</b></p></td>
95107 <td width="8%"></td>
96108 <td width="78%">
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:57 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:21 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:57 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:21 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:57 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:21 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:57 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:21 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Dec 16 16:30:57 2013 -->
1 <!-- CreationDate: Tue Jul 22 16:36:21 2014 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
208208 <TD>A Protocol for Remotely Managing Sieve Scripts</TD></TR>
209209
210210 <TR><TD COLSPAN=2><br><h2>HTTP</h2></TD></TR>
211 <TR><TD><A HREF="http://tools.ietf.org/html/rfc2616">RFC 2616</A></TD>
212 <TD>Hypertext Transfer Protocol -- HTTP/1.1
213 <BR><I>being revised by</I>
214 <A HREF="http://tools.ietf.org/id/draft-ietf-httpbis">
215 draft-ietf-httpbis-*</A></TD></TR>
216 <TR><TD><A HREF="http://tools.ietf.org/html/rfc2617">RFC 2617</A></TD>
211 <TR><TD><A HREF="http://tools.ietf.org/html/rfc7230">RFC 7230</A></TD>
212 <TD>Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing</TD></TR>
213 <TR><TD><A HREF="http://tools.ietf.org/html/rfc7231">RFC 7231</A></TD>
214 <TD>Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content</TD></TR>
215 <TR><TD><A HREF="http://tools.ietf.org/html/rfc7232">RFC 7232</A></TD>
216 <TD>Hypertext Transfer Protocol (HTTP/1.1): Conditional Requests</TD></TR>
217 <TR><TD><A HREF="http://tools.ietf.org/html/rfc7233">RFC 7233</A></TD>
218 <TD>Hypertext Transfer Protocol (HTTP/1.1): Range Requests</TD></TR>
219 <TR><TD><A HREF="http://tools.ietf.org/html/rfc7234">RFC 7234</A></TD>
220 <TD>Hypertext Transfer Protocol (HTTP/1.1): Caching</TD></TR>
221 <TR><TD><A HREF="http://tools.ietf.org/html/rfc7235">RFC 7235</A></TD>
222 <TD>Hypertext Transfer Protocol (HTTP/1.1): Authentication</TD></TR>
223 <TR><TD><A HREF="http://tools.ietf.org/html/rfc2617">RFC 2617</A>
224 <BR><I>being updated by</I>
225 <BR><A HREF="http://tools.ietf.org/html/draft-ietf-httpauth-basicauth-update">
226 draft-ietf-httpauth-basicauth-update</A>
227 <BR><A HREF="http://tools.ietf.org/html/draft-ietf-httpauth-digest">
228 draft-ietf-httpauth-digest</A></TD>
217229 <TD>HTTP Authentication: Basic and Digest Access Authentication</TD></TR>
218230 <TR><TD><A HREF="http://tools.ietf.org/html/rfc4559">RFC 4559</A></TD>
219231 <TD>SPNEGO-based Kerberos and NTLM HTTP Authentication in Microsoft Windows</TD></TR>
225237 <TD>HTTP Over TLS</TD></TR>
226238 <TR><TD><A HREF="http://tools.ietf.org/html/rfc6797">RFC 6797</A></TD>
227239 <TD>HTTP Strict Transport Security (HSTS)</TD></TR>
240 <TR><TD><A HREF="http://tools.ietf.org/html/rfc7239">RFC 7239</A></TD>
241 <TD>Forwarded HTTP Extension</TD></TR>
242 <TR><TD><A HREF="http://tools.ietf.org/html/rfc7240">RFC 7240</A></TD>
243 <TD>Prefer Header for HTTP</TD></TR>
228244 <TR><TD><A HREF="http://tools.ietf.org/html/rfc4918">RFC 4918</A></TD>
229245 <TD>HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)</TD></TR>
230246 <TR><TD><A HREF="http://tools.ietf.org/html/rfc3253">RFC 3253</A></TD>
249265 <TD>CardDAV: vCard Extensions to Web Distributed Authoring and Versioning (WebDAV)</TD></TR>
250266 <TR><TD><A HREF="http://tools.ietf.org/html/rfc6764">RFC 6764</A></TD>
251267 <TD>Locating Services for Calendaring Extensions to WebDAV (CalDAV) and vCard Extensions to WebDAV (CardDAV)</TD></TR>
268 <TR><TD><A HREF="http://tools.ietf.org/html/draft-daboo-icalendar-rscale">draft-daboo-icalendar-rscale</A></TD>
269 <TD>Non-Gregorian Recurrence Rules in iCalendar</TD></TR>
270 <TR><TD><A HREF="http://tools.ietf.org/html/draft-daboo-calendar-availability">draft-daboo-calendar-availability</A></TD>
271 <TD>Calendar Availability</TD></TR>
272 <TR><TD><A HREF="http://tools.ietf.org/html/draft-york-vpoll">draft-york-vpoll</A></TD>
273 <TD>VPOLL: Consensus Scheduling Component for iCalendar</TD></TR>
252274 <TR><TD><A HREF="http://tools.ietf.org/html/draft-desruisseaux-ischedule">draft-desruisseaux-ischedule</A></TD>
253275 <TD>Internet Calendar Scheduling Protocol (iSchedule)</TD></TR>
254276 <TR><TD><A HREF="http://tools.ietf.org/html/draft-douglass-timezone-service">draft-douglass-timezone-service</A></TD>
255277 <TD>Timezone Service Protocol</TD></TR>
256278 <TR><TD><A HREF="http://tools.ietf.org/html/draft-thomson-hybi-http-timeout">draft-thomson-hybi-http-timeout</A></TD>
257279 <TD>Hypertext Transfer Protocol (HTTP) Keep-Alive Header</TD></TR>
258 <TR><TD><A HREF="http://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded">draft-ietf-appsawg-http-forwarded</A></TD>
259 <TD>Forwarded HTTP Extension</TD></TR>
260 <TR><TD><A HREF="http://tools.ietf.org/html/draft-snell-http-prefer">draft-snell-http-prefer</A></TD>
261 <TD>Prefer Header for HTTP</TD></TR>
262280 <TR><TD><A HREF="http://tools.ietf.org/html/draft-murchison-webdav-prefer">draft-murchison-webdav-prefer</A></TD>
263281 <TD>Use of the Prefer Header Field in Web Distributed Authoring and Versioning (WebDAV)</TD></TR>
264282 <TR><TD><A HREF="http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-ctag.txt">caldav-ctag</A></TD>
284302 </TD></TR>
285303 <TR><TD><A HREF="http://tools.ietf.org/html/rfc6047">RFC 6047</a></TD>
286304 <TD>iCalendar Message-Based Interoperability Protocol (iMIP)</TD></TR>
305 <TR><TD><A HREF="http://tools.ietf.org/html/rfc6231">RFC 6231</a></TD>
306 <TD>xCal: The XML Format for iCalendar</TD></TR>
307 <TR><TD><A HREF="http://tools.ietf.org/html/rfc7265">RFC 7265</a></TD>
308 <TD>jCal: The JSON Format for iCalendar</TD></TR>
287309 <TR><TD><A HREF="http://tools.ietf.org/html/rfc6350">RFC 6350</a></TD>
288310 <TD>vCard Format Specification</TD></TR>
289311 <TR><TD><A HREF="http://tools.ietf.org/html/rfc6376">RFC 6376</a></TD>
0 Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta9
1
2 * DAV DB changes now occur in the mailbox API which means that
3 replication works for calendars and addressbooks.
4 * IMAP XFER is now based on replication.
5 * Added support for free/busy query URL to CalDAV.
6 * Authentication for GET/HEAD requests is now done on-demand so that
7 free/busy queries and/or calendar subscriptions can be done
8 anonymously (subject to ACL).
9 * Added support for VAVAILABILITY, VPOLL, RSCALE to CalDAV based on
10 current drafts (requires libical from git).
11 * Updated http_timezone.c to be compliant with current draft.
12 * Numerous other CalDAV fixes/enhancements.
13
014 Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta8
115
216 * Fixed bug in parsing of Accept header (now accepts */* and type/*).
191191
192192 HTTP
193193
194 RFC 2616 Hypertext Transfer Protocol -- HTTP/1.1
195 being revised by draft-ietf-httpbis-*
196
197 RFC 2617 HTTP Authentication: Basic and Digest Access Authentication
194 RFC 7230 Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and
195 Routing
196
197 RFC 7231 Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
198
199 RFC 7232 Hypertext Transfer Protocol (HTTP/1.1): Conditional Requests
200
201 RFC 7233 Hypertext Transfer Protocol (HTTP/1.1): Range Requests
202
203 RFC 7234 Hypertext Transfer Protocol (HTTP/1.1): Caching
204
205 RFC 7235 Hypertext Transfer Protocol (HTTP/1.1): Authentication
206
207 RFC 2617
208 being updated by
209 draft-ietf-httpauth-basicauth-update
210 draft-ietf-httpauth-digest HTTP Authentication: Basic and Digest Access
211 Authentication
198212
199213 RFC 4559 SPNEGO-based Kerberos and NTLM HTTP Authentication in
200214 Microsoft Windows
207221
208222 RFC 6797 HTTP Strict Transport Security (HSTS)
209223
224 RFC 7239 Forwarded HTTP Extension
225
226 RFC 7240 Prefer Header for HTTP
227
210228 RFC 4918 HTTP Extensions for Web Distributed Authoring and Versioning
211229 (WebDAV)
212230
240258 RFC 6764 Locating Services for Calendaring Extensions to WebDAV
241259 (CalDAV) and vCard Extensions to WebDAV (CardDAV)
242260
261 draft-daboo-icalendar-rscale Non-Gregorian Recurrence Rules in
262 iCalendar
263
264 draft-daboo-calendar-availability Calendar Availability
265
266 draft-york-vpoll VPOLL: Consensus Scheduling Component for iCalendar
267
243268 draft-desruisseaux-ischedule Internet Calendar Scheduling Protocol
244269 (iSchedule)
245270
248273 draft-thomson-hybi-http-timeout Hypertext Transfer Protocol (HTTP)
249274 Keep-Alive Header
250275
251 draft-ietf-appsawg-http-forwarded Forwarded HTTP Extension
252
253 draft-snell-http-prefer Prefer Header for HTTP
254
255276 draft-murchison-webdav-prefer Use of the Prefer Header Field in Web
256277 Distributed Authoring and Versioning (WebDAV)
257278
279300
280301 RFC 6047 iCalendar Message-Based Interoperability Protocol (iMIP)
281302
303 RFC 6231 xCal: The XML Format for iCalendar
304
305 RFC 7265 jCal: The JSON Format for iCalendar
306
282307 RFC 6350 vCard Format Specification
283308
284309 RFC 6376 DomainKeys Identified Mail (DKIM) Signatures
5858 CYRUS_GROUP=@cyrus_group@
5959
6060 DEFS = @DEFS@ @LOCALDEFS@
61 CPPFLAGS = -I.. -I$(srcdir)/../lib @COM_ERR_CPPFLAGS@ @SIEVE_CPPFLAGS@ @CPPFLAGS@ @SASLFLAGS@ @XML2_CFLAGS@ @ICAL_CFLAGS@ @SQLITE3_CFLAGS@ @DKIM_CFLAGS@
62 IMAP_LIBS = @IMAP_LIBS@ @LIB_RT@
61 CPPFLAGS = -I.. -I$(srcdir)/../lib @COM_ERR_CPPFLAGS@ @SIEVE_CPPFLAGS@ @CPPFLAGS@ @SASLFLAGS@ @XML2_CFLAGS@ @ICAL_CFLAGS@ @SQLITE3_CFLAGS@ @ICU_CFLAGS@ @DKIM_CFLAGS@ @JSON_CFLAGS@
62 IMAP_LIBS = @IMAP_LIBS@ @LIB_RT@ @ICAL_LIBS@ @ICU_LIBS@ @SQLITE3_LIBS@
6363
6464 RSS_OBJS = http_rss.o
6565 DAV_OBJS = http_dav.o dav_db.o
6666 CALDAV_OBJS = http_caldav.o caldav_db.o smtpclient.o xcal.o jcal.o
6767 CARDDAV_OBJS = http_carddav.o carddav_db.o
6868 ISCHED_OBJS = http_ischedule.o
69 TIMEZONE_OBJS = http_timezone.o zoneinfo_db.o
70 HTTP_OBJS = httpd.o http_err.o http_proxy.o @HTTP_OBJS@
71 HTTP_LIBS = @XML2_LIBS@ @ICAL_LIBS@ @SQLITE3_LIBS@ @DKIM_LIBS@ @JSON_LIBS@
69 TIMEZONE_OBJS = http_timezone.o zoneinfo_db.o tz_err.o
70 HTTP_OBJS = httpd.o http_err.o http_client.o http_proxy.o @HTTP_OBJS@
71 HTTP_LIBS = @XML2_LIBS@ @ICAL_LIBS@ @SQLITE3_LIBS@ @ICU_LIBS@ @DKIM_LIBS@ @JSON_LIBS@
7272
7373 SIEVE_OBJS = @SIEVE_OBJS@
7474 SIEVE_LIBS = @SIEVE_LIBS@
111111 imapparse.o telemetry.o user.o notify.o idle.o quota_db.o \
112112 sync_log.o $(SEEN) mboxkey.o backend.o tls.o message_guid.o \
113113 statuscache_db.o userdeny_db.o sequence.o upgrade_index.o \
114 dlist.o version.o
115
116 IMAPDOBJS=pushstats.o imapd.o proxy.o imap_proxy.o index.o
114 dlist.o version.o dav_util.o caldav_db.o carddav_db.o dav_db.o
115
116 IMAPDOBJS=pushstats.o imapd.o proxy.o imap_proxy.o index.o sync_support.o
117117
118118 LMTPOBJS=lmtpstats.o lmtpengine.o spool.o
119119
132132
133133 BUILTSOURCES = imap_err.c imap_err.h pushstats.c pushstats.h \
134134 lmtpstats.c lmtpstats.h mupdate_err.c mupdate_err.h \
135 nntp_err.c nntp_err.h http_err.c http_err.h
135 nntp_err.c nntp_err.h http_err.c http_err.h tz_err.c tz_err.h
136136
137137 all: $(BUILTSOURCES) $(PROGS) $(SUIDPROGS)
138138
147147 done
148148 ln -f $(DESTDIR)$(service_path)/pop3d $(DESTDIR)$(service_path)/pop3proxyd
149149 ln -f $(DESTDIR)$(service_path)/imapd $(DESTDIR)$(service_path)/proxyd
150 ln -f $(DESTDIR)$(service_path)/imapd $(DESTDIR)$(service_path)/sync_server
150151 ln -f $(DESTDIR)$(service_path)/lmtpd $(DESTDIR)$(service_path)/lmtpproxyd
151152
152153 .c.o:
193194 $(COMPILE_ET) $(srcdir)/http_err.et
194195
195196 http_err.h: http_err.c
197
198 tz_err.c: tz_err.et
199 $(COMPILE_ET) $(srcdir)/tz_err.et
200
201 tz_err.h: tz_err.c
196202
197203 ### Services
198204 idled: idled.o mutex_fake.o libimap.a $(DEPLIBS)
258264 $(CC) $(LDFLAGS) -o smmapd $(SERVICE) smmapd.o mutex_fake.o libimap.a \
259265 $(DEPLIBS) $(LIBS) $(LIB_WRAP)
260266
261 sync_server: sync_server.o sync_support.o \
262 imapparse.o tls.o libimap.a mutex_fake.o $(DEPLIBS) $(SERVICE)
263 $(CC) $(LDFLAGS) -o \
264 sync_server sync_server.o sync_support.o \
265 imapparse.o tls.o $(SERVICE) libimap.a mutex_fake.o \
266 $(DEPLIBS) $(LIBS) $(LIB_WRAP)
267
268267 httpd: $(HTTP_OBJS) proxy.o backend.o index.o spool.o tls.o mutex_fake.o \
269268 libimap.a $(DEPLIBS) $(SERVICE)
270269 $(CC) $(LDFLAGS) -o httpd $(HTTP_OBJS) \
18351835 { NULL, 0, ANNOTATION_PROXY_T_INVALID, 0, 0, NULL, NULL }
18361836 };
18371837
1838 const struct annotate_st_entry dav_mailbox_rw_entry =
1839 { "/vendor/cmu/cyrus-httpd/", ATTRIB_TYPE_STRING, BACKEND_ONLY,
1840 ATTRIB_VALUE_SHARED | ATTRIB_VALUE_PRIV
1841 | ATTRIB_CONTENTTYPE_SHARED | ATTRIB_CONTENTTYPE_PRIV,
1842 0, annotation_set_todb, NULL };
1843
18381844 struct annotate_st_entry_list *server_entries_list = NULL;
18391845 struct annotate_st_entry_list *mailbox_rw_entries_list = NULL;
18401846
18811887 }
18821888 }
18831889 if (!currententry) {
1884 /* unknown annotation */
1885 return IMAP_PERMISSION_DENIED;
1890 if ((mboxname_iscalendarmailbox(mboxname, 0) ||
1891 mboxname_isaddressbookmailbox(mboxname, 0)) &&
1892 !strncmp(e->entry, dav_mailbox_rw_entry.name,
1893 strlen(dav_mailbox_rw_entry.name))) {
1894 static struct annotate_st_entry_list dav_entries_list;
1895
1896 memset(&dav_entries_list, 0,
1897 sizeof(struct annotate_st_entry_list));
1898 dav_entries_list.entry = &dav_mailbox_rw_entry;
1899 currententry = &dav_entries_list;
1900 }
1901 else {
1902 /* unknown annotation */
1903 return IMAP_PERMISSION_DENIED;
1904 }
18861905 }
18871906
18881907 /* Add this entry to our list only if it
4242
4343 #include <config.h>
4444
45 #ifdef WITH_DAV
46
4547 #include <syslog.h>
4648 #include <string.h>
4749
136138 " UNIQUE( mailbox, resource ) );" \
137139 "CREATE INDEX IF NOT EXISTS idx_ical_uid ON ical_objs ( ical_uid );"
138140
139 /* Open DAV DB corresponding to userid */
140 struct caldav_db *caldav_open(const char *userid, int flags)
141 /* Open DAV DB corresponding to mailbox */
142 struct caldav_db *caldav_open(struct mailbox *mailbox, int flags)
141143 {
142144 sqlite3 *db;
143145 struct caldav_db *caldavdb = NULL;
145147
146148 if (flags & CALDAV_TRUNC) cmds = CMD_DROP CMD_CREATE;
147149
148 db = dav_open(userid, cmds);
150 db = dav_open(mailbox, cmds);
149151
150152 if (db) {
153 const char *userid;
154
151155 caldavdb = xzmalloc(sizeof(struct caldav_db));
152156 caldavdb->db = db;
153157
154 /* Construct mailbox name corresponding to userid's scheduling Inbox */
155 caldav_mboxname(SCHED_INBOX, userid, caldavdb->sched_inbox);
158 userid = mboxname_to_userid(mailbox->name);
159 if (userid) {
160 /* Construct mbox name corresponding to userid's scheduling Inbox */
161 caldav_mboxname(SCHED_INBOX, userid, caldavdb->sched_inbox);
162 }
156163 }
157164
158165 return caldavdb;
561568 }
562569 break;
563570
571 #ifdef HAVE_VPOLL
572 case ICAL_VPOLL_COMPONENT:
573 #endif
564574 case ICAL_VTODO_COMPONENT: {
565 struct icaltimetype due = icalcomponent_get_due(comp);
575 struct icaltimetype due = (kind == ICAL_VPOLL_COMPONENT) ?
576 icalcomponent_get_dtend(comp) : icalcomponent_get_due(comp);
566577
567578 if (!icaltime_is_null_time(period->start)) {
568579 /* Has DTSTART */
572583 sizeof(struct icaltimetype));
573584
574585 if (!icaltime_is_null_time(due)) {
575 /* Has DUE */
586 /* Has DUE (DTEND for VPOLL) */
576587 if (icaltime_compare(due, period->start) < 0)
577588 memcpy(&period->start, &due, sizeof(struct icaltimetype));
578589 if (icaltime_compare(due, period->end) > 0)
585596
586597 /* No DTSTART */
587598 if (!icaltime_is_null_time(due)) {
588 /* Has DUE */
599 /* Has DUE (DTEND for VPOLL) */
589600 memcpy(&period->start, &due, sizeof(struct icaltimetype));
590601 memcpy(&period->end, &due, sizeof(struct icaltimetype));
591602 }
654665 }
655666 break;
656667
668 case ICAL_VAVAILABILITY_COMPONENT:
669 /* XXX Probably need to do something different here */
657670 case ICAL_VFREEBUSY_COMPONENT:
658671 if (icaltime_is_null_time(period->start) ||
659672 icaltime_is_null_time(period->end)) {
721734 case ICAL_VTODO_COMPONENT: mykind = CAL_COMP_VTODO; break;
722735 case ICAL_VJOURNAL_COMPONENT: mykind = CAL_COMP_VJOURNAL; break;
723736 case ICAL_VFREEBUSY_COMPONENT: mykind = CAL_COMP_VFREEBUSY; break;
737 case ICAL_VAVAILABILITY_COMPONENT: mykind = CAL_COMP_VAVAILABILITY; break;
738 #ifdef HAVE_VPOLL
739 case ICAL_VPOLL_COMPONENT: mykind = CAL_COMP_VPOLL; break;
740 #endif
724741 default: break;
725742 }
726743 cdata->comp_type = mykind;
728745 /* Get organizer */
729746 prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
730747 if (prop) cdata->organizer = icalproperty_get_organizer(prop)+7;
748 else cdata->organizer = NULL;
731749
732750 /* Get transparency */
733751 prop = icalcomponent_get_first_property(comp, ICAL_TRANSP_PROPERTY);
803821
804822 return 0;
805823 }
824
825 #else
826
827 int caldav_init(void)
828 {
829 return 0;
830 }
831
832
833 int caldav_done(void)
834 {
835 return 0;
836 }
837
838
839 #endif /* WITH_DAV */
4545
4646 #include <config.h>
4747
48 /* prepare for caldav operations in this process */
49 int caldav_init(void);
50
51 /* done with all caldav operations for this process */
52 int caldav_done(void);
53
54 #ifdef WITH_DAV
55
4856 #include <libical/ical.h>
4957
5058 #include "dav_db.h"
59
60 #ifndef HAVE_VAVAILABILITY
61 /* Allow us to compile without #ifdef HAVE_VAVAILABILITY everywhere */
62 #define ICAL_VAVAILABILITY_COMPONENT ICAL_NO_COMPONENT
63 #endif
64
65 #ifndef HAVE_VPOLL
66 /* Allow us to compile without #ifdef HAVE_VPOLL everywhere */
67 #define ICAL_VPOLL_COMPONENT ICAL_NO_COMPONENT
68 #define ICAL_METHOD_POLLSTATUS ICAL_METHOD_NONE
69 #define ICAL_VOTER_PROPERTY ICAL_NO_PROPERTY
70 #define icalproperty_get_voter icalproperty_get_attendee
71 #endif
72
73 /* Bitmask of calendar components */
74 enum {
75 /* "Real" components - MUST remain in this order (values used in DAV DB) */
76 CAL_COMP_VEVENT = (1<<0),
77 CAL_COMP_VTODO = (1<<1),
78 CAL_COMP_VJOURNAL = (1<<2),
79 CAL_COMP_VFREEBUSY = (1<<3),
80 CAL_COMP_VAVAILABILITY = (1<<4),
81 CAL_COMP_VPOLL = (1<<5),
82 /* Append additional "real" components here */
83
84 /* Other components - values don't matter - prepend here */
85 CAL_COMP_VALARM = (1<<13),
86 CAL_COMP_VTIMEZONE = (1<<14),
87 CAL_COMP_VCALENDAR = (1<<15)
88 };
5189
5290 struct caldav_db;
5391
66104 const char *sched_tag;
67105 };
68106
69 /* prepare for caldav operations in this process */
70 int caldav_init(void);
71
72 /* done with all caldav operations for this process */
73 int caldav_done(void);
74
75 /* get a database handle corresponding to userid */
76 struct caldav_db *caldav_open(const char *userid, int flags);
107 /* get a database handle corresponding to mailbox */
108 struct caldav_db *caldav_open(struct mailbox *mailbox, int flags);
77109
78110 /* close this handle */
79111 int caldav_close(struct caldav_db *caldavdb);
118150
119151 int caldav_mboxname(const char *name, const char *userid, char *result);
120152
153 #endif /* WITH_DAV */
154
121155 #endif /* CALDAV_DB_H */
4141 */
4242
4343 #include <config.h>
44
45 #ifdef WITH_DAV
4446
4547 #include <syslog.h>
4648 #include <string.h>
121123 " UNIQUE( mailbox, resource ) );" \
122124 "CREATE INDEX IF NOT EXISTS idx_vcard_uid ON vcard_objs ( vcard_uid );"
123125
124 /* Open DAV DB corresponding to userid */
125 struct carddav_db *carddav_open(const char *userid, int flags)
126 /* Open DAV DB corresponding to mailbox */
127 struct carddav_db *carddav_open(struct mailbox *mailbox, int flags)
126128 {
127129 sqlite3 *db;
128130 struct carddav_db *carddavdb = NULL;
130132
131133 if (flags & CARDDAV_TRUNC) cmds = CMD_DROP CMD_CREATE;
132134
133 db = dav_open(userid, cmds);
135 db = dav_open(mailbox, cmds);
134136
135137 if (db) {
136138 carddavdb = xzmalloc(sizeof(struct carddav_db));
504506
505507 return r;
506508 }
509
510 #else
511
512 int carddav_init(void)
513 {
514 return 0;
515 }
516
517
518 int carddav_done(void)
519 {
520 return 0;
521 }
522
523 #endif /* WITH_DAV */
4545
4646 #include <config.h>
4747
48 /* prepare for carddav operations in this process */
49 int carddav_init(void);
50
51 /* done with all carddav operations for this process */
52 int carddav_done(void);
53
54 #ifdef WITH_DAV
55
56 #include <libical/vcc.h>
57
4858 #include "dav_db.h"
4959
5060 struct carddav_db;
6373 const char *email;
6474 };
6575
66 /* prepare for carddav operations in this process */
67 int carddav_init(void);
68
69 /* done with all carddav operations for this process */
70 int carddav_done(void);
71
72 /* get a database handle corresponding to userid */
73 struct carddav_db *carddav_open(const char *userid, int flags);
76 /* get a database handle corresponding to mailbox */
77 struct carddav_db *carddav_open(struct mailbox *mailbox, int flags);
7478
7579 /* close this handle */
7680 int carddav_close(struct carddav_db *carddavdb);
110114 /* abort transaction */
111115 int carddav_abort(struct carddav_db *carddavdb);
112116
117 #endif /* WITH_DAV */
118
113119 #endif /* CARDDAV_DB_H */
4242
4343 #include <config.h>
4444
45 #ifdef WITH_DAV
46
4547 #include <stdlib.h>
4648 #include <syslog.h>
4749 #include <string.h>
5254 #include <sys/stat.h>
5355 #include <sys/types.h>
5456
57 #include "assert.h"
5558 #include "cyrusdb.h"
5659 #include "dav_db.h"
5760 #include "global.h"
5861 #include "util.h"
5962 #include "xmalloc.h"
6063
61 #define FNAME_DAVSUFFIX ".dav" /* per-user DAV DB extension */
64 struct open_davdb {
65 sqlite3 *db;
66 char *path;
67 unsigned refcount;
68 struct open_davdb *next;
69 };
70
71 static struct open_davdb *open_davdbs = NULL;
72
6273
6374 static int dbinit = 0;
6475
7081 #endif
7182 }
7283
84 assert(!open_davdbs);
85
7386 return 0;
7487 }
7588
8295 #endif
8396 }
8497
98 /* XXX - report the problems? */
99 assert(!open_davdbs);
100
85101 return 0;
86102 }
87103
88104
89 /* Create filename corresponding to userid's DAV DB */
90 static void dav_getpath(struct buf *fname, const char *userid)
91 {
92 char c, *domain;
93
94 buf_reset(fname);
95 if (config_virtdomains && (domain = strchr(userid, '@'))) {
96 char d = (char) dir_hash_c(domain+1, config_fulldirhash);
97 *domain = '\0'; /* split user@domain */
98 c = (char) dir_hash_c(userid, config_fulldirhash);
99 buf_printf(fname, "%s%s%c/%s%s%c/%s%s", config_dir, FNAME_DOMAINDIR, d,
100 domain+1, FNAME_USERDIR, c, userid, FNAME_DAVSUFFIX);
101 *domain = '@'; /* reassemble user@domain */
102 }
103 else {
104 c = (char) dir_hash_c(userid, config_fulldirhash);
105 buf_printf(fname, "%s%s%c/%s%s", config_dir, FNAME_USERDIR, c, userid,
106 FNAME_DAVSUFFIX);
107 }
108 }
109
110
111 static void dav_debug(void *userid, const char *sql)
112 {
113 syslog(LOG_DEBUG, "dav_exec(%s%s): %s",
114 (const char *) userid, FNAME_DAVSUFFIX, sql);
115 }
116
117
118 /* Open DAV DB corresponding to userid */
119 sqlite3 *dav_open(const char *userid, const char *cmds)
120 {
121 int rc;
105 static void dav_debug(void *fname, const char *sql)
106 {
107 syslog(LOG_DEBUG, "dav_exec(%s): %s", (const char *) fname, sql);
108 }
109
110
111 static void free_dav_open(struct open_davdb *open)
112 {
113 free(open->path);
114 free(open);
115 }
116
117
118 /* Open DAV DB corresponding to mailbox */
119 sqlite3 *dav_open(struct mailbox *mailbox, const char *cmds)
120 {
121 int rc = SQLITE_OK;
122122 struct buf fname = BUF_INITIALIZER;
123123 struct stat sbuf;
124 sqlite3 *db = NULL;
125
126 dav_getpath(&fname, userid);
127 rc = stat(buf_cstring(&fname), &sbuf);
124 struct open_davdb *open;
125
126 dav_getpath(&fname, mailbox);
127
128 for (open = open_davdbs; open; open = open->next) {
129 if (!strcmp(open->path, buf_cstring(&fname))) {
130 /* already open! */
131 open->refcount++;
132 goto docmds;
133 }
134 }
135
136 open = xzmalloc(sizeof(struct open_davdb));
137 open->path = buf_release(&fname);
138
139 rc = stat(open->path, &sbuf);
128140 if (rc == -1 && errno == ENOENT) {
129 rc = cyrus_mkdir(buf_cstring(&fname), 0755);
130 }
131
132 #if SQLITE_VERSION_NUMBER >= 3006000
133 rc = sqlite3_open_v2(buf_cstring(&fname), &db,
141 rc = cyrus_mkdir(open->path, 0755);
142 }
143
144 #if SQLITE_VERSION_NUMBER >= 3006000
145 rc = sqlite3_open_v2(open->path, &open->db,
134146 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
135147 #else
136 rc = sqlite3_open(buf_cstring(&fname), &db);
148 rc = sqlite3_open(open->path, &open->db);
137149 #endif
138150 if (rc != SQLITE_OK) {
139151 syslog(LOG_ERR, "dav_open(%s) open: %s",
140 buf_cstring(&fname), db ? sqlite3_errmsg(db) : "failed");
152 open->path, open->db ? sqlite3_errmsg(open->db) : "failed");
153 sqlite3_close(open->db);
154 free_dav_open(open);
155 return NULL;
141156 }
142157 else {
143158 #if SQLITE_VERSION_NUMBER >= 3006000
144 sqlite3_extended_result_codes(db, 1);
145 #endif
146 sqlite3_trace(db, dav_debug, (void *) userid);
147
148 if (cmds) {
149 rc = sqlite3_exec(db, cmds, NULL, NULL, NULL);
150 if (rc != SQLITE_OK) {
151 syslog(LOG_ERR, "dav_open(%s) cmds: %s",
152 buf_cstring(&fname), sqlite3_errmsg(db));
153 }
154 }
155 }
156
157 if (rc != SQLITE_OK) {
158 sqlite3_close(db);
159 db = NULL;
159 sqlite3_extended_result_codes(open->db, 1);
160 #endif
161 sqlite3_trace(open->db, dav_debug, (void *) open->path);
162 }
163
164 /* stitch on up */
165 open->refcount = 1;
166 open->next = open_davdbs;
167 open_davdbs = open;
168
169 docmds:
170 if (cmds) {
171 rc = sqlite3_exec(open->db, cmds, NULL, NULL, NULL);
172 if (rc != SQLITE_OK) {
173 /* XXX - fatal? */
174 syslog(LOG_ERR, "dav_open(%s) cmds: %s",
175 open->path, sqlite3_errmsg(open->db));
176 }
160177 }
161178
162179 buf_free(&fname);
163180
164 return db;
181 return open->db;
165182 }
166183
167184
168185 /* Close DAV DB */
169186 int dav_close(sqlite3 *davdb)
170187 {
171 int rc, r = 0;;
188 int rc, r = 0;
189 struct open_davdb *open, *prev = NULL;
172190
173191 if (!davdb) return 0;
174192
175 rc = sqlite3_close(davdb);
193 for (open = open_davdbs; open; open = open->next) {
194 if (davdb == open->db) {
195 if (--open->refcount) return 0; /* still in use */
196 if (prev)
197 prev->next = open->next;
198 else
199 open_davdbs = open->next;
200 break;
201 }
202 prev = open;
203 }
204
205 assert(open);
206
207 rc = sqlite3_close(open->db);
176208 if (rc != SQLITE_OK) {
177 syslog(LOG_ERR, "dav_close(): %s", sqlite3_errmsg(davdb));
209 syslog(LOG_ERR, "dav_close(%s): %s", open->path, sqlite3_errmsg(open->db));
178210 r = CYRUSDB_INTERNAL;
179211 }
212
213 free_dav_open(open);
180214
181215 return r;
182216 }
236270 }
237271
238272
239 int dav_delete(const char *userid)
273 int dav_delete(struct mailbox *mailbox)
240274 {
241275 struct buf fname = BUF_INITIALIZER;
242276 int r = 0;
243277
244 dav_getpath(&fname, userid);
278 dav_getpath(&fname, mailbox);
245279 if (unlink(buf_cstring(&fname)) && errno != ENOENT) {
246280 syslog(LOG_ERR, "dav_db: error unlinking %s: %m", buf_cstring(&fname));
247281 r = CYRUSDB_INTERNAL;
251285
252286 return r;
253287 }
288
289 #endif /* WITH_DAV */
4343 #ifndef DAV_DB_H
4444 #define DAV_DB_H
4545
46 #include <config.h>
47
48 #ifdef WITH_DAV
49
4650 #include <sqlite3.h>
51 #include "dav_util.h"
4752
4853 struct dav_data {
4954 unsigned rowid;
7277 /* done with all DAV operations for this process */
7378 int dav_done(void);
7479
75 /* get a database handle corresponding to userid */
76 sqlite3 *dav_open(const char *userid, const char *cmds);
80 /* get a database handle corresponding to mailbox */
81 sqlite3 *dav_open(struct mailbox *mailbox, const char *cmds);
7782
7883 /* close this handle */
7984 int dav_close(sqlite3 *davdb);
8489 int (*cb)(sqlite3_stmt *stmt, void *rock), void *rock,
8590 sqlite3_stmt **stmt);
8691
87 /* delete database corresponding to userid */
88 int dav_delete(const char *userid);
92 /* delete database corresponding to mailbox */
93 int dav_delete(struct mailbox *mailbox);
94
95 #endif /* WITH_DAV */
8996
9097 #endif /* DAV_DB_H */
6363 #include "mboxname.h"
6464 #include "mboxlist.h"
6565 #include "xmalloc.h"
66 #include "xstrlcat.h"
6667
6768 extern int optind;
6869 extern char *optarg;
8788 int opt, r;
8889 char buf[MAX_MAILBOX_PATH+1];
8990 char *alt_config = NULL, *userid;
91 struct mailbox mailbox;
9092
9193 if ((geteuid()) == 0 && (become_cyrus() != 0)) {
9294 fatal("must run as the Cyrus user", EC_USAGE);
131133
132134 printf("Reconstructing DAV DB for %s...\n", userid);
133135 caldav_init();
134 caldavdb = caldav_open(userid, CALDAV_CREATE | CALDAV_TRUNC);
135
136 snprintf(buf, sizeof(buf), "user.%s.#calendars.*", userid);
136
137 /* Generate mailboxname of calendar-home-set */
138 caldav_mboxname(NULL, userid, buf);
139
140 /* Open DAV DB corresponding to userid */
141 mailbox.name = buf;
142 caldavdb = caldav_open(&mailbox, CALDAV_CREATE | CALDAV_TRUNC);
143
144 strlcat(buf, ".*", sizeof(buf));
137145 (*recon_namespace.mboxlist_findall)(&recon_namespace, buf, 1, 0, 0,
138146 do_reconstruct, NULL);
139147
0 /* dav_util.c -- utility functions for dealing with DAV database
1 *
2 * Copyright (c) 1994-2014 Carnegie Mellon University. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
14 * distribution.
15 *
16 * 3. The name "Carnegie Mellon University" must not be used to
17 * endorse or promote products derived from this software without
18 * prior written permission. For permission or any legal
19 * details, please contact
20 * Carnegie Mellon University
21 * Center for Technology Transfer and Enterprise Creation
22 * 4615 Forbes Avenue
23 * Suite 302
24 * Pittsburgh, PA 15213
25 * (412) 268-7393, fax: (412) 268-7395
26 * innovation@andrew.cmu.edu
27 *
28 * 4. Redistributions of any form whatsoever must retain the following
29 * acknowledgment:
30 * "This product includes software developed by Computing Services
31 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
32 *
33 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
34 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
35 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
36 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
37 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
38 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
39 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
40 *
41 */
42
43 #include <config.h>
44
45 #include <string.h>
46
47 #include "dav_util.h"
48 #include "global.h"
49 #include "mailbox.h"
50 #include "mboxname.h"
51 #include "util.h"
52
53 /* Create filename corresponding to DAV DB for mailbox */
54 void dav_getpath(struct buf *fname, struct mailbox *mailbox)
55 {
56 const char *userid;
57
58 userid = mboxname_to_userid(mailbox->name);
59
60 if (userid) dav_getpath_byuserid(fname, userid);
61 else buf_setcstr(fname, mailbox_meta_fname(mailbox, META_DAV));
62 }
63
64
65 /* Create filename corresponding to DAV DB for userid */
66 void dav_getpath_byuserid(struct buf *fname, const char *userid)
67 {
68 char c, *domain;
69
70 buf_reset(fname);
71 if (config_virtdomains && (domain = strchr(userid, '@'))) {
72 char d = (char) dir_hash_c(domain+1, config_fulldirhash);
73 *domain = '\0'; /* split user@domain */
74 c = (char) dir_hash_c(userid, config_fulldirhash);
75 buf_printf(fname, "%s%s%c/%s%s%c/%s%s", config_dir, FNAME_DOMAINDIR, d,
76 domain+1, FNAME_USERDIR, c, userid, FNAME_DAVSUFFIX);
77 *domain = '@'; /* reassemble user@domain */
78 }
79 else {
80 c = (char) dir_hash_c(userid, config_fulldirhash);
81 buf_printf(fname, "%s%s%c/%s%s", config_dir, FNAME_USERDIR, c, userid,
82 FNAME_DAVSUFFIX);
83 }
84 }
0 /* dav_util.h -- utility functions for dealing with DAV database
1 *
2 * Copyright (c) 1994-2014 Carnegie Mellon University. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
14 * distribution.
15 *
16 * 3. The name "Carnegie Mellon University" must not be used to
17 * endorse or promote products derived from this software without
18 * prior written permission. For permission or any legal
19 * details, please contact
20 * Carnegie Mellon University
21 * Center for Technology Transfer and Enterprise Creation
22 * 4615 Forbes Avenue
23 * Suite 302
24 * Pittsburgh, PA 15213
25 * (412) 268-7393, fax: (412) 268-7395
26 * innovation@andrew.cmu.edu
27 *
28 * 4. Redistributions of any form whatsoever must retain the following
29 * acknowledgment:
30 * "This product includes software developed by Computing Services
31 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
32 *
33 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
34 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
35 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
36 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
37 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
38 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
39 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
40 *
41 */
42
43 #ifndef DAV_UTIL_H
44 #define DAV_UTIL_H
45
46 #include "mailbox.h"
47 #include "util.h"
48
49 #define FNAME_DAVSUFFIX ".dav" /* per-user DAV DB extension */
50
51 /* Create filename corresponding to DAV DB for mailbox */
52 void dav_getpath(struct buf *fname, struct mailbox *mailbox);
53
54 /* Create filename corresponding to DAV DB for userid */
55 void dav_getpath_byuserid(struct buf *fname, const char *userid);
56
57 #endif /* DAV_UTIL_H */
9090
9191 static void printfile(struct protstream *out, const struct dlist *dl)
9292 {
93 char buf[4096];
9493 struct stat sbuf;
9594 FILE *f;
9695 unsigned long size;
96 struct message_guid guid2;
97 const char *msg_base = NULL;
98 unsigned long msg_len = 0;
9799
98100 f = fopen(dl->sval, "r");
99101 if (!f) {
116118 return;
117119 }
118120
121 map_refresh(fileno(f), 1, &msg_base, &msg_len, sbuf.st_size,
122 "new message", 0);
123
124 message_guid_generate(&guid2, msg_base, msg_len);
125
126 if (!message_guid_equal(&guid2, (struct message_guid *) &dl->gval)) {
127 syslog(LOG_ERR, "IOERROR: GUID mismatch %s",
128 dl->sval);
129 prot_printf(out, "NIL");
130 fclose(f);
131 map_free(&msg_base, &msg_len);
132 return;
133 }
134
119135 prot_printf(out, "%%{");
120136 prot_printastring(out, dl->part);
121137 prot_printf(out, " ");
122138 prot_printastring(out, message_guid_encode(&dl->gval));
123139 prot_printf(out, " %lu}\r\n", size);
124 while (size) {
125 int n = fread(buf, 1, (size > 4096 ? 4096 : size), f);
126 if (n <= 0) break;
127 prot_write(out, buf, n);
128 size -= n;
129 }
140 prot_write(out, msg_base, msg_len);
130141 fclose(f);
131
132 if (size) fatal("failed to finish reading file!", EC_IOERR);
142 map_free(&msg_base, &msg_len);
133143 }
134144
135145 /* XXX - these two functions should be out in append.c or reserve.c
137147 const char *dlist_reserve_path(const char *part, struct message_guid *guid)
138148 {
139149 static char buf[MAX_MAILBOX_PATH];
140 snprintf(buf, MAX_MAILBOX_PATH, "%s/sync./%lu/%s",
150 snprintf(buf, MAX_MAILBOX_PATH, "%s/sync./%lu/%s",
141151 config_partitiondir(part), (unsigned long)getpid(),
142152 message_guid_encode(guid));
143153 cyrus_mkdir(buf, 0755);
6666 #include "acl.h"
6767 #include "append.h"
6868 #include "caldav_db.h"
69 #include "charset.h"
6970 #include "global.h"
7071 #include "hash.h"
7172 #include "httpd.h"
7980 #include "xcal.h"
8081 #include "mailbox.h"
8182 #include "mboxlist.h"
83 #include "md5.h"
8284 #include "message.h"
8385 #include "message_guid.h"
8486 #include "proxy.h"
9395 #include "xstrlcat.h"
9496 #include "xstrlcpy.h"
9597
98 //#define IOPTEST
99
96100 #define NEW_STAG (1<<8) /* Make sure we skip over PREFER bits */
97101
102
103 #ifdef HAVE_RSCALE
104 #include <unicode/uversion.h>
105
106 static int rscale_cmp(const void *a, const void *b)
107 {
108 /* Convert to uppercase since that's what we prefer to output */
109 return strcmp(ucase(*((char **) a)), ucase(*((char **) b)));
110 }
111 #endif /* HAVE_RSCALE */
112
113
114 #ifndef HAVE_SCHEDULING_PARAMS
115
116 /* Functions to replace those not available in libical < v1.0 */
117
118 static icalparameter_scheduleagent
119 icalparameter_get_scheduleagent(icalparameter *param)
120 {
121 const char *agent = NULL;
122
123 if (param) agent = icalparameter_get_iana_value(param);
124
125 if (!agent) return ICAL_SCHEDULEAGENT_NONE;
126 else if (!strcmp(agent, "SERVER")) return ICAL_SCHEDULEAGENT_SERVER;
127 else if (!strcmp(agent, "CLIENT")) return ICAL_SCHEDULEAGENT_CLIENT;
128 else return ICAL_SCHEDULEAGENT_X;
129 }
130
131 static icalparameter_scheduleforcesend
132 icalparameter_get_scheduleforcesend(icalparameter *param)
133 {
134 const char *force = NULL;
135
136 if (param) force = icalparameter_get_iana_value(param);
137
138 if (!force) return ICAL_SCHEDULEFORCESEND_NONE;
139 else if (!strcmp(force, "REQUEST")) return ICAL_SCHEDULEFORCESEND_REQUEST;
140 else if (!strcmp(force, "REPLY")) return ICAL_SCHEDULEFORCESEND_REPLY;
141 else return ICAL_SCHEDULEFORCESEND_X;
142 }
143
144 static icalparameter *icalparameter_new_schedulestatus(const char *stat)
145 {
146 icalparameter *param = icalparameter_new(ICAL_IANA_PARAMETER);
147
148 icalparameter_set_iana_name(param, "SCHEDULE-STATUS");
149 icalparameter_set_iana_value(param, stat);
150
151 return param;
152 }
153
154 /* Wrappers to fetch scheduling parameters by kind */
155
156 static icalparameter*
157 icalproperty_get_iana_parameter_by_name(icalproperty *prop, const char *name)
158 {
159 icalparameter *param;
160
161 for (param = icalproperty_get_first_parameter(prop, ICAL_IANA_PARAMETER);
162 param && strcmp(icalparameter_get_iana_name(param), name);
163 param = icalproperty_get_next_parameter(prop, ICAL_IANA_PARAMETER));
164
165 return param;
166 }
167
168 #define icalproperty_get_scheduleagent_parameter(prop) \
169 icalproperty_get_iana_parameter_by_name(prop, "SCHEDULE-AGENT")
170
171 #define icalproperty_get_scheduleforcesend_parameter(prop) \
172 icalproperty_get_iana_parameter_by_name(prop, "SCHEDULE-FORCE-SEND")
173
174 #define icalproperty_get_schedulestatus_parameter(prop) \
175 icalproperty_get_iana_parameter_by_name(prop, "SCHEDULE-STATUS")
176
177 #else
178
179 /* Wrappers to fetch scheduling parameters by kind */
180
181 #define icalproperty_get_scheduleagent_parameter(prop) \
182 icalproperty_get_first_parameter(prop, ICAL_SCHEDULEAGENT_PARAMETER)
183
184 #define icalproperty_get_scheduleforcesend_parameter(prop) \
185 icalproperty_get_first_parameter(prop, ICAL_SCHEDULEFORCESEND_PARAMETER)
186
187 #define icalproperty_get_schedulestatus_parameter(prop) \
188 icalproperty_get_first_parameter(prop, ICAL_SCHEDULESTATUS_PARAMETER)
189
190 #endif /* HAVE_SCHEDULING_PARAMS */
191
192
98193 struct busytime {
99 struct icalperiodtype *busy;
194 struct icalperiodtype per;
195 icalparameter_fbtype type;
196 };
197
198 struct busytime_array {
199 struct busytime *busy;
100200 unsigned len;
101201 unsigned alloc;
102202 };
105205 unsigned comp;
106206 struct icaltimetype start;
107207 struct icaltimetype end;
108 unsigned check_transp;
109 unsigned save_busytime;
110 struct busytime busytime; /* array of found busytime periods */
208 unsigned busytime_query;
209 unsigned check_cal_transp;
210 struct busytime_array busytime; /* array of found busytime periods */
111211 };
112212
213 static unsigned config_allowsched = IMAP_ENUM_CALDAV_ALLOWSCHEDULING_OFF;
113214 static struct caldav_db *auth_caldavdb = NULL;
114215 static time_t compile_time;
115
216 static struct buf ical_prodid_buf = BUF_INITIALIZER;
217 static const char *ical_prodid = NULL;
218 static struct strlist *cua_domains = NULL;
219 icalarray *rscale_calendars = NULL;
220
221 static struct caldav_db *my_caldav_open(struct mailbox *mailbox);
222 static void my_caldav_close(struct caldav_db *caldavdb);
116223 static void my_caldav_init(struct buf *serverinfo);
117224 static void my_caldav_auth(const char *userid);
118225 static void my_caldav_reset(void);
128235 static int caldav_copy(struct transaction_t *txn,
129236 struct mailbox *src_mbox, struct index_record *src_rec,
130237 struct mailbox *dest_mbox, const char *dest_rsrc,
238 struct caldav_db *dest_davdb,
131239 unsigned overwrite, unsigned flags);
132240 static int caldav_delete_sched(struct transaction_t *txn,
133241 struct mailbox *mailbox,
136244 static int caldav_post(struct transaction_t *txn);
137245 static int caldav_put(struct transaction_t *txn,
138246 struct mime_type_t *mime,
139 struct mailbox *mailbox, unsigned flags);
247 struct mailbox *mailbox,
248 struct caldav_db *caldavdb,
249 unsigned flags);
140250
141251 static int propfind_getcontenttype(const xmlChar *name, xmlNsPtr ns,
142252 struct propfind_ctx *fctx, xmlNodePtr resp,
168278 static int proppatch_caltransp(xmlNodePtr prop, unsigned set,
169279 struct proppatch_ctx *pctx,
170280 struct propstat propstat[], void *rock);
171 static int propfind_timezone(const xmlChar *name, xmlNsPtr ns,
172 struct propfind_ctx *fctx, xmlNodePtr resp,
173 struct propstat propstat[], void *rock);
281 static int propfind_icalcomponent(const xmlChar *name, xmlNsPtr ns,
282 struct propfind_ctx *fctx, xmlNodePtr resp,
283 struct propstat propstat[], void *rock);
174284 static int proppatch_timezone(xmlNodePtr prop, unsigned set,
175285 struct proppatch_ctx *pctx,
286 struct propstat propstat[], void *rock);
287 static int proppatch_availability(xmlNodePtr prop, unsigned set,
288 struct proppatch_ctx *pctx,
289 struct propstat propstat[], void *rock);
290 static int propfind_rscaleset(const xmlChar *name, xmlNsPtr ns,
291 struct propfind_ctx *fctx, xmlNodePtr resp,
176292 struct propstat propstat[], void *rock);
177293
178294 static int report_cal_query(struct transaction_t *txn, xmlNodePtr inroot,
195311
196312 static const char *begin_icalendar(struct buf *buf);
197313 static void end_icalendar(struct buf *buf);
314
315 static int apply_calfilter(struct propfind_ctx *fctx, void *data);
316 static icalcomponent *busytime_query_local(struct transaction_t *txn,
317 struct propfind_ctx *fctx,
318 char mailboxname[],
319 icalproperty_method method,
320 const char *uid,
321 const char *organizer,
322 const char *attendee);
198323
199324 static struct mime_type_t caldav_mime_types[] = {
200325 /* First item MUST be the default type and storage format */
295420 propfind_fromdb, proppatch_todb, NULL },
296421 { "calendar-timezone", NS_CALDAV,
297422 PROP_COLLECTION | PROP_PRESCREEN | PROP_NEEDPROP,
298 propfind_timezone, proppatch_timezone, NULL },
423 propfind_icalcomponent, proppatch_timezone, NULL },
299424 { "supported-calendar-component-set", NS_CALDAV, PROP_COLLECTION,
300425 propfind_calcompset, proppatch_calcompset, NULL },
301426 { "supported-calendar-data", NS_CALDAV, PROP_COLLECTION,
314439 { "schedule-calendar-transp", NS_CALDAV, PROP_COLLECTION,
315440 propfind_caltransp, proppatch_caltransp, NULL },
316441
442 /* Calendar Availability (draft-daboo-calendar-availability) properties */
443 { "calendar-availability", NS_CALDAV,
444 PROP_COLLECTION | PROP_PRESCREEN | PROP_NEEDPROP,
445 propfind_icalcomponent, proppatch_availability, NULL },
446
447 /* Backwards compatibility with Apple VAVAILABILITY clients */
448 { "calendar-availability", NS_CS,
449 PROP_COLLECTION | PROP_PRESCREEN | PROP_NEEDPROP,
450 propfind_icalcomponent, proppatch_availability, NULL },
451
452 /* RSCALE (draft-daboo-icalendar-rscale) properties */
453 { "supported-rscale-set", NS_CALDAV, PROP_COLLECTION,
454 propfind_rscaleset, NULL, NULL },
455
317456 /* Apple Calendar Server properties */
318457 { "getctag", NS_CS, PROP_ALLPROP | PROP_COLLECTION,
319458 propfind_sync_token, NULL, NULL },
326465 caldav_mime_types,
327466 &caldav_parse_path,
328467 &caldav_check_precond,
329 { (void **) &auth_caldavdb,
468 { (db_open_proc_t) &my_caldav_open,
469 (db_close_proc_t) &my_caldav_close,
330470 (db_lookup_proc_t) &caldav_lookup_resource,
331471 (db_foreach_proc_t) &caldav_foreach,
332472 (db_write_proc_t) &caldav_write,
333473 (db_delete_proc_t) &caldav_delete,
334474 (db_delmbox_proc_t) &caldav_delmbox },
335475 &caldav_acl,
336 &caldav_copy,
476 (copy_proc_t) &caldav_copy,
337477 &caldav_delete_sched,
338478 { MBTYPE_CALENDAR, "mkcalendar", "mkcalendar-response", NS_CALDAV },
339479 &caldav_post,
340 { CALDAV_SUPP_DATA, &caldav_put },
480 { CALDAV_SUPP_DATA, (put_proc_t) &caldav_put },
341481 caldav_props,
342 { { "calendar-query", &report_cal_query, DACL_READ,
343 REPORT_NEED_MBOX | REPORT_MULTISTATUS },
344 { "calendar-multiget", &report_cal_multiget, DACL_READ,
345 REPORT_NEED_MBOX | REPORT_MULTISTATUS },
346 { "free-busy-query", &report_fb_query, DACL_READFB,
347 REPORT_NEED_MBOX },
348 { "sync-collection", &report_sync_col, DACL_READ,
349 REPORT_NEED_MBOX | REPORT_MULTISTATUS | REPORT_NEED_PROPS },
350 { NULL, NULL, 0, 0 } }
482 { { "calendar-query", "multistatus", &report_cal_query,
483 DACL_READ, REPORT_NEED_MBOX },
484 { "calendar-multiget", "multistatus", &report_cal_multiget,
485 DACL_READ, REPORT_NEED_MBOX },
486 { "free-busy-query", NULL, &report_fb_query,
487 DACL_READFB, REPORT_NEED_MBOX },
488 { "sync-collection", "multistatus", &report_sync_col,
489 DACL_READ, REPORT_NEED_MBOX | REPORT_NEED_PROPS },
490 { NULL, NULL, NULL, 0, 0 } }
351491 };
352492
353493
355495 struct namespace_t namespace_calendar = {
356496 URL_NS_CALENDAR, 0, "/dav/calendars", "/.well-known/caldav", 1 /* auth */,
357497 (ALLOW_READ | ALLOW_POST | ALLOW_WRITE | ALLOW_DELETE |
498 #ifdef HAVE_VAVAILABILITY
499 ALLOW_CAL_AVAIL |
500 #endif
358501 ALLOW_DAV | ALLOW_WRITECOL | ALLOW_CAL ),
359502 &my_caldav_init, &my_caldav_auth, my_caldav_reset, &my_caldav_shutdown,
360503 {
379522 };
380523
381524
525 static struct caldav_db *my_caldav_open(struct mailbox *mailbox)
526 {
527 if (httpd_userid && mboxname_userownsmailbox(httpd_userid, mailbox->name)) {
528 return auth_caldavdb;
529 }
530 else {
531 return caldav_open(mailbox, CALDAV_CREATE);
532 }
533 }
534
535
536 static void my_caldav_close(struct caldav_db *caldavdb)
537 {
538 if (caldavdb && (caldavdb != auth_caldavdb)) caldav_close(caldavdb);
539 }
540
541
382542 static void my_caldav_init(struct buf *serverinfo)
383543 {
544 const char *domains;
545 char *domain;
546 tok_t tok;
547
548 buf_printf(serverinfo, " libical/%s", ICAL_VERSION);
549 #ifdef HAVE_RSCALE
550 if ((rscale_calendars = icalrecurrencetype_rscale_supported_calendars())) {
551 icalarray_sort(rscale_calendars, &rscale_cmp);
552
553 buf_printf(serverinfo, " ICU4C/%s", U_ICU_VERSION);
554 }
555 #endif
556 #ifdef WITH_JSON
557 buf_printf(serverinfo, " Jansson/%s", JANSSON_VERSION);
558 #endif
559
384560 namespace_calendar.enabled =
385561 config_httpmodules & IMAP_ENUM_HTTPMODULES_CALDAV;
386562
392568
393569 caldav_init();
394570
395 if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) {
396 buf_printf(serverinfo, " libical/%s", ICAL_VERSION);
397 #ifdef WITH_JSON
398 buf_printf(serverinfo, " Jansson/%s", JANSSON_VERSION);
399 #endif
400 }
401
402 if (config_getswitch(IMAPOPT_CALDAV_ALLOWSCHEDULING)) {
571 config_allowsched = config_getenum(IMAPOPT_CALDAV_ALLOWSCHEDULING);
572 if (config_allowsched) {
403573 namespace_calendar.allow |= ALLOW_CAL_SCHED;
404574
575 #ifndef HAVE_SCHEDULING_PARAMS
405576 /* Need to set this to parse CalDAV Scheduling parameters */
406577 ical_set_unknown_token_handling_setting(ICAL_ASSUME_IANA_TOKEN);
407 }
578 #endif
579 }
580
581 namespace_principal.enabled = 1;
582 namespace_principal.allow |= namespace_calendar.allow &
583 (ALLOW_CAL | ALLOW_CAL_AVAIL | ALLOW_CAL_SCHED);
408584
409585 compile_time = calc_compile_time(__TIME__, __DATE__);
586
587 buf_printf(&ical_prodid_buf,
588 "-//CyrusIMAP.org/Cyrus %s//EN", cyrus_version());
589 ical_prodid = buf_cstring(&ical_prodid_buf);
590
591 /* Create an array of calendar-user-adddress-set domains */
592 domains = config_getstring(IMAPOPT_CALENDAR_USER_ADDRESS_SET);
593 if (!domains) domains = config_servername;
594
595 tok_init(&tok, domains, " \t", TOK_TRIMLEFT|TOK_TRIMRIGHT);
596 while ((domain = tok_next(&tok))) appendstrlist(&cua_domains, domain);
597 tok_fini(&tok);
410598 }
411599
412600
418606 char ident[MAX_MAILBOX_NAME];
419607 struct buf acl = BUF_INITIALIZER;
420608
609 /* Generate mailboxname of calendar-home-set */
610 caldav_mboxname(NULL, userid, mailboxname);
611
421612 if (httpd_userisadmin ||
422613 global_authisa(httpd_authstate, IMAPOPT_PROXYSERVERS)) {
423614 /* admin or proxy from frontend - won't have DAV database */
428619 }
429620 else {
430621 /* Open CalDAV DB for 'userid' */
622 struct mailbox mailbox;
623
624 mailbox.name = mailboxname;
625
431626 my_caldav_reset();
432 auth_caldavdb = caldav_open(userid, CALDAV_CREATE);
627 auth_caldavdb = caldav_open(&mailbox, CALDAV_CREATE);
433628 if (!auth_caldavdb) fatal("Unable to open CalDAV DB", EC_IOERR);
434629 }
435630
439634 mboxname_hiersep_toexternal(&httpd_namespace, ident, 0);
440635
441636 /* calendar-home-set */
442 caldav_mboxname(NULL, userid, mailboxname);
443637 r = mboxlist_lookup(mailboxname, &mbentry, NULL);
444638 if (r == IMAP_MAILBOX_NONEXISTENT) {
445639 if (config_mupdate_server) {
461655 }
462656 }
463657 }
658 else r = 0;
464659
465660 /* Create locally */
466661 if (!r) r = mboxlist_createmailboxcheck(mailboxname, 0, NULL, 0,
550745
551746 static void my_caldav_shutdown(void)
552747 {
748 if (rscale_calendars) icalarray_free(rscale_calendars);
749 buf_free(&ical_prodid_buf);
750 freestrlist(cua_domains);
751
553752 caldav_done();
554753 }
555754
631830 done:
632831 /* Set proper Allow bits and flags based on path components */
633832 if (tgt->collection) {
833 if (!strncmp(tgt->collection, SCHED_INBOX, strlen(SCHED_INBOX)))
834 tgt->flags = TGT_SCHED_INBOX;
835 else if (!strncmp(tgt->collection, SCHED_OUTBOX, strlen(SCHED_OUTBOX)))
836 tgt->flags = TGT_SCHED_OUTBOX;
837
634838 if (tgt->resource) {
839 if (!tgt->flags) tgt->allow |= ALLOW_WRITE;
840 tgt->allow |= ALLOW_DELETE;
635841 tgt->allow &= ~ALLOW_WRITECOL;
636 tgt->allow |= (ALLOW_WRITE|ALLOW_DELETE);
637 }
638 else if (!strcmp(tgt->collection, SCHED_INBOX))
639 tgt->flags = TGT_SCHED_INBOX;
640 else if (!strcmp(tgt->collection, SCHED_OUTBOX)) {
641 tgt->flags = TGT_SCHED_OUTBOX;
842 }
843 else if (tgt->flags != TGT_SCHED_INBOX) {
642844 tgt->allow |= ALLOW_POST;
643 }
644 else if (!strcmp(tgt->collection, SCHED_DEFAULT))
645 tgt->allow |= ALLOW_POST;
646 else
647 tgt->allow |= (ALLOW_POST|ALLOW_DELETE);
845
846 if (strcmp(tgt->collection, SCHED_DEFAULT))
847 tgt->allow |= ALLOW_DELETE;
848 }
648849 }
649850 else if (tgt->user) tgt->allow |= ALLOW_DELETE;
650851
694895
695896 /* Per RFC 6638, check Schedule-Tag */
696897 if ((hdr = spool_getheader(txn->req_hdrs, "If-Schedule-Tag-Match"))) {
697 if (!*hdr[0]) {
698 /* XXX Workaround for bug in MacOS X 10.9.0 Calendar client */
699 const char *osx_sched_tag_bug_version =
700 "Mac_OS_X/10.9 (13A603) CalendarAgent/174";
701 const char **ua = spool_getheader(txn->req_hdrs, "User-Agent");
702
703 if (ua && !strcmp(ua[0], osx_sched_tag_bug_version)) return precond;
704 }
898 /* Special case for Apple 'If-Schedule-Tag-Match:' with no value
899 * and also no schedule tag on the record - let that match */
900 if (cdata && !stag && !hdr[0][0]) return precond;
901
705902 if (etagcmp(hdr[0], stag)) return HTTP_PRECOND_FAILED;
706903 }
707904
748945 }
749946 break;
750947 default:
751 if (xmlStrcmp(priv->name, BAD_CAST "read-free-busy"))
948 if (!xmlStrcmp(priv->name, BAD_CAST "read-free-busy"))
752949 *rights |= DACL_READFB;
753950 else {
754951 /* DAV:not-supported-privilege */
8141011 static int caldav_copy(struct transaction_t *txn,
8151012 struct mailbox *src_mbox, struct index_record *src_rec,
8161013 struct mailbox *dest_mbox, const char *dest_rsrc,
1014 struct caldav_db *dest_davdb,
8171015 unsigned overwrite, unsigned flags)
8181016 {
8191017 int ret;
8441042 }
8451043
8461044 /* Store source resource at destination */
847 ret = store_resource(txn, ical, dest_mbox, dest_rsrc, auth_caldavdb,
1045 ret = store_resource(txn, ical, dest_mbox, dest_rsrc, dest_davdb,
8481046 overwrite, flags);
8491047
8501048 icalcomponent_free(ical);
8631061
8641062 if (!(namespace_calendar.allow & ALLOW_CAL_SCHED)) return 0;
8651063
866 if (!mailbox) {
1064 /* Only process deletes on regular calendar collections */
1065 if (txn->req_tgt.flags) return 0;
1066
1067 if (!record) {
8671068 /* XXX DELETE collection - check all resources for sched objects */
8681069 }
8691070 else if (cdata->sched_tag) {
9251126 {
9261127 /* Begin iCalendar stream */
9271128 buf_setcstr(buf, "BEGIN:VCALENDAR\r\n");
928 buf_printf(buf, "PRODID:-//CyrusIMAP.org/Cyrus %s//EN\r\n",
929 cyrus_version());
1129 buf_printf(buf, "PRODID:%s\r\n", ical_prodid);
9301130 buf_appendcstr(buf, "VERSION:2.0\r\n");
9311131
9321132 return "";
9381138 buf_setcstr(buf, "END:VCALENDAR\r\n");
9391139 }
9401140
941 static int dump_calendar(struct transaction_t *txn, struct meth_params *gparams)
1141 static int dump_calendar(struct transaction_t *txn, int rights)
9421142 {
9431143 int ret = 0, r, precond;
9441144 struct resp_body_t *resp_body = &txn->resp_body;
9541154 const char **hdr, *sep;
9551155 struct mime_type_t *mime;
9561156
1157 /* Check rights */
1158 if ((rights & DACL_READ) != DACL_READ) {
1159 /* DAV:need-privileges */
1160 txn->error.precond = DAV_NEED_PRIVS;
1161 txn->error.resource = txn->req_tgt.path;
1162 txn->error.rights = DACL_READ;
1163 return HTTP_NO_PRIVS;
1164 }
1165
9571166 /* Check requested MIME type:
9581167 1st entry in caldav_mime_types array MUST be default MIME type */
9591168 if ((hdr = spool_getheader(txn->req_hdrs, "Accept")))
9621171 if (!mime) return HTTP_NOT_ACCEPTABLE;
9631172
9641173 /* Open mailbox for reading */
965 if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) {
1174 r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox);
1175 if (r) {
9661176 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
9671177 txn->req_tgt.mboxname, error_message(r));
9681178 txn->error.desc = error_message(r);
9731183 /* Check any preconditions */
9741184 sprintf(etag, "%u-%u-%u",
9751185 mailbox->i.uidvalidity, mailbox->i.last_uid, mailbox->i.exists);
976 precond = gparams->check_precond(txn, NULL, etag, mailbox->index_mtime);
1186 precond = caldav_check_precond(txn, NULL, etag, mailbox->index_mtime);
9771187
9781188 switch (precond) {
9791189 case HTTP_OK:
10851295 write_body(0, txn, NULL, 0);
10861296
10871297 done:
1088 if (mailbox) mailbox_unlock_index(mailbox, NULL);
1298 mailbox_close(&mailbox);
10891299
10901300 return ret;
10911301 }
11531363
11541364
11551365 /* Create a HTML document listing all calendars available to the user */
1156 static int list_calendars(struct transaction_t *txn,
1157 struct meth_params *gparams)
1366 static int list_calendars(struct transaction_t *txn, int rights)
11581367 {
11591368 int ret = 0, precond;
11601369 time_t lastmod = compile_time;
11631372 static char etag[63];
11641373 unsigned level = 0;
11651374 struct buf *body = &txn->resp_body.payload;
1166 const char *host = NULL;
1375 const char *proto = NULL, *host = NULL;
1376
1377 /* Check rights */
1378 if ((rights & DACL_READ) != DACL_READ) {
1379 /* DAV:need-privileges */
1380 txn->error.precond = DAV_NEED_PRIVS;
1381 txn->error.resource = txn->req_tgt.path;
1382 txn->error.rights = DACL_READ;
1383 return HTTP_NO_PRIVS;
1384 }
11671385
11681386 /* stat() mailboxes.db for Last-Modified and ETag */
11691387 snprintf(mboxlist, MAX_MAILBOX_PATH, "%s%s", config_dir, FNAME_MBOXLIST);
11721390 sprintf(etag, "%ld-%ld-%ld", compile_time, sbuf.st_mtime, sbuf.st_size);
11731391
11741392 /* Check any preconditions */
1175 precond = gparams->check_precond(txn, NULL, etag, lastmod);
1393 precond = caldav_check_precond(txn, NULL, etag, lastmod);
11761394
11771395 switch (precond) {
11781396 case HTTP_OK:
12151433 buf_reset(body);
12161434
12171435 /* Create base URL for calendars */
1218 http_proto_host(txn->req_hdrs, NULL, &host);
1436 http_proto_host(txn->req_hdrs, &proto, &host);
12191437 assert(!buf_len(&txn->buf));
1220 buf_printf(&txn->buf, "webcal://%s%s", host, txn->req_tgt.path);
1438 buf_printf(&txn->buf, "%s://%s%s", proto, host, txn->req_tgt.path);
12211439
12221440 /* Generate list of calendars */
12231441 strlcat(txn->req_tgt.mboxname, ".%", sizeof(txn->req_tgt.mboxname));
12421460 }
12431461
12441462
1463 /* Create a HTML document listing all actions available on the cal-home-set */
1464 static int list_actions(struct transaction_t *txn, int rights)
1465 {
1466 int ret = 0, precond;
1467 time_t lastmod = compile_time;
1468 static char etag[21];
1469 unsigned level = 0;
1470 struct buf *body = &txn->resp_body.payload;
1471 const char *proto = NULL, *host = NULL;
1472
1473 /* Check rights */
1474 if ((rights & DACL_READ) != DACL_READ) {
1475 /* DAV:need-privileges */
1476 txn->error.precond = DAV_NEED_PRIVS;
1477 txn->error.resource = txn->req_tgt.path;
1478 txn->error.rights = DACL_READ;
1479 return HTTP_NO_PRIVS;
1480 }
1481
1482 sprintf(etag, "%ld", compile_time);
1483
1484 /* Check any preconditions */
1485 precond = caldav_check_precond(txn, NULL, etag, lastmod);
1486
1487 switch (precond) {
1488 case HTTP_OK:
1489 case HTTP_NOT_MODIFIED:
1490 /* Fill in ETag, Last-Modified, and Expires */
1491 txn->resp_body.etag = etag;
1492 txn->resp_body.lastmod = lastmod;
1493 txn->resp_body.maxage = 86400; /* 24 hrs */
1494 txn->flags.cc |= CC_MAXAGE;
1495
1496 if (precond != HTTP_NOT_MODIFIED) break;
1497
1498 default:
1499 /* We failed a precondition - don't perform the request */
1500 ret = precond;
1501 goto done;
1502 }
1503
1504 /* Setup for chunked response */
1505 txn->flags.te |= TE_CHUNKED;
1506 txn->resp_body.type = "text/html; charset=utf-8";
1507
1508 /* Short-circuit for HEAD request */
1509 if (txn->meth == METH_HEAD) {
1510 response_header(HTTP_OK, txn);
1511 goto done;
1512 }
1513
1514 /* Send HTML header */
1515 buf_reset(body);
1516 buf_printf_markup(body, level, HTML_DOCTYPE);
1517 buf_printf_markup(body, level++, "<html>");
1518 buf_printf_markup(body, level++, "<head>");
1519 buf_printf_markup(body, level, "<title>%s</title>", "Available Actions");
1520 buf_printf_markup(body, --level, "</head>");
1521 buf_printf_markup(body, level++, "<body>");
1522 buf_printf_markup(body, level, "<h2>%s</h2>", "Available Actions");
1523 buf_printf_markup(body, level++, "<ul>");
1524
1525 /* Generate list of actions */
1526 http_proto_host(txn->req_hdrs, &proto, &host);
1527 buf_printf_markup(body, level,
1528 "<li><a href=\"%s://%s%s?action=%s\">%s</a></li>",
1529 proto, host, txn->req_tgt.path,
1530 "list", "Available Calendars");
1531 buf_printf_markup(body, level,
1532 "<li><a href=\"%s://%s%s?action=%s\">%s</a></li>",
1533 proto, host, txn->req_tgt.path,
1534 "freebusy", "Free/Busy Query");
1535
1536 /* Finish HTML */
1537 buf_printf_markup(body, --level, "</ul>");
1538 buf_printf_markup(body, --level, "</body>");
1539 buf_printf_markup(body, --level, "</html>");
1540 write_body(HTTP_OK, txn, buf_cstring(body), buf_len(body));
1541
1542 /* End of output */
1543 write_body(0, txn, NULL, 0);
1544
1545 done:
1546 return ret;
1547 }
1548
1549
1550 /* Parse an RFC3339 date/time per
1551 http://www.calconnect.org/pubdocs/CD0903%20Freebusy%20Read%20URL.pdf */
1552 static struct icaltimetype icaltime_from_rfc3339_string(const char *str)
1553 {
1554 struct icaltimetype tt = icaltime_null_time();
1555 size_t size;
1556
1557 size = strlen(str);
1558
1559 if (size == 20) {
1560 /* UTC */
1561 if (sscanf(str, "%4u-%02u-%02uT%02u:%02u:%02uZ",
1562 &tt.year, &tt.month, &tt.day,
1563 &tt.hour, &tt.minute, &tt.second) < 6) {
1564 goto fail;
1565 }
1566
1567 tt = icaltime_normalize(tt);
1568 }
1569 else if (size == 25) {
1570 /* TZ offset */
1571 int offset_hour, offset_minute;
1572 char offset_sign;
1573
1574 if (sscanf(str, "%4u-%02u-%02uT%02u:%02u:%02u%c%02u:%02u",
1575 &tt.year, &tt.month, &tt.day,
1576 &tt.hour, &tt.minute, &tt.second,
1577 &offset_sign, &offset_hour, &offset_minute) < 9) {
1578 goto fail;
1579 }
1580
1581 if (offset_sign == '-') {
1582 /* negative offset */
1583 offset_hour *= -1;
1584 offset_minute *= -1;
1585 }
1586 else if (offset_sign != '+') {
1587 goto fail;
1588 }
1589
1590 icaltime_adjust(&tt, 0, -offset_hour, -offset_minute, 0);
1591 }
1592 else {
1593 goto fail;
1594 }
1595
1596 tt.is_utc = 1;
1597 return tt;
1598
1599 fail:
1600 return icaltime_null_time();
1601 }
1602
1603
1604 /* Execute a free/busy query per
1605 http://www.calconnect.org/pubdocs/CD0903%20Freebusy%20Read%20URL.pdf */
1606 static int freebusy_url(struct transaction_t *txn, int rights)
1607 {
1608 int ret = 0;
1609 struct tm *tm;
1610 struct strlist *param;
1611 struct mime_type_t *mime = NULL;
1612 struct propfind_ctx fctx;
1613 struct calquery_filter calfilter;
1614 time_t start;
1615 struct icaldurationtype period = icaldurationtype_null_duration();
1616 icaltimezone *utc = icaltimezone_get_utc_timezone();
1617 icalcomponent *cal;
1618
1619 /* Check rights */
1620 if (!(rights & DACL_READFB)) {
1621 /* DAV:need-privileges */
1622 txn->error.precond = DAV_NEED_PRIVS;
1623 txn->error.resource = txn->req_tgt.path;
1624 txn->error.rights = DACL_READFB;
1625 return HTTP_NO_PRIVS;
1626 }
1627
1628 /* Check/find 'format' */
1629 param = hash_lookup("format", &txn->req_qparams);
1630 if (param) {
1631 if (param->next /* once only */) return HTTP_BAD_REQUEST;
1632
1633 for (mime = caldav_mime_types; mime->content_type; mime++) {
1634 if (is_mediatype(param->s, mime->content_type)) break;
1635 }
1636 }
1637 else mime = caldav_mime_types;
1638
1639 if (!mime || !mime->content_type) return HTTP_NOT_ACCEPTABLE;
1640
1641 memset(&calfilter, 0, sizeof(struct calquery_filter));
1642 calfilter.comp = CAL_COMP_VEVENT | CAL_COMP_VFREEBUSY;
1643 calfilter.busytime_query = 1;
1644 calfilter.check_cal_transp = 1;
1645
1646 /* Check for 'start' */
1647 param = hash_lookup("start", &txn->req_qparams);
1648 if (param) {
1649 if (param->next /* once only */) return HTTP_BAD_REQUEST;
1650
1651 calfilter.start = icaltime_from_rfc3339_string(param->s);
1652 if (icaltime_is_null_time(calfilter.start)) return HTTP_BAD_REQUEST;
1653
1654 /* Default to end of given day */
1655 start = icaltime_as_timet_with_zone(calfilter.start, utc);
1656 tm = localtime(&start);
1657
1658 period.seconds = 60 - tm->tm_sec;
1659 period.minutes = 59 - tm->tm_min;
1660 period.hours = 23 - tm->tm_hour;
1661 }
1662 else {
1663 /* Default to start of current day */
1664 start = time(0);
1665 tm = localtime(&start);
1666 tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
1667 calfilter.start = icaltime_from_timet_with_zone(mktime(tm), 0, utc);
1668
1669 /* Default to 42 day period */
1670 period.days = 42;
1671 }
1672
1673 /* Check for 'period' */
1674 param = hash_lookup("period", &txn->req_qparams);
1675 if (param) {
1676 if (param->next /* once only */ ||
1677 hash_lookup("end", &txn->req_qparams) /* can't use with 'end' */)
1678 return HTTP_BAD_REQUEST;
1679
1680 period = icaldurationtype_from_string(param->s);
1681 if (icaldurationtype_is_bad_duration(period)) return HTTP_BAD_REQUEST;
1682 }
1683
1684 /* Check for 'end' */
1685 param = hash_lookup("end", &txn->req_qparams);
1686 if (param) {
1687 if (param->next /* once only */) return HTTP_BAD_REQUEST;
1688
1689 calfilter.end = icaltime_from_rfc3339_string(param->s);
1690 if (icaltime_is_null_time(calfilter.end)) return HTTP_BAD_REQUEST;
1691 }
1692 else {
1693 /* Set end based on period */
1694 calfilter.end = icaltime_add(calfilter.start, period);
1695 }
1696
1697
1698 memset(&fctx, 0, sizeof(struct propfind_ctx));
1699 fctx.req_tgt = &txn->req_tgt;
1700 fctx.depth = 2;
1701 fctx.userid = proxy_userid;
1702 fctx.int_userid = httpd_userid;
1703 fctx.userisadmin = httpd_userisadmin;
1704 fctx.authstate = httpd_authstate;
1705 fctx.reqd_privs = 0; /* handled by CALDAV:schedule-deliver on Inbox */
1706 fctx.filter = apply_calfilter;
1707 fctx.filter_crit = &calfilter;
1708 fctx.err = &txn->error;
1709 fctx.ret = &ret;
1710 fctx.fetcheddata = 0;
1711
1712 cal = busytime_query_local(txn, &fctx, txn->req_tgt.mboxname,
1713 0, NULL, NULL, NULL);
1714
1715 if (calfilter.busytime.busy) free(calfilter.busytime.busy);
1716
1717 if (cal) {
1718 const char *proto, *host;
1719 icalcomponent *fb;
1720 icalproperty *url;
1721 char *cal_str;
1722
1723 /* Construct URL */
1724 buf_reset(&txn->buf);
1725 http_proto_host(txn->req_hdrs, &proto, &host);
1726 buf_printf(&txn->buf, "%s://%s%s/user/%.*s/?%s",
1727 proto, host, namespace_calendar.prefix,
1728 (int) txn->req_tgt.userlen, txn->req_tgt.user,
1729 URI_QUERY(txn->req_uri));
1730
1731 /* Set URL property */
1732 fb = icalcomponent_get_first_component(cal, ICAL_VFREEBUSY_COMPONENT);
1733 url = icalproperty_new_url(buf_cstring(&txn->buf));
1734 icalcomponent_add_property(fb, url);
1735
1736 /* Set filename of resource */
1737 buf_reset(&txn->buf);
1738 buf_printf(&txn->buf, "%.*s.%s",
1739 (int) txn->req_tgt.userlen, txn->req_tgt.user,
1740 mime->file_ext2);
1741 txn->resp_body.fname = buf_cstring(&txn->buf);
1742
1743 txn->resp_body.type = mime->content_type;
1744
1745 /* iCalendar data in response should not be transformed */
1746 txn->flags.cc |= CC_NOTRANSFORM;
1747
1748 /* Output the iCalendar object */
1749 cal_str = mime->to_string(cal);
1750 icalcomponent_free(cal);
1751
1752 write_body(HTTP_OK, txn, cal_str, strlen(cal_str));
1753 free(cal_str);
1754 }
1755 else ret = HTTP_NOT_FOUND;
1756
1757 return ret;
1758 }
1759
1760
12451761 /* Perform a GET/HEAD request on a CalDAV resource */
12461762 static int meth_get(struct transaction_t *txn, void *params)
12471763 {
12481764 struct meth_params *gparams = (struct meth_params *) params;
1765 struct strlist *action;
12491766 int r, rights;
12501767 char *server, *acl;
12511768
12691786 }
12701787 }
12711788
1272 /* Check ACL for current user */
1273 rights = acl ? cyrus_acl_myrights(httpd_authstate, acl) : 0;
1274 if ((rights & DACL_READ) != DACL_READ) {
1275 /* DAV:need-privileges */
1276 txn->error.precond = DAV_NEED_PRIVS;
1277 txn->error.resource = txn->req_tgt.path;
1278 txn->error.rights = DACL_READ;
1279 return HTTP_FORBIDDEN;
1280 }
1281
12821789 if (server) {
12831790 /* Remote mailbox */
12841791 struct backend *be;
12921799
12931800 /* Local Mailbox */
12941801
1802 /* Get ACL for current user */
1803 rights = acl ? cyrus_acl_myrights(httpd_authstate, acl) : 0;
1804
12951805 /* Get an entire calendar collection */
1296 if (txn->req_tgt.collection) return dump_calendar(txn, gparams);
1806 if (txn->req_tgt.collection) return dump_calendar(txn, rights);
1807
1808 action = hash_lookup("action", &txn->req_qparams);
1809
1810 /* Display available actions HTML page */
1811 if (!action) return list_actions(txn, rights);
12971812
12981813 /* GET a list of calendars under calendar-home-set */
1299 else return list_calendars(txn, gparams);
1814 if (!strcmp(action->s, "list")) return list_calendars(txn, rights);
1815
1816 /* GET busytime of the user */
1817 if (!strcmp(action->s, "freebusy")) return freebusy_url(txn, rights);
1818
1819 /* Unknown action */
1820 return HTTP_BAD_REQUEST;
13001821 }
13011822
13021823
13541875
13551876 /* Read body */
13561877 txn->req_body.flags |= BODY_DECODE;
1357 r = read_body(httpd_in, txn->req_hdrs, &txn->req_body, &txn->error.desc);
1878 r = http_read_body(httpd_in, httpd_out,
1879 txn->req_hdrs, &txn->req_body, &txn->error.desc);
13581880 if (r) {
13591881 txn->flags.conn = CONN_CLOSE;
13601882 return r;
14131935 txn->error.precond = DAV_NEED_PRIVS;
14141936 txn->error.resource = txn->req_tgt.path;
14151937 txn->error.rights = DACL_SCHEDFB;
1416 ret = HTTP_FORBIDDEN;
1938 ret = HTTP_NO_PRIVS;
14171939 }
14181940 else ret = sched_busytime_query(txn, mime, ical);
14191941 else {
14341956 }
14351957
14361958
1437 static const char *get_icalrestriction_errstr(icalcomponent *ical)
1959 const char *get_icalcomponent_errstr(icalcomponent *ical)
14381960 {
14391961 icalcomponent *comp;
14401962
14411963 for (comp = icalcomponent_get_first_component(ical, ICAL_ANY_COMPONENT);
14421964 comp;
14431965 comp = icalcomponent_get_next_component(ical, ICAL_ANY_COMPONENT)) {
1444 icalproperty *prop =
1445 icalcomponent_get_first_property(comp, ICAL_XLICERROR_PROPERTY);
1446 if (prop) return icalproperty_get_xlicerror(prop);
1966 icalproperty *prop;
1967
1968 for (prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY);
1969 prop;
1970 prop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY)) {
1971
1972 if (icalproperty_isa(prop) == ICAL_XLICERROR_PROPERTY) {
1973 icalparameter *param;
1974 const char *errstr = icalproperty_get_xlicerror(prop);
1975
1976 if (!errstr) return "Unknown iCal parsing error";
1977
1978 param = icalproperty_get_first_parameter(
1979 prop, ICAL_XLICERRORTYPE_PARAMETER);
1980
1981 if (icalparameter_get_xlicerrortype(param) ==
1982 ICAL_XLICERRORTYPE_VALUEPARSEERROR) {
1983 /* Check if this is an empty property error */
1984 char propname[256];
1985 if (sscanf(errstr,
1986 "No value for %s property", propname) == 1) {
1987 /* Empty LOCATION is OK */
1988 if (!strcmp(propname, "LOCATION")) continue;
1989 }
1990 }
1991
1992 return errstr;
1993 }
1994 }
14471995 }
14481996
14491997 return NULL;
14652013 */
14662014 static int caldav_put(struct transaction_t *txn,
14672015 struct mime_type_t *mime,
1468 struct mailbox *mailbox, unsigned flags)
1469 {
1470 int ret;
2016 struct mailbox *mailbox,
2017 struct caldav_db *davdb,
2018 unsigned flags)
2019 {
2020 int ret = 0;
14712021 icalcomponent *ical = NULL, *comp, *nextcomp;
14722022 icalcomponent_kind kind;
2023 icalproperty_kind recip_kind;
14732024 icalproperty *prop;
14742025 const char *uid, *organizer = NULL;
14752026
14762027 /* Parse and validate the iCal data */
14772028 ical = mime->from_string(buf_cstring(&txn->req_body.payload));
14782029 if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) {
2030 txn->error.precond = CALDAV_VALID_OBJECT;
2031 ret = HTTP_FORBIDDEN;
2032 goto done;
2033 }
2034
2035 icalrestriction_check(ical);
2036 if ((txn->error.desc = get_icalcomponent_errstr(ical))) {
2037 assert(!buf_len(&txn->buf));
2038 buf_setcstr(&txn->buf, txn->error.desc);
2039 txn->error.desc = buf_cstring(&txn->buf);
14792040 txn->error.precond = CALDAV_VALID_DATA;
14802041 ret = HTTP_FORBIDDEN;
14812042 goto done;
14822043 }
1483 else if (!icalrestriction_check(ical)) {
1484 txn->error.precond = CALDAV_VALID_OBJECT;
1485 if ((txn->error.desc = get_icalrestriction_errstr(ical))) {
1486 assert(!buf_len(&txn->buf));
1487 buf_setcstr(&txn->buf, txn->error.desc);
1488 txn->error.desc = buf_cstring(&txn->buf);
1489 }
1490 ret = HTTP_FORBIDDEN;
1491 goto done;
1492 }
2044
2045 comp = icalcomponent_get_first_real_component(ical);
2046
2047 #ifdef HAVE_RSCALE
2048 /* Make sure we support the provided RSCALE in an RRULE */
2049 prop = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY);
2050 if (prop && rscale_calendars) {
2051 struct icalrecurrencetype rt = icalproperty_get_rrule(prop);
2052
2053 if (rt.rscale) {
2054 /* Perform binary search on sorted icalarray */
2055 unsigned found = 0, start = 0, end = rscale_calendars->num_elements;
2056
2057 ucase(rt.rscale);
2058 while (!found && start < end) {
2059 unsigned mid = start + (end - start) / 2;
2060 const char **rscale = icalarray_element_at(rscale_calendars, mid);
2061 int r = strcmp(rt.rscale, *rscale);
2062
2063 if (r == 0) found = 1;
2064 else if (r < 0) end = mid;
2065 else start = mid + 1;
2066 }
2067
2068 if (!found) {
2069 txn->error.precond = CALDAV_SUPP_RSCALE;
2070 ret = HTTP_FORBIDDEN;
2071 goto done;
2072 }
2073 }
2074 }
2075 #endif /* HAVE_RSCALE */
14932076
14942077 /* Make sure iCal UIDs [and ORGANIZERs] in all components are the same */
1495 comp = icalcomponent_get_first_real_component(ical);
14962078 kind = icalcomponent_isa(comp);
14972079 uid = icalcomponent_get_uid(comp);
14982080 prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
15212103 }
15222104 }
15232105
1524 if ((namespace_calendar.allow & ALLOW_CAL_SCHED) && organizer) {
1525 /* Scheduling object resource */
1526 const char *userid;
1527 struct caldav_data *cdata;
1528 struct sched_param sparam;
1529 icalcomponent *oldical = NULL;
1530
1531 /* Construct userid corresponding to mailbox */
1532 userid = mboxname_to_userid(txn->req_tgt.mboxname);
1533
1534 /* Make sure iCal UID is unique for this user */
1535 caldav_lookup_uid(auth_caldavdb, uid, 0, &cdata);
1536 /* XXX Check errors */
1537
1538 if (cdata->dav.mailbox &&
1539 (strcmp(cdata->dav.mailbox, txn->req_tgt.mboxname) ||
1540 strcmp(cdata->dav.resource, txn->req_tgt.resource))) {
1541 /* CALDAV:unique-scheduling-object-resource */
1542 char ext_userid[MAX_MAILBOX_NAME+1];
1543
1544 strlcpy(ext_userid, userid, sizeof(ext_userid));
1545 mboxname_hiersep_toexternal(&httpd_namespace, ext_userid, 0);
1546
1547 txn->error.precond = CALDAV_UNIQUE_OBJECT;
1548 assert(!buf_len(&txn->buf));
1549 buf_printf(&txn->buf, "%s/user/%s/%s/%s",
1550 namespace_calendar.prefix,
1551 ext_userid, strrchr(cdata->dav.mailbox, '.')+1,
1552 cdata->dav.resource);
1553 txn->error.resource = buf_cstring(&txn->buf);
1554 ret = HTTP_FORBIDDEN;
1555 goto done;
1556 }
1557
1558 /* Lookup the organizer */
1559 if (caladdress_lookup(organizer, &sparam)) {
1560 syslog(LOG_ERR,
1561 "meth_delete: failed to process scheduling message in %s"
1562 " (org=%s, att=%s)",
1563 txn->req_tgt.mboxname, organizer, userid);
1564 txn->error.desc = "Failed to lookup organizer address\r\n";
1565 ret = HTTP_SERVER_ERROR;
1566 goto done;
1567 }
1568
1569 if (cdata->dav.imap_uid) {
1570 /* Update existing object */
1571 struct index_record record;
1572 const char *msg_base = NULL;
1573 unsigned long msg_size = 0;
1574
1575 /* Load message containing the resource and parse iCal data */
1576 mailbox_find_index_record(mailbox, cdata->dav.imap_uid, &record);
1577 mailbox_map_message(mailbox, record.uid, &msg_base, &msg_size);
1578 oldical = icalparser_parse_string(msg_base + record.header_size);
1579 mailbox_unmap_message(mailbox, record.uid, &msg_base, &msg_size);
1580 }
1581
1582 if (!strcmp(sparam.userid, userid)) {
1583 /* Organizer scheduling object resource */
1584 sched_request(organizer, &sparam, oldical, ical, 0);
1585 }
1586 else {
1587 /* Attendee scheduling object resource */
1588 sched_reply(userid, oldical, ical);
1589 }
1590
1591 if (oldical) icalcomponent_free(oldical);
1592
1593 flags |= NEW_STAG;
2106 switch (kind) {
2107 case ICAL_VEVENT_COMPONENT:
2108 case ICAL_VTODO_COMPONENT:
2109 case ICAL_VPOLL_COMPONENT:
2110 recip_kind = (kind == ICAL_VPOLL_COMPONENT) ?
2111 ICAL_VOTER_PROPERTY : ICAL_ATTENDEE_PROPERTY;
2112
2113 if ((namespace_calendar.allow & ALLOW_CAL_SCHED) && organizer
2114 /* XXX Hack for Outlook */
2115 && icalcomponent_get_first_property(comp, recip_kind)) {
2116 /* Scheduling object resource */
2117 const char *userid;
2118 struct caldav_data *cdata;
2119 struct sched_param sparam;
2120 icalcomponent *oldical = NULL;
2121
2122 /* Construct userid corresponding to mailbox */
2123 userid = mboxname_to_userid(txn->req_tgt.mboxname);
2124
2125 /* Make sure iCal UID is unique for this user */
2126 caldav_lookup_uid(davdb, uid, 0, &cdata);
2127 /* XXX Check errors */
2128
2129 if (cdata->dav.mailbox &&
2130 (strcmp(cdata->dav.mailbox, txn->req_tgt.mboxname) ||
2131 strcmp(cdata->dav.resource, txn->req_tgt.resource))) {
2132 /* CALDAV:unique-scheduling-object-resource */
2133 char ext_userid[MAX_MAILBOX_NAME+1];
2134
2135 strlcpy(ext_userid, userid, sizeof(ext_userid));
2136 mboxname_hiersep_toexternal(&httpd_namespace, ext_userid, 0);
2137
2138 txn->error.precond = CALDAV_UNIQUE_OBJECT;
2139 assert(!buf_len(&txn->buf));
2140 buf_printf(&txn->buf, "%s/user/%s/%s/%s",
2141 namespace_calendar.prefix,
2142 ext_userid, strrchr(cdata->dav.mailbox, '.')+1,
2143 cdata->dav.resource);
2144 txn->error.resource = buf_cstring(&txn->buf);
2145 ret = HTTP_FORBIDDEN;
2146 goto done;
2147 }
2148
2149 /* Lookup the organizer */
2150 if (caladdress_lookup(organizer, &sparam)) {
2151 syslog(LOG_ERR,
2152 "meth_put: failed to process scheduling message in %s"
2153 " (org=%s)",
2154 txn->req_tgt.mboxname, organizer);
2155 txn->error.desc = "Failed to lookup organizer address\r\n";
2156 ret = HTTP_SERVER_ERROR;
2157 goto done;
2158 }
2159
2160 if (cdata->dav.imap_uid) {
2161 /* Update existing object */
2162 struct index_record record;
2163 const char *msg_base = NULL;
2164 unsigned long msg_size = 0;
2165
2166 /* Load message containing the resource and parse iCal data */
2167 mailbox_find_index_record(mailbox, cdata->dav.imap_uid, &record);
2168 mailbox_map_message(mailbox, record.uid, &msg_base, &msg_size);
2169 oldical = icalparser_parse_string(msg_base + record.header_size);
2170 mailbox_unmap_message(mailbox, record.uid, &msg_base, &msg_size);
2171 }
2172
2173 if (cdata->organizer) {
2174 /* Don't allow ORGANIZER to be changed */
2175 const char *p = organizer;
2176
2177 if (!strncasecmp(p, "mailto:", 7)) p += 7;
2178 if (strcmp(cdata->organizer, p)) {
2179 txn->error.desc = "Can not change organizer address";
2180 ret = HTTP_FORBIDDEN;
2181 }
2182 }
2183
2184 if (!strcmp(sparam.userid, userid)) {
2185 /* Organizer scheduling object resource */
2186 if (ret) {
2187 txn->error.precond = CALDAV_ALLOWED_ORG_CHANGE;
2188 goto done;
2189 }
2190 sched_request(organizer, &sparam, oldical, ical, 0);
2191 }
2192 else {
2193 /* Attendee scheduling object resource */
2194 if (ret) {
2195 txn->error.precond = CALDAV_ALLOWED_ATT_CHANGE;
2196 goto done;
2197 }
2198 #if 0
2199 if (!oldical) {
2200 /* Can't reply to a non-existent invitation */
2201 /* XXX But what about invites over iMIP? */
2202 txn->error.desc = "Can not reply to non-existent resource";
2203 ret = HTTP_FORBIDDEN;
2204 goto done;
2205 }
2206 #endif
2207 sched_reply(userid, oldical, ical);
2208 }
2209
2210 if (oldical) icalcomponent_free(oldical);
2211
2212 flags |= NEW_STAG;
2213 }
2214 break;
2215
2216 default:
2217 /* Nothing else to do */
2218 break;
15942219 }
15952220
15962221 /* Store resource at target */
15972222 ret = store_resource(txn, ical, mailbox, txn->req_tgt.resource,
1598 auth_caldavdb, OVERWRITE_CHECK, flags);
2223 davdb, OVERWRITE_CHECK, flags);
15992224
16002225 if (flags & PREFER_REP) {
16012226 struct resp_body_t *resp_body = &txn->resp_body;
16512276 static void add_busytime(icalcomponent *comp, struct icaltime_span *span,
16522277 void *rock)
16532278 {
1654 struct busytime *busytime = (struct busytime *) rock;
2279 struct busytime_array *busytime = (struct busytime_array *) rock;
16552280 int is_date = icaltime_is_date(icalcomponent_get_dtstart(comp));
16562281 icaltimezone *utc = icaltimezone_get_utc_timezone();
1657 struct icalperiodtype *newp;
2282 struct busytime *newb;
16582283
16592284 /* Grow the array, if necessary */
16602285 if (busytime->len == busytime->alloc) {
16612286 busytime->alloc += 100; /* XXX arbitrary */
16622287 busytime->busy = xrealloc(busytime->busy,
16632288 busytime->alloc *
1664 sizeof(struct icalperiodtype));
2289 sizeof(struct busytime));
16652290 }
16662291
16672292 /* Add new busytime */
1668 newp = &busytime->busy[busytime->len++];
1669 newp->start = icaltime_from_timet_with_zone(span->start, is_date, utc);
1670 newp->end = icaltime_from_timet_with_zone(span->end, is_date, utc);
1671 newp->duration = icaldurationtype_null_duration();
2293 newb = &busytime->busy[busytime->len++];
2294 newb->per.start = icaltime_from_timet_with_zone(span->start, is_date, utc);
2295 newb->per.start.is_date = 0; /* MUST be DATE-TIME */
2296 if (is_date && icaltime_is_null_time(icalcomponent_get_dtend(comp))) {
2297 newb->per.end = icaltime_null_time();
2298 newb->per.duration = icaldurationtype_from_int(60*60*24); /* P1D */
2299 }
2300 else {
2301 newb->per.end = icaltime_from_timet_with_zone(span->end, is_date, utc);
2302 newb->per.end.is_date = 0; /* MUST be DATE-TIME */
2303 newb->per.duration = icaldurationtype_null_duration();
2304 }
2305
2306 /* Set FBTYPE */
2307 newb->type = (icalcomponent_get_status(comp) == ICAL_STATUS_TENTATIVE) ?
2308 ICAL_FBTYPE_BUSYTENTATIVE : ICAL_FBTYPE_BUSY;
2309 }
2310
2311
2312 static int is_busytime(struct calquery_filter *calfilter, icalcomponent *comp)
2313 {
2314 if (calfilter->busytime_query) {
2315 /* Check TRANSP and STATUS per RFC 4791, section 7.10 */
2316 const icalproperty *prop;
2317
2318 /* Skip transparent events */
2319 prop = icalcomponent_get_first_property(comp, ICAL_TRANSP_PROPERTY);
2320 if (prop && icalproperty_get_transp(prop) == ICAL_TRANSP_TRANSPARENT)
2321 return 0;
2322
2323 /* Skip cancelled events */
2324 if (icalcomponent_get_status(comp) == ICAL_STATUS_CANCELLED) return 0;
2325 }
2326
2327 return 1;
16722328 }
16732329
16742330
17002356 /* Component is later than range */
17012357 return 0;
17022358 }
1703 else if (!cdata->recurring && !calfilter->save_busytime) {
2359 else if (!cdata->recurring && !calfilter->busytime_query) {
17042360 /* Component is within range, non-recurring,
17052361 and we don't need to save busytime */
17062362 return 1;
17102366 * Need to mmap() and parse iCalendar object
17112367 * to perform complete check of each recurrence.
17122368 */
1713 struct busytime *busytime = &calfilter->busytime;
2369 struct busytime_array *busytime = &calfilter->busytime;
17142370 icalcomponent *ical, *comp;
17152371 icalcomponent_kind kind;
17162372 icaltimezone *utc = icaltimezone_get_utc_timezone();
17412397 /* Mark start of where recurrences will be added */
17422398 firstr = busytime->len;
17432399
1744 /* Add all recurring busytime in specified time-range */
1745 icalcomponent_foreach_recurrence(comp,
1746 calfilter->start, calfilter->end,
1747 add_busytime, busytime);
2400 if (is_busytime(calfilter, comp)) {
2401 /* Add all recurring busytime in specified time-range */
2402 icalcomponent_foreach_recurrence(comp,
2403 calfilter->start,
2404 calfilter->end,
2405 add_busytime, busytime);
2406 }
17482407
17492408 /* Mark end of where recurrences were added */
17502409 lastr = busytime->len;
17822441 recurid =
17832442 icaltime_convert_to_zone(recurid,
17842443 icaltimezone_get_utc_timezone());
2444 recurid.is_date = 0; /* make DATE-TIME for comparison */
17852445
17862446 /* Check if this overridden instance is in our array */
17872447 /* XXX Should we replace this linear search with bsearch() */
17882448 for (n = firstr; n < lastr; n++) {
17892449 if (!icaltime_compare(recurid,
1790 busytime->busy[n].start)) {
2450 busytime->busy[n].per.start)) {
17912451 /* Remove the instance
17922452 by sliding all future instances into its place */
17932453 /* XXX Doesn't handle the RANGE=THISANDFUTURE param */
17942454 busytime->len--;
17952455 memmove(&busytime->busy[n], &busytime->busy[n+1],
1796 sizeof(struct icalperiodtype) *
2456 sizeof(struct busytime) *
17972457 (busytime->len - n));
17982458 lastr--;
17992459
18002460 break;
18012461 }
18022462 }
2463
2464 /* If override component isn't busytime, skip it */
2465 if (!is_busytime(calfilter, comp)) continue;
18032466
18042467 /* Check if the new instance is in our time-range */
18052468 recurspan = icaltime_span_new(icalcomponent_get_dtstart(comp),
18112474 }
18122475 }
18132476
1814 if (lastr == firstr) match = 0;
1815
1816 if (!calfilter->save_busytime) busytime->len = 0;
2477 if (busytime->len == firstr) match = 0;
2478
2479 if (!calfilter->busytime_query) busytime->len = 0;
18172480
18182481 icalcomponent_free(ical);
18192482 }
18692532 filter->comp |= CAL_COMP_VFREEBUSY;
18702533 else if (!xmlStrcmp(name, BAD_CAST "VTIMEZONE"))
18712534 filter->comp |= CAL_COMP_VTIMEZONE;
2535 else if (!xmlStrcmp(name, BAD_CAST "VAVAILABILITY"))
2536 filter->comp |= CAL_COMP_VAVAILABILITY;
2537 else if (!xmlStrcmp(name, BAD_CAST "VPOLL"))
2538 filter->comp |= CAL_COMP_VPOLL;
18722539 else {
18732540 error->precond = CALDAV_SUPP_FILTER;
18742541 ret = HTTP_FORBIDDEN;
19562623 case CAL_COMP_VTODO: comp = "VTODO"; break;
19572624 case CAL_COMP_VJOURNAL: comp = "VJOURNAL"; break;
19582625 case CAL_COMP_VFREEBUSY: comp = "VFREEBUSY"; break;
2626 case CAL_COMP_VAVAILABILITY: comp = "VAVAILABILITY"; break;
2627 case CAL_COMP_VPOLL: comp = "VPOLL"; break;
19592628 }
19602629
19612630 if (comp) buf_printf(&fctx->buf, "; component=%s", comp);
20802749 xmlNodePtr node;
20812750 const char *cal = (const char *) rock;
20822751
2083 if (!fctx->userid) return HTTP_NOT_FOUND;
2752 if (!(namespace_calendar.enabled && fctx->req_tgt->user))
2753 return HTTP_NOT_FOUND;
20842754
20852755 /* sched-def-cal-URL only defined on sched-inbox-URL */
20862756 if (!xmlStrcmp(name, BAD_CAST "schedule-default-calendar-URL") &&
20922762 name, ns, NULL, 0);
20932763
20942764 buf_reset(&fctx->buf);
2095 buf_printf(&fctx->buf, "%s/user/%s/%s",
2096 namespace_calendar.prefix, fctx->userid, cal ? cal : "");
2765 buf_printf(&fctx->buf, "%s/user/%.*s/%s", namespace_calendar.prefix,
2766 (int) fctx->req_tgt->userlen, fctx->req_tgt->user,
2767 cal ? cal : "");
20972768
20982769 xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
20992770
21062777 const char *name;
21072778 unsigned long type;
21082779 } cal_comps[] = {
2109 { "VEVENT", CAL_COMP_VEVENT },
2110 { "VTODO", CAL_COMP_VTODO },
2111 { "VJOURNAL", CAL_COMP_VJOURNAL },
2112 { "VFREEBUSY", CAL_COMP_VFREEBUSY },
2113 // { "VTIMEZONE", CAL_COMP_VTIMEZONE },
2114 // { "VALARM", CAL_COMP_VALARM },
2780 { "VEVENT", CAL_COMP_VEVENT },
2781 { "VTODO", CAL_COMP_VTODO },
2782 { "VJOURNAL", CAL_COMP_VJOURNAL },
2783 { "VFREEBUSY", CAL_COMP_VFREEBUSY },
2784 #ifdef HAVE_VAVAILABILITY
2785 { "VAVAILABILITY", CAL_COMP_VAVAILABILITY },
2786 #endif
2787 #ifdef HAVE_VPOLL
2788 { "VPOLL", CAL_COMP_VPOLL },
2789 #endif
2790 // { "VTIMEZONE", CAL_COMP_VTIMEZONE },
2791 // { "VALARM", CAL_COMP_VALARM },
21152792 { NULL, 0 }
21162793 };
21172794
21212798 struct propstat propstat[],
21222799 void *rock __attribute__((unused)))
21232800 {
2124 const char *prop_annot = ANNOT_NS "CALDAV:supported-calendar-component-set";
2801 const char *prop_annot =
2802 ANNOT_NS "<" XML_NS_CALDAV ">supported-calendar-component-set";
21252803 struct annotation_data attrib;
21262804 unsigned long types = 0;
21272805 xmlNodePtr set, node;
21922870 if (!cur) {
21932871 /* All component types are valid */
21942872 const char *prop_annot =
2195 ANNOT_NS "CALDAV:supported-calendar-component-set";
2873 ANNOT_NS "<" XML_NS_CALDAV ">supported-calendar-component-set";
21962874
21972875 buf_reset(&pctx->buf);
21982876 buf_printf(&pctx->buf, "%lu", types);
22982976 void *rock __attribute__((unused)))
22992977 {
23002978 xmlNodePtr node;
2301
2302 if (!fctx->userid) return HTTP_NOT_FOUND;
2979 struct strlist *domains;
2980
2981 if (!(namespace_calendar.enabled && fctx->req_tgt->user))
2982 return HTTP_NOT_FOUND;
23032983
23042984 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
23052985 name, ns, NULL, 0);
23062986
23072987 /* XXX This needs to be done via an LDAP/DB lookup */
2308 buf_reset(&fctx->buf);
2309 buf_printf(&fctx->buf, "mailto:%s@%s", fctx->userid, config_servername);
2310
2311 xmlNewChild(node, fctx->ns[NS_DAV], BAD_CAST "href",
2312 BAD_CAST buf_cstring(&fctx->buf));
2988 for (domains = cua_domains; domains; domains = domains->next) {
2989 buf_reset(&fctx->buf);
2990 buf_printf(&fctx->buf, "mailto:%.*s@%s", (int) fctx->req_tgt->userlen,
2991 fctx->req_tgt->user, domains->s);
2992
2993 xmlNewChild(node, fctx->ns[NS_DAV], BAD_CAST "href",
2994 BAD_CAST buf_cstring(&fctx->buf));
2995 }
2996
2997 return 0;
2998 }
2999
3000
3001 /* Callback to fetch CALDAV:calendar-user-type */
3002 int propfind_calusertype(const xmlChar *name, xmlNsPtr ns,
3003 struct propfind_ctx *fctx,
3004 xmlNodePtr resp __attribute__((unused)),
3005 struct propstat propstat[],
3006 void *rock __attribute__((unused)))
3007 {
3008 const char *type = fctx->req_tgt->user ? "INDIVIDUAL" : "UNKNOWN";
3009
3010 if (!namespace_calendar.enabled) return HTTP_NOT_FOUND;
3011
3012 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
3013 name, ns, BAD_CAST type, 0);
23133014
23143015 return 0;
23153016 }
23223023 struct propstat propstat[],
23233024 void *rock __attribute__((unused)))
23243025 {
2325 const char *prop_annot = ANNOT_NS "CALDAV:schedule-calendar-transp";
3026 const char *prop_annot =
3027 ANNOT_NS "<" XML_NS_CALDAV ">schedule-calendar-transp";
23263028 struct annotation_data attrib;
23273029 const char *value = NULL;
23283030 xmlNodePtr node;
23543056 {
23553057 if (pctx->req_tgt->collection && !pctx->req_tgt->resource) {
23563058 const char *prop_annot =
2357 ANNOT_NS "CALDAV:schedule-calendar-transp";
3059 ANNOT_NS "<" XML_NS_CALDAV ">schedule-calendar-transp";
23583060 const char *transp = "";
23593061
23603062 if (set) {
24093111 }
24103112
24113113
2412 /* Callback to prescreen/fetch CALDAV:calendar-timezone */
2413 static int propfind_timezone(const xmlChar *name, xmlNsPtr ns,
2414 struct propfind_ctx *fctx,
2415 xmlNodePtr resp __attribute__((unused)),
2416 struct propstat propstat[],
2417 void *rock)
3114 /* Callback to prescreen/fetch CALDAV:calendar-timezone/availability */
3115 static int propfind_icalcomponent(const xmlChar *name, xmlNsPtr ns,
3116 struct propfind_ctx *fctx,
3117 xmlNodePtr resp __attribute__((unused)),
3118 struct propstat propstat[],
3119 void *rock)
24183120 {
24193121 xmlNodePtr prop = (xmlNodePtr) rock;
24203122 const char *data = NULL;
24263128
24273129 buf_reset(&fctx->buf);
24283130 buf_printf(&fctx->buf, ANNOT_NS "<%s>%s",
2429 (const char *) ns->href, name);
3131 (const char *) XML_NS_CALDAV, name);
24303132
24313133 memset(&attrib, 0, sizeof(struct annotation_data));
24323134
25503252 }
25513253
25523254
3255 /* Callback to write calendar-availability property */
3256 static int proppatch_availability(xmlNodePtr prop, unsigned set,
3257 struct proppatch_ctx *pctx,
3258 struct propstat propstat[],
3259 void *rock __attribute__((unused)))
3260 {
3261 if (config_allowsched && pctx->req_tgt->flags == TGT_SCHED_INBOX) {
3262 xmlChar *type, *ver = NULL, *freeme = NULL;
3263 struct mime_type_t *mime;
3264 icalcomponent *ical = NULL;
3265 const char *value = NULL;
3266 size_t len = 0;
3267 unsigned valid = 1;
3268
3269 type = xmlGetProp(prop, BAD_CAST "content-type");
3270 if (type) ver = xmlGetProp(prop, BAD_CAST "version");
3271
3272 /* Check/find requested MIME type */
3273 for (mime = caldav_mime_types; type && mime->content_type; mime++) {
3274 if (is_mediatype(mime->content_type, (const char *) type)) {
3275 if (ver &&
3276 (!mime->version || xmlStrcmp(ver, BAD_CAST mime->version))) {
3277 continue;
3278 }
3279 break;
3280 }
3281 }
3282
3283 if (!mime->content_type) {
3284 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
3285 &propstat[PROPSTAT_FORBID],
3286 prop->name, prop->ns, NULL,
3287 CALDAV_SUPP_DATA);
3288 *pctx->ret = HTTP_FORBIDDEN;
3289 valid = 0;
3290 }
3291 else if (set) {
3292 freeme = xmlNodeGetContent(prop);
3293 value = (const char *) freeme;
3294
3295 /* Parse and validate the iCal data */
3296 ical = mime->from_string(value);
3297 if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) {
3298 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
3299 &propstat[PROPSTAT_FORBID],
3300 prop->name, prop->ns, NULL,
3301 CALDAV_VALID_DATA);
3302 *pctx->ret = HTTP_FORBIDDEN;
3303 valid = 0;
3304 }
3305 else if (!icalcomponent_get_first_component(ical,
3306 ICAL_VAVAILABILITY_COMPONENT)) {
3307 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
3308 &propstat[PROPSTAT_FORBID],
3309 prop->name, prop->ns, NULL,
3310 CALDAV_VALID_OBJECT);
3311 *pctx->ret = HTTP_FORBIDDEN;
3312 valid = 0;
3313 }
3314 else if (mime != caldav_mime_types) {
3315 value = icalcomponent_as_ical_string(ical);
3316 }
3317
3318 len = strlen(value);
3319 }
3320
3321 if (valid) {
3322 buf_reset(&pctx->buf);
3323 buf_printf(&pctx->buf, ANNOT_NS "<%s>%s",
3324 (const char *) XML_NS_CALDAV, prop->name);
3325
3326 if (!annotatemore_write_entry(pctx->mailboxname,
3327 buf_cstring(&pctx->buf),
3328 /* shared */ "", value, NULL,
3329 len, 0, &pctx->tid)) {
3330 xml_add_prop(HTTP_OK, pctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
3331 prop->name, prop->ns, NULL, 0);
3332 }
3333 else {
3334 xml_add_prop(HTTP_SERVER_ERROR, pctx->ns[NS_DAV],
3335 &propstat[PROPSTAT_ERROR],
3336 prop->name, prop->ns, NULL, 0);
3337 }
3338 }
3339
3340 if (ical) icalcomponent_free(ical);
3341 if (freeme) xmlFree(freeme);
3342 if (type) xmlFree(type);
3343 if (ver) xmlFree(ver);
3344 }
3345 else {
3346 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
3347 &propstat[PROPSTAT_FORBID], prop->name, prop->ns, NULL, 0);
3348
3349 *pctx->ret = HTTP_FORBIDDEN;
3350 }
3351
3352 return 0;
3353 }
3354
3355
3356 /* Callback to fetch CALDAV:supported-rscale-set */
3357 static int propfind_rscaleset(const xmlChar *name, xmlNsPtr ns,
3358 struct propfind_ctx *fctx,
3359 xmlNodePtr resp __attribute__((unused)),
3360 struct propstat propstat[],
3361 void *rock __attribute__((unused)))
3362 {
3363 assert(name && ns && fctx && propstat);
3364
3365 if (fctx->req_tgt->resource) return HTTP_NOT_FOUND;
3366
3367 if (rscale_calendars) {
3368 xmlNodePtr top;
3369 int i, n;
3370
3371 top = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
3372 name, ns, NULL, 0);
3373
3374 for (i = 0, n = rscale_calendars->num_elements; i < n; i++) {
3375 const char **rscale = icalarray_element_at(rscale_calendars, i);
3376
3377 xmlNewChild(top, fctx->ns[NS_CALDAV],
3378 BAD_CAST "supported-rscale", BAD_CAST *rscale);
3379 }
3380
3381 return 0;
3382 }
3383
3384 return HTTP_NOT_FOUND;
3385 }
3386
3387
25533388 static int report_cal_query(struct transaction_t *txn,
25543389 xmlNodePtr inroot, struct propfind_ctx *fctx)
25553390 {
25583393 struct calquery_filter calfilter;
25593394
25603395 memset(&calfilter, 0, sizeof(struct calquery_filter));
2561 calfilter.save_busytime = 0;
2562
2563 fctx->davdb = auth_caldavdb;
3396
3397 fctx->filter_crit = &calfilter;
3398 fctx->open_db = (db_open_proc_t) &my_caldav_open;
3399 fctx->close_db = (db_close_proc_t) &my_caldav_close;
25643400 fctx->lookup_resource = (db_lookup_proc_t) &caldav_lookup_resource;
25653401 fctx->foreach_resource = (db_foreach_proc_t) &caldav_foreach;
25663402 fctx->proc_by_resource = &propfind_by_resource;
25713407 if (!xmlStrcmp(node->name, BAD_CAST "filter")) {
25723408 ret = parse_comp_filter(node->children, &calfilter, &txn->error);
25733409 if (ret) return ret;
2574 else {
2575 fctx->filter = apply_calfilter;
2576 fctx->filter_crit = &calfilter;
2577 }
3410 else fctx->filter = apply_calfilter;
25783411 }
25793412 else if (!xmlStrcmp(node->name, BAD_CAST "timezone")) {
25803413 xmlChar *tz = NULL;
25993432 }
26003433 }
26013434
2602 if (fctx->depth > 0) {
3435 if (fctx->depth++ > 0) {
26033436 /* Calendar collection(s) */
26043437 if (txn->req_tgt.collection) {
26053438 /* Add response for target calendar collection */
26133446 httpd_authstate, propfind_by_collection, fctx);
26143447 }
26153448
3449 if (fctx->davdb) my_caldav_close(fctx->davdb);
3450
26163451 ret = *fctx->ret;
26173452 }
26183453
26193454 /* RRULEs still populate busytime array */
26203455 if (calfilter.busytime.busy) free(calfilter.busytime.busy);
26213456
2622 return ret;
3457 return (ret ? ret : HTTP_MULTI_STATUS);
26233458 }
26243459
26253460
26273462 xmlNodePtr inroot, struct propfind_ctx *fctx)
26283463 {
26293464 int r, ret = 0;
2630 struct request_target_t tgt;
26313465 struct mailbox *mailbox = NULL;
26323466 xmlNodePtr node;
26333467 struct buf uri = BUF_INITIALIZER;
2634
2635 memset(&tgt, 0, sizeof(struct request_target_t));
2636 tgt.namespace = URL_NS_CALENDAR;
26373468
26383469 /* Get props for each href */
26393470 for (node = inroot->children; node; node = node->next) {
26413472 !xmlStrcmp(node->name, BAD_CAST "href")) {
26423473 xmlChar *href = xmlNodeListGetString(inroot->doc, node->children, 1);
26433474 int len = xmlStrlen(href);
3475 struct request_target_t tgt;
26443476 struct caldav_data *cdata;
26453477
26463478 buf_ensure(&uri, len);
26483480 xmlFree(href);
26493481
26503482 /* Parse the path */
3483 memset(&tgt, 0, sizeof(struct request_target_t));
3484 tgt.namespace = URL_NS_CALENDAR;
3485
26513486 if ((r = caldav_parse_path(uri.s, &tgt, &fctx->err->desc))) {
26523487 ret = r;
26533488 goto done;
26573492
26583493 /* Check if we already have this mailbox open */
26593494 if (!mailbox || strcmp(mailbox->name, tgt.mboxname)) {
2660 if (mailbox) mailbox_unlock_index(mailbox, NULL);
3495 if (mailbox) mailbox_close(&mailbox);
26613496
26623497 /* Open mailbox for reading */
2663 if ((r = http_mailbox_open(tgt.mboxname, &mailbox, LOCK_SHARED))) {
3498 r = mailbox_open_irl(tgt.mboxname, &mailbox);
3499 if (r && r != IMAP_MAILBOX_NONEXISTENT) {
26643500 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
26653501 tgt.mboxname, error_message(r));
26663502 txn->error.desc = error_message(r);
26713507 fctx->mailbox = mailbox;
26723508 }
26733509
3510 if (!fctx->mailbox || !tgt.resource) {
3511 /* Add response for missing target */
3512 xml_add_response(fctx, HTTP_NOT_FOUND, 0);
3513 continue;
3514 }
3515
3516 /* Open the DAV DB corresponding to the mailbox */
3517 fctx->davdb = my_caldav_open(fctx->mailbox);
3518
26743519 /* Find message UID for the resource */
2675 caldav_lookup_resource(auth_caldavdb,
3520 caldav_lookup_resource(fctx->davdb,
26763521 tgt.mboxname, tgt.resource, 0, &cdata);
26773522 cdata->dav.resource = tgt.resource;
26783523 /* XXX Check errors */
26793524
26803525 propfind_by_resource(fctx, cdata);
3526
3527 my_caldav_close(fctx->davdb);
26813528 }
26823529 }
26833530
26843531 done:
2685 if (mailbox) mailbox_unlock_index(mailbox, NULL);
3532 mailbox_close(&mailbox);
26863533 buf_free(&uri);
26873534
2688 return ret;
3535 return (ret ? ret : HTTP_MULTI_STATUS);
26893536 }
26903537
26913538
27273574 struct calquery_filter *calfilter =
27283575 (struct calquery_filter *) fctx->filter_crit;
27293576
2730 if (calfilter && calfilter->check_transp) {
3577 if (calfilter && calfilter->check_cal_transp) {
27313578 /* Check if the collection is marked as transparent */
27323579 struct annotation_data attrib;
27333580 const char *prop_annot =
2734 ANNOT_NS "CALDAV:schedule-calendar-transp";
3581 ANNOT_NS "<" XML_NS_CALDAV ">schedule-calendar-transp";
27353582
27363583 if (!annotatemore_lookup(mboxname, prop_annot, /* shared */ "", &attrib)
27373584 && attrib.value && !strcmp(attrib.value, "transparent")) return 0;
27413588 }
27423589
27433590
2744 /* Compare start times of busytime period -- used for sorting */
3591 /* Compare type of busytime periods -- used for sorting */
3592 static int compare_busytime_type(struct busytime *a, struct busytime *b)
3593 {
3594 /* UNAVAILABLE < BUSY < TENTATIVE */
3595 if (a->type == b->type) return 0;
3596
3597 else if (a->type == ICAL_FBTYPE_BUSYUNAVAILABLE ||
3598 b->type == ICAL_FBTYPE_BUSYTENTATIVE) return -1;
3599
3600 else return 1;
3601 }
3602
3603 /* Compare start/end times and type of busytime periods -- used for sorting */
27453604 static int compare_busytime(const void *b1, const void *b2)
27463605 {
2747 struct icalperiodtype *a = (struct icalperiodtype *) b1;
2748 struct icalperiodtype *b = (struct icalperiodtype *) b2;
2749
2750 return icaltime_compare(a->start, b->start);
3606 struct busytime *a = (struct busytime *) b1;
3607 struct busytime *b = (struct busytime *) b2;
3608
3609 int r = icaltime_compare(a->per.start, b->per.start);
3610
3611 if (r == 0) {
3612 r = icaltime_compare(a->per.end, b->per.end);
3613
3614 if (r == 0) r = compare_busytime_type(a, b);
3615 }
3616
3617 return r;
27513618 }
27523619
27533620
27623629 {
27633630 struct calquery_filter *calfilter =
27643631 (struct calquery_filter *) fctx->filter_crit;
2765 struct busytime *busytime = &calfilter->busytime;
3632 struct busytime_array *busytime = &calfilter->busytime;
27663633 icalcomponent *cal = NULL;
27673634
3635 fctx->open_db = (db_open_proc_t) &my_caldav_open;
3636 fctx->close_db = (db_close_proc_t) &my_caldav_close;
27683637 fctx->lookup_resource = (db_lookup_proc_t) &caldav_lookup_resource;
27693638 fctx->foreach_resource = (db_foreach_proc_t) &caldav_foreach;
27703639 fctx->proc_by_resource = &busytime_by_resource;
27863655 mailboxname, 1, httpd_userid,
27873656 httpd_authstate, busytime_by_collection, fctx);
27883657 }
3658
3659 if (fctx->davdb) my_caldav_close(fctx->davdb);
27893660 }
27903661
27913662 if (!*fctx->ret) {
2792 struct buf prodid = BUF_INITIALIZER;
27933663 icalcomponent *fb;
27943664 icalproperty *prop;
27953665 time_t now = time(0);
27963666 unsigned n;
27973667
27983668 /* Construct iCalendar object with VFREEBUSY component */
2799 buf_printf(&prodid, "-//CyrusIMAP.org/Cyrus %s//EN", cyrus_version());
28003669 cal = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT,
28013670 icalproperty_new_version("2.0"),
2802 icalproperty_new_prodid(buf_cstring(&prodid)),
3671 icalproperty_new_prodid(ical_prodid),
28033672 0);
2804 buf_free(&prodid);
28053673
28063674 if (method) icalcomponent_set_method(cal, method);
28073675
28283696 icalcomponent_add_component(cal, fb);
28293697
28303698 /* Sort busytime periods by start time */
2831 qsort(busytime->busy, busytime->len, sizeof(struct icalperiodtype),
3699 qsort(busytime->busy, busytime->len, sizeof(struct busytime),
28323700 compare_busytime);
28333701
28343702 /* Add busytime periods to VFREEBUSY component, coalescing as needed */
28353703 for (n = 0; n < busytime->len; n++) {
28363704 if ((n+1 < busytime->len) &&
2837 icaltime_compare(busytime->busy[n].end,
2838 busytime->busy[n+1].start) >= 0) {
2839 /* Periods overlap -- coalesce into next busytime */
2840 memcpy(&busytime->busy[n+1].start, &busytime->busy[n].start,
3705 busytime->busy[n+1].type == busytime->busy[n].type &&
3706 icaltime_compare(busytime->busy[n].per.end,
3707 busytime->busy[n+1].per.start) >= 0) {
3708 /* Periods (same type) overlap -- coalesce into next busytime */
3709 memcpy(&busytime->busy[n+1].per.start,
3710 &busytime->busy[n].per.start,
28413711 sizeof(struct icaltimetype));
2842 if (icaltime_compare(busytime->busy[n].end,
2843 busytime->busy[n+1].end) > 0) {
2844 memcpy(&busytime->busy[n+1].end, &busytime->busy[n].end,
3712 if (icaltime_compare(busytime->busy[n].per.end,
3713 busytime->busy[n+1].per.end) > 0) {
3714 memcpy(&busytime->busy[n+1].per.end,
3715 &busytime->busy[n].per.end,
28453716 sizeof(struct icaltimetype));
28463717 }
28473718 }
28483719 else {
28493720 icalproperty *busy =
2850 icalproperty_new_freebusy(busytime->busy[n]);
3721 icalproperty_new_freebusy(busytime->busy[n].per);
3722
3723 /* Add FBTYPE */
3724 icalproperty_add_parameter(
3725 busy,
3726 icalparameter_new_fbtype(busytime->busy[n].type));
28513727
28523728 icalcomponent_add_property(fb, busy);
28533729 }
28673743 struct calquery_filter calfilter;
28683744 xmlNodePtr node;
28693745 icalcomponent *cal;
3746 icaltimezone *utc = icaltimezone_get_utc_timezone();
28703747
28713748 /* Can not be run against a resource */
28723749 if (txn->req_tgt.resource) return HTTP_FORBIDDEN;
28803757
28813758 memset(&calfilter, 0, sizeof(struct calquery_filter));
28823759 calfilter.comp = CAL_COMP_VEVENT | CAL_COMP_VFREEBUSY;
2883 calfilter.start = icaltime_from_timet_with_zone(INT_MIN, 0, NULL);
2884 calfilter.end = icaltime_from_timet_with_zone(INT_MAX, 0, NULL);
2885 calfilter.save_busytime = 1;
3760 calfilter.start = icaltime_from_timet_with_zone(INT_MIN, 0, utc);
3761 calfilter.end = icaltime_from_timet_with_zone(INT_MAX, 0, utc);
3762 calfilter.busytime_query = 1;
28863763 fctx->filter = apply_calfilter;
28873764 fctx->filter_crit = &calfilter;
2888 fctx->davdb = auth_caldavdb;
28893765
28903766 /* Parse children element of report */
28913767 for (node = inroot->children; node; node = node->next) {
29483824 icalproperty_method meth;
29493825 icalproperty *prop;
29503826 unsigned mykind = 0;
3827 char *header;
29513828 const char *organizer = NULL;
2952 const char *prop_annot = ANNOT_NS "CALDAV:supported-calendar-component-set";
3829 const char *prop_annot =
3830 ANNOT_NS "<" XML_NS_CALDAV ">supported-calendar-component-set";
29533831 struct annotation_data attrib;
29543832 struct caldav_data *cdata;
29553833 FILE *f = NULL;
29563834 struct stagemsg *stage;
29573835 const char *uid, *ics;
29583836 uquota_t size;
3837 uint32_t expunge_uid = 0;
3838 struct index_record oldrecord;
29593839 time_t now = time(NULL);
29603840 char datestr[80];
29613841 struct appendstate as;
29703850 case ICAL_VTODO_COMPONENT: mykind = CAL_COMP_VTODO; break;
29713851 case ICAL_VJOURNAL_COMPONENT: mykind = CAL_COMP_VJOURNAL; break;
29723852 case ICAL_VFREEBUSY_COMPONENT: mykind = CAL_COMP_VFREEBUSY; break;
3853 case ICAL_VAVAILABILITY_COMPONENT: mykind = CAL_COMP_VAVAILABILITY; break;
3854 #ifdef HAVE_VPOLL
3855 case ICAL_VPOLL_COMPONENT: mykind = CAL_COMP_VPOLL; break;
3856 #endif
29733857 default:
29743858 txn->error.precond = CALDAV_SUPP_COMP;
29753859 return HTTP_FORBIDDEN;
29963880 return HTTP_FORBIDDEN;
29973881 }
29983882
3883 /* XXX - theoretical race, but the mailbox is locked, so nothing
3884 * else can ACTUALLY change it */
3885 if (cdata->dav.imap_uid) {
3886 /* Fetch index record for the resource */
3887 r = mailbox_find_index_record(mailbox, cdata->dav.imap_uid,
3888 &oldrecord);
3889
3890 if (overwrite == OVERWRITE_CHECK) {
3891 /* Check any preconditions */
3892 const char *etag = message_guid_encode(&oldrecord.guid);
3893 time_t lastmod = oldrecord.internaldate;
3894 int precond = caldav_check_precond(txn, cdata,
3895 etag, lastmod);
3896
3897 if (precond != HTTP_OK)
3898 return HTTP_PRECOND_FAILED;
3899 }
3900
3901 expunge_uid = cdata->dav.imap_uid;
3902 }
3903
29993904 /* Check for existing iCalendar UID */
30003905 caldav_lookup_uid(caldavdb, uid, 0, &cdata);
30013906 if (!(flags & NO_DUP_CHECK) &&
30303935 prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
30313936 if (prop) {
30323937 organizer = icalproperty_get_organizer(prop)+7;
3033 fprintf(f, "From: %s\r\n", organizer);
3938 header = charset_encode_mimeheader(organizer, 0);
3939 fprintf(f, "From: %s\r\n", header);
3940 free(header);
3941 }
3942 else if (strchr(proxy_userid, '@')) {
3943 /* XXX This needs to be done via an LDAP/DB lookup */
3944 header = charset_encode_mimeheader(proxy_userid, 0);
3945 fprintf(f, "From: %s\r\n", header);
3946 free(header);
30343947 }
30353948 else {
3036 /* XXX This needs to be done via an LDAP/DB lookup */
3037 fprintf(f, "From: %s@%s\r\n", proxy_userid, config_servername);
3038 }
3039
3040 fprintf(f, "Subject: %s\r\n", icalcomponent_get_summary(comp));
3949 struct buf *headbuf = &txn->buf;
3950
3951 assert(!headbuf->len);
3952 buf_printf(headbuf, "%s@%s", proxy_userid, config_servername);
3953 header = charset_encode_mimeheader(headbuf->s, headbuf->len);
3954 buf_reset(headbuf);
3955
3956 fprintf(f, "From: %s\r\n", header);
3957 free(header);
3958 }
3959
3960 header = charset_encode_mimeheader(icalcomponent_get_summary(comp), 0);
3961 fprintf(f, "Subject: %s\r\n", header);
3962 free(header);
30413963
30423964 rfc822date_gen(datestr, sizeof(datestr),
30433965 icaltime_as_timet_with_zone(icalcomponent_get_dtstamp(comp),
30443966 icaltimezone_get_utc_timezone()));
30453967 fprintf(f, "Date: %s\r\n", datestr);
30463968
3047 fprintf(f, "Message-ID: <%s@%s>\r\n", uid, config_servername);
3969 /* XXX - validate uid for mime safety? */
3970 if (strchr(uid, '@')) {
3971 fprintf(f, "Message-ID: <%s>\r\n", uid);
3972 }
3973 else {
3974 fprintf(f, "Message-ID: <%s@%s>\r\n", uid, config_servername);
3975 }
30483976
30493977 fprintf(f, "Content-Type: text/calendar; charset=utf-8");
30503978 if ((meth = icalcomponent_get_method(ical)) != ICAL_METHOD_NONE) {
31084036 }
31094037 else {
31104038 /* append_commit() returns a write-locked index */
3111 struct index_record newrecord, oldrecord, *expunge;
4039 struct index_record newrecord;
31124040
31134041 /* Read index record for new message (always the last one) */
31144042 mailbox_read_index_record(mailbox, mailbox->i.num_records,
31154043 &newrecord);
31164044
3117 /* Find message UID for the current resource, if exists */
3118 caldav_lookup_resource(caldavdb,
3119 mailbox->name, resource, 1, &cdata);
3120 /* XXX check for errors */
3121
3122 if (cdata->dav.imap_uid) {
4045 if (expunge_uid) {
31234046 /* Now that we have the replacement message in place
31244047 and the mailbox locked, re-read the old record
31254048 and see if we should overwrite it. Either way,
31294052
31304053 ret = HTTP_NO_CONTENT;
31314054
3132 /* Fetch index record for the resource */
3133 r = mailbox_find_index_record(mailbox, cdata->dav.imap_uid,
3134 &oldrecord);
3135
3136 if (overwrite == OVERWRITE_CHECK) {
3137 /* Check any preconditions */
3138 const char *etag = message_guid_encode(&oldrecord.guid);
3139 time_t lastmod = oldrecord.internaldate;
3140 int precond = caldav_check_precond(txn, cdata,
3141 etag, lastmod);
3142
3143 overwrite = (precond == HTTP_OK);
3144 }
3145
3146 if (overwrite) {
3147 /* Keep new resource - expunge the old one */
3148 expunge = &oldrecord;
3149 }
3150 else {
3151 /* Keep old resource - expunge the new one */
3152 expunge = &newrecord;
3153 ret = HTTP_PRECOND_FAILED;
3154 }
3155
31564055 /* Perform the actual expunge */
31574056 r = mailbox_user_flag(mailbox, DFLAG_UNBIND, &userflag);
31584057 if (!r) {
3159 expunge->user_flags[userflag/32] |= 1<<(userflag&31);
3160 expunge->system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED;
3161 r = mailbox_rewrite_index_record(mailbox, expunge);
4058 oldrecord.user_flags[userflag/32] |= 1<<(userflag&31);
4059 oldrecord.system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED;
4060 r = mailbox_rewrite_index_record(mailbox, &oldrecord);
31624061 }
31634062 if (r) {
31644063 syslog(LOG_ERR, "expunging record (%s) failed: %s",
31714070 if (!r) {
31724071 struct resp_body_t *resp_body = &txn->resp_body;
31734072
3174 /* Create mapping entry from resource name to UID */
3175 cdata->dav.mailbox = mailbox->name;
3176 cdata->dav.resource = resource;
3177 cdata->dav.imap_uid = newrecord.uid;
3178 caldav_make_entry(ical, cdata);
3179
3180 if (!cdata->dav.creationdate) cdata->dav.creationdate = now;
3181 if (!cdata->organizer) cdata->sched_tag = NULL;
3182 else if (flags & NEW_STAG) {
3183 resp_body->stag = cdata->sched_tag = sched_tag;
4073 if (cdata->organizer && (flags & NEW_STAG)) {
4074 resp_body->stag = sched_tag;
31844075 }
3185
3186 caldav_write(caldavdb, cdata, 1);
3187 /* XXX check for errors, if this fails, backout changes */
31884076
31894077 if ((flags & PREFER_REP) || !(flags & NEW_STAG)) {
31904078 /* Tell client about the new resource */
32164104 if (!addr) return HTTP_NOT_FOUND;
32174105
32184106 p = (char *) addr;
3219 if (!strncmp(addr, "mailto:", 7)) p += 7;
4107 if (!strncasecmp(addr, "mailto:", 7)) p += 7;
32204108
32214109 /* XXX Do LDAP/DB/socket lookup to see if user is local */
32224110 /* XXX Hack until real lookup stuff is written */
32234111 strlcpy(userid, p, sizeof(userid));
3224 if ((p = strchr(userid, '@'))) *p++ = '\0';
4112 if ((p = strchr(userid, '@')) && !(*p = '\0') && *++p) {
4113 struct strlist *domains = cua_domains;
4114
4115 for (; domains && strcmp(p, domains->s); domains = domains->next);
4116
4117 if (!domains) islocal = 0;
4118 }
32254119
32264120 if (islocal) {
32274121 /* User is in a local domain */
32414135 snprintf(mailboxname, sizeof(mailboxname),
32424136 "user.%s.%s", param->userid, calendarprefix);
32434137
3244 if ((r = http_mlookup(mailboxname, &param->server, NULL, NULL))) {
3245 syslog(LOG_ERR, "mlookup(%s) failed: %s",
4138 r = http_mlookup(mailboxname, &param->server, NULL, NULL);
4139 if (!r) {
4140 if (param->server) param->flags |= SCHEDTYPE_ISCHEDULE;
4141 return 0;
4142 }
4143 else {
4144 syslog(LOG_NOTICE, "mlookup(%s) failed: %s",
32464145 mailboxname, error_message(r));
32474146
3248 switch (r) {
3249 case IMAP_PERMISSION_DENIED: return HTTP_FORBIDDEN;
3250 case IMAP_MAILBOX_NONEXISTENT: return HTTP_NOT_FOUND;
3251 default: return HTTP_SERVER_ERROR;
3252 }
3253 }
3254
3255 if (param->server) param->flags |= SCHEDTYPE_ISCHEDULE;
3256 }
3257 else {
3258 /* User is outside of our domain(s) -
3259 Do remote scheduling (default = iMIP) */
3260 param->flags |= SCHEDTYPE_REMOTE;
4147 /* Fall through and try remote */
4148 }
4149 }
4150
4151 /* User is outside of our domain(s) -
4152 Do remote scheduling (default = iMIP) */
4153 param->flags |= SCHEDTYPE_REMOTE;
32614154
32624155 #ifdef WITH_DKIM
3263 /* Do iSchedule DNS SRV lookup */
3264
3265 /* XXX If success, set server, port,
3266 and flags |= SCHEDTYPE_ISCHEDULE [ | SCHEDTYPE_SSL ] */
3267 #endif
3268 }
4156 /* Do iSchedule DNS SRV lookup */
4157
4158 /* XXX If success, set server, port,
4159 and flags |= SCHEDTYPE_ISCHEDULE [ | SCHEDTYPE_SSL ] */
4160
4161 #ifdef IOPTEST /* CalConnect ioptest */
4162 if (!strcmp(p, "example.com")) {
4163 param->userid = userid;
4164 param->server = "ischedule.example.com";
4165 param->port = 8008;
4166 param->flags |= SCHEDTYPE_ISCHEDULE;
4167 }
4168 else if (!strcmp(p, "mysite.edu")) {
4169 param->userid = userid;
4170 param->server = "ischedule.mysite.edu";
4171 param->port = 8080;
4172 param->flags |= SCHEDTYPE_ISCHEDULE;
4173 }
4174 #endif /* IOPTEST */
4175
4176 #endif /* WITH_DKIM */
32694177
32704178 return 0;
32714179 }
32784186 icalproperty *prop;
32794187 icalproperty_method meth;
32804188 icalcomponent_kind kind;
3281 const char *argv[8], *organizer, *subject;
4189 const char *argv[8], *originator, *subject;
32824190 FILE *sm;
32834191 pid_t pid;
32844192 int r;
32854193 time_t t = time(NULL);
32864194 char datestr[80];
32874195 static unsigned send_count = 0;
4196 icalproperty_kind recip_kind;
4197 const char *(*get_recipient)(const icalproperty *);
32884198
32894199 meth = icalcomponent_get_method(ical);
32904200 comp = icalcomponent_get_first_real_component(ical);
32914201 kind = icalcomponent_isa(comp);
3292 prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
3293 organizer = icalproperty_get_organizer(prop) + 7;
4202
4203 /* Determine Originator and Recipient(s) based on methond and component */
4204 if (meth == ICAL_METHOD_REPLY) {
4205 recip_kind = ICAL_ORGANIZER_PROPERTY;
4206 get_recipient = &icalproperty_get_organizer;
4207
4208 if (kind == ICAL_VPOLL_COMPONENT) {
4209 prop = icalcomponent_get_first_property(comp, ICAL_VOTER_PROPERTY);
4210 originator = icalproperty_get_voter(prop) + 7;
4211 }
4212 else {
4213 prop =
4214 icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY);
4215 originator = icalproperty_get_attendee(prop) + 7;
4216 }
4217 }
4218 else {
4219 prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
4220 originator = icalproperty_get_organizer(prop) + 7;
4221
4222 if (kind == ICAL_VPOLL_COMPONENT) {
4223 recip_kind = ICAL_VOTER_PROPERTY;
4224 get_recipient = &icalproperty_get_voter;
4225 }
4226 else {
4227 recip_kind = ICAL_ATTENDEE_PROPERTY;
4228 get_recipient = &icalproperty_get_attendee;
4229 }
4230 }
32944231
32954232 argv[0] = "sendmail";
32964233 argv[1] = "-f";
3297 argv[2] = organizer;
4234 argv[2] = originator;
32984235 argv[3] = "-i";
32994236 argv[4] = "-N";
33004237 argv[5] = "failure,delay";
33054242 if (sm == NULL) return HTTP_UNAVAILABLE;
33064243
33074244 /* Create iMIP message */
3308 fprintf(sm, "From: %s\r\n", organizer);
3309
3310 for (prop = icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY);
4245 fprintf(sm, "From: %s\r\n", originator);
4246
4247 for (prop = icalcomponent_get_first_property(comp, recip_kind);
33114248 prop;
3312 prop = icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) {
3313 fprintf(sm, "To: %s\r\n", icalproperty_get_attendee(prop) + 7);
4249 prop = icalcomponent_get_next_property(comp, recip_kind)) {
4250 fprintf(sm, "To: %s\r\n", get_recipient(prop) + 7);
33144251 }
33154252
33164253 subject = icalcomponent_get_summary(comp);
33554292 resp = xmlNewChild(root, NULL, BAD_CAST "response", NULL);
33564293 recip = xmlNewChild(resp, NULL, BAD_CAST "recipient", NULL);
33574294
3358 if (dav_ns) xmlNewChild(recip, dav_ns, BAD_CAST "href", recipient);
4295 if (dav_ns) xml_add_href(recip, dav_ns, (const char *) recipient);
33594296 else xmlNodeAddContent(recip, recipient);
33604297
33614298 if (status)
35414478 goto done;
35424479 }
35434480
4481 /* Need DAV for hrefs */
4482 ensure_ns(ns, NS_DAV, root, XML_NS_DAV, "D");
4483
35444484 /* Populate our filter and propfind context for local attendees */
35454485 memset(&calfilter, 0, sizeof(struct calquery_filter));
35464486 calfilter.comp = CAL_COMP_VEVENT | CAL_COMP_VFREEBUSY;
35474487 calfilter.start = icalcomponent_get_dtstart(comp);
35484488 calfilter.end = icalcomponent_get_dtend(comp);
3549 calfilter.check_transp = 1;
3550 calfilter.save_busytime = 1;
4489 calfilter.busytime_query = 1;
4490 calfilter.check_cal_transp = 1;
35514491
35524492 memset(&fctx, 0, sizeof(struct propfind_ctx));
35534493 fctx.req_tgt = &txn->req_tgt;
36644604 snprintf(mailboxname, sizeof(mailboxname),
36654605 "user.%s.%s", userid, calendarprefix);
36664606
3667 fctx.davdb = caldav_open(userid, CALDAV_CREATE);
4607 fctx.davdb = NULL;
36684608 fctx.req_tgt->collection = NULL;
36694609 calfilter.busytime.len = 0;
36704610 busy = busytime_query_local(txn, &fctx, mailboxname,
36714611 ICAL_METHOD_REPLY, uid,
36724612 organizer, attendee);
3673
3674 caldav_close(fctx.davdb);
36754613 }
36764614
36774615 if (busy) {
37414679
37424680 if (sched_data) {
37434681 if (sched_data->itip) icalcomponent_free(sched_data->itip);
3744 if (sched_data->force_send) free(sched_data->force_send);
37454682 free(sched_data);
37464683 }
37474684 }
38314768 }
38324769 }
38334770
4771
4772 #ifdef HAVE_VPOLL
4773 /*
4774 * deliver_merge_reply() helper function
4775 *
4776 * Merge VOTER responses into VPOLL subcomponents
4777 */
4778 static void deliver_merge_vpoll_reply(icalcomponent *ical, icalcomponent *reply)
4779 {
4780 icalproperty *mastervoterp;
4781 const char *voter;
4782 icalcomponent *comp;
4783
4784 mastervoterp = icalcomponent_get_first_property(reply, ICAL_VOTER_PROPERTY);
4785 voter = icalproperty_get_voter(mastervoterp);
4786
4787 /* Process each existing VPOLL subcomponent */
4788 for (comp = icalcomponent_get_first_component(ical, ICAL_ANY_COMPONENT);
4789 comp;
4790 comp = icalcomponent_get_next_component(ical, ICAL_ANY_COMPONENT)) {
4791
4792 icalproperty *itemid, *voterp;
4793 int id;
4794
4795 itemid =
4796 icalcomponent_get_first_property(comp, ICAL_POLLITEMID_PROPERTY);
4797 if (!itemid) continue;
4798
4799 id = icalproperty_get_pollitemid(itemid);
4800
4801 /* Remove any existing voter property from the subcomponent */
4802 for (voterp = icalcomponent_get_first_property(comp,
4803 ICAL_VOTER_PROPERTY);
4804 voterp && strcmp(voter, icalproperty_get_voter(voterp));
4805 voterp = icalcomponent_get_next_property(comp,
4806 ICAL_VOTER_PROPERTY));
4807
4808 if (voterp) {
4809 icalcomponent_remove_property(comp, voterp);
4810 icalproperty_free(voterp);
4811 }
4812
4813
4814 /* Find matching poll-item-id in the reply */
4815 for (itemid = icalcomponent_get_first_property(reply,
4816 ICAL_POLLITEMID_PROPERTY);
4817 itemid && (id != icalproperty_get_pollitemid(itemid));
4818 itemid = icalcomponent_get_next_property(reply,
4819 ICAL_POLLITEMID_PROPERTY));
4820 if (itemid) {
4821 icalparameter *param;
4822
4823 /* Add a VOTER property with params from the reply */
4824 voterp = icalproperty_new_clone(mastervoterp);
4825
4826 for (param =
4827 icalproperty_get_first_parameter(itemid,
4828 ICAL_ANY_PARAMETER);
4829 param;
4830 param =
4831 icalproperty_get_next_parameter(itemid,
4832 ICAL_ANY_PARAMETER)) {
4833 switch (icalparameter_isa(param)) {
4834 case ICAL_PUBLICCOMMENT_PARAMETER:
4835 case ICAL_RESPONSE_PARAMETER:
4836 icalproperty_add_parameter(voterp,
4837 icalparameter_new_clone(param));
4838 break;
4839
4840 default:
4841 break;
4842 }
4843 }
4844
4845 icalcomponent_add_property(comp, voterp);
4846 }
4847 }
4848 }
4849
4850
4851 /* sched_reply() helper function
4852 *
4853 * Add voter responses to VPOLL reply and remove subcomponents
4854 *
4855 */
4856 static void sched_vpoll_reply(icalcomponent *poll, const char *voter)
4857 {
4858 icalcomponent *item, *next;
4859 icalproperty *prop;
4860
4861 for (item = icalcomponent_get_first_component(poll, ICAL_ANY_COMPONENT);
4862
4863 item;
4864 item = next) {
4865
4866 next = icalcomponent_get_next_component(poll, ICAL_ANY_COMPONENT);
4867
4868 prop = icalcomponent_get_first_property(item, ICAL_POLLITEMID_PROPERTY);
4869 if (prop) {
4870 int id = icalproperty_get_pollitemid(prop);
4871
4872 for (prop = icalcomponent_get_first_property(item,
4873 ICAL_VOTER_PROPERTY);
4874 prop;
4875 prop =
4876 icalcomponent_get_next_property(item,
4877 ICAL_VOTER_PROPERTY)) {
4878 if (!strcmp(voter, icalproperty_get_voter(prop))) {
4879 icalproperty *itemid = icalproperty_new_pollitemid(id);
4880 icalparameter *param;
4881
4882 for (param =
4883 icalproperty_get_first_parameter(prop,
4884 ICAL_ANY_PARAMETER);
4885 param;
4886 param =
4887 icalproperty_get_next_parameter(prop,
4888 ICAL_ANY_PARAMETER)) {
4889 switch (icalparameter_isa(param)) {
4890 case ICAL_PUBLICCOMMENT_PARAMETER:
4891 case ICAL_RESPONSE_PARAMETER:
4892 icalproperty_add_parameter(itemid,
4893 icalparameter_new_clone(param));
4894 break;
4895
4896 default:
4897 break;
4898 }
4899 }
4900
4901 icalcomponent_add_property(poll, itemid);
4902 }
4903 }
4904 }
4905
4906 icalcomponent_remove_component(poll, item);
4907 icalcomponent_free(item);
4908 }
4909 }
4910
4911
4912 struct pollstatus {
4913 icalcomponent *item;
4914 struct hash_table voter_table;
4915 };
4916
4917 static void free_pollstatus(void *data)
4918 {
4919 struct pollstatus *status = (struct pollstatus *) data;
4920
4921 if (status) {
4922 free_hash_table(&status->voter_table, NULL);
4923 free(status);
4924 }
4925 }
4926
4927 static int deliver_merge_pollstatus(icalcomponent *ical, icalcomponent *request)
4928 {
4929 int deliver_inbox = 0;
4930 struct hash_table comp_table;
4931 icalcomponent *poll, *sub;
4932 icalproperty *prop;
4933 const char *itemid, *voter;
4934
4935 /* Add each sub-component of old object to hash table for comparison */
4936 construct_hash_table(&comp_table, 10, 1);
4937 poll = icalcomponent_get_first_component(ical, ICAL_VPOLL_COMPONENT);
4938 for (sub = icalcomponent_get_first_component(poll, ICAL_ANY_COMPONENT);
4939 sub;
4940 sub = icalcomponent_get_next_component(poll, ICAL_ANY_COMPONENT)) {
4941 struct pollstatus *status = xmalloc(sizeof(struct pollstatus));
4942
4943 status->item = sub;
4944
4945 prop = icalcomponent_get_first_property(sub, ICAL_POLLITEMID_PROPERTY);
4946 itemid = icalproperty_get_value_as_string(prop);
4947
4948 hash_insert(itemid, status, &comp_table);
4949
4950 /* Add each VOTER to voter hash table */
4951 construct_hash_table(&status->voter_table, 10, 1);
4952 for (prop = icalcomponent_get_first_property(sub, ICAL_VOTER_PROPERTY);
4953 prop;
4954 prop =
4955 icalcomponent_get_next_property(sub, ICAL_VOTER_PROPERTY)) {
4956 voter = icalproperty_get_voter(prop);
4957
4958 hash_insert(voter, prop, &status->voter_table);
4959 }
4960 }
4961
4962 /* Process each sub-component in the iTIP request */
4963 poll = icalcomponent_get_first_component(request, ICAL_VPOLL_COMPONENT);
4964 for (sub = icalcomponent_get_first_component(poll, ICAL_ANY_COMPONENT);
4965 sub;
4966 sub = icalcomponent_get_next_component(poll, ICAL_ANY_COMPONENT)) {
4967 struct pollstatus *status;
4968
4969 prop = icalcomponent_get_first_property(sub, ICAL_POLLITEMID_PROPERTY);
4970 itemid = icalproperty_get_value_as_string(prop);
4971
4972 status = hash_del(itemid, &comp_table);
4973 if (status) {
4974 for (prop = icalcomponent_get_first_property(sub,
4975 ICAL_VOTER_PROPERTY);
4976 prop;
4977 prop = icalcomponent_get_next_property(sub,
4978 ICAL_VOTER_PROPERTY)) {
4979
4980 icalproperty *oldvoter;
4981
4982 voter = icalproperty_get_voter(prop);
4983
4984 oldvoter = hash_del(voter, &status->voter_table);
4985 if (oldvoter) {
4986 icalcomponent_remove_property(status->item, oldvoter);
4987 icalproperty_free(oldvoter);
4988 }
4989
4990 icalcomponent_add_property(status->item,
4991 icalproperty_new_clone(prop));
4992 }
4993
4994 free_pollstatus(status);
4995 }
4996 }
4997
4998 free_hash_table(&comp_table, free_pollstatus);
4999
5000 return deliver_inbox;
5001 }
5002
5003
5004 static void sched_pollstatus(const char *organizer,
5005 struct sched_param *sparam, icalcomponent *ical,
5006 const char *voter)
5007 {
5008 struct auth_state *authstate;
5009 struct sched_data sched_data;
5010 icalcomponent *itip, *comp;
5011 icalproperty *prop;
5012
5013 /* XXX Do we need to do more checks here? */
5014 if (sparam->flags & SCHEDTYPE_REMOTE)
5015 authstate = auth_newstate("anonymous");
5016 else
5017 authstate = auth_newstate(sparam->userid);
5018
5019 memset(&sched_data, 0, sizeof(struct sched_data));
5020 sched_data.force_send = ICAL_SCHEDULEFORCESEND_NONE;
5021
5022 /* Create a shell for our iTIP request objects */
5023 itip = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT,
5024 icalproperty_new_version("2.0"),
5025 icalproperty_new_prodid(ical_prodid),
5026 icalproperty_new_method(ICAL_METHOD_POLLSTATUS),
5027 0);
5028
5029 /* Copy over any CALSCALE property */
5030 prop = icalcomponent_get_first_property(ical, ICAL_CALSCALE_PROPERTY);
5031 if (prop) icalcomponent_add_property(itip, icalproperty_new_clone(prop));
5032
5033 /* Process each VPOLL in resource */
5034 for (comp = icalcomponent_get_first_component(ical, ICAL_VPOLL_COMPONENT);
5035 comp;
5036 comp =icalcomponent_get_next_component(ical, ICAL_VPOLL_COMPONENT)) {
5037
5038 icalcomponent *stat, *poll, *sub;
5039 struct strlist *voters = NULL;
5040
5041 /* Make a working copy of the iTIP */
5042 stat = icalcomponent_new_clone(itip);
5043
5044 /* Make a working copy of the VPOLL and add to pollstatus */
5045 poll = icalcomponent_new_clone(comp);
5046 icalcomponent_add_component(stat, poll);
5047
5048 /* Make list of VOTERs (stripping SCHEDULE-STATUS */
5049 for (prop = icalcomponent_get_first_property(poll, ICAL_VOTER_PROPERTY);
5050 prop;
5051 prop =
5052 icalcomponent_get_next_property(poll, ICAL_VOTER_PROPERTY)) {
5053 const char *voter = icalproperty_get_voter(prop);
5054
5055 if (strcmp(voter, organizer))
5056 appendstrlist(&voters, (char *) icalproperty_get_voter(prop));
5057
5058 icalproperty_remove_parameter_by_name(prop, "SCHEDULE-STATUS");
5059 }
5060
5061 /* Process each sub-component of VPOLL */
5062 for (sub = icalcomponent_get_first_component(poll, ICAL_ANY_COMPONENT);
5063 sub;
5064 sub = icalcomponent_get_next_component(poll, ICAL_ANY_COMPONENT)) {
5065
5066 icalproperty *next;
5067
5068 /* Strip all properties other than POLL-ITEM-ID and VOTER */
5069 for (prop =
5070 icalcomponent_get_first_property(sub, ICAL_ANY_PROPERTY);
5071 prop; prop = next) {
5072
5073 next = icalcomponent_get_next_property(sub, ICAL_ANY_PROPERTY);
5074
5075 switch (icalproperty_isa(prop)) {
5076 case ICAL_POLLITEMID_PROPERTY:
5077 case ICAL_VOTER_PROPERTY:
5078 break;
5079
5080 default:
5081 icalcomponent_remove_property(sub, prop);
5082 icalproperty_free(prop);
5083 break;
5084 }
5085 }
5086 }
5087
5088 /* Attempt to deliver to each voter in the list - removing as we go */
5089 while (voters) {
5090 struct strlist *next = voters->next;
5091
5092 /* Don't send status back to VOTER that triggered POLLSTATUS */
5093 if (strcmp(voters->s, voter)) {
5094 sched_data.itip = stat;
5095 sched_deliver(voters->s, &sched_data, authstate);
5096 }
5097
5098 free(voters->s);
5099 free(voters);
5100 voters = next;
5101 }
5102
5103 icalcomponent_free(stat);
5104 }
5105
5106 icalcomponent_free(itip);
5107 auth_freestate(authstate);
5108 }
5109 #else /* HAVE_VPOLL */
5110 static void
5111 deliver_merge_vpoll_reply(icalcomponent *ical __attribute__((unused)),
5112 icalcomponent *reply __attribute__((unused)))
5113 {
5114 return;
5115 }
5116
5117 static void sched_vpoll_reply(icalcomponent *poll __attribute__((unused)),
5118 const char *voter __attribute__((unused)))
5119 {
5120 return;
5121 }
5122
5123 static int
5124 deliver_merge_pollstatus(icalcomponent *ical __attribute__((unused)),
5125 icalcomponent *request __attribute__((unused)))
5126 {
5127 return 0;
5128 }
5129
5130 static void sched_pollstatus(const char *organizer __attribute__((unused)),
5131 struct sched_param *sparam __attribute__((unused)),
5132 icalcomponent *ical __attribute__((unused)),
5133 const char *voter __attribute__((unused)))
5134 {
5135 return;
5136 }
5137 #endif /* HAVE_VPOLL */
5138
5139
5140 static const char *deliver_merge_reply(icalcomponent *ical,
5141 icalcomponent *reply)
5142 {
5143 struct hash_table comp_table;
5144 icalcomponent *comp, *itip;
5145 icalcomponent_kind kind;
5146 icalproperty *prop, *att;
5147 icalparameter *param;
5148 icalparameter_partstat partstat = ICAL_PARTSTAT_NONE;
5149 icalparameter_rsvp rsvp = ICAL_RSVP_NONE;
5150 const char *recurid, *attendee = NULL, *req_stat = SCHEDSTAT_SUCCESS;
5151 icalproperty_kind recip_kind;
5152 const char *(*get_recipient)(const icalproperty *);
5153
5154 /* Add each component of old object to hash table for comparison */
5155 construct_hash_table(&comp_table, 10, 1);
5156 comp = icalcomponent_get_first_real_component(ical);
5157 kind = icalcomponent_isa(comp);
5158 do {
5159 prop =
5160 icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY);
5161 if (prop) recurid = icalproperty_get_value_as_string(prop);
5162 else recurid = "";
5163
5164 hash_insert(recurid, comp, &comp_table);
5165
5166 } while ((comp = icalcomponent_get_next_component(ical, kind)));
5167
5168
5169 if (kind == ICAL_VPOLL_COMPONENT) {
5170 recip_kind = ICAL_VOTER_PROPERTY;
5171 get_recipient = &icalproperty_get_voter;
5172 }
5173 else {
5174 recip_kind = ICAL_ATTENDEE_PROPERTY;
5175 get_recipient = &icalproperty_get_attendee;
5176 }
5177
5178 /* Process each component in the iTIP reply */
5179 for (itip = icalcomponent_get_first_component(reply, kind);
5180 itip;
5181 itip = icalcomponent_get_next_component(reply, kind)) {
5182
5183 /* Lookup this comp in the hash table */
5184 prop =
5185 icalcomponent_get_first_property(itip, ICAL_RECURRENCEID_PROPERTY);
5186 if (prop) recurid = icalproperty_get_value_as_string(prop);
5187 else recurid = "";
5188
5189 comp = hash_lookup(recurid, &comp_table);
5190 if (!comp) {
5191 /* New recurrence overridden by attendee.
5192 Create a new recurrence from master component. */
5193 comp = icalcomponent_new_clone(hash_lookup("", &comp_table));
5194
5195 /* Add RECURRENCE-ID */
5196 icalcomponent_add_property(comp, icalproperty_new_clone(prop));
5197
5198 /* Remove RRULE */
5199 prop = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY);
5200 if (prop) {
5201 icalcomponent_remove_property(comp, prop);
5202 icalproperty_free(prop);
5203 }
5204
5205 /* Replace DTSTART, DTEND, SEQUENCE */
5206 prop =
5207 icalcomponent_get_first_property(comp, ICAL_DTSTART_PROPERTY);
5208 if (prop) {
5209 icalcomponent_remove_property(comp, prop);
5210 icalproperty_free(prop);
5211 }
5212 prop =
5213 icalcomponent_get_first_property(itip, ICAL_DTSTART_PROPERTY);
5214 if (prop)
5215 icalcomponent_add_property(comp, icalproperty_new_clone(prop));
5216
5217 prop =
5218 icalcomponent_get_first_property(comp, ICAL_DTEND_PROPERTY);
5219 if (prop) {
5220 icalcomponent_remove_property(comp, prop);
5221 icalproperty_free(prop);
5222 }
5223 prop =
5224 icalcomponent_get_first_property(itip, ICAL_DTEND_PROPERTY);
5225 if (prop)
5226 icalcomponent_add_property(comp, icalproperty_new_clone(prop));
5227
5228 prop =
5229 icalcomponent_get_first_property(comp, ICAL_SEQUENCE_PROPERTY);
5230 if (prop) {
5231 icalcomponent_remove_property(comp, prop);
5232 icalproperty_free(prop);
5233 }
5234 prop =
5235 icalcomponent_get_first_property(itip, ICAL_SEQUENCE_PROPERTY);
5236 if (prop)
5237 icalcomponent_add_property(comp, icalproperty_new_clone(prop));
5238
5239 icalcomponent_add_component(ical, comp);
5240 }
5241
5242 /* Get the sending attendee */
5243 att = icalcomponent_get_first_property(itip, recip_kind);
5244 attendee = get_recipient(att);
5245 param = icalproperty_get_first_parameter(att, ICAL_PARTSTAT_PARAMETER);
5246 if (param) partstat = icalparameter_get_partstat(param);
5247 param = icalproperty_get_first_parameter(att, ICAL_RSVP_PARAMETER);
5248 if (param) rsvp = icalparameter_get_rsvp(param);
5249
5250 prop =
5251 icalcomponent_get_first_property(itip, ICAL_REQUESTSTATUS_PROPERTY);
5252 if (prop) {
5253 struct icalreqstattype rq = icalproperty_get_requeststatus(prop);
5254 req_stat = icalenum_reqstat_code(rq.code);
5255 }
5256
5257 /* Find matching attendee in existing object */
5258 for (prop =
5259 icalcomponent_get_first_property(comp, recip_kind);
5260 prop && strcmp(attendee, get_recipient(prop));
5261 prop =
5262 icalcomponent_get_next_property(comp, recip_kind));
5263 if (!prop) {
5264 /* Attendee added themselves to this recurrence */
5265 prop = icalproperty_new_clone(att);
5266 icalcomponent_add_property(comp, prop);
5267 }
5268
5269 /* Set PARTSTAT */
5270 if (partstat != ICAL_PARTSTAT_NONE) {
5271 param = icalparameter_new_partstat(partstat);
5272 icalproperty_set_parameter(prop, param);
5273 }
5274
5275 /* Set RSVP */
5276 icalproperty_remove_parameter_by_kind(prop, ICAL_RSVP_PARAMETER);
5277 if (rsvp != ICAL_RSVP_NONE) {
5278 param = icalparameter_new_rsvp(rsvp);
5279 icalproperty_add_parameter(prop, param);
5280 }
5281
5282 /* Set SCHEDULE-STATUS */
5283 param = icalparameter_new_schedulestatus(req_stat);
5284 icalproperty_set_parameter(prop, param);
5285
5286 /* Handle VPOLL reply */
5287 if (kind == ICAL_VPOLL_COMPONENT) deliver_merge_vpoll_reply(comp, itip);
5288 }
5289
5290 free_hash_table(&comp_table, NULL);
5291
5292 return attendee;
5293 }
5294
5295
5296 static int deliver_merge_request(const char *attendee,
5297 icalcomponent *ical, icalcomponent *request)
5298 {
5299 int deliver_inbox = 0;
5300 struct hash_table comp_table;
5301 icalcomponent *comp, *itip;
5302 icalcomponent_kind kind = ICAL_NO_COMPONENT;
5303 icalproperty *prop;
5304 icalparameter *param;
5305 const char *tzid, *recurid;
5306
5307 /* Add each VTIMEZONE of old object to hash table for comparison */
5308 construct_hash_table(&comp_table, 10, 1);
5309 for (comp =
5310 icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT);
5311 comp;
5312 comp =
5313 icalcomponent_get_next_component(ical, ICAL_VTIMEZONE_COMPONENT)) {
5314 prop = icalcomponent_get_first_property(comp, ICAL_TZID_PROPERTY);
5315 tzid = icalproperty_get_tzid(prop);
5316
5317 hash_insert(tzid, comp, &comp_table);
5318 }
5319
5320 /* Process each VTIMEZONE in the iTIP request */
5321 for (itip = icalcomponent_get_first_component(request,
5322 ICAL_VTIMEZONE_COMPONENT);
5323 itip;
5324 itip = icalcomponent_get_next_component(request,
5325 ICAL_VTIMEZONE_COMPONENT)) {
5326 /* Lookup this TZID in the hash table */
5327 prop = icalcomponent_get_first_property(itip, ICAL_TZID_PROPERTY);
5328 tzid = icalproperty_get_tzid(prop);
5329
5330 comp = hash_lookup(tzid, &comp_table);
5331 if (comp) {
5332 /* Remove component from old object */
5333 icalcomponent_remove_component(ical, comp);
5334 icalcomponent_free(comp);
5335 }
5336
5337 /* Add new/modified component from iTIP request */
5338 icalcomponent_add_component(ical, icalcomponent_new_clone(itip));
5339 }
5340
5341 free_hash_table(&comp_table, NULL);
5342
5343 /* Add each component of old object to hash table for comparison */
5344 construct_hash_table(&comp_table, 10, 1);
5345 comp = icalcomponent_get_first_real_component(ical);
5346 if (comp) kind = icalcomponent_isa(comp);
5347 for (; comp; comp = icalcomponent_get_next_component(ical, kind)) {
5348 prop =
5349 icalcomponent_get_first_property(comp, ICAL_RECURRENCEID_PROPERTY);
5350 if (prop) recurid = icalproperty_get_value_as_string(prop);
5351 else recurid = "";
5352
5353 hash_insert(recurid, comp, &comp_table);
5354 }
5355
5356 /* Process each component in the iTIP request */
5357 itip = icalcomponent_get_first_real_component(request);
5358 if (kind == ICAL_NO_COMPONENT) kind = icalcomponent_isa(itip);
5359 for (; itip; itip = icalcomponent_get_next_component(request, kind)) {
5360 icalcomponent *new_comp = icalcomponent_new_clone(itip);
5361
5362 /* Lookup this comp in the hash table */
5363 prop =
5364 icalcomponent_get_first_property(itip, ICAL_RECURRENCEID_PROPERTY);
5365 if (prop) recurid = icalproperty_get_value_as_string(prop);
5366 else recurid = "";
5367
5368 comp = hash_lookup(recurid, &comp_table);
5369 if (comp) {
5370 int old_seq, new_seq;
5371
5372 /* Check if this is something more than an update */
5373 /* XXX Probably need to check PARTSTAT=NEEDS-ACTION
5374 and RSVP=TRUE as well */
5375 old_seq = icalcomponent_get_sequence(comp);
5376 new_seq = icalcomponent_get_sequence(itip);
5377 if (new_seq > old_seq) deliver_inbox = 1;
5378
5379 /* Copy over any COMPLETED, PERCENT-COMPLETE,
5380 or TRANSP properties */
5381 prop =
5382 icalcomponent_get_first_property(comp, ICAL_COMPLETED_PROPERTY);
5383 if (prop) {
5384 icalcomponent_add_property(new_comp,
5385 icalproperty_new_clone(prop));
5386 }
5387 prop =
5388 icalcomponent_get_first_property(comp,
5389 ICAL_PERCENTCOMPLETE_PROPERTY);
5390 if (prop) {
5391 icalcomponent_add_property(new_comp,
5392 icalproperty_new_clone(prop));
5393 }
5394 prop =
5395 icalcomponent_get_first_property(comp, ICAL_TRANSP_PROPERTY);
5396 if (prop) {
5397 icalcomponent_add_property(new_comp,
5398 icalproperty_new_clone(prop));
5399 }
5400
5401 /* Copy over any ORGANIZER;SCHEDULE-STATUS */
5402 /* XXX Do we only do this iff PARTSTAT!=NEEDS-ACTION */
5403 prop =
5404 icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
5405 param = icalproperty_get_schedulestatus_parameter(prop);
5406 if (param) {
5407 param = icalparameter_new_clone(param);
5408 prop =
5409 icalcomponent_get_first_property(new_comp,
5410 ICAL_ORGANIZER_PROPERTY);
5411 icalproperty_add_parameter(prop, param);
5412 }
5413
5414 /* Remove component from old object */
5415 icalcomponent_remove_component(ical, comp);
5416 icalcomponent_free(comp);
5417 }
5418 else {
5419 /* New component */
5420 deliver_inbox = 1;
5421 }
5422
5423 if (config_allowsched == IMAP_ENUM_CALDAV_ALLOWSCHEDULING_APPLE &&
5424 kind == ICAL_VEVENT_COMPONENT) {
5425 /* Make VEVENT component transparent if recipient ATTENDEE
5426 PARTSTAT=NEEDS-ACTION (for compatibility with CalendarServer) */
5427 for (prop =
5428 icalcomponent_get_first_property(new_comp,
5429 ICAL_ATTENDEE_PROPERTY);
5430 prop && strcmp(icalproperty_get_attendee(prop), attendee);
5431 prop =
5432 icalcomponent_get_next_property(new_comp,
5433 ICAL_ATTENDEE_PROPERTY));
5434 param =
5435 icalproperty_get_first_parameter(prop, ICAL_PARTSTAT_PARAMETER);
5436 if (param &&
5437 icalparameter_get_partstat(param) ==
5438 ICAL_PARTSTAT_NEEDSACTION) {
5439 prop =
5440 icalcomponent_get_first_property(new_comp,
5441 ICAL_TRANSP_PROPERTY);
5442 if (prop)
5443 icalproperty_set_transp(prop, ICAL_TRANSP_TRANSPARENT);
5444 else {
5445 prop = icalproperty_new_transp(ICAL_TRANSP_TRANSPARENT);
5446 icalcomponent_add_property(new_comp, prop);
5447 }
5448 }
5449 }
5450
5451 /* Add new/modified component from iTIP request */
5452 icalcomponent_add_component(ical, new_comp);
5453 }
5454
5455 free_hash_table(&comp_table, NULL);
5456
5457 return deliver_inbox;
5458 }
5459
5460
38345461 /* Deliver scheduling object to local recipient */
38355462 static void sched_deliver_local(const char *recipient,
38365463 struct sched_param *sparam,
38375464 struct sched_data *sched_data,
38385465 struct auth_state *authstate)
38395466 {
3840 int r = 0, rights, reqd_privs, deliver_inbox = 0;
5467 int r = 0, rights, reqd_privs, deliver_inbox = 1;
38415468 const char *userid = sparam->userid, *mboxname = NULL, *attendee = NULL;
38425469 static struct buf resource = BUF_INITIALIZER;
38435470 static unsigned sched_count = 0;
38475474 struct caldav_db *caldavdb = NULL;
38485475 struct caldav_data *cdata;
38495476 icalcomponent *ical = NULL;
5477 icalproperty_method method;
5478 icalcomponent_kind kind;
5479 icalcomponent *comp;
38505480 icalproperty *prop;
38515481 struct transaction_t txn;
38525482
38615491 }
38625492
38635493 reqd_privs = sched_data->is_reply ? DACL_REPLY : DACL_INVITE;
3864 rights =
3865 mbentry.acl ? cyrus_acl_myrights(authstate, mbentry.acl) : 0;
5494 rights = mbentry.acl ? cyrus_acl_myrights(authstate, mbentry.acl) : 0;
38665495 if (!(rights & reqd_privs)) {
38675496 sched_data->status =
38685497 sched_data->ischedule ? REQSTAT_NOPRIVS : SCHEDSTAT_NOPRIVS;
5498 syslog(LOG_DEBUG, "No scheduling receive ACL for user %s on Inbox %s",
5499 httpd_userid, userid);
38695500 goto done;
38705501 }
38715502
38785509 goto done;
38795510 }
38805511
5512 /* Get METHOD of the iTIP message */
5513 method = icalcomponent_get_method(sched_data->itip);
5514
38815515 /* Search for iCal UID in recipient's calendars */
3882 caldavdb = caldav_open(userid, CALDAV_CREATE);
5516 caldavdb = caldav_open(inbox, CALDAV_CREATE);
38835517 if (!caldavdb) {
38845518 sched_data->status =
38855519 sched_data->ischedule ? REQSTAT_TEMPFAIL : SCHEDSTAT_TEMPFAIL;
38975531 /* Can't find object belonging to organizer - ignore reply */
38985532 sched_data->status =
38995533 sched_data->ischedule ? REQSTAT_PERMFAIL : SCHEDSTAT_PERMFAIL;
5534 goto done;
5535 }
5536 else if (method == ICAL_METHOD_CANCEL || method == ICAL_METHOD_POLLSTATUS) {
5537 /* Can't find object belonging to attendee - we're done */
5538 sched_data->status =
5539 sched_data->ischedule ? REQSTAT_SUCCESS : SCHEDSTAT_DELIVERED;
39005540 goto done;
39015541 }
39025542 else {
39065546 buf_reset(&resource);
39075547 buf_printf(&resource, "%s.ics",
39085548 icalcomponent_get_uid(sched_data->itip));
5549
5550 /* Create new attendee object */
5551 ical = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT, 0);
5552
5553 /* Copy over VERSION property */
5554 prop = icalcomponent_get_first_property(sched_data->itip,
5555 ICAL_VERSION_PROPERTY);
5556 icalcomponent_add_property(ical, icalproperty_new_clone(prop));
5557
5558 /* Copy over PRODID property */
5559 prop = icalcomponent_get_first_property(sched_data->itip,
5560 ICAL_PRODID_PROPERTY);
5561 icalcomponent_add_property(ical, icalproperty_new_clone(prop));
5562
5563 /* Copy over any CALSCALE property */
5564 prop = icalcomponent_get_first_property(sched_data->itip,
5565 ICAL_CALSCALE_PROPERTY);
5566 if (prop) {
5567 icalcomponent_add_property(ical,
5568 icalproperty_new_clone(prop));
5569 }
39095570 }
39105571
39115572 /* Open recipient's calendar for reading */
39175578 goto done;
39185579 }
39195580
3920 if (!cdata->dav.imap_uid) {
3921 /* Create new object (copy of request w/o METHOD) */
3922 ical = icalcomponent_new_clone(sched_data->itip);
3923
3924 prop = icalcomponent_get_first_property(ical, ICAL_METHOD_PROPERTY);
3925 icalcomponent_remove_property(ical, prop);
3926 icalproperty_free(prop);
3927
3928 deliver_inbox = 1;
3929 }
3930 else {
3931 /* Update existing object */
5581 if (cdata->dav.imap_uid) {
39325582 struct index_record record;
39335583 const char *msg_base = NULL;
39345584 unsigned long msg_size = 0;
3935 icalcomponent *comp;
3936 icalcomponent_kind kind;
3937 icalproperty_method method;
5585 int reject = 0;
39385586
39395587 /* Load message containing the resource and parse iCal data */
39405588 mailbox_find_index_record(mailbox, cdata->dav.imap_uid, &record);
39425590 ical = icalparser_parse_string(msg_base + record.header_size);
39435591 mailbox_unmap_message(mailbox, record.uid, &msg_base, &msg_size);
39445592
5593 for (comp = icalcomponent_get_first_component(sched_data->itip,
5594 ICAL_ANY_COMPONENT);
5595 comp;
5596 comp = icalcomponent_get_next_component(sched_data->itip,
5597 ICAL_ANY_COMPONENT)) {
5598 /* Don't allow component type to be changed */
5599 kind = icalcomponent_isa(comp);
5600 switch (kind) {
5601 case ICAL_VEVENT_COMPONENT:
5602 if (cdata->comp_type != CAL_COMP_VEVENT) reject = 1;
5603 break;
5604 case ICAL_VTODO_COMPONENT:
5605 if (cdata->comp_type != CAL_COMP_VTODO) reject = 1;
5606 break;
5607 case ICAL_VJOURNAL_COMPONENT:
5608 if (cdata->comp_type != CAL_COMP_VJOURNAL) reject = 1;
5609 break;
5610 case ICAL_VFREEBUSY_COMPONENT:
5611 if (cdata->comp_type != CAL_COMP_VFREEBUSY) reject = 1;
5612 break;
5613 case ICAL_VAVAILABILITY_COMPONENT:
5614 if (cdata->comp_type != CAL_COMP_VAVAILABILITY) reject = 1;
5615 break;
5616 #ifdef HAVE_VPOLL
5617 case ICAL_VPOLL_COMPONENT:
5618 if (cdata->comp_type != CAL_COMP_VPOLL) reject = 1;
5619 break;
5620 #endif
5621 default:
5622 break;
5623 }
5624
5625 /* Don't allow ORGANIZER to be changed */
5626 if (!reject && cdata->organizer) {
5627 prop =
5628 icalcomponent_get_first_property(comp,
5629 ICAL_ORGANIZER_PROPERTY);
5630 if (prop) {
5631 const char *organizer =
5632 organizer = icalproperty_get_organizer(prop);
5633
5634 if (!strncasecmp(organizer, "mailto:", 7)) organizer += 7;
5635 if (strcmp(cdata->organizer, organizer)) reject = 1;
5636 }
5637 }
5638
5639 if (reject) {
5640 sched_data->status = sched_data->ischedule ?
5641 REQSTAT_REJECTED : SCHEDSTAT_REJECTED;
5642 goto done;
5643 }
5644 }
5645 }
5646
5647 switch (method) {
5648 case ICAL_METHOD_CANCEL:
39455649 /* Get component type */
39465650 comp = icalcomponent_get_first_real_component(ical);
39475651 kind = icalcomponent_isa(comp);
39485652
3949 /* Get METHOD of the iTIP message */
3950 method = icalcomponent_get_method(sched_data->itip);
3951
3952 switch (method) {
3953 case ICAL_METHOD_CANCEL:
3954 /* Set STATUS:CANCELLED on all components */
3955 do {
3956 icalcomponent_set_status(comp, ICAL_STATUS_CANCELLED);
3957 icalcomponent_set_sequence(comp,
3958 icalcomponent_get_sequence(comp)+1);
3959 } while ((comp = icalcomponent_get_next_component(ical, kind)));
3960
3961 deliver_inbox = 1;
3962
3963 break;
3964
3965 case ICAL_METHOD_REPLY: {
3966 struct hash_table comp_table;
3967 icalcomponent *itip;
3968 icalproperty *att;
3969 icalparameter *param;
3970 icalparameter_partstat partstat;
3971 icalparameter_rsvp rsvp = ICAL_RSVP_NONE;
3972 const char *recurid, *req_stat = SCHEDSTAT_SUCCESS;
3973
3974 /* Add each component of old object to hash table for comparison */
3975 construct_hash_table(&comp_table, 10, 1);
3976 comp = icalcomponent_get_first_real_component(ical);
3977 do {
3978 prop =
3979 icalcomponent_get_first_property(comp,
3980 ICAL_RECURRENCEID_PROPERTY);
3981 if (prop) recurid = icalproperty_get_value_as_string(prop);
3982 else recurid = "";
3983
3984 hash_insert(recurid, comp, &comp_table);
3985
3986 } while ((comp = icalcomponent_get_next_component(ical, kind)));
3987
3988 /* Process each component in the iTIP reply */
3989 itip = icalcomponent_get_first_component(sched_data->itip, kind);
3990 do {
3991 /* Lookup this comp in the hash table */
3992 prop =
3993 icalcomponent_get_first_property(itip,
3994 ICAL_RECURRENCEID_PROPERTY);
3995 if (prop) recurid = icalproperty_get_value_as_string(prop);
3996 else recurid = "";
3997
3998 comp = hash_lookup(recurid, &comp_table);
3999 if (!comp) {
4000 /* New recurrence overridden by attendee.
4001 Create a new recurrence from master component. */
4002 comp =
4003 icalcomponent_new_clone(hash_lookup("", &comp_table));
4004
4005 /* Add RECURRENCE-ID */
4006 icalcomponent_add_property(comp,
4007 icalproperty_new_clone(prop));
4008
4009 /* Remove RRULE */
4010 prop =
4011 icalcomponent_get_first_property(comp,
4012 ICAL_RRULE_PROPERTY);
4013 if (prop) {
4014 icalcomponent_remove_property(comp, prop);
4015 icalproperty_free(prop);
4016 }
4017
4018 /* Replace DTSTART, DTEND, SEQUENCE */
4019 prop =
4020 icalcomponent_get_first_property(comp,
4021 ICAL_DTSTART_PROPERTY);
4022 if (prop) {
4023 icalcomponent_remove_property(comp, prop);
4024 icalproperty_free(prop);
4025 }
4026 prop =
4027 icalcomponent_get_first_property(itip,
4028 ICAL_DTSTART_PROPERTY);
4029 if (prop)
4030 icalcomponent_add_property(comp,
4031 icalproperty_new_clone(prop));
4032
4033 prop =
4034 icalcomponent_get_first_property(comp,
4035 ICAL_DTEND_PROPERTY);
4036 if (prop) {
4037 icalcomponent_remove_property(comp, prop);
4038 icalproperty_free(prop);
4039 }
4040 prop =
4041 icalcomponent_get_first_property(itip,
4042 ICAL_DTEND_PROPERTY);
4043 if (prop)
4044 icalcomponent_add_property(comp,
4045 icalproperty_new_clone(prop));
4046
4047 prop =
4048 icalcomponent_get_first_property(comp,
4049 ICAL_SEQUENCE_PROPERTY);
4050 if (prop) {
4051 icalcomponent_remove_property(comp, prop);
4052 icalproperty_free(prop);
4053 }
4054 prop =
4055 icalcomponent_get_first_property(itip,
4056 ICAL_SEQUENCE_PROPERTY);
4057 if (prop)
4058 icalcomponent_add_property(comp,
4059 icalproperty_new_clone(prop));
4060
4061 icalcomponent_add_component(ical, comp);
4062 }
4063
4064 /* Get the sending attendee */
4065 att = icalcomponent_get_first_property(itip,
4066 ICAL_ATTENDEE_PROPERTY);
4067 attendee = icalproperty_get_attendee(att);
4068 param =
4069 icalproperty_get_first_parameter(att,
4070 ICAL_PARTSTAT_PARAMETER);
4071 partstat = icalparameter_get_partstat(param);
4072 param =
4073 icalproperty_get_first_parameter(att,
4074 ICAL_RSVP_PARAMETER);
4075 if (param) rsvp = icalparameter_get_rsvp(param);
4076
4077 prop =
4078 icalcomponent_get_first_property(itip,
4079 ICAL_REQUESTSTATUS_PROPERTY);
4080 if (prop) {
4081 struct icalreqstattype rq =
4082 icalproperty_get_requeststatus(prop);
4083 req_stat =
4084 icalenum_reqstat_code(rq.code);
4085 }
4086
4087 /* Find matching attendee in existing object */
4088 for (prop =
4089 icalcomponent_get_first_property(comp,
4090 ICAL_ATTENDEE_PROPERTY);
4091 prop && strcmp(attendee, icalproperty_get_attendee(prop));
4092 prop =
4093 icalcomponent_get_next_property(comp,
4094 ICAL_ATTENDEE_PROPERTY));
4095 if (!prop) {
4096 /* Attendee added themselves to this recurrence */
4097 prop = icalproperty_new_clone(att);
4098 icalcomponent_add_property(comp, prop);
4099 }
4100
4101 /* Find and set PARTSTAT */
4102 param =
4103 icalproperty_get_first_parameter(prop,
4104 ICAL_PARTSTAT_PARAMETER);
4105 if (!param) {
4106 param = icalparameter_new(ICAL_PARTSTAT_PARAMETER);
4107 icalproperty_add_parameter(prop, param);
4108 }
4109 icalparameter_set_partstat(param, partstat);
4110
4111 /* Find and set RSVP */
4112 param =
4113 icalproperty_get_first_parameter(prop,
4114 ICAL_RSVP_PARAMETER);
4115 if (param) icalproperty_remove_parameter_by_ref(prop, param);
4116 if (rsvp != ICAL_RSVP_NONE) {
4117 param = icalparameter_new(ICAL_RSVP_PARAMETER);
4118 icalproperty_add_parameter(prop, param);
4119 icalparameter_set_rsvp(param, rsvp);
4120 }
4121
4122 /* Find and set SCHEDULE-STATUS */
4123 for (param =
4124 icalproperty_get_first_parameter(prop,
4125 ICAL_IANA_PARAMETER);
4126 param && strcmp(icalparameter_get_iana_name(param),
4127 "SCHEDULE-STATUS");
4128 param =
4129 icalproperty_get_next_parameter(prop,
4130 ICAL_IANA_PARAMETER));
4131 if (!param) {
4132 param = icalparameter_new(ICAL_IANA_PARAMETER);
4133 icalproperty_add_parameter(prop, param);
4134 icalparameter_set_iana_name(param, "SCHEDULE-STATUS");
4135 }
4136 icalparameter_set_iana_value(param, req_stat);
4137
4138 } while ((itip = icalcomponent_get_next_component(sched_data->itip,
4139 kind)));
4140
4141 free_hash_table(&comp_table, NULL);
4142
4143 deliver_inbox = 1;
4144
4145 break;
4146 }
4147
4148 case ICAL_METHOD_REQUEST: {
4149 struct hash_table comp_table;
4150 icalcomponent *itip;
4151 const char *tzid, *recurid;
4152
4153 /* Add each VTIMEZONE of old object to hash table for comparison */
4154 construct_hash_table(&comp_table, 10, 1);
4155 for (comp = icalcomponent_get_first_component(ical,
4156 ICAL_VTIMEZONE_COMPONENT);
4157 comp;
4158 comp =
4159 icalcomponent_get_next_component(ical,
4160 ICAL_VTIMEZONE_COMPONENT)) {
4161 prop =
4162 icalcomponent_get_first_property(comp, ICAL_TZID_PROPERTY);
4163 tzid = icalproperty_get_tzid(prop);
4164
4165 hash_insert(tzid, comp, &comp_table);
4166 }
4167
4168 /* Process each VTIMEZONE in the iTIP request */
4169 for (itip =
4170 icalcomponent_get_first_component(sched_data->itip,
4171 ICAL_VTIMEZONE_COMPONENT);
4172 itip;
4173 itip =
4174 icalcomponent_get_next_component(sched_data->itip,
4175 ICAL_VTIMEZONE_COMPONENT)) {
4176 /* Lookup this TZID in the hash table */
4177 prop =
4178 icalcomponent_get_first_property(itip,
4179 ICAL_TZID_PROPERTY);
4180 tzid = icalproperty_get_tzid(prop);
4181
4182 comp = hash_lookup(tzid, &comp_table);
4183 if (comp) {
4184 /* Remove component from old object */
4185 icalcomponent_remove_component(ical, comp);
4186 icalcomponent_free(comp);
4187 }
4188
4189 /* Add new/modified component from iTIP request*/
4190 icalcomponent_add_component(ical,
4191 icalcomponent_new_clone(itip));
4192 }
4193
4194 free_hash_table(&comp_table, NULL);
4195
4196 /* Add each component of old object to hash table for comparison */
4197 construct_hash_table(&comp_table, 10, 1);
4198 comp = icalcomponent_get_first_real_component(ical);
4199 do {
4200 prop =
4201 icalcomponent_get_first_property(comp,
4202 ICAL_RECURRENCEID_PROPERTY);
4203 if (prop) recurid = icalproperty_get_value_as_string(prop);
4204 else recurid = "";
4205
4206 hash_insert(recurid, comp, &comp_table);
4207
4208 } while ((comp = icalcomponent_get_next_component(ical, kind)));
4209
4210 /* Process each component in the iTIP request */
4211 itip = icalcomponent_get_first_component(sched_data->itip, kind);
4212 do {
4213 icalcomponent *new_comp = icalcomponent_new_clone(itip);
4214
4215 /* Lookup this comp in the hash table */
4216 prop =
4217 icalcomponent_get_first_property(itip,
4218 ICAL_RECURRENCEID_PROPERTY);
4219 if (prop) recurid = icalproperty_get_value_as_string(prop);
4220 else recurid = "";
4221
4222 comp = hash_lookup(recurid, &comp_table);
4223 if (comp) {
4224 int old_seq, new_seq;
4225 icalparameter *param;
4226
4227 /* Check if this is something more than an update */
4228 /* XXX Probably need to check PARTSTAT=NEEDS-ACTION
4229 and RSVP=TRUE as well */
4230 old_seq = icalcomponent_get_sequence(comp);
4231 new_seq = icalcomponent_get_sequence(itip);
4232 if (new_seq > old_seq) deliver_inbox = 1;
4233
4234 /* Copy over any COMPLETED, PERCENT-COMPLETE,
4235 or TRANSP properties */
4236 prop =
4237 icalcomponent_get_first_property(comp,
4238 ICAL_COMPLETED_PROPERTY);
4239 if (prop) {
4240 icalcomponent_add_property(new_comp,
4241 icalproperty_new_clone(prop));
4242 }
4243 prop =
4244 icalcomponent_get_first_property(comp,
4245 ICAL_PERCENTCOMPLETE_PROPERTY);
4246 if (prop) {
4247 icalcomponent_add_property(new_comp,
4248 icalproperty_new_clone(prop));
4249 }
4250 prop =
4251 icalcomponent_get_first_property(comp,
4252 ICAL_TRANSP_PROPERTY);
4253 if (prop) {
4254 icalcomponent_add_property(new_comp,
4255 icalproperty_new_clone(prop));
4256 }
4257
4258 /* Copy over any ORGANIZER;SCHEDULE-STATUS */
4259 /* XXX Do we only do this iff PARTSTAT!=NEEDS-ACTION */
4260 prop =
4261 icalcomponent_get_first_property(comp,
4262 ICAL_ORGANIZER_PROPERTY);
4263 for (param =
4264 icalproperty_get_first_parameter(prop,
4265 ICAL_IANA_PARAMETER);
4266 param;
4267 param =
4268 icalproperty_get_next_parameter(prop,
4269 ICAL_IANA_PARAMETER)) {
4270 if (!strcmp(icalparameter_get_iana_name(param),
4271 "SCHEDULE-STATUS")) {
4272 const char *sched_stat =
4273 icalparameter_get_iana_value(param);
4274
4275 prop =
4276 icalcomponent_get_first_property(new_comp,
4277 ICAL_ORGANIZER_PROPERTY);
4278 param = icalparameter_new(ICAL_IANA_PARAMETER);
4279 icalproperty_add_parameter(prop, param);
4280 icalparameter_set_iana_name(param,
4281 "SCHEDULE-STATUS");
4282 icalparameter_set_iana_value(param, sched_stat);
4283 }
4284 }
4285
4286 /* Remove component from old object */
4287 icalcomponent_remove_component(ical, comp);
4288 icalcomponent_free(comp);
4289 }
4290 else deliver_inbox = 1;
4291
4292 /* Add new/modified component from iTIP request*/
4293 icalcomponent_add_component(ical, new_comp);
4294
4295 } while ((itip = icalcomponent_get_next_component(sched_data->itip,
4296 kind)));
4297
4298 free_hash_table(&comp_table, NULL);
4299
4300 break;
4301 }
4302
4303 default:
4304 /* Unknown METHOD -- ignore it */
4305 syslog(LOG_ERR, "Unknown iTIP method: %s",
4306 icalenum_method_to_string(method));
4307 goto inbox;
4308 }
5653 /* Set STATUS:CANCELLED on all components */
5654 do {
5655 icalcomponent_set_status(comp, ICAL_STATUS_CANCELLED);
5656 icalcomponent_set_sequence(comp,
5657 icalcomponent_get_sequence(comp)+1);
5658 } while ((comp = icalcomponent_get_next_component(ical, kind)));
5659
5660 break;
5661
5662 case ICAL_METHOD_REPLY:
5663 attendee = deliver_merge_reply(ical, sched_data->itip);
5664
5665 break;
5666
5667 case ICAL_METHOD_REQUEST:
5668 deliver_inbox = deliver_merge_request(recipient,
5669 ical, sched_data->itip);
5670 break;
5671
5672 case ICAL_METHOD_POLLSTATUS:
5673 deliver_inbox = deliver_merge_pollstatus(ical, sched_data->itip);
5674 break;
5675
5676 default:
5677 /* Unknown METHOD -- ignore it */
5678 syslog(LOG_ERR, "Unknown iTIP method: %s",
5679 icalenum_method_to_string(method));
5680
5681 sched_data->is_reply = 0;
5682 goto inbox;
43095683 }
43105684
43115685 /* Store the (updated) object in the recipients's calendar */
43445718
43455719 /* XXX Should this be a config option? - it might have perf implications */
43465720 if (sched_data->is_reply) {
4347 /* Send updates to attendees */
4348 sched_request(recipient, sparam, NULL, ical, attendee);
5721 /* Send updates to attendees - skipping sender of reply */
5722 comp = icalcomponent_get_first_real_component(ical);
5723 if (icalcomponent_isa(comp) == ICAL_VPOLL_COMPONENT)
5724 sched_pollstatus(recipient, sparam, ical, attendee);
5725 else
5726 sched_request(recipient, sparam, NULL, ical, attendee);
43495727 }
43505728
43515729 done:
43525730 if (ical) icalcomponent_free(ical);
4353 if (inbox) {
4354 mailbox_unlock_index(inbox, NULL);
4355 mailbox_close(&inbox);
4356 }
4357 if (mailbox) mailbox_close(&mailbox);
5731 mailbox_close(&inbox);
5732 mailbox_close(&mailbox);
43585733 if (caldavdb) caldav_close(caldavdb);
43595734 }
43605735
43655740 struct sched_data *sched_data = (struct sched_data *) data;
43665741 struct auth_state *authstate = (struct auth_state *) rock;
43675742 struct sched_param sparam;
5743 int islegal;
43685744
43695745 /* Check SCHEDULE-FORCE-SEND value */
4370 if (sched_data->force_send) {
4371 const char *force = sched_data->is_reply ? "REPLY" : "REQUEST";
4372
4373 if (strcmp(sched_data->force_send, force)) {
4374 sched_data->status = SCHEDSTAT_PARAM;
4375 return;
4376 }
5746 switch (sched_data->force_send) {
5747 case ICAL_SCHEDULEFORCESEND_NONE:
5748 islegal = 1;
5749 break;
5750
5751 case ICAL_SCHEDULEFORCESEND_REPLY:
5752 islegal = sched_data->is_reply;
5753 break;
5754
5755 case ICAL_SCHEDULEFORCESEND_REQUEST:
5756 islegal = !sched_data->is_reply;
5757 break;
5758
5759 default:
5760 islegal = 0;
5761 break;
5762 }
5763
5764 if (!islegal) {
5765 sched_data->status = SCHEDSTAT_PARAM;
5766 return;
43775767 }
43785768
43795769 if (caladdress_lookup(recipient, &sparam)) {
44415831 }
44425832
44435833 if (clean_org) {
4444 icalparameter *param, *next;
4445
44465834 /* Grab the organizer */
4447 prop = icalcomponent_get_first_property(comp,
4448 ICAL_ORGANIZER_PROPERTY);
5835 prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
44495836
44505837 /* Remove CalDAV Scheduling parameters from organizer */
4451 for (param =
4452 icalproperty_get_first_parameter(prop, ICAL_IANA_PARAMETER);
4453 param; param = next) {
4454 next = icalproperty_get_next_parameter(prop, ICAL_IANA_PARAMETER);
4455
4456 if (!strcmp(icalparameter_get_iana_name(param),
4457 "SCHEDULE-AGENT")) {
4458 icalproperty_remove_parameter_by_ref(prop, param);
4459 }
4460 else if (!strcmp(icalparameter_get_iana_name(param),
4461 "SCHEDULE-FORCE-SEND")) {
4462 icalproperty_remove_parameter_by_ref(prop, param);
4463 }
4464 }
5838 icalproperty_remove_parameter_by_name(prop, "SCHEDULE-AGENT");
5839 icalproperty_remove_parameter_by_name(prop, "SCHEDULE-FORCE-SEND");
44655840 }
44665841 }
44675842
45245899 {
45255900 icalcomponent *copy;
45265901 icalproperty *prop;
4527 icalparameter *param, *next;
5902 icalparameter *param;
5903 icalcomponent_kind kind = icalcomponent_isa(comp);
5904 icalproperty_kind recip_kind;
5905 const char *(*get_recipient)(const icalproperty *);
5906
5907 if (kind == ICAL_VPOLL_COMPONENT) {
5908 recip_kind = ICAL_VOTER_PROPERTY;
5909 get_recipient = &icalproperty_get_voter;
5910 }
5911 else {
5912 recip_kind = ICAL_ATTENDEE_PROPERTY;
5913 get_recipient = &icalproperty_get_attendee;
5914 }
45285915
45295916 /* Strip SCHEDULE-STATUS from each attendee
45305917 and optionally set PROPSTAT=NEEDS-ACTION */
4531 for (prop = icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY);
5918 for (prop = icalcomponent_get_first_property(comp, recip_kind);
45325919 prop;
4533 prop = icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) {
4534 const char *attendee = icalproperty_get_attendee(prop);
5920 prop = icalcomponent_get_next_property(comp, recip_kind)) {
5921
5922 const char *attendee = get_recipient(prop);
45355923
45365924 /* Don't modify attendee == organizer */
45375925 if (!strcmp(attendee, organizer)) continue;
45385926
4539 for (param =
4540 icalproperty_get_first_parameter(prop, ICAL_IANA_PARAMETER);
4541 param; param = next) {
4542 next = icalproperty_get_next_parameter(prop, ICAL_IANA_PARAMETER);
4543
4544 if (!strcmp(icalparameter_get_iana_name(param),
4545 "SCHEDULE-STATUS")) {
4546 icalproperty_remove_parameter_by_ref(prop, param);
4547 }
4548 }
5927 icalproperty_remove_parameter_by_name(prop, "SCHEDULE-STATUS");
45495928
45505929 if (needs_action) {
4551 param =
4552 icalproperty_get_first_parameter(prop, ICAL_PARTSTAT_PARAMETER);
4553 if (!param) {
4554 param = icalparameter_new(ICAL_PARTSTAT_PARAMETER);
4555 icalproperty_add_parameter(prop, param);
4556 }
4557 icalparameter_set_partstat(param, ICAL_PARTSTAT_NEEDSACTION);
5930 /* Set PARTSTAT */
5931 param = icalparameter_new_partstat(ICAL_PARTSTAT_NEEDSACTION);
5932 icalproperty_set_parameter(prop, param);
45585933 }
45595934 }
45605935
45645939 clean_component(copy, 0);
45655940
45665941 /* Process each attendee */
4567 for (prop = icalcomponent_get_first_property(copy, ICAL_ATTENDEE_PROPERTY);
5942 for (prop = icalcomponent_get_first_property(copy, recip_kind);
45685943 prop;
4569 prop = icalcomponent_get_next_property(copy, ICAL_ATTENDEE_PROPERTY)) {
4570 const char *attendee = icalproperty_get_attendee(prop);
5944 prop = icalcomponent_get_next_property(copy, recip_kind)) {
45715945 unsigned do_sched = 1;
4572 icalparameter *force_send = NULL;
5946 icalparameter_scheduleforcesend force_send =
5947 ICAL_SCHEDULEFORCESEND_NONE;
5948 const char *attendee = get_recipient(prop);
45735949
45745950 /* Don't schedule attendee == organizer */
45755951 if (!strcmp(attendee, organizer)) continue;
45785954 if (att_update && !strcmp(attendee, att_update)) continue;
45795955
45805956 /* Check CalDAV Scheduling parameters */
4581 for (param =
4582 icalproperty_get_first_parameter(prop, ICAL_IANA_PARAMETER);
4583 param; param = next) {
4584 next = icalproperty_get_next_parameter(prop, ICAL_IANA_PARAMETER);
4585
4586 if (!strcmp(icalparameter_get_iana_name(param),
4587 "SCHEDULE-AGENT")) {
4588 do_sched =
4589 !strcmp(icalparameter_get_iana_value(param), "SERVER");
4590 icalproperty_remove_parameter_by_ref(prop, param);
4591 }
4592 else if (!strcmp(icalparameter_get_iana_name(param),
4593 "SCHEDULE-FORCE-SEND")) {
4594 force_send = param;
4595 }
5957 param = icalproperty_get_scheduleagent_parameter(prop);
5958 if (param) {
5959 icalparameter_scheduleagent agent =
5960 icalparameter_get_scheduleagent(param);
5961
5962 if (agent != ICAL_SCHEDULEAGENT_SERVER) do_sched = 0;
5963 icalproperty_remove_parameter_by_ref(prop, param);
5964 }
5965
5966 param = icalproperty_get_scheduleforcesend_parameter(prop);
5967 if (param) {
5968 force_send = icalparameter_get_scheduleforcesend(param);
5969 icalproperty_remove_parameter_by_ref(prop, param);
45965970 }
45975971
45985972 /* Create/update iTIP request for this attendee */
46055979 /* New attendee - add it to the hash table */
46065980 sched_data = xzmalloc(sizeof(struct sched_data));
46075981 sched_data->itip = icalcomponent_new_clone(itip);
4608 if (force_send) {
4609 sched_data->force_send =
4610 xstrdup(icalparameter_get_iana_value(force_send));
4611 }
5982 sched_data->force_send = force_send;
46125983 hash_insert(attendee, sched_data, att_table);
46135984 }
46145985 new_comp = icalcomponent_new_clone(copy);
46185989 /* XXX We assume that the master component is always first */
46195990 if (!ncomp) sched_data->master = new_comp;
46205991 }
4621
4622 if (force_send) icalproperty_remove_parameter_by_ref(prop, force_send);
46235992 }
46245993
46255994 /* XXX We assume that the master component is always first */
46606029 }
46616030
46626031
6032 /*
6033 * Compare the properties of the given kind in two components.
6034 * Returns 0 if equal, 1 otherwise.
6035 *
6036 * If the property exists in neither comp, then they are equal.
6037 * If the property exists in only 1 comp, then they are not equal.
6038 * if the property is RDATE or EXDATE, create an MD5 hash of all
6039 * property strings for each component and compare the hashes.
6040 * Otherwise compare the two property strings.
6041 */
46636042 static unsigned propcmp(icalcomponent *oldical, icalcomponent *newical,
46646043 icalproperty_kind kind)
46656044 {
4666 icalproperty *oldprop, *newprop;
4667
4668 oldprop = icalcomponent_get_first_property(oldical, kind);
4669 newprop = icalcomponent_get_first_property(newical, kind);
4670
4671 if (!oldprop) {
4672 if (newprop) return 1;
4673 }
6045 icalproperty *oldprop = icalcomponent_get_first_property(oldical, kind);
6046 icalproperty *newprop = icalcomponent_get_first_property(newical, kind);
6047
6048 if (!oldprop) return (newprop != NULL);
46746049 else if (!newprop) return 1;
6050 else if ((kind == ICAL_RDATE_PROPERTY) || (kind == ICAL_EXDATE_PROPERTY)) {
6051 MD5_CTX ctx;
6052 const char *str;
6053 unsigned char old_md5[MD5_DIGEST_LENGTH], new_md5[MD5_DIGEST_LENGTH];
6054
6055 MD5Init(&ctx);
6056 do {
6057 str = icalproperty_get_value_as_string(oldprop);
6058 MD5Update(&ctx, str, strlen(str));
6059 } while ((oldprop = icalcomponent_get_next_property(oldical, kind)));
6060
6061 MD5Final(old_md5, &ctx);
6062
6063 MD5Init(&ctx);
6064 do {
6065 str = icalproperty_get_value_as_string(newprop);
6066 MD5Update(&ctx, str, strlen(str));
6067 } while ((newprop = icalcomponent_get_next_property(newical, kind)));
6068
6069 MD5Final(new_md5, &ctx);
6070
6071 return (memcmp(old_md5, new_md5, MD5_DIGEST_LENGTH) != 0);
6072 }
46756073 else {
4676 /* XXX Do something smarter based on property type */
4677 const char *oldstr = icalproperty_get_value_as_string(oldprop);
4678 const char *newstr = icalproperty_get_value_as_string(newprop);
4679
4680 if (strcmp(oldstr, newstr)) return 1;
4681 }
4682
4683 return 0;
6074 return (strcmp(icalproperty_get_value_as_string(oldprop),
6075 icalproperty_get_value_as_string(newprop)) != 0);
6076 }
46846077 }
46856078
46866079
46936086 struct mboxlist_entry mbentry;
46946087 char outboxname[MAX_MAILBOX_BUFFER];
46956088 icalproperty_method method;
4696 static struct buf prodid = BUF_INITIALIZER;
46976089 struct auth_state *authstate;
46986090 icalcomponent *ical, *req, *comp;
46996091 icalproperty *prop;
47296121 if (!(rights & DACL_INVITE)) {
47306122 /* DAV:need-privileges */
47316123 sched_stat = SCHEDSTAT_NOPRIVS;
6124 syslog(LOG_DEBUG, "No scheduling send ACL for user %s on Outbox %s",
6125 httpd_userid, sparam->userid);
47326126
47336127 goto done;
47346128 }
47356129 }
47366130
47376131 /* Create a shell for our iTIP request objects */
4738 if (!buf_len(&prodid)) {
4739 buf_printf(&prodid, "-//CyrusIMAP.org/Cyrus %s//EN", cyrus_version());
4740 }
4741
47426132 req = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT,
47436133 icalproperty_new_version("2.0"),
4744 icalproperty_new_prodid(buf_cstring(&prodid)),
6134 icalproperty_new_prodid(ical_prodid),
47456135 icalproperty_new_method(method),
47466136 0);
47476137
47706160 /* Add each component of old object to hash table for comparison */
47716161 construct_hash_table(&comp_table, 10, 1);
47726162
4773 if (oldical) {
6163 if (!att_update && oldical) {
47746164 comp = icalcomponent_get_first_real_component(oldical);
6165
6166 /* If the existing object isn't a scheduling object,
6167 we don't need to compare components, treat them as new */
6168 if (icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY)) {
6169 do {
6170 old_data = xzmalloc(sizeof(struct comp_data));
6171 old_data->comp = comp;
6172 old_data->sequence = icalcomponent_get_sequence(comp);
6173
6174 prop =
6175 icalcomponent_get_first_property(comp,
6176 ICAL_RECURRENCEID_PROPERTY);
6177 if (prop) recurid = icalproperty_get_value_as_string(prop);
6178 else recurid = "";
6179
6180 hash_insert(recurid, old_data, &comp_table);
6181
6182 } while ((comp = icalcomponent_get_next_component(oldical, kind)));
6183 }
6184 }
6185
6186 /* Create hash table of attendees */
6187 construct_hash_table(&att_table, 10, 1);
6188
6189 /* Process each component of new object */
6190 if (newical) {
6191 unsigned ncomp = 0;
6192
6193 comp = icalcomponent_get_first_real_component(newical);
47756194 do {
4776 old_data = xzmalloc(sizeof(struct comp_data));
4777 old_data->comp = comp;
4778 old_data->sequence = icalcomponent_get_sequence(comp);
6195 unsigned changed = 1, needs_action = 0;
47796196
47806197 prop = icalcomponent_get_first_property(comp,
47816198 ICAL_RECURRENCEID_PROPERTY);
47826199 if (prop) recurid = icalproperty_get_value_as_string(prop);
47836200 else recurid = "";
47846201
4785 hash_insert(recurid, old_data, &comp_table);
4786
4787 } while ((comp = icalcomponent_get_next_component(oldical, kind)));
4788 }
4789
4790 /* Create hash table of attendees */
4791 construct_hash_table(&att_table, 10, 1);
4792
4793 /* Process each component of new object */
4794 if (newical) {
4795 unsigned ncomp = 0;
4796
4797 comp = icalcomponent_get_first_real_component(newical);
4798 do {
4799 unsigned changed = 1, needs_action = 0;
4800
4801 prop = icalcomponent_get_first_property(comp,
4802 ICAL_RECURRENCEID_PROPERTY);
4803 if (prop) recurid = icalproperty_get_value_as_string(prop);
4804 else recurid = "";
4805
48066202 old_data = hash_del(recurid, &comp_table);
48076203
48086204 if (old_data) {
48096205 /* Per RFC 6638, Section 3.2.8: We need to compare
48106206 DTSTART, DTEND, DURATION, DUE, RRULE, RDATE, EXDATE */
4811 needs_action += propcmp(old_data->comp, comp,
4812 ICAL_DTSTART_PROPERTY);
4813 needs_action += propcmp(old_data->comp, comp,
4814 ICAL_DTEND_PROPERTY);
4815 needs_action += propcmp(old_data->comp, comp,
4816 ICAL_DURATION_PROPERTY);
4817 needs_action += propcmp(old_data->comp, comp,
4818 ICAL_DUE_PROPERTY);
4819 needs_action += propcmp(old_data->comp, comp,
4820 ICAL_RRULE_PROPERTY);
4821 needs_action += propcmp(old_data->comp, comp,
4822 ICAL_RDATE_PROPERTY);
4823 needs_action += propcmp(old_data->comp, comp,
4824 ICAL_EXDATE_PROPERTY);
4825
4826 if (old_data->sequence >= icalcomponent_get_sequence(comp)) {
6207 if (propcmp(old_data->comp, comp, ICAL_DTSTART_PROPERTY))
6208 needs_action = 1;
6209 else if (propcmp(old_data->comp, comp, ICAL_DTEND_PROPERTY))
6210 needs_action = 1;
6211 else if (propcmp(old_data->comp, comp, ICAL_DURATION_PROPERTY))
6212 needs_action = 1;
6213 else if (propcmp(old_data->comp, comp, ICAL_DUE_PROPERTY))
6214 needs_action = 1;
6215 else if (propcmp(old_data->comp, comp, ICAL_RRULE_PROPERTY))
6216 needs_action = 1;
6217 else if (propcmp(old_data->comp, comp, ICAL_RDATE_PROPERTY))
6218 needs_action = 1;
6219 else if (propcmp(old_data->comp, comp, ICAL_EXDATE_PROPERTY))
6220 needs_action = 1;
6221 else if (kind == ICAL_VPOLL_COMPONENT) {
6222 }
6223
6224 if (needs_action &&
6225 (old_data->sequence >= icalcomponent_get_sequence(comp))) {
48276226 /* Make sure SEQUENCE is set properly */
4828 if (!needs_action) changed = 0;
4829
48306227 icalcomponent_set_sequence(comp,
4831 old_data->sequence + changed);
6228 old_data->sequence + 1);
48326229 }
48336230
48346231 free(old_data);
48666263 done:
48676264 if (newical) {
48686265 unsigned ncomp = 0;
6266 icalproperty_kind recip_kind;
6267 const char *(*get_recipient)(const icalproperty *);
48696268
48706269 /* Set SCHEDULE-STATUS for each attendee in organizer object */
48716270 comp = icalcomponent_get_first_real_component(newical);
48726271 kind = icalcomponent_isa(comp);
48736272
6273 if (kind == ICAL_VPOLL_COMPONENT) {
6274 recip_kind = ICAL_VOTER_PROPERTY;
6275 get_recipient = &icalproperty_get_voter;
6276 }
6277 else {
6278 recip_kind = ICAL_ATTENDEE_PROPERTY;
6279 get_recipient = &icalproperty_get_attendee;
6280 }
6281
48746282 do {
48756283 for (prop =
4876 icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY);
6284 icalcomponent_get_first_property(comp, recip_kind);
48776285 prop;
48786286 prop =
4879 icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) {
4880 const char *attendee = icalproperty_get_attendee(prop);
6287 icalcomponent_get_next_property(comp, recip_kind)) {
48816288 const char *stat = NULL;
6289 const char *attendee = get_recipient(prop);
48826290
48836291 /* Don't set status if attendee == organizer */
48846292 if (!strcmp(attendee, organizer)) continue;
48936301 }
48946302
48956303 if (stat) {
6304 /* Set SCHEDULE-STATUS */
48966305 icalparameter *param;
4897 for (param =
4898 icalproperty_get_first_parameter(prop,
4899 ICAL_IANA_PARAMETER);
4900 param && strcmp(icalparameter_get_iana_name(param),
4901 "SCHEDULE-STATUS");
4902 param =
4903 icalproperty_get_next_parameter(prop,
4904 ICAL_IANA_PARAMETER));
4905 if (!param) {
4906 param = icalparameter_new(ICAL_IANA_PARAMETER);
4907 icalproperty_add_parameter(prop, param);
4908 icalparameter_set_iana_name(param, "SCHEDULE-STATUS");
4909 }
4910 icalparameter_set_iana_value(param, stat);
6306
6307 param = icalparameter_new_schedulestatus(stat);
6308 icalproperty_set_parameter(prop, param);
49116309 }
49126310 }
49136311
49366334 {
49376335 icalcomponent *copy;
49386336 icalproperty *prop, *nextprop, *myattendee = NULL;
6337 icalcomponent_kind kind;
6338 icalproperty_kind recip_kind;
6339 const char *(*get_recipient)(const icalproperty *);
49396340
49406341 if (partstat) *partstat = ICAL_PARTSTAT_NONE;
49416342
49426343 /* Clone a working copy of the component */
49436344 copy = icalcomponent_new_clone(comp);
49446345
6346 kind = icalcomponent_isa(comp);
6347 if (kind == ICAL_VPOLL_COMPONENT) {
6348 recip_kind = ICAL_VOTER_PROPERTY;
6349 get_recipient = &icalproperty_get_voter;
6350 }
6351 else {
6352 recip_kind = ICAL_ATTENDEE_PROPERTY;
6353 get_recipient = &icalproperty_get_attendee;
6354 }
6355
49456356 /* Locate userid in the attendee list (stripping others) */
4946 for (prop = icalcomponent_get_first_property(copy,
4947 ICAL_ATTENDEE_PROPERTY);
6357 for (prop = icalcomponent_get_first_property(copy, recip_kind);
49486358 prop;
49496359 prop = nextprop) {
4950 const char *att = icalproperty_get_attendee(prop);
6360 const char *att = get_recipient(prop);
49516361 struct sched_param sparam;
49526362
4953 nextprop = icalcomponent_get_next_property(copy,
4954 ICAL_ATTENDEE_PROPERTY);
6363 nextprop = icalcomponent_get_next_property(copy, recip_kind);
49556364
49566365 if (!myattendee &&
49576366 !caladdress_lookup(att, &sparam) &&
50086417 myattendee = icalcomponent_get_first_property(old_data->comp,
50096418 ICAL_ATTENDEE_PROPERTY);
50106419
5011 param =
5012 icalproperty_get_first_parameter(myattendee,
5013 ICAL_PARTSTAT_PARAMETER);
5014 if (!param) {
5015 param = icalparameter_new(ICAL_PARTSTAT_PARAMETER);
5016 icalproperty_add_parameter(myattendee, param);
5017 }
5018 icalparameter_set_partstat(param, ICAL_PARTSTAT_DECLINED);
6420 param = icalparameter_new_partstat(ICAL_PARTSTAT_DECLINED);
6421 icalproperty_set_parameter(myattendee, param);
50196422
50206423 clean_component(old_data->comp, 1);
50216424
50316434 struct mboxlist_entry mbentry;
50326435 char outboxname[MAX_MAILBOX_BUFFER];
50336436 icalcomponent *ical;
5034 static struct buf prodid = BUF_INITIALIZER;
50356437 struct sched_data *sched_data;
50366438 struct auth_state *authstate;
50376439 icalcomponent *comp;
50386440 icalproperty *prop;
5039 icalparameter *param, *force_send = NULL;
6441 icalparameter *param;
50406442 icalcomponent_kind kind;
6443 icalparameter_scheduleforcesend force_send = ICAL_SCHEDULEFORCESEND_NONE;
50416444 const char *organizer, *recurid;
50426445 struct hash_table comp_table;
50436446 struct comp_data *old_data;
50586461 prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
50596462 organizer = icalproperty_get_organizer(prop);
50606463
5061 for (param = icalproperty_get_first_parameter(prop,
5062 ICAL_IANA_PARAMETER);
5063 param;
5064 param = icalproperty_get_next_parameter(prop,
5065 ICAL_IANA_PARAMETER)) {
5066 if (!strcmp(icalparameter_get_iana_name(param),
5067 "SCHEDULE-AGENT")) {
5068 if (strcmp(icalparameter_get_iana_value(param), "SERVER")) {
5069 /* We are not supposed to send replies to the organizer */
5070 return;
5071 }
5072 }
5073 else if (!strcmp(icalparameter_get_iana_name(param),
5074 "SCHEDULE-FORCE-SEND")) {
5075 force_send = param;
5076 }
5077 }
6464 param = icalproperty_get_scheduleagent_parameter(prop);
6465 if (param &&
6466 icalparameter_get_scheduleagent(param) != ICAL_SCHEDULEAGENT_SERVER) {
6467 /* We are not supposed to send replies to the organizer */
6468 return;
6469 }
6470
6471 param = icalproperty_get_scheduleforcesend_parameter(prop);
6472 if (param) force_send = icalparameter_get_scheduleforcesend(param);
50786473
50796474 sched_data = xzmalloc(sizeof(struct sched_data));
50806475 sched_data->is_reply = 1;
5081 if (force_send) {
5082 sched_data->force_send =
5083 xstrdup(icalparameter_get_iana_value(force_send));
5084 }
6476 sched_data->force_send = force_send;
50856477
50866478 /* Check ACL of auth'd user on userid's Scheduling Outbox */
50876479 caldav_mboxname(SCHED_OUTBOX, userid, outboxname);
50976489 if (!(rights & DACL_REPLY)) {
50986490 /* DAV:need-privileges */
50996491 if (newical) sched_data->status = SCHEDSTAT_NOPRIVS;
6492 syslog(LOG_DEBUG, "No scheduling send ACL for user %s on Outbox %s",
6493 httpd_userid, userid);
51006494
51016495 goto done;
51026496 }
51036497
51046498 /* Create our reply iCal object */
5105 if (!buf_len(&prodid)) {
5106 buf_printf(&prodid, "-//CyrusIMAP.org/Cyrus %s//EN", cyrus_version());
5107 }
5108
51096499 sched_data->itip =
51106500 icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT,
51116501 icalproperty_new_version("2.0"),
5112 icalproperty_new_prodid(buf_cstring(&prodid)),
6502 icalproperty_new_prodid(ical_prodid),
51136503 icalproperty_new_method(ICAL_METHOD_REPLY),
51146504 0);
51156505
51666556 old_data = hash_del(recurid, &comp_table);
51676557
51686558 if (old_data) {
5169 /* XXX Need to check EXDATE */
5170
5171 /* Compare PARTSTAT in the two components */
5172 if (old_data->partstat == partstat) {
5173 changed = 0;
6559 if (kind == ICAL_VPOLL_COMPONENT) {
6560 /* VPOLL replies always override existing votes */
6561 sched_vpoll_reply(copy,
6562 icalproperty_get_voter(myattendee));
6563 }
6564 else {
6565 /* XXX Need to check EXDATE */
6566
6567 /* Compare PARTSTAT in the two components */
6568 if (old_data->partstat == partstat) {
6569 changed = 0;
6570 }
51746571 }
51756572
51766573 free_comp_data(old_data);
52206617 prop =
52216618 icalcomponent_get_first_property(comp,
52226619 ICAL_ORGANIZER_PROPERTY);
5223 param = icalparameter_new(ICAL_IANA_PARAMETER);
5224 icalparameter_set_iana_name(param, "SCHEDULE-STATUS");
5225 icalparameter_set_iana_value(param, sched_data->status);
6620 param =
6621 icalparameter_new_schedulestatus(sched_data->status);
52266622 icalproperty_add_parameter(prop, param);
52276623 }
52286624
5757 #include "http_dav.h"
5858
5959
60 #ifndef HAVE_SCHEDULING_PARAMS
61
62 typedef enum {
63 ICAL_SCHEDULEAGENT_X,
64 ICAL_SCHEDULEAGENT_SERVER,
65 ICAL_SCHEDULEAGENT_CLIENT,
66 ICAL_SCHEDULEAGENT_NONE
67 } icalparameter_scheduleagent;
68
69 typedef enum {
70 ICAL_SCHEDULEFORCESEND_X,
71 ICAL_SCHEDULEFORCESEND_REQUEST,
72 ICAL_SCHEDULEFORCESEND_REPLY,
73 ICAL_SCHEDULEFORCESEND_NONE
74 } icalparameter_scheduleforcesend;
75
76 #endif /* !HAVE_SCHEDULING_PARAMS */
77
6078 #define REQSTAT_PENDING "1.0;Pending"
6179 #define REQSTAT_SENT "1.1;Sent"
6280 #define REQSTAT_DELIVERED "1.2;Delivered"
7391 icalcomponent *itip;
7492 icalcomponent *master;
7593 unsigned comp_mask;
76 char *force_send;
94 icalparameter_scheduleforcesend force_send;
7795 const char *status;
7896 };
7997
96114 struct proplist *props; /* List of attendee iCal properties */
97115 };
98116
117 extern icalarray *rscale_calendars;
118 extern const char *get_icalcomponent_errstr(icalcomponent *ical);
99119 extern int isched_send(struct sched_param *sparam, const char *recipient,
100120 icalcomponent *ical, xmlNodePtr *xml);
101121
5858 #include "acl.h"
5959 #include "append.h"
6060 #include "carddav_db.h"
61 #include "charset.h"
6162 #include "global.h"
6263 #include "hash.h"
6364 #include "httpd.h"
8384
8485 static struct carddav_db *auth_carddavdb = NULL;
8586
87 static struct carddav_db *my_carddav_open(struct mailbox *mailbox);
88 static void my_carddav_close(struct carddav_db *carddavdb);
8689 static void my_carddav_init(struct buf *serverinfo);
8790 static void my_carddav_auth(const char *userid);
8891 static void my_carddav_reset(void);
9497 static int carddav_copy(struct transaction_t *txn,
9598 struct mailbox *src_mbox, struct index_record *src_rec,
9699 struct mailbox *dest_mbox, const char *dest_rsrc,
100 struct carddav_db *dest_davdb,
97101 unsigned overwrite, unsigned flags);
98102 static int carddav_put(struct transaction_t *txn,
99103 struct mime_type_t *mime,
100 struct mailbox *mailbox, unsigned flags);
104 struct mailbox *mailbox,
105 struct carddav_db *carddavdb,
106 unsigned flags);
101107 static VObject *vcard_string_as_vobject(const char *str)
102108 {
103109 return Parse_MIME(str, strlen(str));
228234 carddav_mime_types,
229235 &carddav_parse_path,
230236 &check_precond,
231 { (void **) &auth_carddavdb,
237 { (db_open_proc_t) &my_carddav_open,
238 (db_close_proc_t) &my_carddav_close,
232239 (db_lookup_proc_t) &carddav_lookup_resource,
233240 (db_foreach_proc_t) &carddav_foreach,
234241 (db_write_proc_t) &carddav_write,
235242 (db_delete_proc_t) &carddav_delete,
236243 (db_delmbox_proc_t) &carddav_delmbox },
237244 NULL, /* No ACL extensions */
238 &carddav_copy,
245 (copy_proc_t) &carddav_copy,
239246 NULL, /* No special DELETE handling */
240247 { MBTYPE_ADDRESSBOOK, NULL, NULL, 0 }, /* No special MK* method */
241248 NULL, /* No special POST handling */
242 { CARDDAV_SUPP_DATA, &carddav_put },
249 { CARDDAV_SUPP_DATA, (put_proc_t) &carddav_put },
243250 carddav_props,
244 { { "addressbook-query", &report_card_query, DACL_READ,
245 REPORT_NEED_MBOX | REPORT_MULTISTATUS },
246 { "addressbook-multiget", &report_card_multiget, DACL_READ,
247 REPORT_NEED_MBOX | REPORT_MULTISTATUS },
248 { "sync-collection", &report_sync_col, DACL_READ,
249 REPORT_NEED_MBOX | REPORT_MULTISTATUS | REPORT_NEED_PROPS },
250 { NULL, NULL, 0, 0 } }
251 { { "addressbook-query", "multistatus", &report_card_query,
252 DACL_READ, REPORT_NEED_MBOX },
253 { "addressbook-multiget", "multistatus", &report_card_multiget,
254 DACL_READ, REPORT_NEED_MBOX },
255 { "sync-collection", "multistatus", &report_sync_col,
256 DACL_READ, REPORT_NEED_MBOX | REPORT_NEED_PROPS },
257 { NULL, NULL, NULL, 0, 0 } }
251258 };
252259
253260
289296 };
290297
291298
292 static struct namespace carddav_namespace;
293
294 static void my_carddav_init(struct buf *serverinfo)
295 {
296 int r;
297
299 static struct carddav_db *my_carddav_open(struct mailbox *mailbox)
300 {
301 if (httpd_userid && mboxname_userownsmailbox(httpd_userid, mailbox->name)) {
302 return auth_carddavdb;
303 }
304 else {
305 return carddav_open(mailbox, CALDAV_CREATE);
306 }
307 }
308
309
310 static void my_carddav_close(struct carddav_db *carddavdb)
311 {
312 if (carddavdb && (carddavdb != auth_carddavdb)) carddav_close(carddavdb);
313 }
314
315
316 static void my_carddav_init(struct buf *serverinfo __attribute__((unused)))
317 {
298318 namespace_addressbook.enabled =
299319 config_httpmodules & IMAP_ENUM_HTTPMODULES_CARDDAV;
300320
304324 fatal("Required 'addressbookprefix' option is not set", EC_CONFIG);
305325 }
306326
307 /* Set namespace -- force standard (internal) */
308 if ((r = mboxname_init_namespace(&carddav_namespace, 1))) {
309 syslog(LOG_ERR, "%s", error_message(r));
310 fatal(error_message(r), EC_CONFIG);
311 }
312
313327 carddav_init();
314328
315 if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON &&
316 !strstr(buf_cstring(serverinfo), " libical/")) {
317 buf_printf(serverinfo, " libicalvcal/%s", ICAL_VERSION);
318 }
329 namespace_principal.enabled = 1;
330 namespace_principal.allow |= ALLOW_CARD;
319331 }
320332
321333
330342 char ident[MAX_MAILBOX_NAME];
331343 struct buf acl = BUF_INITIALIZER;
332344
345 /* Construct mailbox name corresponding to userid's Inbox */
346 (*httpd_namespace.mboxname_tointernal)(&httpd_namespace, "INBOX",
347 userid, mailboxname);
348 len = strlen(mailboxname);
349
333350 if (httpd_userisadmin ||
334351 global_authisa(httpd_authstate, IMAPOPT_PROXYSERVERS)) {
335352 /* admin or proxy from frontend - won't have DAV database */
340357 }
341358 else {
342359 /* Open CardDAV DB for 'userid' */
360 struct mailbox mailbox;
361
362 mailbox.name = mailboxname;
363
343364 my_carddav_reset();
344 auth_carddavdb = carddav_open(userid, CARDDAV_CREATE);
365 auth_carddavdb = carddav_open(&mailbox, CARDDAV_CREATE);
345366 if (!auth_carddavdb) fatal("Unable to open CardDAV DB", EC_IOERR);
346367 }
347368
348369 /* Auto-provision an addressbook for 'userid' */
349370 strlcpy(ident, userid, sizeof(ident));
350371 mboxname_hiersep_toexternal(&httpd_namespace, ident, 0);
351
352 /* Construct mailbox name corresponding to userid's Inbox */
353 (*carddav_namespace.mboxname_tointernal)(&carddav_namespace, "INBOX",
354 userid, mailboxname);
355 len = strlen(mailboxname);
356372
357373 /* addressbook-home-set */
358374 len += snprintf(mailboxname+len, MAX_MAILBOX_BUFFER - len, ".%s",
378394 }
379395 }
380396 }
397 else r = 0;
381398
382399 /* Create locally */
383400 if (!r) r = mboxlist_createmailboxcheck(mailboxname, 0, NULL, 0,
564581 static int carddav_copy(struct transaction_t *txn,
565582 struct mailbox *src_mbox, struct index_record *src_rec,
566583 struct mailbox *dest_mbox, const char *dest_rsrc,
584 struct carddav_db *dest_davdb,
567585 unsigned overwrite, unsigned flags)
568586 {
569587 int ret;
586604 mailbox_unlock_index(src_mbox, NULL);
587605
588606 /* Store source resource at destination */
589 ret = store_resource(txn, vcard, dest_mbox, dest_rsrc, auth_carddavdb,
607 ret = store_resource(txn, vcard, dest_mbox, dest_rsrc, dest_davdb,
590608 overwrite, flags);
591609
592610 cleanVObject(vcard);
605623 */
606624 static int carddav_put(struct transaction_t *txn,
607625 struct mime_type_t *mime,
608 struct mailbox *mailbox, unsigned flags)
626 struct mailbox *mailbox,
627 struct carddav_db *davdb,
628 unsigned flags)
609629 {
610630 int ret;
611631 VObject *vcard = NULL;
620640
621641 /* Store resource at target */
622642 ret = store_resource(txn, vcard, mailbox, txn->req_tgt.resource,
623 auth_carddavdb, OVERWRITE_CHECK, flags);
643 davdb, OVERWRITE_CHECK, flags);
624644
625645 if (flags & PREFER_REP) {
626646 struct resp_body_t *resp_body = &txn->resp_body;
780800 xmlNodePtr node;
781801 const char *abook = (const char *) rock;
782802
783 if (!fctx->userid) return HTTP_NOT_FOUND;
803 if (!(namespace_addressbook.enabled && fctx->req_tgt->user))
804 return HTTP_NOT_FOUND;
784805
785806 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
786807 name, ns, NULL, 0);
787808
788809 buf_reset(&fctx->buf);
789 buf_printf(&fctx->buf, "%s/user/%s/%s", namespace_addressbook.prefix,
790 fctx->userid, abook ? abook : "");
810 buf_printf(&fctx->buf, "%s/user/%.*s/%s", namespace_addressbook.prefix,
811 (int) fctx->req_tgt->userlen, fctx->req_tgt->user,
812 abook ? abook : "");
791813
792814 xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
793815
838860 int ret = 0;
839861 xmlNodePtr node;
840862
841 fctx->davdb = auth_carddavdb;
863 fctx->filter_crit = (void *) 0xDEADBEEF; /* placeholder until we filter */
864 fctx->open_db = (db_open_proc_t) &my_carddav_open;
865 fctx->close_db = (db_close_proc_t) &my_carddav_close;
842866 fctx->lookup_resource = (db_lookup_proc_t) &carddav_lookup_resource;
843867 fctx->foreach_resource = (db_foreach_proc_t) &carddav_foreach;
844868 fctx->proc_by_resource = &propfind_by_resource;
853877 }
854878 }
855879
856 if (fctx->depth > 0) {
880 if (fctx->depth++ > 0) {
857881 /* Calendar collection(s) */
858882 if (txn->req_tgt.collection) {
859883 /* Add response for target calendar collection */
867891 httpd_authstate, propfind_by_collection, fctx);
868892 }
869893
894 if (fctx->davdb) my_carddav_close(fctx->davdb);
895
870896 ret = *fctx->ret;
871897 }
872898
873 return ret;
899 return (ret ? ret : HTTP_MULTI_STATUS);
874900 }
875901
876902
878904 xmlNodePtr inroot, struct propfind_ctx *fctx)
879905 {
880906 int r, ret = 0;
881 struct request_target_t tgt;
882907 struct mailbox *mailbox = NULL;
883908 xmlNodePtr node;
884909 struct buf uri = BUF_INITIALIZER;
885
886 memset(&tgt, 0, sizeof(struct request_target_t));
887 tgt.namespace = URL_NS_CALENDAR;
888910
889911 /* Get props for each href */
890912 for (node = inroot->children; node; node = node->next) {
892914 !xmlStrcmp(node->name, BAD_CAST "href")) {
893915 xmlChar *href = xmlNodeListGetString(inroot->doc, node->children, 1);
894916 int len = xmlStrlen(href);
917 struct request_target_t tgt;
895918 struct carddav_data *cdata;
896919
897920 buf_ensure(&uri, len);
899922 xmlFree(href);
900923
901924 /* Parse the path */
925 memset(&tgt, 0, sizeof(struct request_target_t));
926 tgt.namespace = URL_NS_CALENDAR;
927
902928 if ((r = carddav_parse_path(uri.s, &tgt, &fctx->err->desc))) {
903929 ret = r;
904930 goto done;
911937 if (mailbox) mailbox_unlock_index(mailbox, NULL);
912938
913939 /* Open mailbox for reading */
914 if ((r = http_mailbox_open(tgt.mboxname, &mailbox, LOCK_SHARED))) {
940 r = mailbox_open_irl(tgt.mboxname, &mailbox);
941 if (r && r != IMAP_MAILBOX_NONEXISTENT) {
915942 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
916943 tgt.mboxname, error_message(r));
917944 txn->error.desc = error_message(r);
922949 fctx->mailbox = mailbox;
923950 }
924951
952 if (!fctx->mailbox || !tgt.resource) {
953 /* Add response for missing target */
954 xml_add_response(fctx, HTTP_NOT_FOUND, 0);
955 continue;
956 }
957
958 /* Open the DAV DB corresponding to the mailbox */
959 fctx->davdb = my_carddav_open(fctx->mailbox);
960
925961 /* Find message UID for the resource */
926 carddav_lookup_resource(auth_carddavdb,
962 carddav_lookup_resource(fctx->davdb,
927963 tgt.mboxname, tgt.resource, 0, &cdata);
928964 cdata->dav.resource = tgt.resource;
929965 /* XXX Check errors */
930966
931967 propfind_by_resource(fctx, cdata);
968
969 my_carddav_close(fctx->davdb);
932970 }
933971 }
934972
935973 done:
936 if (mailbox) mailbox_unlock_index(mailbox, NULL);
974 mailbox_close(&mailbox);
937975 buf_free(&uri);
938976
939 return ret;
977 return (ret ? ret : HTTP_MULTI_STATUS);
940978 }
941979
942980
952990 struct carddav_data *cdata;
953991 FILE *f = NULL;
954992 struct stagemsg *stage;
955 const char *version = NULL, *uid = NULL, *fullname = NULL, *nickname = NULL;
993 const char *version = NULL, *uid = NULL, *fullname = NULL;
956994 uquota_t size;
995 char *header;
996 uint32_t expunge_uid = 0;
997 struct index_record oldrecord;
957998 time_t now = time(NULL);
958999 char datestr[80];
9591000 struct appendstate as;
9761017 }
9771018 else if (!strcmp(name, "FN")) {
9781019 fullname = fakeCString(vObjectUStringZValue(prop));
979 }
980 if (!strcmp(name, "NICKNAME")) {
981 nickname = fakeCString(vObjectUStringZValue(prop));
9821020 }
9831021 }
9841022
10061044 return HTTP_FORBIDDEN;
10071045 }
10081046
1047 if (cdata->dav.imap_uid) {
1048 /* Fetch index record for the resource */
1049 r = mailbox_find_index_record(mailbox, cdata->dav.imap_uid,
1050 &oldrecord);
1051
1052 if (overwrite == OVERWRITE_CHECK) {
1053 /* Check any preconditions */
1054 const char *etag = message_guid_encode(&oldrecord.guid);
1055 time_t lastmod = oldrecord.internaldate;
1056 int precond = check_precond(txn, cdata, etag, lastmod);
1057
1058 if (precond == HTTP_OK)
1059 return HTTP_PRECOND_FAILED;
1060 }
1061
1062 expunge_uid = oldrecord.uid;
1063 }
1064
10091065 /* Prepare to stage the message */
10101066 if (!(f = append_newstage(mailbox->name, now, 0, &stage))) {
10111067 syslog(LOG_ERR, "append_newstage(%s) failed", mailbox->name);
10161072 /* Create iMIP header for resource */
10171073
10181074 /* XXX This needs to be done via an LDAP/DB lookup */
1019 fprintf(f, "From: %s <>\r\n", proxy_userid);
1020
1021 fprintf(f, "Subject: %s\r\n", fullname);
1075 header = charset_encode_mimeheader(proxy_userid, 0);
1076 fprintf(f, "From: %s <>\r\n", header);
1077 free(header);
1078
1079 header = charset_encode_mimeheader(fullname, 0);
1080 fprintf(f, "Subject: %s\r\n", header);
1081 free(header);
10221082
10231083 rfc822date_gen(datestr, sizeof(datestr), now); /* Use REV? */
10241084
10741134 }
10751135 else {
10761136 /* append_commit() returns a write-locked index */
1077 struct index_record newrecord, oldrecord, *expunge;
1137 struct index_record newrecord;
10781138
10791139 /* Read index record for new message (always the last one) */
10801140 mailbox_read_index_record(mailbox, mailbox->i.num_records,
10811141 &newrecord);
10821142
1083 /* Find message UID for the current resource, if exists */
1084 carddav_lookup_resource(carddavdb,
1085 mailbox->name, resource, 1, &cdata);
1086 /* XXX check for errors */
1087
1088 if (cdata->dav.imap_uid) {
1143 if (expunge_uid) {
10891144 /* Now that we have the replacement message in place
10901145 and the mailbox locked, re-read the old record
10911146 and see if we should overwrite it. Either way,
10951150
10961151 ret = HTTP_NO_CONTENT;
10971152
1098 /* Fetch index record for the resource */
1099 r = mailbox_find_index_record(mailbox, cdata->dav.imap_uid,
1100 &oldrecord);
1101
1102 if (overwrite == OVERWRITE_CHECK) {
1103 /* Check any preconditions */
1104 const char *etag = message_guid_encode(&oldrecord.guid);
1105 time_t lastmod = oldrecord.internaldate;
1106 int precond = check_precond(txn, cdata, etag, lastmod);
1107
1108 overwrite = (precond == HTTP_OK);
1109 }
1110
1111 if (overwrite) {
1112 /* Keep new resource - expunge the old one */
1113 expunge = &oldrecord;
1114 }
1115 else {
1116 /* Keep old resource - expunge the new one */
1117 expunge = &newrecord;
1118 ret = HTTP_PRECOND_FAILED;
1119 }
1120
11211153 /* Perform the actual expunge */
11221154 r = mailbox_user_flag(mailbox, DFLAG_UNBIND, &userflag);
11231155 if (!r) {
1124 expunge->user_flags[userflag/32] |= 1<<(userflag&31);
1125 expunge->system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED;
1126 r = mailbox_rewrite_index_record(mailbox, expunge);
1156 oldrecord.user_flags[userflag/32] |= 1<<(userflag&31);
1157 oldrecord.system_flags |= FLAG_EXPUNGED | FLAG_UNLINKED;
1158 r = mailbox_rewrite_index_record(mailbox, &oldrecord);
11271159 }
11281160 if (r) {
11291161 syslog(LOG_ERR, "expunging record (%s) failed: %s",
11361168 if (!r) {
11371169 struct resp_body_t *resp_body = &txn->resp_body;
11381170
1139 /* Create mapping entry from resource name to UID */
1140 cdata->dav.mailbox = mailbox->name;
1141 cdata->dav.resource = resource;
1142 cdata->dav.imap_uid = newrecord.uid;
1143 cdata->vcard_uid = uid;
1144 cdata->fullname = fullname;
1145 cdata->nickname = nickname;
1146
1147 if (!cdata->dav.creationdate) cdata->dav.creationdate = now;
1148
1149 carddav_write(carddavdb, cdata, 1);
1150 /* XXX check for errors, if this fails, backout changes */
1151
11521171 /* Tell client about the new resource */
11531172 resp_body->lastmod = newrecord.internaldate;
11541173 resp_body->etag = message_guid_encode(&newrecord.guid);
0 /* http_client.c - HTTP client-side support functions
1 *
2 * Copyright (c) 1994-2014 Carnegie Mellon University. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
14 * distribution.
15 *
16 * 3. The name "Carnegie Mellon University" must not be used to
17 * endorse or promote products derived from this software without
18 * prior written permission. For permission or any legal
19 * details, please contact
20 * Carnegie Mellon University
21 * Center for Technology Transfer and Enterprise Creation
22 * 4615 Forbes Avenue
23 * Suite 302
24 * Pittsburgh, PA 15213
25 * (412) 268-7393, fax: (412) 268-7395
26 * innovation@andrew.cmu.edu
27 *
28 * 4. Redistributions of any form whatsoever must retain the following
29 * acknowledgment:
30 * "This product includes software developed by Computing Services
31 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
32 *
33 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
34 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
35 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
36 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
37 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
38 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
39 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
40 *
41 */
42
43 #include <config.h>
44
45 #ifdef HAVE_UNISTD_H
46 #include <unistd.h>
47 #endif
48 #include <string.h>
49 #include <syslog.h>
50
51 #include "http_err.h"
52 #include "http_client.h"
53 #include "prot.h"
54 #include "tok.h"
55
56
57 /* Compare Content-Types */
58 int is_mediatype(const char *pat, const char *type)
59 {
60 const char *psep = strchr(pat, '/');
61 const char *tsep = strchr(type, '/');
62 size_t plen;
63 size_t tlen;
64 int alltypes;
65
66 /* Check type */
67 if (!psep || !tsep) return 0;
68 plen = psep - pat;
69 tlen = tsep - type;
70
71 alltypes = !strncmp(pat, "*", plen);
72
73 if (!alltypes && ((tlen != plen) || strncasecmp(pat, type, tlen))) return 0;
74
75 /* Check subtype */
76 pat = ++psep;
77 plen = strcspn(pat, "; \r\n\0");
78 type = ++tsep;
79 tlen = strcspn(type, "; \r\n\0");
80
81 return (!strncmp(pat, "*", plen) ||
82 (!alltypes && (tlen == plen) && !strncasecmp(pat, type, tlen)));
83 }
84
85
86 /*
87 * Parse the framing of a request or response message.
88 * Handles chunked, gzip, deflate TE only.
89 * Handles close-delimited response bodies (no Content-Length specified)
90 */
91 int http_parse_framing(hdrcache_t hdrs, struct body_t *body,
92 const char **errstr)
93 {
94 static unsigned max_msgsize = 0;
95 const char **hdr;
96
97 if (!max_msgsize) {
98 max_msgsize = config_getint(IMAPOPT_MAXMESSAGESIZE);
99
100 /* If max_msgsize is 0, allow any size */
101 if (!max_msgsize) max_msgsize = INT_MAX;
102 }
103
104 body->framing = FRAMING_LENGTH;
105 body->te = TE_NONE;
106 body->len = 0;
107 body->max = max_msgsize;
108
109 /* Check for Transfer-Encoding */
110 if ((hdr = spool_getheader(hdrs, "Transfer-Encoding"))) {
111 for (; *hdr; hdr++) {
112 tok_t tok = TOK_INITIALIZER(*hdr, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
113 char *token;
114
115 while ((token = tok_next(&tok))) {
116 if (body->te & TE_CHUNKED) {
117 /* "chunked" MUST only appear once and MUST be last */
118 break;
119 }
120 else if (!strcasecmp(token, "chunked")) {
121 body->te |= TE_CHUNKED;
122 body->framing = FRAMING_CHUNKED;
123 }
124 else if (body->te & ~TE_CHUNKED) {
125 /* can't combine compression codings */
126 break;
127 }
128 #ifdef HAVE_ZLIB
129 else if (!strcasecmp(token, "deflate"))
130 body->te = TE_DEFLATE;
131 else if (!strcasecmp(token, "gzip") ||
132 !strcasecmp(token, "x-gzip"))
133 body->te = TE_GZIP;
134 #endif
135 else if (!(body->flags & BODY_DISCARD)) {
136 /* unknown/unsupported TE */
137 break;
138 }
139 }
140 tok_fini(&tok);
141 if (token) break; /* error */
142 }
143
144 if (*hdr) {
145 body->te = TE_UNKNOWN;
146 *errstr = "Specified Transfer-Encoding not implemented";
147 return HTTP_BAD_MEDIATYPE;
148 }
149
150 /* Check if this is a non-chunked response */
151 else if (!(body->te & TE_CHUNKED)) {
152 if ((body->flags & BODY_RESPONSE) && (body->flags & BODY_CLOSE)) {
153 body->framing = FRAMING_CLOSE;
154 }
155 else {
156 body->te = TE_UNKNOWN;
157 *errstr = "Final Transfer-Encoding MUST be \"chunked\"";
158 return HTTP_BAD_MEDIATYPE;
159 }
160 }
161 }
162
163 /* Check for Content-Length */
164 else if ((hdr = spool_getheader(hdrs, "Content-Length"))) {
165 if (hdr[1]) {
166 *errstr = "Multiple Content-Length header fields";
167 return HTTP_BAD_REQUEST;
168 }
169
170 body->len = strtoul(hdr[0], NULL, 10);
171 if (body->len > max_msgsize) return HTTP_TOO_LARGE;
172
173 body->framing = FRAMING_LENGTH;
174 }
175
176 /* Check if this is a close-delimited response */
177 else if (body->flags & BODY_RESPONSE) {
178 if (body->flags & BODY_CLOSE) body->framing = FRAMING_CLOSE;
179 else return HTTP_LENGTH_REQUIRED;
180 }
181
182 return 0;
183 }
184
185
186 /*
187 * Read the body of a request or response.
188 * Handles chunked, gzip, deflate TE only.
189 * Handles close-delimited response bodies (no Content-Length specified)
190 * Handles gzip and deflate CE only.
191 */
192 int http_read_body(struct protstream *pin, struct protstream *pout,
193 hdrcache_t hdrs, struct body_t *body, const char **errstr)
194 {
195 char buf[PROT_BUFSIZE];
196 unsigned n;
197 int r = 0;
198
199 syslog(LOG_DEBUG, "read_body(%#x)", body->flags);
200
201 if (body->flags & BODY_DONE) return 0;
202 body->flags |= BODY_DONE;
203
204 if (!(body->flags & BODY_DISCARD)) buf_reset(&body->payload);
205 else if (body->flags & BODY_CONTINUE) {
206 /* Don't care about the body and client hasn't sent it, we're done */
207 return 0;
208 }
209
210 if (body->framing == FRAMING_UNKNOWN) {
211 /* Get message framing */
212 r = http_parse_framing(hdrs, body, errstr);
213 if (r) return r;
214 }
215
216 if (body->flags & BODY_CONTINUE) {
217 /* Tell client to send the body */
218 prot_printf(pout, "%s %s\r\n\r\n",
219 HTTP_VERSION, error_message(HTTP_CONTINUE));
220 prot_flush(pout);
221 }
222
223 /* Read and buffer the body */
224 switch (body->framing) {
225 case FRAMING_LENGTH:
226 /* Read 'len' octets */
227 for (; body->len; body->len -= n) {
228 if (body->flags & BODY_DISCARD)
229 n = prot_read(pin, buf, MIN(body->len, PROT_BUFSIZE));
230 else
231 n = prot_readbuf(pin, &body->payload, body->len);
232
233 if (!n) {
234 syslog(LOG_ERR, "prot_read() error");
235 *errstr = "Unable to read body data";
236 goto read_failure;
237 }
238 }
239
240 break;
241
242 case FRAMING_CHUNKED:
243 {
244 unsigned last = 0;
245
246 /* Read chunks until last-chunk (zero chunk-size) */
247 do {
248 unsigned chunk;
249
250 /* Read chunk-size */
251 if (!prot_fgets(buf, PROT_BUFSIZE, pin) ||
252 sscanf(buf, "%x", &chunk) != 1) {
253 *errstr = "Unable to read chunk size";
254 goto read_failure;
255
256 /* XXX Do we need to parse chunk-ext? */
257 }
258 else if (chunk > body->max - body->len) return HTTP_TOO_LARGE;
259
260 if (!chunk) {
261 /* last-chunk */
262 last = 1;
263
264 /* Read/parse any trailing headers */
265 spool_fill_hdrcache(pin, NULL, hdrs, NULL);
266 }
267
268 /* Read 'chunk' octets */
269 for (; chunk; chunk -= n) {
270 if (body->flags & BODY_DISCARD)
271 n = prot_read(pin, buf, MIN(chunk, PROT_BUFSIZE));
272 else
273 n = prot_readbuf(pin, &body->payload, chunk);
274
275 if (!n) {
276 syslog(LOG_ERR, "prot_read() error");
277 *errstr = "Unable to read chunk data";
278 goto read_failure;
279 }
280 body->len += n;
281 }
282
283 /* Read CRLF terminating the chunk/trailer */
284 if (!prot_fgets(buf, sizeof(buf), pin)) {
285 *errstr = "Missing CRLF following chunk/trailer";
286 goto read_failure;
287 }
288
289 } while (!last);
290
291 body->te &= ~TE_CHUNKED;
292
293 break;
294 }
295
296 case FRAMING_CLOSE:
297 /* Read until EOF */
298 do {
299 if (body->flags & BODY_DISCARD)
300 n = prot_read(pin, buf, PROT_BUFSIZE);
301 else
302 n = prot_readbuf(pin, &body->payload, PROT_BUFSIZE);
303
304 if (n > body->max - body->len) return HTTP_TOO_LARGE;
305 body->len += n;
306
307 } while (n);
308
309 if (!pin->eof) goto read_failure;
310
311 break;
312
313 default:
314 /* XXX Should never get here */
315 *errstr = "Unknown length of read body data";
316 goto read_failure;
317 }
318
319
320 if (!(body->flags & BODY_DISCARD) && buf_len(&body->payload)) {
321 #ifdef HAVE_ZLIB
322 /* Decode the payload, if necessary */
323 if (body->te == TE_DEFLATE)
324 r = buf_inflate(&body->payload, DEFLATE_ZLIB);
325 else if (body->te == TE_GZIP)
326 r = buf_inflate(&body->payload, DEFLATE_GZIP);
327
328 if (r) {
329 *errstr = "Error decoding payload";
330 return HTTP_BAD_REQUEST;
331 }
332 #endif
333
334 /* Decode the representation, if necessary */
335 if (body->flags & BODY_DECODE) {
336 const char **hdr;
337
338 if (!(hdr = spool_getheader(hdrs, "Content-Encoding"))) {
339 /* nothing to see here */
340 }
341
342 #ifdef HAVE_ZLIB
343 else if (!strcasecmp(hdr[0], "deflate")) {
344 const char **ua = spool_getheader(hdrs, "User-Agent");
345
346 /* Try to detect Microsoft's broken deflate */
347 if (ua && strstr(ua[0], "; MSIE "))
348 r = buf_inflate(&body->payload, DEFLATE_RAW);
349 else
350 r = buf_inflate(&body->payload, DEFLATE_ZLIB);
351 }
352 else if (!strcasecmp(hdr[0], "gzip") ||
353 !strcasecmp(hdr[0], "x-gzip"))
354 r = buf_inflate(&body->payload, DEFLATE_GZIP);
355 #endif
356 else {
357 *errstr = "Specified Content-Encoding not accepted";
358 return HTTP_BAD_MEDIATYPE;
359 }
360
361 if (r) {
362 *errstr = "Error decoding content";
363 return HTTP_BAD_REQUEST;
364 }
365 }
366 }
367
368 return 0;
369
370 read_failure:
371 if (strcmpsafe(prot_error(pin), PROT_EOF_STRING)) {
372 /* client timed out */
373 *errstr = prot_error(pin);
374 syslog(LOG_WARNING, "%s, closing connection", *errstr);
375 return HTTP_TIMEOUT;
376 }
377 else return HTTP_BAD_REQUEST;
378 }
379
380
381 /* Read a response from backend */
382 int http_read_response(struct backend *be, unsigned meth, unsigned *code,
383 const char **statline, hdrcache_t *hdrs,
384 struct body_t *body, const char **errstr)
385 {
386 static char statbuf[2048];
387 const char **conn;
388 int c = EOF;
389
390 if (statline) *statline = statbuf;
391 *errstr = NULL;
392 *code = HTTP_BAD_GATEWAY;
393
394 if (*hdrs) spool_free_hdrcache(*hdrs);
395 if (!(*hdrs = spool_new_hdrcache())) {
396 *errstr = "Unable to create header cache for backend response";
397 return HTTP_SERVER_ERROR;
398 }
399 if (!prot_fgets(statbuf, sizeof(statbuf), be->in) ||
400 (sscanf(statbuf, HTTP_VERSION " %u ", code) != 1) ||
401 spool_fill_hdrcache(be->in, NULL, *hdrs, NULL)) {
402 *errstr = "Unable to read status-line/headers from backend";
403 return HTTP_BAD_GATEWAY;
404 }
405 eatline(be->in, c); /* CRLF separating headers & body */
406
407 /* 1xx (provisional) response - nothing else to do */
408 if (*code < 200) return 0;
409
410 /* Final response */
411 if (!body) return 0; /* body will be piped */
412 if (!(body->flags & BODY_DISCARD)) buf_reset(&body->payload);
413
414 /* Check connection persistence */
415 if (!strncmp(statbuf, "HTTP/1.0 ", 9)) body->flags |= BODY_CLOSE;
416 for (conn = spool_getheader(*hdrs, "Connection"); conn && *conn; conn++) {
417 tok_t tok =
418 TOK_INITIALIZER(*conn, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
419 char *token;
420
421 while ((token = tok_next(&tok))) {
422 if (!strcasecmp(token, "keep-alive")) body->flags &= ~BODY_CLOSE;
423 else if (!strcasecmp(token, "close")) body->flags |= BODY_CLOSE;
424 }
425 tok_fini(&tok);
426 }
427
428 /* Not expecting a body for 204/304 response or any HEAD response */
429 switch (*code){
430 case 204: /* No Content */
431 case 304: /* Not Modified */
432 break;
433
434 default:
435 if (meth == METH_HEAD) break;
436
437 else {
438 body->flags |= BODY_RESPONSE;
439 body->framing = FRAMING_UNKNOWN;
440
441 if (http_read_body(be->in, be->out, *hdrs, body, errstr)) {
442 return HTTP_BAD_GATEWAY;
443 }
444 }
445 }
446
447 return 0;
448 }
0 /* http_client.h - HTTP client-side support functions
1 *
2 * Copyright (c) 1994-2014 Carnegie Mellon University. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
14 * distribution.
15 *
16 * 3. The name "Carnegie Mellon University" must not be used to
17 * endorse or promote products derived from this software without
18 * prior written permission. For permission or any legal
19 * details, please contact
20 * Carnegie Mellon University
21 * Center for Technology Transfer and Enterprise Creation
22 * 4615 Forbes Avenue
23 * Suite 302
24 * Pittsburgh, PA 15213
25 * (412) 268-7393, fax: (412) 268-7395
26 * innovation@andrew.cmu.edu
27 *
28 * 4. Redistributions of any form whatsoever must retain the following
29 * acknowledgment:
30 * "This product includes software developed by Computing Services
31 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
32 *
33 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
34 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
35 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
36 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
37 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
38 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
39 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
40 *
41 */
42
43 #ifndef _HTTP_CLIENT_H
44 #define _HTTP_CLIENT_H
45
46 #include "backend.h"
47 #include "spool.h"
48
49 /* Supported HTTP version */
50 #define HTTP_VERSION "HTTP/1.1"
51 #define HTTP_VERSION_LEN 8
52
53 /* Context for reading request/response body */
54 struct body_t {
55 unsigned char flags; /* Disposition flags */
56 unsigned char framing; /* Message framing */
57 unsigned char te; /* Transfer-Encoding */
58 unsigned max; /* Max allowed len */
59 ulong len; /* Content-Length */
60 struct buf payload; /* Payload */
61 };
62
63 /* Message Framing flags */
64 enum {
65 FRAMING_UNKNOWN = 0,
66 FRAMING_LENGTH,
67 FRAMING_CHUNKED,
68 FRAMING_CLOSE
69 };
70
71 /* Transfer-Encoding flags (coding of response payload) */
72 enum {
73 TE_NONE = 0,
74 TE_DEFLATE = (1<<0), /* Implies TE_CHUNKED as final coding */
75 TE_GZIP = (1<<1), /* Implies TE_CHUNKED as final coding */
76 TE_CHUNKED = (1<<2), /* MUST be last */
77 TE_UNKNOWN = 0xff
78 };
79
80 /* http_read_body() flags */
81 enum {
82 BODY_RESPONSE = (1<<0), /* Response body, otherwise request */
83 BODY_CONTINUE = (1<<1), /* Expect:100-continue request */
84 BODY_CLOSE = (1<<2), /* Close-delimited response body */
85 BODY_DECODE = (1<<3), /* Decode any Content-Encoding */
86 BODY_DISCARD = (1<<4), /* Discard body (don't buffer or decode) */
87 BODY_DONE = (1<<5) /* Body has been read */
88 };
89
90 /* Index into known HTTP methods - needs to stay in sync with array */
91 enum {
92 METH_ACL = 0,
93 METH_COPY,
94 METH_DELETE,
95 METH_GET,
96 METH_HEAD,
97 METH_LOCK,
98 METH_MKCALENDAR,
99 METH_MKCOL,
100 METH_MOVE,
101 METH_OPTIONS,
102 METH_POST,
103 METH_PROPFIND,
104 METH_PROPPATCH,
105 METH_PUT,
106 METH_REPORT,
107 METH_TRACE,
108 METH_UNLOCK,
109
110 METH_UNKNOWN, /* MUST be last */
111 };
112
113
114 extern int is_mediatype(const char *pat, const char *type);
115 extern int http_parse_framing(hdrcache_t hdrs, struct body_t *body,
116 const char **errstr);
117 extern int http_read_body(struct protstream *pin, struct protstream *pout,
118 hdrcache_t hdrs, struct body_t *body,
119 const char **errstr);
120 extern int http_read_response(struct backend *be, unsigned meth, unsigned *code,
121 const char **statline, hdrcache_t *hdrs,
122 struct body_t *body, const char **errstr);
123
124 #endif /* _HTTP_CLIENT_H */
8282 #include <libxml/uri.h>
8383
8484
85 #define SYNC_TOKEN_URL_SCHEME "data:,"
86
8587 static const struct dav_namespace_t {
8688 const char *href;
8789 const char *prefix;
106108
107109 static int prin_parse_path(const char *path,
108110 struct request_target_t *tgt, const char **errstr);
111 static int propfind_displayname(const xmlChar *name, xmlNsPtr ns,
112 struct propfind_ctx *fctx, xmlNodePtr resp,
113 struct propstat propstat[], void *rock);
109114 static int propfind_restype(const xmlChar *name, xmlNsPtr ns,
110115 struct propfind_ctx *fctx, xmlNodePtr resp,
111116 struct propstat propstat[], void *rock);
112117 static int propfind_reportset(const xmlChar *name, xmlNsPtr ns,
113118 struct propfind_ctx *fctx, xmlNodePtr resp,
114119 struct propstat propstat[], void *rock);
120 static int propfind_alturiset(const xmlChar *name, xmlNsPtr ns,
121 struct propfind_ctx *fctx, xmlNodePtr resp,
122 struct propstat propstat[], void *rock);
115123 static int propfind_principalurl(const xmlChar *name, xmlNsPtr ns,
116124 struct propfind_ctx *fctx, xmlNodePtr resp,
117125 struct propstat propstat[], void *rock);
126
127 static int report_prin_prop_search(struct transaction_t *txn,
128 xmlNodePtr inroot,
129 struct propfind_ctx *fctx);
130 static int report_prin_search_prop_set(struct transaction_t *txn,
131 xmlNodePtr inroot,
132 struct propfind_ctx *fctx);
118133
119134 static int allprop_cb(const char *mailbox __attribute__((unused)),
120135 const char *entry,
126141
127142 /* WebDAV (RFC 4918) properties */
128143 { "creationdate", NS_DAV, PROP_ALLPROP, NULL, NULL, NULL },
129 { "displayname", NS_DAV, PROP_ALLPROP, NULL, NULL, NULL },
144 { "displayname", NS_DAV, PROP_ALLPROP | PROP_COLLECTION,
145 propfind_displayname, NULL, NULL },
130146 { "getcontentlanguage", NS_DAV, PROP_ALLPROP, NULL, NULL, NULL },
131147 { "getcontentlength", NS_DAV, PROP_ALLPROP | PROP_COLLECTION,
132148 propfind_getlength, NULL, NULL },
145161 propfind_reportset, NULL, NULL },
146162
147163 /* WebDAV ACL (RFC 3744) properties */
148 { "alternate-URI-set", NS_DAV, 0, NULL, NULL, NULL },
164 { "alternate-URI-set", NS_DAV, PROP_COLLECTION,
165 propfind_alturiset, NULL, NULL },
149166 { "principal-URL", NS_DAV, PROP_COLLECTION,
150167 propfind_principalurl, NULL, NULL },
151168 { "group-member-set", NS_DAV, 0, NULL, NULL, NULL },
168185 propfind_calurl, NULL, SCHED_OUTBOX },
169186 { "calendar-user-address-set", NS_CALDAV, PROP_COLLECTION,
170187 propfind_caluseraddr, NULL, NULL },
171 { "calendar-user-type", NS_CALDAV, 0, NULL, NULL, NULL },
188 { "calendar-user-type", NS_CALDAV, PROP_COLLECTION,
189 propfind_calusertype, NULL, NULL },
172190
173191 /* CardDAV (RFC 6352) properties */
174192 { "addressbook-home-set", NS_CARDDAV, PROP_COLLECTION,
183201
184202 static struct meth_params princ_params = {
185203 .parse_path = &prin_parse_path,
186 .lprops = dav_props
204 .lprops = dav_props,
205 .reports = {
206 { "principal-property-search", "multistatus",
207 &report_prin_prop_search, 0, 0 },
208 { "principal-search-property-set", "principal-search-property-set",
209 &report_prin_search_prop_set, 0, 0 },
210 { NULL, NULL, NULL, 0, 0 }
211 }
187212 };
188213
189214 /* Namespace for WebDAV principals */
206231 { &meth_propfind, &princ_params }, /* PROPFIND */
207232 { NULL, NULL }, /* PROPPATCH */
208233 { NULL, NULL }, /* PUT */
209 { &meth_report, NULL }, /* REPORT */
234 { &meth_report, &princ_params }, /* REPORT */
210235 { &meth_trace, NULL }, /* TRACE */
211236 { NULL, NULL } /* UNLOCK */
212237 }
215240
216241 static void my_dav_init(struct buf *serverinfo)
217242 {
218 if (config_httpmodules & IMAP_ENUM_HTTPMODULES_CALDAV) {
219 namespace_principal.enabled = 1;
220 namespace_principal.allow |= ALLOW_CAL;
221 if (config_getswitch(IMAPOPT_CALDAV_ALLOWSCHEDULING))
222 namespace_principal.allow |= ALLOW_CAL_SCHED;
223 }
224 if (config_httpmodules & IMAP_ENUM_HTTPMODULES_CARDDAV) {
225 namespace_principal.enabled = 1;
226 namespace_principal.allow |= ALLOW_CARD;
227 }
228
229 if (!namespace_principal.enabled) return;
230
231 if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) {
232 buf_printf(serverinfo, " SQLite/%s", sqlite3_libversion());
233 }
243 buf_printf(serverinfo, " SQLite/%s", sqlite3_libversion());
234244 }
235245
236246
300310 { "no-uid-conflict", NS_CALDAV },
301311 { "supported-filter", NS_CALDAV },
302312 { "valid-filter", NS_CALDAV },
313
314 /* RSCALE (draft-daboo-icalendar-rscale) preconditions */
315 { "supported-rscale", NS_CALDAV },
303316
304317 /* CalDAV Scheduling (RFC 6638) preconditions */
305318 { "valid-scheduling-message", NS_CALDAV },
483496 int ensure_ns(xmlNsPtr *respNs, int ns, xmlNodePtr node,
484497 const char *url, const char *prefix)
485498 {
486 if (!respNs[ns])
499 if (!respNs[ns]) {
500 xmlNsPtr nsDef;
501 char myprefix[20];
502
503 /* Search for existing namespace using our prefix */
504 for (nsDef = node->nsDef; nsDef; nsDef = nsDef->next) {
505 if ((!nsDef->prefix && !prefix) ||
506 (nsDef->prefix && prefix &&
507 !strcmp((const char *) nsDef->prefix, prefix))) break;
508 }
509
510 if (nsDef) {
511 /* Prefix is already used - generate a new one */
512 snprintf(myprefix, sizeof(myprefix), "X%X", strhash(url) & 0xffff);
513 prefix = myprefix;
514 }
515
487516 respNs[ns] = xmlNewNs(node, BAD_CAST url, BAD_CAST prefix);
517 }
488518
489519 /* XXX check for errors */
490520 return 0;
561591
562592 xmlNodePtr xml_add_href(xmlNodePtr parent, xmlNsPtr ns, const char *href)
563593 {
564 xmlChar *uri = xmlURIEscapeStr(BAD_CAST href, BAD_CAST ":/");
594 xmlChar *uri = xmlURIEscapeStr(BAD_CAST href, BAD_CAST ":/?=");
565595 xmlNodePtr node = xmlNewChild(parent, ns, BAD_CAST "href", uri);
566596
567597 free(uri);
699729
700730 /* Add a response tree to 'root' for the specified href and
701731 either error code or property list */
702 static int xml_add_response(struct propfind_ctx *fctx, long code)
732 int xml_add_response(struct propfind_ctx *fctx, long code, unsigned precond)
703733 {
704734 xmlNodePtr resp;
705735
714744 if (code) {
715745 xmlNewChild(resp, NULL, BAD_CAST "status",
716746 BAD_CAST http_statusline(code));
747
748 if (precond) {
749 xmlNodePtr error = xmlNewChild(resp, NULL, BAD_CAST "error", NULL);
750
751 xmlNewChild(error, NULL, BAD_CAST preconds[precond].name, NULL);
752 }
717753 }
718754 else {
719755 struct propstat propstat[NUM_PROPSTAT], *stat;
925961 }
926962
927963
964 /* Callback to fetch DAV:displayname */
965 static int propfind_displayname(const xmlChar *name, xmlNsPtr ns,
966 struct propfind_ctx *fctx,
967 xmlNodePtr resp __attribute__((unused)),
968 struct propstat propstat[],
969 void *rock __attribute__((unused)))
970 {
971 /* XXX Do LDAP/SQL lookup here */
972
973 buf_reset(&fctx->buf);
974 if (fctx->req_tgt->user) {
975 buf_printf(&fctx->buf, "%.*s",
976 (int) fctx->req_tgt->userlen, fctx->req_tgt->user);
977 }
978
979 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
980 name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
981
982 return 0;
983 }
984
985
928986 /* Callback to fetch DAV:getcontentlength */
929987 int propfind_getlength(const xmlChar *name, xmlNsPtr ns,
930988 struct propfind_ctx *fctx,
10271085 xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
10281086 &propstat[PROPSTAT_OK], name, ns, NULL, 0);
10291087
1030 xmlNewChild(node, NULL, BAD_CAST "collection", NULL);
1088 if (fctx->req_tgt->user)
1089 xmlNewChild(node, NULL, BAD_CAST "principal", NULL);
10311090
10321091 return 0;
10331092 }
11081167 struct propstat propstat[],
11091168 void *rock __attribute__((unused)))
11101169 {
1111 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1112 name, ns, NULL, 0);
1170 xmlNodePtr s, r, top;
1171
1172 top = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
1173 name, ns, NULL, 0);
1174
1175 s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL);
1176 r = xmlNewChild(s, NULL, BAD_CAST "report", NULL);
1177 xmlNewChild(r, fctx->ns[NS_DAV],
1178 BAD_CAST "principal-property-search", NULL);
1179
1180 s = xmlNewChild(top, NULL, BAD_CAST "supported-report", NULL);
1181 r = xmlNewChild(s, NULL, BAD_CAST "report", NULL);
1182 xmlNewChild(r, fctx->ns[NS_DAV],
1183 BAD_CAST "principal-search-property-set", NULL);
11131184
11141185 return 0;
11151186 }
11161187
11171188
1118 /* Callback to fetch DAV:principalurl */
1189 /* Callback to fetch DAV:alternate-URI-set */
1190 static int propfind_alturiset(const xmlChar *name, xmlNsPtr ns,
1191 struct propfind_ctx *fctx,
1192 xmlNodePtr resp __attribute__((unused)),
1193 struct propstat propstat[],
1194 void *rock __attribute__((unused)))
1195 {
1196 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
1197 &propstat[PROPSTAT_OK], name, ns, NULL, 0);
1198
1199 return 0;
1200 }
1201
1202
1203 /* Callback to fetch DAV:principal-URL */
11191204 static int propfind_principalurl(const xmlChar *name, xmlNsPtr ns,
11201205 struct propfind_ctx *fctx,
11211206 xmlNodePtr resp __attribute__((unused)),
14051490 set = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
14061491 name, ns, NULL, 0);
14071492
1408 if (fctx->req_tgt->collection) {
1493 if (!fctx->req_tgt->resource) {
14091494 if (fctx->req_tgt->namespace == URL_NS_CALENDAR) {
14101495 flags = PRIV_IMPLICIT;
14111496
1412 if (!strcmp(fctx->req_tgt->collection, SCHED_INBOX))
1413 flags = PRIV_INBOX;
1414 else if (!strcmp(fctx->req_tgt->collection, SCHED_OUTBOX))
1415 flags = PRIV_OUTBOX;
1497 if (fctx->req_tgt->collection) {
1498 if (!strcmp(fctx->req_tgt->collection, SCHED_INBOX))
1499 flags = PRIV_INBOX;
1500 else if (!strcmp(fctx->req_tgt->collection, SCHED_OUTBOX))
1501 flags = PRIV_OUTBOX;
1502 }
14161503 }
14171504
14181505 add_privs(rights, flags, set, resp->parent, fctx->ns);
14351522 unsigned flags = 0;
14361523
14371524 if (!fctx->mailbox) return HTTP_NOT_FOUND;
1438 if (!((rights = cyrus_acl_myrights(fctx->authstate, fctx->mailbox->acl))
1439 & DACL_ADMIN)) {
1440 return HTTP_UNAUTHORIZED;
1525 /* owner has explicit admin rights */
1526 if (!mboxname_userownsmailbox(httpd_userid, fctx->mailbox->name)) {
1527 if (!((rights = cyrus_acl_myrights(fctx->authstate, fctx->mailbox->acl))
1528 & DACL_ADMIN)) {
1529 return HTTP_UNAUTHORIZED;
1530 }
14411531 }
14421532
14431533 if (fctx->req_tgt->namespace == URL_NS_CALENDAR) {
14661556 rightstr = strchr(userid, '\t');
14671557 if (!rightstr) break;
14681558 *rightstr++ = '\0';
1469
1559
14701560 nextid = strchr(rightstr, '\t');
14711561 if (!nextid) break;
14721562 *nextid++ = '\0';
14911581 node = xmlNewChild(ace, NULL, BAD_CAST "principal", NULL);
14921582 if (!strcmp(userid, fctx->userid))
14931583 xmlNewChild(node, NULL, BAD_CAST "self", NULL);
1494 else if ((strlen(userid) == fctx->req_tgt->userlen) &&
1495 !strncmp(userid, fctx->req_tgt->user, fctx->req_tgt->userlen))
1584 else if (mboxname_userownsmailbox(userid, fctx->mailbox->name))
14961585 xmlNewChild(node, NULL, BAD_CAST "owner", NULL);
14971586 else if (!strcmp(userid, "anyone"))
14981587 xmlNewChild(node, NULL, BAD_CAST "authenticated", NULL);
1588 /* XXX - well, it's better than a user called 'anonymous' */
1589 else if (!strcmp(userid, "anonymous"))
1590 xmlNewChild(node, NULL, BAD_CAST "unauthenticated", NULL);
14991591 else {
15001592 buf_reset(&fctx->buf);
15011593 buf_printf(&fctx->buf, "%s/user/%s/",
16551747 xmlNodePtr node;
16561748 int len;
16571749
1658 if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
1750 if (!fctx->req_tgt->collection ||
1751 !strcmp(fctx->req_tgt->collection, SCHED_INBOX) ||
1752 !strcmp(fctx->req_tgt->collection, SCHED_OUTBOX)) {
1753 /* Only allowed on non-scheduling collections */
1754 return HTTP_NOT_FOUND;
1755 }
16591756
16601757 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
16611758 name, ns, NULL, 0);
16641761 (size_t) (fctx->req_tgt->resource - fctx->req_tgt->path) :
16651762 strlen(fctx->req_tgt->path);
16661763 buf_reset(&fctx->buf);
1667 buf_printf(&fctx->buf, "%.*s", len, fctx->req_tgt->path);
1764 buf_printf(&fctx->buf, "%.*s?action=add-member", len, fctx->req_tgt->path);
16681765
16691766 xml_add_href(node, NULL, buf_cstring(&fctx->buf));
16701767
16791776 struct propstat propstat[],
16801777 void *rock __attribute__((unused)))
16811778 {
1682 if (!fctx->mailbox || fctx->record) return HTTP_NOT_FOUND;
1779 if (!fctx->req_tgt->collection || /* until we support sync on cal-home */
1780 !fctx->mailbox || fctx->record) return HTTP_NOT_FOUND;
16831781
16841782 buf_reset(&fctx->buf);
1685 buf_printf(&fctx->buf, XML_NS_CYRUS "sync/%u-" MODSEQ_FMT,
1783 buf_printf(&fctx->buf, SYNC_TOKEN_URL_SCHEME "%u-" MODSEQ_FMT,
16861784 fctx->mailbox->i.uidvalidity,
16871785 fctx->mailbox->i.highestmodseq);
16881786
20862184
20872185 /* Read body */
20882186 txn->req_body.flags |= BODY_DECODE;
2089 r = read_body(httpd_in, txn->req_hdrs, &txn->req_body, &txn->error.desc);
2187 r = http_read_body(httpd_in, httpd_out,
2188 txn->req_hdrs, &txn->req_body, &txn->error.desc);
20902189 if (r) {
20912190 txn->flags.conn = CONN_CLOSE;
20922191 return r;
21782277 }
21792278 }
21802279
2181 /* Check ACL for current user */
2182 rights = aclstr ? cyrus_acl_myrights(httpd_authstate, aclstr) : 0;
2183 if (!(rights & DACL_ADMIN)) {
2184 /* DAV:need-privileges */
2185 txn->error.precond = DAV_NEED_PRIVS;
2186 txn->error.resource = txn->req_tgt.path;
2187 txn->error.rights = DACL_ADMIN;
2188 return HTTP_FORBIDDEN;
2280 if (!mboxname_userownsmailbox(httpd_userid, txn->req_tgt.mboxname)) {
2281 /* Check ACL for current user */
2282 rights = aclstr ? cyrus_acl_myrights(httpd_authstate, aclstr) : 0;
2283 if (!(rights & DACL_ADMIN)) {
2284 /* DAV:need-privileges */
2285 txn->error.precond = DAV_NEED_PRIVS;
2286 txn->error.resource = txn->req_tgt.path;
2287 txn->error.rights = DACL_ADMIN;
2288 return HTTP_NO_PRIVS;
2289 }
21892290 }
21902291
21912292 if (server) {
22022303 /* Local Mailbox */
22032304
22042305 /* Open mailbox for writing */
2205 if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_EXCLUSIVE))) {
2306 r = mailbox_open_iwl(txn->req_tgt.mboxname, &mailbox);
2307 if (r) {
22062308 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
22072309 txn->req_tgt.mboxname, error_message(r));
22082310 txn->error.desc = error_message(r);
22452347 goto done;
22462348 }
22472349
2248 for (prin = child->children;
2350 for (prin = child->children; prin &&
22492351 prin->type != XML_ELEMENT_NODE; prin = prin->next);
22502352 }
22512353 else if (!xmlStrcmp(child->name, BAD_CAST "grant")) {
22552357 goto done;
22562358 }
22572359
2258 for (privs = child->children;
2360 for (privs = child->children; privs &&
22592361 privs->type != XML_ELEMENT_NODE; privs = privs->next);
22602362 }
22612363 else if (!xmlStrcmp(child->name, BAD_CAST "deny")) {
22652367 goto done;
22662368 }
22672369
2268 for (privs = child->children;
2370 for (privs = child->children; privs &&
22692371 privs->type != XML_ELEMENT_NODE; privs = privs->next);
22702372 deny = 1;
22712373 }
22862388 if (!xmlStrcmp(prin->name, BAD_CAST "self")) {
22872389 userid = proxy_userid;
22882390 }
2289 #if 0 /* XXX Do we need to support this? */
22902391 else if (!xmlStrcmp(prin->name, BAD_CAST "owner")) {
2291 /* XXX construct userid from mailbox name */
2392 userid = mboxname_to_userid(mailbox->name);
22922393 }
2293 #endif
22942394 else if (!xmlStrcmp(prin->name, BAD_CAST "authenticated")) {
22952395 userid = "anyone";
22962396 }
23062406 uri->path[plen] == '/') {
23072407 memset(&tgt, 0, sizeof(struct request_target_t));
23082408 tgt.namespace = URL_NS_PRINCIPAL;
2409 /* XXX: there is no doubt that this leaks memory */
23092410 r = prin_parse_path(uri->path, &tgt, &errstr);
23102411 if (!r && tgt.user) userid = tgt.user;
23112412 }
23372438 BAD_CAST XML_NS_DAV)) {
23382439 /* WebDAV privileges */
23392440 if (!xmlStrcmp(priv->name,
2340 BAD_CAST "all"))
2341 rights |= DACL_ALL;
2441 BAD_CAST "all")) {
2442 if (deny)
2443 rights |= ACL_FULL; /* wipe EVERYTHING */
2444 else
2445 rights |= DACL_ALL;
2446 }
23422447 else if (!xmlStrcmp(priv->name,
23432448 BAD_CAST "read"))
23442449 rights |= DACL_READ;
24312536 done:
24322537 buf_free(&acl);
24332538 if (indoc) xmlFreeDoc(indoc);
2434 if (mailbox) mailbox_unlock_index(mailbox, NULL);
2539 mailbox_close(&mailbox);
24352540
24362541 return ret;
24372542 }
24482553 int ret = HTTP_CREATED, r, precond, rights, overwrite = OVERWRITE_YES;
24492554 const char **hdr;
24502555 xmlURIPtr dest_uri;
2451 struct request_target_t dest_tgt; /* Parsed destination URL */
2556 static struct request_target_t dest_tgt; /* Parsed destination URL */
24522557 char *server, *acl;
24532558 struct backend *src_be = NULL, *dest_be = NULL;
24542559 struct mailbox *src_mbox = NULL, *dest_mbox = NULL;
24572562 const char *etag = NULL;
24582563 time_t lastmod = 0;
24592564 unsigned flags = 0;
2565 void *src_davdb = NULL, *dest_davdb = NULL;
24602566
24612567 /* Response should not be cached */
24622568 txn->flags.cc |= CC_NOCACHE;
25192625 txn->error.resource = txn->req_tgt.path;
25202626 txn->error.rights =
25212627 (rights & DACL_READ) != DACL_READ ? DACL_READ : DACL_RMRSRC;
2522 return HTTP_FORBIDDEN;
2628 return HTTP_NO_PRIVS;
25232629 }
25242630
25252631 if (server) {
25502656 txn->error.resource = dest_tgt.path;
25512657 txn->error.rights =
25522658 !(rights & DACL_ADDRSRC) ? DACL_ADDRSRC : DACL_WRITECONT;
2553 return HTTP_FORBIDDEN;
2659 return HTTP_NO_PRIVS;
25542660 }
25552661
25562662 if (server) {
25812687
25822688 /* Local Mailbox */
25832689
2584 if (!*cparams->davdb.db) {
2585 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
2586 "Check 'configdirectory' permissions or "
2587 "'proxyservers' option on backend server.", proxy_userid);
2588 txn->error.desc = "DAV database is not opened";
2589 return HTTP_SERVER_ERROR;
2590 }
2591
25922690 /* Open dest mailbox for reading */
2593 if ((r = mailbox_open_irl(dest_tgt.mboxname, &dest_mbox))) {
2691 r = mailbox_open_irl(dest_tgt.mboxname, &dest_mbox);
2692 if (r) {
25942693 syslog(LOG_ERR, "mailbox_open_irl(%s) failed: %s",
25952694 dest_tgt.mboxname, error_message(r));
25962695 txn->error.desc = error_message(r);
25982697 goto done;
25992698 }
26002699
2700 /* Open the DAV DB corresponding to the dest mailbox */
2701 dest_davdb = cparams->davdb.open_db(dest_mbox);
2702
26012703 /* Find message UID for the dest resource, if exists */
2602 cparams->davdb.lookup_resource(*cparams->davdb.db, dest_tgt.mboxname,
2704 cparams->davdb.lookup_resource(dest_davdb, dest_tgt.mboxname,
26032705 dest_tgt.resource, 0, (void **) &ddata);
26042706 /* XXX Check errors */
26052707
26192721 }
26202722
26212723 /* Open source mailbox for reading */
2622 if ((r = http_mailbox_open(txn->req_tgt.mboxname, &src_mbox, LOCK_SHARED))) {
2724 r = mailbox_open_irl(txn->req_tgt.mboxname, &src_mbox);
2725 if (r) {
26232726 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
26242727 txn->req_tgt.mboxname, error_message(r));
26252728 txn->error.desc = error_message(r);
26272730 goto done;
26282731 }
26292732
2733 /* Open the DAV DB corresponding to the src mailbox */
2734 src_davdb = cparams->davdb.open_db(src_mbox);
2735
26302736 /* Find message UID for the source resource */
2631 cparams->davdb.lookup_resource(*cparams->davdb.db, txn->req_tgt.mboxname,
2737 cparams->davdb.lookup_resource(src_davdb, txn->req_tgt.mboxname,
26322738 txn->req_tgt.resource, 0, (void **) &ddata);
26332739 if (!ddata->rowid) {
26342740 ret = HTTP_NOT_FOUND;
26762782
26772783 /* Parse, validate, and store the resource */
26782784 ret = cparams->copy(txn, src_mbox, &src_rec, dest_mbox, dest_tgt.resource,
2679 overwrite, flags);
2785 dest_davdb, overwrite, flags);
26802786
26812787 /* For MOVE, we need to delete the source resource */
26822788 if ((txn->meth == METH_MOVE) &&
26852791 mailbox_lock_index(src_mbox, LOCK_EXCLUSIVE);
26862792
26872793 /* Find message UID for the source resource */
2688 cparams->davdb.lookup_resource(*cparams->davdb.db, txn->req_tgt.mboxname,
2794 cparams->davdb.lookup_resource(src_davdb, txn->req_tgt.mboxname,
26892795 txn->req_tgt.resource, 1, (void **) &ddata);
26902796 /* XXX Check errors */
26912797
27032809 goto done;
27042810 }
27052811 }
2706
2707 /* Delete mapping entry for source resource name */
2708 cparams->davdb.delete_resource(*cparams->davdb.db, ddata->rowid, 1);
27092812 }
27102813
27112814 done:
27182821 txn->resp_body.etag = NULL;
27192822 }
27202823
2721 if (dest_mbox) mailbox_close(&dest_mbox);
2722 if (src_mbox) mailbox_unlock_index(src_mbox, NULL);
2824 if (dest_davdb) cparams->davdb.close_db(dest_davdb);
2825 mailbox_close(&dest_mbox);
2826 if (src_davdb) cparams->davdb.close_db(src_davdb);
2827 mailbox_close(&src_mbox);
27232828
27242829 return ret;
27252830 }
27362841 struct index_record record;
27372842 const char *etag = NULL;
27382843 time_t lastmod = 0;
2844 void *davdb = NULL;
27392845
27402846 /* Response should not be cached */
27412847 txn->flags.cc |= CC_NOCACHE;
27622868
27632869 /* Check ACL for current user */
27642870 rights = acl ? cyrus_acl_myrights(httpd_authstate, acl) : 0;
2765 if ((txn->req_tgt.resource && !(rights & DACL_RMRSRC)) ||
2766 !(rights & DACL_RMCOL)) {
2871 if (txn->req_tgt.resource) {
2872 if (!(rights & DACL_RMRSRC)) txn->error.rights = DACL_RMRSRC;
2873 }
2874 else if (!(rights & DACL_RMCOL)) txn->error.rights = DACL_RMCOL;
2875
2876 if (txn->error.rights) {
27672877 /* DAV:need-privileges */
27682878 txn->error.precond = DAV_NEED_PRIVS;
27692879 txn->error.resource = txn->req_tgt.path;
2770 txn->error.rights = txn->req_tgt.resource ? DACL_RMRSRC : DACL_RMCOL;
2771 return HTTP_FORBIDDEN;
2880 return HTTP_NO_PRIVS;
27722881 }
27732882
27742883 if (server) {
27842893
27852894 /* Local Mailbox */
27862895
2787 if (!*dparams->davdb.db) {
2788 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
2789 "Check 'configdirectory' permissions or "
2790 "'proxyservers' option on backend server.", proxy_userid);
2791 txn->error.desc = "DAV database is not opened";
2792 return HTTP_SERVER_ERROR;
2793 }
2794
27952896 if (!txn->req_tgt.resource) {
27962897 /* DELETE collection */
27972898
2899 /* Open mailbox for reading */
2900 r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox);
2901 if (r) {
2902 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
2903 txn->req_tgt.mboxname, error_message(r));
2904 txn->error.desc = error_message(r);
2905 return HTTP_SERVER_ERROR;
2906 }
2907
2908 /* Open the DAV DB corresponding to the mailbox */
2909 davdb = dparams->davdb.open_db(mailbox);
2910
27982911 /* Do any special processing */
2799 if (dparams->delete) dparams->delete(txn, NULL, NULL, NULL);
2912 if (dparams->delete) dparams->delete(txn, mailbox, NULL, NULL);
2913
2914 mailbox_close(&mailbox);
28002915
28012916 r = mboxlist_deletemailbox(txn->req_tgt.mboxname,
28022917 httpd_userisadmin || httpd_userisproxyadmin,
28032918 httpd_userid, httpd_authstate,
28042919 1, 0, 0);
2805
2806 if (!r) dparams->davdb.delete_mbox(*dparams->davdb.db, txn->req_tgt.mboxname, 0);
2920 if (!r) dparams->davdb.delete_mbox(davdb, txn->req_tgt.mboxname, 0);
28072921 else if (r == IMAP_PERMISSION_DENIED) ret = HTTP_FORBIDDEN;
28082922 else if (r == IMAP_MAILBOX_NONEXISTENT) ret = HTTP_NOT_FOUND;
28092923 else if (r) ret = HTTP_SERVER_ERROR;
28102924
2925 dparams->davdb.close_db(davdb);
2926
28112927 return ret;
28122928 }
28132929
28152931 /* DELETE resource */
28162932
28172933 /* Open mailbox for writing */
2818 if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_EXCLUSIVE))) {
2934 r = mailbox_open_iwl(txn->req_tgt.mboxname, &mailbox);
2935 if (r) {
28192936 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
28202937 txn->req_tgt.mboxname, error_message(r));
28212938 txn->error.desc = error_message(r);
28232940 goto done;
28242941 }
28252942
2943 /* Open the DAV DB corresponding to the mailbox */
2944 davdb = dparams->davdb.open_db(mailbox);
2945
28262946 /* Find message UID for the resource, if exists */
2827 dparams->davdb.lookup_resource(*dparams->davdb.db, txn->req_tgt.mboxname,
2828 txn->req_tgt.resource, 1, (void **) &ddata);
2947 dparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname,
2948 txn->req_tgt.resource, 0, (void **) &ddata);
28292949 if (!ddata->rowid) {
28302950 ret = HTTP_NOT_FOUND;
28312951 goto done;
28803000 }
28813001 }
28823002
2883 /* Delete mapping entry for resource name */
2884 dparams->davdb.delete_resource(*dparams->davdb.db, ddata->rowid, 1);
2885
28863003 /* Do any special processing */
28873004 if (dparams->delete) dparams->delete(txn, mailbox, &record, ddata);
28883005
28893006 done:
2890 if (mailbox) mailbox_unlock_index(mailbox, NULL);
3007 if (davdb) dparams->davdb.close_db(davdb);
3008 mailbox_close(&mailbox);
28913009
28923010 return ret;
28933011 }
29093027 struct index_record record;
29103028 const char *etag = NULL;
29113029 time_t lastmod = 0;
3030 void *davdb = NULL;
29123031
29133032 /* Parse the path */
29143033 if ((r = gparams->parse_path(txn->req_uri->path,
29443063 txn->error.precond = DAV_NEED_PRIVS;
29453064 txn->error.resource = txn->req_tgt.path;
29463065 txn->error.rights = DACL_READ;
2947 return HTTP_FORBIDDEN;
3066 return HTTP_NO_PRIVS;
29483067 }
29493068
29503069 if (server) {
29603079
29613080 /* Local Mailbox */
29623081
2963 if (!*gparams->davdb.db) {
2964 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
2965 "Check 'configdirectory' permissions or "
2966 "'proxyservers' option on backend server.", proxy_userid);
2967 txn->error.desc = "DAV database is not opened";
2968 return HTTP_SERVER_ERROR;
2969 }
2970
29713082 /* Open mailbox for reading */
2972 if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) {
3083 r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox);
3084 if (r) {
29733085 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
29743086 txn->req_tgt.mboxname, error_message(r));
29753087 txn->error.desc = error_message(r);
29773089 goto done;
29783090 }
29793091
3092 /* Open the DAV DB corresponding to the mailbox */
3093 davdb = gparams->davdb.open_db(mailbox);
3094
29803095 /* Find message UID for the resource */
2981 gparams->davdb.lookup_resource(*gparams->davdb.db, txn->req_tgt.mboxname,
3096 gparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname,
29823097 txn->req_tgt.resource, 0, (void **) &ddata);
29833098 if (!ddata->rowid) {
29843099 ret = HTTP_NOT_FOUND;
30633178 if (freeme) free(freeme);
30643179
30653180 done:
3066 if (mailbox) mailbox_unlock_index(mailbox, NULL);
3181 if (davdb) gparams->davdb.close_db(davdb);
3182 mailbox_close(&mailbox);
30673183
30683184 return ret;
30693185 }
30913207 xmlNsPtr ns[NUM_NAMESPACE];
30923208 xmlChar *owner = NULL;
30933209 time_t now = time(NULL);
3210 void *davdb = NULL;
30943211
30953212 /* XXX We ignore Depth and Timeout header fields */
30963213
31253242 txn->error.resource = txn->req_tgt.path;
31263243 txn->error.rights =
31273244 !(rights & DACL_WRITECONT) ? DACL_WRITECONT : DACL_ADDRSRC;
3128 return HTTP_FORBIDDEN;
3245 return HTTP_NO_PRIVS;
31293246 }
31303247
31313248 if (server) {
31413258
31423259 /* Local Mailbox */
31433260
3144 if (!*lparams->davdb.db) {
3145 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
3146 "Check 'configdirectory' permissions or "
3147 "'proxyservers' option on backend server.", proxy_userid);
3148 txn->error.desc = "DAV database is not opened";
3149 return HTTP_SERVER_ERROR;
3150 }
3151
31523261 /* Open mailbox for reading */
3153 if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) {
3262 r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox);
3263 if (r) {
31543264 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
31553265 txn->req_tgt.mboxname, error_message(r));
31563266 txn->error.desc = error_message(r);
31583268 goto done;
31593269 }
31603270
3271 /* Open the DAV DB corresponding to the mailbox */
3272 davdb = lparams->davdb.open_db(mailbox);
3273
31613274 /* Find message UID for the resource, if exists */
3162 lparams->davdb.lookup_resource(*lparams->davdb.db, txn->req_tgt.mboxname,
3275 lparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname,
31633276 txn->req_tgt.resource, 1, (void *) &ddata);
31643277
31653278 if (ddata->imap_uid) {
33033416 root = xmlNewChild(root, NULL, BAD_CAST "lockdiscovery", NULL);
33043417 xml_add_lockdisc(root, txn->req_tgt.path, (struct dav_data *) ddata);
33053418
3306 lparams->davdb.write_resource(*lparams->davdb.db, ddata, 1);
3419 lparams->davdb.write_resource(davdb, ddata, 1);
33073420
33083421 txn->resp_body.lock = ddata->lock_token;
33093422
33223435 ret = 0;
33233436
33243437 done:
3325 if (mailbox) mailbox_unlock_index(mailbox, NULL);
3438 if (davdb) lparams->davdb.close_db(davdb);
3439 mailbox_close(&mailbox);
33263440 if (outdoc) xmlFreeDoc(outdoc);
33273441 if (indoc) xmlFreeDoc(indoc);
33283442 if (owner) xmlFree(owner);
33733487 httpd_userisadmin || httpd_userisproxyadmin,
33743488 httpd_userid, httpd_authstate,
33753489 NULL, &partition, 0);
3376
3377 if (r == IMAP_PERMISSION_DENIED) return HTTP_FORBIDDEN;
3378 else if (r == IMAP_MAILBOX_EXISTS) {
3490 switch (r) {
3491 case 0:
3492 break;
3493
3494 case IMAP_MAILBOX_EXISTS:
33793495 txn->error.precond = DAV_RSRC_EXISTS;
3380 return HTTP_FORBIDDEN;
3381 }
3382 else if (r) return HTTP_SERVER_ERROR;
3496 ret = HTTP_FORBIDDEN;
3497 goto done;
3498
3499 case IMAP_PERMISSION_DENIED:
3500 ret = HTTP_NO_PRIVS;
3501 goto done;
3502
3503 default:
3504 ret = HTTP_SERVER_ERROR;
3505 goto done;
3506 }
33833507
33843508 if (!config_partitiondir(partition)) {
33853509 /* Invalid partition, assume its a server (remote mailbox) */
33923516
33933517 be = proxy_findserver(server, &http_protocol, proxy_userid,
33943518 &backend_cached, NULL, NULL, httpd_in);
3395 if (!be) return HTTP_UNAVAILABLE;
3396
3397 return http_pipe_req_resp(be, txn);
3519
3520 if (!be) ret = HTTP_UNAVAILABLE;
3521 else ret = http_pipe_req_resp(be, txn);
3522
3523 goto done;
33983524 }
33993525
34003526 /* Local Mailbox */
34133539 r = xmlStrcmp(root->name, BAD_CAST mparams->mkcol.xml_req);
34143540 if (r) {
34153541 txn->error.desc = "Incorrect root element in XML request\r\n";
3416 return HTTP_BAD_MEDIATYPE;
3542 ret = HTTP_BAD_MEDIATYPE;
3543 goto done;
34173544 }
34183545
34193546 instr = root->children;
34533580 annotatemore_abort(pctx.tid);
34543581
34553582 if (!ret) {
3583 /* Error response MUST be a multistatus */
3584 xmlNodeSetName(root, BAD_CAST "multistatus");
3585 xmlSetNs(root, ns[NS_DAV]);
3586
34563587 /* Output the XML response */
3457 xml_response(HTTP_FORBIDDEN, txn, outdoc);
3588 xml_response(HTTP_MULTI_STATUS, txn, outdoc);
34583589 ret = 0;
34593590 }
34603591
34703601 0, 0, 0);
34713602
34723603 if (!r) ret = HTTP_CREATED;
3473 else if (r == IMAP_PERMISSION_DENIED) ret = HTTP_FORBIDDEN;
3604 else if (r == IMAP_PERMISSION_DENIED) ret = HTTP_NO_PRIVS;
34743605 else if (r == IMAP_MAILBOX_EXISTS) {
34753606 txn->error.precond = DAV_RSRC_EXISTS;
34763607 ret = HTTP_FORBIDDEN;
34943625 done:
34953626 buf_free(&pctx.buf);
34963627
3628 if (partition) free(partition);
34973629 if (outdoc) xmlFreeDoc(outdoc);
34983630 if (indoc) xmlFreeDoc(indoc);
34993631
35413673
35423674 if (!ddata->imap_uid || !fctx->record) {
35433675 /* Add response for missing target */
3544 ret = xml_add_response(fctx, HTTP_NOT_FOUND);
3676 ret = xml_add_response(fctx, HTTP_NOT_FOUND, 0);
35453677 }
35463678 else {
35473679 int add_it = 1;
35503682
35513683 if (add_it) {
35523684 /* Add response for target */
3553 ret = xml_add_response(fctx, 0);
3685 ret = xml_add_response(fctx, 0, 0);
35543686 }
35553687 }
35563688
36293761
36303762 /* If not filtering by calendar resource, and not excluding root,
36313763 add response for collection */
3632 if (!fctx->filter &&
3764 if (!fctx->filter_crit &&
36333765 (!root || (fctx->depth == 1) || !(fctx->prefer & PREFER_NOROOT)) &&
3634 (r = xml_add_response(fctx, 0))) goto done;
3766 (r = xml_add_response(fctx, 0, 0))) goto done;
36353767 }
36363768
36373769 if (fctx->depth > 1) {
36383770 /* Resource(s) */
3771
3772 /* Open the DAV DB corresponding to the mailbox.
3773 *
3774 * Note we open the new one first before closing the old one, so we
3775 * get refcounted retaining of the open database within a single user */
3776 sqlite3 *newdb = fctx->open_db(mailbox);
3777 if (fctx->davdb) fctx->close_db(fctx->davdb);
3778 fctx->davdb = newdb;
36393779
36403780 if (fctx->req_tgt->resource) {
36413781 /* Add response for target resource */
37243864 txn->error.precond = DAV_NEED_PRIVS;
37253865 txn->error.resource = txn->req_tgt.path;
37263866 txn->error.rights = DACL_READ;
3727 ret = HTTP_FORBIDDEN;
3867 ret = HTTP_NO_PRIVS;
37283868 goto done;
37293869 }
37303870
37403880 }
37413881
37423882 /* Local Mailbox */
3743 if (!*fparams->davdb.db) {
3744 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
3745 "Check 'configdirectory' permissions or "
3746 "'proxyservers' option on backend server.", proxy_userid);
3747 txn->error.desc = "DAV database is not opened";
3748 return HTTP_SERVER_ERROR;
3749 }
37503883 }
37513884
37523885 /* Principal or Local Mailbox */
38423975 fctx.reqd_privs = DACL_READ;
38433976 fctx.filter = NULL;
38443977 fctx.filter_crit = NULL;
3845 if (fparams->davdb.db) {
3846 fctx.davdb = *fparams->davdb.db;
3847 fctx.lookup_resource = fparams->davdb.lookup_resource;
3848 fctx.foreach_resource = fparams->davdb.foreach_resource;
3849 }
3978 fctx.open_db = fparams->davdb.open_db;
3979 fctx.close_db = fparams->davdb.close_db;
3980 fctx.lookup_resource = fparams->davdb.lookup_resource;
3981 fctx.foreach_resource = fparams->davdb.foreach_resource;
38503982 fctx.proc_by_resource = &propfind_by_resource;
38513983 fctx.elist = NULL;
38523984 fctx.lprops = fparams->lprops;
38774009 fctx.mailbox = mailbox;
38784010 }
38794011
3880 xml_add_response(&fctx, 0);
4012 xml_add_response(&fctx, 0, 0);
38814013
38824014 mailbox_close(&mailbox);
38834015 }
38964028 txn->req_tgt.mboxname, 1, httpd_userid,
38974029 httpd_authstate, propfind_by_collection, &fctx);
38984030 }
4031
4032 if (fctx.davdb) fctx.close_db(fctx.davdb);
38994033
39004034 ret = *fctx.ret;
39014035 }
39804114 txn->error.precond = DAV_NEED_PRIVS;
39814115 txn->error.resource = txn->req_tgt.path;
39824116 txn->error.rights = DACL_WRITEPROPS;
3983 return HTTP_FORBIDDEN;
4117 return HTTP_NO_PRIVS;
39844118 }
39854119
39864120 if (server) {
40744208 {
40754209 struct meth_params *pparams = (struct meth_params *) params;
40764210 static unsigned post_count = 0;
4211 struct strlist *action;
40774212 int r, ret;
40784213 size_t len;
40794214
40934228 if (ret != HTTP_CONTINUE) return ret;
40944229 }
40954230
4096 /* POST to regular collection */
4231 action = hash_lookup("action", &txn->req_qparams);
4232 if (!action || action->next || strcmp(action->s, "add-member"))
4233 return HTTP_FORBIDDEN;
4234
4235 /* POST add-member to regular collection */
40974236
40984237 /* Append a unique resource name to URL path and perform a PUT */
40994238 len = strlen(txn->req_tgt.path);
41324271 time_t lastmod;
41334272 uquota_t size = 0;
41344273 unsigned flags = 0;
4274 void *davdb = NULL;
41354275
41364276 if (txn->meth == METH_PUT) {
41374277 /* Response should not be cached */
41834323 txn->error.resource = txn->req_tgt.path;
41844324 txn->error.rights =
41854325 !(rights & DACL_WRITECONT) ? DACL_WRITECONT : DACL_ADDRSRC;
4186 return HTTP_FORBIDDEN;
4326 return HTTP_NO_PRIVS;
41874327 }
41884328
41894329 if (server) {
41994339
42004340 /* Local Mailbox */
42014341
4202 if (!*pparams->davdb.db) {
4203 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
4204 "Check 'configdirectory' permissions or "
4205 "'proxyservers' option on backend server.", proxy_userid);
4206 txn->error.desc = "DAV database is not opened";
4207 return HTTP_SERVER_ERROR;
4208 }
4209
42104342 /* Open mailbox for reading */
4211 if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) {
4343 r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox);
4344 if (r) {
42124345 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
42134346 txn->req_tgt.mboxname, error_message(r));
42144347 txn->error.desc = error_message(r);
42164349 goto done;
42174350 }
42184351
4352 /* Open the DAV DB corresponding to the mailbox */
4353 davdb = pparams->davdb.open_db(mailbox);
4354
42194355 /* Find message UID for the resource, if exists */
4220 pparams->davdb.lookup_resource(*pparams->davdb.db, txn->req_tgt.mboxname,
4356 pparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname,
42214357 txn->req_tgt.resource, 0, (void *) &ddata);
42224358 /* XXX Check errors */
42234359
42704406
42714407 /* Read body */
42724408 txn->req_body.flags |= BODY_DECODE;
4273 ret = read_body(httpd_in, txn->req_hdrs, &txn->req_body, &txn->error.desc);
4409 ret = http_read_body(httpd_in, httpd_out,
4410 txn->req_hdrs, &txn->req_body, &txn->error.desc);
42744411 if (ret) {
42754412 txn->flags.conn = CONN_CLOSE;
42764413 goto done;
42974434 if (get_preferences(txn) & PREFER_REP) flags |= PREFER_REP;
42984435
42994436 /* Parse, validate, and store the resource */
4300 ret = pparams->put.proc(txn, mime, mailbox, flags);
4437 ret = pparams->put.proc(txn, mime, mailbox, davdb, flags);
43014438
43024439 done:
4303 if (mailbox) mailbox_unlock_index(mailbox, NULL);
4440 if (davdb) pparams->davdb.close_db(davdb);
4441 mailbox_close(&mailbox);
43044442
43054443 return ret;
43064444 }
43164454 }
43174455
43184456
4457 /* DAV:sync-collection REPORT */
43194458 int report_sync_col(struct transaction_t *txn,
43204459 xmlNodePtr inroot, struct propfind_ctx *fctx)
43214460 {
43224461 int ret = 0, r, userflag;
43234462 struct mailbox *mailbox = NULL;
43244463 uint32_t uidvalidity = 0;
4325 modseq_t syncmodseq = 0, highestmodseq;
4464 modseq_t syncmodseq = 0, basemodseq = 0, highestmodseq, respmodseq;
43264465 uint32_t limit = -1, recno, nresp;
43274466 xmlNodePtr node;
43284467 struct index_state istate;
43344473 istate.map = NULL;
43354474
43364475 /* Open mailbox for reading */
4337 if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) {
4476 r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox);
4477 if (r) {
43384478 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
43394479 txn->req_tgt.mboxname, error_message(r));
43404480 txn->error.desc = error_message(r);
43544494 if (node->type == XML_ELEMENT_NODE) {
43554495 if (!xmlStrcmp(node->name, BAD_CAST "sync-token") &&
43564496 (str = xmlNodeListGetString(inroot->doc, node->children, 1))) {
4357 if (xmlStrncmp(str, BAD_CAST XML_NS_CYRUS "sync/",
4358 strlen(XML_NS_CYRUS "sync/")) ||
4359 (sscanf(strrchr((char *) str, '/') + 1,
4360 "%u-" MODSEQ_FMT,
4361 &uidvalidity, &syncmodseq) != 2) ||
4362 !syncmodseq ||
4497 /* Parse sync-token */
4498 r = sscanf((char *) str, SYNC_TOKEN_URL_SCHEME
4499 "%u-" MODSEQ_FMT "-" MODSEQ_FMT "%1s",
4500 &uidvalidity, &syncmodseq, &basemodseq,
4501 tokenuri /* test for trailing junk */);
4502
4503 /* Sanity check the token components */
4504 if (r < 2 || r > 3 ||
43634505 (uidvalidity != mailbox->i.uidvalidity) ||
4364 (syncmodseq < mailbox->i.deletedmodseq) ||
43654506 (syncmodseq > highestmodseq)) {
4507 fctx->err->desc = "Invalid sync-token";
4508 }
4509 else if (r == 3) {
4510 /* Previous partial read token */
4511 if (basemodseq > highestmodseq) {
4512 fctx->err->desc = "Invalid sync-token";
4513 }
4514 else if (basemodseq < mailbox->i.deletedmodseq) {
4515 fctx->err->desc = "Stale sync-token";
4516 }
4517 }
4518 else {
4519 /* Regular token */
4520 if (syncmodseq < mailbox->i.deletedmodseq) {
4521 fctx->err->desc = "Stale sync-token";
4522 }
4523 }
4524
4525 if (fctx->err->desc) {
43664526 /* DAV:valid-sync-token */
43674527 txn->error.precond = DAV_SYNC_TOKEN;
43684528 ret = HTTP_FORBIDDEN;
4369 }
4529 }
43704530 }
43714531 else if (!xmlStrcmp(node->name, BAD_CAST "sync-level") &&
43724532 (str = xmlNodeListGetString(inroot->doc, node->children, 1))) {
44064566 goto done;
44074567 }
44084568
4569 if (!syncmodseq) {
4570 /* Initial sync - set basemodseq in case client limits results */
4571 basemodseq = highestmodseq;
4572 }
44094573
44104574 /* Construct array of records for sorting and/or fetching cached header */
44114575 istate.mailbox = mailbox;
44324596 continue;
44334597 }
44344598
4435 if (!syncmodseq && (record->system_flags & FLAG_EXPUNGED)) {
4599 if ((record->modseq <= basemodseq) &&
4600 (record->system_flags & FLAG_EXPUNGED)) {
44364601 /* Initial sync - ignore unmapped resources */
44374602 continue;
44384603 }
44564621 if (!nresp) {
44574622 /* DAV:number-of-matches-within-limits */
44584623 fctx->err->desc = "Unable to truncate results";
4459 ret = HTTP_FORBIDDEN; /* HTTP_NO_STORAGE ? */
4624 txn->error.precond = DAV_OVER_LIMIT;
4625 ret = HTTP_NO_STORAGE;
44604626 goto done;
44614627 }
44624628
4463 /* highestmodseq will be modseq of last record we return */
4464 highestmodseq = map[nresp-1].record.modseq;
4629 /* respmodseq will be modseq of last record we return */
4630 respmodseq = map[nresp-1].record.modseq;
44654631
44664632 /* Tell client we truncated the responses */
4467 xml_add_response(fctx, HTTP_NO_STORAGE);
4633 xml_add_response(fctx, HTTP_NO_STORAGE, DAV_OVER_LIMIT);
4634 }
4635 else {
4636 /* Full response - respmodseq will be highestmodseq of mailbox */
4637 respmodseq = highestmodseq;
44684638 }
44694639
44704640 /* Report the resources within the client requested limit (if any) */
45034673 }
45044674
45054675 /* Add sync-token element */
4506 snprintf(tokenuri, MAX_MAILBOX_PATH,
4507 XML_NS_CYRUS "sync/%u-" MODSEQ_FMT,
4508 mailbox->i.uidvalidity, highestmodseq);
4676 if (respmodseq < basemodseq) {
4677 /* Client limited results of initial sync - include basemodseq */
4678 snprintf(tokenuri, MAX_MAILBOX_PATH,
4679 SYNC_TOKEN_URL_SCHEME "%u-" MODSEQ_FMT "-" MODSEQ_FMT,
4680 mailbox->i.uidvalidity, respmodseq, basemodseq);
4681 }
4682 else {
4683 snprintf(tokenuri, MAX_MAILBOX_PATH,
4684 SYNC_TOKEN_URL_SCHEME "%u-" MODSEQ_FMT,
4685 mailbox->i.uidvalidity, respmodseq);
4686 }
45094687 xmlNewChild(fctx->root, NULL, BAD_CAST "sync-token", BAD_CAST tokenuri);
45104688
45114689 done:
45124690 if (istate.map) free(istate.map);
4513 if (mailbox) mailbox_unlock_index(mailbox, NULL);
4514
4515 return ret;
4691 mailbox_close(&mailbox);
4692
4693 return (ret ? ret : HTTP_MULTI_STATUS);
4694 }
4695
4696
4697 struct search_crit {
4698 struct strlist *props;
4699 xmlChar *match;
4700 struct search_crit *next;
4701 };
4702
4703
4704 /* mboxlist_findall() callback to find user principals (has Inbox) */
4705 static int principal_search(char *mboxname,
4706 int matchlen __attribute__((unused)),
4707 int maycreate __attribute__((unused)),
4708 void *rock)
4709 {
4710 struct propfind_ctx *fctx = (struct propfind_ctx *) rock;
4711 char userid[MAX_MAILBOX_NAME+1], *p;
4712 struct search_crit *search_crit;
4713 size_t len;
4714
4715 if (!(p = mboxname_isusermailbox(mboxname, 1))) return 0;
4716
4717 strlcpy(userid, p, MAX_MAILBOX_NAME+1);
4718 mboxname_hiersep_toexternal(&httpd_namespace, userid, 0);
4719
4720 for (search_crit = (struct search_crit *) fctx->filter_crit;
4721 search_crit; search_crit = search_crit->next) {
4722 struct strlist *prop;
4723
4724 for (prop = search_crit->props; prop; prop = prop->next) {
4725 if (!strcmp(prop->s, "displayname")) {
4726 if (!xmlStrcasestr(BAD_CAST userid,
4727 search_crit->match)) return 0;
4728 }
4729 else if (!strcmp(prop->s, "calendar-user-address-set")) {
4730 char email[MAX_MAILBOX_NAME+1];
4731
4732 snprintf(email, MAX_MAILBOX_NAME, "%s@%s",
4733 userid, config_servername);
4734 if (!xmlStrcasestr(BAD_CAST email,
4735 search_crit->match)) return 0;
4736 }
4737 else if (!strcmp(prop->s, "calendar-user-type")) {
4738 if (!xmlStrcasestr(BAD_CAST "INDIVIDUAL",
4739 search_crit->match)) return 0;
4740 }
4741 }
4742 }
4743
4744
4745 /* Append principal name to URL path */
4746 if (!fctx->req_tgt->user) {
4747 len = strlen(namespace_principal.prefix);
4748 p = fctx->req_tgt->path + len;
4749 len += strlcpy(p, "/user/", MAX_MAILBOX_PATH - len);
4750 p = fctx->req_tgt->path + len;
4751 }
4752 else {
4753 p = fctx->req_tgt->user;
4754 len = p - fctx->req_tgt->path;
4755 }
4756 strlcpy(p, userid, MAX_MAILBOX_PATH - len);
4757
4758 fctx->req_tgt->user = p;
4759 fctx->req_tgt->userlen = strlen(p);
4760
4761 return xml_add_response(fctx, 0, 0);
4762 }
4763
4764
4765 static const struct prop_entry prin_search_props[] = {
4766
4767 /* WebDAV (RFC 4918) properties */
4768 { "displayname", NS_DAV, 0, NULL, NULL, NULL },
4769
4770 /* CalDAV Scheduling (RFC 6638) properties */
4771 { "calendar-user-address-set", NS_CALDAV, 0, NULL, NULL, NULL },
4772 { "calendar-user-type", NS_CALDAV, 0, NULL, NULL, NULL },
4773
4774 { NULL, 0, 0, NULL, NULL, NULL }
4775 };
4776
4777
4778 /* DAV:principal-property-search REPORT */
4779 static int report_prin_prop_search(struct transaction_t *txn,
4780 xmlNodePtr inroot,
4781 struct propfind_ctx *fctx)
4782 {
4783 int ret = 0;
4784 xmlNodePtr node;
4785 struct search_crit *search_crit, *next;
4786 unsigned apply_prin_set = 0;
4787
4788 if (fctx->depth != 0) {
4789 txn->error.desc = "Depth header field MUST have value zero (0)";
4790 return HTTP_BAD_REQUEST;
4791 }
4792
4793 /* Parse children element of report */
4794 fctx->filter_crit = NULL;
4795 for (node = inroot->children; node; node = node->next) {
4796 if (node->type == XML_ELEMENT_NODE) {
4797 if (!xmlStrcmp(node->name, BAD_CAST "property-search")) {
4798 xmlNodePtr search;
4799
4800 search_crit = xzmalloc(sizeof(struct search_crit));
4801 search_crit->next = fctx->filter_crit;
4802 fctx->filter_crit = search_crit;
4803
4804 for (search = node->children; search; search = search->next) {
4805 if (search->type == XML_ELEMENT_NODE) {
4806 if (!xmlStrcmp(search->name, BAD_CAST "prop")) {
4807 xmlNodePtr prop;
4808
4809 for (prop = search->children;
4810 prop; prop = prop->next) {
4811 if (prop->type == XML_ELEMENT_NODE) {
4812 const struct prop_entry *entry;
4813
4814 for (entry = prin_search_props;
4815 entry->name &&
4816 xmlStrcmp(prop->name,
4817 BAD_CAST entry->name);
4818 entry++);
4819
4820 if (!entry->name) {
4821 txn->error.desc =
4822 "Unsupported XML search prop";
4823 ret = HTTP_BAD_REQUEST;
4824 goto done;
4825 }
4826 else {
4827 appendstrlist(&search_crit->props,
4828 (char *) entry->name);
4829 }
4830 }
4831 }
4832 }
4833 else if (!xmlStrcmp(search->name, BAD_CAST "match")) {
4834 if (search_crit->match) {
4835 txn->error.desc =
4836 "Too many DAV:match XML elements";
4837 ret = HTTP_BAD_REQUEST;
4838 goto done;
4839 }
4840
4841 search_crit->match =
4842 xmlNodeListGetString(inroot->doc,
4843 search->children, 1);
4844 }
4845 else {
4846 txn->error.desc = "Unknown XML element";
4847 ret = HTTP_BAD_REQUEST;
4848 goto done;
4849 }
4850 }
4851 }
4852
4853 if (!search_crit->props) {
4854 txn->error.desc = "Missing DAV:prop XML element";
4855 ret = HTTP_BAD_REQUEST;
4856 goto done;
4857 }
4858 if (!search_crit->match) {
4859 txn->error.desc = "Missing DAV:match XML element";
4860 ret = HTTP_BAD_REQUEST;
4861 goto done;
4862 }
4863 }
4864 else if (!xmlStrcmp(node->name, BAD_CAST "prop")) {
4865 /* Already parsed in meth_report() */
4866 }
4867 else if (!xmlStrcmp(node->name,
4868 BAD_CAST "apply-to-principal-collection-set")) {
4869 apply_prin_set = 1;
4870 }
4871 else {
4872 txn->error.desc = "Unknown XML element";
4873 ret = HTTP_BAD_REQUEST;
4874 goto done;
4875 }
4876 }
4877 }
4878
4879 if (!fctx->filter_crit) {
4880 txn->error.desc = "Missing DAV:property-search XML element";
4881 ret = HTTP_BAD_REQUEST;
4882 goto done;
4883 }
4884
4885 /* Only search DAV:principal-collection-set */
4886 if (apply_prin_set || !fctx->req_tgt->user) {
4887 /* XXX Do LDAP/SQL lookup of CN/email-address(es) here */
4888
4889 ret = mboxlist_findall(NULL, /* internal namespace */
4890 "user.%", 1, httpd_userid,
4891 httpd_authstate, principal_search, fctx);
4892 }
4893
4894 done:
4895 for (search_crit = fctx->filter_crit; search_crit; search_crit = next) {
4896 next = search_crit->next;
4897
4898 if (search_crit->match) xmlFree(search_crit->match);
4899 freestrlist(search_crit->props);
4900 free(search_crit);
4901 }
4902
4903 return (ret ? ret : HTTP_MULTI_STATUS);
4904 }
4905
4906
4907 /* DAV:principal-search-property-set REPORT */
4908 static int report_prin_search_prop_set(struct transaction_t *txn,
4909 xmlNodePtr inroot,
4910 struct propfind_ctx *fctx)
4911 {
4912 xmlNodePtr node;
4913 const struct prop_entry *entry;
4914
4915 if (fctx->depth != 0) {
4916 txn->error.desc = "Depth header field MUST have value zero (0)";
4917 return HTTP_BAD_REQUEST;
4918 }
4919
4920 /* Look for child elements in request */
4921 for (node = inroot->children; node; node = node->next) {
4922 if (node->type == XML_ELEMENT_NODE) {
4923 txn->error.desc =
4924 "DAV:principal-search-property-set XML element MUST be empty";
4925 return HTTP_BAD_REQUEST;
4926 }
4927 }
4928
4929 for (entry = prin_search_props; entry->name; entry++) {
4930 node = xmlNewChild(fctx->root, NULL,
4931 BAD_CAST "principal-search-property", NULL);
4932 node = xmlNewChild(node, NULL, BAD_CAST "prop", NULL);
4933 ensure_ns(fctx->ns, entry->ns, fctx->root,
4934 known_namespaces[entry->ns].href,
4935 known_namespaces[entry->ns].prefix);
4936 xmlNewChild(node, fctx->ns[entry->ns], BAD_CAST entry->name, NULL);
4937 }
4938
4939 return HTTP_OK;
45164940 }
45174941
45184942
46075031 txn->error.precond = DAV_NEED_PRIVS;
46085032 txn->error.resource = txn->req_tgt.path;
46095033 txn->error.rights = report->reqd_privs;
4610 ret = HTTP_FORBIDDEN;
5034 ret = HTTP_NO_PRIVS;
46115035 }
46125036 goto done;
46135037 }
46245048 }
46255049
46265050 /* Local Mailbox */
4627 if (!*rparams->davdb.db) {
4628 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
4629 "Check 'configdirectory' permissions or "
4630 "'proxyservers' option on backend server.", proxy_userid);
4631 txn->error.desc = "DAV database is not opened";
4632 return HTTP_SERVER_ERROR;
4633 }
46345051 }
46355052
46365053 /* Principal or Local Mailbox */
46655082 }
46665083
46675084 /* Start construction of our multistatus response */
4668 if ((report->flags & REPORT_MULTISTATUS) &&
4669 !(outroot = init_xml_response("multistatus", NS_DAV, inroot, ns))) {
5085 if (report->resp_root &&
5086 !(outroot = init_xml_response(report->resp_root, NS_DAV, inroot, ns))) {
46705087 txn->error.desc = "Unable to create XML response\r\n";
46715088 ret = HTTP_SERVER_ERROR;
46725089 goto done;
46995116 if (!ret) ret = (*report->proc)(txn, inroot, &fctx);
47005117
47015118 /* Output the XML response */
4702 if (!ret && outroot) {
4703 /* iCalendar data in response should not be transformed */
4704 if (fctx.fetcheddata) txn->flags.cc |= CC_NOTRANSFORM;
4705
4706 xml_response(HTTP_MULTI_STATUS, txn, outroot->doc);
5119 if (outroot) {
5120 switch (ret) {
5121 case HTTP_OK:
5122 case HTTP_MULTI_STATUS:
5123 /* iCalendar data in response should not be transformed */
5124 if (fctx.fetcheddata) txn->flags.cc |= CC_NOTRANSFORM;
5125
5126 xml_response(ret, txn, outroot->doc);
5127
5128 ret = 0;
5129 break;
5130
5131 default:
5132 break;
5133 }
47075134 }
47085135
47095136 done:
47445171 const char *etag;
47455172 time_t lastmod;
47465173 size_t len;
5174 void *davdb = NULL;
47475175
47485176 /* Response should not be cached */
47495177 txn->flags.cc |= CC_NOCACHE;
47885216
47895217 /* Local Mailbox */
47905218
4791 if (!*lparams->davdb.db) {
4792 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
4793 "Check 'configdirectory' permissions or "
4794 "'proxyservers' option on backend server.", proxy_userid);
4795 txn->error.desc = "DAV database is not opened";
4796 return HTTP_SERVER_ERROR;
4797 }
4798
47995219 /* Open mailbox for reading */
4800 if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) {
5220 r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox);
5221 if (r) {
48015222 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
48025223 txn->req_tgt.mboxname, error_message(r));
48035224 txn->error.desc = error_message(r);
48055226 goto done;
48065227 }
48075228
5229 /* Open the DAV DB corresponding to the mailbox */
5230 davdb = lparams->davdb.open_db(mailbox);
5231
48085232 /* Find message UID for the resource, if exists */
4809 lparams->davdb.lookup_resource(*lparams->davdb.db, txn->req_tgt.mboxname,
5233 lparams->davdb.lookup_resource(davdb, txn->req_tgt.mboxname,
48105234 txn->req_tgt.resource, 1, (void **) &ddata);
48115235 if (!ddata->rowid) {
48125236 ret = HTTP_NOT_FOUND;
48305254 txn->error.precond = DAV_NEED_PRIVS;
48315255 txn->error.resource = txn->req_tgt.path;
48325256 txn->error.rights = DACL_ADMIN;
4833 ret = HTTP_FORBIDDEN;
5257 ret = HTTP_NO_PRIVS;
48345258 goto done;
48355259 }
48365260 }
48795303 ddata->lock_ownerid = NULL;
48805304 ddata->lock_expire = 0;
48815305
4882 lparams->davdb.write_resource(*lparams->davdb.db, ddata, 1);
5306 lparams->davdb.write_resource(davdb, ddata, 1);
48835307 }
48845308 else {
48855309 /* Unmapped URL - Treat as lock-null and delete mapping entry */
4886 lparams->davdb.delete_resource(lparams->davdb.db, ddata->rowid, 1);
5310 lparams->davdb.delete_resource(davdb, ddata->rowid, 1);
48875311 }
48885312
48895313 done:
4890 if (mailbox) mailbox_unlock_index(mailbox, NULL);
5314 if (davdb) lparams->davdb.close_db(davdb);
5315 mailbox_close(&mailbox);
48915316
48925317 return ret;
48935318 }
149149 schedule-send-reply,
150150 schedule-send-freebusy) */
151151
152 /* Bitmask of calendar components */
153 enum {
154 CAL_COMP_VCALENDAR = 0xf000,
155 CAL_COMP_VEVENT = (1<<0),
156 CAL_COMP_VTODO = (1<<1),
157 CAL_COMP_VJOURNAL = (1<<2),
158 CAL_COMP_VFREEBUSY = (1<<3),
159 CAL_COMP_VTIMEZONE = (1<<4),
160 CAL_COMP_VALARM = (1<<5)
161 };
162
163152 /* Index into preconditions array */
164153 enum {
165154 /* WebDAV (RFC 4918) preconditons */
199188 CALDAV_UID_CONFLICT,
200189 CALDAV_SUPP_FILTER,
201190 CALDAV_VALID_FILTER,
191
192 /* RSCALE (draft-daboo-icalendar-rscale) preconditions */
193 CALDAV_SUPP_RSCALE,
202194
203195 /* CalDAV Scheduling (RFC 6638) preconditions */
204196 CALDAV_VALID_SCHED,
239231 #define NO_DUP_CHECK (1<<7)
240232
241233
234 typedef void *(*db_open_proc_t)(struct mailbox *mailbox);
235 typedef void (*db_close_proc_t)(void *davdb);
236
242237 /* Function to lookup DAV 'resource' in 'mailbox', with optional 'lock',
243238 * placing the record in 'data'
244239 */
263258 const char *int_userid; /* internal userid */
264259 int userisadmin; /* is userid an admin */
265260 struct auth_state *authstate; /* authorization state for userid */
266 void *davdb; /* DAV DB corresponding to userid */
261 void *davdb; /* DAV DB corresponding to collection */
267262 struct mailbox *mailbox; /* mailbox correspondng to collection */
268263 struct quota quota; /* quota info for collection */
269264 struct index_record *record; /* cyrus.index record for resource */
274269 int (*filter)(struct propfind_ctx *,
275270 void *data); /* callback to filter resources */
276271 void *filter_crit; /* criteria to filter resources */
277 db_lookup_proc_t lookup_resource;
278 db_foreach_proc_t foreach_resource;
272 db_open_proc_t open_db; /* open DAV DB for a given mailbox */
273 db_close_proc_t close_db; /* close DAV DB for a given mailbox */
274 db_lookup_proc_t lookup_resource; /* lookup a specific resource */
275 db_foreach_proc_t foreach_resource; /* process all resources in a mailbox */
279276 int (*proc_by_resource)(void *rock, /* Callback to process a resource */
280277 void *data);
281278 struct propfind_entry_list *elist; /* List of props to fetch w/callbacks */
365362 typedef int (*db_delmbox_proc_t)(void *davdb, const char *mailbox, int commit);
366363
367364 struct davdb_params {
368 void **db; /* DAV DB to use for resources */
365 db_open_proc_t open_db; /* open DAV DB for a given mailbox */
366 db_close_proc_t close_db; /* close DAV DB for a given mailbox */
369367 db_lookup_proc_t lookup_resource; /* lookup a specific resource */
370368 db_foreach_proc_t foreach_resource; /* process all resources in a mailbox */
371369 db_write_proc_t write_resource; /* write a specific resource */
385383 typedef int (*copy_proc_t)(struct transaction_t *txn,
386384 struct mailbox *src_mbox, struct index_record *src_rec,
387385 struct mailbox *dest_mbox, const char *dest_rsrc,
386 void *dest_davdb,
388387 unsigned overwrite, unsigned flags);
389388
390389 /* Function to do special processing for DELETE method (optional) */
420419 typedef int (*post_proc_t)(struct transaction_t *txn);
421420
422421 /* meth_put() parameters */
423 typedef int (*put_proc_t)(struct transaction_t *txn,
424 struct mime_type_t *mime,
425 struct mailbox *mailbox, unsigned flags);
422 typedef int (*put_proc_t)(struct transaction_t *txn, struct mime_type_t *mime,
423 struct mailbox *mailbox, void *davdb, unsigned flags);
426424
427425 struct put_params {
428426 unsigned supp_data_precond; /* precond code for unsupported data */
435433
436434 struct report_type_t {
437435 const char *name; /* report name */
436 const char *resp_root; /* name of XML root element in resp */
438437 report_proc_t proc; /* function to generate the report */
439438 unsigned long reqd_privs; /* privileges required to run report */
440439 unsigned flags; /* report-specific flags */
443442 /* Report flags */
444443 enum {
445444 REPORT_NEED_MBOX = (1<<0),
446 REPORT_NEED_PROPS = (1<<1),
447 REPORT_MULTISTATUS = (1<<2)
445 REPORT_NEED_PROPS = (1<<1)
448446 };
449447
450448 /* Overwrite flags */
495493 int ensure_ns(xmlNsPtr *respNs, int ns, xmlNodePtr node,
496494 const char *url, const char *prefix);
497495
496 int xml_add_response(struct propfind_ctx *fctx, long code, unsigned precond);
498497 int propfind_by_resource(void *rock, void *data);
499498 int propfind_by_collection(char *mboxname, int matchlen,
500499 int maycreate, void *rock);
586585 int propfind_caluseraddr(const xmlChar *name, xmlNsPtr ns,
587586 struct propfind_ctx *fctx, xmlNodePtr resp,
588587 struct propstat propstat[], void *rock);
588 int propfind_calusertype(const xmlChar *name, xmlNsPtr ns,
589 struct propfind_ctx *fctx, xmlNodePtr resp,
590 struct propstat propstat[], void *rock);
589591 int propfind_abookurl(const xmlChar *name, xmlNsPtr ns,
590592 struct propfind_ctx *fctx, xmlNodePtr resp,
591593 struct propstat propstat[], void *rock);
7272 #include <dkim.h>
7373
7474 //#define TEST
75 //#define IOPTEST
7576
7677 #define BASE64_LEN(inlen) ((((inlen) + 2) / 3) * 4)
7778
167168 void *params __attribute__((unused)))
168169 {
169170 int precond;
171 struct strlist *action;
170172 struct message_guid guid;
171173 const char *etag;
172174 static time_t lastmod = 0;
174176 static int bufsiz = 0;
175177
176178 /* We don't handle GET on a anything other than ?action=capabilities */
177 if (!URI_QUERY(txn->req_uri) ||
178 strcmp(URI_QUERY(txn->req_uri), "action=capabilities")) {
179 action = hash_lookup("action", &txn->req_qparams);
180 if (!action || action->next || strcmp(action->s, "capabilities")) {
179181 txn->error.desc = "Invalid action";
180182 return HTTP_BAD_REQUEST;
181183 }
214216 xmlNodePtr root, capa, node, comp, meth;
215217 xmlNsPtr ns[NUM_NAMESPACE];
216218 struct mime_type_t *mime;
219 int i, n;
217220
218221 /* Start construction of our query-result */
219222 if (!(root = init_xml_response("query-result", NS_ISCHED, NULL, ns))) {
223226
224227 capa = xmlNewChild(root, NULL, BAD_CAST "capabilities", NULL);
225228
226 node = xmlNewChild(capa, NULL, BAD_CAST "serial-number",
229 xmlNewChild(capa, NULL, BAD_CAST "serial-number",
227230 BAD_CAST buf_cstring(&txn->buf));
228231
229232 node = xmlNewChild(capa, NULL, BAD_CAST "versions", NULL);
230 node = xmlNewChild(node, NULL, BAD_CAST "version", BAD_CAST "1.0");
233 xmlNewChild(node, NULL, BAD_CAST "version", BAD_CAST "1.0");
231234
232235 node = xmlNewChild(capa, NULL,
233236 BAD_CAST "scheduling-messages", NULL);
248251 meth = xmlNewChild(comp, NULL, BAD_CAST "method", NULL);
249252 xmlNewProp(meth, BAD_CAST "name", BAD_CAST "CANCEL");
250253 comp = xmlNewChild(node, NULL, BAD_CAST "component", NULL);
254 xmlNewProp(comp, BAD_CAST "name", BAD_CAST "VPOLL");
255 meth = xmlNewChild(comp, NULL, BAD_CAST "method", NULL);
256 xmlNewProp(meth, BAD_CAST "name", BAD_CAST "POLLSTATUS");
257 meth = xmlNewChild(comp, NULL, BAD_CAST "method", NULL);
258 xmlNewProp(meth, BAD_CAST "name", BAD_CAST "REQUEST");
259 meth = xmlNewChild(comp, NULL, BAD_CAST "method", NULL);
260 xmlNewProp(meth, BAD_CAST "name", BAD_CAST "REPLY");
261 meth = xmlNewChild(comp, NULL, BAD_CAST "method", NULL);
262 xmlNewProp(meth, BAD_CAST "name", BAD_CAST "CANCEL");
263 comp = xmlNewChild(node, NULL, BAD_CAST "component", NULL);
251264 xmlNewProp(comp, BAD_CAST "name", BAD_CAST "VFREEBUSY");
252265 meth = xmlNewChild(comp, NULL, BAD_CAST "method", NULL);
253266 xmlNewProp(meth, BAD_CAST "name", BAD_CAST "REQUEST");
272285 }
273286
274287 node = xmlNewChild(capa, NULL, BAD_CAST "attachments", NULL);
275 node = xmlNewChild(node, NULL, BAD_CAST "inline", NULL);
288 xmlNewChild(node, NULL, BAD_CAST "inline", NULL);
289
290 node = xmlNewChild(capa, NULL, BAD_CAST "rscales", NULL);
291 for (i = 0, n = rscale_calendars->num_elements; i < n; i++) {
292 const char **rscale = icalarray_element_at(rscale_calendars, i);
293
294 xmlNewChild(node, NULL, BAD_CAST "rscale", BAD_CAST *rscale);
295 }
296
297 /* XXX need to fill these with values */
298 xmlNewChild(capa, NULL, BAD_CAST "max-content-length", NULL);
299 xmlNewChild(capa, NULL, BAD_CAST "min-date-time", NULL);
300 xmlNewChild(capa, NULL, BAD_CAST "max-date-time", NULL);
301 xmlNewChild(capa, NULL, BAD_CAST "max-recipients", NULL);
302 xmlNewChild(capa, NULL, BAD_CAST "administrator", NULL);
276303
277304 /* Dump XML response tree into a text buffer */
278305 if (buf) xmlFree(buf);
300327 void *params __attribute__((unused)))
301328 {
302329 int ret = 0, r, authd = 0;
303 const char **hdr;
330 const char **hdr, **recipients;
304331 struct mime_type_t *mime = NULL;
305332 icalcomponent *ical = NULL, *comp;
306333 icalcomponent_kind kind = 0;
340367 return HTTP_BAD_REQUEST;
341368 }
342369
343 /* Check Recipient */
344 if (!(hdr = spool_getheader(txn->req_hdrs, "Recipient"))) {
370 /* Check Recipients */
371 if (!(recipients = spool_getheader(txn->req_hdrs, "Recipient"))) {
345372 txn->error.precond = ISCHED_RECIP_MISSING;
346373 return HTTP_BAD_REQUEST;
347374 }
348375
349376 /* Read body */
350377 txn->req_body.flags |= BODY_DECODE;
351 r = read_body(httpd_in, txn->req_hdrs, &txn->req_body, &txn->error.desc);
378 r = http_read_body(httpd_in, httpd_out,
379 txn->req_hdrs, &txn->req_body, &txn->error.desc);
352380 if (r) {
353381 txn->flags.conn = CONN_CLOSE;
354382 return r;
381409
382410 /* Parse the iCal data for important properties */
383411 ical = mime->from_string(buf_cstring(&txn->req_body.payload));
384 if (!ical || !icalrestriction_check(ical)) {
412 if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) {
413 txn->error.precond = ISCHED_INVALID_DATA;
414 return HTTP_BAD_REQUEST;
415 }
416
417 icalrestriction_check(ical);
418 if ((txn->error.desc = get_icalcomponent_errstr(ical))) {
419 assert(!buf_len(&txn->buf));
420 buf_setcstr(&txn->buf, txn->error.desc);
421 txn->error.desc = buf_cstring(&txn->buf);
385422 txn->error.precond = ISCHED_INVALID_DATA;
386423 return HTTP_BAD_REQUEST;
387424 }
395432 }
396433
397434 /* Check method preconditions */
398 if (!meth || !uid || !prop) {
435 if (!meth || !comp || !uid || !prop) {
399436 txn->error.precond = ISCHED_INVALID_SCHED;
400437 ret = HTTP_BAD_REQUEST;
401438 goto done;
405442 case ICAL_VFREEBUSY_COMPONENT:
406443 if (meth == ICAL_METHOD_REQUEST)
407444 ret = sched_busytime_query(txn, mime, ical);
408 else {
409 txn->error.precond = ISCHED_INVALID_SCHED;
410 ret = HTTP_BAD_REQUEST;
411 }
445 else goto invalid_meth;
412446 break;
413447
414448 case ICAL_VEVENT_COMPONENT:
415449 case ICAL_VTODO_COMPONENT:
450 case ICAL_VPOLL_COMPONENT:
416451 switch (meth) {
452 case ICAL_METHOD_POLLSTATUS:
453 if (kind != ICAL_VPOLL_COMPONENT) goto invalid_meth;
454
417455 case ICAL_METHOD_REQUEST:
418456 case ICAL_METHOD_REPLY:
419457 case ICAL_METHOD_CANCEL: {
420458 struct sched_data sched_data =
421 { 1, meth == ICAL_METHOD_REPLY, ical, NULL, 0, NULL, NULL };
459 { 1, meth == ICAL_METHOD_REPLY,
460 ical, NULL, 0, ICAL_SCHEDULEFORCESEND_NONE, NULL };
422461 xmlNodePtr root = NULL;
423462 xmlNsPtr ns[NUM_NAMESPACE];
424463 struct auth_state *authstate;
425 icalcomponent *comp;
426 icalproperty *prop;
464 int i;
427465
428466 /* Start construction of our schedule-response */
429467 if (!(root = init_xml_response("schedule-response",
434472 }
435473
436474 authstate = auth_newstate("anonymous");
437 comp = icalcomponent_get_first_real_component(ical);
438
439 /* Process each attendee */
440 for (prop = icalcomponent_get_first_property(comp,
441 ICAL_ATTENDEE_PROPERTY);
442 prop;
443 prop =
444 icalcomponent_get_next_property(comp,
445 ICAL_ATTENDEE_PROPERTY)) {
446 const char *attendee;
447 struct sched_param sparam;
448 int r;
449
450 /* Is attendee remote or local? */
451 attendee = icalproperty_get_attendee(prop);
452 r = caladdress_lookup(attendee, &sparam);
453
454 /* Don't allow scheduling of remote users via an iSchedule request */
455 if (sparam.flags & SCHEDTYPE_REMOTE) r = HTTP_FORBIDDEN;
456
457 if (r) sched_data.status = REQSTAT_NOUSER;
458 else sched_deliver((char *) attendee, &sched_data, authstate);
459
460 xml_add_schedresponse(root, NULL, BAD_CAST attendee,
461 BAD_CAST sched_data.status);
475
476 /* Process each recipient */
477 for (i = 0; recipients[i]; i++) {
478 tok_t tok =
479 TOK_INITIALIZER(recipients[i], ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
480 char *recipient;
481
482 while ((recipient = tok_next(&tok))) {
483 /* Is recipient remote or local? */
484 struct sched_param sparam;
485 int r = caladdress_lookup(recipient, &sparam);
486
487 /* Don't allow scheduling with remote users via iSchedule */
488 if (sparam.flags & SCHEDTYPE_REMOTE) r = HTTP_FORBIDDEN;
489
490 if (r) sched_data.status = REQSTAT_NOUSER;
491 else sched_deliver(recipient, &sched_data, authstate);
492
493 xml_add_schedresponse(root, NULL, BAD_CAST recipient,
494 BAD_CAST sched_data.status);
495 }
496 tok_fini(&tok);
462497 }
463498
464499 /* Fill in iSchedule-Capabilities */
471506 break;
472507
473508 default:
509 invalid_meth:
510 txn->error.desc = "Unsupported iTIP method";
474511 txn->error.precond = ISCHED_INVALID_SCHED;
475512 ret = HTTP_BAD_REQUEST;
513 goto done;
476514 }
477515 break;
478516
479517 default:
518 txn->error.desc = "Unsupported iCalendar component";
480519 txn->error.precond = ISCHED_INVALID_SCHED;
481520 ret = HTTP_BAD_REQUEST;
482521 }
495534 struct backend *be;
496535 static unsigned send_count = 0;
497536 static struct buf hdrs = BUF_INITIALIZER;
498 const char *body, *uri;
537 const char *body, *uri, *originator;
499538 size_t bodylen;
500539 icalcomponent *comp;
501540 icalcomponent_kind kind;
511550
512551 /* Open connection to iSchedule receiver.
513552 Use header buffer to construct remote server[:port][/tls] */
514 buf_setcstr(&hdrs, sparam->server);
515 if (sparam->port) buf_printf(&hdrs, ":%u", sparam->port);
516 if (sparam->flags & SCHEDTYPE_SSL) buf_appendcstr(&hdrs, "/tls");
517 if (sparam->flags & SCHEDTYPE_REMOTE) buf_appendcstr(&hdrs, "/noauth");
518 be = proxy_findserver(buf_cstring(&hdrs), &http_protocol, NULL,
553 buf_setcstr(&txn.buf, sparam->server);
554 if (sparam->port) buf_printf(&txn.buf, ":%u", sparam->port);
555 if (sparam->flags & SCHEDTYPE_SSL) buf_appendcstr(&txn.buf, "/tls");
556 if (sparam->flags & SCHEDTYPE_REMOTE) buf_appendcstr(&txn.buf, "/noauth");
557 be = proxy_findserver(buf_cstring(&txn.buf), &http_protocol, NULL,
519558 &backend_cached, NULL, NULL, httpd_in);
520559 if (!be) return HTTP_UNAVAILABLE;
521560
548587
549588 buf_printf(&hdrs, "Content-Length: %u\r\n", (unsigned) bodylen);
550589
551 prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
552 buf_printf(&hdrs, "Originator: %s\r\n", icalproperty_get_organizer(prop));
590 /* Determine Originator based on method and component */
591 if (icalcomponent_get_method(ical) == ICAL_METHOD_REPLY) {
592 if (icalcomponent_isa(comp) == ICAL_VPOLL_COMPONENT) {
593 prop = icalcomponent_get_first_property(comp, ICAL_VOTER_PROPERTY);
594 originator = icalproperty_get_voter(prop);
595 }
596 else {
597 prop =
598 icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY);
599 originator = icalproperty_get_attendee(prop);
600 }
601 }
602 else {
603 prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
604 originator = icalproperty_get_organizer(prop);
605 }
606 buf_printf(&hdrs, "Originator: %s\r\n", originator);
553607
554608 if (recipient) {
555609 /* Single recipient */
644698 case 307:
645699 case 308: /* Redirection */
646700 uri = spool_getheader(txn.req_hdrs, "Location")[0];
701 if (txn.req_body.flags & BODY_CLOSE) {
702 proxy_downserver(be);
703 be = proxy_findserver(buf_cstring(&txn.buf), &http_protocol,
704 NULL, &backend_cached,
705 NULL, NULL, httpd_in);
706 if (!be) {
707 r = HTTP_UNAVAILABLE;
708 break;
709 }
710 }
647711 goto redirect;
648712
649713 default:
653717
654718 if (txn.req_hdrs) spool_free_hdrcache(txn.req_hdrs);
655719 buf_free(&txn.req_body.payload);
720 buf_free(&txn.buf);
656721
657722 return r;
658723 }
683748 /* Split type/options */
684749 if ((opts = strchr(type, '/'))) *opts++ = '\0';
685750
751 #ifdef IOPTEST /* CalConnect ioptest */
752 if (1) {
753 #else
686754 if (!strcmp(type, "private-exchange")) {
755 #endif
687756 const char *prefix = config_getstring(IMAPOPT_HTTPDOCROOT);
688757 struct buf path = BUF_INITIALIZER;
689758 FILE *f;
905974 static void isched_init(struct buf *serverinfo)
906975 {
907976 if (!(config_httpmodules & IMAP_ENUM_HTTPMODULES_CALDAV) ||
908 !config_getswitch(IMAPOPT_CALDAV_ALLOWSCHEDULING)) {
977 !config_getenum(IMAPOPT_CALDAV_ALLOWSCHEDULING)) {
909978 /* Need CALDAV and CALDAV_SCHED in order to have ISCHEDULE */
910979 return;
911980 }
922991 namespace_ischedule.enabled =
923992 config_httpmodules & IMAP_ENUM_HTTPMODULES_ISCHEDULE;
924993 }
994
995 /* Add OpenDKIM version to serverinfo string */
996 buf_printf(serverinfo, " OpenDKIM/%u.%u.%u",
997 (ver >> 24) & 0xff, (ver >> 16) & 0xff, (ver >> 8) & 0xff);
998 if (ver & 0xff) buf_printf(serverinfo, ".%u", ver & 0xff);
925999
9261000 if (namespace_ischedule.enabled) {
9271001 int fd;
9431017 unsigned need_dkim =
9441018 namespace_ischedule.enabled == IMAP_ENUM_HTTPMODULES_ISCHEDULE;
9451019
946 /* Add OpenDKIM version to serverinfo string */
947 buf_printf(serverinfo, " OpenDKIM/%u.%u.%u",
948 (ver >> 24) & 0xff, (ver >> 16) & 0xff, (ver >> 8) & 0xff);
949 if (ver & 0xff) buf_printf(serverinfo, ".%u", ver & 0xff);
950
9511020 /* Initialize DKIM library */
9521021 if (!(dkim_lib = dkim_init(NULL, NULL))) {
9531022 syslog(LOG_ERR, "unable to initialize libopendkim");
214214 if (scheme) {
215215 prot_printf(s->out, "Authorization: %s %s\r\n",
216216 scheme->name, clientout ? clientout : "");
217 if (userid) prot_printf(s->out, "Authorize-As: %s\r\n", userid);
217 prot_printf(s->out, "Authorize-As: %s\r\n",
218 userid ? userid : "anonymous");
218219 }
219220 else {
220221 prot_printf(s->out, "Upgrade: %s\r\n", TLS_VERSION);
434435 prot_puts(s->out, "OPTIONS * HTTP/1.1\r\n");
435436 prot_printf(s->out, "Host: %s\r\n", s->hostname);
436437 prot_printf(s->out, "User-Agent: %s\r\n", buf_cstring(&serverinfo));
437 if (userid) prot_printf(s->out, "Authorize-As: %s\r\n", userid);
438 prot_printf(s->out, "Authorize-As: %s\r\n", userid ? userid : "anonymous");
438439 prot_puts(s->out, "\r\n");
439440 prot_flush(s->out);
440441
593594 prot_printf(pout, "%c%s: %s\r\n", toupper(*name), name+1, contents);
594595 }
595596 }
596 }
597
598
599 /* Read a response from backend */
600 int http_read_response(struct backend *be, unsigned meth, unsigned *code,
601 const char **statline, hdrcache_t *hdrs,
602 struct body_t *body, const char **errstr)
603 {
604 static char statbuf[2048];
605 const char **conn;
606 int c = EOF;
607
608 if (statline) *statline = statbuf;
609 *errstr = NULL;
610 *code = HTTP_BAD_GATEWAY;
611
612 if (*hdrs) spool_free_hdrcache(*hdrs);
613 if (!(*hdrs = spool_new_hdrcache())) {
614 *errstr = "Unable to create header cache for backend response";
615 return HTTP_SERVER_ERROR;
616 }
617 if (!prot_fgets(statbuf, sizeof(statbuf), be->in) ||
618 (sscanf(statbuf, HTTP_VERSION " %u ", code) != 1) ||
619 spool_fill_hdrcache(be->in, NULL, *hdrs, NULL)) {
620 *errstr = "Unable to read status-line/headers from backend";
621 return HTTP_BAD_GATEWAY;
622 }
623 eatline(be->in, c); /* CRLF separating headers & body */
624
625 /* 1xx (provisional) response - nothing else to do */
626 if (*code < 200) return 0;
627
628 /* Final response */
629 if (!body) return 0; /* body will be piped */
630 if (!(body->flags & BODY_DISCARD)) buf_reset(&body->payload);
631
632 /* Check connection persistence */
633 if (!strncmp(statbuf, "HTTP/1.0 ", 9)) body->flags |= BODY_CLOSE;
634 for (conn = spool_getheader(*hdrs, "Connection"); conn && *conn; conn++) {
635 tok_t tok =
636 TOK_INITIALIZER(*conn, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
637 char *token;
638
639 while ((token = tok_next(&tok))) {
640 if (!strcasecmp(token, "keep-alive")) body->flags &= ~BODY_CLOSE;
641 else if (!strcasecmp(token, "close")) body->flags |= BODY_CLOSE;
642 }
643 tok_fini(&tok);
644 }
645
646 /* Not expecting a body for 204/304 response or any HEAD response */
647 switch (*code){
648 case 204: /* No Content */
649 case 304: /* Not Modified */
650 break;
651
652 default:
653 if (meth == METH_HEAD) break;
654
655 else {
656 body->flags |= BODY_RESPONSE;
657 body->framing = FRAMING_UNKNOWN;
658
659 if (read_body(be->in, *hdrs, body, errstr)) {
660 return HTTP_BAD_GATEWAY;
661 }
662 }
663 }
664
665 return 0;
666597 }
667598
668599
753684
754685 if (resp_body->framing == FRAMING_UNKNOWN) {
755686 /* Get message framing */
756 int r = parse_framing(resp_hdrs, resp_body, errstr);
687 int r = http_parse_framing(resp_hdrs, resp_body, errstr);
757688 if (r) return r;
758689 }
759690
900831 unsigned len;
901832
902833 /* Read body from client */
903 r = read_body(httpd_in, txn->req_hdrs, &txn->req_body,
904 &txn->error.desc);
834 r = http_read_body(httpd_in, httpd_out, txn->req_hdrs,
835 &txn->req_body, &txn->error.desc);
905836 if (r) {
906837 /* Couldn't get the body and can't finish request */
907838 txn->flags.conn = CONN_CLOSE;
926857 }
927858 } while (code < 200);
928859
929 if (!r) {
860 if (r) proxy_downserver(be);
861 else if (code == 401) {
862 /* Don't pipe a 401 response (discard body).
863 Frontend should send its own 401 since it will process auth */
864 resp_body.flags |= BODY_DISCARD;
865 http_read_body(be->in, httpd_out,
866 resp_hdrs, &resp_body, &txn->error.desc);
867
868 r = HTTP_UNAUTHORIZED;
869 }
870 else {
930871 /* Send response to client */
931872 send_response(statline, resp_hdrs, NULL, &txn->flags);
932873
947888 }
948889 }
949890
950 if (r || (resp_body.flags & BODY_CLOSE)) proxy_downserver(be);
891 if (resp_body.flags & BODY_CLOSE) proxy_downserver(be);
951892
952893 if (resp_hdrs) spool_free_hdrcache(resp_hdrs);
953894
5555 extern int http_pipe_req_resp(struct backend *be, struct transaction_t *txn);
5656 extern int http_proxy_copy(struct backend *src_be, struct backend *dest_be,
5757 struct transaction_t *txn);
58 extern int http_read_response(struct backend *be, unsigned meth, unsigned *code,
59 const char **statline, hdrcache_t *hdrs,
60 struct body_t *body, const char **errstr);
6158
6259 #endif /* _HTTP_PROXY_H */
7474 #include "xstrlcpy.h"
7575
7676 #define XML_NS_ATOM "http://www.w3.org/2005/Atom"
77 #define XML_NS_CYRUS "http://cyrusimap.org/ns/"
77 #define GUID_URL_SCHEME "data:,"
7878 #define MAX_SECTION_LEN 128
7979 #define FEEDLIST_VAR "%RSS_FEEDLIST%"
8080
144144 static int meth_get(struct transaction_t *txn,
145145 void *params __attribute__((unused)))
146146 {
147 int ret = 0, r;
148 char *server, section[MAX_SECTION_LEN+1] = "";
147 int ret = 0, r, rights;
148 struct strlist *param;
149 char *server, *acl, *section = NULL;
149150 uint32_t uid = 0;
150151 struct mailbox *mailbox = NULL;
151152
163164 if (!is_feed(txn->req_tgt.mboxname)) return HTTP_NOT_FOUND;
164165
165166 /* Locate the mailbox */
166 if ((r = http_mlookup(txn->req_tgt.mboxname, &server, NULL, NULL))) {
167 if ((r = http_mlookup(txn->req_tgt.mboxname, &server, &acl, NULL))) {
167168 syslog(LOG_ERR, "mlookup(%s) failed: %s",
168169 txn->req_tgt.mboxname, error_message(r));
169170 txn->error.desc = error_message(r);
175176 }
176177 }
177178
179 /* Check ACL for current user */
180 rights = acl ? cyrus_acl_myrights(httpd_authstate, acl) : 0;
181 if (!(rights & ACL_READ)) return HTTP_NO_PRIVS;
182
178183 if (server) {
179184 /* Remote mailbox */
180185 struct backend *be;
189194 /* Local Mailbox */
190195
191196 /* Open mailbox for reading */
192 if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) {
197 r = mailbox_open_irl(txn->req_tgt.mboxname, &mailbox);
198 if (r) {
193199 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
194200 txn->req_tgt.mboxname, error_message(r));
195201 txn->error.desc = error_message(r);
201207 }
202208 }
203209
204 /* Parse query params, if any */
205 if (URI_QUERY(txn->req_uri) &&
206 !strncasecmp(URI_QUERY(txn->req_uri), "uid=", 4)) {
207 /* UID */
208 char *end;
209
210 uid = strtoul(URI_QUERY(txn->req_uri)+4, &end, 10);
210 /* Check query params, if any */
211 param = hash_lookup("uid", &txn->req_qparams);
212 if (param) {
213 uid = strtoul(param->s, NULL, 10);
211214 if (!uid) uid = -1;
212215
213 if (!strncasecmp(end, ";section=", 9)) {
214 /* SECTION */
215 strlcpy(section, end+9, MAX_SECTION_LEN);
216 }
216 param = hash_lookup("section", &txn->req_qparams);
217 if (param) section = param->s;
217218 }
218219
219220 /* If no UID specified, list messages as an RSS feed */
237238 struct resp_body_t *resp_body = &txn->resp_body;
238239
239240 /* Check any preconditions */
240 if (!strcmp(section, "0")) {
241 if (section && !strcmp(section, "0")) {
241242 /* Entire raw message */
242243 txn->flags.ranges = 1;
243244 }
264265 goto done;
265266 }
266267
267 if (!*section) {
268 if (!section) {
268269 /* Return entire message formatted as text/html */
269270 display_message(txn, mailbox->name, record.uid, body, msg_base);
270271 }
288289 }
289290 }
290291
291 if (mailbox) mailbox_unlock_index(mailbox, NULL);
292 mailbox_close(&mailbox);
292293
293294 return ret;
294295
813814 buf_printf_markup(buf, level, "<title>%s</title>", mboxname);
814815
815816 /* <id> - required */
816 buf_printf_markup(buf, level, "<id>%sguid/%s</id>",
817 XML_NS_CYRUS, mailbox->uniqueid);
817 buf_printf_markup(buf, level, "<id>%s%s</id>",
818 GUID_URL_SCHEME, mailbox->uniqueid);
818819
819820 /* <updated> - required */
820821 rfc3339date_gen(datestr, sizeof(datestr), lastmod);
903904 free(subj);
904905
905906 /* <id> - required */
906 buf_printf_markup(buf, level, "<id>%sguid/%s</id>",
907 XML_NS_CYRUS, message_guid_encode(&record.guid));
907 buf_printf_markup(buf, level, "<id>%s%s</id>",
908 GUID_URL_SCHEME, message_guid_encode(&record.guid));
908909
909910 /* <updated> - required */
910911 rfc3339date_gen(datestr, sizeof(datestr), record.gmtime);
00 /* http_timezone.c -- Routines for handling timezone service requests in httpd
11 *
2 * Copyright (c) 1994-2013 Carnegie Mellon University. All rights reserved.
2 * Copyright (c) 1994-2014 Carnegie Mellon University. All rights reserved.
33 *
44 * Redistribution and use in source and binary forms, with or without
55 * modification, are permitted provided that the following conditions
4343 /*
4444 * TODO:
4545 * - Implement localized names and "lang" parameter
46 * - Implement action=find with sub-string match anywhere (not just prefix)?
4746 */
4847
4948 #include <config.h>
6564 #include "jcal.h"
6665 #include "map.h"
6766 #include "tok.h"
67 #include "tz_err.h"
6868 #include "util.h"
6969 #include "version.h"
7070 #include "xcal.h"
7878 static void timezone_init(struct buf *serverinfo);
7979 static void timezone_shutdown(void);
8080 static int meth_get(struct transaction_t *txn, void *params);
81 static int action_capa(struct transaction_t *txn, struct hash_table *params);
82 static int action_list(struct transaction_t *txn, struct hash_table *params);
83 static int action_get(struct transaction_t *txn, struct hash_table *params);
84 static int action_get_all(struct transaction_t *txn,
85 struct mime_type_t *mime, icaltimetype *truncate);
86 static int action_expand(struct transaction_t *txn, struct hash_table *params);
81 static int action_capa(struct transaction_t *txn);
82 static int action_list(struct transaction_t *txn);
83 static int action_get(struct transaction_t *txn);
84 static int action_expand(struct transaction_t *txn);
8785 static int json_response(int code, struct transaction_t *txn, json_t *root,
8886 char **resp);
89 static int json_error_response(struct transaction_t *txn, const char *err);
90 static const char *begin_ical(struct buf *buf);
91 static void end_ical(struct buf *buf);
87 static int json_error_response(struct transaction_t *txn, long code);
9288
9389 struct observance {
9490 const char *name;
10096
10197 static const struct action_t {
10298 const char *name;
103 int (*proc)(struct transaction_t *txn, struct hash_table *params);
99 int (*proc)(struct transaction_t *txn);
104100 } actions[] = {
105101 { "capabilities", &action_capa },
106102 { "list", &action_list },
115111 /* First item MUST be the default type and storage format */
116112 { "text/calendar; charset=utf-8", "2.0", "ics", "ifb",
117113 (char* (*)(void *)) &icalcomponent_as_ical_string_r,
118 NULL, NULL, &begin_ical, &end_ical
114 NULL, NULL, NULL, NULL
119115 },
120116 { "application/calendar+xml; charset=utf-8", NULL, "xcs", "xfb",
121117 (char* (*)(void *)) &icalcomponent_as_xcal_string,
122 NULL, NULL, &begin_xcal, &end_xcal
118 NULL, NULL, NULL, NULL
123119 },
124120 { "application/calendar+json; charset=utf-8", NULL, "jcs", "jfb",
125121 (char* (*)(void *)) &icalcomponent_as_jcal_string,
126 NULL, NULL, &begin_jcal, &end_jcal
122 NULL, NULL, NULL, NULL
127123 },
128124 { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
129125 };
144140 { NULL, NULL }, /* MKCOL */
145141 { NULL, NULL }, /* MOVE */
146142 { &meth_options, NULL }, /* OPTIONS */
147 { NULL, NULL }, /* POST */
143 { &meth_get, NULL }, /* POST */
148144 { NULL, NULL }, /* PROPFIND */
149145 { NULL, NULL }, /* PROPPATCH */
150146 { NULL, NULL }, /* PUT */
155151 };
156152
157153
158 static void timezone_init(struct buf *serverinfo)
154 static void timezone_init(struct buf *serverinfo __attribute__((unused)))
159155 {
160156 namespace_timezone.enabled =
161157 config_httpmodules & IMAP_ENUM_HTTPMODULES_TIMEZONE;
168164 return;
169165 }
170166
171 if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON &&
172 !strstr(buf_cstring(serverinfo), " Jansson/")) {
173 buf_printf(serverinfo, " Jansson/%s", JANSSON_VERSION);
174 }
175
176167 compile_time = calc_compile_time(__TIME__, __DATE__);
168
169 initialize_tz_error_table();
177170 }
178171
179172
188181 void *params __attribute__((unused)))
189182 {
190183 int ret;
191 tok_t tok;
192 char *param;
193184 struct strlist *action;
194 struct hash_table query_params;
195185 const struct action_t *ap = NULL;
196186
197 /* Parse the query string and add param/value pairs to hash table */
198 construct_hash_table(&query_params, 10, 1);
199 tok_initm(&tok, URI_QUERY(txn->req_uri), "&=", TOK_TRIMLEFT|TOK_TRIMRIGHT);
200 while ((param = tok_next(&tok))) {
201 struct strlist *vals;
202 char *value = tok_next(&tok);
203 if (!value) break;
204
205 vals = hash_lookup(param, &query_params);
206 appendstrlist(&vals, value);
207 hash_insert(param, vals, &query_params);
208 }
209 tok_fini(&tok);
210
211 action = hash_lookup("action", &query_params);
187 action = hash_lookup("action", &txn->req_qparams);
212188 if (action && !action->next /* mandatory, once only */) {
213189 for (ap = actions; ap->name && strcmp(action->s, ap->name); ap++);
214190 }
215191
216 if (!ap || !ap->name) ret = json_error_response(txn, "invalid-action");
217 else ret = ap->proc(txn, &query_params);
218
219 free_hash_table(&query_params, (void (*)(void *)) &freestrlist);
192 if (!ap || !ap->name) ret = json_error_response(txn, TZ_INVALID_ACTION);
193 else ret = ap->proc(txn);
220194
221195 return ret;
222196 }
223197
224198
225199 /* Perform a capabilities action */
226 static int action_capa(struct transaction_t *txn,
227 struct hash_table *params __attribute__((unused)))
200 static int action_capa(struct transaction_t *txn)
228201 {
229202 int precond;
230203 struct message_guid guid;
273246 root = json_pack("{s:i" /* version */
274247 " s:{s:s s:{s:b s:b} s:[]}" /* info */
275248 " s:[" /* actions */
276 " {s:s s:[]}" /* capabilities */
277 " {s:s s:[" /* list */
278 // " {s:s s:b s:b}"
279 " {s:s s:b s:b}"
280 " {s:s s:b s:b}"
249 " {s:s s:[]}" /* capabilities */
250 " {s:s s:[" /* list */
251 // " {s:s s:b s:b}" /* lang */
252 " {s:s s:b s:b}" /* tzid */
253 " {s:s s:b s:b}" /* changedsince */
281254 " ]}"
282 " {s:s s:[" /* get */
283 // " {s:s s:b s:b}"
284 " {s:s s:b s:b}"
285 " {s:s s:b s:b s:[s s s]}"
286 " {s:s s:b s:b}"
287 " {s:s s:b s:b s:[b b]}"
255 " {s:s s:[" /* get */
256 // " {s:s s:b s:b}" /* lang */
257 " {s:s s:b s:b}" /* tzid */
258 " {s:s s:b s:b s:[s s s]}"/* format */
259 " {s:s s:b s:b}" /* truncate */
288260 " ]}"
289 " {s:s s:[" /* expand */
290 // " {s:s s:b s:b}"
291 " {s:s s:b s:b}"
292 " {s:s s:b s:b}"
293 " {s:s s:b s:b}"
294 " {s:s s:b s:b}"
261 " {s:s s:[" /* expand */
262 // " {s:s s:b s:b}" /* lang */
263 " {s:s s:b s:b}" /* tzid */
264 " {s:s s:b s:b}" /* changedsince */
265 " {s:s s:b s:b}" /* start */
266 " {s:s s:b s:b}" /* end */
295267 " ]}"
296 " {s:s s:[" /* find */
297 // " {s:s s:b s:b}"
298 " {s:s s:b s:b}"
268 " {s:s s:[" /* find */
269 // " {s:s s:b s:b}" /* lang */
270 " {s:s s:b s:b}" /* name */
299271 " ]}"
300272 " ]}",
301273 "version", 1,
316288 "values", "text/calendar", "application/calendar+xml",
317289 "application/calendar+json",
318290 "name", "truncate", "required", 0, "multi", 0,
319 "name", "substitute-alias", "required", 0, "multi", 0,
320 "values", 1, 0,
321291
322292 "name", "expand", "parameters",
323293 // "name", "lang", "required", 0, "multi", 1,
344314 return json_response(precond, txn, root, &resp);
345315 }
346316
317 struct list_rock {
318 json_t *tzarray;
319 struct hash_table *tztable;
320 };
347321
348322 static int list_cb(const char *tzid, int tzidlen,
349323 struct zoneinfo *zi, void *rock)
350324 {
351 json_t *tzarray = (json_t *) rock, *tz;
325 struct list_rock *lrock = (struct list_rock *) rock;
352326 char tzidbuf[200], lastmod[21];
327 json_t *tz;
328
329 if (lrock->tztable) {
330 if (hash_lookup(tzid, lrock->tztable)) return 0;
331 hash_insert(tzid, (void *) 0xDEADBEEF, lrock->tztable);
332 }
353333
354334 strlcpy(tzidbuf, tzid, tzidlen+1);
355335 rfc3339date_gen(lastmod, sizeof(lastmod), zi->dtstamp);
356336
357337 tz = json_pack("{s:s s:s}", "tzid", tzidbuf, "last-modified", lastmod);
358 json_array_append_new(tzarray, tz);
338 json_array_append_new(lrock->tzarray, tz);
359339
360340 if (zi->data) {
361341 struct strlist *sl;
372352
373353
374354 /* Perform a list action */
375 static int action_list(struct transaction_t *txn, struct hash_table *params)
355 static int action_list(struct transaction_t *txn)
376356 {
377357 int r, precond, tzid_only = 1;
378358 struct strlist *param, *name = NULL;
382362 json_t *root = NULL;
383363
384364 /* Sanity check the parameters */
385 param = hash_lookup("action", params);
365 param = hash_lookup("action", &txn->req_qparams);
386366 if (!strcmp("find", param->s)) {
387 name = hash_lookup("name", params);
367 name = hash_lookup("name", &txn->req_qparams);
388368 if (!name || name->next /* mandatory, once only */) {
389 return json_error_response(txn, "invalid-name");
369 return json_error_response(txn, TZ_INVALID_NAME);
390370 }
391371 tzid_only = 0;
392372 }
393373 else {
394 param = hash_lookup("changedsince", params);
374 param = hash_lookup("changedsince", &txn->req_qparams);
395375 if (param) {
396376 changedsince = icaltime_as_timet(icaltime_from_string(param->s));
397377 if (!changedsince || param->next /* once only */)
398 return json_error_response(txn, "invalid-changedsince");
399 }
400
401 name = hash_lookup("tzid", params);
378 return json_error_response(txn, TZ_INVALID_CHANGEDSINCE);
379 }
380
381 name = hash_lookup("tzid", &txn->req_qparams);
402382 if (name) {
403 if (changedsince) return json_error_response(txn, "invalid-tzid");
383 if (changedsince) return json_error_response(txn, TZ_INVALID_TZID);
404384 else {
405385 /* Check for tzid=*, and revert to empty list */
406386 struct strlist *sl;
444424 }
445425
446426
447 if (txn->meth == METH_GET) {
427 if (txn->meth != METH_HEAD) {
428 struct list_rock lrock = { NULL, NULL };
429 struct hash_table tzids;
448430 char dtstamp[21];
449431
450432 /* Start constructing our response */
455437 return HTTP_SERVER_ERROR;
456438 }
457439
440 lrock.tzarray = json_object_get(root, "timezones");
441 if (!tzid_only) {
442 construct_hash_table(&tzids, 500, 1);
443 lrock.tztable = &tzids;
444 }
445
458446 /* Add timezones to array */
459447 do {
460448 zoneinfo_find(name ? name->s : NULL, tzid_only, changedsince,
461 &list_cb, json_object_get(root, "timezones"));
449 &list_cb, &lrock);
462450 } while (name && (name = name->next));
451
452 if (!tzid_only) free_hash_table(&tzids, NULL);
463453 }
464454
465455 /* Output the JSON object */
492482 ((struct rdate *) rdate2)->date.time);
493483 }
494484
495 static void truncate_vtimezone(icalcomponent *vtz, icaltimetype *truncate)
496 {
485 static void expand_vtimezone(icalcomponent *vtz, icalarray *obsarray,
486 icaltimetype start, icaltimetype end)
487 {
488 int truncate = !obsarray;
497489 icalcomponent *comp, *nextc;
498490 struct observance tombstone;
499491
503495 for (comp = icalcomponent_get_first_component(vtz, ICAL_ANY_COMPONENT);
504496 comp; comp = nextc) {
505497 icalproperty *prop, *dtstart_prop = NULL, *rrule_prop = NULL;
506 icalarray *rdate_array = icalarray_new(sizeof(struct rdate), 20);
498 icalarray *rdate_array = icalarray_new(sizeof(struct rdate), 10);
499 icaltimetype dtstart;
507500 struct observance obs;
508501 unsigned n;
509502 int r;
525518
526519 case ICAL_DTSTART_PROPERTY:
527520 dtstart_prop = prop;
528 obs.onset = icalproperty_get_dtstart(prop);
521 obs.onset = dtstart = icalproperty_get_dtstart(prop);
529522 break;
530523
531524 case ICAL_TZOFFSETFROM_PROPERTY:
554547 }
555548
556549 /* We MUST have DTSTART, TZNAME, TZOFFSETFROM, and TZOFFSETTO */
557 if (!dtstart_prop || !obs.name || !obs.offset_from || !obs.offset_to)
558 continue;
559
560 r = icaltime_compare(obs.onset, *truncate);
561 if (r <= 0) {
562 /* Check DTSTART vs tombstone */
563 check_tombstone(&tombstone, &obs, NULL);
564 }
565
566 if (r >= 0) {
567 /* All observances occur on or after our cutoff, nothing to do */
550 if (!dtstart_prop || !obs.name || !obs.offset_from || !obs.offset_to) {
568551 icalarray_free(rdate_array);
569552 continue;
570553 }
571554
572 /* Check RRULE */
555 /* Check DTSTART vs window close */
556 if (icaltime_compare(obs.onset, end) >= 0) {
557 /* All observances occur on/after window close, nothing to do */
558 icalarray_free(rdate_array);
559 continue;
560 }
561
562 /* Check DTSTART vs window open */
563 r = icaltime_compare(obs.onset, start);
564 if (r <= 0) {
565 /* DTSTART is on/prior to our window open - check it vs tombstone */
566 check_tombstone(&tombstone, &obs, NULL);
567 }
568
569 if (r >= 0) {
570 /* DTSTART is on/after our window open */
571 if (truncate) {
572 /* All observances occur on/after window open - nothing to do */
573 icalarray_free(rdate_array);
574 continue;
575 }
576 else if (!rrule_prop) {
577 /* Add the DTSTART observance to our array */
578 icalarray_append(obsarray, &obs);
579 }
580 }
581 else if (rrule_prop) {
582 /* RRULE starts prior to our window open -
583 bump RRULE start to 1 year prior to our window open */
584 obs.onset.year = start.year - 1;
585 obs.onset.month = start.month;
586 obs.onset.day = start.day;
587 }
588
573589 if (rrule_prop) {
590 /* Add any RRULE observances within our window */
574591 struct icalrecurrencetype rrule;
575592
576593 rrule = icalproperty_get_rrule(rrule_prop);
583600 rrule.until.is_utc = 0;
584601 }
585602
586 if (icaltime_compare(rrule.until, *truncate) < 0) {
587 /* RRULE ends prior to our cutoff - remove it */
588 icalcomponent_remove_property(comp, rrule_prop);
589 icalproperty_free(rrule_prop);
603 if (icaltime_compare(rrule.until, start) < 0) {
604 /* RRULE ends prior to our window open */
605 if (truncate) {
606 /* Remove RRULE */
607 icalcomponent_remove_property(comp, rrule_prop);
608 icalproperty_free(rrule_prop);
609 }
590610 rrule_prop = NULL;
591611
592612 /* Check UNTIL vs tombstone */
597617 if (rrule_prop) {
598618 icalrecur_iterator *ritr;
599619
600 /* Set iterator to start 1 year prior to our cutoff */
601 obs.onset.year = truncate->year - 1;
602 obs.onset.month = truncate->month;
603 obs.onset.day = truncate->day;
604
605620 ritr = icalrecur_iterator_new(rrule, obs.onset);
606
607 /* Check last recurrence prior to our cutoff vs tombstone */
608 obs.onset = icalrecur_iterator_next(ritr);
609 check_tombstone(&tombstone, &obs, NULL);
610
611 /* Use first recurrence after our cutoff as new DTSTART */
612 obs.onset = icalrecur_iterator_next(ritr);
613 icalproperty_set_dtstart(dtstart_prop, obs.onset);
614 dtstart_prop = NULL;
615
621 while (!icaltime_is_null_time(obs.onset =
622 icalrecur_iterator_next(ritr))) {
623
624 if (icaltime_compare(obs.onset, end) >= 0) {
625 /* Observance is on/after window close - we're done */
626 break;
627 }
628
629 /* Check observance vs our window open */
630 r = icaltime_compare(obs.onset, start);
631 if (r <= 0) {
632 /* Observance is on/prior to our window open -
633 check it vs tombstone */
634 check_tombstone(&tombstone, &obs, NULL);
635 }
636
637 if (r >= 0) {
638 /* Observance is on/after our window open */
639 if (truncate && dtstart_prop) {
640 /* Make first observance the new DTSTART */
641 icalproperty_set_dtstart(dtstart_prop, obs.onset);
642 dtstart_prop = NULL;
643 break;
644 }
645 else {
646 /* Add the observance to our array */
647 icalarray_append(obsarray, &obs);
648 }
649 }
650 }
616651 icalrecur_iterator_free(ritr);
617652 }
618653 }
624659 for (n = 0; n < rdate_array->num_elements; n++) {
625660 struct rdate *rdate = icalarray_element_at(rdate_array, n);
626661
627 r = icaltime_compare(rdate->date.time, *truncate);
662 obs.onset = rdate->date.time;
663 if (icaltime_compare(obs.onset, end) >= 0) {
664 /* RDATE is after our window close - we're done */
665 break;
666 }
667
668 r = icaltime_compare(obs.onset, start);
628669 if (r <= 0) {
629 /* Check RDATE vs tombstone */
670 /* RDATE is on/prior to window open - check it vs tombstone */
630671 check_tombstone(&tombstone, &obs, &rdate->date.time);
631672 }
632673
633 if (r < 0) {
634 /* RDATE occurs prior to our cutoff - remove it */
674 if (r >= 0) {
675 /* RDATE is on/after our window open */
676 if (truncate) {
677 if (dtstart_prop) {
678 /* Make this RDATE the new DTSTART */
679 icalproperty_set_dtstart(dtstart_prop, rdate->date.time);
680 dtstart_prop = NULL;
681
682 icalcomponent_remove_property(comp, rdate->prop);
683 icalproperty_free(rdate->prop);
684 }
685 break;
686 }
687 else if (icaltime_compare(obs.onset, dtstart) != 0) {
688 /* RDATE != DTSTART - add observance to our array */
689 icalarray_append(obsarray, &obs);
690 }
691 }
692 else if (truncate) {
693 /* RDATE is prior to our window open - remove it */
635694 icalcomponent_remove_property(comp, rdate->prop);
636695 icalproperty_free(rdate->prop);
637696 }
638 else {
639 if (dtstart_prop) {
640 /* Make this RDATE the new DTSTART */
641 icalproperty_set_dtstart(dtstart_prop, rdate->date.time);
642 dtstart_prop = NULL;
643
644 icalcomponent_remove_property(comp, rdate->prop);
645 icalproperty_free(rdate->prop);
646 }
647 break;
648 }
649697 }
650698 icalarray_free(rdate_array);
651699
652700 /* Final check */
653 if (dtstart_prop) {
654 /* All observances occur prior to our cutoff, remove comp */
701 if (truncate && dtstart_prop) {
702 /* All observances in comp occur prior to window open, remove it */
655703 icalcomponent_remove_component(vtz, comp);
656704 icalcomponent_free(comp);
657705 }
658706 }
659707
660 if (icaltime_compare(tombstone.onset, *truncate) < 0) {
661 /* Need to add a tombstone component starting at our cutoff */
662 comp = icalcomponent_vanew(
663 tombstone.is_daylight ?
664 ICAL_XDAYLIGHT_COMPONENT : ICAL_XSTANDARD_COMPONENT,
665 icalproperty_new_tzoffsetfrom(tombstone.offset_from),
666 icalproperty_new_tzoffsetto(tombstone.offset_to),
667 icalproperty_new_tzname(tombstone.name),
668 icalproperty_new_dtstart(*truncate),
669 0);
670 icalcomponent_add_component(vtz, comp);
671 }
708 if (icaltime_compare(tombstone.onset, start) < 0) {
709 /* Need to add tombstone component/observance starting at window open */
710 if (truncate) {
711 comp = icalcomponent_vanew(
712 tombstone.is_daylight ?
713 ICAL_XDAYLIGHT_COMPONENT : ICAL_XSTANDARD_COMPONENT,
714 icalproperty_new_tzoffsetfrom(tombstone.offset_from),
715 icalproperty_new_tzoffsetto(tombstone.offset_to),
716 icalproperty_new_tzname(tombstone.name),
717 icalproperty_new_dtstart(start),
718 0);
719 icalcomponent_add_component(vtz, comp);
720 }
721 else {
722 tombstone.onset = start;
723 icalarray_append(obsarray, &tombstone);
724 }
725 }
726 }
727
728 static void truncate_vtimezone(icalcomponent *vtz, icaltimetype start)
729 {
730 /* We don't have an end date for truncation, so use "end of time" */
731 time_t now = INT_MAX;
732 struct tm *tm = gmtime(&now);
733 icaltimetype end = icaltime_from_day_of_year(1, tm->tm_year + 1900);
734
735 expand_vtimezone(vtz, NULL, start, end);
736 }
737
738 /* Version of icaltime_from_string() which supports just YYYY */
739 static icaltimetype icaltime_from_year_string(const char *str)
740 {
741 struct icaltimetype tt;
742 size_t size = strlen(str);
743
744 if (size < 4 || size > 4) return icaltime_null_time();
745
746 tt = icaltime_from_day_of_year(1, atoi(str));
747 tt.is_date = tt.hour = tt.minute = tt.second = 0;
748
749 return tt;
672750 }
673751
674752 /* Perform a get action */
675 static int action_get(struct transaction_t *txn, struct hash_table *params)
676 {
677 int r, precond, substitute = 0;
753 static int action_get(struct transaction_t *txn)
754 {
755 int r, precond;
678756 struct strlist *param;
679757 const char *tzid;
680758 struct zoneinfo zi;
686764 struct mime_type_t *mime = NULL;
687765
688766 /* Sanity check the parameters */
689 param = hash_lookup("tzid", params);
767 param = hash_lookup("tzid", &txn->req_qparams);
690768 if (!param || param->next /* mandatory, once only */
691769 || strchr(param->s, '.') /* paranoia */) {
692 return json_error_response(txn, "invalid-tzid");
770 return json_error_response(txn, TZ_INVALID_TZID);
693771 }
694772 tzid = param->s;
695773
696774 /* Check/find requested MIME type */
697 param = hash_lookup("format", params);
775 param = hash_lookup("format", &txn->req_qparams);
698776 if (param && !param->next /* optional, once only */) {
699777 for (mime = tz_mime_types; mime->content_type; mime++) {
700778 if (is_mediatype(param->s, mime->content_type)) break;
703781 else mime = tz_mime_types;
704782
705783 if (!mime || !mime->content_type) {
706 return json_error_response(txn, "invalid-format");
784 return json_error_response(txn, TZ_INVALID_FORMAT);
707785 }
708786
709787 /* Check for any truncation */
710 param = hash_lookup("truncate", params);
788 param = hash_lookup("truncate", &txn->req_qparams);
711789 if (param) {
712 truncate = icaltime_from_day_of_year(1, atoi(param->s));
713 truncate.is_date = truncate.hour = truncate.minute = truncate.second = 0;
790 truncate = icaltime_from_year_string(param->s);
714791 if (icaltime_is_null_time(truncate) || param->next /* once only */)
715 return json_error_response(txn, "invalid-truncate");
716 }
717
718 /* Handle tzid=* separately */
719 if (!strcmp(tzid, "*")) return action_get_all(txn, mime, &truncate);
792 return json_error_response(txn, TZ_INVALID_TRUNCATE);
793 }
720794
721795 /* Get info record from the database */
722 if ((r = zoneinfo_lookup(tzid, &zi)))
723 return (r == CYRUSDB_NOTFOUND ? HTTP_NOT_FOUND : HTTP_SERVER_ERROR);
724
725 if (zi.type == ZI_LINK) {
726 /* Check for substitute-alias */
727 param = hash_lookup("substitute-alias", params);
728 if (param && !strcmp(param->s, "true")) substitute = 1;
796 if ((r = zoneinfo_lookup(tzid, &zi))) {
797 return (r == CYRUSDB_NOTFOUND ?
798 json_error_response(txn, TZ_NOT_FOUND) : HTTP_SERVER_ERROR);
729799 }
730800
731801 /* Generate ETag & Last-Modified from info record */
759829 }
760830
761831
762 if (txn->meth == METH_GET) {
832 if (txn->meth != METH_HEAD) {
763833 static struct buf pathbuf = BUF_INITIALIZER;
764834 const char *path, *proto, *host, *msg_base = NULL;
765835 unsigned long msg_size = 0;
784854 vtz = icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT);
785855 prop = icalcomponent_get_first_property(vtz, ICAL_TZID_PROPERTY);
786856
787 if (substitute) {
857 if (zi.type == ZI_LINK) {
858 /* Add EQUIVALENT-TZID */
859 const char *equiv = icalproperty_get_tzid(prop);
860 icalproperty *eprop = icalproperty_new_x(equiv);
861
862 icalproperty_set_x_name(eprop, "EQUIVALENT-TZID");
863 icalcomponent_add_property(vtz, eprop);
864
788865 /* Substitute TZID alias */
789866 icalproperty_set_tzid(prop, tzid);
790867 }
791 else tzid = icalproperty_get_tzid(prop);
792868
793869 /* Start constructing TZURL */
794870 buf_reset(&pathbuf);
800876 (int) strcspn(mime->content_type, ";"),
801877 mime->content_type);
802878 }
803
804879 if (!icaltime_is_null_time(truncate)) {
880 buf_printf(&pathbuf, "&truncate=%d", truncate.year);
881
805882 /* Truncate the VTIMEZONE */
806 truncate_vtimezone(vtz, &truncate);
807
808 buf_printf(&pathbuf, "&truncate=%d", truncate.year);
883 truncate_vtimezone(vtz, truncate);
809884 }
810885
811886 /* Set TZURL property */
832907 }
833908
834909
835 static const char *begin_ical(struct buf *buf)
836 {
837 /* Begin iCalendar stream */
838 buf_setcstr(buf, "BEGIN:VCALENDAR\r\n");
839 buf_printf(buf, "PRODID:-//CyrusIMAP.org/Cyrus %s//EN\r\n",
840 cyrus_version());
841 buf_appendcstr(buf, "VERSION:2.0\r\n");
842
843 return "";
844 }
845
846 static void end_ical(struct buf *buf)
847 {
848 /* End iCalendar stream */
849 buf_setcstr(buf, "END:VCALENDAR\r\n");
850 }
851
852 struct get_rock {
853 struct transaction_t *txn;
854 struct mime_type_t *mime;
855 icaltimetype *truncate;
856 const char *sep;
857 unsigned count;
858 };
859
860 static int get_cb(const char *tzid, int tzidlen,
861 struct zoneinfo *zi __attribute__((unused)),
862 void *rock)
863 {
864 struct get_rock *grock = (struct get_rock *) rock;
865 struct buf *pathbuf = &grock->txn->buf;
866 struct mime_type_t *mime = grock->mime;
867 const char *path, *proto, *host, *msg_base = NULL;
868 unsigned long msg_size = 0;
869 icalcomponent *ical, *vtz;
870 icalproperty *prop;
871 int fd = -1;
872 char *tz_str;
873
874 buf_reset(pathbuf);
875 buf_printf(pathbuf, "%s%s/%.*s.ics",
876 config_dir, FNAME_ZONEINFODIR, tzidlen, tzid);
877 path = buf_cstring(pathbuf);
878
879 /* Open, mmap, and parse the file */
880 if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR;
881 map_refresh(fd, 1, &msg_base, &msg_size, MAP_UNKNOWN_LEN, path, NULL);
882 if (!msg_base) return HTTP_SERVER_ERROR;
883 ical = icalparser_parse_string(msg_base);
884 map_free(&msg_base, &msg_size);
885 close(fd);
886
887 if (grock->count++ && *grock->sep) {
888 /* Add separator, if necessary */
889 struct buf *buf = &grock->txn->resp_body.payload;
890
891 buf_reset(buf);
892 buf_printf_markup(buf, 0, grock->sep);
893 write_body(0, grock->txn, buf_cstring(buf), buf_len(buf));
894 }
895
896 vtz = icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT);
897 prop = icalcomponent_get_first_property(vtz, ICAL_TZID_PROPERTY);
898
899 /* Start constructing TZURL */
900 buf_reset(pathbuf);
901 http_proto_host(grock->txn->req_hdrs, &proto, &host);
902 buf_printf(pathbuf, "%s://%s%s?action=get&tzid=%.*s",
903 proto, host, namespace_timezone.prefix, tzidlen, tzid);
904 if (mime != tz_mime_types) {
905 buf_printf(pathbuf, "&format=%.*s",
906 (int) strcspn(mime->content_type, ";"),
907 mime->content_type);
908 }
909
910 if (!icaltime_is_null_time(*grock->truncate)) {
911 /* Truncate the VTIMEZONE */
912 truncate_vtimezone(vtz, grock->truncate);
913
914 buf_printf(pathbuf, "&truncate=%d", grock->truncate->year);
915 }
916
917 /* Set TZURL property */
918 prop = icalproperty_new_tzurl(buf_cstring(pathbuf));
919 icalcomponent_add_property(vtz, prop);
920
921 /* Output the (converted) VTIMEZONE component */
922 tz_str = mime->to_string(vtz);
923 write_body(0, grock->txn, tz_str, strlen(tz_str));
924 free(tz_str);
925
926 icalcomponent_free(ical);
927
928 return 0;
929 }
930
931
932 static int action_get_all(struct transaction_t *txn,
933 struct mime_type_t *mime, icaltimetype *truncate)
934 {
935 int r, precond;
936 struct resp_body_t *resp_body = &txn->resp_body;
937 struct zoneinfo info;
938 time_t lastmod;
939 struct buf *buf = &resp_body->payload;
940 struct get_rock grock = { txn, mime, truncate, NULL, 0 };
941
942 /* Get info record from the database */
943 if ((r = zoneinfo_lookup_info(&info))) return HTTP_SERVER_ERROR;
944
945 /* Generate ETag & Last-Modified from info record */
946 assert(!buf_len(&txn->buf));
947 buf_printf(&txn->buf, "%u-%ld", strhash(info.data->s), info.dtstamp);
948 lastmod = info.dtstamp;
949 freestrlist(info.data);
950
951 /* Check any preconditions */
952 precond = check_precond(txn, NULL, buf_cstring(&txn->buf), lastmod);
953
954 switch (precond) {
955 case HTTP_OK:
956 case HTTP_NOT_MODIFIED:
957 /* Fill in ETag, Last-Modified, and Expires */
958 resp_body->etag = buf_cstring(&txn->buf);
959 resp_body->lastmod = lastmod;
960 resp_body->maxage = 86400; /* 24 hrs */
961 txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE;
962 if (httpd_userid) txn->flags.cc |= CC_PUBLIC;
963
964 if (precond != HTTP_NOT_MODIFIED) break;
965
966 default:
967 /* We failed a precondition - don't perform the request */
968 resp_body->type = NULL;
969 return precond;
970 }
971
972 /* Setup for chunked response */
973 txn->flags.te |= TE_CHUNKED;
974 txn->flags.vary |= VARY_ACCEPT;
975 txn->resp_body.type = mime->content_type;
976
977 /* Short-circuit for HEAD request */
978 if (txn->meth == METH_HEAD) {
979 response_header(HTTP_OK, txn);
980 return 0;
981 }
982
983 /* iCalendar data in response should not be transformed */
984 txn->flags.cc |= CC_NOTRANSFORM;
985
986 /* Begin (converted) iCalendar stream */
987 grock.sep = mime->begin_stream(buf);
988 write_body(HTTP_OK, txn, buf_cstring(buf), buf_len(buf));
989
990 zoneinfo_find(NULL, 1 /* tzid_only */, 0, &get_cb, &grock);
991
992 /* End (converted) iCalendar stream */
993 mime->end_stream(buf);
994 write_body(0, txn, buf_cstring(buf), buf_len(buf));
995
996 /* End of output */
997 write_body(0, txn, NULL, 0);
998
999 return 0;
1000 }
1001
1002
1003910 static int observance_compare(const void *obs1, const void *obs2)
1004911 {
1005912 return icaltime_compare(((struct observance *) obs1)->onset,
1007914 }
1008915
1009916 /* Perform an expand action */
1010 static int action_expand(struct transaction_t *txn, struct hash_table *params)
917 static int action_expand(struct transaction_t *txn)
1011918 {
1012919 int r, precond;
1013920 struct strlist *param;
1019926 json_t *root = NULL;
1020927
1021928 /* Sanity check the parameters */
1022 param = hash_lookup("tzid", params);
929 param = hash_lookup("tzid", &txn->req_qparams);
1023930 if (!param || param->next /* mandatory, once only */
1024931 || strchr(param->s, '.') /* paranoia */) {
1025 return json_error_response(txn, "invalid-tzid");
932 return json_error_response(txn, TZ_INVALID_TZID);
1026933 }
1027934 tzid = param->s;
1028935
1029 param = hash_lookup("changedsince", params);
936 param = hash_lookup("changedsince", &txn->req_qparams);
1030937 if (param) {
1031938 changedsince = icaltime_as_timet(icaltime_from_string(param->s));
1032939 if (!changedsince || param->next /* once only */)
1033 return json_error_response(txn, "invalid-changedsince");
1034 }
1035
1036 param = hash_lookup("start", params);
940 return json_error_response(txn, TZ_INVALID_CHANGEDSINCE);
941 }
942
943 param = hash_lookup("start", &txn->req_qparams);
1037944 if (param) {
1038 start = icaltime_from_string(param->s);
945 start = icaltime_from_year_string(param->s);
1039946 if (icaltime_is_null_time(start) || param->next /* once only */)
1040 return json_error_response(txn, "invalid-start");
947 return json_error_response(txn, TZ_INVALID_START);
1041948 }
1042949 else {
1043950 /* Default to current year */
1046953
1047954 start = icaltime_from_day_of_year(1, tm->tm_year + 1900);
1048955 }
1049
1050 param = hash_lookup("end", params);
956 start.is_date = 0;
957
958 param = hash_lookup("end", &txn->req_qparams);
1051959 if (param) {
1052 end = icaltime_from_string(param->s);
960 end = icaltime_from_year_string(param->s);
1053961 if (icaltime_compare(end, start) <= 0 /* end MUST be > start */
1054962 || param->next /* once only */)
1055 return json_error_response(txn, "invalid-end");
963 return json_error_response(txn, TZ_INVALID_END);
1056964 }
1057965 else {
1058966 /* Default to start year + 10 */
1059967 memcpy(&end, &start, sizeof(icaltimetype));
1060968 end.year += 10;
1061969 }
970 end.is_date = 0;
1062971
1063972 /* Get info record from the database */
1064 if ((r = zoneinfo_lookup(tzid, &zi)))
1065 return (r == CYRUSDB_NOTFOUND ? HTTP_NOT_FOUND : HTTP_SERVER_ERROR);
973 if ((r = zoneinfo_lookup(tzid, &zi))) {
974 return (r == CYRUSDB_NOTFOUND ?
975 json_error_response(txn, TZ_NOT_FOUND) : HTTP_SERVER_ERROR);
976 }
1066977
1067978 /* Generate ETag & Last-Modified from info record */
1068979 assert(!buf_len(&txn->buf));
10951006 }
10961007
10971008
1098 if (txn->meth == METH_GET) {
1009 if (txn->meth != METH_HEAD) {
10991010 static struct buf pathbuf = BUF_INITIALIZER;
11001011 const char *path, *msg_base = NULL;
11011012 unsigned long msg_size = 0;
1102 icalcomponent *ical, *vtz, *comp;
1013 icalcomponent *ical, *vtz;
11031014 char dtstamp[21];
11041015 icalarray *obsarray;
11051016 json_t *jobsarray;
11301041
11311042 /* Create an array of observances */
11321043 obsarray = icalarray_new(sizeof(struct observance), 20);
1133
1134 /* Process each VTMEZONE STANDARD/DAYLIGHT subcomponent */
11351044 vtz = icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT);
1136 for (comp = icalcomponent_get_first_component(vtz, ICAL_ANY_COMPONENT);
1137 comp;
1138 comp = icalcomponent_get_next_component(vtz, ICAL_ANY_COMPONENT)) {
1139
1140 icaltimetype dtstart = icaltime_null_time();
1141 struct observance obs;
1142 icalproperty *prop, *rrule_prop = NULL;
1143
1144 /* Grab the properties that we require to expand recurrences */
1145 memset(&obs, 0, sizeof(struct observance));
1146 for (prop = icalcomponent_get_first_property(comp,
1147 ICAL_ANY_PROPERTY);
1148 prop;
1149 prop = icalcomponent_get_next_property(comp,
1150 ICAL_ANY_PROPERTY)) {
1151
1152 switch (icalproperty_isa(prop)) {
1153 case ICAL_TZNAME_PROPERTY:
1154 obs.name = icalproperty_get_tzname(prop);
1155 break;
1156
1157 case ICAL_DTSTART_PROPERTY:
1158 dtstart = icalproperty_get_dtstart(prop);
1159 break;
1160
1161 case ICAL_TZOFFSETFROM_PROPERTY:
1162 obs.offset_from = icalproperty_get_tzoffsetfrom(prop);
1163 break;
1164
1165 case ICAL_TZOFFSETTO_PROPERTY:
1166 obs.offset_to = icalproperty_get_tzoffsetto(prop);
1167 break;
1168
1169 case ICAL_RRULE_PROPERTY:
1170 rrule_prop = prop;
1171 break;
1172
1173 default:
1174 /* ignore all other properties */
1175 break;
1176 }
1177 }
1178
1179 /* We MUST have TZNAME, DTSTART, TZOFFSETFROM and TZOFFSETTO */
1180 if (!obs.name || !obs.offset_from || !obs.offset_to ||
1181 icaltime_is_null_time(dtstart)) continue;
1182
1183 /* Adjust DTSTART to UTC */
1184 memcpy(&obs.onset, &dtstart, sizeof(icaltimetype));
1185 icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
1186 obs.onset.is_utc = 1;
1187
1188 if (icaltime_compare(obs.onset, end) > 0) {
1189 /* Skip observance(s) after our window */
1190 }
1191 else if (rrule_prop) {
1192 /* Add any RRULE observances within our window */
1193 struct icalrecurrencetype rrule;
1194 icalrecur_iterator *ritr;
1195 icaltimetype recur;
1196
1197 rrule = icalproperty_get_rrule(rrule_prop);
1198
1199 if (!icaltime_is_null_time(rrule.until) && rrule.until.is_utc) {
1200 /* Adjust UNTIL to local time */
1201 icaltime_adjust(&rrule.until, 0, 0, 0, obs.offset_from);
1202 rrule.until.is_utc = 0;
1203 }
1204
1205 if (icaltime_compare(start, obs.onset) > 0) {
1206 /* Set iterator dtstart to be 1 day prior to our window */
1207 obs.onset.year = start.year;
1208 obs.onset.month = start.month;
1209 obs.onset.day = start.day - 1;
1210 }
1211
1212 /* Adjust iterator dtstart to local time */
1213 icaltime_adjust(&obs.onset, 0, 0, 0, obs.offset_from);
1214 obs.onset.is_utc = 0;
1215
1216 ritr = icalrecur_iterator_new(rrule, obs.onset);
1217 while (!icaltime_is_null_time(recur =
1218 icalrecur_iterator_next(ritr))) {
1219 /* Adjust observance to UTC */
1220 memcpy(&obs.onset, &recur, sizeof(icaltimetype));
1221 icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
1222 obs.onset.is_utc = 1;
1223
1224 if (icaltime_compare(obs.onset, end) > 0) {
1225 /* Quit if we've gone past our window */
1226 break;
1227 }
1228 else if (icaltime_compare(obs.onset, start) < 0) {
1229 /* Skip observances prior to our window */
1230 }
1231 else {
1232 /* Add the observance to our array */
1233 icalarray_append(obsarray, &obs);
1234 }
1235 }
1236 icalrecur_iterator_free(ritr);
1237 }
1238 else if (icaltime_compare(obs.onset, start) < 0) {
1239 /* Skip observances prior to our window */
1240 }
1241 else {
1242 /* Add the DTSTART observance to our array */
1243 icalarray_append(obsarray, &obs);
1244 }
1245
1246 /* Add any RDATE observances within our window */
1247 for (prop = icalcomponent_get_first_property(comp,
1248 ICAL_RDATE_PROPERTY);
1249 prop;
1250 prop = icalcomponent_get_next_property(comp,
1251 ICAL_RDATE_PROPERTY)) {
1252 struct icaldatetimeperiodtype rdate =
1253 icalproperty_get_rdate(prop);
1254
1255 /* Adjust RDATE to UTC */
1256 memcpy(&obs.onset, &rdate.time, sizeof(icaltimetype));
1257 icaltime_adjust(&obs.onset, 0, 0, 0, -obs.offset_from);
1258 obs.onset.is_utc = 1;
1259
1260 if (icaltime_compare(obs.onset, start) < 0) {
1261 /* Skip observances prior to our window */
1262 }
1263 else if (icaltime_compare(obs.onset, end) > 0) {
1264 /* Skip observances after our window */
1265 }
1266 else if (icaltime_compare(obs.onset, dtstart) == 0) {
1267 /* Skip duplicates of DTSTART observance */
1268 }
1269 else {
1270 /* Add the RDATE observance to our array */
1271 icalarray_append(obsarray, &obs);
1272 }
1273 }
1274 }
1045 expand_vtimezone(vtz, obsarray, start, end);
12751046
12761047 /* Sort the observances by onset */
12771048 icalarray_sort(obsarray, &observance_compare);
12851056 json_pack("{s:s s:s s:i s:i}",
12861057 "name", obs->name,
12871058 "onset",
1288 icaltime_as_ical_string(obs->onset),
1059 icaltime_as_iso_string(obs->onset),
12891060 "utc-offset-from", obs->offset_from,
12901061 "utc-offset-to", obs->offset_to));
12911062 }
13321103 }
13331104
13341105
1335 static int json_error_response(struct transaction_t *txn, const char *err)
1106 static int json_error_response(struct transaction_t *txn, long tz_code)
13361107 {
13371108 json_t *root;
1338
1339 root = json_pack("{s:s}", "error", err);
1109 long http_code;
1110
1111 root = json_pack("{s:s}", "error", error_message(tz_code));
13401112 if (!root) {
13411113 txn->error.desc = "Unable to create JSON response";
13421114 return HTTP_SERVER_ERROR;
13431115 }
13441116
1345 return json_response(HTTP_BAD_REQUEST, txn, root, NULL);
1346 }
1117 switch (tz_code) {
1118 case TZ_NOT_FOUND:
1119 http_code = HTTP_NOT_FOUND;
1120 break;
1121
1122 default:
1123 http_code = HTTP_BAD_REQUEST;
1124 break;
1125 }
1126
1127 return json_response(http_code, txn, root, NULL);
1128 }
124124
125125 sasl_conn_t *httpd_saslconn; /* the sasl connection context */
126126
127 static struct mailbox *httpd_mailbox = NULL;
128127 static struct wildmat *allow_cors = NULL;
129128 int httpd_timeout, httpd_keepalive;
130129 char *httpd_userid = NULL, *proxy_userid = NULL;
323322 backend_cached = NULL;
324323 backend_current = NULL;
325324
326 if (httpd_mailbox) mailbox_close(&httpd_mailbox);
327 httpd_mailbox = NULL;
328
329325 if (httpd_in) {
330326 prot_NONBLOCK(httpd_in);
331327 prot_fill(httpd_in);
479475 }
480476
481477 /* Construct serverinfo string */
482 if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) {
483 buf_printf(&serverinfo, "Cyrus/%s%s Cyrus-SASL/%u.%u.%u",
484 cyrus_version(), config_mupdate_server ? " (Murder)" : "",
485 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP);
478 buf_printf(&serverinfo, "Cyrus/%s%s Cyrus-SASL/%u.%u.%u",
479 cyrus_version(), config_mupdate_server ? " (Murder)" : "",
480 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP);
486481 #ifdef HAVE_SSL
487 buf_printf(&serverinfo, " OpenSSL/%s", SHLIB_VERSION_NUMBER);
482 buf_printf(&serverinfo, " OpenSSL/%s", SHLIB_VERSION_NUMBER);
488483 #endif
489484 #ifdef HAVE_ZLIB
490 buf_printf(&serverinfo, " zlib/%s", ZLIB_VERSION);
485 buf_printf(&serverinfo, " zlib/%s", ZLIB_VERSION);
491486 #endif
492 buf_printf(&serverinfo, " libxml2/%s", LIBXML_DOTTED_VERSION);
493 }
487 buf_printf(&serverinfo, " libxml/%s", LIBXML_DOTTED_VERSION);
494488
495489 /* Do any namespace specific initialization */
496490 config_httpmodules = config_getbitfield(IMAPOPT_HTTPMODULES);
701695 }
702696 if (backend_cached) free(backend_cached);
703697
704 if (httpd_mailbox) mailbox_close(&httpd_mailbox);
705
706698 sync_log_done();
707699
708700 mboxlist_close();
922914 int ret, empty, r, i, c;
923915 char *p;
924916 tok_t tok;
925 const char **hdr;
917 const char **hdr, *query;
926918 const struct namespace_t *namespace;
927919 const struct method_t *meth_t;
928920 struct request_line_t *req_line = &txn.req_line;
934926 txn.flags.vary = VARY_AE;
935927 memset(req_line, 0, sizeof(struct request_line_t));
936928 memset(&txn.req_tgt, 0, sizeof(struct request_target_t));
929 construct_hash_table(&txn.req_qparams, 10, 1);
937930 txn.req_uri = NULL;
938931 txn.auth_chal.param = NULL;
939932 txn.req_hdrs = NULL;
11071100 }
11081101
11091102 /* Check message framing */
1110 else if ((r = parse_framing(txn.req_hdrs, &txn.req_body,
1111 &txn.error.desc))) {
1103 else if ((r = http_parse_framing(txn.req_hdrs, &txn.req_body,
1104 &txn.error.desc))) {
11121105 ret = r;
11131106 }
11141107
11451138
11461139 if (ret) goto done;
11471140
1141 query = URI_QUERY(txn.req_uri);
1142
11481143 /* Find the namespace of the requested resource */
11491144 for (i = 0; namespaces[i]; i++) {
11501145 const char *path = txn.req_uri->path;
1151 const char *query = URI_QUERY(txn.req_uri);
11521146 size_t len;
11531147
11541148 /* Skip disabled namespaces */
11591153 len = strlen(namespaces[i]->well_known);
11601154 if (!strncmp(path, namespaces[i]->well_known, len) &&
11611155 (!path[len] || path[len] == '/')) {
1162
1163 buf_setcstr(&txn.buf, namespaces[i]->prefix);
1156
1157 hdr = spool_getheader(txn.req_hdrs, "Host");
1158 buf_reset(&txn.buf);
1159 buf_printf(&txn.buf, "%s://%s",
1160 https? "https" : "http", hdr[0]);
1161 buf_appendcstr(&txn.buf, namespaces[i]->prefix);
11641162 buf_appendcstr(&txn.buf, path + len);
11651163 if (query) buf_printf(&txn.buf, "?%s", query);
11661164 txn.location = buf_cstring(&txn.buf);
12151213 syslog(LOG_DEBUG, "auth failed - reinit");
12161214 reset_saslconn(&httpd_saslconn);
12171215 txn.auth_chal.scheme = NULL;
1218 r = SASL_FAIL;
1216 ret = HTTP_UNAUTHORIZED;
12191217 }
12201218 }
12211219 else if (!httpd_userid && txn.auth_chal.scheme) {
12371235 syslog(LOG_DEBUG, "proxy authz failed - reinit");
12381236 reset_saslconn(&httpd_saslconn);
12391237 txn.auth_chal.scheme = NULL;
1238 ret = HTTP_UNAUTHORIZED;
12401239 }
12411240 else {
12421241 httpd_userid = xstrdup(authzid);
12451244 }
12461245
12471246 /* Request authentication, if necessary */
1248 if (!httpd_userid &&
1249 (r || (namespace->need_auth && txn.meth != METH_OPTIONS))) {
1250 need_auth:
1251 /* User must authenticate */
1252
1253 if (httpd_tls_required) {
1254 /* We only support TLS+Basic, so tell client to use TLS */
1255
1256 /* Check which response is required */
1257 if ((hdr = spool_getheader(txn.req_hdrs, "Upgrade")) &&
1258 !strncmp(hdr[0], TLS_VERSION, strcspn(hdr[0], " ,"))) {
1259 /* Client (Murder proxy) supports RFC 2817 (TLS upgrade) */
1260
1261 response_header(HTTP_UPGRADE, &txn);
1262 }
1263 else {
1264 /* All other clients use RFC 2818 (HTTPS) */
1265 const char *path = txn.req_uri->path;
1266 const char *query = URI_QUERY(txn.req_uri);
1267 struct buf *html = &txn.resp_body.payload;
1268
1269 /* Create https URL */
1270 hdr = spool_getheader(txn.req_hdrs, "Host");
1271 buf_printf(&txn.buf, "https://%s", hdr[0]);
1272 if (strcmp(path, "*")) {
1273 buf_appendcstr(&txn.buf, path);
1274 if (query) buf_printf(&txn.buf, "?%s", query);
1275 }
1276
1277 txn.location = buf_cstring(&txn.buf);
1278
1279 /* Create HTML body */
1280 buf_reset(html);
1281 buf_printf(html, tls_message,
1282 buf_cstring(&txn.buf), buf_cstring(&txn.buf));
1283
1284 /* Output our HTML response */
1285 txn.resp_body.type = "text/html; charset=utf-8";
1286 write_body(HTTP_MOVED, &txn,
1287 buf_cstring(html), buf_len(html));
1288 }
1289 }
1290 else {
1291 /* Tell client to authenticate */
1247 switch (txn.meth) {
1248 case METH_GET:
1249 case METH_HEAD:
1250 case METH_OPTIONS:
1251 /* Let method processing function decide if auth is needed */
1252 break;
1253
1254 default:
1255 if (!httpd_userid && namespace->need_auth) {
1256 /* Authentication required */
12921257 ret = HTTP_UNAUTHORIZED;
1293 if (r == SASL_CONTINUE)
1294 txn.error.desc = "Continue authentication exchange";
1295 else if (r) txn.error.desc = "Authentication failed";
1296 else txn.error.desc =
1297 "Must authenticate to access the specified target";
1298 }
1299
1300 goto done;
1301 }
1258 }
1259 }
1260
1261 if (ret) goto need_auth;
13021262
13031263 /* Check if this is a Cross-Origin Resource Sharing request */
13041264 if (allow_cors && (hdr = spool_getheader(txn.req_hdrs, "Origin"))) {
13741334 }
13751335 }
13761336
1337 /* Parse any query parameters */
1338 if (query) {
1339 /* Parse the query string and add param/value pairs to hash table */
1340 tok_t tok;
1341 char *param;
1342
1343 assert(!buf_len(&txn.buf)); /* Unescape buffer */
1344
1345 tok_init(&tok, (char *) query, ";&=", TOK_TRIMLEFT|TOK_TRIMRIGHT);
1346 while ((param = tok_next(&tok))) {
1347 struct strlist *vals;
1348 char *value = tok_next(&tok);
1349 size_t len;
1350
1351 if (!value) value = "";
1352 len = strlen(value);
1353 buf_ensure(&txn.buf, len);
1354
1355 vals = hash_lookup(param, &txn.req_qparams);
1356 appendstrlist(&vals,
1357 xmlURIUnescapeString(value, len, txn.buf.s));
1358 hash_insert(param, vals, &txn.req_qparams);
1359 }
1360 tok_fini(&tok);
1361
1362 buf_reset(&txn.buf);
1363 }
1364
13771365 /* Start method processing alarm (HTTP/1.1+ only) */
13781366 if (!txn.flags.ver1_0) alarm(httpd_keepalive);
13791367
13801368 /* Process the requested method */
13811369 ret = (*meth_t->proc)(&txn, meth_t->params);
1382 if (ret == HTTP_UNAUTHORIZED) goto need_auth;
1370
1371 need_auth:
1372 if (ret == HTTP_UNAUTHORIZED) {
1373 /* User must authenticate */
1374
1375 if (httpd_tls_required) {
1376 /* We only support TLS+Basic, so tell client to use TLS */
1377 ret = 0;
1378
1379 /* Check which response is required */
1380 if ((hdr = spool_getheader(txn.req_hdrs, "Upgrade")) &&
1381 !strncmp(hdr[0], TLS_VERSION, strcspn(hdr[0], " ,"))) {
1382 /* Client (Murder proxy) supports RFC 2817 (TLS upgrade) */
1383
1384 response_header(HTTP_UPGRADE, &txn);
1385 }
1386 else {
1387 /* All other clients use RFC 2818 (HTTPS) */
1388 const char *path = txn.req_uri->path;
1389 struct buf *html = &txn.resp_body.payload;
1390
1391 /* Create https URL */
1392 hdr = spool_getheader(txn.req_hdrs, "Host");
1393 buf_printf(&txn.buf, "https://%s", hdr[0]);
1394 if (strcmp(path, "*")) {
1395 buf_appendcstr(&txn.buf, path);
1396 if (query) buf_printf(&txn.buf, "?%s", query);
1397 }
1398
1399 txn.location = buf_cstring(&txn.buf);
1400
1401 /* Create HTML body */
1402 buf_reset(html);
1403 buf_printf(html, tls_message,
1404 buf_cstring(&txn.buf), buf_cstring(&txn.buf));
1405
1406 /* Output our HTML response */
1407 txn.resp_body.type = "text/html; charset=utf-8";
1408 write_body(HTTP_MOVED, &txn,
1409 buf_cstring(html), buf_len(html));
1410 }
1411 }
1412 else {
1413 /* Tell client to authenticate */
1414 if (r == SASL_CONTINUE)
1415 txn.error.desc = "Continue authentication exchange";
1416 else if (r) txn.error.desc = "Authentication failed";
1417 else txn.error.desc =
1418 "Must authenticate to access the specified target";
1419 }
1420 }
13831421
13841422 done:
13851423 /* Handle errors (success responses handled by method functions) */
13881426 /* Read and discard any unread request body */
13891427 if (!(txn.flags.conn & CONN_CLOSE)) {
13901428 txn.req_body.flags |= BODY_DISCARD;
1391 if (read_body(httpd_in, txn.req_hdrs, &txn.req_body,
1392 &txn.error.desc)) {
1429 if (http_read_body(httpd_in, httpd_out,
1430 txn.req_hdrs, &txn.req_body, &txn.error.desc)) {
13931431 txn.flags.conn = CONN_CLOSE;
13941432 }
13951433 }
13971435 /* Memory cleanup */
13981436 if (txn.req_uri) xmlFreeURI(txn.req_uri);
13991437 if (txn.req_hdrs) spool_free_hdrcache(txn.req_hdrs);
1438 free_hash_table(&txn.req_qparams, (void (*)(void *)) &freestrlist);
14001439
14011440 if (txn.flags.conn & CONN_CLOSE) {
14021441 buf_free(&txn.buf);
14681507 }
14691508
14701509
1471 /* Compare Content-Types */
1472 int is_mediatype(const char *pat, const char *type)
1473 {
1474 const char *psep = strchr(pat, '/');
1475 const char *tsep = strchr(type, '/');
1476 size_t plen;
1477 size_t tlen;
1478 int alltypes;
1479
1480 /* Check type */
1481 if (!psep || !tsep) return 0;
1482 plen = psep - pat;
1483 tlen = tsep - type;
1484
1485 alltypes = !strncmp(pat, "*", plen);
1486
1487 if (!alltypes && ((tlen != plen) || strncasecmp(pat, type, tlen))) return 0;
1488
1489 /* Check subtype */
1490 pat = ++psep;
1491 plen = strcspn(pat, "; \r\n\0");
1492 type = ++tsep;
1493 tlen = strcspn(type, "; \r\n\0");
1494
1495 return (!strncmp(pat, "*", plen) ||
1496 (!alltypes && (tlen == plen) && !strncasecmp(pat, type, tlen)));
1497 }
1498
1499
15001510 /* Calculate compile time of a file for use as Last-Modified and/or ETag */
15011511 time_t calc_compile_time(const char *time, const char *date)
15021512 {
15171527 }
15181528
15191529 return mktime(&tm);
1520 }
1521
1522
1523 /*
1524 * Parse the framing of a request or response message.
1525 * Handles chunked, gzip, deflate TE only.
1526 * Handles close-delimited response bodies (no Content-Length specified)
1527 */
1528 int parse_framing(hdrcache_t hdrs, struct body_t *body, const char **errstr)
1529 {
1530 static unsigned max_msgsize = 0;
1531 const char **hdr;
1532
1533 if (!max_msgsize) {
1534 max_msgsize = config_getint(IMAPOPT_MAXMESSAGESIZE);
1535
1536 /* If max_msgsize is 0, allow any size */
1537 if (!max_msgsize) max_msgsize = INT_MAX;
1538 }
1539
1540 body->framing = FRAMING_LENGTH;
1541 body->te = TE_NONE;
1542 body->len = 0;
1543 body->max = max_msgsize;
1544
1545 /* Check for Transfer-Encoding */
1546 if ((hdr = spool_getheader(hdrs, "Transfer-Encoding"))) {
1547 for (; *hdr; hdr++) {
1548 tok_t tok = TOK_INITIALIZER(*hdr, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
1549 char *token;
1550
1551 while ((token = tok_next(&tok))) {
1552 if (body->te & TE_CHUNKED) {
1553 /* "chunked" MUST only appear once and MUST be last */
1554 break;
1555 }
1556 else if (!strcasecmp(token, "chunked")) {
1557 body->te |= TE_CHUNKED;
1558 body->framing = FRAMING_CHUNKED;
1559 }
1560 else if (body->te & ~TE_CHUNKED) {
1561 /* can't combine compression codings */
1562 break;
1563 }
1564 #ifdef HAVE_ZLIB
1565 else if (!strcasecmp(token, "deflate"))
1566 body->te = TE_DEFLATE;
1567 else if (!strcasecmp(token, "gzip") ||
1568 !strcasecmp(token, "x-gzip"))
1569 body->te = TE_GZIP;
1570 #endif
1571 else if (!(body->flags & BODY_DISCARD)) {
1572 /* unknown/unsupported TE */
1573 break;
1574 }
1575 }
1576 tok_fini(&tok);
1577 if (token) break; /* error */
1578 }
1579
1580 if (*hdr) {
1581 *errstr = "Specified Transfer-Encoding not implemented";
1582 return HTTP_NOT_IMPLEMENTED;
1583 }
1584
1585 /* Check if this is a non-chunked response */
1586 else if (!(body->te & TE_CHUNKED)) {
1587 if ((body->flags & BODY_RESPONSE) && (body->flags & BODY_CLOSE)) {
1588 body->framing = FRAMING_CLOSE;
1589 }
1590 else {
1591 *errstr = "Final Transfer-Encoding MUST be \"chunked\"";
1592 return HTTP_NOT_IMPLEMENTED;
1593 }
1594 }
1595 }
1596
1597 /* Check for Content-Length */
1598 else if ((hdr = spool_getheader(hdrs, "Content-Length"))) {
1599 if (hdr[1]) {
1600 *errstr = "Multiple Content-Length header fields";
1601 return HTTP_BAD_REQUEST;
1602 }
1603
1604 body->len = strtoul(hdr[0], NULL, 10);
1605 if (body->len > max_msgsize) return HTTP_TOO_LARGE;
1606
1607 body->framing = FRAMING_LENGTH;
1608 }
1609
1610 /* Check if this is a close-delimited response */
1611 else if (body->flags & BODY_RESPONSE) {
1612 if (body->flags & BODY_CLOSE) body->framing = FRAMING_CLOSE;
1613 else return HTTP_LENGTH_REQUIRED;
1614 }
1615
1616 return 0;
1617 }
1618
1619
1620 /*
1621 * Read the body of a request or response.
1622 * Handles chunked, gzip, deflate TE only.
1623 * Handles close-delimited response bodies (no Content-Length specified)
1624 * Handles gzip and deflate CE only.
1625 */
1626 int read_body(struct protstream *pin, hdrcache_t hdrs, struct body_t *body,
1627 const char **errstr)
1628 {
1629 char buf[PROT_BUFSIZE];
1630 unsigned n;
1631 int r = 0;
1632
1633 syslog(LOG_DEBUG, "read_body(%#x)", body->flags);
1634
1635 if (body->flags & BODY_DONE) return 0;
1636 body->flags |= BODY_DONE;
1637
1638 if (!(body->flags & BODY_DISCARD)) buf_reset(&body->payload);
1639 else if (body->flags & BODY_CONTINUE) {
1640 /* Don't care about the body and client hasn't sent it, we're done */
1641 return 0;
1642 }
1643
1644 if (body->framing == FRAMING_UNKNOWN) {
1645 /* Get message framing */
1646 r = parse_framing(hdrs, body, errstr);
1647 if (r) return r;
1648 }
1649
1650 if (body->flags & BODY_CONTINUE) {
1651 /* Tell client to send the body */
1652 response_header(HTTP_CONTINUE, NULL);
1653 }
1654
1655 /* Read and buffer the body */
1656 switch (body->framing) {
1657 case FRAMING_LENGTH:
1658 /* Read 'len' octets */
1659 for (; body->len; body->len -= n) {
1660 if (body->flags & BODY_DISCARD)
1661 n = prot_read(pin, buf, MIN(body->len, PROT_BUFSIZE));
1662 else
1663 n = prot_readbuf(pin, &body->payload, body->len);
1664
1665 if (!n) {
1666 syslog(LOG_ERR, "prot_read() error");
1667 *errstr = "Unable to read body data";
1668 goto read_failure;
1669 }
1670 }
1671
1672 break;
1673
1674 case FRAMING_CHUNKED:
1675 {
1676 unsigned last = 0;
1677
1678 /* Read chunks until last-chunk (zero chunk-size) */
1679 do {
1680 unsigned chunk;
1681
1682 /* Read chunk-size */
1683 if (!prot_fgets(buf, PROT_BUFSIZE, pin) ||
1684 sscanf(buf, "%x", &chunk) != 1) {
1685 *errstr = "Unable to read chunk size";
1686 goto read_failure;
1687
1688 /* XXX Do we need to parse chunk-ext? */
1689 }
1690 else if (chunk > body->max - body->len) return HTTP_TOO_LARGE;
1691
1692 if (!chunk) {
1693 /* last-chunk */
1694 last = 1;
1695
1696 /* Read/parse any trailing headers */
1697 spool_fill_hdrcache(pin, NULL, hdrs, NULL);
1698 }
1699
1700 /* Read 'chunk' octets */
1701 for (; chunk; chunk -= n) {
1702 if (body->flags & BODY_DISCARD)
1703 n = prot_read(pin, buf, MIN(chunk, PROT_BUFSIZE));
1704 else
1705 n = prot_readbuf(pin, &body->payload, chunk);
1706
1707 if (!n) {
1708 syslog(LOG_ERR, "prot_read() error");
1709 *errstr = "Unable to read chunk data";
1710 goto read_failure;
1711 }
1712 body->len += n;
1713 }
1714
1715 /* Read CRLF terminating the chunk/trailer */
1716 if (!prot_fgets(buf, sizeof(buf), pin)) {
1717 *errstr = "Missing CRLF following chunk/trailer";
1718 goto read_failure;
1719 }
1720
1721 } while (!last);
1722
1723 body->te &= ~TE_CHUNKED;
1724
1725 break;
1726 }
1727
1728 case FRAMING_CLOSE:
1729 /* Read until EOF */
1730 do {
1731 if (body->flags & BODY_DISCARD)
1732 n = prot_read(pin, buf, PROT_BUFSIZE);
1733 else
1734 n = prot_readbuf(pin, &body->payload, PROT_BUFSIZE);
1735
1736 if (n > body->max - body->len) return HTTP_TOO_LARGE;
1737 body->len += n;
1738
1739 } while (n);
1740
1741 if (!pin->eof) goto read_failure;
1742
1743 break;
1744
1745 default:
1746 /* XXX Should never get here */
1747 *errstr = "Unknown length of read body data";
1748 goto read_failure;
1749 }
1750
1751
1752 if (!(body->flags & BODY_DISCARD) && buf_len(&body->payload)) {
1753 #ifdef HAVE_ZLIB
1754 /* Decode the payload, if necessary */
1755 if (body->te == TE_DEFLATE)
1756 r = buf_inflate(&body->payload, DEFLATE_ZLIB);
1757 else if (body->te == TE_GZIP)
1758 r = buf_inflate(&body->payload, DEFLATE_GZIP);
1759
1760 if (r) {
1761 *errstr = "Error decoding payload";
1762 return HTTP_BAD_REQUEST;
1763 }
1764 #endif
1765
1766 /* Decode the representation, if necessary */
1767 if (body->flags & BODY_DECODE) {
1768 const char **hdr;
1769
1770 if (!(hdr = spool_getheader(hdrs, "Content-Encoding"))) {
1771 /* nothing to see here */
1772 }
1773
1774 #ifdef HAVE_ZLIB
1775 else if (!strcasecmp(hdr[0], "deflate")) {
1776 const char **ua = spool_getheader(hdrs, "User-Agent");
1777
1778 /* Try to detect Microsoft's broken deflate */
1779 if (ua && strstr(ua[0], "; MSIE "))
1780 r = buf_inflate(&body->payload, DEFLATE_RAW);
1781 else
1782 r = buf_inflate(&body->payload, DEFLATE_ZLIB);
1783 }
1784 else if (!strcasecmp(hdr[0], "gzip") ||
1785 !strcasecmp(hdr[0], "x-gzip"))
1786 r = buf_inflate(&body->payload, DEFLATE_GZIP);
1787 #endif
1788 else {
1789 *errstr = "Specified Content-Encoding not accepted";
1790 return HTTP_BAD_MEDIATYPE;
1791 }
1792
1793 if (r) {
1794 *errstr = "Error decoding content";
1795 return HTTP_BAD_REQUEST;
1796 }
1797 }
1798 }
1799
1800 return 0;
1801
1802 read_failure:
1803 if (strcmpsafe(prot_error(httpd_in), PROT_EOF_STRING)) {
1804 /* client timed out */
1805 *errstr = prot_error(httpd_in);
1806 syslog(LOG_WARNING, "%s, closing connection", *errstr);
1807 return HTTP_TIMEOUT;
1808 }
1809 else return HTTP_BAD_REQUEST;
18101530 }
18111531
18121532
21851905
21861906
21871907 /* Response Context */
2188 if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) {
2189 prot_printf(httpd_out, "Server: %s\r\n", buf_cstring(&serverinfo));
2190 }
21911908 if (txn->flags.mime) {
21921909 prot_puts(httpd_out, "MIME-Version: 1.0\r\n");
21931910 }
22181935 break;
22191936
22201937 case METH_OPTIONS:
1938 if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) {
1939 prot_printf(httpd_out, "Server: %s\r\n",
1940 buf_cstring(&serverinfo));
1941 }
1942
22211943 if (txn->req_tgt.allow & ALLOW_DAV) {
22221944 /* Construct DAV header(s) based on namespace of request URL */
22231945 prot_printf(httpd_out, "DAV: 1,%s 3, access-control%s\r\n",
22251947 (txn->req_tgt.allow & ALLOW_WRITECOL) ?
22261948 ", extended-mkcol" : "");
22271949 if (txn->req_tgt.allow & ALLOW_CAL) {
2228 prot_printf(httpd_out, "DAV: calendar-access%s\r\n",
1950 prot_printf(httpd_out, "DAV: calendar-access%s%s\r\n",
1951 (txn->req_tgt.allow & ALLOW_CAL_AVAIL) ?
1952 ", calendar-availability" : "",
22291953 (txn->req_tgt.allow & ALLOW_CAL_SCHED) ?
22301954 ", calendar-auto-schedule" : "");
1955
1956 /* Backwards compatibility with Apple VAV clients */
1957 if ((txn->req_tgt.allow &
1958 (ALLOW_CAL_AVAIL | ALLOW_CAL_SCHED)) ==
1959 (ALLOW_CAL_AVAIL | ALLOW_CAL_SCHED))
1960 prot_printf(httpd_out, "DAV: inbox-availability\r\n");
22311961 }
22321962 if (txn->req_tgt.allow & ALLOW_CARD) {
22331963 prot_puts(httpd_out, "DAV: addressbook\r\n");
22461976 allow:
22471977 /* Construct Allow header(s) for OPTIONS and 405 response */
22481978 allow_hdr("Allow", txn->req_tgt.allow);
1979 goto authorized;
1980
1981 case HTTP_BAD_MEDIATYPE:
1982 if (txn->req_body.te == TE_UNKNOWN) {
1983 /* Construct Allow-Encoding header for 415 response */
1984 #ifdef HAVE_ZLIB
1985 prot_puts(httpd_out, "Allow-Encoding: gzip, deflate\r\n");
1986 #else
1987 prot_puts(httpd_out, "Allow-Encoding: identity\r\n");
1988 #endif
1989 }
22491990 goto authorized;
22501991
22511992 case HTTP_UNAUTHORIZED:
26522393
26532394 if (code) {
26542395 /* Initial call - prepare response header based on CE, TE and version */
2655 if (do_md5) MD5_Init(&ctx);
2396 if (do_md5) MD5Init(&ctx);
26562397
26572398 if (txn->flags.te & ~TE_CHUNKED) {
26582399 /* Transfer-Encoded content MUST be chunked */
27082449 }
27092450
27102451 if (outlen && do_md5) {
2711 MD5_Update(&ctx, buf+offset, outlen);
2712 MD5_Final(md5, &ctx);
2452 MD5Update(&ctx, buf+offset, outlen);
2453 MD5Final(md5, &ctx);
27132454 txn->resp_body.md5 = md5;
27142455 }
27152456 }
27432484 prot_write(httpd_out, buf, outlen);
27442485 prot_puts(httpd_out, "\r\n");
27452486
2746 if (do_md5) MD5_Update(&ctx, buf, outlen);
2487 if (do_md5) MD5Update(&ctx, buf, outlen);
27472488 }
27482489 if (!len) {
27492490 /* Terminate the HTTP/1.1 body with a zero-length chunk */
27512492
27522493 /* Trailer */
27532494 if (do_md5) {
2754 MD5_Final(md5, &ctx);
2495 MD5Final(md5, &ctx);
27552496 Content_MD5(md5);
27562497 }
27572498
28342575 txn->resp_body.prefs = 0;
28352576
28362577 #ifdef WITH_DAV
2837 if (txn->error.precond) {
2578 if (code != HTTP_UNAUTHORIZED && txn->error.precond) {
28382579 xmlNodePtr root = xml_add_error(NULL, &txn->error, NULL);
28392580
28402581 if (root) {
30092750
30102751 proc_register("httpd", httpd_clienthost, httpd_userid, (char *)0);
30112752
3012 syslog(LOG_NOTICE, "login: %s %s %s%s %s",
2753 syslog(LOG_NOTICE, "login: %s %s %s%s %s SESSIONID=<%s>",
30132754 httpd_clienthost, httpd_userid, scheme->name,
3014 httpd_tls_done ? "+TLS" : "", "User logged in");
2755 httpd_tls_done ? "+TLS" : "", "User logged in",
2756 session_id());
30152757
30162758
30172759 /* Recreate telemetry log entry for request (w/ credentials redacted) */
33093051 /************************* Method Execution Routines ************************/
33103052
33113053
3312 /* "Open" the requested mailbox. Either return the existing open
3313 * mailbox if it matches, or close the existing and open the requested.
3314 */
3315 int http_mailbox_open(const char *name, struct mailbox **mailbox, int locktype)
3316 {
3317 int r;
3318
3319 if (httpd_mailbox && !strcmp(httpd_mailbox->name, name)) {
3320 r = mailbox_lock_index(httpd_mailbox, locktype);
3321 }
3322 else {
3323 if (httpd_mailbox) {
3324 mailbox_close(&httpd_mailbox);
3325 httpd_mailbox = NULL;
3326 }
3327 if (locktype == LOCK_EXCLUSIVE)
3328 r = mailbox_open_iwl(name, &httpd_mailbox);
3329 else
3330 r = mailbox_open_irl(name, &httpd_mailbox);
3331 }
3332
3333 *mailbox = httpd_mailbox;
3334 return r;
3335 }
3336
3337
33383054 /* Compare an etag in a header to a resource etag.
33393055 * Returns 0 if a match, non-zero otherwise.
33403056 */
36433359 const char *type;
36443360 unsigned int compressible;
36453361 } mimetypes[] = {
3646 { ".css", "text/css", 1 },
3647 { ".htm", "text/html", 1 },
3362 { ".css", "text/css", 1 },
3363 { ".htm", "text/html", 1 },
36483364 { ".html", "text/html", 1 },
3365 { ".ics", "text/calendar", 1 },
3366 { ".ifb", "text/calendar", 1 },
36493367 { ".text", "text/plain", 1 },
3650 { ".txt", "text/plain", 1 },
3651
3652 { ".gif", "image/gif", 0 },
3653 { ".jpg", "image/jpeg", 0 },
3368 { ".txt", "text/plain", 1 },
3369
3370 { ".cgm", "image/cgm", 1 },
3371 { ".gif", "image/gif", 0 },
3372 { ".jpg", "image/jpeg", 0 },
36543373 { ".jpeg", "image/jpeg", 0 },
3655 { ".png", "image/png", 0 },
3656
3657 { ".svg", "image/svg+xml", 1 },
3658 { ".tif", "image/tiff", 1 },
3374 { ".png", "image/png", 0 },
3375 { ".svg", "image/svg+xml", 1 },
3376 { ".tif", "image/tiff", 1 },
36593377 { ".tiff", "image/tiff", 1 },
36603378
3661 { ".bz", "application/x-bzip", 0 },
3662 { ".bz2", "application/x-bzip2", 0 },
3663 { ".gz", "application/gzip", 0 },
3379 { ".aac", "audio/aac", 0 },
3380 { ".m4a", "audio/mp4", 0 },
3381 { ".mp3", "audio/mpeg", 0 },
3382 { ".mpeg", "audio/mpeg", 0 },
3383 { ".oga", "audio/ogg", 0 },
3384 { ".ogg", "audio/ogg", 0 },
3385 { ".wav", "audio/wav", 0 },
3386
3387 { ".avi", "video/x-msvideo", 0 },
3388 { ".mov", "video/quicktime", 0 },
3389 { ".m4v", "video/mp4", 0 },
3390 { ".ogv", "video/ogg", 0 },
3391 { ".qt", "video/quicktime", 0 },
3392 { ".wmv", "video/x-ms-wmv", 0 },
3393
3394 { ".bz", "application/x-bzip", 0 },
3395 { ".bz2", "application/x-bzip2", 0 },
3396 { ".gz", "application/gzip", 0 },
36643397 { ".gzip", "application/gzip", 0 },
3665 { ".tgz", "application/gzip", 0 },
3666 { ".zip", "application/zip", 0 },
3667
3668 { ".doc", "application/msword", 1 },
3669 { ".js", "application/javascript", 1 },
3670 { ".pdf", "application/pdf", 1 },
3671 { ".ppt", "application/vnd.ms-powerpoint", 1 },
3672 { ".sh", "application/x-sh", 1 },
3673 { ".tar", "application/x-tar", 1 },
3674 { ".xls", "application/vnd.ms-excel", 1 },
3675 { ".xml", "application/xml", 1 },
3398 { ".tgz", "application/gzip", 0 },
3399 { ".zip", "application/zip", 0 },
3400
3401 { ".doc", "application/msword", 1 },
3402 { ".jcs", "application/calendar+json", 1 },
3403 { ".jfb", "application/calendar+json", 1 },
3404 { ".js", "application/javascript", 1 },
3405 { ".json", "application/json", 1 },
3406 { ".pdf", "application/pdf", 1 },
3407 { ".ppt", "application/vnd.ms-powerpoint", 1 },
3408 { ".sh", "application/x-sh", 1 },
3409 { ".tar", "application/x-tar", 1 },
3410 { ".xcs", "application/calendar+xml", 1 },
3411 { ".xfb", "application/calendar+xml", 1 },
3412 { ".xls", "application/vnd.ms-excel", 1 },
3413 { ".xml", "application/xml", 1 },
36763414
36773415 { NULL, NULL, 0 }
36783416 };
37833521 prefix = config_getstring(IMAPOPT_HTTPDOCROOT);
37843522 if (!prefix) return HTTP_NOT_FOUND;
37853523
3524 if (*prefix != '/') {
3525 /* Remote content */
3526 struct backend *be;
3527
3528 be = proxy_findserver(prefix, &http_protocol, proxy_userid,
3529 &backend_cached, NULL, NULL, httpd_in);
3530 if (!be) return HTTP_UNAVAILABLE;
3531
3532 return http_pipe_req_resp(be, txn);
3533 }
3534
3535 /* Local content */
37863536 if ((urls = config_getstring(IMAPOPT_HTTPALLOWEDURLS))) {
37873537 tok_t tok = TOK_INITIALIZER(urls, " \t", TOK_TRIMLEFT|TOK_TRIMRIGHT);
37883538 char *token;
39593709 .lprops = root_props
39603710 };
39613711
3962 if (!httpd_userid) return HTTP_UNAUTHORIZED;
3963
39643712 /* Make a working copy of target path */
39653713 strlcpy(txn->req_tgt.path, txn->req_uri->path,
39663714 sizeof(txn->req_tgt.path));
5151 #include <zlib.h>
5252 #endif /* HAVE_ZLIB */
5353
54 #include "annotate.h" /* for strlist */
55 #include "hash.h"
56 #include "http_client.h"
5457 #include "mailbox.h"
5558 #include "spool.h"
5659
5760 #define MAX_REQ_LINE 8000 /* minimum size per HTTPbis */
5861 #define MARKUP_INDENT 2 /* # spaces to indent each line of markup */
5962 #define GZIP_MIN_LEN 300 /* minimum length of data to gzip */
60
61 /* Supported HTTP version */
62 #define HTTP_VERSION "HTTP/1.1"
63 #define HTTP_VERSION_LEN 8
6463
6564 /* Supported TLS version for Upgrade */
6665 #define TLS_VERSION "TLS/1.0"
6968 #define HTML_DOCTYPE \
7069 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" " \
7170 "\"http://www.w3.org/TR/html4/loose.dtd\">"
71
72 /* Macro to return proper response code when user privileges are insufficient */
73 #define HTTP_NO_PRIVS \
74 (httpd_userid && !is_userid_anonymous(httpd_userid) ? \
75 HTTP_FORBIDDEN : HTTP_UNAUTHORIZED)
7276
7377 /* Macro to access query part of URI */
7478 #if LIBXML_VERSION >= 20700
99103 METH_NOBODY = (1<<0), /* Method does not expect a body */
100104 };
101105
102 /* Index into known HTTP methods - needs to stay in sync with array */
103 enum {
104 METH_ACL = 0,
105 METH_COPY,
106 METH_DELETE,
107 METH_GET,
108 METH_HEAD,
109 METH_LOCK,
110 METH_MKCALENDAR,
111 METH_MKCOL,
112 METH_MOVE,
113 METH_OPTIONS,
114 METH_POST,
115 METH_PROPFIND,
116 METH_PROPPATCH,
117 METH_PUT,
118 METH_REPORT,
119 METH_TRACE,
120 METH_UNLOCK,
121
122 METH_UNKNOWN, /* MUST be last */
123 };
124
125106
126107 /* Path namespaces */
127108 enum {
145126 ALLOW_DAV = (1<<5), /* WebDAV specific methods/features */
146127 ALLOW_WRITECOL = (1<<6), /* Create/modify collections */
147128 ALLOW_CAL = (1<<7), /* CalDAV specific methods/features */
148 ALLOW_CAL_SCHED = (1<<8), /* CalDAV Scheduling specific features */
149 ALLOW_CARD = (1<<9), /* CardDAV specific methods/features */
150 ALLOW_ISCHEDULE = (1<<10) /* iSchedule specific methods/features */
129 ALLOW_CAL_AVAIL = (1<<8), /* CalDAV Availability specific features */
130 ALLOW_CAL_SCHED = (1<<9), /* CalDAV Scheduling specific features */
131 ALLOW_CARD = (1<<10),/* CardDAV specific methods/features */
132 ALLOW_ISCHEDULE = (1<<11) /* iSchedule specific methods/features */
151133 };
152134
153135 struct auth_scheme_t {
236218 unsigned long first;
237219 unsigned long last;
238220 struct range *next;
239 };
240
241 /* Context for reading request/response body */
242 struct body_t {
243 unsigned char flags; /* Disposition flags */
244 unsigned char framing; /* Message framing */
245 unsigned char te; /* Transfer-Encoding */
246 unsigned max; /* Max allowed len */
247 ulong len; /* Content-Length */
248 struct buf payload; /* Payload */
249 };
250
251 /* Message Framing flags */
252 enum {
253 FRAMING_UNKNOWN = 0,
254 FRAMING_LENGTH,
255 FRAMING_CHUNKED,
256 FRAMING_CLOSE
257221 };
258222
259223
298262 struct request_line_t req_line; /* Parsed request-line */
299263 xmlURIPtr req_uri; /* Parsed request-target URI */
300264 struct request_target_t req_tgt; /* Parsed request-target path */
265 hash_table req_qparams; /* Parsed query params */
301266 hdrcache_t req_hdrs; /* Cached HTTP headers */
302267 struct body_t req_body; /* Buffered request body */
303268 struct auth_challenge_t auth_chal; /* Authentication challenge */
338303 CORS_PREFLIGHT = 2
339304 };
340305
341 /* read_body() flags */
342 enum {
343 BODY_RESPONSE = (1<<0), /* Response body, otherwise request */
344 BODY_CONTINUE = (1<<1), /* Expect:100-continue request */
345 BODY_CLOSE = (1<<1), /* Close-delimited response body */
346 BODY_DECODE = (1<<2), /* Decode any Content-Encoding */
347 BODY_DISCARD = (1<<3), /* Discard body (don't buffer or decode) */
348 BODY_DONE = (1<<4) /* Body has been read */
349 };
350
351 /* Transfer-Encoding flags (coding of response payload) */
352 enum {
353 TE_NONE = 0,
354 TE_DEFLATE = (1<<0), /* Implies TE_CHUNKED as final coding */
355 TE_GZIP = (1<<1), /* Implies TE_CHUNKED as final coding */
356 TE_CHUNKED = (1<<2) /* MUST be last */
357 };
358
359306 /* Content-Encoding flags (coding of representation) */
360307 enum {
361308 CE_IDENTITY = 0,
452399 extern xmlURIPtr parse_uri(unsigned meth, const char *uri, unsigned path_reqd,
453400 const char **errstr);
454401 extern struct accept *parse_accept(const char **hdr);
455 extern int is_mediatype(const char *pat, const char *type);
456402 extern time_t calc_compile_time(const char *time, const char *date);
457 extern int http_mailbox_open(const char *name, struct mailbox **mailbox,
458 int locktype);
459403 extern const char *http_statusline(long code);
460404 extern char *rfc3339date_gen(char *buf, size_t len, time_t t);
461405 extern char *httpdate_gen(char *buf, size_t len, time_t t);
476420 extern int etagcmp(const char *hdr, const char *etag);
477421 extern int check_precond(struct transaction_t *txn, const void *data,
478422 const char *etag, time_t lastmod);
479 extern int parse_framing(hdrcache_t hdrs, struct body_t *body,
480 const char **errstr);
481 extern int read_body(struct protstream *pin, hdrcache_t hdrs,
482 struct body_t *body, const char **errstr);
483423
484424 #endif /* HTTPD_H */
114114 { " MULTIAPPEND", CAPA_MULTIAPPEND },
115115 { " RIGHTS=kxte", CAPA_ACLRIGHTS },
116116 { " LIST-EXTENDED", CAPA_LISTEXTENDED },
117 { " X-REPLICATION", CAPA_REPLICATION },
117118 { NULL, 0 } } },
118119 { "S01 STARTTLS", "S01 OK", "S01 NO", 0 },
119120 { "A01 AUTHENTICATE", 0, 0, "A01 OK", "A01 NO", "+ ", "*",
6161 CAPA_MUPDATE = (1 << 4),
6262 CAPA_MULTIAPPEND = (1 << 5),
6363 CAPA_ACLRIGHTS = (1 << 6),
64 CAPA_LISTEXTENDED = (1 << 7)
64 CAPA_LISTEXTENDED = (1 << 7),
65 CAPA_REPLICATION = (1 << 8)
6566 };
6667
6768 extern struct protocol_t imap_protocol;
7474 #include "auth.h"
7575 #include "backend.h"
7676 #include "bsearch.h"
77 #include "caldav_db.h"
78 #include "carddav_db.h"
7779 #include "charset.h"
7880 #include "dlist.h"
7981 #include "exitcodes.h"
101103 #include "seen.h"
102104 #include "statuscache.h"
103105 #include "sync_log.h"
106 #include "sync_support.h"
104107 #include "telemetry.h"
105108 #include "tls.h"
106109 #include "user.h"
308311 { "QRESYNC", 2 },
309312 { "SCAN", 2 },
310313 { "XLIST", 2 },
314 { "X-REPLICATION", 2 },
311315
312316 #ifdef HAVE_SSL
313317 { "URLAUTH", 2 },
396400
397401 void cmd_enable(char* tag);
398402
403 static void cmd_syncapply(char *tag, struct dlist *kl,
404 struct sync_reserve_list *reserve_list);
405 static void cmd_syncget(char *tag, struct dlist *kl);
406 static void cmd_syncrestart(char *tag, struct sync_reserve_list **reserve_listp,
407 int realloc);
408
399409 int parsecreateargs(struct dlist **extargs);
400410
401411 int getannotatefetchdata(char *tag,
790800 quotadb_init(0);
791801 quotadb_open(NULL);
792802
803 /* open the DAV dbs, we'll need then for real work */
804 caldav_init();
805 carddav_init();
806
793807 /* open the user deny db */
794808 denydb_init(0);
795809 denydb_open(NULL);
10221036 quotadb_close();
10231037 quotadb_done();
10241038
1039 carddav_done();
1040 caldav_done();
1041
10251042 denydb_close();
10261043 denydb_done();
10271044
11381155 const char *err;
11391156 const char * commandmintimer;
11401157 double commandmintimerd = 0.0;
1158 struct sync_reserve_list *reserve_list =
1159 sync_reserve_list_create(SYNC_MESSAGE_LIST_HASH_SIZE);
11411160
11421161 prot_printf(imapd_out, "* OK [CAPABILITY ");
11431162 capa_response(CAPA_PREAUTH);
12031222 syslog(LOG_WARNING, "%s, closing connection", err);
12041223 prot_printf(imapd_out, "* BYE %s\r\n", err);
12051224 }
1206 return;
1225 break;
12071226 }
12081227 if (c != ' ' || !imparse_isatom(tag.s) || (tag.s[0] == '*' && !tag.s[1])) {
12091228 prot_printf(imapd_out, "* BAD Invalid tag\r\n");
15431562 error_message(IMAP_BYE_LOGOUT));
15441563 prot_printf(imapd_out, "%s OK %s\r\n", tag.s,
15451564 error_message(IMAP_OK_COMPLETED));
1546 telemetry_rusage( imapd_userid );
1547 return;
1565 telemetry_rusage( imapd_userid );
1566 break;
15481567 }
15491568 else if (!imapd_userid) goto nologin;
15501569 else if (!strcmp(cmd.s, "List")) {
19411960 cmd_list(tag.s, &listargs);
19421961
19431962 snmp_increment(SCAN_COUNT, 1);
1963 }
1964 else if (!strcmp(cmd.s, "Syncapply")) {
1965 struct dlist *kl = sync_parseline(imapd_in);
1966
1967 if (kl) {
1968 cmd_syncapply(tag.s, kl, reserve_list);
1969 dlist_free(&kl);
1970 }
1971 else goto extraargs;
1972 }
1973 else if (!strcmp(cmd.s, "Syncget")) {
1974 struct dlist *kl = sync_parseline(imapd_in);
1975
1976 if (kl) {
1977 cmd_syncget(tag.s, kl);
1978 dlist_free(&kl);
1979 }
1980 else goto extraargs;
1981 }
1982 else if (!strcmp(cmd.s, "Syncrestart")) {
1983 if (c == '\r') c = prot_getc(imapd_in);
1984 if (c != '\n') goto extraargs;
1985
1986 /* just clear the GUID cache */
1987 cmd_syncrestart(tag.s, &reserve_list, 1);
19441988 }
19451989 else goto badcmd;
19461990 break;
21412185 eatline(imapd_in, c);
21422186 continue;
21432187 }
2188
2189 cmd_syncrestart(NULL, &reserve_list, 0);
21442190 }
21452191
21462192 static void authentication_success(void)
53725418
53735419 r = mboxlist_renamemailbox(name, text->newmailboxname,
53745420 text->partition,
5375 1, imapd_userid, imapd_authstate, 0,
5421 1, imapd_userid, imapd_authstate, 0, 0,
53765422 text->rename_user);
53775423
53785424 (*imapd_namespace.mboxname_toexternal)(&imapd_namespace,
56705716 /* attempt to rename the base mailbox */
56715717 if (!r) {
56725718 r = mboxlist_renamemailbox(oldmailboxname, newmailboxname, partition,
5673 imapd_userisadmin,
5674 imapd_userid, imapd_authstate, 0, rename_user);
5719 imapd_userisadmin, imapd_userid,
5720 imapd_authstate, 0, 0, rename_user);
56755721 /* it's OK to not exist if there are subfolders */
56765722 if (r == IMAP_MAILBOX_NONEXISTENT && subcount && !rename_user &&
56775723 mboxname_userownsmailbox(imapd_userid, oldmailboxname) &&
76647710 return EOF;
76657711 }
76667712
7713 if (!searchargs->returnopts) searchargs->returnopts = SEARCH_RETURN_ALL;
7714
76677715 c = prot_getc(imapd_in);
76687716
76697717 return c;
82688316 int i=0, j=0;
82698317 char tagbuf[128];
82708318 int c; /* getword() returns an int */
8271 struct buf tag, cmd, tmp, user;
8319 struct buf cmd, tmp, user;
82728320 int r = 0;
82738321
8274 memset(&tag, 0, sizeof(struct buf));
82758322 memset(&cmd, 0, sizeof(struct buf));
82768323 memset(&tmp, 0, sizeof(struct buf));
82778324 memset(&user, 0, sizeof(struct buf));
82808327 strlen(mailbox), mailbox);
82818328
82828329 while(1) {
8283 c = getword(pin, &tag);
8284 if (c == EOF) {
8285 r = IMAP_SERVER_UNAVAILABLE;
8330 c = prot_getc(pin);
8331 if (c != '*') {
8332 prot_ungetc(c, pin);
8333 r = getresult(pin, "ACL0");
82868334 break;
82878335 }
82888336
8337 c = prot_getc(pin); /* skip SP */
82898338 c = getword(pin, &cmd);
82908339 if (c == EOF) {
82918340 r = IMAP_SERVER_UNAVAILABLE;
82928341 break;
82938342 }
82948343
8295 if(c == '\r') {
8296 c = prot_getc(pin);
8297 if(c != '\n') {
8298 r = IMAP_SERVER_UNAVAILABLE;
8299 goto cleanup;
8300 }
8301 }
8302 if(c == '\n') goto cleanup;
8303
8304 if (tag.s[0] == '*' && !strncmp(cmd.s, "ACL", 3)) {
8344 if (!strncmp(cmd.s, "ACL", 3)) {
83058345 while(c != '\n') {
83068346 /* An ACL response, we should send a DELETEACL command */
83078347 c = getastring(pin, pout, &tmp);
83178357 goto cleanup;
83188358 }
83198359 }
8320 if(c == '\n') goto cleanup;
8360 if(c == '\n') break; /* end of * ACL */
83218361
83228362 c = getastring(pin, pout, &user);
83238363 if (c == EOF) {
83408380 }
83418381 /* if the next character is \n, we'll exit the loop */
83428382 }
8343 continue;
8344 } else if (!strncmp(tag.s, "ACL0", 4)) {
8345 /* end of this command */
8346 if (!strcasecmp(cmd.s, "OK")) { break; }
8347 if (!strcasecmp(cmd.s, "NO")) { r = IMAP_REMOTE_DENIED; break; }
8348 r = IMAP_SERVER_UNAVAILABLE;
8349 break;
8383 }
8384 else {
8385 /* skip this line, we don't really care */
8386 eatline(pin, c);
83508387 }
83518388 }
83528389
83558392 /* Now cleanup after all the DELETEACL commands */
83568393 if(!r) {
83578394 while(j < i) {
8358 c = getword(pin, &tag);
8359 if (c == EOF) {
8360 r = IMAP_SERVER_UNAVAILABLE;
8361 break;
8362 }
8363
8364 eatline(pin, c);
8365
8366 if(!strncmp("ACL", tag.s, 3)) {
8367 j++;
8368 }
8395 snprintf(tagbuf, sizeof(tagbuf), "ACL%d", ++j);
8396 r = getresult(pin, tagbuf);
8397 if (r) break;
83698398 }
83708399 }
83718400
83748403 buf_free(&user);
83758404 buf_free(&tmp);
83768405 buf_free(&cmd);
8377 buf_free(&tag);
83788406
83798407 return r;
83808408 }
83838411 char *mailbox, char *acl_in)
83848412 {
83858413 int r = 0;
8386 int c; /* getword() returns an int */
83878414 char tag[128];
83888415 int tagnum = 1;
83898416 char *rights, *nextid;
83908417 char *acl_safe = acl_in ? xstrdup(acl_in) : NULL;
83918418 char *acl = acl_safe;
8392 struct buf inbuf;
8393
8394 memset(&inbuf, 0, sizeof(struct buf));
83958419
83968420 while (acl) {
83978421 rights = strchr(acl, '\t');
84118435 strlen(acl), acl,
84128436 strlen(rights), rights);
84138437
8414 while(1) {
8415 c = getword(pin, &inbuf);
8416 if (c == EOF) {
8417 r = IMAP_SERVER_UNAVAILABLE;
8418 break;
8419 }
8420 if(strncmp(tag, inbuf.s, strlen(tag))) {
8421 eatline(pin, c);
8422 continue;
8423 } else {
8424 /* this is our line */
8425 break;
8426 }
8427 }
8428
8429 /* Are we OK? */
8430
8431 c = getword(pin, &inbuf);
8432 if (c == EOF) {
8433 r = IMAP_SERVER_UNAVAILABLE;
8434 break;
8435 }
8436
8437 if(strncmp("OK", inbuf.s, 2)) {
8438 r = IMAP_REMOTE_DENIED;
8439 break;
8440 }
8441
8442 /* Eat the line and get the next one */
8443 eatline(pin, c);
8438 r = getresult(pin, tag);
8439 if (r) break;
8440
84448441 acl = nextid;
84458442 }
84468443
8447 buf_free(&inbuf);
84488444 if(acl_safe) free(acl_safe);
84498445
84508446 return r;
84728468 mupdate_handle *mupdate_h;
84738469 struct backend *be;
84748470 int remoteversion;
8471 unsigned long use_replication;
8472 struct buf tagbuf;
8473 char *userid;
84758474 char *toserver;
84768475 char *topart;
84778476 struct seen *seendb;
85168515 {
85178516 struct xfer_header *xfer = *xferptr;
85188517 struct xfer_item *item, *next;
8518
8519 syslog(LOG_INFO, "XFER: disconnecting from servers");
85198520
85208521 /* remove items */
85218522 item = xfer->items;
85368537
85378538 seen_close(&xfer->seendb);
85388539
8540 buf_free(&xfer->tagbuf);
8541
85398542 free(xfer);
85408543
85418544 *xferptr = NULL;
85948597 struct xfer_header *xfer = xzmalloc(sizeof(struct xfer_header));
85958598 int r;
85968599
8600 syslog(LOG_INFO, "XFER: connecting to server '%s'", toserver);
8601
85978602 /* Get a connection to the remote backend */
85988603 xfer->be = backend_connect(NULL, toserver, &imap_protocol,
85998604 "", NULL, NULL);
86008605 if (!xfer->be) {
8606 syslog(LOG_ERR, "Failed to connect to server '%s'", toserver);
86018607 r = IMAP_SERVER_UNAVAILABLE;
86028608 goto fail;
86038609 }
86048610
86058611 xfer->remoteversion = backend_version(xfer->be);
8612 if (xfer->be->capability & CAPA_REPLICATION) {
8613 syslog(LOG_INFO, "XFER: destination supports replication");
8614 xfer->use_replication = 1;
8615
8616 /* attach our IMAP tag buffer to our protstreams as userdata */
8617 xfer->be->in->userdata = xfer->be->out->userdata = &xfer->tagbuf;
8618 }
86068619
86078620 xfer->toserver = xstrdup(toserver);
86088621 xfer->topart = xstrdup(topart);
86108623
86118624 /* connect to mupdate server if configured */
86128625 if (config_mupdate_server) {
8626 syslog(LOG_INFO, "XFER: connecting to mupdate '%s'",
8627 config_mupdate_server);
8628
86138629 r = mupdate_connect(config_mupdate_server, NULL,
86148630 &xfer->mupdate_h, NULL);
8615 if (r) goto fail;
8631 if (r) {
8632 syslog(LOG_INFO, "Failed to connect to mupdate '%s'",
8633 config_mupdate_server);
8634 goto fail;
8635 }
86168636 }
86178637
86188638 *xferptr = xfer;
86488668 struct xfer_item *item;
86498669 int r;
86508670
8671 syslog(LOG_INFO, "XFER: creating mailboxes on destination");
8672
86518673 for (item = xfer->items; item; item = item->next) {
86528674 if (xfer->topart) {
86538675 /* need to send partition as an atom */
87118733 struct xfer_item *item;
87128734 int r;
87138735
8736 syslog(LOG_INFO, "XFER: deactivating mailboxes");
8737
87148738 /* Step 3: mupdate.DEACTIVATE(mailbox, newserver) */
87158739 for (item = xfer->items; item; item = item->next) {
87168740 r = xfer_mupdate(xfer, 0, item->name, item->part,
87358759 struct mailbox *mailbox = NULL;
87368760 char buf[MAX_PARTITION_LEN+HOSTNAME_SIZE+2];
87378761
8762 syslog(LOG_INFO, "XFER: dumping mailboxes to destination");
8763
87388764 for (item = xfer->items; item; item = item->next) {
87398765 r = mailbox_open_irl(item->name, &mailbox);
87408766 if (r) {
87538779 "Could not move mailbox: %s, mboxlist_update() failed %s",
87548780 item->name, error_message(r));
87558781 }
8782 else item->state = XFER_LOCAL_MOVING;
87568783
87578784 if (!r && xfer->seendb) {
87588785 /* Backport the user's seendb on-the-fly */
87598786 item->mailbox = mailbox;
87608787 r = xfer_backport_seen_item(item, xfer->seendb);
8788 if (r) syslog(LOG_WARNING,
8789 "Failed to backport seen state for mailbox '%s'",
8790 item->name);
87618791
87628792 /* Need to close seendb before dumping Inbox (last item) */
87638793 if (!item->next) seen_close(&xfer->seendb);
88118841 return 0;
88128842 }
88138843
8844 static int xfer_user_cb(char *name, int matchlen, int maycreate, void *rock);
8845 static int xfer_addsubmailboxes(struct xfer_header *xfer, const char *mboxname);
8846
8847 static int xfer_initialsync(struct xfer_header *xfer)
8848 {
8849 unsigned flags = SYNC_FLAG_LOGGING | SYNC_FLAG_LOCALONLY;
8850 int r;
8851
8852 if (xfer->userid) {
8853 struct xfer_item *item, *next;
8854
8855 syslog(LOG_INFO, "XFER: initial sync of user %s", xfer->userid);
8856
8857 r = sync_do_user(xfer->userid, xfer->topart, xfer->be, flags);
8858 if (r) return r;
8859
8860 /* User moves may take a while, do another non-blocking sync */
8861 syslog(LOG_INFO, "XFER: second sync of user %s", xfer->userid);
8862
8863 r = sync_do_user(xfer->userid, xfer->topart, xfer->be, flags);
8864 if (r) return r;
8865
8866 /* User may have renamed/deleted a mailbox while syncing,
8867 recreate the submailboxes list */
8868 for (item = xfer->items; (next = item->next); item = next) {
8869 free(item->name);
8870 free(item->part);
8871 free(item->acl);
8872 free(item);
8873 }
8874 xfer->items = item; /* Inbox is the last item */
8875
8876 r = xfer_addsubmailboxes(xfer, item->name);
8877 }
8878 else {
8879 struct sync_name_list *mboxname_list = sync_name_list_create();
8880
8881 syslog(LOG_INFO, "XFER: initial sync of mailbox %s", xfer->items->name);
8882
8883 sync_name_list_add(mboxname_list, xfer->items->name);
8884 r = sync_do_mailboxes(mboxname_list, xfer->topart, xfer->be, flags);
8885 sync_name_list_free(&mboxname_list);
8886 }
8887
8888 return r;
8889 }
8890
8891 static int sync_mailbox(struct mailbox *mailbox,
8892 struct sync_folder_list *replica_folders,
8893 const char *topart,
8894 struct backend *be)
8895 {
8896 int r = 0;
8897 struct sync_folder_list *master_folders;
8898 struct sync_reserve_list *reserve_guids;
8899 struct sync_msgid_list *part_list;
8900 struct sync_reserve *reserve;
8901 struct sync_folder *mfolder, *rfolder;
8902
8903 if (!topart) topart = mailbox->part;
8904 reserve_guids = sync_reserve_list_create(SYNC_MSGID_LIST_HASH_SIZE);
8905 part_list = sync_reserve_partlist(reserve_guids, topart);
8906
8907 master_folders = sync_folder_list_create();
8908 sync_folder_list_add(master_folders, mailbox->uniqueid, mailbox->name,
8909 mailbox->mbtype,
8910 mailbox->part, mailbox->acl, mailbox->i.options,
8911 mailbox->i.uidvalidity, mailbox->i.last_uid,
8912 mailbox->i.highestmodseq, mailbox->i.sync_crc,
8913 mailbox->i.recentuid, mailbox->i.recenttime,
8914 mailbox->i.pop3_last_login);
8915
8916 mfolder = master_folders->head;
8917 mfolder->mailbox = mailbox;
8918
8919 rfolder = sync_folder_lookup(replica_folders, mfolder->uniqueid);
8920 if (rfolder) {
8921 rfolder->mark = 1;
8922
8923 /* does it need a rename? */
8924 if (strcmp(mfolder->name, rfolder->name) ||
8925 strcmp(topart, rfolder->part)) {
8926 /* bail and retry */
8927 syslog(LOG_NOTICE,
8928 "XFER: rename %s!%s -> %s!%s during final sync"
8929 " - must try XFER again",
8930 mfolder->name, mfolder->part, rfolder->name, rfolder->part);
8931 r = IMAP_AGAIN;
8932 goto cleanup;
8933 }
8934
8935 sync_find_reserve_messages(mailbox, rfolder->last_uid, part_list);
8936 }
8937 else sync_find_reserve_messages(mailbox, 0, part_list);
8938
8939 reserve = reserve_guids->head;
8940 r = sync_reserve_partition(reserve->part, replica_folders,
8941 reserve->list, be);
8942 if (r) {
8943 syslog(LOG_ERR, "sync_mailbox(): reserve partition failed: %s '%s'",
8944 mfolder->name, error_message(r));
8945 goto cleanup;
8946 }
8947
8948 r = sync_update_mailbox(mfolder, rfolder, topart, reserve_guids, be,
8949 SYNC_FLAG_LOCALONLY);
8950 if (r) {
8951 syslog(LOG_ERR, "sync_mailbox(): update failed: %s '%s'",
8952 mfolder->name, error_message(r));
8953 }
8954
8955 cleanup:
8956 sync_reserve_list_free(&reserve_guids);
8957 sync_folder_list_free(&master_folders);
8958
8959 return r;
8960 }
8961
8962 static int xfer_finalsync(struct xfer_header *xfer)
8963 {
8964 struct sync_name_list *master_quotaroots = sync_name_list_create();
8965 struct sync_folder_list *replica_folders = sync_folder_list_create();
8966 struct sync_folder *rfolder;
8967 struct sync_name_list *replica_subs = NULL;
8968 struct sync_sieve_list *replica_sieve = NULL;
8969 struct sync_seen_list *replica_seen = NULL;
8970 struct sync_quota_list *replica_quota = sync_quota_list_create();
8971 const char *cmd;
8972 struct dlist *kl = NULL;
8973 struct xfer_item *item;
8974 struct mailbox *mailbox = NULL;
8975 char buf[MAX_PARTITION_LEN+HOSTNAME_SIZE+2];
8976 unsigned flags = SYNC_FLAG_LOGGING | SYNC_FLAG_LOCALONLY;
8977 int r;
8978
8979 if (xfer->userid) {
8980 syslog(LOG_INFO, "XFER: final sync of user %s", xfer->userid);
8981
8982 replica_subs = sync_name_list_create();
8983 replica_sieve = sync_sieve_list_create();
8984 replica_seen = sync_seen_list_create();
8985
8986 cmd = "USER";
8987 kl = dlist_atom(NULL, cmd, xfer->userid);
8988 }
8989 else {
8990 syslog(LOG_INFO, "XFER: final sync of mailbox %s", xfer->items->name);
8991
8992 cmd = "MAILBOXES";
8993 kl = dlist_list(NULL, cmd);
8994 dlist_atom(kl, "MBOXNAME", xfer->items->name);
8995 }
8996
8997 sync_send_lookup(kl, xfer->be->out);
8998 dlist_free(&kl);
8999
9000 r = sync_response_parse(xfer->be->in, cmd, replica_folders, replica_subs,
9001 replica_sieve, replica_seen, replica_quota);
9002
9003 if (r) goto done;
9004
9005 for (item = xfer->items; item; item = item->next) {
9006 r = mailbox_open_iwl(item->name, &mailbox);
9007 if (r) {
9008 syslog(LOG_ERR,
9009 "Failed to open mailbox %s for xfer_finalsync() %s",
9010 item->name, error_message(r));
9011 goto done;
9012 }
9013
9014 /* Step 3.5: Set mailbox as MOVING on local server */
9015 snprintf(buf, sizeof(buf), "%s!%s", xfer->toserver, xfer->topart);
9016 r = mboxlist_update(item->name, item->mbtype|MBTYPE_MOVING,
9017 buf, item->acl, 1);
9018 if (r) {
9019 syslog(LOG_ERR,
9020 "Could not move mailbox: %s, mboxlist_update() failed %s",
9021 item->name, error_message(r));
9022 }
9023 else item->state = XFER_LOCAL_MOVING;
9024
9025 /* Step 4: Sync local -> remote */
9026 if (!r) {
9027 r = sync_mailbox(mailbox, replica_folders, xfer->topart, xfer->be);
9028 if (r) {
9029 syslog(LOG_ERR,
9030 "Could not move mailbox: %s, sync_mailbox() failed %s",
9031 item->name, error_message(r));
9032 }
9033 else {
9034 if (mailbox->quotaroot &&
9035 !sync_name_lookup(master_quotaroots, mailbox->quotaroot)) {
9036 sync_name_list_add(master_quotaroots, mailbox->quotaroot);
9037 }
9038
9039 r = sync_do_annotation(mailbox->name, xfer->be, flags);
9040 if (r) {
9041 syslog(LOG_ERR, "Could not move mailbox: %s,"
9042 " sync_do_annotation() failed %s",
9043 item->name, error_message(r));
9044 }
9045 }
9046 }
9047
9048 mailbox_close(&mailbox);
9049
9050 if (r) goto done;
9051
9052 item->state = XFER_UNDUMPED;
9053 }
9054
9055 /* Delete folders on replica which no longer exist on master */
9056 for (rfolder = replica_folders->head; rfolder; rfolder = rfolder->next) {
9057 if (rfolder->mark) continue;
9058
9059 r = sync_folder_delete(rfolder->name, xfer->be, flags);
9060 if (r) {
9061 syslog(LOG_ERR, "sync_folder_delete(): failed: %s '%s'",
9062 rfolder->name, error_message(r));
9063 goto done;
9064 }
9065 }
9066
9067 /* Handle any mailbox/user metadata */
9068 r = sync_do_user_quota(master_quotaroots, replica_quota, xfer->be);
9069 if (!r && xfer->userid) {
9070 r = sync_do_user_seen(xfer->userid, replica_seen, xfer->be);
9071 if (!r) r = sync_do_user_sub(xfer->userid, replica_subs, xfer->be, flags);
9072 if (!r) r = sync_do_user_sieve(xfer->userid, replica_sieve, xfer->be);
9073 }
9074
9075 done:
9076 sync_name_list_free(&master_quotaroots);
9077 sync_folder_list_free(&replica_folders);
9078 if (replica_subs) sync_name_list_free(&replica_subs);
9079 if (replica_sieve) sync_sieve_list_free(&replica_sieve);
9080 if (replica_seen) sync_seen_list_free(&replica_seen);
9081 if (replica_quota) sync_quota_list_free(&replica_quota);
9082
9083 return r;
9084 }
9085
88149086 static int xfer_reactivate(struct xfer_header *xfer)
88159087 {
88169088 struct xfer_item *item;
88179089 int r;
9090
9091 syslog(LOG_INFO, "XFER: reactivating mailboxes");
88189092
88199093 if (!xfer->mupdate_h) return 0;
88209094
88379111 {
88389112 struct xfer_item *item;
88399113 int r;
9114
9115 syslog(LOG_INFO, "XFER: deleting mailboxes on source");
88409116
88419117 /* 7) local delete of mailbox
88429118 * & remove local "remote" mailboxlist entry */
88789154 struct xfer_item *item;
88799155 int r;
88809156
9157 syslog(LOG_INFO, "XFER: recovering");
9158
88819159 /* Backout any changes - we stop on first untouched mailbox */
88829160 for (item = xfer->items; item && item->state; item = item->next) {
88839161 switch (item->state) {
88939171 }
88949172
88959173 case XFER_REMOTE_CREATED:
8896 /* Delete remote mailbox */
8897 prot_printf(xfer->be->out,
8898 "LD1 LOCALDELETE {" SIZE_T_FMT "+}\r\n%s\r\n",
8899 strlen(item->extname), item->extname);
8900 r = getresult(xfer->be->in, "LD1");
8901 if (r) {
8902 syslog(LOG_ERR,
8903 "Could not back out remote mailbox during move of %s (%s)",
8904 item->name, error_message(r));
9174 if (!xfer->use_replication) {
9175 /* Delete remote mailbox */
9176 prot_printf(xfer->be->out,
9177 "LD1 LOCALDELETE {" SIZE_T_FMT "+}\r\n%s\r\n",
9178 strlen(item->extname), item->extname);
9179 r = getresult(xfer->be->in, "LD1");
9180 if (r) {
9181 syslog(LOG_ERR,
9182 "Could not back out remote mailbox during move of %s (%s)",
9183 item->name, error_message(r));
9184 }
89059185 }
89069186
89079187 case XFER_DEACTIVATED:
89409220
89419221 static int do_xfer(struct xfer_header *xfer)
89429222 {
8943 int r;
9223 int r = 0;
9224
9225 if (xfer->use_replication) {
9226 /* Initial non-blocking sync */
9227 r = xfer_initialsync(xfer);
9228 if (r) return r;
9229 }
89449230
89459231 r = xfer_deactivate(xfer);
8946 if (!r) r = xfer_localcreate(xfer);
8947 if (!r) r = xfer_undump(xfer);
9232
9233 if (!r) {
9234 if (xfer->use_replication) {
9235 /* Final sync with write locks on mailboxes */
9236 r = xfer_finalsync(xfer);
9237 }
9238 else {
9239 r = xfer_localcreate(xfer);
9240 if (!r) r = xfer_undump(xfer);
9241 }
9242 }
89489243
89499244 if (r) {
89509245 /* Something failed, revert back to local server */
89669261 struct quota quota;
89679262 int r;
89689263 char extname[MAX_MAILBOX_NAME];
9264
9265 syslog(LOG_INFO, "XFER: setting quota root %s", mboxname);
89699266
89709267 (*imapd_namespace.mboxname_toexternal)(&imapd_namespace, mboxname,
89719268 imapd_userid, extname);
90009297 char buf[MAX_MAILBOX_NAME];
90019298 int r;
90029299
9300 syslog(LOG_INFO, "XFER: adding submailboxes of %s", mboxname);
9301
90039302 snprintf(buf, sizeof(buf), "%s.*", mboxname);
90049303 r = mboxlist_findall(NULL, buf, 1, imapd_userid,
90059304 imapd_authstate, xfer_user_cb,
90069305 xfer);
9007 if (r) return r;
9306 if (r) {
9307 syslog(LOG_ERR, "Failed getting submailboxes of %s", mboxname);
9308 return r;
9309 }
90089310
90099311 /* also move DELETED maiboxes for this user */
90109312 if (mboxlist_delayed_delete_isenabled()) {
90139315 r = mboxlist_findall(NULL, buf, 1, imapd_userid,
90149316 imapd_authstate, xfer_user_cb,
90159317 xfer);
9318 if (r) syslog(LOG_ERR, "Failed getting DELETED mailboxes of %s",
9319 mboxname);
90169320 }
90179321
90189322 return r;
90849388
90859389 /* if we are not moving a user, just move the one mailbox */
90869390 if (!moving_user) {
9391 syslog(LOG_INFO, "XFER: mailbox '%s' -> %s!%s",
9392 xfer->items->name, toserver, topart);
9393
90879394 /* is the selected mailbox the one we're moving? */
90889395 if (imapd_index && !strcmp(mailboxname, imapd_index->mailbox->name)) {
90899396 r = IMAP_MAILBOX_LOCKED;
90919398 }
90929399 r = do_xfer(xfer);
90939400 } else {
9094 char *userid = mboxname_to_userid(mailboxname);
9401 char *userid = xfer->userid = mboxname_to_userid(mailboxname);
9402
9403 syslog(LOG_INFO, "XFER: user '%s' -> %s!%s",
9404 xfer->userid, toserver, topart);
90959405
90969406 /* is the selected mailbox in the namespace we're moving? */
90979407 if (imapd_index && !strncmp(mailboxname, imapd_index->mailbox->name,
91009410 goto done;
91019411 }
91029412
9103 /* set the quotaroot if needed */
9104 r = xfer_setquotaroot(xfer, mailboxname);
9105 if (r) goto done;
9413 if (!xfer->use_replication) {
9414 /* set the quotaroot if needed */
9415 r = xfer_setquotaroot(xfer, mailboxname);
9416 if (r) goto done;
9417
9418 /* backport the seen file if needed */
9419 if (xfer->remoteversion < 12) {
9420 r = seen_open(userid, SEEN_CREATE, &xfer->seendb);
9421 if (r) goto done;
9422 }
9423 }
91069424
91079425 /* add all submailboxes to the move list as well */
91089426 r = xfer_addsubmailboxes(xfer, mailboxname);
91099427 if (r) goto done;
9110
9111 /* backport the seen file if needed */
9112 if (xfer->remoteversion < 12) {
9113 r = seen_open(userid, SEEN_CREATE, &xfer->seendb);
9114 if (r) goto done;
9115 }
91169428
91179429 /* NOTE: mailboxes were added in reverse, so the inbox is
91189430 * done last */
91219433
91229434 /* this was a successful user delete, and we need to delete
91239435 certain user meta-data (but not seen state!) */
9436 syslog(LOG_INFO, "XFER: deleting user metadata");
91249437 user_deletedata(userid, imapd_userid, imapd_authstate, 0);
91259438 }
91269439
1116511478 prot_printf(imapd_out, "%s OK %s\r\n", tag,
1116611479 error_message(IMAP_OK_COMPLETED));
1116711480 }
11481
11482
11483 /***************************** server-side sync *****************************/
11484
11485 static void cmd_syncapply(char *tag, struct dlist *kin,
11486 struct sync_reserve_list *reserve_list)
11487 {
11488 int r;
11489 struct sync_state sync_state = {
11490 imapd_userid, imapd_userisadmin || imapd_userisproxyadmin,
11491 imapd_authstate, &imapd_namespace, imapd_out, 0 /* local_only */
11492 };
11493
11494 ucase(kin->name);
11495
11496 if (!strcmp(kin->name, "MESSAGE"))
11497 r = sync_apply_message(kin, reserve_list, &sync_state);
11498 else if (!strcmp(kin->name, "EXPUNGE"))
11499 r = sync_apply_expunge(kin, &sync_state);
11500
11501 /* dump protocol */
11502 else if (!strcmp(kin->name, "ACTIVATE_SIEVE"))
11503 r = sync_apply_activate_sieve(kin, &sync_state);
11504 else if (!strcmp(kin->name, "ANNOTATION"))
11505 r = sync_apply_annotation(kin, &sync_state);
11506 else if (!strcmp(kin->name, "MAILBOX"))
11507 r = sync_apply_mailbox(kin, &sync_state);
11508 else if (!strcmp(kin->name, "LOCAL_MAILBOX")) {
11509 sync_state.local_only = 1;
11510 r = sync_apply_mailbox(kin, &sync_state);
11511 }
11512 else if (!strcmp(kin->name, "QUOTA"))
11513 r = sync_apply_quota(kin, &sync_state);
11514 else if (!strcmp(kin->name, "SEEN"))
11515 r = sync_apply_seen(kin, &sync_state);
11516 else if (!strcmp(kin->name, "RENAME"))
11517 r = sync_apply_rename(kin, &sync_state);
11518 else if (!strcmp(kin->name, "LOCAL_RENAME")) {
11519 sync_state.local_only = 1;
11520 r = sync_apply_rename(kin, &sync_state);
11521 }
11522 else if (!strcmp(kin->name, "RESERVE"))
11523 r = sync_apply_reserve(kin, reserve_list, &sync_state);
11524 else if (!strcmp(kin->name, "SIEVE"))
11525 r = sync_apply_sieve(kin, &sync_state);
11526 else if (!strcmp(kin->name, "SUB"))
11527 r = sync_apply_changesub(kin, &sync_state);
11528
11529 /* "un"dump protocol ;) */
11530 else if (!strcmp(kin->name, "UNACTIVATE_SIEVE"))
11531 r = sync_apply_unactivate_sieve(kin, &sync_state);
11532 else if (!strcmp(kin->name, "UNANNOTATION"))
11533 r = sync_apply_unannotation(kin, &sync_state);
11534 else if (!strcmp(kin->name, "UNMAILBOX"))
11535 r = sync_apply_unmailbox(kin, &sync_state);
11536 else if (!strcmp(kin->name, "LOCAL_UNMAILBOX")) {
11537 sync_state.local_only = 1;
11538 r = sync_apply_unmailbox(kin, &sync_state);
11539 }
11540 else if (!strcmp(kin->name, "UNQUOTA"))
11541 r = sync_apply_unquota(kin, &sync_state);
11542 else if (!strcmp(kin->name, "UNSIEVE"))
11543 r = sync_apply_unsieve(kin, &sync_state);
11544 else if (!strcmp(kin->name, "UNSUB"))
11545 r = sync_apply_changesub(kin, &sync_state);
11546
11547 /* user is a special case that's not paired, there's no "upload user"
11548 * as such - we just call the individual commands with their items */
11549 else if (!strcmp(kin->name, "UNUSER"))
11550 r = sync_apply_unuser(kin, &sync_state);
11551 else if (!strcmp(kin->name, "LOCAL_UNUSER")) {
11552 sync_state.local_only = 1;
11553 r = sync_apply_unuser(kin, &sync_state);
11554 }
11555
11556 else
11557 r = IMAP_PROTOCOL_ERROR;
11558
11559 sync_print_response(tag, r, imapd_out);
11560
11561 /* Reset inactivity timer in case we spent a long time processing data */
11562 prot_resettimeout(imapd_in);
11563 }
11564
11565 static void cmd_syncget(char *tag, struct dlist *kin)
11566 {
11567 int r;
11568 struct sync_state sync_state = {
11569 imapd_userid, imapd_userisadmin, imapd_authstate,
11570 &imapd_namespace, imapd_out, 0 /* local_only */
11571 };
11572
11573 ucase(kin->name);
11574
11575 if (!strcmp(kin->name, "ANNOTATION"))
11576 r = sync_get_annotation(kin, &sync_state);
11577 else if (!strcmp(kin->name, "FETCH"))
11578 r = sync_get_message(kin, &sync_state);
11579 else if (!strcmp(kin->name, "FETCH_SIEVE"))
11580 r = sync_get_sieve(kin, &sync_state);
11581 else if (!strcmp(kin->name, "FULLMAILBOX"))
11582 r = sync_get_fullmailbox(kin, &sync_state);
11583 else if (!strcmp(kin->name, "MAILBOXES"))
11584 r = sync_get_mailboxes(kin, &sync_state);
11585 else if (!strcmp(kin->name, "META"))
11586 r = sync_get_meta(kin, &sync_state);
11587 else if (!strcmp(kin->name, "QUOTA"))
11588 r = sync_get_quota(kin, &sync_state);
11589 else if (!strcmp(kin->name, "USER"))
11590 r = sync_get_user(kin, &sync_state);
11591 else
11592 r = IMAP_PROTOCOL_ERROR;
11593
11594 sync_print_response(tag, r, imapd_out);
11595 }
11596
11597
11598 /* partition_list is simple linked list of names used by cmd_restart */
11599
11600 struct partition_list {
11601 struct partition_list *next;
11602 char *name;
11603 };
11604
11605 static struct partition_list *
11606 partition_list_add(char *name, struct partition_list *pl)
11607 {
11608 struct partition_list *p;
11609
11610 /* Is name already on list? */
11611 for (p=pl; p; p = p->next) {
11612 if (!strcmp(p->name, name))
11613 return(pl);
11614 }
11615
11616 /* Add entry to start of list and return new list */
11617 p = xzmalloc(sizeof(struct partition_list));
11618 p->next = pl;
11619 p->name = xstrdup(name);
11620
11621 return(p);
11622 }
11623
11624 static void
11625 partition_list_free(struct partition_list *current)
11626 {
11627 while (current) {
11628 struct partition_list *next = current->next;
11629
11630 free(current->name);
11631 free(current);
11632
11633 current = next;
11634 }
11635
11636 }
11637
11638 static void cmd_syncrestart(char *tag, struct sync_reserve_list **reserve_listp,
11639 int re_alloc)
11640 {
11641 struct sync_reserve *res;
11642 struct sync_reserve_list *l = *reserve_listp;
11643 struct sync_msgid *msg;
11644 const char *fname;
11645 int hash_size = l->hash_size;
11646 struct partition_list *p, *pl = NULL;
11647
11648 for (res = l->head; res; res = res->next) {
11649 for (msg = res->list->head; msg; msg = msg->next) {
11650 pl = partition_list_add(res->part, pl);
11651
11652 fname = dlist_reserve_path(res->part, &msg->guid);
11653 unlink(fname);
11654 }
11655 }
11656 sync_reserve_list_free(reserve_listp);
11657
11658 /* Remove all <partition>/sync./<pid> directories referred to above */
11659 for (p=pl; p ; p = p->next) {
11660 static char buf[MAX_MAILBOX_PATH];
11661
11662 snprintf(buf, MAX_MAILBOX_PATH, "%s/sync./%lu",
11663 config_partitiondir(p->name), (unsigned long)getpid());
11664 rmdir(buf);
11665 }
11666 partition_list_free(pl);
11667
11668 if (re_alloc) {
11669 *reserve_listp = sync_reserve_list_create(hash_size);
11670
11671 prot_printf(imapd_out, "%s OK Restarting\r\n", tag);
11672 }
11673 else *reserve_listp = NULL;
11674 }
647647 syslog(LOG_WARNING,
648648 "Mailbox %s has been (re)moved out from under client",
649649 mailbox->name);
650 mailbox_close(&mailbox);
650651 fatal("Mailbox has been (re)moved", EC_IOERR);
651652 }
652653
5151 #include "httpd.h"
5252 #include "jcal.h"
5353 #include "xcal.h"
54 #include "tok.h"
5455 #include "util.h"
5556 #include "version.h"
5657 #include "xstrlcat.h"
8283 /*
8384 * Add an iCalendar recur-rule-part to a JSON recur object.
8485 */
86 static void icalrecur_add_obj_to_json_object(json_t *jrecur, const char *rpart,
87 json_t *obj)
88 {
89 json_t *old_rpart = json_object_get(jrecur, rpart);
90
91 if (old_rpart) {
92 /* Already have a value for this BY* rpart - needs to be an array */
93 json_t *byarray;
94
95 if (!json_is_array(old_rpart)) {
96 /* Create an array from existing value */
97 byarray = json_array();
98 json_array_append(byarray, old_rpart);
99 json_object_set_new(jrecur, rpart, byarray);
100 }
101 else byarray = old_rpart;
102
103 /* Append value to array */
104 json_array_append_new(byarray, obj);
105 }
106 else json_object_set_new(jrecur, rpart, obj);
107 }
108
85109 static void icalrecur_add_int_to_json_object(void *jrecur, const char *rpart,
86110 int i)
87111 {
88 json_object_set_new((json_t *) jrecur, rpart, json_integer(i));
112 icalrecur_add_obj_to_json_object(jrecur, rpart, json_integer(i));
89113 }
90114
91115 static void icalrecur_add_string_to_json_object(void *jrecur, const char *rpart,
92116 const char *s)
93117 {
94 json_object_set_new((json_t *) jrecur, rpart, json_string(s));
118 icalrecur_add_obj_to_json_object(jrecur, rpart, json_string(s));
95119 }
96120
97121
307331
308332
309333 /* Add value */
310 /* XXX Need to handle multi-valued properties */
311334 value = icalproperty_get_value(prop);
312 if (value) json_array_append_new(jprop, icalvalue_as_json_object(value));
335 if (value) {
336 switch (icalproperty_isa(prop)) {
337 case ICAL_CATEGORIES_PROPERTY:
338 case ICAL_RESOURCES_PROPERTY:
339 case ICAL_POLLPROPERTIES_PROPERTY:
340 if (icalvalue_isa(value) == ICAL_TEXT_VALUE) {
341 /* Handle multi-valued properties */
342 const char *str = icalvalue_as_ical_string(value);
343 tok_t tok;
344
345 tok_init(&tok, str, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT|TOK_EMPTY);
346 while ((str = tok_next(&tok))) {
347 if (*str) json_array_append_new(jprop, json_string(str));
348 }
349 tok_fini(&tok);
350 break;
351 }
352
353 default:
354 json_array_append_new(jprop, icalvalue_as_json_object(value));
355 break;
356 }
357 }
313358
314359 return jprop;
315360 }
414459 extern void icalrecur_add_bydayrules(struct icalrecur_parser *parser,
415460 const char* vals);
416461
417 static const char *json_x_value(json_t *jvalue)
462 static const char *_json_x_value(json_t *jvalue)
418463 {
419464 static char buf[21];
420465
425470 }
426471 else return json_string_value(jvalue);
427472 }
473
474 static const char *json_x_value(json_t *jvalue)
475 {
476 static struct buf buf = BUF_INITIALIZER;
477
478 if (json_is_array(jvalue)) {
479 size_t i, n = json_array_size(jvalue);
480 const char *sep = "";
481
482 buf_reset(&buf);
483 for (i = 0; i < n; i++) {
484 buf_printf(&buf, "%s%s",
485 sep, _json_x_value(json_array_get(jvalue, i)));
486 sep = ",";
487 }
488 return buf_cstring(&buf);
489 }
490 else return _json_x_value(jvalue);
491 }
492
428493
429494 /*
430495 * Construct an iCalendar property value from a JSON object.
475540 case ICAL_INTEGER_VALUE:
476541 if (json_is_integer(jvalue))
477542 value = icalvalue_new_integer((int) json_integer_value(jvalue));
543 else if (json_is_string(jvalue))
544 value = icalvalue_new_integer(atoi(json_string_value(jvalue)));
478545 else
479546 syslog(LOG_WARNING, "jCal integer object expected");
480547 break;
578645 icalproperty *prop = NULL;
579646 icalvalue_kind valkind;
580647 icalvalue *value;
648 int len;
581649
582650 /* Sanity check the types of the jCal property object */
583 if (!json_is_array(jprop) || json_array_size(jprop) < 4) {
651 if (!json_is_array(jprop) || (len = json_array_size(jprop)) < 4) {
584652 syslog(LOG_WARNING,
585653 "jCal component object is not an array of 4+ objects");
586654 return NULL;
635703 }
636704
637705 /* Add value */
638 /* XXX Need to handle multi-valued properties */
639706 jvalue = json_array_get(jprop, 3);
640 value = json_object_to_icalvalue(jvalue, valkind);
641 if (!value) {
642 syslog(LOG_ERR, "Creation of new %s property value failed", propname);
643 goto error;
707 switch (kind) {
708 case ICAL_CATEGORIES_PROPERTY:
709 case ICAL_RESOURCES_PROPERTY:
710 case ICAL_POLLPROPERTIES_PROPERTY:
711 if (json_is_string(jvalue) && len > 4) {
712 /* Handle multi-valued properties */
713 struct buf buf = BUF_INITIALIZER;
714 int i;
715
716 buf_setcstr(&buf, json_string_value(jvalue));
717 for (i = 4; i < len; i++) {
718 buf_putc(&buf, ',');
719 jvalue = json_array_get(jprop, i);
720 buf_appendcstr(&buf, json_string_value(jvalue));
721 }
722 value = icalvalue_new_from_string(valkind, buf_cstring(&buf));
723 buf_free(&buf);
724 break;
725 }
726
727 default:
728 value = json_object_to_icalvalue(jvalue, valkind);
729 if (!value) {
730 syslog(LOG_ERR, "Creation of new %s property value failed",
731 propname);
732 goto error;
733 }
734 break;
644735 }
645736
646737 icalproperty_set_value(prop, value);
7070 #include "assert.h"
7171 #include "auth.h"
7272 #include "backend.h"
73 #include "caldav_db.h"
74 #include "carddav_db.h"
7375 #include "duplicate.h"
7476 #include "exitcodes.h"
7577 #include "global.h"
224226 /* so we can do quota operations */
225227 quotadb_init(0);
226228 quotadb_open(NULL);
229
230 /* so we can do DAV opterations */
231 caldav_init();
232 carddav_init();
227233
228234 /* Initialize the annotatemore db (for sieve on shared mailboxes) */
229235 annotatemore_init(0, NULL, NULL);
959965 quotadb_close();
960966 quotadb_done();
961967
968 carddav_done();
969 caldav_done();
970
962971 annotatemore_close();
963972 annotatemore_done();
964973
7272
7373 #include "acl.h"
7474 #include "assert.h"
75 #include "caldav_db.h"
76 #include "carddav_db.h"
7577 #include "crc32.h"
7678 #include "exitcodes.h"
7779 #include "global.h"
716718 hash %= PRIME;
717719 }
718720
719 free(mailbox->uniqueid);
721 if (mailbox->uniqueid) free(mailbox->uniqueid);
720722 mailbox->uniqueid = xmalloc(32);
721723
722724 snprintf(mailbox->uniqueid, 32, "%08x%08x",
11161118 mailbox->quotaroot = xstrndup(p, tab - p);
11171119 }
11181120
1121 if (mailbox->uniqueid) free(mailbox->uniqueid);
11191122 if (oldformat) {
11201123 /* uniqueid needs to be generated when we know the uidvalidity */
11211124 mailbox->uniqueid = NULL;
20472050 mailbox_index_update_counts(mailbox, &record, 1);
20482051 }
20492052
2053 return r;
2054 }
2055
2056 #ifdef WITH_DAV
2057 static int mailbox_update_carddav(struct mailbox *mailbox,
2058 struct index_record *old,
2059 struct index_record *new)
2060 {
2061 struct carddav_db *carddavdb = NULL;
2062 struct param *param;
2063 struct body *body = NULL;
2064 struct carddav_data *cdata = NULL;
2065 const char *resource = NULL;
2066 int r = 0;
2067
2068 /* conditions in which there's nothing to do */
2069 if (!new) goto done;
2070
2071 /* phantom record - never really existed here */
2072 if (!old && (new->system_flags & FLAG_EXPUNGED))
2073 goto done;
2074
2075 r = mailbox_cacherecord(mailbox, new);
2076 if (r) goto done;
2077
2078 /* Get resource URL from filename param in Content-Disposition header */
2079 message_read_bodystructure(new, &body);
2080 for (param = body->disposition_params; param; param = param->next) {
2081 if (!strcmp(param->attribute, "FILENAME")) {
2082 resource = param->value;
2083 }
2084 }
2085
2086 carddavdb = carddav_open(mailbox, 0);
2087
2088 /* Find existing record for this resource */
2089 carddav_lookup_resource(carddavdb, mailbox->name, resource, 1, &cdata);
2090
2091 /* XXX - if not matching by UID, skip - this record doesn't refer to the current item */
2092
2093 if (new->system_flags & FLAG_EXPUNGED) {
2094 /* is there an existing record? */
2095 if (!cdata) goto done;
2096
2097 /* does it still come from this UID? */
2098 if (cdata->dav.imap_uid != new->uid) goto done;
2099
2100 /* delete entry */
2101 r = carddav_delete(carddavdb, cdata->dav.rowid, 0);
2102 }
2103 else {
2104 const char *uid = NULL, *fullname = NULL, *nickname = NULL;
2105 VObjectIterator i;
2106 const char *msg_base = NULL;
2107 size_t msg_size = 0;
2108 VObject *vcard;
2109
2110 /* already seen this message, so do we update it? No */
2111 if (old) goto done;
2112
2113 /* Load message containing the resource and parse vcard data */
2114 r = mailbox_map_message(mailbox, new->uid, &msg_base, &msg_size);
2115 if (r) goto done;
2116
2117 vcard = Parse_MIME(msg_base + new->header_size,
2118 new->size - new->header_size);
2119 mailbox_unmap_message(mailbox, new->uid, &msg_base, &msg_size);
2120 if (!vcard) goto done;
2121
2122 initPropIterator(&i, vcard);
2123 while (moreIteration(&i)) {
2124 VObject *prop = nextVObject(&i);
2125 const char *name = vObjectName(prop);
2126
2127 if (!strcmp(name, "UID")) {
2128 uid = fakeCString(vObjectUStringZValue(prop));
2129 }
2130 else if (!strcmp(name, "FN")) {
2131 fullname = fakeCString(vObjectUStringZValue(prop));
2132 }
2133 if (!strcmp(name, "NICKNAME")) {
2134 nickname = fakeCString(vObjectUStringZValue(prop));
2135 }
2136 }
2137
2138 /* Create mapping entry from resource name to UID */
2139 cdata->dav.mailbox = mailbox->name;
2140 cdata->dav.resource = resource;
2141 cdata->dav.imap_uid = new->uid;
2142 cdata->vcard_uid = uid;
2143 cdata->fullname = fullname;
2144 cdata->nickname = nickname;
2145
2146 if (!cdata->dav.creationdate)
2147 cdata->dav.creationdate = new->internaldate;
2148
2149 r = carddav_write(carddavdb, cdata, 0);
2150 }
2151
2152 done:
2153 if (carddavdb) {
2154 carddav_commit(carddavdb);
2155 carddav_close(carddavdb);
2156 }
2157
2158 return r;
2159 }
2160
2161 static int mailbox_update_caldav(struct mailbox *mailbox,
2162 struct index_record *old,
2163 struct index_record *new)
2164 {
2165 struct caldav_db *caldavdb = NULL;
2166 struct param *param;
2167 struct body *body = NULL;
2168 struct caldav_data *cdata = NULL;
2169 const char *resource = NULL;
2170 const char *sched_tag = NULL;
2171 int r = 0;
2172
2173 /* conditions in which there's nothing to do */
2174 if (!new) goto done;
2175
2176 /* phantom record - never really existed here */
2177 if (!old && (new->system_flags & FLAG_EXPUNGED))
2178 goto done;
2179
2180 r = mailbox_cacherecord(mailbox, new);
2181 if (r) goto done;
2182
2183 /* Get resource URL from filename param in Content-Disposition header */
2184 message_read_bodystructure(new, &body);
2185 for (param = body->disposition_params; param; param = param->next) {
2186 if (!strcmp(param->attribute, "FILENAME")) {
2187 resource = param->value;
2188 }
2189 else if (!strcmp(param->attribute, "SCHEDULE-TAG")) {
2190 sched_tag = param->value;
2191 }
2192 }
2193
2194 caldavdb = caldav_open(mailbox, 0);
2195
2196 /* Find existing record for this resource */
2197 caldav_lookup_resource(caldavdb, mailbox->name, resource, 1, &cdata);
2198
2199 /* XXX - if not matching by UID, skip - this record doesn't refer to the current item */
2200
2201 if (new->system_flags & FLAG_EXPUNGED) {
2202 /* is there an existing record? */
2203 if (!cdata) goto done;
2204
2205 /* does it still come from this UID? */
2206 if (cdata->dav.imap_uid != new->uid) goto done;
2207
2208 /* delete entry */
2209 r = caldav_delete(caldavdb, cdata->dav.rowid, 0);
2210 }
2211 else {
2212 const char *msg_base = NULL;
2213 size_t msg_size = 0;
2214 icalcomponent *ical = NULL;
2215
2216 /* already seen this message, so do we update it? No */
2217 if (old) goto done;
2218
2219 /* Load message containing the resource and parse iCal data */
2220 r = mailbox_map_message(mailbox, new->uid, &msg_base, &msg_size);
2221 if (r) goto done;
2222
2223 ical = icalparser_parse_string(msg_base + new->header_size);
2224 mailbox_unmap_message(mailbox, new->uid, &msg_base, &msg_size);
2225 if (!ical) goto done;
2226
2227 cdata->dav.creationdate = new->internaldate;
2228 cdata->dav.mailbox = mailbox->name;
2229 cdata->dav.imap_uid = new->uid;
2230 cdata->dav.resource = resource;
2231 cdata->sched_tag = sched_tag;
2232
2233 caldav_make_entry(ical, cdata);
2234
2235 r = caldav_write(caldavdb, cdata, 0);
2236
2237 icalcomponent_free(ical);
2238 }
2239
2240 done:
2241 message_free_body(body);
2242 free(body);
2243
2244 if (caldavdb) {
2245 caldav_commit(caldavdb);
2246 caldav_close(caldavdb);
2247 }
2248
2249 return r;
2250 }
2251 #else
2252 static int
2253 mailbox_update_carddav(struct mailbox *mailbox __attribute__((unused)),
2254 struct index_record *old __attribute__((unused)),
2255 struct index_record *new __attribute__((unused)))
2256 {
2257 return 0;
2258 }
2259
2260 static int
2261 mailbox_update_caldav(struct mailbox *mailbox __attribute__((unused)),
2262 struct index_record *old __attribute__((unused)),
2263 struct index_record *new __attribute__((unused)))
2264 {
2265 return 0;
2266 }
2267 #endif /* WITH_DAV */
2268
2269 /* NOTE: maybe make this able to return error codes if we have
2270 * support for transactional mailbox updates later. For now,
2271 * we expect callers to have already done all sanity checking */
2272 static int mailbox_update_indexes(struct mailbox *mailbox,
2273 struct index_record *old,
2274 struct index_record *new)
2275 {
2276 const char *userid = mboxname_to_userid(mailbox->name);
2277 int r = 0;
2278
2279 if (old)
2280 mailbox_index_update_counts(mailbox, old, 0);
2281 if (new)
2282 mailbox_index_update_counts(mailbox, new, 1);
2283
2284 if (!userid) return 0;
2285
2286 if (mailbox->mbtype & MBTYPE_CALENDAR) {
2287 r = mailbox_update_caldav(mailbox, old, new);
2288 if (r) return r;
2289 }
2290
2291 if (mailbox->mbtype & MBTYPE_ADDRESSBOOK) {
2292 r = mailbox_update_carddav(mailbox, old, new);
2293 if (r) return r;
2294 }
2295
20502296 return 0;
20512297 }
20522298
21202366 /* remove the counts for the old copy, and add them for
21212367 * the new copy */
21222368
2123 mailbox_index_update_counts(mailbox, &oldrecord, 0);
2124 mailbox_index_update_counts(mailbox, record, 1);
2369 mailbox_update_indexes(mailbox, &oldrecord, record);
21252370
21262371 mailbox_index_record_to_buf(record, buf);
21272372
22332478 }
22342479
22352480 /* add counts */
2236 mailbox_index_update_counts(mailbox, record, 1);
2481 mailbox_update_indexes(mailbox, NULL, record);
22372482
22382483 mailbox_index_record_to_buf(record, buf);
2239
2484
22402485 recno = mailbox->i.num_records + 1;
22412486
22422487 offset = mailbox->i.start_offset +
26882933 /* returns a mailbox locked in MAILBOX EXCLUSIVE mode, so you
26892934 * don't need to lock the index file to work with it :) */
26902935 int mailbox_create(const char *name,
2936 uint32_t mbtype,
26912937 const char *part,
26922938 const char *acl,
26932939 const char *uniqueid,
27202966
27212967 mailbox->part = xstrdup(part);
27222968 mailbox->acl = xstrdup(acl);
2969 mailbox->mbtype = mbtype;
27232970
27242971 hasquota = quota_findroot(quotaroot, sizeof(quotaroot), name);
27252972
31133360 assert(mailbox_index_islocked(oldmailbox, 1));
31143361
31153362 /* Create new mailbox */
3116 r = mailbox_create(newname, newpartition,
3117 oldmailbox->acl, NULL,
3118 oldmailbox->i.options,
3363 r = mailbox_create(newname, oldmailbox->mbtype, newpartition,
3364 oldmailbox->acl, NULL, oldmailbox->i.options,
31193365 time(0), &newmailbox);
31203366
31213367 if (r) return r;
35213767
35223768 mailbox->part = xstrdup(mbentry.partition);
35233769 mailbox->acl = xstrdup(mbentry.acl);
3770 mailbox->mbtype = mbentry.mbtype;
35243771
35253772 /* Attempt to open index */
35263773 r = mailbox_open_index(mailbox);
35313778 /* no cyrus.index file at all - well, we're in a pickle!
35323779 * no point trying to rescue anything else... */
35333780 mailbox_close(&mailbox);
3534 return mailbox_create(name, mbentry.partition, mbentry.acl,
3535 NULL, options, time(0), mbptr);
3781 return mailbox_create(name, mbentry.mbtype, mbentry.partition,
3782 mbentry.acl, NULL, options, time(0), mbptr);
35363783 }
35373784
35383785 /* read header, if it is not there, we need to create it */
7575 #define FNAME_CACHE "/cyrus.cache"
7676 #define FNAME_SQUAT "/cyrus.squat"
7777 #define FNAME_EXPUNGE "/cyrus.expunge"
78 #define FNAME_DAV "/cyrus.dav"
7879
7980 enum meta_filename {
8081 META_HEADER = 1,
8182 META_INDEX,
8283 META_CACHE,
8384 META_SQUAT,
84 META_EXPUNGE
85 META_EXPUNGE,
86 META_DAV
8587 };
8688
8789 #define MAILBOX_FNAME_LEN 256
481483 mailbox_decideproc_t *decideproc, void *deciderock);
482484 extern void mailbox_unlock_index(struct mailbox *mailbox, struct statusdata *sd);
483485
484 extern int mailbox_create(const char *name, const char *part, const char *acl,
485 const char *uniqueid, int options, unsigned uidvalidity,
486 struct mailbox **mailboxptr);
486 extern int mailbox_create(const char *name, uint32_t mbtype, const char *part,
487 const char *acl, const char *uniqueid, int options,
488 unsigned uidvalidity, struct mailbox **mailboxptr);
487489
488490 extern int mailbox_copy_files(struct mailbox *mailbox, const char *newpart,
489491 const char *newname);
6363
6464 #include "assert.h"
6565 #include "annotate.h"
66 #include "dav_util.h"
6667 #include "exitcodes.h"
6768 #include "global.h"
6869 #include "imap_err.h"
468469 { META_INDEX, "cyrus.index" },
469470 { META_CACHE, "cyrus.cache" },
470471 { META_EXPUNGE, "cyrus.expunge" },
472 { META_DAV, "cyrus.dav" },
471473 { 0, NULL }
472474 };
473475
474 enum { SEEN_DB = 0, SUBS_DB = 1, MBOXKEY_DB = 2 };
475 static int NUM_USER_DATA_FILES = 3;
476 enum { SEEN_DB = 0, SUBS_DB = 1, MBOXKEY_DB = 2, DAV_DB = 3 };
477 static int NUM_USER_DATA_FILES = 4;
476478
477479 int dump_mailbox(const char *tag, struct mailbox *mailbox, uint32_t uid_start,
478480 int oldversion,
628630 fname = mboxkey_getpath(userid);
629631 ftag = "MBOXKEY";
630632 break;
633 case DAV_DB: {
634 struct buf dav_file = BUF_INITIALIZER;
635
636 dav_getpath_byuserid(&dav_file, userid);
637 fname = (char *) buf_cstring(&dav_file);
638 ftag = "DAV";
639 break;
640 }
631641 default:
632642 fatal("unknown user data file", EC_OSFILE);
633643 }
835845 if (r == IMAP_MAILBOX_NONEXISTENT) {
836846 struct mboxlist_entry mbentry;
837847 r = mboxlist_lookup(mbname, &mbentry, NULL);
838 if (!r) r = mailbox_create(mbname, mbentry.partition, mbentry.acl,
839 NULL, 0, 0, &mailbox);
848 if (!r) r = mailbox_create(mbname, mbentry.mbtype, mbentry.partition,
849 mbentry.acl, NULL, 0, 0, &mailbox);
840850 }
841851 if(r) goto done;
842852
986996 char *s = user_hash_subs(userid);
987997 strlcpy(fnamebuf, s, sizeof(fnamebuf));
988998 free(s);
999 } else if (userid && !strcmp(file.s, "DAV")) {
1000 /* overwriting this outright is absolutely what we want to do */
1001 struct buf dav_file = BUF_INITIALIZER;
1002 dav_getpath_byuserid(&dav_file, userid);
1003 strlcpy(fnamebuf, buf_cstring(&dav_file), sizeof(fnamebuf));
1004 buf_free(&dav_file);
9891005 } else if (userid && !strcmp(file.s, "SEEN")) {
9901006 seen_file = seen_getpath(userid);
9911007
109109 return mboxent;
110110 }
111111
112 const char *mboxlist_mbtype_to_string(uint32_t mbtype)
113 {
114 static struct buf buf = BUF_INITIALIZER;
115
116 buf_reset(&buf);
117
118 if (mbtype & MBTYPE_DELETED)
119 buf_putc(&buf, 'd');
120 if (mbtype & MBTYPE_MOVING)
121 buf_putc(&buf, 'm');
122 if (mbtype & MBTYPE_NETNEWS)
123 buf_putc(&buf, 'n');
124 if (mbtype & MBTYPE_REMOTE)
125 buf_putc(&buf, 'r');
126 if (mbtype & MBTYPE_RESERVE)
127 buf_putc(&buf, 'z');
128 if (mbtype & MBTYPE_CALENDAR)
129 buf_putc(&buf, 'c');
130 if (mbtype & MBTYPE_ADDRESSBOOK)
131 buf_putc(&buf, 'a');
132
133 return buf_cstring(&buf);
134 }
135
112136 /*
113137 * Lookup 'name' in the mailbox list.
114138 * The capitalization of 'name' is canonicalized to the way it appears
154178 }
155179
156180 /* never get here */
181 }
182
183 uint32_t mboxlist_string_to_mbtype(const char *string)
184 {
185 uint32_t mbtype = 0;
186
187 if (!string) return 0; /* null just means default */
188
189 for (; *string; string++) {
190 switch (*string) {
191 case 'a':
192 mbtype |= MBTYPE_ADDRESSBOOK;
193 break;
194 case 'c':
195 mbtype |= MBTYPE_CALENDAR;
196 break;
197 case 'd':
198 mbtype |= MBTYPE_DELETED;
199 break;
200 case 'm':
201 mbtype |= MBTYPE_MOVING;
202 break;
203 case 'n':
204 mbtype |= MBTYPE_NETNEWS;
205 break;
206 case 'r':
207 mbtype |= MBTYPE_REMOTE;
208 break;
209 case 'z':
210 mbtype |= MBTYPE_RESERVE;
211 break;
212 }
213 }
214
215 return mbtype;
157216 }
158217
159218 static int mboxlist_mylookup(const char *name, struct mboxlist_entry *entry,
306365 int RMW, int localonly, int force_user_create,
307366 struct txn **tid)
308367 {
309 int r;
368 int r = 0;
310369 struct mboxlist_entry mbentry;
311370 char *name = xstrdup(mboxname);
312371 char *mbox = name;
323382
324383 /* Check for invalid name/partition */
325384 if (partition && strlen(partition) > MAX_PARTITION_LEN) {
326 return IMAP_PARTITION_UNKNOWN;
385 r = IMAP_PARTITION_UNKNOWN;
386 goto done;
327387 }
328388 if (config_virtdomains && (p = strchr(name, '!'))) {
329389 /* pointer to mailbox w/o domain prefix */
332392 r = mboxname_policycheck(mbox);
333393 /* filthy hack to support rename pre-check semantics, which
334394 will go away when we refactor this code */
335 if (r && force_user_create != 1) return r;
395 if (r && force_user_create != 1) goto done;
336396
337397 /* you must be a real admin to create a local-only mailbox */
338 if(!isadmin && localonly) return IMAP_PERMISSION_DENIED;
339 if(!isadmin && force_user_create) return IMAP_PERMISSION_DENIED;
398 if (!isadmin && (localonly || force_user_create)) {
399 r = IMAP_PERMISSION_DENIED;
400 goto done;
401 }
340402
341403 /* User has admin rights over their own mailbox namespace */
342404 if (mboxname_userownsmailbox(userid, name) && strchr(mbox+5, '.') &&
356418 r = IMAP_PERMISSION_DENIED;
357419 }
358420
359 return r;
360 break;
421 goto done;
422
361423 case IMAP_MAILBOX_NONEXISTENT:
362424 break;
363425
364426 default:
365 return r;
366 break;
427 goto done;
367428 }
368429
369430 /* Search for a parent - stop if we hit the domain separator */
387448 break;
388449
389450 default:
390 return r;
391 break;
451 goto done;
392452 }
393453 }
394454
403463 >= user_folder_limit) {
404464 syslog(LOG_ERR, "LIMIT: refused to create %s for %s because of limit %d",
405465 mbox, userid, user_folder_limit);
406 return IMAP_PERMISSION_DENIED;
466 r = IMAP_PERMISSION_DENIED;
467 goto done;
407468 }
408469 }
409470 }
412473 /* check acl */
413474 if (!isadmin &&
414475 !(cyrus_acl_myrights(auth_state, mbentry.acl) & ACL_CREATE)) {
415 return IMAP_PERMISSION_DENIED;
476 r = IMAP_PERMISSION_DENIED;
477 goto done;
416478 }
417479
418480 /* Copy partition, if not specified */
433495 strncpy(name, parent, strlen(parent));
434496 } else { /* parentlen == 0, no parent mailbox */
435497 if (!isadmin) {
436 return IMAP_PERMISSION_DENIED;
498 r = IMAP_PERMISSION_DENIED;
499 goto done;
437500 }
438501
439502 ouracl = xstrdup("");
441504 char *firstdot = strchr(mbox+5, '.');
442505 if (!force_user_create && firstdot) {
443506 /* Disallow creating user.X.* when no user.X */
444 free(name);
445507 free(ouracl);
446 return IMAP_PERMISSION_DENIED;
508 r = IMAP_PERMISSION_DENIED;
509 goto done;
447510 }
448511
449512 /*
525588 if (newacl) *newacl = ouracl;
526589 else free(ouracl);
527590
528 return 0;
591 r = 0;
592
593 done:
594 free(name);
595
596 return r;
529597 }
530598
531599 int
593661
594662 if (!dbonly && !isremote) {
595663 /* Filesystem Operations */
596 r = mailbox_create(name, newpartition, acl, uniqueid,
664 r = mailbox_create(name, mbtype, newpartition, acl, uniqueid,
597665 options, uidvalidity, &newmailbox);
598666 }
599667
653721 localonly, forceuser, dbonly, NULL);
654722 }
655723
656 int mboxlist_createsync(const char *name, int mbtype,
657 const char *partition,
724 int mboxlist_createsync(const char *name, int mbtype, const char *partition,
658725 const char *userid, struct auth_state *auth_state,
659726 int options, unsigned uidvalidity,
660727 const char *acl, const char *uniqueid,
661 struct mailbox **mboxptr)
728 int local_only, struct mailbox **mboxptr)
662729 {
663730 return mboxlist_createmailbox_full(name, mbtype, partition,
664731 1, userid, auth_state,
665732 options, uidvalidity, acl, uniqueid,
666 0, 1, 0, mboxptr);
733 local_only, 1, 0, mboxptr);
667734 }
668735
669736 /* insert an entry for the proxy */
848915 /* Get mboxlist_renamemailbox to do the hard work. No ACL checks needed */
849916 r = mboxlist_renamemailbox((char *)name, newname, mbentry.partition,
850917 1 /* isadmin */, userid,
851 auth_state, force, 1);
918 auth_state, 0, force, 1);
852919 if (r) goto out;
853920
854921 /* Rename mailbox annotations */
9881055 int mboxlist_renamemailbox(const char *oldname, const char *newname,
9891056 const char *partition, int isadmin,
9901057 const char *userid, struct auth_state *auth_state,
991 int forceuser, int ignorequota)
1058 int local_only, int forceuser, int ignorequota)
9921059 {
9931060 int r;
9941061 long myrights;
11511218 goto done;
11521219 }
11531220
1154 if (config_mupdate_server) {
1221 if (!local_only && config_mupdate_server) {
11551222 /* commit the mailbox in MUPDATE */
11561223 char buf[MAX_PARTITION_LEN + HOSTNAME_SIZE + 2];
11571224 const char *acl = newmailbox ? newmailbox->acl : oldmailbox->acl;
8484 char *acl;
8585 };
8686
87 /* Convert mbtype to/from a string */
88 const char *mboxlist_mbtype_to_string(uint32_t mbtype);
89 uint32_t mboxlist_string_to_mbtype(const char *string);
90
8791 /* Lookup 'name' in the mailbox list. */
8892 int mboxlist_lookup(const char *name, struct mboxlist_entry *entry, struct txn **tid);
8993
116120 int localonly, int forceuser, int dbonly);
117121
118122 /* create mailbox from sync */
119 int mboxlist_createsync(const char *name, int mbtype,
120 const char *partition,
123 int mboxlist_createsync(const char *name, int mbtype, const char *partition,
121124 const char *userid, struct auth_state *auth_state,
122125 int options, unsigned uidvalidity, const char *acl,
123 const char *uniqueid, struct mailbox **mboxptr);
126 const char *uniqueid, int local_only,
127 struct mailbox **mboxptr);
124128
125129 int mboxlist_createmailbox_full(const char *name, int mbtype,
126130 const char *partition,
151155 int mboxlist_renamemailbox(const char *oldname, const char *newname,
152156 const char *partition, int isadmin,
153157 const char *userid, struct auth_state *auth_state,
154 int forceuser, int ignorequota);
158 int local_only, int forceuser, int ignorequota);
155159
156160 /* change ACL */
157161 int mboxlist_setacl(const char *name, const char *identifier,
12501250 metaflag = IMAP_ENUM_METAPARTITION_FILES_SQUAT;
12511251 filename = FNAME_SQUAT;
12521252 break;
1253 case META_DAV:
1254 snprintf(confkey, 256, "metadir-dav-%s", partition);
1255 metaflag = IMAP_ENUM_METAPARTITION_FILES_DAV;
1256 filename = FNAME_DAV;
1257 break;
12531258 case 0:
12541259 break;
12551260 default:
10601060
10611061 be = backend_current;
10621062 if (arg1.len &&
1063 (!is_newsgroup(arg1.s) ||
1064 (r = open_group(arg1.s, 1, &be, NULL)))) goto nogroup;
1063 (r = open_group(arg1.s, 0, &be, NULL))) goto nogroup;
10651064 else if (be) {
10661065 prot_printf(be->out, "%s", cmd.s);
10671066 if (arg1.len) {
10681067 prot_printf(be->out, " %s", arg1.s);
1069 if (LISTGROUP) prot_printf(be->out, " %s", arg2.s);
1068 if (LISTGROUP) prot_printf(be->out, " %s", arg2.s);
10701069 }
10711070 prot_printf(be->out, "\r\n");
10721071
17761775 if (!has_prefix) {
17771776 snprintf(mailboxname, sizeof(mailboxname), "%s%s", newsprefix, name);
17781777 name = mailboxname;
1778
1779 if (!is_newsgroup(name)) return IMAP_MAILBOX_NONEXISTENT;
17791780 }
17801781
17811782 if (!r) r = mlookup(name, &newserver, &acl, NULL);
25402541 }
25412542 }
25422543 else {
2543 prot_printf(nntp_out, "%s %u %u %c\r\n", name,
2544 prot_printf(nntp_out, "%s %u %u %c\r\n", name+strlen(newsprefix),
25442545 group_state->exists ? index_getuid(group_state, group_state->exists) :
25452546 group_state->mailbox->i.last_uid,
25462547 group_state->exists ? index_getuid(group_state, 1) :
29982999 if (!rcpt) return -1;
29993000
30003001 /* construct the mailbox name */
3001 sprintf(rcpt, "%.*s", (int) n, p);
3002 sprintf(rcpt, "%s%.*s", newsprefix, (int) n, p);
30023003
30033004 /* skip mailboxes that we don't serve as newsgroups */
30043005 if (!is_newsgroup(rcpt)) continue;
35373538 newsprefix, (int)len, group);
35383539
35393540 r = mboxlist_renamemailbox(oldmailboxname, newmailboxname, NULL, 0,
3540 newsmaster, newsmaster_authstate, 0, 0);
3541 newsmaster, newsmaster_authstate, 0, 0, 0);
35413542
35423543 /* XXX check body of message for useful MIME parts */
35433544
4646
4747 #include "saslclient.h"
4848
49 #define MAX_CAPA 8
49 #define MAX_CAPA 9
5050
5151 enum {
5252 /* generic capabilities */
8282 #include "acl.h"
8383 #include "assert.h"
8484 #include "bsearch.h"
85 #include "caldav_db.h"
86 #include "carddav_db.h"
8587 #include "crc32.h"
8688 #include "hash.h"
8789 #include "imparse.h"
254256 quotadb_init(0);
255257 quotadb_open(NULL);
256258
259 caldav_init();
260 carddav_init();
261
257262 /* Deal with nonexistent mailboxes */
258263 if (start_part) {
259264 /* We were handed a mailbox that does not exist currently */
391396
392397 quotadb_close();
393398 quotadb_done();
399
400 carddav_done();
401 caldav_done();
394402
395403 cyrus_done();
396404
7979 #include "mboxname.h"
8080 #include "map.h"
8181 #include "imapd.h"
82 #include "imap_proxy.h"
8283 #include "imparse.h"
8384 #include "util.h"
8485 #include "prot.h"
106107 static struct backend *sync_backend = NULL;
107108 static struct protstream *sync_out = NULL;
108109 static struct protstream *sync_in = NULL;
110 static struct buf tagbuf = BUF_INITIALIZER;
109111
110112 static struct namespace sync_namespace;
111113
114 static unsigned flags = 0;
112115 static int verbose = 0;
113116 static int verbose_logging = 0;
114117 static int connect_once = 0;
115118 static int background = 0;
116119 static int do_compress = 0;
120
121 static char *imap_parsemechlist(const char *str, struct stdprot_t *std)
122 {
123 char *ret = xzmalloc(strlen(str)+1);
124 char *tmp;
125 int num = 0;
126
127 if (strstr(str, " SASL-IR")) {
128 /* server supports initial response in AUTHENTICATE command */
129 std->sasl_cmd.maxlen = USHRT_MAX;
130 }
131
132 while ((tmp = strstr(str, " AUTH="))) {
133 char *end = (tmp += 6);
134
135 while((*end != ' ') && (*end != '\0')) end++;
136
137 /* add entry to list */
138 if (num++ > 0) strcat(ret, " ");
139 strlcat(ret, tmp, strlen(ret) + (end - tmp) + 1);
140
141 /* reset the string */
142 str = end;
143 }
144
145 return ret;
146 }
147
148 struct protocol_t imap_csync_protocol =
149 { "imap", "imap", TYPE_STD,
150 { { { 1, NULL },
151 { "C01 CAPABILITY", NULL, "C01 ", &imap_parsemechlist,
152 { { " AUTH=", CAPA_AUTH },
153 { " STARTTLS", CAPA_STARTTLS },
154 { " COMPRESS=DEFLATE", CAPA_COMPRESS },
155 { " X-REPLICATION", CAPA_REPLICATION },
156 { NULL, 0 } } },
157 { "S01 STARTTLS", "S01 OK", "S01 NO", 0 },
158 { "A01 AUTHENTICATE", 0, 0, "A01 OK", "A01 NO", "+ ", "*",
159 NULL, AUTO_CAPA_AUTH_OK },
160 { "Z01 COMPRESS DEFLATE", "* ", "Z01 OK" },
161 { "N01 NOOP", "* ", "N01 OK" },
162 { "Q01 LOGOUT", "* ", "Q01 " } } }
163 };
117164
118165 static struct protocol_t csync_protocol =
119166 { "csync", "csync", TYPE_STD,
124171 { NULL, 0 } } },
125172 { "STARTTLS", "OK", "NO", 1 },
126173 { "AUTHENTICATE", USHRT_MAX, 0, "OK", "NO", "+ ", "*", NULL, 0 },
127 { NULL, NULL, NULL },
174 { "COMPRESS DEFLATE", NULL, "OK" },
128175 { "NOOP", NULL, "OK" },
129176 { "EXIT", NULL, "OK" } } }
130177 };
131
132 static int do_meta(char *user);
133178
134179 static void shut_down(int code) __attribute__((noreturn));
135180 static void shut_down(int code)
164209
165210 /* ====================================================================== */
166211
167 /* Routines relevant to reserve operation */
168
169 /* Find the messages that we will want to upload from this mailbox,
170 * flag messages that are already available at the server end */
171
172 static int find_reserve_messages(struct mailbox *mailbox,
173 unsigned last_uid,
174 struct sync_msgid_list *part_list)
175 {
176 struct index_record record;
177 uint32_t recno;
178 int r;
179
180 for (recno = 1; recno <= mailbox->i.num_records; recno++) {
181 r = mailbox_read_index_record(mailbox, recno, &record);
182
183 if (r) {
184 syslog(LOG_ERR,
185 "IOERROR: reading index entry for recno %u of %s: %m",
186 recno, mailbox->name);
187 return IMAP_IOERROR;
188 }
189
190 if (record.system_flags & FLAG_UNLINKED)
191 continue;
192
193 /* skip over records already on replica */
194 if (record.uid <= last_uid)
195 continue;
196
197 sync_msgid_add(part_list, &record.guid);
198 }
199
200 return(0);
201 }
202
203 static int find_reserve_all(struct sync_name_list *mboxname_list,
204 struct sync_folder_list *master_folders,
205 struct sync_folder_list *replica_folders,
206 struct sync_reserve_list *reserve_guids)
207 {
208 struct sync_name *mbox;
209 struct sync_folder *rfolder;
210 struct sync_msgid_list *part_list;
211 struct mailbox *mailbox = NULL;
212 int r = 0;
213
214 /* Find messages we want to upload that are available on server */
215 for (mbox = mboxname_list->head; mbox; mbox = mbox->next) {
216 r = mailbox_open_irl(mbox->name, &mailbox);
217
218 /* Quietly skip over folders which have been deleted since we
219 started working (but record fact in case caller cares) */
220 if (r == IMAP_MAILBOX_NONEXISTENT) {
221 r = 0;
222 continue;
223 }
224
225 /* Quietly ignore objects that we don't have access to.
226 * Includes directory stubs, which have not underlying cyrus.*
227 * files in the filesystem */
228 if (r == IMAP_PERMISSION_DENIED) {
229 r = 0;
230 continue;
231 }
232
233 if (r) {
234 syslog(LOG_ERR, "IOERROR: Failed to open %s: %s",
235 mbox->name, error_message(r));
236 goto bail;
237 }
238
239 /* mailbox is open from here, no exiting without closing it! */
240
241 part_list = sync_reserve_partlist(reserve_guids, mailbox->part);
242
243 sync_folder_list_add(master_folders, mailbox->uniqueid, mailbox->name,
244 mailbox->part, mailbox->acl, mailbox->i.options,
245 mailbox->i.uidvalidity, mailbox->i.last_uid,
246 mailbox->i.highestmodseq, mailbox->i.sync_crc,
247 mailbox->i.recentuid, mailbox->i.recenttime,
248 mailbox->i.pop3_last_login);
249
250 rfolder = sync_folder_lookup(replica_folders, mailbox->uniqueid);
251 if (rfolder)
252 find_reserve_messages(mailbox, rfolder->last_uid, part_list);
253 else
254 find_reserve_messages(mailbox, 0, part_list);
255
256 mailbox_close(&mailbox);
257 }
258
259 bail:
260 return r;
261 }
262
263 static int mark_missing (struct dlist *kin,
264 struct sync_msgid_list *part_list)
265 {
266 struct dlist *kl = kin->head;
267 struct dlist *ki;
268 struct message_guid tmp_guid;
269 struct sync_msgid *msgid;
270
271 /* no missing at all, good */
272 if (!kl) return 0;
273
274 if (strcmp(kl->name, "MISSING")) {
275 syslog(LOG_ERR, "SYNCERROR: Illegal response to RESERVE: %s",
276 kl->name);
277 return IMAP_PROTOCOL_BAD_PARAMETERS;
278 }
279
280 /* unmark each missing item */
281 for (ki = kl->head; ki; ki = ki->next) {
282 if (!message_guid_decode(&tmp_guid, ki->sval)) {
283 syslog(LOG_ERR, "SYNCERROR: reserve: failed to parse GUID %s",
284 ki->sval);
285 return IMAP_PROTOCOL_BAD_PARAMETERS;
286 }
287
288 msgid = sync_msgid_lookup(part_list, &tmp_guid);
289 if (!msgid) {
290 syslog(LOG_ERR, "SYNCERROR: reserve: Got unexpected GUID %s", ki->sval);
291 return IMAP_PROTOCOL_BAD_PARAMETERS;
292 }
293
294 msgid->mark = 0;
295 part_list->marked--;
296 }
297
298 return 0;
299 }
300
301 static int reserve_partition(char *partition,
302 struct sync_folder_list *replica_folders,
303 struct sync_msgid_list *part_list)
304 {
305 const char *cmd = "RESERVE";
306 struct sync_msgid *msgid;
307 struct sync_folder *folder;
308 struct dlist *kl;
309 struct dlist *kin = NULL;
310 struct dlist *ki;
311 int r;
312
313 if (!part_list->count)
314 return 0; /* nothing to reserve */
315
316 if (!replica_folders->head)
317 return 0; /* nowhere to reserve */
318
319 kl = dlist_new(cmd);
320 dlist_atom(kl, "PARTITION", partition);
321
322 ki = dlist_list(kl, "MBOXNAME");
323 for (folder = replica_folders->head; folder; folder = folder->next)
324 dlist_atom(ki, "MBOXNAME", folder->name);
325
326 ki = dlist_list(kl, "GUID");
327 for (msgid = part_list->head; msgid; msgid = msgid->next) {
328 dlist_atom(ki, "GUID", message_guid_encode(&msgid->guid));
329 msgid->mark = 1;
330 part_list->marked++;
331 }
332
333 sync_send_apply(kl, sync_out);
334 dlist_free(&kl);
335
336 r = sync_parse_response(cmd, sync_in, &kin);
337 if (r) return r;
338
339 r = mark_missing(kin, part_list);
340 dlist_free(&kin);
341
342 return r;
343 }
344
345 static int reserve_messages(struct sync_name_list *mboxname_list,
346 struct sync_folder_list *master_folders,
347 struct sync_folder_list *replica_folders,
348 struct sync_reserve_list *reserve_guids)
349 {
350 struct sync_reserve *reserve;
351 int r;
352
353 r = find_reserve_all(mboxname_list, master_folders,
354 replica_folders, reserve_guids);
355 if (r) return r;
356
357 for (reserve = reserve_guids->head; reserve; reserve = reserve->next) {
358 r = reserve_partition(reserve->part, replica_folders, reserve->list);
359 if (r) return r;
360 }
361
362 return 0;
363 }
364
365 /* ====================================================================== */
366
367 static int response_parse(const char *cmd,
368 struct sync_folder_list *folder_list,
369 struct sync_name_list *sub_list,
370 struct sync_sieve_list *sieve_list,
371 struct sync_seen_list *seen_list,
372 struct sync_quota_list *quota_list)
373 {
374 struct dlist *kin = NULL;
375 struct dlist *kl;
376 int r;
377
378 r = sync_parse_response(cmd, sync_in, &kin);
379
380 /* Unpleasant: translate remote access error into "please reset me" */
381 if (r == IMAP_MAILBOX_NONEXISTENT)
382 return 0;
383
384 if (r) return r;
385
386 for (kl = kin->head; kl; kl = kl->next) {
387 if (!strcmp(kl->name, "SIEVE")) {
388 const char *filename = NULL;
389 time_t modtime = 0;
390 uint32_t active = 0;
391 if (!sieve_list) goto parse_err;
392 if (!dlist_getatom(kl, "FILENAME", &filename)) goto parse_err;
393 if (!dlist_getdate(kl, "LAST_UPDATE", &modtime)) goto parse_err;
394 dlist_getnum(kl, "ISACTIVE", &active); /* optional */
395 sync_sieve_list_add(sieve_list, filename, modtime, active);
396 }
397
398 else if (!strcmp(kl->name, "QUOTA")) {
399 const char *root = NULL;
400 uint32_t limit = 0;
401 if (!quota_list) goto parse_err;
402 if (!dlist_getatom(kl, "ROOT", &root)) goto parse_err;
403 if (!dlist_getnum(kl, "LIMIT", &limit)) goto parse_err;
404 sync_quota_list_add(quota_list, root, limit);
405 }
406
407 else if (!strcmp(kl->name, "LSUB")) {
408 struct dlist *i;
409 if (!sub_list) goto parse_err;
410 for (i = kl->head; i; i = i->next) {
411 sync_name_list_add(sub_list, i->sval);
412 }
413 }
414
415 else if (!strcmp(kl->name, "SEEN")) {
416 const char *uniqueid = NULL;
417 time_t lastread = 0;
418 uint32_t lastuid = 0;
419 time_t lastchange = 0;
420 const char *seenuids = NULL;
421 if (!seen_list) goto parse_err;
422 if (!dlist_getatom(kl, "UNIQUEID", &uniqueid)) goto parse_err;
423 if (!dlist_getdate(kl, "LASTREAD", &lastread)) goto parse_err;
424 if (!dlist_getnum(kl, "LASTUID", &lastuid)) goto parse_err;
425 if (!dlist_getdate(kl, "LASTCHANGE", &lastchange)) goto parse_err;
426 if (!dlist_getatom(kl, "SEENUIDS", &seenuids)) goto parse_err;
427 sync_seen_list_add(seen_list, uniqueid, lastread,
428 lastuid, lastchange, seenuids);
429 }
430
431 else if (!strcmp(kl->name, "MAILBOX")) {
432 const char *uniqueid = NULL;
433 const char *mboxname = NULL;
434 const char *part = NULL;
435 const char *acl = NULL;
436 const char *options = NULL;
437 modseq_t highestmodseq = 0;
438 uint32_t uidvalidity = 0;
439 uint32_t last_uid = 0;
440 uint32_t sync_crc = 0;
441 uint32_t recentuid = 0;
442 time_t recenttime = 0;
443 time_t pop3_last_login = 0;
444 if (!folder_list) goto parse_err;
445 if (!dlist_getatom(kl, "UNIQUEID", &uniqueid)) goto parse_err;
446 if (!dlist_getatom(kl, "MBOXNAME", &mboxname)) goto parse_err;
447 if (!dlist_getatom(kl, "PARTITION", &part)) goto parse_err;
448 if (!dlist_getatom(kl, "ACL", &acl)) goto parse_err;
449 if (!dlist_getatom(kl, "OPTIONS", &options)) goto parse_err;
450 if (!dlist_getmodseq(kl, "HIGHESTMODSEQ", &highestmodseq)) goto parse_err;
451 if (!dlist_getnum(kl, "UIDVALIDITY", &uidvalidity)) goto parse_err;
452 if (!dlist_getnum(kl, "LAST_UID", &last_uid)) goto parse_err;
453 if (!dlist_getnum(kl, "SYNC_CRC", &sync_crc)) goto parse_err;
454 if (!dlist_getnum(kl, "RECENTUID", &recentuid)) goto parse_err;
455 if (!dlist_getdate(kl, "RECENTTIME", &recenttime)) goto parse_err;
456 if (!dlist_getdate(kl, "POP3_LAST_LOGIN", &pop3_last_login)) goto parse_err;
457
458 sync_folder_list_add(folder_list, uniqueid,
459 mboxname, part, acl,
460 sync_parse_options(options),
461 uidvalidity, last_uid,
462 highestmodseq, sync_crc,
463 recentuid, recenttime,
464 pop3_last_login);
465 }
466 else
467 goto parse_err;
468 }
469 dlist_free(&kin);
470
471 return r;
472
473 parse_err:
474 dlist_free(&kin);
475 syslog(LOG_ERR, "SYNCERROR: %s: Invalid response %s",
476 cmd, dlist_lastkey());
477 return IMAP_PROTOCOL_BAD_PARAMETERS;
478 }
479
480 static int user_reset(char *userid)
481 {
482 const char *cmd = "UNUSER";
483 struct dlist *kl;
484
485 kl = dlist_atom(NULL, cmd, userid);
486 sync_send_apply(kl, sync_out);
487 dlist_free(&kl);
488
489 return sync_parse_response(cmd, sync_in, NULL);
490 }
491
492 static int folder_rename(char *oldname, char *newname, char *partition)
493 {
494 const char *cmd = "RENAME";
495 struct dlist *kl;
496
497 kl = dlist_new(cmd);
498 dlist_atom(kl, "OLDMBOXNAME", oldname);
499 dlist_atom(kl, "NEWMBOXNAME", newname);
500 dlist_atom(kl, "PARTITION", partition);
501 sync_send_apply(kl, sync_out);
502 dlist_free(&kl);
503
504 return sync_parse_response(cmd, sync_in, NULL);
505 }
506
507 static int folder_delete(char *mboxname)
508 {
509 const char *cmd = "UNMAILBOX";
510 struct dlist *kl;
511
512 kl = dlist_atom(NULL, cmd, mboxname);
513 sync_send_apply(kl, sync_out);
514 dlist_free(&kl);
515
516 return sync_parse_response(cmd, sync_in, NULL);
517 }
518
519 static int set_sub(const char *userid, const char *mboxname, int add)
520 {
521 const char *cmd = add ? "SUB" : "UNSUB";
522 struct dlist *kl;
523
524 if (verbose)
525 printf("%s %s %s\n", cmd, userid, mboxname);
526
527 if (verbose_logging)
528 syslog(LOG_INFO, "%s %s %s", cmd, userid, mboxname);
529
530 kl = dlist_new(cmd);
531 dlist_atom(kl, "USERID", userid);
532 dlist_atom(kl, "MBOXNAME", mboxname);
533 sync_send_apply(kl, sync_out);
534 dlist_free(&kl);
535
536 return sync_parse_response(cmd, sync_in, NULL);
537 }
538
539 static int folder_setannotation(const char *mboxname, const char *entry,
540 const char *userid, const char *value)
541 {
542 const char *cmd = "ANNOTATION";
543 struct dlist *kl;
544
545 kl = dlist_new(cmd);
546 dlist_atom(kl, "MBOXNAME", mboxname);
547 dlist_atom(kl, "ENTRY", entry);
548 dlist_atom(kl, "USERID", userid);
549 dlist_atom(kl, "VALUE", value);
550 sync_send_apply(kl, sync_out);
551 dlist_free(&kl);
552
553 return sync_parse_response(cmd, sync_in, NULL);
554 }
555
556 static int folder_unannotation(const char *mboxname, const char *entry,
557 const char *userid)
558 {
559 const char *cmd = "UNANNOTATION";
560 struct dlist *kl;
561
562 kl = dlist_new(cmd);
563 dlist_atom(kl, "MBOXNAME", mboxname);
564 dlist_atom(kl, "ENTRY", entry);
565 dlist_atom(kl, "USERID", userid);
566 sync_send_apply(kl, sync_out);
567 dlist_free(&kl);
568
569 return sync_parse_response(cmd, sync_in, NULL);
570 }
571
572 /* ====================================================================== */
573
574 static int sieve_upload(const char *userid, const char *filename,
575 unsigned long last_update)
576 {
577 const char *cmd = "SIEVE";
578 struct dlist *kl;
579 char *sieve;
580 uint32_t size;
581
582 sieve = sync_sieve_read(userid, filename, &size);
583 if (!sieve) return IMAP_IOERROR;
584
585 kl = dlist_new(cmd);
586 dlist_atom(kl, "USERID", userid);
587 dlist_atom(kl, "FILENAME", filename);
588 dlist_date(kl, "LAST_UPDATE", last_update);
589 dlist_buf(kl, "CONTENT", sieve, size);
590 sync_send_apply(kl, sync_out);
591 dlist_free(&kl);
592 free(sieve);
593
594 return sync_parse_response(cmd, sync_in, NULL);
595 }
596
597 static int sieve_delete(const char *userid, const char *filename)
598 {
599 const char *cmd = "UNSIEVE";
600 struct dlist *kl;
601
602 kl = dlist_new(cmd);
603 dlist_atom(kl, "USERID", userid);
604 dlist_atom(kl, "FILENAME", filename);
605 sync_send_apply(kl, sync_out);
606 dlist_free(&kl);
607
608 return sync_parse_response(cmd, sync_in, NULL);
609 }
610
611 static int sieve_activate(const char *userid, const char *filename)
612 {
613 const char *cmd = "ACTIVATE_SIEVE";
614 struct dlist *kl;
615
616 kl = dlist_new(cmd);
617 dlist_atom(kl, "USERID", userid);
618 dlist_atom(kl, "FILENAME", filename);
619 sync_send_apply(kl, sync_out);
620 dlist_free(&kl);
621
622 return sync_parse_response(cmd, sync_in, NULL);
623 }
624
625 static int sieve_deactivate(const char *userid)
626 {
627 const char *cmd = "UNACTIVATE_SIEVE";
628 struct dlist *kl;
629
630 kl = dlist_new(cmd);
631 dlist_atom(kl, "USERID", userid);
632 sync_send_apply(kl, sync_out);
633 dlist_free(&kl);
634
635 return sync_parse_response(cmd, sync_in, NULL);
636 }
637
638 /* ====================================================================== */
639
640 static int delete_quota(const char *root)
641 {
642 const char *cmd = "UNQUOTA";
643 struct dlist *kl;
644
645 kl = dlist_atom(NULL, cmd, root);
646 sync_send_apply(kl, sync_out);
647 dlist_free(&kl);
648
649 return sync_parse_response(cmd, sync_in, NULL);
650 }
651
652 static int update_quota_work(struct quota *client,
653 struct sync_quota *server)
654 {
655 const char *cmd = "QUOTA";
656 struct dlist *kl;
657 int r;
658
659 r = quota_read(client, NULL, 0);
660
661 /* disappeared? Delete it*/
662 if (r == IMAP_QUOTAROOT_NONEXISTENT)
663 return delete_quota(client->root);
664
665 if (r) {
666 syslog(LOG_INFO, "Warning: failed to read quotaroot %s: %s",
667 client->root, error_message(r));
668 return r;
669 }
670
671 if (server && (client->limit == server->limit))
672 return(0);
673
674 kl = dlist_new(cmd);
675 dlist_atom(kl, "ROOT", client->root);
676 dlist_num(kl, "LIMIT", client->limit);
677 sync_send_apply(kl, sync_out);
678 dlist_free(&kl);
679
680 return sync_parse_response(cmd, sync_in, NULL);
681 }
682
683212 static int user_sub(const char *userid, const char *mboxname)
684213 {
685214 int r;
688217
689218 switch (r) {
690219 case CYRUSDB_OK:
691 return set_sub(userid, mboxname, 1);
220 return sync_set_sub(userid, mboxname, 1, sync_backend, flags);
692221 case CYRUSDB_NOTFOUND:
693 return set_sub(userid, mboxname, 0);
222 return sync_set_sub(userid, mboxname, 0, sync_backend, flags);
694223 default:
695224 return r;
696225 }
697 }
698
699 static int copy_local(struct mailbox *mailbox, unsigned long uid)
700 {
701 uint32_t recno;
702 struct index_record oldrecord;
703 int r;
704
705 for (recno = 1; recno <= mailbox->i.num_records; recno++) {
706 r = mailbox_read_index_record(mailbox, recno, &oldrecord);
707 if (r) return r;
708
709 /* found the record, renumber it */
710 if (oldrecord.uid == uid) {
711 char *oldfname, *newfname;
712 struct index_record newrecord;
713
714 /* create the new record as a clone of the old record */
715 newrecord = oldrecord;
716 newrecord.uid = mailbox->i.last_uid + 1;
717
718 /* copy the file in to place */
719 oldfname = xstrdup(mailbox_message_fname(mailbox, oldrecord.uid));
720 newfname = xstrdup(mailbox_message_fname(mailbox, newrecord.uid));
721 r = mailbox_copyfile(oldfname, newfname, 0);
722 free(oldfname);
723 free(newfname);
724 if (r) return r;
725
726 /* append the new record */
727 r = mailbox_append_index_record(mailbox, &newrecord);
728 if (r) return r;
729
730 /* and expunge the old record */
731 oldrecord.system_flags |= FLAG_EXPUNGED;
732 r = mailbox_rewrite_index_record(mailbox, &oldrecord);
733
734 /* done - return */
735 return r;
736 }
737 }
738
739 /* not finding the record is an error! (should never happen) */
740 return IMAP_MAILBOX_NONEXISTENT;
741 }
742
743 static int fetch_file(struct mailbox *mailbox, unsigned long uid,
744 struct index_record *rp)
745 {
746 const char *cmd = "FETCH";
747 struct dlist *kin = NULL;
748 struct dlist *kl;
749 int r = 0;
750 const char *fname = dlist_reserve_path(mailbox->part, &rp->guid);
751 struct stat sbuf;
752
753 /* already reserved? great */
754 if (stat(fname, &sbuf) == 0)
755 return 0;
756
757 kl = dlist_new(cmd);
758 dlist_atom(kl, "MBOXNAME", mailbox->name);
759 dlist_atom(kl, "PARTITION", mailbox->part);
760 dlist_atom(kl, "GUID", message_guid_encode(&rp->guid));
761 dlist_num(kl, "UID", uid);
762 sync_send_lookup(kl, sync_out);
763 dlist_free(&kl);
764
765 r = sync_parse_response(cmd, sync_in, &kin);
766 if (r) return r;
767
768 kl = kin->head;
769 if (!kl) {
770 r = IMAP_MAILBOX_NONEXISTENT;
771 goto done;
772 }
773
774 if (!message_guid_equal(&kl->gval, &rp->guid))
775 r = IMAP_MAILBOX_CRC;
776
777 done:
778 dlist_free(&kin);
779 return r;
780 }
781
782 static int copy_remote(struct mailbox *mailbox, unsigned long uid,
783 struct dlist *kr)
784 {
785 struct index_record record;
786 struct dlist *ki;
787 int r;
788
789 for (ki = kr->head; ki; ki = ki->next) {
790 r = parse_upload(ki, mailbox, &record);
791 if (r) return r;
792 if (record.uid == uid) {
793 /* choose the destination UID */
794 record.uid = mailbox->i.last_uid + 1;
795
796 /* already fetched the file in the parse phase */
797
798 /* append the file */
799 r = sync_append_copyfile(mailbox, &record);
800
801 return r;
802 }
803 }
804 /* not finding the record is an error! (should never happen) */
805 return IMAP_MAILBOX_NONEXISTENT;
806 }
807
808 static int copyback_one_record(struct mailbox *mailbox,
809 struct index_record *rp,
810 struct dlist *kaction)
811 {
812 int r;
813
814 /* don't want to copy back expunged records! */
815 if (rp->system_flags & FLAG_EXPUNGED)
816 return 0;
817
818 /* if the UID is lower than master's last_uid,
819 * we'll need to renumber */
820 if (rp->uid <= mailbox->i.last_uid) {
821 /* Ok, now we need to check if it's just really stale
822 * (has been cleaned out locally) or an error.
823 * In the error case we copy back, stale
824 * we remove from the replica */
825 if (rp->modseq < mailbox->i.deletedmodseq) {
826 if (kaction)
827 dlist_num(kaction, "EXPUNGE", rp->uid);
828 }
829 else {
830 r = fetch_file(mailbox, rp->uid, rp);
831 if (r) return r;
832 if (kaction)
833 dlist_num(kaction, "COPYBACK", rp->uid);
834 }
835 }
836
837 /* otherwise we can pull it in with the same UID,
838 * which saves causing renumbering on the replica
839 * end, so is preferable */
840 else {
841 /* grab the file */
842 r = fetch_file(mailbox, rp->uid, rp);
843 if (r) return r;
844 /* make sure we're actually making changes now */
845 if (!kaction) return 0;
846 /* append the file */
847 r = sync_append_copyfile(mailbox, rp);
848 if (r) return r;
849 }
850
851 return 0;
852 }
853
854 static int renumber_one_record(struct index_record *mp,
855 struct dlist *kaction)
856 {
857 /* don't want to renumber expunged records */
858 if (mp->system_flags & FLAG_EXPUNGED)
859 return 0;
860
861 if (kaction)
862 dlist_num(kaction, "RENUMBER", mp->uid);
863
864 return 0;
865 }
866
867 static const char *make_flags(struct mailbox *mailbox, struct index_record *record)
868 {
869 static char buf[4096];
870 const char *sep = "";
871 int flag;
872
873 if (record->system_flags & FLAG_DELETED) {
874 snprintf(buf, 4096, "%s\\Deleted", sep);
875 sep = " ";
876 }
877 if (record->system_flags & FLAG_ANSWERED) {
878 snprintf(buf, 4096, "%s\\Answered", sep);
879 sep = " ";
880 }
881 if (record->system_flags & FLAG_FLAGGED) {
882 snprintf(buf, 4096, "%s\\Flagged", sep);
883 sep = " ";
884 }
885 if (record->system_flags & FLAG_DRAFT) {
886 snprintf(buf, 4096, "%s\\Draft", sep);
887 sep = " ";
888 }
889 if (record->system_flags & FLAG_EXPUNGED) {
890 snprintf(buf, 4096, "%s\\Expunged", sep);
891 sep = " ";
892 }
893 if (record->system_flags & FLAG_SEEN) {
894 snprintf(buf, 4096, "%s\\Seen", sep);
895 sep = " ";
896 }
897
898 /* print user flags in mailbox order */
899 for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
900 if (!mailbox->flagname[flag])
901 continue;
902 if (!(record->user_flags[flag/32] & (1<<(flag&31))))
903 continue;
904 snprintf(buf, 4096, "%s%s", sep, mailbox->flagname[flag]);
905 sep = " ";
906 }
907
908 return buf;
909 }
910
911 static void log_record(const char *name, struct mailbox *mailbox,
912 struct index_record *record)
913 {
914 syslog(LOG_NOTICE, "SYNCNOTICE: %s uid:%u modseq:" MODSEQ_FMT " "
915 "last_updated:%lu internaldate:%lu flags:(%s)",
916 name, record->uid, record->modseq,
917 record->last_updated, record->internaldate,
918 make_flags(mailbox, record));
919 }
920
921 static void log_mismatch(const char *reason, struct mailbox *mailbox,
922 struct index_record *mp,
923 struct index_record *rp)
924 {
925 syslog(LOG_NOTICE, "SYNCNOTICE: record mismatch with replica: %s %s",
926 mailbox->name, reason);
927 log_record("master", mailbox, mp);
928 log_record("replica", mailbox, rp);
929 }
930
931 static int compare_one_record(struct mailbox *mailbox,
932 struct index_record *mp,
933 struct index_record *rp,
934 struct dlist *kaction)
935 {
936 int diff = 0;
937 int i;
938 int r;
939
940 /* are there any differences? */
941 if (mp->modseq != rp->modseq)
942 diff = 1;
943 else if (mp->last_updated != rp->last_updated)
944 diff = 1;
945 else if (mp->internaldate != rp->internaldate)
946 diff = 1;
947 else if (mp->system_flags != rp->system_flags)
948 diff = 1;
949 else if (!message_guid_equal(&mp->guid, &rp->guid))
950 diff = 1;
951 else {
952 for (i = 0; i < MAX_USER_FLAGS/32; i++) {
953 if (mp->user_flags[i] != rp->user_flags[i])
954 diff = 1;
955 }
956 }
957
958 /* if differences we'll have to rewrite to bump the modseq
959 * so that regular replication will cause an update */
960 if (diff) {
961 /* interesting case - expunged locally */
962 if (mp->system_flags & FLAG_EXPUNGED) {
963 /* if expunged, fall through - the rewrite will lift
964 * the modseq to force the change to stick */
965 }
966 else if (rp->system_flags & FLAG_EXPUNGED) {
967 /* mark expunged - rewrite will cause both sides to agree
968 * again */
969 mp->system_flags |= FLAG_EXPUNGED;
970 }
971
972 /* general case */
973 else {
974 if (!message_guid_equal(&mp->guid, &rp->guid)) {
975 char *mguid = xstrdup(message_guid_encode(&mp->guid));
976 char *rguid = xstrdup(message_guid_encode(&rp->guid));
977 syslog(LOG_ERR, "SYNCERROR: guid mismatch %s %u (%s %s)",
978 mailbox->name, mp->uid, rguid, mguid);
979 free(rguid);
980 free(mguid);
981 /* we will need to renumber both ends to get in sync */
982
983 /* ORDERING - always lower GUID first */
984 if (message_guid_cmp(&mp->guid, &rp->guid) < 0) {
985 r = copyback_one_record(mailbox, rp, kaction);
986 if (!r) r = renumber_one_record(mp, kaction);
987 }
988 else {
989 r = renumber_one_record(mp, kaction);
990 if (!r) r = copyback_one_record(mailbox, rp, kaction);
991 }
992
993 return r;
994 }
995
996 /* is the replica "newer"? */
997 if (rp->modseq > mp->modseq &&
998 rp->last_updated >= mp->last_updated) {
999 /* then copy all the flag data over from the replica */
1000 mp->system_flags = rp->system_flags;
1001 for (i = 0; i < MAX_USER_FLAGS/32; i++)
1002 mp->user_flags[i] = rp->user_flags[i];
1003
1004 log_mismatch("more recent on replica", mailbox, mp, rp);
1005 }
1006 else {
1007 log_mismatch("more recent on master", mailbox, mp, rp);
1008 }
1009 }
1010
1011 /* are we making changes yet? */
1012 if (!kaction) return 0;
1013
1014 /* this will bump the modseq and force a resync either way :) */
1015 r = mailbox_rewrite_index_record(mailbox, mp);
1016 if (r) return r;
1017 }
1018
1019 return 0;
1020 }
1021
1022
1023 static int mailbox_update_loop(struct mailbox *mailbox,
1024 struct dlist *ki,
1025 uint32_t last_uid,
1026 modseq_t highestmodseq,
1027 struct dlist *kaction)
1028 {
1029 struct index_record mrecord;
1030 struct index_record rrecord;
1031 uint32_t recno = 1;
1032 uint32_t old_num_records = mailbox->i.num_records;
1033 int r;
1034
1035 /* while there are more records on either master OR replica,
1036 * work out what to do with them */
1037 while (ki || recno <= old_num_records) {
1038 /* most common case - both a master AND a replica record exist */
1039 if (ki && recno <= old_num_records) {
1040 r = mailbox_read_index_record(mailbox, recno, &mrecord);
1041 if (r) return r;
1042 r = parse_upload(ki, mailbox, &rrecord);
1043 if (r) return r;
1044
1045 /* same UID - compare the records */
1046 if (rrecord.uid == mrecord.uid) {
1047 r = compare_one_record(mailbox,
1048 &mrecord, &rrecord,
1049 kaction);
1050 if (r) return r;
1051 /* increment both */
1052 recno++;
1053 ki = ki->next;
1054 }
1055 else if (rrecord.uid > mrecord.uid) {
1056 /* record only exists on the master */
1057 if (!(mrecord.system_flags & FLAG_EXPUNGED)) {
1058 syslog(LOG_ERR, "SYNCERROR: only exists on master %s %u (%s)",
1059 mailbox->name, mrecord.uid,
1060 message_guid_encode(&mrecord.guid));
1061 r = renumber_one_record(&mrecord, kaction);
1062 if (r) return r;
1063 }
1064 /* only increment master */
1065 recno++;
1066 }
1067 else {
1068 /* record only exists on the replica */
1069 if (!(rrecord.system_flags & FLAG_EXPUNGED)) {
1070 if (kaction)
1071 syslog(LOG_ERR, "SYNCERROR: only exists on replica %s %u (%s)",
1072 mailbox->name, rrecord.uid,
1073 message_guid_encode(&rrecord.guid));
1074 r = copyback_one_record(mailbox, &rrecord, kaction);
1075 if (r) return r;
1076 }
1077 /* only increment replica */
1078 ki = ki->next;
1079 }
1080 }
1081
1082 /* no more replica records, but still master records */
1083 else if (recno <= old_num_records) {
1084 r = mailbox_read_index_record(mailbox, recno, &mrecord);
1085 if (r) return r;
1086 /* if the replica has seen this UID, we need to renumber.
1087 * Otherwise it will replicate fine as-is */
1088 if (mrecord.uid <= last_uid) {
1089 r = renumber_one_record(&mrecord, kaction);
1090 if (r) return r;
1091 }
1092 else if (mrecord.modseq <= highestmodseq) {
1093 if (kaction) {
1094 /* bump our modseq so we sync */
1095 syslog(LOG_NOTICE, "SYNCNOTICE: bumping modseq %s %u",
1096 mailbox->name, mrecord.uid);
1097 r = mailbox_rewrite_index_record(mailbox, &mrecord);
1098 if (r) return r;
1099 }
1100 }
1101 recno++;
1102 }
1103
1104 /* record only exists on the replica */
1105 else {
1106 r = parse_upload(ki, mailbox, &rrecord);
1107 if (r) return r;
1108
1109 if (kaction)
1110 syslog(LOG_NOTICE, "SYNCNOTICE: only on replica %s %u",
1111 mailbox->name, rrecord.uid);
1112
1113 /* going to need this one */
1114 r = copyback_one_record(mailbox, &rrecord, kaction);
1115 if (r) return r;
1116
1117 ki = ki->next;
1118 }
1119 }
1120
1121 return 0;
1122 }
1123
1124 static int mailbox_full_update(const char *mboxname)
1125 {
1126 const char *cmd = "FULLMAILBOX";
1127 struct mailbox *mailbox = NULL;
1128 int r;
1129 struct dlist *kin = NULL;
1130 struct dlist *kr = NULL;
1131 struct dlist *ka = NULL;
1132 struct dlist *kuids = NULL;
1133 struct dlist *kl = NULL;
1134 struct dlist *kaction = NULL;
1135 struct dlist *kexpunge = NULL;
1136 modseq_t highestmodseq;
1137 uint32_t uidvalidity;
1138 uint32_t last_uid;
1139
1140 kl = dlist_atom(NULL, cmd, mboxname);
1141 sync_send_lookup(kl, sync_out);
1142 dlist_free(&kl);
1143
1144 r = sync_parse_response(cmd, sync_in, &kin);
1145 if (r) return r;
1146
1147 kl = kin->head;
1148
1149 if (!kl) {
1150 r = IMAP_MAILBOX_NONEXISTENT;
1151 goto done;
1152 }
1153
1154 /* XXX - handle the header. I want to do some ordering on timestamps
1155 * in particular here - if there's more recent data on the replica then
1156 * it should be copied back. This depends on having a nice way to
1157 * parse the mailbox structure back in to a struct index_header rather
1158 * than the by hand stuff though, because that sucks. NOTE - this
1159 * doesn't really matter too much, because we'll blat the replica's
1160 * values anyway! */
1161
1162 if (!dlist_getmodseq(kl, "HIGHESTMODSEQ", &highestmodseq)) {
1163 r = IMAP_PROTOCOL_BAD_PARAMETERS;
1164 goto done;
1165 }
1166
1167 if (!dlist_getnum(kl, "UIDVALIDITY", &uidvalidity)) {
1168 r = IMAP_PROTOCOL_BAD_PARAMETERS;
1169 goto done;
1170 }
1171
1172 if (!dlist_getnum(kl, "LAST_UID", &last_uid)) {
1173 r = IMAP_PROTOCOL_BAD_PARAMETERS;
1174 goto done;
1175 }
1176
1177 if (!dlist_getlist(kl, "RECORD", &kr)) {
1178 r = IMAP_PROTOCOL_BAD_PARAMETERS;
1179 goto done;
1180 }
1181
1182 /* we'll be updating it! */
1183 r = mailbox_open_iwl(mboxname, &mailbox);
1184 if (r) goto done;
1185
1186 /* re-calculate our local CRC just in case it's out of sync */
1187 r = mailbox_index_recalc(mailbox);
1188 if (r) goto done;
1189
1190 /* if local UIDVALIDITY is lower, copy from remote, otherwise
1191 * remote will copy ours when we sync */
1192 if (mailbox->i.uidvalidity < uidvalidity) {
1193 syslog(LOG_NOTICE, "SYNCNOTICE: uidvalidity higher on replica %s"
1194 ", updating %u => %u",
1195 mailbox->name, mailbox->i.uidvalidity, uidvalidity);
1196 mailbox_index_dirty(mailbox);
1197 mailbox->i.uidvalidity = uidvalidity;
1198 }
1199
1200 if (mailbox->i.highestmodseq < highestmodseq) {
1201 mailbox_modseq_dirty(mailbox);
1202 /* highestmodseq on replica is dirty - we must go to at least
1203 * one higher! */
1204 syslog(LOG_NOTICE, "SYNCNOTICE: highestmodseq higher on replica %s"
1205 ", updating " MODSEQ_FMT " => " MODSEQ_FMT,
1206 mailbox->name, mailbox->i.highestmodseq, highestmodseq+1);
1207 mailbox->i.highestmodseq = highestmodseq+1;
1208 }
1209
1210 r = mailbox_update_loop(mailbox, kr->head, last_uid,
1211 highestmodseq, NULL);
1212 if (r) {
1213 syslog(LOG_ERR, "SYNCNOTICE: failed to prepare update for %s: %s",
1214 mailbox->name, error_message(r));
1215 goto done;
1216 }
1217
1218 /* OK - now we're committed to make changes! */
1219
1220 kaction = dlist_list(NULL, "ACTION");
1221 r = mailbox_update_loop(mailbox, kr->head, last_uid,
1222 highestmodseq, kaction);
1223 if (r) goto cleanup;
1224
1225 /* if replica still has a higher last_uid, bump our local
1226 * number to match so future records don't clash */
1227 if (mailbox->i.last_uid < last_uid)
1228 mailbox->i.last_uid = last_uid;
1229
1230 /* blatant reuse 'r' us */
1231 kexpunge = dlist_new("EXPUNGE");
1232 dlist_atom(kexpunge, "MBOXNAME", mailbox->name);
1233 dlist_atom(kexpunge, "UNIQUEID", mailbox->uniqueid); /* just for safety */
1234 kuids = dlist_list(kexpunge, "UID");
1235 for (ka = kaction->head; ka; ka = ka->next) {
1236 if (!strcmp(ka->name, "EXPUNGE")) {
1237 dlist_num(kuids, "UID", ka->nval);
1238 }
1239 else if (!strcmp(ka->name, "COPYBACK")) {
1240 r = copy_remote(mailbox, ka->nval, kr);
1241 if (r) goto cleanup;
1242 dlist_num(kuids, "UID", ka->nval);
1243 }
1244 else if (!strcmp(ka->name, "RENUMBER")) {
1245 r = copy_local(mailbox, ka->nval);
1246 if (r) goto cleanup;
1247 }
1248 }
1249
1250 /* we still need to do the EXPUNGEs */
1251 cleanup:
1252
1253 /* close the mailbox before sending any expunges
1254 * to avoid deadlocks */
1255 mailbox_close(&mailbox);
1256
1257 /* only send expunge if we have some UIDs to expunge */
1258 if (kuids->head) {
1259 int r2;
1260 sync_send_apply(kexpunge, sync_out);
1261 r2 = sync_parse_response("EXPUNGE", sync_in, NULL);
1262 if (r2) {
1263 syslog(LOG_ERR, "SYNCERROR: failed to expunge in cleanup %s",
1264 mboxname);
1265 }
1266 }
1267
1268 done:
1269 if (mailbox) mailbox_close(&mailbox);
1270 dlist_free(&kin);
1271 dlist_free(&kaction);
1272 dlist_free(&kexpunge);
1273 return r;
1274 }
1275
1276 static int is_unchanged(struct mailbox *mailbox, struct sync_folder *remote)
1277 {
1278 /* look for any mismatches */
1279 unsigned options = mailbox->i.options & MAILBOX_OPTIONS_MASK;
1280 if (!remote) return 0;
1281 if (remote->last_uid != mailbox->i.last_uid) return 0;
1282 if (remote->highestmodseq != mailbox->i.highestmodseq) return 0;
1283 if (remote->sync_crc != mailbox->i.sync_crc) return 0;
1284 if (remote->recentuid != mailbox->i.recentuid) return 0;
1285 if (remote->recenttime != mailbox->i.recenttime) return 0;
1286 if (remote->pop3_last_login != mailbox->i.pop3_last_login) return 0;
1287 if (remote->options != options) return 0;
1288 if (strcmp(remote->acl, mailbox->acl)) return 0;
1289
1290 /* otherwise it's unchanged! */
1291 return 1;
1292 }
1293
1294 static int update_mailbox_once(struct sync_folder *local,
1295 struct sync_folder *remote,
1296 struct sync_reserve_list *reserve_guids,
1297 int is_repeat)
1298 {
1299 struct sync_msgid_list *part_list;
1300 struct mailbox *mailbox = NULL;
1301 int r = 0;
1302 struct dlist *kl = dlist_new("MAILBOX");
1303 struct dlist *kupload = dlist_list(NULL, "MESSAGE");
1304
1305 r = mailbox_open_irl(local->name, &mailbox);
1306 if (r == IMAP_MAILBOX_NONEXISTENT) {
1307 /* been deleted in the meanwhile... */
1308 if (remote)
1309 r = folder_delete(remote->name);
1310 else
1311 r = 0;
1312 goto done;
1313 }
1314 else if (r)
1315 goto done;
1316
1317 /* definitely bad if these don't match! */
1318 if (strcmp(mailbox->uniqueid, local->uniqueid) ||
1319 strcmp(mailbox->part, local->part)) {
1320 r = IMAP_MAILBOX_MOVED;
1321 goto done;
1322 }
1323
1324 /* check that replication stands a chance of succeeding */
1325 if (remote && !is_repeat) {
1326 if (mailbox->i.deletedmodseq > remote->highestmodseq) {
1327 syslog(LOG_NOTICE, "inefficient replication ("
1328 MODSEQ_FMT " > " MODSEQ_FMT ") %s",
1329 mailbox->i.deletedmodseq, remote->highestmodseq,
1330 local->name);
1331 r = IMAP_AGAIN;
1332 goto done;
1333 }
1334 /* need a full sync to fix uidvalidity issues so we get a
1335 * writelocked mailbox */
1336 if (mailbox->i.uidvalidity < remote->uidvalidity) {
1337 r = IMAP_AGAIN;
1338 goto done;
1339 }
1340 }
1341
1342 /* nothing changed - nothing to send */
1343 if (is_unchanged(mailbox, remote))
1344 goto done;
1345
1346 part_list = sync_reserve_partlist(reserve_guids, mailbox->part);
1347 r = sync_mailbox(mailbox, remote, part_list, kl, kupload, 1);
1348 if (r) goto done;
1349
1350 /* upload any messages required */
1351 if (kupload->head) {
1352 /* keep the mailbox locked for shorter time! Unlock the index now
1353 * but don't close it, because we need to guarantee that message
1354 * files don't get deleted until we're finished with them... */
1355 mailbox_unlock_index(mailbox, NULL);
1356 sync_send_apply(kupload, sync_out);
1357 r = sync_parse_response("MESSAGE", sync_in, NULL);
1358 if (!r) {
1359 /* update our list of reserved messages on the replica */
1360 struct dlist *ki;
1361 struct sync_msgid *msgid;
1362 for (ki = kupload->head; ki; ki = ki->next) {
1363 msgid = sync_msgid_lookup(part_list, &ki->gval);
1364 if (!msgid)
1365 msgid = sync_msgid_add(part_list, &ki->gval);
1366 msgid->mark = 1;
1367 part_list->marked++;
1368 }
1369 }
1370 }
1371
1372 /* close before sending the apply - all data is already read */
1373 mailbox_close(&mailbox);
1374
1375 /* update the mailbox */
1376 sync_send_apply(kl, sync_out);
1377 r = sync_parse_response("MAILBOX", sync_in, NULL);
1378
1379 done:
1380 if (mailbox) mailbox_close(&mailbox);
1381 dlist_free(&kupload);
1382 dlist_free(&kl);
1383 return r;
1384 }
1385
1386 static int update_mailbox(struct sync_folder *local,
1387 struct sync_folder *remote,
1388 struct sync_reserve_list *reserve_guids)
1389 {
1390 int r = update_mailbox_once(local, remote, reserve_guids, 0);
1391
1392 if (r == IMAP_AGAIN) {
1393 r = mailbox_full_update(local->name);
1394 if (!r) r = update_mailbox_once(local, remote, reserve_guids, 1);
1395 }
1396 else if (r == IMAP_MAILBOX_CRC) {
1397 syslog(LOG_ERR, "CRC failure on sync for %s, trying full update",
1398 local->name);
1399 r = mailbox_full_update(local->name);
1400 if (!r) r = update_mailbox_once(local, remote, reserve_guids, 1);
1401 }
1402
1403 return r;
1404 }
1405
1406 /* ====================================================================== */
1407
1408
1409 static int update_seen_work(const char *user, const char *uniqueid,
1410 struct seendata *sd)
1411 {
1412 const char *cmd = "SEEN";
1413 struct dlist *kl;
1414
1415 /* Update seen list */
1416 kl = dlist_new(cmd);
1417 dlist_atom(kl, "USERID", user);
1418 dlist_atom(kl, "UNIQUEID", uniqueid);
1419 dlist_date(kl, "LASTREAD", sd->lastread);
1420 dlist_num(kl, "LASTUID", sd->lastuid);
1421 dlist_date(kl, "LASTCHANGE", sd->lastchange);
1422 dlist_atom(kl, "SEENUIDS", sd->seenuids);
1423 sync_send_apply(kl, sync_out);
1424 dlist_free(&kl);
1425
1426 return sync_parse_response(cmd, sync_in, NULL);
1427 }
1428
1429 static int do_seen(char *user, char *uniqueid)
1430 {
1431 int r = 0;
1432 struct seen *seendb = NULL;
1433 struct seendata sd = SEENDATA_INITIALIZER;
1434
1435 if (verbose)
1436 printf("SEEN %s %s\n", user, uniqueid);
1437
1438 if (verbose_logging)
1439 syslog(LOG_INFO, "SEEN %s %s", user, uniqueid);
1440
1441 /* ignore read failures */
1442 r = seen_open(user, SEEN_SILENT, &seendb);
1443 if (r) return 0;
1444
1445 r = seen_read(seendb, uniqueid, &sd);
1446
1447 if (!r) r = update_seen_work(user, uniqueid, &sd);
1448
1449 seen_close(&seendb);
1450 seen_freedata(&sd);
1451
1452 return r;
1453 }
1454
1455 /* ====================================================================== */
1456
1457 static int do_quota(const char *root)
1458 {
1459 int r = 0;
1460 struct quota q;
1461
1462 if (verbose)
1463 printf("SETQUOTA %s\n", root);
1464
1465 if (verbose_logging)
1466 syslog(LOG_INFO, "SETQUOTA: %s", root);
1467
1468 q.root = root;
1469 r = update_quota_work(&q, NULL);
1470
1471 return r;
1472 }
1473
1474 static int getannotation_cb(const char *mailbox __attribute__((unused)),
1475 const char *entry, const char *userid,
1476 struct annotation_data *attrib, void *rock)
1477 {
1478 struct sync_annot_list *l = (struct sync_annot_list *) rock;
1479
1480 sync_annot_list_add(l, entry, userid, attrib->value);
1481
1482 return 0;
1483 }
1484
1485 static int parse_annotation(struct dlist *kin,
1486 struct sync_annot_list *replica_annot)
1487 {
1488 struct dlist *kl;
1489 const char *entry;
1490 const char *userid;
1491 const char *value;
1492
1493 for (kl = kin->head; kl; kl = kl->next) {
1494 if (!dlist_getatom(kl, "ENTRY", &entry))
1495 return IMAP_PROTOCOL_BAD_PARAMETERS;
1496 if (!dlist_getatom(kl, "USERID", &userid))
1497 return IMAP_PROTOCOL_BAD_PARAMETERS;
1498 if (!dlist_getatom(kl, "VALUE", &value))
1499 return IMAP_PROTOCOL_BAD_PARAMETERS;
1500 sync_annot_list_add(replica_annot, entry, userid, value);
1501 }
1502
1503 return 0;
1504 }
1505
1506 static int do_getannotation(char *mboxname,
1507 struct sync_annot_list *replica_annot)
1508 {
1509 const char *cmd = "ANNOTATION";
1510 struct dlist *kl;
1511 struct dlist *kin = NULL;
1512 int r;
1513
1514 /* Update seen list */
1515 kl = dlist_atom(NULL, cmd, mboxname);
1516 sync_send_lookup(kl, sync_out);
1517 dlist_free(&kl);
1518
1519 r = sync_parse_response(cmd, sync_in, &kin);
1520 if (r) return r;
1521
1522 r = parse_annotation(kin, replica_annot);
1523 dlist_free(&kin);
1524
1525 return r;
1526 }
1527
1528 static int do_annotation(char *mboxname)
1529 {
1530 int r;
1531 struct sync_annot_list *replica_annot = sync_annot_list_create();
1532 struct sync_annot_list *master_annot = sync_annot_list_create();
1533 struct sync_annot *ma, *ra;
1534 int n;
1535
1536 r = do_getannotation(mboxname, replica_annot);
1537 if (r) goto bail;
1538
1539 r = annotatemore_findall(mboxname, "*", &getannotation_cb, master_annot, NULL);
1540 if (r) goto bail;
1541
1542 /* both lists are sorted, so we work our way through the lists
1543 top-to-bottom and determine what we need to do based on order */
1544 ma = master_annot->head;
1545 ra = replica_annot->head;
1546 while (ma || ra) {
1547 if (!ra) n = -1; /* add all master annotations */
1548 else if (!ma) n = 1; /* remove all replica annotations */
1549 else if ((n = strcmp(ma->entry, ra->entry)) == 0)
1550 n = strcmp(ma->userid, ra->userid);
1551
1552 if (n > 0) {
1553 /* remove replica annotation */
1554 r = folder_unannotation(mboxname, ra->entry, ra->userid);
1555 if (r) goto bail;
1556 ra = ra->next;
1557 continue;
1558 }
1559
1560 if (n == 0) {
1561 /* already have the annotation, but is the value different? */
1562 if (!strcmp(ra->value, ma->value)) {
1563 ra = ra->next;
1564 ma = ma->next;
1565 continue;
1566 }
1567 ra = ra->next;
1568 }
1569
1570 /* add the current client annotation */
1571 r = folder_setannotation(mboxname, ma->entry, ma->userid, ma->value);
1572 if (r) goto bail;
1573
1574 ma = ma->next;
1575 }
1576
1577 bail:
1578 sync_annot_list_free(&master_annot);
1579 sync_annot_list_free(&replica_annot);
1580 return r;
1581 }
1582
1583 /* ====================================================================== */
1584
1585 int do_folders(struct sync_name_list *mboxname_list,
1586 struct sync_folder_list *replica_folders)
1587 {
1588 int r;
1589 struct sync_folder_list *master_folders;
1590 struct sync_rename_list *rename_folders;
1591 struct sync_reserve_list *reserve_guids;
1592 struct sync_folder *mfolder, *rfolder;
1593
1594 master_folders = sync_folder_list_create();
1595 rename_folders = sync_rename_list_create();
1596 reserve_guids = sync_reserve_list_create(SYNC_MSGID_LIST_HASH_SIZE);
1597
1598 r = reserve_messages(mboxname_list, master_folders,
1599 replica_folders, reserve_guids);
1600 if (r) {
1601 syslog(LOG_ERR, "reserve messages: failed: %s", error_message(r));
1602 goto bail;
1603 }
1604
1605 /* Tag folders on server which still exist on the client. Anything
1606 * on the server which remains untagged can be deleted immediately */
1607 for (mfolder = master_folders->head; mfolder; mfolder = mfolder->next) {
1608 rfolder = sync_folder_lookup(replica_folders, mfolder->uniqueid);
1609 if (!rfolder) continue;
1610 rfolder->mark = 1;
1611
1612 /* does it need a rename? */
1613 if (strcmp(mfolder->name, rfolder->name) || strcmp(mfolder->part, rfolder->part))
1614 sync_rename_list_add(rename_folders, mfolder->uniqueid, rfolder->name,
1615 mfolder->name, mfolder->part);
1616 }
1617
1618 /* Delete folders on server which no longer exist on client */
1619 for (rfolder = replica_folders->head; rfolder; rfolder = rfolder->next) {
1620 if (rfolder->mark) continue;
1621 r = folder_delete(rfolder->name);
1622 if (r) {
1623 syslog(LOG_ERR, "folder_delete(): failed: %s '%s'",
1624 rfolder->name, error_message(r));
1625 goto bail;
1626 }
1627 }
1628
1629 /* Need to rename folders in an order which avoids dependancy conflicts
1630 * following isn't wildly efficient, but rename_folders will typically be
1631 * short and contain few dependancies. Algorithm is to simply pick a
1632 * rename operation which has no dependancy and repeat until done */
1633
1634 while (rename_folders->done < rename_folders->count) {
1635 int rename_success = 0;
1636 struct sync_rename *item, *item2;
1637
1638 for (item = rename_folders->head; item; item = item->next) {
1639 if (item->done) continue;
1640
1641 /* don't skip rename to different partition */
1642 if (strcmp(item->oldname, item->newname)) {
1643 item2 = sync_rename_lookup(rename_folders, item->newname);
1644 if (item2 && !item2->done) continue;
1645 }
1646
1647 /* Found unprocessed item which should rename cleanly */
1648 r = folder_rename(item->oldname, item->newname, item->part);
1649 if (r) {
1650 syslog(LOG_ERR, "do_folders(): failed to rename: %s -> %s ",
1651 item->oldname, item->newname);
1652 goto bail;
1653 }
1654
1655 rename_folders->done++;
1656 item->done = 1;
1657 rename_success = 1;
1658 }
1659
1660 if (!rename_success) {
1661 /* Scanned entire list without a match */
1662 syslog(LOG_ERR,
1663 "do_folders(): failed to order folders correctly");
1664 r = IMAP_AGAIN;
1665 goto bail;
1666 }
1667 }
1668
1669 for (mfolder = master_folders->head; mfolder; mfolder = mfolder->next) {
1670 /* NOTE: rfolder->name may now be wrong, but we're guaranteed that
1671 * it was successfully renamed above, so just use mfolder->name for
1672 * all commands */
1673 rfolder = sync_folder_lookup(replica_folders, mfolder->uniqueid);
1674 r = update_mailbox(mfolder, rfolder, reserve_guids);
1675 if (r) {
1676 syslog(LOG_ERR, "do_folders(): update failed: %s '%s'",
1677 mfolder->name, error_message(r));
1678 goto bail;
1679 }
1680 }
1681
1682 bail:
1683 sync_folder_list_free(&master_folders);
1684 sync_rename_list_free(&rename_folders);
1685 sync_reserve_list_free(&reserve_guids);
1686 return r;
1687 }
1688
1689 static int do_mailboxes(struct sync_name_list *mboxname_list)
1690 {
1691 struct sync_name *mbox;
1692 struct sync_folder_list *replica_folders = sync_folder_list_create();
1693 struct dlist *kl = NULL;
1694 int r;
1695
1696 if (verbose) {
1697 printf("MAILBOXES");
1698 for (mbox = mboxname_list->head; mbox; mbox = mbox->next) {
1699 printf(" %s", mbox->name);
1700 }
1701 printf("\n");
1702 }
1703
1704 if (verbose_logging) {
1705 for (mbox = mboxname_list->head; mbox; mbox = mbox->next)
1706 syslog(LOG_INFO, "MAILBOX %s", mbox->name);
1707 }
1708
1709 kl = dlist_list(NULL, "MAILBOXES");
1710 for (mbox = mboxname_list->head; mbox; mbox = mbox->next)
1711 dlist_atom(kl, "MBOXNAME", mbox->name);
1712 sync_send_lookup(kl, sync_out);
1713 dlist_free(&kl);
1714
1715 r = response_parse("MAILBOXES", replica_folders, NULL, NULL, NULL, NULL);
1716
1717 if (!r)
1718 r = do_folders(mboxname_list, replica_folders);
1719
1720 sync_folder_list_free(&replica_folders);
1721
1722 return r;
1723 }
1724
1725 /* ====================================================================== */
1726
1727 struct mboxinfo {
1728 struct sync_name_list *mboxlist;
1729 struct sync_name_list *quotalist;
1730 };
1731
1732 static int do_mailbox_info(char *name,
1733 int matchlen __attribute__((unused)),
1734 int maycreate __attribute__((unused)),
1735 void *rock)
1736 {
1737 int r;
1738 struct mailbox *mailbox = NULL;
1739 struct mboxinfo *info = (struct mboxinfo *)rock;
1740
1741 r = mailbox_open_irl(name, &mailbox);
1742 /* doesn't exist? Probably not finished creating or removing yet */
1743 if (r == IMAP_MAILBOX_NONEXISTENT) return 0;
1744 if (r == IMAP_MAILBOX_RESERVED) return 0;
1745 if (r) return r;
1746
1747 if (info->quotalist && mailbox->quotaroot) {
1748 if (!sync_name_lookup(info->quotalist, mailbox->quotaroot))
1749 sync_name_list_add(info->quotalist, mailbox->quotaroot);
1750 }
1751
1752 mailbox_close(&mailbox);
1753
1754 addmbox(name, 0, 0, info->mboxlist);
1755
1756 return 0;
1757 }
1758
1759 static int do_user_quota(struct sync_name_list *master_quotaroots,
1760 struct sync_quota_list *replica_quota)
1761 {
1762 int r;
1763 struct sync_name *mitem;
1764 struct sync_quota *rquota;
1765 struct quota q;
1766
1767 /* set any new or changed quotas */
1768 for (mitem = master_quotaroots->head; mitem; mitem = mitem->next) {
1769 rquota = sync_quota_lookup(replica_quota, mitem->name);
1770 q.root = mitem->name;
1771 if (rquota)
1772 rquota->done = 1;
1773 r = update_quota_work(&q, rquota);
1774 if (r) return r;
1775 }
1776
1777 /* delete any quotas no longer on the master */
1778 for (rquota = replica_quota->head; rquota; rquota = rquota->next) {
1779 if (rquota->done) continue;
1780 r = delete_quota(rquota->root);
1781 if (r) return r;
1782 }
1783
1784 return 0;
1785 }
1786
1787 int do_user_main(char *user, struct sync_folder_list *replica_folders,
1788 struct sync_quota_list *replica_quota)
1789 {
1790 char buf[MAX_MAILBOX_BUFFER];
1791 int r = 0;
1792 struct sync_name_list *mboxname_list = sync_name_list_create();
1793 struct sync_name_list *master_quotaroots = sync_name_list_create();
1794 struct mboxinfo info;
1795
1796 info.mboxlist = mboxname_list;
1797 info.quotalist = master_quotaroots;
1798
1799 /* Generate full list of folders on client side */
1800 (sync_namespace.mboxname_tointernal)(&sync_namespace, "INBOX",
1801 user, buf);
1802 do_mailbox_info(buf, 0, 0, &info);
1803
1804 /* deleted namespace items if enabled */
1805 if (mboxlist_delayed_delete_isenabled()) {
1806 char deletedname[MAX_MAILBOX_BUFFER];
1807 mboxname_todeleted(buf, deletedname, 0);
1808 strlcat(deletedname, ".*", sizeof(deletedname));
1809 r = (sync_namespace.mboxlist_findall)(&sync_namespace, deletedname, 1,
1810 user, NULL, do_mailbox_info,
1811 &info);
1812 }
1813
1814 /* subfolders */
1815 if (!r) {
1816 strlcat(buf, ".*", sizeof(buf));
1817 r = (sync_namespace.mboxlist_findall)(&sync_namespace, buf, 1,
1818 user, NULL, do_mailbox_info,
1819 &info);
1820 }
1821
1822 if (!r) r = do_folders(mboxname_list, replica_folders);
1823 if (!r) r = do_user_quota(master_quotaroots, replica_quota);
1824
1825 sync_name_list_free(&mboxname_list);
1826 sync_name_list_free(&master_quotaroots);
1827
1828 if (r) syslog(LOG_ERR, "IOERROR: %s", error_message(r));
1829
1830 return r;
1831 }
1832
1833 int do_user_sub(const char *userid, struct sync_name_list *replica_subs)
1834 {
1835 struct sync_name_list *master_subs = sync_name_list_create();
1836 struct sync_name *msubs, *rsubs;
1837 int r = 0;
1838
1839 /* Includes subsiduary nodes automatically */
1840 r = mboxlist_allsubs(userid, addmbox_sub, master_subs);
1841 if (r) goto bail;
1842
1843 /* add any folders that need adding, and mark any which
1844 * still exist */
1845 for (msubs = master_subs->head; msubs; msubs = msubs->next) {
1846 rsubs = sync_name_lookup(replica_subs, msubs->name);
1847 if (rsubs) {
1848 rsubs->mark = 1;
1849 continue;
1850 }
1851 r = set_sub(userid, msubs->name, 1);
1852 if (r) goto bail;
1853 }
1854
1855 /* remove any no-longer-subscribed folders */
1856 for (rsubs = replica_subs->head; rsubs; rsubs = rsubs->next) {
1857 if (rsubs->mark)
1858 continue;
1859 r = set_sub(userid, rsubs->name, 0);
1860 if (r) goto bail;
1861 }
1862
1863 bail:
1864 sync_name_list_free(&master_subs);
1865 return r;
1866 }
1867
1868 static int get_seen(const char *uniqueid, struct seendata *sd, void *rock)
1869 {
1870 struct sync_seen_list *list = (struct sync_seen_list *)rock;
1871
1872 sync_seen_list_add(list, uniqueid, sd->lastread, sd->lastuid,
1873 sd->lastchange, sd->seenuids);
1874
1875 return 0;
1876 }
1877
1878 static int do_user_seen(char *user, struct sync_seen_list *replica_seen)
1879 {
1880 int r;
1881 struct sync_seen *mseen, *rseen;
1882 struct seen *seendb = NULL;
1883 struct sync_seen_list *list;
1884
1885 /* silently ignore errors */
1886 r = seen_open(user, SEEN_SILENT, &seendb);
1887 if (r) return 0;
1888
1889 list = sync_seen_list_create();
1890
1891 seen_foreach(seendb, get_seen, list);
1892 seen_close(&seendb);
1893
1894 for (mseen = list->head; mseen; mseen = mseen->next) {
1895 rseen = sync_seen_list_lookup(replica_seen, mseen->uniqueid);
1896 if (rseen) {
1897 rseen->mark = 1;
1898 if (seen_compare(&rseen->sd, &mseen->sd))
1899 continue; /* nothing changed */
1900 }
1901 r = update_seen_work(user, mseen->uniqueid, &mseen->sd);
1902 }
1903
1904 /* XXX - delete seen on the replica for records that don't exist? */
1905
1906 sync_seen_list_free(&list);
1907
1908 return 0;
1909 }
1910
1911 int do_user_sieve(char *userid, struct sync_sieve_list *replica_sieve)
1912 {
1913 int r = 0;
1914 struct sync_sieve_list *master_sieve;
1915 struct sync_sieve *mitem, *ritem;
1916 int master_active = 0;
1917 int replica_active = 0;
1918
1919 master_sieve = sync_sieve_list_generate(userid);
1920 if (!master_sieve) {
1921 syslog(LOG_ERR, "Unable to list sieve scripts for %s", userid);
1922 return IMAP_IOERROR;
1923 }
1924
1925 /* Upload missing and out of date scripts */
1926 for (mitem = master_sieve->head; mitem; mitem = mitem->next) {
1927 ritem = sync_sieve_lookup(replica_sieve, mitem->name);
1928 if (ritem) {
1929 ritem->mark = 1;
1930 if (ritem->last_update >= mitem->last_update)
1931 continue; /* doesn't need updating */
1932 }
1933 r = sieve_upload(userid, mitem->name, mitem->last_update);
1934 if (r) goto bail;
1935 }
1936
1937 /* Delete scripts which no longer exist on the master */
1938 replica_active = 0;
1939 for (ritem = replica_sieve->head; ritem; ritem = ritem->next) {
1940 if (ritem->mark) {
1941 if (ritem->active)
1942 replica_active = 1;
1943 } else {
1944 r = sieve_delete(userid, ritem->name);
1945 if (r) goto bail;
1946 }
1947 }
1948
1949 /* Change active script if necessary */
1950 master_active = 0;
1951 for (mitem = master_sieve->head; mitem; mitem = mitem->next) {
1952 if (!mitem->active)
1953 continue;
1954
1955 master_active = 1;
1956 ritem = sync_sieve_lookup(replica_sieve, mitem->name);
1957 if (ritem && ritem->active)
1958 break;
1959
1960 r = sieve_activate(userid, mitem->name);
1961 if (r) goto bail;
1962
1963 replica_active = 1;
1964 break;
1965 }
1966
1967 if (!master_active && replica_active)
1968 r = sieve_deactivate(userid);
1969
1970 bail:
1971 sync_sieve_list_free(&master_sieve);
1972 return(r);
1973 }
1974
1975 int do_user(char *userid)
1976 {
1977 char buf[MAX_MAILBOX_BUFFER];
1978 int r = 0;
1979 struct sync_folder_list *replica_folders = sync_folder_list_create();
1980 struct sync_name_list *replica_subs = sync_name_list_create();
1981 struct sync_sieve_list *replica_sieve = sync_sieve_list_create();
1982 struct sync_seen_list *replica_seen = sync_seen_list_create();
1983 struct sync_quota_list *replica_quota = sync_quota_list_create();
1984 struct dlist *kl = NULL;
1985 struct mailbox *mailbox = NULL;
1986
1987 if (verbose)
1988 printf("USER %s\n", userid);
1989
1990 if (verbose_logging)
1991 syslog(LOG_INFO, "USER %s", userid);
1992
1993 kl = dlist_atom(NULL, "USER", userid);
1994 sync_send_lookup(kl, sync_out);
1995 dlist_free(&kl);
1996
1997 r = response_parse("USER",
1998 replica_folders, replica_subs,
1999 replica_sieve, replica_seen, replica_quota);
2000 /* can happen! */
2001 if (r == IMAP_MAILBOX_NONEXISTENT) r = 0;
2002 if (r) goto done;
2003
2004 (sync_namespace.mboxname_tointernal)(&sync_namespace, "INBOX",
2005 userid, buf);
2006 r = mailbox_open_irl(buf, &mailbox);
2007 if (r == IMAP_MAILBOX_NONEXISTENT) {
2008 /* user has been removed, RESET server */
2009 syslog(LOG_ERR, "Inbox missing on master for %s", userid);
2010 r = user_reset(userid);
2011 goto done;
2012 }
2013 if (r) goto done;
2014
2015 /* we don't hold locks while sending commands */
2016 mailbox_close(&mailbox);
2017 r = do_user_main(userid, replica_folders, replica_quota);
2018 if (r) goto done;
2019 r = do_user_sub(userid, replica_subs);
2020 if (r) goto done;
2021 r = do_user_sieve(userid, replica_sieve);
2022 if (r) goto done;
2023 r = do_user_seen(userid, replica_seen);
2024
2025 done:
2026 sync_folder_list_free(&replica_folders);
2027 sync_name_list_free(&replica_subs);
2028 sync_sieve_list_free(&replica_sieve);
2029 sync_seen_list_free(&replica_seen);
2030 sync_quota_list_free(&replica_quota);
2031
2032 return r;
2033 }
2034
2035 /* ====================================================================== */
2036
2037 static int do_meta(char *userid)
2038 {
2039 struct sync_name_list *replica_subs = sync_name_list_create();
2040 struct sync_sieve_list *replica_sieve = sync_sieve_list_create();
2041 struct sync_seen_list *replica_seen = sync_seen_list_create();
2042 struct dlist *kl = NULL;
2043 int r = 0;
2044
2045 if (verbose)
2046 printf("META %s\n", userid);
2047
2048 if (verbose_logging)
2049 syslog(LOG_INFO, "META %s", userid);
2050
2051 kl = dlist_atom(NULL, "META", userid);
2052 sync_send_lookup(kl, sync_out);
2053 dlist_free(&kl);
2054
2055 r = response_parse("META", NULL, replica_subs, replica_sieve, replica_seen, NULL);
2056 if (!r) r = do_user_seen(userid, replica_seen);
2057 if (!r) r = do_user_sub(userid, replica_subs);
2058 if (!r) r = do_user_sieve(userid, replica_sieve);
2059 sync_seen_list_free(&replica_seen);
2060 sync_name_list_free(&replica_subs);
2061 sync_sieve_list_free(&replica_sieve);
2062
2063 return r;
2064226 }
2065227
2066228 /* ====================================================================== */
2232394 if (!action->active)
2233395 continue;
2234396
2235 if (do_quota(action->name)) {
397 if (sync_do_quota(action->name, sync_backend, flags)) {
2236398 /* XXX - bogus handling, should be user */
2237399 sync_action_list_add(mailbox_list, action->name, NULL);
2238400 if (verbose) {
2253415 /* NOTE: ANNOTATION "" is a special case - it's a server
2254416 * annotation, hence the check for a character at the
2255417 * start of the name */
2256 if (do_annotation(action->name) && *action->name) {
418 if (sync_do_annotation(action->name, sync_backend,
419 flags) && *action->name) {
2257420 /* XXX - bogus handling, should be ... er, something */
2258421 sync_action_list_add(mailbox_list, action->name, NULL);
2259422 if (verbose) {
2271434 if (!action->active)
2272435 continue;
2273436
2274 if (do_seen(action->user, action->name)) {
437 if (sync_do_seen(action->user, action->name, sync_backend, flags)) {
2275438 char *userid = mboxname_isusermailbox(action->name, 1);
2276439 if (userid && !strcmp(userid, action->user)) {
2277440 sync_action_list_add(user_list, NULL, action->user);
2323486
2324487 if (mboxname_list->count) {
2325488 int nonuser = 0;
2326 r = do_mailboxes(mboxname_list);
489 r = sync_do_mailboxes(mboxname_list, NULL, sync_backend, flags);
2327490 if (r) {
2328491 /* promote failed personal mailboxes to USER */
2329492 struct sync_name *mbox;
2363526 if (!action->active)
2364527 continue;
2365528
2366 r = do_meta(action->user);
529 r = sync_do_meta(action->user, sync_backend, flags);
2367530 if (r) {
2368531 if (r == IMAP_INVALID_USER) goto cleanup;
2369532
2380543 }
2381544
2382545 for (action = user_list->head; action; action = action->next) {
2383 r = do_user(action->user);
546 r = sync_do_user(action->user, NULL, sync_backend, flags);
2384547 if (r) goto cleanup;
2385548 }
2386549
2424587 time_t single_start;
2425588 int delta;
2426589 struct stat sbuf;
590 int restartcnt = 0;
2427591
2428592 *restartp = RESTART_NONE;
2429593
2501665 free(work_file_name);
2502666
2503667 if (*restartp == RESTART_NORMAL) {
668 if (sync_out->userdata) {
669 /* IMAP flavor (w/ tag) */
670 prot_printf(sync_out, "R%d SYNC", restartcnt++);
671 }
2504672 prot_printf(sync_out, "RESTART\r\n");
2505673 prot_flush(sync_out);
2506674
2574742 int wait;
2575743 struct protoent *proto;
2576744 sasl_callback_t *cb;
745 const char *port, *auth_status = NULL;
2577746
2578747 cb = mysasl_callbacks(NULL,
2579748 get_config(channel, "sync_authname"),
2581750 get_config(channel, "sync_password"));
2582751
2583752 /* get the right port */
2584 csync_protocol.service = get_config(channel, "sync_port");
753 port = get_config(channel, "sync_port");
754 if (port) imap_csync_protocol.service = port;
2585755
2586756 for (wait = 15;; wait *= 2) {
2587757 sync_backend = backend_connect(sync_backend, servername,
2588 &csync_protocol, "", cb, NULL);
2589
2590 if (sync_backend || connect_once || wait > 1000) break;
758 &imap_csync_protocol, "", cb,
759 &auth_status);
760
761 if (sync_backend || auth_status || connect_once || wait > 1000) break;
2591762
2592763 fprintf(stderr,
2593764 "Can not connect to server '%s', retrying in %d seconds\n",
2594765 servername, wait);
2595766 sleep(wait);
767 }
768
769 if (sync_backend) {
770 if (sync_backend->capability & CAPA_REPLICATION) {
771 /* attach our IMAP tag buffer to our protstreams as userdata */
772 sync_backend->in->userdata = sync_backend->out->userdata = &tagbuf;
773 }
774 else {
775 backend_disconnect(sync_backend);
776 sync_backend = NULL;
777 }
778 }
779
780 if (!sync_backend) {
781 if (port) csync_protocol.service = port;
782
783 for (wait = 15;; wait *= 2) {
784 sync_backend = backend_connect(sync_backend, servername,
785 &csync_protocol, "", cb, NULL);
786
787 if (sync_backend || connect_once || wait > 1000) break;
788
789 fprintf(stderr,
790 "Can not connect to server '%s', retrying in %d seconds\n",
791 servername, wait);
792 sleep(wait);
793 }
2596794 }
2597795
2598796 if (!sync_backend) {
2662860 #ifdef HAVE_ZLIB
2663861 /* check if we should compress */
2664862 if (do_compress || config_getswitch(IMAPOPT_SYNC_COMPRESS)) {
2665 prot_printf(sync_backend->out, "COMPRESS DEFLATE\r\n");
863 prot_printf(sync_backend->out, "%s\r\n",
864 sync_backend->prot->u.std.compress_cmd.cmd);
2666865 prot_flush(sync_backend->out);
2667866
2668867 if (sync_parse_response("COMPRESS", sync_backend->in, NULL)) {
2743942 const char *sync_log_file;
2744943 const char *channel = NULL;
2745944 const char *sync_shutdown_file = NULL;
945 const char *partition = NULL;
2746946 char buf[512];
2747947 FILE *file;
2748948 int len;
2756956
2757957 setbuf(stdout, NULL);
2758958
2759 while ((opt = getopt(argc, argv, "C:vlS:F:f:w:t:d:n:rRumsoz")) != EOF) {
959 while ((opt = getopt(argc, argv, "C:vlLS:F:f:w:t:d:n:rRumsozp:")) != EOF) {
2760960 switch (opt) {
2761961 case 'C': /* alt config file */
2762962 alt_config = optarg;
2773973 case 'l': /* verbose Logging */
2774974 verbose_logging++;
2775975 break;
976
977 case 'L': /* local mailbox operations only */
978 flags |= SYNC_FLAG_LOCALONLY;
979 break;
2776980
2777981 case 'S': /* Socket descriptor for server */
2778982 servername = optarg;
28391043 #endif
28401044 break;
28411045
1046 case 'p':
1047 partition = optarg;
1048 break;
1049
28421050 default:
28431051 usage("sync_client");
28441052 }
28461054
28471055 if (mode == MODE_UNKNOWN)
28481056 fatal("No replication mode specified", EC_USAGE);
1057
1058 if (verbose) flags |= SYNC_FLAG_VERBOSE;
1059 if (verbose_logging) flags |= SYNC_FLAG_LOGGING;
28491060
28501061 /* fork if required */
28511062 if (background && !input_filename) {
29211132 mboxname_hiersep_tointernal(&sync_namespace, buf,
29221133 config_virtdomains ?
29231134 strcspn(buf, "@") : 0);
2924 if (do_user(buf)) {
1135 if (sync_do_user(buf, partition, sync_backend, flags)) {
29251136 if (verbose)
29261137 fprintf(stderr,
29271138 "Error from do_user(%s): bailing out!\n",
29361147 mboxname_hiersep_tointernal(&sync_namespace, argv[i],
29371148 config_virtdomains ?
29381149 strcspn(argv[i], "@") : 0);
2939 if (do_user(argv[i])) {
1150 if (sync_do_user(argv[i], partition, sync_backend, flags)) {
29401151 if (verbose)
29411152 fprintf(stderr, "Error from do_user(%s): bailing out!\n",
29421153 argv[i]);
29791190 sync_name_list_add(mboxname_list, mailboxname);
29801191 }
29811192
2982 if (do_mailboxes(mboxname_list)) {
1193 if (sync_do_mailboxes(mboxname_list, partition, sync_backend, flags)) {
29831194 if (verbose) {
29841195 fprintf(stderr,
29851196 "Error from do_mailboxes(): bailing out!\n");
30001211 mboxname_hiersep_tointernal(&sync_namespace, argv[i],
30011212 config_virtdomains ?
30021213 strcspn(argv[i], "@") : 0);
3003 if (do_meta(argv[i])) {
1214 if (sync_do_meta(argv[i], sync_backend, flags)) {
30041215 if (verbose) {
30051216 fprintf(stderr,
30061217 "Error from do_meta(%s): bailing out!\n",
30451256 break;
30461257 }
30471258
1259 buf_free(&tagbuf);
1260
30481261 shut_down(exit_rc);
30491262 }
9696 static char *sync_userid = NULL;
9797
9898 static int verbose = 0;
99 static int local_only = 0;
99100
100101 static void shut_down(int code) __attribute__((noreturn));
101102 static void shut_down(int code)
167168
168169 for (item = list->head; item; item = item->next) {
169170 r = mboxlist_deletemailbox(item->name, 1, sync_userid,
170 sync_authstate, 0, 0, 1);
171 sync_authstate, 0, local_only, 1);
171172 if (r) goto fail;
172173 }
173174
175176 (sync_namespacep->mboxname_tointernal)(sync_namespacep, "INBOX",
176177 userid, buf);
177178 r = mboxlist_deletemailbox(buf, 1, sync_userid,
178 sync_authstate, 0, 0, 1);
179 sync_authstate, 0, local_only, 1);
179180 if (r && (r != IMAP_MAILBOX_NONEXISTENT)) goto fail;
180181
181182 r = user_deletedata((char *)userid, sync_userid, sync_authstate, 1);
203204
204205 setbuf(stdout, NULL);
205206
206 while ((opt = getopt(argc, argv, "C:vf")) != EOF) {
207 while ((opt = getopt(argc, argv, "C:vfL")) != EOF) {
207208 switch (opt) {
208209 case 'C': /* alt config file */
209210 alt_config = optarg;
216217 case 'f': /* force: confirm option */
217218 force++;
218219 break;
220
221 case 'L': /* local mailbox operations only */
222 local_only++;
223 break;
219224
220225 default:
221226 usage("sync_reset");
+0
-2210
imap/sync_server.c less more
0 /* sync_server.c -- Cyrus synchonization server
1 *
2 * Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
14 * distribution.
15 *
16 * 3. The name "Carnegie Mellon University" must not be used to
17 * endorse or promote products derived from this software without
18 * prior written permission. For permission or any legal
19 * details, please contact
20 * Carnegie Mellon University
21 * Center for Technology Transfer and Enterprise Creation
22 * 4615 Forbes Avenue
23 * Suite 302
24 * Pittsburgh, PA 15213
25 * (412) 268-7393, fax: (412) 268-7395
26 * innovation@andrew.cmu.edu
27 *
28 * 4. Redistributions of any form whatsoever must retain the following
29 * acknowledgment:
30 * "This product includes software developed by Computing Services
31 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
32 *
33 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
34 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
35 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
36 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
37 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
38 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
39 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
40 *
41 * $Id: sync_server.c,v 1.34 2010/01/06 17:01:41 murch Exp $
42 *
43 * Original version written by David Carter <dpc22@cam.ac.uk>
44 * Rewritten and integrated into Cyrus by Ken Murchison <ken@oceana.com>
45 */
46
47 #include <config.h>
48
49 #ifdef HAVE_STDINT_H
50 #include <stdint.h>
51 #endif
52 #ifdef HAVE_UNISTD_H
53 #include <unistd.h>
54 #endif
55 #include <stdio.h>
56 #include <errno.h>
57 #include <string.h>
58 #include <fcntl.h>
59 #include <signal.h>
60 #include <sys/types.h>
61 #include <sys/wait.h>
62 #include <sys/param.h>
63 #include <syslog.h>
64 #include <netdb.h>
65 #include <sys/socket.h>
66 #include <sys/un.h>
67 #include <netinet/in.h>
68 #include <netinet/tcp.h>
69 #include <arpa/inet.h>
70 #include <ctype.h>
71
72 #include <sasl/sasl.h>
73 #include <sasl/saslutil.h>
74
75 #include "assert.h"
76 #include "acl.h"
77 #include "annotate.h"
78 #include "append.h"
79 #include "auth.h"
80 #include "dlist.h"
81 #include "duplicate.h"
82 #include "exitcodes.h"
83 #include "global.h"
84 #include "hash.h"
85 #include "imap_err.h"
86 #include "imparse.h"
87 #include "iptostring.h"
88 #include "mailbox.h"
89 #include "map.h"
90 #include "mboxlist.h"
91 #include "proc.h"
92 #include "prot.h"
93 #include "quota.h"
94 #include "retry.h"
95 #include "seen.h"
96 #include "spool.h"
97 #include "statuscache.h"
98 #include "sync_log.h"
99 #include "telemetry.h"
100 #include "tls.h"
101 #include "user.h"
102 #include "util.h"
103 #include "version.h"
104 #include "xmalloc.h"
105 #include "xstrlcat.h"
106 #include "xstrlcpy.h"
107
108 #include "message_guid.h"
109 #include "sync_support.h"
110 /*#include "cdb.h"*/
111
112 extern int optind;
113 extern char *optarg;
114 extern int opterr;
115
116 /* for config.c */
117 const int config_need_data = CONFIG_NEED_PARTITION_DATA;
118
119 static sasl_ssf_t extprops_ssf = 0;
120
121 #ifdef HAVE_SSL
122 static SSL *tls_conn;
123 #endif /* HAVE_SSL */
124
125 sasl_conn_t *sync_saslconn = NULL; /* the sasl connection context */
126
127 char *sync_userid = 0;
128 struct namespace sync_namespace;
129 struct namespace *sync_namespacep = &sync_namespace;
130 struct auth_state *sync_authstate = 0;
131 int sync_userisadmin = 0;
132 struct sockaddr_storage sync_localaddr, sync_remoteaddr;
133 int sync_haveaddr = 0;
134 char sync_clienthost[NI_MAXHOST*2+1] = "[local]";
135 struct protstream *sync_out = NULL;
136 struct protstream *sync_in = NULL;
137 static int sync_logfd = -1;
138 static int sync_starttls_done = 0;
139 static int sync_compress_done = 0;
140
141 /* commands that have specific names */
142 static void cmdloop(void);
143 static void cmd_authenticate(char *mech, char *resp);
144 static void cmd_starttls(void);
145 static void cmd_restart(struct sync_reserve_list **reserve_listp,
146 int realloc);
147 static void cmd_compress(char *alg);
148
149 /* generic commands - in dlist format */
150 static void cmd_get(struct dlist *kl);
151 static void cmd_apply(struct dlist *kl,
152 struct sync_reserve_list *reserve_list);
153
154 void usage(void);
155 void shut_down(int code) __attribute__ ((noreturn));
156
157 extern int saslserver(sasl_conn_t *conn, const char *mech,
158 const char *init_resp, const char *resp_prefix,
159 const char *continuation, const char *empty_resp,
160 struct protstream *pin, struct protstream *pout,
161 int *sasl_result, char **success_data);
162
163 static struct {
164 char *ipremoteport;
165 char *iplocalport;
166 sasl_ssf_t ssf;
167 char *authid;
168 } saslprops = {NULL,NULL,0,NULL};
169
170 /* the sasl proxy policy context */
171 static struct proxy_context sync_proxyctx = {
172 0, 1, &sync_authstate, &sync_userisadmin, NULL
173 };
174
175 static struct sasl_callback mysasl_cb[] = {
176 { SASL_CB_GETOPT, (mysasl_cb_ft *) &mysasl_config, NULL },
177 { SASL_CB_PROXY_POLICY, (mysasl_cb_ft *) &mysasl_proxy_policy, (void*) &sync_proxyctx },
178 { SASL_CB_CANON_USER, (mysasl_cb_ft *) &mysasl_canon_user, NULL },
179 { SASL_CB_LIST_END, NULL, NULL }
180 };
181
182 static void sync_reset(void)
183 {
184 proc_cleanup();
185
186 if (sync_in) {
187 prot_NONBLOCK(sync_in);
188 prot_fill(sync_in);
189
190 prot_free(sync_in);
191 }
192
193 if (sync_out) {
194 prot_flush(sync_out);
195 prot_free(sync_out);
196 }
197
198 sync_in = sync_out = NULL;
199
200 #ifdef HAVE_SSL
201 if (tls_conn) {
202 tls_reset_servertls(&tls_conn);
203 tls_conn = NULL;
204 }
205 #endif
206
207 cyrus_reset_stdio();
208
209 strcpy(sync_clienthost, "[local]");
210 if (sync_logfd != -1) {
211 close(sync_logfd);
212 sync_logfd = -1;
213 }
214 if (sync_userid != NULL) {
215 free(sync_userid);
216 sync_userid = NULL;
217 }
218 if (sync_authstate) {
219 auth_freestate(sync_authstate);
220 sync_authstate = NULL;
221 }
222 if (sync_saslconn) {
223 sasl_dispose(&sync_saslconn);
224 sync_saslconn = NULL;
225 }
226 sync_starttls_done = 0;
227
228 if(saslprops.iplocalport) {
229 free(saslprops.iplocalport);
230 saslprops.iplocalport = NULL;
231 }
232 if(saslprops.ipremoteport) {
233 free(saslprops.ipremoteport);
234 saslprops.ipremoteport = NULL;
235 }
236 if(saslprops.authid) {
237 free(saslprops.authid);
238 saslprops.authid = NULL;
239 }
240 saslprops.ssf = 0;
241 }
242
243 /*
244 * run once when process is forked;
245 * MUST NOT exit directly; must return with non-zero error code
246 */
247 int service_init(int argc __attribute__((unused)),
248 char **argv __attribute__((unused)),
249 char **envp __attribute__((unused)))
250 {
251 int opt, r;
252
253 if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
254 setproctitle_init(argc, argv, envp);
255
256 /* set signal handlers */
257 signals_set_shutdown(&shut_down);
258 signal(SIGPIPE, SIG_IGN);
259
260 /* load the SASL plugins */
261 global_sasl_init(1, 1, mysasl_cb);
262
263 while ((opt = getopt(argc, argv, "p:")) != EOF) {
264 switch(opt) {
265 case 'p': /* external protection */
266 extprops_ssf = atoi(optarg);
267 break;
268 default:
269 usage();
270 }
271 }
272
273 /* Set namespace -- force standard (internal) */
274 if ((r = mboxname_init_namespace(sync_namespacep, 1)) != 0) {
275 fatal(error_message(r), EC_CONFIG);
276 }
277
278 /* open the mboxlist, we'll need it for real work */
279 mboxlist_init(0);
280 mboxlist_open(NULL);
281
282 /* open the quota db, we'll need it for real work */
283 quotadb_init(0);
284 quotadb_open(NULL);
285
286 /* Initialize the annotatemore extention */
287 annotatemore_init(0, NULL, NULL);
288 annotatemore_open(NULL);
289
290 /* Open the statuscache so we can invalidate seen states */
291 if (config_getswitch(IMAPOPT_STATUSCACHE)) {
292 statuscache_open(NULL);
293 }
294
295 return 0;
296 }
297
298 /*
299 * Issue the capability banner
300 */
301 static void dobanner(void)
302 {
303 const char *mechlist;
304 int mechcount;
305
306 if (!sync_userid) {
307 if (sasl_listmech(sync_saslconn, NULL,
308 "* SASL ", " ", "\r\n",
309 &mechlist, NULL, &mechcount) == SASL_OK
310 && mechcount > 0) {
311 prot_printf(sync_out, "%s", mechlist);
312 }
313
314 if (tls_enabled() && !sync_starttls_done) {
315 prot_printf(sync_out, "* STARTTLS\r\n");
316 }
317
318 #ifdef HAVE_ZLIB
319 if (!sync_compress_done && !sync_starttls_done) {
320 prot_printf(sync_out, "* COMPRESS DEFLATE\r\n");
321 }
322 #endif
323 }
324
325 prot_printf(sync_out,
326 "* OK %s Cyrus sync server %s\r\n",
327 config_servername, cyrus_version());
328
329 prot_flush(sync_out);
330 }
331
332 /*
333 * run for each accepted connection
334 */
335 int service_main(int argc __attribute__((unused)),
336 char **argv __attribute__((unused)),
337 char **envp __attribute__((unused)))
338 {
339 struct protoent *proto;
340 socklen_t salen;
341 char localip[60], remoteip[60];
342 char hbuf[NI_MAXHOST];
343 int niflags;
344 sasl_security_properties_t *secprops = NULL;
345
346 signals_poll();
347
348 sync_in = prot_new(0, 0);
349 sync_out = prot_new(1, 1);
350
351 /* Force use of LITERAL+ so we don't need two way communications */
352 prot_setisclient(sync_in, 1);
353 prot_setisclient(sync_out, 1);
354
355 /* Find out name of client host */
356 salen = sizeof(sync_remoteaddr);
357 if (getpeername(0, (struct sockaddr *)&sync_remoteaddr, &salen) == 0 &&
358 (sync_remoteaddr.ss_family == AF_INET ||
359 sync_remoteaddr.ss_family == AF_INET6)) {
360 if (getnameinfo((struct sockaddr *)&sync_remoteaddr, salen,
361 hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD) == 0) {
362 strncpy(sync_clienthost, hbuf, sizeof(hbuf));
363 strlcat(sync_clienthost, " ", sizeof(sync_clienthost));
364 sync_clienthost[sizeof(sync_clienthost)-30] = '\0';
365 } else {
366 sync_clienthost[0] = '\0';
367 }
368 niflags = NI_NUMERICHOST;
369 #ifdef NI_WITHSCOPEID
370 if (((struct sockaddr *)&sync_remoteaddr)->sa_family == AF_INET6)
371 niflags |= NI_WITHSCOPEID;
372 #endif
373 if (getnameinfo((struct sockaddr *)&sync_remoteaddr, salen, hbuf,
374 sizeof(hbuf), NULL, 0, niflags) != 0)
375 strlcpy(hbuf, "unknown", sizeof(hbuf));
376 strlcat(sync_clienthost, "[", sizeof(sync_clienthost));
377 strlcat(sync_clienthost, hbuf, sizeof(sync_clienthost));
378 strlcat(sync_clienthost, "]", sizeof(sync_clienthost));
379 salen = sizeof(sync_localaddr);
380 if (getsockname(0, (struct sockaddr *)&sync_localaddr, &salen) == 0) {
381 sync_haveaddr = 1;
382 }
383
384 /* other params should be filled in */
385 if (sasl_server_new("csync", config_servername, NULL, NULL, NULL,
386 NULL, 0, &sync_saslconn) != SASL_OK)
387 fatal("SASL failed initializing: sasl_server_new()",EC_TEMPFAIL);
388
389 /* will always return something valid */
390 secprops = mysasl_secprops(SASL_SEC_NOANONYMOUS);
391 if (sasl_setprop(sync_saslconn, SASL_SEC_PROPS, secprops) != SASL_OK)
392 fatal("Failed to set SASL property", EC_TEMPFAIL);
393
394 if (sasl_setprop(sync_saslconn, SASL_SSF_EXTERNAL, &extprops_ssf) != SASL_OK)
395 fatal("Failed to set SASL property", EC_TEMPFAIL);
396
397 if(iptostring((struct sockaddr *)&sync_localaddr, salen,
398 localip, 60) == 0) {
399 sasl_setprop(sync_saslconn, SASL_IPLOCALPORT, localip);
400 saslprops.iplocalport = xstrdup(localip);
401 }
402
403 if(iptostring((struct sockaddr *)&sync_remoteaddr, salen,
404 remoteip, 60) == 0) {
405 if (sasl_setprop(sync_saslconn, SASL_IPREMOTEPORT, remoteip) != SASL_OK)
406 fatal("failed to set sasl property", EC_TEMPFAIL);
407 saslprops.ipremoteport = xstrdup(remoteip);
408 }
409
410 /* Disable Nagle's Algorithm => increase throughput
411 *
412 * http://en.wikipedia.org/wiki/Nagle's_algorithm
413 */
414 if ((proto = getprotobyname("tcp")) != NULL) {
415 int on = 1;
416
417 if (setsockopt(1, proto->p_proto, TCP_NODELAY,
418 (void *) &on, sizeof(on)) != 0) {
419 syslog(LOG_ERR, "unable to setsocketopt(TCP_NODELAY): %m");
420 }
421 } else {
422 syslog(LOG_ERR, "unable to getprotobyname(\"tcp\"): %m");
423 }
424 } else {
425 /* we're not connected to an internet socket! */
426 strcpy(sync_clienthost, "[unix socket]");
427 sync_userid = xstrdup("cyrus");
428 sync_userisadmin = 1;
429 }
430
431 proc_register("sync_server", sync_clienthost, NULL, NULL);
432 #if 0
433 /* Set inactivity timer */
434 timeout = config_getint(IMAPOPT_TIMEOUT);
435 if (timeout < 3) timeout = 3;
436 prot_settimeout(sync_in, timeout*60);
437 #endif
438 prot_setflushonread(sync_in, sync_out);
439
440 sync_log_init();
441 if (!config_getswitch(IMAPOPT_SYNC_LOG_CHAIN))
442 sync_log_suppress();
443
444 dobanner();
445
446 cmdloop();
447
448 /* EXIT executed */
449
450 /* cleanup */
451 sync_reset();
452
453 return 0;
454 }
455
456 /* Called by service API to shut down the service */
457 void service_abort(int error)
458 {
459 shut_down(error);
460 }
461
462 void usage(void)
463 {
464 prot_printf(sync_out, "* usage: sync_server [-C <alt_config>]\r\n");
465 prot_flush(sync_out);
466 exit(EC_USAGE);
467 }
468
469 /*
470 * Cleanly shut down and exit
471 */
472 void shut_down(int code)
473 {
474 in_shutdown = 1;
475
476 proc_cleanup();
477
478 if (config_getswitch(IMAPOPT_STATUSCACHE)) {
479 statuscache_close();
480 statuscache_done();
481 }
482
483 seen_done();
484 mboxlist_close();
485 mboxlist_done();
486
487 quotadb_close();
488 quotadb_done();
489
490 annotatemore_close();
491 annotatemore_done();
492
493 if (sync_in) {
494 prot_NONBLOCK(sync_in);
495 prot_fill(sync_in);
496 prot_free(sync_in);
497 }
498
499 if (sync_out) {
500 prot_flush(sync_out);
501 prot_free(sync_out);
502 }
503
504 #ifdef HAVE_SSL
505 tls_shutdown_serverengine();
506 #endif
507
508 cyrus_done();
509
510 exit(code);
511 }
512
513 void fatal(const char* s, int code)
514 {
515 static int recurse_code = 0;
516
517 if (recurse_code) {
518 /* We were called recursively. Just give up */
519 proc_cleanup();
520 exit(recurse_code);
521 }
522 recurse_code = code;
523 if (sync_out) {
524 prot_printf(sync_out, "* Fatal error: %s\r\n", s);
525 prot_flush(sync_out);
526 }
527 syslog(LOG_ERR, "Fatal error: %s", s);
528 shut_down(code);
529 }
530
531 /* Reset the given sasl_conn_t to a sane state */
532 static int reset_saslconn(sasl_conn_t **conn)
533 {
534 int ret;
535 sasl_security_properties_t *secprops = NULL;
536
537 sasl_dispose(conn);
538 /* do initialization typical of service_main */
539 ret = sasl_server_new("csync", config_servername,
540 NULL, NULL, NULL,
541 NULL, 0, conn);
542 if (ret != SASL_OK) return ret;
543
544 if (saslprops.ipremoteport)
545 ret = sasl_setprop(*conn, SASL_IPREMOTEPORT,
546 saslprops.ipremoteport);
547 if (ret != SASL_OK) return ret;
548
549 if (saslprops.iplocalport)
550 ret = sasl_setprop(*conn, SASL_IPLOCALPORT,
551 saslprops.iplocalport);
552 if (ret != SASL_OK) return ret;
553 secprops = mysasl_secprops(SASL_SEC_NOANONYMOUS);
554 ret = sasl_setprop(*conn, SASL_SEC_PROPS, secprops);
555 if (ret != SASL_OK) return ret;
556 /* end of service_main initialization excepting SSF */
557
558 /* If we have TLS/SSL info, set it */
559 if (saslprops.ssf) {
560 ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &saslprops.ssf);
561 } else {
562 ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &extprops_ssf);
563 }
564
565 if (ret != SASL_OK) return ret;
566
567 if (saslprops.authid) {
568 ret = sasl_setprop(*conn, SASL_AUTH_EXTERNAL, saslprops.authid);
569 if(ret != SASL_OK) return ret;
570 }
571 /* End TLS/SSL Info */
572
573 return SASL_OK;
574 }
575
576 static void cmdloop(void)
577 {
578 struct sync_reserve_list *reserve_list;
579 static struct buf cmd;
580 static struct buf arg1, arg2;
581 int c;
582 char *p;
583 struct dlist *kl;
584
585 syslog(LOG_DEBUG, "cmdloop(): startup");
586
587 reserve_list = sync_reserve_list_create(SYNC_MESSAGE_LIST_HASH_SIZE);
588
589 for (;;) {
590 prot_flush(sync_out);
591
592 /* Parse command name */
593 if ((c = getword(sync_in, &cmd)) == EOF)
594 break;
595
596 if (!cmd.s[0]) {
597 prot_printf(sync_out, "BAD Null command\r\n");
598 eatline(sync_in, c);
599 continue;
600 }
601
602 if (Uislower(cmd.s[0]))
603 cmd.s[0] = toupper((unsigned char) cmd.s[0]);
604 for (p = &cmd.s[1]; *p; p++) {
605 if (Uisupper(*p)) *p = tolower((unsigned char) *p);
606 }
607
608 /* Must be an admin */
609 if (sync_userid && !sync_userisadmin) goto noperm;
610
611 switch (cmd.s[0]) {
612 case 'A':
613 if (!strcmp(cmd.s, "Authenticate")) {
614 int haveinitresp = 0;
615 if (c != ' ') goto missingargs;
616 c = getword(sync_in, &arg1);
617 if (!imparse_isatom(arg1.s)) {
618 prot_printf(sync_out, "BAD Invalid mechanism\r\n");
619 eatline(sync_in, c);
620 continue;
621 }
622 if (c == ' ') {
623 haveinitresp = 1;
624 c = getword(sync_in, &arg2);
625 if (c == EOF) goto missingargs;
626 }
627 if (c == '\r') c = prot_getc(sync_in);
628 if (c != '\n') goto extraargs;
629
630 if (sync_userid) {
631 prot_printf(sync_out, "BAD Already authenticated\r\n");
632 continue;
633 }
634 cmd_authenticate(arg1.s, haveinitresp ? arg2.s : NULL);
635 continue;
636 }
637 if (!sync_userid) goto nologin;
638 if (!strcmp(cmd.s, "Apply")) {
639 kl = sync_parseline(sync_in);
640 if (kl) {
641 cmd_apply(kl, reserve_list);
642 dlist_free(&kl);
643 }
644 else
645 prot_printf(sync_out, "BAD IMAP_PROTOCOL_ERROR Failed to parse APPLY line\r\n");
646 continue;
647 }
648 break;
649
650 case 'C':
651 if (!strcmp(cmd.s, "Compress")) {
652 if (c != ' ') goto missingargs;
653 c = getword(sync_in, &arg1);
654 if (c == '\r') c = prot_getc(sync_in);
655 if (c != '\n') goto extraargs;
656 cmd_compress(arg1.s);
657 continue;
658 }
659 break;
660
661 case 'G':
662 if (!sync_userid) goto nologin;
663 if (!strcmp(cmd.s, "Get")) {
664 kl = sync_parseline(sync_in);
665 if (kl) {
666 cmd_get(kl);
667 dlist_free(&kl);
668 }
669 else
670 prot_printf(sync_out, "BAD IMAP_PROTOCOL_ERROR Failed to parse GET line\r\n");
671 continue;
672 }
673 break;
674
675 case 'E':
676 if (!strcmp(cmd.s, "Exit")) {
677 if (c == '\r') c = prot_getc(sync_in);
678 if (c != '\n') goto extraargs;
679 prot_printf(sync_out, "OK Finished\r\n");
680 prot_flush(sync_out);
681 goto exit;
682 }
683 break;
684
685 case 'N':
686 if (!strcmp(cmd.s, "Noop")) {
687 if (c == '\r') c = prot_getc(sync_in);
688 if (c != '\n') goto extraargs;
689 prot_printf(sync_out, "OK Noop completed\r\n");
690 continue;
691 }
692 break;
693
694 case 'R':
695 if (!strcmp(cmd.s, "Restart")) {
696 if (c == '\r') c = prot_getc(sync_in);
697 if (c != '\n') goto extraargs;
698 /* just clear the GUID cache */
699 cmd_restart(&reserve_list, 1);
700 prot_printf(sync_out, "OK Restarting\r\n");
701 continue;
702 }
703 else if (!sync_userid) goto nologin;
704 break;
705
706 case 'S':
707 if (!strcmp(cmd.s, "Starttls") && tls_enabled()) {
708 if (c == '\r') c = prot_getc(sync_in);
709 if (c != '\n') goto extraargs;
710
711 /* XXX discard any input pipelined after STARTTLS */
712 prot_flush(sync_in);
713
714 /* if we've already done SASL fail */
715 if (sync_userid != NULL) {
716 prot_printf(sync_out,
717 "BAD Can't Starttls after authentication\r\n");
718 continue;
719 }
720 /* check if already did a successful tls */
721 if (sync_starttls_done == 1) {
722 prot_printf(sync_out,
723 "BAD Already did a successful Starttls\r\n");
724 continue;
725 }
726 cmd_starttls();
727 continue;
728 }
729 break;
730
731 }
732
733 prot_printf(sync_out, "BAD IMAP_PROTOCOL_ERROR Unrecognized command\r\n");
734 eatline(sync_in, c);
735 continue;
736
737 nologin:
738 prot_printf(sync_out, "NO Please authenticate first\r\n");
739 eatline(sync_in, c);
740 continue;
741
742 noperm:
743 prot_printf(sync_out, "NO %s\r\n",
744 error_message(IMAP_PERMISSION_DENIED));
745 eatline(sync_in, c);
746 continue;
747
748 missingargs:
749 prot_printf(sync_out, "BAD Missing required argument to %s\r\n", cmd.s);
750 eatline(sync_in, c);
751 continue;
752
753 extraargs:
754 prot_printf(sync_out, "BAD Unexpected extra arguments to %s\r\n", cmd.s);
755 eatline(sync_in, c);
756 continue;
757 }
758
759 exit:
760 cmd_restart(&reserve_list, 0);
761 }
762
763 static void cmd_authenticate(char *mech, char *resp)
764 {
765 int r, sasl_result;
766 sasl_ssf_t ssf;
767 char *ssfmsg = NULL;
768 const void *val;
769 int failedloginpause;
770
771 if (sync_userid) {
772 prot_printf(sync_out, "BAD Already authenticated\r\n");
773 return;
774 }
775
776 r = saslserver(sync_saslconn, mech, resp, "", "+ ", "",
777 sync_in, sync_out, &sasl_result, NULL);
778
779 if (r) {
780 const char *errorstring = NULL;
781
782 switch (r) {
783 case IMAP_SASL_CANCEL:
784 prot_printf(sync_out,
785 "BAD Client canceled authentication\r\n");
786 break;
787 case IMAP_SASL_PROTERR:
788 errorstring = prot_error(sync_in);
789
790 prot_printf(sync_out,
791 "NO Error reading client response: %s\r\n",
792 errorstring ? errorstring : "");
793 break;
794 default:
795 /* failed authentication */
796 errorstring = sasl_errstring(sasl_result, NULL, NULL);
797
798 syslog(LOG_NOTICE, "badlogin: %s %s [%s]",
799 sync_clienthost, mech, sasl_errdetail(sync_saslconn));
800
801 failedloginpause = config_getint(IMAPOPT_FAILEDLOGINPAUSE);
802 if (failedloginpause != 0) {
803 sleep(failedloginpause);
804 }
805
806 if (errorstring) {
807 prot_printf(sync_out, "NO %s\r\n", errorstring);
808 } else {
809 prot_printf(sync_out, "NO Error authenticating\r\n");
810 }
811 }
812
813 reset_saslconn(&sync_saslconn);
814 return;
815 }
816
817 /* successful authentication */
818
819 /* get the userid from SASL --- already canonicalized from
820 * mysasl_proxy_policy()
821 */
822 sasl_result = sasl_getprop(sync_saslconn, SASL_USERNAME, &val);
823 if (sasl_result != SASL_OK) {
824 prot_printf(sync_out, "NO weird SASL error %d SASL_USERNAME\r\n",
825 sasl_result);
826 syslog(LOG_ERR, "weird SASL error %d getting SASL_USERNAME",
827 sasl_result);
828 reset_saslconn(&sync_saslconn);
829 return;
830 }
831
832 sync_userid = xstrdup((const char *) val);
833 proc_register("sync_server", sync_clienthost, sync_userid, NULL);
834
835 syslog(LOG_NOTICE, "login: %s %s %s%s %s", sync_clienthost, sync_userid,
836 mech, sync_starttls_done ? "+TLS" : "", "User logged in");
837
838 sasl_getprop(sync_saslconn, SASL_SSF, &val);
839 ssf = *((sasl_ssf_t *) val);
840
841 /* really, we should be doing a sasl_getprop on SASL_SSF_EXTERNAL,
842 but the current libsasl doesn't allow that. */
843 if (sync_starttls_done) {
844 switch(ssf) {
845 case 0: ssfmsg = "tls protection"; break;
846 case 1: ssfmsg = "tls plus integrity protection"; break;
847 default: ssfmsg = "tls plus privacy protection"; break;
848 }
849 } else {
850 switch(ssf) {
851 case 0: ssfmsg = "no protection"; break;
852 case 1: ssfmsg = "integrity protection"; break;
853 default: ssfmsg = "privacy protection"; break;
854 }
855 }
856
857 prot_printf(sync_out, "OK Success (%s)\r\n", ssfmsg);
858
859 prot_setsasl(sync_in, sync_saslconn);
860 prot_setsasl(sync_out, sync_saslconn);
861
862 /* Create telemetry log */
863 sync_logfd = telemetry_log(sync_userid, sync_in, sync_out, 0);
864 }
865
866 void printstring(const char *s __attribute__((unused)))
867 {
868 /* needed to link against annotate.o */
869 fatal("printstring() executed, but its not used for sync_server!",
870 EC_SOFTWARE);
871 }
872
873 #ifdef HAVE_SSL
874 static void cmd_starttls(void)
875 {
876 int result;
877 int *layerp;
878 sasl_ssf_t ssf;
879 char *auth_id;
880
881 if (sync_starttls_done == 1) {
882 prot_printf(sync_out, "NO %s\r\n",
883 "Already successfully executed STARTTLS");
884 return;
885 }
886
887 /* SASL and openssl have different ideas about whether ssf is signed */
888 layerp = (int *) &ssf;
889
890 result=tls_init_serverengine("csync",
891 5, /* depth to verify */
892 1, /* can client auth? */
893 1); /* TLS only? */
894
895 if (result == -1) {
896 syslog(LOG_ERR, "error initializing TLS");
897 prot_printf(sync_out, "NO %s\r\n", "Error initializing TLS");
898 return;
899 }
900
901 prot_printf(sync_out, "OK %s\r\n", "Begin TLS negotiation now");
902 /* must flush our buffers before starting tls */
903 prot_flush(sync_out);
904
905 result=tls_start_servertls(0, /* read */
906 1, /* write */
907 180, /* 3 minutes */
908 layerp,
909 &auth_id,
910 &tls_conn);
911
912 /* if error */
913 if (result==-1) {
914 prot_printf(sync_out, "NO Starttls failed\r\n");
915 syslog(LOG_NOTICE, "STARTTLS failed: %s", sync_clienthost);
916 return;
917 }
918
919 /* tell SASL about the negotiated layer */
920 result = sasl_setprop(sync_saslconn, SASL_SSF_EXTERNAL, &ssf);
921 if (result != SASL_OK) {
922 fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
923 }
924 saslprops.ssf = ssf;
925
926 result = sasl_setprop(sync_saslconn, SASL_AUTH_EXTERNAL, auth_id);
927 if (result != SASL_OK) {
928 fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
929 }
930 if (saslprops.authid) {
931 free(saslprops.authid);
932 saslprops.authid = NULL;
933 }
934 if (auth_id)
935 saslprops.authid = xstrdup(auth_id);
936
937 /* tell the prot layer about our new layers */
938 prot_settls(sync_in, tls_conn);
939 prot_settls(sync_out, tls_conn);
940
941 sync_starttls_done = 1;
942
943 dobanner();
944 }
945 #else
946 static void cmd_starttls(void)
947 {
948 fatal("cmd_starttls() called, but no OpenSSL", EC_SOFTWARE);
949 }
950 #endif /* HAVE_SSL */
951
952 #ifdef HAVE_ZLIB
953 static void cmd_compress(char *alg)
954 {
955 if (sync_compress_done) {
956 prot_printf(sync_out, "NO Compression already active: %s\r\n", alg);
957 return;
958 }
959 if (strcasecmp(alg, "DEFLATE")) {
960 prot_printf(sync_out, "NO Unknown compression algorithm: %s\r\n", alg);
961 return;
962 }
963 if (ZLIB_VERSION[0] != zlibVersion()[0]) {
964 prot_printf(sync_out, "NO Error initializing %s "
965 "(incompatible zlib version)\r\n", alg);
966 return;
967 }
968 prot_printf(sync_out, "OK %s active\r\n", alg);
969 prot_flush(sync_out);
970 prot_setcompress(sync_in);
971 prot_setcompress(sync_out);
972 sync_compress_done = 1;
973 }
974 #else
975 static void cmd_compress(char *alg)
976 {
977 prot_printf(sync_out, "NO ZLIB not available\r\n");
978 }
979 #endif
980
981 /* ====================================================================== */
982
983 /* partition_list is simple linked list of names used by cmd_restart */
984
985 struct partition_list {
986 struct partition_list *next;
987 char *name;
988 };
989
990 static struct partition_list *
991 partition_list_add(char *name, struct partition_list *pl)
992 {
993 struct partition_list *p;
994
995 /* Is name already on list? */
996 for (p=pl; p; p = p->next) {
997 if (!strcmp(p->name, name))
998 return(pl);
999 }
1000
1001 /* Add entry to start of list and return new list */
1002 p = xzmalloc(sizeof(struct partition_list));
1003 p->next = pl;
1004 p->name = xstrdup(name);
1005
1006 return(p);
1007 }
1008
1009 static void
1010 partition_list_free(struct partition_list *current)
1011 {
1012 while (current) {
1013 struct partition_list *next = current->next;
1014
1015 free(current->name);
1016 free(current);
1017
1018 current = next;
1019 }
1020
1021 }
1022
1023 static void cmd_restart(struct sync_reserve_list **reserve_listp, int re_alloc)
1024 {
1025 struct sync_reserve *res;
1026 struct sync_reserve_list *l = *reserve_listp;
1027 struct sync_msgid *msg;
1028 const char *fname;
1029 int hash_size = l->hash_size;
1030 struct partition_list *p, *pl = NULL;
1031
1032 for (res = l->head; res; res = res->next) {
1033 for (msg = res->list->head; msg; msg = msg->next) {
1034 pl = partition_list_add(res->part, pl);
1035
1036 fname = dlist_reserve_path(res->part, &msg->guid);
1037 unlink(fname);
1038 }
1039 }
1040 sync_reserve_list_free(reserve_listp);
1041
1042 /* Remove all <partition>/sync./<pid> directories referred to above */
1043 for (p=pl; p ; p = p->next) {
1044 static char buf[MAX_MAILBOX_PATH];
1045
1046 snprintf(buf, MAX_MAILBOX_PATH, "%s/sync./%lu",
1047 config_partitiondir(p->name), (unsigned long)getpid());
1048 rmdir(buf);
1049 }
1050 partition_list_free(pl);
1051
1052 if (re_alloc)
1053 *reserve_listp = sync_reserve_list_create(hash_size);
1054 else
1055 *reserve_listp = NULL;
1056 }
1057
1058 /* ====================================================================== */
1059
1060 void reserve_folder(const char *part, const char *mboxname,
1061 struct sync_msgid_list *part_list)
1062 {
1063 struct mailbox *mailbox = NULL;
1064 struct index_record record;
1065 struct index_record record2;
1066 int r;
1067 struct sync_msgid *item;
1068 const char *mailbox_msg_path, *stage_msg_path;
1069 uint32_t recno;
1070
1071 /* Open and lock mailbox */
1072 r = mailbox_open_irl(mboxname, &mailbox);
1073
1074 if (r) return;
1075
1076 for (recno = 1;
1077 part_list->marked < part_list->count && recno <= mailbox->i.num_records;
1078 recno++) {
1079 if (mailbox_read_index_record(mailbox, recno, &record))
1080 continue;
1081
1082 if (record.system_flags & FLAG_UNLINKED)
1083 continue;
1084
1085 item = sync_msgid_lookup(part_list, &record.guid);
1086 if (!item || item->mark)
1087 continue;
1088
1089 /* Attempt to reserve this message */
1090 mailbox_msg_path = mailbox_message_fname(mailbox, record.uid);
1091 stage_msg_path = dlist_reserve_path(part, &record.guid);
1092
1093 /* check that the sha1 of the file on disk is correct */
1094 memset(&record2, 0, sizeof(struct index_record));
1095 r = message_parse(mailbox_msg_path, &record2);
1096 if (r) {
1097 syslog(LOG_ERR, "IOERROR: Unable to parse %s",
1098 mailbox_msg_path);
1099 continue;
1100 }
1101 if (!message_guid_equal(&record.guid, &record2.guid)) {
1102 syslog(LOG_ERR, "IOERROR: GUID mismatch on parse for %s",
1103 mailbox_msg_path);
1104 continue;
1105 }
1106
1107 if (mailbox_copyfile(mailbox_msg_path, stage_msg_path, 0) != 0) {
1108 syslog(LOG_ERR, "IOERROR: Unable to link %s -> %s: %m",
1109 mailbox_msg_path, stage_msg_path);
1110 continue;
1111 }
1112
1113 item->mark = 1;
1114 part_list->marked++;
1115 }
1116
1117 mailbox_close(&mailbox);
1118 }
1119
1120 static int do_reserve(struct dlist *kl, struct sync_reserve_list *reserve_list)
1121 {
1122 struct message_guid tmp_guid;
1123 struct sync_name_list *missing = sync_name_list_create();
1124 struct sync_name_list *folder_names = sync_name_list_create();
1125 struct sync_msgid_list *part_list;
1126 struct sync_msgid *item;
1127 struct sync_name *folder;
1128 struct mboxlist_entry mbentry;
1129 const char *partition = NULL;
1130 struct dlist *ml;
1131 struct dlist *gl;
1132 struct dlist *i;
1133 struct dlist *kout;
1134
1135 if (!dlist_getatom(kl, "PARTITION", &partition)) goto parse_err;
1136 if (!dlist_getlist(kl, "MBOXNAME", &ml)) goto parse_err;
1137 if (!dlist_getlist(kl, "GUID", &gl)) goto parse_err;
1138
1139 part_list = sync_reserve_partlist(reserve_list, partition);
1140 for (i = gl->head; i; i = i->next) {
1141 if (!i->sval || !message_guid_decode(&tmp_guid, i->sval))
1142 goto parse_err;
1143 sync_msgid_add(part_list, &tmp_guid);
1144 }
1145
1146 /* need a list so we can mark items */
1147 for (i = ml->head; i; i = i->next) {
1148 sync_name_list_add(folder_names, i->sval);
1149 }
1150
1151 for (folder = folder_names->head;
1152 part_list->marked < part_list->count && folder;
1153 folder = folder->next) {
1154 if (mboxlist_lookup(folder->name, &mbentry, 0) ||
1155 strcmp(mbentry.partition, partition))
1156 continue; /* try folders on the same partition first! */
1157 reserve_folder(partition, folder->name, part_list);
1158 folder->mark = 1;
1159 }
1160
1161 /* if we have other folders, check them now */
1162 for (folder = folder_names->head;
1163 part_list->marked < part_list->count && folder;
1164 folder = folder->next) {
1165 if (folder->mark)
1166 continue;
1167 reserve_folder(partition, folder->name, part_list);
1168 }
1169
1170 /* check if we missed any */
1171 kout = dlist_list(NULL, "MISSING");
1172 for (i = gl->head; i; i = i->next) {
1173 if (!message_guid_decode(&tmp_guid, i->sval))
1174 continue;
1175 item = sync_msgid_lookup(part_list, &tmp_guid);
1176 if (item && !item->mark)
1177 dlist_atom(kout, "GUID", i->sval);
1178 }
1179 if (kout->head)
1180 sync_send_response(kout, sync_out);
1181 dlist_free(&kout);
1182
1183 sync_name_list_free(&folder_names);
1184 sync_name_list_free(&missing);
1185
1186 return 0;
1187
1188 parse_err:
1189 sync_name_list_free(&folder_names);
1190 sync_name_list_free(&missing);
1191
1192 return IMAP_PROTOCOL_BAD_PARAMETERS;
1193 }
1194
1195 /* ====================================================================== */
1196
1197 static int do_unquota(struct dlist *kin)
1198 {
1199 return mboxlist_unsetquota(kin->sval);
1200 }
1201
1202 static int do_quota(struct dlist *kin)
1203 {
1204 const char *root;
1205 uint32_t limit;
1206
1207 if (!dlist_getatom(kin, "ROOT", &root))
1208 return IMAP_PROTOCOL_BAD_PARAMETERS;
1209 if (!dlist_getnum(kin, "LIMIT", &limit))
1210 return IMAP_PROTOCOL_BAD_PARAMETERS;
1211
1212 return mboxlist_setquota(root, limit, 1);
1213 }
1214
1215 /* ====================================================================== */
1216
1217 static int mailbox_compare_update(struct mailbox *mailbox,
1218 struct dlist *kr, int doupdate)
1219 {
1220 struct index_record mrecord;
1221 struct index_record rrecord;
1222 uint32_t recno = 1;
1223 struct dlist *ki;
1224 int r;
1225 int i;
1226
1227 rrecord.uid = 0;
1228 for (ki = kr->head; ki; ki = ki->next) {
1229 r = parse_upload(ki, mailbox, &mrecord);
1230 if (r) {
1231 syslog(LOG_ERR, "Failed to parse uploaded record");
1232 return r;
1233 }
1234
1235 while (rrecord.uid < mrecord.uid) {
1236 /* hit the end? Magic marker */
1237 if (recno > mailbox->i.num_records) {
1238 rrecord.uid = UINT32_MAX;
1239 break;
1240 }
1241
1242 /* read another record */
1243 r = mailbox_read_index_record(mailbox, recno, &rrecord);
1244 if (r) {
1245 syslog(LOG_ERR, "Failed to read record %s %d",
1246 mailbox->name, recno);
1247 return r;
1248 }
1249 recno++;
1250 }
1251
1252 /* found a match, check for updates */
1253 if (rrecord.uid == mrecord.uid) {
1254 /* GUID mismatch on a non-expunged record is an error straight away */
1255 if (!(mrecord.system_flags & FLAG_EXPUNGED)) {
1256 if (!message_guid_equal(&mrecord.guid, &rrecord.guid)) {
1257 syslog(LOG_ERR, "SYNCERROR: guid mismatch %s %u",
1258 mailbox->name, mrecord.uid);
1259 return IMAP_MAILBOX_CRC;
1260 }
1261 if (rrecord.system_flags & FLAG_EXPUNGED) {
1262 syslog(LOG_ERR, "SYNCERROR: expunged on replica %s %u",
1263 mailbox->name, mrecord.uid);
1264 return IMAP_MAILBOX_CRC;
1265 }
1266 }
1267 /* higher modseq on the replica is an error */
1268 if (rrecord.modseq > mrecord.modseq) {
1269 syslog(LOG_ERR, "SYNCERROR: higher modseq on replica %s %u",
1270 mailbox->name, mrecord.uid);
1271 return IMAP_MAILBOX_CRC;
1272 }
1273
1274 /* skip out on the first pass */
1275 if (!doupdate) continue;
1276
1277 rrecord.modseq = mrecord.modseq;
1278 rrecord.last_updated = mrecord.last_updated;
1279 rrecord.internaldate = mrecord.internaldate;
1280 rrecord.system_flags = (mrecord.system_flags & ~FLAG_UNLINKED) |
1281 (rrecord.system_flags & FLAG_UNLINKED);
1282 for (i = 0; i < MAX_USER_FLAGS/32; i++)
1283 rrecord.user_flags[i] = mrecord.user_flags[i];
1284 rrecord.silent = 1;
1285 r = mailbox_rewrite_index_record(mailbox, &rrecord);
1286 if (r) {
1287 syslog(LOG_ERR, "IOERROR: failed to rewrite record %s %d",
1288 mailbox->name, recno);
1289 return r;
1290 }
1291 }
1292
1293 /* not found and less than LAST_UID, bogus */
1294 else if (mrecord.uid <= mailbox->i.last_uid) {
1295 /* Expunged, just skip it */
1296 if (!(mrecord.system_flags & FLAG_EXPUNGED))
1297 return IMAP_MAILBOX_CRC;
1298 }
1299
1300 /* after LAST_UID, it's an append, that's OK */
1301 else {
1302 /* skip out on the first pass */
1303 if (!doupdate) continue;
1304
1305 mrecord.silent = 1;
1306 r = sync_append_copyfile(mailbox, &mrecord);
1307 if (r) {
1308 syslog(LOG_ERR, "IOERROR: failed to append file %s %d",
1309 mailbox->name, recno);
1310 return r;
1311 }
1312 }
1313 }
1314
1315 return 0;
1316 }
1317
1318 static int do_mailbox(struct dlist *kin)
1319 {
1320 /* fields from the request */
1321 const char *uniqueid;
1322 const char *partition;
1323 const char *mboxname;
1324 uint32_t last_uid;
1325 modseq_t highestmodseq;
1326 uint32_t recentuid;
1327 time_t recenttime;
1328 time_t last_appenddate;
1329 time_t pop3_last_login;
1330 uint32_t uidvalidity;
1331 const char *acl;
1332 const char *options_str;
1333 uint32_t sync_crc;
1334
1335 uint32_t options;
1336
1337 struct mailbox *mailbox = NULL;
1338 uint32_t newcrc;
1339 struct dlist *kr;
1340 int r;
1341
1342 if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
1343 return IMAP_PROTOCOL_BAD_PARAMETERS;
1344 if (!dlist_getatom(kin, "PARTITION", &partition))
1345 return IMAP_PROTOCOL_BAD_PARAMETERS;
1346 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
1347 return IMAP_PROTOCOL_BAD_PARAMETERS;
1348 if (!dlist_getnum(kin, "LAST_UID", &last_uid))
1349 return IMAP_PROTOCOL_BAD_PARAMETERS;
1350 if (!dlist_getmodseq(kin, "HIGHESTMODSEQ", &highestmodseq))
1351 return IMAP_PROTOCOL_BAD_PARAMETERS;
1352 if (!dlist_getnum(kin, "RECENTUID", &recentuid))
1353 return IMAP_PROTOCOL_BAD_PARAMETERS;
1354 if (!dlist_getdate(kin, "RECENTTIME", &recenttime))
1355 return IMAP_PROTOCOL_BAD_PARAMETERS;
1356 if (!dlist_getdate(kin, "LAST_APPENDDATE", &last_appenddate))
1357 return IMAP_PROTOCOL_BAD_PARAMETERS;
1358 if (!dlist_getdate(kin, "POP3_LAST_LOGIN", &pop3_last_login))
1359 return IMAP_PROTOCOL_BAD_PARAMETERS;
1360 if (!dlist_getnum(kin, "SYNC_CRC", &sync_crc))
1361 return IMAP_PROTOCOL_BAD_PARAMETERS;
1362 if (!dlist_getnum(kin, "UIDVALIDITY", &uidvalidity))
1363 return IMAP_PROTOCOL_BAD_PARAMETERS;
1364 if (!dlist_getatom(kin, "ACL", &acl))
1365 return IMAP_PROTOCOL_BAD_PARAMETERS;
1366 if (!dlist_getatom(kin, "OPTIONS", &options_str))
1367 return IMAP_PROTOCOL_BAD_PARAMETERS;
1368 if (!dlist_getlist(kin, "RECORD", &kr))
1369 return IMAP_PROTOCOL_BAD_PARAMETERS;
1370
1371 options = sync_parse_options(options_str);
1372
1373 r = mailbox_open_iwl(mboxname, &mailbox);
1374 if (r == IMAP_MAILBOX_NONEXISTENT) {
1375 r = mboxlist_createsync(mboxname, 0, partition,
1376 sync_userid, sync_authstate,
1377 options, uidvalidity, acl,
1378 uniqueid, &mailbox);
1379 }
1380 if (r) {
1381 syslog(LOG_ERR, "Failed to open mailbox %s to update", mboxname);
1382 return r;
1383 }
1384
1385 if (strcmp(mailbox->uniqueid, uniqueid)) {
1386 syslog(LOG_ERR, "Mailbox uniqueid changed %s - retry", mboxname);
1387 mailbox_close(&mailbox);
1388 return IMAP_MAILBOX_MOVED;
1389 }
1390
1391 /* skip out now, it's going to mismatch for sure! */
1392 if (highestmodseq < mailbox->i.highestmodseq) {
1393 syslog(LOG_ERR, "higher modseq on replica %s - "
1394 MODSEQ_FMT " < " MODSEQ_FMT,
1395 mboxname, highestmodseq, mailbox->i.highestmodseq);
1396 mailbox_close(&mailbox);
1397 return IMAP_MAILBOX_CRC;
1398 }
1399
1400 if (last_uid < mailbox->i.last_uid) {
1401 syslog(LOG_ERR, "higher last_uid on replica %s - %u < %u",
1402 mboxname, last_uid, mailbox->i.last_uid);
1403 mailbox_close(&mailbox);
1404 return IMAP_MAILBOX_CRC;
1405 }
1406
1407 if (strcmp(mailbox->acl, acl)) {
1408 mailbox_set_acl(mailbox, acl, 0);
1409 r = mboxlist_sync_setacls(mboxname, acl);
1410 if (r) {
1411 mailbox_close(&mailbox);
1412 return r;
1413 }
1414 }
1415
1416 r = mailbox_compare_update(mailbox, kr, 0);
1417 if (r) {
1418 mailbox_close(&mailbox);
1419 return r;
1420 }
1421
1422 /* now we're committed to writing something no matter what happens! */
1423
1424 r = mailbox_compare_update(mailbox, kr, 1);
1425 if (r) {
1426 abort();
1427 return r;
1428 }
1429
1430 mailbox_index_dirty(mailbox);
1431 assert(mailbox->i.last_uid <= last_uid);
1432 mailbox->i.last_uid = last_uid;
1433 mailbox->i.highestmodseq = highestmodseq;
1434 mailbox->i.recentuid = recentuid;
1435 mailbox->i.recenttime = recenttime;
1436 mailbox->i.last_appenddate = last_appenddate;
1437 mailbox->i.pop3_last_login = pop3_last_login;
1438 /* mailbox->i.options = options; ... not really, there's unsyncable stuff in here */
1439
1440 if (mailbox->i.uidvalidity < uidvalidity) {
1441 syslog(LOG_ERR, "%s uidvalidity higher on master, updating %u => %u",
1442 mailbox->name, mailbox->i.uidvalidity, uidvalidity);
1443 mailbox->i.uidvalidity = uidvalidity;
1444 }
1445
1446 /* try re-calculating the CRC on mismatch... */
1447 if (mailbox->i.sync_crc != sync_crc) {
1448 mailbox_index_recalc(mailbox);
1449 }
1450 newcrc = mailbox->i.sync_crc;
1451 mailbox_close(&mailbox);
1452
1453 /* check return value */
1454 if (r) return r;
1455 if (newcrc != sync_crc)
1456 return IMAP_MAILBOX_CRC;
1457 return 0;
1458 }
1459
1460 /* ====================================================================== */
1461
1462 static int getannotation_cb(const char *mailbox __attribute__((unused)),
1463 const char *entry, const char *userid,
1464 struct annotation_data *attrib,
1465 void *rock)
1466 {
1467 const char *mboxname = (char *)rock;
1468 struct dlist *kl;
1469
1470 kl = dlist_new("ANNOTATION");
1471 dlist_atom(kl, "MBOXNAME", mboxname);
1472 dlist_atom(kl, "ENTRY", entry);
1473 dlist_atom(kl, "USERID", userid);
1474 dlist_atom(kl, "VALUE", attrib->value);
1475 sync_send_response(kl, sync_out);
1476 dlist_free(&kl);
1477
1478 return 0;
1479 }
1480
1481 static int do_getannotation(struct dlist *kin)
1482 {
1483 const char *mboxname = kin->sval;
1484 return annotatemore_findall(mboxname, "*", &getannotation_cb,
1485 (void *)mboxname, NULL);
1486 }
1487
1488 static void print_quota(struct quota *q)
1489 {
1490 struct dlist *kl;
1491
1492 kl = dlist_new("QUOTA");
1493 dlist_atom(kl, "ROOT", q->root);
1494 dlist_num(kl, "LIMIT", q->limit);
1495 sync_send_response(kl, sync_out);
1496 dlist_free(&kl);
1497 }
1498
1499 static int quota_work(const char *root)
1500 {
1501 struct quota q;
1502
1503 q.root = root;
1504 if (!quota_read(&q, NULL, 0))
1505 print_quota(&q);
1506
1507 return 0;
1508 }
1509
1510 static int do_getquota(struct dlist *kin)
1511 {
1512 return quota_work(kin->sval);
1513 }
1514
1515 static int mailbox_cb(char *name,
1516 int matchlen __attribute__((unused)),
1517 int maycreate __attribute__((unused)),
1518 void *rock)
1519 {
1520 struct sync_name_list *qrl = (struct sync_name_list *)rock;
1521 struct mailbox *mailbox = NULL;
1522 struct dlist *kl = dlist_kvlist(NULL, "MAILBOX");
1523 int r;
1524
1525 r = mailbox_open_irl(name, &mailbox);
1526 /* doesn't exist? Probably not finished creating or removing yet */
1527 if (r == IMAP_MAILBOX_NONEXISTENT) return 0;
1528 if (r == IMAP_MAILBOX_RESERVED) return 0;
1529 if (r) return r;
1530
1531 if (qrl && mailbox->quotaroot &&
1532 !sync_name_lookup(qrl, mailbox->quotaroot))
1533 sync_name_list_add(qrl, mailbox->quotaroot);
1534
1535 r = sync_mailbox(mailbox, NULL, NULL, kl, NULL, 0);
1536 if (!r) sync_send_response(kl, sync_out);
1537 dlist_free(&kl);
1538 mailbox_close(&mailbox);
1539
1540 return r;
1541 }
1542
1543 static int do_getfullmailbox(struct dlist *kin)
1544 {
1545 struct mailbox *mailbox = NULL;
1546 struct dlist *kl = dlist_kvlist(NULL, "MAILBOX");
1547 int r;
1548
1549 r = mailbox_open_irl(kin->sval, &mailbox);
1550 if (r) return r;
1551
1552 r = sync_mailbox(mailbox, NULL, NULL, kl, NULL, 1);
1553 if (!r) sync_send_response(kl, sync_out);
1554 dlist_free(&kl);
1555 mailbox_close(&mailbox);
1556
1557 return r;
1558 }
1559
1560 static int do_getmailboxes(struct dlist *kin)
1561 {
1562 struct dlist *ki;
1563
1564 for (ki = kin->head; ki; ki = ki->next)
1565 mailbox_cb(ki->sval, 0, 0, NULL);
1566
1567 return 0;
1568 }
1569
1570 /* ====================================================================== */
1571
1572 static int print_seen(const char *uniqueid, struct seendata *sd,
1573 void *rock __attribute__((unused)))
1574 {
1575 struct dlist *kl;
1576
1577 kl = dlist_new("SEEN");
1578 dlist_atom(kl, "UNIQUEID", uniqueid);
1579 dlist_date(kl, "LASTREAD", sd->lastread);
1580 dlist_num(kl, "LASTUID", sd->lastuid);
1581 dlist_date(kl, "LASTCHANGE", sd->lastchange);
1582 dlist_atom(kl, "SEENUIDS", sd->seenuids);
1583 sync_send_response(kl, sync_out);
1584 dlist_free(&kl);
1585
1586 return 0;
1587 }
1588
1589 static int user_seen(const char *userid)
1590 {
1591 struct seen *seendb = NULL;
1592
1593 /* no SEEN DB is OK, just return */
1594 if (seen_open(userid, SEEN_SILENT, &seendb))
1595 return 0;
1596
1597 seen_foreach(seendb, print_seen, NULL);
1598 seen_close(&seendb);
1599
1600 return 0;
1601 }
1602
1603
1604 static int user_sub(const char *userid)
1605 {
1606 struct sync_name_list *list = sync_name_list_create();
1607 struct sync_name *item;
1608 struct dlist *kl;
1609
1610 mboxlist_allsubs(userid, addmbox_sub, list);
1611
1612 kl = dlist_list(NULL, "LSUB");
1613 for (item = list->head; item; item = item->next) {
1614 dlist_atom(kl, "MBOXNAME", item->name);
1615 }
1616 if (kl->head)
1617 sync_send_response(kl, sync_out);
1618
1619 dlist_free(&kl);
1620 sync_name_list_free(&list);
1621
1622 return 0;
1623 }
1624
1625 static int user_sieve(const char *userid)
1626 {
1627 struct sync_sieve_list *sieve_list;
1628 struct sync_sieve *sieve;
1629 struct dlist *kl;
1630
1631 sieve_list = sync_sieve_list_generate(userid);
1632
1633 if (!sieve_list) return 0;
1634
1635 for (sieve = sieve_list->head; sieve; sieve = sieve->next) {
1636 kl = dlist_new("SIEVE");
1637 dlist_atom(kl, "FILENAME", sieve->name);
1638 dlist_date(kl, "LAST_UPDATE", sieve->last_update);
1639 dlist_num(kl, "ISACTIVE", sieve->active ? 1 : 0);
1640 sync_send_response(kl, sync_out);
1641 dlist_free(&kl);
1642 }
1643
1644 sync_sieve_list_free(&sieve_list);
1645
1646 return 0;
1647 }
1648
1649 static int user_meta(const char *userid)
1650 {
1651 user_seen(userid);
1652 user_sub(userid);
1653 user_sieve(userid);
1654 return 0;
1655 }
1656
1657 static int do_getmeta(struct dlist *kin)
1658 {
1659 return user_meta(kin->sval);
1660 }
1661
1662 static int do_getuser(struct dlist *kin)
1663 {
1664 char buf[MAX_MAILBOX_PATH];
1665 int r;
1666 struct sync_name_list *quotaroots;
1667 struct sync_name *qr;
1668 const char *userid = kin->sval;
1669
1670 quotaroots = sync_name_list_create();
1671
1672 /* inbox */
1673 ((*sync_namespacep).mboxname_tointernal)(sync_namespacep, "INBOX",
1674 userid, buf);
1675 r = mailbox_cb(buf, 0, 0, quotaroots);
1676 if (r) goto bail;
1677
1678 /* deleted namespace items if enabled */
1679 if (mboxlist_delayed_delete_isenabled()) {
1680 char deletedname[MAX_MAILBOX_BUFFER];
1681 mboxname_todeleted(buf, deletedname, 0);
1682 strlcat(deletedname, ".*", sizeof(deletedname));
1683 r = (sync_namespace.mboxlist_findall)(sync_namespacep, deletedname,
1684 sync_userisadmin,
1685 userid, sync_authstate,
1686 mailbox_cb, quotaroots);
1687 if (r) goto bail;
1688 }
1689
1690 /* And then all folders */
1691 strlcat(buf, ".*", sizeof(buf));
1692 r = ((*sync_namespacep).mboxlist_findall)(sync_namespacep, buf,
1693 sync_userisadmin,
1694 userid, sync_authstate,
1695 mailbox_cb, quotaroots);
1696 if (r) goto bail;
1697
1698 for (qr = quotaroots->head; qr; qr = qr->next) {
1699 r = quota_work(qr->name);
1700 if (r) goto bail;
1701 }
1702
1703 r = user_meta(userid);
1704 if (r) goto bail;
1705
1706 sync_log_user(userid);
1707
1708 bail:
1709 sync_name_list_free(&quotaroots);
1710 return r;
1711 }
1712
1713 /* ====================================================================== */
1714
1715 static int do_unmailbox(struct dlist *kin)
1716 {
1717 const char *mboxname = kin->sval;
1718
1719 /* Delete with admin priveleges */
1720 return mboxlist_deletemailbox(mboxname, sync_userisadmin, sync_userid,
1721 sync_authstate, 0, 0, 1);
1722 }
1723
1724 static int do_rename(struct dlist *kin)
1725 {
1726 const char *oldmboxname;
1727 const char *newmboxname;
1728 const char *partition;
1729
1730 if (!dlist_getatom(kin, "OLDMBOXNAME", &oldmboxname))
1731 return IMAP_PROTOCOL_BAD_PARAMETERS;
1732 if (!dlist_getatom(kin, "NEWMBOXNAME", &newmboxname))
1733 return IMAP_PROTOCOL_BAD_PARAMETERS;
1734 if (!dlist_getatom(kin, "PARTITION", &partition))
1735 return IMAP_PROTOCOL_BAD_PARAMETERS;
1736
1737 return mboxlist_renamemailbox(oldmboxname, newmboxname, partition,
1738 1, sync_userid, sync_authstate, 1, 1);
1739 }
1740
1741 static int do_changesub(struct dlist *kin)
1742 {
1743 const char *mboxname;
1744 const char *userid;
1745 int add;
1746
1747 /* SUB or UNSUB */
1748 add = strcmp(kin->name, "SUB") ? 0 : 1;
1749
1750 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
1751 return IMAP_PROTOCOL_BAD_PARAMETERS;
1752 if (!dlist_getatom(kin, "USERID", &userid))
1753 return IMAP_PROTOCOL_BAD_PARAMETERS;
1754
1755 return mboxlist_changesub(mboxname, userid, sync_authstate, add, add);
1756 }
1757
1758 /* ====================================================================== */
1759
1760 static int do_annotation(struct dlist *kin)
1761 {
1762 struct entryattlist *entryatts = NULL;
1763 struct attvaluelist *attvalues = NULL;
1764 const char *mboxname = NULL;
1765 const char *entry = NULL;
1766 const char *value = NULL;
1767 const char *userid = NULL;
1768 char *name = NULL;
1769 int r;
1770
1771 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
1772 return IMAP_PROTOCOL_BAD_PARAMETERS;
1773 if (!dlist_getatom(kin, "ENTRY", &entry))
1774 return IMAP_PROTOCOL_BAD_PARAMETERS;
1775 if (!dlist_getatom(kin, "USERID", &userid))
1776 return IMAP_PROTOCOL_BAD_PARAMETERS;
1777 if (!dlist_getatom(kin, "VALUE", &value))
1778 return IMAP_PROTOCOL_BAD_PARAMETERS;
1779
1780 /* annotatemore_store() expects external mailbox names,
1781 so translate the separator character */
1782 name = xstrdup(mboxname);
1783 mboxname_hiersep_toexternal(sync_namespacep, name, 0);
1784
1785 appendattvalue(&attvalues, *userid ? "value.priv" : "value.shared", value);
1786 appendentryatt(&entryatts, entry, attvalues);
1787 r = annotatemore_store(name, entryatts, sync_namespacep,
1788 sync_userisadmin, userid, sync_authstate);
1789
1790 freeentryatts(entryatts);
1791 free(name);
1792
1793 return r;
1794 }
1795
1796 static int do_unannotation(struct dlist *kin)
1797 {
1798 struct entryattlist *entryatts = NULL;
1799 struct attvaluelist *attvalues = NULL;
1800 const char *mboxname = NULL;
1801 const char *entry = NULL;
1802 const char *userid = NULL;
1803 char *name = NULL;
1804 int r;
1805
1806 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
1807 return IMAP_PROTOCOL_BAD_PARAMETERS;
1808 if (!dlist_getatom(kin, "ENTRY", &entry))
1809 return IMAP_PROTOCOL_BAD_PARAMETERS;
1810 if (!dlist_getatom(kin, "USERID", &userid))
1811 return IMAP_PROTOCOL_BAD_PARAMETERS;
1812
1813 /* annotatemore_store() expects external mailbox names,
1814 so translate the separator character */
1815 name = xstrdup(mboxname);
1816 mboxname_hiersep_toexternal(sync_namespacep, name, 0);
1817
1818 appendattvalue(&attvalues, *userid ? "value.priv" : "value.shared", NULL);
1819 appendentryatt(&entryatts, entry, attvalues);
1820 r = annotatemore_store(name, entryatts, sync_namespacep,
1821 sync_userisadmin, userid, sync_authstate);
1822
1823 freeentryatts(entryatts);
1824 free(name);
1825
1826 return r;
1827 }
1828
1829 static int do_sieve(struct dlist *kin)
1830 {
1831 const char *userid;
1832 const char *filename;
1833 time_t last_update;
1834 const char *content;
1835 size_t len;
1836
1837 if (!dlist_getatom(kin, "USERID", &userid))
1838 return IMAP_PROTOCOL_BAD_PARAMETERS;
1839 if (!dlist_getatom(kin, "FILENAME", &filename))
1840 return IMAP_PROTOCOL_BAD_PARAMETERS;
1841 if (!dlist_getdate(kin, "LAST_UPDATE", &last_update))
1842 return IMAP_PROTOCOL_BAD_PARAMETERS;
1843 if (!dlist_getbuf(kin, "CONTENT", &content, &len))
1844 return IMAP_PROTOCOL_BAD_PARAMETERS;
1845
1846 return sync_sieve_upload(userid, filename, last_update, content, len);
1847 }
1848
1849 static int do_unsieve(struct dlist *kin)
1850 {
1851 const char *userid;
1852 const char *filename;
1853
1854 if (!dlist_getatom(kin, "USERID", &userid))
1855 return IMAP_PROTOCOL_BAD_PARAMETERS;
1856 if (!dlist_getatom(kin, "FILENAME", &filename))
1857 return IMAP_PROTOCOL_BAD_PARAMETERS;
1858
1859 return sync_sieve_delete(userid, filename);
1860 }
1861
1862 static int do_activate_sieve(struct dlist *kin)
1863 {
1864 const char *userid;
1865 const char *filename;
1866
1867 if (!dlist_getatom(kin, "USERID", &userid))
1868 return IMAP_PROTOCOL_BAD_PARAMETERS;
1869 if (!dlist_getatom(kin, "FILENAME", &filename))
1870 return IMAP_PROTOCOL_BAD_PARAMETERS;
1871
1872 return sync_sieve_activate(userid, filename);
1873 }
1874
1875 static int do_unactivate_sieve(struct dlist *kin)
1876 {
1877 const char *userid;
1878
1879 if (!dlist_getatom(kin, "USERID", &userid))
1880 return IMAP_PROTOCOL_BAD_PARAMETERS;
1881
1882 return sync_sieve_deactivate(userid);
1883 }
1884
1885 static int do_seen(struct dlist *kin)
1886 {
1887 int r;
1888 struct seen *seendb = NULL;
1889 struct seendata sd = SEENDATA_INITIALIZER;
1890 const char *seenuids;
1891 const char *userid;
1892 const char *uniqueid;
1893
1894 if (!dlist_getatom(kin, "USERID", &userid))
1895 return IMAP_PROTOCOL_BAD_PARAMETERS;
1896 if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
1897 return IMAP_PROTOCOL_BAD_PARAMETERS;
1898 if (!dlist_getdate(kin, "LASTREAD", &sd.lastread))
1899 return IMAP_PROTOCOL_BAD_PARAMETERS;
1900 if (!dlist_getnum(kin, "LASTUID", &sd.lastuid))
1901 return IMAP_PROTOCOL_BAD_PARAMETERS;
1902 if (!dlist_getdate(kin, "LASTCHANGE", &sd.lastchange))
1903 return IMAP_PROTOCOL_BAD_PARAMETERS;
1904 if (!dlist_getatom(kin, "SEENUIDS", &seenuids))
1905 return IMAP_PROTOCOL_BAD_PARAMETERS;
1906 sd.seenuids = xstrdup(seenuids);
1907
1908 r = seen_open(userid, SEEN_CREATE, &seendb);
1909 if (r) return r;
1910
1911 r = seen_write(seendb, uniqueid, &sd);
1912 seen_close(&seendb);
1913
1914 seen_freedata(&sd);
1915
1916 return r;
1917 }
1918
1919 static int do_unuser(struct dlist *kin)
1920 {
1921 struct sync_name_list *list = sync_name_list_create();
1922 struct sync_name *item;
1923 const char *userid = kin->sval;
1924 char buf[MAX_MAILBOX_NAME];
1925 int r = 0;
1926
1927 /* Nuke subscriptions */
1928 mboxlist_allsubs(userid, addmbox_sub, list);
1929
1930 /* ignore failures here - the subs file gets deleted soon anyway */
1931 for (item = list->head; item; item = item->next) {
1932 mboxlist_changesub(item->name, userid, sync_authstate, 0, 0);
1933 }
1934 sync_name_list_free(&list);
1935
1936 /* Nuke normal folders */
1937 list = sync_name_list_create();
1938
1939 (sync_namespacep->mboxname_tointernal)(sync_namespacep, "INBOX",
1940 userid, buf);
1941 strlcat(buf, ".*", sizeof(buf));
1942 r = (sync_namespacep->mboxlist_findall)(sync_namespacep, buf,
1943 sync_userisadmin,
1944 sync_userid, sync_authstate,
1945 addmbox, (void *)list);
1946 if (r) goto fail;
1947
1948 for (item = list->head; item; item = item->next) {
1949 r = mboxlist_deletemailbox(item->name, sync_userisadmin,
1950 sync_userid, sync_authstate, 0, 0, 1);
1951 if (r) goto fail;
1952 }
1953
1954 /* Nuke inbox (recursive nuke possible?) */
1955 (sync_namespacep->mboxname_tointernal)(sync_namespacep, "INBOX",
1956 userid, buf);
1957 r = mboxlist_deletemailbox(buf, sync_userisadmin, sync_userid,
1958 sync_authstate, 0, 0, 1);
1959 if (r && (r != IMAP_MAILBOX_NONEXISTENT)) goto fail;
1960
1961 r = user_deletedata((char *)userid, sync_userid, sync_authstate, 1);
1962
1963 fail:
1964 sync_name_list_free(&list);
1965
1966 return r;
1967 }
1968
1969 /* ====================================================================== */
1970
1971 static int do_fetchsieve(struct dlist *kin)
1972 {
1973 struct dlist *kl;
1974 const char *userid;
1975 const char *filename;
1976 uint32_t size;
1977 char *sieve;
1978
1979 if (!dlist_getatom(kin, "USERID", &userid))
1980 return IMAP_PROTOCOL_BAD_PARAMETERS;
1981 if (!dlist_getatom(kin, "FILENAME", &filename))
1982 return IMAP_PROTOCOL_BAD_PARAMETERS;
1983
1984 sieve = sync_sieve_read(userid, filename, &size);
1985 if (!sieve)
1986 return IMAP_MAILBOX_NONEXISTENT;
1987
1988 kl = dlist_new("SIEVE");
1989 dlist_atom(kl, "USERID", userid);
1990 dlist_atom(kl, "FILENAME", filename);
1991 dlist_buf(kl, "CONTENT", sieve, size);
1992 sync_send_response(kl, sync_out);
1993 dlist_free(&kl);
1994
1995 return 0;
1996 }
1997
1998 /* NOTE - can't lock a mailbox here, because it could deadlock,
1999 * so just pick the file out from under the hood */
2000 static int do_fetch(struct dlist *kin)
2001 {
2002 const char *mboxname;
2003 const char *partition;
2004 const char *guid;
2005 uint32_t uid;
2006 const char *fname;
2007 struct dlist *kl;
2008 struct message_guid tmp_guid;
2009 struct stat sbuf;
2010
2011 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
2012 return IMAP_PROTOCOL_BAD_PARAMETERS;
2013 if (!dlist_getatom(kin, "PARTITION", &partition))
2014 return IMAP_PROTOCOL_BAD_PARAMETERS;
2015 if (!dlist_getatom(kin, "GUID", &guid))
2016 return IMAP_PROTOCOL_BAD_PARAMETERS;
2017 if (!dlist_getnum(kin, "UID", &uid))
2018 return IMAP_PROTOCOL_BAD_PARAMETERS;
2019 if (!message_guid_decode(&tmp_guid, guid))
2020 return IMAP_PROTOCOL_BAD_PARAMETERS;
2021
2022 fname = mboxname_datapath(partition, mboxname, uid);
2023 if (stat(fname, &sbuf) == -1)
2024 return IMAP_MAILBOX_NONEXISTENT;
2025
2026 kl = dlist_file(NULL, "MESSAGE", partition, &tmp_guid, sbuf.st_size, fname);
2027 sync_send_response(kl, sync_out);
2028 dlist_free(&kl);
2029
2030 return 0;
2031 }
2032
2033 static int do_expunge(struct dlist *kin)
2034 {
2035 const char *mboxname;
2036 const char *uniqueid;
2037 struct dlist *ul;
2038 struct dlist *ui;
2039 struct mailbox *mailbox = NULL;
2040 struct index_record record;
2041 uint32_t recno;
2042 int r = 0;
2043
2044 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
2045 return IMAP_PROTOCOL_BAD_PARAMETERS;
2046 if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
2047 return IMAP_PROTOCOL_BAD_PARAMETERS;
2048 if (!dlist_getlist(kin, "UID", &ul))
2049 return IMAP_PROTOCOL_BAD_PARAMETERS;
2050
2051 r = mailbox_open_iwl(mboxname, &mailbox);
2052 if (r) goto done;
2053
2054 /* don't want to expunge the wrong mailbox! */
2055 if (strcmp(mailbox->uniqueid, uniqueid)) {
2056 r = IMAP_MAILBOX_MOVED;
2057 goto done;
2058 }
2059
2060 ui = ul->head;
2061
2062 for (recno = 1; recno <= mailbox->i.num_records; recno++) {
2063 r = mailbox_read_index_record(mailbox, recno, &record);
2064 if (r) goto done;
2065 if (record.system_flags & FLAG_EXPUNGED) continue;
2066 while (ui && ui->nval < record.uid) ui = ui->next;
2067 if (!ui) break; /* no point continuing */
2068 if (record.uid == ui->nval) {
2069 record.system_flags |= FLAG_EXPUNGED;
2070 record.silent = 1; /* so the next sync will succeed */
2071 r = mailbox_rewrite_index_record(mailbox, &record);
2072 if (r) goto done;
2073 }
2074 }
2075
2076 done:
2077 mailbox_close(&mailbox);
2078 return r;
2079 }
2080
2081 static int do_upload(struct dlist *kin, struct sync_reserve_list *reserve_list)
2082 {
2083 struct sync_msgid_list *part_list;
2084 struct dlist *ki;
2085 struct sync_msgid *msgid;
2086
2087 for (ki = kin->head; ki; ki = ki->next) {
2088 if (ki->type != DL_FILE)
2089 continue;
2090
2091 part_list = sync_reserve_partlist(reserve_list, ki->part);
2092 msgid = sync_msgid_lookup(part_list, &ki->gval);
2093 if (!msgid)
2094 msgid = sync_msgid_add(part_list, &ki->gval);
2095 if (!msgid->mark) {
2096 msgid->mark = 1;
2097 part_list->marked++;
2098 }
2099 }
2100
2101 return 0;
2102 }
2103
2104 static void print_response(int r)
2105 {
2106 switch (r) {
2107 case 0:
2108 prot_printf(sync_out, "OK success\r\n");
2109 break;
2110 case IMAP_INVALID_USER:
2111 prot_printf(sync_out, "NO IMAP_INVALID_USER No Such User\r\n");
2112 break;
2113 case IMAP_MAILBOX_NONEXISTENT:
2114 prot_printf(sync_out, "NO IMAP_MAILBOX_NONEXISTENT No Such Mailbox\r\n");
2115 break;
2116 case IMAP_MAILBOX_CRC:
2117 prot_printf(sync_out, "NO IMAP_MAILBOX_CRC Checksum Failure\r\n");
2118 break;
2119 case IMAP_PROTOCOL_ERROR:
2120 prot_printf(sync_out, "NO IMAP_PROTOCOL_ERROR Protocol error\r\n");
2121 break;
2122 case IMAP_PROTOCOL_BAD_PARAMETERS:
2123 prot_printf(sync_out, "NO IMAP_PROTOCOL_BAD_PARAMETERS near %s\r\n", dlist_lastkey());
2124 break;
2125 default:
2126 prot_printf(sync_out, "NO %s\r\n", error_message(r));
2127 }
2128 }
2129
2130 static void cmd_apply(struct dlist *kin, struct sync_reserve_list *reserve_list)
2131 {
2132 int r;
2133
2134 if (!strcmp(kin->name, "MESSAGE"))
2135 r = do_upload(kin, reserve_list);
2136 else if (!strcmp(kin->name, "EXPUNGE"))
2137 r = do_expunge(kin);
2138
2139 /* dump protocol */
2140 else if (!strcmp(kin->name, "ACTIVATE_SIEVE"))
2141 r = do_activate_sieve(kin);
2142 else if (!strcmp(kin->name, "ANNOTATION"))
2143 r = do_annotation(kin);
2144 else if (!strcmp(kin->name, "MAILBOX"))
2145 r = do_mailbox(kin);
2146 else if (!strcmp(kin->name, "QUOTA"))
2147 r = do_quota(kin);
2148 else if (!strcmp(kin->name, "SEEN"))
2149 r = do_seen(kin);
2150 else if (!strcmp(kin->name, "RENAME"))
2151 r = do_rename(kin);
2152 else if (!strcmp(kin->name, "RESERVE"))
2153 r = do_reserve(kin, reserve_list);
2154 else if (!strcmp(kin->name, "SIEVE"))
2155 r = do_sieve(kin);
2156 else if (!strcmp(kin->name, "SUB"))
2157 r = do_changesub(kin);
2158
2159 /* "un"dump protocol ;) */
2160 else if (!strcmp(kin->name, "UNACTIVATE_SIEVE"))
2161 r = do_unactivate_sieve(kin);
2162 else if (!strcmp(kin->name, "UNANNOTATION"))
2163 r = do_unannotation(kin);
2164 else if (!strcmp(kin->name, "UNMAILBOX"))
2165 r = do_unmailbox(kin);
2166 else if (!strcmp(kin->name, "UNQUOTA"))
2167 r = do_unquota(kin);
2168 else if (!strcmp(kin->name, "UNSIEVE"))
2169 r = do_unsieve(kin);
2170 else if (!strcmp(kin->name, "UNSUB"))
2171 r = do_changesub(kin);
2172
2173 /* user is a special case that's not paired, there's no "upload user"
2174 * as such - we just call the individual commands with their items */
2175 else if (!strcmp(kin->name, "UNUSER"))
2176 r = do_unuser(kin);
2177
2178 else
2179 r = IMAP_PROTOCOL_ERROR;
2180
2181 print_response(r);
2182 }
2183
2184 static void cmd_get(struct dlist *kin)
2185 {
2186 int r;
2187
2188 if (!strcmp(kin->name, "ANNOTATION"))
2189 r = do_getannotation(kin);
2190 else if (!strcmp(kin->name, "FETCH"))
2191 r = do_fetch(kin);
2192 else if (!strcmp(kin->name, "FETCH_SIEVE"))
2193 r = do_fetchsieve(kin);
2194 else if (!strcmp(kin->name, "FULLMAILBOX"))
2195 r = do_getfullmailbox(kin);
2196 else if (!strcmp(kin->name, "MAILBOXES"))
2197 r = do_getmailboxes(kin);
2198 else if (!strcmp(kin->name, "META"))
2199 r = do_getmeta(kin);
2200 else if (!strcmp(kin->name, "QUOTA"))
2201 r = do_getquota(kin);
2202 else if (!strcmp(kin->name, "USER"))
2203 r = do_getuser(kin);
2204 else
2205 r = IMAP_PROTOCOL_ERROR;
2206
2207 print_response(r);
2208 }
2209
0 /* sync_support.c -- Cyrus synchonization support functions
0 /* sync_support.c -- Cyrus synchronization support functions
11 *
22 * Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
33 *
477477
478478 struct sync_folder *sync_folder_list_add(struct sync_folder_list *l,
479479 const char *uniqueid, const char *name,
480 const char *part, const char *acl,
480 uint32_t mbtype,
481 const char *part, const char *acl,
481482 uint32_t options,
482483 uint32_t uidvalidity,
483484 uint32_t last_uid,
497498 l->count++;
498499
499500 result->next = NULL;
501 result->mailbox = NULL;
500502
501503 result->uniqueid = (uniqueid) ? xstrdup(uniqueid) : NULL;
502504 result->name = (name) ? xstrdup(name) : NULL;
505 result->mbtype = mbtype;
503506 result->part = (part) ? xstrdup(part) : NULL;
504507 result->acl = (acl) ? xstrdup(acl) : NULL;
505508 result->uidvalidity = uidvalidity;
13011304 prot_printf(out, "\r\n");
13021305 }
13031306
1307 static const char *sync_gentag(struct buf *tag)
1308 {
1309 static unsigned cmdcnt = 0;
1310
1311 buf_reset(tag);
1312 buf_printf(tag, "S%d", cmdcnt++);
1313 return buf_cstring(tag);
1314 }
1315
13041316 /* these are one-shot commands for get and apply, so flush the stream
13051317 * after sending */
13061318 void sync_send_apply(struct dlist *kl, struct protstream *out)
13071319 {
1320 if (out->userdata) {
1321 /* IMAP flavor (w/ tag) */
1322 prot_printf(out, "%s SYNC", sync_gentag((struct buf *) out->userdata));
1323 }
13081324 prot_printf(out, "APPLY ");
13091325 dlist_print(kl, 1, out);
13101326 prot_printf(out, "\r\n");
13131329
13141330 void sync_send_lookup(struct dlist *kl, struct protstream *out)
13151331 {
1332 if (out->userdata) {
1333 /* IMAP flavor (w/ tag) */
1334 prot_printf(out, "%s SYNC", sync_gentag((struct buf *) out->userdata));
1335 }
13161336 prot_printf(out, "GET ");
13171337 dlist_print(kl, 1, out);
13181338 prot_printf(out, "\r\n");
13361356 }
13371357
13381358 static int sync_send_file(struct mailbox *mailbox,
1359 const char *topart,
13391360 struct index_record *record,
13401361 struct sync_msgid_list *part_list,
13411362 struct dlist *kupload)
13421363 {
13431364 struct sync_msgid *msgid;
1344 char *fname;
1345 struct index_record record2;
1346 struct stat sbuf;
1347 int r;
1365 const char *fname;
13481366
13491367 /* is it already reserved? */
13501368 msgid = sync_msgid_lookup(part_list, &record->guid);
13511369 if (msgid && msgid->mark)
13521370 return 0;
13531371
1354 /* have to make sure the file exists */
1372 /* we'll trust that it exists - if not, we'll bail later,
1373 * but right now we're under locks, so be fast */
13551374 fname = mailbox_message_fname(mailbox, record->uid);
13561375 if (!fname) return IMAP_MAILBOX_BADNAME;
13571376
1358 if (stat(fname, &sbuf) < 0) {
1359 syslog(LOG_ERR, "IOERROR: failed to stat file %s", fname);
1360 return IMAP_IOERROR;
1361 }
1362
1363 if ((unsigned) sbuf.st_size != record->size) {
1364 syslog(LOG_ERR, "IOERROR: size mismatch %s %u (%lu != %u)",
1365 mailbox->name, record->uid, sbuf.st_size, record->size);
1366 return IMAP_IOERROR;
1367 }
1368
1369 /* parse the file to make sure the GUID matches */
1370 memset(&record2, 0, sizeof(struct index_record));
1371 r = message_parse(fname, &record2);
1372 if (r) return r;
1373 if (!message_guid_equal(&record->guid, &record2.guid)) {
1374 syslog(LOG_ERR, "IOERROR: GUID mismatch %s %u",
1375 mailbox->name, record->uid);
1376 return IMAP_IOERROR;
1377 }
1378
1379 dlist_file(kupload, "MESSAGE", mailbox->part,
1380 &record->guid, record->size, fname);
1377 dlist_file(kupload, "MESSAGE", topart, &record->guid, record->size, fname);
13811378
13821379 return 0;
13831380 }
13841381
1385 int sync_mailbox(struct mailbox *mailbox,
1386 struct sync_folder *remote,
1387 struct sync_msgid_list *part_list,
1388 struct dlist *kl, struct dlist *kupload,
1389 int printrecords)
1390 {
1382 static int sync_mailbox(struct mailbox *mailbox,
1383 struct sync_folder *remote,
1384 const char *topart,
1385 struct sync_msgid_list *part_list,
1386 struct dlist *kl, struct dlist *kupload,
1387 int printrecords)
1388 {
1389 if (!topart) topart = mailbox->part;
13911390
13921391 dlist_atom(kl, "UNIQUEID", mailbox->uniqueid);
13931392 dlist_atom(kl, "MBOXNAME", mailbox->name);
1393 if (mailbox->mbtype)
1394 dlist_atom(kl, "MBOXTYPE", mboxlist_mbtype_to_string(mailbox->mbtype));
13941395 dlist_num(kl, "LAST_UID", mailbox->i.last_uid);
13951396 dlist_modseq(kl, "HIGHESTMODSEQ", mailbox->i.highestmodseq);
13961397 dlist_num(kl, "RECENTUID", mailbox->i.recentuid);
13981399 dlist_date(kl, "LAST_APPENDDATE", mailbox->i.last_appenddate);
13991400 dlist_date(kl, "POP3_LAST_LOGIN", mailbox->i.pop3_last_login);
14001401 dlist_num(kl, "UIDVALIDITY", mailbox->i.uidvalidity);
1401 dlist_atom(kl, "PARTITION", mailbox->part);
1402 dlist_atom(kl, "PARTITION", topart);
14021403 dlist_atom(kl, "ACL", mailbox->acl);
14031404 dlist_atom(kl, "OPTIONS", sync_encode_options(mailbox->i.options));
14041405 dlist_num(kl, "SYNC_CRC", mailbox->i.sync_crc);
14481449 send_file = 0;
14491450
14501451 if (send_file) {
1451 int r = sync_send_file(mailbox, &record, part_list, kupload);
1452 int r = sync_send_file(mailbox, topart,
1453 &record, part_list, kupload);
14521454 if (r) return r;
14531455 }
14541456
14741476 struct dlist *kl = NULL;
14751477 int c;
14761478
1477 if ((c = getword(in, &response)) == EOF)
1478 return IMAP_PROTOCOL_ERROR;
1479 if ((c = getword(in, &response)) == EOF) {
1480 syslog(LOG_ERR, "IOERROR: zero length response to %s (%s)",
1481 cmd, prot_error(in));
1482 return IMAP_PROTOCOL_ERROR;
1483 }
14791484
14801485 if (c != ' ') goto parse_err;
14811486
14881493 goto parse_err;
14891494 }
14901495
1496 if (in->userdata) {
1497 /* check IMAP response tag */
1498 if (strcmp(response.s, buf_cstring((struct buf *) in->userdata)))
1499 goto parse_err;
1500
1501 /* first word was IMAP response tag - get response token */
1502 if ((c = getword(in, &response)) == EOF)
1503 return IMAP_PROTOCOL_ERROR;
1504
1505 if (c != ' ') goto parse_err;
1506 }
1507
14911508 if (!strcmp(response.s, "OK")) {
14921509 if (klp) *klp = kl;
14931510 else dlist_free(&kl);
15011518
15021519 /* Slight hack to transform certain error strings into equivalent
15031520 * imap_err value so that caller has some idea of cause. Match
1504 * this to the logic at print_response in sync_server */
1505 if (!strncmp(errmsg.s, "IMAP_INVALID_USER ",
1506 strlen("IMAP_INVALID_USER ")))
1521 * this to the logic at sync_print_response() */
1522 if (!strncmp(errmsg.s, "[IMAP_INVALID_USER] ",
1523 strlen("[IMAP_INVALID_USER] ")))
15071524 return IMAP_INVALID_USER;
1508 else if (!strncmp(errmsg.s, "IMAP_MAILBOX_NONEXISTENT ",
1509 strlen("IMAP_MAILBOX_NONEXISTENT ")))
1525 else if (!strncmp(errmsg.s, "[IMAP_MAILBOX_NONEXISTENT] ",
1526 strlen("[IMAP_MAILBOX_NONEXISTENT] ")))
15101527 return IMAP_MAILBOX_NONEXISTENT;
1511 else if (!strncmp(errmsg.s, "IMAP_MAILBOX_CRC ",
1512 strlen("IMAP_MAILBOX_CRC ")))
1528 else if (!strncmp(errmsg.s, "[IMAP_MAILBOX_CRC] ",
1529 strlen("[IMAP_MAILBOX_CRC] ")))
15131530 return IMAP_MAILBOX_CRC;
1514 else if (!strncmp(errmsg.s, "IMAP_PROTOCOL_ERROR ",
1515 strlen("IMAP_PROTOCOL_ERROR ")))
1531 else if (!strncmp(errmsg.s, "[IMAP_PROTOCOL_ERROR] ",
1532 strlen("[IMAP_PROTOCOL_ERROR] ")))
15161533 return IMAP_PROTOCOL_ERROR;
1517 else if (!strncmp(errmsg.s, "IMAP_PROTOCOL_BAD_PARAMETERS ",
1518 strlen("IMAP_PROTOCOL_BAD_PARAMETERS ")))
1534 else if (!strncmp(errmsg.s, "[IMAP_PROTOCOL_BAD_PARAMETERS] ",
1535 strlen("[IMAP_PROTOCOL_BAD_PARAMETERS] ")))
15191536 return IMAP_PROTOCOL_BAD_PARAMETERS;
15201537 else
15211538 return IMAP_REMOTE_DENIED;
15241541 parse_err:
15251542 dlist_free(&kl);
15261543 sync_getline(in, &errmsg);
1527 syslog(LOG_ERR, "%s received %s response: %s",
1528 cmd, response.s, errmsg.s);
1544 syslog(LOG_ERR, "IOERROR: %s received %s response: %s",
1545 cmd, response.s, errmsg.s);
15291546 return IMAP_PROTOCOL_ERROR;
15301547 }
15311548
15751592 return mailbox_append_index_record(mailbox, record);
15761593 }
15771594
1595
1596 /* ======================= server-side sync =========================== */
1597
1598
1599 static void reserve_folder(const char *part, const char *mboxname,
1600 struct sync_msgid_list *part_list)
1601 {
1602 struct mailbox *mailbox = NULL;
1603 struct index_record record;
1604 struct index_record record2;
1605 int r;
1606 struct sync_msgid *item;
1607 const char *mailbox_msg_path, *stage_msg_path;
1608 uint32_t recno;
1609
1610 /* Open and lock mailbox */
1611 r = mailbox_open_irl(mboxname, &mailbox);
1612
1613 if (r) return;
1614
1615 for (recno = 1;
1616 part_list->marked < part_list->count && recno <= mailbox->i.num_records;
1617 recno++) {
1618 if (mailbox_read_index_record(mailbox, recno, &record))
1619 continue;
1620
1621 if (record.system_flags & FLAG_UNLINKED)
1622 continue;
1623
1624 item = sync_msgid_lookup(part_list, &record.guid);
1625 if (!item || item->mark)
1626 continue;
1627
1628 /* Attempt to reserve this message */
1629 mailbox_msg_path = mailbox_message_fname(mailbox, record.uid);
1630 stage_msg_path = dlist_reserve_path(part, &record.guid);
1631
1632 /* check that the sha1 of the file on disk is correct */
1633 memset(&record2, 0, sizeof(struct index_record));
1634 r = message_parse(mailbox_msg_path, &record2);
1635 if (r) {
1636 syslog(LOG_ERR, "IOERROR: Unable to parse %s",
1637 mailbox_msg_path);
1638 continue;
1639 }
1640 if (!message_guid_equal(&record.guid, &record2.guid)) {
1641 syslog(LOG_ERR, "IOERROR: GUID mismatch on parse for %s",
1642 mailbox_msg_path);
1643 continue;
1644 }
1645
1646 if (mailbox_copyfile(mailbox_msg_path, stage_msg_path, 0) != 0) {
1647 syslog(LOG_ERR, "IOERROR: Unable to link %s -> %s: %m",
1648 mailbox_msg_path, stage_msg_path);
1649 continue;
1650 }
1651
1652 item->mark = 1;
1653 part_list->marked++;
1654 }
1655
1656 mailbox_close(&mailbox);
1657 }
1658
1659 int sync_apply_reserve(struct dlist *kl,
1660 struct sync_reserve_list *reserve_list,
1661 struct sync_state *sstate)
1662 {
1663 struct message_guid tmp_guid;
1664 struct sync_name_list *missing = sync_name_list_create();
1665 struct sync_name_list *folder_names = sync_name_list_create();
1666 struct sync_msgid_list *part_list;
1667 struct sync_msgid *item;
1668 struct sync_name *folder;
1669 struct mboxlist_entry mbentry;
1670 const char *partition = NULL;
1671 struct dlist *ml;
1672 struct dlist *gl;
1673 struct dlist *i;
1674 struct dlist *kout;
1675
1676 if (!dlist_getatom(kl, "PARTITION", &partition)) goto parse_err;
1677 if (!dlist_getlist(kl, "MBOXNAME", &ml)) goto parse_err;
1678 if (!dlist_getlist(kl, "GUID", &gl)) goto parse_err;
1679
1680 part_list = sync_reserve_partlist(reserve_list, partition);
1681 for (i = gl->head; i; i = i->next) {
1682 if (!i->sval || !message_guid_decode(&tmp_guid, i->sval))
1683 goto parse_err;
1684 sync_msgid_add(part_list, &tmp_guid);
1685 }
1686
1687 /* need a list so we can mark items */
1688 for (i = ml->head; i; i = i->next) {
1689 sync_name_list_add(folder_names, i->sval);
1690 }
1691
1692 for (folder = folder_names->head;
1693 part_list->marked < part_list->count && folder;
1694 folder = folder->next) {
1695 if (mboxlist_lookup(folder->name, &mbentry, 0) ||
1696 strcmp(mbentry.partition, partition))
1697 continue; /* try folders on the same partition first! */
1698 reserve_folder(partition, folder->name, part_list);
1699 folder->mark = 1;
1700 }
1701
1702 /* if we have other folders, check them now */
1703 for (folder = folder_names->head;
1704 part_list->marked < part_list->count && folder;
1705 folder = folder->next) {
1706 if (folder->mark)
1707 continue;
1708 reserve_folder(partition, folder->name, part_list);
1709 }
1710
1711 /* check if we missed any */
1712 kout = dlist_list(NULL, "MISSING");
1713 for (i = gl->head; i; i = i->next) {
1714 if (!message_guid_decode(&tmp_guid, i->sval))
1715 continue;
1716 item = sync_msgid_lookup(part_list, &tmp_guid);
1717 if (item && !item->mark)
1718 dlist_atom(kout, "GUID", i->sval);
1719 }
1720 if (kout->head)
1721 sync_send_response(kout, sstate->pout);
1722 dlist_free(&kout);
1723
1724 sync_name_list_free(&folder_names);
1725 sync_name_list_free(&missing);
1726
1727 return 0;
1728
1729 parse_err:
1730 sync_name_list_free(&folder_names);
1731 sync_name_list_free(&missing);
1732
1733 return IMAP_PROTOCOL_BAD_PARAMETERS;
1734 }
1735
1736 /* ====================================================================== */
1737
1738 int sync_apply_unquota(struct dlist *kin,
1739 struct sync_state *sstate __attribute__((unused)))
1740 {
1741 return mboxlist_unsetquota(kin->sval);
1742 }
1743
1744 int sync_apply_quota(struct dlist *kin,
1745 struct sync_state *sstate __attribute__((unused)))
1746 {
1747 const char *root;
1748 uint32_t limit;
1749
1750 if (!dlist_getatom(kin, "ROOT", &root))
1751 return IMAP_PROTOCOL_BAD_PARAMETERS;
1752 if (!dlist_getnum(kin, "LIMIT", &limit))
1753 return IMAP_PROTOCOL_BAD_PARAMETERS;
1754
1755 return mboxlist_setquota(root, limit, 1);
1756 }
1757
1758 /* ====================================================================== */
1759
1760 static int mailbox_compare_update(struct mailbox *mailbox,
1761 struct dlist *kr, int doupdate)
1762 {
1763 struct index_record mrecord;
1764 struct index_record rrecord;
1765 uint32_t recno = 1;
1766 struct dlist *ki;
1767 int r;
1768 int i;
1769
1770 rrecord.uid = 0;
1771 for (ki = kr->head; ki; ki = ki->next) {
1772 r = parse_upload(ki, mailbox, &mrecord);
1773 if (r) {
1774 syslog(LOG_ERR, "Failed to parse uploaded record");
1775 return r;
1776 }
1777
1778 while (rrecord.uid < mrecord.uid) {
1779 /* hit the end? Magic marker */
1780 if (recno > mailbox->i.num_records) {
1781 rrecord.uid = UINT32_MAX;
1782 break;
1783 }
1784
1785 /* read another record */
1786 r = mailbox_read_index_record(mailbox, recno, &rrecord);
1787 if (r) {
1788 syslog(LOG_ERR, "Failed to read record %s %d",
1789 mailbox->name, recno);
1790 return r;
1791 }
1792 recno++;
1793 }
1794
1795 /* found a match, check for updates */
1796 if (rrecord.uid == mrecord.uid) {
1797 /* GUID mismatch on a non-expunged record is an error straight away */
1798 if (!(mrecord.system_flags & FLAG_EXPUNGED)) {
1799 if (!message_guid_equal(&mrecord.guid, &rrecord.guid)) {
1800 syslog(LOG_ERR, "SYNCERROR: guid mismatch %s %u",
1801 mailbox->name, mrecord.uid);
1802 return IMAP_MAILBOX_CRC;
1803 }
1804 if (rrecord.system_flags & FLAG_EXPUNGED) {
1805 syslog(LOG_ERR, "SYNCERROR: expunged on replica %s %u",
1806 mailbox->name, mrecord.uid);
1807 return IMAP_MAILBOX_CRC;
1808 }
1809 }
1810 /* higher modseq on the replica is an error */
1811 if (rrecord.modseq > mrecord.modseq) {
1812 syslog(LOG_ERR, "SYNCERROR: higher modseq on replica %s %u",
1813 mailbox->name, mrecord.uid);
1814 return IMAP_MAILBOX_CRC;
1815 }
1816
1817 /* skip out on the first pass */
1818 if (!doupdate) continue;
1819
1820 rrecord.modseq = mrecord.modseq;
1821 rrecord.last_updated = mrecord.last_updated;
1822 rrecord.internaldate = mrecord.internaldate;
1823 rrecord.system_flags = (mrecord.system_flags & ~FLAG_UNLINKED) |
1824 (rrecord.system_flags & FLAG_UNLINKED);
1825 for (i = 0; i < MAX_USER_FLAGS/32; i++)
1826 rrecord.user_flags[i] = mrecord.user_flags[i];
1827 rrecord.silent = 1;
1828 r = mailbox_rewrite_index_record(mailbox, &rrecord);
1829 if (r) {
1830 syslog(LOG_ERR, "IOERROR: failed to rewrite record %s %d",
1831 mailbox->name, recno);
1832 return r;
1833 }
1834 }
1835
1836 /* not found and less than LAST_UID, bogus */
1837 else if (mrecord.uid <= mailbox->i.last_uid) {
1838 /* Expunged, just skip it */
1839 if (!(mrecord.system_flags & FLAG_EXPUNGED))
1840 return IMAP_MAILBOX_CRC;
1841 }
1842
1843 /* after LAST_UID, it's an append, that's OK */
1844 else {
1845 /* skip out on the first pass */
1846 if (!doupdate) continue;
1847
1848 mrecord.silent = 1;
1849 r = sync_append_copyfile(mailbox, &mrecord);
1850 if (r) {
1851 syslog(LOG_ERR, "IOERROR: failed to append file %s %d",
1852 mailbox->name, recno);
1853 return r;
1854 }
1855 }
1856 }
1857
1858 return 0;
1859 }
1860
1861 int sync_apply_mailbox(struct dlist *kin, struct sync_state *sstate)
1862 {
1863 /* fields from the request */
1864 const char *uniqueid;
1865 const char *partition;
1866 const char *mboxname;
1867 const char *mboxtype = NULL; /* optional */
1868 uint32_t mbtype;
1869 uint32_t last_uid;
1870 modseq_t highestmodseq;
1871 uint32_t recentuid;
1872 time_t recenttime;
1873 time_t last_appenddate;
1874 time_t pop3_last_login;
1875 uint32_t uidvalidity;
1876 const char *acl;
1877 const char *options_str;
1878 uint32_t sync_crc;
1879
1880 uint32_t options;
1881
1882 struct mailbox *mailbox = NULL;
1883 uint32_t newcrc;
1884 struct dlist *kr;
1885 int r;
1886
1887 if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
1888 return IMAP_PROTOCOL_BAD_PARAMETERS;
1889 if (!dlist_getatom(kin, "PARTITION", &partition))
1890 return IMAP_PROTOCOL_BAD_PARAMETERS;
1891 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
1892 return IMAP_PROTOCOL_BAD_PARAMETERS;
1893 if (!dlist_getnum(kin, "LAST_UID", &last_uid))
1894 return IMAP_PROTOCOL_BAD_PARAMETERS;
1895 if (!dlist_getmodseq(kin, "HIGHESTMODSEQ", &highestmodseq))
1896 return IMAP_PROTOCOL_BAD_PARAMETERS;
1897 if (!dlist_getnum(kin, "RECENTUID", &recentuid))
1898 return IMAP_PROTOCOL_BAD_PARAMETERS;
1899 if (!dlist_getdate(kin, "RECENTTIME", &recenttime))
1900 return IMAP_PROTOCOL_BAD_PARAMETERS;
1901 if (!dlist_getdate(kin, "LAST_APPENDDATE", &last_appenddate))
1902 return IMAP_PROTOCOL_BAD_PARAMETERS;
1903 if (!dlist_getdate(kin, "POP3_LAST_LOGIN", &pop3_last_login))
1904 return IMAP_PROTOCOL_BAD_PARAMETERS;
1905 if (!dlist_getnum(kin, "SYNC_CRC", &sync_crc))
1906 return IMAP_PROTOCOL_BAD_PARAMETERS;
1907 if (!dlist_getnum(kin, "UIDVALIDITY", &uidvalidity))
1908 return IMAP_PROTOCOL_BAD_PARAMETERS;
1909 if (!dlist_getatom(kin, "ACL", &acl))
1910 return IMAP_PROTOCOL_BAD_PARAMETERS;
1911 if (!dlist_getatom(kin, "OPTIONS", &options_str))
1912 return IMAP_PROTOCOL_BAD_PARAMETERS;
1913 if (!dlist_getlist(kin, "RECORD", &kr))
1914 return IMAP_PROTOCOL_BAD_PARAMETERS;
1915 /* optional */
1916 dlist_getatom(kin, "MBOXTYPE", &mboxtype);
1917
1918 options = sync_parse_options(options_str);
1919
1920 mbtype = mboxlist_string_to_mbtype(mboxtype);
1921
1922 r = mailbox_open_iwl(mboxname, &mailbox);
1923 if (r == IMAP_MAILBOX_NONEXISTENT) {
1924 r = mboxlist_createsync(mboxname, mbtype, partition,
1925 sstate->userid, sstate->authstate,
1926 options, uidvalidity, acl, uniqueid,
1927 sstate->local_only, &mailbox);
1928 }
1929 if (r) {
1930 syslog(LOG_ERR, "Failed to open mailbox %s to update", mboxname);
1931 return r;
1932 }
1933
1934 if (mailbox->mbtype != (int) mbtype) {
1935 /* is this even possible ? */
1936 syslog(LOG_ERR, "Invalid Mailbox Type %s (%d %d)",
1937 mailbox->name, mailbox->mbtype, mbtype);
1938 mailbox_close(&mailbox);
1939 return IMAP_MAILBOX_BADTYPE;
1940 }
1941
1942 if (strcmp(mailbox->uniqueid, uniqueid)) {
1943 syslog(LOG_ERR, "Mailbox uniqueid changed %s - retry", mboxname);
1944 mailbox_close(&mailbox);
1945 return IMAP_MAILBOX_MOVED;
1946 }
1947
1948 /* skip out now, it's going to mismatch for sure! */
1949 if (highestmodseq < mailbox->i.highestmodseq) {
1950 syslog(LOG_ERR, "higher modseq on replica %s - "
1951 MODSEQ_FMT " < " MODSEQ_FMT,
1952 mboxname, highestmodseq, mailbox->i.highestmodseq);
1953 mailbox_close(&mailbox);
1954 return IMAP_MAILBOX_CRC;
1955 }
1956
1957 if (last_uid < mailbox->i.last_uid) {
1958 syslog(LOG_ERR, "higher last_uid on replica %s - %u < %u",
1959 mboxname, last_uid, mailbox->i.last_uid);
1960 mailbox_close(&mailbox);
1961 return IMAP_MAILBOX_CRC;
1962 }
1963
1964 if (strcmp(mailbox->acl, acl)) {
1965 mailbox_set_acl(mailbox, acl, 0);
1966 r = mboxlist_sync_setacls(mboxname, acl);
1967 if (r) {
1968 mailbox_close(&mailbox);
1969 return r;
1970 }
1971 }
1972
1973 r = mailbox_compare_update(mailbox, kr, 0);
1974 if (r) {
1975 mailbox_close(&mailbox);
1976 return r;
1977 }
1978
1979 /* now we're committed to writing something no matter what happens! */
1980
1981 r = mailbox_compare_update(mailbox, kr, 1);
1982 if (r) {
1983 abort();
1984 return r;
1985 }
1986
1987 mailbox_index_dirty(mailbox);
1988 assert(mailbox->i.last_uid <= last_uid);
1989 mailbox->i.last_uid = last_uid;
1990 mailbox->i.highestmodseq = highestmodseq;
1991 mailbox->i.recentuid = recentuid;
1992 mailbox->i.recenttime = recenttime;
1993 mailbox->i.last_appenddate = last_appenddate;
1994 mailbox->i.pop3_last_login = pop3_last_login;
1995 /* mailbox->i.options = options; ... not really, there's unsyncable stuff in here */
1996
1997 if (mailbox->i.uidvalidity < uidvalidity) {
1998 syslog(LOG_ERR, "%s uidvalidity higher on master, updating %u => %u",
1999 mailbox->name, mailbox->i.uidvalidity, uidvalidity);
2000 mailbox->i.uidvalidity = uidvalidity;
2001 }
2002
2003 /* try re-calculating the CRC on mismatch... */
2004 if (mailbox->i.sync_crc != sync_crc) {
2005 mailbox_index_recalc(mailbox);
2006 }
2007 newcrc = mailbox->i.sync_crc;
2008 mailbox_close(&mailbox);
2009
2010 /* check return value */
2011 if (r) return r;
2012 if (newcrc != sync_crc)
2013 return IMAP_MAILBOX_CRC;
2014 return 0;
2015 }
2016
2017 /* ====================================================================== */
2018
2019 static int getannotation_cb(const char *mailbox,
2020 const char *entry, const char *userid,
2021 struct annotation_data *attrib,
2022 void *rock)
2023 {
2024 struct protstream *pout = (struct protstream *) rock;
2025 struct dlist *kl;
2026
2027 kl = dlist_new("ANNOTATION");
2028 dlist_atom(kl, "MBOXNAME", mailbox);
2029 dlist_atom(kl, "ENTRY", entry);
2030 dlist_atom(kl, "USERID", userid);
2031 dlist_atom(kl, "VALUE", attrib->value);
2032 sync_send_response(kl, pout);
2033 dlist_free(&kl);
2034
2035 return 0;
2036 }
2037
2038 int sync_get_annotation(struct dlist *kin, struct sync_state *sstate)
2039 {
2040 const char *mboxname = kin->sval;
2041 return annotatemore_findall(mboxname, "*", &getannotation_cb,
2042 (void *) sstate->pout, NULL);
2043 }
2044
2045 static void print_quota(struct quota *q, struct protstream *pout)
2046 {
2047 struct dlist *kl;
2048
2049 kl = dlist_new("QUOTA");
2050 dlist_atom(kl, "ROOT", q->root);
2051 dlist_num(kl, "LIMIT", q->limit);
2052 sync_send_response(kl, pout);
2053 dlist_free(&kl);
2054 }
2055
2056 static int quota_work(const char *root, struct protstream *pout)
2057 {
2058 struct quota q;
2059
2060 q.root = root;
2061 if (!quota_read(&q, NULL, 0)) print_quota(&q, pout);
2062
2063 return 0;
2064 }
2065
2066 int sync_get_quota(struct dlist *kin, struct sync_state *sstate)
2067 {
2068 return quota_work(kin->sval, sstate->pout);
2069 }
2070
2071 struct mbox_rock {
2072 struct protstream *pout;
2073 struct sync_name_list *qrl;
2074 };
2075
2076 static int mailbox_cb(char *name,
2077 int matchlen __attribute__((unused)),
2078 int maycreate __attribute__((unused)),
2079 void *rock)
2080 {
2081 struct mbox_rock *mrock = (struct mbox_rock *) rock;
2082 struct sync_name_list *qrl = mrock->qrl;
2083 struct mailbox *mailbox = NULL;
2084 struct dlist *kl = dlist_kvlist(NULL, "MAILBOX");
2085 int r;
2086
2087 r = mailbox_open_irl(name, &mailbox);
2088 /* doesn't exist? Probably not finished creating or removing yet */
2089 if (r == IMAP_MAILBOX_NONEXISTENT) return 0;
2090 if (r == IMAP_MAILBOX_RESERVED) return 0;
2091 if (r) return r;
2092
2093 if (qrl && mailbox->quotaroot &&
2094 !sync_name_lookup(qrl, mailbox->quotaroot))
2095 sync_name_list_add(qrl, mailbox->quotaroot);
2096
2097 r = sync_mailbox(mailbox, NULL, NULL, NULL, kl, NULL, 0);
2098 if (!r) sync_send_response(kl, mrock->pout);
2099 dlist_free(&kl);
2100 mailbox_close(&mailbox);
2101
2102 return r;
2103 }
2104
2105 int sync_get_fullmailbox(struct dlist *kin, struct sync_state *sstate)
2106 {
2107 struct mailbox *mailbox = NULL;
2108 struct dlist *kl = dlist_kvlist(NULL, "MAILBOX");
2109 int r;
2110
2111 r = mailbox_open_irl(kin->sval, &mailbox);
2112 if (r) return r;
2113
2114 r = sync_mailbox(mailbox, NULL, NULL, NULL, kl, NULL, 1);
2115 if (!r) sync_send_response(kl, sstate->pout);
2116 dlist_free(&kl);
2117 mailbox_close(&mailbox);
2118
2119 return r;
2120 }
2121
2122 int sync_get_mailboxes(struct dlist *kin, struct sync_state *sstate)
2123 {
2124 struct dlist *ki;
2125 struct mbox_rock mrock = { sstate->pout, NULL };
2126
2127 for (ki = kin->head; ki; ki = ki->next)
2128 mailbox_cb(ki->sval, 0, 0, &mrock);
2129
2130 return 0;
2131 }
2132
2133 /* ====================================================================== */
2134
2135 static int print_seen(const char *uniqueid, struct seendata *sd, void *rock)
2136 {
2137 struct dlist *kl;
2138 struct protstream *pout = (struct protstream *) rock;
2139
2140 kl = dlist_new("SEEN");
2141 dlist_atom(kl, "UNIQUEID", uniqueid);
2142 dlist_date(kl, "LASTREAD", sd->lastread);
2143 dlist_num(kl, "LASTUID", sd->lastuid);
2144 dlist_date(kl, "LASTCHANGE", sd->lastchange);
2145 dlist_atom(kl, "SEENUIDS", sd->seenuids);
2146 sync_send_response(kl, pout);
2147 dlist_free(&kl);
2148
2149 return 0;
2150 }
2151
2152 static int user_getseen(const char *userid, struct protstream *pout)
2153 {
2154 struct seen *seendb = NULL;
2155
2156 /* no SEEN DB is OK, just return */
2157 if (seen_open(userid, SEEN_SILENT, &seendb))
2158 return 0;
2159
2160 seen_foreach(seendb, print_seen, pout);
2161 seen_close(&seendb);
2162
2163 return 0;
2164 }
2165
2166
2167 static int user_getsub(const char *userid, struct protstream *pout)
2168 {
2169 struct sync_name_list *list = sync_name_list_create();
2170 struct sync_name *item;
2171 struct dlist *kl;
2172
2173 mboxlist_allsubs(userid, addmbox_sub, list);
2174
2175 kl = dlist_list(NULL, "LSUB");
2176 for (item = list->head; item; item = item->next) {
2177 dlist_atom(kl, "MBOXNAME", item->name);
2178 }
2179 if (kl->head)
2180 sync_send_response(kl, pout);
2181
2182 dlist_free(&kl);
2183 sync_name_list_free(&list);
2184
2185 return 0;
2186 }
2187
2188 static int user_getsieve(const char *userid, struct protstream *pout)
2189 {
2190 struct sync_sieve_list *sieve_list;
2191 struct sync_sieve *sieve;
2192 struct dlist *kl;
2193
2194 sieve_list = sync_sieve_list_generate(userid);
2195
2196 if (!sieve_list) return 0;
2197
2198 for (sieve = sieve_list->head; sieve; sieve = sieve->next) {
2199 kl = dlist_new("SIEVE");
2200 dlist_atom(kl, "FILENAME", sieve->name);
2201 dlist_date(kl, "LAST_UPDATE", sieve->last_update);
2202 dlist_num(kl, "ISACTIVE", sieve->active ? 1 : 0);
2203 sync_send_response(kl, pout);
2204 dlist_free(&kl);
2205 }
2206
2207 sync_sieve_list_free(&sieve_list);
2208
2209 return 0;
2210 }
2211
2212 static int user_meta(const char *userid, struct protstream *pout)
2213 {
2214 user_getseen(userid, pout);
2215 user_getsub(userid, pout);
2216 user_getsieve(userid, pout);
2217 return 0;
2218 }
2219
2220 int sync_get_meta(struct dlist *kin, struct sync_state *sstate)
2221 {
2222 return user_meta(kin->sval, sstate->pout);
2223 }
2224
2225 int sync_get_user(struct dlist *kin, struct sync_state *sstate)
2226 {
2227 char buf[MAX_MAILBOX_PATH];
2228 int r;
2229 struct sync_name_list *quotaroots;
2230 struct sync_name *qr;
2231 const char *userid = kin->sval;
2232 struct mbox_rock mrock;
2233
2234 quotaroots = sync_name_list_create();
2235 mrock.qrl = quotaroots;
2236 mrock.pout = sstate->pout;
2237
2238 /* inbox */
2239 (*sstate->namespace->mboxname_tointernal)(sstate->namespace, "INBOX",
2240 userid, buf);
2241 r = mailbox_cb(buf, 0, 0, &mrock);
2242 if (r) goto bail;
2243
2244 /* deleted namespace items if enabled */
2245 if (mboxlist_delayed_delete_isenabled()) {
2246 char deletedname[MAX_MAILBOX_BUFFER];
2247 mboxname_todeleted(buf, deletedname, 0);
2248 strlcat(deletedname, ".*", sizeof(deletedname));
2249 r = (*sstate->namespace->mboxlist_findall)(sstate->namespace,
2250 deletedname,
2251 sstate->userisadmin,
2252 userid, sstate->authstate,
2253 mailbox_cb, &mrock);
2254 if (r) goto bail;
2255 }
2256
2257 /* And then all folders */
2258 strlcat(buf, ".*", sizeof(buf));
2259 r = (*sstate->namespace->mboxlist_findall)(sstate->namespace, buf,
2260 sstate->userisadmin,
2261 userid, sstate->authstate,
2262 mailbox_cb, &mrock);
2263 if (r) goto bail;
2264
2265 for (qr = quotaroots->head; qr; qr = qr->next) {
2266 r = quota_work(qr->name, sstate->pout);
2267 if (r) goto bail;
2268 }
2269
2270 r = user_meta(userid, sstate->pout);
2271 if (r) goto bail;
2272
2273 sync_log_user(userid);
2274
2275 bail:
2276 sync_name_list_free(&quotaroots);
2277 return r;
2278 }
2279
2280 /* ====================================================================== */
2281
2282 int sync_apply_unmailbox(struct dlist *kin, struct sync_state *sstate)
2283 {
2284 const char *mboxname = kin->sval;
2285
2286 /* Delete with admin priveleges */
2287 return mboxlist_deletemailbox(mboxname, sstate->userisadmin, sstate->userid,
2288 sstate->authstate, 0, sstate->local_only, 1);
2289 }
2290
2291 int sync_apply_rename(struct dlist *kin, struct sync_state *sstate)
2292 {
2293 const char *oldmboxname;
2294 const char *newmboxname;
2295 const char *partition;
2296
2297 if (!dlist_getatom(kin, "OLDMBOXNAME", &oldmboxname))
2298 return IMAP_PROTOCOL_BAD_PARAMETERS;
2299 if (!dlist_getatom(kin, "NEWMBOXNAME", &newmboxname))
2300 return IMAP_PROTOCOL_BAD_PARAMETERS;
2301 if (!dlist_getatom(kin, "PARTITION", &partition))
2302 return IMAP_PROTOCOL_BAD_PARAMETERS;
2303
2304 return mboxlist_renamemailbox(oldmboxname, newmboxname, partition,
2305 1, sstate->userid, sstate->authstate,
2306 sstate->local_only, 1, 1);
2307 }
2308
2309 int sync_apply_changesub(struct dlist *kin, struct sync_state *sstate)
2310 {
2311 const char *mboxname;
2312 const char *userid;
2313 int add;
2314
2315 /* SUB or UNSUB */
2316 add = strcmp(kin->name, "SUB") ? 0 : 1;
2317
2318 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
2319 return IMAP_PROTOCOL_BAD_PARAMETERS;
2320 if (!dlist_getatom(kin, "USERID", &userid))
2321 return IMAP_PROTOCOL_BAD_PARAMETERS;
2322
2323 return mboxlist_changesub(mboxname, userid, sstate->authstate, add, add);
2324 }
2325
2326 /* ====================================================================== */
2327
2328 int sync_apply_annotation(struct dlist *kin, struct sync_state *sstate)
2329 {
2330 struct entryattlist *entryatts = NULL;
2331 struct attvaluelist *attvalues = NULL;
2332 const char *mboxname = NULL;
2333 const char *entry = NULL;
2334 const char *value = NULL;
2335 const char *userid = NULL;
2336 char *name = NULL;
2337 int r;
2338
2339 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
2340 return IMAP_PROTOCOL_BAD_PARAMETERS;
2341 if (!dlist_getatom(kin, "ENTRY", &entry))
2342 return IMAP_PROTOCOL_BAD_PARAMETERS;
2343 if (!dlist_getatom(kin, "USERID", &userid))
2344 return IMAP_PROTOCOL_BAD_PARAMETERS;
2345 if (!dlist_getatom(kin, "VALUE", &value))
2346 return IMAP_PROTOCOL_BAD_PARAMETERS;
2347
2348 /* annotatemore_store() expects external mailbox names,
2349 so translate the separator character */
2350 name = xstrdup(mboxname);
2351 mboxname_hiersep_toexternal(sstate->namespace, name, 0);
2352
2353 appendattvalue(&attvalues, *userid ? "value.priv" : "value.shared", value);
2354 appendentryatt(&entryatts, entry, attvalues);
2355 r = annotatemore_store(name, entryatts, sstate->namespace,
2356 sstate->userisadmin, userid, sstate->authstate);
2357
2358 freeentryatts(entryatts);
2359 free(name);
2360
2361 return r;
2362 }
2363
2364 int sync_apply_unannotation(struct dlist *kin, struct sync_state *sstate)
2365 {
2366 struct entryattlist *entryatts = NULL;
2367 struct attvaluelist *attvalues = NULL;
2368 const char *mboxname = NULL;
2369 const char *entry = NULL;
2370 const char *userid = NULL;
2371 char *name = NULL;
2372 int r;
2373
2374 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
2375 return IMAP_PROTOCOL_BAD_PARAMETERS;
2376 if (!dlist_getatom(kin, "ENTRY", &entry))
2377 return IMAP_PROTOCOL_BAD_PARAMETERS;
2378 if (!dlist_getatom(kin, "USERID", &userid))
2379 return IMAP_PROTOCOL_BAD_PARAMETERS;
2380
2381 /* annotatemore_store() expects external mailbox names,
2382 so translate the separator character */
2383 name = xstrdup(mboxname);
2384 mboxname_hiersep_toexternal(sstate->namespace, name, 0);
2385
2386 appendattvalue(&attvalues, *userid ? "value.priv" : "value.shared", NULL);
2387 appendentryatt(&entryatts, entry, attvalues);
2388 r = annotatemore_store(name, entryatts, sstate->namespace,
2389 sstate->userisadmin, userid, sstate->authstate);
2390
2391 freeentryatts(entryatts);
2392 free(name);
2393
2394 return r;
2395 }
2396
2397 int sync_apply_sieve(struct dlist *kin,
2398 struct sync_state *sstate __attribute__((unused)))
2399 {
2400 const char *userid;
2401 const char *filename;
2402 time_t last_update;
2403 const char *content;
2404 size_t len;
2405
2406 if (!dlist_getatom(kin, "USERID", &userid))
2407 return IMAP_PROTOCOL_BAD_PARAMETERS;
2408 if (!dlist_getatom(kin, "FILENAME", &filename))
2409 return IMAP_PROTOCOL_BAD_PARAMETERS;
2410 if (!dlist_getdate(kin, "LAST_UPDATE", &last_update))
2411 return IMAP_PROTOCOL_BAD_PARAMETERS;
2412 if (!dlist_getbuf(kin, "CONTENT", &content, &len))
2413 return IMAP_PROTOCOL_BAD_PARAMETERS;
2414
2415 return sync_sieve_upload(userid, filename, last_update, content, len);
2416 }
2417
2418 int sync_apply_unsieve(struct dlist *kin,
2419 struct sync_state *sstate __attribute__((unused)))
2420 {
2421 const char *userid;
2422 const char *filename;
2423
2424 if (!dlist_getatom(kin, "USERID", &userid))
2425 return IMAP_PROTOCOL_BAD_PARAMETERS;
2426 if (!dlist_getatom(kin, "FILENAME", &filename))
2427 return IMAP_PROTOCOL_BAD_PARAMETERS;
2428
2429 return sync_sieve_delete(userid, filename);
2430 }
2431
2432 int sync_apply_activate_sieve(struct dlist *kin,
2433 struct sync_state *sstate __attribute__((unused)))
2434 {
2435 const char *userid;
2436 const char *filename;
2437
2438 if (!dlist_getatom(kin, "USERID", &userid))
2439 return IMAP_PROTOCOL_BAD_PARAMETERS;
2440 if (!dlist_getatom(kin, "FILENAME", &filename))
2441 return IMAP_PROTOCOL_BAD_PARAMETERS;
2442
2443 return sync_sieve_activate(userid, filename);
2444 }
2445
2446 int sync_apply_unactivate_sieve(struct dlist *kin,
2447 struct sync_state *sstate __attribute__((unused)))
2448 {
2449 const char *userid;
2450
2451 if (!dlist_getatom(kin, "USERID", &userid))
2452 return IMAP_PROTOCOL_BAD_PARAMETERS;
2453
2454 return sync_sieve_deactivate(userid);
2455 }
2456
2457 int sync_apply_seen(struct dlist *kin,
2458 struct sync_state *sstate __attribute__((unused)))
2459 {
2460 int r;
2461 struct seen *seendb = NULL;
2462 struct seendata sd = SEENDATA_INITIALIZER;
2463 const char *seenuids;
2464 const char *userid;
2465 const char *uniqueid;
2466
2467 if (!dlist_getatom(kin, "USERID", &userid))
2468 return IMAP_PROTOCOL_BAD_PARAMETERS;
2469 if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
2470 return IMAP_PROTOCOL_BAD_PARAMETERS;
2471 if (!dlist_getdate(kin, "LASTREAD", &sd.lastread))
2472 return IMAP_PROTOCOL_BAD_PARAMETERS;
2473 if (!dlist_getnum(kin, "LASTUID", &sd.lastuid))
2474 return IMAP_PROTOCOL_BAD_PARAMETERS;
2475 if (!dlist_getdate(kin, "LASTCHANGE", &sd.lastchange))
2476 return IMAP_PROTOCOL_BAD_PARAMETERS;
2477 if (!dlist_getatom(kin, "SEENUIDS", &seenuids))
2478 return IMAP_PROTOCOL_BAD_PARAMETERS;
2479 sd.seenuids = xstrdup(seenuids);
2480
2481 r = seen_open(userid, SEEN_CREATE, &seendb);
2482 if (r) return r;
2483
2484 r = seen_write(seendb, uniqueid, &sd);
2485 seen_close(&seendb);
2486
2487 seen_freedata(&sd);
2488
2489 return r;
2490 }
2491
2492 int sync_apply_unuser(struct dlist *kin, struct sync_state *sstate)
2493 {
2494 struct sync_name_list *list = sync_name_list_create();
2495 struct sync_name *item;
2496 const char *userid = kin->sval;
2497 char buf[MAX_MAILBOX_NAME];
2498 int r = 0;
2499
2500 /* Nuke subscriptions */
2501 mboxlist_allsubs(userid, addmbox_sub, list);
2502
2503 /* ignore failures here - the subs file gets deleted soon anyway */
2504 for (item = list->head; item; item = item->next) {
2505 mboxlist_changesub(item->name, userid, sstate->authstate, 0, 0);
2506 }
2507 sync_name_list_free(&list);
2508
2509 /* Nuke normal folders */
2510 list = sync_name_list_create();
2511
2512 (*sstate->namespace->mboxname_tointernal)(sstate->namespace, "INBOX",
2513 userid, buf);
2514 strlcat(buf, ".*", sizeof(buf));
2515 r = (*sstate->namespace->mboxlist_findall)(sstate->namespace, buf,
2516 sstate->userisadmin,
2517 sstate->userid,
2518 sstate->authstate,
2519 addmbox, (void *)list);
2520 if (r) goto fail;
2521
2522 for (item = list->head; item; item = item->next) {
2523 r = mboxlist_deletemailbox(item->name, sstate->userisadmin,
2524 sstate->userid, sstate->authstate,
2525 0, sstate->local_only, 1);
2526 if (r) goto fail;
2527 }
2528
2529 /* Nuke inbox (recursive nuke possible?) */
2530 (*sstate->namespace->mboxname_tointernal)(sstate->namespace, "INBOX",
2531 userid, buf);
2532 r = mboxlist_deletemailbox(buf, sstate->userisadmin, sstate->userid,
2533 sstate->authstate, 0, sstate->local_only, 1);
2534 if (r && (r != IMAP_MAILBOX_NONEXISTENT)) goto fail;
2535
2536 r = user_deletedata((char *)userid, sstate->userid, sstate->authstate, 1);
2537
2538 fail:
2539 sync_name_list_free(&list);
2540
2541 return r;
2542 }
2543
2544 /* ====================================================================== */
2545
2546 int sync_get_sieve(struct dlist *kin, struct sync_state *sstate)
2547 {
2548 struct dlist *kl;
2549 const char *userid;
2550 const char *filename;
2551 uint32_t size;
2552 char *sieve;
2553
2554 if (!dlist_getatom(kin, "USERID", &userid))
2555 return IMAP_PROTOCOL_BAD_PARAMETERS;
2556 if (!dlist_getatom(kin, "FILENAME", &filename))
2557 return IMAP_PROTOCOL_BAD_PARAMETERS;
2558
2559 sieve = sync_sieve_read(userid, filename, &size);
2560 if (!sieve)
2561 return IMAP_MAILBOX_NONEXISTENT;
2562
2563 kl = dlist_new("SIEVE");
2564 dlist_atom(kl, "USERID", userid);
2565 dlist_atom(kl, "FILENAME", filename);
2566 dlist_buf(kl, "CONTENT", sieve, size);
2567 sync_send_response(kl, sstate->pout);
2568 dlist_free(&kl);
2569
2570 return 0;
2571 }
2572
2573 /* NOTE - can't lock a mailbox here, because it could deadlock,
2574 * so just pick the file out from under the hood */
2575 int sync_get_message(struct dlist *kin, struct sync_state *sstate)
2576 {
2577 const char *mboxname;
2578 const char *partition;
2579 const char *guid;
2580 uint32_t uid;
2581 const char *fname;
2582 struct dlist *kl;
2583 struct message_guid tmp_guid;
2584 struct stat sbuf;
2585
2586 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
2587 return IMAP_PROTOCOL_BAD_PARAMETERS;
2588 if (!dlist_getatom(kin, "PARTITION", &partition))
2589 return IMAP_PROTOCOL_BAD_PARAMETERS;
2590 if (!dlist_getatom(kin, "GUID", &guid))
2591 return IMAP_PROTOCOL_BAD_PARAMETERS;
2592 if (!dlist_getnum(kin, "UID", &uid))
2593 return IMAP_PROTOCOL_BAD_PARAMETERS;
2594 if (!message_guid_decode(&tmp_guid, guid))
2595 return IMAP_PROTOCOL_BAD_PARAMETERS;
2596
2597 fname = mboxname_datapath(partition, mboxname, uid);
2598 if (stat(fname, &sbuf) == -1)
2599 return IMAP_MAILBOX_NONEXISTENT;
2600
2601 kl = dlist_file(NULL, "MESSAGE", partition, &tmp_guid, sbuf.st_size, fname);
2602 sync_send_response(kl, sstate->pout);
2603 dlist_free(&kl);
2604
2605 return 0;
2606 }
2607
2608 int sync_apply_expunge(struct dlist *kin,
2609 struct sync_state *sstate __attribute__((unused)))
2610 {
2611 const char *mboxname;
2612 const char *uniqueid;
2613 struct dlist *ul;
2614 struct dlist *ui;
2615 struct mailbox *mailbox = NULL;
2616 struct index_record record;
2617 uint32_t recno;
2618 int r = 0;
2619
2620 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
2621 return IMAP_PROTOCOL_BAD_PARAMETERS;
2622 if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
2623 return IMAP_PROTOCOL_BAD_PARAMETERS;
2624 if (!dlist_getlist(kin, "UID", &ul))
2625 return IMAP_PROTOCOL_BAD_PARAMETERS;
2626
2627 r = mailbox_open_iwl(mboxname, &mailbox);
2628 if (r) goto done;
2629
2630 /* don't want to expunge the wrong mailbox! */
2631 if (strcmp(mailbox->uniqueid, uniqueid)) {
2632 r = IMAP_MAILBOX_MOVED;
2633 goto done;
2634 }
2635
2636 ui = ul->head;
2637
2638 for (recno = 1; recno <= mailbox->i.num_records; recno++) {
2639 r = mailbox_read_index_record(mailbox, recno, &record);
2640 if (r) goto done;
2641 if (record.system_flags & FLAG_EXPUNGED) continue;
2642 while (ui && ui->nval < record.uid) ui = ui->next;
2643 if (!ui) break; /* no point continuing */
2644 if (record.uid == ui->nval) {
2645 record.system_flags |= FLAG_EXPUNGED;
2646 record.silent = 1; /* so the next sync will succeed */
2647 r = mailbox_rewrite_index_record(mailbox, &record);
2648 if (r) goto done;
2649 }
2650 }
2651
2652 done:
2653 mailbox_close(&mailbox);
2654 return r;
2655 }
2656
2657 int sync_apply_message(struct dlist *kin,
2658 struct sync_reserve_list *reserve_list,
2659 struct sync_state *sstate __attribute__((unused)))
2660 {
2661 struct sync_msgid_list *part_list;
2662 struct dlist *ki;
2663 struct sync_msgid *msgid;
2664
2665 for (ki = kin->head; ki; ki = ki->next) {
2666 if (ki->type != DL_FILE)
2667 continue;
2668
2669 part_list = sync_reserve_partlist(reserve_list, ki->part);
2670 msgid = sync_msgid_lookup(part_list, &ki->gval);
2671 if (!msgid)
2672 msgid = sync_msgid_add(part_list, &ki->gval);
2673 if (!msgid->mark) {
2674 msgid->mark = 1;
2675 part_list->marked++;
2676 }
2677 }
2678
2679 return 0;
2680 }
2681
2682 void sync_print_response(char *tag, int r, struct protstream *pout)
2683 {
2684 const char *resp;
2685
2686 switch (r) {
2687 case 0:
2688 resp = "OK";
2689 break;
2690 case IMAP_INVALID_USER:
2691 resp = "NO [IMAP_INVALID_USER]";
2692 break;
2693 case IMAP_MAILBOX_NONEXISTENT:
2694 resp = "NO [IMAP_MAILBOX_NONEXISTENT]";
2695 break;
2696 case IMAP_MAILBOX_CRC:
2697 resp = "NO [IMAP_MAILBOX_CRC]";
2698 break;
2699 case IMAP_PROTOCOL_ERROR:
2700 resp = "NO [IMAP_PROTOCOL_ERROR]";
2701 break;
2702 case IMAP_PROTOCOL_BAD_PARAMETERS:
2703 resp = "NO [IMAP_PROTOCOL_BAD_PARAMETERS]";
2704 break;
2705 default:
2706 resp = "NO";
2707 }
2708
2709 prot_printf(pout, "%s %s %s\r\n", tag, resp, error_message(r));
2710 }
2711
2712
2713 /* ======================= client-side sync =========================== */
2714
2715
2716 /* Routines relevant to reserve operation */
2717
2718 /* Find the messages that we will want to upload from this mailbox,
2719 * flag messages that are already available at the server end */
2720
2721 int sync_find_reserve_messages(struct mailbox *mailbox,
2722 unsigned last_uid,
2723 struct sync_msgid_list *part_list)
2724 {
2725 struct index_record record;
2726 uint32_t recno;
2727 int r;
2728
2729 for (recno = 1; recno <= mailbox->i.num_records; recno++) {
2730 r = mailbox_read_index_record(mailbox, recno, &record);
2731
2732 if (r) {
2733 syslog(LOG_ERR,
2734 "IOERROR: reading index entry for recno %u of %s: %m",
2735 recno, mailbox->name);
2736 return IMAP_IOERROR;
2737 }
2738
2739 if (record.system_flags & FLAG_UNLINKED)
2740 continue;
2741
2742 /* skip over records already on replica */
2743 if (record.uid <= last_uid)
2744 continue;
2745
2746 sync_msgid_add(part_list, &record.guid);
2747 }
2748
2749 return(0);
2750 }
2751
2752 static int find_reserve_all(struct sync_name_list *mboxname_list,
2753 const char *topart,
2754 struct sync_folder_list *master_folders,
2755 struct sync_folder_list *replica_folders,
2756 struct sync_reserve_list *reserve_guids)
2757 {
2758 struct sync_name *mbox;
2759 struct sync_folder *rfolder;
2760 struct sync_msgid_list *part_list;
2761 struct mailbox *mailbox = NULL;
2762 int r = 0;
2763
2764 /* Find messages we want to upload that are available on server */
2765 for (mbox = mboxname_list->head; mbox; mbox = mbox->next) {
2766 r = mailbox_open_irl(mbox->name, &mailbox);
2767
2768 /* Quietly skip over folders which have been deleted since we
2769 started working (but record fact in case caller cares) */
2770 if (r == IMAP_MAILBOX_NONEXISTENT) {
2771 r = 0;
2772 continue;
2773 }
2774
2775 /* Quietly ignore objects that we don't have access to.
2776 * Includes directory stubs, which have not underlying cyrus.*
2777 * files in the filesystem */
2778 if (r == IMAP_PERMISSION_DENIED) {
2779 r = 0;
2780 continue;
2781 }
2782
2783 if (r) {
2784 syslog(LOG_ERR, "IOERROR: Failed to open %s: %s",
2785 mbox->name, error_message(r));
2786 goto bail;
2787 }
2788
2789 /* mailbox is open from here, no exiting without closing it! */
2790
2791 part_list = sync_reserve_partlist(reserve_guids,
2792 topart ? topart : mailbox->part);
2793
2794 sync_folder_list_add(master_folders, mailbox->uniqueid, mailbox->name,
2795 mailbox->mbtype,
2796 mailbox->part, mailbox->acl, mailbox->i.options,
2797 mailbox->i.uidvalidity, mailbox->i.last_uid,
2798 mailbox->i.highestmodseq, mailbox->i.sync_crc,
2799 mailbox->i.recentuid, mailbox->i.recenttime,
2800 mailbox->i.pop3_last_login);
2801
2802 rfolder = sync_folder_lookup(replica_folders, mailbox->uniqueid);
2803 if (rfolder)
2804 sync_find_reserve_messages(mailbox, rfolder->last_uid, part_list);
2805 else
2806 sync_find_reserve_messages(mailbox, 0, part_list);
2807
2808 mailbox_close(&mailbox);
2809 }
2810
2811 bail:
2812 return r;
2813 }
2814
2815 static int mark_missing (struct dlist *kin,
2816 struct sync_msgid_list *part_list)
2817 {
2818 struct dlist *kl = kin->head;
2819 struct dlist *ki;
2820 struct message_guid tmp_guid;
2821 struct sync_msgid *msgid;
2822
2823 /* no missing at all, good */
2824 if (!kl) return 0;
2825
2826 if (strcmp(kl->name, "MISSING")) {
2827 syslog(LOG_ERR, "SYNCERROR: Illegal response to RESERVE: %s",
2828 kl->name);
2829 return IMAP_PROTOCOL_BAD_PARAMETERS;
2830 }
2831
2832 /* unmark each missing item */
2833 for (ki = kl->head; ki; ki = ki->next) {
2834 if (!message_guid_decode(&tmp_guid, ki->sval)) {
2835 syslog(LOG_ERR, "SYNCERROR: reserve: failed to parse GUID %s",
2836 ki->sval);
2837 return IMAP_PROTOCOL_BAD_PARAMETERS;
2838 }
2839
2840 msgid = sync_msgid_lookup(part_list, &tmp_guid);
2841 if (!msgid) {
2842 syslog(LOG_ERR, "SYNCERROR: reserve: Got unexpected GUID %s", ki->sval);
2843 return IMAP_PROTOCOL_BAD_PARAMETERS;
2844 }
2845
2846 msgid->mark = 0;
2847 part_list->marked--;
2848 }
2849
2850 return 0;
2851 }
2852
2853 int sync_reserve_partition(char *partition,
2854 struct sync_folder_list *replica_folders,
2855 struct sync_msgid_list *part_list,
2856 struct backend *sync_be)
2857 {
2858 const char *cmd = "RESERVE";
2859 struct sync_msgid *msgid;
2860 struct sync_folder *folder;
2861 struct dlist *kl;
2862 struct dlist *kin = NULL;
2863 struct dlist *ki;
2864 int r;
2865
2866 if (!part_list->count)
2867 return 0; /* nothing to reserve */
2868
2869 if (!replica_folders->head)
2870 return 0; /* nowhere to reserve */
2871
2872 kl = dlist_new(cmd);
2873 dlist_atom(kl, "PARTITION", partition);
2874
2875 ki = dlist_list(kl, "MBOXNAME");
2876 for (folder = replica_folders->head; folder; folder = folder->next)
2877 dlist_atom(ki, "MBOXNAME", folder->name);
2878
2879 ki = dlist_list(kl, "GUID");
2880 for (msgid = part_list->head; msgid; msgid = msgid->next) {
2881 dlist_atom(ki, "GUID", message_guid_encode(&msgid->guid));
2882 msgid->mark = 1;
2883 part_list->marked++;
2884 }
2885
2886 sync_send_apply(kl, sync_be->out);
2887 dlist_free(&kl);
2888
2889 r = sync_parse_response(cmd, sync_be->in, &kin);
2890 if (r) return r;
2891
2892 r = mark_missing(kin, part_list);
2893 dlist_free(&kin);
2894
2895 return r;
2896 }
2897
2898 static int reserve_messages(struct sync_name_list *mboxname_list,
2899 const char *topart,
2900 struct sync_folder_list *master_folders,
2901 struct sync_folder_list *replica_folders,
2902 struct sync_reserve_list *reserve_guids,
2903 struct backend *sync_be)
2904 {
2905 struct sync_reserve *reserve;
2906 int r;
2907
2908 r = find_reserve_all(mboxname_list, topart, master_folders,
2909 replica_folders, reserve_guids);
2910 if (r) return r;
2911
2912 for (reserve = reserve_guids->head; reserve; reserve = reserve->next) {
2913 r = sync_reserve_partition(reserve->part, replica_folders,
2914 reserve->list, sync_be);
2915 if (r) return r;
2916 }
2917
2918 return 0;
2919 }
2920
2921
2922 int sync_response_parse(struct protstream *sync_in, const char *cmd,
2923 struct sync_folder_list *folder_list,
2924 struct sync_name_list *sub_list,
2925 struct sync_sieve_list *sieve_list,
2926 struct sync_seen_list *seen_list,
2927 struct sync_quota_list *quota_list)
2928 {
2929 struct dlist *kin = NULL;
2930 struct dlist *kl;
2931 int r;
2932
2933 r = sync_parse_response(cmd, sync_in, &kin);
2934
2935 /* Unpleasant: translate remote access error into "please reset me" */
2936 if (r == IMAP_MAILBOX_NONEXISTENT)
2937 return 0;
2938
2939 if (r) return r;
2940
2941 for (kl = kin->head; kl; kl = kl->next) {
2942 if (!strcmp(kl->name, "SIEVE")) {
2943 const char *filename = NULL;
2944 time_t modtime = 0;
2945 uint32_t active = 0;
2946 if (!sieve_list) goto parse_err;
2947 if (!dlist_getatom(kl, "FILENAME", &filename)) goto parse_err;
2948 if (!dlist_getdate(kl, "LAST_UPDATE", &modtime)) goto parse_err;
2949 dlist_getnum(kl, "ISACTIVE", &active); /* optional */
2950 sync_sieve_list_add(sieve_list, filename, modtime, active);
2951 }
2952
2953 else if (!strcmp(kl->name, "QUOTA")) {
2954 const char *root = NULL;
2955 uint32_t limit = 0;
2956 if (!quota_list) goto parse_err;
2957 if (!dlist_getatom(kl, "ROOT", &root)) goto parse_err;
2958 if (!dlist_getnum(kl, "LIMIT", &limit)) goto parse_err;
2959 sync_quota_list_add(quota_list, root, limit);
2960 }
2961
2962 else if (!strcmp(kl->name, "LSUB")) {
2963 struct dlist *i;
2964 if (!sub_list) goto parse_err;
2965 for (i = kl->head; i; i = i->next) {
2966 sync_name_list_add(sub_list, i->sval);
2967 }
2968 }
2969
2970 else if (!strcmp(kl->name, "SEEN")) {
2971 const char *uniqueid = NULL;
2972 time_t lastread = 0;
2973 uint32_t lastuid = 0;
2974 time_t lastchange = 0;
2975 const char *seenuids = NULL;
2976 if (!seen_list) goto parse_err;
2977 if (!dlist_getatom(kl, "UNIQUEID", &uniqueid)) goto parse_err;
2978 if (!dlist_getdate(kl, "LASTREAD", &lastread)) goto parse_err;
2979 if (!dlist_getnum(kl, "LASTUID", &lastuid)) goto parse_err;
2980 if (!dlist_getdate(kl, "LASTCHANGE", &lastchange)) goto parse_err;
2981 if (!dlist_getatom(kl, "SEENUIDS", &seenuids)) goto parse_err;
2982 sync_seen_list_add(seen_list, uniqueid, lastread,
2983 lastuid, lastchange, seenuids);
2984 }
2985
2986 else if (!strcmp(kl->name, "MAILBOX")) {
2987 const char *uniqueid = NULL;
2988 const char *mboxname = NULL;
2989 const char *mboxtype = NULL;
2990 const char *part = NULL;
2991 const char *acl = NULL;
2992 const char *options = NULL;
2993 modseq_t highestmodseq = 0;
2994 uint32_t uidvalidity = 0;
2995 uint32_t last_uid = 0;
2996 uint32_t sync_crc = 0;
2997 uint32_t recentuid = 0;
2998 time_t recenttime = 0;
2999 time_t pop3_last_login = 0;
3000 if (!folder_list) goto parse_err;
3001 if (!dlist_getatom(kl, "UNIQUEID", &uniqueid)) goto parse_err;
3002 if (!dlist_getatom(kl, "MBOXNAME", &mboxname)) goto parse_err;
3003 if (!dlist_getatom(kl, "PARTITION", &part)) goto parse_err;
3004 if (!dlist_getatom(kl, "ACL", &acl)) goto parse_err;
3005 if (!dlist_getatom(kl, "OPTIONS", &options)) goto parse_err;
3006 if (!dlist_getmodseq(kl, "HIGHESTMODSEQ", &highestmodseq)) goto parse_err;
3007 if (!dlist_getnum(kl, "UIDVALIDITY", &uidvalidity)) goto parse_err;
3008 if (!dlist_getnum(kl, "LAST_UID", &last_uid)) goto parse_err;
3009 if (!dlist_getnum(kl, "SYNC_CRC", &sync_crc)) goto parse_err;
3010 if (!dlist_getnum(kl, "RECENTUID", &recentuid)) goto parse_err;
3011 if (!dlist_getdate(kl, "RECENTTIME", &recenttime)) goto parse_err;
3012 if (!dlist_getdate(kl, "POP3_LAST_LOGIN", &pop3_last_login)) goto parse_err;
3013 /* optional */
3014 dlist_getatom(kl, "MBOXTYPE", &mboxtype);
3015
3016 sync_folder_list_add(folder_list, uniqueid, mboxname,
3017 mboxlist_string_to_mbtype(mboxtype),
3018 part, acl,
3019 sync_parse_options(options),
3020 uidvalidity, last_uid,
3021 highestmodseq, sync_crc,
3022 recentuid, recenttime,
3023 pop3_last_login);
3024 }
3025 else
3026 goto parse_err;
3027 }
3028 dlist_free(&kin);
3029
3030 return r;
3031
3032 parse_err:
3033 dlist_free(&kin);
3034 syslog(LOG_ERR, "SYNCERROR: %s: Invalid response %s",
3035 cmd, dlist_lastkey());
3036 return IMAP_PROTOCOL_BAD_PARAMETERS;
3037 }
3038
3039 static int user_reset(char *userid, struct backend *sync_be)
3040 {
3041 const char *cmd = "UNUSER";
3042 struct dlist *kl;
3043
3044 kl = dlist_atom(NULL, cmd, userid);
3045 sync_send_apply(kl, sync_be->out);
3046 dlist_free(&kl);
3047
3048 return sync_parse_response(cmd, sync_be->in, NULL);
3049 }
3050
3051 static int folder_rename(char *oldname, char *newname, char *partition,
3052 struct backend *sync_be, unsigned flags)
3053 {
3054 const char *cmd = (flags & SYNC_FLAG_LOCALONLY) ? "LOCAL_RENAME" : "RENAME";
3055 struct dlist *kl;
3056
3057 kl = dlist_new(cmd);
3058 dlist_atom(kl, "OLDMBOXNAME", oldname);
3059 dlist_atom(kl, "NEWMBOXNAME", newname);
3060 dlist_atom(kl, "PARTITION", partition);
3061 sync_send_apply(kl, sync_be->out);
3062 dlist_free(&kl);
3063
3064 return sync_parse_response(cmd, sync_be->in, NULL);
3065 }
3066
3067 int sync_folder_delete(char *mboxname, struct backend *sync_be, unsigned flags)
3068 {
3069 const char *cmd =
3070 (flags & SYNC_FLAG_LOCALONLY) ? "LOCAL_UNMAILBOX" :"UNMAILBOX";
3071 struct dlist *kl;
3072
3073 kl = dlist_atom(NULL, cmd, mboxname);
3074 sync_send_apply(kl, sync_be->out);
3075 dlist_free(&kl);
3076
3077 return sync_parse_response(cmd, sync_be->in, NULL);
3078 }
3079
3080 int sync_set_sub(const char *userid, const char *mboxname, int add,
3081 struct backend *sync_be, unsigned flags)
3082 {
3083 const char *cmd = add ? "SUB" : "UNSUB";
3084 struct dlist *kl;
3085
3086 if (flags & SYNC_FLAG_VERBOSE)
3087 printf("%s %s %s\n", cmd, userid, mboxname);
3088
3089 if (flags & SYNC_FLAG_LOGGING)
3090 syslog(LOG_INFO, "%s %s %s", cmd, userid, mboxname);
3091
3092 kl = dlist_new(cmd);
3093 dlist_atom(kl, "USERID", userid);
3094 dlist_atom(kl, "MBOXNAME", mboxname);
3095 sync_send_apply(kl, sync_be->out);
3096 dlist_free(&kl);
3097
3098 return sync_parse_response(cmd, sync_be->in, NULL);
3099 }
3100
3101 static int folder_setannotation(const char *mboxname, const char *entry,
3102 const char *userid, const char *value,
3103 struct backend *sync_be)
3104 {
3105 const char *cmd = "ANNOTATION";
3106 struct dlist *kl;
3107
3108 kl = dlist_new(cmd);
3109 dlist_atom(kl, "MBOXNAME", mboxname);
3110 dlist_atom(kl, "ENTRY", entry);
3111 dlist_atom(kl, "USERID", userid);
3112 dlist_atom(kl, "VALUE", value);
3113 sync_send_apply(kl, sync_be->out);
3114 dlist_free(&kl);
3115
3116 return sync_parse_response(cmd, sync_be->in, NULL);
3117 }
3118
3119 static int folder_unannotation(const char *mboxname, const char *entry,
3120 const char *userid,
3121 struct backend *sync_be)
3122 {
3123 const char *cmd = "UNANNOTATION";
3124 struct dlist *kl;
3125
3126 kl = dlist_new(cmd);
3127 dlist_atom(kl, "MBOXNAME", mboxname);
3128 dlist_atom(kl, "ENTRY", entry);
3129 dlist_atom(kl, "USERID", userid);
3130 sync_send_apply(kl, sync_be->out);
3131 dlist_free(&kl);
3132
3133 return sync_parse_response(cmd, sync_be->in, NULL);
3134 }
3135
3136 /* ====================================================================== */
3137
3138 static int sieve_upload(const char *userid, const char *filename,
3139 unsigned long last_update,
3140 struct backend *sync_be)
3141 {
3142 const char *cmd = "SIEVE";
3143 struct dlist *kl;
3144 char *sieve;
3145 uint32_t size;
3146
3147 sieve = sync_sieve_read(userid, filename, &size);
3148 if (!sieve) return IMAP_IOERROR;
3149
3150 kl = dlist_new(cmd);
3151 dlist_atom(kl, "USERID", userid);
3152 dlist_atom(kl, "FILENAME", filename);
3153 dlist_date(kl, "LAST_UPDATE", last_update);
3154 dlist_buf(kl, "CONTENT", sieve, size);
3155 sync_send_apply(kl, sync_be->out);
3156 dlist_free(&kl);
3157 free(sieve);
3158
3159 return sync_parse_response(cmd, sync_be->in, NULL);
3160 }
3161
3162 static int sieve_delete(const char *userid, const char *filename,
3163 struct backend *sync_be)
3164 {
3165 const char *cmd = "UNSIEVE";
3166 struct dlist *kl;
3167
3168 kl = dlist_new(cmd);
3169 dlist_atom(kl, "USERID", userid);
3170 dlist_atom(kl, "FILENAME", filename);
3171 sync_send_apply(kl, sync_be->out);
3172 dlist_free(&kl);
3173
3174 return sync_parse_response(cmd, sync_be->in, NULL);
3175 }
3176
3177 static int sieve_activate(const char *userid, const char *filename,
3178 struct backend *sync_be)
3179 {
3180 const char *cmd = "ACTIVATE_SIEVE";
3181 struct dlist *kl;
3182
3183 kl = dlist_new(cmd);
3184 dlist_atom(kl, "USERID", userid);
3185 dlist_atom(kl, "FILENAME", filename);
3186 sync_send_apply(kl, sync_be->out);
3187 dlist_free(&kl);
3188
3189 return sync_parse_response(cmd, sync_be->in, NULL);
3190 }
3191
3192 static int sieve_deactivate(const char *userid,
3193 struct backend *sync_be)
3194 {
3195 const char *cmd = "UNACTIVATE_SIEVE";
3196 struct dlist *kl;
3197
3198 kl = dlist_new(cmd);
3199 dlist_atom(kl, "USERID", userid);
3200 sync_send_apply(kl, sync_be->out);
3201 dlist_free(&kl);
3202
3203 return sync_parse_response(cmd, sync_be->in, NULL);
3204 }
3205
3206 /* ====================================================================== */
3207
3208 static int delete_quota(const char *root, struct backend *sync_be)
3209 {
3210 const char *cmd = "UNQUOTA";
3211 struct dlist *kl;
3212
3213 kl = dlist_atom(NULL, cmd, root);
3214 sync_send_apply(kl, sync_be->out);
3215 dlist_free(&kl);
3216
3217 return sync_parse_response(cmd, sync_be->in, NULL);
3218 }
3219
3220 static int update_quota_work(struct quota *client,
3221 struct sync_quota *server,
3222 struct backend *sync_be)
3223 {
3224 const char *cmd = "QUOTA";
3225 struct dlist *kl;
3226 int r;
3227
3228 r = quota_read(client, NULL, 0);
3229
3230 /* disappeared? Delete it*/
3231 if (r == IMAP_QUOTAROOT_NONEXISTENT)
3232 return delete_quota(client->root, sync_be);
3233
3234 if (r) {
3235 syslog(LOG_INFO, "Warning: failed to read quotaroot %s: %s",
3236 client->root, error_message(r));
3237 return r;
3238 }
3239
3240 if (server && (client->limit == server->limit))
3241 return(0);
3242
3243 kl = dlist_new(cmd);
3244 dlist_atom(kl, "ROOT", client->root);
3245 dlist_num(kl, "LIMIT", client->limit);
3246 sync_send_apply(kl, sync_be->out);
3247 dlist_free(&kl);
3248
3249 return sync_parse_response(cmd, sync_be->in, NULL);
3250 }
3251
3252 static int copy_local(struct mailbox *mailbox, unsigned long uid)
3253 {
3254 uint32_t recno;
3255 struct index_record oldrecord;
3256 int r;
3257
3258 for (recno = 1; recno <= mailbox->i.num_records; recno++) {
3259 r = mailbox_read_index_record(mailbox, recno, &oldrecord);
3260 if (r) return r;
3261
3262 /* found the record, renumber it */
3263 if (oldrecord.uid == uid) {
3264 char *oldfname, *newfname;
3265 struct index_record newrecord;
3266
3267 /* create the new record as a clone of the old record */
3268 newrecord = oldrecord;
3269 newrecord.uid = mailbox->i.last_uid + 1;
3270
3271 /* copy the file in to place */
3272 oldfname = xstrdup(mailbox_message_fname(mailbox, oldrecord.uid));
3273 newfname = xstrdup(mailbox_message_fname(mailbox, newrecord.uid));
3274 r = mailbox_copyfile(oldfname, newfname, 0);
3275 free(oldfname);
3276 free(newfname);
3277 if (r) return r;
3278
3279 /* append the new record */
3280 r = mailbox_append_index_record(mailbox, &newrecord);
3281 if (r) return r;
3282
3283 /* and expunge the old record */
3284 oldrecord.system_flags |= FLAG_EXPUNGED;
3285 r = mailbox_rewrite_index_record(mailbox, &oldrecord);
3286
3287 /* done - return */
3288 return r;
3289 }
3290 }
3291
3292 /* not finding the record is an error! (should never happen) */
3293 return IMAP_MAILBOX_NONEXISTENT;
3294 }
3295
3296 static int fetch_file(struct mailbox *mailbox, unsigned long uid,
3297 struct index_record *rp,
3298 struct backend *sync_be)
3299 {
3300 const char *cmd = "FETCH";
3301 struct dlist *kin = NULL;
3302 struct dlist *kl;
3303 int r = 0;
3304 const char *fname = dlist_reserve_path(mailbox->part, &rp->guid);
3305 struct stat sbuf;
3306
3307 /* already reserved? great */
3308 if (stat(fname, &sbuf) == 0)
3309 return 0;
3310
3311 kl = dlist_new(cmd);
3312 dlist_atom(kl, "MBOXNAME", mailbox->name);
3313 dlist_atom(kl, "PARTITION", mailbox->part);
3314 dlist_atom(kl, "GUID", message_guid_encode(&rp->guid));
3315 dlist_num(kl, "UID", uid);
3316 sync_send_lookup(kl, sync_be->out);
3317 dlist_free(&kl);
3318
3319 r = sync_parse_response(cmd, sync_be->in, &kin);
3320 if (r) return r;
3321
3322 kl = kin->head;
3323 if (!kl) {
3324 r = IMAP_MAILBOX_NONEXISTENT;
3325 goto done;
3326 }
3327
3328 if (!message_guid_equal(&kl->gval, &rp->guid))
3329 r = IMAP_MAILBOX_CRC;
3330
3331 done:
3332 dlist_free(&kin);
3333 return r;
3334 }
3335
3336 static int copy_remote(struct mailbox *mailbox, unsigned long uid,
3337 struct dlist *kr)
3338 {
3339 struct index_record record;
3340 struct dlist *ki;
3341 int r;
3342
3343 for (ki = kr->head; ki; ki = ki->next) {
3344 r = parse_upload(ki, mailbox, &record);
3345 if (r) return r;
3346 if (record.uid == uid) {
3347 /* choose the destination UID */
3348 record.uid = mailbox->i.last_uid + 1;
3349
3350 /* already fetched the file in the parse phase */
3351
3352 /* append the file */
3353 r = sync_append_copyfile(mailbox, &record);
3354
3355 return r;
3356 }
3357 }
3358 /* not finding the record is an error! (should never happen) */
3359 return IMAP_MAILBOX_NONEXISTENT;
3360 }
3361
3362 static int copyback_one_record(struct mailbox *mailbox,
3363 struct index_record *rp,
3364 struct dlist *kaction,
3365 struct backend *sync_be)
3366 {
3367 int r;
3368
3369 /* don't want to copy back expunged records! */
3370 if (rp->system_flags & FLAG_EXPUNGED)
3371 return 0;
3372
3373 /* if the UID is lower than master's last_uid,
3374 * we'll need to renumber */
3375 if (rp->uid <= mailbox->i.last_uid) {
3376 /* Ok, now we need to check if it's just really stale
3377 * (has been cleaned out locally) or an error.
3378 * In the error case we copy back, stale
3379 * we remove from the replica */
3380 if (rp->modseq < mailbox->i.deletedmodseq) {
3381 if (kaction)
3382 dlist_num(kaction, "EXPUNGE", rp->uid);
3383 }
3384 else {
3385 r = fetch_file(mailbox, rp->uid, rp, sync_be);
3386 if (r) return r;
3387 if (kaction)
3388 dlist_num(kaction, "COPYBACK", rp->uid);
3389 }
3390 }
3391
3392 /* otherwise we can pull it in with the same UID,
3393 * which saves causing renumbering on the replica
3394 * end, so is preferable */
3395 else {
3396 /* grab the file */
3397 r = fetch_file(mailbox, rp->uid, rp, sync_be);
3398 if (r) return r;
3399 /* make sure we're actually making changes now */
3400 if (!kaction) return 0;
3401 /* append the file */
3402 r = sync_append_copyfile(mailbox, rp);
3403 if (r) return r;
3404 }
3405
3406 return 0;
3407 }
3408
3409 static int renumber_one_record(struct index_record *mp,
3410 struct dlist *kaction)
3411 {
3412 /* don't want to renumber expunged records */
3413 if (mp->system_flags & FLAG_EXPUNGED)
3414 return 0;
3415
3416 if (kaction)
3417 dlist_num(kaction, "RENUMBER", mp->uid);
3418
3419 return 0;
3420 }
3421
3422 static const char *make_flags(struct mailbox *mailbox, struct index_record *record)
3423 {
3424 static char buf[4096];
3425 const char *sep = "";
3426 int flag;
3427
3428 if (record->system_flags & FLAG_DELETED) {
3429 snprintf(buf, 4096, "%s\\Deleted", sep);
3430 sep = " ";
3431 }
3432 if (record->system_flags & FLAG_ANSWERED) {
3433 snprintf(buf, 4096, "%s\\Answered", sep);
3434 sep = " ";
3435 }
3436 if (record->system_flags & FLAG_FLAGGED) {
3437 snprintf(buf, 4096, "%s\\Flagged", sep);
3438 sep = " ";
3439 }
3440 if (record->system_flags & FLAG_DRAFT) {
3441 snprintf(buf, 4096, "%s\\Draft", sep);
3442 sep = " ";
3443 }
3444 if (record->system_flags & FLAG_EXPUNGED) {
3445 snprintf(buf, 4096, "%s\\Expunged", sep);
3446 sep = " ";
3447 }
3448 if (record->system_flags & FLAG_SEEN) {
3449 snprintf(buf, 4096, "%s\\Seen", sep);
3450 sep = " ";
3451 }
3452
3453 /* print user flags in mailbox order */
3454 for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
3455 if (!mailbox->flagname[flag])
3456 continue;
3457 if (!(record->user_flags[flag/32] & (1<<(flag&31))))
3458 continue;
3459 snprintf(buf, 4096, "%s%s", sep, mailbox->flagname[flag]);
3460 sep = " ";
3461 }
3462
3463 return buf;
3464 }
3465
3466 static void log_record(const char *name, struct mailbox *mailbox,
3467 struct index_record *record)
3468 {
3469 syslog(LOG_NOTICE, "SYNCNOTICE: %s uid:%u modseq:" MODSEQ_FMT " "
3470 "last_updated:%lu internaldate:%lu flags:(%s)",
3471 name, record->uid, record->modseq,
3472 record->last_updated, record->internaldate,
3473 make_flags(mailbox, record));
3474 }
3475
3476 static void log_mismatch(const char *reason, struct mailbox *mailbox,
3477 struct index_record *mp,
3478 struct index_record *rp)
3479 {
3480 syslog(LOG_NOTICE, "SYNCNOTICE: record mismatch with replica: %s %s",
3481 mailbox->name, reason);
3482 log_record("master", mailbox, mp);
3483 log_record("replica", mailbox, rp);
3484 }
3485
3486 static int compare_one_record(struct mailbox *mailbox,
3487 struct index_record *mp,
3488 struct index_record *rp,
3489 struct dlist *kaction,
3490 struct backend *sync_be)
3491 {
3492 int diff = 0;
3493 int i;
3494 int r;
3495
3496 /* are there any differences? */
3497 if (mp->modseq != rp->modseq)
3498 diff = 1;
3499 else if (mp->last_updated != rp->last_updated)
3500 diff = 1;
3501 else if (mp->internaldate != rp->internaldate)
3502 diff = 1;
3503 else if (mp->system_flags != rp->system_flags)
3504 diff = 1;
3505 else if (!message_guid_equal(&mp->guid, &rp->guid))
3506 diff = 1;
3507 else {
3508 for (i = 0; i < MAX_USER_FLAGS/32; i++) {
3509 if (mp->user_flags[i] != rp->user_flags[i])
3510 diff = 1;
3511 }
3512 }
3513
3514 /* if differences we'll have to rewrite to bump the modseq
3515 * so that regular replication will cause an update */
3516 if (diff) {
3517 /* interesting case - expunged locally */
3518 if (mp->system_flags & FLAG_EXPUNGED) {
3519 /* if expunged, fall through - the rewrite will lift
3520 * the modseq to force the change to stick */
3521 }
3522 else if (rp->system_flags & FLAG_EXPUNGED) {
3523 /* mark expunged - rewrite will cause both sides to agree
3524 * again */
3525 mp->system_flags |= FLAG_EXPUNGED;
3526 }
3527
3528 /* general case */
3529 else {
3530 if (!message_guid_equal(&mp->guid, &rp->guid)) {
3531 char *mguid = xstrdup(message_guid_encode(&mp->guid));
3532 char *rguid = xstrdup(message_guid_encode(&rp->guid));
3533 syslog(LOG_ERR, "SYNCERROR: guid mismatch %s %u (%s %s)",
3534 mailbox->name, mp->uid, rguid, mguid);
3535 free(rguid);
3536 free(mguid);
3537 /* we will need to renumber both ends to get in sync */
3538
3539 /* ORDERING - always lower GUID first */
3540 if (message_guid_cmp(&mp->guid, &rp->guid) < 0) {
3541 r = copyback_one_record(mailbox, rp, kaction,
3542 sync_be);
3543 if (!r) r = renumber_one_record(mp, kaction);
3544 }
3545 else {
3546 r = renumber_one_record(mp, kaction);
3547 if (!r) r = copyback_one_record(mailbox, rp, kaction,
3548 sync_be);
3549 }
3550
3551 return r;
3552 }
3553
3554 /* is the replica "newer"? */
3555 if (rp->modseq > mp->modseq &&
3556 rp->last_updated >= mp->last_updated) {
3557 /* then copy all the flag data over from the replica */
3558 mp->system_flags = rp->system_flags;
3559 for (i = 0; i < MAX_USER_FLAGS/32; i++)
3560 mp->user_flags[i] = rp->user_flags[i];
3561
3562 log_mismatch("more recent on replica", mailbox, mp, rp);
3563 }
3564 else {
3565 log_mismatch("more recent on master", mailbox, mp, rp);
3566 }
3567 }
3568
3569 /* are we making changes yet? */
3570 if (!kaction) return 0;
3571
3572 /* this will bump the modseq and force a resync either way :) */
3573 r = mailbox_rewrite_index_record(mailbox, mp);
3574 if (r) return r;
3575 }
3576
3577 return 0;
3578 }
3579
3580
3581 static int mailbox_update_loop(struct mailbox *mailbox,
3582 struct dlist *ki,
3583 uint32_t last_uid,
3584 modseq_t highestmodseq,
3585 struct dlist *kaction,
3586 struct backend *sync_be)
3587 {
3588 struct index_record mrecord;
3589 struct index_record rrecord;
3590 uint32_t recno = 1;
3591 uint32_t old_num_records = mailbox->i.num_records;
3592 int r;
3593
3594 /* while there are more records on either master OR replica,
3595 * work out what to do with them */
3596 while (ki || recno <= old_num_records) {
3597 /* most common case - both a master AND a replica record exist */
3598 if (ki && recno <= old_num_records) {
3599 r = mailbox_read_index_record(mailbox, recno, &mrecord);
3600 if (r) return r;
3601 r = parse_upload(ki, mailbox, &rrecord);
3602 if (r) return r;
3603
3604 /* same UID - compare the records */
3605 if (rrecord.uid == mrecord.uid) {
3606 r = compare_one_record(mailbox, &mrecord, &rrecord, kaction,
3607 sync_be);
3608 if (r) return r;
3609 /* increment both */
3610 recno++;
3611 ki = ki->next;
3612 }
3613 else if (rrecord.uid > mrecord.uid) {
3614 /* record only exists on the master */
3615 if (!(mrecord.system_flags & FLAG_EXPUNGED)) {
3616 syslog(LOG_ERR, "SYNCERROR: only exists on master %s %u (%s)",
3617 mailbox->name, mrecord.uid,
3618 message_guid_encode(&mrecord.guid));
3619 r = renumber_one_record(&mrecord, kaction);
3620 if (r) return r;
3621 }
3622 /* only increment master */
3623 recno++;
3624 }
3625 else {
3626 /* record only exists on the replica */
3627 if (!(rrecord.system_flags & FLAG_EXPUNGED)) {
3628 if (kaction)
3629 syslog(LOG_ERR, "SYNCERROR: only exists on replica %s %u (%s)",
3630 mailbox->name, rrecord.uid,
3631 message_guid_encode(&rrecord.guid));
3632 r = copyback_one_record(mailbox, &rrecord, kaction,
3633 sync_be);
3634 if (r) return r;
3635 }
3636 /* only increment replica */
3637 ki = ki->next;
3638 }
3639 }
3640
3641 /* no more replica records, but still master records */
3642 else if (recno <= old_num_records) {
3643 r = mailbox_read_index_record(mailbox, recno, &mrecord);
3644 if (r) return r;
3645 /* if the replica has seen this UID, we need to renumber.
3646 * Otherwise it will replicate fine as-is */
3647 if (mrecord.uid <= last_uid) {
3648 r = renumber_one_record(&mrecord, kaction);
3649 if (r) return r;
3650 }
3651 else if (mrecord.modseq <= highestmodseq) {
3652 if (kaction) {
3653 /* bump our modseq so we sync */
3654 syslog(LOG_NOTICE, "SYNCNOTICE: bumping modseq %s %u",
3655 mailbox->name, mrecord.uid);
3656 r = mailbox_rewrite_index_record(mailbox, &mrecord);
3657 if (r) return r;
3658 }
3659 }
3660 recno++;
3661 }
3662
3663 /* record only exists on the replica */
3664 else {
3665 r = parse_upload(ki, mailbox, &rrecord);
3666 if (r) return r;
3667
3668 if (kaction)
3669 syslog(LOG_NOTICE, "SYNCNOTICE: only on replica %s %u",
3670 mailbox->name, rrecord.uid);
3671
3672 /* going to need this one */
3673 r = copyback_one_record(mailbox, &rrecord, kaction, sync_be);
3674 if (r) return r;
3675
3676 ki = ki->next;
3677 }
3678 }
3679
3680 return 0;
3681 }
3682
3683 static int mailbox_full_update(struct sync_folder *local,
3684 struct backend *sync_be,
3685 unsigned flags)
3686 {
3687 const char *cmd = "FULLMAILBOX";
3688 struct mailbox *mailbox = NULL;
3689 int r;
3690 struct dlist *kin = NULL;
3691 struct dlist *kr = NULL;
3692 struct dlist *ka = NULL;
3693 struct dlist *kuids = NULL;
3694 struct dlist *kl = NULL;
3695 struct dlist *kaction = NULL;
3696 struct dlist *kexpunge = NULL;
3697 modseq_t highestmodseq;
3698 uint32_t uidvalidity;
3699 uint32_t last_uid;
3700
3701 if (flags & SYNC_FLAG_LOGGING) {
3702 syslog(LOG_INFO, "%s %s", cmd, local->name);
3703 }
3704
3705 kl = dlist_atom(NULL, cmd, local->name);
3706 sync_send_lookup(kl, sync_be->out);
3707 dlist_free(&kl);
3708
3709 r = sync_parse_response(cmd, sync_be->in, &kin);
3710 if (r) return r;
3711
3712 kl = kin->head;
3713
3714 if (!kl) {
3715 r = IMAP_MAILBOX_NONEXISTENT;
3716 goto done;
3717 }
3718
3719 /* XXX - handle the header. I want to do some ordering on timestamps
3720 * in particular here - if there's more recent data on the replica then
3721 * it should be copied back. This depends on having a nice way to
3722 * parse the mailbox structure back in to a struct index_header rather
3723 * than the by hand stuff though, because that sucks. NOTE - this
3724 * doesn't really matter too much, because we'll blat the replica's
3725 * values anyway! */
3726
3727 if (!dlist_getmodseq(kl, "HIGHESTMODSEQ", &highestmodseq)) {
3728 r = IMAP_PROTOCOL_BAD_PARAMETERS;
3729 goto done;
3730 }
3731
3732 if (!dlist_getnum(kl, "UIDVALIDITY", &uidvalidity)) {
3733 r = IMAP_PROTOCOL_BAD_PARAMETERS;
3734 goto done;
3735 }
3736
3737 if (!dlist_getnum(kl, "LAST_UID", &last_uid)) {
3738 r = IMAP_PROTOCOL_BAD_PARAMETERS;
3739 goto done;
3740 }
3741
3742 if (!dlist_getlist(kl, "RECORD", &kr)) {
3743 r = IMAP_PROTOCOL_BAD_PARAMETERS;
3744 goto done;
3745 }
3746
3747 /* we'll be updating it! */
3748 if (local->mailbox) mailbox = local->mailbox;
3749 else r = mailbox_open_iwl(local->name, &mailbox);
3750 if (r) goto done;
3751
3752 /* re-calculate our local CRC just in case it's out of sync */
3753 r = mailbox_index_recalc(mailbox);
3754 if (r) goto done;
3755
3756 /* if local UIDVALIDITY is lower, copy from remote, otherwise
3757 * remote will copy ours when we sync */
3758 if (mailbox->i.uidvalidity < uidvalidity) {
3759 syslog(LOG_NOTICE, "SYNCNOTICE: uidvalidity higher on replica %s"
3760 ", updating %u => %u",
3761 mailbox->name, mailbox->i.uidvalidity, uidvalidity);
3762 mailbox_index_dirty(mailbox);
3763 mailbox->i.uidvalidity = uidvalidity;
3764 }
3765
3766 if (mailbox->i.highestmodseq < highestmodseq) {
3767 mailbox_modseq_dirty(mailbox);
3768 /* highestmodseq on replica is dirty - we must go to at least
3769 * one higher! */
3770 syslog(LOG_NOTICE, "SYNCNOTICE: highestmodseq higher on replica %s"
3771 ", updating " MODSEQ_FMT " => " MODSEQ_FMT,
3772 mailbox->name, mailbox->i.highestmodseq, highestmodseq+1);
3773 mailbox->i.highestmodseq = highestmodseq+1;
3774 }
3775
3776 r = mailbox_update_loop(mailbox, kr->head, last_uid,
3777 highestmodseq, NULL, sync_be);
3778 if (r) {
3779 syslog(LOG_ERR, "SYNCNOTICE: failed to prepare update for %s: %s",
3780 mailbox->name, error_message(r));
3781 goto done;
3782 }
3783
3784 /* OK - now we're committed to make changes! */
3785
3786 kaction = dlist_list(NULL, "ACTION");
3787 r = mailbox_update_loop(mailbox, kr->head, last_uid,
3788 highestmodseq, kaction, sync_be);
3789 if (r) goto cleanup;
3790
3791 /* if replica still has a higher last_uid, bump our local
3792 * number to match so future records don't clash */
3793 if (mailbox->i.last_uid < last_uid)
3794 mailbox->i.last_uid = last_uid;
3795
3796 /* blatant reuse 'r' us */
3797 kexpunge = dlist_new("EXPUNGE");
3798 dlist_atom(kexpunge, "MBOXNAME", mailbox->name);
3799 dlist_atom(kexpunge, "UNIQUEID", mailbox->uniqueid); /* just for safety */
3800 kuids = dlist_list(kexpunge, "UID");
3801 for (ka = kaction->head; ka; ka = ka->next) {
3802 if (!strcmp(ka->name, "EXPUNGE")) {
3803 dlist_num(kuids, "UID", ka->nval);
3804 }
3805 else if (!strcmp(ka->name, "COPYBACK")) {
3806 r = copy_remote(mailbox, ka->nval, kr);
3807 if (r) goto cleanup;
3808 dlist_num(kuids, "UID", ka->nval);
3809 }
3810 else if (!strcmp(ka->name, "RENUMBER")) {
3811 r = copy_local(mailbox, ka->nval);
3812 if (r) goto cleanup;
3813 }
3814 }
3815
3816 /* we still need to do the EXPUNGEs */
3817 cleanup:
3818
3819 /* close the mailbox before sending any expunges
3820 * to avoid deadlocks */
3821 if (!local->mailbox) mailbox_close(&mailbox);
3822
3823 /* only send expunge if we have some UIDs to expunge */
3824 if (kuids->head) {
3825 int r2;
3826 sync_send_apply(kexpunge, sync_be->out);
3827 r2 = sync_parse_response("EXPUNGE", sync_be->in, NULL);
3828 if (r2) {
3829 syslog(LOG_ERR, "SYNCERROR: failed to expunge in cleanup %s",
3830 local->name);
3831 }
3832 }
3833
3834 done:
3835 if (mailbox && !local->mailbox) mailbox_close(&mailbox);
3836 dlist_free(&kin);
3837 dlist_free(&kaction);
3838 dlist_free(&kexpunge);
3839 return r;
3840 }
3841
3842 static int is_unchanged(struct mailbox *mailbox, struct sync_folder *remote)
3843 {
3844 /* look for any mismatches */
3845 unsigned options = mailbox->i.options & MAILBOX_OPTIONS_MASK;
3846 if (!remote) return 0;
3847 if (remote->mbtype != (uint32_t) mailbox->mbtype) return 0;
3848 if (remote->last_uid != mailbox->i.last_uid) return 0;
3849 if (remote->highestmodseq != mailbox->i.highestmodseq) return 0;
3850 if (remote->sync_crc != mailbox->i.sync_crc) return 0;
3851 if (remote->recentuid != mailbox->i.recentuid) return 0;
3852 if (remote->recenttime != mailbox->i.recenttime) return 0;
3853 if (remote->pop3_last_login != mailbox->i.pop3_last_login) return 0;
3854 if (remote->options != options) return 0;
3855 if (strcmp(remote->acl, mailbox->acl)) return 0;
3856
3857 /* otherwise it's unchanged! */
3858 return 1;
3859 }
3860
3861 #define SYNC_FLAG_ISREPEAT (1<<15)
3862
3863 static int update_mailbox_once(struct sync_folder *local,
3864 struct sync_folder *remote,
3865 const char *topart,
3866 struct sync_reserve_list *reserve_guids,
3867 struct backend *sync_be, unsigned flags)
3868 {
3869 struct sync_msgid_list *part_list;
3870 struct mailbox *mailbox = NULL;
3871 int r = 0;
3872 const char *cmd =
3873 (flags & SYNC_FLAG_LOCALONLY) ? "LOCAL_MAILBOX" : "MAILBOX";
3874 struct dlist *kl = dlist_new(cmd);
3875 struct dlist *kupload = dlist_list(NULL, "MESSAGE");
3876
3877 if (flags & SYNC_FLAG_LOGGING) {
3878 syslog(LOG_INFO, "%s %s", cmd, local->name);
3879 }
3880
3881 if (local->mailbox) mailbox = local->mailbox;
3882 else r = mailbox_open_irl(local->name, &mailbox);
3883 if (r == IMAP_MAILBOX_NONEXISTENT) {
3884 /* been deleted in the meanwhile... */
3885 if (remote)
3886 r = sync_folder_delete(remote->name, sync_be, flags);
3887 else
3888 r = 0;
3889 goto done;
3890 }
3891 else if (r)
3892 goto done;
3893
3894 /* definitely bad if these don't match! */
3895 if (strcmp(mailbox->uniqueid, local->uniqueid) ||
3896 strcmp(mailbox->part, local->part)) {
3897 r = IMAP_MAILBOX_MOVED;
3898 goto done;
3899 }
3900
3901 /* check that replication stands a chance of succeeding */
3902 if (remote && !(flags & SYNC_FLAG_ISREPEAT)) {
3903 if (mailbox->i.deletedmodseq > remote->highestmodseq) {
3904 syslog(LOG_NOTICE, "inefficient replication ("
3905 MODSEQ_FMT " > " MODSEQ_FMT ") %s",
3906 mailbox->i.deletedmodseq, remote->highestmodseq,
3907 local->name);
3908 r = IMAP_AGAIN;
3909 goto done;
3910 }
3911 /* need a full sync to fix uidvalidity issues so we get a
3912 * writelocked mailbox */
3913 if (mailbox->i.uidvalidity < remote->uidvalidity) {
3914 r = IMAP_AGAIN;
3915 goto done;
3916 }
3917 }
3918
3919 /* nothing changed - nothing to send */
3920 if (is_unchanged(mailbox, remote))
3921 goto done;
3922
3923 if (!topart) topart = mailbox->part;
3924 part_list = sync_reserve_partlist(reserve_guids, topart);
3925 r = sync_mailbox(mailbox, remote, topart, part_list, kl, kupload, 1);
3926 if (r) goto done;
3927
3928 /* upload any messages required */
3929 if (kupload->head) {
3930 /* keep the mailbox locked for shorter time! Unlock the index now
3931 * but don't close it, because we need to guarantee that message
3932 * files don't get deleted until we're finished with them... */
3933 if (!local->mailbox) mailbox_unlock_index(mailbox, NULL);
3934 sync_send_apply(kupload, sync_be->out);
3935 r = sync_parse_response("MESSAGE", sync_be->in, NULL);
3936 if (!r) {
3937 /* update our list of reserved messages on the replica */
3938 struct dlist *ki;
3939 struct sync_msgid *msgid;
3940 for (ki = kupload->head; ki; ki = ki->next) {
3941 msgid = sync_msgid_lookup(part_list, &ki->gval);
3942 if (!msgid)
3943 msgid = sync_msgid_add(part_list, &ki->gval);
3944 msgid->mark = 1;
3945 part_list->marked++;
3946 }
3947 }
3948 }
3949
3950 /* close before sending the apply - all data is already read */
3951 if (!local->mailbox) mailbox_close(&mailbox);
3952
3953 /* update the mailbox */
3954 sync_send_apply(kl, sync_be->out);
3955 r = sync_parse_response("MAILBOX", sync_be->in, NULL);
3956
3957 done:
3958 if (mailbox && !local->mailbox) mailbox_close(&mailbox);
3959 dlist_free(&kupload);
3960 dlist_free(&kl);
3961 return r;
3962 }
3963
3964 int sync_update_mailbox(struct sync_folder *local,
3965 struct sync_folder *remote,
3966 const char *topart,
3967 struct sync_reserve_list *reserve_guids,
3968 struct backend *sync_be, unsigned flags)
3969 {
3970 int r = update_mailbox_once(local, remote, topart,
3971 reserve_guids, sync_be, flags);
3972
3973 flags |= SYNC_FLAG_ISREPEAT;
3974
3975 if (r == IMAP_AGAIN) {
3976 r = mailbox_full_update(local, sync_be, flags);
3977 if (!r) r = update_mailbox_once(local, remote, topart,
3978 reserve_guids, sync_be, flags);
3979 }
3980 else if (r == IMAP_MAILBOX_CRC) {
3981 syslog(LOG_ERR, "CRC failure on sync for %s, trying full update",
3982 local->name);
3983 r = mailbox_full_update(local, sync_be, flags);
3984 if (!r) r = update_mailbox_once(local, remote, topart,
3985 reserve_guids, sync_be, flags);
3986 }
3987
3988 return r;
3989 }
3990
3991 /* ====================================================================== */
3992
3993
3994 static int update_seen_work(const char *user, const char *uniqueid,
3995 struct seendata *sd,
3996 struct backend *sync_be)
3997 {
3998 const char *cmd = "SEEN";
3999 struct dlist *kl;
4000
4001 /* Update seen list */
4002 kl = dlist_new(cmd);
4003 dlist_atom(kl, "USERID", user);
4004 dlist_atom(kl, "UNIQUEID", uniqueid);
4005 dlist_date(kl, "LASTREAD", sd->lastread);
4006 dlist_num(kl, "LASTUID", sd->lastuid);
4007 dlist_date(kl, "LASTCHANGE", sd->lastchange);
4008 dlist_atom(kl, "SEENUIDS", sd->seenuids);
4009 sync_send_apply(kl, sync_be->out);
4010 dlist_free(&kl);
4011
4012 return sync_parse_response(cmd, sync_be->in, NULL);
4013 }
4014
4015 int sync_do_seen(char *user, char *uniqueid, struct backend *sync_be,
4016 unsigned flags)
4017 {
4018 int r = 0;
4019 struct seen *seendb = NULL;
4020 struct seendata sd = SEENDATA_INITIALIZER;
4021
4022 if (flags & SYNC_FLAG_VERBOSE)
4023 printf("SEEN %s %s\n", user, uniqueid);
4024
4025 if (flags & SYNC_FLAG_LOGGING)
4026 syslog(LOG_INFO, "SEEN %s %s", user, uniqueid);
4027
4028 /* ignore read failures */
4029 r = seen_open(user, SEEN_SILENT, &seendb);
4030 if (r) return 0;
4031
4032 r = seen_read(seendb, uniqueid, &sd);
4033
4034 if (!r) r = update_seen_work(user, uniqueid, &sd, sync_be);
4035
4036 seen_close(&seendb);
4037 seen_freedata(&sd);
4038
4039 return r;
4040 }
4041
4042 /* ====================================================================== */
4043
4044 int sync_do_quota(const char *root, struct backend *sync_be,
4045 unsigned flags)
4046 {
4047 int r = 0;
4048 struct quota q;
4049
4050 if (flags & SYNC_FLAG_VERBOSE)
4051 printf("SETQUOTA %s\n", root);
4052
4053 if (flags & SYNC_FLAG_LOGGING)
4054 syslog(LOG_INFO, "SETQUOTA: %s", root);
4055
4056 q.root = root;
4057 r = update_quota_work(&q, NULL, sync_be);
4058
4059 return r;
4060 }
4061
4062 static int do_annotation_cb(const char *mailbox __attribute__((unused)),
4063 const char *entry, const char *userid,
4064 struct annotation_data *attrib, void *rock)
4065 {
4066 struct sync_annot_list *l = (struct sync_annot_list *) rock;
4067
4068 sync_annot_list_add(l, entry, userid, attrib->value);
4069
4070 return 0;
4071 }
4072
4073 static int parse_annotation(struct dlist *kin,
4074 struct sync_annot_list *replica_annot)
4075 {
4076 struct dlist *kl;
4077 const char *entry;
4078 const char *userid;
4079 const char *value;
4080
4081 for (kl = kin->head; kl; kl = kl->next) {
4082 if (!dlist_getatom(kl, "ENTRY", &entry))
4083 return IMAP_PROTOCOL_BAD_PARAMETERS;
4084 if (!dlist_getatom(kl, "USERID", &userid))
4085 return IMAP_PROTOCOL_BAD_PARAMETERS;
4086 if (!dlist_getatom(kl, "VALUE", &value))
4087 return IMAP_PROTOCOL_BAD_PARAMETERS;
4088 sync_annot_list_add(replica_annot, entry, userid, value);
4089 }
4090
4091 return 0;
4092 }
4093
4094 static int do_getannotation(char *mboxname,
4095 struct sync_annot_list *replica_annot,
4096 struct backend *sync_be)
4097 {
4098 const char *cmd = "ANNOTATION";
4099 struct dlist *kl;
4100 struct dlist *kin = NULL;
4101 int r;
4102
4103 /* Update seen list */
4104 kl = dlist_atom(NULL, cmd, mboxname);
4105 sync_send_lookup(kl, sync_be->out);
4106 dlist_free(&kl);
4107
4108 r = sync_parse_response(cmd, sync_be->in, &kin);
4109 if (r) return r;
4110
4111 r = parse_annotation(kin, replica_annot);
4112 dlist_free(&kin);
4113
4114 return r;
4115 }
4116
4117 int sync_do_annotation(char *mboxname, struct backend *sync_be,
4118 unsigned flags __attribute__((unused)))
4119 {
4120 int r;
4121 struct sync_annot_list *replica_annot = sync_annot_list_create();
4122 struct sync_annot_list *master_annot = sync_annot_list_create();
4123 struct sync_annot *ma, *ra;
4124 int n;
4125
4126 r = do_getannotation(mboxname, replica_annot, sync_be);
4127 if (r) goto bail;
4128
4129 r = annotatemore_findall(mboxname, "*",
4130 &do_annotation_cb, master_annot, NULL);
4131 if (r) {
4132 syslog(LOG_ERR, "IOERROR: fetching annotations for %s", mboxname);
4133 r = IMAP_IOERROR;
4134 goto bail;
4135 }
4136
4137 /* both lists are sorted, so we work our way through the lists
4138 top-to-bottom and determine what we need to do based on order */
4139 ma = master_annot->head;
4140 ra = replica_annot->head;
4141 while (ma || ra) {
4142 if (!ra) n = -1; /* add all master annotations */
4143 else if (!ma) n = 1; /* remove all replica annotations */
4144 else if ((n = strcmp(ma->entry, ra->entry)) == 0)
4145 n = strcmp(ma->userid, ra->userid);
4146
4147 if (n > 0) {
4148 /* remove replica annotation */
4149 r = folder_unannotation(mboxname, ra->entry, ra->userid, sync_be);
4150 if (r) goto bail;
4151 ra = ra->next;
4152 continue;
4153 }
4154
4155 if (n == 0) {
4156 /* already have the annotation, but is the value different? */
4157 if (!strcmp(ra->value, ma->value)) {
4158 ra = ra->next;
4159 ma = ma->next;
4160 continue;
4161 }
4162 ra = ra->next;
4163 }
4164
4165 /* add the current client annotation */
4166 r = folder_setannotation(mboxname, ma->entry, ma->userid, ma->value,
4167 sync_be);
4168 if (r) goto bail;
4169
4170 ma = ma->next;
4171 }
4172
4173 bail:
4174 sync_annot_list_free(&master_annot);
4175 sync_annot_list_free(&replica_annot);
4176 return r;
4177 }
4178
4179 /* ====================================================================== */
4180
4181 static int do_folders(struct sync_name_list *mboxname_list, const char *topart,
4182 struct sync_folder_list *replica_folders,
4183 struct backend *sync_be,
4184 unsigned flags)
4185 {
4186 int r;
4187 struct sync_folder_list *master_folders;
4188 struct sync_rename_list *rename_folders;
4189 struct sync_reserve_list *reserve_guids;
4190 struct sync_folder *mfolder, *rfolder;
4191 const char *part;
4192
4193 master_folders = sync_folder_list_create();
4194 rename_folders = sync_rename_list_create();
4195 reserve_guids = sync_reserve_list_create(SYNC_MSGID_LIST_HASH_SIZE);
4196
4197 r = reserve_messages(mboxname_list, topart, master_folders,
4198 replica_folders, reserve_guids, sync_be);
4199 if (r) {
4200 syslog(LOG_ERR, "reserve messages: failed: %s", error_message(r));
4201 goto bail;
4202 }
4203
4204 /* Tag folders on server which still exist on the client. Anything
4205 * on the server which remains untagged can be deleted immediately */
4206 for (mfolder = master_folders->head; mfolder; mfolder = mfolder->next) {
4207 rfolder = sync_folder_lookup(replica_folders, mfolder->uniqueid);
4208 if (!rfolder) continue;
4209 rfolder->mark = 1;
4210
4211 /* does it need a rename? */
4212 part = topart ? topart : mfolder->part;
4213 if (strcmp(mfolder->name, rfolder->name) || strcmp(part, rfolder->part))
4214 sync_rename_list_add(rename_folders, mfolder->uniqueid, rfolder->name,
4215 mfolder->name, part);
4216 }
4217
4218 /* Delete folders on server which no longer exist on client */
4219 for (rfolder = replica_folders->head; rfolder; rfolder = rfolder->next) {
4220 if (rfolder->mark) continue;
4221 r = sync_folder_delete(rfolder->name, sync_be, flags);
4222 if (r) {
4223 syslog(LOG_ERR, "sync_folder_delete(): failed: %s '%s'",
4224 rfolder->name, error_message(r));
4225 goto bail;
4226 }
4227 }
4228
4229 /* Need to rename folders in an order which avoids dependancy conflicts
4230 * following isn't wildly efficient, but rename_folders will typically be
4231 * short and contain few dependancies. Algorithm is to simply pick a
4232 * rename operation which has no dependancy and repeat until done */
4233
4234 while (rename_folders->done < rename_folders->count) {
4235 int rename_success = 0;
4236 struct sync_rename *item, *item2;
4237
4238 for (item = rename_folders->head; item; item = item->next) {
4239 if (item->done) continue;
4240
4241 /* don't skip rename to different partition */
4242 if (strcmp(item->oldname, item->newname)) {
4243 item2 = sync_rename_lookup(rename_folders, item->newname);
4244 if (item2 && !item2->done) continue;
4245 }
4246
4247 /* Found unprocessed item which should rename cleanly */
4248 r = folder_rename(item->oldname, item->newname, item->part,
4249 sync_be, flags);
4250 if (r) {
4251 syslog(LOG_ERR, "do_folders(): failed to rename: %s -> %s ",
4252 item->oldname, item->newname);
4253 goto bail;
4254 }
4255
4256 rename_folders->done++;
4257 item->done = 1;
4258 rename_success = 1;
4259 }
4260
4261 if (!rename_success) {
4262 /* Scanned entire list without a match */
4263 syslog(LOG_ERR,
4264 "do_folders(): failed to order folders correctly");
4265 r = IMAP_AGAIN;
4266 goto bail;
4267 }
4268 }
4269
4270 for (mfolder = master_folders->head; mfolder; mfolder = mfolder->next) {
4271 /* NOTE: rfolder->name may now be wrong, but we're guaranteed that
4272 * it was successfully renamed above, so just use mfolder->name for
4273 * all commands */
4274 rfolder = sync_folder_lookup(replica_folders, mfolder->uniqueid);
4275 r = sync_update_mailbox(mfolder, rfolder, topart, reserve_guids,
4276 sync_be, flags);
4277 if (r) {
4278 syslog(LOG_ERR, "do_folders(): update failed: %s '%s'",
4279 mfolder->name, error_message(r));
4280 goto bail;
4281 }
4282 }
4283
4284 bail:
4285 sync_folder_list_free(&master_folders);
4286 sync_rename_list_free(&rename_folders);
4287 sync_reserve_list_free(&reserve_guids);
4288 return r;
4289 }
4290
4291 int sync_do_mailboxes(struct sync_name_list *mboxname_list, const char *topart,
4292 struct backend *sync_be, unsigned flags)
4293
4294 {
4295 struct sync_name *mbox;
4296 struct sync_folder_list *replica_folders = sync_folder_list_create();
4297 struct dlist *kl = NULL;
4298 int r;
4299
4300 if (flags & SYNC_FLAG_VERBOSE) {
4301 printf("MAILBOXES");
4302 for (mbox = mboxname_list->head; mbox; mbox = mbox->next) {
4303 printf(" %s", mbox->name);
4304 }
4305 printf("\n");
4306 }
4307
4308 if (flags & SYNC_FLAG_LOGGING) {
4309 struct buf buf = BUF_INITIALIZER;
4310
4311 buf_setcstr(&buf, "MAILBOXES");
4312 for (mbox = mboxname_list->head; mbox; mbox = mbox->next)
4313 buf_printf(&buf, " %s", mbox->name);
4314 syslog(LOG_INFO, "%s", buf_cstring(&buf));
4315 buf_free(&buf);
4316 }
4317
4318 kl = dlist_list(NULL, "MAILBOXES");
4319 for (mbox = mboxname_list->head; mbox; mbox = mbox->next)
4320 dlist_atom(kl, "MBOXNAME", mbox->name);
4321 sync_send_lookup(kl, sync_be->out);
4322 dlist_free(&kl);
4323
4324 r = sync_response_parse(sync_be->in, "MAILBOXES", replica_folders,
4325 NULL, NULL, NULL, NULL);
4326
4327 if (!r) r = do_folders(mboxname_list, topart,
4328 replica_folders, sync_be, flags);
4329
4330 sync_folder_list_free(&replica_folders);
4331
4332 return r;
4333 }
4334
4335 /* ====================================================================== */
4336
4337 struct mboxinfo {
4338 struct sync_name_list *mboxlist;
4339 struct sync_name_list *quotalist;
4340 };
4341
4342 static int do_mailbox_info(char *name,
4343 int matchlen __attribute__((unused)),
4344 int maycreate __attribute__((unused)),
4345 void *rock)
4346 {
4347 int r;
4348 struct mailbox *mailbox = NULL;
4349 struct mboxinfo *info = (struct mboxinfo *)rock;
4350
4351 r = mailbox_open_irl(name, &mailbox);
4352 /* doesn't exist? Probably not finished creating or removing yet */
4353 if (r == IMAP_MAILBOX_NONEXISTENT) return 0;
4354 if (r == IMAP_MAILBOX_RESERVED) return 0;
4355 if (r) return r;
4356
4357 if (info->quotalist && mailbox->quotaroot) {
4358 if (!sync_name_lookup(info->quotalist, mailbox->quotaroot))
4359 sync_name_list_add(info->quotalist, mailbox->quotaroot);
4360 }
4361
4362 mailbox_close(&mailbox);
4363
4364 addmbox(name, 0, 0, info->mboxlist);
4365
4366 return 0;
4367 }
4368
4369 int sync_do_user_quota(struct sync_name_list *master_quotaroots,
4370 struct sync_quota_list *replica_quota,
4371 struct backend *sync_be)
4372 {
4373 int r;
4374 struct sync_name *mitem;
4375 struct sync_quota *rquota;
4376 struct quota q;
4377
4378 /* set any new or changed quotas */
4379 for (mitem = master_quotaroots->head; mitem; mitem = mitem->next) {
4380 rquota = sync_quota_lookup(replica_quota, mitem->name);
4381 q.root = mitem->name;
4382 if (rquota)
4383 rquota->done = 1;
4384 r = update_quota_work(&q, rquota, sync_be);
4385 if (r) return r;
4386 }
4387
4388 /* delete any quotas no longer on the master */
4389 for (rquota = replica_quota->head; rquota; rquota = rquota->next) {
4390 if (rquota->done) continue;
4391 r = delete_quota(rquota->root, sync_be);
4392 if (r) return r;
4393 }
4394
4395 return 0;
4396 }
4397
4398 static int do_user_main(char *user, const char *topart,
4399 struct sync_folder_list *replica_folders,
4400 struct sync_quota_list *replica_quota,
4401 struct namespace *sync_namespace,
4402 struct backend *sync_be,
4403 unsigned flags)
4404 {
4405 char buf[MAX_MAILBOX_BUFFER];
4406 int r = 0;
4407 struct sync_name_list *mboxname_list = sync_name_list_create();
4408 struct sync_name_list *master_quotaroots = sync_name_list_create();
4409 struct mboxinfo info;
4410
4411 info.mboxlist = mboxname_list;
4412 info.quotalist = master_quotaroots;
4413
4414 /* Generate full list of folders on client side */
4415 (*sync_namespace->mboxname_tointernal)(sync_namespace, "INBOX",
4416 user, buf);
4417 do_mailbox_info(buf, 0, 0, &info);
4418
4419 /* deleted namespace items if enabled */
4420 if (mboxlist_delayed_delete_isenabled()) {
4421 char deletedname[MAX_MAILBOX_BUFFER];
4422 mboxname_todeleted(buf, deletedname, 0);
4423 strlcat(deletedname, ".*", sizeof(deletedname));
4424 r = (*sync_namespace->mboxlist_findall)(sync_namespace, deletedname, 1,
4425 user, NULL, do_mailbox_info,
4426 &info);
4427 }
4428
4429 /* subfolders */
4430 if (!r) {
4431 strlcat(buf, ".*", sizeof(buf));
4432 r = (*sync_namespace->mboxlist_findall)(sync_namespace, buf, 1,
4433 user, NULL, do_mailbox_info,
4434 &info);
4435 }
4436
4437 if (!r) r = do_folders(mboxname_list, topart,
4438 replica_folders, sync_be, flags);
4439 if (!r) r = sync_do_user_quota(master_quotaroots, replica_quota, sync_be);
4440
4441 sync_name_list_free(&mboxname_list);
4442 sync_name_list_free(&master_quotaroots);
4443
4444 if (r) syslog(LOG_ERR, "IOERROR: %s", error_message(r));
4445
4446 return r;
4447 }
4448
4449 int sync_do_user_sub(const char *userid, struct sync_name_list *replica_subs,
4450 struct backend *sync_be, unsigned flags)
4451 {
4452 struct sync_name_list *master_subs = sync_name_list_create();
4453 struct sync_name *msubs, *rsubs;
4454 int r = 0;
4455
4456 /* Includes subsiduary nodes automatically */
4457 r = mboxlist_allsubs(userid, addmbox_sub, master_subs);
4458 if (r) {
4459 syslog(LOG_ERR, "IOERROR: fetching subscriptions for %s", userid);
4460 r = IMAP_IOERROR;
4461 goto bail;
4462 }
4463
4464 /* add any folders that need adding, and mark any which
4465 * still exist */
4466 for (msubs = master_subs->head; msubs; msubs = msubs->next) {
4467 rsubs = sync_name_lookup(replica_subs, msubs->name);
4468 if (rsubs) {
4469 rsubs->mark = 1;
4470 continue;
4471 }
4472 r = sync_set_sub(userid, msubs->name, 1, sync_be, flags);
4473 if (r) goto bail;
4474 }
4475
4476 /* remove any no-longer-subscribed folders */
4477 for (rsubs = replica_subs->head; rsubs; rsubs = rsubs->next) {
4478 if (rsubs->mark)
4479 continue;
4480 r = sync_set_sub(userid, rsubs->name, 0, sync_be, flags);
4481 if (r) goto bail;
4482 }
4483
4484 bail:
4485 sync_name_list_free(&master_subs);
4486 return r;
4487 }
4488
4489 static int get_seen(const char *uniqueid, struct seendata *sd, void *rock)
4490 {
4491 struct sync_seen_list *list = (struct sync_seen_list *)rock;
4492
4493 sync_seen_list_add(list, uniqueid, sd->lastread, sd->lastuid,
4494 sd->lastchange, sd->seenuids);
4495
4496 return 0;
4497 }
4498
4499 int sync_do_user_seen(char *user, struct sync_seen_list *replica_seen,
4500 struct backend *sync_be)
4501 {
4502 int r;
4503 struct sync_seen *mseen, *rseen;
4504 struct seen *seendb = NULL;
4505 struct sync_seen_list *list;
4506
4507 /* silently ignore errors */
4508 r = seen_open(user, SEEN_SILENT, &seendb);
4509 if (r) return 0;
4510
4511 list = sync_seen_list_create();
4512
4513 seen_foreach(seendb, get_seen, list);
4514 seen_close(&seendb);
4515
4516 for (mseen = list->head; mseen; mseen = mseen->next) {
4517 rseen = sync_seen_list_lookup(replica_seen, mseen->uniqueid);
4518 if (rseen) {
4519 rseen->mark = 1;
4520 if (seen_compare(&rseen->sd, &mseen->sd))
4521 continue; /* nothing changed */
4522 }
4523 r = update_seen_work(user, mseen->uniqueid, &mseen->sd, sync_be);
4524 }
4525
4526 /* XXX - delete seen on the replica for records that don't exist? */
4527
4528 sync_seen_list_free(&list);
4529
4530 return 0;
4531 }
4532
4533 int sync_do_user_sieve(char *userid, struct sync_sieve_list *replica_sieve,
4534 struct backend *sync_be)
4535 {
4536 int r = 0;
4537 struct sync_sieve_list *master_sieve;
4538 struct sync_sieve *mitem, *ritem;
4539 int master_active = 0;
4540 int replica_active = 0;
4541
4542 master_sieve = sync_sieve_list_generate(userid);
4543 if (!master_sieve) {
4544 syslog(LOG_ERR, "Unable to list sieve scripts for %s", userid);
4545 return IMAP_IOERROR;
4546 }
4547
4548 /* Upload missing and out of date scripts */
4549 for (mitem = master_sieve->head; mitem; mitem = mitem->next) {
4550 ritem = sync_sieve_lookup(replica_sieve, mitem->name);
4551 if (ritem) {
4552 ritem->mark = 1;
4553 if (ritem->last_update >= mitem->last_update)
4554 continue; /* doesn't need updating */
4555 }
4556 r = sieve_upload(userid, mitem->name, mitem->last_update, sync_be);
4557 if (r) goto bail;
4558 }
4559
4560 /* Delete scripts which no longer exist on the master */
4561 replica_active = 0;
4562 for (ritem = replica_sieve->head; ritem; ritem = ritem->next) {
4563 if (ritem->mark) {
4564 if (ritem->active)
4565 replica_active = 1;
4566 } else {
4567 r = sieve_delete(userid, ritem->name, sync_be);
4568 if (r) goto bail;
4569 }
4570 }
4571
4572 /* Change active script if necessary */
4573 master_active = 0;
4574 for (mitem = master_sieve->head; mitem; mitem = mitem->next) {
4575 if (!mitem->active)
4576 continue;
4577
4578 master_active = 1;
4579 ritem = sync_sieve_lookup(replica_sieve, mitem->name);
4580 if (ritem && ritem->active)
4581 break;
4582
4583 r = sieve_activate(userid, mitem->name, sync_be);
4584 if (r) goto bail;
4585
4586 replica_active = 1;
4587 break;
4588 }
4589
4590 if (!master_active && replica_active)
4591 r = sieve_deactivate(userid, sync_be);
4592
4593 bail:
4594 sync_sieve_list_free(&master_sieve);
4595 return(r);
4596 }
4597
4598 int sync_do_user(char *userid, const char *topart,
4599 struct backend *sync_be, unsigned flags)
4600 {
4601 char buf[MAX_MAILBOX_BUFFER];
4602 int r = 0;
4603 struct sync_folder_list *replica_folders = sync_folder_list_create();
4604 struct sync_name_list *replica_subs = sync_name_list_create();
4605 struct sync_sieve_list *replica_sieve = sync_sieve_list_create();
4606 struct sync_seen_list *replica_seen = sync_seen_list_create();
4607 struct sync_quota_list *replica_quota = sync_quota_list_create();
4608 struct dlist *kl = NULL;
4609 struct mailbox *mailbox = NULL;
4610 static struct namespace sync_namespace = NAMESPACE_INITIALIZER;
4611
4612 if (flags & SYNC_FLAG_VERBOSE)
4613 printf("USER %s\n", userid);
4614
4615 if (flags & SYNC_FLAG_LOGGING)
4616 syslog(LOG_INFO, "USER %s", userid);
4617
4618 kl = dlist_atom(NULL, "USER", userid);
4619 sync_send_lookup(kl, sync_be->out);
4620 dlist_free(&kl);
4621
4622 r = sync_response_parse(sync_be->in, "USER", replica_folders, replica_subs,
4623 replica_sieve, replica_seen, replica_quota);
4624 /* can happen! */
4625 if (r == IMAP_MAILBOX_NONEXISTENT) r = 0;
4626 if (r) goto done;
4627
4628 if (!sync_namespace.mboxname_tointernal &&
4629 (r = mboxname_init_namespace(&sync_namespace, 1)) != 0) {
4630 fatal(error_message(r), EC_CONFIG);
4631 }
4632
4633 (*sync_namespace.mboxname_tointernal)(&sync_namespace, "INBOX",
4634 userid, buf);
4635 r = mailbox_open_irl(buf, &mailbox);
4636 if (r == IMAP_MAILBOX_NONEXISTENT) {
4637 /* user has been removed, RESET server */
4638 syslog(LOG_ERR, "Inbox missing on master for %s", userid);
4639 r = user_reset(userid, sync_be);
4640 goto done;
4641 }
4642 if (r) goto done;
4643
4644 /* we don't hold locks while sending commands */
4645 mailbox_close(&mailbox);
4646 r = do_user_main(userid, topart, replica_folders, replica_quota,
4647 &sync_namespace, sync_be, flags);
4648 if (r) goto done;
4649 r = sync_do_user_sub(userid, replica_subs, sync_be, flags);
4650 if (r) goto done;
4651 r = sync_do_user_sieve(userid, replica_sieve, sync_be);
4652 if (r) goto done;
4653 r = sync_do_user_seen(userid, replica_seen, sync_be);
4654
4655 done:
4656 sync_folder_list_free(&replica_folders);
4657 sync_name_list_free(&replica_subs);
4658 sync_sieve_list_free(&replica_sieve);
4659 sync_seen_list_free(&replica_seen);
4660 sync_quota_list_free(&replica_quota);
4661
4662 return r;
4663 }
4664
4665 /* ====================================================================== */
4666
4667 int sync_do_meta(char *userid, struct backend *sync_be, unsigned flags)
4668 {
4669 struct sync_name_list *replica_subs = sync_name_list_create();
4670 struct sync_sieve_list *replica_sieve = sync_sieve_list_create();
4671 struct sync_seen_list *replica_seen = sync_seen_list_create();
4672 struct dlist *kl = NULL;
4673 int r = 0;
4674
4675 if (flags & SYNC_FLAG_VERBOSE)
4676 printf("META %s\n", userid);
4677
4678 if (flags & SYNC_FLAG_LOGGING)
4679 syslog(LOG_INFO, "META %s", userid);
4680
4681 kl = dlist_atom(NULL, "META", userid);
4682 sync_send_lookup(kl, sync_be->out);
4683 dlist_free(&kl);
4684
4685 r = sync_response_parse(sync_be->in, "META", NULL,
4686 replica_subs, replica_sieve, replica_seen, NULL);
4687 if (!r) r = sync_do_user_seen(userid, replica_seen, sync_be);
4688 if (!r) r = sync_do_user_sub(userid, replica_subs, sync_be, flags);
4689 if (!r) r = sync_do_user_sieve(userid, replica_sieve, sync_be);
4690 sync_seen_list_free(&replica_seen);
4691 sync_name_list_free(&replica_subs);
4692 sync_sieve_list_free(&replica_sieve);
4693
4694 return r;
4695 }
4696
4697 /* ====================================================================== */
0 /* sync_support.h -- Cyrus synchonization support functions
0 /* sync_support.h -- Cyrus synchronization support functions
11 *
22 * Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
33 *
4747 #ifndef INCLUDED_SYNC_SUPPORT_H
4848 #define INCLUDED_SYNC_SUPPORT_H
4949
50 #include "backend.h"
5051 #include "dlist.h"
5152 #include "prot.h"
5253 #include "mailbox.h"
136137
137138 struct sync_folder {
138139 struct sync_folder *next;
140 struct mailbox *mailbox;
139141 char *uniqueid;
140142 char *name;
143 uint32_t mbtype;
141144 char *part;
142145 char *acl;
143146 unsigned last_uid;
149152 time_t recenttime;
150153 time_t pop3_last_login;
151154 struct quota quota;
152 int mark;
155 int mark;
153156 int reserve; /* Folder has been processed by reserve operation */
154157 };
155158
162165
163166 struct sync_folder *sync_folder_list_add(struct sync_folder_list *l,
164167 const char *uniqueid, const char *name,
165 const char *part, const char *acl,
168 uint32_t mbtype,
169 const char *part, const char *acl,
166170 uint32_t options,
167 uint32_t uidvalidity,
171 uint32_t uidvalidity,
168172 uint32_t last_uid,
169173 modseq_t highestmodseq,
170174 uint32_t crc,
389393 const char *data __attribute__((unused)),
390394 int datalen __attribute__((unused)));
391395
392 int sync_mailbox(struct mailbox *mailbox,
393 struct sync_folder *remote,
394 struct sync_msgid_list *part_list,
395 struct dlist *kl, struct dlist *kupload,
396 int printrecords);
397
398 int parse_upload(struct dlist *kr, struct mailbox *mailbox,
399 struct index_record *record);
400 int sync_append_copyfile(struct mailbox *mailbox,
401 struct index_record *record);
396 /* ======================= server-side sync =========================== */
397
398 struct sync_state {
399 char *userid;
400 int userisadmin;
401 struct auth_state *authstate;
402 struct namespace *namespace;
403 struct protstream *pout;
404 int local_only;
405 };
406
407 int sync_get_message(struct dlist *kin, struct sync_state *sstate);
408 int sync_get_sieve(struct dlist *kin, struct sync_state *sstate);
409 int sync_get_annotation(struct dlist *kin, struct sync_state *sstate);
410 int sync_get_quota(struct dlist *kin, struct sync_state *sstate);
411 int sync_get_fullmailbox(struct dlist *kin, struct sync_state *sstate);
412 int sync_get_mailboxes(struct dlist *kin, struct sync_state *sstate);
413 int sync_get_meta(struct dlist *kin, struct sync_state *sstate);
414 int sync_get_user(struct dlist *kin, struct sync_state *sstate);
415
416 int sync_apply_reserve(struct dlist *kl,
417 struct sync_reserve_list *reserve_list,
418 struct sync_state *sstate);
419 int sync_apply_unquota(struct dlist *kin, struct sync_state *sstate);
420 int sync_apply_quota(struct dlist *kin, struct sync_state *sstate);
421 int sync_apply_mailbox(struct dlist *kin, struct sync_state *sstate);
422 int sync_apply_unmailbox(struct dlist *kin, struct sync_state *sstate);
423 int sync_apply_rename(struct dlist *kin, struct sync_state *sstate);
424 int sync_apply_changesub(struct dlist *kin, struct sync_state *sstate);
425 int sync_apply_annotation(struct dlist *kin, struct sync_state *sstate);
426 int sync_apply_unannotation(struct dlist *kin, struct sync_state *sstate);
427 int sync_apply_sieve(struct dlist *kin, struct sync_state *sstate);
428 int sync_apply_unsieve(struct dlist *kin, struct sync_state *sstate);
429 int sync_apply_activate_sieve(struct dlist *kin, struct sync_state *sstate);
430 int sync_apply_unactivate_sieve(struct dlist *kin, struct sync_state *sstate);
431 int sync_apply_seen(struct dlist *kin, struct sync_state *sstate);
432 int sync_apply_unuser(struct dlist *kin, struct sync_state *sstate);
433 int sync_apply_expunge(struct dlist *kin, struct sync_state *sstate);
434 int sync_apply_message(struct dlist *kin,
435 struct sync_reserve_list *reserve_list,
436 struct sync_state *sstate);
437
438 void sync_print_response(char *tag, int r, struct protstream *pout);
439
440 /* ======================= client-side sync =========================== */
441
442 #define SYNC_FLAG_VERBOSE (1<<0)
443 #define SYNC_FLAG_LOGGING (1<<1)
444 #define SYNC_FLAG_LOCALONLY (1<<2)
445
446 int sync_do_seen(char *user, char *uniqueid, struct backend *sync_be,
447 unsigned flags);
448 int sync_do_quota(const char *root, struct backend *sync_be, unsigned flags);
449 int sync_do_annotation(char *mboxname, struct backend *sync_be, unsigned flags);
450 int sync_do_mailboxes(struct sync_name_list *mboxname_list,
451 const char *topart,
452 struct backend *sync_be, unsigned flags);
453 int sync_do_user(char *userid, const char *topart,
454 struct backend *sync_be, unsigned flags);
455 int sync_do_meta(char *userid, struct backend *sync_be, unsigned flags);
456 int sync_set_sub(const char *userid, const char *mboxname, int add,
457 struct backend *sync_be, unsigned flags);
458 int sync_response_parse(struct protstream *sync_in, const char *cmd,
459 struct sync_folder_list *folder_list,
460 struct sync_name_list *sub_list,
461 struct sync_sieve_list *sieve_list,
462 struct sync_seen_list *seen_list,
463 struct sync_quota_list *quota_list);
464 int sync_find_reserve_messages(struct mailbox *mailbox,
465 unsigned last_uid,
466 struct sync_msgid_list *part_list);
467 int sync_reserve_partition(char *partition,
468 struct sync_folder_list *replica_folders,
469 struct sync_msgid_list *part_list,
470 struct backend *sync_be);
471 int sync_update_mailbox(struct sync_folder *local,
472 struct sync_folder *remote,
473 const char *topart,
474 struct sync_reserve_list *reserve_guids,
475 struct backend *sync_be, unsigned flags);
476 int sync_folder_delete(char *mboxname, struct backend *sync_be, unsigned flags);
477 int sync_do_user_quota(struct sync_name_list *master_quotaroots,
478 struct sync_quota_list *replica_quota,
479 struct backend *sync_be);
480 int sync_do_user_sub(const char *userid, struct sync_name_list *replica_subs,
481 struct backend *sync_be, unsigned flags);
482 int sync_do_user_seen(char *user, struct sync_seen_list *replica_seen,
483 struct backend *sync_be);
484 int sync_do_user_sieve(char *userid, struct sync_sieve_list *replica_sieve,
485 struct backend *sync_be);
402486
403487 /* ====================================================================== */
404488
684684 SSL_CTX_set_options(s_ctx, off);
685685 SSL_CTX_set_info_callback(s_ctx, (void (*)()) apps_ssl_info_callback);
686686
687 /* Don't use an internal session cache */
688 SSL_CTX_sess_set_cache_size(s_ctx, 1); /* 0 is unlimited, so use 1 */
689 SSL_CTX_set_session_cache_mode(s_ctx, SSL_SESS_CACHE_SERVER |
690 SSL_SESS_CACHE_NO_AUTO_CLEAR |
691 SSL_SESS_CACHE_NO_INTERNAL_LOOKUP);
692
693 /* Get the session timeout from the config file (in minutes) */
694 timeout = config_getint(IMAPOPT_TLS_SESSION_TIMEOUT);
695 if (timeout < 0) timeout = 0;
696 if (timeout > 1440) timeout = 1440; /* 24 hours max */
697
698 /* A timeout of zero disables session caching */
699 if (timeout) {
700 const char *fname = NULL;
701 char *tofree = NULL;
702 int r;
703
704 /* Set the context for session reuse -- use the service ident */
705 SSL_CTX_set_session_id_context(s_ctx, (void*) ident, strlen(ident));
706
707 /* Set the timeout for the internal/external cache (in seconds) */
708 SSL_CTX_set_timeout(s_ctx, timeout*60);
709
710 /* Set the callback functions for the external session cache */
711 SSL_CTX_sess_set_new_cb(s_ctx, new_session_cb);
712 SSL_CTX_sess_set_remove_cb(s_ctx, remove_session_cb);
713 SSL_CTX_sess_set_get_cb(s_ctx, get_session_cb);
714
715 fname = config_getstring(IMAPOPT_TLSCACHE_DB_PATH);
716
717 /* create the name of the db file */
718 if (!fname) {
719 tofree = strconcat(config_dir, FNAME_TLSSESSIONS, (char *)NULL);
720 fname = tofree;
721 }
722
723 r = (DB->open)(fname, CYRUSDB_CREATE, &sessdb);
724 if (r != 0) {
725 syslog(LOG_ERR, "DBERROR: opening %s: %s",
726 fname, cyrusdb_strerror(ret));
727 }
728 else
729 sess_dbopen = 1;
730
731 free(tofree);
732 }
733
734687 cipher_list = config_getstring(IMAPOPT_TLS_CIPHER_LIST);
735688 if (!SSL_CTX_set_cipher_list(s_ctx, cipher_list)) {
736689 syslog(LOG_ERR,"TLS server engine: cannot load cipher list '%s'",
784737 } else {
785738 SSL_CTX_set_client_CA_list(s_ctx, SSL_load_client_CA_file(CAfile));
786739 }
740 }
741
742 /* Don't use an internal session cache */
743 SSL_CTX_sess_set_cache_size(s_ctx, 1); /* 0 is unlimited, so use 1 */
744 SSL_CTX_set_session_cache_mode(s_ctx, SSL_SESS_CACHE_SERVER |
745 SSL_SESS_CACHE_NO_AUTO_CLEAR |
746 SSL_SESS_CACHE_NO_INTERNAL_LOOKUP);
747
748 /* Get the session timeout from the config file (in minutes) */
749 timeout = config_getint(IMAPOPT_TLS_SESSION_TIMEOUT);
750 if (timeout < 0) timeout = 0;
751 if (timeout > 1440) timeout = 1440; /* 24 hours max */
752
753 /* A timeout of zero disables session caching */
754 if (timeout) {
755 const char *fname = NULL;
756 char *tofree = NULL;
757 int r;
758
759 /* Set the context for session reuse -- use the service ident */
760 SSL_CTX_set_session_id_context(s_ctx, (void*) ident, strlen(ident));
761
762 /* Set the timeout for the internal/external cache (in seconds) */
763 SSL_CTX_set_timeout(s_ctx, timeout*60);
764
765 /* Set the callback functions for the external session cache */
766 SSL_CTX_sess_set_new_cb(s_ctx, new_session_cb);
767 SSL_CTX_sess_set_remove_cb(s_ctx, remove_session_cb);
768 SSL_CTX_sess_set_get_cb(s_ctx, get_session_cb);
769
770 fname = config_getstring(IMAPOPT_TLSCACHE_DB_PATH);
771
772 /* create the name of the db file */
773 if (!fname) {
774 tofree = strconcat(config_dir, FNAME_TLSSESSIONS, (char *)NULL);
775 fname = tofree;
776 }
777
778 r = (DB->open)(fname, CYRUSDB_CREATE, &sessdb);
779 if (r != 0) {
780 syslog(LOG_ERR, "DBERROR: opening %s: %s",
781 fname, cyrusdb_strerror(ret));
782 }
783 else
784 sess_dbopen = 1;
785
786 free(tofree);
787787 }
788788
789789 tls_serverengine = 1;
0 # timezone_err.et -- Error codes for the Cyrus Timezone Service
1 #
2 # Copyright (c) 1994-2014 Carnegie Mellon University. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions
6 # are met:
7 #
8 # 1. Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #
11 # 2. Redistributions in binary form must reproduce the above copyright
12 # notice, this list of conditions and the following disclaimer in
13 # the documentation and/or other materials provided with the
14 # distribution.
15 #
16 # 3. The name "Carnegie Mellon University" must not be used to
17 # endorse or promote products derived from this software without
18 # prior written permission. For permission or any legal
19 # details, please contact
20 # Carnegie Mellon University
21 # Center for Technology Transfer and Enterprise Creation
22 # 4615 Forbes Avenue
23 # Suite 302
24 # Pittsburgh, PA 15213
25 # (412) 268-7393, fax: (412) 268-7395
26 # innovation@andrew.cmu.edu
27 #
28 # 4. Redistributions of any form whatsoever must retain the following
29 # acknowledgment:
30 # "This product includes software developed by Computing Services
31 # at Carnegie Mellon University (http://www.cmu.edu/computing/)."
32 #
33 # CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
34 # THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
35 # AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
36 # FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
37 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
38 # AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
39 # OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
40 #
41
42 error_table tz
43
44 ec TZ_INVALID_ACTION,
45 "invalid-action"
46
47 ec TZ_INVALID_TZID,
48 "invalid-tzid"
49
50 ec TZ_INVALID_NAME,
51 "invalid-name"
52
53 ec TZ_INVALID_FORMAT,
54 "invalid-format"
55
56 ec TZ_INVALID_START,
57 "invalid-start"
58
59 ec TZ_INVALID_END,
60 "invalid-end"
61
62 ec TZ_INVALID_CHANGEDSINCE,
63 "invalid-changedsince"
64
65 ec TZ_INVALID_TRUNCATE,
66 "invalid-truncate"
67
68 ec TZ_NOT_FOUND,
69 "tzid-not-found"
70
71 end
7171 # endif
7272 #endif
7373
74 #include "dav_util.h"
7475 #include "global.h"
7576 #include "user.h"
7677 #include "mboxkey.h"
182183 return 0;
183184 }
184185
186 static int user_deletedav(const char *userid)
187 {
188 struct buf fname = BUF_INITIALIZER;
189 int r = 0;
190
191 dav_getpath_byuserid(&fname, userid);
192 if (unlink(buf_cstring(&fname)) && errno != ENOENT) {
193 syslog(LOG_WARNING, "error unlinking %s: %m", buf_cstring(&fname));
194 r = CYRUSDB_IOERROR;
195 }
196
197 buf_free(&fname);
198
199 return r;
200 }
201
185202 int user_deletedata(char *user, char *userid __attribute__((unused)),
186203 struct auth_state *authstate __attribute__((unused)),
187204 int wipe_user)
216233
217234 /* delete sieve scripts */
218235 user_deletesieve(user);
236
237 /* delete DAV database */
238 user_deletedav(user);
219239
220240 sync_log_user(user);
221241
4949 #include <libxml/tree.h>
5050
5151 #include "httpd.h"
52 #include "tok.h"
5253 #include "util.h"
5354 #include "version.h"
5455 #include "xcal.h"
5758 extern icalvalue_kind icalproperty_kind_to_value_kind(icalproperty_kind kind);
5859 extern const char* icalrecur_freq_to_string(icalrecurrencetype_frequency kind);
5960 extern const char* icalrecur_weekday_to_string(icalrecurrencetype_weekday kind);
61 #ifdef HAVE_RSCALE
62 extern const char* icalrecur_skip_to_string(icalrecurrencetype_skip kind);
63 #endif
6064
6165
6266 /*
197201
198202 add_str(obj, "freq", icalrecur_freq_to_string(recur->freq));
199203
204 #ifdef HAVE_RSCALE
205 if (recur->rscale) {
206 add_str(obj, "rscale", recur->rscale);
207
208 if (recur->skip != ICAL_SKIP_BACKWARD)
209 add_str(obj, "skip", icalrecur_skip_to_string(recur->skip));
210 }
211 #endif
212
200213 /* until and count are mutually exclusive */
201214 if (recur->until.year) {
202215 add_str(obj, "until", icaltime_as_iso_string(recur->until));
225238 int limit = recurmap[j].limit - 1;
226239
227240 for (i = 0; i < limit && array[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) {
241 char temp[20];
242
228243 if (j == 3) { /* BYDAY */
229244 const char *daystr;
230245 int pos;
234249 pos = icalrecurrencetype_day_position(array[i]);
235250
236251 if (pos != 0) {
237 char temp[20];
238
239252 snprintf(temp, sizeof(temp), "%d%s", pos, daystr);
240253 daystr = temp;
241254 }
242255
243256 add_str(obj, recurmap[j].str, daystr);
244257 }
258 #ifdef HAVE_RSCALE
259 else if (j == 7 /* BYMONTH */
260 && icalrecurrencetype_month_is_leap(array[i])) {
261 snprintf(temp, sizeof(temp), "%dL",
262 icalrecurrencetype_month_month(array[i]));
263 add_str(obj, recurmap[j].str, temp);
264 }
265 #endif
245266 else add_int(obj, recurmap[j].str, array[i]);
246267 }
247268 }
456477
457478 default:
458479 str = icalvalue_as_ical_string(value);
480
481 switch (icalproperty_isa(prop)) {
482 case ICAL_CATEGORIES_PROPERTY:
483 case ICAL_RESOURCES_PROPERTY:
484 case ICAL_POLLPROPERTIES_PROPERTY:
485 if (strchr(str, ',')) {
486 /* Handle multi-valued properties */
487 tok_t tok;
488
489 tok_init(&tok, str, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT|TOK_EMPTY);
490 str = tok_next(&tok);
491 xmlAddChild(xtype, xmlNewText(BAD_CAST str));
492
493 while ((str = tok_next(&tok))) {
494 if (*str) {
495 xtype = xmlNewChild(xprop, NULL, BAD_CAST type, NULL);
496 xmlAddChild(xtype, xmlNewText(BAD_CAST str));
497 }
498 }
499 tok_fini(&tok);
500 return;
501 }
502
503 default: break;
504 }
505
459506 break;
460507 }
461508
508555
509556
510557 /* Add value */
511 /* XXX Need to handle multi-valued properties */
512558 icalproperty_add_value_as_xml_element(xprop, prop);
513559
514560 return xprop;
633679 };
634680
635681 extern icalrecurrencetype_frequency icalrecur_string_to_freq(const char* str);
682 #ifdef HAVE_RSCALE
683 extern icalrecurrencetype_skip icalrecur_string_to_skip(const char* str);
684 #endif
636685 extern void icalrecur_add_byrules(struct icalrecur_parser *parser, short *array,
637686 int size, char* vals);
638687 extern void icalrecur_add_bydayrules(struct icalrecur_parser *parser,
654703 if (!strcmp(rpart, "freq")) {
655704 (*rt)->freq = icalrecur_string_to_freq(get_str(data));
656705 }
706 #ifdef HAVE_RSCALE
707 else if (!strcmp(rpart, "rscale")) {
708 (*rt)->rscale = icalmemory_tmp_copy(get_str(data));
709 }
710 else if (!strcmp(rpart, "skip")) {
711 (*rt)->skip = icalrecur_string_to_skip(get_str(data));
712 }
713 #endif
657714 else if (!strcmp(rpart, "count")) {
658715 (*rt)->count = get_int(data);
659716 }
9861043
9871044
9881045 /* Add value */
989 /* XXX Need to handle multi-valued properties */
990 value = xml_element_to_icalvalue(node, valkind);
991 if (!value) {
992 syslog(LOG_ERR, "Parsing %s property value failed", propname);
993 goto error;
1046 switch (kind) {
1047 case ICAL_CATEGORIES_PROPERTY:
1048 case ICAL_RESOURCES_PROPERTY:
1049 case ICAL_POLLPROPERTIES_PROPERTY:
1050 if (valkind == ICAL_TEXT_VALUE) {
1051 /* Handle multi-valued properties */
1052 struct buf buf = BUF_INITIALIZER;
1053 xmlChar *content = NULL;
1054
1055 content = xmlNodeGetContent(node);
1056 buf_setcstr(&buf, (const char *) content);
1057 free(content);
1058
1059 while ((node = xmlNextElementSibling(node))) {
1060 buf_putc(&buf, ',');
1061 content = xmlNodeGetContent(node);
1062 buf_appendcstr(&buf, (const char *) content);
1063 free(content);
1064 }
1065
1066 value = icalvalue_new_from_string(valkind, buf_cstring(&buf));
1067 buf_free(&buf);
1068 break;
1069 }
1070
1071 default:
1072 value = xml_element_to_icalvalue(node, valkind);
1073 if (!value) {
1074 syslog(LOG_ERR, "Parsing %s property value failed", propname);
1075 goto error;
1076 }
9941077 }
9951078
9961079 icalproperty_set_value(prop, value);
4646
4747 #include "util.h"
4848
49 #ifndef HAVE_VPOLL
50 /* Allow us to compile without #ifdef HAVE_VPOLL everywhere */
51 #define ICAL_POLLPROPERTIES_PROPERTY ICAL_NO_PROPERTY
52 #endif
53
4954 #define XML_NS_ICALENDAR "urn:ietf:params:xml:ns:icalendar-2.0"
5055
5156 extern const char *icalproperty_value_kind_as_string(icalproperty *prop);
6161 #include "xmalloc.h"
6262
6363 #include "zoneinfo_db.h"
64
65 //#define FIND_CONTAINS
6664
6765 #define DB config_zoneinfo_db
6866
202200 }
203201
204202
205 #ifdef FIND_CONTAINS
206 /*
207 * Find the first occurrence of find in s, where the search is limited to the
208 * first slen characters of s.
209 *
210 * Copyright (c) 2001 Mike Barcroft <mike@FreeBSD.org>
211 * Copyright (c) 1990, 1993
212 * The Regents of the University of California. All rights reserved.
213 *
214 * This code is derived from software contributed to Berkeley by
215 * Chris Torek.
216 *
217 * Fix for case-insensitive match of first char added by Ken Murchison
218 */
219 static char *strncasestr(const char *s, const char *find, size_t slen)
220 {
221 char c, sc;
222 size_t len;
223
224 if ((c = tolower(*find++)) != '\0') {
225 len = strlen(find);
226 do {
227 do {
228 if ((sc = *s++) == '\0' || slen-- < 1) return (NULL);
229 } while (tolower(sc) != c);
230
231 if (len > slen) return (NULL);
232 } while (strncasecmp(s, find, len) != 0);
233
234 s--;
235 }
236
237 return ((char *)s);
238 }
239 #endif /* FIND_CONTAINS */
203 static int tzmatch(const char *str, const char *pat)
204 {
205 for ( ; *pat; str++, pat++) {
206 /* End of string and more pattern */
207 if (!*str && *pat != '*') return 0;
208
209 switch (*pat) {
210 case '*':
211 /* Collapse consecutive stars */
212 while (*++pat == '*') continue;
213
214 /* Trailing star matches anything */
215 if (!*pat) return 1;
216
217 while (*str) if (tzmatch(str++, pat)) return 1;
218 return 0;
219
220 case ' ':
221 case '_':
222 /* Treat ' ' == '_' */
223 if (*str != ' ' && *str != '_') return 0;
224 break;
225
226 default:
227 /* Case-insensitive comparison */
228 if (tolower(*str) != tolower(*pat)) return 0;
229 break;
230 }
231 }
232
233 /* Did we reach end of string? */
234 return (!*str);
235 }
236
240237
241238 struct findrock {
242239 const char *find;
246243 void *rock;
247244 };
248245
249 static int find_p(void *rock,
250 const char *tzid __attribute__((unused)), int tzidlen,
246 static int find_p(void *rock, const char *tzid, int tzidlen,
251247 const char *data, int datalen)
252248 {
253249 struct findrock *frock = (struct findrock *) rock;
269265
270266 if (!frock->find) return 1;
271267 else if (frock->tzid_only) return (tzidlen == (int) strlen(frock->find));
272 #ifdef FIND_CONTAINS
273 else return (strncasestr(tzid, frock->find, tzidlen) != NULL);
274 #else
275 else return 1;
276 #endif
268 else return tzmatch(tzid, frock->find);
277269 }
278270
279271 static int find_cb(void *rock,
320312 frock.proc = proc;
321313 frock.rock = rock;
322314
323 if (!find
324 #ifdef FIND_CONTAINS
325 || !tzid_only
326 #endif
327 ) find = "";
315 if (!find || !tzid_only) find = "";
328316
329317 /* process each matching entry in our database */
330318 return DB->foreach(zoneinfodb, (char *) find, strlen(find),
12011201 static void sigint_handler(int sig __attribute__((unused)))
12021202 {
12031203 gotsigint = 1;
1204 }
1205
1206 static int haveinput(struct protstream *s)
1207 {
1208 /* Is something currently pending in our protstream's buffer? */
1209 #ifdef HAVE_SSL
1210 if (s->cnt == 0 && s->tls_conn != NULL) {
1211 /* Maybe there's data pending in the SSL buffer? */
1212 int n = SSL_pending(s->tls_conn);
1213 if (verbose) printf("SSL_pending=%d\n", n);
1214 return n;
1215 }
1216 #endif
1217 return s->cnt;
12041218 }
12051219
12061220 /* This needs to support 3 modes:
13671381 buf[count] = '\0';
13681382 printf("%s", buf);
13691383 }
1370 } while (pin->cnt > 0);
1384 } while (haveinput(pin) > 0);
13711385 } else if ((FD_ISSET(fd, &rset)) && (FD_ISSET(sock, &wset))
13721386 && (donewritingfile == 0)) {
13731387 /* This does input for both socket and file modes */
6262 extern const struct charset chartables_charset_table[];
6363 extern const int chartables_num_charsets;
6464
65 char QPSAFECHAR[256] = {
66 /* control chars are unsafe */
67 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
68 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
69 /* http://en.wikipedia.org/wiki/Quoted-printable */
70 /* All printable ASCII characters (decimal values between 33 and 126) */
71 /* may be represented by themselves, except "=" (decimal 61). */
72 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
73 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
74 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
75 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
76 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
77 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
78 /* all high bits are unsafe */
79 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
80 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
81 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
82 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
83 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
84 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
85 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
86 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
87 };
88
6589 struct qp_state {
6690 int isheader;
6791 int bytesleft;
13041328 return (b64_len ? retval : NULL);
13051329 }
13061330
1331 /* returns a buffer which the caller must free */
1332 char *charset_encode_mimeheader(const char *header, size_t len)
1333 {
1334 struct buf buf = BUF_INITIALIZER;
1335 size_t n;
1336 int need_quote = 0;
1337
1338 if (!header) return NULL;
1339
1340 if (!len) len = strlen(header);
1341
1342 for (n = 0; n < len; n++) {
1343 unsigned char this = header[n];
1344 if (QPSAFECHAR[this] || this == ' ') continue;
1345 need_quote = 1;
1346 break;
1347 }
1348
1349 if (need_quote) {
1350 buf_printf(&buf, "=?UTF-8?Q?");
1351 for (n = 0; n < len; n++) {
1352 unsigned char this = header[n];
1353 if (QPSAFECHAR[this]) {
1354 buf_putc(&buf, (char)this);
1355 }
1356 else {
1357 buf_printf(&buf, "=%02X", this);
1358 }
1359 }
1360 buf_printf(&buf, "?=");
1361 }
1362 else {
1363 buf_setmap(&buf, header, len);
1364 }
1365
1366 return buf_release(&buf);
1367 }
1368
124124 size_t len, charset_index charset,
125125 int encoding);
126126
127
128 /* Function to generate new mimeheaders on created messages. Caller must
129 * free the memory */
130 extern char *charset_encode_mimeheader(const char *header, size_t len);
131
127132 #endif /* INCLUDED_CHARSET_H */
252252 layers of MIME structure. The default of 1000 is much higher
253253 than any sane message should have. */
254254
255 { "caldav_allowscheduling", 1, SWITCH }
256 /* If enabled, the server will perform calendar scheduling operations. */
255 { "caldav_allowscheduling", "on", ENUM("off", "on", "apple") }
256 /* Enable calendar scheduling operations. If set to "apple", the
257 server will emulate Apple CalendarServer behavior as closely as
258 possible. */
257259
258260 { "caldav_realm", NULL, STRING }
259261 /* The realm to present for HTTP authentication of CalDAV resources.
265267 delimiter will be automatically appended. The public calendar
266268 hierarchy will be at the toplevel of the shared namespace. A
267269 user's personal calendar hierarchy will be a child of their Inbox. */
270
271 { "calendar_user_address_set", NULL, STRING }
272 /* Space-separated list of domains corresponding to calendar user
273 addresses for which the server is responsible. If not set (the
274 default), the value of the "servername" option will be used. */
268275
269276 { "carddav_realm", NULL, STRING }
270277 /* The realm to present for HTTP authentication of CardDAV resources.
793800 { "mboxname_lockpath", NULL, STRING }
794801 /* Path to mailbox name lock files (default $conf/lock) */
795802
796 { "metapartition_files", "", BITFIELD("header", "index", "cache", "expunge", "squat", "lock") }
803 { "metapartition_files", "", BITFIELD("header", "index", "cache", "expunge", "squat", "lock", "dav") }
797804 /* Space-separated list of metadata files to be stored on a
798805 \fImetapartition\fR rather than in the mailbox directory on a spool
799806 partition. */
12451252 { "servername", NULL, STRING }
12461253 /* This is the hostname visible in the greeting messages of the POP,
12471254 IMAP and LMTP daemons. If it is unset, then the result returned
1248 from gethostname(2) is used. */
1249
1255 from gethostname(2) is used. This is also the value used by murder
1256 clusters to identify the host name. It should be resolvable by
1257 DNS to the correct host, and unique within an active cluster. If
1258 you are using low level replication (e.g. drbd) then it should be
1259 the same on each copy and the DNS name should also be moved to
1260 the new master on failover. */
1261
12501262 { "serverinfo", "on", ENUM("off", "min", "on") }
12511263 /* The server information to display in the greeting and capability
12521264 responses. Information is displayed as follows:
14121424 /* The default password to use when authenticating to a sync server.
14131425 Prefix with a channel name to only apply for that channel */
14141426
1415 { "sync_port", "csync", STRING }
1427 { "sync_port", NULL, STRING }
14161428 /* Name of the service (or port number) of the replication service on
1417 replica host. The default is "csync" which is usally port 2005, but
1418 any service name or numeric port can be specified.
1419 Prefix with a channel name to only apply for that channel */
1429 replica host. Prefix with a channel name to only apply for that
1430 channel. If not specified, the replication client will first
1431 try "imap" (port 143) to check if imapd supports replication,
1432 otherwise it will default to "csync" (usually port 2005). */
14201433
14211434 { "sync_realm", NULL, STRING }
14221435 /* The authentication realm to use when authenticating to a sync server.
102102 { IMAPOPT_BOUNDARY_LIMIT, "boundary_limit", 0, OPT_INT,
103103 {(void*)1000},
104104 { { NULL, IMAP_ENUM_ZERO } } },
105 { IMAPOPT_CALDAV_ALLOWSCHEDULING, "caldav_allowscheduling", 0, OPT_SWITCH,
106 {(void*)1},
107 { { NULL, IMAP_ENUM_ZERO } } },
105 { IMAPOPT_CALDAV_ALLOWSCHEDULING, "caldav_allowscheduling", 0, OPT_ENUM,
106 {(void *)(IMAP_ENUM_CALDAV_ALLOWSCHEDULING_ON)},
107 { { "off" , IMAP_ENUM_CALDAV_ALLOWSCHEDULING_OFF },
108 { "on" , IMAP_ENUM_CALDAV_ALLOWSCHEDULING_ON },
109 { "apple" , IMAP_ENUM_CALDAV_ALLOWSCHEDULING_APPLE },
110 { NULL, IMAP_ENUM_ZERO } } },
108111 { IMAPOPT_CALDAV_REALM, "caldav_realm", 0, OPT_STRING,
109112 {(void *)(NULL)},
110113 { { NULL, IMAP_ENUM_ZERO } } },
111114 { IMAPOPT_CALENDARPREFIX, "calendarprefix", 0, OPT_STRING,
112115 {(void *)("#calendars")},
116 { { NULL, IMAP_ENUM_ZERO } } },
117 { IMAPOPT_CALENDAR_USER_ADDRESS_SET, "calendar_user_address_set", 0, OPT_STRING,
118 {(void *)(NULL)},
113119 { { NULL, IMAP_ENUM_ZERO } } },
114120 { IMAPOPT_CARDDAV_REALM, "carddav_realm", 0, OPT_STRING,
115121 {(void *)(NULL)},
478484 { "expunge" , IMAP_ENUM_METAPARTITION_FILES_EXPUNGE },
479485 { "squat" , IMAP_ENUM_METAPARTITION_FILES_SQUAT },
480486 { "lock" , IMAP_ENUM_METAPARTITION_FILES_LOCK },
487 { "dav" , IMAP_ENUM_METAPARTITION_FILES_DAV },
481488 { NULL, IMAP_ENUM_ZERO } } },
482489 { IMAPOPT_MUPDATE_AUTHNAME, "mupdate_authname", 0, OPT_STRING,
483490 {(void *)(NULL)},
884891 {(void *)(NULL)},
885892 { { NULL, IMAP_ENUM_ZERO } } },
886893 { IMAPOPT_SYNC_PORT, "sync_port", 0, OPT_STRING,
887 {(void *)("csync")},
894 {(void *)(NULL)},
888895 { { NULL, IMAP_ENUM_ZERO } } },
889896 { IMAPOPT_SYNC_REALM, "sync_realm", 0, OPT_STRING,
890897 {(void *)(NULL)},
4141 IMAPOPT_CALDAV_ALLOWSCHEDULING,
4242 IMAPOPT_CALDAV_REALM,
4343 IMAPOPT_CALENDARPREFIX,
44 IMAPOPT_CALENDAR_USER_ADDRESS_SET,
4445 IMAPOPT_CARDDAV_REALM,
4546 IMAPOPT_CLIENT_TIMEOUT,
4647 IMAPOPT_COMMANDMINTIMER,
298299
299300 IMAP_ENUM_ZERO = 0,
300301
302 IMAP_ENUM_CALDAV_ALLOWSCHEDULING_OFF = 0,
303 IMAP_ENUM_CALDAV_ALLOWSCHEDULING_ON,
304 IMAP_ENUM_CALDAV_ALLOWSCHEDULING_APPLE,
301305 IMAP_ENUM_DELETE_MODE_IMMEDIATE = 0,
302306 IMAP_ENUM_DELETE_MODE_DELAYED,
303307 IMAP_ENUM_EXPUNGE_MODE_DEFAULT = 0,
317321 IMAP_ENUM_METAPARTITION_FILES_EXPUNGE = (1<<3),
318322 IMAP_ENUM_METAPARTITION_FILES_SQUAT = (1<<4),
319323 IMAP_ENUM_METAPARTITION_FILES_LOCK = (1<<5),
324 IMAP_ENUM_METAPARTITION_FILES_DAV = (1<<6),
320325 IMAP_ENUM_MUPDATE_CONFIG_STANDARD = 0,
321326 IMAP_ENUM_MUPDATE_CONFIG_UNIFIED,
322327 IMAP_ENUM_MUPDATE_CONFIG_REPLICATED,
556556 return buf->s;
557557 }
558558
559 char *buf_release(struct buf *buf)
560 {
561 char *ret;
562
563 /* make sure it's NULL terminated - also guarantees it's a
564 * malloc'ed string */
565 buf_ensure(buf, 1);
566 ret = buf->s;
567 ret[buf->len] = '\0';
568
569 /* zero out the buffer so it no longer manages the string */
570 buf->s = NULL;
571 buf->len = 0;
572 buf->alloc = 0;
573 buf->flags = 0;
574
575 return ret;
576 }
577
559578 void buf_getmap(struct buf *buf, const char **base, unsigned *len)
560579 {
561580 *base = buf->s;
190190
191191 const char *buf_cstring(struct buf *buf);
192192 void buf_ensure(struct buf *buf, unsigned morebytes);
193 char *buf_release(struct buf *buf);
193194 void buf_getmap(struct buf *buf, const char **base, unsigned *len);
194195 unsigned buf_len(const struct buf *buf);
195196 void buf_reset(struct buf *buf);
230230 can cause a stack overflow. Do not parse deeper than this many
231231 layers of MIME structure. The default of 1000 is much higher
232232 than any sane message should have.
233 .IP "\fBcaldav_allowscheduling:\fR 1" 5
234 If enabled, the server will perform calendar scheduling operations.
233 .IP "\fBcaldav_allowscheduling:\fR on" 5
234 Enable calendar scheduling operations. If set to "apple", the
235 server will emulate Apple CalendarServer behavior as closely as
236 possible.
237
238 Allowed values: \fIoff\fR, \fIon\fR, \fIapple\fR
235239 .IP "\fBcaldav_realm:\fR <none>" 5
236240 The realm to present for HTTP authentication of CalDAV resources.
237241 If not set (the default), the value of the "servername" option will
241245 delimiter will be automatically appended. The public calendar
242246 hierarchy will be at the toplevel of the shared namespace. A
243247 user's personal calendar hierarchy will be a child of their Inbox.
248 .IP "\fBcalendar_user_address_set:\fR <none>" 5
249 Space-separated list of domains corresponding to calendar user
250 addresses for which the server is responsible. If not set (the
251 default), the value of the "servername" option will be used.
244252 .IP "\fBcarddav_realm:\fR <none>" 5
245253 The realm to present for HTTP authentication of CardDAV resources.
246254 If not set (the default), the value of the "servername" option will
681689 \fImetapartition\fR rather than in the mailbox directory on a spool
682690 partition.
683691
684 Allowed values: \fIheader\fR, \fIindex\fR, \fIcache\fR, \fIexpunge\fR, \fIsquat\fR, \fIlock\fR
692 Allowed values: \fIheader\fR, \fIindex\fR, \fIcache\fR, \fIexpunge\fR, \fIsquat\fR, \fIlock\fR, \fIdav\fR
685693 .IP "\fBmetapartition-name:\fR <none>" 5
686694 The pathname of the metadata partition \fIname\fR, corresponding to
687695 spool partition \fBpartition-name\fR. For any mailbox residing in
10581066 .IP "\fBservername:\fR <none>" 5
10591067 This is the hostname visible in the greeting messages of the POP,
10601068 IMAP and LMTP daemons. If it is unset, then the result returned
1061 from gethostname(2) is used.
1069 from gethostname(2) is used. This is also the value used by murder
1070 clusters to identify the host name. It should be resolvable by
1071 DNS to the correct host, and unique within an active cluster. If
1072 you are using low level replication (e.g. drbd) then it should be
1073 the same on each copy and the DNS name should also be moved to
1074 the new master on failover.
10621075 .IP "\fBserverinfo:\fR on" 5
10631076 The server information to display in the greeting and capability
10641077 responses. Information is displayed as follows:
11981211 .IP "\fBsync_password:\fR <none>" 5
11991212 The default password to use when authenticating to a sync server.
12001213 Prefix with a channel name to only apply for that channel
1201 .IP "\fBsync_port:\fR csync" 5
1214 .IP "\fBsync_port:\fR <none>" 5
12021215 Name of the service (or port number) of the replication service on
1203 replica host. The default is "csync" which is usally port 2005, but
1204 any service name or numeric port can be specified.
1205 Prefix with a channel name to only apply for that channel
1216 replica host. Prefix with a channel name to only apply for that
1217 channel. If not specified, the replication client will first
1218 try "imap" (port 143) to check if imapd supports replication,
1219 otherwise it will default to "csync" (usually port 2005).
12061220 .IP "\fBsync_realm:\fR <none>" 5
12071221 The authentication realm to use when authenticating to a sync server.
12081222 Prefix with a channel name to only apply for that channel
123123 .\" ========================================================================
124124 .\"
125125 .IX Title "SIEVESHELL 1"
126 .TH SIEVESHELL 1 "2013-12-16" "perl v5.16.3" "User Contributed Perl Documentation"
126 .TH SIEVESHELL 1 "2014-07-22" "perl v5.16.3" "User Contributed Perl Documentation"
127127 .\" For nroff, turn off justification. Always turn off hyphenation; it makes
128128 .\" way too many mistakes in technical documents.
129129 .if n .ad l
5151 .B \-l
5252 ]
5353 [
54 .B \-L
55 ]
56 [
5457 .B \-z
5558 ]
5659 [
9598 .br
9699 [
97100 .B \-s
101 ]
102 [
103 .B \-p
104 .I partition
98105 ]
99106 .IR objects ...
100107
112119 .BI \-l
113120 Verbose logging mode.
114121 .TP
122 .BI \-L
123 Perform only local mailbox operations (do not do mupdate operations).
124 .TP
115125 .BI \-o
116126 Only attempt to connect to the backend server once rather than waiting
117127 up to 1000 seconds before giving up.
192202 Remaining arguments are list of users whose Sieve files should be replicated.
193203 Principally used for debugging purposes: not exposed to
194204 .B sync_client(8).
205 .TP
206 .BI \-p " partition"
207 In mailbox or user replication mode: provides the name of the
208 partition on the replica to which the mailboxes/users should be replicated.
195209 .SH FILES
196210 .TP
197211 .B /etc/imapd.conf
5252 .B \-v
5353 ]
5454 [
55 .B \-L
56 ]
57 [
5558 .B \-f
5659 ]
5760 .SH DESCRIPTION
6770 .BI \-v
6871 Verbose mode.
6972 .TP
73 .BI \-L
74 Perform only local mailbox operations (do not do mupdate operations).
75 .TP
7076 .BI \-f
7177 Force operation. Without this flag \fIsync_reset\fR just bails out with
7278 an error. Principally here to try and prevent accidents with command
6969
7070 SERVICE=../master/service.o
7171
72 IMAP_LIBS = @IMAP_LIBS@ @LIB_RT@
72 IMAP_LIBS = @IMAP_LIBS@ @LIB_RT@ @ICAL_LIBS@ @ICU_LIBS@ @SQLITE3_LIBS@
7373 IMAP_COM_ERR_LIBS = @IMAP_COM_ERR_LIBS@
7474 LIB_WRAP = @LIB_WRAP@
7575 LIBS = @ZEPHYR_LIBS@ @LIBS@ $(IMAP_COM_ERR_LIBS)
0 #!/usr/bin/perl -c
1
2 package Cyrus::CacheFile;
3
4 use strict;
5 use warnings;
6
7 use IO::File;
8 use IO::File::fcntl;
9 use IO::Handle;
10 use File::Temp;
11 use YAML;
12
13 =pod
14
15 =head1 NAME
16
17 Cyrus::CacheFile - A pure perl interface to the "cyrus.cache" file
18 format as generated by Cyrus IMAPd.
19
20 =head1 EXAMPLES
21
22 XXX: document.
23
24 See examples/index_uids.pl for some usage
25
26 =cut
27
28 # /* Access assistance macros for memory-mapped cache file data */
29 # /* CACHE_ITEM_BIT32: Convert to host byte order */
30 # /* CACHE_ITEM_LEN: Get the length out */
31 # /* CACHE_ITEM_NEXT: Return a pointer to the next entry. Sizes are
32 # * 4-byte aligned, so round up to the next 4 byte boundry */
33 # #define CACHE_ITEM_BIT32(ptr) (ntohl(*((bit32 *)(ptr))))
34 # #define CACHE_ITEM_LEN(ptr) CACHE_ITEM_BIT32(ptr)
35 # #define CACHE_ITEM_NEXT(ptr) ((ptr)+4+((3+CACHE_ITEM_LEN(ptr))&~3))
36
37 # #define MAILBOX_CACHE_MINOR_VERSION 2
38 # #define NUM_CACHE_FIELDS 10
39
40 our $NUM_CACHE_FIELDS = 10;
41 our @NAMES = qw(
42 ENVELOPE
43 BODYSTRUCTURE
44 BODY
45 SECTION
46 HEADERS
47 FROM
48 TO
49 CC
50 BCC
51 SUBJECT
52 );
53
54 # PUBLIC API
55
56 sub new {
57 my $class = shift;
58 my $handle = shift;
59
60 # read header
61 my $buf;
62 # XXX - check for success!
63 sysread($handle, $buf, 4);
64 my $version = unpack('N', $buf);
65 my $Self = bless { version => $version, handle => $handle, offset => 4 }, ref($class) || $class;
66 return $Self;
67 }
68
69 sub new_file {
70 my $class = shift;
71 my $file = shift;
72 my $lockopts = shift;
73
74 my $fh;
75 if ($lockopts) {
76 $lockopts = ['lock_ex'] unless ref($lockopts) eq 'ARRAY';
77 $fh = IO::File::fcntl->new($file, '+<', @$lockopts)
78 || die "Can't open $file for locked read: $!";
79 } else {
80 $fh = IO::File->new("< $file")
81 || die "Can't open $file for read: $!";
82 }
83
84 return $class->new($fh);
85 }
86
87 sub next_record {
88 my $Self = shift;
89 my $buf;
90
91 my @record;
92 my $size = 0;
93 for (1..$NUM_CACHE_FIELDS) {
94 sysread($Self->{handle}, $buf, 4);
95 return undef unless $buf;
96 my $num = unpack('N', $buf);
97 my $bytes = $num;
98 $bytes += 4 - $num % 4 if $num % 4; # offsets are multiple of 4 bytes
99 sysread($Self->{handle}, $buf, $bytes);
100 push @record, [$num, $bytes, $buf];
101 $size += $bytes + 4;
102 }
103
104 my $ret = {
105 size => $size,
106 records => \@record,
107 };
108
109 $Self->{record} = $ret;
110 $Self->{offset} += $size;
111
112 return $ret;
113 }
114
115 sub record {
116 my $Self = shift;
117 my $Field = shift;
118
119 return undef unless ($Self->{record});
120
121 if ($Field) {
122 return $Self->{record}{$Field};
123 }
124 return $Self->{record};
125 }
126
127 sub offset {
128 my $Self = shift;
129
130 if (@_) {
131 my $spot = shift;
132 seek($Self->{handle}, $spot, 0);
133 $Self->{offset} = $spot;
134 }
135
136 return $Self->{offset};
137 }
138
139 sub dump {
140 my $Self = shift;
141
142 while (my $rec = $Self->next_record()) {
143 $Self->dump_record($rec);
144 }
145 }
146
147 sub dump_record {
148 my $Self = shift;
149 my $rec = shift || $Self->{record};
150 return unless $rec;
151 print Dump($rec->{records});
152 }
153
154 sub print_record {
155 my $Self = shift;
156 my $rec = shift || $Self->{record};
157 return unless $rec;
158 foreach my $rnum (0..$NUM_CACHE_FIELDS-1) {
159 my $record = $rec->{records}[$rnum];
160 my $str = substr($record->[2], 0, $record->[0]);
161 if ($rnum == 3) { # section
162 my @items = unpack('N*', $str);
163 $str = parse_section(0, \@items);
164 }
165 print "$NAMES[$rnum]: $str\n";
166 }
167 }
168
169 sub parse_section {
170 my $part = shift;
171 my $items = shift;
172 my $num_parts = shift @$items;
173 if ($num_parts == 0) {
174 return "$part:()";
175 }
176 my $ret = "$part:(" . parse_item($items);
177 my $n = 1;
178 while ($n < $num_parts) {
179 my $subpart = $part ? "$part.$n" : $n;
180 $ret .= " " . parse_item($items);
181 $n++;
182 }
183 $n = 1;
184 $ret .= ")";
185 while ($n < $num_parts) {
186 my $subpart = $part ? "$part.$n" : $n;
187 $ret .= " " . parse_section($subpart, $items);
188 $n++;
189 }
190 return $ret;
191 }
192
193 sub parse_item {
194 my $items = shift;
195 my $header_offset = shift @$items;
196 my $header_size = shift @$items;
197 my $content_offset = shift @$items;
198 my $content_size = shift @$items;
199 my $encoding = shift @$items;
200 return "($header_offset:$header_size $content_offset:$content_size $encoding)";
201 }
202
203 =head1 AUTHOR AND COPYRIGHT
204
205 Bron Gondwana <brong@fastmail.fm> - Copyright 2008 FastMail
206
207 Licenced under the same terms as Cyrus IMAPd.
208
209 =cut
210
211 1;
0 #!/usr/bin/perl -c
1
2 # Package to handle Cyrus Header files
3
4 package Cyrus::HeaderFile;
5
6 use strict;
7 use warnings;
8
9 use IO::File;
10 use IO::File::fcntl;
11 use IO::Handle;
12 use File::Temp;
13 use Data::Dumper;
14
15 =pod
16
17 =head1 NAME
18
19 Cyrus::HeaderFile - A pure perl interface to the "cyrus.header" file
20 format as generated by Cyrus IMAPd.
21
22 =head1 EXAMPLES
23
24 Like Cyrus::IndexFile, uses fcntl locking (default for Cyrus on systems
25 which support it)
26
27 my $header = Cyrus::HeaderFile->new_file("path/to/cyrus.header");
28
29 XXX: see index_uids.pl
30
31 =cut
32
33 our $HL1 = qq{\241\002\213\015Cyrus mailbox header};
34 our $HL2 = qq{"The best thing about this system was that it had lots of goals."};
35 our $HL3 = qq{\t--Jim Morris on Andrew};
36
37 =head1 PUBLIC API
38
39 =over
40
41 =item Cyrus::HeaderFile->new($fh)
42
43 Read the header file in $fh
44
45 =cut
46
47 sub new {
48 my $class = shift;
49 my $handle = shift;
50
51 # read header
52 local $/ = undef;
53 my $body = <$handle>;
54
55 my $Self = bless {}, ref($class) || $class;
56 $Self->{handle} = $handle; # keep for locking
57 $Self->{rawheader} = $body;
58 $Self->{header} = $Self->parse_header($body);
59
60 return $Self;
61 }
62
63 =item Cyrus::HeaderFile->new_file($fname, $lockopts)
64
65 Open the file to read, optionally locking it with IO::File::fcntl. If you
66 pass a scalar for lockopts then it will be locked with ['lock_ex'], otherwise
67 you can pass a tuple, e.g. ['lock_ex', 5] for a 5 second timeout.
68
69 This function will die if it can't open or lock the file. On success, it
70 calls $class->new() with the filehandle.
71
72 =cut
73
74 sub new_file {
75 my $class = shift;
76 my $file = shift;
77 my $lockopts = shift;
78
79 my $fh;
80 if ($lockopts) {
81 $lockopts = ['lock_ex'] unless ref($lockopts) eq 'ARRAY';
82 $fh = IO::File::fcntl->new($file, '+<', @$lockopts)
83 || die "Can't open $file for locked read: $!";
84 } else {
85 $fh = IO::File->new("< $file")
86 || die "Can't open $file for read: $!";
87 }
88
89 return $class->new($fh);
90 }
91
92 =item $header->header([$Field])
93
94 Return the entire header as a hash, or individual named field.
95
96 =cut
97
98 sub header {
99 my $Self = shift;
100 my $Field = shift;
101
102 if ($Field) {
103 return $Self->{header}{$Field};
104 }
105
106 return $Self->{header};
107 }
108
109 =item $header->write_header($fh, $headerData)
110
111 Write a header file with the data (e.g. returned from ->header())
112 to the given filehandle.
113
114 =cut
115
116 sub write_header {
117 my $Self = shift;
118 my $fh = shift;
119 my $header = shift || $Self->header();
120
121 $fh->print($Self->make_header($header));
122 }
123
124 sub make_header {
125 my $Self = shift;
126 my $ds = shift || $Self->header();
127
128 # NOTE: acl and flags should have '' as the last element!
129 my $flags = join(" ", @{$ds->{Flags}}, '');
130 my $acl = join("\t", @{$ds->{ACL}}, '');
131 my $buf = <<EOF;
132 $HL1
133 $HL2
134 $HL3
135 $ds->{QuotaRoot} $ds->{UniqueId}
136 $flags
137 $acl
138 EOF
139 return $buf;
140 }
141
142 sub parse_header {
143 my $Self = shift;
144 my $body = shift;
145
146 my @lines = split /\n/, $body;
147
148 die "Not a mailbox header file" unless $lines[0] eq $HL1;
149 die "Not a mailbox header file" unless $lines[1] eq $HL2;
150 die "Not a mailbox header file" unless $lines[2] eq $HL3;
151 my ($quotaroot, $uniqueid) = split /\t/, $lines[3];
152 my (@flags) = split / /, $lines[4];
153 my (@acl) = split /\t/, $lines[5];
154
155 return {
156 QuotaRoot => $quotaroot,
157 UniqueId => $uniqueid,
158 Flags => \@flags,
159 ACL => \@acl,
160 };
161 }
162
163 =back
164
165 =head1 AUTHOR AND COPYRIGHT
166
167 Bron Gondwana <brong@fastmail.fm> - Copyright 2008 FastMail
168
169 Licenced under the same terms as Cyrus IMAPd.
170
171 =cut
172
173
174 1;
0 #!/usr/bin/perl -c
1
2 package Cyrus::IndexFile;
3
4 use strict;
5 use warnings;
6
7 use IO::File;
8 use IO::Handle;
9 use String::CRC32 qw(crc32);
10
11 =pod
12
13 =head1 NAME
14
15 Cyrus::IndexFile - A pure perl interface to the "cyrus.index" file
16 format as generated by Cyrus IMAPd.
17
18 =head1 EXAMPLES
19
20 use Cyrus::IndexFile;
21
22 # Note: requires IO::File::fcntl module installed for locking support
23 my $index = Cyrus::IndexFile->new_file("$path/cyrus.index", ['lock_ex', 5]);
24
25 print "EXISTS: " . $index->header('Exists') . "\n";
26 while (my $record = $index->next_record_hash()) {
27 print "$record->{Uid}: $record->{MessageGuid} $record->{Size}\n";
28 }
29
30 =head1 SUPPORTED FORMAT VERSIONS
31
32 Definitions:
33 ============
34
35 * int32 4 - 32 bit value taking 4 octets on disk. Visible in perl as an integer
36 * int64 8 - 64 bit value taking 8 octets on disk. Visible in perl as an integer
37 * time_t 4 - same as int32
38 * bitmap N - a bitmap taking up N octets on disk. Visible in perl as a string of 1s and 0s.
39 * hex N - a big value taking up N octets on disk. Visible in perl as a hexadecimal string (0-9a-f)
40
41 These values can be referenced by name using the hash API, or by index using the array API.
42 You can also use the 'raw' API to get the record in on-disk format.
43
44 All numbers are in network byte order as per Cyrus standard encoding. Bitmap and hex values are
45 layed out as octets on disk and encoded directly in order.
46
47 Version 9:
48 ==========
49
50 Header:
51 0: Generation int32 4
52 1: Format int32 4
53 2: MinorVersion int32 4
54 3: StartOffset int32 4
55 4: RecordSize int32 4
56 5: Exists int32 4
57 6: LastAppenddate time_t 4
58 7: LastUid int32 4
59 8: QuotaUsed int64 8
60 9: Pop3LastLogin time_t 4
61 10: UidValidity int32 4
62 11: Deleted int32 4
63 12: Answered int32 4
64 13: Flagged int32 4
65 14: Options bitmap 4
66 15: LeakedCache int32 4
67 16: HighestModseq int64 8
68 17: Spare0 int32 4
69 18: Spare1 int32 4
70 19: Spare2 int32 4
71 20: Spare3 int32 4
72 21: Spare4 int32 4
73
74 Record:
75 0: Uid int32 4
76 1: InternalDate time_t 4
77 2: SentDate time_t 4
78 3: Size int32 4
79 4: HeaderSize int32 4
80 5: ContentOffset int32 4
81 6: CacheOffset int32 4
82 7: LastUpdated time_t 4
83 8: SystemFlags bitmap 4
84 9: UserFlags bitmap 16
85 10: ContentLines int32 4
86 11: CacheVersion int32 4
87 12: MessageUuid hex 12
88 13: Modseq int64 8
89
90 Version 10:
91 ===========
92
93 Header:
94 0: Generation int32 4
95 1: Format int32 4
96 2: MinorVersion int32 4
97 3: StartOffset int32 4
98 4: RecordSize int32 4
99 5: Exists int32 4
100 6: LastAppenddate time_t 4
101 7: LastUid int32 4
102 8: QuotaUsed int64 8
103 9: Pop3LastLogin time_t 4
104 10: UidValidity int32 4
105 11: Deleted int32 4
106 12: Answered int32 4
107 13: Flagged int32 4
108 14: Options bitmap 4
109 15: LeakedCache int32 4
110 16: HighestModseq int64 8
111 17: Spare0 int32 4
112 18: Spare1 int32 4
113 19: Spare2 int32 4
114 20: Spare3 int32 4
115 21: Spare4 int32 4
116
117 Record:
118 0: Uid int32 4
119 1: InternalDate time_t 4
120 2: SentDate time_t 4
121 3: Size int32 4
122 4: HeaderSize int32 4
123 5: ContentOffset int32 4
124 6: CacheOffset int32 4
125 7: LastUpdated time_t 4
126 8: SystemFlags bitmap 4
127 9: UserFlags bitmap 16
128 10: ContentLines int32 4
129 11: CacheVersion int32 4
130 12: MessageGuid hex 20
131 13: Modseq int64 8
132
133 SKIPPED VERSION 11 - Fastmail internal only
134
135 Version 12:
136 ===========
137
138 Header:
139 0: Generation int32 4
140 1: Format int32 4
141 2: MinorVersion int32 4
142 3: StartOffset int32 4
143 4: RecordSize int32 4
144 5: Exists int32 4
145 6: LastAppenddate time_t 4
146 7: LastUid int32 4
147 8: QuotaUsed int64 8
148 9: Pop3LastLogin time_t 4
149 10: UidValidity int32 4
150 11: Deleted int32 4
151 12: Answered int32 4
152 13: Flagged int32 4
153 14: Options bitmap 4
154 15: LeakedCache int32 4
155 16: HighestModseq int64 8
156 17: DeletedModseq int64 8
157 18: Exists int32 4
158 19: FirstExpunged time_t 4
159 20: LastCleanup time_t 4
160 21: HeaderFileCRC int32 4
161 22: SyncCRC int32 4
162 23: RecentUid int32 4
163 24: RecentTime time_t 4
164 25: Spare0 int32 4
165 26: Spare1 int32 4
166 27: Spare2 int32 4
167 28: HeaderCRC int32 4
168
169 Record:
170 0: Uid int32 4
171 1: InternalDate time_t 4
172 2: SentDate time_t 4
173 3: Size int32 4
174 4: HeaderSize int32 4
175 5: GmTime time_t 4
176 6: CacheOffset int32 4
177 7: LastUpdated time_t 4
178 8: SystemFlags bitmap 4
179 9: UserFlags bitmap 16
180 10: ContentLines int32 4
181 11: CacheVersion int32 4
182 12: MessageGuid hex 20
183 13: Modseq int64 8
184 14: CacheCRC int32 4
185 15: RecordCRC int32 4
186
187 Version 13:
188 ===========
189
190 Header:
191 0: Generation int32 4
192 1: Format int32 4
193 2: MinorVersion int32 4
194 3: StartOffset int32 4
195 4: RecordSize int32 4
196 5: Exists int32 4
197 6: LastAppenddate time_t 4
198 7: LastUid int32 4
199 8: QuotaUsed int64 8
200 9: Pop3LastLogin time_t 4
201 10: UidValidity int32 4
202 11: Deleted int32 4
203 12: Answered int32 4
204 13: Flagged int32 4
205 14: Options bitmap 4
206 15: LeakedCache int32 4
207 16: HighestModseq int64 8
208 17: DeletedModseq int64 8
209 18: Exists int32 4
210 19: FirstExpunged time_t 4
211 20: LastCleanup time_t 4
212 21: HeaderFileCRC int32 4
213 22: SyncCRC int32 4
214 23: RecentUid int32 4
215 24: RecentTime time_t 4
216 25: Spare0 int32 4
217 26: Spare1 int32 4
218 27: Spare2 int32 4
219 28: HeaderCRC int32 4
220
221 Record:
222 0: Uid int32 4
223 1: InternalDate time_t 4
224 2: SentDate time_t 4
225 3: Size int32 4
226 4: HeaderSize int32 4
227 5: GmTime time_t 4
228 6: CacheOffset int32 4
229 7: LastUpdated time_t 4
230 8: SystemFlags bitmap 4
231 9: UserFlags bitmap 16
232 10: ContentLines int32 4
233 11: CacheVersion int32 4
234 12: MessageGuid hex 20
235 13: Modseq int64 8
236 14: CID hex 8
237 15: CacheCRC int32 4
238 16: RecordCRC int32 4
239
240 =cut
241
242 # Set up header and record formatting information {{{
243
244 my $VersionFormats = {
245 9 => {
246 HeaderSize => 96,
247 _make_fields('Header',<<EOF),
248 Generation int32 4
249 Format int32 4
250 MinorVersion int32 4
251 StartOffset int32 4
252 RecordSize int32 4
253 Exists int32 4
254 LastAppenddate time_t 4
255 LastUid int32 4
256 QuotaUsed int64 8
257 Pop3LastLogin time_t 4
258 UidValidity int32 4
259 Deleted int32 4
260 Answered int32 4
261 Flagged int32 4
262 Options bitmap 4
263 LeakedCache int32 4
264 HighestModseq int64 8
265 HighestModseq int64 8
266 Spare0 int32 4
267 Spare1 int32 4
268 Spare2 int32 4
269 Spare3 int32 4
270 Spare4 int32 4
271 EOF
272 RecordSize => 80, # defined in file too, check it!
273 _make_fields('Record', <<EOF),
274 Uid int32 4
275 InternalDate time_t 4
276 SentDate time_t 4
277 Size int32 4
278 HeaderSize int32 4
279 ContentOffset int32 4
280 CacheOffset int32 4
281 LastUpdated time_t 4
282 SystemFlags bitmap 4
283 UserFlags bitmap 16
284 ContentLines int32 4
285 CacheVersion int32 4
286 MessageUuid hex 12
287 Modseq int64 8
288 EOF
289 },
290 10 => {
291 HeaderSize => 96,
292 _make_fields('Header',<<EOF),
293 Generation int32 4
294 Format int32 4
295 MinorVersion int32 4
296 StartOffset int32 4
297 RecordSize int32 4
298 Exists int32 4
299 LastAppenddate time_t 4
300 LastUid int32 4
301 QuotaUsed int64 8
302 Pop3LastLogin time_t 4
303 UidValidity int32 4
304 Deleted int32 4
305 Answered int32 4
306 Flagged int32 4
307 Options bitmap 4
308 LeakedCache int32 4
309 HighestModseq int64 8
310 Spare0 int32 4
311 Spare1 int32 4
312 Spare2 int32 4
313 Spare3 int32 4
314 Spare4 int32 4
315 EOF
316 RecordSize => 88, # defined in file too, check it!
317 _make_fields('Record', <<EOF),
318 Uid int32 4
319 InternalDate time_t 4
320 SentDate time_t 4
321 Size int32 4
322 HeaderSize int32 4
323 ContentOffset int32 4
324 CacheOffset int32 4
325 LastUpdated time_t 4
326 SystemFlags bitmap 4
327 UserFlags bitmap 16
328 ContentLines int32 4
329 CacheVersion int32 4
330 MessageGuid hex 20
331 Modseq int64 8
332 EOF
333 },
334 11 => {
335 HeaderSize => 96,
336 _make_fields('Header',<<EOF),
337 Generation int32 4
338 Format int32 4
339 MinorVersion int32 4
340 StartOffset int32 4
341 RecordSize int32 4
342 Exists int32 4
343 LastAppenddate time_t 4
344 LastUid int32 4
345 QuotaUsed int64 8
346 Pop3LastLogin time_t 4
347 UidValidity int32 4
348 Deleted int32 4
349 Answered int32 4
350 Flagged int32 4
351 Options bitmap 4
352 LeakedCache int32 4
353 HighestModseq int64 8
354 Spare0 int32 4
355 Spare1 int32 4
356 Spare2 int32 4
357 Spare3 int32 4
358 HeaderCrc int32 4
359 EOF
360 RecordSize => 96, # defined in file too, check it!
361 _make_fields('Record', <<EOF),
362 Uid int32 4
363 InternalDate time_t 4
364 SentDate time_t 4
365 Size int32 4
366 HeaderSize int32 4
367 ContentOffset int32 4
368 CacheOffset int32 4
369 LastUpdated time_t 4
370 SystemFlags bitmap 4
371 UserFlags bitmap 16
372 ContentLines int32 4
373 CacheVersion int32 4
374 MessageGuid hex 20
375 Modseq int64 8
376 CacheCrc int32 4
377 RecordCrc int32 4
378 EOF
379 },
380 12 => {
381 HeaderSize => 128,
382 _make_fields('Header',<<EOF),
383 Generation int32 4
384 Format int32 4
385 MinorVersion int32 4
386 StartOffset int32 4
387 RecordSize int32 4
388 NumRecords int32 4
389 LastAppenddate time_t 4
390 LastUid int32 4
391 QuotaUsed int64 8
392 Pop3LastLogin time_t 4
393 UidValidity int32 4
394 Deleted int32 4
395 Answered int32 4
396 Flagged int32 4
397 Options bitmap 4
398 LeakedCache int32 4
399 HighestModseq int64 8
400 DeletedModseq int64 8
401 Exists int32 4
402 FirstExpunged time_t 4
403 LastCleanup time_t 4
404 HeaderFileCRC int32 4
405 SyncCRC int32 4
406 RecentUid int32 4
407 RecentTime time_t 4
408 Spare0 int32 4
409 Spare1 int32 4
410 Spare2 int32 4
411 HeaderCrc int32 4
412 EOF
413 RecordSize => 96, # defined in file too, check it!
414 _make_fields('Record', <<EOF),
415 Uid int32 4
416 InternalDate time_t 4
417 SentDate time_t 4
418 Size int32 4
419 HeaderSize int32 4
420 GmTime time_t 4
421 CacheOffset int32 4
422 LastUpdated time_t 4
423 SystemFlags bitmap 4
424 UserFlags bitmap 16
425 ContentLines int32 4
426 CacheVersion int32 4
427 MessageGuid hex 20
428 Modseq int64 8
429 CacheCrc int32 4
430 RecordCrc int32 4
431 EOF
432 },
433 13 => {
434 HeaderSize => 128,
435 _make_fields('Header',<<EOF),
436 Generation int32 4
437 Format int32 4
438 MinorVersion int32 4
439 StartOffset int32 4
440 RecordSize int32 4
441 NumRecords int32 4
442 LastAppenddate time_t 4
443 LastUid int32 4
444 QuotaUsed int64 8
445 Pop3LastLogin time_t 4
446 UidValidity int32 4
447 Deleted int32 4
448 Answered int32 4
449 Flagged int32 4
450 Options bitmap 4
451 LeakedCache int32 4
452 HighestModseq int64 8
453 DeletedModseq int64 8
454 Exists int32 4
455 FirstExpunged time_t 4
456 LastCleanup time_t 4
457 HeaderFileCRC int32 4
458 SyncCRC int32 4
459 RecentUid int32 4
460 RecentTime time_t 4
461 Pop3ShowAfter int32 4
462 QuotaAnnotUsed int32 4
463 SyncCRCVersion int32 4
464 HeaderCrc int32 4
465 EOF
466 RecordSize => 104, # defined in file too, check it!
467 _make_fields('Record', <<EOF),
468 Uid int32 4
469 InternalDate time_t 4
470 SentDate time_t 4
471 Size int32 4
472 HeaderSize int32 4
473 GmTime time_t 4
474 CacheOffset int32 4
475 LastUpdated time_t 4
476 SystemFlags bitmap 4
477 UserFlags bitmap 16
478 ContentLines int32 4
479 CacheVersion int32 4
480 MessageGuid hex 20
481 Modseq int64 8
482 CID hex 8
483 CacheCrc int32 4
484 RecordCrc int32 4
485 EOF
486 },
487 };
488
489 my %SystemFlagMap = (
490 0 => "\\Answered",
491 1 => "\\Flagged",
492 2 => "\\Deleted",
493 3 => "\\Draft",
494 4 => "\\Seen",
495 29 => "[ARCHIVED]",
496 30 => "[UNLINKED]",
497 31 => "[EXPUNGED]",
498 );
499
500 # parse our the plaintext field definitions into a useful datastructure
501 sub _make_fields {
502 my $prefix = shift;
503 my $string = shift;
504
505 my @lines = grep { m/\S/ } split /\n/, $string;
506
507 my @names;
508 my @items;
509 my @packitems;
510 my $Pos = 0;
511 my $Num = 0;
512 foreach my $line (@lines) {
513 my ($Name, $Type, $Size) = split /\s+/, $line;
514
515 push @names, $Name;
516 push @items, [$Name, $Type, $Size, $Num, $Pos];
517 push @packitems, _make_pack($Type, $Size);
518
519 $Pos += $Size;
520 $Num++;
521 }
522
523 return (
524 $prefix . 'Names' => { map { $names[$_] => $_ } 0..$#names },
525 $prefix . 'Fields' => \@items,
526 $prefix . 'Pack' => join("", @packitems),
527 );
528 }
529
530 # build the pack/unpack expression for a single field
531 sub _make_pack {
532 my $format = shift;
533 my $size = shift;
534 if ($format eq 'int32' or $format eq 'time_t') {
535 return 'N';
536 }
537 elsif ($format eq 'int64') { # ignore start..
538 return 'x[N]N';
539 }
540 elsif ($format eq 'bitmap') {
541 return 'B' . (8 * $size);
542 }
543 elsif ($format eq 'hex') {
544 return 'H' . (2 * $size);
545 }
546 }
547
548 # end format definitions
549 # }}}
550
551 =head1 PUBLIC API
552
553 =over
554
555 =item Cyrus::IndexFile->new($fh)
556
557 Build a new Cyrus::IndexFile object from a filehandle. The handle is not
558 required to be seekable, so make sure you have rewound it before use.
559
560 seek($fh, 0, 0);
561 my $index = Cyrus::IndexFile->new($fh);
562
563 This function reads the header from the file and returns a Cyrus::IndexFile
564 object. The filehandle will be pointing at the start of the first record.
565
566 If there is a problem, then the position of the filehandle is undefined
567 (though probably either at 12 bytes or the end of the header) and the
568 function will "die".
569
570 Causes of death:
571
572 * unable to read a full header's length of data from the file
573 * version of the file is not one of the supported versions
574
575 =cut
576
577 sub new {
578 my $class = shift;
579 my $handle = shift;
580
581 my $buf;
582
583 # read initial header information to determine version
584 my $read = sysread($handle, $buf, 12);
585 die "Unable to read header information\n"
586 unless $read == 12;
587
588 # version is always at this offset!
589 my $version = unpack('N', substr($buf, 8));
590
591 # check that it's a supported version
592 my $frm = $VersionFormats->{$version}
593 || die "Unknown header format $version\n";
594
595 # read the rest of the header (length depends on version)
596 sysread($handle, $buf, $frm->{HeaderSize} - 12, 12);
597 my $Self = bless {
598 @_,
599 version => $version,
600 handle => $handle,
601 format => $frm,
602 rawheader => $buf,
603 recno => 0,
604 }, ref($class) || $class;
605
606 $Self->{header} = $Self->_header_b2h($buf);
607 die "Unable to parse header" unless $Self->{header};
608
609 return $Self;
610 }
611
612 =item Cyrus::IndexFile->new_file($filename, $lockopts)
613
614 Open the file to read, optionally locking it with IO::File::fcntl. If you
615 pass a scalar for lockopts then it will be locked with ['lock_ex'], otherwise
616 you can pass a tuple, e.g. ['lock_ex', 5] for a 5 second timeout.
617
618 This function will die if it can't open or lock the file. On success, it
619 calls $class->new() with the filehandle.
620
621 =cut
622
623 sub new_file {
624 my $class = shift;
625 my $filename = shift;
626 my $lockopts = shift;
627
628 my $fh;
629 if ($lockopts) {
630 require 'IO/File/fcntl.pm' || die "can't lock without IO::File::fcntl module";
631 $lockopts = ['lock_ex'] unless ref($lockopts) eq 'ARRAY';
632 $fh = IO::File::fcntl->new($filename, '+<', @$lockopts)
633 || die "Can't open $filename for locked read: $!";
634 } else {
635 $fh = IO::File->new("< $filename")
636 || die "Can't open $filename for read: $!";
637 }
638
639 return $class->new($fh, @_);
640 }
641
642 =item Cyrus::IndexFile->new_empty($version)
643
644 Create a new empty index file with the specified version. This is
645 useful when you want to generate a new index file, as you can use
646 the write_record function and set header fields on the new object.
647
648 =cut
649
650 sub new_empty {
651 my $class = shift;
652 my $version = shift;
653
654 # check that the version is supported
655 my $frm = $VersionFormats->{$version}
656 || die "unknown version $version";
657
658 my $Self = bless {
659 @_,
660 version => $version,
661 format => $frm,
662 }, ref($class) || $class;
663
664 return $Self;
665 }
666
667 =item $index->stream_copy($outfh, $decider, %Opts)
668
669 Currently broken! Supposed to copy this file into the output filehandle.
670
671 NOTE: outfh must be seekable, as we write an initial header record with
672 Exists == 0, then update the header at the end with a new Exists and a
673 new LastUpdated.
674
675 =cut
676
677 sub stream_copy {
678 my $Self = shift;
679 my $outfh = shift;
680 my $decide = shift;
681 my %Opts = @_;
682
683 my $out = $Self->new_empty($Opts{version} || $Self->{version});
684
685 my $newheader = $Self->header_copy();
686 if ($Opts{headerfields}) {
687 foreach my $field (keys %{$Opts{headerfields}}) {
688 $newheader->{$field} = $Opts{headerfields}{$field};
689 }
690 }
691
692 # initially empty
693 $newheader->{NumRecords} = 0;
694 # Important! Otherwise you get versions out of skew!
695 $newheader->{MinorVersion} = $out->{version};
696 $newheader->{RecordSize} = $out->{format}{RecordSize};
697 $out->write_header($outfh, $newheader);
698
699 $Self->reset();
700 while (my $record = $Self->next_record()) {
701 if ($decide->($newheader, $record)) {
702 $newheader->{NumRecords}++;
703 $out->write_record($outfh, $record);
704 }
705 }
706
707 # update exists and last updated
708 $newheader->{LastUpdated} = time();
709 sysseek($outfh, 0, 0);
710 $out->write_header($outfh, $newheader);
711 }
712
713 =item $index->header()
714
715 =item $index->header_hash()
716
717 Returns a hash reference of the entire header
718
719 =item $index->header($field)
720
721 Returns just the single named field from the header. Dies if there is no
722 field with that name in the header.
723
724 =item $index->header_array($field)
725
726 Returns an array reference with the values in the order given in the version
727 information above.
728
729 =item $index->header_raw()
730
731 Returns the raw packed header as it is on disk.
732
733 =cut
734
735 sub header {
736 my $Self = shift;
737 my $Field = shift;
738
739 if ($Field) {
740 die "No such header field $Field\n" unless exists $Self->{header}{$Field};
741 return $Self->{header}{$Field};
742 }
743
744 return $Self->{header};
745 }
746
747 sub header_array {
748 my $Self = shift;
749 return $Self->_header_h2a($Self->{header});
750 }
751
752 sub header_hash {
753 my $Self = shift;
754 return $Self->{header};
755 }
756
757 sub header_raw {
758 my $Self = shift;
759 return $Self->{rawheader};
760 }
761
762 =item $index->header_copy()
763
764 Returns a hashref the same as header_hash, but it's "non live", so you can
765 make destructive changes without affecting the original.
766
767 =cut
768
769 sub header_copy {
770 my $Self = shift;
771 my $orig = $Self->{header};
772 return { %$orig };
773 }
774
775 =item $index->reset($num)
776
777 Deletes the cached 'current record' and seeks back to the given record
778 number, or the end of the header (record 0) if no number given.
779
780 Requires the input filehandle to be seekable.
781
782 =cut
783
784 sub reset {
785 my $Self = shift;
786 my $num = shift || 0;
787
788 my $NumRecords = $Self->{header}{MinorVersion} < 12 ?
789 $Self->{header}{Exists} : $Self->{header}{NumRecords};
790
791 die "Invalid record $num (must be >= 0 and <= $NumRecords"
792 unless ($num >= 0 and $num <= $NumRecords);
793
794 my $HeaderSize = $Self->{format}{HeaderSize};
795 my $RecordSize = $Self->{format}{RecordSize};
796
797 sysseek($Self->{handle}, $HeaderSize + ($num * $RecordSize), 0)
798 || die "unable to seek on this filehandle";
799
800 $Self->{recno} = $num;
801
802 delete $Self->{record};
803 delete $Self->{rawrecord};
804 delete $Self->{checksum_failure};
805 }
806
807 =item $index->next_record()
808 =item $index->next_record_hash()
809
810 Read the next record from the file and parse it in to a hash reference
811 per the format of the index file.
812
813 This works even on non-seekable files.
814
815 Returns undef when there are no more records (until you call reset)
816
817 =item $index->next_record_array()
818
819 As above, but returns the array in the format order.
820
821 More efficient, as the hash doesn't need to be created.
822
823 =item $index->next_record_raw()
824
825 Returns the raw bytes of the index file. Most efficient, as no unpacking
826 is done, but then you have to deal with all the version checking and
827 offsets yourself.
828
829 =cut
830
831 sub next_record {
832 my $Self = shift;
833 $Self->next_record_raw();
834 return $Self->record(@_);
835 }
836
837 sub next_record_hash {
838 my $Self = shift;
839 $Self->next_record_raw();
840 return $Self->record_hash(@_);
841 }
842
843 sub next_record_array {
844 my $Self = shift;
845 $Self->next_record_raw();
846 return $Self->record_array(@_);
847 }
848
849 sub next_record_raw {
850 my $Self = shift;
851
852 delete $Self->{record};
853 delete $Self->{checksum_failure};
854
855 # use direct access for speed
856 my $NumRecords = $Self->{header}{MinorVersion} < 12 ?
857 $Self->{header}{Exists} : $Self->{header}{NumRecords};
858 my $RecordSize = $Self->{header}{RecordSize};
859
860 return undef unless $RecordSize;
861
862 if ($Self->{recno} < $NumRecords) {
863 my $res = sysread($Self->{handle}, $Self->{rawrecord}, $RecordSize);
864 die "Failed to read entire record" unless $RecordSize == $res;
865 # rewrite if passed so save the allocation cost
866 $Self->{recno}++;
867 return $Self->{rawrecord};
868 }
869 else {
870 delete $Self->{rawrecord};
871 return undef; # no more records!
872 }
873 }
874
875 =item $index->record()
876 =item $index->record_hash()
877
878 Returns the "current" record, i.e. the last record returned by
879 next_record_*() as a hash reference.
880
881 Returns undef if there is no current record (either next_record has never
882 been called, reset has just been called, or the file is finished)
883
884 =item $index->record_array()
885
886 As above, but return the version-dependant arrayref or undef
887
888 =item $index->record_raw()
889
890 As above, but return just the raw record bytes as a string or undef
891
892 =item $index->record($field)
893
894 If a field name is given, return that field only from the record, or die if
895 it doesn't exist in this version.
896
897 Returns undef if there is no current record. No legitimate field ever
898 returns undef, because there's no such concept in the datastructure.
899
900 =cut
901
902 sub record {
903 my $Self = shift;
904 my $Field = shift;
905
906 my $record = $Self->record_hash();
907 return undef unless $record;
908
909 if ($Field) {
910 die "No such record field $Field\n" unless exists $record->{$Field};
911 return $record->{$Field};
912 }
913
914 return $record;
915 }
916
917 sub record_hash {
918 my $Self = shift;
919 unless (exists $Self->{record}{hash}) {
920 $Self->{record}{hash} = $Self->_record_a2h($Self->record_array(@_));
921 }
922 return $Self->{record}{hash};
923 }
924
925 sub record_array {
926 my $Self = shift;
927 unless (exists $Self->{record}{array}) {
928 $Self->{record}{array} = $Self->_record_b2a($Self->{rawrecord});
929 }
930 return $Self->{record}{array};
931 }
932
933 sub record_raw {
934 my $Self = shift;
935 return $Self->{rawrecord};
936 }
937
938 =item $index->system_flags([$Key])
939
940 Returns a hash of the system flags set on the current record, or just the
941 named flag if a Key is passed.
942
943 =cut
944
945 sub system_flags {
946 my $Self = shift;
947 my $Field = shift;
948
949 my @sfdata = reverse split //, $Self->record('SystemFlags');
950 my %hash;
951 foreach my $key (0..$#sfdata) {
952 next unless $sfdata[$key];
953 $hash{$SystemFlagMap{$key} || $key} = $key;
954 }
955
956 if ($Field) {
957 return $hash{$Field};
958 }
959
960 return wantarray ? %hash : \%hash;
961 }
962
963 =item $index->flagslist($Header)
964
965 Given a Cyrus::HeaderFile object to name the UserFlags, return an array of all
966 flags, both SystemFlags and UserFlags set on the record.
967
968 =cut
969 sub flagslist {
970 my $Self = shift;
971 my $Header = shift;
972 my @flags;
973
974 # 32 bit sets
975 my @sfdata = reverse split //, $Self->record('SystemFlags');
976 foreach my $i (0..$#sfdata) {
977 next unless $sfdata[$i];
978 push @flags, $SystemFlagMap{$i} || $i;
979 }
980
981 if ($Header) {
982 my $userflags = $Header->header('Flags');
983 my @ufdata = split //, $Self->record('UserFlags');
984 foreach my $base (0, 32, 64, 96) {
985 foreach my $i (0..31) {
986 my $f = $userflags->[$base+31-$i];
987 push @flags, $f if ($f and $ufdata[$base+$i]);
988 }
989 }
990 }
991
992 return wantarray ? @flags : \@flags;
993 }
994
995 =item $index->field_number($Field)
996
997 Return the field number in a record array for the named field, or die
998 if there isn't one.
999
1000 =cut
1001
1002 sub field_number {
1003 my $Self = shift;
1004 my $Field = shift;
1005 my $names = $Self->{format}{RecordNames};
1006 die "No such record field $Field\n" unless exists $names->{$Field};
1007 return $names->{$Field};
1008 }
1009
1010 =item $index->write_header($fh, $header)
1011
1012 Writes a header to $fh - you need to make sure it's seeked to the start (can be used on a non-seekable filehandle)
1013
1014 $header can be in array, hash or buffer format
1015
1016 =cut
1017
1018 sub write_header {
1019 my $Self = shift;
1020 my $fh = shift;
1021 my $header = shift;
1022
1023 my $buf = $Self->_make_header($header);
1024
1025 syswrite($fh, $buf);
1026 }
1027
1028 =item $index->append_record($record)
1029
1030 Appends the record (can be hash, array or buf) to the current file. Needs the filehandle to be seekable. Uses "Exists" from the header to find the position, so don't mess it up!
1031
1032 Also seeks back to the header and rewrites it with exists incremented by one.
1033
1034 =cut
1035
1036 sub append_record {
1037 my $Self = shift;
1038 my $record = shift;
1039
1040 my $NumRecords = $Self->{header}{MinorVersion} < 12 ?
1041 $Self->{header}{Exists} : $Self->{header}{NumRecords};
1042
1043 $Self->reset($NumRecords);
1044 $Self->write_record($Self->{handle}, $record);
1045
1046 # extend the header:
1047 # XXX - sysflags
1048 my $header = $Self->header();
1049 $header->{NumRecords}++;
1050 $Self->rewrite_header($header);
1051 }
1052
1053 sub rewrite_header {
1054 my $Self = shift;
1055 my $header = shift || $Self->header();
1056
1057 sysseek($Self->{handle}, 0, 0);
1058 $Self->write_header($Self->{handle}, $header);
1059
1060 $Self->reset(); # remove any cache and update the seek pointer
1061 }
1062
1063 =item $index->rewrite_record($record, $num)
1064
1065 Rewrite the record at position given by $num with the record (hash, array or buf) passed.
1066
1067 =cut
1068
1069 sub rewrite_record {
1070 my $Self = shift;
1071 my $record = shift;
1072 my $num = @_ ? shift : ($Self->{recno} - 1);
1073
1074 $Self->reset($num);
1075
1076 $Self->write_record($Self->{handle}, $record);
1077
1078 $Self->{recno}++;
1079 }
1080
1081 =item $index->write_record($fh, $record, $num)
1082
1083 Write the record to the new filehandle $fh. If $num is not given then it doesn't need to be seekable.
1084
1085 XXX - $num support not done yet
1086 =cut
1087
1088 sub write_record {
1089 my $Self = shift;
1090 my $fh = shift;
1091 my $record = shift;
1092 my $num = shift; # XXX - seek?
1093
1094 my $buf = $Self->_make_record($record);
1095
1096 syswrite($fh, $buf);
1097 }
1098
1099 =item $index->merge_indexes($target, @extras)
1100
1101 XXX - broken anyway. The purpose of this function is to allow multiple index files to combined into one (say, an expunged file and an index file)
1102
1103 =cut
1104
1105 sub merge_indexes {
1106 my $Self = shift;
1107 my $target = shift;
1108 my @extras = shift;
1109
1110 # copy the current header first
1111 my $targetpos = tell($target);
1112 my $header = $Self->header();
1113 # reset some stuff
1114 $header->{NumRecords} = 0;
1115 $header->{LastAppenddate} = 0;
1116 $header->{LastUid} = 0;
1117 $header->{QuotaUsed} = 0;
1118 $header->{Deleted} = 0;
1119 $header->{Answered} = 0;
1120 $header->{Flagged} = 0;
1121 $header->{HighestModseq} = 0;
1122 $Self->write_header($target, $header);
1123
1124 my @all = ($Self, @extras);
1125
1126 my @records = map { $_->next_record() } @all;
1127
1128 my $nextuid = -1;
1129
1130 while ($nextuid) {
1131 my $this;
1132 my $higheruid;
1133
1134 # read the first record of all lists
1135 foreach my $n (0..$#all) {
1136 next unless $records[$n];
1137 if ($records[$n]{Uid} == $nextuid) {
1138 # algorithm: keep most recently modified
1139 if (not $this or $this->{LastModified} < $records[$n]{LastModified}) {
1140 $this = $records[$n]{LastModified};
1141 }
1142 # step forwards
1143 $records[$n] = $all[$n]->next_record();
1144 }
1145 # find the minimum now
1146 if (not $higheruid or $higheruid > $records[$n]{Uid}) {
1147 $higheruid = $records[$n]{Uid};
1148 }
1149 }
1150
1151 # write out the best record found
1152 if ($this) {
1153 $Self->write_record($target, $this);
1154 $header->{NumRecords}++;
1155 # XXX - to make everything else work, we probably need to reconstruct or
1156 # put the entire logic here!
1157 }
1158
1159 # move along
1160 $nextuid = $higheruid;
1161 }
1162
1163 # move back to the start of this file and re-write the header
1164 seek($target, $targetpos, 0);
1165 $Self->write_header($target, $header);
1166 }
1167
1168 =item $index->header_dump()
1169
1170 =item $index->record_dump()
1171
1172 =item $index->header_longdump()
1173
1174 =item $index->record_longdump()
1175
1176 =item $index->header_undump()
1177
1178 =item $index->record_undump()
1179
1180 Dump the headers and records in either space separated fields or named lines with a blank line between for long.
1181
1182 The "undump" option is able to parse the space separated format, allowing pipe to a standard unix tool to
1183 process the records, and then re-parse them back into a binary index file.
1184
1185 =cut
1186
1187 sub header_dump {
1188 my $Self = shift;
1189 my $array = $Self->header_array();
1190 return join(' ', @$array);
1191 }
1192
1193 sub header_longdump {
1194 my $Self = shift;
1195 my $array = $Self->header_array();
1196 my @data;
1197 my $frm = $Self->{format}{HeaderFields};
1198 foreach my $field (0..$#$frm) {
1199 my $name = $frm->[$field][0];
1200 my $val = $array->[$field];
1201 $val = sprintf("%08x", $val) if $name =~ m/Crc$/;
1202 push @data, "$name: $val";
1203 }
1204 return join("\n", @data, '');
1205 }
1206
1207 sub header_undump {
1208 my $Self = shift;
1209 my $string = shift;
1210 my @items = split ' ', $string;
1211 return \@items;
1212 }
1213
1214 sub record_dump {
1215 my $Self = shift;
1216 my $array = $Self->record_array();
1217 return join(' ', @$array);
1218 }
1219
1220 sub record_longdump {
1221 my $Self = shift;
1222 my $array = $Self->record_array();
1223 my @data;
1224 my $frm = $Self->{format}{RecordFields};
1225 foreach my $field (0..$#$frm) {
1226 my $name = $frm->[$field][0];
1227 my $val = $array->[$field];
1228 $val = sprintf("%08x", $val) if $name =~ m/Crc$/;
1229 push @data, "$name: $val";
1230 }
1231 return join("\n", @data, '');
1232 }
1233
1234 sub record_undump {
1235 my $Self = shift;
1236 my $string = shift;
1237 my @items = split ' ', $string;
1238 return \@items;
1239 }
1240
1241 # INTERNAL METHODS
1242
1243 sub _make_header {
1244 my $Self = shift;
1245 my $ds = shift;
1246
1247 my $ref = ref($ds);
1248
1249 # check what sort of format it is:
1250
1251 # scalar - already a buffer
1252 return $ds unless $ref;
1253
1254 # array
1255 return $Self->_header_a2b($ds) if ref($ds) eq 'ARRAY';
1256
1257 # must be hash
1258 return $Self->_header_h2b($ds);
1259 }
1260
1261 sub _make_record {
1262 my $Self = shift;
1263 my $ds = shift;
1264
1265 my $ref = ref($ds);
1266
1267 # check what sort of format it is:
1268
1269 # scalar - already a buffer
1270 return $ds unless $ref;
1271
1272 # array
1273 return $Self->_record_a2b($ds) if ref($ds) eq 'ARRAY';
1274
1275 # must be hash
1276 return $Self->_record_h2b($ds);
1277 }
1278
1279 ####################
1280 # Header Conversions
1281
1282 sub _header_b2h {
1283 my $Self = shift;
1284 my $buf = shift;
1285 return undef unless $buf;
1286
1287 my $array = $Self->_header_b2a($buf);
1288 my $hash = $Self->_header_a2h($array);
1289
1290 return $hash;
1291 }
1292
1293 sub _header_b2a {
1294 my $Self = shift;
1295 my $buf = shift;
1296 return undef unless $buf;
1297
1298 my @array = unpack($Self->{format}{HeaderPack}, $buf);
1299
1300 # check checksum match!
1301 if ($Self->{version} >= 11) {
1302 my $Header = $Self->{format}{HeaderFields}[$Self->{format}{HeaderNames}{HeaderCrc}];
1303 my $crc = crc32(substr($buf, 0, $Header->[4]));
1304 if ($array[$Header->[3]] != $crc) {
1305 $Self->{checksum_failure} = 1;
1306 warn "Header CRC Failure $array[$Header->[3]] != $crc";
1307 die "Header CRC Failure $array[$Header->[3]] != $crc"
1308 if $Self->{strict_crc};
1309 }
1310 }
1311
1312 return \@array;
1313 }
1314
1315 sub _header_h2b {
1316 my $Self = shift;
1317 my $hash = shift;
1318 return undef unless $hash;
1319
1320 my $array = $Self->_header_h2a($hash);
1321 my $buf = $Self->_header_a2b($array);
1322
1323 return $buf;
1324 }
1325
1326 sub _header_a2b {
1327 my $Self = shift;
1328 my $array = shift;
1329 return undef unless $array;
1330
1331 my $buf = pack($Self->{format}{HeaderPack}, @$array);
1332
1333 if ($Self->{version} >= 11) {
1334 my $Header = $Self->{format}{HeaderFields}[$Self->{format}{HeaderNames}{HeaderCrc}];
1335 my $crc = crc32(substr($buf, 0, $Header->[4]));
1336 substr($buf, $Header->[4]) = pack('N', $crc);
1337 }
1338
1339 return $buf;
1340 }
1341
1342 sub _header_a2h {
1343 my $Self = shift;
1344 my $array = shift;
1345 return undef unless $array;
1346
1347 my %res;
1348 my $frm = $Self->{format}{HeaderFields};
1349 for (0..$#$frm) {
1350 $res{$frm->[$_][0]} = $array->[$_];
1351 }
1352
1353 return \%res;
1354 }
1355
1356 sub _header_h2a {
1357 my $Self = shift;
1358 my $hash = shift;
1359 return undef unless $hash;
1360
1361 my @array;
1362 my $frm = $Self->{format}{HeaderFields};
1363 for (0..$#$frm) {
1364 $array[$_] = $hash->{$frm->[$_][0]};
1365 }
1366
1367 return \@array;
1368 }
1369
1370 ####################
1371 # Record conversions
1372
1373 sub _record_h2b {
1374 my $Self = shift;
1375 my $hash = shift;
1376 return undef unless $hash;
1377
1378 my $array = $Self->_record_h2a($hash);
1379 my $buf = $Self->_record_a2b($array);
1380
1381 return $buf;
1382 }
1383
1384 sub _record_a2b {
1385 my $Self = shift;
1386 my $array = shift;
1387 return undef unless $array;
1388
1389 my $buf = pack($Self->{format}{RecordPack}, @$array);
1390
1391 if ($Self->{version} >= 11) {
1392 my $Record = $Self->{format}{RecordFields}[$Self->{format}{RecordNames}{RecordCrc}];
1393 my $crc = crc32(substr($buf, 0, $Record->[4]));
1394 substr($buf, $Record->[4]) = pack('N', $crc);
1395 }
1396
1397 return $buf;
1398 }
1399
1400 sub _record_b2h {
1401 my $Self = shift;
1402 my $buf = shift;
1403 return undef unless $buf;
1404
1405 my $array = $Self->_record_b2a($buf);
1406 my $hash = $Self->_record_a2h($array);
1407
1408 return $hash;
1409 }
1410
1411 sub _record_b2a {
1412 my $Self = shift;
1413 my $buf = shift;
1414 return undef unless $buf;
1415
1416 my @array = unpack($Self->{format}{RecordPack}, $buf);
1417
1418 # check checksum match!
1419 if ($Self->{version} >= 11) {
1420 my $Record = $Self->{format}{RecordFields}[$Self->{format}{RecordNames}{RecordCrc}];
1421 my $crc = crc32(substr($buf, 0, $Record->[4]));
1422 if ($array[$Record->[3]] != $crc) {
1423 $Self->{checksum_failure} = 1;
1424 warn "Record CRC Failure ($Self->{recno}) $array[$Record->[3]] != $crc";
1425 die "Record CRC Failure ($Self->{recno}) $array[$Record->[3]] != $crc"
1426 if $Self->{strict_crc};
1427 }
1428 }
1429
1430 return \@array;
1431 }
1432
1433 sub _record_a2h {
1434 my $Self = shift;
1435 my $array = shift;
1436 return undef unless $array;
1437
1438 my %res;
1439 my $frm = $Self->{format}{RecordFields};
1440 for (0..$#$frm) {
1441 $res{$frm->[$_][0]} = $array->[$_];
1442 }
1443
1444 return \%res;
1445 }
1446
1447 sub _record_h2a {
1448 my $Self = shift;
1449 my $hash = shift;
1450 return undef unless $hash;
1451
1452 my @array;
1453 my $frm = $Self->{format}{RecordFields};
1454 for (0..$#$frm) {
1455 $array[$_] = $hash->{$frm->[$_][0]};
1456 }
1457
1458 return \@array;
1459 }
1460
1461 =back
1462
1463 =head1 AUTHOR AND COPYRIGHT
1464
1465 Bron Gondwana <brong@fastmail.fm> - Copyright 2008 FastMail
1466
1467 Licenced under the same terms as Cyrus IMAPd.
1468
1469 =cut
1470
1471 1;
0 #!/usr/bin/perl
1
2 use strict;
3 use warnings;
4 use Getopt::Std;
5
6 use Cyrus::IndexFile;
7 use Cyrus::CacheFile;
8 use Cyrus::HeaderFile;
9
10 my %Opts;
11 getopts('CHcdDu:', \%Opts);
12
13 my $file = shift || die "Usage: $0 <indexfile>\n";
14 unless (-f $file) {
15 die "File doesn't exist $file\n";
16 }
17
18 my $cfile = $Opts{C};
19 my $hfile = $Opts{H};
20 unless ($cfile) {
21 $cfile = $file;
22 $cfile =~ s/index$/cache/;
23 }
24 unless ($hfile) {
25 $hfile = $file;
26 $hfile =~ s/index$/header/;
27 }
28 my $index = Cyrus::IndexFile->new_file($file);
29 my $cache;
30 if ($Opts{c}) {
31 $cache = Cyrus::CacheFile->new_file($cfile);
32 }
33 my $headerfile = Cyrus::HeaderFile->new_file($hfile);
34 my $header = $index->header();
35 unless ($Opts{u}) {
36 if ($Opts{d}) {
37 print $index->header_dump() . "\n";
38 } elsif ($Opts{D}) {
39 print $index->header_longdump() . "\n";
40 } else {
41 $header->{NumRecords} ||= $header->{Exists};
42 print "V:$header->{MinorVersion} E:$header->{Exists} N:$header->{NumRecords} U:$header->{LastUid} M:$header->{HighestModseq}\n";
43 }
44 }
45 while (my $r = $index->next_record) {
46 next if ($Opts{u} and $Opts{u} != $r->{Uid});
47 if ($Opts{d}) {
48 print $index->record_dump() . "\n";
49 }
50 elsif ($Opts{D}) {
51 my $offset = sysseek($index->{handle}, 0, 1);
52 print "Offset: $offset\n";
53 print $index->record_longdump();
54 my @flags = $index->flagslist($headerfile);
55 print "FLAGS: @flags\n";
56 print "\n";
57 }
58 elsif ($header->{MinorVersion} == 9) {
59 print "$r->{Uid} $r->{MessageUuid} $r->{Size}\n";
60 }
61 elsif ($header->{MinorVersion} < 13) {
62 my @flags = $index->flagslist();
63 print "$r->{Uid}\@$r->{Modseq} $r->{MessageGuid} $r->{Size} (@flags)\n";
64 }
65 else {
66 my @flags = $index->flagslist($headerfile);
67 printf "$r->{Uid}\@$r->{Modseq} $r->{MessageGuid} $r->{CID} $r->{Size} (@flags)\n";
68 }
69 if ($Opts{c}) {
70 $cache->offset($r->{CacheOffset});
71 my $r = $cache->next_record();
72 print $cache->print_record($r);
73 print "------------------------------------------------\n";
74 }
75 }
5555 CPPFLAGS = -I.. -I$(srcdir)/../imap -I$(srcdir)/../lib @COM_ERR_CPPFLAGS@ @CPPFLAGS@ @SASLFLAGS@
5656 AFS_LDFLAGS = @AFS_LDFLAGS@ @COM_ERR_LDFLAGS@
5757 AFS_LIBS = @AFS_LIBS@
58 IMAP_LIBS = @IMAP_LIBS@ @LIB_RT@
58 IMAP_LIBS = @IMAP_LIBS@ @LIB_RT@ @ICAL_LIBS@ @SQLITE3_LIBS@
5959 LIBS = $(IMAP_LIBS) @COM_ERR_LIBS@
6060 LIB_SASL = @LIB_SASL@
6161 LIB_WRAP = @LIB_WRAP@
6969
7070 SERVICE=../master/service.o
7171
72 IMAP_LIBS = @IMAP_LIBS@ @LIB_RT@
72 IMAP_LIBS = @IMAP_LIBS@ @LIB_RT@ @ICAL_LIBS@ @ICU_LIBS@ @SQLITE3_LIBS@
7373 IMAP_COM_ERR_LIBS = @IMAP_COM_ERR_LIBS@
7474 LIB_WRAP = @LIB_WRAP@
7575 LIBS = $(IMAP_COM_ERR_LIBS)
8080 since otherwise RDATEs are more efficient. Actually, I've set this high
8181 so we only use RRULEs for infinite recurrences. Since expanding RRULEs is
8282 very time-consuming, this seems sensible. */
83 #define MIN_RRULE_OCCURRENCES 1
83 #define MIN_RRULE_OCCURRENCES 10
8484
8585
8686 /* The year we go up to when dumping the list of timezone changes (used
500500 g_strdup (zone_name));
501501 }
502502
503 sprintf (output_directory, "%s/%s", directory, zone_directory);
504 ensure_directory_exists (output_directory);
505 sprintf (filename, "%s/%s.ics", output_directory, zone_filename);
506
507 if (VzicDumpChanges) {
508 sprintf (output_directory, "%s/ChangesVzic/%s", directory, zone_directory);
509 ensure_directory_exists (output_directory);
510 sprintf (changes_filename, "%s/%s", output_directory, zone_filename);
511 }
512
513503 if (zone_subdirectory) {
514504 sprintf (output_directory, "%s/%s/%s", directory, zone_directory,
515505 zone_subdirectory);
523513 sprintf (changes_filename, "%s/%s", output_directory, zone_filename);
524514 }
525515 }
516 else if (zone_directory) {
517 sprintf (output_directory, "%s/%s", directory, zone_directory);
518 ensure_directory_exists (output_directory);
519 sprintf (filename, "%s/%s.ics", output_directory, zone_filename);
520
521 if (VzicDumpChanges) {
522 sprintf (output_directory, "%s/ChangesVzic/%s", directory, zone_directory);
523 ensure_directory_exists (output_directory);
524 sprintf (changes_filename, "%s/%s", output_directory, zone_filename);
525 }
526 }
527 else {
528 sprintf (output_directory, "%s", directory);
529 ensure_directory_exists (output_directory);
530 sprintf (filename, "%s/%s.ics", output_directory, zone_filename);
531
532 if (VzicDumpChanges) {
533 sprintf (output_directory, "%s/ChangesVzic", directory);
534 ensure_directory_exists (output_directory);
535 sprintf (changes_filename, "%s/%s", output_directory, zone_filename);
536 }
537 }
538
526539
527540 /* Create the files. */
528541 fp = fopen (filename, "w");
595608 }
596609 }
597610 }
598
611 #if 0
599612 if (!first_slash_pos) {
600613 #if 0
601614 fprintf (stderr, "No '/' character in Zone name: %s. Skipping.\n", name);
602615 #endif
603616 return FALSE;
604617 }
605
618 #endif
606619 if (invalid) {
607620 *directory = g_strdup ("Invalid");
608621 *filename = g_strdup_printf ("Zone%i", invalid_zone_num++);
622 } else if (!first_slash_pos) {
623 *directory = NULL;
624 *subdirectory = NULL;
625 *filename = g_strdup (name);
609626 } else {
610627 *first_slash_pos = '\0';
611628 *directory = g_strdup (name);
10871104 if (VzicUrlPrefix != NULL)
10881105 fprintf (fp, "TZURL:%s/%s\r\n", VzicUrlPrefix, name);
10891106
1090 #if 0
10911107 /* We use an 'X-' property to place the city name in. */
10921108 fprintf (fp, "X-LIC-LOCATION:%s\r\n", name);
1093 #endif
10941109
10951110 /* We try to find any recurring components first, or they may get output
10961111 as lots of RDATES instead. */
498498 for (i = 0; i < len; i++) {
499499 dirs += to[i] == '/' ? 1 : 0;
500500 }
501 if (dirs) {
501 if (dirs >= 0) {
502502 char rel_from[255];
503503 char to_dir[255];
504504 char to_path[255];
505 if (dirs == 1) {
505 if (dirs == 0) {
506 sprintf(rel_from, "%s.ics", from);
507 } else if (dirs == 1) {
506508 sprintf(rel_from, "../%s.ics", from);
507509 } else if (dirs == 2) {
508510 sprintf(rel_from, "../../%s.ics", from);
166166
167167 /* These are backwards-compatability and weird stuff. */
168168 convert_olson_file ("backward");
169 convert_olson_file ("etcetera");
169170 #if 0
170 convert_olson_file ("etcetera");
171171 convert_olson_file ("leapseconds");
172172 convert_olson_file ("pacificnew");
173173 convert_olson_file ("solar87");
0 /* Release cyrus-imapd-2.4.17-caldav-beta9 */
1 #define _CYRUS_VERSION "v2.4.17-caldav-beta9"
2 #define CYRUS_GITVERSION "6406aa83 2013-12-16"
0 /* Release cyrus-imapd-2.4.17-caldav-beta10 */
1 #define _CYRUS_VERSION "v2.4.17-caldav-beta10"
2 #define CYRUS_GITVERSION "97aa1c46 2014-07-22"