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