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