1 /* $OpenBSD: relay_http.c,v 1.100 2026/06/03 20:00:34 kirill Exp $ */
4 * Copyright (c) 2006 - 2016 Reyk Floeter <reyk@openbsd.org>
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.
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.
19 #include <sys/types.h>
20 #include <sys/queue.h>
22 #include <sys/socket.h>
25 #include <netinet/in.h>
26 #include <arpa/inet.h>
44 static int _relay_lookup_url(struct ctl_relay_event *, char *, char *,
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 *,
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 *,
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 *,
85 static struct relayd *env = NULL;
87 static struct http_method http_methods[] = HTTP_METHODS;
88 static struct http_error http_errors[] = HTTP_ERRORS;
91 relay_http(struct relayd *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);
108 relay_http_init(struct relay *rlay)
110 rlay->rl_proto->close = relay_close_http;
114 /* Calculate skip step for the filter rules (may take a while) */
115 relay_calc_skip_steps(&rlay->rl_proto->rules);
119 relay_http_priv_init(struct rsession *con)
122 struct http_session *hs;
124 if ((hs = calloc(1, sizeof(*hs))) == NULL)
126 SIMPLEQ_INIT(&hs->hs_methods);
127 DPRINTF("%s: session %d http_session %p", __func__,
130 return (relay_httpdesc_init(&con->se_in));
134 relay_httpdesc_init(struct ctl_relay_event *cre)
136 struct http_descriptor *desc;
138 if ((desc = calloc(1, sizeof(*desc))) == NULL)
141 RB_INIT(&desc->http_headers);
148 relay_httpdesc_free(struct http_descriptor *desc)
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;
169 relay_http_header_name_valid(const char *name)
172 * RFC 9110 specifies that only the following characters are
173 * permitted within HTTP header field names.
175 const char token_chars[] = "!#$%&'*+-.^_`|~0123456789"
176 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
177 const size_t len = strspn(name, token_chars);
179 return (name[len] == '\0');
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;
195 size_t size, linelen;
196 struct kv *hdr = NULL;
197 struct kv *upgrade = NULL, *upgrade_ws = NULL;
198 struct kv *connection_close = NULL;
200 int headers_only = 0;
201 enum httpmethod request_method = HTTP_METHOD_NONE;
203 getmonotime(&con->se_tv_last);
206 size = EVBUFFER_LENGTH(src);
207 DPRINTF("%s: session %d: size %lu, to read %lld",
208 __func__, con->se_id, size, cre->toread);
210 if (cre->dir == RELAY_DIR_RESPONSE)
212 cre->toread = TOREAD_HTTP_HEADER;
217 line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF);
220 * We do not process the last header on premature
221 * EOF as it may not be complete.
227 * An empty line indicates the end of the request.
228 * libevent already stripped the \r\n for us.
235 /* Process last (complete) header line. */
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);
249 /* Reject requests with an embedded NUL byte. */
250 if (memchr(line, '\0', linelen) != NULL) {
251 relay_abort_http(con, 400, "malformed", 0);
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) {
266 * RFC 9112 section 5.2 permits unconditional rejection.
267 * Obs-fold creates parser differentials between intermediaries
270 if (*line == ' ' || *line == '\t') {
271 if (cre->dir == RELAY_DIR_RESPONSE)
272 relay_abort_http(con, 502, "Bad Gateway", 0);
274 relay_abort_http(con, 400, "malformed", 0);
278 /* Process the last complete header line. */
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:
293 * These methods should not have a body
294 * and thus no Content-Length header.
296 relay_abort_http(con, 400, "malformed",
299 case HTTP_METHOD_GET:
300 case HTTP_METHOD_HEAD:
301 case HTTP_METHOD_COPY:
302 case HTTP_METHOD_MOVE:
304 * We strip the body (if present) from
305 * the GET, HEAD, COPY and MOVE methods
306 * so strip Content-Length too.
308 kv_delete(&desc->http_headers,
309 desc->http_lastheader);
311 case HTTP_METHOD_RESPONSE:
312 if (request_method == HTTP_METHOD_HEAD)
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.
324 if (*value == '+' || *value == '-') {
327 cre->toread = strtonum(value, 0,
331 relay_abort_http(con, 500,
335 desc->http_cl = desc->http_lastheader;
336 if (desc->http_cl->kv_parent != NULL)
338 desc->http_cl->kv_parent;
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.
349 if (desc->http_method == HTTP_METHOD_RESPONSE &&
350 (((desc->http_status >= 100 &&
351 desc->http_status < 200) ||
352 desc->http_status == 204)) &&
354 relay_abort_http(con, 502,
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,
366 desc->http_chunked = 1;
369 if (strcasecmp("Host", key) == 0) {
371 * The path may contain a URL. The host in the
372 * URL has to match the Host: value.
374 if (parse_url(desc->http_path,
375 &urlproto, &host, &path) == 0) {
376 ret = strcasecmp(host, value);
381 relay_abort_http(con, 400,
382 "malformed host", 0);
392 /* Validate header field name and check for missing value. */
394 if ((value = strchr(line, ':')) == NULL) {
395 relay_abort_http(con, 400, "malformed", 0);
399 value += strspn(value, " \t\r\n");
401 if (!relay_http_header_name_valid(key)) {
402 relay_abort_http(con, 400, "malformed", 0);
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);
414 desc->http_lastheader = hdr;
420 if (desc->http_method == HTTP_METHOD_NONE) {
421 relay_abort_http(con, 406, "no method", 0);
425 action = relay_test(proto, cre);
428 relay_close(con, "filter rule failed", 1);
431 relay_abort_http(con, 400, "Bad Request",
435 relay_abort_http(con, 500, "Internal Server Error",
439 if (action != RES_PASS) {
440 relay_abort_http(con, 403, "Forbidden", con->se_label);
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.
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;
457 * HTTP 101 Switching Protocols
460 upgrade = kv_find_value(&desc->http_headers,
461 "Connection", "upgrade", ",");
462 upgrade_ws = kv_find_value(&desc->http_headers,
463 "Upgrade", "websocket", ",");
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);
470 } else if (upgrade == NULL) {
471 relay_abort_http(con, 400,
472 "Bad Websocket Request", 0);
474 } else if (desc->http_method != HTTP_METHOD_GET) {
475 relay_abort_http(con, 405,
476 "Websocket Method Not Allowed", 0);
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)) {
484 cre->dst->toread = TOREAD_UNLIMITED;
485 cre->dst->bev->readcb = relay_read;
487 relay_abort_http(con, 502,
488 "Bad Websocket Gateway", 0);
493 connection_close = kv_find_value(&desc->http_headers,
494 "Connection", "close", ",");
496 * RFC 9112 section 6.3: these responses end at the empty
497 * line after the header section. 101 upgrades become streams.
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);
506 switch (desc->http_method) {
507 case HTTP_METHOD_CONNECT:
509 cre->toread = TOREAD_UNLIMITED;
510 bev->readcb = relay_read;
512 case HTTP_METHOD_GET:
513 case HTTP_METHOD_HEAD:
515 case HTTP_METHOD_COPY:
516 case HTTP_METHOD_MOVE:
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:
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;
561 cre->toread = TOREAD_HTTP_HEADER;
562 bev->readcb = relay_read_http;
565 if (desc->http_chunked && !headers_only) {
566 /* Chunked transfer encoding */
567 cre->toread = TOREAD_HTTP_CHUNK_LENGTH;
568 bev->readcb = relay_read_httpchunks;
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.
577 if (cre->toread == TOREAD_UNLIMITED &&
578 connection_close == NULL && !ws_response)
579 if (kv_add(&desc->http_headers, "Connection",
583 if (cre->dir == RELAY_DIR_REQUEST) {
584 if (relay_writerequest_http(cre->dst, cre) == -1)
587 if (relay_writeresponse_http(cre->dst, cre) == -1)
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)
595 relay_reset_http(cre);
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);
603 if (relay_connect(con) == -1) {
604 relay_abort_http(con, 502, "session failed", 0);
610 relay_close(con, "last http read (done)", 0);
613 switch (relay_splice(cre)) {
615 relay_close(con, strerror(errno), 1);
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. */
627 relay_abort_http(con, 500, strerror(errno), 0);
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);
643 getmonotime(&con->se_tv_last);
646 size = EVBUFFER_LENGTH(src);
647 DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
648 con->se_id, size, cre->toread);
651 if (relay_spliceadjust(cre) == -1)
654 if (cre->toread > 0) {
655 /* Read content data */
656 if ((off_t)size > cre->toread) {
658 if (relay_bufferevent_write_chunk(cre->dst, src, size)
663 if (relay_bufferevent_write_buffer(cre->dst, src) == -1)
667 DPRINTF("%s: done, size %lu, to read %lld", __func__,
670 if (cre->toread == 0) {
671 cre->toread = TOREAD_HTTP_HEADER;
672 bev->readcb = relay_read_http;
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. */
687 relay_close(con, "last http content read", 0);
690 relay_close(con, strerror(errno), 1);
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);
702 size_t size, linelen;
704 getmonotime(&con->se_tv_last);
707 size = EVBUFFER_LENGTH(src);
708 DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
709 con->se_id, size, cre->toread);
712 if (relay_spliceadjust(cre) == -1)
715 if (cre->toread > 0) {
716 /* Read chunk data */
717 if ((off_t)size > cre->toread) {
719 if (relay_bufferevent_write_chunk(cre->dst, src, size)
724 if (relay_bufferevent_write_buffer(cre->dst, src) == -1)
728 DPRINTF("%s: done, size %lu, to read %lld", __func__,
731 switch (cre->toread) {
732 case TOREAD_HTTP_CHUNK_LENGTH:
733 line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF);
735 /* Ignore empty line, continue */
736 bufferevent_enable(bev, EV_READ);
745 * Read prepended chunk size in hex without leading +0[Xx].
746 * The returned signed value must not be negative.
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. */
754 llval = strtoll(line, &ep, 16);
756 if (ep == line || *ep != '\0' || llval < 0 ||
757 (errno == ERANGE && llval == LLONG_MAX)) {
759 relay_close(con, "invalid chunk size", 1);
763 if (relay_bufferevent_print(cre->dst, line) == -1 ||
764 relay_bufferevent_print(cre->dst, "\r\n") == -1) {
770 if ((cre->toread = llval) == 0) {
771 DPRINTF("%s: last chunk", __func__);
772 cre->toread = TOREAD_HTTP_CHUNK_TRAILER;
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);
779 /* Ignore empty line, continue */
780 bufferevent_enable(bev, EV_READ);
783 if (relay_bufferevent_print(cre->dst, line) == -1 ||
784 relay_bufferevent_print(cre->dst, "\r\n") == -1) {
789 /* Switch to HTTP header mode */
790 cre->toread = TOREAD_HTTP_HEADER;
791 bev->readcb = relay_read_http;
796 /* Chunk is terminated by an empty newline */
797 line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF);
799 if (relay_bufferevent_print(cre->dst, "\r\n") == -1)
801 cre->toread = TOREAD_HTTP_CHUNK_LENGTH;
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. */
820 relay_close(con, "last http chunk read (done)", 0);
823 relay_close(con, strerror(errno), 1);
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;
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;
847 const char *str = NULL;
849 if (asprintf(&val, "%s%s%s%s",
851 query == NULL ? "" : "?",
852 query == NULL ? "" : query) == -1) {
853 relay_abort_http(con, 500, "failed to allocate URL", 0);
857 switch (kv->kv_digest) {
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);
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) {
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;
893 char *hi[RELAY_MAXLOOKUPLEVELS], *p, *pp, *c, ch;
894 char ph[HOST_NAME_MAX+1];
897 if (desc->http_path == NULL)
901 * This is an URL lookup algorithm inspired by
902 * http://code.google.com/apis/safebrowsing/
903 * developers_guide.html#PerformingLookups
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) {
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))
925 if ((pp = strdup(desc->http_path)) == NULL) {
926 return (RES_INTERNAL);
928 for (i = (RELAY_MAXLOOKUPLEVELS - 1); i >= 0; i--) {
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)
938 /* 2. complete path without query */
939 if ((ret = _relay_lookup_url(cre, hi[i],
940 pp, NULL, kv)) != RES_PASS)
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')
951 if ((ret = _relay_lookup_url(cre, hi[i],
952 pp, NULL, kv)) != RES_PASS)
965 relay_lookup_cookie(struct ctl_relay_event *cre, const char *str,
968 char *val, *ptr, *key, *value;
971 if ((val = strdup(str)) == NULL) {
972 return (RES_INTERNAL);
975 for (ptr = val; ptr != NULL && strlen(ptr);) {
979 if ((ptr = strchr(ptr, ';')) != NULL)
982 * XXX We do not handle attributes
983 * ($Path, $Domain, or $Port)
989 strchr(key, '=')) == NULL ||
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))) {
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;
1026 if (desc->http_query == NULL)
1028 if ((val = strdup(desc->http_query)) == NULL) {
1029 return (RES_INTERNAL);
1033 while (ptr != NULL && strlen(ptr)) {
1035 if ((ptr = strchr(ptr, '&')) != NULL)
1037 if ((tmpval = strchr(tmpkey, '=')) == NULL || strlen(tmpval)
1042 if (fnmatch(kv->kv_key, tmpkey, 0) != FNM_NOMATCH &&
1043 (kv->kv_value == NULL || fnmatch(kv->kv_value, tmpval, 0)
1050 if (tmpkey == NULL || tmpval == NULL)
1053 match->kv_key = strdup(tmpkey);
1054 if (match->kv_key == NULL)
1056 match->kv_value = strdup(tmpval);
1057 if (match->kv_value == NULL)
1067 relay_http_time(time_t t, char *tmbuf, size_t len)
1071 /* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */
1072 if (t == -1 || gmtime_r(&t, &tm) == NULL)
1075 return (strftime(tmbuf, len, "%a, %d %h %Y %T GMT", &tm));
1079 relay_abort_http(struct rsession *con, u_int code, const char *msg,
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;
1090 if ((httperr = relay_httperror_byid(code)) == NULL)
1091 httperr = "Unknown Error";
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);
1106 /* Some system information */
1107 if (print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) == NULL)
1110 if (relay_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0)
1113 /* Do not send details of the Internal Server Error */
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,
1133 "<title>%03d %s</title>\n"
1134 "<style type=\"text/css\"><!--\n%s\n--></style>\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"
1143 code, httperr, style, httperr, text,
1144 label == NULL ? "" : label,
1145 RELAYD_SERVERNAME, hbuf, ntohs(rlay->rl_conf.port))) == -1)
1148 /* Generate simple HTTP+HTML error document */
1149 if (asprintf(&httpmsg,
1150 "HTTP/1.0 %03d %s\r\n"
1153 "Connection: close\r\n"
1154 "Content-Type: text/html\r\n"
1155 "Content-Length: %d\r\n"
1158 code, httperr, tmbuf, RELAYD_SERVERNAME, bodylen, body) == -1)
1161 /* Dump the message without checking for success */
1162 relay_dump(&con->se_in, httpmsg, strlen(httpmsg));
1167 if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1)
1168 relay_close(con, msg, 1);
1170 relay_close(con, httpmsg, 1);
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__,
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));
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);
1198 relay_expand_http(struct ctl_relay_event *cre, char *val, char *buf,
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;
1207 if (strlcpy(buf, val, len) >= len)
1210 if (strstr(val, "$HOST") != NULL) {
1211 key.kv_key = "Host";
1212 host = kv_find(&desc->http_headers, &key);
1214 if (host->kv_value == NULL)
1216 snprintf(ibuf, sizeof(ibuf), "%s", host->kv_value);
1218 if (print_host(&rlay->rl_conf.ss,
1219 ibuf, sizeof(ibuf)) == NULL)
1222 if (expand_string(buf, len, "$HOST", ibuf))
1225 if (strstr(val, "$REMOTE_") != NULL) {
1226 if (strstr(val, "$REMOTE_ADDR") != NULL) {
1227 if (print_host(&cre->ss, ibuf, sizeof(ibuf)) == NULL)
1229 if (expand_string(buf, len,
1230 "$REMOTE_ADDR", ibuf) != 0)
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)
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)
1245 if (expand_string(buf, len,
1246 "$SERVER_ADDR", ibuf) != 0)
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)
1256 if (strstr(val, "$SERVER_NAME") != NULL) {
1257 if (expand_string(buf, len,
1258 "$SERVER_NAME", RELAYD_SERVERNAME) != 0)
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)
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)
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)
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)
1315 relay_writeheader_kv(struct ctl_relay_event *dst, struct kv *hdr)
1320 if (hdr->kv_flags & KV_FLAG_INVALID)
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;
1329 ptr = hdr->kv_value;
1330 if (relay_bufferevent_print(dst, key) == -1 ||
1332 (relay_bufferevent_print(dst, ": ") == -1 ||
1333 relay_bufferevent_print(dst, ptr) == -1 ||
1334 relay_bufferevent_print(dst, "\r\n") == -1)))
1336 DPRINTF("%s: %s: %s", __func__, key,
1337 hdr->kv_value == NULL ? "" : hdr->kv_value);
1343 relay_writeheader_http(struct ctl_relay_event *dst, struct ctl_relay_event
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)
1352 TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) {
1353 if (relay_writeheader_kv(dst, kv) == -1)
1362 relay_httpmethod_byname(const char *name)
1364 enum httpmethod id = HTTP_METHOD_NONE;
1365 struct http_method method, *res = NULL;
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;
1379 relay_httpmethod_byid(u_int id)
1381 const char *name = NULL;
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;
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));
1408 relay_httperror_byid(u_int id)
1410 struct http_error error, *res = NULL;
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);
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);
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];
1440 if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_QUERY)
1442 else if (kv->kv_key == NULL)
1444 else if ((res = relay_lookup_query(cre, kv)) != 0)
1447 relay_match(actions, kv, match, NULL);
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];
1460 if (kv->kv_type != KEY_TYPE_HEADER)
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 */
1472 if (fnmatch(kv->kv_key, match->kv_key,
1473 FNM_CASEFOLD) == FNM_NOMATCH)
1475 if (kv->kv_value != NULL &&
1476 match->kv_value != NULL &&
1477 fnmatch(kv->kv_value, match->kv_value, 0) == FNM_NOMATCH)
1481 relay_match(actions, kv, match, &desc->http_headers);
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;
1495 if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_PATH)
1497 else if (kv->kv_option != KEY_OPTION_STRIP) {
1498 if (kv->kv_key == NULL)
1500 else if (fnmatch(kv->kv_key, desc->http_path, 0) == FNM_NOMATCH)
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)
1509 relay_match(actions, kv, match, NULL);
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;
1524 if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_URL ||
1528 key.kv_key = "Host";
1529 host = kv_find(&desc->http_headers, &key);
1531 if (host == NULL || host->kv_value == NULL)
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)
1539 relay_match(actions, kv, match, NULL);
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;
1553 if (kv->kv_type != KEY_TYPE_COOKIE)
1557 case RELAY_DIR_REQUEST:
1558 key.kv_key = "Cookie";
1560 case RELAY_DIR_RESPONSE:
1561 key.kv_key = "Set-Cookie";
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 */
1573 match = kv_find(&desc->http_headers, &key);
1576 if (kv->kv_key == NULL || match->kv_value == NULL)
1578 else if ((res = relay_lookup_cookie(cre, match->kv_value,
1583 relay_match(actions, kv, match, &desc->http_headers);
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;
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);
1615 TAILQ_FOREACH(kv, matches, kv_match_entry) {
1616 TAILQ_INSERT_TAIL(actions, kv, kv_action_entry);
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;
1631 struct kv *kv, *match, *kp, *mp, kvcopy, matchcopy, key;
1632 int addkv, ret, nstrip;
1633 char buf[IBUF_READ_SIZE], *ptr;
1635 const char *meth = NULL;
1637 memset(&kvcopy, 0, sizeof(kvcopy));
1638 memset(&matchcopy, 0, sizeof(matchcopy));
1642 TAILQ_FOREACH(kv, actions, kv_action_entry) {
1644 match = kv->kv_match;
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))
1654 switch (kv->kv_option) {
1655 case KEY_OPTION_APPEND:
1656 case KEY_OPTION_SET:
1657 switch (kv->kv_type) {
1659 if (kv->kv_option == KEY_OPTION_APPEND) {
1660 if (kv_setkey(match, "%s%s",
1661 match->kv_key, kv->kv_key) == -1)
1664 if (kv_setkey(match, "%s",
1665 kv->kv_value) == -1)
1669 case KEY_TYPE_COOKIE:
1671 if (kv_inherit(kp, kv) == NULL)
1673 if (kv_set(kp, "%s=%s;", kp->kv_key,
1674 kp->kv_value) == -1)
1676 if (kv_setkey(kp, "%s", cre->dir ==
1678 "Cookie" : "Set-Cookie") == -1)
1680 /* FALLTHROUGH cookie is a header */
1681 case KEY_TYPE_HEADER:
1682 if (match == NULL) {
1686 if (match->kv_value == NULL ||
1687 kv->kv_option == KEY_OPTION_SET) {
1688 if (kv_set(match, "%s",
1689 kv->kv_value) == -1)
1695 /* query, url not supported */
1699 case KEY_OPTION_REMOVE:
1700 switch (kv->kv_type) {
1702 if (kv_setkey(match, "/") == -1)
1705 case KEY_TYPE_COOKIE:
1706 case KEY_TYPE_HEADER:
1707 if (kv->kv_matchtree != NULL)
1708 match->kv_flags |= KV_FLAG_INVALID;
1711 match = kv->kv_match = NULL;
1714 /* query and url not supported */
1718 case KEY_OPTION_HASH:
1719 switch (kv->kv_type) {
1721 value = match->kv_key;
1724 value = match->kv_value;
1727 SipHash24_Update(&con->se_siphashctx,
1728 value, strlen(value));
1730 case KEY_OPTION_LOG:
1731 /* perform this later */
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,
1743 fatalx("%s: invalid action", __func__);
1747 /* from now on, reads from kp writes to 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)
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)
1764 if (kv_set(match, "%s", ptr) == -1)
1769 switch (kv->kv_option) {
1770 case KEY_OPTION_LOG:
1774 if (kv_inherit(mp, match) == NULL)
1776 if (mp->kv_flags & KV_FLAG_INVALID) {
1777 if (kv_set(mp, "%s (removed)",
1778 mp->kv_value) == -1)
1781 switch (kv->kv_type) {
1783 key.kv_key = "Host";
1784 host = kv_find(&desc->http_headers, &key);
1785 switch (kv->kv_digest) {
1788 host->kv_value == NULL)
1790 if (kv_setkey(mp, "%s%s",
1791 host->kv_value, mp->kv_key) ==
1796 if (kv_setkey(mp, "%s", kv->kv_key)
1805 if (kv_log(con, mp, con->se_label, cre->dir)
1813 /* actions applied, cleanup kv */
1814 kv->kv_match = NULL;
1815 kv->kv_matchtree = NULL;
1816 TAILQ_REMOVE(actions, kv, kv_match_entry);
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;
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));
1841 relay_log(con, cre->dir == RELAY_DIR_REQUEST ? "" : ";");
1845 kv_free(&matchcopy);
1850 #define RELAY_GET_SKIP_STEP(i) \
1852 r = r->rule_skip[i]; \
1853 DPRINTF("%s:%d: skip %d rules", __func__, __LINE__, i); \
1856 #define RELAY_GET_NEXT_STEP \
1858 DPRINTF("%s:%d: next rule", __func__, __LINE__); \
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;
1875 TAILQ_INIT(&actions);
1877 r = TAILQ_FIRST(&proto->rules);
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;
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 */
1920 RELAY_GET_NEXT_STEP;
1921 } else if (r->rule_action == RULE_ACTION_BLOCK)
1923 else if (r->rule_action == RULE_ACTION_PASS)
1929 /* Temporarily save actions */
1930 TAILQ_FOREACH(kv, &matches, kv_match_entry) {
1931 TAILQ_INSERT_TAIL(&rule->rule_kvlist,
1935 if (rule->rule_flags & RULE_FLAG_QUICK)
1939 /* Continue to find last matching policy */
1940 DPRINTF("%s: session %d, res %d", __func__,
1942 if (res == RES_BAD || res == RES_INTERNAL)
1945 r = TAILQ_NEXT(r, rule_entry);
1949 if (rule != NULL && relay_match_actions(cre, rule, NULL, &actions, &tbl)
1951 /* Something bad happened, drop */
1955 if (relay_apply_actions(cre, &actions, tbl) != 0) {
1956 /* Something bad happened, drop */
1960 DPRINTF("%s: session %d: action %d", __func__,
1961 con->se_id, action);
1966 #define RELAY_SET_SKIP_STEPS(i) \
1968 while (head[i] != cur) { \
1969 head[i]->rule_skip[i] = cur; \
1970 head[i] = TAILQ_NEXT(head[i], rule_entry); \
1974 /* This code is derived from pf_calc_skip_steps() from pf.c */
1976 relay_calc_skip_steps(struct relay_rules *rules)
1978 struct relay_rule *head[RULE_SKIP_COUNT], *cur, *prev;
1981 cur = TAILQ_FIRST(rules);
1983 for (i = 0; i < RULE_SKIP_COUNT; ++i)
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);
2000 cur = TAILQ_NEXT(cur, rule_entry);
2002 for (i = 0; i < RULE_SKIP_COUNT; ++i)
2003 RELAY_SET_SKIP_STEPS(i);
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);
2018 server_root_strip(char *path, int n)
2022 /* Strip strip leading directories. Leading '/' is ignored. */
2023 for (; n > 0 && *path != '\0'; n--)
2024 if ((p = strchr(++path, '/')) != NULL)
2033 * Handle the first line of an HTTP message (Request- or Status-Line).
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;
2046 DPRINTF("%s: session %d http_session %p", __func__, con->se_id, hs);
2049 if ((value = strchr(key, ' ')) == NULL) {
2050 relay_abort_http(con, 400, "malformed", 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.
2064 *request_method = HTTP_METHOD_NONE;
2065 DPRINTF("%s: session %d unbalanced response",
2066 __func__, con->se_id);
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));
2077 * Decode response path and query
2079 desc->http_version = strdup(key);
2080 if (desc->http_version == NULL)
2083 desc->http_rescode = strdup(value);
2084 if (desc->http_rescode == NULL)
2087 desc->http_resmesg = strchr(desc->http_rescode, ' ');
2088 if (desc->http_resmesg == NULL)
2091 *desc->http_resmesg++ = '\0';
2092 desc->http_resmesg = strdup(desc->http_resmesg);
2093 if (desc->http_resmesg == NULL)
2096 desc->http_status = strtonum(desc->http_rescode, 100, 599,
2100 "%s: http_status %s: errno %d, %s",
2101 __func__, desc->http_rescode, errno,
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)
2114 if ((hmn = calloc(1, sizeof *hmn)) == NULL)
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)
2127 desc->http_version = strchr(desc->http_path, ' ');
2128 if (desc->http_version == NULL)
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
2141 if ((desc->http_version = strdup(desc->http_version)) == NULL)
2144 if (desc->http_query != NULL && (desc->http_query =
2145 strdup(desc->http_query)) == NULL)
2150 relay_abort_http(con, 500, strerror(errno), 0);