1 /* httpd.c -- HTTP/RSS/xDAV/JMAP/TZdist/iSchedule server protocol parsing
2  *
3  * Copyright (c) 1994-2017 Carnegie Mellon University.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * 3. The name "Carnegie Mellon University" must not be used to
18  *    endorse or promote products derived from this software without
19  *    prior written permission. For permission or any legal
20  *    details, please contact
21  *      Carnegie Mellon University
22  *      Center for Technology Transfer and Enterprise Creation
23  *      4615 Forbes Avenue
24  *      Suite 302
25  *      Pittsburgh, PA  15213
26  *      (412) 268-7393, fax: (412) 268-7395
27  *      innovation@andrew.cmu.edu
28  *
29  * 4. Redistributions of any form whatsoever must retain the following
30  *    acknowledgment:
31  *    "This product includes software developed by Computing Services
32  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33  *
34  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41  *
42  */
43 
44 #include <config.h>
45 
46 
47 #ifdef HAVE_UNISTD_H
48 #include <unistd.h>
49 #endif
50 #include <stdio.h>
51 #include <errno.h>
52 #include <string.h>
53 #include <fcntl.h>
54 #include <signal.h>
55 #include <sys/types.h>
56 #include <sys/param.h>
57 #include <sysexits.h>
58 #include <syslog.h>
59 #include <netdb.h>
60 #include <sys/socket.h>
61 #include <netinet/in.h>
62 #include <arpa/inet.h>
63 #include <ctype.h>
64 #include "prot.h"
65 
66 #include <sasl/sasl.h>
67 #include <sasl/saslutil.h>
68 #include <jansson.h>
69 
70 #include "httpd.h"
71 #include "http_h2.h"
72 #include "http_proxy.h"
73 #include "http_ws.h"
74 
75 #include "acl.h"
76 #include "assert.h"
77 #include "util.h"
78 #include "iptostring.h"
79 #include "global.h"
80 #include "tls.h"
81 #include "map.h"
82 
83 #include "imapd.h"
84 #include "proc.h"
85 #include "version.h"
86 #include "stristr.h"
87 #include "xstrlcpy.h"
88 #include "xstrlcat.h"
89 #include "telemetry.h"
90 #include "backend.h"
91 #include "prometheus.h"
92 #include "proxy.h"
93 #include "sync_support.h"
94 #include "userdeny.h"
95 #include "message.h"
96 #include "idle.h"
97 #include "times.h"
98 #include "tok.h"
99 #include "wildmat.h"
100 #include "md5.h"
101 
102 /* generated headers are not necessarily in current directory */
103 #include "imap/http_err.h"
104 
105 #ifdef WITH_DAV
106 #include "http_dav.h"
107 #endif
108 
109 #include <libxml/tree.h>
110 #include <libxml/HTMLtree.h>
111 #include <libxml/uri.h>
112 
113 static unsigned accept_encodings = 0;
114 
115 #ifdef HAVE_ZLIB
116 #include <zlib.h>
117 
zlib_init()118 HIDDEN void *zlib_init()
119 {
120     z_stream *zstrm = xzmalloc(sizeof(z_stream));
121 
122     /* Always use gzip format because IE incorrectly uses raw deflate */
123     if (deflateInit2(zstrm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
124                      16+MAX_WBITS /* gzip */,
125                      MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) {
126         free(zstrm);
127         return NULL;
128     }
129     else {
130         accept_encodings |= CE_DEFLATE | CE_GZIP;
131         return zstrm;
132     }
133 }
134 
zlib_compress(struct transaction_t * txn,unsigned flags,const char * buf,unsigned len)135 HIDDEN int zlib_compress(struct transaction_t *txn, unsigned flags,
136                          const char *buf, unsigned len)
137 {
138     z_stream *zstrm = txn->zstrm;
139     unsigned flush, pending;
140 
141     if (flags & COMPRESS_START) deflateReset(zstrm);
142 
143     if (txn->ws_ctx) flush = Z_SYNC_FLUSH;
144     else {
145         /* Only flush for static content or on last (zero-length) chunk */
146         if (flags & COMPRESS_END) flush = Z_FINISH;
147         else flush = Z_NO_FLUSH;
148     }
149 
150     zstrm->next_in = (Bytef *) buf;
151     zstrm->avail_in = len;
152 
153     buf_reset(&txn->zbuf);
154     buf_ensure(&txn->zbuf, deflateBound(zstrm, zstrm->avail_in));
155 
156     do {
157         int zr;
158 
159         zstrm->next_out = (Bytef *) txn->zbuf.s + txn->zbuf.len;
160         zstrm->avail_out = txn->zbuf.alloc - txn->zbuf.len;
161 
162         zr = deflate(zstrm, flush);
163         if (!(zr == Z_OK || zr == Z_STREAM_END || zr == Z_BUF_ERROR)) {
164             /* something went wrong */
165             syslog(LOG_ERR, "zlib deflate error: %d %s", zr, zstrm->msg);
166             return -1;
167         }
168 
169         txn->zbuf.len = txn->zbuf.alloc - zstrm->avail_out;
170 
171         if (zstrm->avail_out) {
172             pending = 0;
173         }
174         else {
175             /* http://www.zlib.net/manual.html says:
176              * If deflate returns with avail_out == 0, this function must be
177              * called again with the same value of the flush parameter and
178              * more output space (updated avail_out), until the flush is
179              * complete (deflate returns with non-zero avail_out).
180              * In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure
181              * that avail_out is greater than six to avoid repeated
182              * flush markers due to avail_out == 0 on return.
183              */
184 #ifdef HAVE_DEFLATE_PENDING
185             zr = deflatePending(zstrm, &pending, Z_NULL);
186             if (zr != Z_OK) {
187                 /* something went wrong */
188                 syslog(LOG_ERR, "zlib deflate error: %d %s", zr, zstrm->msg);
189                 return -1;
190             }
191 #else
192             /* Even if we have used all input, this will return non-zero */
193             pending = deflateBound(zstrm, zstrm->avail_in);
194 #endif
195 
196             buf_ensure(&txn->zbuf, pending);
197         }
198 
199     } while (pending);
200 
201     return 0;
202 }
203 
zlib_done(z_stream * zstrm)204 static void zlib_done(z_stream *zstrm)
205 {
206     if (zstrm) {
207         deflateEnd(zstrm);
208         free(zstrm);
209     }
210 }
211 #else /* !HAVE_ZLIB */
212 
zlib_init()213 HIDDEN void *zlib_init() { return NULL; }
214 
zlib_compress(struct transaction_t * txn,unsigned flags,const char * buf,unsigned len)215 HIDDEN int zlib_compress(struct transaction_t *txn __attribute__((unused)),
216                          unsigned flags __attribute__((unused)),
217                          const char *buf __attribute__((unused)),
218                          unsigned len __attribute__((unused)))
219 {
220     fatal("Compression requested, but no zlib", EX_SOFTWARE);
221 }
222 
zlib_done(void * zstrm)223 static void zlib_done(void *zstrm __attribute__((unused))) { }
224 
225 #endif /* HAVE_ZLIB */
226 
227 
228 #ifdef HAVE_BROTLI
229 #include <brotli/encode.h>
230 
brotli_init()231 HIDDEN void *brotli_init()
232 {
233     BrotliEncoderState *brotli = BrotliEncoderCreateInstance(NULL, NULL, NULL);
234 
235     if (brotli) {
236         BrotliEncoderSetParameter(brotli, BROTLI_PARAM_MODE,
237                                   BROTLI_DEFAULT_MODE);
238         BrotliEncoderSetParameter(brotli, BROTLI_PARAM_QUALITY,
239                                   BROTLI_DEFAULT_QUALITY);
240         BrotliEncoderSetParameter(brotli, BROTLI_PARAM_LGWIN,
241                                   BROTLI_DEFAULT_WINDOW);
242         BrotliEncoderSetParameter(brotli, BROTLI_PARAM_LGBLOCK,
243                                   BROTLI_MAX_INPUT_BLOCK_BITS);
244     }
245 
246     return brotli;
247 }
248 
brotli_compress(struct transaction_t * txn,unsigned flags,const char * buf,unsigned len)249 static int brotli_compress(struct transaction_t *txn,
250                            unsigned flags, const char *buf, unsigned len)
251 {
252     /* Only flush for static content or on last (zero-length) chunk */
253     unsigned op = (flags & COMPRESS_END) ?
254         BROTLI_OPERATION_FINISH : BROTLI_OPERATION_FLUSH;
255     BrotliEncoderState *brotli = txn->brotli;
256     const uint8_t *next_in = (const uint8_t *) buf;
257     size_t avail_in = (size_t) len;
258 
259     buf_reset(&txn->zbuf);
260     buf_ensure(&txn->zbuf, BrotliEncoderMaxCompressedSize(avail_in));
261 
262     do {
263         uint8_t *next_out = (uint8_t *) txn->zbuf.s + txn->zbuf.len;
264         size_t avail_out = txn->zbuf.alloc - txn->zbuf.len;
265 
266         if (!BrotliEncoderCompressStream(brotli, op,
267                                          &avail_in, &next_in,
268                                          &avail_out, &next_out, NULL)) {
269             syslog(LOG_ERR, "Brotli: Error while compressing data");
270             return -1;
271         }
272 
273         txn->zbuf.len = txn->zbuf.alloc - avail_out;
274     } while (avail_in || BrotliEncoderHasMoreOutput(brotli));
275 
276     if (BrotliEncoderIsFinished(brotli)) {
277         BrotliEncoderDestroyInstance(brotli);
278         txn->brotli = brotli_init();
279     }
280 
281     return 0;
282 }
283 
brotli_done(BrotliEncoderState * brotli)284 static void brotli_done(BrotliEncoderState *brotli)
285 {
286     if (brotli) BrotliEncoderDestroyInstance(brotli);
287 }
288 
289 #else /* !HAVE_BROTLI */
290 
brotli_init()291 HIDDEN void *brotli_init() { return NULL; }
292 
brotli_compress(struct transaction_t * txn,unsigned flags,const char * buf,unsigned len)293 static int brotli_compress(struct transaction_t *txn __attribute__((unused)),
294                            unsigned flags __attribute__((unused)),
295                            const char *buf __attribute__((unused)),
296                            unsigned len __attribute__((unused)))
297 {
298     fatal("Brotli Compression requested, but not available", EX_SOFTWARE);
299 }
300 
brotli_done(void * brotli)301 static void brotli_done(void *brotli __attribute__((unused))) {}
302 
303 #endif /* HAVE_BROTLI */
304 
305 
306 #ifdef HAVE_ZSTD
307 #include <zstd.h>
308 #include <zstd_errors.h>
309 
zstd_init()310 HIDDEN void *zstd_init()
311 {
312     ZSTD_CCtx *cctx = ZSTD_createCCtx();
313 
314     if (cctx) {
315         ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel,
316                                ZSTD_CLEVEL_DEFAULT);
317         ZSTD_CCtx_setParameter(cctx, ZSTD_c_checksumFlag, 1);
318     }
319 
320     return cctx;
321 }
322 
zstd_compress(struct transaction_t * txn,unsigned flags,const char * buf,unsigned len)323 static int zstd_compress(struct transaction_t *txn,
324                          unsigned flags, const char *buf, unsigned len)
325 {
326     /* Only flush for static content or on last (zero-length) chunk */
327     ZSTD_EndDirective mode = (flags & COMPRESS_END) ? ZSTD_e_end : ZSTD_e_flush;
328     ZSTD_inBuffer input = { buf, len, 0 };
329     ZSTD_CCtx *cctx = txn->zstd;
330     size_t remaining;
331 
332     if (flags & COMPRESS_START) ZSTD_CCtx_reset(cctx, ZSTD_reset_session_only);
333 
334     buf_reset(&txn->zbuf);
335     buf_ensure(&txn->zbuf, ZSTD_compressBound(len));
336 
337     ZSTD_outBuffer output = { txn->zbuf.s, txn->zbuf.alloc, 0 };
338     do {
339         remaining = ZSTD_compressStream2(cctx, &output, &input, mode);
340 
341         if (ZSTD_isError(remaining)) {
342             syslog(LOG_ERR, "Zstandard: %s",
343                    ZSTD_getErrorString(ZSTD_getErrorCode(remaining)));
344             return -1;
345         }
346     } while (remaining || (input.pos != input.size));
347 
348     buf_truncate(&txn->zbuf, output.pos);
349 
350     return 0;
351 }
352 
zstd_done(ZSTD_CCtx * cctx)353 static void zstd_done(ZSTD_CCtx *cctx)
354 {
355     if (cctx) ZSTD_freeCCtx(cctx);
356 }
357 
358 #else /* !HAVE_ZSTD */
359 
zstd_init()360 HIDDEN void *zstd_init() { return NULL; }
361 
zstd_compress(struct transaction_t * txn,unsigned flags,const char * buf,unsigned len)362 static int zstd_compress(struct transaction_t *txn __attribute__((unused)),
363                            unsigned flags __attribute__((unused)),
364                            const char *buf __attribute__((unused)),
365                            unsigned len __attribute__((unused)))
366 {
367     fatal("Zstandard Compression requested, but not available", EX_SOFTWARE);
368 }
369 
zstd_done(void * brotli)370 static void zstd_done(void *brotli __attribute__((unused))) {}
371 
372 #endif /* HAVE_ZSTD */
373 
374 
375 static const char tls_message[] =
376     HTML_DOCTYPE
377     "<html>\n<head>\n<title>TLS Required</title>\n</head>\n" \
378     "<body>\n<h2>TLS is required prior to authentication</h2>\n" \
379     "Use <a href=\"%s\">%s</a> instead.\n" \
380     "</body>\n</html>\n";
381 
382 extern int optind;
383 extern char *optarg;
384 extern int opterr;
385 
386 sasl_conn_t *httpd_saslconn; /* the sasl connection context */
387 
388 static struct wildmat *allow_cors = NULL;
389 int httpd_timeout, httpd_keepalive;
390 char *httpd_authid = NULL;
391 char *httpd_userid = NULL;
392 char *httpd_extrafolder = NULL;
393 char *httpd_extradomain = NULL;
394 struct auth_state *httpd_authstate = 0;
395 int httpd_userisadmin = 0;
396 int httpd_userisproxyadmin = 0;
397 int httpd_userisanonymous = 1;
398 const char *httpd_localip = NULL, *httpd_remoteip = NULL;
399 struct protstream *httpd_out = NULL;
400 struct protstream *httpd_in = NULL;
401 struct protgroup *protin = NULL;
402 strarray_t *httpd_log_headers = NULL;
403 
404 static sasl_ssf_t extprops_ssf = 0;
405 int https = 0;
406 int httpd_tls_required = 0;
407 unsigned avail_auth_schemes = 0; /* bitmask of available auth schemes */
408 unsigned long config_httpmodules;
409 int config_httpprettytelemetry;
410 
411 static time_t compile_time;
412 struct buf serverinfo = BUF_INITIALIZER;
413 
414 int ignorequota = 0;
415 int apns_enabled = 0;
416 
417 /* List of HTTP auth schemes that we support -
418    in descending order of security properties */
419 struct auth_scheme_t auth_schemes[] = {
420     { AUTH_SPNEGO, "Negotiate", "GSS-SPNEGO",
421       AUTH_BASE64 | AUTH_SUCCESS_WWW },
422     { AUTH_SCRAM_SHA256, "SCRAM-SHA-256", "SCRAM-SHA-256",
423       AUTH_NEED_PERSIST | AUTH_SERVER_FIRST | AUTH_BASE64 |
424       AUTH_REALM_PARAM | AUTH_DATA_PARAM },
425     { AUTH_SCRAM_SHA1, "SCRAM-SHA-1", "SCRAM-SHA-1",
426       AUTH_NEED_PERSIST | AUTH_SERVER_FIRST | AUTH_BASE64 |
427       AUTH_REALM_PARAM | AUTH_DATA_PARAM },
428     { AUTH_DIGEST, "Digest", HTTP_DIGEST_MECH,
429       AUTH_NEED_REQUEST | AUTH_SERVER_FIRST },
430     { AUTH_NTLM, "NTLM", "NTLM",
431       AUTH_NEED_PERSIST | AUTH_BASE64 },
432     { AUTH_BEARER, "Bearer", NULL,
433       AUTH_SERVER_FIRST | AUTH_REALM_PARAM },
434       AUTH_SCHEME_BASIC,
435     { 0, NULL, NULL, 0 }
436 };
437 
438 
439 /* the sasl proxy policy context */
440 static struct proxy_context httpd_proxyctx = {
441     0, 1, &httpd_authstate, &httpd_userisadmin, &httpd_userisproxyadmin
442 };
443 
444 /* signal to config.c */
445 const int config_need_data = CONFIG_NEED_PARTITION_DATA;
446 
447 /* current namespace */
448 HIDDEN struct namespace httpd_namespace;
449 
450 /* PROXY STUFF */
451 /* we want a list of our outgoing connections here and which one we're
452    currently piping */
453 
454 /* the current server most commands go to */
455 struct backend *backend_current = NULL;
456 
457 /* our cached connections */
458 struct backend **backend_cached = NULL;
459 
460 /* end PROXY stuff */
461 
462 static int starttls(struct transaction_t *txn, struct http_connection *conn);
463 void usage(void);
464 void shut_down(int code) __attribute__ ((noreturn));
465 
466 /* Enable the resetting of a sasl_conn_t */
467 static int reset_saslconn(sasl_conn_t **conn);
468 
469 static void cmdloop(struct http_connection *conn);
470 static int parse_expect(struct transaction_t *txn);
471 static int parse_connection(struct transaction_t *txn);
472 static int parse_ranges(const char *hdr, unsigned long len,
473                         struct range **ranges);
474 static int proxy_authz(const char **authzid, struct transaction_t *txn);
475 static int auth_success(struct transaction_t *txn, const char *userid);
476 static int http_auth(const char *creds, struct transaction_t *txn);
477 
478 static int meth_get(struct transaction_t *txn, void *params);
479 static int meth_propfind_root(struct transaction_t *txn, void *params);
480 
481 static struct saslprops_t saslprops = SASLPROPS_INITIALIZER;
482 
483 static struct sasl_callback mysasl_cb[] = {
484     { SASL_CB_GETOPT, (mysasl_cb_ft *) &mysasl_config, NULL },
485     { SASL_CB_PROXY_POLICY, (mysasl_cb_ft *) &mysasl_proxy_policy, (void*) &httpd_proxyctx },
486     { SASL_CB_CANON_USER, (mysasl_cb_ft *) &mysasl_canon_user, NULL },
487     { SASL_CB_LIST_END, NULL, NULL }
488 };
489 
490 /* Array of HTTP methods known by our server. */
491 const struct known_meth_t http_methods[] = {
492     { "ACL",           0,                          CYRUS_HTTP_ACL_TOTAL },
493     { "BIND",          0,                          CYRUS_HTTP_BIND_TOTAL },
494     { "CONNECT",       METH_NOBODY,                CYRUS_HTTP_CONNECT_TOTAL },
495     { "COPY",          METH_NOBODY,                CYRUS_HTTP_COPY_TOTAL },
496     { "DELETE",        METH_NOBODY,                CYRUS_HTTP_DELETE_TOTAL },
497     { "GET",           METH_NOBODY | METH_SAFE,    CYRUS_HTTP_GET_TOTAL },
498     { "HEAD",          METH_NOBODY | METH_SAFE,    CYRUS_HTTP_HEAD_TOTAL },
499     { "LOCK",          0,                          CYRUS_HTTP_LOCK_TOTAL },
500     { "MKCALENDAR",    0,                          CYRUS_HTTP_MKCALENDAR_TOTAL },
501     { "MKCOL",         0,                          CYRUS_HTTP_MKCOL_TOTAL },
502     { "MOVE",          METH_NOBODY,                CYRUS_HTTP_MOVE_TOTAL },
503     { "OPTIONS",       METH_NOBODY | METH_SAFE,    CYRUS_HTTP_OPTIONS_TOTAL },
504     { "PATCH",         0,                          CYRUS_HTTP_PATCH_TOTAL },
505     { "POST",          0,                          CYRUS_HTTP_POST_TOTAL },
506     { "PROPFIND",      METH_SAFE,                  CYRUS_HTTP_PROPFIND_TOTAL },
507     { "PROPPATCH",     0,                          CYRUS_HTTP_PROPPATCH_TOTAL },
508     { "PUT",           0,                          CYRUS_HTTP_PUT_TOTAL },
509     { "REPORT",        METH_SAFE,                  CYRUS_HTTP_REPORT_TOTAL },
510     { "SEARCH",        METH_SAFE,                  CYRUS_HTTP_SEARCH_TOTAL },
511     { "TRACE",         METH_NOBODY | METH_SAFE,    CYRUS_HTTP_TRACE_TOTAL },
512     { "UNBIND",        0,                          CYRUS_HTTP_UNBIND_TOTAL },
513     { "UNLOCK",        METH_NOBODY,                CYRUS_HTTP_UNLOCK_TOTAL },
514     { NULL,            0,                          0 }
515 };
516 
517 /* WebSocket handler */
518 static int ws_echo(struct buf *inbuf, struct buf *outbuf,
519                    struct buf *logbuf, void **rock);
520 
521 static struct connect_params ws_params = {
522     "/", NULL /* sub-protocol */, &ws_echo
523 };
524 
525 /* Namespace to fetch static content from filesystem */
526 struct namespace_t namespace_default = {
527     URL_NS_DEFAULT, 1, "default", "", NULL,
528     http_allow_noauth, /*authschemes*/0,
529     /*mbtype*/0,
530     ALLOW_READ,
531     NULL, NULL, NULL, NULL, NULL, NULL,
532     {
533         { NULL,                 NULL },                 /* ACL          */
534         { NULL,                 NULL },                 /* BIND         */
535         { &meth_connect,        &ws_params },           /* CONNECT      */
536         { NULL,                 NULL },                 /* COPY         */
537         { NULL,                 NULL },                 /* DELETE       */
538         { &meth_get,            NULL },                 /* GET          */
539         { &meth_get,            NULL },                 /* HEAD         */
540         { NULL,                 NULL },                 /* LOCK         */
541         { NULL,                 NULL },                 /* MKCALENDAR   */
542         { NULL,                 NULL },                 /* MKCOL        */
543         { NULL,                 NULL },                 /* MOVE         */
544         { &meth_options,        NULL },                 /* OPTIONS      */
545         { NULL,                 NULL },                 /* PATCH        */
546         { NULL,                 NULL },                 /* POST         */
547         { &meth_propfind_root,  NULL },                 /* PROPFIND     */
548         { NULL,                 NULL },                 /* PROPPATCH    */
549         { NULL,                 NULL },                 /* PUT          */
550         { NULL,                 NULL },                 /* REPORT       */
551         { &meth_trace,          NULL },                 /* TRACE        */
552         { NULL,                 NULL },                 /* UNBIND       */
553         { NULL,                 NULL },                 /* UNLOCK       */
554     }
555 };
556 
557 /* Array of different namespaces and features supported by the server */
558 struct namespace_t *http_namespaces[] = {
559 #ifdef WITH_JMAP
560     &namespace_jmap,
561 #endif
562     &namespace_tzdist,          /* MUST be before namespace_calendar!! */
563 #ifdef WITH_DAV
564     &namespace_calendar,
565     &namespace_freebusy,
566     &namespace_addressbook,
567     &namespace_drive,
568     &namespace_principal,       /* MUST be after namespace_cal & addr & drive */
569     &namespace_notify,          /* MUST be after namespace_principal */
570     &namespace_applepush,       /* MUST be after namespace_cal & addr */
571     &namespace_ischedule,
572     &namespace_domainkey,
573 #endif /* WITH_DAV */
574     &namespace_rss,
575     &namespace_dblookup,
576     &namespace_admin,
577     &namespace_prometheus,
578     &namespace_cgi,
579     &namespace_default,         /* MUST be present and be last!! */
580     NULL,
581 };
582 
583 
httpd_reset(struct http_connection * conn)584 static void httpd_reset(struct http_connection *conn)
585 {
586     int i;
587     int bytes_in = 0;
588     int bytes_out = 0;
589 
590     /* Do any namespace specific cleanup */
591     for (i = 0; http_namespaces[i]; i++) {
592         if (http_namespaces[i]->enabled && http_namespaces[i]->reset)
593             http_namespaces[i]->reset();
594     }
595 
596     /* Reset available authentication schemes */
597     avail_auth_schemes = 0;
598 
599     proc_cleanup();
600 
601     /* close backend connections */
602     i = 0;
603     while (backend_cached && backend_cached[i]) {
604         proxy_downserver(backend_cached[i]);
605         free(backend_cached[i]->context);
606         free(backend_cached[i]);
607         i++;
608     }
609     if (backend_cached) free(backend_cached);
610     backend_cached = NULL;
611     backend_current = NULL;
612 
613     index_text_extractor_destroy();
614 
615     if (httpd_in) {
616         prot_NONBLOCK(httpd_in);
617         prot_fill(httpd_in);
618         bytes_in = prot_bytes_in(httpd_in);
619         prot_free(httpd_in);
620     }
621 
622     if (httpd_out) {
623         prot_flush(httpd_out);
624         bytes_out = prot_bytes_out(httpd_out);
625         prot_free(httpd_out);
626     }
627 
628     if (config_auditlog) {
629         syslog(LOG_NOTICE,
630                "auditlog: traffic sessionid=<%s> bytes_in=<%d> bytes_out=<%d>",
631                session_id(), bytes_in, bytes_out);
632     }
633 
634     httpd_in = httpd_out = NULL;
635 
636     if (protin) protgroup_reset(protin);
637 
638 #ifdef HAVE_SSL
639     if (conn->tls_ctx) {
640         tls_reset_servertls((SSL **) &conn->tls_ctx);
641         conn->tls_ctx = NULL;
642     }
643 #endif
644 
645     xmlFreeParserCtxt(conn->xml);
646 
647     http2_end_session(conn->sess_ctx);
648 
649     cyrus_reset_stdio();
650 
651     conn->clienthost = "[local]";
652     buf_free(&conn->logbuf);
653     if (conn->logfd != -1) {
654         close(conn->logfd);
655         conn->logfd = -1;
656     }
657     if (httpd_authid != NULL) {
658         free(httpd_authid);
659         httpd_authid = NULL;
660     }
661     if (httpd_userid != NULL) {
662         free(httpd_userid);
663         httpd_userid = NULL;
664     }
665     httpd_userisanonymous = 1;
666     if (httpd_extrafolder != NULL) {
667         free(httpd_extrafolder);
668         httpd_extrafolder = NULL;
669     }
670     if (httpd_extradomain != NULL) {
671         free(httpd_extradomain);
672         httpd_extradomain = NULL;
673     }
674     if (httpd_authstate) {
675         auth_freestate(httpd_authstate);
676         httpd_authstate = NULL;
677     }
678     if (httpd_saslconn) {
679         sasl_dispose(&httpd_saslconn);
680         httpd_saslconn = NULL;
681     }
682 
683     saslprops_reset(&saslprops);
684 
685     session_new_id();
686 }
687 
688 /*
689  * run once when process is forked;
690  * MUST NOT exit directly; must return with non-zero error code
691  */
service_init(int argc,char ** argv,char ** envp)692 int service_init(int argc __attribute__((unused)),
693                  char **argv __attribute__((unused)),
694                  char **envp __attribute__((unused)))
695 {
696     int r, events, opt, i;
697     int allow_trace = config_getswitch(IMAPOPT_HTTPALLOWTRACE);
698     unsigned long version;
699     unsigned int status, patch, fix, minor, major;
700 
701     LIBXML_TEST_VERSION
702 
703     if (geteuid() == 0) fatal("must run as the Cyrus user", EX_USAGE);
704     setproctitle_init(argc, argv, envp);
705 
706     /* set signal handlers */
707     signals_set_shutdown(&shut_down);
708     signal(SIGPIPE, SIG_IGN);
709 
710     /* load the SASL plugins */
711     global_sasl_init(1, 1, mysasl_cb);
712 
713     /* setup for sending IMAP IDLE notifications */
714     idle_enabled();
715 
716     /* Set namespace */
717     if ((r = mboxname_init_namespace(&httpd_namespace, 1)) != 0) {
718         fatal(error_message(r), EX_CONFIG);
719     }
720 
721     /* open the mboxevent system */
722     events = mboxevent_init();
723     apns_enabled = (events & EVENT_APPLEPUSHSERVICE_DAV);
724 
725     mboxevent_setnamespace(&httpd_namespace);
726 
727     while ((opt = getopt(argc, argv, "sp:q")) != EOF) {
728         switch(opt) {
729         case 's': /* https (do TLS right away) */
730             https = 1;
731             if (!tls_enabled()) {
732                 fatal("https: required OpenSSL options not present",
733                       EX_CONFIG);
734             }
735             break;
736 
737         case 'q':
738             ignorequota = 1;
739             break;
740 
741         case 'p': /* external protection */
742             extprops_ssf = atoi(optarg);
743             break;
744 
745         default:
746             usage();
747         }
748     }
749 
750     /* Create a protgroup for input from the client and selected backend */
751     protin = protgroup_new(2);
752 
753     config_httpprettytelemetry = config_getswitch(IMAPOPT_HTTPPRETTYTELEMETRY);
754 
755     httpd_log_headers = strarray_split(config_getstring(IMAPOPT_HTTPLOGHEADERS),
756                                        " ", STRARRAY_TRIM | STRARRAY_LCASE);
757 
758     if (config_getstring(IMAPOPT_HTTPALLOWCORS)) {
759         allow_cors =
760             split_wildmats((char *) config_getstring(IMAPOPT_HTTPALLOWCORS),
761                            NULL);
762     }
763 
764     /* Construct serverinfo string */
765     buf_printf(&serverinfo,
766                "Cyrus-HTTP/%s Cyrus-SASL/%u.%u.%u Lib/XML%s Jansson/%s",
767                CYRUS_VERSION,
768                SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
769                LIBXML_DOTTED_VERSION, JANSSON_VERSION);
770 
771     http2_init(&serverinfo);
772     ws_init(&serverinfo);
773 
774 #ifdef HAVE_SSL
775     version = OPENSSL_VERSION_NUMBER;
776     status  = version & 0x0f; version >>= 4;
777     patch   = version & 0xff; version >>= 8;
778     fix     = version & 0xff; version >>= 8;
779     minor   = version & 0xff; version >>= 8;
780     major   = version & 0xff;
781 
782     buf_printf(&serverinfo, " OpenSSL/%u.%u.%u", major, minor, fix);
783 
784     if (status == 0) buf_appendcstr(&serverinfo, "-dev");
785     else if (status < 15) buf_printf(&serverinfo, "-beta%u", status);
786     else if (patch) buf_putc(&serverinfo, patch + 'a' - 1);
787 #endif
788 
789 #ifdef HAVE_ZLIB
790     buf_printf(&serverinfo, " Zlib/%s", ZLIB_VERSION);
791 #endif
792 #ifdef HAVE_BROTLI
793     version = BrotliEncoderVersion();
794     fix     = version & 0xfff; version >>= 12;
795     minor   = version & 0xfff; version >>= 12;
796     major   = version & 0xfff;
797 
798     buf_printf(&serverinfo, " Brotli/%u.%u.%u", major, minor, fix);
799 #endif
800 #ifdef HAVE_ZSTD
801     buf_printf(&serverinfo, " Zstd/%s", ZSTD_versionString());
802 #endif
803 
804     /* Initialize libical */
805     ical_support_init();
806 
807     /* Do any namespace specific initialization */
808     config_httpmodules = config_getbitfield(IMAPOPT_HTTPMODULES);
809     for (i = 0; http_namespaces[i]; i++) {
810         if (allow_trace) http_namespaces[i]->allow |= ALLOW_TRACE;
811         if (http_namespaces[i]->init) http_namespaces[i]->init(&serverinfo);
812     }
813 
814     compile_time = calc_compile_time(__TIME__, __DATE__);
815 
816     prometheus_increment(CYRUS_HTTP_READY_LISTENERS);
817 
818     return 0;
819 }
820 
821 
822 static volatile sig_atomic_t gotsigalrm = 0;
823 
sigalrm_handler(int sig)824 static void sigalrm_handler(int sig __attribute__((unused)))
825 {
826     gotsigalrm = 1;
827 }
828 
829 
830 /*
831  * run for each accepted connection
832  */
service_main(int argc,char ** argv,char ** envp)833 int service_main(int argc __attribute__((unused)),
834                  char **argv __attribute__((unused)),
835                  char **envp __attribute__((unused)))
836 {
837     sasl_security_properties_t *secprops=NULL;
838     const char *mechlist, *mech;
839     int mechcount = 0;
840     size_t mechlen;
841     struct auth_scheme_t *scheme;
842     struct http_connection http_conn;
843 
844     /* fatal/shut_down will adjust these, so we need to set them early */
845     prometheus_decrement(CYRUS_HTTP_READY_LISTENERS);
846     prometheus_increment(CYRUS_HTTP_ACTIVE_CONNECTIONS);
847 
848     session_new_id();
849 
850     signals_poll();
851 
852     httpd_in = prot_new(0, 0);
853     httpd_out = prot_new(1, 1);
854     protgroup_insert(protin, httpd_in);
855 
856     /* Setup HTTP connection */
857     memset(&http_conn, 0, sizeof(struct http_connection));
858     http_conn.pin = httpd_in;
859     http_conn.pout = httpd_out;
860     http_conn.logfd = -1;
861 
862     /* Create XML parser context */
863     if (!(http_conn.xml = xmlNewParserCtxt())) {
864         fatal("Unable to create XML parser", EX_TEMPFAIL);
865     }
866 
867     /* Find out name of client host */
868     http_conn.clienthost = get_clienthost(0, &httpd_localip, &httpd_remoteip);
869 
870     if (httpd_localip && httpd_remoteip) {
871         buf_setcstr(&saslprops.ipremoteport, httpd_remoteip);
872         buf_setcstr(&saslprops.iplocalport, httpd_localip);
873     }
874 
875     /* other params should be filled in */
876     if (sasl_server_new("HTTP", config_servername, NULL,
877                         buf_cstringnull_ifempty(&saslprops.iplocalport),
878                         buf_cstringnull_ifempty(&saslprops.ipremoteport),
879                         NULL, SASL_USAGE_FLAGS, &httpd_saslconn) != SASL_OK)
880         fatal("SASL failed initializing: sasl_server_new()",EX_TEMPFAIL);
881 
882     /* will always return something valid */
883     secprops = mysasl_secprops(0);
884 
885     /* no HTTP clients seem to use "auth-int" */
886     secprops->max_ssf = 0;                              /* "auth" only */
887     secprops->maxbufsize = 0;                           /* don't need maxbuf */
888     if (sasl_setprop(httpd_saslconn, SASL_SEC_PROPS, secprops) != SASL_OK)
889         fatal("Failed to set SASL property", EX_TEMPFAIL);
890     if (sasl_setprop(httpd_saslconn, SASL_SSF_EXTERNAL, &extprops_ssf) != SASL_OK)
891         fatal("Failed to set SASL property", EX_TEMPFAIL);
892 
893     if (httpd_remoteip) {
894         char hbuf[NI_MAXHOST], *p;
895 
896         /* Create pre-authentication telemetry log based on client IP */
897         strlcpy(hbuf, httpd_remoteip, NI_MAXHOST);
898         if ((p = strchr(hbuf, ';'))) *p = '\0';
899         http_conn.logfd = telemetry_log(hbuf, httpd_in, httpd_out, 0);
900     }
901 
902     /* See which auth schemes are available to us */
903     avail_auth_schemes = 0; /* Reset auth schemes for each connection */
904     if ((extprops_ssf >= 2) || config_getswitch(IMAPOPT_ALLOWPLAINTEXT)) {
905         avail_auth_schemes |=  AUTH_BASIC;
906     }
907     sasl_listmech(httpd_saslconn, NULL, NULL, " ", NULL,
908                   &mechlist, NULL, &mechcount);
909     for (mech = mechlist; mechcount--; mech += ++mechlen) {
910         mechlen = strcspn(mech, " \0");
911         for (scheme = auth_schemes; scheme->name; scheme++) {
912             if (scheme->saslmech && !strncmp(mech, scheme->saslmech, mechlen)) {
913                 avail_auth_schemes |= scheme->id;
914                 break;
915             }
916         }
917     }
918     httpd_tls_required =
919         config_getswitch(IMAPOPT_TLS_REQUIRED) || !avail_auth_schemes;
920 
921     proc_register(config_ident, http_conn.clienthost, NULL, NULL, NULL);
922 
923     /* Set inactivity timer */
924     httpd_timeout = config_getduration(IMAPOPT_HTTPTIMEOUT, 'm');
925     if (httpd_timeout < 0) httpd_timeout = 0;
926     prot_settimeout(httpd_in, httpd_timeout);
927     prot_setflushonread(httpd_in, httpd_out);
928 
929     /* we were connected on https port so we should do
930        TLS negotiation immediately */
931     if (https == 1) {
932         if (starttls(NULL, &http_conn) != 0) shut_down(0);
933     }
934     else if (http2_preface(&http_conn)) {
935         /* HTTP/2 client connection preface */
936         if (http2_start_session(NULL, &http_conn) != 0)
937             fatal("Failed initializing HTTP/2 session", EX_TEMPFAIL);
938     }
939 
940     /* Setup the signal handler for keepalive heartbeat */
941     httpd_keepalive = config_getduration(IMAPOPT_HTTPKEEPALIVE, 's');
942     if (httpd_keepalive < 0) httpd_keepalive = 0;
943     if (httpd_keepalive) {
944         struct sigaction action;
945 
946         sigemptyset(&action.sa_mask);
947         action.sa_flags = 0;
948 #ifdef SA_RESTART
949         action.sa_flags |= SA_RESTART;
950 #endif
951         action.sa_handler = sigalrm_handler;
952         if (sigaction(SIGALRM, &action, NULL) < 0) {
953             syslog(LOG_ERR, "unable to install signal handler for %d: %m", SIGALRM);
954             httpd_keepalive = 0;
955         }
956     }
957 
958     index_text_extractor_init(httpd_in);
959 
960     /* count the connection, now that it's established */
961     prometheus_increment(CYRUS_HTTP_CONNECTIONS_TOTAL);
962 
963     cmdloop(&http_conn);
964 
965     prometheus_decrement(CYRUS_HTTP_ACTIVE_CONNECTIONS);
966 
967     /* Closing connection */
968 
969     /* cleanup */
970     signal(SIGALRM, SIG_IGN);
971     httpd_reset(&http_conn);
972 
973     prometheus_increment(CYRUS_HTTP_READY_LISTENERS);
974 
975     return 0;
976 }
977 
978 
979 /* Called by service API to shut down the service */
service_abort(int error)980 void service_abort(int error)
981 {
982     shut_down(error);
983 }
984 
985 
usage(void)986 void usage(void)
987 {
988     prot_printf(httpd_out, "%s: usage: httpd [-C <alt_config>] [-s]\r\n",
989                 error_message(HTTP_SERVER_ERROR));
990     prot_flush(httpd_out);
991     exit(EX_USAGE);
992 }
993 
994 
995 /*
996  * Cleanly shut down and exit
997  */
shut_down(int code)998 void shut_down(int code)
999 {
1000     int i;
1001     int bytes_in = 0;
1002     int bytes_out = 0;
1003 
1004     in_shutdown = 1;
1005 
1006     if (allow_cors) free_wildmats(allow_cors);
1007 
1008     strarray_free(httpd_log_headers);
1009 
1010     /* Do any namespace specific cleanup */
1011     for (i = 0; http_namespaces[i]; i++) {
1012         if (http_namespaces[i]->enabled && http_namespaces[i]->shutdown)
1013             http_namespaces[i]->shutdown();
1014     }
1015 
1016     xmlCleanupParser();
1017 
1018     proc_cleanup();
1019 
1020     /* close backend connections */
1021     i = 0;
1022     while (backend_cached && backend_cached[i]) {
1023         proxy_downserver(backend_cached[i]);
1024         free(backend_cached[i]->context);
1025         free(backend_cached[i]);
1026         i++;
1027     }
1028     if (backend_cached) free(backend_cached);
1029 
1030     index_text_extractor_destroy();
1031 
1032     annotatemore_close();
1033 
1034     if (httpd_in) {
1035         prot_NONBLOCK(httpd_in);
1036         prot_fill(httpd_in);
1037         bytes_in = prot_bytes_in(httpd_in);
1038         prot_free(httpd_in);
1039     }
1040 
1041     if (httpd_out) {
1042         prot_flush(httpd_out);
1043         bytes_out = prot_bytes_out(httpd_out);
1044         prot_free(httpd_out);
1045 
1046         /* one less active connection */
1047         prometheus_decrement(CYRUS_HTTP_ACTIVE_CONNECTIONS);
1048     }
1049     else {
1050         /* one less ready listener */
1051         prometheus_decrement(CYRUS_HTTP_READY_LISTENERS);
1052     }
1053 
1054     prometheus_increment(code ? CYRUS_HTTP_SHUTDOWN_TOTAL_STATUS_ERROR
1055                               : CYRUS_HTTP_SHUTDOWN_TOTAL_STATUS_OK);
1056 
1057     if (protin) protgroup_free(protin);
1058 
1059     if (config_auditlog)
1060         syslog(LOG_NOTICE,
1061                "auditlog: traffic sessionid=<%s> bytes_in=<%d> bytes_out=<%d>",
1062                session_id(), bytes_in, bytes_out);
1063 
1064 #ifdef HAVE_SSL
1065     tls_shutdown_serverengine();
1066 #endif
1067 
1068     saslprops_free(&saslprops);
1069 
1070     http2_done();
1071 
1072     cyrus_done();
1073 
1074     exit(code);
1075 }
1076 
1077 
fatal(const char * s,int code)1078 EXPORTED void fatal(const char* s, int code)
1079 {
1080     static int recurse_code = 0;
1081     const char *fatal = "Fatal error: ";
1082 
1083     if (recurse_code) {
1084         /* We were called recursively. Just give up */
1085         proc_cleanup();
1086         if (httpd_out) {
1087             /* one less active connection */
1088             prometheus_decrement(CYRUS_HTTP_ACTIVE_CONNECTIONS);
1089         }
1090         else {
1091             /* one less ready listener */
1092             prometheus_decrement(CYRUS_HTTP_READY_LISTENERS);
1093         }
1094         prometheus_increment(CYRUS_HTTP_SHUTDOWN_TOTAL_STATUS_ERROR);
1095         exit(recurse_code);
1096     }
1097     recurse_code = code;
1098     if (httpd_out) {
1099         prot_printf(httpd_out,
1100                     "HTTP/1.1 %s\r\n"
1101                     "Content-Type: text/plain\r\n"
1102                     "Content-Length: %zu\r\n"
1103                     "Connection: close\r\n"
1104                     "\r\n"
1105                     "%s%s\r\n",
1106                     error_message(HTTP_SERVER_ERROR),
1107                     strlen(fatal) + strlen(s) + 2, fatal, s);
1108         prot_flush(httpd_out);
1109     }
1110     syslog(LOG_ERR, "%s%s", fatal, s);
1111     shut_down(code);
1112 }
1113 
1114 
1115 #ifdef HAVE_SSL
starttls(struct transaction_t * txn,struct http_connection * conn)1116 static int starttls(struct transaction_t *txn, struct http_connection *conn)
1117 {
1118     int https = (txn == NULL);
1119     int result;
1120     SSL_CTX *ctx = NULL;
1121 
1122     if (!conn) conn = txn->conn;
1123 
1124     result=tls_init_serverengine("http",
1125                                  5,        /* depth to verify */
1126                                  !https,   /* can client auth? */
1127                                  &ctx);
1128 
1129     if (result == -1) {
1130         syslog(LOG_ERR, "error initializing TLS");
1131 
1132         if (txn) txn->error.desc = "Error initializing TLS";
1133         return HTTP_SERVER_ERROR;
1134     }
1135 
1136     if (http2_enabled()) {
1137 #ifdef HAVE_TLS_ALPN
1138         /* enable TLS ALPN extension */
1139         SSL_CTX_set_alpn_select_cb(ctx, alpn_select_cb, conn);
1140 #endif
1141     }
1142 
1143     if (!https) {
1144         /* tell client to start TLS upgrade (RFC 2817) */
1145         response_header(HTTP_SWITCH_PROT, txn);
1146     }
1147 
1148     result=tls_start_servertls(0, /* read */
1149                                1, /* write */
1150                                https ? 180 : httpd_timeout,
1151                                &saslprops,
1152                                (SSL **) &conn->tls_ctx);
1153 
1154     /* if error */
1155     if (result == -1) {
1156         syslog(LOG_NOTICE, "starttls failed: %s", conn->clienthost);
1157 
1158         if (txn) txn->error.desc = "Error negotiating TLS";
1159         return HTTP_BAD_REQUEST;
1160     }
1161 
1162     /* tell SASL about the negotiated layer */
1163     result = saslprops_set_tls(&saslprops, httpd_saslconn);
1164     if (result != SASL_OK) {
1165         syslog(LOG_NOTICE, "saslprops_set_tls() failed: cmd_starttls()");
1166         if (https == 0) {
1167             fatal("saslprops_set_tls() failed: cmd_starttls()", EX_TEMPFAIL);
1168         } else {
1169             shut_down(0);
1170         }
1171     }
1172 
1173     /* tell the prot layer about our new layers */
1174     prot_settls(httpd_in, conn->tls_ctx);
1175     prot_settls(httpd_out, conn->tls_ctx);
1176 
1177     httpd_tls_required = 0;
1178 
1179     avail_auth_schemes |= AUTH_BASIC;
1180 
1181     return 0;
1182 }
1183 #else
starttls(struct transaction_t * txn,struct http_connection * conn)1184 static int starttls(struct transaction_t *txn __attribute__((unused)),
1185                     struct http_connection *conn __attribute__((unused)))
1186 {
1187     fatal("starttls() called, but no OpenSSL", EX_SOFTWARE);
1188 }
1189 #endif /* HAVE_SSL */
1190 
1191 
1192 /* Reset the given sasl_conn_t to a sane state */
reset_saslconn(sasl_conn_t ** conn)1193 static int reset_saslconn(sasl_conn_t **conn)
1194 {
1195     int ret;
1196     sasl_security_properties_t *secprops = NULL;
1197 
1198     sasl_dispose(conn);
1199     /* do initialization typical of service_main */
1200     ret = sasl_server_new("HTTP", config_servername, NULL,
1201                           buf_cstringnull_ifempty(&saslprops.iplocalport),
1202                           buf_cstringnull_ifempty(&saslprops.ipremoteport),
1203                           NULL, SASL_USAGE_FLAGS, conn);
1204     if(ret != SASL_OK) return ret;
1205 
1206     secprops = mysasl_secprops(0);
1207 
1208     /* no HTTP clients seem to use "auth-int" */
1209     secprops->max_ssf = 0;                              /* "auth" only */
1210     secprops->maxbufsize = 0;                           /* don't need maxbuf */
1211     ret = sasl_setprop(*conn, SASL_SEC_PROPS, secprops);
1212     if(ret != SASL_OK) return ret;
1213     /* end of service_main initialization excepting SSF */
1214 
1215     /* If we have TLS/SSL info, set it */
1216     if(saslprops.ssf) {
1217         ret = saslprops_set_tls(&saslprops, *conn);
1218     } else {
1219         ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &extprops_ssf);
1220     }
1221 
1222     if(ret != SASL_OK) return ret;
1223     /* End TLS/SSL Info */
1224 
1225     return SASL_OK;
1226 }
1227 
1228 
parse_request_line(struct transaction_t * txn)1229 static int parse_request_line(struct transaction_t *txn)
1230 {
1231     struct request_line_t *req_line = &txn->req_line;
1232     char *p;
1233     tok_t tok;
1234     int ret = 0;
1235 
1236     /* Trim CRLF from request-line */
1237     p = req_line->buf + strlen(req_line->buf);
1238     if (p[-1] == '\n') *--p = '\0';
1239     if (p[-1] == '\r') *--p = '\0';
1240 
1241     /* Parse request-line = method SP request-target SP HTTP-version CRLF */
1242     tok_initm(&tok, req_line->buf, " ", 0);
1243     if (!(req_line->meth = tok_next(&tok))) {
1244         ret = HTTP_BAD_REQUEST;
1245         txn->error.desc = "Missing method in request-line";
1246     }
1247     else if (!(req_line->uri = tok_next(&tok))) {
1248         ret = HTTP_BAD_REQUEST;
1249         txn->error.desc = "Missing request-target in request-line";
1250     }
1251     else if ((size_t) (p - req_line->buf) > MAX_REQ_LINE - 2) {
1252         /* request-line overran the size of our buffer */
1253         ret = HTTP_URI_TOO_LONG;
1254         buf_printf(&txn->buf,
1255                    "Length of request-line MUST be less than %u octets",
1256                    MAX_REQ_LINE);
1257         txn->error.desc = buf_cstring(&txn->buf);
1258     }
1259     else if (!(req_line->ver = tok_next(&tok))) {
1260         ret = HTTP_BAD_REQUEST;
1261         txn->error.desc = "Missing HTTP-version in request-line";
1262     }
1263     else if (tok_next(&tok)) {
1264         ret = HTTP_BAD_REQUEST;
1265         txn->error.desc = "Unexpected extra argument(s) in request-line";
1266     }
1267 
1268     /* Check HTTP-Version - MUST be HTTP/1.x */
1269     else if (strlen(req_line->ver) != HTTP_VERSION_LEN
1270              || strncmp(req_line->ver, HTTP_VERSION, HTTP_VERSION_LEN-1)
1271              || !isdigit(req_line->ver[HTTP_VERSION_LEN-1])) {
1272         ret = HTTP_BAD_VERSION;
1273         buf_printf(&txn->buf,
1274                    "This server only speaks %.*sx",
1275                    HTTP_VERSION_LEN-1, HTTP_VERSION);
1276         txn->error.desc = buf_cstring(&txn->buf);
1277     }
1278     else if (req_line->ver[HTTP_VERSION_LEN-1] == '0') {
1279         /* HTTP/1.0 connection */
1280         txn->flags.ver = VER_1_0;
1281     }
1282     tok_fini(&tok);
1283 
1284     return ret;
1285 }
1286 
1287 
client_need_auth(struct transaction_t * txn,int sasl_result)1288 static int client_need_auth(struct transaction_t *txn, int sasl_result)
1289 {
1290     if (httpd_tls_required) {
1291         /* We only support TLS+Basic, so tell client to use TLS */
1292         const char **hdr;
1293 
1294         /* Check which response is required */
1295         if ((hdr = spool_getheader(txn->req_hdrs, "Upgrade")) &&
1296             stristr(hdr[0], TLS_VERSION)) {
1297             /* Client (Murder proxy) supports RFC 2817 (TLS upgrade) */
1298 
1299             txn->flags.conn |= CONN_UPGRADE;
1300             txn->flags.upgrade = UPGRADE_TLS;
1301             return HTTP_UPGRADE;
1302         }
1303         else {
1304             /* All other clients use RFC 2818 (HTTPS) */
1305             const char *path = txn->req_uri->path;
1306             const char *query = URI_QUERY(txn->req_uri);
1307             struct buf *html = &txn->resp_body.payload;
1308 
1309             /* Create https URL */
1310             hdr = spool_getheader(txn->req_hdrs, ":authority");
1311             buf_printf(&txn->buf, "https://%s", hdr[0]);
1312             if (strcmp(path, "*")) {
1313                 buf_appendcstr(&txn->buf, path);
1314                 if (query) buf_printf(&txn->buf, "?%s", query);
1315             }
1316 
1317             txn->location = buf_cstring(&txn->buf);
1318 
1319             /* Create HTML body */
1320             buf_reset(html);
1321             buf_printf(html, tls_message,
1322                        buf_cstring(&txn->buf), buf_cstring(&txn->buf));
1323 
1324             /* Output our HTML response */
1325             txn->resp_body.type = "text/html; charset=utf-8";
1326             return HTTP_MOVED;
1327         }
1328     }
1329     else {
1330         /* Tell client to authenticate */
1331         if (sasl_result == SASL_CONTINUE)
1332             txn->error.desc = "Continue authentication exchange";
1333         else if (sasl_result) txn->error.desc = "Authentication failed";
1334         else txn->error.desc =
1335                  "Must authenticate to access the specified target";
1336 
1337         return HTTP_UNAUTHORIZED;
1338     }
1339 }
1340 
1341 
check_method(struct transaction_t * txn)1342 static int check_method(struct transaction_t *txn)
1343 {
1344     const char **hdr;
1345     struct request_line_t *req_line = &txn->req_line;
1346 
1347     if (txn->flags.redirect) return 0;
1348 
1349     /* Check for HTTP method override */
1350     if (!strcmp(req_line->meth, "POST") &&
1351         (hdr = spool_getheader(txn->req_hdrs, "X-HTTP-Method-Override"))) {
1352         txn->flags.override = 1;
1353         req_line->meth = (char *) hdr[0];
1354     }
1355 
1356     /* Check Method against our list of known methods */
1357     for (txn->meth = 0; (txn->meth < METH_UNKNOWN) &&
1358              strcmp(http_methods[txn->meth].name, req_line->meth);
1359          txn->meth++);
1360 
1361     if (txn->meth == METH_UNKNOWN) return HTTP_NOT_IMPLEMENTED;
1362 
1363     return 0;
1364 }
1365 
preauth_check_hdrs(struct transaction_t * txn)1366 static int preauth_check_hdrs(struct transaction_t *txn)
1367 {
1368     int ret = 0;
1369     const char **hdr;
1370 
1371     if (txn->flags.redirect) return 0;
1372 
1373     /* Check for mandatory Host header (HTTP/1.1+ only) */
1374     if ((hdr = spool_getheader(txn->req_hdrs, "Host"))) {
1375         if (hdr[1]) {
1376             txn->error.desc = "Too many Host headers";
1377             return HTTP_BAD_REQUEST;
1378         }
1379 
1380         /* Create an :authority pseudo header from Host */
1381         spool_cache_header(xstrdup(":authority"),
1382                            xstrdup(hdr[0]), txn->req_hdrs);
1383     }
1384     else {
1385         switch (txn->flags.ver) {
1386         case VER_2:
1387             /* HTTP/2 - check for :authority pseudo header */
1388             if (spool_getheader(txn->req_hdrs, ":authority")) break;
1389 
1390             /* Fall through and create an :authority pseudo header */
1391             GCC_FALLTHROUGH
1392 
1393         case VER_1_0:
1394             /* HTTP/1.0 - create an :authority pseudo header from URI */
1395             if (txn->req_uri->server) {
1396                 buf_setcstr(&txn->buf, txn->req_uri->server);
1397                 if (txn->req_uri->port)
1398                     buf_printf(&txn->buf, ":%d", txn->req_uri->port);
1399             }
1400             else buf_setcstr(&txn->buf, config_servername);
1401 
1402             spool_cache_header(xstrdup(":authority"),
1403                                buf_release(&txn->buf), txn->req_hdrs);
1404             break;
1405 
1406         case VER_1_1:
1407         default:
1408             txn->error.desc = "Missing Host header";
1409             return HTTP_BAD_REQUEST;
1410         }
1411     }
1412 
1413     /* Check message framing */
1414     if ((ret = http_parse_framing(txn->flags.ver == VER_2, txn->req_hdrs,
1415                                   &txn->req_body, &txn->error.desc))) return ret;
1416 
1417     /* Check for Expectations */
1418     if ((ret = parse_expect(txn))) return ret;
1419 
1420     /* Check for Connection options */
1421     if ((ret = parse_connection(txn))) return ret;
1422 
1423     syslog(LOG_DEBUG, "conn flags: %#x  upgrade flags: %#x  tls req: %d",
1424            txn->flags.conn, txn->flags.upgrade, httpd_tls_required);
1425     if (txn->flags.conn & CONN_UPGRADE) {
1426         /* Read any request body (can't upgrade in middle of request) */
1427         txn->req_body.flags |= BODY_DECODE;
1428         ret = http_read_req_body(txn);
1429         if (ret) {
1430             txn->flags.conn = CONN_CLOSE;
1431             return ret;
1432         }
1433 
1434         if (txn->flags.upgrade & UPGRADE_TLS) {
1435             if ((ret = starttls(txn, NULL))) {
1436                 txn->flags.conn = CONN_CLOSE;
1437                 return ret;
1438             }
1439 
1440             /* Don't advertise TLS Upgrade anymore */
1441             txn->flags.upgrade &= ~UPGRADE_TLS;
1442         }
1443 
1444         syslog(LOG_DEBUG, "upgrade flags: %#x  tls req: %d",
1445                txn->flags.upgrade, httpd_tls_required);
1446         if ((txn->flags.upgrade & UPGRADE_HTTP2) && !httpd_tls_required) {
1447             if ((ret = http2_start_session(txn, NULL))) {
1448                 txn->flags.conn = CONN_CLOSE;
1449                 return ret;
1450             }
1451 
1452             /* Upgrade header field mechanism not available under HTTP/2 */
1453             txn->flags.upgrade = 0;
1454         }
1455     }
1456     else if (!txn->conn->tls_ctx && txn->flags.ver == VER_1_1) {
1457         /* Advertise available upgrade protocols */
1458         if (tls_enabled() &&
1459             config_mupdate_server && config_getstring(IMAPOPT_PROXYSERVERS)) {
1460             txn->flags.upgrade |= UPGRADE_TLS;
1461         }
1462         if (http2_enabled()) txn->flags.upgrade |= UPGRADE_HTTP2;
1463     }
1464 
1465     if (txn->flags.upgrade) txn->flags.conn |= CONN_UPGRADE;
1466     else txn->flags.conn &= ~CONN_UPGRADE;
1467 
1468     return 0;
1469 }
1470 
1471 
check_namespace(struct transaction_t * txn)1472 static int check_namespace(struct transaction_t *txn)
1473 {
1474     int i;
1475     const char **hdr, *query = URI_QUERY(txn->req_uri);
1476     const struct namespace_t *namespace;
1477     const struct method_t *meth_t;
1478 
1479     /* Find the namespace of the requested resource */
1480     for (i = 0; http_namespaces[i]; i++) {
1481         const char *path = txn->req_uri->path;
1482         size_t len;
1483 
1484         /* Skip disabled namespaces */
1485         if (!http_namespaces[i]->enabled) continue;
1486 
1487         /* Handle any /.well-known/ bootstrapping */
1488         if (http_namespaces[i]->well_known) {
1489             len = strlen(http_namespaces[i]->well_known);
1490             if (!strncmp(path, http_namespaces[i]->well_known, len) &&
1491                 (!path[len] || path[len] == '/')) {
1492 
1493                 hdr = spool_getheader(txn->req_hdrs, ":authority");
1494                 buf_reset(&txn->buf);
1495                 buf_printf(&txn->buf, "%s://%s",
1496                            https ? "https" : "http", hdr[0]);
1497                 buf_appendcstr(&txn->buf, http_namespaces[i]->prefix);
1498                 buf_appendcstr(&txn->buf, path + len);
1499                 if (query) buf_printf(&txn->buf, "?%s", query);
1500                 txn->location = buf_cstring(&txn->buf);
1501 
1502                 return HTTP_MOVED;
1503             }
1504         }
1505 
1506         /* See if the prefix matches - terminated with NUL or '/' */
1507         len = strlen(http_namespaces[i]->prefix);
1508         if (!strncmp(path, http_namespaces[i]->prefix, len) &&
1509             (!path[len] || (path[len] == '/') || !strcmp(path, "*"))) {
1510             break;
1511         }
1512     }
1513     if ((namespace = http_namespaces[i])) {
1514         txn->req_tgt.namespace = namespace;
1515         txn->req_tgt.allow = namespace->allow;
1516 
1517         /* Check if method is supported in this namespace */
1518         meth_t = &namespace->methods[txn->meth];
1519         if (!meth_t->proc) return HTTP_NOT_ALLOWED;
1520 
1521         if (config_getswitch(IMAPOPT_READONLY) &&
1522               !(http_methods[txn->meth].flags & METH_SAFE) &&
1523               !(namespace->allow & ALLOW_READONLY)) {
1524             return HTTP_NOT_ALLOWED;
1525         }
1526 
1527         /* Check if method expects a body */
1528         else if ((http_methods[txn->meth].flags & METH_NOBODY) &&
1529                  (txn->req_body.framing != FRAMING_LENGTH ||
1530                   /* XXX  Will break if client sends just a last-chunk */
1531                   txn->req_body.len)) {
1532             return HTTP_BAD_MEDIATYPE;
1533         }
1534     } else {
1535         /* XXX  Should never get here */
1536         return HTTP_SERVER_ERROR;
1537     }
1538 
1539     /* See if this namespace whitelists auth schemes */
1540     if (namespace->auth_schemes) {
1541         avail_auth_schemes = (namespace->auth_schemes & avail_auth_schemes);
1542 
1543         /* Bearer auth must be advertised and supported by the namespace */
1544         if ((namespace->auth_schemes & AUTH_BEARER) && namespace->bearer) {
1545             avail_auth_schemes |= AUTH_BEARER;
1546         }
1547     }
1548 
1549     return 0;
1550 }
1551 
1552 
auth_check_hdrs(struct transaction_t * txn,int * sasl_result)1553 static int auth_check_hdrs(struct transaction_t *txn, int *sasl_result)
1554 {
1555     int ret = 0, r = 0;
1556     const char **hdr;
1557 
1558     if (txn->flags.redirect) return 0;
1559 
1560     /* Perform authentication, if necessary */
1561     if ((hdr = spool_getheader(txn->req_hdrs, "Authorization"))) {
1562         if (httpd_userid) {
1563             /* Reauth - reinitialize */
1564             syslog(LOG_DEBUG, "reauth - reinit");
1565             reset_saslconn(&httpd_saslconn);
1566             txn->auth_chal.scheme = NULL;
1567         }
1568 
1569         if (httpd_tls_required) {
1570             /* TLS required - redirect handled below */
1571             ret = HTTP_UNAUTHORIZED;
1572         }
1573         else {
1574             /* Check the auth credentials */
1575             r = http_auth(hdr[0], txn);
1576             if ((r < 0) || !txn->auth_chal.scheme) {
1577                 /* Auth failed - reinitialize */
1578                 syslog(LOG_DEBUG, "auth failed - reinit");
1579                 reset_saslconn(&httpd_saslconn);
1580                 txn->auth_chal.scheme = NULL;
1581                 if (r == SASL_UNAVAIL) {
1582                     /* The namespace to authenticate to is unavailable.
1583                      * There could be any reason for this, e.g. the DAV
1584                      * handler could have run into a timeout for the
1585                      * user's dabatase. In any case, there's no sense
1586                      * to challenge the client for authentication. */
1587                     ret = HTTP_UNAVAILABLE;
1588                 }
1589                 else if (r == SASL_FAIL) {
1590                     ret = HTTP_SERVER_ERROR;
1591                 }
1592                 else {
1593                     ret = HTTP_UNAUTHORIZED;
1594                 }
1595             }
1596             else if (r == SASL_CONTINUE) {
1597                 /* Continue with multi-step authentication */
1598                 ret = HTTP_UNAUTHORIZED;
1599             }
1600         }
1601     }
1602     else if (!httpd_userid && txn->auth_chal.scheme) {
1603         /* Started auth exchange, but client didn't engage - reinit */
1604         syslog(LOG_DEBUG, "client didn't complete auth - reinit");
1605         reset_saslconn(&httpd_saslconn);
1606         txn->auth_chal.scheme = NULL;
1607     }
1608 
1609     /* Drop auth credentials, if not a backend in a Murder */
1610     else if (!config_mupdate_server || !config_getstring(IMAPOPT_PROXYSERVERS)) {
1611         syslog(LOG_DEBUG, "drop auth creds");
1612 
1613         free(httpd_userid);
1614         httpd_userid = NULL;
1615 
1616         free(httpd_extrafolder);
1617         httpd_extrafolder = NULL;
1618 
1619         free(httpd_extradomain);
1620         httpd_extradomain = NULL;
1621 
1622         if (httpd_authstate) {
1623             auth_freestate(httpd_authstate);
1624             httpd_authstate = NULL;
1625         }
1626     }
1627 
1628     /* Perform proxy authorization, if necessary */
1629     else if (httpd_authid &&
1630              (hdr = spool_getheader(txn->req_hdrs, "Authorize-As")) &&
1631              *hdr[0]) {
1632         const char *authzid = hdr[0];
1633 
1634         r = proxy_authz(&authzid, txn);
1635         if (r) {
1636             /* Proxy authz failed - reinitialize */
1637             syslog(LOG_DEBUG, "proxy authz failed - reinit");
1638             reset_saslconn(&httpd_saslconn);
1639             txn->auth_chal.scheme = NULL;
1640             ret = HTTP_UNAUTHORIZED;
1641         }
1642         else {
1643             ret = auth_success(txn, authzid);
1644         }
1645     }
1646 
1647     *sasl_result = r;
1648 
1649     return ret;
1650 }
1651 
1652 
postauth_check_hdrs(struct transaction_t * txn)1653 static void postauth_check_hdrs(struct transaction_t *txn)
1654 {
1655     const char **hdr;
1656 
1657     if (txn->flags.redirect) return;
1658 
1659     /* Check if this is a Cross-Origin Resource Sharing request */
1660     if (allow_cors && (hdr = spool_getheader(txn->req_hdrs, "Origin"))) {
1661         const char *err = NULL;
1662         xmlURIPtr uri = parse_uri(METH_UNKNOWN, hdr[0], 0, &err);
1663 
1664         if (uri && uri->scheme && uri->server) {
1665             int o_https = !strcasecmp(uri->scheme, "https");
1666 
1667             if ((https == o_https) &&
1668                 !strcasecmp(uri->server,
1669                             *spool_getheader(txn->req_hdrs, ":authority"))) {
1670                 txn->flags.cors = CORS_SIMPLE;
1671             }
1672             else {
1673                 struct wildmat *wild;
1674 
1675                 /* Create URI w/o path or default port */
1676                 assert(!buf_len(&txn->buf));
1677                 buf_printf(&txn->buf, "%s://%s",
1678                            lcase(uri->scheme), lcase(uri->server));
1679                 if (uri->port &&
1680                     ((o_https && uri->port != 443) ||
1681                      (!o_https && uri->port != 80))) {
1682                     buf_printf(&txn->buf, ":%d", uri->port);
1683                 }
1684 
1685                 /* Check Origin against the 'httpallowcors' wildmat */
1686                 for (wild = allow_cors; wild->pat; wild++) {
1687                     if (wildmat(buf_cstring(&txn->buf), wild->pat)) {
1688                         /* If we have a non-negative match, allow request */
1689                         if (!wild->not) txn->flags.cors = CORS_SIMPLE;
1690                         break;
1691                     }
1692                 }
1693                 buf_reset(&txn->buf);
1694             }
1695         }
1696         xmlFreeURI(uri);
1697     }
1698 
1699     /* Check if we should compress response body
1700 
1701        XXX  Do we want to support deflate even though M$
1702        doesn't implement it correctly (raw deflate vs. zlib)? */
1703     if (txn->zstrm &&
1704         txn->flags.ver == VER_1_1 &&
1705         (hdr = spool_getheader(txn->req_hdrs, "TE"))) {
1706         struct accept *e, *enc = parse_accept(hdr);
1707 
1708         for (e = enc; e && e->token; e++) {
1709             if (e->qual > 0.0 &&
1710                 (!strcasecmp(e->token, "gzip") ||
1711                  !strcasecmp(e->token, "x-gzip"))) {
1712                 txn->flags.te = TE_GZIP;
1713             }
1714             free(e->token);
1715         }
1716         if (enc) free(enc);
1717     }
1718     else if ((txn->zstrm || txn->brotli || txn->zstd) &&
1719              (hdr = spool_getheader(txn->req_hdrs, "Accept-Encoding"))) {
1720         struct accept *e, *enc = parse_accept(hdr);
1721         float qual = 0.0;
1722 
1723         for (e = enc; e && e->token; e++) {
1724             if (e->qual > 0.0 && e->qual >= qual) {
1725                 unsigned ce = CE_IDENTITY;
1726                 encode_proc_t proc = NULL;
1727 
1728                 if (txn->zstd && !strcasecmp(e->token, "zstd")) {
1729                     ce = CE_ZSTD;
1730                     proc = &zstd_compress;
1731                 }
1732                 else if (txn->brotli && !strcasecmp(e->token, "br")) {
1733                     ce = CE_BR;
1734                     proc = &brotli_compress;
1735                 }
1736                 else if (txn->zstrm && (!strcasecmp(e->token, "gzip") ||
1737                                         !strcasecmp(e->token, "x-gzip"))) {
1738                     ce = CE_GZIP;
1739                     proc = &zlib_compress;
1740                 }
1741                 else {
1742                     /* Unknown/unsupported */
1743                     e->qual = 0.0;
1744                 }
1745 
1746                 /* Favor Zstandard over Brotli over GZIP if q values are equal */
1747                 if (e->qual > qual || txn->resp_body.enc.type < ce) {
1748                     txn->resp_body.enc.type = ce;
1749                     txn->resp_body.enc.proc = proc;
1750                     qual = e->qual;
1751                 }
1752             }
1753             free(e->token);
1754         }
1755         if (enc) free(enc);
1756     }
1757 }
1758 
1759 
examine_request(struct transaction_t * txn,const char * uri)1760 EXPORTED int examine_request(struct transaction_t *txn, const char *uri)
1761 {
1762     int ret = 0, sasl_result = 0;
1763     const char *query;
1764     const struct namespace_t *namespace;
1765     struct request_line_t *req_line = &txn->req_line;
1766 
1767     if (!uri) uri = req_line->uri;
1768 
1769     /* Check method */
1770     if ((ret = check_method(txn))) return ret;
1771 
1772     /* Parse request-target URI */
1773     if (!(txn->req_uri = parse_uri(txn->meth, uri, 1, &txn->error.desc))) {
1774         return HTTP_BAD_REQUEST;
1775     }
1776 
1777     /* Perform pre-authentication check of headers */
1778     if ((ret = preauth_check_hdrs(txn))) return ret;
1779 
1780     /* Find the namespace of the requested resource */
1781     if ((ret = check_namespace(txn))) return ret;
1782 
1783     /* Perform check of authentication headers */
1784     ret = auth_check_hdrs(txn, &sasl_result);
1785 
1786     if (ret && ret != HTTP_UNAUTHORIZED) return ret;
1787 
1788     /* Register service/module and method */
1789     namespace = txn->req_tgt.namespace;
1790     buf_printf(&txn->buf, "%s%s", config_ident,
1791                namespace->well_known ? strrchr(namespace->well_known, '/') :
1792                namespace->prefix);
1793     proc_register(buf_cstring(&txn->buf), txn->conn->clienthost, httpd_userid,
1794                   txn->req_tgt.path, txn->req_line.meth);
1795     buf_reset(&txn->buf);
1796 
1797     /* Request authentication, if necessary */
1798     if (!httpd_userid && namespace->need_auth(txn)) {
1799         ret = HTTP_UNAUTHORIZED;
1800     }
1801 
1802     if (ret) return client_need_auth(txn, sasl_result);
1803 
1804     /* Parse any query parameters */
1805     construct_hash_table(&txn->req_qparams, 10, 1);
1806     query = URI_QUERY(txn->req_uri);
1807     if (query) parse_query_params(txn, query);
1808 
1809     /* Perform post-authentication check of headers */
1810     postauth_check_hdrs(txn);
1811 
1812     return 0;
1813 }
1814 
1815 
process_request(struct transaction_t * txn)1816 EXPORTED int process_request(struct transaction_t *txn)
1817 {
1818     int ret = 0;
1819 
1820     if (txn->req_tgt.namespace->premethod) {
1821         ret = txn->req_tgt.namespace->premethod(txn);
1822     }
1823     if (!ret) {
1824         const struct method_t *meth_t =
1825             &txn->req_tgt.namespace->methods[txn->meth];
1826 
1827         ret = (*meth_t->proc)(txn, meth_t->params);
1828 
1829         prometheus_increment(
1830             prometheus_lookup_label(http_methods[txn->meth].metric,
1831                                     txn->req_tgt.namespace->name));
1832     }
1833 
1834     if (ret == HTTP_UNAUTHORIZED) {
1835         /* User must authenticate */
1836         ret = client_need_auth(txn, 0);
1837     }
1838 
1839     return ret;
1840 }
1841 
1842 
http1_input(struct transaction_t * txn)1843 static int http1_input(struct transaction_t *txn)
1844 {
1845     struct request_line_t *req_line = &txn->req_line;
1846     int ignore_empty = 1, ret = 0;
1847 
1848     do {
1849         /* Read request-line */
1850         syslog(LOG_DEBUG, "read & parse request-line");
1851         if (!prot_fgets(req_line->buf, MAX_REQ_LINE+1, httpd_in)) {
1852             txn->error.desc = prot_error(httpd_in);
1853             if (txn->error.desc && strcmp(txn->error.desc, PROT_EOF_STRING)) {
1854                 /* client timed out */
1855                 syslog(LOG_WARNING, "%s, closing connection", txn->error.desc);
1856                 ret = HTTP_TIMEOUT;
1857             }
1858             else {
1859                 /* client closed connection */
1860                 syslog(LOG_DEBUG, "client closed connection");
1861             }
1862 
1863             txn->flags.conn = CONN_CLOSE;
1864             return ret;
1865         }
1866 
1867         /* Ignore 1 empty line before request-line per RFC 7230 Sec 3.5 */
1868     } while (ignore_empty-- && (strcspn(req_line->buf, "\r\n") == 0));
1869 
1870 
1871     /* Parse request-line = method SP request-target SP HTTP-version CRLF */
1872     ret = parse_request_line(txn);
1873 
1874     /* Parse headers */
1875     if (!ret) {
1876         ret = http_read_headers(httpd_in, 1 /* read_sep */,
1877                                 &txn->req_hdrs, &txn->error.desc);
1878     }
1879 
1880     if (ret) {
1881         txn->flags.conn = CONN_CLOSE;
1882         goto done;
1883     }
1884 
1885     /* Examine request */
1886     ret = examine_request(txn, NULL);
1887     if (ret) goto done;
1888 
1889     /* Start method processing alarm (HTTP/1.1 only) */
1890     if (txn->flags.ver == VER_1_1) alarm(httpd_keepalive);
1891 
1892     /* Process the requested method */
1893     ret = process_request(txn);
1894 
1895   done:
1896     /* Handle errors (success responses handled by method functions) */
1897     if (ret) error_response(ret, txn);
1898 
1899     /* Read and discard any unread request body */
1900     if (!(txn->flags.conn & CONN_CLOSE)) {
1901         txn->req_body.flags |= BODY_DISCARD;
1902         if (http_read_req_body(txn)) {
1903             txn->flags.conn = CONN_CLOSE;
1904         }
1905     }
1906 
1907     return 0;
1908 }
1909 
1910 
transaction_reset(struct transaction_t * txn)1911 static void transaction_reset(struct transaction_t *txn)
1912 {
1913     txn->meth = METH_UNKNOWN;
1914 
1915     memset(&txn->flags, 0, sizeof(struct txn_flags_t));
1916     txn->flags.ver = VER_1_1;
1917     txn->flags.vary = VARY_AE;
1918 
1919     memset(&txn->req_line, 0, sizeof(struct request_line_t));
1920 
1921     /* Reset Bearer auth scheme for each transaction */
1922     avail_auth_schemes &= ~AUTH_BEARER;
1923 
1924     if (txn->req_uri) xmlFreeURI(txn->req_uri);
1925     txn->req_uri = NULL;
1926 
1927     /* XXX - split this into a req_tgt cleanup */
1928     free(txn->req_tgt.userid);
1929     mboxlist_entry_free(&txn->req_tgt.mbentry);
1930     memset(&txn->req_tgt, 0, sizeof(struct request_target_t));
1931 
1932     free_hash_table(&txn->req_qparams, (void (*)(void *)) &freestrlist);
1933 
1934     if (txn->req_hdrs) spool_free_hdrcache(txn->req_hdrs);
1935     txn->req_hdrs = NULL;
1936 
1937     txn->req_body.flags = 0;
1938     buf_reset(&txn->req_body.payload);
1939 
1940     txn->auth_chal.param = NULL;
1941     txn->location = NULL;
1942     memset(&txn->error, 0, sizeof(struct error_t));
1943 
1944     strarray_fini(&txn->resp_body.links);
1945     memset(&txn->resp_body, 0,  /* Don't zero the response payload buffer */
1946            sizeof(struct resp_body_t) - sizeof(struct buf));
1947     buf_reset(&txn->resp_body.payload);
1948 
1949     /* Pre-allocate our working buffer */
1950     buf_reset(&txn->buf);
1951     buf_ensure(&txn->buf, 1024);
1952 }
1953 
1954 
transaction_free(struct transaction_t * txn)1955 EXPORTED void transaction_free(struct transaction_t *txn)
1956 {
1957     transaction_reset(txn);
1958 
1959     ws_end_channel(txn->ws_ctx);
1960     http2_end_stream(txn->strm_ctx);
1961 
1962     zlib_done(txn->zstrm);
1963     zstd_done(txn->zstd);
1964     brotli_done(txn->brotli);
1965 
1966     buf_free(&txn->req_body.payload);
1967     buf_free(&txn->resp_body.payload);
1968     buf_free(&txn->zbuf);
1969     buf_free(&txn->buf);
1970 }
1971 
1972 
1973 /*
1974  * Top-level command loop parsing
1975  */
cmdloop(struct http_connection * conn)1976 static void cmdloop(struct http_connection *conn)
1977 {
1978     struct transaction_t txn;
1979 
1980     /* Start with an empty (clean) transaction */
1981     memset(&txn, 0, sizeof(struct transaction_t));
1982     txn.conn = conn;
1983 
1984     if (config_getswitch(IMAPOPT_HTTPALLOWCOMPRESS)) {
1985         txn.zstrm = zlib_init();
1986         txn.zstd = zstd_init();
1987         txn.brotli = brotli_init();
1988     }
1989 
1990     /* Enable command timer */
1991     cmdtime_settimer(1);
1992 
1993     /* Enable provisional responses for long-running mailbox ops */
1994     mailbox_set_wait_cb((mailbox_wait_cb_t *) &keepalive_response, &txn);
1995 
1996     do {
1997         int ret = 0;
1998 
1999         /* Reset txn state */
2000         transaction_reset(&txn);
2001 
2002         /* make sure nothing leaked */
2003         assert(!open_mailboxes_exist());
2004         assert(!open_mboxlocks_exist());
2005 
2006         sync_log_reset();
2007 
2008         /* Check for input from client */
2009         do {
2010             /* Flush any buffered output */
2011             prot_flush(httpd_out);
2012             if (backend_current) prot_flush(backend_current->out);
2013 
2014             /* Check for shutdown file */
2015             if (shutdown_file(txn.buf.s, txn.buf.alloc) ||
2016                 (httpd_userid &&
2017                  userdeny(httpd_userid, config_ident, txn.buf.s, txn.buf.alloc))) {
2018                 txn.error.desc = txn.buf.s;
2019                 txn.flags.conn = CONN_CLOSE;
2020                 ret = HTTP_SHUTDOWN;
2021                 break;
2022             }
2023 
2024             signals_poll();
2025 
2026             syslog(LOG_DEBUG, "proxy_check_input()");
2027 
2028         } while (!proxy_check_input(protin, httpd_in, httpd_out,
2029                                     backend_current ? backend_current->in : NULL,
2030                                     NULL, 30));
2031 
2032 
2033         /* Start command timer */
2034         cmdtime_starttimer();
2035 
2036         if (txn.conn->sess_ctx) {
2037             /* HTTP/2 input */
2038             http2_input(&txn);
2039         }
2040         else if (txn.ws_ctx) {
2041             /* WebSocket over HTTP/1.1 input */
2042             ws_input(&txn);
2043         }
2044         else if (!ret) {
2045             /* HTTP/1.x request */
2046             http1_input(&txn);
2047         }
2048 
2049         if (ret == HTTP_SHUTDOWN) {
2050             syslog(LOG_WARNING,
2051                    "Shutdown file: \"%s\", closing connection", txn.error.desc);
2052             protgroup_free(protin);
2053             shut_down(0);
2054         }
2055 
2056     } while (!(txn.flags.conn & CONN_CLOSE));
2057 
2058     /* Memory cleanup */
2059     transaction_free(&txn);
2060 }
2061 
2062 /****************************  Parsing Routines  ******************************/
2063 
2064 /* Parse URI, returning the path */
parse_uri(unsigned meth,const char * uri,unsigned path_reqd,const char ** errstr)2065 EXPORTED xmlURIPtr parse_uri(unsigned meth, const char *uri, unsigned path_reqd,
2066                     const char **errstr)
2067 {
2068     xmlURIPtr p_uri;  /* parsed URI */
2069 
2070     /* Parse entire URI */
2071     if ((p_uri = xmlParseURI(uri)) == NULL) {
2072         *errstr = "Illegal request target URI";
2073         goto bad_request;
2074     }
2075 
2076     if (p_uri->scheme) {
2077         /* Check sanity of scheme */
2078 
2079         if (strcasecmp(p_uri->scheme, "http") &&
2080             strcasecmp(p_uri->scheme, "https")) {
2081             *errstr = "Unsupported URI scheme";
2082             goto bad_request;
2083         }
2084     }
2085 
2086     /* Check sanity of path */
2087     if (path_reqd && (!p_uri->path || !*p_uri->path)) {
2088         *errstr = "Empty path in target URI";
2089         goto bad_request;
2090     }
2091     else if (p_uri->path) {
2092         size_t pathlen = strlen(p_uri->path);
2093         if ((p_uri->path[0] != '/') &&
2094             (strcmp(p_uri->path, "*") || (meth != METH_OPTIONS))) {
2095             /* No special URLs except for "OPTIONS * HTTP/1.1" */
2096             *errstr = "Illegal request target URI";
2097             goto bad_request;
2098         }
2099         else if (strstr(p_uri->path, "/../")) {
2100             /* Don't allow access up directory tree */
2101             *errstr = "Illegal request target URI";
2102             goto bad_request;
2103         }
2104         else if (pathlen >= 3 && !strcmp("/..", p_uri->path + pathlen - 3)) {
2105             /* Don't allow access up directory tree */
2106             *errstr = "Illegal request target URI";
2107             goto bad_request;
2108         }
2109         else if (pathlen > MAX_MAILBOX_PATH) {
2110             *errstr = "Request target URI too long";
2111             goto bad_request;
2112         }
2113     }
2114 
2115     return p_uri;
2116 
2117   bad_request:
2118     if (p_uri) xmlFreeURI(p_uri);
2119     return NULL;
2120 }
2121 
2122 
2123 /* Calculate compile time of a file for use as Last-Modified and/or ETag */
calc_compile_time(const char * time,const char * date)2124 EXPORTED time_t calc_compile_time(const char *time, const char *date)
2125 {
2126     struct tm tm;
2127     char month[4];
2128 
2129     memset(&tm, 0, sizeof(struct tm));
2130     tm.tm_isdst = -1;
2131     sscanf(time, "%02d:%02d:%02d", &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
2132     sscanf(date, "%3s %2d %4d", month, &tm.tm_mday, &tm.tm_year);
2133     tm.tm_year -= 1900;
2134     for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
2135         if (!strcmp(month, monthname[tm.tm_mon])) break;
2136     }
2137 
2138     return mktime(&tm);
2139 }
2140 
2141 /* Parse Expect header(s) for interesting expectations */
parse_expect(struct transaction_t * txn)2142 static int parse_expect(struct transaction_t *txn)
2143 {
2144     const char **exp = spool_getheader(txn->req_hdrs, "Expect");
2145     int i, ret = 0;
2146 
2147     /* Expect not supported by HTTP/1.0 clients */
2148     if (exp && txn->flags.ver == VER_1_0) return HTTP_EXPECT_FAILED;
2149 
2150     /* Look for interesting expectations.  Unknown == error */
2151     for (i = 0; !ret && exp && exp[i]; i++) {
2152         tok_t tok = TOK_INITIALIZER(exp[i], ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
2153         char *token;
2154 
2155         while (!ret && (token = tok_next(&tok))) {
2156             /* Check if client wants acknowledgment before sending body */
2157             if (!strcasecmp(token, "100-continue")) {
2158                 syslog(LOG_DEBUG, "Expect: 100-continue");
2159                 txn->req_body.flags |= BODY_CONTINUE;
2160             }
2161             else {
2162                 txn->error.desc = "Unsupported Expectation";
2163                 ret = HTTP_EXPECT_FAILED;
2164             }
2165         }
2166 
2167         tok_fini(&tok);
2168     }
2169 
2170     return ret;
2171 }
2172 
2173 
2174 /* Parse Connection header(s) for interesting options */
parse_connection(struct transaction_t * txn)2175 static int parse_connection(struct transaction_t *txn)
2176 {
2177     const char **conn = spool_getheader(txn->req_hdrs, "Connection");
2178     int i;
2179 
2180     if (conn && txn->flags.ver == VER_2) {
2181         txn->error.desc = "Connection not allowed in HTTP/2";
2182         return HTTP_BAD_REQUEST;
2183     }
2184 
2185     if (!httpd_timeout || txn->flags.ver == VER_1_0) {
2186         /* Non-persistent connection by default */
2187         txn->flags.conn |= CONN_CLOSE;
2188     }
2189 
2190     /* Look for interesting connection tokens */
2191     for (i = 0; conn && conn[i]; i++) {
2192         tok_t tok = TOK_INITIALIZER(conn[i], ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
2193         char *token;
2194 
2195         while ((token = tok_next(&tok))) {
2196             switch (txn->flags.ver) {
2197             case VER_1_1:
2198                 if (!strcasecmp(token, "Upgrade")) {
2199                     /* Client wants to upgrade */
2200                     const char **upgrade =
2201                         spool_getheader(txn->req_hdrs, "Upgrade");
2202 
2203                     if (upgrade && upgrade[0]) {
2204                         if (!txn->conn->tls_ctx && tls_enabled() &&
2205                             !strncasecmp(upgrade[0], TLS_VERSION,
2206                                          strcspn(upgrade[0], " ,"))) {
2207                             /* Upgrade to TLS */
2208                             txn->flags.conn |= CONN_UPGRADE;
2209                             txn->flags.upgrade |= UPGRADE_TLS;
2210                         }
2211                         else if (http2_enabled() &&
2212                                  !strncasecmp(upgrade[0],
2213                                               NGHTTP2_CLEARTEXT_PROTO_VERSION_ID,
2214                                               strcspn(upgrade[0], " ,"))) {
2215                             /* Upgrade to HTTP/2 */
2216                             txn->flags.conn |= CONN_UPGRADE;
2217                             txn->flags.upgrade |= UPGRADE_HTTP2;
2218                         }
2219                         else if (ws_enabled() &&
2220                                  !strncasecmp(upgrade[0], WS_TOKEN,
2221                                               strcspn(upgrade[0], " ,"))) {
2222                             /* Upgrade to WebSockets */
2223                             txn->flags.conn |= CONN_UPGRADE;
2224                             txn->flags.upgrade |= UPGRADE_WS;
2225                         }
2226                         else {
2227                             /* Unknown/unsupported protocol - no upgrade */
2228                         }
2229                     }
2230                 }
2231                 else if (!strcasecmp(token, "close")) {
2232                     /* Non-persistent connection */
2233                     txn->flags.conn |= CONN_CLOSE;
2234                 }
2235                 break;
2236 
2237             case VER_1_0:
2238                 if (httpd_timeout && !strcasecmp(token, "keep-alive")) {
2239                     /* Persistent connection */
2240                     txn->flags.conn = CONN_KEEPALIVE;
2241                 }
2242                 break;
2243             }
2244         }
2245 
2246         tok_fini(&tok);
2247     }
2248 
2249     return 0;
2250 }
2251 
2252 
2253 /* Compare accept quality values so that they sort in descending order */
compare_accept(const struct accept * a1,const struct accept * a2)2254 static int compare_accept(const struct accept *a1, const struct accept *a2)
2255 {
2256     if (a2->qual < a1->qual) return -1;
2257     if (a2->qual > a1->qual) return 1;
2258     return 0;
2259 }
2260 
parse_accept(const char ** hdr)2261 struct accept *parse_accept(const char **hdr)
2262 {
2263     int i, n = 0, alloc = 0;
2264     struct accept *ret = NULL;
2265 #define GROW_ACCEPT 10;
2266 
2267     for (i = 0; hdr && hdr[i]; i++) {
2268         tok_t tok = TOK_INITIALIZER(hdr[i], ";,", TOK_TRIMLEFT|TOK_TRIMRIGHT);
2269         char *token;
2270 
2271         while ((token = tok_next(&tok))) {
2272             if (!strncmp(token, "q=", 2)) {
2273                 if (!ret) break;
2274                 ret[n-1].qual = strtof(token+2, NULL);
2275             }
2276             else {
2277                 if (n + 1 >= alloc)  {
2278                     alloc += GROW_ACCEPT;
2279                     ret = xrealloc(ret, alloc * sizeof(struct accept));
2280                 }
2281                 ret[n].token = xstrdup(token);
2282                 ret[n].qual = 1.0;
2283                 ret[++n].token = NULL;
2284             }
2285         }
2286         tok_fini(&tok);
2287     }
2288 
2289     qsort(ret, n, sizeof(struct accept),
2290           (int (*)(const void *, const void *)) &compare_accept);
2291 
2292     return ret;
2293 }
2294 
2295 
2296 /* Parse the query string and add key/value pairs to hash table */
parse_query_params(struct transaction_t * txn,const char * query)2297 void parse_query_params(struct transaction_t *txn, const char *query)
2298 {
2299     tok_t tok;
2300     char *param;
2301 
2302     assert(!buf_len(&txn->buf));  /* Unescape buffer */
2303 
2304     tok_init(&tok, query, "&", TOK_TRIMLEFT|TOK_TRIMRIGHT|TOK_EMPTY);
2305     while ((param = tok_next(&tok))) {
2306         struct strlist *vals;
2307         char *key, *value;
2308         size_t len;
2309 
2310         /* Split param into key and optional value */
2311         key = param;
2312         value = strchr(param, '=');
2313 
2314         if (!value) value = "";
2315         else *value++ = '\0';
2316         len = strlen(value);
2317         buf_ensure(&txn->buf, len+1);
2318 
2319         vals = hash_lookup(key, &txn->req_qparams);
2320         appendstrlist(&vals, xmlURIUnescapeString(value, len, txn->buf.s));
2321         hash_insert(key, vals, &txn->req_qparams);
2322     }
2323     tok_fini(&tok);
2324 
2325     buf_reset(&txn->buf);
2326 }
2327 
2328 
2329 /****************************  Response Routines  *****************************/
2330 
2331 
2332 /* Create HTTP-date ('buf' must be at least 30 characters) */
httpdate_gen(char * buf,size_t len,time_t t)2333 EXPORTED char *httpdate_gen(char *buf, size_t len, time_t t)
2334 {
2335     struct tm *tm = gmtime(&t);
2336 
2337     snprintf(buf, len, "%3s, %02d %3s %4d %02d:%02d:%02d GMT",
2338              wday[tm->tm_wday],
2339              tm->tm_mday, monthname[tm->tm_mon], tm->tm_year + 1900,
2340              tm->tm_hour, tm->tm_min, tm->tm_sec);
2341 
2342     return buf;
2343 }
2344 
2345 
2346 /* Create an HTTP Status-Line given response code */
http_statusline(unsigned ver,long code)2347 EXPORTED const char *http_statusline(unsigned ver, long code)
2348 {
2349     static struct buf statline = BUF_INITIALIZER;
2350 
2351     if (ver == VER_2) buf_setcstr(&statline, HTTP2_VERSION);
2352     else {
2353         buf_setmap(&statline, HTTP_VERSION, HTTP_VERSION_LEN-1);
2354         buf_putc(&statline, ver + '0');
2355     }
2356 
2357     buf_putc(&statline, ' ');
2358     buf_appendcstr(&statline, error_message(code));
2359     return buf_cstring(&statline);
2360 }
2361 
2362 
2363 /* Output an HTTP response header.
2364  * 'code' specifies the HTTP Status-Code and Reason-Phrase.
2365  * 'txn' contains the transaction context
2366  */
2367 
simple_hdr(struct transaction_t * txn,const char * name,const char * value,...)2368 EXPORTED void simple_hdr(struct transaction_t *txn,
2369                          const char *name, const char *value, ...)
2370 {
2371     struct buf buf = BUF_INITIALIZER;
2372     va_list args;
2373 
2374     va_start(args, value);
2375     buf_vprintf(&buf, value, args);
2376     va_end(args);
2377 
2378     syslog(LOG_DEBUG, "simple_hdr(%s: %s)", name, buf_cstring(&buf));
2379 
2380     if (txn->flags.ver == VER_2) {
2381         http2_add_header(txn, name, &buf);
2382     }
2383     else {
2384         prot_printf(txn->conn->pout, "%c%s: ", toupper(name[0]), name+1);
2385         prot_puts(txn->conn->pout, buf_cstring(&buf));
2386         prot_puts(txn->conn->pout, "\r\n");
2387 
2388         buf_free(&buf);
2389     }
2390 }
2391 
2392 #define WWW_Authenticate(name, param)                           \
2393     simple_hdr(txn, "WWW-Authenticate", param ? "%s %s" : "%s", name, param)
2394 
2395 #define Access_Control_Expose(hdr)                              \
2396     simple_hdr(txn, "Access-Control-Expose-Headers", hdr)
2397 
comma_list_body(struct buf * buf,const char * vals[],unsigned flags,int has_args,va_list args)2398 static void comma_list_body(struct buf *buf,
2399                             const char *vals[], unsigned flags, int has_args, va_list args)
2400 {
2401     const char *sep = "";
2402     int i;
2403 
2404     for (i = 0; vals[i]; i++) {
2405         if (flags & (1 << i)) {
2406             buf_appendcstr(buf, sep);
2407             if (has_args) buf_vprintf(buf, vals[i], args);
2408             else buf_appendcstr(buf, vals[i]);
2409             sep = ", ";
2410         }
2411         else if (has_args) {
2412             /* discard any unused args */
2413             vsnprintf(NULL, 0, vals[i], args);
2414         }
2415     }
2416 }
2417 
comma_list_hdr(struct transaction_t * txn,const char * name,const char * vals[],unsigned flags,...)2418 EXPORTED void comma_list_hdr(struct transaction_t *txn, const char *name,
2419                              const char *vals[], unsigned flags, ...)
2420 {
2421     struct buf buf = BUF_INITIALIZER;
2422     va_list args;
2423 
2424     va_start(args, flags);
2425 
2426     comma_list_body(&buf, vals, flags, 1, args);
2427 
2428     va_end(args);
2429 
2430     simple_hdr(txn, name, "%s", buf_cstring(&buf));
2431 
2432     buf_free(&buf);
2433 }
2434 
list_auth_schemes(struct transaction_t * txn)2435 EXPORTED void list_auth_schemes(struct transaction_t *txn)
2436 {
2437     struct auth_challenge_t *auth_chal = &txn->auth_chal;
2438     unsigned conn_close = (txn->flags.conn & CONN_CLOSE);
2439     struct auth_scheme_t *scheme;
2440 
2441     /* Advertise available schemes that can work with the type of connection */
2442     for (scheme = auth_schemes; scheme->name; scheme++) {
2443         if ((avail_auth_schemes & scheme->id) &&
2444             !(conn_close && (scheme->flags & AUTH_NEED_PERSIST))) {
2445             auth_chal->param = NULL;
2446 
2447             if (scheme->flags & AUTH_SERVER_FIRST) {
2448                 /* Generate the initial challenge */
2449                 http_auth(scheme->name, txn);
2450 
2451                 if (!auth_chal->param) continue;  /* If fail, skip it */
2452             }
2453             WWW_Authenticate(scheme->name, auth_chal->param);
2454         }
2455     }
2456 }
2457 
allow_hdr(struct transaction_t * txn,const char * name,unsigned allow)2458 EXPORTED void allow_hdr(struct transaction_t *txn,
2459                         const char *name, unsigned allow)
2460 {
2461     const char *meths[] = {
2462         "OPTIONS, GET, HEAD", "POST", "PUT",
2463         "PATCH", "DELETE", "TRACE", "CONNECT", NULL
2464     };
2465 
2466     comma_list_hdr(txn, name, meths, allow);
2467 
2468     if (allow & ALLOW_DAV) {
2469         simple_hdr(txn, name, "PROPFIND, REPORT, COPY%s%s%s%s%s",
2470                    (allow & ALLOW_DELETE)    ? ", MOVE" : "",
2471                    (allow & ALLOW_PROPPATCH) ? ", PROPPATCH" : "",
2472                    (allow & ALLOW_MKCOL)     ? ", MKCOL" : "",
2473                    (allow & ALLOW_WRITE)     ? ", LOCK, UNLOCK" : "",
2474                    (allow & ALLOW_ACL)       ? ", ACL" : "");
2475         if ((allow & ALLOW_CAL) && (allow & ALLOW_MKCOL))
2476             simple_hdr(txn, name, "MKCALENDAR");
2477     }
2478 }
2479 
accept_patch_hdr(struct transaction_t * txn,const struct patch_doc_t * patch)2480 EXPORTED void accept_patch_hdr(struct transaction_t *txn,
2481                                const struct patch_doc_t *patch)
2482 {
2483     struct buf buf = BUF_INITIALIZER;
2484     const char *sep = "";
2485     int i;
2486 
2487     for (i = 0; patch[i].format; i++) {
2488         buf_appendcstr(&buf, sep);
2489         buf_appendcstr(&buf, patch[i].format);
2490         sep = ", ";
2491     }
2492 
2493     simple_hdr(txn, "Accept-Patch", "%s", buf_cstring(&buf));
2494 
2495     buf_free(&buf);
2496 }
2497 
2498 #define MD5_BASE64_LEN 25   /* ((MD5_DIGEST_LENGTH / 3) + 1) * 4 */
2499 
content_md5_hdr(struct transaction_t * txn,const unsigned char * md5)2500 EXPORTED void content_md5_hdr(struct transaction_t *txn,
2501                               const unsigned char *md5)
2502 {
2503     char base64[MD5_BASE64_LEN+1];
2504 
2505     sasl_encode64((char *) md5, MD5_DIGEST_LENGTH, base64, MD5_BASE64_LEN, NULL);
2506     simple_hdr(txn, "Content-MD5", "%s", base64);
2507 }
2508 
begin_resp_headers(struct transaction_t * txn,long code)2509 EXPORTED void begin_resp_headers(struct transaction_t *txn, long code)
2510 {
2511     if (txn->flags.ver == VER_2) {
2512         http2_begin_headers(txn);
2513         if (code) simple_hdr(txn, ":status", "%.3s", error_message(code));
2514     }
2515     else if (code) prot_printf(txn->conn->pout, "%s\r\n",
2516                                http_statusline(txn->flags.ver, code));
2517 }
2518 
end_resp_headers(struct transaction_t * txn,long code)2519 EXPORTED int end_resp_headers(struct transaction_t *txn, long code)
2520 {
2521     int r = 0;
2522 
2523     if (txn->flags.ver == VER_2) {
2524         r = http2_end_headers(txn, code);
2525     }
2526     else {
2527         /* CRLF terminating the header block */
2528         prot_puts(txn->conn->pout, "\r\n");
2529     }
2530 
2531     return r;
2532 }
2533 
2534 
2535 /* Write end-to-end header (ignoring hop-by-hop) from cache to protstream. */
write_cachehdr(const char * name,const char * contents,const char * raw,void * rock)2536 static void write_cachehdr(const char *name, const char *contents,
2537                            const char *raw __attribute__((unused)), void *rock)
2538 {
2539     struct transaction_t *txn = (struct transaction_t *) rock;
2540     const char **hdr, *hop_by_hop[] =
2541         { "connection", "content-length", "content-type", "date", "forwarded",
2542           "keep-alive", "location", "status", "strict-transport-security",
2543           "upgrade", "via", NULL };
2544 
2545     /* Ignore private headers in our cache */
2546     if (name[0] == ':') return;
2547 
2548     for (hdr = hop_by_hop; *hdr; hdr++) {
2549         if (!strcasecmp(name, *hdr)) return;
2550     }
2551 
2552     simple_hdr(txn, name, "%s", contents);
2553 }
2554 
response_header(long code,struct transaction_t * txn)2555 EXPORTED void response_header(long code, struct transaction_t *txn)
2556 {
2557     int i, size;
2558     time_t now;
2559     char datestr[30];
2560     va_list noargs;
2561     double cmdtime, nettime;
2562     const char **hdr, *sep;
2563     struct auth_challenge_t *auth_chal = &txn->auth_chal;
2564     struct resp_body_t *resp_body = &txn->resp_body;
2565     struct buf *logbuf = &txn->conn->logbuf;
2566     const char *upgrd_tokens[] =
2567         { TLS_VERSION, NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, WS_TOKEN, NULL };
2568     const char *te[] = { "deflate", "gzip", "chunked", NULL };
2569     const char *ce[] = { "deflate", "gzip", "br", "zstd", NULL };
2570 
2571     /* Stop method processing alarm */
2572     alarm(0);
2573     gotsigalrm = 0;
2574 
2575 
2576     /* Status-Line */
2577     begin_resp_headers(txn, code);
2578 
2579 
2580     switch (code) {
2581     default:
2582         /* Final response */
2583         now = time(0);
2584         httpdate_gen(datestr, sizeof(datestr), now);
2585         simple_hdr(txn, "Date", "%s", datestr);
2586 
2587         /* Fall through and specify connection options and/or links */
2588         GCC_FALLTHROUGH
2589 
2590     case HTTP_SWITCH_PROT:
2591         if (txn->flags.conn && (txn->flags.ver < VER_2)) {
2592             /* Construct Connection header */
2593             const char *conn_tokens[] =
2594                 { "close", "Upgrade", "Keep-Alive", NULL };
2595 
2596             comma_list_hdr(txn, "Connection", conn_tokens, txn->flags.conn);
2597 
2598             if (txn->flags.upgrade) {
2599                 /* Construct Upgrade header */
2600                 comma_list_hdr(txn, "Upgrade", upgrd_tokens, txn->flags.upgrade);
2601 
2602                 if (txn->flags.upgrade & UPGRADE_WS) {
2603                     /* Add WebSocket headers */
2604                     ws_add_resp_hdrs(txn);
2605                 }
2606             }
2607             if (txn->flags.conn & CONN_KEEPALIVE) {
2608                 simple_hdr(txn, "Keep-Alive", "timeout=%d", httpd_timeout);
2609             }
2610         }
2611 
2612         /* Fall through and specify links */
2613         GCC_FALLTHROUGH
2614 
2615     case HTTP_EARLY_HINTS:
2616         size = strarray_size(&resp_body->links);
2617         for (i = 0; i < size; i++) {
2618             simple_hdr(txn, "Link", "%s", strarray_nth(&resp_body->links, i));
2619         }
2620 
2621         if (code >= HTTP_OK) break;
2622 
2623         /* Fall through as provisional response */
2624         GCC_FALLTHROUGH
2625 
2626     case HTTP_CONTINUE:
2627     case HTTP_PROCESSING:
2628         /* Provisional response - nothing else needed */
2629         end_resp_headers(txn, code);
2630 
2631         /* Force the response to the client immediately */
2632         prot_flush(httpd_out);
2633 
2634         /* Restart method processing alarm (HTTP/1.1 only) */
2635         if (!txn->ws_ctx && (txn->flags.ver == VER_1_1)) alarm(httpd_keepalive);
2636 
2637         goto log;
2638     }
2639 
2640 
2641     /* Control Data */
2642     if (txn->conn->tls_ctx) {
2643         simple_hdr(txn, "Strict-Transport-Security", "max-age=600");
2644     }
2645     if (txn->location) {
2646         simple_hdr(txn, "Location", "%s", txn->location);
2647     }
2648     if (txn->flags.mime) {
2649         simple_hdr(txn, "MIME-Version", "1.0");
2650     }
2651     if (txn->flags.cc) {
2652         /* Construct Cache-Control header */
2653         const char *cc_dirs[] =
2654             { "must-revalidate", "no-cache", "no-store", "no-transform",
2655               "public", "private", "max-age=%d", "immutable", NULL };
2656 
2657         comma_list_hdr(txn, "Cache-Control",
2658                        cc_dirs, txn->flags.cc, resp_body->maxage);
2659 
2660         if (txn->flags.cc & CC_MAXAGE) {
2661             httpdate_gen(datestr, sizeof(datestr), now + resp_body->maxage);
2662             simple_hdr(txn, "Expires", "%s", datestr);
2663         }
2664     }
2665     if (txn->flags.cors) {
2666         /* Construct Cross-Origin Resource Sharing headers */
2667         simple_hdr(txn, "Access-Control-Allow-Origin", "%s",
2668                       *spool_getheader(txn->req_hdrs, "Origin"));
2669         simple_hdr(txn, "Access-Control-Allow-Credentials", "true");
2670 
2671         if (txn->flags.cors == CORS_PREFLIGHT) {
2672             allow_hdr(txn, "Access-Control-Allow-Methods", txn->req_tgt.allow);
2673 
2674             for (hdr = spool_getheader(txn->req_hdrs,
2675                                        "Access-Control-Request-Headers");
2676                  hdr && *hdr; hdr++) {
2677                 simple_hdr(txn, "Access-Control-Allow-Headers", "%s", *hdr);
2678             }
2679             simple_hdr(txn, "Access-Control-Max-Age", "3600");
2680         }
2681     }
2682     if (txn->flags.vary && !(txn->flags.cc & CC_NOCACHE)) {
2683         /* Construct Vary header */
2684         const char *vary_hdrs[] = { "Accept", "Accept-Encoding", "Brief",
2685                                     "Prefer", "If-None-Match",
2686                                     "CalDAV-Timezones", NULL };
2687 
2688         comma_list_hdr(txn, "Vary", vary_hdrs, txn->flags.vary);
2689     }
2690 
2691 
2692     /* Authentication Challenges */
2693     if (code == HTTP_UNAUTHORIZED) {
2694         if (!auth_chal->scheme) {
2695             /* Require authentication by advertising all available schemes */
2696             list_auth_schemes(txn);
2697         }
2698         else {
2699             /* Continue with current authentication exchange */
2700             WWW_Authenticate(auth_chal->scheme->name, auth_chal->param);
2701         }
2702     }
2703     else if (auth_chal->param) {
2704         /* Authentication completed with success data */
2705         if (auth_chal->scheme->flags & AUTH_SUCCESS_WWW) {
2706             /* Special handling of success data for this scheme */
2707             WWW_Authenticate(auth_chal->scheme->name, auth_chal->param);
2708         }
2709         else {
2710             /* Default handling of success data */
2711             simple_hdr(txn, "Authentication-Info", "%s", auth_chal->param);
2712         }
2713     }
2714 
2715     /* Response Context */
2716     if (txn->req_tgt.allow & ALLOW_ISCHEDULE) {
2717         simple_hdr(txn, "iSchedule-Version", "1.0");
2718 
2719         if (resp_body->iserial) {
2720             simple_hdr(txn, "iSchedule-Capabilities", TIME_T_FMT, resp_body->iserial);
2721         }
2722     }
2723     if (resp_body->patch) {
2724         accept_patch_hdr(txn, resp_body->patch);
2725     }
2726 
2727     switch (code) {
2728     case HTTP_OK:
2729         switch (txn->meth) {
2730         case METH_CONNECT:
2731             if (txn->ws_ctx) {
2732                 /* Add WebSocket headers */
2733                 ws_add_resp_hdrs(txn);
2734             }
2735             break;
2736 
2737         case METH_GET:
2738         case METH_HEAD:
2739             /* Construct Accept-Ranges header for GET and HEAD responses */
2740             simple_hdr(txn, "Accept-Ranges",
2741                        txn->flags.ranges ? "bytes" : "none");
2742             break;
2743 
2744         case METH_OPTIONS:
2745             if (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) {
2746                 simple_hdr(txn, "Server", "%s", buf_cstring(&serverinfo));
2747             }
2748 
2749             if (!httpd_userid && !auth_chal->scheme) {
2750                 /* Advertise all available auth schemes */
2751                 list_auth_schemes(txn);
2752             }
2753 
2754             if (txn->req_tgt.allow & ALLOW_DAV) {
2755                 /* Construct DAV header(s) based on namespace of request URL */
2756                 simple_hdr(txn, "DAV", "1, 2, 3, access-control,"
2757                            " extended-mkcol, resource-sharing");
2758                 if (txn->req_tgt.allow & ALLOW_CAL) {
2759                     simple_hdr(txn, "DAV", "calendar-access%s%s",
2760                                (txn->req_tgt.allow & ALLOW_CAL_SCHED) ?
2761                                ", calendar-auto-schedule" : "",
2762                                (txn->req_tgt.allow & ALLOW_CAL_NOTZ) ?
2763                                ", calendar-no-timezone" : "");
2764                     simple_hdr(txn, "DAV", "calendar-query-extended%s%s",
2765                                (txn->req_tgt.allow & ALLOW_CAL_AVAIL) ?
2766                                ", calendar-availability" : "",
2767                                (txn->req_tgt.allow & ALLOW_CAL_ATTACH) ?
2768                                ", calendar-managed-attachments" : "");
2769 
2770                     /* Backwards compatibility with older Apple clients */
2771                     simple_hdr(txn, "DAV", "calendarserver-sharing%s",
2772                                (txn->req_tgt.allow &
2773                                 (ALLOW_CAL_AVAIL | ALLOW_CAL_SCHED)) ==
2774                                (ALLOW_CAL_AVAIL | ALLOW_CAL_SCHED) ?
2775                                ", inbox-availability" : "");
2776                 }
2777                 if (txn->req_tgt.allow & ALLOW_CARD) {
2778                     simple_hdr(txn, "DAV", "addressbook");
2779                 }
2780             }
2781 
2782             /* Access-Control-Allow-Methods supersedes Allow */
2783             if (txn->flags.cors != CORS_PREFLIGHT) {
2784                 /* Construct Allow header(s) */
2785                 allow_hdr(txn, "Allow", txn->req_tgt.allow);
2786             }
2787             break;
2788         }
2789 
2790         /* Fall through and specify supported content codings */
2791         GCC_FALLTHROUGH
2792 
2793     case HTTP_CREATED:
2794     case HTTP_ACCEPTED:
2795     case HTTP_NO_CONTENT:
2796     case HTTP_RESET_CONTENT:
2797     case HTTP_PARTIAL:
2798     case HTTP_MULTI_STATUS:
2799         if (accept_encodings && buf_len(&txn->req_body.payload)) {
2800             comma_list_hdr(txn, "Accept-Encoding", ce, accept_encodings);
2801         }
2802         break;
2803 
2804     case HTTP_NOT_ALLOWED:
2805         /* Construct Allow header(s) for 405 response */
2806         allow_hdr(txn, "Allow", txn->req_tgt.allow);
2807         break;
2808 
2809     case HTTP_BAD_CE:
2810         /* Construct Accept-Encoding header for 415 response */
2811         if (accept_encodings) {
2812             comma_list_hdr(txn, "Accept-Encoding", ce, accept_encodings);
2813         }
2814         else simple_hdr(txn, "Accept-Encoding", "identity");
2815         break;
2816     }
2817 
2818 
2819     /* Validators */
2820     if (resp_body->lock) {
2821         simple_hdr(txn, "Lock-Token", "<%s>", resp_body->lock);
2822         if (txn->flags.cors) Access_Control_Expose("Lock-Token");
2823     }
2824     if (resp_body->ctag) {
2825         simple_hdr(txn, "CTag", "%s", resp_body->ctag);
2826         if (txn->flags.cors) Access_Control_Expose("CTag");
2827     }
2828     if (resp_body->stag) {
2829         simple_hdr(txn, "Schedule-Tag", "\"%s\"", resp_body->stag);
2830         if (txn->flags.cors) Access_Control_Expose("Schedule-Tag");
2831     }
2832     if (resp_body->etag) {
2833         simple_hdr(txn, "ETag", "%s\"%s\"",
2834                       resp_body->enc.proc ? "W/" : "", resp_body->etag);
2835         if (txn->flags.cors) Access_Control_Expose("ETag");
2836     }
2837     if (resp_body->lastmod) {
2838         /* Last-Modified MUST NOT be in the future */
2839         resp_body->lastmod = MIN(resp_body->lastmod, now);
2840         httpdate_gen(datestr, sizeof(datestr), resp_body->lastmod);
2841         simple_hdr(txn, "Last-Modified", "%s", datestr);
2842     }
2843 
2844 
2845     /* Representation Metadata */
2846     if (resp_body->prefs) {
2847         /* Construct Preference-Applied header */
2848         const char *prefs[] =
2849             { "return=minimal", "return=representation", "depth-noroot", NULL };
2850 
2851         comma_list_hdr(txn, "Preference-Applied", prefs, resp_body->prefs);
2852         if (txn->flags.cors) Access_Control_Expose("Preference-Applied");
2853     }
2854     if (resp_body->cmid) {
2855         simple_hdr(txn, "Cal-Managed-ID", "%s", resp_body->cmid);
2856         if (txn->flags.cors) Access_Control_Expose("Cal-Managed-ID");
2857     }
2858     if (resp_body->type) {
2859         simple_hdr(txn, "Content-Type", "%s", resp_body->type);
2860         if (resp_body->dispo.fname) {
2861             /* Construct Content-Disposition header */
2862             const unsigned char *p = (const unsigned char *)resp_body->dispo.fname;
2863             char *encfname = NULL;
2864             for (p = (unsigned char *)resp_body->dispo.fname; p && *p; p++) {
2865                 if (*p >= 0x80) {
2866                     encfname = charset_encode_mimexvalue(resp_body->dispo.fname, NULL);
2867                     break;
2868                 }
2869             }
2870             if (encfname) {
2871                 simple_hdr(txn, "Content-Disposition", "%s; filename*=%s",
2872                         resp_body->dispo.attach ? "attachment" : "inline",
2873                         encfname);
2874             }
2875             else {
2876                 simple_hdr(txn, "Content-Disposition", "%s; filename=\"%s\"",
2877                         resp_body->dispo.attach ? "attachment" : "inline",
2878                         resp_body->dispo.fname);
2879             }
2880             free(encfname);
2881         }
2882         if (txn->resp_body.enc.proc) {
2883             /* Construct Content-Encoding header */
2884             comma_list_hdr(txn, "Content-Encoding", ce, txn->resp_body.enc.type);
2885         }
2886         if (resp_body->lang) {
2887             simple_hdr(txn, "Content-Language", "%s", resp_body->lang);
2888         }
2889         if (resp_body->loc) {
2890             xmlChar *uri = xmlURIEscapeStr(BAD_CAST resp_body->loc, BAD_CAST ":/?=");
2891             simple_hdr(txn, "Content-Location", "%s", (const char *) uri);
2892             free(uri);
2893 
2894             if (txn->flags.cors) Access_Control_Expose("Content-Location");
2895         }
2896         if (resp_body->md5) {
2897             content_md5_hdr(txn, resp_body->md5);
2898         }
2899     }
2900 
2901 
2902     /* Payload */
2903     switch (code) {
2904     case HTTP_NO_CONTENT:
2905     case HTTP_NOT_MODIFIED:
2906         /* MUST NOT include a body */
2907         resp_body->len = 0;
2908         break;
2909 
2910     case HTTP_PARTIAL:
2911     case HTTP_BAD_RANGE:
2912         if (resp_body->range) {
2913             simple_hdr(txn, "Content-Range", "bytes %lu-%lu/%lu",
2914                        resp_body->range->first, resp_body->range->last,
2915                        resp_body->len);
2916 
2917             /* Set actual content length of range */
2918             resp_body->len =
2919                 resp_body->range->last - resp_body->range->first + 1;
2920 
2921             free(resp_body->range);
2922         }
2923         else {
2924             simple_hdr(txn, "Content-Range", "bytes */%lu", resp_body->len);
2925             resp_body->len = 0;  /* No content */
2926         }
2927 
2928         /* Fall through and specify framing */
2929         GCC_FALLTHROUGH
2930 
2931     default:
2932         if (txn->flags.te) {
2933             /* HTTP/1.1 only - we use close-delimiting for HTTP/1.0 */
2934             if (txn->flags.ver == VER_1_1) {
2935                 /* Construct Transfer-Encoding header */
2936                 comma_list_hdr(txn, "Transfer-Encoding", te, txn->flags.te);
2937             }
2938 
2939             if (txn->flags.trailer & ~TRAILER_PROXY) {
2940                 /* Construct Trailer header */
2941                 const char *trailer_hdrs[] = { "Content-MD5", "CTag", NULL };
2942 
2943                 comma_list_hdr(txn, "Trailer", trailer_hdrs, txn->flags.trailer);
2944             }
2945         }
2946         else {
2947             /* Content-Length */
2948             switch (txn->meth) {
2949             case METH_CONNECT:
2950                 /* MUST NOT include per Section 4.3.6 of RFC 7231 */
2951                 break;
2952 
2953             case METH_HEAD:
2954                 if (!resp_body->len) {
2955                     /* We don't know if the length is zero or if it wasn't set -
2956                        MUST NOT include if it doesn't match what would be
2957                        returned for GET, per Section 3.3.2 of RFC 7231 */
2958                     break;
2959                 }
2960 
2961                 GCC_FALLTHROUGH
2962 
2963             default:
2964                 simple_hdr(txn, "Content-Length", "%lu", resp_body->len);
2965                 break;
2966             }
2967         }
2968     }
2969 
2970 
2971     /* Extra headers */
2972     if (resp_body->extra_hdrs) {
2973         spool_enum_hdrcache(resp_body->extra_hdrs, &write_cachehdr, txn);
2974     }
2975 
2976 
2977     /* End of headers */
2978     end_resp_headers(txn, code);
2979 
2980 
2981   log:
2982     /* Log the client request and our response */
2983     buf_reset(logbuf);
2984 
2985     /* Add client data */
2986     buf_printf(logbuf, "%s", txn->conn->clienthost);
2987     if (httpd_userid) buf_printf(logbuf, " as \"%s\"", httpd_userid);
2988     if (txn->req_hdrs &&
2989         (hdr = spool_getheader(txn->req_hdrs, "User-Agent"))) {
2990         buf_printf(logbuf, " with \"%s\"", hdr[0]);
2991         if ((hdr = spool_getheader(txn->req_hdrs, "X-Client")))
2992             buf_printf(logbuf, " by \"%s\"", hdr[0]);
2993         else if ((hdr = spool_getheader(txn->req_hdrs, "X-Requested-With")))
2994             buf_printf(logbuf, " by \"%s\"", hdr[0]);
2995     }
2996 
2997     /* Add session id */
2998     buf_printf(logbuf, " via SESSIONID=<%s>", session_id());
2999 
3000     /* Add request-line */
3001     buf_appendcstr(logbuf, "; \"");
3002     if (txn->req_line.meth) {
3003         buf_printf(logbuf, "%s",
3004                    txn->flags.override ? "POST" : txn->req_line.meth);
3005         if (txn->req_line.uri) {
3006             buf_printf(logbuf, " %s", txn->req_line.uri);
3007             if (txn->req_line.ver) {
3008                 buf_printf(logbuf, " %s", txn->req_line.ver);
3009                 if (code != HTTP_URI_TOO_LONG && *txn->req_line.buf) {
3010                     const char *p =
3011                         txn->req_line.ver + strlen(txn->req_line.ver) + 1;
3012                     if (*p) buf_printf(logbuf, " %s", p);
3013                 }
3014             }
3015         }
3016     }
3017     buf_appendcstr(logbuf, "\"");
3018 
3019     if (txn->req_hdrs) {
3020         /* Add any request modifying headers */
3021         sep = " (";
3022 
3023         if (txn->flags.override) {
3024             buf_printf(logbuf, "%smethod-override=%s", sep, txn->req_line.meth);
3025             sep = "; ";
3026         }
3027         if ((hdr = spool_getheader(txn->req_hdrs, "Origin"))) {
3028             buf_printf(logbuf, "%sorigin=%s", sep, hdr[0]);
3029             sep = "; ";
3030         }
3031         if ((hdr = spool_getheader(txn->req_hdrs, "Referer"))) {
3032             buf_printf(logbuf, "%sreferer=%s", sep, hdr[0]);
3033             sep = "; ";
3034         }
3035         if (txn->flags.upgrade &&
3036             (hdr = spool_getheader(txn->req_hdrs, "Upgrade"))) {
3037             buf_printf(logbuf, "%supgrade=%s", sep, hdr[0]);
3038             sep = "; ";
3039         }
3040         if (code == HTTP_CONTINUE || code == HTTP_EXPECT_FAILED) {
3041             hdr = spool_getheader(txn->req_hdrs, "Expect");
3042             buf_printf(logbuf, "%sexpect=%s", sep, hdr[0]);
3043             sep = "; ";
3044         }
3045         if ((hdr = spool_getheader(txn->req_hdrs, "Transfer-Encoding"))) {
3046             buf_printf(logbuf, "%stx-encoding=%s", sep, hdr[0]);
3047             sep = "; ";
3048         }
3049         if ((hdr = spool_getheader(txn->req_hdrs, "Content-Encoding"))) {
3050             buf_printf(logbuf, "%scnt-encoding=%s", sep, hdr[0]);
3051             sep = "; ";
3052         }
3053         if (txn->auth_chal.scheme) {
3054             buf_printf(logbuf, "%sauth=%s", sep, txn->auth_chal.scheme->name);
3055             sep = "; ";
3056         }
3057         if ((hdr = spool_getheader(txn->req_hdrs, "Destination"))) {
3058             buf_printf(logbuf, "%sdestination=%s", sep, hdr[0]);
3059             sep = "; ";
3060         }
3061         if ((hdr = spool_getheader(txn->req_hdrs, "Lock-Token"))) {
3062             buf_printf(logbuf, "%slock-token=%s", sep, hdr[0]);
3063             sep = "; ";
3064         }
3065         if ((hdr = spool_getheader(txn->req_hdrs, "If"))) {
3066             buf_printf(logbuf, "%sif=%s", sep, hdr[0]);
3067             sep = "; ";
3068         }
3069         if ((hdr = spool_getheader(txn->req_hdrs, "If-Schedule-Tag-Match"))) {
3070             buf_printf(logbuf, "%sif-schedule-tag-match=%s", sep, hdr[0]);
3071             sep = "; ";
3072         }
3073         else if ((hdr = spool_getheader(txn->req_hdrs, "If-Match"))) {
3074             buf_printf(logbuf, "%sif-match=%s", sep, hdr[0]);
3075             sep = "; ";
3076         }
3077         else if ((hdr = spool_getheader(txn->req_hdrs, "If-Unmodified-Since"))) {
3078             buf_printf(logbuf, "%sif-unmodified-since=%s", sep, hdr[0]);
3079             sep = "; ";
3080         }
3081         if ((hdr = spool_getheader(txn->req_hdrs, "If-None-Match"))) {
3082             buf_printf(logbuf, "%sif-none-match=%s", sep, hdr[0]);
3083             sep = "; ";
3084         }
3085         else if ((hdr = spool_getheader(txn->req_hdrs, "If-Modified-Since"))) {
3086             buf_printf(logbuf, "%sif-modified-since=%s", sep, hdr[0]);
3087             sep = "; ";
3088         }
3089         if ((hdr = spool_getheader(txn->req_hdrs, ":type"))) {
3090             buf_printf(logbuf, "%stype=%s", sep, hdr[0]);
3091             sep = "; ";
3092         }
3093         if ((hdr = spool_getheader(txn->req_hdrs, ":token"))) {
3094             buf_printf(logbuf, "%stoken=%s", sep, hdr[0]);
3095             sep = "; ";
3096         }
3097         if ((hdr = spool_getheader(txn->req_hdrs, ":jmap"))) {
3098             buf_printf(logbuf, "%sjmap=%s", sep, hdr[0]);
3099             sep = "; ";
3100         }
3101         if ((hdr = spool_getheader(txn->req_hdrs, ":dblookup"))) {
3102             buf_printf(logbuf, "%slookup=%s", sep, hdr[0]);
3103             sep = "; ";
3104         }
3105         if ((hdr = spool_getheader(txn->req_hdrs, "Depth"))) {
3106             buf_printf(logbuf, "%sdepth=%s", sep, hdr[0]);
3107             sep = "; ";
3108         }
3109         if ((hdr = spool_getheader(txn->req_hdrs, "Prefer"))) {
3110             buf_printf(logbuf, "%sprefer=%s", sep, hdr[0]);
3111             sep = "; ";
3112         }
3113         else if ((hdr = spool_getheader(txn->req_hdrs, "Brief"))) {
3114             buf_printf(logbuf, "%sbrief=%s", sep, hdr[0]);
3115             sep = "; ";
3116         }
3117         if ((hdr = spool_getheader(txn->req_hdrs, "CalDAV-Timezones"))) {
3118             buf_printf(logbuf, "%scaldav-timezones=%s", sep, hdr[0]);
3119             sep = "; ";
3120         }
3121 
3122         /* Add httplogheaders */
3123         size = strarray_size(httpd_log_headers);
3124         for (i = 0; i < size; i++) {
3125             const char *name = strarray_nth(httpd_log_headers, i);
3126 
3127             if ((hdr = spool_getheader(txn->req_hdrs, name))) {
3128                 buf_printf(logbuf, "%s%s=\"%s\"", sep, name, hdr[0]);
3129                 sep = "; ";
3130             }
3131         }
3132 
3133         if (*sep == ';') buf_appendcstr(logbuf, ")");
3134     }
3135 
3136     if (txn->flags.redirect) {
3137         /* Add CGI local redirect */
3138         buf_printf(logbuf, " => \"%s %s %s\"",
3139                    txn->req_line.meth, txn->req_tgt.path, txn->req_line.ver);
3140     }
3141 
3142     /* Add response */
3143     buf_printf(logbuf, " => \"%s\"", http_statusline(txn->flags.ver, code));
3144 
3145     /* Add any auxiliary response data */
3146     sep = " (";
3147     if (txn->strm_ctx) {
3148         buf_printf(logbuf, "%sstream-id=%d", sep,
3149                    http2_get_streamid(txn->strm_ctx));
3150         sep = "; ";
3151     }
3152     if (code == HTTP_SWITCH_PROT || code == HTTP_UPGRADE) {
3153         buf_printf(logbuf, "%supgrade=", sep);
3154         comma_list_body(logbuf, upgrd_tokens, txn->flags.upgrade, 0, noargs);
3155         sep = "; ";
3156     }
3157     if (txn->flags.te) {
3158         buf_printf(logbuf, "%stx-encoding=", sep);
3159         comma_list_body(logbuf, te, txn->flags.te, 0, noargs);
3160         sep = "; ";
3161     }
3162     if (resp_body->enc.proc && (resp_body->len || txn->flags.te)) {
3163         buf_printf(logbuf, "%scnt-encoding=", sep);
3164         comma_list_body(logbuf, ce, resp_body->enc.type, 0, noargs);
3165         sep = "; ";
3166     }
3167     if (txn->location) {
3168         buf_printf(logbuf, "%slocation=%s", sep, txn->location);
3169         sep = "; ";
3170     }
3171     else if (txn->flags.cors) {
3172         buf_printf(logbuf, "%sallow-origin", sep);
3173         sep = "; ";
3174     }
3175     else if (txn->error.desc) {
3176         buf_printf(logbuf, "%serror=%s", sep, txn->error.desc);
3177         sep = "; ";
3178     }
3179     if (*sep == ';') buf_appendcstr(logbuf, ")");
3180 
3181     /* Add timing stats */
3182     cmdtime_endtimer(&cmdtime, &nettime);
3183     buf_printf(logbuf, " [timing: cmd=%f net=%f total=%f]",
3184                cmdtime, nettime, cmdtime + nettime);
3185 
3186     syslog(LOG_INFO, "%s", buf_cstring(logbuf));
3187 }
3188 
3189 
3190 #ifdef HAVE_DECLARE_OPTIMIZE
3191 EXPORTED inline void keepalive_response(struct transaction_t *txn)
3192     __attribute__((always_inline, optimize("-O3")));
3193 #endif
keepalive_response(struct transaction_t * txn)3194 EXPORTED void keepalive_response(struct transaction_t *txn)
3195 {
3196     if (gotsigalrm) {
3197         response_header(HTTP_PROCESSING, txn);
3198     }
3199 }
3200 
3201 
3202 /*
3203  * Output an HTTP response with multipart body data.
3204  *
3205  * An initial call with 'code' != 0 will output a response header
3206  * and the preamble.
3207  *
3208  * All subsequent calls should have 'code' = 0 to output just a body part.
3209  * A body part may include custom headers (exluding Content-Type and Length),
3210  * which must be properly folded and must end with CRLF.
3211  *
3212  * A final call with 'len' = 0 ends the multipart body.
3213  */
write_multipart_body(long code,struct transaction_t * txn,const char * buf,unsigned len,const char * part_headers)3214 EXPORTED void write_multipart_body(long code, struct transaction_t *txn,
3215                                    const char *buf, unsigned len,
3216                                    const char *part_headers)
3217 {
3218     static char boundary[100];
3219     struct buf *body = &txn->resp_body.payload;
3220 
3221     if (code) {
3222         const char *preamble =
3223             "This is a message with multiple parts in MIME format.\r\n";
3224 
3225         txn->flags.mime = 1;
3226 
3227         /* Create multipart boundary */
3228         snprintf(boundary, sizeof(boundary), "%s-%ld-%ld-%ld",
3229                  *spool_getheader(txn->req_hdrs, ":authority"),
3230                  (long) getpid(), (long) time(0), (long) rand());
3231 
3232         /* Create Content-Type w/ boundary */
3233         assert(!buf_len(&txn->buf));
3234         buf_printf(&txn->buf, "%s; boundary=\"%s\"",
3235                    txn->resp_body.type, boundary);
3236         txn->resp_body.type = buf_cstring(&txn->buf);
3237 
3238         /* Setup for chunked response and begin multipart */
3239         txn->flags.te |= TE_CHUNKED;
3240         if (!buf) {
3241             buf = preamble;
3242             len = strlen(preamble);
3243         }
3244         write_body(code, txn, buf, len);
3245     }
3246     else if (len) {
3247         /* Output delimiter and MIME part-headers */
3248         buf_reset(body);
3249         buf_printf(body, "\r\n--%s\r\n", boundary);
3250         buf_printf(body, "Content-Type: %s\r\n", txn->resp_body.type);
3251         if (txn->resp_body.range) {
3252             buf_printf(body, "Content-Range: bytes %lu-%lu/%lu\r\n",
3253                        txn->resp_body.range->first,
3254                        txn->resp_body.range->last,
3255                        txn->resp_body.len);
3256         }
3257         buf_printf(body, "Content-Length: %d\r\n", len);
3258         if (part_headers) {
3259             buf_appendcstr(body, part_headers);
3260         }
3261         buf_appendcstr(body, "\r\n");
3262         write_body(0, txn, buf_cstring(body), buf_len(body));
3263 
3264         /* Output body-part data */
3265         write_body(0, txn, buf, len);
3266     }
3267     else {
3268         const char *epilogue = "\r\nEnd of MIME multipart body.\r\n";
3269 
3270         /* Output close-delimiter and epilogue */
3271         buf_reset(body);
3272         buf_printf(body, "\r\n--%s--\r\n%s", boundary, epilogue);
3273         write_body(0, txn, buf_cstring(body), buf_len(body));
3274 
3275         /* End of output */
3276         write_body(0, txn, NULL, 0);
3277     }
3278 }
3279 
3280 
3281 /* Output multipart/byteranges */
multipart_byteranges(struct transaction_t * txn,const char * msg_base)3282 static void multipart_byteranges(struct transaction_t *txn,
3283                                  const char *msg_base)
3284 {
3285     /* Save Content-Range and Content-Type pointers */
3286     struct range *range = txn->resp_body.range;
3287     const char *type = txn->resp_body.type;
3288 
3289     /* Start multipart response */
3290     txn->resp_body.range = NULL;
3291     txn->resp_body.type = "multipart/byteranges";
3292     write_multipart_body(HTTP_PARTIAL, txn, NULL, 0, NULL);
3293 
3294     txn->resp_body.type = type;
3295     while (range) {
3296         unsigned long offset = range->first;
3297         unsigned long datalen = range->last - range->first + 1;
3298         struct range *next = range->next;
3299 
3300         /* Output range as body part */
3301         txn->resp_body.range = range;
3302         write_multipart_body(0, txn, msg_base + offset, datalen, NULL);
3303 
3304         /* Cleanup */
3305         free(range);
3306         range = next;
3307     }
3308 
3309     /* End of multipart body */
3310     write_multipart_body(0, txn, NULL, 0, NULL);
3311 }
3312 
3313 
3314 /*
3315  * Output an HTTP response with body data, compressed as necessary.
3316  *
3317  * For chunked body data, an initial call with 'code' != 0 will output
3318  * a response header and the first body chunk.
3319  * All subsequent calls should have 'code' = 0 to output just the body chunk.
3320  * A final call with 'len' = 0 ends the chunked body.
3321  *
3322  * NOTE: HTTP/1.0 clients can't handle chunked encoding,
3323  *       so we use bare chunks and close the connection when done.
3324  */
write_body(long code,struct transaction_t * txn,const char * buf,unsigned len)3325 EXPORTED void write_body(long code, struct transaction_t *txn,
3326                          const char *buf, unsigned len)
3327 {
3328     unsigned outlen = len, offset = 0, last_chunk;
3329     int do_md5 = (txn->meth == METH_HEAD) ? 0 :
3330         config_getswitch(IMAPOPT_HTTPCONTENTMD5);
3331     static MD5_CTX ctx;
3332     static unsigned char md5[MD5_DIGEST_LENGTH];
3333 
3334     syslog(LOG_DEBUG, "write_body(code = %ld, flags.te = %#x, len = %u)",
3335            code, txn->flags.te, len);
3336 
3337     if (txn->flags.te & TE_CHUNKED) last_chunk = !(code || len);
3338     else {
3339         /* Handle static content as last chunk */
3340         last_chunk = 1;
3341 
3342         if (len < GZIP_MIN_LEN) {
3343             /* Don't compress small static content */
3344             txn->resp_body.enc.type = CE_IDENTITY;
3345             txn->resp_body.enc.proc = NULL;
3346             txn->flags.te = TE_NONE;
3347         }
3348     }
3349 
3350     /* Compress data */
3351     if (txn->resp_body.enc.proc || txn->flags.te & ~TE_CHUNKED) {
3352         unsigned flags = 0;
3353 
3354         if (code) flags |= COMPRESS_START;
3355         if (last_chunk) flags |= COMPRESS_END;
3356 
3357         if (txn->resp_body.enc.proc(txn, flags, buf, len) < 0) {
3358             fatal("Error while compressing data", EX_SOFTWARE);
3359         }
3360 
3361         buf = txn->zbuf.s;
3362         outlen = txn->zbuf.len;
3363     }
3364 
3365     if (code) {
3366         /* Initial call - prepare response header based on CE, TE and version */
3367         if (do_md5) MD5Init(&ctx);
3368 
3369         if (txn->flags.te & ~TE_CHUNKED) {
3370             /* Transfer-Encoded content MUST be chunked */
3371             txn->flags.te |= TE_CHUNKED;
3372         }
3373 
3374         if (!txn->flags.te) {
3375             /* Full/partial body (no encoding).
3376              *
3377              * In all cases, 'resp_body.len' is used to specify complete-length
3378              * In the case of a 206 or 416 response, Content-Length will be
3379              * set accordingly in response_header().
3380              */
3381             txn->resp_body.len = outlen;
3382 
3383             if (code == HTTP_PARTIAL) {
3384                 /* check_precond() tells us that this is a range request */
3385                 code = parse_ranges(*spool_getheader(txn->req_hdrs, "Range"),
3386                                     outlen, &txn->resp_body.range);
3387 
3388                 switch (code) {
3389                 case HTTP_OK:
3390                     /* Full body (unknown range-unit) */
3391                     break;
3392 
3393                 case HTTP_PARTIAL:
3394                     /* One or more range request(s) */
3395                     txn->resp_body.len = outlen;
3396 
3397                     if (txn->resp_body.range->next) {
3398                         /* Multiple ranges */
3399                         multipart_byteranges(txn, buf);
3400                         return;
3401                     }
3402                     else {
3403                         /* Single range - set data parameters accordingly */
3404                         offset += txn->resp_body.range->first;
3405                         outlen = txn->resp_body.range->last -
3406                             txn->resp_body.range->first + 1;
3407                     }
3408                     break;
3409 
3410                 case HTTP_BAD_RANGE:
3411                     /* No valid ranges */
3412                     outlen = 0;
3413                     break;
3414                 }
3415             }
3416 
3417             if (outlen && do_md5) {
3418                 MD5Update(&ctx, buf+offset, outlen);
3419                 MD5Final(md5, &ctx);
3420                 txn->resp_body.md5 = md5;
3421             }
3422         }
3423         else if (txn->flags.ver == VER_1_0) {
3424             /* HTTP/1.0 doesn't support chunked - close-delimit the body */
3425             txn->flags.conn = CONN_CLOSE;
3426         }
3427         else if (do_md5) txn->flags.trailer |= TRAILER_CMD5;
3428 
3429         response_header(code, txn);
3430 
3431         /* MUST NOT send a body for 1xx/204/304 response or any HEAD response */
3432         switch (code) {
3433         case HTTP_CONTINUE:
3434         case HTTP_SWITCH_PROT:
3435         case HTTP_PROCESSING:
3436         case HTTP_NO_CONTENT:
3437         case HTTP_NOT_MODIFIED:
3438             return;
3439 
3440         default:
3441             if (txn->meth == METH_HEAD) return;
3442         }
3443     }
3444 
3445     /* Output data */
3446     if (txn->flags.ver == VER_2) {
3447         /* HTTP/2 chunk */
3448         if (outlen || txn->flags.te) {
3449             http2_data_chunk(txn, buf + offset, outlen, last_chunk, &ctx);
3450         }
3451     }
3452     else if (txn->flags.te && txn->flags.ver == VER_1_1) {
3453         /* HTTP/1.1 chunk */
3454         if (outlen) {
3455             syslog(LOG_DEBUG, "write_body: chunk(%d)", outlen);
3456             prot_printf(httpd_out, "%x\r\n", outlen);
3457             prot_write(httpd_out, buf, outlen);
3458             prot_puts(httpd_out, "\r\n");
3459 
3460             if (txn->flags.trailer & TRAILER_CMD5) MD5Update(&ctx, buf, outlen);
3461         }
3462         if (last_chunk) {
3463             /* Terminate the HTTP/1.1 body with a zero-length chunk */
3464             syslog(LOG_DEBUG, "write_body: last chunk");
3465             prot_puts(httpd_out, "0\r\n");
3466 
3467             /* Trailer */
3468             if (txn->flags.trailer & TRAILER_CMD5) {
3469                 syslog(LOG_DEBUG, "write_body: trailer Content-MD5");
3470                 MD5Final(md5, &ctx);
3471                 content_md5_hdr(txn, md5);
3472             }
3473             if ((txn->flags.trailer & TRAILER_CTAG) && txn->resp_body.ctag) {
3474                 syslog(LOG_DEBUG, "write_body: trailer CTag");
3475                 simple_hdr(txn, "CTag", "%s", txn->resp_body.ctag);
3476             }
3477 
3478             if (txn->flags.trailer != TRAILER_PROXY) {
3479                 syslog(LOG_DEBUG, "write_body: CRLF");
3480                 prot_puts(httpd_out, "\r\n");
3481             }
3482         }
3483     }
3484     else {
3485         /* Full body or HTTP/1.0 close-delimited body */
3486         prot_write(httpd_out, buf + offset, outlen);
3487     }
3488 }
3489 
3490 
3491 /* Output an HTTP response with application/xml body */
xml_response(long code,struct transaction_t * txn,xmlDocPtr xml)3492 EXPORTED void xml_response(long code, struct transaction_t *txn, xmlDocPtr xml)
3493 {
3494     xmlChar *buf;
3495     int bufsiz;
3496 
3497     switch (code) {
3498     case HTTP_OK:
3499     case HTTP_CREATED:
3500     case HTTP_NO_CONTENT:
3501     case HTTP_MULTI_STATUS:
3502         break;
3503 
3504     default:
3505         /* Neither Brief nor Prefer affect error response bodies */
3506         txn->flags.vary &= ~(VARY_BRIEF | VARY_PREFER);
3507         txn->resp_body.prefs = 0;
3508     }
3509 
3510     /* Dump XML response tree into a text buffer */
3511     xmlDocDumpFormatMemoryEnc(xml, &buf, &bufsiz, "utf-8",
3512                               config_httpprettytelemetry);
3513 
3514     if (buf) {
3515         if (txn->flags.te & TE_CHUNKED) {
3516             /* Start of XML chunked response */
3517             xmlChar *cp;
3518             int n;
3519 
3520             /* Leave root element open */
3521             for (cp = buf + --bufsiz, n = 0; *cp != '/'; cp--, n++);
3522             if (*(cp+1) == '>') memmove(cp, cp+1, n);  /* <root/> */
3523             else bufsiz -= n+1;  /* </root> */
3524         }
3525 
3526         /* Output the XML response */
3527         txn->resp_body.type = "application/xml; charset=utf-8";
3528 
3529         write_body(code, txn, (char *) buf, bufsiz);
3530 
3531         /* Cleanup */
3532         xmlFree(buf);
3533     }
3534     else {
3535         txn->error.precond = 0;
3536         txn->error.desc = "Error dumping XML tree\r\n";
3537         error_response(HTTP_SERVER_ERROR, txn);
3538     }
3539 }
3540 
3541 /* Output a chunk of an XML response */
xml_partial_response(struct transaction_t * txn,xmlDocPtr doc,xmlNodePtr node,unsigned level,xmlBufferPtr * buf)3542 EXPORTED void xml_partial_response(struct transaction_t *txn,
3543                                    xmlDocPtr doc, xmlNodePtr node,
3544                                    unsigned level, xmlBufferPtr *buf)
3545 {
3546     const char *eol = "\n";
3547     unsigned n;
3548 
3549     if (!config_httpprettytelemetry) {
3550         level = 0;
3551         eol = "";
3552     }
3553 
3554     /* Start with clean buffer */
3555     if (!*buf) *buf = xmlBufferCreate();
3556 
3557     if (node) {
3558         /* Add leading indent to buffer */
3559         for (n = 0; n < level * MARKUP_INDENT; n++) xmlBufferCCat(*buf, " ");
3560 
3561         /* Dump XML node into buffer */
3562         xmlNodeDump(*buf, doc, node, level, config_httpprettytelemetry);
3563 
3564         /* Add trailing EOL to buffer */
3565         xmlBufferCCat(*buf, eol);
3566     }
3567     else {
3568         /* End of chunked XML response */
3569         xmlNodePtr root = xmlDocGetRootElement(doc);
3570 
3571         /* Add close of root element to buffer */
3572         xmlBufferCCat(*buf, "</");
3573         if (root->ns->prefix) {
3574             xmlBufferCat(*buf, root->ns->prefix);
3575             xmlBufferCCat(*buf, ":");
3576         }
3577         xmlBufferCat(*buf, root->name);
3578         xmlBufferCCat(*buf, ">");
3579 
3580         /* Add trailing EOL to buffer */
3581         xmlBufferCCat(*buf, eol);
3582     }
3583 
3584     if (txn) {
3585         /* Output the XML buffer */
3586         write_body(0, txn,
3587                    (char *) xmlBufferContent(*buf), xmlBufferLength(*buf));
3588 
3589         /* Reset the buffer for next chunk */
3590         xmlBufferEmpty(*buf);
3591     }
3592 }
3593 
buf_printf_markup(struct buf * buf,unsigned level,const char * fmt,...)3594 EXPORTED void buf_printf_markup(struct buf *buf, unsigned level,
3595                                 const char *fmt, ...)
3596 {
3597     va_list args;
3598     const char *eol = "\n";
3599 
3600     if (!config_httpprettytelemetry) {
3601         level = 0;
3602         eol = "";
3603     }
3604 
3605     va_start(args, fmt);
3606 
3607     buf_printf(buf, "%*s", level * MARKUP_INDENT, "");
3608     buf_vprintf(buf, fmt, args);
3609     buf_appendcstr(buf, eol);
3610 
3611     va_end(args);
3612 }
3613 
3614 
3615 /* Output an HTTP error response with optional XML or HTML body */
error_response(long code,struct transaction_t * txn)3616 EXPORTED void error_response(long code, struct transaction_t *txn)
3617 {
3618     struct buf *html = &txn->resp_body.payload;
3619 
3620     /* Neither Brief nor Prefer affect error response bodies */
3621     txn->flags.vary &= ~(VARY_BRIEF | VARY_PREFER);
3622     txn->resp_body.prefs = 0;
3623 
3624 #ifdef WITH_DAV
3625     if (code != HTTP_UNAUTHORIZED && txn->error.precond) {
3626         xmlNodePtr root = xml_add_error(NULL, &txn->error, NULL);
3627 
3628         if (root) {
3629             xml_response(code, txn, root->doc);
3630             xmlFreeDoc(root->doc);
3631             return;
3632         }
3633     }
3634 #endif /* WITH_DAV */
3635 
3636     if (!txn->error.desc) {
3637         switch (code) {
3638             /* 4xx codes */
3639         case HTTP_BAD_REQUEST:
3640             txn->error.desc =
3641                 "The request was not understood by this server.";
3642             break;
3643 
3644         case HTTP_NOT_FOUND:
3645             txn->error.desc =
3646                 "The requested URL was not found on this server.";
3647             break;
3648 
3649         case HTTP_NOT_ALLOWED:
3650             txn->error.desc =
3651                 "The requested method is not allowed for the URL.";
3652             break;
3653 
3654         case HTTP_GONE:
3655             txn->error.desc =
3656                 "The requested URL has been removed from this server.";
3657             break;
3658 
3659             /* 5xx codes */
3660         case HTTP_SERVER_ERROR:
3661             txn->error.desc =
3662                 "The server encountered an internal error.";
3663             break;
3664 
3665         case HTTP_NOT_IMPLEMENTED:
3666             txn->error.desc =
3667                 "The requested method is not implemented by this server.";
3668             break;
3669 
3670         case HTTP_UNAVAILABLE:
3671             txn->error.desc =
3672                 "The server is unable to process the request at this time.";
3673             break;
3674         }
3675     }
3676 
3677     if (txn->error.desc) {
3678         const char **hdr, *host = config_servername;
3679         char *port = NULL;
3680         unsigned level = 0;
3681 
3682         if (txn->req_hdrs &&
3683             (hdr = spool_getheader(txn->req_hdrs, ":authority")) &&
3684             hdr[0] && *hdr[0]) {
3685             host = (char *) hdr[0];
3686             if ((port = strchr(host, ':'))) *port++ = '\0';
3687         }
3688 
3689         if (!port) {
3690             port = (buf_len(&saslprops.iplocalport)) ?
3691                 strchr(buf_cstring(&saslprops.iplocalport), ';')+1 : "";
3692         }
3693 
3694         buf_printf_markup(html, level, HTML_DOCTYPE);
3695         buf_printf_markup(html, level++, "<html>");
3696         buf_printf_markup(html, level++, "<head>");
3697         buf_printf_markup(html, level, "<title>%s</title>",
3698                           error_message(code));
3699         buf_printf_markup(html, --level, "</head>");
3700         buf_printf_markup(html, level++, "<body>");
3701         buf_printf_markup(html, level, "<h1>%s</h1>", error_message(code)+4);
3702         buf_printf_markup(html, level, "<p>%s</p>", txn->error.desc);
3703         if (config_serverinfo) {
3704             buf_printf_markup(html, level, "<hr>");
3705             buf_printf_markup(html, level,
3706                               "<address>%s Server at %s Port %s</address>",
3707                               (config_serverinfo == IMAP_ENUM_SERVERINFO_ON) ?
3708                               buf_cstring(&serverinfo) : "HTTP",
3709                               host, port);
3710         }
3711         buf_printf_markup(html, --level, "</body>");
3712         buf_printf_markup(html, --level, "</html>");
3713 
3714         txn->resp_body.type = "text/html; charset=utf-8";
3715     }
3716 
3717     write_body(code, txn, buf_cstring(html), buf_len(html));
3718 }
3719 
3720 
proxy_authz(const char ** authzid,struct transaction_t * txn)3721 static int proxy_authz(const char **authzid, struct transaction_t *txn)
3722 {
3723     static char authzbuf[MAX_MAILBOX_BUFFER];
3724     unsigned authzlen;
3725     int status;
3726 
3727     syslog(LOG_DEBUG, "proxy_auth: authzid='%s'", *authzid);
3728 
3729     /* Free userid & authstate previously allocated for auth'd user */
3730     if (httpd_userid) {
3731         free(httpd_userid);
3732         httpd_userid = NULL;
3733     }
3734     if (httpd_extrafolder) {
3735         free(httpd_extrafolder);
3736         httpd_extrafolder = NULL;
3737     }
3738     if (httpd_extradomain) {
3739         free(httpd_extradomain);
3740         httpd_extradomain = NULL;
3741     }
3742     if (httpd_authstate) {
3743         auth_freestate(httpd_authstate);
3744         httpd_authstate = NULL;
3745     }
3746 
3747     if (!(config_mupdate_server && config_getstring(IMAPOPT_PROXYSERVERS))) {
3748         /* Not a backend in a Murder - proxy authz is not allowed */
3749         syslog(LOG_NOTICE, "badlogin: %s %s %s %s",
3750                txn->conn->clienthost, txn->auth_chal.scheme->name, httpd_authid,
3751                "proxy authz attempted on non-Murder backend");
3752         return SASL_NOAUTHZ;
3753     }
3754 
3755     /* Canonify the authzid */
3756     status = mysasl_canon_user(httpd_saslconn, NULL,
3757                                *authzid, strlen(*authzid),
3758                                SASL_CU_AUTHZID, NULL,
3759                                authzbuf, sizeof(authzbuf), &authzlen);
3760     if (status) {
3761         syslog(LOG_NOTICE, "badlogin: %s %s %s invalid user",
3762                txn->conn->clienthost, txn->auth_chal.scheme->name,
3763                beautify_string(*authzid));
3764         return status;
3765     }
3766 
3767     /* See if auth'd user is allowed to proxy */
3768     status = mysasl_proxy_policy(httpd_saslconn, &httpd_proxyctx,
3769                                  authzbuf, authzlen,
3770                                  httpd_authid, strlen(httpd_authid),
3771                                  NULL, 0, NULL);
3772 
3773     if (status) {
3774         syslog(LOG_NOTICE, "badlogin: %s %s %s %s",
3775                txn->conn->clienthost, txn->auth_chal.scheme->name, httpd_authid,
3776                sasl_errdetail(httpd_saslconn));
3777         return status;
3778     }
3779 
3780     *authzid = authzbuf;
3781 
3782     return status;
3783 }
3784 
3785 
3786 /* Write cached header (redacting authorization credentials) to buffer. */
log_cachehdr(const char * name,const char * contents,const char * raw,void * rock)3787 HIDDEN void log_cachehdr(const char *name, const char *contents,
3788                          const char *raw, void *rock)
3789 {
3790     struct buf *buf = (struct buf *) rock;
3791 
3792     /* Ignore private headers in our cache */
3793     if (name[0] == ':') return;
3794 
3795     if (!strcasecmp(name, "authorization") && strchr(contents, ' ')) {
3796         /* Replace authorization credentials with an ellipsis */
3797         const char *creds = strchr(contents, ' ') + 1;
3798         buf_printf(buf, "%c%s: %.*s%-*s\r\n", toupper(name[0]), name+1,
3799                    (int) (creds - contents), contents,
3800                    (int) strlen(creds), "...");
3801     }
3802     else if (raw)
3803         buf_appendcstr(buf, raw);
3804     else
3805         buf_printf(buf, "%c%s: %s\r\n", toupper(name[0]), name+1, contents);
3806 }
3807 
3808 
auth_success(struct transaction_t * txn,const char * userid)3809 static int auth_success(struct transaction_t *txn, const char *userid)
3810 {
3811     struct auth_scheme_t *scheme = txn->auth_chal.scheme;
3812     int logfd = txn->conn->logfd;
3813     int i;
3814 
3815     httpd_userid = xstrdup(userid);
3816     httpd_userisanonymous = is_userid_anonymous(httpd_userid);
3817 
3818     syslog(LOG_NOTICE, "login: %s %s %s%s %s SESSIONID=<%s>",
3819            txn->conn->clienthost, httpd_userid, scheme->name,
3820            txn->conn->tls_ctx ? "+TLS" : "", "User logged in",
3821            session_id());
3822 
3823 
3824     /* Recreate telemetry log entry for request (w/ credentials redacted) */
3825     assert(!buf_len(&txn->buf));
3826     buf_printf(&txn->buf, "<" TIME_T_FMT "<", time(NULL)); /* timestamp */
3827     buf_printf(&txn->buf, "%s %s %s\r\n",               /* request-line*/
3828                txn->req_line.meth, txn->req_line.uri, txn->req_line.ver);
3829     spool_enum_hdrcache(txn->req_hdrs,                  /* header fields */
3830                         &log_cachehdr, &txn->buf);
3831     buf_appendcstr(&txn->buf, "\r\n");                  /* CRLF */
3832     buf_append(&txn->buf, &txn->req_body.payload);      /* message body */
3833     buf_appendmap(&txn->buf,                            /* buffered input */
3834                   (const char *) httpd_in->ptr, httpd_in->cnt);
3835 
3836     if (logfd != -1) {
3837         /* Rewind log to current request and truncate it */
3838         off_t end = lseek(logfd, 0, SEEK_END);
3839 
3840         if (ftruncate(logfd, end - buf_len(&txn->buf)))
3841             syslog(LOG_ERR, "IOERROR: failed to truncate http log");
3842 
3843         /* Close existing telemetry log */
3844         close(logfd);
3845     }
3846 
3847     prot_setlog(httpd_in, PROT_NO_FD);
3848     prot_setlog(httpd_out, PROT_NO_FD);
3849 
3850     /* Create telemetry log based on new userid */
3851     if (txn->conn->sess_ctx)
3852         txn->conn->logfd = logfd = telemetry_log(userid, NULL, NULL, 0);
3853     else
3854         txn->conn->logfd = logfd = telemetry_log(userid, httpd_in, httpd_out, 0);
3855 
3856     if (logfd != -1) {
3857         /* Log credential-redacted request */
3858         if (write(logfd, buf_cstring(&txn->buf), buf_len(&txn->buf)) < 0)
3859             syslog(LOG_ERR, "IOERROR: failed to write to http log");
3860     }
3861 
3862     buf_reset(&txn->buf);
3863 
3864     /* Do any namespace specific post-auth processing */
3865     for (i = 0; http_namespaces[i]; i++) {
3866         if (http_namespaces[i]->enabled && http_namespaces[i]->auth) {
3867             int ret = http_namespaces[i]->auth(httpd_userid);
3868             if (ret) return ret;
3869         }
3870     }
3871 
3872     return 0;
3873 }
3874 
3875 /* Perform HTTP Authentication based on the given credentials ('creds').
3876  * Returns the selected auth scheme and any server challenge in 'chal'.
3877  * May be called multiple times if auth scheme requires multiple steps.
3878  * SASL status between steps is maintained in 'status'.
3879  */
3880 #define MAX_AUTHPARAM_SIZE 10   /* "sid=,data=" */
3881 #define MAX_BASE64_SIZE 21848   /* per RFC 4422: ((16K / 3) + 1) * 4  */
3882 #define BASE64_BUF_SIZE (MAX_AUTHPARAM_SIZE +MAX_SESSIONID_SIZE +MAX_BASE64_SIZE)
3883 
http_auth(const char * creds,struct transaction_t * txn)3884 static int http_auth(const char *creds, struct transaction_t *txn)
3885 {
3886     struct auth_challenge_t *chal = &txn->auth_chal;
3887     static int status = SASL_OK;
3888     int slen, r;
3889     const char *clientin = NULL, *realm = NULL, *user, **authzid;
3890     unsigned int clientinlen = 0;
3891     struct auth_scheme_t *scheme;
3892     static char base64[BASE64_BUF_SIZE+1];
3893     const void *canon_user = NULL;
3894 
3895     /* Split credentials into auth scheme and response */
3896     slen = strcspn(creds, " ");
3897     if ((clientin = strchr(creds + slen, ' '))) {
3898         while (strchr(" ", *++clientin));  /* Trim leading 1*SP */
3899         clientinlen = strlen(clientin);
3900     }
3901 
3902     syslog(LOG_DEBUG,
3903            "http_auth: status=%d   scheme='%s'   creds='%.*s%s'",
3904            status, chal->scheme ? chal->scheme->name : "",
3905            slen, creds, clientin ? " <response>" : "");
3906 
3907     /* Free userid & authstate previously allocated for auth'd user */
3908     if (httpd_userid) {
3909         free(httpd_userid);
3910         httpd_userid = NULL;
3911     }
3912     if (httpd_extrafolder) {
3913         free(httpd_extrafolder);
3914         httpd_extrafolder = NULL;
3915     }
3916     if (httpd_extradomain) {
3917         free(httpd_extradomain);
3918         httpd_extradomain = NULL;
3919     }
3920     if (httpd_authstate) {
3921         auth_freestate(httpd_authstate);
3922         httpd_authstate = NULL;
3923     }
3924     chal->param = NULL;
3925 
3926     if (chal->scheme) {
3927         /* Use current scheme, if possible */
3928         scheme = chal->scheme;
3929 
3930         if (strncasecmp(scheme->name, creds, slen)) {
3931             /* Changing auth scheme -> reset state */
3932             syslog(LOG_DEBUG, "http_auth: changing scheme");
3933             reset_saslconn(&httpd_saslconn);
3934             chal->scheme = NULL;
3935             status = SASL_OK;
3936         }
3937     }
3938 
3939     if (!chal->scheme) {
3940         /* Find the client-specified auth scheme */
3941         syslog(LOG_DEBUG, "http_auth: find client scheme");
3942         for (scheme = auth_schemes; scheme->name; scheme++) {
3943             if (slen && !strncasecmp(scheme->name, creds, slen)) {
3944                 /* Found a supported scheme, see if its available */
3945                 if (!(avail_auth_schemes & scheme->id)) scheme = NULL;
3946                 break;
3947             }
3948         }
3949         if (!scheme || !scheme->name) {
3950             /* Didn't find a matching scheme that is available */
3951             syslog(LOG_DEBUG, "Unknown auth scheme '%.*s'", slen, creds);
3952             return SASL_NOMECH;
3953         }
3954         /* We found it! */
3955         syslog(LOG_DEBUG, "http_auth: found matching scheme: %s", scheme->name);
3956         chal->scheme = scheme;
3957         status = SASL_OK;
3958 
3959         if (!clientin && (scheme->flags & AUTH_REALM_PARAM)) {
3960             /* Get realm - based on namespace of URL */
3961             switch (txn->req_tgt.namespace->id) {
3962             case URL_NS_DEFAULT:
3963             case URL_NS_PRINCIPAL:
3964                 realm = config_getstring(IMAPOPT_DAV_REALM);
3965                 break;
3966 
3967             case URL_NS_CALENDAR:
3968                 realm = config_getstring(IMAPOPT_CALDAV_REALM);
3969                 break;
3970 
3971             case URL_NS_ADDRESSBOOK:
3972                 realm = config_getstring(IMAPOPT_CARDDAV_REALM);
3973                 break;
3974 
3975             case URL_NS_RSS:
3976                 realm = config_getstring(IMAPOPT_RSS_REALM);
3977                 break;
3978             }
3979             if (!realm) realm = config_servername;
3980 
3981             /* Create initial challenge (base64 buffer is static) */
3982             snprintf(base64, BASE64_BUF_SIZE, "realm=\"%s\"", realm);
3983             chal->param = base64;
3984             chal->scheme = NULL;  /* make sure we don't reset the SASL ctx */
3985             return status;
3986         }
3987     }
3988 
3989     /* Parse any auth parameters, if necessary */
3990     if (clientin && (scheme->flags & AUTH_DATA_PARAM)) {
3991         const char *sid = NULL;
3992         unsigned int sid_len;
3993 
3994         r = http_parse_auth_params(clientin, NULL /* realm */, NULL,
3995                                    &sid, &sid_len, &clientin, &clientinlen);
3996         if (r != SASL_OK) return r;
3997 
3998         if (sid) {
3999             const char *mysid = session_id();
4000 
4001             if (sid_len != strlen(mysid) ||
4002                 strncmp(mysid, sid, sid_len)) {
4003                 syslog(LOG_ERR, "%s: Incorrect 'sid' parameter in credentials",
4004                        scheme->name);
4005                 return SASL_BADAUTH;
4006             }
4007         }
4008     }
4009 
4010     /* Base64 decode any client response, if necessary */
4011     if (clientin && (scheme->flags & AUTH_BASE64)) {
4012         r = sasl_decode64(clientin, clientinlen,
4013                           base64, BASE64_BUF_SIZE, &clientinlen);
4014         if (r != SASL_OK) {
4015             syslog(LOG_ERR, "Base64 decode failed: %s",
4016                    sasl_errstring(r, NULL, NULL));
4017             return r;
4018         }
4019         clientin = base64;
4020     }
4021 
4022     if (scheme->id == AUTH_BASIC) {
4023         /* Basic (plaintext) authentication */
4024         char *pass;
4025         char *extra;
4026         char *plus;
4027         char *domain;
4028 
4029         /* Split credentials into <user> ':' <pass>.
4030          * We are working with base64 buffer, so we can modify it.
4031          */
4032         user = base64;
4033         pass = strchr(base64, ':');
4034         if (!pass) {
4035             syslog(LOG_ERR, "Basic auth: Missing password");
4036             return SASL_BADPARAM;
4037         }
4038         *pass++ = '\0';
4039         domain = strchr(user, '@');
4040         if (domain) *domain++ = '\0';
4041         extra = strchr(user, '%');
4042         if (extra) *extra++ = '\0';
4043         plus = strchr(user, '+');
4044         if (plus) *plus++ = '\0';
4045 
4046         /* Verify the password */
4047         char *realuser =
4048             domain ? strconcat(user, "@", domain, (char *) NULL) : xstrdup(user);
4049 
4050         status = sasl_checkpass(httpd_saslconn, realuser, strlen(realuser),
4051                                 pass, strlen(pass));
4052         memset(pass, 0, strlen(pass));          /* erase plaintext password */
4053 
4054         if (status) {
4055             syslog(LOG_NOTICE, "badlogin: %s Basic %s %s",
4056                    txn->conn->clienthost, realuser,
4057                    sasl_errdetail(httpd_saslconn));
4058             free(realuser);
4059 
4060             /* Don't allow user probing */
4061             if (status == SASL_NOUSER) status = SASL_BADAUTH;
4062             return status;
4063         }
4064         free(realuser);
4065 
4066         /* Successful authentication - fall through */
4067         httpd_extrafolder = xstrdupnull(plus);
4068         httpd_extradomain = xstrdupnull(extra);
4069     }
4070     else if (scheme->id == AUTH_BEARER) {
4071         /* Bearer authentication */
4072         assert(txn->req_tgt.namespace->bearer);
4073 
4074         /* Call namespace bearer authentication.
4075          * We are working with base64 buffer, so the namespace can
4076          * write the canonicalized userid into the buffer */
4077         base64[0] = 0;
4078         status = txn->req_tgt.namespace->bearer(clientin,
4079                                                 base64, BASE64_BUF_SIZE);
4080         if (status) return status;
4081         canon_user = user = base64;
4082 
4083         /* Successful authentication - fall through */
4084         httpd_extrafolder = NULL;
4085         httpd_extradomain = NULL;
4086         httpd_authstate = auth_newstate(user);
4087     }
4088     else {
4089         /* SASL-based authentication (SCRAM_*, Digest, Negotiate, NTLM) */
4090         const char *serverout = NULL;
4091         unsigned int serveroutlen = 0;
4092         unsigned int auth_params_len = 0;
4093 
4094 #ifdef SASL_HTTP_REQUEST
4095         /* Setup SASL HTTP request, if necessary */
4096         sasl_http_request_t sasl_http_req;
4097 
4098         if (scheme->flags & AUTH_NEED_REQUEST) {
4099             sasl_http_req.method = txn->req_line.meth;
4100             sasl_http_req.uri = txn->req_line.uri;
4101             sasl_http_req.entity = NULL;
4102             sasl_http_req.elen = 0;
4103             sasl_http_req.non_persist = txn->flags.conn & CONN_CLOSE;
4104             sasl_setprop(httpd_saslconn, SASL_HTTP_REQUEST, &sasl_http_req);
4105         }
4106 #endif /* SASL_HTTP_REQUEST */
4107 
4108         if (status == SASL_CONTINUE) {
4109             /* Continue current authentication exchange */
4110             syslog(LOG_DEBUG, "http_auth: continue %s", scheme->saslmech);
4111             status = sasl_server_step(httpd_saslconn, clientin, clientinlen,
4112                                       &serverout, &serveroutlen);
4113         }
4114         else {
4115             /* Start new authentication exchange */
4116             syslog(LOG_DEBUG, "http_auth: start %s", scheme->saslmech);
4117             status = sasl_server_start(httpd_saslconn, scheme->saslmech,
4118                                        clientin, clientinlen,
4119                                        &serverout, &serveroutlen);
4120         }
4121 
4122         /* Failure - probably bad client response */
4123         if ((status != SASL_OK) && (status != SASL_CONTINUE)) {
4124             syslog(LOG_ERR, "SASL failed: %s",
4125                    sasl_errstring(status, NULL, NULL));
4126             return status;
4127         }
4128 
4129         /* Prepend any auth parameters, if necessary */
4130         if (scheme->flags & AUTH_DATA_PARAM) {
4131             auth_params_len = snprintf(base64,
4132                                        MAX_AUTHPARAM_SIZE + MAX_SESSIONID_SIZE,
4133                                        "sid=%s%s", session_id(),
4134                                        serverout ? ",data=" : "");
4135         }
4136 
4137         /* Base64 encode any server challenge, if necessary */
4138         if (serverout && (scheme->flags & AUTH_BASE64)) {
4139             r = sasl_encode64(serverout, serveroutlen,
4140                               base64 + auth_params_len, MAX_BASE64_SIZE, NULL);
4141             if (r != SASL_OK) {
4142                 syslog(LOG_ERR, "Base64 encode failed: %s",
4143                        sasl_errstring(r, NULL, NULL));
4144                 return r;
4145             }
4146             serverout = base64;
4147         }
4148 
4149         chal->param = serverout;
4150 
4151         if (status == SASL_CONTINUE) {
4152             /* Need another step to complete authentication */
4153             return status;
4154         }
4155 
4156         /* Successful authentication
4157          *
4158          * HTTP doesn't support security layers,
4159          * so don't attach SASL context to prot layer.
4160          */
4161     }
4162 
4163     if (!canon_user) {
4164         /* Get the userid from SASL - already canonicalized */
4165         status = sasl_getprop(httpd_saslconn, SASL_USERNAME, &canon_user);
4166         if (status != SASL_OK) {
4167             syslog(LOG_ERR, "weird SASL error %d getting SASL_USERNAME", status);
4168             return status;
4169         }
4170         user = (const char *) canon_user;
4171     }
4172 
4173     if (httpd_authid) free(httpd_authid);
4174     httpd_authid = xstrdup(user);
4175 
4176     authzid = spool_getheader(txn->req_hdrs, "Authorize-As");
4177     if (authzid && *authzid[0]) {
4178         /* Trying to proxy as another user */
4179         user = authzid[0];
4180 
4181         status = proxy_authz(&user, txn);
4182         if (status) return status;
4183     }
4184 
4185     /* Post-process the successful authentication. */
4186     r = auth_success(txn, user);
4187     if (r == HTTP_UNAVAILABLE) {
4188         status = SASL_UNAVAIL;
4189     }
4190     else if (r) {
4191         /* Any error here comes after the user already logged in,
4192          * so avoid to return SASL_BADAUTH. It would trigger the
4193          * HTTP handler to send UNAUTHORIZED, and might confuse
4194          * users that provided their correct credentials. */
4195         syslog(LOG_ERR, "auth_success returned error: %s", error_message(r));
4196         status = SASL_FAIL;
4197     }
4198 
4199     return status;
4200 }
4201 
4202 
4203 /*************************  Method Execution Routines  ************************/
4204 
4205 
4206 /* Compare an etag in a header to a resource etag.
4207  * Returns 0 if a match, non-zero otherwise.
4208  */
etagcmp(const char * hdr,const char * etag)4209 EXPORTED int etagcmp(const char *hdr, const char *etag)
4210 {
4211     size_t len;
4212 
4213     if (!etag) return -1;               /* no representation       */
4214     if (!strcmp(hdr, "*")) return 0;    /* any representation      */
4215 
4216     len = strlen(etag);
4217     if (!strncmp(hdr, "W/", 2)) hdr+=2; /* skip weak prefix        */
4218     if (*hdr++ != '\"') return 1;       /* match/skip open DQUOTE  */
4219     if (strlen(hdr) != len+1) return 1; /* make sure lengths match */
4220     if (hdr[len] != '\"') return 1;     /* match close DQUOTE      */
4221 
4222     return strncmp(hdr, etag, len);
4223 }
4224 
4225 
4226 /* Compare a resource etag to a comma-separated list and/or multiple headers
4227  * looking for a match.  Returns 1 if a match is found, 0 otherwise.
4228  */
etag_match(const char * hdr[],const char * etag)4229 static unsigned etag_match(const char *hdr[], const char *etag)
4230 {
4231     unsigned i, match = 0;
4232     tok_t tok;
4233     char *token;
4234 
4235     for (i = 0; !match && hdr[i]; i++) {
4236         tok_init(&tok, hdr[i], ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
4237         while (!match && (token = tok_next(&tok))) {
4238             if (!etagcmp(token, etag)) match = 1;
4239         }
4240         tok_fini(&tok);
4241     }
4242 
4243     return match;
4244 }
4245 
4246 
parse_ranges(const char * hdr,unsigned long len,struct range ** ranges)4247 static int parse_ranges(const char *hdr, unsigned long len,
4248                         struct range **ranges)
4249 {
4250     int ret = HTTP_BAD_RANGE;
4251     struct range *new, *tail = *ranges = NULL;
4252     tok_t tok;
4253     char *token;
4254 
4255     if (!len) return HTTP_OK;  /* need to know length of representation */
4256 
4257     /* we only handle byte-unit */
4258     if (!hdr || strncmp(hdr, "bytes=", 6)) return HTTP_OK;
4259 
4260     tok_init(&tok, hdr+6, ",", TOK_TRIMLEFT|TOK_TRIMRIGHT);
4261     while ((token = tok_next(&tok))) {
4262         /* default to entire representation */
4263         unsigned long first = 0;
4264         unsigned long last = len - 1;
4265         char *p, *endp;
4266 
4267         if (!(p = strchr(token, '-'))) continue;  /* bad byte-range-set */
4268 
4269         if (p == token) {
4270             /* suffix-byte-range-spec */
4271             unsigned long suffix = strtoul(++p, &endp, 10);
4272 
4273             if (endp == p || *endp) continue;  /* bad suffix-length */
4274             if (!suffix) continue;      /* unsatisfiable suffix-length */
4275 
4276             /* don't start before byte zero */
4277             if (suffix < len) first = len - suffix;
4278         }
4279         else {
4280             /* byte-range-spec */
4281             first = strtoul(token, &endp, 10);
4282             if (endp != p) continue;      /* bad first-byte-pos */
4283             if (first >= len) continue;   /* unsatisfiable first-byte-pos */
4284 
4285             if (*++p) {
4286                 /* last-byte-pos */
4287                 last = strtoul(p, &endp, 10);
4288                 if (*endp || last < first) continue; /* bad last-byte-pos */
4289 
4290                 /* don't go past end of representation */
4291                 if (last >= len) last = len - 1;
4292             }
4293         }
4294 
4295         ret = HTTP_PARTIAL;
4296 
4297         /* Coalesce overlapping ranges, or those with a gap < 80 bytes */
4298         if (tail &&
4299             first >= tail->first && (long) (first - tail->last) < 80) {
4300             tail->last = MAX(last, tail->last);
4301             continue;
4302         }
4303 
4304         /* Create a new range and append it to linked list */
4305         new = xzmalloc(sizeof(struct range));
4306         new->first = first;
4307         new->last = last;
4308 
4309         if (tail) tail->next = new;
4310         else *ranges = new;
4311         tail = new;
4312     }
4313 
4314     tok_fini(&tok);
4315 
4316     return ret;
4317 }
4318 
4319 
4320 /* Check headers for any preconditions.
4321  *
4322  * Interaction is complex and is documented in RFC 7232
4323  */
check_precond(struct transaction_t * txn,const char * etag,time_t lastmod)4324 EXPORTED int check_precond(struct transaction_t *txn,
4325                            const char *etag, time_t lastmod)
4326 {
4327     hdrcache_t hdrcache = txn->req_hdrs;
4328     const char **hdr;
4329     time_t since = 0;
4330 
4331     /* Step 1 */
4332     if ((hdr = spool_getheader(hdrcache, "If-Match"))) {
4333         if (!etag_match(hdr, etag)) return HTTP_PRECOND_FAILED;
4334 
4335         /* Continue to step 3 */
4336     }
4337 
4338     /* Step 2 */
4339     else if ((hdr = spool_getheader(hdrcache, "If-Unmodified-Since"))) {
4340         if (time_from_rfc5322(hdr[0], &since, DATETIME_FULL) < 0)
4341             return HTTP_BAD_REQUEST;
4342 
4343         if (lastmod > since) return HTTP_PRECOND_FAILED;
4344 
4345         /* Continue to step 3 */
4346     }
4347 
4348     /* Step 3 */
4349     if ((hdr = spool_getheader(hdrcache, "If-None-Match"))) {
4350         if (etag_match(hdr, etag)) {
4351             if (txn->meth == METH_GET || txn->meth == METH_HEAD)
4352                 return HTTP_NOT_MODIFIED;
4353             else
4354                 return HTTP_PRECOND_FAILED;
4355         }
4356 
4357         /* Continue to step 5 */
4358     }
4359 
4360     /* Step 4 */
4361     else if ((txn->meth == METH_GET || txn->meth == METH_HEAD) &&
4362              (hdr = spool_getheader(hdrcache, "If-Modified-Since"))) {
4363         if (time_from_rfc5322(hdr[0], &since, DATETIME_FULL) < 0)
4364             return HTTP_BAD_REQUEST;
4365 
4366         if (lastmod <= since) return HTTP_NOT_MODIFIED;
4367 
4368         /* Continue to step 5 */
4369     }
4370 
4371     /* Step 5 */
4372     if (txn->flags.ranges &&  /* Only if we support Range requests */
4373         txn->meth == METH_GET && (hdr = spool_getheader(hdrcache, "Range"))) {
4374 
4375         if ((hdr = spool_getheader(hdrcache, "If-Range"))) {
4376             time_from_rfc5322(hdr[0], &since, DATETIME_FULL); /* error OK here, could be an etag */
4377         }
4378 
4379         /* Only process Range if If-Range isn't present or validator matches */
4380         if (!hdr || (since && (lastmod <= since)) || !etagcmp(hdr[0], etag))
4381             return HTTP_PARTIAL;
4382     }
4383 
4384     /* Step 6 */
4385     return HTTP_OK;
4386 }
4387 
4388 
4389 const struct mimetype {
4390     const char *ext;
4391     const char *type;
4392     unsigned int compressible;
4393 } mimetypes[] = {
4394     { ".css",  "text/css", 1 },
4395     { ".htm",  "text/html", 1 },
4396     { ".html", "text/html", 1 },
4397     { ".ics",  "text/calendar", 1 },
4398     { ".ifb",  "text/calendar", 1 },
4399     { ".text", "text/plain", 1 },
4400     { ".txt",  "text/plain", 1 },
4401 
4402     { ".cgm",  "image/cgm", 1 },
4403     { ".gif",  "image/gif", 0 },
4404     { ".jpg",  "image/jpeg", 0 },
4405     { ".jpeg", "image/jpeg", 0 },
4406     { ".png",  "image/png", 0 },
4407     { ".svg",  "image/svg+xml", 1 },
4408     { ".tif",  "image/tiff", 1 },
4409     { ".tiff", "image/tiff", 1 },
4410 
4411     { ".aac",  "audio/aac", 0 },
4412     { ".m4a",  "audio/mp4", 0 },
4413     { ".mp3",  "audio/mpeg", 0 },
4414     { ".mpeg", "audio/mpeg", 0 },
4415     { ".oga",  "audio/ogg", 0 },
4416     { ".ogg",  "audio/ogg", 0 },
4417     { ".wav",  "audio/wav", 0 },
4418 
4419     { ".avi",  "video/x-msvideo", 0 },
4420     { ".mov",  "video/quicktime", 0 },
4421     { ".m4v",  "video/mp4", 0 },
4422     { ".ogv",  "video/ogg", 0 },
4423     { ".qt",   "video/quicktime", 0 },
4424     { ".wmv",  "video/x-ms-wmv", 0 },
4425 
4426     { ".bz",   "application/x-bzip", 0 },
4427     { ".bz2",  "application/x-bzip2", 0 },
4428     { ".gz",   "application/gzip", 0 },
4429     { ".gzip", "application/gzip", 0 },
4430     { ".tgz",  "application/gzip", 0 },
4431     { ".zip",  "application/zip", 0 },
4432 
4433     { ".doc",  "application/msword", 1 },
4434     { ".jcs",  "application/calendar+json", 1 },
4435     { ".jfb",  "application/calendar+json", 1 },
4436     { ".js",   "application/javascript", 1 },
4437     { ".json", "application/json", 1 },
4438     { ".pdf",  "application/pdf", 1 },
4439     { ".ppt",  "application/vnd.ms-powerpoint", 1 },
4440     { ".sh",   "application/x-sh", 1 },
4441     { ".tar",  "application/x-tar", 1 },
4442     { ".xcs",  "application/calendar+xml", 1 },
4443     { ".xfb",  "application/calendar+xml", 1 },
4444     { ".xls",  "application/vnd.ms-excel", 1 },
4445     { ".xml",  "application/xml", 1 },
4446 
4447     { NULL, NULL, 0 }
4448 };
4449 
4450 
list_well_known(struct transaction_t * txn)4451 static int list_well_known(struct transaction_t *txn)
4452 {
4453     static struct buf body = BUF_INITIALIZER;
4454     static time_t lastmod = 0;
4455     struct stat sbuf;
4456     int precond;
4457 
4458     /* stat() imapd.conf for Last-Modified and ETag */
4459     stat(config_filename, &sbuf);
4460     assert(!buf_len(&txn->buf));
4461     buf_printf(&txn->buf, TIME_T_FMT "-" TIME_T_FMT "-" OFF_T_FMT,
4462                compile_time, sbuf.st_mtime, sbuf.st_size);
4463     sbuf.st_mtime = MAX(compile_time, sbuf.st_mtime);
4464 
4465     /* Check any preconditions, including range request */
4466     txn->flags.ranges = 1;
4467     precond = check_precond(txn, buf_cstring(&txn->buf), sbuf.st_mtime);
4468 
4469     switch (precond) {
4470     case HTTP_OK:
4471     case HTTP_NOT_MODIFIED:
4472         /* Fill in ETag, Last-Modified, and Expires */
4473         txn->resp_body.etag = buf_cstring(&txn->buf);
4474         txn->resp_body.lastmod = sbuf.st_mtime;
4475         txn->resp_body.maxage = 86400;  /* 24 hrs */
4476         txn->flags.cc |= CC_MAXAGE;
4477 
4478         if (precond != HTTP_NOT_MODIFIED) break;
4479 
4480         GCC_FALLTHROUGH
4481 
4482     default:
4483         /* We failed a precondition - don't perform the request */
4484         return precond;
4485     }
4486 
4487     if (txn->resp_body.lastmod > lastmod) {
4488         const char *proto = NULL, *host = NULL;
4489         unsigned i, level = 0;
4490 
4491         /* Start HTML */
4492         buf_reset(&body);
4493         buf_printf_markup(&body, level, HTML_DOCTYPE);
4494         buf_printf_markup(&body, level++, "<html>");
4495         buf_printf_markup(&body, level++, "<head>");
4496         buf_printf_markup(&body, level,
4497                           "<title>%s</title>", "Well-Known Locations");
4498         buf_printf_markup(&body, --level, "</head>");
4499         buf_printf_markup(&body, level++, "<body>");
4500         buf_printf_markup(&body, level,
4501                           "<h2>%s</h2>", "Well-Known Locations");
4502         buf_printf_markup(&body, level++, "<ul>");
4503 
4504         /* Add the list of enabled /.well-known/ URLs */
4505         http_proto_host(txn->req_hdrs, &proto, &host);
4506         for (i = 0; http_namespaces[i]; i++) {
4507 
4508             if (http_namespaces[i]->enabled && http_namespaces[i]->well_known) {
4509                 buf_printf_markup(&body, level,
4510                                   "<li><a href=\"%s://%s%s\">%s</a></li>",
4511                                   proto, host, http_namespaces[i]->prefix,
4512                                   http_namespaces[i]->well_known);
4513             }
4514         }
4515 
4516         /* Finish HTML */
4517         buf_printf_markup(&body, --level, "</ul>");
4518         buf_printf_markup(&body, --level, "</body>");
4519         buf_printf_markup(&body, --level, "</html>");
4520 
4521         lastmod = txn->resp_body.lastmod;
4522     }
4523 
4524     /* Output the HTML response */
4525     txn->resp_body.type = "text/html; charset=utf-8";
4526     write_body(precond, txn, buf_cstring(&body), buf_len(&body));
4527 
4528     return 0;
4529 }
4530 
4531 
4532 /*
4533  * WebSockets data callback (no sub-protocol): Echo back non-control messages.
4534  *
4535  * Can be tested with:
4536  *   https://github.com/websockets/wscat
4537  *   https://addons.mozilla.org/en-US/firefox/addon/simple-websocket-client/
4538  *   https://chrome.google.com/webstore/detail/simple-websocket-client/gobngblklhkgmjhbpbdlkglbhhlafjnh
4539  *   https://chrome.google.com/webstore/detail/web-socket-client/lifhekgaodigcpmnakfhaaaboididbdn
4540  *
4541  * WebSockets over HTTP/2 currently only available in:
4542  *   https://www.google.com/chrome/browser/canary.html
4543  */
ws_echo(struct buf * inbuf,struct buf * outbuf,struct buf * logbuf,void ** rock)4544 static int ws_echo(struct buf *inbuf, struct buf *outbuf,
4545                    struct buf *logbuf __attribute__((unused)),
4546                    void **rock __attribute__((unused)))
4547 {
4548     buf_init_ro(outbuf, buf_base(inbuf), buf_len(inbuf));
4549 
4550     return 0;
4551 }
4552 
4553 
meth_connect(struct transaction_t * txn,void * params)4554 HIDDEN int meth_connect(struct transaction_t *txn, void *params)
4555 {
4556     struct connect_params *cparams = (struct connect_params *) params;
4557 
4558     /* Bootstrap WebSockets over HTTP/2, if requested */
4559     if ((txn->flags.ver != VER_2) ||
4560         !ws_enabled() || !cparams || !cparams->endpoint) {
4561         return HTTP_NOT_IMPLEMENTED;
4562     }
4563 
4564     if (strcmp(txn->req_uri->path, cparams->endpoint)) return HTTP_NOT_ALLOWED;
4565 
4566     if (!(txn->flags.upgrade & UPGRADE_WS)) {
4567         txn->error.desc = "Missing/unsupported :protocol value ";
4568         return HTTP_BAD_REQUEST;
4569     }
4570 
4571     int ret = ws_start_channel(txn, cparams->subprotocol, cparams->data_cb);
4572 
4573     return (ret == HTTP_UPGRADE) ? HTTP_BAD_REQUEST : ret;
4574 }
4575 
4576 
4577 #define WELL_KNOWN_PREFIX "/.well-known"
4578 
4579 /* Perform a GET/HEAD request */
meth_get(struct transaction_t * txn,void * params)4580 static int meth_get(struct transaction_t *txn,
4581                     void *params __attribute__((unused)))
4582 {
4583     int r, fd = -1, precond, len;
4584     const char *prefix, *urls, *path, *ext;
4585     static struct buf pathbuf = BUF_INITIALIZER;
4586     struct stat sbuf;
4587     const char *msg_base = NULL;
4588     size_t msg_size = 0;
4589     struct resp_body_t *resp_body = &txn->resp_body;
4590 
4591     /* Upgrade to WebSockets over HTTP/1.1 on root, if requested */
4592     if (!strcmp(txn->req_uri->path, "/")) {
4593         if (txn->flags.upgrade & UPGRADE_WS) {
4594             return ws_start_channel(txn, NULL, &ws_echo);
4595         }
4596 
4597         if (ws_enabled()) {
4598             txn->flags.upgrade |= UPGRADE_WS;
4599             txn->flags.conn |= CONN_UPGRADE;
4600         }
4601     }
4602 
4603     /* Check if this is a request for /.well-known/ listing */
4604     len = strlen(WELL_KNOWN_PREFIX);
4605     if (!strncmp(txn->req_uri->path, WELL_KNOWN_PREFIX, len)) {
4606         if (txn->req_uri->path[len] == '/') len++;
4607         if (txn->req_uri->path[len] == '\0') {
4608             return list_well_known(txn);
4609         }
4610 
4611         return HTTP_NOT_FOUND;
4612     }
4613 
4614     /* Serve up static pages */
4615     prefix = config_getstring(IMAPOPT_HTTPDOCROOT);
4616     if (!prefix) return HTTP_NOT_FOUND;
4617 
4618     if (*prefix != '/') {
4619         /* Remote content */
4620         struct backend *be;
4621 
4622         be = proxy_findserver(prefix, &http_protocol, httpd_userid,
4623                               &backend_cached, NULL, NULL, httpd_in);
4624         if (!be) return HTTP_UNAVAILABLE;
4625 
4626         return http_pipe_req_resp(be, txn);
4627     }
4628 
4629     /* Local content */
4630     if ((urls = config_getstring(IMAPOPT_HTTPALLOWEDURLS))) {
4631         tok_t tok = TOK_INITIALIZER(urls, " \t", TOK_TRIMLEFT|TOK_TRIMRIGHT);
4632         char *token;
4633 
4634         while ((token = tok_next(&tok)) && strcmp(token, txn->req_uri->path));
4635         tok_fini(&tok);
4636 
4637         if (!token) return HTTP_NOT_FOUND;
4638     }
4639 
4640     buf_setcstr(&pathbuf, prefix);
4641     buf_appendcstr(&pathbuf, txn->req_uri->path);
4642     path = buf_cstring(&pathbuf);
4643 
4644     /* See if path is a directory and look for index.html */
4645     if (!(r = stat(path, &sbuf)) && S_ISDIR(sbuf.st_mode)) {
4646         buf_appendcstr(&pathbuf, "/index.html");
4647         path = buf_cstring(&pathbuf);
4648         r = stat(path, &sbuf);
4649     }
4650 
4651     /* See if file exists and get Content-Length & Last-Modified time */
4652     if (r || !S_ISREG(sbuf.st_mode)) return HTTP_NOT_FOUND;
4653 
4654     if (!resp_body->type) {
4655         /* Caller hasn't specified the Content-Type */
4656         resp_body->type = "application/octet-stream";
4657 
4658         if ((ext = strrchr(path, '.'))) {
4659             /* Try to use filename extension to identity Content-Type */
4660             const struct mimetype *mtype;
4661 
4662             for (mtype = mimetypes; mtype->ext; mtype++) {
4663                 if (!strcasecmp(ext, mtype->ext)) {
4664                     resp_body->type = mtype->type;
4665                     if (!mtype->compressible) {
4666                         /* Never compress non-compressible resources */
4667                         txn->resp_body.enc.type = CE_IDENTITY;
4668                         txn->resp_body.enc.proc = NULL;
4669                         txn->flags.te = TE_NONE;
4670                         txn->flags.vary &= ~VARY_AE;
4671                     }
4672                     break;
4673                 }
4674             }
4675         }
4676     }
4677 
4678     /* Generate Etag */
4679     assert(!buf_len(&txn->buf));
4680     buf_printf(&txn->buf, "%ld-%ld", (long) sbuf.st_mtime, (long) sbuf.st_size);
4681 
4682     /* Check any preconditions, including range request */
4683     txn->flags.ranges = 1;
4684     precond = check_precond(txn, buf_cstring(&txn->buf), sbuf.st_mtime);
4685 
4686     switch (precond) {
4687     case HTTP_OK:
4688     case HTTP_PARTIAL:
4689     case HTTP_NOT_MODIFIED:
4690         /* Fill in ETag, Last-Modified, and Expires */
4691         resp_body->etag = buf_cstring(&txn->buf);
4692         resp_body->lastmod = sbuf.st_mtime;
4693         resp_body->maxage = 86400;  /* 24 hrs */
4694         txn->flags.cc |= CC_MAXAGE;
4695         if (!httpd_userisanonymous) txn->flags.cc |= CC_PUBLIC;
4696 
4697         if (precond != HTTP_NOT_MODIFIED) break;
4698 
4699         GCC_FALLTHROUGH
4700 
4701     default:
4702         /* We failed a precondition - don't perform the request */
4703         resp_body->type = NULL;
4704         return precond;
4705     }
4706 
4707     if (txn->meth == METH_GET) {
4708         /* Open and mmap the file */
4709         if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR;
4710         map_refresh(fd, 1, &msg_base, &msg_size, sbuf.st_size, path, NULL);
4711     }
4712 
4713     write_body(precond, txn, msg_base, sbuf.st_size);
4714 
4715     if (fd != -1) {
4716         map_free(&msg_base, &msg_size);
4717         close(fd);
4718     }
4719 
4720     return 0;
4721 }
4722 
4723 
4724 /* Perform an OPTIONS request */
meth_options(struct transaction_t * txn,void * params)4725 EXPORTED int meth_options(struct transaction_t *txn, void *params)
4726 {
4727     parse_path_t parse_path = (parse_path_t) params;
4728     int r, i;
4729 
4730     /* Response should not be cached */
4731     txn->flags.cc |= CC_NOCACHE;
4732 
4733     /* Response doesn't have a body, so no Vary */
4734     txn->flags.vary = 0;
4735 
4736     /* Special case "*" - show all features/methods available on server */
4737     if (!strcmp(txn->req_uri->path, "*")) {
4738         for (i = 0; http_namespaces[i]; i++) {
4739             if (http_namespaces[i]->enabled)
4740                 txn->req_tgt.allow |= http_namespaces[i]->allow;
4741         }
4742 
4743         if (ws_enabled() && (txn->flags.ver == VER_2)) {
4744             /* CONNECT allowed for bootstrapping WebSocket over HTTP/2 */
4745             txn->req_tgt.allow |= ALLOW_CONNECT;
4746         }
4747     }
4748     else {
4749         if (parse_path) {
4750             /* Parse the path */
4751             r = parse_path(txn->req_uri->path, &txn->req_tgt, &txn->error.desc);
4752             if (r) return r;
4753         }
4754         else if (!strcmp(txn->req_uri->path, "/") &&
4755                  ws_enabled() && (txn->flags.ver == VER_2)) {
4756             /* WS 'echo' endpoint */
4757             txn->req_tgt.allow |= ALLOW_CONNECT;
4758         }
4759 
4760         if (txn->flags.cors) {
4761             const char **hdr =
4762                 spool_getheader(txn->req_hdrs, "Access-Control-Request-Method");
4763 
4764             if (hdr) {
4765                 /* CORS preflight request */
4766                 unsigned meth;
4767 
4768                 txn->flags.cors = CORS_PREFLIGHT;
4769 
4770                 /* Check Method against our list of known methods */
4771                 for (meth = 0; (meth < METH_UNKNOWN) &&
4772                          strcmp(http_methods[meth].name, hdr[0]); meth++);
4773 
4774                 if (meth == METH_UNKNOWN) txn->flags.cors = 0;
4775                 else {
4776                     /* Check Method against those supported by the resource */
4777                     if (!txn->req_tgt.namespace->methods[meth].proc)
4778                         txn->flags.cors = 0;
4779                 }
4780             }
4781         }
4782     }
4783 
4784     response_header(HTTP_OK, txn);
4785     return 0;
4786 }
4787 
4788 
4789 /* Perform an PROPFIND request on "/" iff we support CalDAV */
meth_propfind_root(struct transaction_t * txn,void * params)4790 static int meth_propfind_root(struct transaction_t *txn,
4791                               void *params __attribute__((unused)))
4792 {
4793     assert(txn);
4794 
4795 #ifdef WITH_DAV
4796     /* Apple iCal and Evolution both check "/" */
4797     if (!strcmp(txn->req_uri->path, "/") ||
4798         !strcmp(txn->req_uri->path, "/dav/")) {
4799         /* Array of known "live" properties */
4800         const struct prop_entry root_props[] = {
4801 
4802             /* WebDAV ACL (RFC 3744) properties */
4803             { "principal-collection-set", NS_DAV, PROP_COLLECTION,
4804               propfind_princolset, NULL, NULL },
4805 
4806             /* WebDAV Current Principal (RFC 5397) properties */
4807             { "current-user-principal", NS_DAV, PROP_COLLECTION,
4808               propfind_curprin, NULL, NULL },
4809 
4810             { NULL, 0, 0, NULL, NULL, NULL }
4811         };
4812 
4813         struct meth_params root_params = {
4814             .propfind = { DAV_FINITE_DEPTH, root_props }
4815         };
4816 
4817         /* Make a working copy of target path */
4818         strlcpy(txn->req_tgt.path, txn->req_uri->path,
4819                 sizeof(txn->req_tgt.path));
4820         txn->req_tgt.tail = txn->req_tgt.path + strlen(txn->req_tgt.path);
4821 
4822         txn->req_tgt.allow |= ALLOW_DAV;
4823         return meth_propfind(txn, &root_params);
4824     }
4825 #endif /* WITH_DAV */
4826 
4827     return HTTP_NOT_ALLOWED;
4828 }
4829 
4830 
4831 /* Write cached header to buf, excluding any that might have sensitive data. */
trace_cachehdr(const char * name,const char * contents,const char * raw,void * rock)4832 static void trace_cachehdr(const char *name, const char *contents, const char *raw, void *rock)
4833 {
4834     struct buf *buf = (struct buf *) rock;
4835     const char **hdr, *sensitive[] =
4836         { "authorization", "cookie", "proxy-authorization", NULL };
4837 
4838     /* Ignore private headers in our cache */
4839     if (name[0] == ':') return;
4840 
4841     for (hdr = sensitive; *hdr && strcmp(name, *hdr); hdr++);
4842 
4843     if (!*hdr) {
4844         if (raw) buf_appendcstr(buf, raw);
4845         else buf_printf(buf, "%c%s: %s\r\n",
4846                             toupper(name[0]), name+1, contents);
4847     }
4848 }
4849 
4850 /* Perform an TRACE request */
meth_trace(struct transaction_t * txn,void * params)4851 EXPORTED int meth_trace(struct transaction_t *txn, void *params)
4852 {
4853     parse_path_t parse_path = (parse_path_t) params;
4854     const char **hdr;
4855     unsigned long max_fwd = -1;
4856     struct buf *msg = &txn->resp_body.payload;
4857 
4858     /* Response should not be cached */
4859     txn->flags.cc |= CC_NOCACHE;
4860 
4861     /* Make sure method is allowed */
4862     if (!(txn->req_tgt.allow & ALLOW_TRACE)) return HTTP_NOT_ALLOWED;
4863 
4864     if ((hdr = spool_getheader(txn->req_hdrs, "Max-Forwards"))) {
4865         max_fwd = strtoul(hdr[0], NULL, 10);
4866     }
4867 
4868     if (max_fwd && parse_path) {
4869         /* Parse the path */
4870         int r;
4871 
4872         if ((r = parse_path(txn->req_uri->path,
4873                             &txn->req_tgt, &txn->error.desc))) return r;
4874 
4875         if (txn->req_tgt.mbentry && txn->req_tgt.mbentry->server) {
4876             /* Remote mailbox */
4877             struct backend *be;
4878 
4879             be = proxy_findserver(txn->req_tgt.mbentry->server,
4880                                   &http_protocol, httpd_userid,
4881                                   &backend_cached, NULL, NULL, httpd_in);
4882             if (!be) return HTTP_UNAVAILABLE;
4883 
4884             return http_pipe_req_resp(be, txn);
4885         }
4886 
4887         /* Local mailbox */
4888     }
4889 
4890     /* Echo the request back to the client as a message/http:
4891      *
4892      * - Piece the Request-line back together
4893      * - Use all non-sensitive cached headers from client
4894      */
4895     buf_reset(msg);
4896     buf_printf(msg, "TRACE %s %s\r\n", txn->req_line.uri, txn->req_line.ver);
4897     spool_enum_hdrcache(txn->req_hdrs, &trace_cachehdr, msg);
4898     buf_appendcstr(msg, "\r\n");
4899 
4900     txn->resp_body.type = "message/http";
4901     txn->resp_body.len = buf_len(msg);
4902 
4903     write_body(HTTP_OK, txn, buf_cstring(msg), buf_len(msg));
4904 
4905     return 0;
4906 }
4907 
4908 /* simple wrapper to implicity add READFB if we have the READ ACL */
httpd_myrights(struct auth_state * authstate,const mbentry_t * mbentry)4909 EXPORTED int httpd_myrights(struct auth_state *authstate, const mbentry_t *mbentry)
4910 {
4911     int rights = 0;
4912 
4913     if (mbentry && mbentry->acl) {
4914         rights = cyrus_acl_myrights(authstate, mbentry->acl);
4915 
4916         if (mbentry->mbtype == MBTYPE_CALENDAR &&
4917             (rights & DACL_READ) == DACL_READ) {
4918             rights |= DACL_READFB;
4919         }
4920     }
4921 
4922     return rights;
4923 }
4924 
4925 /* Allow unauthenticated GET/HEAD, deny all other unauthenticated requests */
http_allow_noauth_get(struct transaction_t * txn)4926 EXPORTED int http_allow_noauth_get(struct transaction_t *txn)
4927 {
4928     /* Inverse logic: True means we *require* authentication */
4929     switch (txn->meth) {
4930     case METH_GET:
4931     case METH_HEAD:
4932         /* Let method processing function decide if auth is needed */
4933         return 0;
4934     default:
4935         return 1;
4936     }
4937 }
4938 
4939 /* Allow unauthenticated requests */
http_allow_noauth(struct transaction_t * txn)4940 EXPORTED int http_allow_noauth(struct transaction_t *txn __attribute__((unused)))
4941 {
4942     return 0;
4943 }
4944 
4945 
4946 /* Read the body of a request */
http_read_req_body(struct transaction_t * txn)4947 EXPORTED int http_read_req_body(struct transaction_t *txn)
4948 {
4949     struct body_t *body = &txn->req_body;
4950 
4951     syslog(LOG_DEBUG, "http_read_req_body(flags=%#x, framing=%d)",
4952            body->flags, body->framing);
4953 
4954     if (body->flags & BODY_DONE) return 0;
4955     body->flags |= BODY_DONE;
4956 
4957     if (body->flags & BODY_CONTINUE) {
4958         body->flags &= ~BODY_CONTINUE;
4959 
4960         if (body->flags & BODY_DISCARD) {
4961             /* Don't care about the body and client hasn't sent it, we're done */
4962             return 0;
4963         }
4964 
4965         /* Tell client to send the body */
4966         response_header(HTTP_CONTINUE, txn);
4967     }
4968 
4969     /* Read body from client */
4970     return http_read_body(txn->conn->pin, txn->req_hdrs, body, &txn->error.desc);
4971 }
4972