Codebase list cyrus-imapd / upstream/2.4.17+caldav_beta7
New upstream version 2.4.17+caldav~beta7 Ondřej Surý 10 years ago
84 changed file(s) with 2033 addition(s) and 951 deletion(s). Raw diff Collapse all Expand all
6363 COMPILE_ET = @COMPILE_ET@
6464
6565 PACKAGE = cyrus-imapd
66 VERSION = 2.4.17-caldav-beta6
66 VERSION = 2.4.17-caldav-beta7
6767 GIT_VERSION = $(VERSION).git$(shell date +'%Y%m%d%H%M')
6868
6969 all:: xversion
81108110 fi
81118111
81128112 CFLAGS="${CFLAGS} -I${with_afs_incdir}/include"
8113 AFS_LIBS="${with_afs_libdir}/afs/libkauth.a ${with_afs_libdir}/afs/libprot.a ${with_afs_libdir}/afs/libauth.a ${with_afs_libdir}/afs/libsys.a ${with_afs_libdir}/librxkad.a ${with_afs_libdir}/librx.a ${with_afs_libdir}/afs/libsys.a ${with_afs_libdir}/libubik.a ${with_afs_libdir}/liblwp.a ${with_afs_libdir}/afs/util.a ${with_afs_libdir}/afs/libcom_err.a"
8113 AFS_LIBS="${with_afs_libdir}/afs/libkauth.a ${with_afs_libdir}/afs/libprot.a ${with_afs_libdir}/afs/libauth.a ${with_afs_libdir}/afs/libsys.a ${with_afs_libdir}/librxkad.a ${with_afs_libdir}/librx.a ${with_afs_libdir}/afs/libsys.a ${with_afs_libdir}/libubik.a ${with_afs_libdir}/liblwp.a ${with_afs_libdir}/afs/util.a"
8114 if test -f ${with_afs_libdir}/afs/libafscom_err.a; then
8115 AFS_LIBS="$AFS_LIBS ${with_afs_libdir}/afs/libafscom_err.a"
8116 else
8117 AFS_LIBS="$AFS_LIBS ${with_afs_libdir}/afs/libcom_err.a"
8118 fi
81148119 if test -f ${with_afs_libdir}/afs/libaudit.a; then
81158120 AFS_LIBS="$AFS_LIBS ${with_afs_libdir}/afs/libaudit.a"
81168121 fi
1125911264 fi
1126011265
1126111266 if test "$sievedir" != "no"; then
11267 if test "$enable_server" != "no"; then
1126211268 EXTRA_SUBDIRS="${EXTRA_SUBDIRS} timsieved notifyd"
1126311269 EXTRA_OUTPUT="${EXTRA_OUTPUT} timsieved/Makefile notifyd/Makefile"
11270 fi
1126411271
1126511272 PERL_SUBDIRS="${PERL_SUBDIRS} sieve"
1126611273 PERL_DEPSUBDIRS="sieve"
666666 ])
667667
668668 CFLAGS="${CFLAGS} -I${with_afs_incdir}/include"
669 AFS_LIBS="${with_afs_libdir}/afs/libkauth.a ${with_afs_libdir}/afs/libprot.a ${with_afs_libdir}/afs/libauth.a ${with_afs_libdir}/afs/libsys.a ${with_afs_libdir}/librxkad.a ${with_afs_libdir}/librx.a ${with_afs_libdir}/afs/libsys.a ${with_afs_libdir}/libubik.a ${with_afs_libdir}/liblwp.a ${with_afs_libdir}/afs/util.a ${with_afs_libdir}/afs/libcom_err.a"
669 AFS_LIBS="${with_afs_libdir}/afs/libkauth.a ${with_afs_libdir}/afs/libprot.a ${with_afs_libdir}/afs/libauth.a ${with_afs_libdir}/afs/libsys.a ${with_afs_libdir}/librxkad.a ${with_afs_libdir}/librx.a ${with_afs_libdir}/afs/libsys.a ${with_afs_libdir}/libubik.a ${with_afs_libdir}/liblwp.a ${with_afs_libdir}/afs/util.a"
670 if test -f ${with_afs_libdir}/afs/libafscom_err.a; then
671 AFS_LIBS="$AFS_LIBS ${with_afs_libdir}/afs/libafscom_err.a"
672 else
673 AFS_LIBS="$AFS_LIBS ${with_afs_libdir}/afs/libcom_err.a"
674 fi
670675 if test -f ${with_afs_libdir}/afs/libaudit.a; then
671676 AFS_LIBS="$AFS_LIBS ${with_afs_libdir}/afs/libaudit.a"
672677 fi
12671272
12681273 dnl for timsieved
12691274 if test "$sievedir" != "no"; then
1275 if test "$enable_server" != "no"; then
12701276 EXTRA_SUBDIRS="${EXTRA_SUBDIRS} timsieved notifyd"
12711277 EXTRA_OUTPUT="${EXTRA_OUTPUT} timsieved/Makefile notifyd/Makefile"
1278 fi
12721279
12731280 PERL_SUBDIRS="${PERL_SUBDIRS} sieve"
12741281 PERL_DEPSUBDIRS="sieve"
6767 all:
6868
6969 install:
70 $(srcdir)/../install-sh -d ${DESTDIR}etc
70 $(srcdir)/../install-sh -d ${DESTDIR}/etc
7171 $(INSTALL) -m 644 $(srcdir)/depot.conf $(DESTDIR)
72 $(INSTALL) -m 644 $(srcdir)/rc.local.imap $(DESTDIR)etc
73 $(INSTALL) -m 644 $(srcdir)/rc.local.ptclient $(DESTDIR)etc
72 $(INSTALL) -m 644 $(srcdir)/rc.local.imap $(DESTDIR)/etc
73 $(INSTALL) -m 644 $(srcdir)/rc.local.ptclient $(DESTDIR)/etc
7474
7575 .c.o:
7676 $(CC) -c $(CPPFLAGS) $(DEFS) $(CFLAGS) $<
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-beta6</h1>
11 <ul>
12 <li>Plugged several memory leaks found by Valgrind</li>
13 <li>Less verbose reconnect communication between frontend and
14 backend</li>
15 <li>GET on calendar-home-set now returns a list of subscribe-able
16 calendars</li>
17 <li>Auto-provisioning of calendars/addressbooks now works via a
18 frontend proxy</li>
19 <li>Fixed several conformance bugs detected by CalDAVTester</li>
20 <li>Added support for optionally adding Content-MD5 header to
21 responses (see <tt>httpcontentmd5</tt> option)</li>
22 <li>Fixed time-based queries for components other than VEVENT</li>
23 </ul>
924
1025 <h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta5</h1>
1126 <ul>
3752 <li>Don't show addressbook mailboxes in IMAP LIST output</li>
3853 <li>Plugged leaked memory found by Valgrind</li>
3954 <li>Better handling of request/response bodies</li>
40 <li>Added <tt>httpprettytelemetry</tt> option<li>
41 <li>Added <tt>httpallowcors</tt> option (Cross-Origin Resource Sharing)<li>
55 <li>Added <tt>httpprettytelemetry</tt> option</li>
56 <li>Added <tt>httpallowcors</tt> option (Cross-Origin Resource Sharing)</li>
4257 </ul>
4358
4459 <h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta3</h1>
4762 <li>Rewrote list_feeds() to not use memmem()</li>
4863 <li>OPTIONS method can be used without authentication</li>
4964 <li>Better handling of Connection:keep-alive</li>
50 <li>Added <tt>httpallowedurls</tt> option<li>
65 <li>Added <tt>httpallowedurls</tt> option</li>
5166 </ul>
5267
5368 <h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta2</h1>
381381 </ol>
382382 </p>
383383
384 <h4><a href="http://www.acal.me">aCal</a></h4>
385
386 <p>This client will autodetect all available calendars on a server.
387 To add a Cyrus server to this client, perform the following steps:
388
389 <ol>
390 <li>Press the Andoid "Menu" button.</li>
391 <li>Select "Settings".</li>
392 <li>Select "Servers".</li>
393 <li>Select "Add Server".</li>
394 <li>Select "Manual Configuration".</li>
395 <li>Fill in Username, Password, and User URL (servername) accordingly.</li>
396 <li>Press "Apply".</li>
397 </ol>
398 </p>
399
400 <h4><a href="http://www.inf-it.com/open-source/clients/caldavzap/">
401 CalDavZAP</a></h4>
402
403 <p>This client will autodetect all available calendars on a server.
404 To configure this client for a Cyrus server, edit <tt>config.js</tt> as
405 follows:
406
407 <ol>
408 <li>Set the <tt>href</tt> value in
409 the <tt>globalNetworkCheckSettings</tt> array to a URL of the following
410 form: <tt>https://&lt;servername&gt;/dav/principals/user/</tt>
411 <br>Note that the trailing "/" is REQUIRED.</li>
412 <li>Set the <tt>globalSettingsType</tt> option
413 to <tt>calendar-home-set</tt></li>
414 <li>Set any other options as desired
415 (e.g. <tt>globalDatepickerFirstDayOfWeek</tt>, <tt>globalTimeZone</tt>).</li>
416 </ol>
417 </p>
418
384419
385420 <h2>CardDAV Module</h2>
386421
454489 </ol>
455490 </p>
456491
492 <h4><a href="http://www.inf-it.com/open-source/clients/carddavmate/">
493 CardDavMATE</a></h4>
494
495 <p>This client will autodetect all available addressbooks on a server.
496 To configure this client for a Cyrus server, edit <tt>config.js</tt> as
497 follows:
498
499 <ol>
500 <li>Set the <tt>href</tt> value in
501 the <tt>globalNetworkCheckSettings</tt> array to a URL of the following
502 form: <tt>https://&lt;servername&gt;/dav/principals/user/</tt>
503 <br>Note that the trailing "/" is REQUIRED.</li>
504 <li>Set the <tt>globalSettingsType</tt> option
505 to <tt>addressbook-home-set</tt></li>
506 <li>Set any other options as desired.</li>
507 </ol>
508 </p>
509
457510
458511 <h2>iSchedule Module</h2>
459512
66 <body>
77
88 <h1>Upgrading From Previous Versions</h1>
9
10 <h2>Upgrading from 2.4.17-caldav-beta6 or earlier</h2>
11 <ul>
12 <li>The time span calculations for various VCALENDAR components,
13 especially VTODO (tasks), have been fixed. We recommend ALL sites
14 run the <tt>dav_reconstruct</tt> utility for each of their CalDAV
15 users.</li>
16 </ul>
917
1018 <h2>Upgrading from 2.4.15</h2>
1119 <ul>
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Jul 1 13:58:04 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:53 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:53 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:53 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:53 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:53 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:53 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:53 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:53 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:53 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:53 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:53 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:53 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:05 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:06 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
710710 <p style="margin-left:11%; margin-top: 1em">Note that any
711711 path specified by &quot;rss_feedlist_template&quot; is an
712712 exception to this rule. <b><br>
713 httpdocroot:</b> &lt;none&gt;</p>
713 httpcontentmd5:</b> 0</p>
714
715 <p style="margin-left:18%;">If enabled, HTTP responses will
716 include a Content-MD5 header for the purpose of providing an
717 end-to-end message integrity check (MIC) of the payload
718 body. Note that enabling this option will use additional CPU
719 to generate the MD5 digest, which may be ignored by clients
720 anyways.</p>
721
722 <p style="margin-left:11%;"><b>httpdocroot:</b>
723 &lt;none&gt;</p>
714724
715725 <p style="margin-left:18%;">If set, http will serve the
716726 static content (html/text/jpeg/gif files, etc) rooted at
00 <!-- Creator : groff version 1.22.1 -->
1 <!-- CreationDate: Mon Jul 1 13:58:06 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:06 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:06 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:06 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:06 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:06 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:06 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:06 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:06 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:06 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:54 2013 -->
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 Jul 1 13:58:06 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:07 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:08 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:08 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:55 2013 -->
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 Jul 1 13:58:08 2013 -->
1 <!-- CreationDate: Wed Sep 18 15:19:56 2013 -->
22 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
33 "http://www.w3.org/TR/html4/loose.dtd">
44 <html>
0 Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta6
1
2 * Plugged several memory leaks found by Valgrind
3 * Less verbose reconnect communication between frontend and backend
4 * GET on calendar-home-set now returns a list of subscribe-able
5 calendars
6 * Auto-provisioning of calendars/addressbooks now works via a
7 frontend proxy
8 * Fixed several conformance bugs detected by CalDAVTester
9 * Added support for optionally adding Content-MD5 header to responses
10 (see httpcontentmd5 option)
11 * Fixed time-based queries for components other than VEVENT
12
013 Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta5
114
215 * RSS module now produces Atom 1.0 output rather than RSS 2.0 (we
2336 * Don't show addressbook mailboxes in IMAP LIST output
2437 * Plugged leaked memory found by Valgrind
2538 * Better handling of request/response bodies
26 * Added httpprettytelemetry option
27 *
28 * Added httpallowcors option (Cross-Origin Resource Sharing)
29 *
39 * Added httpprettytelemetry option
40 * Added httpallowcors option (Cross-Origin Resource Sharing)
3041
3142 Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta3
3243
3445 * Rewrote list_feeds() to not use memmem()
3546 * OPTIONS method can be used without authentication
3647 * Better handling of Connection:keep-alive
37 * Added httpallowedurls option
38 *
48 * Added httpallowedurls option
3949
4050 Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta2
4151
00 Upgrading From Previous Versions
1
2 Upgrading from 2.4.17-caldav-beta6 or earlier
3
4 * The time span calculations for various VCALENDAR components,
5 especially VTODO (tasks), have been fixed. We recommend ALL sites
6 run the dav_reconstruct utility for each of their CalDAV users.
17
28 Upgrading from 2.4.15
39
291291 struct sockaddr_storage saddr_l, saddr_r;
292292 char remoteip[60], localip[60];
293293 socklen_t addrsize;
294 int local_cb = 0;
295294 char buf[2048], optstr[128], *p;
296295 const char *mech_conf, *pass;
297296
309308 return SASL_FAIL;
310309
311310 if (!cb) {
312 local_cb = 1;
313311 strlcpy(optstr, s->hostname, sizeof(optstr));
314312 p = strchr(optstr, '.');
315313 if (p) *p = '\0';
320318 config_getstring(IMAPOPT_PROXY_AUTHNAME),
321319 config_getstring(IMAPOPT_PROXY_REALM),
322320 pass);
321 s->sasl_cb = cb;
323322 }
324323
325324 /* Require proxying if we have an "interesting" userid (authzid) */
328327 (prot->u.std.sasl_cmd.parse_success ? SASL_SUCCESS_DATA : 0),
329328 &s->saslconn);
330329 if (r != SASL_OK) {
331 if (local_cb) free_callbacks(cb);
332330 return r;
333331 }
334332
335333 r = sasl_setprop(s->saslconn, SASL_SEC_PROPS, &secprops);
336334 if (!r) r = sasl_setprop(s->saslconn, SASL_SSF_EXTERNAL, &s->ext_ssf);
337335 if (r != SASL_OK) {
338 if (local_cb) free_callbacks(cb);
339336 return r;
340337 }
341338
384381 (*mechlist = ask_capability(s->out, s->in, prot,
385382 &s->capability, NULL,
386383 prot->u.std.tls_cmd.auto_capa)));
387
388 /* xxx unclear that this is correct */
389 if (local_cb) free_callbacks(cb);
390384
391385 if (r == SASL_OK) {
392386 prot_setsasl(s->in, s->saslconn);
698692 }
699693
700694 if (r) {
695 backend_disconnect(ret);
701696 if (!ret_backend) free(ret);
702 close(sock);
703697 ret = NULL;
704698 }
705699
799793 s->saslconn = NULL;
800794 }
801795
796 /* Free any SASL callbacks */
797 if (s->sasl_cb) {
798 free_callbacks(s->sasl_cb);
799 s->sasl_cb = NULL;
800 }
801
802802 /* free last_result buffer */
803803 buf_free(&s->last_result);
804804 }
7070 struct prot_waitevent *timeout; /* event for idle timeout */
7171
7272 sasl_conn_t *saslconn;
73 sasl_callback_t *sasl_cb;
7374 sasl_ssf_t ext_ssf;
7475 #ifdef HAVE_SSL
7576 SSL *tlsconn;
533533 }
534534
535535
536 /* icalcomponent_foreach_recurrence() callback to find ealiest/latest time */
537 static void get_times(icalcomponent *comp, struct icaltime_span *span,
538 void *rock)
536 /* Get time period (start/end) of a component based in RFC 4791 Sec 9.9 */
537 static void get_period(icalcomponent *comp, icalcomponent_kind kind,
538 struct icalperiodtype *period)
539 {
540 icaltimezone *utc = icaltimezone_get_utc_timezone();
541
542 period->start =
543 icaltime_convert_to_zone(icalcomponent_get_dtstart(comp), utc);
544 period->end =
545 icaltime_convert_to_zone(icalcomponent_get_dtend(comp), utc);
546
547 switch (kind) {
548 case ICAL_VEVENT_COMPONENT:
549 if (icaltime_is_null_time(period->end)) {
550 /* No DTEND or DURATION */
551 memcpy(&period->end, &period->start,
552 sizeof(struct icaltimetype));
553
554 if (icaltime_is_date(period->start)) {
555 /* DTSTART is not DATE-TIME */
556 struct icaldurationtype dur;
557
558 dur = icaldurationtype_from_int(60*60*24 - 1); /* P1D */
559 icaltime_add(period->end, dur);
560 }
561 }
562 break;
563
564 case ICAL_VTODO_COMPONENT: {
565 struct icaltimetype due = icalcomponent_get_due(comp);
566
567 if (!icaltime_is_null_time(period->start)) {
568 /* Has DTSTART */
569 if (icaltime_is_null_time(period->end)) {
570 /* No DURATION */
571 memcpy(&period->end, &period->start,
572 sizeof(struct icaltimetype));
573
574 if (!icaltime_is_null_time(due)) {
575 /* Has DUE */
576 if (icaltime_compare(due, period->start) < 0)
577 memcpy(&period->start, &due, sizeof(struct icaltimetype));
578 if (icaltime_compare(due, period->end) > 0)
579 memcpy(&period->end, &due, sizeof(struct icaltimetype));
580 }
581 }
582 }
583 else {
584 icalproperty *prop;
585
586 /* No DTSTART */
587 if (!icaltime_is_null_time(due)) {
588 /* Has DUE */
589 memcpy(&period->start, &due, sizeof(struct icaltimetype));
590 memcpy(&period->end, &due, sizeof(struct icaltimetype));
591 }
592 else if ((prop =
593 icalcomponent_get_first_property(comp,
594 ICAL_COMPLETED_PROPERTY))) {
595 /* Has COMPLETED */
596 period->start =
597 icaltime_convert_to_zone(icalproperty_get_completed(prop),
598 utc);
599 memcpy(&period->end, &period->start, sizeof(struct icaltimetype));
600
601 if ((prop =
602 icalcomponent_get_first_property(comp,
603 ICAL_CREATED_PROPERTY))) {
604 /* Has CREATED */
605 struct icaltimetype created =
606 icaltime_convert_to_zone(icalproperty_get_created(prop),
607 utc);
608 if (icaltime_compare(created, period->start) < 0)
609 memcpy(&period->start, &created, sizeof(struct icaltimetype));
610 if (icaltime_compare(created, period->end) > 0)
611 memcpy(&period->end, &created, sizeof(struct icaltimetype));
612 }
613 }
614 else if ((prop =
615 icalcomponent_get_first_property(comp,
616 ICAL_CREATED_PROPERTY))) {
617 /* Has CREATED */
618 period->start =
619 icaltime_convert_to_zone(icalproperty_get_created(prop),
620 utc);
621 memcpy(&period->end, &period->start, sizeof(struct icaltimetype));
622 }
623 else {
624 /* Always */
625 period->start =
626 icaltime_from_timet_with_zone(INT_MIN, 0, NULL);
627 period->end =
628 icaltime_from_timet_with_zone(INT_MAX, 0, NULL);
629 }
630 }
631 break;
632 }
633
634 case ICAL_VJOURNAL_COMPONENT:
635 if (!icaltime_is_null_time(period->start)) {
636 /* Has DTSTART */
637 memcpy(&period->end, &period->start,
638 sizeof(struct icaltimetype));
639
640 if (icaltime_is_date(period->start)) {
641 /* DTSTART is not DATE-TIME */
642 struct icaldurationtype dur;
643
644 dur = icaldurationtype_from_int(60*60*24 - 1); /* P1D */
645 icaltime_add(period->end, dur);
646 }
647 }
648 else {
649 /* Never */
650 period->start =
651 icaltime_from_timet_with_zone(INT_MAX, 0, NULL);
652 period->end =
653 icaltime_from_timet_with_zone(INT_MIN, 0, NULL);
654 }
655 break;
656
657 case ICAL_VFREEBUSY_COMPONENT:
658 if (icaltime_is_null_time(period->start) ||
659 icaltime_is_null_time(period->end)) {
660 /* No DTSTART or DTEND */
661 icalproperty *fb =
662 icalcomponent_get_first_property(comp,
663 ICAL_FREEBUSY_PROPERTY);
664
665
666 if (fb) {
667 /* Has FREEBUSY */
668 /* XXX Convert FB period into our period */
669 }
670 else {
671 /* Never */
672 period->start =
673 icaltime_from_timet_with_zone(INT_MAX, 0, NULL);
674 period->end =
675 icaltime_from_timet_with_zone(INT_MIN, 0, NULL);
676 }
677 }
678 break;
679
680 default:
681 break;
682 }
683 }
684
685
686 /* icalcomponent_foreach_recurrence() callback to find earliest/latest time */
687 static void recur_cb(icalcomponent *comp, struct icaltime_span *span,
688 void *rock)
539689 {
540690 struct icalperiodtype *period = (struct icalperiodtype *) rock;
541691 int is_date = icaltime_is_date(icalcomponent_get_dtstart(comp));
556706 void caldav_make_entry(icalcomponent *ical, struct caldav_data *cdata)
557707 {
558708 icalcomponent *comp = icalcomponent_get_first_real_component(ical);
559 icaltimezone *utc = icaltimezone_get_utc_timezone();
560709 icalcomponent_kind kind;
561710 icalproperty *prop;
562711 unsigned mykind = 0, recurring = 0, transp = 0;
563 struct icalperiodtype period;
712 struct icalperiodtype span;
564713
565714 /* Get iCalendar UID */
566715 cdata->ical_uid = icalcomponent_get_uid(comp);
599748 cdata->transp = transp;
600749
601750 /* Get base dtstart and dtend */
602 period.start =
603 icaltime_convert_to_zone(icalcomponent_get_dtstart(comp), utc);
604 period.end =
605 icaltime_convert_to_zone(icalcomponent_get_dtend(comp), utc);
751 get_period(comp, kind, &span);
606752
607753 /* See if its a recurring event */
608754 if (icalcomponent_get_first_property(comp,ICAL_RRULE_PROPERTY) ||
615761 comp,
616762 icaltime_from_timet_with_zone(INT_MIN, 0, NULL),
617763 icaltime_from_timet_with_zone(INT_MAX, 0, NULL),
618 get_times,
619 &period);
764 recur_cb,
765 &span);
620766
621767 /* Handle overridden recurrences */
622768 while ((comp = icalcomponent_get_next_component(ical, kind))) {
623 struct icaltimetype start =
624 icaltime_convert_to_zone(icalcomponent_get_dtstart(comp), utc);
625 struct icaltimetype end =
626 icaltime_convert_to_zone(icalcomponent_get_dtend(comp), utc);
627
628 if (icaltime_compare(start, period.start) < 0)
629 memcpy(&period.start, &start, sizeof(struct icaltimetype));
630
631 if (icaltime_compare(end, period.end) > 0)
632 memcpy(&period.end, &end, sizeof(struct icaltimetype));
769 struct icalperiodtype period;
770
771 get_period(comp, kind, &period);
772
773 if (icaltime_compare(period.start, span.start) < 0)
774 memcpy(&span.start, &period.start, sizeof(struct icaltimetype));
775
776 if (icaltime_compare(period.end, span.end) > 0)
777 memcpy(&span.end, &period.end, sizeof(struct icaltimetype));
633778 }
634779 }
635780
636 cdata->dtstart = icaltime_as_ical_string(period.start);
637 cdata->dtend = icaltime_as_ical_string(period.end);
781 cdata->dtstart = icaltime_as_ical_string(span.start);
782 cdata->dtend = icaltime_as_ical_string(span.end);
638783 cdata->recurring = recurring;
639784 }
640785
204204 for (recno = 1; recno <= mailbox->i.num_records; recno++) {
205205 struct body *body;
206206 struct param *param;
207 const char *msg_base = NULL, *resource = NULL;
207 const char *msg_base = NULL;
208208 unsigned long msg_size = 0;
209209 icalcomponent *ical = NULL;
210210
229229 message_read_bodystructure(&record, &body);
230230 for (param = body->disposition_params; param; param = param->next) {
231231 if (!strcmp(param->attribute, "FILENAME")) {
232 resource = param->value;
233 break;
232 cdata.dav.resource = param->value;
233 }
234 else if (!strcmp(param->attribute, "SCHEDULE-TAG")) {
235 cdata.sched_tag = param->value;
234236 }
235237 }
236 cdata.dav.resource = resource;
237238
238239 caldav_make_entry(ical, &cdata);
239
240 /* XXX Do we need to do something with Schedule-Tag?
241 Perhaps we need to keep it in resource.
242 */
243240
244241 caldav_write(caldavdb, &cdata, 0);
245242
466466 sasl_seterror(conn, 0, "buffer overflow while canonicalizing");
467467 return SASL_BUFOVER;
468468 }
469 memcpy(out, user, ulen);
469 memmove(out, user, ulen);
470470 out[ulen] = '\0';
471471
472472 canonuser = canonify_userid(out, NULL, (int*) context);
8484 #include "smtpclient.h"
8585 #include "spool.h"
8686 #include "stristr.h"
87 #include "tok.h"
8788 #include "util.h"
8889 #include "version.h"
8990 #include "xmalloc.h"
108109 };
109110
110111 static struct caldav_db *auth_caldavdb = NULL;
112 static time_t compile_time;
111113
112114 static void my_caldav_init(struct buf *serverinfo);
113115 static void my_caldav_auth(const char *userid);
146148 unsigned flags);
147149
148150 static void sched_request(const char *organizer, struct sched_param *sparam,
149 icalcomponent *oldical, icalcomponent *newical);
151 icalcomponent *oldical, icalcomponent *newical,
152 const char *att_update);
150153 static void sched_reply(const char *userid,
151154 icalcomponent *oldical, icalcomponent *newical);
152155
229232 /* Need to set this to parse CalDAV Scheduling parameters */
230233 ical_set_unknown_token_handling_setting(ICAL_ASSUME_IANA_TOKEN);
231234 }
235
236 compile_time = calc_compile_time(__TIME__, __DATE__);
232237 }
233238
234239
240245 char ident[MAX_MAILBOX_NAME];
241246 struct buf acl = BUF_INITIALIZER;
242247
243 if (config_mupdate_server && !config_getstring(IMAPOPT_PROXYSERVERS)) {
244 /* proxy-only server - won't have DAV databases */
245 return;
246 }
247 else if (httpd_userisadmin ||
248 global_authisa(httpd_authstate, IMAPOPT_PROXYSERVERS)) {
248 if (httpd_userisadmin ||
249 global_authisa(httpd_authstate, IMAPOPT_PROXYSERVERS)) {
249250 /* admin or proxy from frontend - won't have DAV database */
250251 return;
252 }
253 else if (config_mupdate_server && !config_getstring(IMAPOPT_PROXYSERVERS)) {
254 /* proxy-only server - won't have DAV database */
255 }
256 else {
257 /* Open CalDAV DB for 'userid' */
258 my_caldav_reset();
259 auth_caldavdb = caldav_open(userid, CALDAV_CREATE);
260 if (!auth_caldavdb) fatal("Unable to open CalDAV DB", EC_IOERR);
251261 }
252262
253263 /* Auto-provision calendars for 'userid' */
259269 caldav_mboxname(NULL, userid, mailboxname);
260270 r = mboxlist_lookup(mailboxname, &mbentry, NULL);
261271 if (r == IMAP_MAILBOX_NONEXISTENT) {
262 r = mboxlist_createmailboxcheck(mailboxname, 0, NULL, 0,
263 userid, httpd_authstate, NULL,
264 &partition, 0);
272 if (config_mupdate_server) {
273 /* Find location of INBOX */
274 char inboxname[MAX_MAILBOX_BUFFER];
275
276 r = (*httpd_namespace.mboxname_tointernal)(&httpd_namespace,
277 "INBOX",
278 userid, inboxname);
279 if (!r) {
280 char *server;
281
282 r = http_mlookup(inboxname, &server, NULL, NULL);
283 if (!r && server) {
284 proxy_findserver(server, &http_protocol, proxy_userid,
285 &backend_cached, NULL, NULL, httpd_in);
286
287 return;
288 }
289 }
290 }
291
292 /* Create locally */
293 if (!r) r = mboxlist_createmailboxcheck(mailboxname, 0, NULL, 0,
294 userid, httpd_authstate, NULL,
295 &partition, 0);
265296 if (!r) {
266297 buf_reset(&acl);
267298 cyrus_acl_masktostr(ACL_ALL | DACL_READFB, rights);
334365
335366 if (partition) free(partition);
336367 buf_free(&acl);
337
338 /* Open CalDAV DB for 'userid' */
339 my_caldav_reset();
340 auth_caldavdb = caldav_open(userid, CALDAV_CREATE);
341 if (!auth_caldavdb) fatal("Unable to open CalDAV DB", EC_IOERR);
342368 }
343369
344370
397423 tgt->userlen = len;
398424
399425 p += len;
400 if (!*p || !*++p) goto done;
426 if (!*p || !*++p) {
427 /* Make sure calendar-home-set is terminated with '/' */
428 if (p[-1] != '/') *p++ = '/';
429 goto done;
430 }
401431
402432 len = strcspn(p, "/");
403433 }
421451 p += len;
422452
423453 if (*p) {
424 *errstr = "Too many segments in request target path";
425 return HTTP_FORBIDDEN;
454 // *errstr = "Too many segments in request target path";
455 return HTTP_NOT_FOUND;
426456 }
427457
428458 done:
695725
696726 if (!strcmp(sparam.userid, userid)) {
697727 /* Organizer scheduling object resource */
698 sched_request(organizer, &sparam, ical, NULL);
728 sched_request(organizer, &sparam, ical, NULL, 0);
699729 }
700730 else if (!(hdr = spool_getheader(txn->req_hdrs, "Schedule-Reply")) ||
701731 strcmp(hdr[0], "F")) {
711741 }
712742
713743
714 /* Perform a GET/HEAD request on a CalDAV resource */
715 static int meth_get(struct transaction_t *txn, void *params)
716 {
717 struct meth_params *gparams = (struct meth_params *) params;
718 int ret = 0, r, precond, rights;
744 static int dump_calendar(struct transaction_t *txn, struct meth_params *gparams)
745 {
746 int ret = 0, r, precond;
719747 struct resp_body_t *resp_body = &txn->resp_body;
720748 struct buf *buf = &resp_body->payload;
721 char *server, *acl;
722749 struct mailbox *mailbox = NULL;
723750 static char etag[33];
724751 uint32_t recno;
725752 struct index_record record;
726753 struct hash_table tzid_table;
754 static const char *displayname_annot =
755 ANNOT_NS "<" XML_NS_DAV ">displayname";
756 struct annotation_data attrib;
757
758 /* Open mailbox for reading */
759 if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) {
760 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
761 txn->req_tgt.mboxname, error_message(r));
762 txn->error.desc = error_message(r);
763 ret = HTTP_SERVER_ERROR;
764 goto done;
765 }
766
767 /* Check any preconditions */
768 sprintf(etag, "%u-%u-%u",
769 mailbox->i.uidvalidity, mailbox->i.last_uid, mailbox->i.exists);
770 precond = gparams->check_precond(txn, NULL, etag, mailbox->index_mtime);
771
772 switch (precond) {
773 case HTTP_OK:
774 case HTTP_NOT_MODIFIED:
775 /* Fill in ETag, Last-Modified, Expires, and Cache-Control */
776 txn->resp_body.etag = etag;
777 txn->resp_body.lastmod = mailbox->index_mtime;
778 txn->resp_body.maxage = 3600; /* 1 hr */
779 txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; /* don't use stale data */
780
781 if (precond != HTTP_NOT_MODIFIED) break;
782
783 default:
784 /* We failed a precondition - don't perform the request */
785 ret = precond;
786 goto done;
787 }
788
789 /* Setup for chunked response */
790 txn->flags.te |= TE_CHUNKED;
791 txn->resp_body.type = gparams->content_type;
792
793 /* Set filename of resource */
794 memset(&attrib, 0, sizeof(struct annotation_data));
795 r = annotatemore_lookup(mailbox->name, displayname_annot,
796 /* shared */ "", &attrib);
797 if (r || !attrib.value) attrib.value = strrchr(mailbox->name, '.') + 1;
798
799 buf_reset(&txn->buf);
800 buf_printf(&txn->buf, "%s.ics", attrib.value);
801 txn->resp_body.fname = buf_cstring(&txn->buf);
802
803 /* Short-circuit for HEAD request */
804 if (txn->meth == METH_HEAD) {
805 response_header(HTTP_OK, txn);
806 return 0;
807 }
808
809 /* iCalendar data in response should not be transformed */
810 txn->flags.cc |= CC_NOTRANSFORM;
811
812 /* Create hash table for TZIDs */
813 construct_hash_table(&tzid_table, 10, 1);
814
815 /* Begin iCalendar */
816 buf_setcstr(buf, "BEGIN:VCALENDAR\r\n");
817 buf_printf(buf, "PRODID:-//CyrusIMAP.org/Cyrus %s//EN\r\n",
818 cyrus_version());
819 buf_appendcstr(buf, "VERSION:2.0\r\n");
820 write_body(HTTP_OK, txn, buf_cstring(buf), buf_len(buf));
821
822 for (recno = 1; recno <= mailbox->i.num_records; recno++) {
823 const char *msg_base = NULL;
824 unsigned long msg_size = 0;
825 icalcomponent *ical;
826
827 if (mailbox_read_index_record(mailbox, recno, &record)) continue;
828
829 if (record.system_flags & (FLAG_EXPUNGED | FLAG_DELETED)) continue;
830
831 /* Map and parse existing iCalendar resource */
832 mailbox_map_message(mailbox, record.uid, &msg_base, &msg_size);
833 ical = icalparser_parse_string(msg_base + record.header_size);
834 mailbox_unmap_message(mailbox, record.uid, &msg_base, &msg_size);
835
836 if (ical) {
837 icalcomponent *comp;
838 const char *cal_str = NULL;
839
840 for (comp = icalcomponent_get_first_component(ical,
841 ICAL_ANY_COMPONENT);
842 comp;
843 comp = icalcomponent_get_next_component(ical,
844 ICAL_ANY_COMPONENT)) {
845 icalcomponent_kind kind = icalcomponent_isa(comp);
846
847 /* Don't duplicate any TZIDs in our iCalendar */
848 if (kind == ICAL_VTIMEZONE_COMPONENT) {
849 icalproperty *prop =
850 icalcomponent_get_first_property(comp,
851 ICAL_TZID_PROPERTY);
852 const char *tzid = icalproperty_get_tzid(prop);
853
854 if (hash_lookup(tzid, &tzid_table)) continue;
855 else hash_insert(tzid, (void *)0xDEADBEEF, &tzid_table);
856 }
857
858 /* Include this component in our iCalendar */
859 cal_str = icalcomponent_as_ical_string(comp);
860 write_body(0, txn, cal_str, strlen(cal_str));
861 }
862
863 icalcomponent_free(ical);
864 }
865 }
866
867 free_hash_table(&tzid_table, NULL);
868
869 /* End iCalendar */
870 buf_setcstr(buf, "END:VCALENDAR\r\n");
871 write_body(0, txn, buf_cstring(buf), buf_len(buf));
872
873 /* End of output */
874 write_body(0, txn, NULL, 0);
875
876 done:
877 if (mailbox) mailbox_unlock_index(mailbox, NULL);
878
879 return ret;
880 }
881
882
883 /*
884 * mboxlist_findall() callback function to list calendars
885 */
886 static int list_cb(char *name,
887 int matchlen __attribute__((unused)),
888 int maycreate __attribute__((unused)),
889 void *rock)
890 {
891 struct transaction_t *txn = (struct transaction_t *) rock;
892 struct buf *body = &txn->resp_body.payload;
893 struct buf *url = &txn->buf;
894 static size_t inboxlen = 0;
895 static size_t outboxlen = 0;
896 char *acl, *shortname;
897 size_t len;
898 int r;
899 static const char *displayname_annot =
900 ANNOT_NS "<" XML_NS_DAV ">displayname";
901 struct annotation_data displayname;
902
903 if (!inboxlen) inboxlen = strlen(SCHED_INBOX) - 1;
904 if (!outboxlen) outboxlen = strlen(SCHED_OUTBOX) - 1;
905
906 shortname = strrchr(name, '.') + 1;
907 len = strlen(shortname);
908
909 /* Don't list scheduling Inbox/Outbox */
910 if ((len == inboxlen && !strncmp(shortname, SCHED_INBOX, inboxlen)) ||
911 (len == outboxlen && !strncmp(shortname, SCHED_OUTBOX, outboxlen)))
912 return 0;
913
914 /* Don't list deleted mailboxes */
915 if (mboxname_isdeletedmailbox(name)) return 0;
916
917 /* Lookup the mailbox and make sure its readable */
918 http_mlookup(name, NULL, &acl, NULL);
919 if (!acl || !(cyrus_acl_myrights(httpd_authstate, acl) & ACL_READ))
920 return 0;
921
922 /* Send a body chunk once in a while */
923 if (buf_len(body) > PROT_BUFSIZE) {
924 write_body(0, txn, buf_cstring(body), buf_len(body));
925 buf_reset(body);
926 }
927
928 /* Lookup DAV:displayname */
929 memset(&displayname, 0, sizeof(struct annotation_data));
930 r = annotatemore_lookup(name, displayname_annot,
931 /* shared */ "", &displayname);
932 if (r || !displayname.value) displayname.value = shortname;
933
934 /* Add available calendar with link */
935 len = buf_len(url);
936 buf_printf_markup(body, 3, "<li><a href=\"%s%s\">%s</a></li>",
937 buf_cstring(url), shortname, displayname.value);
938 buf_truncate(url, len);
939
940 return 0;
941 }
942
943
944 /* Create a HTML document listing all calendars available to the user */
945 static int list_calendars(struct transaction_t *txn,
946 struct meth_params *gparams)
947 {
948 int ret = 0, precond;
949 time_t lastmod = compile_time;
950 char mboxlist[MAX_MAILBOX_PATH+1];
951 struct stat sbuf;
952 static char etag[63];
953 unsigned level = 0;
954 struct buf *body = &txn->resp_body.payload;
955 const char *host = NULL;
956
957 /* stat() mailboxes.db for Last-Modified and ETag */
958 snprintf(mboxlist, MAX_MAILBOX_PATH, "%s%s", config_dir, FNAME_MBOXLIST);
959 stat(mboxlist, &sbuf);
960 lastmod = MAX(compile_time, sbuf.st_mtime);
961 sprintf(etag, "%ld-%ld-%ld", compile_time, sbuf.st_mtime, sbuf.st_size);
962
963 /* Check any preconditions */
964 precond = gparams->check_precond(txn, NULL, etag, lastmod);
965
966 switch (precond) {
967 case HTTP_OK:
968 case HTTP_NOT_MODIFIED:
969 /* Fill in ETag, Last-Modified, and Expires */
970 txn->resp_body.etag = etag;
971 txn->resp_body.lastmod = lastmod;
972 txn->resp_body.maxage = 86400; /* 24 hrs */
973 txn->flags.cc |= CC_MAXAGE;
974
975 if (precond != HTTP_NOT_MODIFIED) break;
976
977 default:
978 /* We failed a precondition - don't perform the request */
979 ret = precond;
980 goto done;
981 }
982
983 /* Setup for chunked response */
984 txn->flags.te |= TE_CHUNKED;
985 txn->resp_body.type = "text/html; charset=utf-8";
986
987 /* Short-circuit for HEAD request */
988 if (txn->meth == METH_HEAD) {
989 response_header(HTTP_OK, txn);
990 goto done;
991 }
992
993 /* Send HTML header */
994 buf_reset(body);
995 buf_printf_markup(body, level, HTML_DOCTYPE);
996 buf_printf_markup(body, level++, "<html>");
997 buf_printf_markup(body, level++, "<head>");
998 buf_printf_markup(body, level, "<title>%s</title>", "Available Calendars");
999 buf_printf_markup(body, --level, "</head>");
1000 buf_printf_markup(body, level++, "<body>");
1001 buf_printf_markup(body, level, "<h2>%s</h2>", "Available Calendars");
1002 buf_printf_markup(body, level++, "<ul>");
1003 write_body(HTTP_OK, txn, buf_cstring(body), buf_len(body));
1004 buf_reset(body);
1005
1006 /* Create base URL for calendars */
1007 http_proto_host(txn->req_hdrs, NULL, &host);
1008 assert(!buf_len(&txn->buf));
1009 buf_printf(&txn->buf, "webcal://%s%s", host, txn->req_tgt.path);
1010
1011 /* Generate list of calendars */
1012 strlcat(txn->req_tgt.mboxname, ".%", sizeof(txn->req_tgt.mboxname));
1013
1014 mboxlist_findall(NULL, txn->req_tgt.mboxname, 1, httpd_userid,
1015 httpd_authstate, list_cb, txn);
1016
1017 if (buf_len(body)) write_body(0, txn, buf_cstring(body), buf_len(body));
1018
1019 /* Finish HTML */
1020 buf_reset(body);
1021 buf_printf_markup(body, --level, "</ul>");
1022 buf_printf_markup(body, --level, "</body>");
1023 buf_printf_markup(body, --level, "</html>");
1024 write_body(0, txn, buf_cstring(body), buf_len(body));
1025
1026 /* End of output */
1027 write_body(0, txn, NULL, 0);
1028
1029 done:
1030 return ret;
1031 }
1032
1033
1034 /* Perform a GET/HEAD request on a CalDAV resource */
1035 static int meth_get(struct transaction_t *txn, void *params)
1036 {
1037 struct meth_params *gparams = (struct meth_params *) params;
1038 int r, rights;
1039 char *server, *acl;
7271040
7281041 /* Parse the path */
7291042 if ((r = gparams->parse_path(txn->req_uri->path,
7301043 &txn->req_tgt, &txn->error.desc))) return r;
7311044
7321045 /* GET an individual resource */
733 if (txn->req_tgt.resource) return meth_get_dav(txn, params);
734
735 /* We don't handle GET on a home-set */
736 if (!txn->req_tgt.collection) return HTTP_NO_CONTENT;
1046 if (txn->req_tgt.resource) return meth_get_dav(txn, gparams);
7371047
7381048 /* Locate the mailbox */
7391049 if ((r = http_mlookup(txn->req_tgt.mboxname, &server, &acl, NULL))) {
7711081
7721082 /* Local Mailbox */
7731083
774 if (!*gparams->davdb.db) {
775 syslog(LOG_ERR, "DAV database for user '%s' is not opened. "
776 "Check 'configdirectory' permissions or "
777 "'proxyservers' option on backend server.", proxy_userid);
778 txn->error.desc = "DAV database is not opened";
779 return HTTP_SERVER_ERROR;
780 }
781
782 /* Open mailbox for reading */
783 if ((r = http_mailbox_open(txn->req_tgt.mboxname, &mailbox, LOCK_SHARED))) {
784 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
785 txn->req_tgt.mboxname, error_message(r));
786 txn->error.desc = error_message(r);
787 ret = HTTP_SERVER_ERROR;
788 goto done;
789 }
790
791 /* Check any preconditions */
792 sprintf(etag, "%u-%u-%u",
793 mailbox->i.uidvalidity, mailbox->i.last_uid, mailbox->i.exists);
794 precond = gparams->check_precond(txn, NULL, etag, 0);
795
796 switch (precond) {
797 case HTTP_OK:
798 case HTTP_NOT_MODIFIED:
799 /* Fill in ETag, Expires, and Cache-Control */
800 txn->resp_body.etag = etag;
801 txn->resp_body.maxage = 3600; /* 1 hr */
802 txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; /* don't use stale data */
803
804 if (precond != HTTP_NOT_MODIFIED) break;
805
806 default:
807 /* We failed a precondition - don't perform the request */
808 ret = precond;
809 goto done;
810 }
811
812 /* Setup for chunked response */
813 txn->flags.te |= TE_CHUNKED;
814 txn->resp_body.type = gparams->content_type;
815
816 /* Short-circuit for HEAD request */
817 if (txn->meth == METH_HEAD) {
818 response_header(HTTP_OK, txn);
819 return 0;
820 }
821
822 /* iCalendar data in response should not be transformed */
823 txn->flags.cc |= CC_NOTRANSFORM;
824
825 /* Create hash table for TZIDs */
826 construct_hash_table(&tzid_table, 10, 1);
827
828 /* Begin iCalendar */
829 buf_setcstr(buf, "BEGIN:VCALENDAR\r\n");
830 buf_printf(buf, "PRODID:-//CyrusIMAP.org/Cyrus %s//EN\r\n",
831 cyrus_version());
832 buf_appendcstr(buf, "VERSION:2.0\r\n");
833 write_body(HTTP_OK, txn, buf_cstring(buf), buf_len(buf));
834
835 for (recno = 1; recno <= mailbox->i.num_records; recno++) {
836 const char *msg_base = NULL;
837 unsigned long msg_size = 0;
838 icalcomponent *ical;
839
840 if (mailbox_read_index_record(mailbox, recno, &record)) continue;
841
842 if (record.system_flags & (FLAG_EXPUNGED | FLAG_DELETED)) continue;
843
844 /* Map and parse existing iCalendar resource */
845 mailbox_map_message(mailbox, record.uid, &msg_base, &msg_size);
846 ical = icalparser_parse_string(msg_base + record.header_size);
847 mailbox_unmap_message(mailbox, record.uid, &msg_base, &msg_size);
848
849 if (ical) {
850 icalcomponent *comp;
851 const char *cal_str = NULL;
852
853 for (comp = icalcomponent_get_first_component(ical,
854 ICAL_ANY_COMPONENT);
855 comp;
856 comp = icalcomponent_get_next_component(ical,
857 ICAL_ANY_COMPONENT)) {
858 icalcomponent_kind kind = icalcomponent_isa(comp);
859
860 /* Don't duplicate any TZIDs in our iCalendar */
861 if (kind == ICAL_VTIMEZONE_COMPONENT) {
862 icalproperty *prop =
863 icalcomponent_get_first_property(comp,
864 ICAL_TZID_PROPERTY);
865 const char *tzid = icalproperty_get_tzid(prop);
866
867 if (hash_lookup(tzid, &tzid_table)) continue;
868 else hash_insert(tzid, (void *)0xDEADBEEF, &tzid_table);
869 }
870
871 /* Include this component in our iCalendar */
872 cal_str = icalcomponent_as_ical_string(comp);
873 write_body(0, txn, cal_str, strlen(cal_str));
874 }
875
876 icalcomponent_free(ical);
877 }
878 }
879
880 free_hash_table(&tzid_table, NULL);
881
882 /* End iCalendar */
883 buf_setcstr(buf, "END:VCALENDAR\r\n");
884 write_body(0, txn, buf_cstring(buf), buf_len(buf));
885
886 /* End of output */
887 write_body(0, txn, NULL, 0);
888
889 done:
890 if (mailbox) mailbox_unlock_index(mailbox, NULL);
891
892 return ret;
1084 /* Get an entire calendar collection */
1085 if (txn->req_tgt.collection) return dump_calendar(txn, gparams);
1086
1087 /* GET a list of calendars under calendar-home-set */
1088 else return list_calendars(txn, gparams);
8931089 }
8941090
8951091
9411137 rights = acl ? cyrus_acl_myrights(httpd_authstate, acl) : 0;
9421138
9431139 /* Read body */
944 txn->flags.body |= BODY_DECODE;
945 r = read_body(httpd_in, txn->req_hdrs, &txn->req_body,
946 &txn->flags.body, &txn->error.desc);
1140 txn->req_body.flags |= BODY_DECODE;
1141 r = read_body(httpd_in, txn->req_hdrs, &txn->req_body, &txn->error.desc);
9471142 if (r) {
9481143 txn->flags.conn = CONN_CLOSE;
9491144 return r;
9501145 }
9511146
9521147 /* Make sure we have a body */
953 if (!buf_len(&txn->req_body)) {
1148 if (!buf_len(&txn->req_body.payload)) {
9541149 txn->error.desc = "Missing request body\r\n";
9551150 return HTTP_BAD_REQUEST;
9561151 }
9571152
9581153 /* Parse the iCal data for important properties */
959 ical = icalparser_parse_string(buf_cstring(&txn->req_body));
1154 ical = icalparser_parse_string(buf_cstring(&txn->req_body.payload));
9601155 if (!ical || !icalrestriction_check(ical)) {
9611156 txn->error.precond = CALDAV_VALID_DATA;
962 return HTTP_BAD_REQUEST;
1157 ret = HTTP_BAD_REQUEST;
1158 goto done;
9631159 }
9641160
9651161 meth = icalcomponent_get_method(ical);
10611257 const char *uid, *organizer = NULL;
10621258
10631259 /* Parse and validate the iCal data */
1064 ical = icalparser_parse_string(buf_cstring(&txn->req_body));
1260 ical = icalparser_parse_string(buf_cstring(&txn->req_body.payload));
10651261 if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) {
10661262 txn->error.precond = CALDAV_VALID_DATA;
10671263 ret = HTTP_FORBIDDEN;
11681364
11691365 if (!strcmp(sparam.userid, userid)) {
11701366 /* Organizer scheduling object resource */
1171 sched_request(organizer, &sparam, oldical, ical);
1367 sched_request(organizer, &sparam, oldical, ical, 0);
11721368 }
11731369 else {
11741370 /* Attendee scheduling object resource */
13721568 {
13731569 return (icaltime_is_valid_time(start) && icaltime_is_valid_time(end) &&
13741570 !icaltime_is_date(start) && !icaltime_is_date(end) &&
1375 start.zone && end.zone);
1571 (icaltime_is_utc(start) || start.zone) &&
1572 (icaltime_is_utc(end) || end.zone));
13761573 }
13771574
13781575
13931590 filter->comp = CAL_COMP_VCALENDAR;
13941591 else {
13951592 error->precond = CALDAV_VALID_FILTER;
1396 return HTTP_FORBIDDEN;
1593 ret = HTTP_FORBIDDEN;
13971594 }
13981595 }
13991596 else if (filter->comp == CAL_COMP_VCALENDAR) {
14001597 if (!xmlStrcmp(name, BAD_CAST "VCALENDAR") ||
14011598 !xmlStrcmp(name, BAD_CAST "VALARM")) {
14021599 error->precond = CALDAV_VALID_FILTER;
1403 return HTTP_FORBIDDEN;
1600 ret = HTTP_FORBIDDEN;
14041601 }
14051602 else if (!xmlStrcmp(name, BAD_CAST "VEVENT"))
14061603 filter->comp |= CAL_COMP_VEVENT;
14141611 filter->comp |= CAL_COMP_VTIMEZONE;
14151612 else {
14161613 error->precond = CALDAV_SUPP_FILTER;
1417 return HTTP_FORBIDDEN;
1614 ret = HTTP_FORBIDDEN;
14181615 }
14191616 }
14201617 else if (filter->comp & (CAL_COMP_VEVENT | CAL_COMP_VTODO)) {
14221619 filter->comp |= CAL_COMP_VALARM;
14231620 else {
14241621 error->precond = CALDAV_VALID_FILTER;
1425 return HTTP_FORBIDDEN;
1622 ret = HTTP_FORBIDDEN;
14261623 }
14271624 }
14281625 else {
14291626 error->precond = CALDAV_SUPP_FILTER;
1430 return HTTP_FORBIDDEN;
1627 ret = HTTP_FORBIDDEN;
14311628 }
14321629
1433 ret = parse_comp_filter(node->children, filter, error);
1630 xmlFree(name);
1631
1632 if (!ret)
1633 ret = parse_comp_filter(node->children, filter, error);
14341634 if (ret) return ret;
14351635 }
14361636 else if (!xmlStrcmp(node->name, BAD_CAST "time-range")) {
1437 const char *start, *end;
1637 icaltimezone *utc = icaltimezone_get_utc_timezone();
1638 xmlChar *start, *end;
14381639
14391640 if (!(filter->comp & (CAL_COMP_VEVENT | CAL_COMP_VTODO))) {
14401641 error->precond = CALDAV_VALID_FILTER;
14411642 return HTTP_FORBIDDEN;
14421643 }
14431644
1444 start = (const char *) xmlGetProp(node, BAD_CAST "start");
1445 filter->start = start ? icaltime_from_string(start) :
1446 icaltime_from_timet_with_zone(INT_MIN, 0, NULL);
1447
1448 end = (const char *) xmlGetProp(node, BAD_CAST "end");
1449 filter->end = end ? icaltime_from_string(end) :
1450 icaltime_from_timet_with_zone(INT_MAX, 0, NULL);
1645 start = xmlGetProp(node, BAD_CAST "start");
1646 if (start) {
1647 filter->start = icaltime_from_string((char *) start);
1648 xmlFree(start);
1649 }
1650 else {
1651 filter->start =
1652 icaltime_from_timet_with_zone(INT_MIN, 0, utc);
1653 }
1654
1655 end = xmlGetProp(node, BAD_CAST "end");
1656 if (end) {
1657 filter->end = icaltime_from_string((char *) end);
1658 xmlFree(end);
1659 }
1660 else {
1661 filter->end =
1662 icaltime_from_timet_with_zone(INT_MAX, 0, utc);
1663 }
14511664
14521665 if (!is_valid_timerange(filter->start, filter->end)) {
14531666 error->precond = CALDAV_VALID_FILTER;
15021715 ICAL_VTIMEZONE_COMPONENT)
15031716 || icalcomponent_get_first_real_component(ical)) {
15041717 txn->error.precond = CALDAV_VALID_DATA;
1505 return HTTP_FORBIDDEN;
1718 ret = HTTP_FORBIDDEN;
15061719 }
1720
1721 if (tz) xmlFree(tz);
1722 if (ical) icalcomponent_free(ical);
1723 if (ret) return ret;
15071724 }
15081725 }
15091726 }
17882005 for (node = inroot->children; node; node = node->next) {
17892006 if (node->type == XML_ELEMENT_NODE) {
17902007 if (!xmlStrcmp(node->name, BAD_CAST "time-range")) {
1791 const char *start, *end;
1792
1793 start = (const char *) xmlGetProp(node, BAD_CAST "start");
1794 if (start) calfilter.start = icaltime_from_string(start);
1795
1796 end = (const char *) xmlGetProp(node, BAD_CAST "end");
1797 if (end) calfilter.end = icaltime_from_string(end);
2008 xmlChar *start, *end;
2009
2010 start = xmlGetProp(node, BAD_CAST "start");
2011 if (start) {
2012 calfilter.start = icaltime_from_string((char *) start);
2013 xmlFree(start);
2014 }
2015
2016 end = xmlGetProp(node, BAD_CAST "end");
2017 if (end) {
2018 calfilter.end = icaltime_from_string((char *) end);
2019 xmlFree(end);
2020 }
17982021
17992022 if (!is_valid_timerange(calfilter.start, calfilter.end)) {
18002023 return HTTP_BAD_REQUEST;
18382061 icalproperty_method meth;
18392062 icalproperty *prop;
18402063 unsigned mykind = 0;
2064 const char *organizer = NULL;
18412065 const char *prop_annot = ANNOT_NS "CALDAV:supported-calendar-component-set";
18422066 struct annotation_data attrib;
18432067 struct caldav_data *cdata;
18872111
18882112 /* Check for existing iCalendar UID */
18892113 caldav_lookup_uid(caldavdb, uid, 0, &cdata);
1890 if (cdata->dav.mailbox && !strcmp(cdata->dav.mailbox, mailbox->name) &&
2114 if (!(flags & NO_DUP_CHECK) &&
2115 cdata->dav.mailbox && !strcmp(cdata->dav.mailbox, mailbox->name) &&
18912116 strcmp(cdata->dav.resource, resource)) {
18922117 /* CALDAV:no-uid-conflict */
18932118 char *owner = mboxname_to_userid(cdata->dav.mailbox);
19092134 return HTTP_SERVER_ERROR;
19102135 }
19112136
2137 /* Remove all X-LIC-ERROR properties*/
2138 icalcomponent_strip_errors(ical);
2139
19122140 ics = icalcomponent_as_ical_string(ical);
19132141
19142142 /* Create iMIP header for resource */
19152143 prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
19162144 if (prop) {
1917 fprintf(f, "From: %s\r\n", icalproperty_get_organizer(prop)+7);
2145 organizer = icalproperty_get_organizer(prop)+7;
2146 fprintf(f, "From: %s\r\n", organizer);
19182147 }
19192148 else {
19202149 /* XXX This needs to be done via an LDAP/DB lookup */
19372166 fprintf(f, "; component=%s\r\n", icalcomponent_kind_to_string(kind));
19382167
19392168 fprintf(f, "Content-Length: %u\r\n", (unsigned) strlen(ics));
1940 fprintf(f, "Content-Disposition: inline; filename=\"%s\"\r\n", resource);
2169 fprintf(f, "Content-Disposition: inline; filename=\"%s\"", resource);
2170 if (organizer) {
2171 const char *stag;
2172 if (flags & NEW_STAG) {
2173 sprintf(sched_tag, "%d-%ld-%u", getpid(), now, store_count++);
2174 stag = sched_tag;
2175 }
2176 else stag = cdata->sched_tag;
2177 if (stag) fprintf(f, ";\r\n\tschedule-tag=%s", stag);
2178 }
2179 fprintf(f, "\r\n");
19412180
19422181 /* XXX Check domain of data and use appropriate CTE */
19432182
20432282 }
20442283
20452284 if (!r) {
2285 struct resp_body_t *resp_body = &txn->resp_body;
2286
20462287 /* Create mapping entry from resource name to UID */
20472288 cdata->dav.mailbox = mailbox->name;
20482289 cdata->dav.resource = resource;
20522293 if (!cdata->dav.creationdate) cdata->dav.creationdate = now;
20532294 if (!cdata->organizer) cdata->sched_tag = NULL;
20542295 else if (flags & NEW_STAG) {
2055 sprintf(sched_tag, "%d-%ld-%u",
2056 getpid(), now, store_count++);
2057 txn->resp_body.stag = cdata->sched_tag = sched_tag;
2296 resp_body->stag = cdata->sched_tag = sched_tag;
20582297 }
20592298
20602299 caldav_write(caldavdb, cdata, 1);
20612300 /* XXX check for errors, if this fails, backout changes */
20622301
20632302 if (flags & PREFER_REP) {
2064 struct resp_body_t *resp_body = &txn->resp_body;
2065
20662303 /* Fill in Last-Modified, ETag, and Content-Type */
20672304 resp_body->lastmod = newrecord.internaldate;
20682305 resp_body->etag = message_guid_encode(&newrecord.guid);
20702307 resp_body->loc = txn->req_tgt.path;
20712308 resp_body->len = strlen(ics);
20722309
2073 /* iCalendar data in response should not be transformed */
2074 txn->flags.cc |= CC_NOTRANSFORM;
2310 /* Fill in Expires and Cache-Control */
2311 resp_body->maxage = 3600; /* 1 hr */
2312 txn->flags.cc = CC_MAXAGE
2313 | CC_REVALIDATE /* don't use stale data */
2314 | CC_NOTRANSFORM; /* don't alter iCal data */
20752315
20762316 write_body(ret, txn, ics, strlen(ics));
20772317 ret = 0;
20782318 }
20792319 else if (!(flags & NEW_STAG)) {
20802320 /* Tell client about the new resource */
2081 txn->resp_body.lastmod = newrecord.internaldate;
2082 txn->resp_body.etag =
2083 message_guid_encode(&newrecord.guid);
2321 resp_body->lastmod = newrecord.internaldate;
2322 resp_body->etag = message_guid_encode(&newrecord.guid);
20842323 }
20852324 }
20862325
24682707 !(txn->req_tgt.allow & ALLOW_ISCHEDULE) ?
24692708 ns[NS_DAV] : NULL,
24702709 BAD_CAST attendee, BAD_CAST REQSTAT_NOUSER);
2710
2711 icalproperty_free(prop);
24712712 }
24722713 else if (sparam.flags) {
24732714 /* Remote attendee */
25012742 /* Local attendee on this server */
25022743 xmlNodePtr resp;
25032744 const char *userid = sparam.userid;
2504 struct mboxlist_entry mbentry;
2505 int rights;
2745 struct mboxlist_entry mbentry = { NULL, 0, NULL, NULL };
25062746 icalcomponent *busy = NULL;
25072747
25082748 resp =
25212761 mailboxname, error_message(r));
25222762 xmlNewChild(resp, NULL, BAD_CAST "request-status",
25232763 BAD_CAST REQSTAT_REJECTED);
2524 continue;
2525 }
2526
2527 rights =
2528 mbentry.acl ? cyrus_acl_myrights(org_authstate, mbentry.acl) : 0;
2529 if (!(rights & DACL_SCHEDFB)) {
2764 }
2765
2766 else if (!mbentry.acl ||
2767 !(cyrus_acl_myrights(org_authstate, mbentry.acl) &
2768 DACL_SCHEDFB)) {
25302769 xmlNewChild(resp, NULL, BAD_CAST "request-status",
25312770 BAD_CAST REQSTAT_NOPRIVS);
2532 continue;
2533 }
2534
2535 /* Start query at attendee's calendar-home-set */
2536 snprintf(mailboxname, sizeof(mailboxname),
2537 "user.%s.%s", userid, calendarprefix);
2538
2539 fctx.davdb = caldav_open(userid, CALDAV_CREATE);
2540 fctx.req_tgt->collection = NULL;
2541 calfilter.busytime.len = 0;
2542 busy = busytime_query_local(txn, &fctx, mailboxname,
2543 ICAL_METHOD_REPLY, uid,
2544 organizer, attendee);
2545
2546 caldav_close(fctx.davdb);
2771 }
2772
2773 else {
2774 /* Start query at attendee's calendar-home-set */
2775 snprintf(mailboxname, sizeof(mailboxname),
2776 "user.%s.%s", userid, calendarprefix);
2777
2778 fctx.davdb = caldav_open(userid, CALDAV_CREATE);
2779 fctx.req_tgt->collection = NULL;
2780 calfilter.busytime.len = 0;
2781 busy = busytime_query_local(txn, &fctx, mailboxname,
2782 ICAL_METHOD_REPLY, uid,
2783 organizer, attendee);
2784
2785 caldav_close(fctx.davdb);
2786 }
25472787
25482788 if (busy) {
25492789 xmlNodePtr cdata;
25672807 xmlNewChild(resp, NULL, BAD_CAST "request-status",
25682808 BAD_CAST REQSTAT_NOUSER);
25692809 }
2810
2811 icalproperty_free(prop);
25702812 }
25712813 }
25722814
25822824 done:
25832825 if (org_authstate) auth_freestate(org_authstate);
25842826 if (calfilter.busytime.busy) free(calfilter.busytime.busy);
2585 if (root) xmlFree(root->doc);
2827 if (root) xmlFreeDoc(root->doc);
25862828
25872829 return ret;
25882830 }
26852927 }
26862928
26872929 /* Deliver scheduling object to local recipient */
2688 static void sched_deliver_local(const char *recipient __attribute__((unused)),
2930 static void sched_deliver_local(const char *recipient,
26892931 struct sched_param *sparam,
26902932 struct sched_data *sched_data,
26912933 struct auth_state *authstate)
26922934 {
2693 int r = 0, rights, reqd_privs;
2694 const char *userid = sparam->userid, *mboxname = NULL;
2935 int r = 0, rights, reqd_privs, deliver_inbox = 0;
2936 const char *userid = sparam->userid, *mboxname = NULL, *attendee = NULL;
26952937 static struct buf resource = BUF_INITIALIZER;
26962938 static unsigned sched_count = 0;
26972939 char namebuf[MAX_MAILBOX_BUFFER];
27763018
27773019 prop = icalcomponent_get_first_property(ical, ICAL_METHOD_PROPERTY);
27783020 icalcomponent_remove_property(ical, prop);
3021 icalproperty_free(prop);
3022
3023 deliver_inbox = 1;
27793024 }
27803025 else {
27813026 /* Update existing object */
28043049 /* Set STATUS:CANCELLED on all components */
28053050 do {
28063051 icalcomponent_set_status(comp, ICAL_STATUS_CANCELLED);
2807 // icalcomponent_set_sequence(comp,
2808 // icalcomponent_get_sequence(comp)+1);
3052 icalcomponent_set_sequence(comp,
3053 icalcomponent_get_sequence(comp)+1);
28093054 } while ((comp = icalcomponent_get_next_component(ical, kind)));
3055
3056 deliver_inbox = 1;
28103057
28113058 break;
28123059
28163063 icalproperty *att;
28173064 icalparameter *param;
28183065 icalparameter_partstat partstat;
2819 const char *attendee, *recurid, *req_stat = SCHEDSTAT_SUCCESS;
3066 icalparameter_rsvp rsvp = ICAL_RSVP_NONE;
3067 const char *recurid, *req_stat = SCHEDSTAT_SUCCESS;
28203068
28213069 /* Add each component of old object to hash table for comparison */
28223070 construct_hash_table(&comp_table, 10, 1);
28573105 prop =
28583106 icalcomponent_get_first_property(comp,
28593107 ICAL_RRULE_PROPERTY);
2860 if (prop) icalcomponent_remove_property(comp, prop);
3108 if (prop) {
3109 icalcomponent_remove_property(comp, prop);
3110 icalproperty_free(prop);
3111 }
28613112
28623113 /* Replace DTSTART, DTEND, SEQUENCE */
28633114 prop =
28643115 icalcomponent_get_first_property(comp,
28653116 ICAL_DTSTART_PROPERTY);
2866 if (prop) icalcomponent_remove_property(comp, prop);
3117 if (prop) {
3118 icalcomponent_remove_property(comp, prop);
3119 icalproperty_free(prop);
3120 }
28673121 prop =
28683122 icalcomponent_get_first_property(itip,
28693123 ICAL_DTSTART_PROPERTY);
28743128 prop =
28753129 icalcomponent_get_first_property(comp,
28763130 ICAL_DTEND_PROPERTY);
2877 if (prop) icalcomponent_remove_property(comp, prop);
3131 if (prop) {
3132 icalcomponent_remove_property(comp, prop);
3133 icalproperty_free(prop);
3134 }
28783135 prop =
28793136 icalcomponent_get_first_property(itip,
28803137 ICAL_DTEND_PROPERTY);
28853142 prop =
28863143 icalcomponent_get_first_property(comp,
28873144 ICAL_SEQUENCE_PROPERTY);
2888 if (prop) icalcomponent_remove_property(comp, prop);
3145 if (prop) {
3146 icalcomponent_remove_property(comp, prop);
3147 icalproperty_free(prop);
3148 }
28893149 prop =
28903150 icalcomponent_get_first_property(itip,
28913151 ICAL_SEQUENCE_PROPERTY);
29043164 icalproperty_get_first_parameter(att,
29053165 ICAL_PARTSTAT_PARAMETER);
29063166 partstat = icalparameter_get_partstat(param);
3167 param =
3168 icalproperty_get_first_parameter(att,
3169 ICAL_RSVP_PARAMETER);
3170 if (param) rsvp = icalparameter_get_rsvp(param);
29073171
29083172 prop =
29093173 icalcomponent_get_first_property(itip,
29383202 icalproperty_add_parameter(prop, param);
29393203 }
29403204 icalparameter_set_partstat(param, partstat);
3205
3206 /* Find and set RSVP */
3207 param =
3208 icalproperty_get_first_parameter(prop,
3209 ICAL_RSVP_PARAMETER);
3210 if (param) icalproperty_remove_parameter_by_ref(prop, param);
3211 if (rsvp != ICAL_RSVP_NONE) {
3212 param = icalparameter_new(ICAL_RSVP_PARAMETER);
3213 icalproperty_add_parameter(prop, param);
3214 icalparameter_set_rsvp(param, rsvp);
3215 }
29413216
29423217 /* Find and set SCHEDULE-STATUS */
29433218 for (param =
29603235
29613236 free_hash_table(&comp_table, NULL);
29623237
3238 deliver_inbox = 1;
3239
29633240 break;
29643241 }
29653242
29663243 case ICAL_METHOD_REQUEST: {
29673244 struct hash_table comp_table;
29683245 icalcomponent *itip;
2969 const char *recurid;
3246 const char *tzid, *recurid;
3247
3248 /* Add each VTIMEZONE of old object to hash table for comparison */
3249 construct_hash_table(&comp_table, 10, 1);
3250 for (comp = icalcomponent_get_first_component(ical,
3251 ICAL_VTIMEZONE_COMPONENT);
3252 comp;
3253 comp =
3254 icalcomponent_get_next_component(ical,
3255 ICAL_VTIMEZONE_COMPONENT)) {
3256 prop =
3257 icalcomponent_get_first_property(comp, ICAL_TZID_PROPERTY);
3258 tzid = icalproperty_get_tzid(prop);
3259
3260 hash_insert(tzid, comp, &comp_table);
3261 }
3262
3263 /* Process each VTIMEZONE in the iTIP request */
3264 for (itip =
3265 icalcomponent_get_first_component(sched_data->itip,
3266 ICAL_VTIMEZONE_COMPONENT);
3267 itip;
3268 itip =
3269 icalcomponent_get_next_component(sched_data->itip,
3270 ICAL_VTIMEZONE_COMPONENT)) {
3271 /* Lookup this TZID in the hash table */
3272 prop =
3273 icalcomponent_get_first_property(itip,
3274 ICAL_TZID_PROPERTY);
3275 tzid = icalproperty_get_tzid(prop);
3276
3277 comp = hash_lookup(tzid, &comp_table);
3278 if (comp) {
3279 /* Remove component from old object */
3280 icalcomponent_remove_component(ical, comp);
3281 icalcomponent_free(comp);
3282 }
3283
3284 /* Add new/modified component from iTIP request*/
3285 icalcomponent_add_component(ical,
3286 icalcomponent_new_clone(itip));
3287 }
3288
3289 free_hash_table(&comp_table, NULL);
29703290
29713291 /* Add each component of old object to hash table for comparison */
29723292 construct_hash_table(&comp_table, 10, 1);
29853305 /* Process each component in the iTIP request */
29863306 itip = icalcomponent_get_first_component(sched_data->itip, kind);
29873307 do {
3308 icalcomponent *new_comp = icalcomponent_new_clone(itip);
3309
29883310 /* Lookup this comp in the hash table */
29893311 prop =
29903312 icalcomponent_get_first_property(itip,
29943316
29953317 comp = hash_lookup(recurid, &comp_table);
29963318 if (comp) {
3319 int old_seq, new_seq;
3320 icalparameter *param;
3321
3322 /* Check if this is something more than an update */
3323 /* XXX Probably need to check PARTSTAT=NEEDS-ACTION
3324 and RSVP=TRUE as well */
3325 old_seq = icalcomponent_get_sequence(comp);
3326 new_seq = icalcomponent_get_sequence(itip);
3327 if (new_seq > old_seq) deliver_inbox = 1;
3328
3329 /* Copy over any COMPLETED, PERCENT-COMPLETE,
3330 or TRANSP properties */
3331 prop =
3332 icalcomponent_get_first_property(comp,
3333 ICAL_COMPLETED_PROPERTY);
3334 if (prop) {
3335 icalcomponent_add_property(new_comp,
3336 icalproperty_new_clone(prop));
3337 }
3338 prop =
3339 icalcomponent_get_first_property(comp,
3340 ICAL_PERCENTCOMPLETE_PROPERTY);
3341 if (prop) {
3342 icalcomponent_add_property(new_comp,
3343 icalproperty_new_clone(prop));
3344 }
3345 prop =
3346 icalcomponent_get_first_property(comp,
3347 ICAL_TRANSP_PROPERTY);
3348 if (prop) {
3349 icalcomponent_add_property(new_comp,
3350 icalproperty_new_clone(prop));
3351 }
3352
3353 /* Copy over any ORGANIZER;SCHEDULE-STATUS */
3354 /* XXX Do we only do this iff PARTSTAT!=NEEDS-ACTION */
3355 prop =
3356 icalcomponent_get_first_property(comp,
3357 ICAL_ORGANIZER_PROPERTY);
3358 for (param =
3359 icalproperty_get_first_parameter(prop,
3360 ICAL_IANA_PARAMETER);
3361 param;
3362 param =
3363 icalproperty_get_next_parameter(prop,
3364 ICAL_IANA_PARAMETER)) {
3365 if (!strcmp(icalparameter_get_iana_name(param),
3366 "SCHEDULE-STATUS")) {
3367 const char *sched_stat =
3368 icalparameter_get_iana_value(param);
3369
3370 prop =
3371 icalcomponent_get_first_property(new_comp,
3372 ICAL_ORGANIZER_PROPERTY);
3373 param = icalparameter_new(ICAL_IANA_PARAMETER);
3374 icalproperty_add_parameter(prop, param);
3375 icalparameter_set_iana_name(param,
3376 "SCHEDULE-STATUS");
3377 icalparameter_set_iana_value(param, sched_stat);
3378 }
3379 }
3380
29973381 /* Remove component from old object */
29983382 icalcomponent_remove_component(ical, comp);
3383 icalcomponent_free(comp);
29993384 }
3385 else deliver_inbox = 1;
30003386
30013387 /* Add new/modified component from iTIP request*/
3002 icalcomponent_add_component(ical,
3003 icalcomponent_new_clone(itip));
3388 icalcomponent_add_component(ical, new_comp);
30043389
30053390 } while ((itip = icalcomponent_get_next_component(sched_data->itip,
30063391 kind)));
30373422 }
30383423
30393424 inbox:
3040 /* Create a name for the new iTIP message resource */
3041 buf_reset(&resource);
3042 buf_printf(&resource, "%x-%d-%ld-%u.ics",
3043 strhash(icalcomponent_get_uid(sched_data->itip)), getpid(),
3044 time(0), sched_count++);
3045
3046 /* Store the message in the recipient's Inbox */
3047 mailbox_unlock_index(inbox, NULL);
3048
3049 r = store_resource(&txn, sched_data->itip, inbox, buf_cstring(&resource),
3050 caldavdb, OVERWRITE_NO, 0);
3051 /* XXX What do we do if storing to Inbox fails? */
3425 if (deliver_inbox) {
3426 /* Create a name for the new iTIP message resource */
3427 buf_reset(&resource);
3428 buf_printf(&resource, "%x-%d-%ld-%u.ics",
3429 strhash(icalcomponent_get_uid(sched_data->itip)), getpid(),
3430 time(0), sched_count++);
3431
3432 /* Store the message in the recipient's Inbox */
3433 mailbox_unlock_index(inbox, NULL);
3434
3435 r = store_resource(&txn, sched_data->itip, inbox,
3436 buf_cstring(&resource), caldavdb, OVERWRITE_NO, 0);
3437 /* XXX What do we do if storing to Inbox fails? */
3438 }
3439
3440 /* XXX Should this be a config option? - it might have perf implications */
3441 if (sched_data->is_reply) {
3442 /* Send updates to attendees */
3443 sched_request(recipient, sparam, NULL, ical, attendee);
3444 }
30523445
30533446 done:
30543447 if (ical) icalcomponent_free(ical);
3055 if (inbox) mailbox_close(&inbox);
3448 if (inbox) {
3449 mailbox_unlock_index(inbox, NULL);
3450 mailbox_close(&inbox);
3451 }
30563452 if (mailbox) mailbox_close(&mailbox);
30573453 if (caldavdb) caldav_close(caldavdb);
30583454 }
31263522 prop = icalcomponent_get_first_property(comp,
31273523 ICAL_DTSTAMP_PROPERTY);
31283524 icalcomponent_remove_property(comp, prop);
3525 icalproperty_free(prop);
31293526 prop =
31303527 icalproperty_new_dtstamp(icaltime_from_timet_with_zone(now, 0, utc));
31313528 icalcomponent_add_property(comp, prop);
31353532 alarm; alarm = next) {
31363533 next = icalcomponent_get_next_component(comp, ICAL_VALARM_COMPONENT);
31373534 icalcomponent_remove_component(comp, alarm);
3535 icalcomponent_free(alarm);
31383536 }
31393537
31403538 if (clean_org) {
3141 icalparameter *param;
3539 icalparameter *param, *next;
31423540
31433541 /* Grab the organizer */
31443542 prop = icalcomponent_get_first_property(comp,
31453543 ICAL_ORGANIZER_PROPERTY);
31463544
31473545 /* Remove CalDAV Scheduling parameters from organizer */
3148 for (param = icalproperty_get_first_parameter(prop,
3149 ICAL_IANA_PARAMETER);
3150 param;
3151 param = icalproperty_get_next_parameter(prop,
3152 ICAL_IANA_PARAMETER)) {
3546 for (param =
3547 icalproperty_get_first_parameter(prop, ICAL_IANA_PARAMETER);
3548 param; param = next) {
3549 next = icalproperty_get_next_parameter(prop, ICAL_IANA_PARAMETER);
3550
31533551 if (!strcmp(icalparameter_get_iana_name(param),
31543552 "SCHEDULE-AGENT")) {
31553553 icalproperty_remove_parameter_by_ref(prop, param);
32153613 * properly modified component to the attendee's iTIP request if necessary
32163614 */
32173615 static void process_attendees(icalcomponent *comp, unsigned ncomp,
3218 const char *organizer,
3616 const char *organizer, const char *att_update,
32193617 struct hash_table *att_table,
3220 icalcomponent *itip)
3221 {
3618 icalcomponent *itip, unsigned needs_action)
3619 {
3620 icalcomponent *copy;
32223621 icalproperty *prop;
3223 icalparameter *param;
3224
3225 clean_component(comp, 0);
3226
3227 /* Process each attendee */
3622 icalparameter *param, *next;
3623
3624 /* Strip SCHEDULE-STATUS from each attendee
3625 and optionally set PROPSTAT=NEEDS-ACTION */
32283626 for (prop = icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY);
32293627 prop;
32303628 prop = icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) {
32313629 const char *attendee = icalproperty_get_attendee(prop);
3630
3631 /* Don't modify attendee == organizer */
3632 if (!strcmp(attendee, organizer)) continue;
3633
3634 for (param =
3635 icalproperty_get_first_parameter(prop, ICAL_IANA_PARAMETER);
3636 param; param = next) {
3637 next = icalproperty_get_next_parameter(prop, ICAL_IANA_PARAMETER);
3638
3639 if (!strcmp(icalparameter_get_iana_name(param),
3640 "SCHEDULE-STATUS")) {
3641 icalproperty_remove_parameter_by_ref(prop, param);
3642 }
3643 }
3644
3645 if (needs_action) {
3646 param =
3647 icalproperty_get_first_parameter(prop, ICAL_PARTSTAT_PARAMETER);
3648 if (!param) {
3649 param = icalparameter_new(ICAL_PARTSTAT_PARAMETER);
3650 icalproperty_add_parameter(prop, param);
3651 }
3652 icalparameter_set_partstat(param, ICAL_PARTSTAT_NEEDSACTION);
3653 }
3654 }
3655
3656 /* Clone a working copy of the component */
3657 copy = icalcomponent_new_clone(comp);
3658
3659 clean_component(copy, 0);
3660
3661 /* Process each attendee */
3662 for (prop = icalcomponent_get_first_property(copy, ICAL_ATTENDEE_PROPERTY);
3663 prop;
3664 prop = icalcomponent_get_next_property(copy, ICAL_ATTENDEE_PROPERTY)) {
3665 const char *attendee = icalproperty_get_attendee(prop);
32323666 unsigned do_sched = 1;
32333667 icalparameter *force_send = NULL;
32343668
32353669 /* Don't schedule attendee == organizer */
32363670 if (!strcmp(attendee, organizer)) continue;
3671
3672 /* Don't send an update to the attendee that just sent a reply */
3673 if (att_update && !strcmp(attendee, att_update)) continue;
32373674
32383675 /* Check CalDAV Scheduling parameters */
32393676 for (param =
32403677 icalproperty_get_first_parameter(prop, ICAL_IANA_PARAMETER);
3241 param;
3242 param =
3243 icalproperty_get_next_parameter(prop, ICAL_IANA_PARAMETER)) {
3678 param; param = next) {
3679 next = icalproperty_get_next_parameter(prop, ICAL_IANA_PARAMETER);
3680
32443681 if (!strcmp(icalparameter_get_iana_name(param),
32453682 "SCHEDULE-AGENT")) {
32463683 do_sched =
32693706 }
32703707 hash_insert(attendee, sched_data, att_table);
32713708 }
3272 new_comp = icalcomponent_new_clone(comp);
3709 new_comp = icalcomponent_new_clone(copy);
32733710 icalcomponent_add_component(sched_data->itip, new_comp);
32743711 sched_data->comp_mask |= (1 << ncomp);
32753712
32793716
32803717 if (force_send) icalproperty_remove_parameter_by_ref(prop, force_send);
32813718 }
3719
3720 /* XXX We assume that the master component is always first */
3721 if (ncomp) {
3722 /* Handle attendees that are excluded from this recurrence */
3723 struct exclude_rock erock = { ncomp, copy };
3724
3725 hash_enumerate(att_table, sched_exclude, &erock);
3726 }
3727
3728 icalcomponent_free(copy);
32823729 }
32833730
32843731
32983745 {
32993746 struct comp_data *old_data = (struct comp_data *) data;
33003747 struct cancel_rock *crock = (struct cancel_rock *) rock;
3301 icalcomponent *copy;
3302
3303 /* Clone a working copy of the component */
3304 copy = icalcomponent_new_clone(old_data->comp);
33053748
33063749 /* Deleting the object -- set STATUS to CANCELLED for component */
3307 icalcomponent_set_status(copy, ICAL_STATUS_CANCELLED);
3308 // icalcomponent_set_sequence(copy, old_data->sequence+1);
3309
3310 process_attendees(copy, 0, crock->organizer, crock->att_table, crock->itip);
3311
3312 icalcomponent_free(copy);
3750 icalcomponent_set_status(old_data->comp, ICAL_STATUS_CANCELLED);
3751 // icalcomponent_set_sequence(old_data->comp, old_data->sequence+1);
3752
3753 process_attendees(old_data->comp, 0, crock->organizer, NULL,
3754 crock->att_table, crock->itip, 0);
3755 }
3756
3757
3758 static unsigned propcmp(icalcomponent *oldical, icalcomponent *newical,
3759 icalproperty_kind kind)
3760 {
3761 icalproperty *oldprop, *newprop;
3762
3763 oldprop = icalcomponent_get_first_property(oldical, kind);
3764 newprop = icalcomponent_get_first_property(newical, kind);
3765
3766 if (!oldprop) {
3767 if (newprop) return 1;
3768 }
3769 else if (!newprop) return 1;
3770 else {
3771 /* XXX Do something smarter based on property type */
3772 const char *oldstr = icalproperty_get_value_as_string(oldprop);
3773 const char *newstr = icalproperty_get_value_as_string(newprop);
3774
3775 if (strcmp(oldstr, newstr)) return 1;
3776 }
3777
3778 return 0;
33133779 }
33143780
33153781
33163782 /* Create and deliver an organizer scheduling request */
33173783 static void sched_request(const char *organizer, struct sched_param *sparam,
3318 icalcomponent *oldical, icalcomponent *newical)
3784 icalcomponent *oldical, icalcomponent *newical,
3785 const char *att_update)
33193786 {
33203787 int r, rights;
33213788 struct mboxlist_entry mbentry;
33423809 method = ICAL_METHOD_REQUEST;
33433810 }
33443811
3345 /* Check ACL of auth'd user on userid's Scheduling Outbox */
3346 caldav_mboxname(SCHED_OUTBOX, sparam->userid, outboxname);
3347
3348 if ((r = mboxlist_lookup(outboxname, &mbentry, NULL))) {
3349 syslog(LOG_INFO, "mboxlist_lookup(%s) failed: %s",
3350 outboxname, error_message(r));
3351 mbentry.acl = NULL;
3352 }
3353
3354 rights =
3355 mbentry.acl ? cyrus_acl_myrights(httpd_authstate, mbentry.acl) : 0;
3356 if (!(rights & DACL_INVITE)) {
3357 /* DAV:need-privileges */
3358 sched_stat = SCHEDSTAT_NOPRIVS;
3359
3360 goto done;
3812 if (!att_update) {
3813 /* Check ACL of auth'd user on userid's Scheduling Outbox */
3814 caldav_mboxname(SCHED_OUTBOX, sparam->userid, outboxname);
3815
3816 if ((r = mboxlist_lookup(outboxname, &mbentry, NULL))) {
3817 syslog(LOG_INFO, "mboxlist_lookup(%s) failed: %s",
3818 outboxname, error_message(r));
3819 mbentry.acl = NULL;
3820 }
3821
3822 rights =
3823 mbentry.acl ? cyrus_acl_myrights(httpd_authstate, mbentry.acl) : 0;
3824 if (!(rights & DACL_INVITE)) {
3825 /* DAV:need-privileges */
3826 sched_stat = SCHEDSTAT_NOPRIVS;
3827
3828 goto done;
3829 }
33613830 }
33623831
33633832 /* Create a shell for our iTIP request objects */
34003869 comp = icalcomponent_get_first_real_component(oldical);
34013870 do {
34023871 old_data = xzmalloc(sizeof(struct comp_data));
3403
34043872 old_data->comp = comp;
34053873 old_data->sequence = icalcomponent_get_sequence(comp);
34063874
34233891
34243892 comp = icalcomponent_get_first_real_component(newical);
34253893 do {
3426 icalcomponent *copy;
3894 unsigned changed = 1, needs_action = 0;
34273895
34283896 prop = icalcomponent_get_first_property(comp,
34293897 ICAL_RECURRENCEID_PROPERTY);
34323900
34333901 old_data = hash_del(recurid, &comp_table);
34343902
3435 if (old_data &&
3436 old_data->sequence >= icalcomponent_get_sequence(comp)) {
3437 /* This component hasn't changed, skip it */
3438 /* XXX We probably need to compare EXDATEs */
3439 continue;
3440 }
3441
3442 /* Clone a working copy of the component */
3443 copy = icalcomponent_new_clone(comp);
3444
3445 /* Process all attendees in created/modified components */
3446 process_attendees(copy, ncomp, organizer, &att_table, req);
3447
3448 /* XXX We assume that the master component is always first */
3449 if (ncomp) {
3450 /* Handle attendees that are excluded from this recurrence */
3451 struct exclude_rock erock = { ncomp, copy };
3452
3453 hash_enumerate(&att_table, sched_exclude, &erock);
3454 }
3455
3456 icalcomponent_free(copy);
3457
3458 ncomp++;
3903 if (old_data) {
3904 /* Per RFC 6638, Section 3.2.8: We need to compare
3905 DTSTART, DTEND, DURATION, DUE, RRULE, RDATE, EXDATE */
3906 needs_action += propcmp(old_data->comp, comp,
3907 ICAL_DTSTART_PROPERTY);
3908 needs_action += propcmp(old_data->comp, comp,
3909 ICAL_DTEND_PROPERTY);
3910 needs_action += propcmp(old_data->comp, comp,
3911 ICAL_DURATION_PROPERTY);
3912 needs_action += propcmp(old_data->comp, comp,
3913 ICAL_DUE_PROPERTY);
3914 needs_action += propcmp(old_data->comp, comp,
3915 ICAL_RRULE_PROPERTY);
3916 needs_action += propcmp(old_data->comp, comp,
3917 ICAL_RDATE_PROPERTY);
3918 needs_action += propcmp(old_data->comp, comp,
3919 ICAL_EXDATE_PROPERTY);
3920
3921 if (old_data->sequence >= icalcomponent_get_sequence(comp)) {
3922 /* Make sure SEQUENCE is set properly */
3923 if (!needs_action) changed = 0;
3924
3925 icalcomponent_set_sequence(comp,
3926 old_data->sequence + changed);
3927 }
3928
3929 free(old_data);
3930 }
3931
3932 if (changed) {
3933 /* Process all attendees in created/modified components */
3934 process_attendees(comp, ncomp++, organizer, att_update,
3935 &att_table, req, needs_action);
3936 }
34593937
34603938 } while ((comp = icalcomponent_get_next_component(newical, kind)));
34613939 }
34663944
34673945 hash_enumerate(&comp_table, sched_cancel, &crock);
34683946 }
3469 free_hash_table(&comp_table, NULL);
3947 free_hash_table(&comp_table, free);
34703948
34713949 icalcomponent_free(req);
34723950
34823960
34833961 done:
34843962 if (newical) {
3963 unsigned ncomp = 0;
3964
34853965 /* Set SCHEDULE-STATUS for each attendee in organizer object */
34863966 comp = icalcomponent_get_first_real_component(newical);
3487
3488 for (prop =
3489 icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY);
3490 prop;
3491 prop =
3492 icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) {
3493 const char *attendee = icalproperty_get_attendee(prop);
3494 const char *stat = NULL;
3495
3496 /* Don't set status if attendee == organizer */
3497 if (!strcmp(attendee, organizer)) continue;
3498
3499 if (sched_stat) stat = sched_stat;
3500 else {
3501 struct sched_data *sched_data;
3502
3503 sched_data = hash_lookup(attendee, &att_table);
3504 if (sched_data) stat = sched_data->status;
3505 }
3506
3507 if (stat) {
3508 icalparameter *param = icalparameter_new(ICAL_IANA_PARAMETER);
3509 icalparameter_set_iana_name(param, "SCHEDULE-STATUS");
3510 icalparameter_set_iana_value(param, stat);
3511 icalproperty_add_parameter(prop, param);
3512 }
3513 }
3967 kind = icalcomponent_isa(comp);
3968
3969 do {
3970 for (prop =
3971 icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY);
3972 prop;
3973 prop =
3974 icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) {
3975 const char *attendee = icalproperty_get_attendee(prop);
3976 const char *stat = NULL;
3977
3978 /* Don't set status if attendee == organizer */
3979 if (!strcmp(attendee, organizer)) continue;
3980
3981 if (sched_stat) stat = sched_stat;
3982 else {
3983 struct sched_data *sched_data;
3984
3985 sched_data = hash_lookup(attendee, &att_table);
3986 if (sched_data && (sched_data->comp_mask & (1 << ncomp)))
3987 stat = sched_data->status;
3988 }
3989
3990 if (stat) {
3991 icalparameter *param;
3992 for (param =
3993 icalproperty_get_first_parameter(prop,
3994 ICAL_IANA_PARAMETER);
3995 param && strcmp(icalparameter_get_iana_name(param),
3996 "SCHEDULE-STATUS");
3997 param =
3998 icalproperty_get_next_parameter(prop,
3999 ICAL_IANA_PARAMETER));
4000 if (!param) {
4001 param = icalparameter_new(ICAL_IANA_PARAMETER);
4002 icalproperty_add_parameter(prop, param);
4003 icalparameter_set_iana_name(param, "SCHEDULE-STATUS");
4004 }
4005 icalparameter_set_iana_value(param, stat);
4006 }
4007 }
4008
4009 ncomp++;
4010 } while ((comp = icalcomponent_get_next_component(newical, kind)));
35144011 }
35154012
35164013 /* Cleanup */
35694066 else {
35704067 /* Some other attendee, remove it */
35714068 icalcomponent_remove_property(copy, prop);
4069 icalproperty_free(prop);
35724070 }
35734071 }
35744072
36084106 param =
36094107 icalproperty_get_first_parameter(myattendee,
36104108 ICAL_PARTSTAT_PARAMETER);
3611 if (param) {
3612 icalproperty_remove_parameter_by_ref(myattendee, param);
3613 }
3614 param = icalparameter_new(ICAL_PARTSTAT_PARAMETER);
3615 icalproperty_add_parameter(myattendee, param);
4109 if (!param) {
4110 param = icalparameter_new(ICAL_PARTSTAT_PARAMETER);
4111 icalproperty_add_parameter(myattendee, param);
4112 }
36164113 icalparameter_set_partstat(param, ICAL_PARTSTAT_DECLINED);
36174114
36184115 clean_component(old_data->comp, 1);
37484245
37494246 /* Process each component of new object */
37504247 if (newical) {
4248 unsigned ncomp = 0;
4249
37514250 comp = icalcomponent_get_first_real_component(newical);
37524251 do {
37534252 icalcomponent *copy;
37824281 clean_component(copy, 1);
37834282
37844283 icalcomponent_add_component(sched_data->itip, copy);
4284 sched_data->comp_mask |= (1 << ncomp);
37854285 }
37864286 else icalcomponent_free(copy);
37874287
4288 ncomp++;
37884289 } while ((comp = icalcomponent_get_next_component(newical, kind)));
37894290 }
37904291
38054306 }
38064307
38074308 if (newical) {
3808 /* XXX Do we do this for ALL components, just the first one,
3809 or just the updated one(s)? */
4309 unsigned ncomp = 0;
4310
38104311 /* Set SCHEDULE-STATUS for organizer in attendee object */
38114312 comp = icalcomponent_get_first_real_component(newical);
3812 prop = icalcomponent_get_first_property(comp,
3813 ICAL_ORGANIZER_PROPERTY);
3814 param = icalparameter_new(ICAL_IANA_PARAMETER);
3815 icalparameter_set_iana_name(param, "SCHEDULE-STATUS");
3816 icalparameter_set_iana_value(param, sched_data->status);
3817 icalproperty_add_parameter(prop, param);
4313 do {
4314 if (sched_data->comp_mask & (1 << ncomp)) {
4315 prop =
4316 icalcomponent_get_first_property(comp,
4317 ICAL_ORGANIZER_PROPERTY);
4318 param = icalparameter_new(ICAL_IANA_PARAMETER);
4319 icalparameter_set_iana_name(param, "SCHEDULE-STATUS");
4320 icalparameter_set_iana_value(param, sched_data->status);
4321 icalproperty_add_parameter(prop, param);
4322 }
4323
4324 ncomp++;
4325 } while ((comp = icalcomponent_get_next_component(newical, kind)));
38184326 }
38194327 }
38204328
212212 char ident[MAX_MAILBOX_NAME];
213213 struct buf acl = BUF_INITIALIZER;
214214
215 if (config_mupdate_server && !config_getstring(IMAPOPT_PROXYSERVERS)) {
216 /* proxy-only server - won't have DAV databases */
217 return;
218 }
219 else if (httpd_userisadmin ||
220 global_authisa(httpd_authstate, IMAPOPT_PROXYSERVERS)) {
215 if (httpd_userisadmin ||
216 global_authisa(httpd_authstate, IMAPOPT_PROXYSERVERS)) {
221217 /* admin or proxy from frontend - won't have DAV database */
222218 return;
219 }
220 else if (config_mupdate_server && !config_getstring(IMAPOPT_PROXYSERVERS)) {
221 /* proxy-only server - won't have DAV databases */
222 }
223 else {
224 /* Open CardDAV DB for 'userid' */
225 my_carddav_reset();
226 auth_carddavdb = carddav_open(userid, CARDDAV_CREATE);
227 if (!auth_carddavdb) fatal("Unable to open CardDAV DB", EC_IOERR);
223228 }
224229
225230 /* Auto-provision an addressbook for 'userid' */
236241 config_getstring(IMAPOPT_ADDRESSBOOKPREFIX));
237242 r = mboxlist_lookup(mailboxname, &mbentry, NULL);
238243 if (r == IMAP_MAILBOX_NONEXISTENT) {
239 r = mboxlist_createmailboxcheck(mailboxname, 0, NULL, 0,
240 userid, httpd_authstate, NULL,
241 &partition, 0);
244 if (config_mupdate_server) {
245 /* Find location of INBOX */
246 char inboxname[MAX_MAILBOX_BUFFER];
247
248 r = (*httpd_namespace.mboxname_tointernal)(&httpd_namespace,
249 "INBOX",
250 userid, inboxname);
251 if (!r) {
252 char *server;
253
254 r = http_mlookup(inboxname, &server, NULL, NULL);
255 if (!r && server) {
256 proxy_findserver(server, &http_protocol, proxy_userid,
257 &backend_cached, NULL, NULL, httpd_in);
258
259 return;
260 }
261 }
262 }
263
264 /* Create locally */
265 if (!r) r = mboxlist_createmailboxcheck(mailboxname, 0, NULL, 0,
266 userid, httpd_authstate, NULL,
267 &partition, 0);
242268 if (!r) {
243269 buf_reset(&acl);
244270 cyrus_acl_masktostr(ACL_ALL, rights);
276302
277303 if (partition) free(partition);
278304 buf_free(&acl);
279
280 /* Open CardDAV DB for 'userid' */
281 my_carddav_reset();
282 auth_carddavdb = carddav_open(userid, CARDDAV_CREATE);
283 if (!auth_carddavdb) fatal("Unable to open CardDAV DB", EC_IOERR);
284305 }
285306
286307
363384 p += len;
364385
365386 if (*p) {
366 *errstr = "Too many segments in request target path";
367 return HTTP_FORBIDDEN;
387 // *errstr = "Too many segments in request target path";
388 return HTTP_NOT_FOUND;
368389 }
369390
370391 done:
471492 VObject *vcard = NULL;
472493
473494 /* Parse and validate the vCard data */
474 vcard = Parse_MIME(buf_cstring(&txn->req_body), buf_len(&txn->req_body));
495 vcard = Parse_MIME(buf_cstring(&txn->req_body.payload),
496 buf_len(&txn->req_body.payload));
475497 if (!vcard || strcmp(vObjectName(vcard), "VCARD")) {
476498 txn->error.precond = CARDDAV_VALID_DATA;
477499 ret = HTTP_FORBIDDEN;
650672
651673 /* Check for existing vCard UID */
652674 carddav_lookup_uid(carddavdb, uid, 0, &cdata);
653 if (cdata->dav.mailbox && !strcmp(cdata->dav.mailbox, mailbox->name) &&
675 if (!(flags & NO_DUP_CHECK) &&
676 cdata->dav.mailbox && !strcmp(cdata->dav.mailbox, mailbox->name) &&
654677 strcmp(cdata->dav.resource, resource)) {
655678 /* CARDDAV:no-uid-conflict */
656679 char *owner = mboxname_to_userid(cdata->dav.mailbox);
687710
688711 fprintf(f, "Content-Type: text/vcard; charset=utf-8\r\n");
689712
690 fprintf(f, "Content-Length: %u\r\n", buf_len(&txn->req_body));
713 fprintf(f, "Content-Length: %u\r\n", buf_len(&txn->req_body.payload));
691714 fprintf(f, "Content-Disposition: inline; filename=\"%s\"\r\n", resource);
692715
693716 /* XXX Check domain of data and use appropriate CTE */
696719 fprintf(f, "\r\n");
697720
698721 /* Write the vCard data to the file */
699 fprintf(f, "%s", buf_cstring(&txn->req_body));
722 fprintf(f, "%s", buf_cstring(&txn->req_body.payload));
700723 size = ftell(f);
701724
702725 fclose(f);
793816 }
794817
795818 if (!r) {
819 struct resp_body_t *resp_body = &txn->resp_body;
820
796821 /* Create mapping entry from resource name to UID */
797822 cdata->dav.mailbox = mailbox->name;
798823 cdata->dav.resource = resource;
807832 /* XXX check for errors, if this fails, backout changes */
808833
809834 /* Tell client about the new resource */
810 txn->resp_body.etag = message_guid_encode(&newrecord.guid);
835 resp_body->lastmod = newrecord.internaldate;
836 resp_body->etag = message_guid_encode(&newrecord.guid);
811837
812838 if (flags & PREFER_REP) {
813 struct resp_body_t *resp_body = &txn->resp_body;
814
839 resp_body->type = "text/vcard; charset=utf-8";
815840 resp_body->loc = txn->req_tgt.path;
816 resp_body->type = "text/vcard; charset=utf-8";
817 resp_body->len = buf_len(&txn->req_body);
818
819 /* vCard data in response should not be transformed */
820 txn->flags.cc |= CC_NOTRANSFORM;
841 resp_body->len = buf_len(&txn->req_body.payload);
842
843 /* Fill in Expires and Cache-Control */
844 resp_body->maxage = 3600; /* 1 hr */
845 txn->flags.cc = CC_MAXAGE
846 | CC_REVALIDATE /* don't use stale data */
847 | CC_NOTRANSFORM; /* don't alter vCard data */
821848
822849 write_body(ret, txn,
823 buf_cstring(&txn->req_body), resp_body->len);
850 buf_cstring(&txn->req_body.payload),
851 buf_len(&txn->req_body.payload));
824852 ret = 0;
825853 }
826854 }
362362 else return HTTP_NOT_FOUND; /* need to specify a userid */
363363
364364 if (*p) {
365 *errstr = "Too many segments in request target path";
366 return HTTP_FORBIDDEN;
365 // *errstr = "Too many segments in request target path";
366 return HTTP_NOT_FOUND;
367367 }
368368
369369 return 0;
840840 struct propstat propstat[],
841841 void *rock __attribute__((unused)))
842842 {
843 uint32_t len = 0;
844
845 if (!fctx->record) return HTTP_NOT_FOUND;
846
847 len = fctx->record->size - fctx->record->header_size;
848
849843 buf_reset(&fctx->buf);
850 buf_printf(&fctx->buf, "%u", len);
844
845 if (fctx->record) {
846 buf_printf(&fctx->buf, "%u",
847 fctx->record->size - fctx->record->header_size);
848 }
849
851850 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
852851 name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
853852
891890 struct propstat propstat[],
892891 void *rock __attribute__((unused)))
893892 {
894 if (!fctx->record) return HTTP_NOT_FOUND;
893 if (!fctx->mailbox ||
894 (fctx->req_tgt->resource && !fctx->record)) return HTTP_NOT_FOUND;
895895
896896 buf_ensure(&fctx->buf, 30);
897897 httpdate_gen(fctx->buf.s, fctx->buf.alloc,
898 fctx->record->internaldate);
898 fctx->record ? fctx->record->internaldate :
899 fctx->mailbox->index_mtime);
899900
900901 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
901902 name, ns, BAD_CAST fctx->buf.s, 0);
18091810 /* Make sure we have a valid component type */
18101811 for (comp = cal_comps;
18111812 comp->name && xmlStrcmp(name, BAD_CAST comp->name); comp++);
1813 xmlFree(name);
1814
18121815 if (comp->name) types |= comp->type; /* found match in our list */
18131816 else break; /* no match - invalid type */
18141817 }
22802283 propfind_fromdb, proppatch_todb, NULL },
22812284 { "getcontentlanguage", NS_DAV, PROP_ALLPROP | PROP_RESOURCE,
22822285 propfind_fromhdr, NULL, "Content-Language" },
2283 { "getcontentlength", NS_DAV, PROP_ALLPROP | PROP_RESOURCE,
2286 { "getcontentlength", NS_DAV,
2287 PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
22842288 propfind_getlength, NULL, NULL },
22852289 { "getcontenttype", NS_DAV,
22862290 PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
22882292 { "getetag", NS_DAV, PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
22892293 propfind_getetag, NULL, NULL },
22902294 { "getlastmodified", NS_DAV,
2291 PROP_ALLPROP | /*PROP_COLLECTION |*/ PROP_RESOURCE,
2295 PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
22922296 propfind_getlastmod, NULL, NULL },
22932297 { "lockdiscovery", NS_DAV, PROP_ALLPROP | PROP_RESOURCE,
22942298 propfind_lockdisc, NULL, NULL },
27162720 *root = NULL;
27172721
27182722 /* Read body */
2719 txn->flags.body |= BODY_DECODE;
2720 r = read_body(httpd_in, txn->req_hdrs, &txn->req_body,
2721 &txn->flags.body, &txn->error.desc);
2723 txn->req_body.flags |= BODY_DECODE;
2724 r = read_body(httpd_in, txn->req_hdrs, &txn->req_body, &txn->error.desc);
27222725 if (r) {
27232726 txn->flags.conn = CONN_CLOSE;
27242727 return r;
27252728 }
27262729
2727 if (!buf_len(&txn->req_body)) return 0;
2730 if (!buf_len(&txn->req_body.payload)) return 0;
27282731
27292732 /* Check Content-Type */
27302733 if (!(hdr = spool_getheader(txn->req_hdrs, "Content-Type")) ||
27372740 /* Parse the XML request */
27382741 ctxt = xmlNewParserCtxt();
27392742 if (ctxt) {
2740 doc = xmlCtxtReadMemory(ctxt, buf_cstring(&txn->req_body),
2741 buf_len(&txn->req_body), NULL, NULL,
2743 doc = xmlCtxtReadMemory(ctxt, buf_cstring(&txn->req_body.payload),
2744 buf_len(&txn->req_body.payload), NULL, NULL,
27422745 XML_PARSE_NOWARNING);
27432746 xmlFreeParserCtxt(ctxt);
27442747 }
29382941 uri->path[plen] == '/') {
29392942 memset(&tgt, 0, sizeof(struct request_target_t));
29402943 tgt.namespace = URL_NS_PRINCIPAL;
2941 r = aparams->parse_path(uri->path, &tgt, &errstr);
2944 r = prin_parse_path(uri->path, &tgt, &errstr);
29422945 if (!r && tgt.user) userid = tgt.user;
29432946 }
29442947 if (uri) xmlFreeURI(uri);
31243127 }
31253128 xmlFreeURI(dest_uri);
31263129
3127 if (r) return r;
3130 if (r) return HTTP_FORBIDDEN;
31283131
31293132 /* We don't yet handle COPY/MOVE on collections */
31303133 if (!dest_tgt.resource) return HTTP_NOT_ALLOWED;
33033306 }
33043307
33053308 if (get_preferences(txn) & PREFER_REP) flags |= PREFER_REP;
3309 if ((txn->meth == METH_MOVE) && (dest_mbox == src_mbox))
3310 flags |= NO_DUP_CHECK;
33063311
33073312 /* Parse, validate, and store the resource */
33083313 ret = cparams->copy(txn, src_mbox, &src_rec, dest_mbox, dest_tgt.resource,
43984403 }
43994404 else if (!xmlStrcmp(cur->name, BAD_CAST "allprop")) {
44004405 fctx.mode = PROPFIND_ALL;
4401
4402 /* Check for 'include' element */
4403 for (cur = cur->next;
4404 cur && cur->type != XML_ELEMENT_NODE; cur = cur->next);
4405
4406 if (cur && !xmlStrcmp(cur->name, BAD_CAST "include")) {
4407 props = cur->children;
4408 }
44094406 }
44104407 else if (!xmlStrcmp(cur->name, BAD_CAST "propname")) {
44114408 fctx.mode = PROPFIND_NAME;
44184415 else {
44194416 ret = HTTP_BAD_REQUEST;
44204417 goto done;
4418 }
4419
4420 /* Check for extra elements */
4421 for (cur = cur->next; cur; cur = cur->next) {
4422 if (cur->type == XML_ELEMENT_NODE) {
4423 if ((fctx.mode == PROPFIND_ALL) && !props &&
4424 /* Check for 'include' element */
4425 !xmlStrcmp(cur->name, BAD_CAST "include")) {
4426 props = cur->children;
4427 }
4428 else {
4429 ret = HTTP_BAD_REQUEST;
4430 goto done;
4431 }
4432 }
44214433 }
44224434 }
44234435
47384750 /* Parse the path */
47394751 if ((r = pparams->parse_path(txn->req_uri->path,
47404752 &txn->req_tgt, &txn->error.desc))) {
4741 return r;
4753 return HTTP_FORBIDDEN;
47424754 }
47434755
47444756 /* Make sure method is allowed (only allowed on resources) */
48614873 }
48624874
48634875 /* Read body */
4864 txn->flags.body |= BODY_DECODE;
4865 ret = read_body(httpd_in, txn->req_hdrs, &txn->req_body,
4866 &txn->flags.body, &txn->error.desc);
4876 txn->req_body.flags |= BODY_DECODE;
4877 ret = read_body(httpd_in, txn->req_hdrs, &txn->req_body, &txn->error.desc);
48674878 if (ret) {
48684879 txn->flags.conn = CONN_CLOSE;
48694880 goto done;
48704881 }
48714882
48724883 /* Make sure we have a body */
4873 size = buf_len(&txn->req_body);
4884 size = buf_len(&txn->req_body.payload);
48744885 if (!size) {
48754886 txn->error.desc = "Missing request body\r\n";
48764887 ret = HTTP_BAD_REQUEST;
48784889 }
48794890
48804891 /* Check if we can append a new message to mailbox */
4881 if ((r = append_check(txn->req_tgt.mboxname, httpd_authstate, ACL_INSERT, size))) {
4892 if ((r = append_check(txn->req_tgt.mboxname,
4893 httpd_authstate, ACL_INSERT, size))) {
48824894 txn->error.desc = error_message(r);
48834895 ret = HTTP_SERVER_ERROR;
48844896 goto done;
51135125 int ret = 0, r;
51145126 const char **hdr;
51155127 unsigned depth = 0;
5116 xmlNodePtr inroot = NULL, outroot = NULL, cur, props = NULL;
5128 xmlNodePtr inroot = NULL, outroot = NULL, cur, prop = NULL, props = NULL;
51175129 const struct report_type_t *report = NULL;
51185130 xmlNsPtr ns[NUM_NAMESPACE];
51195131 struct hash_table ns_table = { 0, NULL, NULL };
52305242 if (cur->type == XML_ELEMENT_NODE) {
52315243 if (!xmlStrcmp(cur->name, BAD_CAST "allprop")) {
52325244 fctx.mode = PROPFIND_ALL;
5245 prop = cur;
52335246 break;
52345247 }
52355248 else if (!xmlStrcmp(cur->name, BAD_CAST "propname")) {
52365249 fctx.mode = PROPFIND_NAME;
52375250 fctx.prefer = PREFER_MIN; /* Don't want 404 (Not Found) */
5251 prop = cur;
52385252 break;
52395253 }
52405254 else if (!xmlStrcmp(cur->name, BAD_CAST "prop")) {
52415255 fctx.mode = PROPFIND_PROP;
5256 prop = cur;
52425257 props = cur->children;
52435258 break;
52445259 }
52455260 }
52465261 }
52475262
5248 if (!props && (report->flags & REPORT_NEED_PROPS)) {
5263 if (!prop && (report->flags & REPORT_NEED_PROPS)) {
52495264 txn->error.desc = "Missing <prop> element in REPORT\r\n";
52505265 ret = HTTP_BAD_REQUEST;
52515266 goto done;
235235 PREFER_REP = (1<<1),
236236 PREFER_NOROOT = (1<<2)
237237 };
238
239 #define NO_DUP_CHECK (1<<7)
240
238241
239242 /* Function to lookup DAV 'resource' in 'mailbox', with optional 'lock',
240243 * placing the record in 'data'
138138 };
139139
140140
141 /* Calculate compile time of this file for use as Etag for capabilities */
142 static void calc_compile_time()
143 {
144 struct tm tm;
145 char month[4];
146 const char *monthname[] = {
147 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
148 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
149 };
150
151 memset(&tm, 0, sizeof(struct tm));
152 tm.tm_isdst = -1;
153 sscanf(__TIME__, "%02d:%02d:%02d", &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
154 sscanf(__DATE__, "%s %2d %4d", month, &tm.tm_mday, &tm.tm_year);
155 tm.tm_year -= 1900;
156 for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
157 if (!strcmp(month, monthname[tm.tm_mon])) break;
158 }
159
160 compile_time = mktime(&tm);
161 }
162
163
164141 /* iSchedule Receiver Capabilities */
165142 static int meth_get_isched(struct transaction_t *txn,
166143 void *params __attribute__((unused)))
326303 }
327304
328305 /* Read body */
329 txn->flags.body |= BODY_DECODE;
330 r = read_body(httpd_in, txn->req_hdrs, &txn->req_body,
331 &txn->flags.body, &txn->error.desc);
306 txn->req_body.flags |= BODY_DECODE;
307 r = read_body(httpd_in, txn->req_hdrs, &txn->req_body, &txn->error.desc);
332308 if (r) {
333309 txn->flags.conn = CONN_CLOSE;
334310 return r;
335311 }
336312
337313 /* Make sure we have a body */
338 if (!buf_len(&txn->req_body)) {
314 if (!buf_len(&txn->req_body.payload)) {
339315 txn->error.desc = "Missing request body\r\n";
340316 return HTTP_BAD_REQUEST;
341317 }
360336 }
361337
362338 /* Parse the iCal data for important properties */
363 ical = icalparser_parse_string(buf_cstring(&txn->req_body));
339 ical = icalparser_parse_string(buf_cstring(&txn->req_body.payload));
364340 if (!ical || !icalrestriction_check(ical)) {
365341 txn->error.precond = ISCHED_INVALID_DATA;
366342 return HTTP_BAD_REQUEST;
480456 icalcomponent *comp;
481457 icalcomponent_kind kind;
482458 icalproperty *prop;
483 unsigned code, close;
459 unsigned code;
484460 struct transaction_t txn;
485461
486462 *xml = NULL;
610586 prot_write(be->out, body, bodylen);
611587
612588 /* Read response (req_hdr and req_body are actually the response) */
613 r = http_read_response(be, METH_POST, BODY_DECODE, &code, &close, NULL,
589 txn.req_body.flags = BODY_DECODE;
590 r = http_read_response(be, METH_POST, &code, NULL,
614591 &txn.req_hdrs, &txn.req_body, &txn.error.desc);
615592 if (!r) {
616593 switch (code) {
631608 }
632609
633610 if (txn.req_hdrs) spool_free_hdrcache(txn.req_hdrs);
634 buf_free(&txn.req_body);
611 buf_free(&txn.req_body.payload);
635612
636613 return r;
637614 }
771748 dkim_cachehdr(NULL, NULL, dkim); /* Force canon of last header */
772749 stat = dkim_eoh(dkim);
773750 if (stat == DKIM_STAT_OK) {
774 stat = dkim_body(dkim, (u_char *) buf_cstring(&txn->req_body),
775 buf_len(&txn->req_body));
751 stat = dkim_body(dkim, (u_char *) buf_cstring(&txn->req_body.payload),
752 buf_len(&txn->req_body.payload));
776753 stat = dkim_eom(dkim, NULL);
777754 }
778755
836813 return;
837814 }
838815
839 calc_compile_time();
816 compile_time = calc_compile_time(__TIME__, __DATE__);
840817
841818 if (config_mupdate_server && config_getstring(IMAPOPT_PROXYSERVERS)) {
842819 /* If backend server, we require ISCHEDULE (w/o DKIM) */
125125 static int login(struct backend *s, const char *userid,
126126 sasl_callback_t *cb, const char **status)
127127 {
128 int r = 0, local_cb = 0;
128 int r = 0;
129129 socklen_t addrsize;
130130 struct sockaddr_storage saddr_l, saddr_r;
131131 char remoteip[60], localip[60];
156156
157157 /* Create callbacks, if necessary */
158158 if (!cb) {
159 local_cb = 1;
160159 buf_setmap(&buf, s->hostname, strcspn(s->hostname, "."));
161160 buf_appendcstr(&buf, "_password");
162161 pass = config_getoverflowstring(buf_cstring(&buf), NULL);
165164 config_getstring(IMAPOPT_PROXY_AUTHNAME),
166165 config_getstring(IMAPOPT_PROXY_REALM),
167166 pass);
168 }
169
170 /* Require proxying if we have an "interesting" userid (authzid) */
167 s->sasl_cb = cb;
168 }
169
170 /* Create SASL context */
171171 r = sasl_client_new(s->prot->sasl_service, s->hostname,
172 localip, remoteip, cb,
173 /* (userid && *userid ? SASL_NEED_PROXY : 0) | */
174 SASL_USAGE_FLAGS, &s->saslconn);
172 localip, remoteip, cb, SASL_USAGE_FLAGS, &s->saslconn);
175173 if (r != SASL_OK) goto done;
176174
177175 r = sasl_setprop(s->saslconn, SASL_SEC_PROPS, &secprops);
189187 unsigned code;
190188 const char **hdr, *errstr, *serverin;
191189 char base64[BASE64_BUF_SIZE+1];
192 unsigned int serverinlen, non_persist;
190 unsigned int serverinlen;
191 struct body_t resp_body;
193192 #ifdef SASL_HTTP_REQUEST
194193 sasl_http_request_t httpreq = { "OPTIONS", /* Method */
195194 "*", /* URI */
232231
233232 /* Read response(s) from backend until final response or error */
234233 do {
235 r = http_read_response(s, METH_OPTIONS, 0, &code, &non_persist,
236 NULL, &hdrs, NULL, &errstr);
234 resp_body.flags = BODY_DISCARD;
235 r = http_read_response(s, METH_OPTIONS, &code, NULL,
236 &hdrs, &resp_body, &errstr);
237237 if (r) {
238238 if (status) *status = errstr;
239239 break;
302302 for (scheme = auth_schemes; scheme->name; scheme++) {
303303 if (!strncmp(scheme->name, hdr[i], len) &&
304304 !((scheme->flags & AUTH_NEED_PERSIST) &&
305 non_persist)) {
305 (resp_body.flags & BODY_CLOSE))) {
306306 /* Tag the scheme as available */
307307 avail_auth_schemes |= (1 << scheme->idx);
308308
337337
338338 #ifdef SASL_HTTP_REQUEST
339339 /* Set HTTP request as specified above (REQUIRED) */
340 httpreq.non_persist = non_persist;
340 httpreq.non_persist = (resp_body.flags & BODY_CLOSE);
341341 sasl_setprop(s->saslconn, SASL_HTTP_REQUEST, &httpreq);
342342 #endif
343343
414414 } while (need_tls || clientout);
415415
416416 done:
417 if (local_cb) free_callbacks(cb);
418417 if (hdrs) spool_free_hdrcache(hdrs);
419418
420419 if (r && status && !*status) *status = sasl_errstring(r, NULL, NULL);
425424
426425 static int ping(struct backend *s, const char *userid)
427426 {
428 return login(s, userid, NULL, NULL);
427 unsigned code = 0;
428 const char *errstr;
429 hdrcache_t resp_hdrs = NULL;
430 struct body_t resp_body;
431
432 /* Send Authorization request to server */
433 prot_puts(s->out, "OPTIONS * HTTP/1.1\r\n");
434 prot_printf(s->out, "Host: %s\r\n", s->hostname);
435 prot_printf(s->out, "User-Agent: %s\r\n", buf_cstring(&serverinfo));
436 if (userid) prot_printf(s->out, "Authorize-As: %s\r\n", userid);
437 prot_puts(s->out, "\r\n");
438 prot_flush(s->out);
439
440 /* Read response(s) from backend until final response or error */
441 do {
442 resp_body.flags = BODY_DISCARD;
443 if (http_read_response(s, METH_OPTIONS, &code, NULL,
444 &resp_hdrs, &resp_body, &errstr)) {
445 break;
446 }
447 } while (code < 200);
448
449 if (resp_hdrs) spool_free_hdrcache(resp_hdrs);
450
451 return (code != 200);
429452 }
430453
431454
471494 }
472495
473496
497 /* Fetch protocol and host used for request from headers */
498 void http_proto_host(hdrcache_t req_hdrs, const char **proto, const char **host)
499 {
500 const char **fwd;
501
502 if (config_mupdate_server && config_getstring(IMAPOPT_PROXYSERVERS) &&
503 (fwd = spool_getheader(req_hdrs, "Forwarded"))) {
504 /* Proxied request - parse last Forwarded header for proto and host */
505 /* XXX This is destructive of the header but we don't care
506 * and more importantly, we need the tokens available after tok_fini()
507 */
508 tok_t tok;
509 char *token;
510
511 while (fwd[1]) ++fwd; /* Skip to last Forwarded header */
512
513 tok_initm(&tok, (char *) fwd[0], ";", 0);
514 while ((token = tok_next(&tok))) {
515 if (proto && !strncmp(token, "proto=", 6)) *proto = token+6;
516 else if (host && !strncmp(token, "host=", 5)) *host = token+5;
517 }
518 tok_fini(&tok);
519 }
520 else {
521 /* Use our protocol and host */
522 if (proto) *proto = https ? "https" : "http";
523 if (host) *host = *spool_getheader(req_hdrs, "Host");
524 }
525 }
526
474527 /* Construct and write Via header to protstream. */
475528 static void write_forwarding_hdrs(struct protstream *pout, hdrcache_t hdrs,
476529 const char *version, const char *proto)
543596
544597
545598 /* Read a response from backend */
546 int http_read_response(struct backend *be, unsigned meth, unsigned decode,
547 unsigned *code, unsigned *close, const char **statline,
548 hdrcache_t *hdrs, struct buf *body, const char **errstr)
599 int http_read_response(struct backend *be, unsigned meth, unsigned *code,
600 const char **statline, hdrcache_t *hdrs,
601 struct body_t *body, const char **errstr)
549602 {
550603 static char statbuf[2048];
551604 const char **conn;
553606
554607 if (statline) *statline = statbuf;
555608 *errstr = NULL;
556 *close = 0;
557609 *code = HTTP_BAD_GATEWAY;
558610
559611 if (*hdrs) spool_free_hdrcache(*hdrs);
573625 if (*code < 200) return 0;
574626
575627 /* Final response */
576 if (body) buf_reset(body);
628 if (!(body->flags & BODY_DISCARD)) buf_reset(&body->payload);
577629
578630 /* Check connection persistence */
579 *close = !strncmp(statbuf, "HTTP/1.0 ", 9);
631 if (!strncmp(statbuf, "HTTP/1.0 ", 9)) body->flags |= BODY_CLOSE;
580632 for (conn = spool_getheader(*hdrs, "Connection"); conn && *conn; conn++) {
581633 tok_t tok =
582634 TOK_INITIALIZER(*conn, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
583635 char *token;
584636
585637 while ((token = tok_next(&tok))) {
586 if (!strcasecmp(token, "keep-alive")) *close = 0;
587 else if (!strcasecmp(token, "close")) *close = 1;
638 if (!strcasecmp(token, "keep-alive")) body->flags &= ~BODY_CLOSE;
639 else if (!strcasecmp(token, "close")) body->flags |= BODY_CLOSE;
588640 }
589641 tok_fini(&tok);
590642 }
599651 if (meth == METH_HEAD) break;
600652
601653 else {
602 unsigned char flags = BODY_RESPONSE;
603
604 if (decode) flags |= BODY_DECODE;
605 if (*close) flags |= BODY_CLOSE;
606
607 if (read_body(be->in, *hdrs, body, &flags, errstr)) {
654 body->flags |= BODY_RESPONSE;
655 body->framing = FRAMING_UNKNOWN;
656
657 if (read_body(be->in, *hdrs, body, errstr)) {
608658 return HTTP_BAD_GATEWAY;
609659 }
610660 }
680730 {
681731 int r = 0, sent_body = 0;
682732 xmlChar *uri;
683 unsigned code, close;
733 unsigned code;
684734 const char *statline;
685735 hdrcache_t resp_hdrs = NULL;
686 struct buf *resp_body = &txn->resp_body.payload;
736 struct body_t resp_body;
687737
688738 /*
689739 * Send client request to backend:
716766 prot_flush(be->out);
717767
718768 /* Read response(s) from backend until final response or error */
769 resp_body.flags = 0;
770 resp_body.payload = txn->resp_body.payload;
771
719772 do {
720 r = http_read_response(be, txn->meth, 0, &code, &close, &statline,
721 &resp_hdrs, resp_body, &txn->error.desc);
773 r = http_read_response(be, txn->meth, &code, &statline,
774 &resp_hdrs, &resp_body, &txn->error.desc);
722775 if (r) break;
723776
724777 if ((code == 100) /* Continue */ && !sent_body++) {
726779
727780 /* Read body from client */
728781 r = read_body(httpd_in, txn->req_hdrs, &txn->req_body,
729 &txn->flags.body, &txn->error.desc);
782 &txn->error.desc);
730783 if (r) {
731784 /* Couldn't get the body and can't finish request */
732785 txn->flags.conn = CONN_CLOSE;
734787 }
735788
736789 /* Send single-chunk body to backend to complete the request */
737 if ((len = buf_len(&txn->req_body))) {
790 if ((len = buf_len(&txn->req_body.payload))) {
738791 prot_printf(be->out, "%x\r\n", len);
739 prot_putbuf(be->out, &txn->req_body);
792 prot_putbuf(be->out, &txn->req_body.payload);
740793 prot_puts(be->out, "\r\n");
741794 }
742795 prot_puts(be->out, "0\r\n\r\n");
744797 }
745798 } while (code < 200);
746799
800 txn->resp_body.payload = resp_body.payload;
801
747802 if (!r) {
748803 /* Send response to client */
749 send_response(httpd_out, statline, resp_hdrs, resp_body, &txn->flags);
750 }
751
752 if (r || close) proxy_downserver(be);
804 send_response(httpd_out, statline, resp_hdrs,
805 &resp_body.payload, &txn->flags);
806 }
807
808 if (r || (resp_body.flags & BODY_CLOSE)) proxy_downserver(be);
753809
754810 if (resp_hdrs) spool_free_hdrcache(resp_hdrs);
755811
769825 struct transaction_t *txn)
770826 {
771827 int r = 0;
772 unsigned code, close;
828 unsigned code;
773829 char *etag = NULL, *lastmod = NULL;;
774830 const char **hdr, *statline;
775831 hdrcache_t resp_hdrs = NULL;
776 struct buf *resp_body = &txn->resp_body.payload;
832 struct body_t resp_body;
777833
778834 /*
779835 * Send a GET request to source backend to fetch body:
794850 prot_flush(src_be->out);
795851
796852 /* Read response(s) from source backend until final response or error */
853 resp_body.flags = 0;
854 resp_body.payload = txn->resp_body.payload;
855
797856 do {
798 r = http_read_response(src_be, METH_GET, 0, &code, &close, &statline,
799 &resp_hdrs, resp_body, &txn->error.desc);
800 if (r || close) {
857 r = http_read_response(src_be, METH_GET, &code, &statline,
858 &resp_hdrs, &resp_body, &txn->error.desc);
859 if (r || (resp_body.flags & BODY_CLOSE)) {
801860 proxy_downserver(src_be);
802861 break;
803862 }
843902 prot_printf(dest_be->out, "Content-Encoding: %s\r\n", hdr[0]);
844903 if ((hdr = spool_getheader(resp_hdrs, "Content-Language")))
845904 prot_printf(dest_be->out, "Content-Language: %s\r\n", hdr[0]);
846 prot_printf(dest_be->out, "Content-Length: %u\r\n", buf_len(resp_body));
905 prot_printf(dest_be->out, "Content-Length: %u\r\n",
906 buf_len(&resp_body.payload));
847907 prot_puts(dest_be->out, "\r\n");
848908 prot_flush(dest_be->out);
849909
850910 /* Read response(s) from dest backend until final response or error */
911 resp_body.flags = 0;
912
851913 do {
852 r = http_read_response(dest_be, METH_PUT, 0,
853 &code, &close, &statline,
854 &resp_hdrs, resp_body, &txn->error.desc);
855 if (r || close) {
914 r = http_read_response(dest_be, METH_PUT, &code, &statline,
915 &resp_hdrs, &resp_body, &txn->error.desc);
916 if (r || (resp_body.flags & BODY_CLOSE)) {
856917 proxy_downserver(dest_be);
857918 break;
858919 }
859920
860921 if ((code == 100) /* Continue */ && !sent_body++) {
861922 /* Send body to dest backend to complete the PUT */
862 prot_putbuf(dest_be->out, resp_body);
923 prot_putbuf(dest_be->out, &resp_body.payload);
863924 prot_flush(dest_be->out);
864925 }
865926 } while (code < 200);
866927 }
928
929 txn->resp_body.payload = resp_body.payload;
867930
868931 if (!r) {
869932 /* Send response to client */
870 send_response(httpd_out, statline, resp_hdrs, resp_body, &txn->flags);
933 send_response(httpd_out, statline, resp_hdrs,
934 &resp_body.payload, &txn->flags);
871935
872936 if ((txn->meth == METH_MOVE) && (code < 300)) {
873937 /*
892956 prot_flush(src_be->out);
893957
894958 /* Read response(s) from source backend until final resp or error */
959 resp_body.flags = BODY_DISCARD;
960
895961 do {
896 if (http_read_response(src_be, METH_DELETE, 0,
897 &code, &close, NULL,
898 &resp_hdrs, NULL, &txn->error.desc)
899 || close) {
962 if (http_read_response(src_be, METH_DELETE, &code, NULL,
963 &resp_hdrs, &resp_body, &txn->error.desc)
964 || (resp_body.flags & BODY_CLOSE)) {
900965 proxy_downserver(src_be);
901966 break;
902967 }
5050
5151 extern int http_mlookup(const char *name,
5252 char **server, char **aclp, void *tid);
53 extern void http_proto_host(hdrcache_t req_hdrs,
54 const char **proto, const char **host);
5355 extern int http_pipe_req_resp(struct backend *be, struct transaction_t *txn);
5456 extern int http_proxy_copy(struct backend *src_be, struct backend *dest_be,
5557 struct transaction_t *txn);
56 extern int http_read_response(struct backend *be, unsigned meth,
57 unsigned decode, unsigned *code, unsigned *close,
58 extern int http_read_response(struct backend *be, unsigned meth, unsigned *code,
5859 const char **statline, hdrcache_t *hdrs,
59 struct buf *body, const char **errstr);
60 struct body_t *body, const char **errstr);
6061
6162 #endif /* _HTTP_PROXY_H */
130130 }
131131 };
132132
133 /* Calculate compile time of this file for use as ETag for capabilities */
134 static void calc_compile_time()
135 {
136 struct tm tm;
137 char month[4];
138 const char *monthname[] = {
139 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
140 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
141 };
142
143 memset(&tm, 0, sizeof(struct tm));
144 tm.tm_isdst = -1;
145 sscanf(__TIME__, "%02d:%02d:%02d", &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
146 sscanf(__DATE__, "%s %2d %4d", month, &tm.tm_mday, &tm.tm_year);
147 tm.tm_year -= 1900;
148 for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
149 if (!strcmp(month, monthname[tm.tm_mon])) break;
150 }
151
152 compile_time = mktime(&tm);
153 }
154
155133
156134 static void rss_init(struct buf *serverinfo __attribute__((unused)))
157135 {
159137
160138 if (!namespace_rss.enabled) return;
161139
162 calc_compile_time();
140 compile_time = calc_compile_time(__TIME__, __DATE__);
163141 }
164142
165143 /* Perform a GET/HEAD request */
492470 const char *template_file = config_getstring(IMAPOPT_RSS_FEEDLIST_TEMPLATE);
493471 const char *var = NULL, *template = NULL, *prefix, *suffix;
494472 unsigned long template_len = 0, prefix_len, suffix_len;
495 unsigned varlen = strlen(FEEDLIST_VAR);
473 size_t varlen = strlen(FEEDLIST_VAR);
496474 int fd = -1;
497475 struct message_guid guid;
498476 time_t lastmod;
506484 if (template_file) {
507485 /* See if template exists and contains feedlist variable */
508486 if (!stat(template_file, &sbuf) && S_ISREG(sbuf.st_mode) &&
509 sbuf.st_size >= varlen &&
487 (size_t) sbuf.st_size >= varlen &&
510488 (fd = open(template_file, O_RDONLY)) != -1) {
511489 const char *p;
512490 unsigned long len;
739717 /* List messages as an RSS feed */
740718 static int list_messages(struct transaction_t *txn, struct mailbox *mailbox)
741719 {
742 const char **fwd, *proto = NULL, *host = NULL;
720 const char *proto = NULL, *host = NULL;
743721 uint32_t url_len, recno, recentuid = 0;
744722 int max_age, max_items, max_len, nitems, precond;
745723 time_t age_mark = 0, lastmod;
829807 mboxname_hiersep_toexternal(&httpd_namespace, mboxname, 0);
830808
831809 /* Construct base URL */
832 if (config_mupdate_server && config_getstring(IMAPOPT_PROXYSERVERS) &&
833 (fwd = spool_getheader(txn->req_hdrs, "Forwarded"))) {
834 /* Proxied request - parse last Forwarded header for proto and host */
835 /* XXX This is destructive of the header but we don't care
836 * and more importantly, we need the tokens available after tok_fini()
837 */
838 tok_t tok;
839 char *token;
840
841 while (fwd[1]) ++fwd; /* Skip to last Forwarded header */
842
843 tok_initm(&tok, (char *) fwd[0], ";", 0);
844 while ((token = tok_next(&tok))) {
845 if (!strncmp(token, "proto=", 6)) proto = token+6;
846 else if (!strncmp(token, "host=", 5)) host = token+5;
847 }
848 tok_fini(&tok);
849 }
850 else {
851 /* Use our protocol and host */
852 proto = https ? "https" : "http";
853 host = *spool_getheader(txn->req_hdrs, "Host");
854 }
810 http_proto_host(txn->req_hdrs, &proto, &host);
855811 assert(!buf_len(url));
856812 buf_printf(url, "%s://%s%s", proto, host, txn->req_uri->path);
857813 url_len = buf_len(url);
9191 #include "rfc822date.h"
9292 #include "tok.h"
9393 #include "wildmat.h"
94 #include "md5.h"
9495
9596 #ifdef WITH_DAV
9697 #include "http_dav.h"
206207 static struct accept *parse_accept(const char **hdr);
207208 static int parse_ranges(const char *hdr, unsigned long len,
208209 struct range **ranges);
210 static int parse_framing(hdrcache_t hdrs, struct body_t *body,
211 const char **errstr);
212 static int proxy_authz(const char **authzid, struct transaction_t *txn);
213 static void auth_success(struct transaction_t *txn);
209214 static int http_auth(const char *creds, struct transaction_t *txn);
210215 static void keep_alive(int sig);
211216
934939 txn.req_uri = NULL;
935940 txn.auth_chal.param = NULL;
936941 txn.req_hdrs = NULL;
942 txn.req_body.flags = 0;
937943 txn.location = NULL;
938944 memset(&txn.error, 0, sizeof(struct error_t));
939945 memset(&txn.resp_body, 0, /* Don't zero the response payload buffer */
10871093
10881094 if (txn.meth == METH_UNKNOWN) ret = HTTP_NOT_IMPLEMENTED;
10891095
1090 /* Check for Expectations (HTTP/1.1+ only) */
1091 else if (!txn.flags.ver1_0 && (r = parse_expect(&txn))) ret = r;
1092
10931096 /* Parse request-target URI */
10941097 else if (!(txn.req_uri = parse_uri(txn.meth, req_line->uri, 1,
10951098 &txn.error.desc))) {
10961099 ret = HTTP_BAD_REQUEST;
1100 }
1101
1102 /* Check message framing */
1103 else if ((r = parse_framing(txn.req_hdrs, &txn.req_body,
1104 &txn.error.desc))) {
1105 ret = r;
1106 }
1107
1108 /* Check for Expectations */
1109 else if ((r = parse_expect(&txn))) {
1110 ret = r;
10971111 }
10981112
10991113 /* Check for mandatory Host header (HTTP/1.1+ only) */
11641178 meth_t = &namespace->methods[txn.meth];
11651179 if (!meth_t->proc) ret = HTTP_NOT_ALLOWED;
11661180
1167 /* Check if method doesn't expect a body */
1181 /* Check if method expects a body */
11681182 else if ((http_methods[txn.meth].flags & METH_NOBODY) &&
1169 (spool_getheader(txn.req_hdrs, "Transfer-Encoding") ||
1183 (txn.req_body.framing != FRAMING_LENGTH ||
11701184 /* XXX Will break if client sends just a last-chunk */
1171 ((hdr = spool_getheader(txn.req_hdrs, "Content-Length"))
1172 && strtoul(hdr[0], NULL, 10))))
1185 txn.req_body.len)) {
11731186 ret = HTTP_BAD_MEDIATYPE;
1187 }
11741188 } else {
11751189 /* XXX Should never get here */
11761190 ret = HTTP_SERVER_ERROR;
11831197 if (httpd_userid) {
11841198 /* Reauth - reinitialize */
11851199 syslog(LOG_DEBUG, "reauth - reinit");
1186 if (httpd_userid) {
1187 free(httpd_userid);
1188 httpd_userid = NULL;
1189 }
1190 if (httpd_authstate) {
1191 auth_freestate(httpd_authstate);
1192 httpd_authstate = NULL;
1193 }
11941200 reset_saslconn(&httpd_saslconn);
11951201 txn.auth_chal.scheme = NULL;
11961202 }
12101216 syslog(LOG_DEBUG, "client didn't complete auth - reinit");
12111217 reset_saslconn(&httpd_saslconn);
12121218 txn.auth_chal.scheme = NULL;
1219 }
1220
1221 /* Perform proxy authorization, if necessary */
1222 else if (saslprops.authid &&
1223 (hdr = spool_getheader(txn.req_hdrs, "Authorize-As")) &&
1224 *hdr[0]) {
1225 const char *authzid = hdr[0];
1226
1227 r = proxy_authz(&authzid, &txn);
1228 if (r) {
1229 /* Proxy authz failed - reinitialize */
1230 syslog(LOG_DEBUG, "proxy authz failed - reinit");
1231 reset_saslconn(&httpd_saslconn);
1232 txn.auth_chal.scheme = NULL;
1233 }
1234 else {
1235 httpd_userid = xstrdup(authzid);
1236 auth_success(&txn);
1237 }
12131238 }
12141239
12151240 /* Request authentication, if necessary */
13511376 /* Handle errors (success responses handled by method functions) */
13521377 if (ret) error_response(ret, &txn);
13531378
1379 /* Read and discard any unread request body */
1380 if (!(txn.flags.conn & CONN_CLOSE)) {
1381 txn.req_body.flags |= BODY_DISCARD;
1382 if (read_body(httpd_in, txn.req_hdrs, &txn.req_body,
1383 &txn.error.desc)) {
1384 txn.flags.conn = CONN_CLOSE;
1385 }
1386 }
1387
13541388 /* Memory cleanup */
13551389 if (txn.req_uri) xmlFreeURI(txn.req_uri);
13561390 if (txn.req_hdrs) spool_free_hdrcache(txn.req_hdrs);
13571391
13581392 if (txn.flags.conn & CONN_CLOSE) {
13591393 buf_free(&txn.buf);
1360 buf_free(&txn.req_body);
1394 buf_free(&txn.req_body.payload);
13611395 buf_free(&txn.resp_body.payload);
13621396 #ifdef HAVE_ZLIB
13631397 deflateEnd(&txn.zstrm);
14351469 }
14361470
14371471
1472 /* Calculate compile time of a file for use as Last-Modified and/or ETag */
1473 time_t calc_compile_time(const char *time, const char *date)
1474 {
1475 struct tm tm;
1476 char month[4];
1477 const char *monthname[] = {
1478 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1479 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1480 };
1481
1482 memset(&tm, 0, sizeof(struct tm));
1483 tm.tm_isdst = -1;
1484 sscanf(time, "%02d:%02d:%02d", &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
1485 sscanf(date, "%s %2d %4d", month, &tm.tm_mday, &tm.tm_year);
1486 tm.tm_year -= 1900;
1487 for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
1488 if (!strcmp(month, monthname[tm.tm_mon])) break;
1489 }
1490
1491 return mktime(&tm);
1492 }
1493
1494
1495 /*
1496 * Parse the framing of a request or response message.
1497 * Handles chunked, gzip, deflate TE only.
1498 * Handles close-delimited response bodies (no Content-Length specified)
1499 */
1500 static int parse_framing(hdrcache_t hdrs, struct body_t *body,
1501 const char **errstr)
1502 {
1503 static unsigned max_msgsize = 0;
1504 const char **hdr;
1505
1506 if (!max_msgsize) {
1507 max_msgsize = config_getint(IMAPOPT_MAXMESSAGESIZE);
1508
1509 /* If max_msgsize is 0, allow any size */
1510 if (!max_msgsize) max_msgsize = INT_MAX;
1511 }
1512
1513 body->framing = FRAMING_LENGTH;
1514 body->te = TE_NONE;
1515 body->len = 0;
1516 body->max = max_msgsize;
1517
1518 /* Check for Transfer-Encoding */
1519 if ((hdr = spool_getheader(hdrs, "Transfer-Encoding"))) {
1520 for (; *hdr; hdr++) {
1521 tok_t tok = TOK_INITIALIZER(*hdr, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
1522 char *token;
1523
1524 while ((token = tok_next(&tok))) {
1525 if (body->te & TE_CHUNKED) {
1526 /* "chunked" MUST only appear once and MUST be last */
1527 break;
1528 }
1529 else if (!strcasecmp(token, "chunked")) {
1530 body->te |= TE_CHUNKED;
1531 body->framing = FRAMING_CHUNKED;
1532 }
1533 else if (body->te & ~TE_CHUNKED) {
1534 /* can't combine compression codings */
1535 break;
1536 }
1537 #ifdef HAVE_ZLIB
1538 else if (!strcasecmp(token, "deflate"))
1539 body->te = TE_DEFLATE;
1540 else if (!strcasecmp(token, "gzip") ||
1541 !strcasecmp(token, "x-gzip"))
1542 body->te = TE_GZIP;
1543 #endif
1544 else if (!(body->flags & BODY_DISCARD)) {
1545 /* unknown/unsupported TE */
1546 break;
1547 }
1548 }
1549 tok_fini(&tok);
1550 if (token) break; /* error */
1551 }
1552
1553 if (*hdr) {
1554 *errstr = "Specified Transfer-Encoding not implemented";
1555 return HTTP_NOT_IMPLEMENTED;
1556 }
1557
1558 /* Check if this is a non-chunked response */
1559 else if (!(body->te & TE_CHUNKED)) {
1560 if ((body->flags & BODY_RESPONSE) && (body->flags & BODY_CLOSE)) {
1561 body->framing = FRAMING_CLOSE;
1562 }
1563 else {
1564 *errstr = "Final Transfer-Encoding MUST be \"chunked\"";
1565 return HTTP_NOT_IMPLEMENTED;
1566 }
1567 }
1568 }
1569
1570 /* Check for Content-Length */
1571 else if ((hdr = spool_getheader(hdrs, "Content-Length"))) {
1572 if (hdr[1]) {
1573 *errstr = "Multiple Content-Length header fields";
1574 return HTTP_BAD_REQUEST;
1575 }
1576
1577 body->len = strtoul(hdr[0], NULL, 10);
1578 if (body->len > max_msgsize) return HTTP_TOO_LARGE;
1579
1580 body->framing = FRAMING_LENGTH;
1581 }
1582
1583 /* Check if this is a close-delimited response */
1584 else if (body->flags & BODY_RESPONSE) {
1585 if (body->flags & BODY_CLOSE) body->framing = FRAMING_CLOSE;
1586 else return HTTP_LENGTH_REQUIRED;
1587 }
1588
1589 return 0;
1590 }
1591
1592
14381593 /*
14391594 * Read the body of a request or response.
14401595 * Handles chunked, gzip, deflate TE only.
14411596 * Handles close-delimited response bodies (no Content-Length specified)
14421597 * Handles gzip and deflate CE only.
14431598 */
1444 int read_body(struct protstream *pin, hdrcache_t hdrs, struct buf *body,
1445 unsigned char *flags, const char **errstr)
1446 {
1447 const char **hdr;
1448 unsigned long len = 0;
1449 enum { LEN_DELIM_LENGTH, LEN_DELIM_CHUNKED, LEN_DELIM_CLOSE };
1450 unsigned len_delim = LEN_DELIM_LENGTH, te = TE_NONE, max_msgsize, n;
1599 int read_body(struct protstream *pin, hdrcache_t hdrs, struct body_t *body,
1600 const char **errstr)
1601 {
14511602 char buf[PROT_BUFSIZE];
1452
1453 syslog(LOG_DEBUG, "read_body(%#x%s)", *flags, !body ? ", discard" : "");
1454
1455 if (*flags & BODY_DONE) return 0;
1456 *flags |= BODY_DONE;
1457
1458 if (body) buf_reset(body);
1459 else if (*flags & BODY_CONTINUE) {
1603 unsigned n;
1604 int r = 0;
1605
1606 syslog(LOG_DEBUG, "read_body(%#x)", body->flags);
1607
1608 if (body->flags & BODY_DONE) return 0;
1609 body->flags |= BODY_DONE;
1610
1611 if (!(body->flags & BODY_DISCARD)) buf_reset(&body->payload);
1612 else if (body->flags & BODY_CONTINUE) {
14601613 /* Don't care about the body and client hasn't sent it, we're done */
14611614 return 0;
14621615 }
14631616
1464 max_msgsize = config_getint(IMAPOPT_MAXMESSAGESIZE);
1465
1466 /* If max_msgsize is 0, allow any size */
1467 if (!max_msgsize) max_msgsize = INT_MAX;
1468
1469 /* Check for Transfer-Encoding */
1470 if ((hdr = spool_getheader(hdrs, "Transfer-Encoding"))) {
1471 for (; *hdr; hdr++) {
1472 tok_t tok = TOK_INITIALIZER(*hdr, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
1473 char *token;
1474
1475 while ((token = tok_next(&tok))) {
1476 if (te & TE_CHUNKED) {
1477 /* "chunked" MUST only appear once and MUST be last */
1478 break;
1479 }
1480 else if (!strcasecmp(token, "chunked")) {
1481 te |= TE_CHUNKED;
1482 len_delim = LEN_DELIM_CHUNKED;
1483 }
1484 else if (te & ~TE_CHUNKED) {
1485 /* can't combine compression codings */
1486 break;
1487 }
1488 #ifdef HAVE_ZLIB
1489 else if (!strcasecmp(token, "deflate"))
1490 te = TE_DEFLATE;
1491 else if (!strcasecmp(token, "gzip") ||
1492 !strcasecmp(token, "x-gzip"))
1493 te = TE_GZIP;
1494 #endif
1495 else if (body) {
1496 /* unknown/unsupported TE */
1497 break;
1498 }
1499 }
1500 tok_fini(&tok);
1501 if (token) break; /* error */
1502 }
1503
1504 if (*hdr) {
1505 *errstr = "Specified Transfer-Encoding not implemented";
1506 return HTTP_NOT_IMPLEMENTED;
1507 }
1508
1509 /* Check if this is a non-chunked response */
1510 else if (!(te & TE_CHUNKED)) {
1511 if ((*flags & BODY_RESPONSE) && (*flags & BODY_CLOSE)) {
1512 len_delim = LEN_DELIM_CLOSE;
1513 }
1514 else {
1515 *errstr = "Final Transfer-Encoding MUST be \"chunked\"";
1516 return HTTP_NOT_IMPLEMENTED;
1517 }
1518 }
1519 }
1520
1521 /* Check for Content-Length */
1522 else if ((hdr = spool_getheader(hdrs, "Content-Length"))) {
1523 if (hdr[1]) {
1524 *errstr = "Multiple Content-Length header fields";
1525 return HTTP_BAD_REQUEST;
1526 }
1527
1528 len = strtoul(hdr[0], NULL, 10);
1529 if (len > max_msgsize) return HTTP_TOO_LARGE;
1530
1531 len_delim = LEN_DELIM_LENGTH;
1532 }
1533
1534 /* Check if this is a close-delimited response */
1535 else if (*flags & BODY_RESPONSE) {
1536 if (*flags & BODY_CLOSE) len_delim = LEN_DELIM_CLOSE;
1537 else return HTTP_LENGTH_REQUIRED;
1538 }
1539
1540
1541 if (*flags & BODY_CONTINUE) {
1617 if (body->framing == FRAMING_UNKNOWN) {
1618 /* Get message framing */
1619 r = parse_framing(hdrs, body, errstr);
1620 if (r) return r;
1621 }
1622
1623 if (body->flags & BODY_CONTINUE) {
15421624 /* Tell client to send the body */
15431625 response_header(HTTP_CONTINUE, NULL);
15441626 }
15451627
15461628 /* Read and buffer the body */
1547 if (len_delim == LEN_DELIM_CHUNKED) {
1629 switch (body->framing) {
1630 case FRAMING_LENGTH:
1631 /* Read 'len' octets */
1632 for (; body->len; body->len -= n) {
1633 if (body->flags & BODY_DISCARD)
1634 n = prot_read(pin, buf, MIN(body->len, PROT_BUFSIZE));
1635 else
1636 n = prot_readbuf(pin, &body->payload, body->len);
1637
1638 if (!n) {
1639 syslog(LOG_ERR, "prot_read() error");
1640 *errstr = "Unable to read body data";
1641 goto read_failure;
1642 }
1643 }
1644
1645 break;
1646
1647 case FRAMING_CHUNKED:
1648 {
15481649 unsigned last = 0;
15491650
15501651 /* Read chunks until last-chunk (zero chunk-size) */
15561657 sscanf(buf, "%x", &chunk) != 1) {
15571658 *errstr = "Unable to read chunk size";
15581659 goto read_failure;
1559 }
1560 if ((len += chunk) > max_msgsize) return HTTP_TOO_LARGE;
1660
1661 /* XXX Do we need to parse chunk-ext? */
1662 }
1663 else if (chunk > body->max - body->len) return HTTP_TOO_LARGE;
15611664
15621665 if (!chunk) {
15631666 /* last-chunk */
15691672
15701673 /* Read 'chunk' octets */
15711674 for (; chunk; chunk -= n) {
1572 if (body) n = prot_readbuf(pin, body, chunk);
1573 else n = prot_read(pin, buf, MIN(chunk, PROT_BUFSIZE));
1675 if (body->flags & BODY_DISCARD)
1676 n = prot_read(pin, buf, MIN(chunk, PROT_BUFSIZE));
1677 else
1678 n = prot_readbuf(pin, &body->payload, chunk);
15741679
15751680 if (!n) {
15761681 syslog(LOG_ERR, "prot_read() error");
15771682 *errstr = "Unable to read chunk data";
15781683 goto read_failure;
15791684 }
1685 body->len += n;
15801686 }
15811687
15821688 /* Read CRLF terminating the chunk/trailer */
15871693
15881694 } while (!last);
15891695
1590 te &= ~TE_CHUNKED;
1591 }
1592 else if (len_delim == LEN_DELIM_CLOSE) {
1696 body->te &= ~TE_CHUNKED;
1697
1698 break;
1699 }
1700
1701 case FRAMING_CLOSE:
15931702 /* Read until EOF */
15941703 do {
1595 if (body) n = prot_readbuf(pin, body, PROT_BUFSIZE);
1596 else n = prot_read(pin, buf, PROT_BUFSIZE);
1597
1598 if ((len += n) > max_msgsize) return HTTP_TOO_LARGE;
1704 if (body->flags & BODY_DISCARD)
1705 n = prot_read(pin, buf, PROT_BUFSIZE);
1706 else
1707 n = prot_readbuf(pin, &body->payload, PROT_BUFSIZE);
1708
1709 if (n > body->max - body->len) return HTTP_TOO_LARGE;
1710 body->len += n;
15991711
16001712 } while (n);
16011713
16021714 if (!pin->eof) goto read_failure;
1603 }
1604 else if (len) {
1605 /* Read 'len' octets */
1606 for (; len; len -= n) {
1607 if (body) n = prot_readbuf(pin, body, len);
1608 else n = prot_read(pin, buf, MIN(len, PROT_BUFSIZE));
1609
1610 if (!n) {
1611 syslog(LOG_ERR, "prot_read() error");
1612 *errstr = "Unable to read body data";
1613 goto read_failure;
1614 }
1615 }
1616 }
1617
1618
1619 if (body && buf_len(body)) {
1620 int r = 0;
1621
1715
1716 break;
1717
1718 default:
1719 /* XXX Should never get here */
1720 *errstr = "Unknown length of read body data";
1721 goto read_failure;
1722 }
1723
1724
1725 if (!(body->flags & BODY_DISCARD) && buf_len(&body->payload)) {
16221726 #ifdef HAVE_ZLIB
16231727 /* Decode the payload, if necessary */
1624 if (te == TE_DEFLATE)
1625 r = buf_inflate(body, DEFLATE_ZLIB);
1626 else if (te == TE_GZIP)
1627 r = buf_inflate(body, DEFLATE_GZIP);
1728 if (body->te == TE_DEFLATE)
1729 r = buf_inflate(&body->payload, DEFLATE_ZLIB);
1730 else if (body->te == TE_GZIP)
1731 r = buf_inflate(&body->payload, DEFLATE_GZIP);
16281732
16291733 if (r) {
16301734 *errstr = "Error decoding payload";
16331737 #endif
16341738
16351739 /* Decode the representation, if necessary */
1636 if (*flags & BODY_DECODE) {
1740 if (body->flags & BODY_DECODE) {
1741 const char **hdr;
1742
16371743 if (!(hdr = spool_getheader(hdrs, "Content-Encoding"))) {
16381744 /* nothing to see here */
16391745 }
16441750
16451751 /* Try to detect Microsoft's broken deflate */
16461752 if (ua && strstr(ua[0], "; MSIE "))
1647 r = buf_inflate(body, DEFLATE_RAW);
1753 r = buf_inflate(&body->payload, DEFLATE_RAW);
16481754 else
1649 r = buf_inflate(body, DEFLATE_ZLIB);
1755 r = buf_inflate(&body->payload, DEFLATE_ZLIB);
16501756 }
16511757 else if (!strcasecmp(hdr[0], "gzip") ||
16521758 !strcasecmp(hdr[0], "x-gzip"))
1653 r = buf_inflate(body, DEFLATE_GZIP);
1759 r = buf_inflate(&body->payload, DEFLATE_GZIP);
16541760 #endif
16551761 else {
16561762 *errstr = "Specified Content-Encoding not accepted";
16831789 const char **exp = spool_getheader(txn->req_hdrs, "Expect");
16841790 int i, ret = 0;
16851791
1792 /* Expect not supported by HTTP/1.0 clients */
1793 if (exp && txn->flags.ver1_0) return HTTP_EXPECT_FAILED;
1794
16861795 /* Look for interesting expectations. Unknown == error */
16871796 for (i = 0; !ret && exp && exp[i]; i++) {
16881797 tok_t tok = TOK_INITIALIZER(exp[i], ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
16921801 /* Check if this is a non-persistent connection */
16931802 if (!strcasecmp(token, "100-continue")) {
16941803 syslog(LOG_DEBUG, "Expect: 100-continue");
1695 txn->flags.body |= BODY_CONTINUE;
1804 txn->req_body.flags |= BODY_CONTINUE;
16961805 }
16971806 else {
16981807 txn->error.desc = "Unsupported Expectation";
19012010 }
19022011 }
19032012
2013 #define MD5_BASE64_LEN 25 /* ((MD5_DIGEST_LENGTH / 3) + 1) * 4 */
2014
2015 void Content_MD5(const unsigned char *md5)
2016 {
2017 char base64[MD5_BASE64_LEN+1];
2018
2019 sasl_encode64((char *) md5, MD5_DIGEST_LENGTH,
2020 base64, MD5_BASE64_LEN, NULL);
2021 prot_printf(httpd_out, "Content-MD5: %s\r\n", base64);
2022 }
2023
2024
19042025 void response_header(long code, struct transaction_t *txn)
19052026 {
19062027 time_t now;
19102031 struct auth_challenge_t *auth_chal;
19112032 struct resp_body_t *resp_body;
19122033 static struct buf log = BUF_INITIALIZER;
1913
1914 /* Read and discard any unread body */
1915 if (txn && txn->req_hdrs &&
1916 read_body(httpd_in, txn->req_hdrs, NULL,
1917 &txn->flags.body, &txn->error.desc)) {
1918 txn->flags.conn = CONN_CLOSE;
1919 }
1920
19212034
19222035 /* Stop method processing alarm */
19232036 alarm(0);
21652278 if (resp_body->type) {
21662279 prot_printf(httpd_out, "Content-Type: %s\r\n", resp_body->type);
21672280
2281 if (resp_body->fname) {
2282 prot_printf(httpd_out,
2283 "Content-Disposition: inline; filename=\"%s\"\r\n",
2284 resp_body->fname);
2285 }
21682286 if (txn->resp_body.enc) {
21692287 /* Construct Content-Encoding header */
21702288 const char *ce[] =
21782296 if (resp_body->loc) {
21792297 prot_printf(httpd_out, "Content-Location: %s\r\n", resp_body->loc);
21802298 if (txn->flags.cors) Access_Control_Expose("Content-Location");
2299 }
2300 if (resp_body->md5) {
2301 Content_MD5(resp_body->md5);
21812302 }
21822303 }
21832304
22202341 { "deflate", "gzip", "chunked", NULL };
22212342
22222343 comma_list_hdr("Transfer-Encoding", te, txn->flags.te);
2344
2345 if (txn->flags.trailer) {
2346 /* Construct Trailer header */
2347 const char *trailer_hdrs[] =
2348 { "Content-MD5", NULL };
2349
2350 comma_list_hdr("Trailer", trailer_hdrs, txn->flags.trailer);
2351 }
22232352 }
22242353 }
22252354 else prot_printf(httpd_out, "Content-Length: %lu\r\n", resp_body->len);
23222451 {
23232452 unsigned is_dynamic = code ? (txn->flags.te & TE_CHUNKED) : 1;
23242453 unsigned outlen = len, offset = 0;
2454 int do_md5 = config_getswitch(IMAPOPT_HTTPCONTENTMD5);
2455 static MD5_CTX ctx;
2456 static unsigned char md5[MD5_DIGEST_LENGTH];
23252457
23262458 if (!is_dynamic && len < GZIP_MIN_LEN) {
23272459 /* Don't compress small static content */
23632495
23642496 if (code) {
23652497 /* Initial call - prepare response header based on CE, TE and version */
2498 if (do_md5) MD5_Init(&ctx);
23662499
23672500 if (txn->flags.te & ~TE_CHUNKED) {
23682501 /* Transfer-Encoded content MUST be chunked */
24162549 break;
24172550 }
24182551 }
2552
2553 if (outlen && do_md5) {
2554 MD5_Update(&ctx, buf+offset, outlen);
2555 MD5_Final(md5, &ctx);
2556 txn->resp_body.md5 = md5;
2557 }
24192558 }
24202559 else if (txn->flags.ver1_0) {
24212560 /* HTTP/1.0 doesn't support chunked - close-delimit the body */
24222561 txn->flags.conn = CONN_CLOSE;
24232562 }
2563 else if (do_md5) txn->flags.trailer = TRAILER_CMD5;
24242564
24252565 response_header(code, txn);
24262566
24452585 prot_printf(httpd_out, "%x\r\n", outlen);
24462586 prot_write(httpd_out, buf, outlen);
24472587 prot_puts(httpd_out, "\r\n");
2588
2589 if (do_md5) MD5_Update(&ctx, buf, outlen);
24482590 }
24492591 if (!len) {
24502592 /* Terminate the HTTP/1.1 body with a zero-length chunk */
24512593 prot_puts(httpd_out, "0\r\n");
24522594
2453 /* Empty trailer */
2595 /* Trailer */
2596 if (do_md5) {
2597 MD5_Final(md5, &ctx);
2598 Content_MD5(md5);
2599 }
2600
24542601 prot_puts(httpd_out, "\r\n");
24552602 }
24562603 }
26222769 }
26232770
26242771
2772 static int proxy_authz(const char **authzid, struct transaction_t *txn)
2773 {
2774 static char authzbuf[MAX_MAILBOX_BUFFER];
2775 unsigned authzlen;
2776 int status;
2777
2778 syslog(LOG_DEBUG, "proxy_auth: authzid='%s'", *authzid);
2779
2780 /* Free userid & authstate previously allocated for auth'd user */
2781 if (httpd_userid) {
2782 free(httpd_userid);
2783 httpd_userid = NULL;
2784 }
2785 if (httpd_authstate) {
2786 auth_freestate(httpd_authstate);
2787 httpd_authstate = NULL;
2788 }
2789
2790 if (!(config_mupdate_server && config_getstring(IMAPOPT_PROXYSERVERS))) {
2791 /* Not a backend in a Murder - proxy authz is not allowed */
2792 syslog(LOG_NOTICE, "badlogin: %s %s %s %s",
2793 httpd_clienthost, txn->auth_chal.scheme->name, saslprops.authid,
2794 "proxy authz attempted on non-Murder backend");
2795 return SASL_NOAUTHZ;
2796 }
2797
2798 /* Canonify the authzid */
2799 status = mysasl_canon_user(httpd_saslconn, NULL,
2800 *authzid, strlen(*authzid),
2801 SASL_CU_AUTHZID, NULL,
2802 authzbuf, sizeof(authzbuf), &authzlen);
2803 if (status) {
2804 syslog(LOG_NOTICE, "badlogin: %s %s %s invalid user",
2805 httpd_clienthost, txn->auth_chal.scheme->name,
2806 beautify_string(*authzid));
2807 return status;
2808 }
2809
2810 /* See if auth'd user is allowed to proxy */
2811 status = mysasl_proxy_policy(httpd_saslconn, &httpd_proxyctx,
2812 authzbuf, authzlen,
2813 saslprops.authid, strlen(saslprops.authid),
2814 NULL, 0, NULL);
2815
2816 if (status) {
2817 syslog(LOG_NOTICE, "badlogin: %s %s %s %s",
2818 httpd_clienthost, txn->auth_chal.scheme->name, saslprops.authid,
2819 sasl_errdetail(httpd_saslconn));
2820 return status;
2821 }
2822
2823 *authzid = authzbuf;
2824
2825 return status;
2826 }
2827
2828
26252829 /* Write cached header (redacting authorization credentials) to buffer. */
26262830 static void log_cachehdr(const char *name, const char *contents, void *rock)
26272831 {
26412845 }
26422846
26432847
2848 static void auth_success(struct transaction_t *txn)
2849 {
2850 struct auth_scheme_t *scheme = txn->auth_chal.scheme;
2851 int i;
2852
2853 proc_register("httpd", httpd_clienthost, httpd_userid, (char *)0);
2854
2855 syslog(LOG_NOTICE, "login: %s %s %s%s %s",
2856 httpd_clienthost, httpd_userid, scheme->name,
2857 httpd_tls_done ? "+TLS" : "", "User logged in");
2858
2859
2860 /* Recreate telemetry log entry for request (w/ credentials redacted) */
2861 assert(!buf_len(&txn->buf));
2862 buf_printf(&txn->buf, "<%ld<", time(NULL)); /* timestamp */
2863 buf_printf(&txn->buf, "%s %s %s\r\n", /* request-line*/
2864 txn->req_line.meth, txn->req_line.uri, txn->req_line.ver);
2865 spool_enum_hdrcache(txn->req_hdrs, /* header fields */
2866 &log_cachehdr, &txn->buf);
2867 buf_appendcstr(&txn->buf, "\r\n"); /* CRLF */
2868 buf_append(&txn->buf, &txn->req_body.payload); /* message body */
2869 buf_appendmap(&txn->buf, /* buffered input */
2870 (const char *) httpd_in->ptr, httpd_in->cnt);
2871
2872 if (httpd_logfd != -1) {
2873 /* Rewind log to current request and truncate it */
2874 off_t end = lseek(httpd_logfd, 0, SEEK_END);
2875
2876 ftruncate(httpd_logfd, end - buf_len(&txn->buf));
2877 }
2878
2879 if (!proxy_userid || strcmp(proxy_userid, httpd_userid)) {
2880 /* Close existing telemetry log */
2881 close(httpd_logfd);
2882
2883 prot_setlog(httpd_in, PROT_NO_FD);
2884 prot_setlog(httpd_out, PROT_NO_FD);
2885
2886 /* Create telemetry log based on new userid */
2887 httpd_logfd = telemetry_log(httpd_userid, httpd_in, httpd_out, 0);
2888 }
2889
2890 if (httpd_logfd != -1) {
2891 /* Log credential-redacted request */
2892 write(httpd_logfd, buf_cstring(&txn->buf), buf_len(&txn->buf));
2893 }
2894
2895 buf_reset(&txn->buf);
2896
2897 /* Make a copy of the external userid for use in proxying */
2898 if (proxy_userid) free(proxy_userid);
2899 proxy_userid = xstrdup(httpd_userid);
2900
2901 /* Translate any separators in userid */
2902 mboxname_hiersep_tointernal(&httpd_namespace, httpd_userid,
2903 config_virtdomains ?
2904 strcspn(httpd_userid, "@") : 0);
2905
2906 /* Do any namespace specific post-auth processing */
2907 for (i = 0; namespaces[i]; i++) {
2908 if (namespaces[i]->enabled && namespaces[i]->auth)
2909 namespaces[i]->auth(httpd_userid);
2910 }
2911 }
2912
2913
26442914 /* Perform HTTP Authentication based on the given credentials ('creds').
26452915 * Returns the selected auth scheme and any server challenge in 'chal'.
26462916 * May be called multiple times if auth scheme requires multiple steps.
26532923 struct auth_challenge_t *chal = &txn->auth_chal;
26542924 static int status = SASL_OK;
26552925 int slen;
2656 const char *clientin = NULL, *realm = NULL, *user;
2926 const char *clientin = NULL, *realm = NULL, *user, **authzid;
26572927 unsigned int clientinlen = 0;
26582928 struct auth_scheme_t *scheme;
26592929 static char base64[BASE64_BUF_SIZE+1];
26602930 const void *canon_user;
2661 const char **authzid = spool_getheader(txn->req_hdrs, "Authorize-As");
2662 int i;
2663
2664 chal->param = NULL;
26652931
26662932 /* Split credentials into auth scheme and response */
26672933 slen = strcspn(creds, " \0");
26682934 if ((clientin = strchr(creds, ' '))) clientinlen = strlen(++clientin);
26692935
26702936 syslog(LOG_DEBUG,
2671 "http_auth: status=%d scheme='%s' creds='%.*s%s' authzid='%s'",
2937 "http_auth: status=%d scheme='%s' creds='%.*s%s'",
26722938 status, chal->scheme ? chal->scheme->name : "",
2673 slen, creds, clientin ? " <response>" : "",
2674 authzid ? authzid[0] : "");
2939 slen, creds, clientin ? " <response>" : "");
2940
2941 /* Free userid & authstate previously allocated for auth'd user */
2942 if (httpd_userid) {
2943 free(httpd_userid);
2944 httpd_userid = NULL;
2945 }
2946 if (httpd_authstate) {
2947 auth_freestate(httpd_authstate);
2948 httpd_authstate = NULL;
2949 }
2950 chal->param = NULL;
26752951
26762952 if (chal->scheme) {
26772953 /* Use current scheme, if possible */
28513127 syslog(LOG_ERR, "weird SASL error %d getting SASL_USERNAME", status);
28523128 return status;
28533129 }
2854
3130 user = (const char *) canon_user;
3131
3132 if (saslprops.authid) free(saslprops.authid);
3133 saslprops.authid = xstrdup(user);
3134
3135 authzid = spool_getheader(txn->req_hdrs, "Authorize-As");
28553136 if (authzid && *authzid[0]) {
28563137 /* Trying to proxy as another user */
2857 char authzbuf[MAX_MAILBOX_BUFFER];
2858 unsigned authzlen;
2859
2860 /* Free authstate previously allocated for auth'd user */
2861 if (httpd_authstate) {
2862 auth_freestate(httpd_authstate);
2863 httpd_authstate = NULL;
2864 }
2865
2866 /* Canonify the authzid */
2867 status = mysasl_canon_user(httpd_saslconn, NULL,
2868 authzid[0], strlen(authzid[0]),
2869 SASL_CU_AUTHZID, NULL,
2870 authzbuf, sizeof(authzbuf), &authzlen);
2871 if (status) {
2872 syslog(LOG_NOTICE, "badlogin: %s %s %s invalid user",
2873 httpd_clienthost, scheme->name, beautify_string(authzid[0]));
2874 return status;
2875 }
2876 user = (const char *) canon_user;
2877
2878 /* See if user is allowed to proxy */
2879 status = mysasl_proxy_policy(httpd_saslconn, &httpd_proxyctx,
2880 authzbuf, authzlen, user, strlen(user),
2881 NULL, 0, NULL);
2882
2883 if (status) {
2884 syslog(LOG_NOTICE, "badlogin: %s %s %s %s",
2885 httpd_clienthost, scheme->name, user,
2886 sasl_errdetail(httpd_saslconn));
2887 return status;
2888 }
2889
2890 canon_user = authzbuf;
2891 }
2892
2893 httpd_userid = xstrdup((const char *) canon_user);
2894
2895 proc_register("httpd", httpd_clienthost, httpd_userid, (char *)0);
2896
2897 syslog(LOG_NOTICE, "login: %s %s %s%s %s",
2898 httpd_clienthost, httpd_userid, scheme->name,
2899 httpd_tls_done ? "+TLS" : "", "User logged in");
2900
2901
2902 /* Recreate telemetry log entry for request (w/ credentials redacted) */
2903 assert(!buf_len(&txn->buf));
2904 buf_printf(&txn->buf, "<%ld<", time(NULL)); /* timestamp */
2905 buf_printf(&txn->buf, "%s %s %s\r\n", /* request-line*/
2906 txn->req_line.meth, txn->req_line.uri, txn->req_line.ver);
2907 spool_enum_hdrcache(txn->req_hdrs, /* header fields */
2908 &log_cachehdr, &txn->buf);
2909 buf_appendcstr(&txn->buf, "\r\n"); /* CRLF */
2910 buf_append(&txn->buf, &txn->req_body); /* message body */
2911 buf_appendmap(&txn->buf, /* buffered input */
2912 (const char *) httpd_in->ptr, httpd_in->cnt);
2913
2914 if (httpd_logfd != -1) {
2915 /* Rewind log to current request and truncate it */
2916 off_t end = lseek(httpd_logfd, 0, SEEK_END);
2917
2918 ftruncate(httpd_logfd, end - buf_len(&txn->buf));
2919 }
2920
2921 if (!proxy_userid || strcmp(proxy_userid, httpd_userid)) {
2922 /* Close existing telemetry log */
2923 close(httpd_logfd);
2924
2925 prot_setlog(httpd_in, PROT_NO_FD);
2926 prot_setlog(httpd_out, PROT_NO_FD);
2927
2928 /* Create telemetry log based on new userid */
2929 httpd_logfd = telemetry_log(httpd_userid, httpd_in, httpd_out, 0);
2930 }
2931
2932 if (httpd_logfd != -1) {
2933 /* Log credential-redacted request */
2934 write(httpd_logfd, buf_cstring(&txn->buf), buf_len(&txn->buf));
2935 }
2936
2937 buf_reset(&txn->buf);
2938
2939 /* Make a copy of the external userid for use in proxying */
2940 if (proxy_userid) free(proxy_userid);
2941 proxy_userid = xstrdup(httpd_userid);
2942
2943 /* Translate any separators in userid */
2944 mboxname_hiersep_tointernal(&httpd_namespace, httpd_userid,
2945 config_virtdomains ?
2946 strcspn(httpd_userid, "@") : 0);
2947
2948 /* Do any namespace specific post-auth processing */
2949 for (i = 0; namespaces[i]; i++) {
2950 if (namespaces[i]->enabled && namespaces[i]->auth)
2951 namespaces[i]->auth(httpd_userid);
2952 }
3138 user = authzid[0];
3139
3140 status = proxy_authz(&user, txn);
3141 if (status) return status;
3142 }
3143
3144 httpd_userid = xstrdup(user);
3145
3146 auth_success(txn);
29533147
29543148 return status;
29553149 }
35553749
35563750 #ifdef WITH_DAV
35573751 /* Apple iCal and Evolution both check "/" */
3558 if (!strcmp(txn->req_uri->path, "/")) {
3752 if (!strcmp(txn->req_uri->path, "/") ||
3753 !strcmp(txn->req_uri->path, "/dav/")) {
35593754 if (!httpd_userid) return HTTP_UNAUTHORIZED;
3755
3756 /* Make a working copy of target path */
3757 strlcpy(txn->req_tgt.path, txn->req_uri->path,
3758 sizeof(txn->req_tgt.path));
3759 txn->req_tgt.tail = txn->req_tgt.path + strlen(txn->req_tgt.path);
35603760
35613761 txn->req_tgt.allow |= ALLOW_DAV;
35623762 return meth_propfind(txn, NULL);
237237 struct range *next;
238238 };
239239
240 /* Context for reading request/response body */
241 struct body_t {
242 unsigned char flags; /* Disposition flags */
243 unsigned char framing; /* Message framing */
244 unsigned char te; /* Transfer-Encoding */
245 unsigned max; /* Max allowed len */
246 ulong len; /* Content-Length */
247 struct buf payload; /* Payload */
248 };
249
250 /* Message Framing flags */
251 enum {
252 FRAMING_UNKNOWN = 0,
253 FRAMING_LENGTH,
254 FRAMING_CHUNKED,
255 FRAMING_CLOSE
256 };
257
258
240259 /* Meta-data for response body (payload & representation headers) */
241260 struct resp_body_t {
242261 ulong len; /* Content-Length */
243262 struct range *range;/* Content-Range */
263 const char *fname; /* Content-Dispo */
244264 unsigned char enc; /* Content-Encoding */
245265 const char *lang; /* Content-Language */
246266 const char *loc; /* Content-Location */
267 const u_char *md5; /* Content-MD5 */
247268 const char *type; /* Content-Type */
248269 unsigned prefs; /* Prefer */
249270 const char *lock; /* Lock-Token */
260281 unsigned char ver1_0; /* Request from HTTP/1.0 client */
261282 unsigned char conn; /* Connection opts on req/resp */
262283 unsigned char cors; /* Cross-Origin Resource Sharing */
263 unsigned char body; /* read_body() flags on req */
264284 unsigned char te; /* Transfer-Encoding for resp */
265285 unsigned char cc; /* Cache-Control directives for resp */
266286 unsigned char ranges; /* Accept range requests for resource */
267287 unsigned char vary; /* Headers on which response varied */
288 unsigned char trailer; /* Headers which will be in trailer */
268289 };
269290
270291 /* Transaction context */
275296 xmlURIPtr req_uri; /* Parsed request-target URI */
276297 struct request_target_t req_tgt; /* Parsed request-target path */
277298 hdrcache_t req_hdrs; /* Cached HTTP headers */
278 struct buf req_body; /* Buffered request body */
299 struct body_t req_body; /* Buffered request body */
279300 struct auth_challenge_t auth_chal; /* Authentication challenge */
280301 const char *location; /* Location of resource */
281302 struct error_t error; /* Error response meta-data */
320341 BODY_CONTINUE = (1<<1), /* Expect:100-continue request */
321342 BODY_CLOSE = (1<<1), /* Close-delimited response body */
322343 BODY_DECODE = (1<<2), /* Decode any Content-Encoding */
323 BODY_DONE = (1<<3) /* Body has been read */
344 BODY_DISCARD = (1<<3), /* Discard body (don't buffer or decode) */
345 BODY_DONE = (1<<4) /* Body has been read */
324346 };
325347
326348 /* Transfer-Encoding flags (coding of response payload) */
354376 VARY_AE = (1<<0), /* Accept-Encoding */
355377 VARY_BRIEF = (1<<1),
356378 VARY_PREFER = (1<<2)
379 };
380
381 /* Trailer header flags */
382 enum {
383 TRAILER_CMD5 = (1<<0) /* Content-MD5 */
357384 };
358385
359386 typedef int (*method_proc_t)(struct transaction_t *txn, void *params);
414441 extern xmlURIPtr parse_uri(unsigned meth, const char *uri, unsigned path_reqd,
415442 const char **errstr);
416443 extern int is_mediatype(const char *hdr, const char *type);
444 extern time_t calc_compile_time(const char *time, const char *date);
417445 extern int http_mailbox_open(const char *name, struct mailbox **mailbox,
418446 int locktype);
419447 extern const char *http_statusline(long code);
436464 extern int etagcmp(const char *hdr, const char *etag);
437465 extern int check_precond(struct transaction_t *txn, const void *data,
438466 const char *etag, time_t lastmod);
439 extern int read_body(struct protstream *pin, hdrcache_t hdrs, struct buf *body,
440 unsigned char *flags, const char **errstr);
467 extern int read_body(struct protstream *pin, hdrcache_t hdrs,
468 struct body_t *body, const char **errstr);
441469
442470 #endif /* HTTPD_H */
23492349 /* new files */
23502350 fname = mailbox_meta_newfname(mailbox, META_INDEX);
23512351 repack->newindex_fd = open(fname, O_RDWR|O_TRUNC|O_CREAT, 0666);
2352 if (repack->newindex_fd == -1) goto fail;
2352 if (repack->newindex_fd == -1) {
2353 syslog(LOG_ERR, "IOERROR: failed to create %s: %m", fname);
2354 goto fail;
2355 }
23532356
23542357 fname = mailbox_meta_newfname(mailbox, META_CACHE);
23552358 repack->newcache_fd = open(fname, O_RDWR|O_TRUNC|O_CREAT, 0666);
2356 if (repack->newcache_fd == -1) goto fail;
2359 if (repack->newcache_fd == -1) {
2360 syslog(LOG_ERR, "IOERROR: failed to create %s: %m", fname);
2361 goto fail;
2362 }
23572363
23582364 /* update the generation number */
23592365 repack->i.generation_no++;
23662372 repack->i.answered = 0;
23672373 repack->i.deleted = 0;
23682374 repack->i.flagged = 0;
2369 repack->i.exists = 0;
2375 repack->i.exists = 0;
23702376 repack->i.first_expunged = 0;
23712377 repack->i.leaked_cache_records = 0;
23722378
23782384 if (n == -1) goto fail;
23792385
23802386 n = retry_write(repack->newindex_fd, buf, INDEX_HEADER_SIZE);
2381 if (n == -1) goto fail;
2382
2387 if (n == -1) goto fail;
2388
23832389 *repackptr = repack;
23842390 return 0;
23852391
325325 }
326326 no_expunge:
327327
328 mailbox_repack_setup(mailbox, &repack);
328 r = mailbox_repack_setup(mailbox, &repack);
329 if (r) goto fail;
329330
330331 /* Write the rest of new index */
331332 recno = 1;
452452
453453 if(mytid) {
454454 /* transaction present, this means we do the slow way */
455 if (!savebuf || keylen > savebuflen) {
455 if (!savebuf || keylen >= savebuflen) {
456456 int dblsize = 2 * savebuflen;
457457 int addsize = keylen + 32;
458458
470470 Note that any path specified by "rss_feedlist_template" is an
471471 exception to this rule.*/
472472
473 { "httpcontentmd5", 0, SWITCH }
474 /* If enabled, HTTP responses will include a Content-MD5 header for
475 the purpose of providing an end-to-end message integrity check
476 (MIC) of the payload body. Note that enabling this option will
477 use additional CPU to generate the MD5 digest, which may be ignored
478 by clients anyways. */
479
473480 { "httpdocroot", NULL, STRING }
474481 /* If set, http will serve the static content (html/text/jpeg/gif
475482 files, etc) rooted at this directory. Otherwise, httpd will not
220220 { { NULL, IMAP_ENUM_ZERO } } },
221221 { IMAPOPT_HTTPALLOWEDURLS, "httpallowedurls", 0, OPT_STRING,
222222 {(void *)(NULL)},
223 { { NULL, IMAP_ENUM_ZERO } } },
224 { IMAPOPT_HTTPCONTENTMD5, "httpcontentmd5", 0, OPT_SWITCH,
225 {(void*)0},
223226 { { NULL, IMAP_ENUM_ZERO } } },
224227 { IMAPOPT_HTTPDOCROOT, "httpdocroot", 0, OPT_STRING,
225228 {(void *)(NULL)},
7474 IMAPOPT_HTTPALLOWCORS,
7575 IMAPOPT_HTTPALLOWTRACE,
7676 IMAPOPT_HTTPALLOWEDURLS,
77 IMAPOPT_HTTPCONTENTMD5,
7778 IMAPOPT_HTTPDOCROOT,
7879 IMAPOPT_HTTPKEEPALIVE,
7980 IMAPOPT_HTTPMODULES,
412412 .PP
413413 Note that any path specified by "rss_feedlist_template" is an
414414 exception to this rule.
415 .IP "\fBhttpcontentmd5:\fR 0" 5
416 If enabled, HTTP responses will include a Content-MD5 header for
417 the purpose of providing an end-to-end message integrity check
418 (MIC) of the payload body. Note that enabling this option will
419 use additional CPU to generate the MD5 digest, which may be ignored
420 by clients anyways.
415421 .IP "\fBhttpdocroot:\fR <none>" 5
416422 If set, http will serve the static content (html/text/jpeg/gif
417423 files, etc) rooted at this directory. Otherwise, httpd will not
123123 .\" ========================================================================
124124 .\"
125125 .IX Title "SIEVESHELL 1"
126 .TH SIEVESHELL 1 "2013-07-01" "perl v5.16.3" "User Contributed Perl Documentation"
126 .TH SIEVESHELL 1 "2013-09-18" "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
8181 if (strcmp(str,"active")==0) return TOKEN_ACTIVE;
8282 if (strcmp(str,"referral")==0) return TOKEN_REFERRAL;
8383 if (strcmp(str,"sasl")==0) return TOKEN_SASL;
84
84 if (strcmp(str,"quota/maxscripts")==0) return RESP_CODE_QUOTA_MAXSCRIPTS;
85 if (strcmp(str,"quota/maxsize")==0) return RESP_CODE_QUOTA_MAXSIZE;
86 if (strcmp(str,"quota")==0) return RESP_CODE_QUOTA;
87 if (strcmp(str,"transition-needed")==0) return RESP_CODE_TRANSITION_NEEDED;
88 if (strcmp(str,"trylater")==0) return RESP_CODE_TRYLATER;
89 if (strcmp(str,"nonexistant")==0) return RESP_CODE_NONEXISTANT;
90 if (strcmp(str,"alreadyexists")==0) return RESP_CODE_ALREADYEXISTS;
91 if (strcmp(str,"warning")==0) return RESP_CODE_WARNINGS;
92 if (strcmp(str,"tag")==0) return RESP_CODE_TAG;
93
8594 return -1;
8695 }
8796
266275 }
267276 break;
268277 case LEXER_STATE_ATOM:
269 if (!isalpha((unsigned char) ch)) {
278 if (!(isalpha((unsigned char) ch) || ch == '/')) {
270279 int token;
271280
272281 buffer[ buff_ptr - buffer] = '\0';
7070 TOKEN_ACTIVE = 291,
7171
7272 TOKEN_REFERRAL = 301,
73 TOKEN_SASL = 302
73 TOKEN_SASL = 302,
74 RESP_CODE_QUOTA = 303,
75 RESP_CODE_QUOTA_MAXSCRIPTS = 304,
76 RESP_CODE_QUOTA_MAXSIZE = 305,
77 RESP_CODE_TRANSITION_NEEDED = 306,
78 RESP_CODE_TRYLATER = 307,
79 RESP_CODE_NONEXISTANT = 308,
80 RESP_CODE_ALREADYEXISTS = 309,
81 RESP_CODE_WARNINGS = 310,
82 RESP_CODE_TAG = 311
7483 };
7584
7685 enum {
116116 res = yylex(&state, pin);
117117 }
118118 if(res != ')') {
119 parseerror("expected RPARAN");
119 parseerror("expected RPAREN");
120120 }
121121 }
122122
146146 res = yylex(&state, pin);
147147 }
148148 if(res != ')') {
149 parseerror("expected RPARAN");
149 parseerror("expected RPAREN");
150150 }
151151
152152 res = yylex(&state, pin);
0 /* Release cyrus-imapd-2.4.17-caldav-beta6 */
1 #define _CYRUS_VERSION "v2.4.17-caldav-beta6"
2 #define CYRUS_GITVERSION "5e525dd0 2013-07-01"
0 /* Release cyrus-imapd-2.4.17-caldav-beta7 */
1 #define _CYRUS_VERSION "v2.4.17-caldav-beta7"
2 #define CYRUS_GITVERSION "954b0f07 2013-09-18"