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