Blob


1 /* $OpenBSD: relay_http.c,v 1.100 2026/06/03 20:00:34 kirill Exp $ */
3 /*
4 * Copyright (c) 2006 - 2016 Reyk Floeter <reyk@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
19 #include <sys/types.h>
20 #include <sys/queue.h>
21 #include <sys/time.h>
22 #include <sys/socket.h>
23 #include <sys/tree.h>
25 #include <netinet/in.h>
26 #include <arpa/inet.h>
28 #include <limits.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <errno.h>
32 #include <string.h>
33 #include <time.h>
34 #include <event.h>
35 #include <fnmatch.h>
36 #include <siphash.h>
37 #include <imsg.h>
38 #include <unistd.h>
40 #include "relayd.h"
41 #include "http.h"
42 #include "log.h"
44 static int _relay_lookup_url(struct ctl_relay_event *, char *, char *,
45 char *, struct kv *);
46 int relay_lookup_url(struct ctl_relay_event *,
47 const char *, struct kv *);
48 int relay_lookup_query(struct ctl_relay_event *, struct kv *);
49 int relay_lookup_cookie(struct ctl_relay_event *, const char *,
50 struct kv *);
51 void relay_read_httpcontent(struct bufferevent *, void *);
52 void relay_read_httpchunks(struct bufferevent *, void *);
53 char *relay_expand_http(struct ctl_relay_event *, char *,
54 char *, size_t);
55 int relay_writeheader_kv(struct ctl_relay_event *, struct kv *);
56 int relay_writeheader_http(struct ctl_relay_event *,
57 struct ctl_relay_event *);
58 int relay_writerequest_http(struct ctl_relay_event *,
59 struct ctl_relay_event *);
60 int relay_writeresponse_http(struct ctl_relay_event *,
61 struct ctl_relay_event *);
62 void relay_reset_http(struct ctl_relay_event *);
63 static int relay_httpmethod_cmp(const void *, const void *);
64 static int relay_httperror_cmp(const void *, const void *);
65 int relay_httpquery_test(struct ctl_relay_event *,
66 struct relay_rule *, struct kvlist *);
67 int relay_httpheader_test(struct ctl_relay_event *,
68 struct relay_rule *, struct kvlist *);
69 int relay_httppath_test(struct ctl_relay_event *,
70 struct relay_rule *, struct kvlist *);
71 int relay_httpurl_test(struct ctl_relay_event *,
72 struct relay_rule *, struct kvlist *);
73 int relay_httpcookie_test(struct ctl_relay_event *,
74 struct relay_rule *, struct kvlist *);
75 int relay_apply_actions(struct ctl_relay_event *, struct kvlist *,
76 struct relay_table *);
77 int relay_match_actions(struct ctl_relay_event *,
78 struct relay_rule *, struct kvlist *, struct kvlist *,
79 struct relay_table **);
80 void relay_httpdesc_free(struct http_descriptor *);
81 char * server_root_strip(char *, int);
82 int relay_http_parse_startline(struct ctl_relay_event *, char *,
83 enum httpmethod *);
85 static struct relayd *env = NULL;
87 static struct http_method http_methods[] = HTTP_METHODS;
88 static struct http_error http_errors[] = HTTP_ERRORS;
90 void
91 relay_http(struct relayd *x_env)
92 {
93 if (x_env != NULL)
94 env = x_env;
96 DPRINTF("%s: sorting lookup tables, pid %d", __func__, getpid());
98 /* Sort the HTTP lookup arrays */
99 qsort(http_methods, sizeof(http_methods) /
100 sizeof(http_methods[0]) - 1,
101 sizeof(http_methods[0]), relay_httpmethod_cmp);
102 qsort(http_errors, sizeof(http_errors) /
103 sizeof(http_errors[0]) - 1,
104 sizeof(http_errors[0]), relay_httperror_cmp);
107 void
108 relay_http_init(struct relay *rlay)
110 rlay->rl_proto->close = relay_close_http;
112 relay_http(NULL);
114 /* Calculate skip step for the filter rules (may take a while) */
115 relay_calc_skip_steps(&rlay->rl_proto->rules);
118 int
119 relay_http_priv_init(struct rsession *con)
122 struct http_session *hs;
124 if ((hs = calloc(1, sizeof(*hs))) == NULL)
125 return (-1);
126 SIMPLEQ_INIT(&hs->hs_methods);
127 DPRINTF("%s: session %d http_session %p", __func__,
128 con->se_id, hs);
129 con->se_priv = hs;
130 return (relay_httpdesc_init(&con->se_in));
133 int
134 relay_httpdesc_init(struct ctl_relay_event *cre)
136 struct http_descriptor *desc;
138 if ((desc = calloc(1, sizeof(*desc))) == NULL)
139 return (-1);
141 RB_INIT(&desc->http_headers);
142 cre->desc = desc;
144 return (0);
147 void
148 relay_httpdesc_free(struct http_descriptor *desc)
150 if (desc == NULL)
151 return;
153 free(desc->http_path);
154 desc->http_path = NULL;
155 free(desc->http_query);
156 desc->http_query = NULL;
157 free(desc->http_version);
158 desc->http_version = NULL;
159 free(desc->query_key);
160 desc->query_key = NULL;
161 free(desc->query_val);
162 desc->query_val = NULL;
163 kv_purge(&desc->http_headers);
164 desc->http_lastheader = NULL;
165 desc->http_cl = NULL;
168 static int
169 relay_http_header_name_valid(const char *name)
171 /*
172 * RFC 9110 specifies that only the following characters are
173 * permitted within HTTP header field names.
174 */
175 const char token_chars[] = "!#$%&'*+-.^_`|~0123456789"
176 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
177 const size_t len = strspn(name, token_chars);
179 return (name[len] == '\0');
182 void
183 relay_read_http(struct bufferevent *bev, void *arg)
185 struct ctl_relay_event *cre = arg;
186 struct http_descriptor *desc = cre->desc;
187 struct rsession *con = cre->con;
188 struct relay *rlay = con->se_relay;
189 struct protocol *proto = rlay->rl_proto;
190 struct evbuffer *src = EVBUFFER_INPUT(bev);
191 char *line = NULL, *key, *value;
192 char *urlproto, *host, *path;
193 int action, unique, ret;
194 const char *errstr;
195 size_t size, linelen;
196 struct kv *hdr = NULL;
197 struct kv *upgrade = NULL, *upgrade_ws = NULL;
198 struct kv *connection_close = NULL;
199 int ws_response = 0;
200 int headers_only = 0;
201 enum httpmethod request_method = HTTP_METHOD_NONE;
203 getmonotime(&con->se_tv_last);
204 cre->timedout = 0;
206 size = EVBUFFER_LENGTH(src);
207 DPRINTF("%s: session %d: size %lu, to read %lld",
208 __func__, con->se_id, size, cre->toread);
209 if (size == 0) {
210 if (cre->dir == RELAY_DIR_RESPONSE)
211 return;
212 cre->toread = TOREAD_HTTP_HEADER;
213 goto done;
216 for (;;) {
217 line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF);
218 if (line == NULL) {
219 /*
220 * We do not process the last header on premature
221 * EOF as it may not be complete.
222 */
223 break;
226 /*
227 * An empty line indicates the end of the request.
228 * libevent already stripped the \r\n for us.
229 */
230 if (linelen == 0) {
231 cre->done = 1;
232 free(line);
233 line = NULL;
234 if (cre->line > 1) {
235 /* Process last (complete) header line. */
236 goto last_header;
238 break;
241 /* Limit the total header length minus \r\n */
242 cre->headerlen += linelen;
243 if (cre->headerlen > proto->httpheaderlen) {
244 relay_abort_http(con, 413,
245 "request headers too large", 0);
246 goto abort;
249 /* Reject requests with an embedded NUL byte. */
250 if (memchr(line, '\0', linelen) != NULL) {
251 relay_abort_http(con, 400, "malformed", 0);
252 goto abort;
254 /* Process the HTTP start-line (request or response status) */
255 if (++cre->line == 1) {
256 if (relay_http_parse_startline(cre, line,
257 &request_method) == -1) {
258 free(line);
259 return;
261 free(line);
262 continue;
265 /*
266 * RFC 9112 section 5.2 permits unconditional rejection.
267 * Obs-fold creates parser differentials between intermediaries
268 * and backends.
269 */
270 if (*line == ' ' || *line == '\t') {
271 if (cre->dir == RELAY_DIR_RESPONSE)
272 relay_abort_http(con, 502, "Bad Gateway", 0);
273 else
274 relay_abort_http(con, 400, "malformed", 0);
275 goto abort;
278 /* Process the last complete header line. */
279 last_header:
280 if (desc->http_lastheader != NULL) {
281 key = desc->http_lastheader->kv_key;
282 value = desc->http_lastheader->kv_value;
284 DPRINTF("%s: session %d: header '%s: %s'", __func__,
285 con->se_id, key, value);
287 if (desc->http_method != HTTP_METHOD_NONE &&
288 strcasecmp("Content-Length", key) == 0) {
289 switch (desc->http_method) {
290 case HTTP_METHOD_TRACE:
291 case HTTP_METHOD_CONNECT:
292 /*
293 * These methods should not have a body
294 * and thus no Content-Length header.
295 */
296 relay_abort_http(con, 400, "malformed",
297 0);
298 goto abort;
299 case HTTP_METHOD_GET:
300 case HTTP_METHOD_HEAD:
301 case HTTP_METHOD_COPY:
302 case HTTP_METHOD_MOVE:
303 /*
304 * We strip the body (if present) from
305 * the GET, HEAD, COPY and MOVE methods
306 * so strip Content-Length too.
307 */
308 kv_delete(&desc->http_headers,
309 desc->http_lastheader);
310 break;
311 case HTTP_METHOD_RESPONSE:
312 if (request_method == HTTP_METHOD_HEAD)
313 break;
314 /* FALLTHROUGH */
315 default:
316 /*
317 * Need to read data from the client
318 * after the HTTP header.
319 * XXX What about non-standard clients
320 * not using the carriage return? And
321 * some browsers seem to include the
322 * line length in the content-length.
323 */
324 if (*value == '+' || *value == '-') {
325 errstr = "invalid";
326 } else {
327 cre->toread = strtonum(value, 0,
328 LLONG_MAX, &errstr);
330 if (errstr) {
331 relay_abort_http(con, 500,
332 errstr, 0);
333 goto abort;
335 desc->http_cl = desc->http_lastheader;
336 if (desc->http_cl->kv_parent != NULL)
337 desc->http_cl =
338 desc->http_cl->kv_parent;
339 break;
341 /*
342 * Response with a status code of 1xx
343 * (Informational) or 204 (No Content) MUST
344 * not have a Content-Length (rfc 7230 3.3.3)
345 * Instead we check for value != 0 because there
346 * are servers that do not follow the rfc and
347 * send Content-Length: 0.
348 */
349 if (desc->http_method == HTTP_METHOD_RESPONSE &&
350 (((desc->http_status >= 100 &&
351 desc->http_status < 200) ||
352 desc->http_status == 204)) &&
353 cre->toread != 0) {
354 relay_abort_http(con, 502,
355 "Bad Gateway", 0);
356 goto abort;
359 if (strcasecmp("Transfer-Encoding", key) == 0) {
360 /* We don't support other encodings. */
361 if (strcasecmp("chunked", value) != 0) {
362 relay_abort_http(con, 400,
363 "malformed", 0);
364 goto abort;
366 desc->http_chunked = 1;
369 if (strcasecmp("Host", key) == 0) {
370 /*
371 * The path may contain a URL. The host in the
372 * URL has to match the Host: value.
373 */
374 if (parse_url(desc->http_path,
375 &urlproto, &host, &path) == 0) {
376 ret = strcasecmp(host, value);
377 free(urlproto);
378 free(host);
379 free(path);
380 if (ret != 0) {
381 relay_abort_http(con, 400,
382 "malformed host", 0);
383 goto abort;
389 if (cre->done)
390 break;
392 /* Validate header field name and check for missing value. */
393 key = line;
394 if ((value = strchr(line, ':')) == NULL) {
395 relay_abort_http(con, 400, "malformed", 0);
396 goto abort;
398 *value++ = '\0';
399 value += strspn(value, " \t\r\n");
401 if (!relay_http_header_name_valid(key)) {
402 relay_abort_http(con, 400, "malformed", 0);
403 goto abort;
406 /* The "Host" header must only occur once. */
407 unique = strcasecmp("Host", key) == 0;
409 if ((hdr = kv_add(&desc->http_headers, key,
410 value, unique)) == NULL) {
411 relay_abort_http(con, 400, "malformed header", 0);
412 goto abort;
414 desc->http_lastheader = hdr;
416 free(line);
419 if (cre->done) {
420 if (desc->http_method == HTTP_METHOD_NONE) {
421 relay_abort_http(con, 406, "no method", 0);
422 return;
425 action = relay_test(proto, cre);
426 switch (action) {
427 case RES_FAIL:
428 relay_close(con, "filter rule failed", 1);
429 return;
430 case RES_BAD:
431 relay_abort_http(con, 400, "Bad Request",
432 con->se_label);
433 return;
434 case RES_INTERNAL:
435 relay_abort_http(con, 500, "Internal Server Error",
436 con->se_label);
437 return;
439 if (action != RES_PASS) {
440 relay_abort_http(con, 403, "Forbidden", con->se_label);
441 return;
444 /*
445 * RFC 9112 section 6.1 requires an intermediary that
446 * forwards a message with Transfer-Encoding to first
447 * remove Content-Length. relayd keeps chunked framing,
448 * so strip Content-Length before header emission.
449 */
450 if (desc->http_chunked && desc->http_cl != NULL) {
451 kv_delete(&desc->http_headers, desc->http_cl);
452 desc->http_cl = NULL;
453 desc->http_lastheader = NULL;
456 /*
457 * HTTP 101 Switching Protocols
458 */
460 upgrade = kv_find_value(&desc->http_headers,
461 "Connection", "upgrade", ",");
462 upgrade_ws = kv_find_value(&desc->http_headers,
463 "Upgrade", "websocket", ",");
464 ws_response = 0;
465 if (cre->dir == RELAY_DIR_REQUEST && upgrade_ws != NULL) {
466 if ((proto->httpflags & HTTPFLAG_WEBSOCKETS) == 0) {
467 relay_abort_http(con, 403,
468 "Websocket Forbidden", 0);
469 return;
470 } else if (upgrade == NULL) {
471 relay_abort_http(con, 400,
472 "Bad Websocket Request", 0);
473 return;
474 } else if (desc->http_method != HTTP_METHOD_GET) {
475 relay_abort_http(con, 405,
476 "Websocket Method Not Allowed", 0);
477 return;
479 } else if (cre->dir == RELAY_DIR_RESPONSE &&
480 desc->http_status == 101) {
481 if (upgrade_ws != NULL && upgrade != NULL &&
482 (proto->httpflags & HTTPFLAG_WEBSOCKETS)) {
483 ws_response = 1;
484 cre->dst->toread = TOREAD_UNLIMITED;
485 cre->dst->bev->readcb = relay_read;
486 } else {
487 relay_abort_http(con, 502,
488 "Bad Websocket Gateway", 0);
489 return;
493 connection_close = kv_find_value(&desc->http_headers,
494 "Connection", "close", ",");
495 /*
496 * RFC 9112 section 6.3: these responses end at the empty
497 * line after the header section. 101 upgrades become streams.
498 */
499 headers_only = cre->dir == RELAY_DIR_RESPONSE && !ws_response &&
500 (request_method == HTTP_METHOD_HEAD ||
501 (desc->http_status >= 100 && desc->http_status < 200) ||
502 desc->http_status == 204 || desc->http_status == 304);
503 if (headers_only)
504 cre->toread = 0;
506 switch (desc->http_method) {
507 case HTTP_METHOD_CONNECT:
508 /* Data stream */
509 cre->toread = TOREAD_UNLIMITED;
510 bev->readcb = relay_read;
511 break;
512 case HTTP_METHOD_GET:
513 case HTTP_METHOD_HEAD:
514 /* WebDAV methods */
515 case HTTP_METHOD_COPY:
516 case HTTP_METHOD_MOVE:
517 cre->toread = 0;
518 break;
519 case HTTP_METHOD_DELETE:
520 case HTTP_METHOD_OPTIONS:
521 case HTTP_METHOD_POST:
522 case HTTP_METHOD_PUT:
523 case HTTP_METHOD_RESPONSE:
524 /* WebDAV methods */
525 case HTTP_METHOD_PROPFIND:
526 case HTTP_METHOD_PROPPATCH:
527 case HTTP_METHOD_MKCOL:
528 case HTTP_METHOD_LOCK:
529 case HTTP_METHOD_UNLOCK:
530 case HTTP_METHOD_VERSION_CONTROL:
531 case HTTP_METHOD_REPORT:
532 case HTTP_METHOD_CHECKOUT:
533 case HTTP_METHOD_CHECKIN:
534 case HTTP_METHOD_UNCHECKOUT:
535 case HTTP_METHOD_MKWORKSPACE:
536 case HTTP_METHOD_UPDATE:
537 case HTTP_METHOD_LABEL:
538 case HTTP_METHOD_MERGE:
539 case HTTP_METHOD_BASELINE_CONTROL:
540 case HTTP_METHOD_MKACTIVITY:
541 case HTTP_METHOD_ORDERPATCH:
542 case HTTP_METHOD_ACL:
543 case HTTP_METHOD_MKREDIRECTREF:
544 case HTTP_METHOD_UPDATEREDIRECTREF:
545 case HTTP_METHOD_SEARCH:
546 case HTTP_METHOD_MKCALENDAR:
547 case HTTP_METHOD_PATCH:
548 /* HTTP request payload */
549 if (cre->toread > 0) {
550 bev->readcb = relay_read_httpcontent;
553 /* Single-pass HTTP body */
554 if (cre->toread < 0) {
555 cre->toread = TOREAD_UNLIMITED;
556 bev->readcb = relay_read;
558 break;
559 default:
560 /* HTTP handler */
561 cre->toread = TOREAD_HTTP_HEADER;
562 bev->readcb = relay_read_http;
563 break;
565 if (desc->http_chunked && !headers_only) {
566 /* Chunked transfer encoding */
567 cre->toread = TOREAD_HTTP_CHUNK_LENGTH;
568 bev->readcb = relay_read_httpchunks;
571 /*
572 * Ask the server to close the connection after this request
573 * since we don't read any further request headers. Only add
574 * this header if it does not already exist or if this is a
575 * outbound websocket upgrade response.
576 */
577 if (cre->toread == TOREAD_UNLIMITED &&
578 connection_close == NULL && !ws_response)
579 if (kv_add(&desc->http_headers, "Connection",
580 "close", 0) == NULL)
581 goto fail;
583 if (cre->dir == RELAY_DIR_REQUEST) {
584 if (relay_writerequest_http(cre->dst, cre) == -1)
585 goto fail;
586 } else {
587 if (relay_writeresponse_http(cre->dst, cre) == -1)
588 goto fail;
590 if (relay_bufferevent_print(cre->dst, "\r\n") == -1 ||
591 relay_writeheader_http(cre->dst, cre) == -1 ||
592 relay_bufferevent_print(cre->dst, "\r\n") == -1)
593 goto fail;
595 relay_reset_http(cre);
596 done:
597 if (cre->dir == RELAY_DIR_REQUEST && cre->toread <= 0 &&
598 cre->dst->state != STATE_CONNECTED) {
599 if (rlay->rl_conf.fwdmode == FWD_TRANS) {
600 relay_bindanyreq(con, 0, IPPROTO_TCP);
601 return;
603 if (relay_connect(con) == -1) {
604 relay_abort_http(con, 502, "session failed", 0);
605 return;
609 if (con->se_done) {
610 relay_close(con, "last http read (done)", 0);
611 return;
613 switch (relay_splice(cre)) {
614 case -1:
615 relay_close(con, strerror(errno), 1);
616 case 1:
617 return;
618 case 0:
619 break;
621 bufferevent_enable(bev, EV_READ);
622 if (EVBUFFER_LENGTH(src) && bev->readcb != relay_read_http)
623 bev->readcb(bev, arg);
624 /* The callback readcb() might have freed the session. */
625 return;
626 fail:
627 relay_abort_http(con, 500, strerror(errno), 0);
628 return;
629 abort:
630 free(line);
633 void
634 relay_read_httpcontent(struct bufferevent *bev, void *arg)
636 struct ctl_relay_event *cre = arg;
637 struct rsession *con = cre->con;
638 struct protocol *proto = con->se_relay->rl_proto;
640 struct evbuffer *src = EVBUFFER_INPUT(bev);
641 size_t size;
643 getmonotime(&con->se_tv_last);
644 cre->timedout = 0;
646 size = EVBUFFER_LENGTH(src);
647 DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
648 con->se_id, size, cre->toread);
649 if (!size)
650 return;
651 if (relay_spliceadjust(cre) == -1)
652 goto fail;
654 if (cre->toread > 0) {
655 /* Read content data */
656 if ((off_t)size > cre->toread) {
657 size = cre->toread;
658 if (relay_bufferevent_write_chunk(cre->dst, src, size)
659 == -1)
660 goto fail;
661 cre->toread = 0;
662 } else {
663 if (relay_bufferevent_write_buffer(cre->dst, src) == -1)
664 goto fail;
665 cre->toread -= size;
667 DPRINTF("%s: done, size %lu, to read %lld", __func__,
668 size, cre->toread);
670 if (cre->toread == 0) {
671 cre->toread = TOREAD_HTTP_HEADER;
672 bev->readcb = relay_read_http;
674 if (con->se_done)
675 goto done;
676 bufferevent_enable(bev, EV_READ);
678 if (cre->dst->bev && EVBUFFER_LENGTH(EVBUFFER_OUTPUT(cre->dst->bev)) >
679 (size_t)RELAY_MAX_PREFETCH * proto->tcpbufsiz)
680 bufferevent_disable(cre->bev, EV_READ);
682 if (bev->readcb != relay_read_httpcontent)
683 bev->readcb(bev, arg);
684 /* The callback readcb() might have freed the session. */
685 return;
686 done:
687 relay_close(con, "last http content read", 0);
688 return;
689 fail:
690 relay_close(con, strerror(errno), 1);
693 void
694 relay_read_httpchunks(struct bufferevent *bev, void *arg)
696 struct ctl_relay_event *cre = arg;
697 struct rsession *con = cre->con;
698 struct protocol *proto = con->se_relay->rl_proto;
699 struct evbuffer *src = EVBUFFER_INPUT(bev);
700 char *line, *ep;
701 long long llval;
702 size_t size, linelen;
704 getmonotime(&con->se_tv_last);
705 cre->timedout = 0;
707 size = EVBUFFER_LENGTH(src);
708 DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
709 con->se_id, size, cre->toread);
710 if (!size)
711 return;
712 if (relay_spliceadjust(cre) == -1)
713 goto fail;
715 if (cre->toread > 0) {
716 /* Read chunk data */
717 if ((off_t)size > cre->toread) {
718 size = cre->toread;
719 if (relay_bufferevent_write_chunk(cre->dst, src, size)
720 == -1)
721 goto fail;
722 cre->toread = 0;
723 } else {
724 if (relay_bufferevent_write_buffer(cre->dst, src) == -1)
725 goto fail;
726 cre->toread -= size;
728 DPRINTF("%s: done, size %lu, to read %lld", __func__,
729 size, cre->toread);
731 switch (cre->toread) {
732 case TOREAD_HTTP_CHUNK_LENGTH:
733 line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF);
734 if (line == NULL) {
735 /* Ignore empty line, continue */
736 bufferevent_enable(bev, EV_READ);
737 return;
739 if (linelen == 0) {
740 free(line);
741 goto next;
744 /*
745 * Read prepended chunk size in hex without leading +0[Xx].
746 * The returned signed value must not be negative.
747 */
748 if (line[0] == '+' || line[0] == '-' ||
749 (line[0] == '0' && (line[1] == 'x' || line[1] == 'X'))) {
750 /* Reject values like 0xdead and 0XBEEF or +FEED. */
751 ep = line;
752 } else {
753 errno = 0;
754 llval = strtoll(line, &ep, 16);
756 if (ep == line || *ep != '\0' || llval < 0 ||
757 (errno == ERANGE && llval == LLONG_MAX)) {
758 free(line);
759 relay_close(con, "invalid chunk size", 1);
760 return;
763 if (relay_bufferevent_print(cre->dst, line) == -1 ||
764 relay_bufferevent_print(cre->dst, "\r\n") == -1) {
765 free(line);
766 goto fail;
768 free(line);
770 if ((cre->toread = llval) == 0) {
771 DPRINTF("%s: last chunk", __func__);
772 cre->toread = TOREAD_HTTP_CHUNK_TRAILER;
774 break;
775 case TOREAD_HTTP_CHUNK_TRAILER:
776 /* Last chunk is 0 bytes followed by trailer and empty line */
777 line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF);
778 if (line == NULL) {
779 /* Ignore empty line, continue */
780 bufferevent_enable(bev, EV_READ);
781 return;
783 if (relay_bufferevent_print(cre->dst, line) == -1 ||
784 relay_bufferevent_print(cre->dst, "\r\n") == -1) {
785 free(line);
786 goto fail;
788 if (linelen == 0) {
789 /* Switch to HTTP header mode */
790 cre->toread = TOREAD_HTTP_HEADER;
791 bev->readcb = relay_read_http;
793 free(line);
794 break;
795 case 0:
796 /* Chunk is terminated by an empty newline */
797 line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF);
798 free(line);
799 if (relay_bufferevent_print(cre->dst, "\r\n") == -1)
800 goto fail;
801 cre->toread = TOREAD_HTTP_CHUNK_LENGTH;
802 break;
805 next:
806 if (con->se_done)
807 goto done;
808 bufferevent_enable(bev, EV_READ);
810 if (cre->dst->bev && EVBUFFER_LENGTH(EVBUFFER_OUTPUT(cre->dst->bev)) >
811 (size_t)RELAY_MAX_PREFETCH * proto->tcpbufsiz)
812 bufferevent_disable(cre->bev, EV_READ);
814 if (EVBUFFER_LENGTH(src))
815 bev->readcb(bev, arg);
816 /* The callback readcb() might have freed the session. */
817 return;
819 done:
820 relay_close(con, "last http chunk read (done)", 0);
821 return;
822 fail:
823 relay_close(con, strerror(errno), 1);
826 void
827 relay_reset_http(struct ctl_relay_event *cre)
829 struct http_descriptor *desc = cre->desc;
831 relay_httpdesc_free(desc);
832 desc->http_method = 0;
833 desc->http_chunked = 0;
834 desc->http_cl = NULL;
835 cre->headerlen = 0;
836 cre->line = 0;
837 cre->done = 0;
840 static int
841 _relay_lookup_url(struct ctl_relay_event *cre, char *host, char *path,
842 char *query, struct kv *kv)
844 struct rsession *con = cre->con;
845 char *val, *md = NULL;
846 int ret = RES_FAIL;
847 const char *str = NULL;
849 if (asprintf(&val, "%s%s%s%s",
850 host, path,
851 query == NULL ? "" : "?",
852 query == NULL ? "" : query) == -1) {
853 relay_abort_http(con, 500, "failed to allocate URL", 0);
854 return (RES_FAIL);
857 switch (kv->kv_digest) {
858 case DIGEST_SHA1:
859 case DIGEST_MD5:
860 if ((md = digeststr(kv->kv_digest,
861 val, strlen(val), NULL)) == NULL) {
862 relay_abort_http(con, 500,
863 "failed to allocate digest", 0);
864 goto fail;
866 str = md;
867 break;
868 case DIGEST_NONE:
869 str = val;
870 break;
873 DPRINTF("%s: session %d: %s, %s: %d", __func__, con->se_id,
874 str, kv->kv_key, strcasecmp(kv->kv_key, str));
876 if (strcasecmp(kv->kv_key, str) == 0) {
877 ret = RES_DROP;
878 goto fail;
881 ret = RES_PASS;
882 fail:
883 free(md);
884 free(val);
885 return (ret);
888 int
889 relay_lookup_url(struct ctl_relay_event *cre, const char *host, struct kv *kv)
891 struct http_descriptor *desc = (struct http_descriptor *)cre->desc;
892 int i, j, dots;
893 char *hi[RELAY_MAXLOOKUPLEVELS], *p, *pp, *c, ch;
894 char ph[HOST_NAME_MAX+1];
895 int ret;
897 if (desc->http_path == NULL)
898 return (RES_PASS);
900 /*
901 * This is an URL lookup algorithm inspired by
902 * http://code.google.com/apis/safebrowsing/
903 * developers_guide.html#PerformingLookups
904 */
906 DPRINTF("%s: host '%s', path '%s', query '%s'",
907 __func__, host, desc->http_path,
908 desc->http_query == NULL ? "" : desc->http_query);
910 if (canonicalize_host(host, ph, sizeof(ph)) == NULL) {
911 return (RES_BAD);
914 bzero(hi, sizeof(hi));
915 for (dots = -1, i = strlen(ph) - 1; i > 0; i--) {
916 if (ph[i] == '.' && ++dots)
917 hi[dots - 1] = &ph[i + 1];
918 if (dots > (RELAY_MAXLOOKUPLEVELS - 2))
919 break;
921 if (dots == -1)
922 dots = 0;
923 hi[dots] = ph;
925 if ((pp = strdup(desc->http_path)) == NULL) {
926 return (RES_INTERNAL);
928 for (i = (RELAY_MAXLOOKUPLEVELS - 1); i >= 0; i--) {
929 if (hi[i] == NULL)
930 continue;
932 /* 1. complete path with query */
933 if (desc->http_query != NULL)
934 if ((ret = _relay_lookup_url(cre, hi[i],
935 pp, desc->http_query, kv)) != RES_PASS)
936 goto done;
938 /* 2. complete path without query */
939 if ((ret = _relay_lookup_url(cre, hi[i],
940 pp, NULL, kv)) != RES_PASS)
941 goto done;
943 /* 3. traverse path */
944 for (j = 0, p = strchr(pp, '/');
945 p != NULL; p = strchr(p, '/'), j++) {
946 if (j > (RELAY_MAXLOOKUPLEVELS - 2) || *(++p) == '\0')
947 break;
948 c = &pp[p - pp];
949 ch = *c;
950 *c = '\0';
951 if ((ret = _relay_lookup_url(cre, hi[i],
952 pp, NULL, kv)) != RES_PASS)
953 goto done;
954 *c = ch;
958 ret = RES_PASS;
959 done:
960 free(pp);
961 return (ret);
964 int
965 relay_lookup_cookie(struct ctl_relay_event *cre, const char *str,
966 struct kv *kv)
968 char *val, *ptr, *key, *value;
969 int ret;
971 if ((val = strdup(str)) == NULL) {
972 return (RES_INTERNAL);
975 for (ptr = val; ptr != NULL && strlen(ptr);) {
976 if (*ptr == ' ')
977 *ptr++ = '\0';
978 key = ptr;
979 if ((ptr = strchr(ptr, ';')) != NULL)
980 *ptr++ = '\0';
981 /*
982 * XXX We do not handle attributes
983 * ($Path, $Domain, or $Port)
984 */
985 if (*key == '$')
986 continue;
988 if ((value =
989 strchr(key, '=')) == NULL ||
990 strlen(value) < 1)
991 continue;
992 *value++ = '\0';
993 if (*value == '"')
994 *value++ = '\0';
995 if (value[strlen(value) - 1] == '"')
996 value[strlen(value) - 1] = '\0';
998 DPRINTF("%s: key %s = %s, %s = %s : %d",
999 __func__, key, value, kv->kv_key, kv->kv_value,
1000 strcasecmp(kv->kv_key, key));
1002 if (strcasecmp(kv->kv_key, key) == 0 &&
1003 ((kv->kv_value == NULL) ||
1004 (fnmatch(kv->kv_value, value,
1005 FNM_CASEFOLD) != FNM_NOMATCH))) {
1006 ret = RES_DROP;
1007 goto done;
1011 ret = RES_PASS;
1013 done:
1014 free(val);
1015 return (ret);
1018 int
1019 relay_lookup_query(struct ctl_relay_event *cre, struct kv *kv)
1021 struct http_descriptor *desc = cre->desc;
1022 struct kv *match = &desc->http_matchquery;
1023 char *val, *ptr, *tmpkey = NULL, *tmpval = NULL;
1024 int ret = -1;
1026 if (desc->http_query == NULL)
1027 return (-1);
1028 if ((val = strdup(desc->http_query)) == NULL) {
1029 return (RES_INTERNAL);
1032 ptr = val;
1033 while (ptr != NULL && strlen(ptr)) {
1034 tmpkey = ptr;
1035 if ((ptr = strchr(ptr, '&')) != NULL)
1036 *ptr++ = '\0';
1037 if ((tmpval = strchr(tmpkey, '=')) == NULL || strlen(tmpval)
1038 < 1)
1039 continue;
1040 *tmpval++ = '\0';
1042 if (fnmatch(kv->kv_key, tmpkey, 0) != FNM_NOMATCH &&
1043 (kv->kv_value == NULL || fnmatch(kv->kv_value, tmpval, 0)
1044 != FNM_NOMATCH))
1045 break;
1046 else
1047 tmpkey = NULL;
1050 if (tmpkey == NULL || tmpval == NULL)
1051 goto done;
1053 match->kv_key = strdup(tmpkey);
1054 if (match->kv_key == NULL)
1055 goto done;
1056 match->kv_value = strdup(tmpval);
1057 if (match->kv_value == NULL)
1058 goto done;
1059 ret = 0;
1061 done:
1062 free(val);
1063 return (ret);
1066 ssize_t
1067 relay_http_time(time_t t, char *tmbuf, size_t len)
1069 struct tm tm;
1071 /* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */
1072 if (t == -1 || gmtime_r(&t, &tm) == NULL)
1073 return (-1);
1074 else
1075 return (strftime(tmbuf, len, "%a, %d %h %Y %T GMT", &tm));
1078 void
1079 relay_abort_http(struct rsession *con, u_int code, const char *msg,
1080 u_int16_t labelid)
1082 struct relay *rlay = con->se_relay;
1083 struct bufferevent *bev = con->se_in.bev;
1084 const char *httperr = NULL, *text = "";
1085 char *httpmsg, *body = NULL;
1086 char tmbuf[32], hbuf[128];
1087 const char *style, *label = NULL;
1088 int bodylen;
1090 if ((httperr = relay_httperror_byid(code)) == NULL)
1091 httperr = "Unknown Error";
1093 if (labelid != 0)
1094 label = label_id2name(labelid);
1096 /* In some cases this function may be called from generic places */
1097 if (rlay->rl_proto->type != RELAY_PROTO_HTTP ||
1098 (rlay->rl_proto->flags & F_RETURN) == 0) {
1099 relay_close(con, msg, 0);
1100 return;
1103 if (bev == NULL)
1104 goto done;
1106 /* Some system information */
1107 if (print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) == NULL)
1108 goto done;
1110 if (relay_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0)
1111 goto done;
1113 /* Do not send details of the Internal Server Error */
1114 switch (code) {
1115 case 500:
1116 break;
1117 default:
1118 text = msg;
1119 break;
1122 /* A CSS stylesheet allows minimal customization by the user */
1123 style = (rlay->rl_proto->style != NULL) ? rlay->rl_proto->style :
1124 "body { background-color: #a00000; color: white; font-family: "
1125 "'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }\n"
1126 "hr { border: 0; border-bottom: 1px dashed; }\n";
1128 /* Generate simple HTTP+HTML error document */
1129 if ((bodylen = asprintf(&body,
1130 "<!DOCTYPE html>\n"
1131 "<html>\n"
1132 "<head>\n"
1133 "<title>%03d %s</title>\n"
1134 "<style type=\"text/css\"><!--\n%s\n--></style>\n"
1135 "</head>\n"
1136 "<body>\n"
1137 "<h1>%s</h1>\n"
1138 "<div id='m'>%s</div>\n"
1139 "<div id='l'>%s</div>\n"
1140 "<hr><address>%s at %s port %d</address>\n"
1141 "</body>\n"
1142 "</html>\n",
1143 code, httperr, style, httperr, text,
1144 label == NULL ? "" : label,
1145 RELAYD_SERVERNAME, hbuf, ntohs(rlay->rl_conf.port))) == -1)
1146 goto done;
1148 /* Generate simple HTTP+HTML error document */
1149 if (asprintf(&httpmsg,
1150 "HTTP/1.0 %03d %s\r\n"
1151 "Date: %s\r\n"
1152 "Server: %s\r\n"
1153 "Connection: close\r\n"
1154 "Content-Type: text/html\r\n"
1155 "Content-Length: %d\r\n"
1156 "\r\n"
1157 "%s",
1158 code, httperr, tmbuf, RELAYD_SERVERNAME, bodylen, body) == -1)
1159 goto done;
1161 /* Dump the message without checking for success */
1162 relay_dump(&con->se_in, httpmsg, strlen(httpmsg));
1163 free(httpmsg);
1165 done:
1166 free(body);
1167 if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1)
1168 relay_close(con, msg, 1);
1169 else {
1170 relay_close(con, httpmsg, 1);
1171 free(httpmsg);
1175 void
1176 relay_close_http(struct rsession *con)
1178 struct http_session *hs = con->se_priv;
1179 struct http_method_node *hmn;
1181 DPRINTF("%s: session %d http_session %p", __func__,
1182 con->se_id, hs);
1183 if (hs != NULL)
1184 while (!SIMPLEQ_EMPTY(&hs->hs_methods)) {
1185 hmn = SIMPLEQ_FIRST(&hs->hs_methods);
1186 SIMPLEQ_REMOVE_HEAD(&hs->hs_methods, hmn_entry);
1187 DPRINTF("%s: session %d freeing %s", __func__,
1188 con->se_id, relay_httpmethod_byid(hmn->hmn_method));
1189 free(hmn);
1191 relay_httpdesc_free(con->se_in.desc);
1192 free(con->se_in.desc);
1193 relay_httpdesc_free(con->se_out.desc);
1194 free(con->se_out.desc);
1197 char *
1198 relay_expand_http(struct ctl_relay_event *cre, char *val, char *buf,
1199 size_t len)
1201 struct rsession *con = cre->con;
1202 struct relay *rlay = con->se_relay;
1203 struct http_descriptor *desc = cre->desc;
1204 struct kv *host, key;
1205 char ibuf[128];
1207 if (strlcpy(buf, val, len) >= len)
1208 return (NULL);
1210 if (strstr(val, "$HOST") != NULL) {
1211 key.kv_key = "Host";
1212 host = kv_find(&desc->http_headers, &key);
1213 if (host) {
1214 if (host->kv_value == NULL)
1215 return (NULL);
1216 snprintf(ibuf, sizeof(ibuf), "%s", host->kv_value);
1217 } else {
1218 if (print_host(&rlay->rl_conf.ss,
1219 ibuf, sizeof(ibuf)) == NULL)
1220 return (NULL);
1222 if (expand_string(buf, len, "$HOST", ibuf))
1223 return (NULL);
1225 if (strstr(val, "$REMOTE_") != NULL) {
1226 if (strstr(val, "$REMOTE_ADDR") != NULL) {
1227 if (print_host(&cre->ss, ibuf, sizeof(ibuf)) == NULL)
1228 return (NULL);
1229 if (expand_string(buf, len,
1230 "$REMOTE_ADDR", ibuf) != 0)
1231 return (NULL);
1233 if (strstr(val, "$REMOTE_PORT") != NULL) {
1234 snprintf(ibuf, sizeof(ibuf), "%u", ntohs(cre->port));
1235 if (expand_string(buf, len,
1236 "$REMOTE_PORT", ibuf) != 0)
1237 return (NULL);
1240 if (strstr(val, "$SERVER_") != NULL) {
1241 if (strstr(val, "$SERVER_ADDR") != NULL) {
1242 if (print_host(&rlay->rl_conf.ss,
1243 ibuf, sizeof(ibuf)) == NULL)
1244 return (NULL);
1245 if (expand_string(buf, len,
1246 "$SERVER_ADDR", ibuf) != 0)
1247 return (NULL);
1249 if (strstr(val, "$SERVER_PORT") != NULL) {
1250 snprintf(ibuf, sizeof(ibuf), "%u",
1251 ntohs(rlay->rl_conf.port));
1252 if (expand_string(buf, len,
1253 "$SERVER_PORT", ibuf) != 0)
1254 return (NULL);
1256 if (strstr(val, "$SERVER_NAME") != NULL) {
1257 if (expand_string(buf, len,
1258 "$SERVER_NAME", RELAYD_SERVERNAME) != 0)
1259 return (NULL);
1262 if (strstr(val, "$TIMEOUT") != NULL) {
1263 snprintf(ibuf, sizeof(ibuf), "%lld",
1264 (long long)rlay->rl_conf.timeout.tv_sec);
1265 if (expand_string(buf, len, "$TIMEOUT", ibuf) != 0)
1266 return (NULL);
1269 return (buf);
1272 int
1273 relay_writerequest_http(struct ctl_relay_event *dst,
1274 struct ctl_relay_event *cre)
1276 struct http_descriptor *desc = (struct http_descriptor *)cre->desc;
1277 const char *name = NULL;
1279 if ((name = relay_httpmethod_byid(desc->http_method)) == NULL)
1280 return (-1);
1282 if (relay_bufferevent_print(dst, name) == -1 ||
1283 relay_bufferevent_print(dst, " ") == -1 ||
1284 relay_bufferevent_print(dst, desc->http_path) == -1 ||
1285 (desc->http_query != NULL &&
1286 (relay_bufferevent_print(dst, "?") == -1 ||
1287 relay_bufferevent_print(dst, desc->http_query) == -1)) ||
1288 relay_bufferevent_print(dst, " ") == -1 ||
1289 relay_bufferevent_print(dst, desc->http_version) == -1)
1290 return (-1);
1292 return (0);
1295 int
1296 relay_writeresponse_http(struct ctl_relay_event *dst,
1297 struct ctl_relay_event *cre)
1299 struct http_descriptor *desc = (struct http_descriptor *)cre->desc;
1301 DPRINTF("version: %s rescode: %s resmsg: %s", desc->http_version,
1302 desc->http_rescode, desc->http_resmesg);
1304 if (relay_bufferevent_print(dst, desc->http_version) == -1 ||
1305 relay_bufferevent_print(dst, " ") == -1 ||
1306 relay_bufferevent_print(dst, desc->http_rescode) == -1 ||
1307 relay_bufferevent_print(dst, " ") == -1 ||
1308 relay_bufferevent_print(dst, desc->http_resmesg) == -1)
1309 return (-1);
1311 return (0);
1314 int
1315 relay_writeheader_kv(struct ctl_relay_event *dst, struct kv *hdr)
1317 char *ptr;
1318 const char *key;
1320 if (hdr->kv_flags & KV_FLAG_INVALID)
1321 return (0);
1323 /* The key might have been updated in the parent */
1324 if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL)
1325 key = hdr->kv_parent->kv_key;
1326 else
1327 key = hdr->kv_key;
1329 ptr = hdr->kv_value;
1330 if (relay_bufferevent_print(dst, key) == -1 ||
1331 (ptr != NULL &&
1332 (relay_bufferevent_print(dst, ": ") == -1 ||
1333 relay_bufferevent_print(dst, ptr) == -1 ||
1334 relay_bufferevent_print(dst, "\r\n") == -1)))
1335 return (-1);
1336 DPRINTF("%s: %s: %s", __func__, key,
1337 hdr->kv_value == NULL ? "" : hdr->kv_value);
1339 return (0);
1342 int
1343 relay_writeheader_http(struct ctl_relay_event *dst, struct ctl_relay_event
1344 *cre)
1346 struct kv *hdr, *kv;
1347 struct http_descriptor *desc = (struct http_descriptor *)cre->desc;
1349 RB_FOREACH(hdr, kvtree, &desc->http_headers) {
1350 if (relay_writeheader_kv(dst, hdr) == -1)
1351 return (-1);
1352 TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) {
1353 if (relay_writeheader_kv(dst, kv) == -1)
1354 return (-1);
1358 return (0);
1361 enum httpmethod
1362 relay_httpmethod_byname(const char *name)
1364 enum httpmethod id = HTTP_METHOD_NONE;
1365 struct http_method method, *res = NULL;
1367 /* Set up key */
1368 method.method_name = name;
1370 if ((res = bsearch(&method, http_methods,
1371 sizeof(http_methods) / sizeof(http_methods[0]) - 1,
1372 sizeof(http_methods[0]), relay_httpmethod_cmp)) != NULL)
1373 id = res->method_id;
1375 return (id);
1378 const char *
1379 relay_httpmethod_byid(u_int id)
1381 const char *name = NULL;
1382 int i;
1384 for (i = 0; http_methods[i].method_name != NULL; i++) {
1385 if (http_methods[i].method_id == id) {
1386 name = http_methods[i].method_name;
1387 break;
1391 return (name);
1394 static int
1395 relay_httpmethod_cmp(const void *a, const void *b)
1397 const struct http_method *ma = a;
1398 const struct http_method *mb = b;
1401 * RFC 2616 section 5.1.1 says that the method is case
1402 * sensitive so we don't do a strcasecmp here.
1404 return (strcmp(ma->method_name, mb->method_name));
1407 const char *
1408 relay_httperror_byid(u_int id)
1410 struct http_error error, *res = NULL;
1412 /* Set up key */
1413 error.error_code = (int)id;
1415 if ((res = bsearch(&error, http_errors,
1416 sizeof(http_errors) / sizeof(http_errors[0]) - 1,
1417 sizeof(http_errors[0]), relay_httperror_cmp)) != NULL)
1418 return (res->error_name);
1420 return (NULL);
1423 static int
1424 relay_httperror_cmp(const void *a, const void *b)
1426 const struct http_error *ea = a;
1427 const struct http_error *eb = b;
1428 return (ea->error_code - eb->error_code);
1431 int
1432 relay_httpquery_test(struct ctl_relay_event *cre, struct relay_rule *rule,
1433 struct kvlist *actions)
1435 struct http_descriptor *desc = cre->desc;
1436 struct kv *match = &desc->http_matchquery;
1437 struct kv *kv = &rule->rule_kv[KEY_TYPE_QUERY];
1438 int res = 0;
1440 if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_QUERY)
1441 return (0);
1442 else if (kv->kv_key == NULL)
1443 return (0);
1444 else if ((res = relay_lookup_query(cre, kv)) != 0)
1445 return (res);
1447 relay_match(actions, kv, match, NULL);
1449 return (0);
1452 int
1453 relay_httpheader_test(struct ctl_relay_event *cre, struct relay_rule *rule,
1454 struct kvlist *actions)
1456 struct http_descriptor *desc = cre->desc;
1457 struct kv *kv = &rule->rule_kv[KEY_TYPE_HEADER];
1458 struct kv *match;
1460 if (kv->kv_type != KEY_TYPE_HEADER)
1461 return (0);
1463 match = kv_find(&desc->http_headers, kv);
1465 if (kv->kv_option == KEY_OPTION_APPEND ||
1466 kv->kv_option == KEY_OPTION_SET) {
1467 /* header can be NULL and will be added later */
1468 } else if (match == NULL) {
1469 /* Fail if header doesn't exist */
1470 return (-1);
1471 } else {
1472 if (fnmatch(kv->kv_key, match->kv_key,
1473 FNM_CASEFOLD) == FNM_NOMATCH)
1474 return (-1);
1475 if (kv->kv_value != NULL &&
1476 match->kv_value != NULL &&
1477 fnmatch(kv->kv_value, match->kv_value, 0) == FNM_NOMATCH)
1478 return (-1);
1481 relay_match(actions, kv, match, &desc->http_headers);
1483 return (0);
1486 int
1487 relay_httppath_test(struct ctl_relay_event *cre, struct relay_rule *rule,
1488 struct kvlist *actions)
1490 struct http_descriptor *desc = cre->desc;
1491 struct kv *kv = &rule->rule_kv[KEY_TYPE_PATH];
1492 struct kv *match = &desc->http_pathquery;
1493 const char *query;
1495 if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_PATH)
1496 return (0);
1497 else if (kv->kv_option != KEY_OPTION_STRIP) {
1498 if (kv->kv_key == NULL)
1499 return (0);
1500 else if (fnmatch(kv->kv_key, desc->http_path, 0) == FNM_NOMATCH)
1501 return (-1);
1502 else if (kv->kv_value != NULL && kv->kv_option == KEY_OPTION_NONE) {
1503 query = desc->http_query == NULL ? "" : desc->http_query;
1504 if (fnmatch(kv->kv_value, query, FNM_CASEFOLD) == FNM_NOMATCH)
1505 return (-1);
1509 relay_match(actions, kv, match, NULL);
1511 return (0);
1514 int
1515 relay_httpurl_test(struct ctl_relay_event *cre, struct relay_rule *rule,
1516 struct kvlist *actions)
1518 struct http_descriptor *desc = cre->desc;
1519 struct kv *host, key;
1520 struct kv *kv = &rule->rule_kv[KEY_TYPE_URL];
1521 struct kv *match = &desc->http_pathquery;
1522 int res;
1524 if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_URL ||
1525 kv->kv_key == NULL)
1526 return (0);
1528 key.kv_key = "Host";
1529 host = kv_find(&desc->http_headers, &key);
1531 if (host == NULL || host->kv_value == NULL)
1532 return (0);
1533 else if (rule->rule_action != RULE_ACTION_BLOCK &&
1534 kv->kv_option == KEY_OPTION_LOG &&
1535 fnmatch(kv->kv_key, match->kv_key, FNM_CASEFOLD) != FNM_NOMATCH) {
1536 /* fnmatch url only for logging */
1537 } else if ((res = relay_lookup_url(cre, host->kv_value, kv)) != 0)
1538 return (res);
1539 relay_match(actions, kv, match, NULL);
1541 return (0);
1544 int
1545 relay_httpcookie_test(struct ctl_relay_event *cre, struct relay_rule *rule,
1546 struct kvlist *actions)
1548 struct http_descriptor *desc = cre->desc;
1549 struct kv *kv = &rule->rule_kv[KEY_TYPE_COOKIE], key;
1550 struct kv *match = NULL;
1551 int res;
1553 if (kv->kv_type != KEY_TYPE_COOKIE)
1554 return (0);
1556 switch (cre->dir) {
1557 case RELAY_DIR_REQUEST:
1558 key.kv_key = "Cookie";
1559 break;
1560 case RELAY_DIR_RESPONSE:
1561 key.kv_key = "Set-Cookie";
1562 break;
1563 default:
1564 return (0);
1565 /* NOTREACHED */
1566 break;
1569 if (kv->kv_option == KEY_OPTION_APPEND ||
1570 kv->kv_option == KEY_OPTION_SET) {
1571 /* no cookie, can be NULL and will be added later */
1572 } else {
1573 match = kv_find(&desc->http_headers, &key);
1574 if (match == NULL)
1575 return (-1);
1576 if (kv->kv_key == NULL || match->kv_value == NULL)
1577 return (0);
1578 else if ((res = relay_lookup_cookie(cre, match->kv_value,
1579 kv)) != 0)
1580 return (res);
1583 relay_match(actions, kv, match, &desc->http_headers);
1585 return (0);
1588 int
1589 relay_match_actions(struct ctl_relay_event *cre, struct relay_rule *rule,
1590 struct kvlist *matches, struct kvlist *actions, struct relay_table **tbl)
1592 struct rsession *con = cre->con;
1593 struct kv *kv;
1596 * Apply the following options instantly (action per match).
1598 if (rule->rule_table != NULL) {
1599 *tbl = rule->rule_table;
1600 con->se_out.ss.ss_family = AF_UNSPEC;
1602 if (rule->rule_tag != 0)
1603 con->se_tag = rule->rule_tag == -1 ? 0 : rule->rule_tag;
1604 if (rule->rule_label != 0)
1605 con->se_label = rule->rule_label == -1 ? 0 : rule->rule_label;
1608 * Apply the remaining options once after evaluation.
1610 if (matches == NULL) {
1611 /* 'pass' or 'block' rule */
1612 TAILQ_CONCAT(actions, &rule->rule_kvlist, kv_rule_entry);
1613 } else {
1614 /* 'match' rule */
1615 TAILQ_FOREACH(kv, matches, kv_match_entry) {
1616 TAILQ_INSERT_TAIL(actions, kv, kv_action_entry);
1620 return (0);
1623 int
1624 relay_apply_actions(struct ctl_relay_event *cre, struct kvlist *actions,
1625 struct relay_table *tbl)
1627 struct rsession *con = cre->con;
1628 struct http_descriptor *desc = cre->desc;
1629 struct kv *host = NULL;
1630 const char *value;
1631 struct kv *kv, *match, *kp, *mp, kvcopy, matchcopy, key;
1632 int addkv, ret, nstrip;
1633 char buf[IBUF_READ_SIZE], *ptr;
1634 char *msg = NULL;
1635 const char *meth = NULL;
1637 memset(&kvcopy, 0, sizeof(kvcopy));
1638 memset(&matchcopy, 0, sizeof(matchcopy));
1640 ret = -1;
1641 kp = mp = NULL;
1642 TAILQ_FOREACH(kv, actions, kv_action_entry) {
1643 kp = NULL;
1644 match = kv->kv_match;
1645 addkv = 0;
1648 * Although marked as deleted, give a chance to non-critical
1649 * actions, ie. log, to be performed
1651 if (match != NULL && (match->kv_flags & KV_FLAG_INVALID))
1652 goto matchdel;
1654 switch (kv->kv_option) {
1655 case KEY_OPTION_APPEND:
1656 case KEY_OPTION_SET:
1657 switch (kv->kv_type) {
1658 case KEY_TYPE_PATH:
1659 if (kv->kv_option == KEY_OPTION_APPEND) {
1660 if (kv_setkey(match, "%s%s",
1661 match->kv_key, kv->kv_key) == -1)
1662 goto fail;
1663 } else {
1664 if (kv_setkey(match, "%s",
1665 kv->kv_value) == -1)
1666 goto fail;
1668 break;
1669 case KEY_TYPE_COOKIE:
1670 kp = &kvcopy;
1671 if (kv_inherit(kp, kv) == NULL)
1672 goto fail;
1673 if (kv_set(kp, "%s=%s;", kp->kv_key,
1674 kp->kv_value) == -1)
1675 goto fail;
1676 if (kv_setkey(kp, "%s", cre->dir ==
1677 RELAY_DIR_REQUEST ?
1678 "Cookie" : "Set-Cookie") == -1)
1679 goto fail;
1680 /* FALLTHROUGH cookie is a header */
1681 case KEY_TYPE_HEADER:
1682 if (match == NULL) {
1683 addkv = 1;
1684 break;
1686 if (match->kv_value == NULL ||
1687 kv->kv_option == KEY_OPTION_SET) {
1688 if (kv_set(match, "%s",
1689 kv->kv_value) == -1)
1690 goto fail;
1691 } else
1692 addkv = 1;
1693 break;
1694 default:
1695 /* query, url not supported */
1696 break;
1698 break;
1699 case KEY_OPTION_REMOVE:
1700 switch (kv->kv_type) {
1701 case KEY_TYPE_PATH:
1702 if (kv_setkey(match, "/") == -1)
1703 goto fail;
1704 break;
1705 case KEY_TYPE_COOKIE:
1706 case KEY_TYPE_HEADER:
1707 if (kv->kv_matchtree != NULL)
1708 match->kv_flags |= KV_FLAG_INVALID;
1709 else
1710 kv_free(match);
1711 match = kv->kv_match = NULL;
1712 break;
1713 default:
1714 /* query and url not supported */
1715 break;
1717 break;
1718 case KEY_OPTION_HASH:
1719 switch (kv->kv_type) {
1720 case KEY_TYPE_PATH:
1721 value = match->kv_key;
1722 break;
1723 default:
1724 value = match->kv_value;
1725 break;
1727 SipHash24_Update(&con->se_siphashctx,
1728 value, strlen(value));
1729 break;
1730 case KEY_OPTION_LOG:
1731 /* perform this later */
1732 break;
1733 case KEY_OPTION_STRIP:
1734 nstrip = strtonum(kv->kv_value, 0, INT_MAX, NULL);
1735 if (kv->kv_type == KEY_TYPE_PATH) {
1736 if (kv_setkey(match, "%s",
1737 server_root_strip(match->kv_key,
1738 nstrip)) == -1)
1739 goto fail;
1741 break;
1742 default:
1743 fatalx("%s: invalid action", __func__);
1744 /* NOTREACHED */
1747 /* from now on, reads from kp writes to kv */
1748 if (kp == NULL)
1749 kp = kv;
1750 if (addkv && kv->kv_matchtree != NULL) {
1751 /* Add new entry to the list (eg. new HTTP header) */
1752 if ((match = kv_add(kv->kv_matchtree, kp->kv_key,
1753 kp->kv_value, 0)) == NULL)
1754 goto fail;
1755 match->kv_option = kp->kv_option;
1756 match->kv_type = kp->kv_type;
1757 kv->kv_match = match;
1759 if (match != NULL && kp->kv_flags & KV_FLAG_MACRO) {
1760 bzero(buf, sizeof(buf));
1761 if ((ptr = relay_expand_http(cre, kp->kv_value, buf,
1762 sizeof(buf))) == NULL)
1763 goto fail;
1764 if (kv_set(match, "%s", ptr) == -1)
1765 goto fail;
1768 matchdel:
1769 switch (kv->kv_option) {
1770 case KEY_OPTION_LOG:
1771 if (match == NULL)
1772 break;
1773 mp = &matchcopy;
1774 if (kv_inherit(mp, match) == NULL)
1775 goto fail;
1776 if (mp->kv_flags & KV_FLAG_INVALID) {
1777 if (kv_set(mp, "%s (removed)",
1778 mp->kv_value) == -1)
1779 goto fail;
1781 switch (kv->kv_type) {
1782 case KEY_TYPE_URL:
1783 key.kv_key = "Host";
1784 host = kv_find(&desc->http_headers, &key);
1785 switch (kv->kv_digest) {
1786 case DIGEST_NONE:
1787 if (host == NULL ||
1788 host->kv_value == NULL)
1789 break;
1790 if (kv_setkey(mp, "%s%s",
1791 host->kv_value, mp->kv_key) ==
1792 -1)
1793 goto fail;
1794 break;
1795 default:
1796 if (kv_setkey(mp, "%s", kv->kv_key)
1797 == -1)
1798 goto fail;
1799 break;
1801 break;
1802 default:
1803 break;
1805 if (kv_log(con, mp, con->se_label, cre->dir)
1806 == -1)
1807 goto fail;
1808 break;
1809 default:
1810 break;
1813 /* actions applied, cleanup kv */
1814 kv->kv_match = NULL;
1815 kv->kv_matchtree = NULL;
1816 TAILQ_REMOVE(actions, kv, kv_match_entry);
1818 kv_free(&kvcopy);
1819 kv_free(&matchcopy);
1823 * Change the backend if the forward table has been changed.
1824 * This only works in the request direction.
1826 if (cre->dir == RELAY_DIR_REQUEST && con->se_table != tbl) {
1827 relay_reset_event(con, &con->se_out);
1828 con->se_table = tbl;
1829 con->se_haslog = 1;
1833 * log tag for request and response, request method
1834 * and end of request marker ","
1836 if ((con->se_log != NULL) &&
1837 ((meth = relay_httpmethod_byid(desc->http_method)) != NULL) &&
1838 (asprintf(&msg, " %s", meth) != -1))
1839 evbuffer_add(con->se_log, msg, strlen(msg));
1840 free(msg);
1841 relay_log(con, cre->dir == RELAY_DIR_REQUEST ? "" : ";");
1842 ret = 0;
1843 fail:
1844 kv_free(&kvcopy);
1845 kv_free(&matchcopy);
1847 return (ret);
1850 #define RELAY_GET_SKIP_STEP(i) \
1851 do { \
1852 r = r->rule_skip[i]; \
1853 DPRINTF("%s:%d: skip %d rules", __func__, __LINE__, i); \
1854 } while (0)
1856 #define RELAY_GET_NEXT_STEP \
1857 do { \
1858 DPRINTF("%s:%d: next rule", __func__, __LINE__); \
1859 goto nextrule; \
1860 } while (0)
1862 int
1863 relay_test(struct protocol *proto, struct ctl_relay_event *cre)
1865 struct rsession *con;
1866 struct http_descriptor *desc = cre->desc;
1867 struct relay_rule *r = NULL, *rule = NULL;
1868 struct relay_table *tbl = NULL;
1869 u_int action = RES_PASS;
1870 struct kvlist actions, matches;
1871 struct kv *kv;
1872 int res = 0;
1874 con = cre->con;
1875 TAILQ_INIT(&actions);
1877 r = TAILQ_FIRST(&proto->rules);
1878 while (r != NULL) {
1879 TAILQ_INIT(&matches);
1880 TAILQ_INIT(&r->rule_kvlist);
1882 if (r->rule_dir && r->rule_dir != cre->dir)
1883 RELAY_GET_SKIP_STEP(RULE_SKIP_DIR);
1884 else if (proto->type != r->rule_proto)
1885 RELAY_GET_SKIP_STEP(RULE_SKIP_PROTO);
1886 else if (RELAY_AF_NEQ(r->rule_af, cre->ss.ss_family) ||
1887 RELAY_AF_NEQ(r->rule_af, cre->dst->ss.ss_family))
1888 RELAY_GET_SKIP_STEP(RULE_SKIP_AF);
1889 else if (RELAY_ADDR_CMP(&r->rule_src, &cre->ss) != 0)
1890 RELAY_GET_SKIP_STEP(RULE_SKIP_SRC);
1891 else if (RELAY_ADDR_CMP(&r->rule_dst, &con->se_sockname) != 0)
1892 RELAY_GET_SKIP_STEP(RULE_SKIP_DST);
1893 else if (r->rule_method != HTTP_METHOD_NONE &&
1894 (desc->http_method == HTTP_METHOD_RESPONSE ||
1895 desc->http_method != r->rule_method))
1896 RELAY_GET_SKIP_STEP(RULE_SKIP_METHOD);
1897 else if (r->rule_tagged && con->se_tag != r->rule_tagged)
1898 RELAY_GET_NEXT_STEP;
1899 else if (relay_httpheader_test(cre, r, &matches) != 0)
1900 RELAY_GET_NEXT_STEP;
1901 else if ((res = relay_httpquery_test(cre, r, &matches)) != 0)
1902 RELAY_GET_NEXT_STEP;
1903 else if (relay_httppath_test(cre, r, &matches) != 0)
1904 RELAY_GET_NEXT_STEP;
1905 else if ((res = relay_httpurl_test(cre, r, &matches)) != 0)
1906 RELAY_GET_NEXT_STEP;
1907 else if ((res = relay_httpcookie_test(cre, r, &matches)) != 0)
1908 RELAY_GET_NEXT_STEP;
1909 else {
1910 DPRINTF("%s: session %d: matched rule %d",
1911 __func__, con->se_id, r->rule_id);
1913 if (r->rule_action == RULE_ACTION_MATCH) {
1914 if (relay_match_actions(cre, r, &matches,
1915 &actions, &tbl) != 0) {
1916 /* Something bad happened, drop */
1917 action = RES_DROP;
1918 break;
1920 RELAY_GET_NEXT_STEP;
1921 } else if (r->rule_action == RULE_ACTION_BLOCK)
1922 action = RES_DROP;
1923 else if (r->rule_action == RULE_ACTION_PASS)
1924 action = RES_PASS;
1926 /* Rule matched */
1927 rule = r;
1929 /* Temporarily save actions */
1930 TAILQ_FOREACH(kv, &matches, kv_match_entry) {
1931 TAILQ_INSERT_TAIL(&rule->rule_kvlist,
1932 kv, kv_rule_entry);
1935 if (rule->rule_flags & RULE_FLAG_QUICK)
1936 break;
1938 nextrule:
1939 /* Continue to find last matching policy */
1940 DPRINTF("%s: session %d, res %d", __func__,
1941 con->se_id, res);
1942 if (res == RES_BAD || res == RES_INTERNAL)
1943 return (res);
1944 res = 0;
1945 r = TAILQ_NEXT(r, rule_entry);
1949 if (rule != NULL && relay_match_actions(cre, rule, NULL, &actions, &tbl)
1950 != 0) {
1951 /* Something bad happened, drop */
1952 action = RES_DROP;
1955 if (relay_apply_actions(cre, &actions, tbl) != 0) {
1956 /* Something bad happened, drop */
1957 action = RES_DROP;
1960 DPRINTF("%s: session %d: action %d", __func__,
1961 con->se_id, action);
1963 return (action);
1966 #define RELAY_SET_SKIP_STEPS(i) \
1967 do { \
1968 while (head[i] != cur) { \
1969 head[i]->rule_skip[i] = cur; \
1970 head[i] = TAILQ_NEXT(head[i], rule_entry); \
1971 } \
1972 } while (0)
1974 /* This code is derived from pf_calc_skip_steps() from pf.c */
1975 void
1976 relay_calc_skip_steps(struct relay_rules *rules)
1978 struct relay_rule *head[RULE_SKIP_COUNT], *cur, *prev;
1979 int i;
1981 cur = TAILQ_FIRST(rules);
1982 prev = cur;
1983 for (i = 0; i < RULE_SKIP_COUNT; ++i)
1984 head[i] = cur;
1985 while (cur != NULL) {
1986 if (cur->rule_dir != prev->rule_dir)
1987 RELAY_SET_SKIP_STEPS(RULE_SKIP_DIR);
1988 else if (cur->rule_proto != prev->rule_proto)
1989 RELAY_SET_SKIP_STEPS(RULE_SKIP_PROTO);
1990 else if (RELAY_AF_NEQ(cur->rule_af, prev->rule_af))
1991 RELAY_SET_SKIP_STEPS(RULE_SKIP_AF);
1992 else if (RELAY_ADDR_NEQ(&cur->rule_src, &prev->rule_src))
1993 RELAY_SET_SKIP_STEPS(RULE_SKIP_SRC);
1994 else if (RELAY_ADDR_NEQ(&cur->rule_dst, &prev->rule_dst))
1995 RELAY_SET_SKIP_STEPS(RULE_SKIP_DST);
1996 else if (cur->rule_method != prev->rule_method)
1997 RELAY_SET_SKIP_STEPS(RULE_SKIP_METHOD);
1999 prev = cur;
2000 cur = TAILQ_NEXT(cur, rule_entry);
2002 for (i = 0; i < RULE_SKIP_COUNT; ++i)
2003 RELAY_SET_SKIP_STEPS(i);
2006 void
2007 relay_match(struct kvlist *actions, struct kv *kv, struct kv *match,
2008 struct kvtree *matchtree)
2010 if (kv->kv_option != KEY_OPTION_NONE) {
2011 kv->kv_match = match;
2012 kv->kv_matchtree = matchtree;
2013 TAILQ_INSERT_TAIL(actions, kv, kv_match_entry);
2017 char *
2018 server_root_strip(char *path, int n)
2020 char *p;
2022 /* Strip strip leading directories. Leading '/' is ignored. */
2023 for (; n > 0 && *path != '\0'; n--)
2024 if ((p = strchr(++path, '/')) != NULL)
2025 path = p;
2026 else
2027 path--;
2029 return (path);
2033 * Handle the first line of an HTTP message (Request- or Status-Line).
2035 int
2036 relay_http_parse_startline(struct ctl_relay_event *cre, char *line,
2037 enum httpmethod *request_method)
2039 struct rsession *con = cre->con;
2040 struct http_descriptor *desc = cre->desc;
2041 struct http_session *hs = con->se_priv;
2042 struct http_method_node *hmn;
2043 char *key, *value;
2044 const char *errstr;
2046 DPRINTF("%s: session %d http_session %p", __func__, con->se_id, hs);
2048 key = line;
2049 if ((value = strchr(key, ' ')) == NULL) {
2050 relay_abort_http(con, 400, "malformed", 0);
2051 return (-1);
2053 *value++ = '\0';
2055 if (cre->dir == RELAY_DIR_RESPONSE) {
2056 desc->http_method = HTTP_METHOD_RESPONSE;
2057 hmn = SIMPLEQ_FIRST(&hs->hs_methods);
2060 * There is nothing preventing the relay from
2061 * sending an unbalanced response. Be prepared.
2063 if (hmn == NULL) {
2064 *request_method = HTTP_METHOD_NONE;
2065 DPRINTF("%s: session %d unbalanced response",
2066 __func__, con->se_id);
2067 } else {
2068 SIMPLEQ_REMOVE_HEAD(&hs->hs_methods, hmn_entry);
2069 *request_method = hmn->hmn_method;
2070 DPRINTF("%s: session %d dequeuing %s",
2071 __func__, con->se_id,
2072 relay_httpmethod_byid(*request_method));
2073 free(hmn);
2077 * Decode response path and query
2079 desc->http_version = strdup(key);
2080 if (desc->http_version == NULL)
2081 goto fail;
2083 desc->http_rescode = strdup(value);
2084 if (desc->http_rescode == NULL)
2085 goto fail;
2087 desc->http_resmesg = strchr(desc->http_rescode, ' ');
2088 if (desc->http_resmesg == NULL)
2089 goto fail;
2091 *desc->http_resmesg++ = '\0';
2092 desc->http_resmesg = strdup(desc->http_resmesg);
2093 if (desc->http_resmesg == NULL)
2094 goto fail;
2096 desc->http_status = strtonum(desc->http_rescode, 100, 599,
2097 &errstr);
2098 if (errstr) {
2099 DPRINTF(
2100 "%s: http_status %s: errno %d, %s",
2101 __func__, desc->http_rescode, errno,
2102 errstr);
2103 goto fail;
2105 DPRINTF("http_version %s http_rescode %s http_resmesg %s",
2106 desc->http_version, desc->http_rescode,
2107 desc->http_resmesg);
2108 } else if (cre->dir == RELAY_DIR_REQUEST) {
2109 desc->http_method = relay_httpmethod_byname(key);
2111 if (desc->http_method == HTTP_METHOD_NONE)
2112 goto fail;
2114 if ((hmn = calloc(1, sizeof *hmn)) == NULL)
2115 goto fail;
2116 hmn->hmn_method = desc->http_method;
2117 DPRINTF("%s: session %d enqueuing %s", __func__, con->se_id,
2118 relay_httpmethod_byid(hmn->hmn_method));
2119 SIMPLEQ_INSERT_TAIL(&hs->hs_methods, hmn, hmn_entry);
2121 * Decode request path and query
2123 desc->http_path = strdup(value);
2124 if (desc->http_path == NULL)
2125 goto fail;
2127 desc->http_version = strchr(desc->http_path, ' ');
2128 if (desc->http_version == NULL)
2129 goto fail;
2131 *desc->http_version++ = '\0';
2132 desc->http_query = strchr(desc->http_path, '?');
2133 if (desc->http_query != NULL)
2134 *desc->http_query++ = '\0';
2137 * Have to allocate the strings because they
2138 * could be changed independently by the
2139 * filters later.
2141 if ((desc->http_version = strdup(desc->http_version)) == NULL)
2142 goto fail;
2144 if (desc->http_query != NULL && (desc->http_query =
2145 strdup(desc->http_query)) == NULL)
2146 goto fail;
2148 return (0);
2149 fail:
2150 relay_abort_http(con, 500, strerror(errno), 0);
2151 return (-1);