New upstream version 2.4.17+caldav~beta7
Ondřej Surý
10 years ago
63 | 63 | COMPILE_ET = @COMPILE_ET@ |
64 | 64 | |
65 | 65 | PACKAGE = cyrus-imapd |
66 | VERSION = 2.4.17-caldav-beta6 | |
66 | VERSION = 2.4.17-caldav-beta7 | |
67 | 67 | GIT_VERSION = $(VERSION).git$(shell date +'%Y%m%d%H%M') |
68 | 68 | |
69 | 69 | all:: xversion |
8110 | 8110 | fi |
8111 | 8111 | |
8112 | 8112 | 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 | |
8114 | 8119 | if test -f ${with_afs_libdir}/afs/libaudit.a; then |
8115 | 8120 | AFS_LIBS="$AFS_LIBS ${with_afs_libdir}/afs/libaudit.a" |
8116 | 8121 | fi |
11259 | 11264 | fi |
11260 | 11265 | |
11261 | 11266 | if test "$sievedir" != "no"; then |
11267 | if test "$enable_server" != "no"; then | |
11262 | 11268 | EXTRA_SUBDIRS="${EXTRA_SUBDIRS} timsieved notifyd" |
11263 | 11269 | EXTRA_OUTPUT="${EXTRA_OUTPUT} timsieved/Makefile notifyd/Makefile" |
11270 | fi | |
11264 | 11271 | |
11265 | 11272 | PERL_SUBDIRS="${PERL_SUBDIRS} sieve" |
11266 | 11273 | PERL_DEPSUBDIRS="sieve" |
666 | 666 | ]) |
667 | 667 | |
668 | 668 | 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 | |
670 | 675 | if test -f ${with_afs_libdir}/afs/libaudit.a; then |
671 | 676 | AFS_LIBS="$AFS_LIBS ${with_afs_libdir}/afs/libaudit.a" |
672 | 677 | fi |
1267 | 1272 | |
1268 | 1273 | dnl for timsieved |
1269 | 1274 | if test "$sievedir" != "no"; then |
1275 | if test "$enable_server" != "no"; then | |
1270 | 1276 | EXTRA_SUBDIRS="${EXTRA_SUBDIRS} timsieved notifyd" |
1271 | 1277 | EXTRA_OUTPUT="${EXTRA_OUTPUT} timsieved/Makefile notifyd/Makefile" |
1278 | fi | |
1272 | 1279 | |
1273 | 1280 | PERL_SUBDIRS="${PERL_SUBDIRS} sieve" |
1274 | 1281 | PERL_DEPSUBDIRS="sieve" |
67 | 67 | all: |
68 | 68 | |
69 | 69 | install: |
70 | $(srcdir)/../install-sh -d ${DESTDIR}etc | |
70 | $(srcdir)/../install-sh -d ${DESTDIR}/etc | |
71 | 71 | $(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 | |
74 | 74 | |
75 | 75 | .c.o: |
76 | 76 | $(CC) -c $(CPPFLAGS) $(DEFS) $(CFLAGS) $< |
6 | 6 | <title>Changes to the Cyrus IMAP Server</title> |
7 | 7 | </head> |
8 | 8 | <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> | |
9 | 24 | |
10 | 25 | <h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta5</h1> |
11 | 26 | <ul> |
37 | 52 | <li>Don't show addressbook mailboxes in IMAP LIST output</li> |
38 | 53 | <li>Plugged leaked memory found by Valgrind</li> |
39 | 54 | <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> | |
42 | 57 | </ul> |
43 | 58 | |
44 | 59 | <h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta3</h1> |
47 | 62 | <li>Rewrote list_feeds() to not use memmem()</li> |
48 | 63 | <li>OPTIONS method can be used without authentication</li> |
49 | 64 | <li>Better handling of Connection:keep-alive</li> |
50 | <li>Added <tt>httpallowedurls</tt> option<li> | |
65 | <li>Added <tt>httpallowedurls</tt> option</li> | |
51 | 66 | </ul> |
52 | 67 | |
53 | 68 | <h1>Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta2</h1> |
381 | 381 | </ol> |
382 | 382 | </p> |
383 | 383 | |
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://<servername>/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 | ||
384 | 419 | |
385 | 420 | <h2>CardDAV Module</h2> |
386 | 421 | |
454 | 489 | </ol> |
455 | 490 | </p> |
456 | 491 | |
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://<servername>/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 | ||
457 | 510 | |
458 | 511 | <h2>iSchedule Module</h2> |
459 | 512 |
6 | 6 | <body> |
7 | 7 | |
8 | 8 | <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> | |
9 | 17 | |
10 | 18 | <h2>Upgrading from 2.4.15</h2> |
11 | 19 | <ul> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:04 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:53 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:53 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:53 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:53 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:53 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:53 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:53 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:53 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:53 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:53 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:53 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:53 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:05 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:06 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
710 | 710 | <p style="margin-left:11%; margin-top: 1em">Note that any |
711 | 711 | path specified by "rss_feedlist_template" is an |
712 | 712 | exception to this rule. <b><br> |
713 | httpdocroot:</b> <none></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 | <none></p> | |
714 | 724 | |
715 | 725 | <p style="margin-left:18%;">If set, http will serve the |
716 | 726 | static content (html/text/jpeg/gif files, etc) rooted at |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:06 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:06 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:06 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:06 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:06 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:06 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:06 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:06 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:06 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:06 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:54 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:06 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:07 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:08 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:08 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:55 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <html> |
0 | 0 | <!-- Creator : groff version 1.22.1 --> |
1 | <!-- CreationDate: Mon Jul 1 13:58:08 2013 --> | |
1 | <!-- CreationDate: Wed Sep 18 15:19:56 2013 --> | |
2 | 2 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" |
3 | 3 | "http://www.w3.org/TR/html4/loose.dtd"> |
4 | 4 | <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 | ||
0 | 13 | Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta5 |
1 | 14 | |
2 | 15 | * RSS module now produces Atom 1.0 output rather than RSS 2.0 (we |
23 | 36 | * Don't show addressbook mailboxes in IMAP LIST output |
24 | 37 | * Plugged leaked memory found by Valgrind |
25 | 38 | * 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) | |
30 | 41 | |
31 | 42 | Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta3 |
32 | 43 | |
34 | 45 | * Rewrote list_feeds() to not use memmem() |
35 | 46 | * OPTIONS method can be used without authentication |
36 | 47 | * Better handling of Connection:keep-alive |
37 | * Added httpallowedurls option | |
38 | * | |
48 | * Added httpallowedurls option | |
39 | 49 | |
40 | 50 | Changes to the Cyrus IMAP Server since 2.4.17-caldav-beta2 |
41 | 51 |
0 | 0 | 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. | |
1 | 7 | |
2 | 8 | Upgrading from 2.4.15 |
3 | 9 |
291 | 291 | struct sockaddr_storage saddr_l, saddr_r; |
292 | 292 | char remoteip[60], localip[60]; |
293 | 293 | socklen_t addrsize; |
294 | int local_cb = 0; | |
295 | 294 | char buf[2048], optstr[128], *p; |
296 | 295 | const char *mech_conf, *pass; |
297 | 296 | |
309 | 308 | return SASL_FAIL; |
310 | 309 | |
311 | 310 | if (!cb) { |
312 | local_cb = 1; | |
313 | 311 | strlcpy(optstr, s->hostname, sizeof(optstr)); |
314 | 312 | p = strchr(optstr, '.'); |
315 | 313 | if (p) *p = '\0'; |
320 | 318 | config_getstring(IMAPOPT_PROXY_AUTHNAME), |
321 | 319 | config_getstring(IMAPOPT_PROXY_REALM), |
322 | 320 | pass); |
321 | s->sasl_cb = cb; | |
323 | 322 | } |
324 | 323 | |
325 | 324 | /* Require proxying if we have an "interesting" userid (authzid) */ |
328 | 327 | (prot->u.std.sasl_cmd.parse_success ? SASL_SUCCESS_DATA : 0), |
329 | 328 | &s->saslconn); |
330 | 329 | if (r != SASL_OK) { |
331 | if (local_cb) free_callbacks(cb); | |
332 | 330 | return r; |
333 | 331 | } |
334 | 332 | |
335 | 333 | r = sasl_setprop(s->saslconn, SASL_SEC_PROPS, &secprops); |
336 | 334 | if (!r) r = sasl_setprop(s->saslconn, SASL_SSF_EXTERNAL, &s->ext_ssf); |
337 | 335 | if (r != SASL_OK) { |
338 | if (local_cb) free_callbacks(cb); | |
339 | 336 | return r; |
340 | 337 | } |
341 | 338 | |
384 | 381 | (*mechlist = ask_capability(s->out, s->in, prot, |
385 | 382 | &s->capability, NULL, |
386 | 383 | prot->u.std.tls_cmd.auto_capa))); |
387 | ||
388 | /* xxx unclear that this is correct */ | |
389 | if (local_cb) free_callbacks(cb); | |
390 | 384 | |
391 | 385 | if (r == SASL_OK) { |
392 | 386 | prot_setsasl(s->in, s->saslconn); |
698 | 692 | } |
699 | 693 | |
700 | 694 | if (r) { |
695 | backend_disconnect(ret); | |
701 | 696 | if (!ret_backend) free(ret); |
702 | close(sock); | |
703 | 697 | ret = NULL; |
704 | 698 | } |
705 | 699 | |
799 | 793 | s->saslconn = NULL; |
800 | 794 | } |
801 | 795 | |
796 | /* Free any SASL callbacks */ | |
797 | if (s->sasl_cb) { | |
798 | free_callbacks(s->sasl_cb); | |
799 | s->sasl_cb = NULL; | |
800 | } | |
801 | ||
802 | 802 | /* free last_result buffer */ |
803 | 803 | buf_free(&s->last_result); |
804 | 804 | } |
70 | 70 | struct prot_waitevent *timeout; /* event for idle timeout */ |
71 | 71 | |
72 | 72 | sasl_conn_t *saslconn; |
73 | sasl_callback_t *sasl_cb; | |
73 | 74 | sasl_ssf_t ext_ssf; |
74 | 75 | #ifdef HAVE_SSL |
75 | 76 | SSL *tlsconn; |
533 | 533 | } |
534 | 534 | |
535 | 535 | |
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) | |
539 | 689 | { |
540 | 690 | struct icalperiodtype *period = (struct icalperiodtype *) rock; |
541 | 691 | int is_date = icaltime_is_date(icalcomponent_get_dtstart(comp)); |
556 | 706 | void caldav_make_entry(icalcomponent *ical, struct caldav_data *cdata) |
557 | 707 | { |
558 | 708 | icalcomponent *comp = icalcomponent_get_first_real_component(ical); |
559 | icaltimezone *utc = icaltimezone_get_utc_timezone(); | |
560 | 709 | icalcomponent_kind kind; |
561 | 710 | icalproperty *prop; |
562 | 711 | unsigned mykind = 0, recurring = 0, transp = 0; |
563 | struct icalperiodtype period; | |
712 | struct icalperiodtype span; | |
564 | 713 | |
565 | 714 | /* Get iCalendar UID */ |
566 | 715 | cdata->ical_uid = icalcomponent_get_uid(comp); |
599 | 748 | cdata->transp = transp; |
600 | 749 | |
601 | 750 | /* 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); | |
606 | 752 | |
607 | 753 | /* See if its a recurring event */ |
608 | 754 | if (icalcomponent_get_first_property(comp,ICAL_RRULE_PROPERTY) || |
615 | 761 | comp, |
616 | 762 | icaltime_from_timet_with_zone(INT_MIN, 0, NULL), |
617 | 763 | icaltime_from_timet_with_zone(INT_MAX, 0, NULL), |
618 | get_times, | |
619 | &period); | |
764 | recur_cb, | |
765 | &span); | |
620 | 766 | |
621 | 767 | /* Handle overridden recurrences */ |
622 | 768 | 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)); | |
633 | 778 | } |
634 | 779 | } |
635 | 780 | |
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); | |
638 | 783 | cdata->recurring = recurring; |
639 | 784 | } |
640 | 785 |
204 | 204 | for (recno = 1; recno <= mailbox->i.num_records; recno++) { |
205 | 205 | struct body *body; |
206 | 206 | struct param *param; |
207 | const char *msg_base = NULL, *resource = NULL; | |
207 | const char *msg_base = NULL; | |
208 | 208 | unsigned long msg_size = 0; |
209 | 209 | icalcomponent *ical = NULL; |
210 | 210 | |
229 | 229 | message_read_bodystructure(&record, &body); |
230 | 230 | for (param = body->disposition_params; param; param = param->next) { |
231 | 231 | 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; | |
234 | 236 | } |
235 | 237 | } |
236 | cdata.dav.resource = resource; | |
237 | 238 | |
238 | 239 | 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 | */ | |
243 | 240 | |
244 | 241 | caldav_write(caldavdb, &cdata, 0); |
245 | 242 |
466 | 466 | sasl_seterror(conn, 0, "buffer overflow while canonicalizing"); |
467 | 467 | return SASL_BUFOVER; |
468 | 468 | } |
469 | memcpy(out, user, ulen); | |
469 | memmove(out, user, ulen); | |
470 | 470 | out[ulen] = '\0'; |
471 | 471 | |
472 | 472 | canonuser = canonify_userid(out, NULL, (int*) context); |
84 | 84 | #include "smtpclient.h" |
85 | 85 | #include "spool.h" |
86 | 86 | #include "stristr.h" |
87 | #include "tok.h" | |
87 | 88 | #include "util.h" |
88 | 89 | #include "version.h" |
89 | 90 | #include "xmalloc.h" |
108 | 109 | }; |
109 | 110 | |
110 | 111 | static struct caldav_db *auth_caldavdb = NULL; |
112 | static time_t compile_time; | |
111 | 113 | |
112 | 114 | static void my_caldav_init(struct buf *serverinfo); |
113 | 115 | static void my_caldav_auth(const char *userid); |
146 | 148 | unsigned flags); |
147 | 149 | |
148 | 150 | 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); | |
150 | 153 | static void sched_reply(const char *userid, |
151 | 154 | icalcomponent *oldical, icalcomponent *newical); |
152 | 155 | |
229 | 232 | /* Need to set this to parse CalDAV Scheduling parameters */ |
230 | 233 | ical_set_unknown_token_handling_setting(ICAL_ASSUME_IANA_TOKEN); |
231 | 234 | } |
235 | ||
236 | compile_time = calc_compile_time(__TIME__, __DATE__); | |
232 | 237 | } |
233 | 238 | |
234 | 239 | |
240 | 245 | char ident[MAX_MAILBOX_NAME]; |
241 | 246 | struct buf acl = BUF_INITIALIZER; |
242 | 247 | |
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)) { | |
249 | 250 | /* admin or proxy from frontend - won't have DAV database */ |
250 | 251 | 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); | |
251 | 261 | } |
252 | 262 | |
253 | 263 | /* Auto-provision calendars for 'userid' */ |
259 | 269 | caldav_mboxname(NULL, userid, mailboxname); |
260 | 270 | r = mboxlist_lookup(mailboxname, &mbentry, NULL); |
261 | 271 | 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); | |
265 | 296 | if (!r) { |
266 | 297 | buf_reset(&acl); |
267 | 298 | cyrus_acl_masktostr(ACL_ALL | DACL_READFB, rights); |
334 | 365 | |
335 | 366 | if (partition) free(partition); |
336 | 367 | 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); | |
342 | 368 | } |
343 | 369 | |
344 | 370 | |
397 | 423 | tgt->userlen = len; |
398 | 424 | |
399 | 425 | 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 | } | |
401 | 431 | |
402 | 432 | len = strcspn(p, "/"); |
403 | 433 | } |
421 | 451 | p += len; |
422 | 452 | |
423 | 453 | 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; | |
426 | 456 | } |
427 | 457 | |
428 | 458 | done: |
695 | 725 | |
696 | 726 | if (!strcmp(sparam.userid, userid)) { |
697 | 727 | /* Organizer scheduling object resource */ |
698 | sched_request(organizer, &sparam, ical, NULL); | |
728 | sched_request(organizer, &sparam, ical, NULL, 0); | |
699 | 729 | } |
700 | 730 | else if (!(hdr = spool_getheader(txn->req_hdrs, "Schedule-Reply")) || |
701 | 731 | strcmp(hdr[0], "F")) { |
711 | 741 | } |
712 | 742 | |
713 | 743 | |
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; | |
719 | 747 | struct resp_body_t *resp_body = &txn->resp_body; |
720 | 748 | struct buf *buf = &resp_body->payload; |
721 | char *server, *acl; | |
722 | 749 | struct mailbox *mailbox = NULL; |
723 | 750 | static char etag[33]; |
724 | 751 | uint32_t recno; |
725 | 752 | struct index_record record; |
726 | 753 | 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; | |
727 | 1040 | |
728 | 1041 | /* Parse the path */ |
729 | 1042 | if ((r = gparams->parse_path(txn->req_uri->path, |
730 | 1043 | &txn->req_tgt, &txn->error.desc))) return r; |
731 | 1044 | |
732 | 1045 | /* 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); | |
737 | 1047 | |
738 | 1048 | /* Locate the mailbox */ |
739 | 1049 | if ((r = http_mlookup(txn->req_tgt.mboxname, &server, &acl, NULL))) { |
771 | 1081 | |
772 | 1082 | /* Local Mailbox */ |
773 | 1083 | |
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); | |
893 | 1089 | } |
894 | 1090 | |
895 | 1091 | |
941 | 1137 | rights = acl ? cyrus_acl_myrights(httpd_authstate, acl) : 0; |
942 | 1138 | |
943 | 1139 | /* 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); | |
947 | 1142 | if (r) { |
948 | 1143 | txn->flags.conn = CONN_CLOSE; |
949 | 1144 | return r; |
950 | 1145 | } |
951 | 1146 | |
952 | 1147 | /* Make sure we have a body */ |
953 | if (!buf_len(&txn->req_body)) { | |
1148 | if (!buf_len(&txn->req_body.payload)) { | |
954 | 1149 | txn->error.desc = "Missing request body\r\n"; |
955 | 1150 | return HTTP_BAD_REQUEST; |
956 | 1151 | } |
957 | 1152 | |
958 | 1153 | /* 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)); | |
960 | 1155 | if (!ical || !icalrestriction_check(ical)) { |
961 | 1156 | txn->error.precond = CALDAV_VALID_DATA; |
962 | return HTTP_BAD_REQUEST; | |
1157 | ret = HTTP_BAD_REQUEST; | |
1158 | goto done; | |
963 | 1159 | } |
964 | 1160 | |
965 | 1161 | meth = icalcomponent_get_method(ical); |
1061 | 1257 | const char *uid, *organizer = NULL; |
1062 | 1258 | |
1063 | 1259 | /* 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)); | |
1065 | 1261 | if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) { |
1066 | 1262 | txn->error.precond = CALDAV_VALID_DATA; |
1067 | 1263 | ret = HTTP_FORBIDDEN; |
1168 | 1364 | |
1169 | 1365 | if (!strcmp(sparam.userid, userid)) { |
1170 | 1366 | /* Organizer scheduling object resource */ |
1171 | sched_request(organizer, &sparam, oldical, ical); | |
1367 | sched_request(organizer, &sparam, oldical, ical, 0); | |
1172 | 1368 | } |
1173 | 1369 | else { |
1174 | 1370 | /* Attendee scheduling object resource */ |
1372 | 1568 | { |
1373 | 1569 | return (icaltime_is_valid_time(start) && icaltime_is_valid_time(end) && |
1374 | 1570 | !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)); | |
1376 | 1573 | } |
1377 | 1574 | |
1378 | 1575 | |
1393 | 1590 | filter->comp = CAL_COMP_VCALENDAR; |
1394 | 1591 | else { |
1395 | 1592 | error->precond = CALDAV_VALID_FILTER; |
1396 | return HTTP_FORBIDDEN; | |
1593 | ret = HTTP_FORBIDDEN; | |
1397 | 1594 | } |
1398 | 1595 | } |
1399 | 1596 | else if (filter->comp == CAL_COMP_VCALENDAR) { |
1400 | 1597 | if (!xmlStrcmp(name, BAD_CAST "VCALENDAR") || |
1401 | 1598 | !xmlStrcmp(name, BAD_CAST "VALARM")) { |
1402 | 1599 | error->precond = CALDAV_VALID_FILTER; |
1403 | return HTTP_FORBIDDEN; | |
1600 | ret = HTTP_FORBIDDEN; | |
1404 | 1601 | } |
1405 | 1602 | else if (!xmlStrcmp(name, BAD_CAST "VEVENT")) |
1406 | 1603 | filter->comp |= CAL_COMP_VEVENT; |
1414 | 1611 | filter->comp |= CAL_COMP_VTIMEZONE; |
1415 | 1612 | else { |
1416 | 1613 | error->precond = CALDAV_SUPP_FILTER; |
1417 | return HTTP_FORBIDDEN; | |
1614 | ret = HTTP_FORBIDDEN; | |
1418 | 1615 | } |
1419 | 1616 | } |
1420 | 1617 | else if (filter->comp & (CAL_COMP_VEVENT | CAL_COMP_VTODO)) { |
1422 | 1619 | filter->comp |= CAL_COMP_VALARM; |
1423 | 1620 | else { |
1424 | 1621 | error->precond = CALDAV_VALID_FILTER; |
1425 | return HTTP_FORBIDDEN; | |
1622 | ret = HTTP_FORBIDDEN; | |
1426 | 1623 | } |
1427 | 1624 | } |
1428 | 1625 | else { |
1429 | 1626 | error->precond = CALDAV_SUPP_FILTER; |
1430 | return HTTP_FORBIDDEN; | |
1627 | ret = HTTP_FORBIDDEN; | |
1431 | 1628 | } |
1432 | 1629 | |
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); | |
1434 | 1634 | if (ret) return ret; |
1435 | 1635 | } |
1436 | 1636 | 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; | |
1438 | 1639 | |
1439 | 1640 | if (!(filter->comp & (CAL_COMP_VEVENT | CAL_COMP_VTODO))) { |
1440 | 1641 | error->precond = CALDAV_VALID_FILTER; |
1441 | 1642 | return HTTP_FORBIDDEN; |
1442 | 1643 | } |
1443 | 1644 | |
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 | } | |
1451 | 1664 | |
1452 | 1665 | if (!is_valid_timerange(filter->start, filter->end)) { |
1453 | 1666 | error->precond = CALDAV_VALID_FILTER; |
1502 | 1715 | ICAL_VTIMEZONE_COMPONENT) |
1503 | 1716 | || icalcomponent_get_first_real_component(ical)) { |
1504 | 1717 | txn->error.precond = CALDAV_VALID_DATA; |
1505 | return HTTP_FORBIDDEN; | |
1718 | ret = HTTP_FORBIDDEN; | |
1506 | 1719 | } |
1720 | ||
1721 | if (tz) xmlFree(tz); | |
1722 | if (ical) icalcomponent_free(ical); | |
1723 | if (ret) return ret; | |
1507 | 1724 | } |
1508 | 1725 | } |
1509 | 1726 | } |
1788 | 2005 | for (node = inroot->children; node; node = node->next) { |
1789 | 2006 | if (node->type == XML_ELEMENT_NODE) { |
1790 | 2007 | 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 | } | |
1798 | 2021 | |
1799 | 2022 | if (!is_valid_timerange(calfilter.start, calfilter.end)) { |
1800 | 2023 | return HTTP_BAD_REQUEST; |
1838 | 2061 | icalproperty_method meth; |
1839 | 2062 | icalproperty *prop; |
1840 | 2063 | unsigned mykind = 0; |
2064 | const char *organizer = NULL; | |
1841 | 2065 | const char *prop_annot = ANNOT_NS "CALDAV:supported-calendar-component-set"; |
1842 | 2066 | struct annotation_data attrib; |
1843 | 2067 | struct caldav_data *cdata; |
1887 | 2111 | |
1888 | 2112 | /* Check for existing iCalendar UID */ |
1889 | 2113 | 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) && | |
1891 | 2116 | strcmp(cdata->dav.resource, resource)) { |
1892 | 2117 | /* CALDAV:no-uid-conflict */ |
1893 | 2118 | char *owner = mboxname_to_userid(cdata->dav.mailbox); |
1909 | 2134 | return HTTP_SERVER_ERROR; |
1910 | 2135 | } |
1911 | 2136 | |
2137 | /* Remove all X-LIC-ERROR properties*/ | |
2138 | icalcomponent_strip_errors(ical); | |
2139 | ||
1912 | 2140 | ics = icalcomponent_as_ical_string(ical); |
1913 | 2141 | |
1914 | 2142 | /* Create iMIP header for resource */ |
1915 | 2143 | prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY); |
1916 | 2144 | 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); | |
1918 | 2147 | } |
1919 | 2148 | else { |
1920 | 2149 | /* XXX This needs to be done via an LDAP/DB lookup */ |
1937 | 2166 | fprintf(f, "; component=%s\r\n", icalcomponent_kind_to_string(kind)); |
1938 | 2167 | |
1939 | 2168 | 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"); | |
1941 | 2180 | |
1942 | 2181 | /* XXX Check domain of data and use appropriate CTE */ |
1943 | 2182 | |
2043 | 2282 | } |
2044 | 2283 | |
2045 | 2284 | if (!r) { |
2285 | struct resp_body_t *resp_body = &txn->resp_body; | |
2286 | ||
2046 | 2287 | /* Create mapping entry from resource name to UID */ |
2047 | 2288 | cdata->dav.mailbox = mailbox->name; |
2048 | 2289 | cdata->dav.resource = resource; |
2052 | 2293 | if (!cdata->dav.creationdate) cdata->dav.creationdate = now; |
2053 | 2294 | if (!cdata->organizer) cdata->sched_tag = NULL; |
2054 | 2295 | 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; | |
2058 | 2297 | } |
2059 | 2298 | |
2060 | 2299 | caldav_write(caldavdb, cdata, 1); |
2061 | 2300 | /* XXX check for errors, if this fails, backout changes */ |
2062 | 2301 | |
2063 | 2302 | if (flags & PREFER_REP) { |
2064 | struct resp_body_t *resp_body = &txn->resp_body; | |
2065 | ||
2066 | 2303 | /* Fill in Last-Modified, ETag, and Content-Type */ |
2067 | 2304 | resp_body->lastmod = newrecord.internaldate; |
2068 | 2305 | resp_body->etag = message_guid_encode(&newrecord.guid); |
2070 | 2307 | resp_body->loc = txn->req_tgt.path; |
2071 | 2308 | resp_body->len = strlen(ics); |
2072 | 2309 | |
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 */ | |
2075 | 2315 | |
2076 | 2316 | write_body(ret, txn, ics, strlen(ics)); |
2077 | 2317 | ret = 0; |
2078 | 2318 | } |
2079 | 2319 | else if (!(flags & NEW_STAG)) { |
2080 | 2320 | /* 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); | |
2084 | 2323 | } |
2085 | 2324 | } |
2086 | 2325 | |
2468 | 2707 | !(txn->req_tgt.allow & ALLOW_ISCHEDULE) ? |
2469 | 2708 | ns[NS_DAV] : NULL, |
2470 | 2709 | BAD_CAST attendee, BAD_CAST REQSTAT_NOUSER); |
2710 | ||
2711 | icalproperty_free(prop); | |
2471 | 2712 | } |
2472 | 2713 | else if (sparam.flags) { |
2473 | 2714 | /* Remote attendee */ |
2501 | 2742 | /* Local attendee on this server */ |
2502 | 2743 | xmlNodePtr resp; |
2503 | 2744 | const char *userid = sparam.userid; |
2504 | struct mboxlist_entry mbentry; | |
2505 | int rights; | |
2745 | struct mboxlist_entry mbentry = { NULL, 0, NULL, NULL }; | |
2506 | 2746 | icalcomponent *busy = NULL; |
2507 | 2747 | |
2508 | 2748 | resp = |
2521 | 2761 | mailboxname, error_message(r)); |
2522 | 2762 | xmlNewChild(resp, NULL, BAD_CAST "request-status", |
2523 | 2763 | 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)) { | |
2530 | 2769 | xmlNewChild(resp, NULL, BAD_CAST "request-status", |
2531 | 2770 | 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 | } | |
2547 | 2787 | |
2548 | 2788 | if (busy) { |
2549 | 2789 | xmlNodePtr cdata; |
2567 | 2807 | xmlNewChild(resp, NULL, BAD_CAST "request-status", |
2568 | 2808 | BAD_CAST REQSTAT_NOUSER); |
2569 | 2809 | } |
2810 | ||
2811 | icalproperty_free(prop); | |
2570 | 2812 | } |
2571 | 2813 | } |
2572 | 2814 | |
2582 | 2824 | done: |
2583 | 2825 | if (org_authstate) auth_freestate(org_authstate); |
2584 | 2826 | if (calfilter.busytime.busy) free(calfilter.busytime.busy); |
2585 | if (root) xmlFree(root->doc); | |
2827 | if (root) xmlFreeDoc(root->doc); | |
2586 | 2828 | |
2587 | 2829 | return ret; |
2588 | 2830 | } |
2685 | 2927 | } |
2686 | 2928 | |
2687 | 2929 | /* 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, | |
2689 | 2931 | struct sched_param *sparam, |
2690 | 2932 | struct sched_data *sched_data, |
2691 | 2933 | struct auth_state *authstate) |
2692 | 2934 | { |
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; | |
2695 | 2937 | static struct buf resource = BUF_INITIALIZER; |
2696 | 2938 | static unsigned sched_count = 0; |
2697 | 2939 | char namebuf[MAX_MAILBOX_BUFFER]; |
2776 | 3018 | |
2777 | 3019 | prop = icalcomponent_get_first_property(ical, ICAL_METHOD_PROPERTY); |
2778 | 3020 | icalcomponent_remove_property(ical, prop); |
3021 | icalproperty_free(prop); | |
3022 | ||
3023 | deliver_inbox = 1; | |
2779 | 3024 | } |
2780 | 3025 | else { |
2781 | 3026 | /* Update existing object */ |
2804 | 3049 | /* Set STATUS:CANCELLED on all components */ |
2805 | 3050 | do { |
2806 | 3051 | 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); | |
2809 | 3054 | } while ((comp = icalcomponent_get_next_component(ical, kind))); |
3055 | ||
3056 | deliver_inbox = 1; | |
2810 | 3057 | |
2811 | 3058 | break; |
2812 | 3059 | |
2816 | 3063 | icalproperty *att; |
2817 | 3064 | icalparameter *param; |
2818 | 3065 | 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; | |
2820 | 3068 | |
2821 | 3069 | /* Add each component of old object to hash table for comparison */ |
2822 | 3070 | construct_hash_table(&comp_table, 10, 1); |
2857 | 3105 | prop = |
2858 | 3106 | icalcomponent_get_first_property(comp, |
2859 | 3107 | 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 | } | |
2861 | 3112 | |
2862 | 3113 | /* Replace DTSTART, DTEND, SEQUENCE */ |
2863 | 3114 | prop = |
2864 | 3115 | icalcomponent_get_first_property(comp, |
2865 | 3116 | 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 | } | |
2867 | 3121 | prop = |
2868 | 3122 | icalcomponent_get_first_property(itip, |
2869 | 3123 | ICAL_DTSTART_PROPERTY); |
2874 | 3128 | prop = |
2875 | 3129 | icalcomponent_get_first_property(comp, |
2876 | 3130 | 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 | } | |
2878 | 3135 | prop = |
2879 | 3136 | icalcomponent_get_first_property(itip, |
2880 | 3137 | ICAL_DTEND_PROPERTY); |
2885 | 3142 | prop = |
2886 | 3143 | icalcomponent_get_first_property(comp, |
2887 | 3144 | 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 | } | |
2889 | 3149 | prop = |
2890 | 3150 | icalcomponent_get_first_property(itip, |
2891 | 3151 | ICAL_SEQUENCE_PROPERTY); |
2904 | 3164 | icalproperty_get_first_parameter(att, |
2905 | 3165 | ICAL_PARTSTAT_PARAMETER); |
2906 | 3166 | 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); | |
2907 | 3171 | |
2908 | 3172 | prop = |
2909 | 3173 | icalcomponent_get_first_property(itip, |
2938 | 3202 | icalproperty_add_parameter(prop, param); |
2939 | 3203 | } |
2940 | 3204 | 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 | } | |
2941 | 3216 | |
2942 | 3217 | /* Find and set SCHEDULE-STATUS */ |
2943 | 3218 | for (param = |
2960 | 3235 | |
2961 | 3236 | free_hash_table(&comp_table, NULL); |
2962 | 3237 | |
3238 | deliver_inbox = 1; | |
3239 | ||
2963 | 3240 | break; |
2964 | 3241 | } |
2965 | 3242 | |
2966 | 3243 | case ICAL_METHOD_REQUEST: { |
2967 | 3244 | struct hash_table comp_table; |
2968 | 3245 | 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); | |
2970 | 3290 | |
2971 | 3291 | /* Add each component of old object to hash table for comparison */ |
2972 | 3292 | construct_hash_table(&comp_table, 10, 1); |
2985 | 3305 | /* Process each component in the iTIP request */ |
2986 | 3306 | itip = icalcomponent_get_first_component(sched_data->itip, kind); |
2987 | 3307 | do { |
3308 | icalcomponent *new_comp = icalcomponent_new_clone(itip); | |
3309 | ||
2988 | 3310 | /* Lookup this comp in the hash table */ |
2989 | 3311 | prop = |
2990 | 3312 | icalcomponent_get_first_property(itip, |
2994 | 3316 | |
2995 | 3317 | comp = hash_lookup(recurid, &comp_table); |
2996 | 3318 | 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 | ||
2997 | 3381 | /* Remove component from old object */ |
2998 | 3382 | icalcomponent_remove_component(ical, comp); |
3383 | icalcomponent_free(comp); | |
2999 | 3384 | } |
3385 | else deliver_inbox = 1; | |
3000 | 3386 | |
3001 | 3387 | /* Add new/modified component from iTIP request*/ |
3002 | icalcomponent_add_component(ical, | |
3003 | icalcomponent_new_clone(itip)); | |
3388 | icalcomponent_add_component(ical, new_comp); | |
3004 | 3389 | |
3005 | 3390 | } while ((itip = icalcomponent_get_next_component(sched_data->itip, |
3006 | 3391 | kind))); |
3037 | 3422 | } |
3038 | 3423 | |
3039 | 3424 | 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 | } | |
3052 | 3445 | |
3053 | 3446 | done: |
3054 | 3447 | 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 | } | |
3056 | 3452 | if (mailbox) mailbox_close(&mailbox); |
3057 | 3453 | if (caldavdb) caldav_close(caldavdb); |
3058 | 3454 | } |
3126 | 3522 | prop = icalcomponent_get_first_property(comp, |
3127 | 3523 | ICAL_DTSTAMP_PROPERTY); |
3128 | 3524 | icalcomponent_remove_property(comp, prop); |
3525 | icalproperty_free(prop); | |
3129 | 3526 | prop = |
3130 | 3527 | icalproperty_new_dtstamp(icaltime_from_timet_with_zone(now, 0, utc)); |
3131 | 3528 | icalcomponent_add_property(comp, prop); |
3135 | 3532 | alarm; alarm = next) { |
3136 | 3533 | next = icalcomponent_get_next_component(comp, ICAL_VALARM_COMPONENT); |
3137 | 3534 | icalcomponent_remove_component(comp, alarm); |
3535 | icalcomponent_free(alarm); | |
3138 | 3536 | } |
3139 | 3537 | |
3140 | 3538 | if (clean_org) { |
3141 | icalparameter *param; | |
3539 | icalparameter *param, *next; | |
3142 | 3540 | |
3143 | 3541 | /* Grab the organizer */ |
3144 | 3542 | prop = icalcomponent_get_first_property(comp, |
3145 | 3543 | ICAL_ORGANIZER_PROPERTY); |
3146 | 3544 | |
3147 | 3545 | /* 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 | ||
3153 | 3551 | if (!strcmp(icalparameter_get_iana_name(param), |
3154 | 3552 | "SCHEDULE-AGENT")) { |
3155 | 3553 | icalproperty_remove_parameter_by_ref(prop, param); |
3215 | 3613 | * properly modified component to the attendee's iTIP request if necessary |
3216 | 3614 | */ |
3217 | 3615 | static void process_attendees(icalcomponent *comp, unsigned ncomp, |
3218 | const char *organizer, | |
3616 | const char *organizer, const char *att_update, | |
3219 | 3617 | struct hash_table *att_table, |
3220 | icalcomponent *itip) | |
3221 | { | |
3618 | icalcomponent *itip, unsigned needs_action) | |
3619 | { | |
3620 | icalcomponent *copy; | |
3222 | 3621 | 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 */ | |
3228 | 3626 | for (prop = icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY); |
3229 | 3627 | prop; |
3230 | 3628 | prop = icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) { |
3231 | 3629 | 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); | |
3232 | 3666 | unsigned do_sched = 1; |
3233 | 3667 | icalparameter *force_send = NULL; |
3234 | 3668 | |
3235 | 3669 | /* Don't schedule attendee == organizer */ |
3236 | 3670 | 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; | |
3237 | 3674 | |
3238 | 3675 | /* Check CalDAV Scheduling parameters */ |
3239 | 3676 | for (param = |
3240 | 3677 | 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 | ||
3244 | 3681 | if (!strcmp(icalparameter_get_iana_name(param), |
3245 | 3682 | "SCHEDULE-AGENT")) { |
3246 | 3683 | do_sched = |
3269 | 3706 | } |
3270 | 3707 | hash_insert(attendee, sched_data, att_table); |
3271 | 3708 | } |
3272 | new_comp = icalcomponent_new_clone(comp); | |
3709 | new_comp = icalcomponent_new_clone(copy); | |
3273 | 3710 | icalcomponent_add_component(sched_data->itip, new_comp); |
3274 | 3711 | sched_data->comp_mask |= (1 << ncomp); |
3275 | 3712 | |
3279 | 3716 | |
3280 | 3717 | if (force_send) icalproperty_remove_parameter_by_ref(prop, force_send); |
3281 | 3718 | } |
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); | |
3282 | 3729 | } |
3283 | 3730 | |
3284 | 3731 | |
3298 | 3745 | { |
3299 | 3746 | struct comp_data *old_data = (struct comp_data *) data; |
3300 | 3747 | 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); | |
3305 | 3748 | |
3306 | 3749 | /* 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; | |
3313 | 3779 | } |
3314 | 3780 | |
3315 | 3781 | |
3316 | 3782 | /* Create and deliver an organizer scheduling request */ |
3317 | 3783 | 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) | |
3319 | 3786 | { |
3320 | 3787 | int r, rights; |
3321 | 3788 | struct mboxlist_entry mbentry; |
3342 | 3809 | method = ICAL_METHOD_REQUEST; |
3343 | 3810 | } |
3344 | 3811 | |
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 | } | |
3361 | 3830 | } |
3362 | 3831 | |
3363 | 3832 | /* Create a shell for our iTIP request objects */ |
3400 | 3869 | comp = icalcomponent_get_first_real_component(oldical); |
3401 | 3870 | do { |
3402 | 3871 | old_data = xzmalloc(sizeof(struct comp_data)); |
3403 | ||
3404 | 3872 | old_data->comp = comp; |
3405 | 3873 | old_data->sequence = icalcomponent_get_sequence(comp); |
3406 | 3874 | |
3423 | 3891 | |
3424 | 3892 | comp = icalcomponent_get_first_real_component(newical); |
3425 | 3893 | do { |
3426 | icalcomponent *copy; | |
3894 | unsigned changed = 1, needs_action = 0; | |
3427 | 3895 | |
3428 | 3896 | prop = icalcomponent_get_first_property(comp, |
3429 | 3897 | ICAL_RECURRENCEID_PROPERTY); |
3432 | 3900 | |
3433 | 3901 | old_data = hash_del(recurid, &comp_table); |
3434 | 3902 | |
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 | } | |
3459 | 3937 | |
3460 | 3938 | } while ((comp = icalcomponent_get_next_component(newical, kind))); |
3461 | 3939 | } |
3466 | 3944 | |
3467 | 3945 | hash_enumerate(&comp_table, sched_cancel, &crock); |
3468 | 3946 | } |
3469 | free_hash_table(&comp_table, NULL); | |
3947 | free_hash_table(&comp_table, free); | |
3470 | 3948 | |
3471 | 3949 | icalcomponent_free(req); |
3472 | 3950 | |
3482 | 3960 | |
3483 | 3961 | done: |
3484 | 3962 | if (newical) { |
3963 | unsigned ncomp = 0; | |
3964 | ||
3485 | 3965 | /* Set SCHEDULE-STATUS for each attendee in organizer object */ |
3486 | 3966 | 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))); | |
3514 | 4011 | } |
3515 | 4012 | |
3516 | 4013 | /* Cleanup */ |
3569 | 4066 | else { |
3570 | 4067 | /* Some other attendee, remove it */ |
3571 | 4068 | icalcomponent_remove_property(copy, prop); |
4069 | icalproperty_free(prop); | |
3572 | 4070 | } |
3573 | 4071 | } |
3574 | 4072 | |
3608 | 4106 | param = |
3609 | 4107 | icalproperty_get_first_parameter(myattendee, |
3610 | 4108 | 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 | } | |
3616 | 4113 | icalparameter_set_partstat(param, ICAL_PARTSTAT_DECLINED); |
3617 | 4114 | |
3618 | 4115 | clean_component(old_data->comp, 1); |
3748 | 4245 | |
3749 | 4246 | /* Process each component of new object */ |
3750 | 4247 | if (newical) { |
4248 | unsigned ncomp = 0; | |
4249 | ||
3751 | 4250 | comp = icalcomponent_get_first_real_component(newical); |
3752 | 4251 | do { |
3753 | 4252 | icalcomponent *copy; |
3782 | 4281 | clean_component(copy, 1); |
3783 | 4282 | |
3784 | 4283 | icalcomponent_add_component(sched_data->itip, copy); |
4284 | sched_data->comp_mask |= (1 << ncomp); | |
3785 | 4285 | } |
3786 | 4286 | else icalcomponent_free(copy); |
3787 | 4287 | |
4288 | ncomp++; | |
3788 | 4289 | } while ((comp = icalcomponent_get_next_component(newical, kind))); |
3789 | 4290 | } |
3790 | 4291 | |
3805 | 4306 | } |
3806 | 4307 | |
3807 | 4308 | 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 | ||
3810 | 4311 | /* Set SCHEDULE-STATUS for organizer in attendee object */ |
3811 | 4312 | 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))); | |
3818 | 4326 | } |
3819 | 4327 | } |
3820 | 4328 |
212 | 212 | char ident[MAX_MAILBOX_NAME]; |
213 | 213 | struct buf acl = BUF_INITIALIZER; |
214 | 214 | |
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)) { | |
221 | 217 | /* admin or proxy from frontend - won't have DAV database */ |
222 | 218 | 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); | |
223 | 228 | } |
224 | 229 | |
225 | 230 | /* Auto-provision an addressbook for 'userid' */ |
236 | 241 | config_getstring(IMAPOPT_ADDRESSBOOKPREFIX)); |
237 | 242 | r = mboxlist_lookup(mailboxname, &mbentry, NULL); |
238 | 243 | 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); | |
242 | 268 | if (!r) { |
243 | 269 | buf_reset(&acl); |
244 | 270 | cyrus_acl_masktostr(ACL_ALL, rights); |
276 | 302 | |
277 | 303 | if (partition) free(partition); |
278 | 304 | 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); | |
284 | 305 | } |
285 | 306 | |
286 | 307 | |
363 | 384 | p += len; |
364 | 385 | |
365 | 386 | 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; | |
368 | 389 | } |
369 | 390 | |
370 | 391 | done: |
471 | 492 | VObject *vcard = NULL; |
472 | 493 | |
473 | 494 | /* 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)); | |
475 | 497 | if (!vcard || strcmp(vObjectName(vcard), "VCARD")) { |
476 | 498 | txn->error.precond = CARDDAV_VALID_DATA; |
477 | 499 | ret = HTTP_FORBIDDEN; |
650 | 672 | |
651 | 673 | /* Check for existing vCard UID */ |
652 | 674 | 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) && | |
654 | 677 | strcmp(cdata->dav.resource, resource)) { |
655 | 678 | /* CARDDAV:no-uid-conflict */ |
656 | 679 | char *owner = mboxname_to_userid(cdata->dav.mailbox); |
687 | 710 | |
688 | 711 | fprintf(f, "Content-Type: text/vcard; charset=utf-8\r\n"); |
689 | 712 | |
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)); | |
691 | 714 | fprintf(f, "Content-Disposition: inline; filename=\"%s\"\r\n", resource); |
692 | 715 | |
693 | 716 | /* XXX Check domain of data and use appropriate CTE */ |
696 | 719 | fprintf(f, "\r\n"); |
697 | 720 | |
698 | 721 | /* 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)); | |
700 | 723 | size = ftell(f); |
701 | 724 | |
702 | 725 | fclose(f); |
793 | 816 | } |
794 | 817 | |
795 | 818 | if (!r) { |
819 | struct resp_body_t *resp_body = &txn->resp_body; | |
820 | ||
796 | 821 | /* Create mapping entry from resource name to UID */ |
797 | 822 | cdata->dav.mailbox = mailbox->name; |
798 | 823 | cdata->dav.resource = resource; |
807 | 832 | /* XXX check for errors, if this fails, backout changes */ |
808 | 833 | |
809 | 834 | /* 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); | |
811 | 837 | |
812 | 838 | if (flags & PREFER_REP) { |
813 | struct resp_body_t *resp_body = &txn->resp_body; | |
814 | ||
839 | resp_body->type = "text/vcard; charset=utf-8"; | |
815 | 840 | 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 */ | |
821 | 848 | |
822 | 849 | 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)); | |
824 | 852 | ret = 0; |
825 | 853 | } |
826 | 854 | } |
362 | 362 | else return HTTP_NOT_FOUND; /* need to specify a userid */ |
363 | 363 | |
364 | 364 | 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; | |
367 | 367 | } |
368 | 368 | |
369 | 369 | return 0; |
840 | 840 | struct propstat propstat[], |
841 | 841 | void *rock __attribute__((unused))) |
842 | 842 | { |
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 | ||
849 | 843 | 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 | ||
851 | 850 | xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK], |
852 | 851 | name, ns, BAD_CAST buf_cstring(&fctx->buf), 0); |
853 | 852 | |
891 | 890 | struct propstat propstat[], |
892 | 891 | void *rock __attribute__((unused))) |
893 | 892 | { |
894 | if (!fctx->record) return HTTP_NOT_FOUND; | |
893 | if (!fctx->mailbox || | |
894 | (fctx->req_tgt->resource && !fctx->record)) return HTTP_NOT_FOUND; | |
895 | 895 | |
896 | 896 | buf_ensure(&fctx->buf, 30); |
897 | 897 | httpdate_gen(fctx->buf.s, fctx->buf.alloc, |
898 | fctx->record->internaldate); | |
898 | fctx->record ? fctx->record->internaldate : | |
899 | fctx->mailbox->index_mtime); | |
899 | 900 | |
900 | 901 | xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK], |
901 | 902 | name, ns, BAD_CAST fctx->buf.s, 0); |
1809 | 1810 | /* Make sure we have a valid component type */ |
1810 | 1811 | for (comp = cal_comps; |
1811 | 1812 | comp->name && xmlStrcmp(name, BAD_CAST comp->name); comp++); |
1813 | xmlFree(name); | |
1814 | ||
1812 | 1815 | if (comp->name) types |= comp->type; /* found match in our list */ |
1813 | 1816 | else break; /* no match - invalid type */ |
1814 | 1817 | } |
2280 | 2283 | propfind_fromdb, proppatch_todb, NULL }, |
2281 | 2284 | { "getcontentlanguage", NS_DAV, PROP_ALLPROP | PROP_RESOURCE, |
2282 | 2285 | propfind_fromhdr, NULL, "Content-Language" }, |
2283 | { "getcontentlength", NS_DAV, PROP_ALLPROP | PROP_RESOURCE, | |
2286 | { "getcontentlength", NS_DAV, | |
2287 | PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, | |
2284 | 2288 | propfind_getlength, NULL, NULL }, |
2285 | 2289 | { "getcontenttype", NS_DAV, |
2286 | 2290 | PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, |
2288 | 2292 | { "getetag", NS_DAV, PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, |
2289 | 2293 | propfind_getetag, NULL, NULL }, |
2290 | 2294 | { "getlastmodified", NS_DAV, |
2291 | PROP_ALLPROP | /*PROP_COLLECTION |*/ PROP_RESOURCE, | |
2295 | PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE, | |
2292 | 2296 | propfind_getlastmod, NULL, NULL }, |
2293 | 2297 | { "lockdiscovery", NS_DAV, PROP_ALLPROP | PROP_RESOURCE, |
2294 | 2298 | propfind_lockdisc, NULL, NULL }, |
2716 | 2720 | *root = NULL; |
2717 | 2721 | |
2718 | 2722 | /* 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); | |
2722 | 2725 | if (r) { |
2723 | 2726 | txn->flags.conn = CONN_CLOSE; |
2724 | 2727 | return r; |
2725 | 2728 | } |
2726 | 2729 | |
2727 | if (!buf_len(&txn->req_body)) return 0; | |
2730 | if (!buf_len(&txn->req_body.payload)) return 0; | |
2728 | 2731 | |
2729 | 2732 | /* Check Content-Type */ |
2730 | 2733 | if (!(hdr = spool_getheader(txn->req_hdrs, "Content-Type")) || |
2737 | 2740 | /* Parse the XML request */ |
2738 | 2741 | ctxt = xmlNewParserCtxt(); |
2739 | 2742 | 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, | |
2742 | 2745 | XML_PARSE_NOWARNING); |
2743 | 2746 | xmlFreeParserCtxt(ctxt); |
2744 | 2747 | } |
2938 | 2941 | uri->path[plen] == '/') { |
2939 | 2942 | memset(&tgt, 0, sizeof(struct request_target_t)); |
2940 | 2943 | tgt.namespace = URL_NS_PRINCIPAL; |
2941 | r = aparams->parse_path(uri->path, &tgt, &errstr); | |
2944 | r = prin_parse_path(uri->path, &tgt, &errstr); | |
2942 | 2945 | if (!r && tgt.user) userid = tgt.user; |
2943 | 2946 | } |
2944 | 2947 | if (uri) xmlFreeURI(uri); |
3124 | 3127 | } |
3125 | 3128 | xmlFreeURI(dest_uri); |
3126 | 3129 | |
3127 | if (r) return r; | |
3130 | if (r) return HTTP_FORBIDDEN; | |
3128 | 3131 | |
3129 | 3132 | /* We don't yet handle COPY/MOVE on collections */ |
3130 | 3133 | if (!dest_tgt.resource) return HTTP_NOT_ALLOWED; |
3303 | 3306 | } |
3304 | 3307 | |
3305 | 3308 | if (get_preferences(txn) & PREFER_REP) flags |= PREFER_REP; |
3309 | if ((txn->meth == METH_MOVE) && (dest_mbox == src_mbox)) | |
3310 | flags |= NO_DUP_CHECK; | |
3306 | 3311 | |
3307 | 3312 | /* Parse, validate, and store the resource */ |
3308 | 3313 | ret = cparams->copy(txn, src_mbox, &src_rec, dest_mbox, dest_tgt.resource, |
4398 | 4403 | } |
4399 | 4404 | else if (!xmlStrcmp(cur->name, BAD_CAST "allprop")) { |
4400 | 4405 | 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 | } | |
4409 | 4406 | } |
4410 | 4407 | else if (!xmlStrcmp(cur->name, BAD_CAST "propname")) { |
4411 | 4408 | fctx.mode = PROPFIND_NAME; |
4418 | 4415 | else { |
4419 | 4416 | ret = HTTP_BAD_REQUEST; |
4420 | 4417 | 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 | } | |
4421 | 4433 | } |
4422 | 4434 | } |
4423 | 4435 | |
4738 | 4750 | /* Parse the path */ |
4739 | 4751 | if ((r = pparams->parse_path(txn->req_uri->path, |
4740 | 4752 | &txn->req_tgt, &txn->error.desc))) { |
4741 | return r; | |
4753 | return HTTP_FORBIDDEN; | |
4742 | 4754 | } |
4743 | 4755 | |
4744 | 4756 | /* Make sure method is allowed (only allowed on resources) */ |
4861 | 4873 | } |
4862 | 4874 | |
4863 | 4875 | /* 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); | |
4867 | 4878 | if (ret) { |
4868 | 4879 | txn->flags.conn = CONN_CLOSE; |
4869 | 4880 | goto done; |
4870 | 4881 | } |
4871 | 4882 | |
4872 | 4883 | /* Make sure we have a body */ |
4873 | size = buf_len(&txn->req_body); | |
4884 | size = buf_len(&txn->req_body.payload); | |
4874 | 4885 | if (!size) { |
4875 | 4886 | txn->error.desc = "Missing request body\r\n"; |
4876 | 4887 | ret = HTTP_BAD_REQUEST; |
4878 | 4889 | } |
4879 | 4890 | |
4880 | 4891 | /* 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))) { | |
4882 | 4894 | txn->error.desc = error_message(r); |
4883 | 4895 | ret = HTTP_SERVER_ERROR; |
4884 | 4896 | goto done; |
5113 | 5125 | int ret = 0, r; |
5114 | 5126 | const char **hdr; |
5115 | 5127 | unsigned depth = 0; |
5116 | xmlNodePtr inroot = NULL, outroot = NULL, cur, props = NULL; | |
5128 | xmlNodePtr inroot = NULL, outroot = NULL, cur, prop = NULL, props = NULL; | |
5117 | 5129 | const struct report_type_t *report = NULL; |
5118 | 5130 | xmlNsPtr ns[NUM_NAMESPACE]; |
5119 | 5131 | struct hash_table ns_table = { 0, NULL, NULL }; |
5230 | 5242 | if (cur->type == XML_ELEMENT_NODE) { |
5231 | 5243 | if (!xmlStrcmp(cur->name, BAD_CAST "allprop")) { |
5232 | 5244 | fctx.mode = PROPFIND_ALL; |
5245 | prop = cur; | |
5233 | 5246 | break; |
5234 | 5247 | } |
5235 | 5248 | else if (!xmlStrcmp(cur->name, BAD_CAST "propname")) { |
5236 | 5249 | fctx.mode = PROPFIND_NAME; |
5237 | 5250 | fctx.prefer = PREFER_MIN; /* Don't want 404 (Not Found) */ |
5251 | prop = cur; | |
5238 | 5252 | break; |
5239 | 5253 | } |
5240 | 5254 | else if (!xmlStrcmp(cur->name, BAD_CAST "prop")) { |
5241 | 5255 | fctx.mode = PROPFIND_PROP; |
5256 | prop = cur; | |
5242 | 5257 | props = cur->children; |
5243 | 5258 | break; |
5244 | 5259 | } |
5245 | 5260 | } |
5246 | 5261 | } |
5247 | 5262 | |
5248 | if (!props && (report->flags & REPORT_NEED_PROPS)) { | |
5263 | if (!prop && (report->flags & REPORT_NEED_PROPS)) { | |
5249 | 5264 | txn->error.desc = "Missing <prop> element in REPORT\r\n"; |
5250 | 5265 | ret = HTTP_BAD_REQUEST; |
5251 | 5266 | goto done; |
235 | 235 | PREFER_REP = (1<<1), |
236 | 236 | PREFER_NOROOT = (1<<2) |
237 | 237 | }; |
238 | ||
239 | #define NO_DUP_CHECK (1<<7) | |
240 | ||
238 | 241 | |
239 | 242 | /* Function to lookup DAV 'resource' in 'mailbox', with optional 'lock', |
240 | 243 | * placing the record in 'data' |
138 | 138 | }; |
139 | 139 | |
140 | 140 | |
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 | ||
164 | 141 | /* iSchedule Receiver Capabilities */ |
165 | 142 | static int meth_get_isched(struct transaction_t *txn, |
166 | 143 | void *params __attribute__((unused))) |
326 | 303 | } |
327 | 304 | |
328 | 305 | /* 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); | |
332 | 308 | if (r) { |
333 | 309 | txn->flags.conn = CONN_CLOSE; |
334 | 310 | return r; |
335 | 311 | } |
336 | 312 | |
337 | 313 | /* Make sure we have a body */ |
338 | if (!buf_len(&txn->req_body)) { | |
314 | if (!buf_len(&txn->req_body.payload)) { | |
339 | 315 | txn->error.desc = "Missing request body\r\n"; |
340 | 316 | return HTTP_BAD_REQUEST; |
341 | 317 | } |
360 | 336 | } |
361 | 337 | |
362 | 338 | /* 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)); | |
364 | 340 | if (!ical || !icalrestriction_check(ical)) { |
365 | 341 | txn->error.precond = ISCHED_INVALID_DATA; |
366 | 342 | return HTTP_BAD_REQUEST; |
480 | 456 | icalcomponent *comp; |
481 | 457 | icalcomponent_kind kind; |
482 | 458 | icalproperty *prop; |
483 | unsigned code, close; | |
459 | unsigned code; | |
484 | 460 | struct transaction_t txn; |
485 | 461 | |
486 | 462 | *xml = NULL; |
610 | 586 | prot_write(be->out, body, bodylen); |
611 | 587 | |
612 | 588 | /* 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, | |
614 | 591 | &txn.req_hdrs, &txn.req_body, &txn.error.desc); |
615 | 592 | if (!r) { |
616 | 593 | switch (code) { |
631 | 608 | } |
632 | 609 | |
633 | 610 | if (txn.req_hdrs) spool_free_hdrcache(txn.req_hdrs); |
634 | buf_free(&txn.req_body); | |
611 | buf_free(&txn.req_body.payload); | |
635 | 612 | |
636 | 613 | return r; |
637 | 614 | } |
771 | 748 | dkim_cachehdr(NULL, NULL, dkim); /* Force canon of last header */ |
772 | 749 | stat = dkim_eoh(dkim); |
773 | 750 | 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)); | |
776 | 753 | stat = dkim_eom(dkim, NULL); |
777 | 754 | } |
778 | 755 | |
836 | 813 | return; |
837 | 814 | } |
838 | 815 | |
839 | calc_compile_time(); | |
816 | compile_time = calc_compile_time(__TIME__, __DATE__); | |
840 | 817 | |
841 | 818 | if (config_mupdate_server && config_getstring(IMAPOPT_PROXYSERVERS)) { |
842 | 819 | /* If backend server, we require ISCHEDULE (w/o DKIM) */ |
125 | 125 | static int login(struct backend *s, const char *userid, |
126 | 126 | sasl_callback_t *cb, const char **status) |
127 | 127 | { |
128 | int r = 0, local_cb = 0; | |
128 | int r = 0; | |
129 | 129 | socklen_t addrsize; |
130 | 130 | struct sockaddr_storage saddr_l, saddr_r; |
131 | 131 | char remoteip[60], localip[60]; |
156 | 156 | |
157 | 157 | /* Create callbacks, if necessary */ |
158 | 158 | if (!cb) { |
159 | local_cb = 1; | |
160 | 159 | buf_setmap(&buf, s->hostname, strcspn(s->hostname, ".")); |
161 | 160 | buf_appendcstr(&buf, "_password"); |
162 | 161 | pass = config_getoverflowstring(buf_cstring(&buf), NULL); |
165 | 164 | config_getstring(IMAPOPT_PROXY_AUTHNAME), |
166 | 165 | config_getstring(IMAPOPT_PROXY_REALM), |
167 | 166 | pass); |
168 | } | |
169 | ||
170 | /* Require proxying if we have an "interesting" userid (authzid) */ | |
167 | s->sasl_cb = cb; | |
168 | } | |
169 | ||
170 | /* Create SASL context */ | |
171 | 171 | 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); | |
175 | 173 | if (r != SASL_OK) goto done; |
176 | 174 | |
177 | 175 | r = sasl_setprop(s->saslconn, SASL_SEC_PROPS, &secprops); |
189 | 187 | unsigned code; |
190 | 188 | const char **hdr, *errstr, *serverin; |
191 | 189 | char base64[BASE64_BUF_SIZE+1]; |
192 | unsigned int serverinlen, non_persist; | |
190 | unsigned int serverinlen; | |
191 | struct body_t resp_body; | |
193 | 192 | #ifdef SASL_HTTP_REQUEST |
194 | 193 | sasl_http_request_t httpreq = { "OPTIONS", /* Method */ |
195 | 194 | "*", /* URI */ |
232 | 231 | |
233 | 232 | /* Read response(s) from backend until final response or error */ |
234 | 233 | 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); | |
237 | 237 | if (r) { |
238 | 238 | if (status) *status = errstr; |
239 | 239 | break; |
302 | 302 | for (scheme = auth_schemes; scheme->name; scheme++) { |
303 | 303 | if (!strncmp(scheme->name, hdr[i], len) && |
304 | 304 | !((scheme->flags & AUTH_NEED_PERSIST) && |
305 | non_persist)) { | |
305 | (resp_body.flags & BODY_CLOSE))) { | |
306 | 306 | /* Tag the scheme as available */ |
307 | 307 | avail_auth_schemes |= (1 << scheme->idx); |
308 | 308 | |
337 | 337 | |
338 | 338 | #ifdef SASL_HTTP_REQUEST |
339 | 339 | /* Set HTTP request as specified above (REQUIRED) */ |
340 | httpreq.non_persist = non_persist; | |
340 | httpreq.non_persist = (resp_body.flags & BODY_CLOSE); | |
341 | 341 | sasl_setprop(s->saslconn, SASL_HTTP_REQUEST, &httpreq); |
342 | 342 | #endif |
343 | 343 | |
414 | 414 | } while (need_tls || clientout); |
415 | 415 | |
416 | 416 | done: |
417 | if (local_cb) free_callbacks(cb); | |
418 | 417 | if (hdrs) spool_free_hdrcache(hdrs); |
419 | 418 | |
420 | 419 | if (r && status && !*status) *status = sasl_errstring(r, NULL, NULL); |
425 | 424 | |
426 | 425 | static int ping(struct backend *s, const char *userid) |
427 | 426 | { |
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); | |
429 | 452 | } |
430 | 453 | |
431 | 454 | |
471 | 494 | } |
472 | 495 | |
473 | 496 | |
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 | ||
474 | 527 | /* Construct and write Via header to protstream. */ |
475 | 528 | static void write_forwarding_hdrs(struct protstream *pout, hdrcache_t hdrs, |
476 | 529 | const char *version, const char *proto) |
543 | 596 | |
544 | 597 | |
545 | 598 | /* 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) | |
549 | 602 | { |
550 | 603 | static char statbuf[2048]; |
551 | 604 | const char **conn; |
553 | 606 | |
554 | 607 | if (statline) *statline = statbuf; |
555 | 608 | *errstr = NULL; |
556 | *close = 0; | |
557 | 609 | *code = HTTP_BAD_GATEWAY; |
558 | 610 | |
559 | 611 | if (*hdrs) spool_free_hdrcache(*hdrs); |
573 | 625 | if (*code < 200) return 0; |
574 | 626 | |
575 | 627 | /* Final response */ |
576 | if (body) buf_reset(body); | |
628 | if (!(body->flags & BODY_DISCARD)) buf_reset(&body->payload); | |
577 | 629 | |
578 | 630 | /* Check connection persistence */ |
579 | *close = !strncmp(statbuf, "HTTP/1.0 ", 9); | |
631 | if (!strncmp(statbuf, "HTTP/1.0 ", 9)) body->flags |= BODY_CLOSE; | |
580 | 632 | for (conn = spool_getheader(*hdrs, "Connection"); conn && *conn; conn++) { |
581 | 633 | tok_t tok = |
582 | 634 | TOK_INITIALIZER(*conn, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT); |
583 | 635 | char *token; |
584 | 636 | |
585 | 637 | 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; | |
588 | 640 | } |
589 | 641 | tok_fini(&tok); |
590 | 642 | } |
599 | 651 | if (meth == METH_HEAD) break; |
600 | 652 | |
601 | 653 | 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)) { | |
608 | 658 | return HTTP_BAD_GATEWAY; |
609 | 659 | } |
610 | 660 | } |
680 | 730 | { |
681 | 731 | int r = 0, sent_body = 0; |
682 | 732 | xmlChar *uri; |
683 | unsigned code, close; | |
733 | unsigned code; | |
684 | 734 | const char *statline; |
685 | 735 | hdrcache_t resp_hdrs = NULL; |
686 | struct buf *resp_body = &txn->resp_body.payload; | |
736 | struct body_t resp_body; | |
687 | 737 | |
688 | 738 | /* |
689 | 739 | * Send client request to backend: |
716 | 766 | prot_flush(be->out); |
717 | 767 | |
718 | 768 | /* Read response(s) from backend until final response or error */ |
769 | resp_body.flags = 0; | |
770 | resp_body.payload = txn->resp_body.payload; | |
771 | ||
719 | 772 | 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); | |
722 | 775 | if (r) break; |
723 | 776 | |
724 | 777 | if ((code == 100) /* Continue */ && !sent_body++) { |
726 | 779 | |
727 | 780 | /* Read body from client */ |
728 | 781 | r = read_body(httpd_in, txn->req_hdrs, &txn->req_body, |
729 | &txn->flags.body, &txn->error.desc); | |
782 | &txn->error.desc); | |
730 | 783 | if (r) { |
731 | 784 | /* Couldn't get the body and can't finish request */ |
732 | 785 | txn->flags.conn = CONN_CLOSE; |
734 | 787 | } |
735 | 788 | |
736 | 789 | /* 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))) { | |
738 | 791 | 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); | |
740 | 793 | prot_puts(be->out, "\r\n"); |
741 | 794 | } |
742 | 795 | prot_puts(be->out, "0\r\n\r\n"); |
744 | 797 | } |
745 | 798 | } while (code < 200); |
746 | 799 | |
800 | txn->resp_body.payload = resp_body.payload; | |
801 | ||
747 | 802 | if (!r) { |
748 | 803 | /* 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); | |
753 | 809 | |
754 | 810 | if (resp_hdrs) spool_free_hdrcache(resp_hdrs); |
755 | 811 | |
769 | 825 | struct transaction_t *txn) |
770 | 826 | { |
771 | 827 | int r = 0; |
772 | unsigned code, close; | |
828 | unsigned code; | |
773 | 829 | char *etag = NULL, *lastmod = NULL;; |
774 | 830 | const char **hdr, *statline; |
775 | 831 | hdrcache_t resp_hdrs = NULL; |
776 | struct buf *resp_body = &txn->resp_body.payload; | |
832 | struct body_t resp_body; | |
777 | 833 | |
778 | 834 | /* |
779 | 835 | * Send a GET request to source backend to fetch body: |
794 | 850 | prot_flush(src_be->out); |
795 | 851 | |
796 | 852 | /* 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 | ||
797 | 856 | 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)) { | |
801 | 860 | proxy_downserver(src_be); |
802 | 861 | break; |
803 | 862 | } |
843 | 902 | prot_printf(dest_be->out, "Content-Encoding: %s\r\n", hdr[0]); |
844 | 903 | if ((hdr = spool_getheader(resp_hdrs, "Content-Language"))) |
845 | 904 | 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)); | |
847 | 907 | prot_puts(dest_be->out, "\r\n"); |
848 | 908 | prot_flush(dest_be->out); |
849 | 909 | |
850 | 910 | /* Read response(s) from dest backend until final response or error */ |
911 | resp_body.flags = 0; | |
912 | ||
851 | 913 | 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)) { | |
856 | 917 | proxy_downserver(dest_be); |
857 | 918 | break; |
858 | 919 | } |
859 | 920 | |
860 | 921 | if ((code == 100) /* Continue */ && !sent_body++) { |
861 | 922 | /* 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); | |
863 | 924 | prot_flush(dest_be->out); |
864 | 925 | } |
865 | 926 | } while (code < 200); |
866 | 927 | } |
928 | ||
929 | txn->resp_body.payload = resp_body.payload; | |
867 | 930 | |
868 | 931 | if (!r) { |
869 | 932 | /* 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); | |
871 | 935 | |
872 | 936 | if ((txn->meth == METH_MOVE) && (code < 300)) { |
873 | 937 | /* |
892 | 956 | prot_flush(src_be->out); |
893 | 957 | |
894 | 958 | /* Read response(s) from source backend until final resp or error */ |
959 | resp_body.flags = BODY_DISCARD; | |
960 | ||
895 | 961 | 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)) { | |
900 | 965 | proxy_downserver(src_be); |
901 | 966 | break; |
902 | 967 | } |
50 | 50 | |
51 | 51 | extern int http_mlookup(const char *name, |
52 | 52 | char **server, char **aclp, void *tid); |
53 | extern void http_proto_host(hdrcache_t req_hdrs, | |
54 | const char **proto, const char **host); | |
53 | 55 | extern int http_pipe_req_resp(struct backend *be, struct transaction_t *txn); |
54 | 56 | extern int http_proxy_copy(struct backend *src_be, struct backend *dest_be, |
55 | 57 | 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, | |
58 | 59 | const char **statline, hdrcache_t *hdrs, |
59 | struct buf *body, const char **errstr); | |
60 | struct body_t *body, const char **errstr); | |
60 | 61 | |
61 | 62 | #endif /* _HTTP_PROXY_H */ |
130 | 130 | } |
131 | 131 | }; |
132 | 132 | |
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 | ||
155 | 133 | |
156 | 134 | static void rss_init(struct buf *serverinfo __attribute__((unused))) |
157 | 135 | { |
159 | 137 | |
160 | 138 | if (!namespace_rss.enabled) return; |
161 | 139 | |
162 | calc_compile_time(); | |
140 | compile_time = calc_compile_time(__TIME__, __DATE__); | |
163 | 141 | } |
164 | 142 | |
165 | 143 | /* Perform a GET/HEAD request */ |
492 | 470 | const char *template_file = config_getstring(IMAPOPT_RSS_FEEDLIST_TEMPLATE); |
493 | 471 | const char *var = NULL, *template = NULL, *prefix, *suffix; |
494 | 472 | unsigned long template_len = 0, prefix_len, suffix_len; |
495 | unsigned varlen = strlen(FEEDLIST_VAR); | |
473 | size_t varlen = strlen(FEEDLIST_VAR); | |
496 | 474 | int fd = -1; |
497 | 475 | struct message_guid guid; |
498 | 476 | time_t lastmod; |
506 | 484 | if (template_file) { |
507 | 485 | /* See if template exists and contains feedlist variable */ |
508 | 486 | if (!stat(template_file, &sbuf) && S_ISREG(sbuf.st_mode) && |
509 | sbuf.st_size >= varlen && | |
487 | (size_t) sbuf.st_size >= varlen && | |
510 | 488 | (fd = open(template_file, O_RDONLY)) != -1) { |
511 | 489 | const char *p; |
512 | 490 | unsigned long len; |
739 | 717 | /* List messages as an RSS feed */ |
740 | 718 | static int list_messages(struct transaction_t *txn, struct mailbox *mailbox) |
741 | 719 | { |
742 | const char **fwd, *proto = NULL, *host = NULL; | |
720 | const char *proto = NULL, *host = NULL; | |
743 | 721 | uint32_t url_len, recno, recentuid = 0; |
744 | 722 | int max_age, max_items, max_len, nitems, precond; |
745 | 723 | time_t age_mark = 0, lastmod; |
829 | 807 | mboxname_hiersep_toexternal(&httpd_namespace, mboxname, 0); |
830 | 808 | |
831 | 809 | /* 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); | |
855 | 811 | assert(!buf_len(url)); |
856 | 812 | buf_printf(url, "%s://%s%s", proto, host, txn->req_uri->path); |
857 | 813 | url_len = buf_len(url); |
91 | 91 | #include "rfc822date.h" |
92 | 92 | #include "tok.h" |
93 | 93 | #include "wildmat.h" |
94 | #include "md5.h" | |
94 | 95 | |
95 | 96 | #ifdef WITH_DAV |
96 | 97 | #include "http_dav.h" |
206 | 207 | static struct accept *parse_accept(const char **hdr); |
207 | 208 | static int parse_ranges(const char *hdr, unsigned long len, |
208 | 209 | 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); | |
209 | 214 | static int http_auth(const char *creds, struct transaction_t *txn); |
210 | 215 | static void keep_alive(int sig); |
211 | 216 | |
934 | 939 | txn.req_uri = NULL; |
935 | 940 | txn.auth_chal.param = NULL; |
936 | 941 | txn.req_hdrs = NULL; |
942 | txn.req_body.flags = 0; | |
937 | 943 | txn.location = NULL; |
938 | 944 | memset(&txn.error, 0, sizeof(struct error_t)); |
939 | 945 | memset(&txn.resp_body, 0, /* Don't zero the response payload buffer */ |
1087 | 1093 | |
1088 | 1094 | if (txn.meth == METH_UNKNOWN) ret = HTTP_NOT_IMPLEMENTED; |
1089 | 1095 | |
1090 | /* Check for Expectations (HTTP/1.1+ only) */ | |
1091 | else if (!txn.flags.ver1_0 && (r = parse_expect(&txn))) ret = r; | |
1092 | ||
1093 | 1096 | /* Parse request-target URI */ |
1094 | 1097 | else if (!(txn.req_uri = parse_uri(txn.meth, req_line->uri, 1, |
1095 | 1098 | &txn.error.desc))) { |
1096 | 1099 | 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; | |
1097 | 1111 | } |
1098 | 1112 | |
1099 | 1113 | /* Check for mandatory Host header (HTTP/1.1+ only) */ |
1164 | 1178 | meth_t = &namespace->methods[txn.meth]; |
1165 | 1179 | if (!meth_t->proc) ret = HTTP_NOT_ALLOWED; |
1166 | 1180 | |
1167 | /* Check if method doesn't expect a body */ | |
1181 | /* Check if method expects a body */ | |
1168 | 1182 | else if ((http_methods[txn.meth].flags & METH_NOBODY) && |
1169 | (spool_getheader(txn.req_hdrs, "Transfer-Encoding") || | |
1183 | (txn.req_body.framing != FRAMING_LENGTH || | |
1170 | 1184 | /* 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)) { | |
1173 | 1186 | ret = HTTP_BAD_MEDIATYPE; |
1187 | } | |
1174 | 1188 | } else { |
1175 | 1189 | /* XXX Should never get here */ |
1176 | 1190 | ret = HTTP_SERVER_ERROR; |
1183 | 1197 | if (httpd_userid) { |
1184 | 1198 | /* Reauth - reinitialize */ |
1185 | 1199 | 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 | } | |
1194 | 1200 | reset_saslconn(&httpd_saslconn); |
1195 | 1201 | txn.auth_chal.scheme = NULL; |
1196 | 1202 | } |
1210 | 1216 | syslog(LOG_DEBUG, "client didn't complete auth - reinit"); |
1211 | 1217 | reset_saslconn(&httpd_saslconn); |
1212 | 1218 | 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 | } | |
1213 | 1238 | } |
1214 | 1239 | |
1215 | 1240 | /* Request authentication, if necessary */ |
1351 | 1376 | /* Handle errors (success responses handled by method functions) */ |
1352 | 1377 | if (ret) error_response(ret, &txn); |
1353 | 1378 | |
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 | ||
1354 | 1388 | /* Memory cleanup */ |
1355 | 1389 | if (txn.req_uri) xmlFreeURI(txn.req_uri); |
1356 | 1390 | if (txn.req_hdrs) spool_free_hdrcache(txn.req_hdrs); |
1357 | 1391 | |
1358 | 1392 | if (txn.flags.conn & CONN_CLOSE) { |
1359 | 1393 | buf_free(&txn.buf); |
1360 | buf_free(&txn.req_body); | |
1394 | buf_free(&txn.req_body.payload); | |
1361 | 1395 | buf_free(&txn.resp_body.payload); |
1362 | 1396 | #ifdef HAVE_ZLIB |
1363 | 1397 | deflateEnd(&txn.zstrm); |
1435 | 1469 | } |
1436 | 1470 | |
1437 | 1471 | |
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 | ||
1438 | 1593 | /* |
1439 | 1594 | * Read the body of a request or response. |
1440 | 1595 | * Handles chunked, gzip, deflate TE only. |
1441 | 1596 | * Handles close-delimited response bodies (no Content-Length specified) |
1442 | 1597 | * Handles gzip and deflate CE only. |
1443 | 1598 | */ |
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 | { | |
1451 | 1602 | 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) { | |
1460 | 1613 | /* Don't care about the body and client hasn't sent it, we're done */ |
1461 | 1614 | return 0; |
1462 | 1615 | } |
1463 | 1616 | |
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) { | |
1542 | 1624 | /* Tell client to send the body */ |
1543 | 1625 | response_header(HTTP_CONTINUE, NULL); |
1544 | 1626 | } |
1545 | 1627 | |
1546 | 1628 | /* 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 | { | |
1548 | 1649 | unsigned last = 0; |
1549 | 1650 | |
1550 | 1651 | /* Read chunks until last-chunk (zero chunk-size) */ |
1556 | 1657 | sscanf(buf, "%x", &chunk) != 1) { |
1557 | 1658 | *errstr = "Unable to read chunk size"; |
1558 | 1659 | 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; | |
1561 | 1664 | |
1562 | 1665 | if (!chunk) { |
1563 | 1666 | /* last-chunk */ |
1569 | 1672 | |
1570 | 1673 | /* Read 'chunk' octets */ |
1571 | 1674 | 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); | |
1574 | 1679 | |
1575 | 1680 | if (!n) { |
1576 | 1681 | syslog(LOG_ERR, "prot_read() error"); |
1577 | 1682 | *errstr = "Unable to read chunk data"; |
1578 | 1683 | goto read_failure; |
1579 | 1684 | } |
1685 | body->len += n; | |
1580 | 1686 | } |
1581 | 1687 | |
1582 | 1688 | /* Read CRLF terminating the chunk/trailer */ |
1587 | 1693 | |
1588 | 1694 | } while (!last); |
1589 | 1695 | |
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: | |
1593 | 1702 | /* Read until EOF */ |
1594 | 1703 | 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; | |
1599 | 1711 | |
1600 | 1712 | } while (n); |
1601 | 1713 | |
1602 | 1714 | 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)) { | |
1622 | 1726 | #ifdef HAVE_ZLIB |
1623 | 1727 | /* 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); | |
1628 | 1732 | |
1629 | 1733 | if (r) { |
1630 | 1734 | *errstr = "Error decoding payload"; |
1633 | 1737 | #endif |
1634 | 1738 | |
1635 | 1739 | /* Decode the representation, if necessary */ |
1636 | if (*flags & BODY_DECODE) { | |
1740 | if (body->flags & BODY_DECODE) { | |
1741 | const char **hdr; | |
1742 | ||
1637 | 1743 | if (!(hdr = spool_getheader(hdrs, "Content-Encoding"))) { |
1638 | 1744 | /* nothing to see here */ |
1639 | 1745 | } |
1644 | 1750 | |
1645 | 1751 | /* Try to detect Microsoft's broken deflate */ |
1646 | 1752 | if (ua && strstr(ua[0], "; MSIE ")) |
1647 | r = buf_inflate(body, DEFLATE_RAW); | |
1753 | r = buf_inflate(&body->payload, DEFLATE_RAW); | |
1648 | 1754 | else |
1649 | r = buf_inflate(body, DEFLATE_ZLIB); | |
1755 | r = buf_inflate(&body->payload, DEFLATE_ZLIB); | |
1650 | 1756 | } |
1651 | 1757 | else if (!strcasecmp(hdr[0], "gzip") || |
1652 | 1758 | !strcasecmp(hdr[0], "x-gzip")) |
1653 | r = buf_inflate(body, DEFLATE_GZIP); | |
1759 | r = buf_inflate(&body->payload, DEFLATE_GZIP); | |
1654 | 1760 | #endif |
1655 | 1761 | else { |
1656 | 1762 | *errstr = "Specified Content-Encoding not accepted"; |
1683 | 1789 | const char **exp = spool_getheader(txn->req_hdrs, "Expect"); |
1684 | 1790 | int i, ret = 0; |
1685 | 1791 | |
1792 | /* Expect not supported by HTTP/1.0 clients */ | |
1793 | if (exp && txn->flags.ver1_0) return HTTP_EXPECT_FAILED; | |
1794 | ||
1686 | 1795 | /* Look for interesting expectations. Unknown == error */ |
1687 | 1796 | for (i = 0; !ret && exp && exp[i]; i++) { |
1688 | 1797 | tok_t tok = TOK_INITIALIZER(exp[i], ",", TOK_TRIMLEFT|TOK_TRIMRIGHT); |
1692 | 1801 | /* Check if this is a non-persistent connection */ |
1693 | 1802 | if (!strcasecmp(token, "100-continue")) { |
1694 | 1803 | syslog(LOG_DEBUG, "Expect: 100-continue"); |
1695 | txn->flags.body |= BODY_CONTINUE; | |
1804 | txn->req_body.flags |= BODY_CONTINUE; | |
1696 | 1805 | } |
1697 | 1806 | else { |
1698 | 1807 | txn->error.desc = "Unsupported Expectation"; |
1901 | 2010 | } |
1902 | 2011 | } |
1903 | 2012 | |
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 | ||
1904 | 2025 | void response_header(long code, struct transaction_t *txn) |
1905 | 2026 | { |
1906 | 2027 | time_t now; |
1910 | 2031 | struct auth_challenge_t *auth_chal; |
1911 | 2032 | struct resp_body_t *resp_body; |
1912 | 2033 | 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 | ||
1921 | 2034 | |
1922 | 2035 | /* Stop method processing alarm */ |
1923 | 2036 | alarm(0); |
2165 | 2278 | if (resp_body->type) { |
2166 | 2279 | prot_printf(httpd_out, "Content-Type: %s\r\n", resp_body->type); |
2167 | 2280 | |
2281 | if (resp_body->fname) { | |
2282 | prot_printf(httpd_out, | |
2283 | "Content-Disposition: inline; filename=\"%s\"\r\n", | |
2284 | resp_body->fname); | |
2285 | } | |
2168 | 2286 | if (txn->resp_body.enc) { |
2169 | 2287 | /* Construct Content-Encoding header */ |
2170 | 2288 | const char *ce[] = |
2178 | 2296 | if (resp_body->loc) { |
2179 | 2297 | prot_printf(httpd_out, "Content-Location: %s\r\n", resp_body->loc); |
2180 | 2298 | if (txn->flags.cors) Access_Control_Expose("Content-Location"); |
2299 | } | |
2300 | if (resp_body->md5) { | |
2301 | Content_MD5(resp_body->md5); | |
2181 | 2302 | } |
2182 | 2303 | } |
2183 | 2304 | |
2220 | 2341 | { "deflate", "gzip", "chunked", NULL }; |
2221 | 2342 | |
2222 | 2343 | 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 | } | |
2223 | 2352 | } |
2224 | 2353 | } |
2225 | 2354 | else prot_printf(httpd_out, "Content-Length: %lu\r\n", resp_body->len); |
2322 | 2451 | { |
2323 | 2452 | unsigned is_dynamic = code ? (txn->flags.te & TE_CHUNKED) : 1; |
2324 | 2453 | 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]; | |
2325 | 2457 | |
2326 | 2458 | if (!is_dynamic && len < GZIP_MIN_LEN) { |
2327 | 2459 | /* Don't compress small static content */ |
2363 | 2495 | |
2364 | 2496 | if (code) { |
2365 | 2497 | /* Initial call - prepare response header based on CE, TE and version */ |
2498 | if (do_md5) MD5_Init(&ctx); | |
2366 | 2499 | |
2367 | 2500 | if (txn->flags.te & ~TE_CHUNKED) { |
2368 | 2501 | /* Transfer-Encoded content MUST be chunked */ |
2416 | 2549 | break; |
2417 | 2550 | } |
2418 | 2551 | } |
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 | } | |
2419 | 2558 | } |
2420 | 2559 | else if (txn->flags.ver1_0) { |
2421 | 2560 | /* HTTP/1.0 doesn't support chunked - close-delimit the body */ |
2422 | 2561 | txn->flags.conn = CONN_CLOSE; |
2423 | 2562 | } |
2563 | else if (do_md5) txn->flags.trailer = TRAILER_CMD5; | |
2424 | 2564 | |
2425 | 2565 | response_header(code, txn); |
2426 | 2566 | |
2445 | 2585 | prot_printf(httpd_out, "%x\r\n", outlen); |
2446 | 2586 | prot_write(httpd_out, buf, outlen); |
2447 | 2587 | prot_puts(httpd_out, "\r\n"); |
2588 | ||
2589 | if (do_md5) MD5_Update(&ctx, buf, outlen); | |
2448 | 2590 | } |
2449 | 2591 | if (!len) { |
2450 | 2592 | /* Terminate the HTTP/1.1 body with a zero-length chunk */ |
2451 | 2593 | prot_puts(httpd_out, "0\r\n"); |
2452 | 2594 | |
2453 | /* Empty trailer */ | |
2595 | /* Trailer */ | |
2596 | if (do_md5) { | |
2597 | MD5_Final(md5, &ctx); | |
2598 | Content_MD5(md5); | |
2599 | } | |
2600 | ||
2454 | 2601 | prot_puts(httpd_out, "\r\n"); |
2455 | 2602 | } |
2456 | 2603 | } |
2622 | 2769 | } |
2623 | 2770 | |
2624 | 2771 | |
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 | ||
2625 | 2829 | /* Write cached header (redacting authorization credentials) to buffer. */ |
2626 | 2830 | static void log_cachehdr(const char *name, const char *contents, void *rock) |
2627 | 2831 | { |
2641 | 2845 | } |
2642 | 2846 | |
2643 | 2847 | |
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 | ||
2644 | 2914 | /* Perform HTTP Authentication based on the given credentials ('creds'). |
2645 | 2915 | * Returns the selected auth scheme and any server challenge in 'chal'. |
2646 | 2916 | * May be called multiple times if auth scheme requires multiple steps. |
2653 | 2923 | struct auth_challenge_t *chal = &txn->auth_chal; |
2654 | 2924 | static int status = SASL_OK; |
2655 | 2925 | int slen; |
2656 | const char *clientin = NULL, *realm = NULL, *user; | |
2926 | const char *clientin = NULL, *realm = NULL, *user, **authzid; | |
2657 | 2927 | unsigned int clientinlen = 0; |
2658 | 2928 | struct auth_scheme_t *scheme; |
2659 | 2929 | static char base64[BASE64_BUF_SIZE+1]; |
2660 | 2930 | const void *canon_user; |
2661 | const char **authzid = spool_getheader(txn->req_hdrs, "Authorize-As"); | |
2662 | int i; | |
2663 | ||
2664 | chal->param = NULL; | |
2665 | 2931 | |
2666 | 2932 | /* Split credentials into auth scheme and response */ |
2667 | 2933 | slen = strcspn(creds, " \0"); |
2668 | 2934 | if ((clientin = strchr(creds, ' '))) clientinlen = strlen(++clientin); |
2669 | 2935 | |
2670 | 2936 | 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'", | |
2672 | 2938 | 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; | |
2675 | 2951 | |
2676 | 2952 | if (chal->scheme) { |
2677 | 2953 | /* Use current scheme, if possible */ |
2851 | 3127 | syslog(LOG_ERR, "weird SASL error %d getting SASL_USERNAME", status); |
2852 | 3128 | return status; |
2853 | 3129 | } |
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"); | |
2855 | 3136 | if (authzid && *authzid[0]) { |
2856 | 3137 | /* 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); | |
2953 | 3147 | |
2954 | 3148 | return status; |
2955 | 3149 | } |
3555 | 3749 | |
3556 | 3750 | #ifdef WITH_DAV |
3557 | 3751 | /* 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/")) { | |
3559 | 3754 | 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); | |
3560 | 3760 | |
3561 | 3761 | txn->req_tgt.allow |= ALLOW_DAV; |
3562 | 3762 | return meth_propfind(txn, NULL); |
237 | 237 | struct range *next; |
238 | 238 | }; |
239 | 239 | |
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 | ||
240 | 259 | /* Meta-data for response body (payload & representation headers) */ |
241 | 260 | struct resp_body_t { |
242 | 261 | ulong len; /* Content-Length */ |
243 | 262 | struct range *range;/* Content-Range */ |
263 | const char *fname; /* Content-Dispo */ | |
244 | 264 | unsigned char enc; /* Content-Encoding */ |
245 | 265 | const char *lang; /* Content-Language */ |
246 | 266 | const char *loc; /* Content-Location */ |
267 | const u_char *md5; /* Content-MD5 */ | |
247 | 268 | const char *type; /* Content-Type */ |
248 | 269 | unsigned prefs; /* Prefer */ |
249 | 270 | const char *lock; /* Lock-Token */ |
260 | 281 | unsigned char ver1_0; /* Request from HTTP/1.0 client */ |
261 | 282 | unsigned char conn; /* Connection opts on req/resp */ |
262 | 283 | unsigned char cors; /* Cross-Origin Resource Sharing */ |
263 | unsigned char body; /* read_body() flags on req */ | |
264 | 284 | unsigned char te; /* Transfer-Encoding for resp */ |
265 | 285 | unsigned char cc; /* Cache-Control directives for resp */ |
266 | 286 | unsigned char ranges; /* Accept range requests for resource */ |
267 | 287 | unsigned char vary; /* Headers on which response varied */ |
288 | unsigned char trailer; /* Headers which will be in trailer */ | |
268 | 289 | }; |
269 | 290 | |
270 | 291 | /* Transaction context */ |
275 | 296 | xmlURIPtr req_uri; /* Parsed request-target URI */ |
276 | 297 | struct request_target_t req_tgt; /* Parsed request-target path */ |
277 | 298 | hdrcache_t req_hdrs; /* Cached HTTP headers */ |
278 | struct buf req_body; /* Buffered request body */ | |
299 | struct body_t req_body; /* Buffered request body */ | |
279 | 300 | struct auth_challenge_t auth_chal; /* Authentication challenge */ |
280 | 301 | const char *location; /* Location of resource */ |
281 | 302 | struct error_t error; /* Error response meta-data */ |
320 | 341 | BODY_CONTINUE = (1<<1), /* Expect:100-continue request */ |
321 | 342 | BODY_CLOSE = (1<<1), /* Close-delimited response body */ |
322 | 343 | 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 */ | |
324 | 346 | }; |
325 | 347 | |
326 | 348 | /* Transfer-Encoding flags (coding of response payload) */ |
354 | 376 | VARY_AE = (1<<0), /* Accept-Encoding */ |
355 | 377 | VARY_BRIEF = (1<<1), |
356 | 378 | VARY_PREFER = (1<<2) |
379 | }; | |
380 | ||
381 | /* Trailer header flags */ | |
382 | enum { | |
383 | TRAILER_CMD5 = (1<<0) /* Content-MD5 */ | |
357 | 384 | }; |
358 | 385 | |
359 | 386 | typedef int (*method_proc_t)(struct transaction_t *txn, void *params); |
414 | 441 | extern xmlURIPtr parse_uri(unsigned meth, const char *uri, unsigned path_reqd, |
415 | 442 | const char **errstr); |
416 | 443 | extern int is_mediatype(const char *hdr, const char *type); |
444 | extern time_t calc_compile_time(const char *time, const char *date); | |
417 | 445 | extern int http_mailbox_open(const char *name, struct mailbox **mailbox, |
418 | 446 | int locktype); |
419 | 447 | extern const char *http_statusline(long code); |
436 | 464 | extern int etagcmp(const char *hdr, const char *etag); |
437 | 465 | extern int check_precond(struct transaction_t *txn, const void *data, |
438 | 466 | 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); | |
441 | 469 | |
442 | 470 | #endif /* HTTPD_H */ |
2349 | 2349 | /* new files */ |
2350 | 2350 | fname = mailbox_meta_newfname(mailbox, META_INDEX); |
2351 | 2351 | 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 | } | |
2353 | 2356 | |
2354 | 2357 | fname = mailbox_meta_newfname(mailbox, META_CACHE); |
2355 | 2358 | 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 | } | |
2357 | 2363 | |
2358 | 2364 | /* update the generation number */ |
2359 | 2365 | repack->i.generation_no++; |
2366 | 2372 | repack->i.answered = 0; |
2367 | 2373 | repack->i.deleted = 0; |
2368 | 2374 | repack->i.flagged = 0; |
2369 | repack->i.exists = 0; | |
2375 | repack->i.exists = 0; | |
2370 | 2376 | repack->i.first_expunged = 0; |
2371 | 2377 | repack->i.leaked_cache_records = 0; |
2372 | 2378 | |
2378 | 2384 | if (n == -1) goto fail; |
2379 | 2385 | |
2380 | 2386 | n = retry_write(repack->newindex_fd, buf, INDEX_HEADER_SIZE); |
2381 | if (n == -1) goto fail; | |
2382 | ||
2387 | if (n == -1) goto fail; | |
2388 | ||
2383 | 2389 | *repackptr = repack; |
2384 | 2390 | return 0; |
2385 | 2391 |
325 | 325 | } |
326 | 326 | no_expunge: |
327 | 327 | |
328 | mailbox_repack_setup(mailbox, &repack); | |
328 | r = mailbox_repack_setup(mailbox, &repack); | |
329 | if (r) goto fail; | |
329 | 330 | |
330 | 331 | /* Write the rest of new index */ |
331 | 332 | recno = 1; |
452 | 452 | |
453 | 453 | if(mytid) { |
454 | 454 | /* transaction present, this means we do the slow way */ |
455 | if (!savebuf || keylen > savebuflen) { | |
455 | if (!savebuf || keylen >= savebuflen) { | |
456 | 456 | int dblsize = 2 * savebuflen; |
457 | 457 | int addsize = keylen + 32; |
458 | 458 |
470 | 470 | Note that any path specified by "rss_feedlist_template" is an |
471 | 471 | exception to this rule.*/ |
472 | 472 | |
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 | ||
473 | 480 | { "httpdocroot", NULL, STRING } |
474 | 481 | /* If set, http will serve the static content (html/text/jpeg/gif |
475 | 482 | files, etc) rooted at this directory. Otherwise, httpd will not |
220 | 220 | { { NULL, IMAP_ENUM_ZERO } } }, |
221 | 221 | { IMAPOPT_HTTPALLOWEDURLS, "httpallowedurls", 0, OPT_STRING, |
222 | 222 | {(void *)(NULL)}, |
223 | { { NULL, IMAP_ENUM_ZERO } } }, | |
224 | { IMAPOPT_HTTPCONTENTMD5, "httpcontentmd5", 0, OPT_SWITCH, | |
225 | {(void*)0}, | |
223 | 226 | { { NULL, IMAP_ENUM_ZERO } } }, |
224 | 227 | { IMAPOPT_HTTPDOCROOT, "httpdocroot", 0, OPT_STRING, |
225 | 228 | {(void *)(NULL)}, |
74 | 74 | IMAPOPT_HTTPALLOWCORS, |
75 | 75 | IMAPOPT_HTTPALLOWTRACE, |
76 | 76 | IMAPOPT_HTTPALLOWEDURLS, |
77 | IMAPOPT_HTTPCONTENTMD5, | |
77 | 78 | IMAPOPT_HTTPDOCROOT, |
78 | 79 | IMAPOPT_HTTPKEEPALIVE, |
79 | 80 | IMAPOPT_HTTPMODULES, |
412 | 412 | .PP |
413 | 413 | Note that any path specified by "rss_feedlist_template" is an |
414 | 414 | 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. | |
415 | 421 | .IP "\fBhttpdocroot:\fR <none>" 5 |
416 | 422 | If set, http will serve the static content (html/text/jpeg/gif |
417 | 423 | files, etc) rooted at this directory. Otherwise, httpd will not |
123 | 123 | .\" ======================================================================== |
124 | 124 | .\" |
125 | 125 | .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" | |
127 | 127 | .\" For nroff, turn off justification. Always turn off hyphenation; it makes |
128 | 128 | .\" way too many mistakes in technical documents. |
129 | 129 | .if n .ad l |
81 | 81 | if (strcmp(str,"active")==0) return TOKEN_ACTIVE; |
82 | 82 | if (strcmp(str,"referral")==0) return TOKEN_REFERRAL; |
83 | 83 | 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 | ||
85 | 94 | return -1; |
86 | 95 | } |
87 | 96 | |
266 | 275 | } |
267 | 276 | break; |
268 | 277 | case LEXER_STATE_ATOM: |
269 | if (!isalpha((unsigned char) ch)) { | |
278 | if (!(isalpha((unsigned char) ch) || ch == '/')) { | |
270 | 279 | int token; |
271 | 280 | |
272 | 281 | buffer[ buff_ptr - buffer] = '\0'; |
70 | 70 | TOKEN_ACTIVE = 291, |
71 | 71 | |
72 | 72 | 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 | |
74 | 83 | }; |
75 | 84 | |
76 | 85 | enum { |
116 | 116 | res = yylex(&state, pin); |
117 | 117 | } |
118 | 118 | if(res != ')') { |
119 | parseerror("expected RPARAN"); | |
119 | parseerror("expected RPAREN"); | |
120 | 120 | } |
121 | 121 | } |
122 | 122 | |
146 | 146 | res = yylex(&state, pin); |
147 | 147 | } |
148 | 148 | if(res != ')') { |
149 | parseerror("expected RPARAN"); | |
149 | parseerror("expected RPAREN"); | |
150 | 150 | } |
151 | 151 | |
152 | 152 | res = yylex(&state, pin); |