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