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 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 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 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 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 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 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 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 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 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 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 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 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 * 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 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 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 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 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 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 * 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 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 * 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 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 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 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 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 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 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 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 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 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 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 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 * 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