1*79db477bSanton /* $OpenBSD: relay_http.c,v 1.90 2024/07/20 06:54:15 anton Exp $ */ 2a15b848eSreyk 3a15b848eSreyk /* 48df09a06Sreyk * Copyright (c) 2006 - 2016 Reyk Floeter <reyk@openbsd.org> 5a15b848eSreyk * 6a15b848eSreyk * Permission to use, copy, modify, and distribute this software for any 7a15b848eSreyk * purpose with or without fee is hereby granted, provided that the above 8a15b848eSreyk * copyright notice and this permission notice appear in all copies. 9a15b848eSreyk * 10a15b848eSreyk * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11a15b848eSreyk * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12a15b848eSreyk * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13a15b848eSreyk * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14a15b848eSreyk * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15a15b848eSreyk * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16a15b848eSreyk * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17a15b848eSreyk */ 18a15b848eSreyk 19a15b848eSreyk #include <sys/types.h> 20a15b848eSreyk #include <sys/queue.h> 21a15b848eSreyk #include <sys/time.h> 22a15b848eSreyk #include <sys/socket.h> 23a15b848eSreyk #include <sys/tree.h> 24a15b848eSreyk 25a15b848eSreyk #include <netinet/in.h> 26f04ff968Sreyk #include <arpa/inet.h> 27a15b848eSreyk 28f04ff968Sreyk #include <limits.h> 29a15b848eSreyk #include <stdio.h> 30f04ff968Sreyk #include <stdlib.h> 31f04ff968Sreyk #include <errno.h> 32f04ff968Sreyk #include <string.h> 33f04ff968Sreyk #include <time.h> 34a15b848eSreyk #include <event.h> 35a15b848eSreyk #include <fnmatch.h> 36f04ff968Sreyk #include <siphash.h> 37f04ff968Sreyk #include <imsg.h> 3824d4a724Sbluhm #include <unistd.h> 39a15b848eSreyk 40a15b848eSreyk #include "relayd.h" 41cb8b0e56Sreyk #include "http.h" 42a15b848eSreyk 43a15b848eSreyk static int _relay_lookup_url(struct ctl_relay_event *, char *, char *, 44cb8b0e56Sreyk char *, struct kv *); 45a15b848eSreyk int relay_lookup_url(struct ctl_relay_event *, 46cb8b0e56Sreyk const char *, struct kv *); 47cb8b0e56Sreyk int relay_lookup_query(struct ctl_relay_event *, struct kv *); 48cb8b0e56Sreyk int relay_lookup_cookie(struct ctl_relay_event *, const char *, 49cb8b0e56Sreyk struct kv *); 50a15b848eSreyk void relay_read_httpcontent(struct bufferevent *, void *); 51a15b848eSreyk void relay_read_httpchunks(struct bufferevent *, void *); 52a15b848eSreyk char *relay_expand_http(struct ctl_relay_event *, char *, 53a15b848eSreyk char *, size_t); 54c307a266Sreyk int relay_writeheader_kv(struct ctl_relay_event *, struct kv *); 55cb8b0e56Sreyk int relay_writeheader_http(struct ctl_relay_event *, 56cb8b0e56Sreyk struct ctl_relay_event *); 57cb8b0e56Sreyk int relay_writerequest_http(struct ctl_relay_event *, 58cb8b0e56Sreyk struct ctl_relay_event *); 59cb8b0e56Sreyk int relay_writeresponse_http(struct ctl_relay_event *, 60cb8b0e56Sreyk struct ctl_relay_event *); 61cb8b0e56Sreyk void relay_reset_http(struct ctl_relay_event *); 62cb8b0e56Sreyk static int relay_httpmethod_cmp(const void *, const void *); 63543ef491Sreyk static int relay_httperror_cmp(const void *, const void *); 64cb8b0e56Sreyk int relay_httpquery_test(struct ctl_relay_event *, 65cb8b0e56Sreyk struct relay_rule *, struct kvlist *); 66cb8b0e56Sreyk int relay_httpheader_test(struct ctl_relay_event *, 67cb8b0e56Sreyk struct relay_rule *, struct kvlist *); 68cb8b0e56Sreyk int relay_httppath_test(struct ctl_relay_event *, 69cb8b0e56Sreyk struct relay_rule *, struct kvlist *); 70cb8b0e56Sreyk int relay_httpurl_test(struct ctl_relay_event *, 71cb8b0e56Sreyk struct relay_rule *, struct kvlist *); 72cb8b0e56Sreyk int relay_httpcookie_test(struct ctl_relay_event *, 73cb8b0e56Sreyk struct relay_rule *, struct kvlist *); 7465f47834Sreyk int relay_apply_actions(struct ctl_relay_event *, struct kvlist *, 7565f47834Sreyk struct relay_table *); 76cb8b0e56Sreyk int relay_match_actions(struct ctl_relay_event *, 7765f47834Sreyk struct relay_rule *, struct kvlist *, struct kvlist *, 7865f47834Sreyk struct relay_table **); 79cb8b0e56Sreyk void relay_httpdesc_free(struct http_descriptor *); 80eeb1fea4Sdenis char * server_root_strip(char *, int); 81cb8b0e56Sreyk 82cb8b0e56Sreyk static struct relayd *env = NULL; 83cb8b0e56Sreyk 84cb8b0e56Sreyk static struct http_method http_methods[] = HTTP_METHODS; 85543ef491Sreyk static struct http_error http_errors[] = HTTP_ERRORS; 86cb8b0e56Sreyk 87cb8b0e56Sreyk void 88cb8b0e56Sreyk relay_http(struct relayd *x_env) 89cb8b0e56Sreyk { 90cb8b0e56Sreyk if (x_env != NULL) 91cb8b0e56Sreyk env = x_env; 92cb8b0e56Sreyk 93cb8b0e56Sreyk DPRINTF("%s: sorting lookup tables, pid %d", __func__, getpid()); 94cb8b0e56Sreyk 95cb8b0e56Sreyk /* Sort the HTTP lookup arrays */ 96cb8b0e56Sreyk qsort(http_methods, sizeof(http_methods) / 97cb8b0e56Sreyk sizeof(http_methods[0]) - 1, 98cb8b0e56Sreyk sizeof(http_methods[0]), relay_httpmethod_cmp); 99543ef491Sreyk qsort(http_errors, sizeof(http_errors) / 100543ef491Sreyk sizeof(http_errors[0]) - 1, 101543ef491Sreyk sizeof(http_errors[0]), relay_httperror_cmp); 102cb8b0e56Sreyk } 103cb8b0e56Sreyk 104cb8b0e56Sreyk void 105cb8b0e56Sreyk relay_http_init(struct relay *rlay) 106cb8b0e56Sreyk { 107cb8b0e56Sreyk rlay->rl_proto->close = relay_close_http; 108cb8b0e56Sreyk 109cb8b0e56Sreyk relay_http(NULL); 110cb8b0e56Sreyk 111cb8b0e56Sreyk /* Calculate skip step for the filter rules (may take a while) */ 112cb8b0e56Sreyk relay_calc_skip_steps(&rlay->rl_proto->rules); 113cb8b0e56Sreyk } 114cb8b0e56Sreyk 115cb8b0e56Sreyk int 11653e8df0dSbenno relay_http_priv_init(struct rsession *con) 11753e8df0dSbenno { 11853e8df0dSbenno 11953e8df0dSbenno struct http_session *hs; 12053e8df0dSbenno 12153e8df0dSbenno if ((hs = calloc(1, sizeof(*hs))) == NULL) 12253e8df0dSbenno return (-1); 12353e8df0dSbenno SIMPLEQ_INIT(&hs->hs_methods); 12453e8df0dSbenno DPRINTF("%s: session %d http_session %p", __func__, 12553e8df0dSbenno con->se_id, hs); 12653e8df0dSbenno con->se_priv = hs; 12753e8df0dSbenno return (relay_httpdesc_init(&con->se_in)); 12853e8df0dSbenno } 12953e8df0dSbenno 13053e8df0dSbenno int 131cb8b0e56Sreyk relay_httpdesc_init(struct ctl_relay_event *cre) 132cb8b0e56Sreyk { 133cb8b0e56Sreyk struct http_descriptor *desc; 134cb8b0e56Sreyk 135cb8b0e56Sreyk if ((desc = calloc(1, sizeof(*desc))) == NULL) 136cb8b0e56Sreyk return (-1); 137c307a266Sreyk 138c307a266Sreyk RB_INIT(&desc->http_headers); 139cb8b0e56Sreyk cre->desc = desc; 140cb8b0e56Sreyk 141cb8b0e56Sreyk return (0); 142cb8b0e56Sreyk } 143cb8b0e56Sreyk 144cb8b0e56Sreyk void 145cb8b0e56Sreyk relay_httpdesc_free(struct http_descriptor *desc) 146cb8b0e56Sreyk { 147954d713bSclaudio if (desc == NULL) 148954d713bSclaudio return; 149954d713bSclaudio 150cb8b0e56Sreyk free(desc->http_path); 151cb8b0e56Sreyk desc->http_path = NULL; 152cb8b0e56Sreyk free(desc->http_query); 153cb8b0e56Sreyk desc->http_query = NULL; 154cb8b0e56Sreyk free(desc->http_version); 155cb8b0e56Sreyk desc->http_version = NULL; 156cb8b0e56Sreyk free(desc->query_key); 157cb8b0e56Sreyk desc->query_key = NULL; 158cb8b0e56Sreyk free(desc->query_val); 159cb8b0e56Sreyk desc->query_val = NULL; 160cb8b0e56Sreyk kv_purge(&desc->http_headers); 1619779e108Sbluhm desc->http_lastheader = NULL; 162cb8b0e56Sreyk } 163a15b848eSreyk 1641c543edcSmillert static int 1651c543edcSmillert relay_http_header_name_valid(const char *name) 1661c543edcSmillert { 1671c543edcSmillert /* 1681c543edcSmillert * RFC 9110 specifies that only the following characters are 1691c543edcSmillert * permitted within HTTP header field names. 1701c543edcSmillert */ 1711c543edcSmillert const char token_chars[] = "!#$%&'*+-.^_`|~0123456789" 1721c543edcSmillert "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 1731c543edcSmillert const size_t len = strspn(name, token_chars); 1741c543edcSmillert 1751c543edcSmillert return (name[len] == '\0'); 1761c543edcSmillert } 1771c543edcSmillert 178a15b848eSreyk void 179a15b848eSreyk relay_read_http(struct bufferevent *bev, void *arg) 180a15b848eSreyk { 18148240b8fSbluhm struct ctl_relay_event *cre = arg; 182cb8b0e56Sreyk struct http_descriptor *desc = cre->desc; 183a15b848eSreyk struct rsession *con = cre->con; 18448240b8fSbluhm struct relay *rlay = con->se_relay; 185a15b848eSreyk struct protocol *proto = rlay->rl_proto; 186a15b848eSreyk struct evbuffer *src = EVBUFFER_INPUT(bev); 187cb8b0e56Sreyk char *line = NULL, *key, *value; 188c9822c56Sreyk char *urlproto, *host, *path; 189c9822c56Sreyk int action, unique, ret; 190a15b848eSreyk const char *errstr; 191d6db3fedSreyk size_t size, linelen; 192cb8b0e56Sreyk struct kv *hdr = NULL; 193a168ef1aSreyk struct kv *upgrade = NULL, *upgrade_ws = NULL; 19403c01c05Sbenno struct kv *connection_close = NULL; 19503c01c05Sbenno int ws_response = 0; 19653e8df0dSbenno struct http_method_node *hmn; 19753e8df0dSbenno struct http_session *hs; 19853e8df0dSbenno enum httpmethod request_method; 199a15b848eSreyk 200fd1841a3Sreyk getmonotime(&con->se_tv_last); 20130791b79Sbluhm cre->timedout = 0; 202fd1841a3Sreyk 203a15b848eSreyk size = EVBUFFER_LENGTH(src); 204cb8b0e56Sreyk DPRINTF("%s: session %d: size %lu, to read %lld", 205cb8b0e56Sreyk __func__, con->se_id, size, cre->toread); 206954d713bSclaudio if (size == 0) { 207a15b848eSreyk if (cre->dir == RELAY_DIR_RESPONSE) 208a15b848eSreyk return; 209a9feb0c6Sbluhm cre->toread = TOREAD_HTTP_HEADER; 210a15b848eSreyk goto done; 211a15b848eSreyk } 212a15b848eSreyk 213eefb3de5Smillert for (;;) { 214536becf6Sbluhm line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF); 215eefb3de5Smillert if (line == NULL) { 216eefb3de5Smillert /* 217eefb3de5Smillert * We do not process the last header on premature 218eefb3de5Smillert * EOF as it may not be complete. 219eefb3de5Smillert */ 220536becf6Sbluhm break; 221eefb3de5Smillert } 222d6db3fedSreyk 223a15b848eSreyk /* 224a15b848eSreyk * An empty line indicates the end of the request. 225a15b848eSreyk * libevent already stripped the \r\n for us. 226a15b848eSreyk */ 227536becf6Sbluhm if (linelen == 0) { 228a15b848eSreyk cre->done = 1; 229a15b848eSreyk free(line); 230eefb3de5Smillert line = NULL; 231eefb3de5Smillert if (cre->line > 1) { 232eefb3de5Smillert /* Process last (complete) header line. */ 233eefb3de5Smillert goto last_header; 234eefb3de5Smillert } 235a15b848eSreyk break; 236a15b848eSreyk } 237a15b848eSreyk 238d6db3fedSreyk /* Limit the total header length minus \r\n */ 239d6db3fedSreyk cre->headerlen += linelen; 2400ad20b85Sbenno if (cre->headerlen > proto->httpheaderlen) { 2410ad20b85Sbenno relay_abort_http(con, 413, 2420ad20b85Sbenno "request headers too large", 0); 2431c543edcSmillert goto abort; 2441c543edcSmillert } 2451c543edcSmillert 2461c543edcSmillert /* Reject requests with an embedded NUL byte. */ 2471c543edcSmillert if (memchr(line, '\0', linelen) != NULL) { 2481c543edcSmillert relay_abort_http(con, 400, "malformed", 0); 2491c543edcSmillert goto abort; 250d6db3fedSreyk } 251d6db3fedSreyk 252eefb3de5Smillert hs = con->se_priv; 253eefb3de5Smillert DPRINTF("%s: session %d http_session %p", __func__, 254eefb3de5Smillert con->se_id, hs); 255eefb3de5Smillert 256a15b848eSreyk /* 257a15b848eSreyk * The first line is the GET/POST/PUT/... request, 258a15b848eSreyk * subsequent lines are HTTP headers. 259a15b848eSreyk */ 2601c543edcSmillert if (++cre->line == 1) { 261eefb3de5Smillert key = line; 2621c543edcSmillert if ((value = strchr(key, ' ')) == NULL) { 2631c543edcSmillert relay_abort_http(con, 400, "malformed", 0); 2641c543edcSmillert goto abort; 2651c543edcSmillert } 266eefb3de5Smillert *value++ = '\0'; 267eefb3de5Smillert 268eefb3de5Smillert if (cre->dir == RELAY_DIR_RESPONSE) { 269eefb3de5Smillert desc->http_method = HTTP_METHOD_RESPONSE; 270eefb3de5Smillert hmn = SIMPLEQ_FIRST(&hs->hs_methods); 271eefb3de5Smillert 272eefb3de5Smillert /* 273eefb3de5Smillert * There is nothing preventing the relay from 274eefb3de5Smillert * sending an unbalanced response. Be prepared. 275eefb3de5Smillert */ 276eefb3de5Smillert if (hmn == NULL) { 277eefb3de5Smillert request_method = HTTP_METHOD_NONE; 278eefb3de5Smillert DPRINTF("%s: session %d unbalanced " 279eefb3de5Smillert "response", __func__, con->se_id); 2801c543edcSmillert } else { 281eefb3de5Smillert SIMPLEQ_REMOVE_HEAD(&hs->hs_methods, 282eefb3de5Smillert hmn_entry); 283eefb3de5Smillert request_method = hmn->hmn_method; 284eefb3de5Smillert DPRINTF("%s: session %d dequeuing %s", 285eefb3de5Smillert __func__, con->se_id, 286eefb3de5Smillert relay_httpmethod_byid(request_method)); 287eefb3de5Smillert free(hmn); 288eefb3de5Smillert } 289eefb3de5Smillert 290eefb3de5Smillert /* 291eefb3de5Smillert * Decode response path and query 292eefb3de5Smillert */ 293eefb3de5Smillert desc->http_version = strdup(key); 294eefb3de5Smillert if (desc->http_version == NULL) { 295eefb3de5Smillert free(line); 296eefb3de5Smillert goto fail; 297eefb3de5Smillert } 298eefb3de5Smillert desc->http_rescode = strdup(value); 299eefb3de5Smillert if (desc->http_rescode == NULL) { 300eefb3de5Smillert free(line); 301eefb3de5Smillert goto fail; 302eefb3de5Smillert } 303eefb3de5Smillert desc->http_resmesg = strchr(desc->http_rescode, 304eefb3de5Smillert ' '); 305eefb3de5Smillert if (desc->http_resmesg == NULL) { 306eefb3de5Smillert free(line); 307eefb3de5Smillert goto fail; 308eefb3de5Smillert } 309eefb3de5Smillert *desc->http_resmesg++ = '\0'; 310eefb3de5Smillert desc->http_resmesg = strdup(desc->http_resmesg); 311eefb3de5Smillert if (desc->http_resmesg == NULL) { 312eefb3de5Smillert free(line); 313eefb3de5Smillert goto fail; 314eefb3de5Smillert } 315eefb3de5Smillert desc->http_status = strtonum(desc->http_rescode, 316eefb3de5Smillert 100, 599, &errstr); 317eefb3de5Smillert if (errstr) { 318eefb3de5Smillert DPRINTF( 319eefb3de5Smillert "%s: http_status %s: errno %d, %s", 320eefb3de5Smillert __func__, desc->http_rescode, errno, 321eefb3de5Smillert errstr); 322eefb3de5Smillert free(line); 323eefb3de5Smillert goto fail; 324eefb3de5Smillert } 325eefb3de5Smillert DPRINTF("http_version %s http_rescode %s " 326eefb3de5Smillert "http_resmesg %s", desc->http_version, 327eefb3de5Smillert desc->http_rescode, desc->http_resmesg); 328eefb3de5Smillert } else if (cre->dir == RELAY_DIR_REQUEST) { 329eefb3de5Smillert desc->http_method = 330eefb3de5Smillert relay_httpmethod_byname(key); 331eefb3de5Smillert if (desc->http_method == HTTP_METHOD_NONE) { 332eefb3de5Smillert free(line); 333eefb3de5Smillert goto fail; 334eefb3de5Smillert } 335eefb3de5Smillert if ((hmn = calloc(1, sizeof *hmn)) == NULL) { 336eefb3de5Smillert free(line); 337eefb3de5Smillert goto fail; 338eefb3de5Smillert } 339eefb3de5Smillert hmn->hmn_method = desc->http_method; 340eefb3de5Smillert DPRINTF("%s: session %d enqueuing %s", 341eefb3de5Smillert __func__, con->se_id, 342eefb3de5Smillert relay_httpmethod_byid(hmn->hmn_method)); 343eefb3de5Smillert SIMPLEQ_INSERT_TAIL(&hs->hs_methods, hmn, 344eefb3de5Smillert hmn_entry); 345eefb3de5Smillert /* 346eefb3de5Smillert * Decode request path and query 347eefb3de5Smillert */ 348eefb3de5Smillert desc->http_path = strdup(value); 349eefb3de5Smillert if (desc->http_path == NULL) { 350eefb3de5Smillert free(line); 351eefb3de5Smillert goto fail; 352eefb3de5Smillert } 353eefb3de5Smillert desc->http_version = strchr(desc->http_path, 354eefb3de5Smillert ' '); 355eefb3de5Smillert if (desc->http_version == NULL) { 356eefb3de5Smillert free(line); 357eefb3de5Smillert goto fail; 358eefb3de5Smillert } 359eefb3de5Smillert *desc->http_version++ = '\0'; 360eefb3de5Smillert desc->http_query = strchr(desc->http_path, '?'); 361eefb3de5Smillert if (desc->http_query != NULL) 362eefb3de5Smillert *desc->http_query++ = '\0'; 363eefb3de5Smillert 364eefb3de5Smillert /* 365eefb3de5Smillert * Have to allocate the strings because they 366eefb3de5Smillert * could be changed independently by the 367eefb3de5Smillert * filters later. 368eefb3de5Smillert */ 369eefb3de5Smillert if ((desc->http_version = 370eefb3de5Smillert strdup(desc->http_version)) == NULL) { 371eefb3de5Smillert free(line); 372eefb3de5Smillert goto fail; 373eefb3de5Smillert } 374eefb3de5Smillert if (desc->http_query != NULL && 375eefb3de5Smillert (desc->http_query = 376eefb3de5Smillert strdup(desc->http_query)) == NULL) { 377eefb3de5Smillert free(line); 378eefb3de5Smillert goto fail; 3791c543edcSmillert } 3801c543edcSmillert } 381eefb3de5Smillert 382eefb3de5Smillert free(line); 383eefb3de5Smillert continue; 384eefb3de5Smillert } 385eefb3de5Smillert 386eefb3de5Smillert /* Multiline headers wrap with a space or tab. */ 387eefb3de5Smillert if (*line == ' ' || *line == '\t') { 388eefb3de5Smillert if (cre->line == 2) { 389eefb3de5Smillert /* First header line cannot start with space. */ 390a15b848eSreyk relay_abort_http(con, 400, "malformed", 0); 3911c543edcSmillert goto abort; 392a15b848eSreyk } 393a15b848eSreyk 394cb8b0e56Sreyk /* Append line to the last header, if present */ 395c307a266Sreyk if (kv_extend(&desc->http_headers, 396c307a266Sreyk desc->http_lastheader, line) == NULL) { 397a15b848eSreyk free(line); 398a15b848eSreyk goto fail; 399a15b848eSreyk } 400cb8b0e56Sreyk 401a15b848eSreyk free(line); 402a15b848eSreyk continue; 403a15b848eSreyk } 4041c543edcSmillert 405eefb3de5Smillert /* Process the last complete header line. */ 406eefb3de5Smillert last_header: 407eefb3de5Smillert if (desc->http_lastheader != NULL) { 408eefb3de5Smillert key = desc->http_lastheader->kv_key; 409eefb3de5Smillert value = desc->http_lastheader->kv_value; 410a15b848eSreyk 411cb8b0e56Sreyk DPRINTF("%s: session %d: header '%s: %s'", __func__, 412cb8b0e56Sreyk con->se_id, key, value); 413a15b848eSreyk 414eefb3de5Smillert if (desc->http_method != HTTP_METHOD_NONE && 415cb8b0e56Sreyk strcasecmp("Content-Length", key) == 0) { 416f5376943Smillert switch (desc->http_method) { 417f5376943Smillert case HTTP_METHOD_TRACE: 418f5376943Smillert case HTTP_METHOD_CONNECT: 419cb8b0e56Sreyk /* 420055a638bSbenno * These methods should not have a body 421cb8b0e56Sreyk * and thus no Content-Length header. 422cb8b0e56Sreyk */ 423eefb3de5Smillert relay_abort_http(con, 400, "malformed", 424eefb3de5Smillert 0); 425a15b848eSreyk goto abort; 426f5376943Smillert case HTTP_METHOD_GET: 427f5376943Smillert case HTTP_METHOD_HEAD: 428f5376943Smillert case HTTP_METHOD_COPY: 429f5376943Smillert case HTTP_METHOD_MOVE: 430055a638bSbenno /* 431f5376943Smillert * We strip the body (if present) from 432f5376943Smillert * the GET, HEAD, COPY and MOVE methods 433f5376943Smillert * so strip Content-Length too. 43453e8df0dSbenno */ 435f5376943Smillert kv_delete(&desc->http_headers, 436f5376943Smillert desc->http_lastheader); 437f5376943Smillert break; 438*79db477bSanton case HTTP_METHOD_RESPONSE: 439*79db477bSanton if (request_method == HTTP_METHOD_HEAD) 440*79db477bSanton break; 441*79db477bSanton /* FALLTHROUGH */ 442f5376943Smillert default: 44353e8df0dSbenno /* 444eefb3de5Smillert * Need to read data from the client 445eefb3de5Smillert * after the HTTP header. 446eefb3de5Smillert * XXX What about non-standard clients 447eefb3de5Smillert * not using the carriage return? And 448eefb3de5Smillert * some browsers seem to include the 449eefb3de5Smillert * line length in the content-length. 450a15b848eSreyk */ 4511c543edcSmillert if (*value == '+' || *value == '-') { 4521c543edcSmillert errstr = "invalid"; 4531c543edcSmillert } else { 4541c543edcSmillert cre->toread = strtonum(value, 0, 4551c543edcSmillert LLONG_MAX, &errstr); 4561c543edcSmillert } 457a15b848eSreyk if (errstr) { 458eefb3de5Smillert relay_abort_http(con, 500, 459eefb3de5Smillert errstr, 0); 460a15b848eSreyk goto abort; 461a15b848eSreyk } 462f5376943Smillert break; 46353e8df0dSbenno } 464feb6514bSbenno /* 465eefb3de5Smillert * Response with a status code of 1xx 466feb6514bSbenno * (Informational) or 204 (No Content) MUST 467feb6514bSbenno * not have a Content-Length (rfc 7230 3.3.3) 468eefb3de5Smillert * Instead we check for value != 0 because there 469eefb3de5Smillert * are servers that do not follow the rfc and 470eefb3de5Smillert * send Content-Length: 0. 471feb6514bSbenno */ 472eefb3de5Smillert if (desc->http_method == HTTP_METHOD_RESPONSE && 473eefb3de5Smillert (((desc->http_status >= 100 && 474feb6514bSbenno desc->http_status < 200) || 475feb6514bSbenno desc->http_status == 204)) && 476feb6514bSbenno cre->toread != 0) { 477feb6514bSbenno relay_abort_http(con, 502, 478feb6514bSbenno "Bad Gateway", 0); 479feb6514bSbenno goto abort; 480feb6514bSbenno } 481a15b848eSreyk } 4821c543edcSmillert if (strcasecmp("Transfer-Encoding", key) == 0) { 4831c543edcSmillert /* We don't support other encodings. */ 4841c543edcSmillert if (strcasecmp("chunked", value) != 0) { 4851c543edcSmillert relay_abort_http(con, 400, 4861c543edcSmillert "malformed", 0); 4871c543edcSmillert goto abort; 4881c543edcSmillert } 489cb8b0e56Sreyk desc->http_chunked = 1; 4901c543edcSmillert } 491a15b848eSreyk 492c9822c56Sreyk if (strcasecmp("Host", key) == 0) { 493c9822c56Sreyk /* 494c9822c56Sreyk * The path may contain a URL. The host in the 495c9822c56Sreyk * URL has to match the Host: value. 496c9822c56Sreyk */ 497c9822c56Sreyk if (parse_url(desc->http_path, 498c9822c56Sreyk &urlproto, &host, &path) == 0) { 499c9822c56Sreyk ret = strcasecmp(host, value); 500c9822c56Sreyk free(urlproto); 501c9822c56Sreyk free(host); 502c9822c56Sreyk free(path); 503c9822c56Sreyk if (ret != 0) { 504c9822c56Sreyk relay_abort_http(con, 400, 505c9822c56Sreyk "malformed host", 0); 506c9822c56Sreyk goto abort; 507c9822c56Sreyk } 508c9822c56Sreyk } 509eefb3de5Smillert } 510eefb3de5Smillert } 511c9822c56Sreyk 512eefb3de5Smillert if (cre->done) 513eefb3de5Smillert break; 514eefb3de5Smillert 515eefb3de5Smillert /* Validate header field name and check for missing value. */ 516eefb3de5Smillert key = line; 517eefb3de5Smillert if ((value = strchr(line, ':')) == NULL) { 518eefb3de5Smillert relay_abort_http(con, 400, "malformed", 0); 519eefb3de5Smillert goto abort; 520eefb3de5Smillert } 521eefb3de5Smillert *value++ = '\0'; 522eefb3de5Smillert value += strspn(value, " \t\r\n"); 523eefb3de5Smillert 524eefb3de5Smillert if (!relay_http_header_name_valid(key)) { 525eefb3de5Smillert relay_abort_http(con, 400, "malformed", 0); 526eefb3de5Smillert goto abort; 527eefb3de5Smillert } 528eefb3de5Smillert 529eefb3de5Smillert /* The "Host" header must only occur once. */ 530eefb3de5Smillert unique = strcasecmp("Host", key) == 0; 531eefb3de5Smillert 532cb8b0e56Sreyk if ((hdr = kv_add(&desc->http_headers, key, 533c9822c56Sreyk value, unique)) == NULL) { 534eefb3de5Smillert relay_abort_http(con, 400, "malformed header", 0); 535c9822c56Sreyk goto abort; 536a15b848eSreyk } 537c307a266Sreyk desc->http_lastheader = hdr; 538cb8b0e56Sreyk 539a15b848eSreyk free(line); 540a15b848eSreyk } 541eefb3de5Smillert 542a15b848eSreyk if (cre->done) { 543cb8b0e56Sreyk if (desc->http_method == HTTP_METHOD_NONE) { 544cb8b0e56Sreyk relay_abort_http(con, 406, "no method", 0); 545a15b848eSreyk return; 546a15b848eSreyk } 547a15b848eSreyk 548cb8b0e56Sreyk action = relay_test(proto, cre); 5499357f4aaSbenno switch (action) { 5509357f4aaSbenno case RES_FAIL: 5510be9d00aSbenno relay_close(con, "filter rule failed", 1); 552a15b848eSreyk return; 5539357f4aaSbenno case RES_BAD: 5549357f4aaSbenno relay_abort_http(con, 400, "Bad Request", 5559357f4aaSbenno con->se_label); 5569357f4aaSbenno return; 5579357f4aaSbenno case RES_INTERNAL: 5589357f4aaSbenno relay_abort_http(con, 500, "Internal Server Error", 5599357f4aaSbenno con->se_label); 5609357f4aaSbenno return; 5619357f4aaSbenno } 5629357f4aaSbenno if (action != RES_PASS) { 563cb8b0e56Sreyk relay_abort_http(con, 403, "Forbidden", con->se_label); 564cb8b0e56Sreyk return; 565cb8b0e56Sreyk } 566cb8b0e56Sreyk 567a168ef1aSreyk /* 568a168ef1aSreyk * HTTP 101 Switching Protocols 569a168ef1aSreyk */ 57003c01c05Sbenno 571a168ef1aSreyk upgrade = kv_find_value(&desc->http_headers, 572a168ef1aSreyk "Connection", "upgrade", ","); 573a168ef1aSreyk upgrade_ws = kv_find_value(&desc->http_headers, 574a168ef1aSreyk "Upgrade", "websocket", ","); 57503c01c05Sbenno ws_response = 0; 576a168ef1aSreyk if (cre->dir == RELAY_DIR_REQUEST && upgrade_ws != NULL) { 577a168ef1aSreyk if ((proto->httpflags & HTTPFLAG_WEBSOCKETS) == 0) { 578e7742cb1Sbenno relay_abort_http(con, 403, 579e7742cb1Sbenno "Websocket Forbidden", 0); 580e7742cb1Sbenno return; 581a168ef1aSreyk } else if (upgrade == NULL) { 582e7742cb1Sbenno relay_abort_http(con, 400, 583e7742cb1Sbenno "Bad Websocket Request", 0); 584e7742cb1Sbenno return; 585a168ef1aSreyk } else if (desc->http_method != HTTP_METHOD_GET) { 586e7742cb1Sbenno relay_abort_http(con, 405, 587e7742cb1Sbenno "Websocket Method Not Allowed", 0); 588e7742cb1Sbenno return; 589e7742cb1Sbenno } 590e7742cb1Sbenno } else if (cre->dir == RELAY_DIR_RESPONSE && 591e7742cb1Sbenno desc->http_status == 101) { 592a168ef1aSreyk if (upgrade_ws != NULL && upgrade != NULL && 593a168ef1aSreyk (proto->httpflags & HTTPFLAG_WEBSOCKETS)) { 59403c01c05Sbenno ws_response = 1; 595e7742cb1Sbenno cre->dst->toread = TOREAD_UNLIMITED; 596e7742cb1Sbenno cre->dst->bev->readcb = relay_read; 597e7742cb1Sbenno } else { 598e7742cb1Sbenno relay_abort_http(con, 502, 599e7742cb1Sbenno "Bad Websocket Gateway", 0); 600e7742cb1Sbenno return; 601e7742cb1Sbenno } 602e7742cb1Sbenno } 603e7742cb1Sbenno 60403c01c05Sbenno connection_close = kv_find_value(&desc->http_headers, 60503c01c05Sbenno "Connection", "close", ","); 60603c01c05Sbenno 607cb8b0e56Sreyk switch (desc->http_method) { 608a15b848eSreyk case HTTP_METHOD_CONNECT: 609a15b848eSreyk /* Data stream */ 610a9feb0c6Sbluhm cre->toread = TOREAD_UNLIMITED; 611a15b848eSreyk bev->readcb = relay_read; 612a15b848eSreyk break; 613a15b848eSreyk case HTTP_METHOD_GET: 614a15b848eSreyk case HTTP_METHOD_HEAD: 61596e6e983Sreyk /* WebDAV methods */ 61696e6e983Sreyk case HTTP_METHOD_COPY: 61796e6e983Sreyk case HTTP_METHOD_MOVE: 6188f8f77d7Sbluhm cre->toread = 0; 61905adfb5fSbenno break; 620d45e8c85Sreyk case HTTP_METHOD_DELETE: 621986dfe8aSreyk case HTTP_METHOD_OPTIONS: 622a15b848eSreyk case HTTP_METHOD_POST: 623a15b848eSreyk case HTTP_METHOD_PUT: 624a15b848eSreyk case HTTP_METHOD_RESPONSE: 62596e6e983Sreyk /* WebDAV methods */ 62696e6e983Sreyk case HTTP_METHOD_PROPFIND: 62796e6e983Sreyk case HTTP_METHOD_PROPPATCH: 62896e6e983Sreyk case HTTP_METHOD_MKCOL: 62996e6e983Sreyk case HTTP_METHOD_LOCK: 63096e6e983Sreyk case HTTP_METHOD_UNLOCK: 63196e6e983Sreyk case HTTP_METHOD_VERSION_CONTROL: 63296e6e983Sreyk case HTTP_METHOD_REPORT: 63396e6e983Sreyk case HTTP_METHOD_CHECKOUT: 63496e6e983Sreyk case HTTP_METHOD_CHECKIN: 63596e6e983Sreyk case HTTP_METHOD_UNCHECKOUT: 63696e6e983Sreyk case HTTP_METHOD_MKWORKSPACE: 63796e6e983Sreyk case HTTP_METHOD_UPDATE: 63896e6e983Sreyk case HTTP_METHOD_LABEL: 63996e6e983Sreyk case HTTP_METHOD_MERGE: 64096e6e983Sreyk case HTTP_METHOD_BASELINE_CONTROL: 64196e6e983Sreyk case HTTP_METHOD_MKACTIVITY: 64296e6e983Sreyk case HTTP_METHOD_ORDERPATCH: 64396e6e983Sreyk case HTTP_METHOD_ACL: 64496e6e983Sreyk case HTTP_METHOD_MKREDIRECTREF: 64596e6e983Sreyk case HTTP_METHOD_UPDATEREDIRECTREF: 64696e6e983Sreyk case HTTP_METHOD_SEARCH: 64796e6e983Sreyk case HTTP_METHOD_PATCH: 648a15b848eSreyk /* HTTP request payload */ 64953e8df0dSbenno if (cre->toread > 0) { 650a15b848eSreyk bev->readcb = relay_read_httpcontent; 65153e8df0dSbenno } 652a15b848eSreyk 6538f8f77d7Sbluhm /* Single-pass HTTP body */ 6548f8f77d7Sbluhm if (cre->toread < 0) { 655a9feb0c6Sbluhm cre->toread = TOREAD_UNLIMITED; 656a15b848eSreyk bev->readcb = relay_read; 657a9feb0c6Sbluhm } 658a15b848eSreyk break; 659a15b848eSreyk default: 660a15b848eSreyk /* HTTP handler */ 661a9feb0c6Sbluhm cre->toread = TOREAD_HTTP_HEADER; 662a15b848eSreyk bev->readcb = relay_read_http; 663a15b848eSreyk break; 664a15b848eSreyk } 665cb8b0e56Sreyk if (desc->http_chunked) { 666a15b848eSreyk /* Chunked transfer encoding */ 667a9feb0c6Sbluhm cre->toread = TOREAD_HTTP_CHUNK_LENGTH; 668a15b848eSreyk bev->readcb = relay_read_httpchunks; 669a15b848eSreyk } 670a15b848eSreyk 6712c58e087Sreyk /* 6722c58e087Sreyk * Ask the server to close the connection after this request 67303c01c05Sbenno * since we don't read any further request headers. Only add 67403c01c05Sbenno * this header if it does not already exist or if this is a 67503c01c05Sbenno * outbound websocket upgrade response. 6762c58e087Sreyk */ 67703c01c05Sbenno if (cre->toread == TOREAD_UNLIMITED && 67803c01c05Sbenno connection_close == NULL && !ws_response) 6792c58e087Sreyk if (kv_add(&desc->http_headers, "Connection", 6802c58e087Sreyk "close", 0) == NULL) 6812c58e087Sreyk goto fail; 6822c58e087Sreyk 683cb8b0e56Sreyk if (cre->dir == RELAY_DIR_REQUEST) { 684cb8b0e56Sreyk if (relay_writerequest_http(cre->dst, cre) == -1) 685cb8b0e56Sreyk goto fail; 686cb8b0e56Sreyk } else { 687cb8b0e56Sreyk if (relay_writeresponse_http(cre->dst, cre) == -1) 688cb8b0e56Sreyk goto fail; 689cb8b0e56Sreyk } 690cb8b0e56Sreyk if (relay_bufferevent_print(cre->dst, "\r\n") == -1 || 691cb8b0e56Sreyk relay_writeheader_http(cre->dst, cre) == -1 || 692cb8b0e56Sreyk relay_bufferevent_print(cre->dst, "\r\n") == -1) 693a15b848eSreyk goto fail; 694a15b848eSreyk 695cb8b0e56Sreyk relay_reset_http(cre); 696a15b848eSreyk done: 697412ad10bSbluhm if (cre->dir == RELAY_DIR_REQUEST && cre->toread <= 0 && 698b40fcd2fSreyk cre->dst->state != STATE_CONNECTED) { 699a15b848eSreyk if (rlay->rl_conf.fwdmode == FWD_TRANS) { 700a15b848eSreyk relay_bindanyreq(con, 0, IPPROTO_TCP); 701a15b848eSreyk return; 702a15b848eSreyk } 7031e4bf96aSbluhm if (relay_connect(con) == -1) { 704a15b848eSreyk relay_abort_http(con, 502, "session failed", 0); 705a15b848eSreyk return; 706a15b848eSreyk } 707a15b848eSreyk } 7081e4bf96aSbluhm } 709a15b848eSreyk if (con->se_done) { 7100be9d00aSbenno relay_close(con, "last http read (done)", 0); 711a15b848eSreyk return; 712a15b848eSreyk } 71370d03dadSbluhm switch (relay_splice(cre)) { 71470d03dadSbluhm case -1: 7150be9d00aSbenno relay_close(con, strerror(errno), 1); 71670d03dadSbluhm case 1: 71770d03dadSbluhm return; 71870d03dadSbluhm case 0: 71970d03dadSbluhm break; 72070d03dadSbluhm } 72170d03dadSbluhm bufferevent_enable(bev, EV_READ); 722a15b848eSreyk if (EVBUFFER_LENGTH(src) && bev->readcb != relay_read_http) 723a15b848eSreyk bev->readcb(bev, arg); 72470d03dadSbluhm /* The callback readcb() might have freed the session. */ 725a15b848eSreyk return; 726a15b848eSreyk fail: 727a15b848eSreyk relay_abort_http(con, 500, strerror(errno), 0); 728a15b848eSreyk return; 729a15b848eSreyk abort: 730a15b848eSreyk free(line); 731a15b848eSreyk } 732a15b848eSreyk 733a15b848eSreyk void 734a15b848eSreyk relay_read_httpcontent(struct bufferevent *bev, void *arg) 735a15b848eSreyk { 73648240b8fSbluhm struct ctl_relay_event *cre = arg; 737a15b848eSreyk struct rsession *con = cre->con; 738807a0a46Sbenno struct protocol *proto = con->se_relay->rl_proto; 739807a0a46Sbenno 740a15b848eSreyk struct evbuffer *src = EVBUFFER_INPUT(bev); 741a15b848eSreyk size_t size; 742a15b848eSreyk 743fd1841a3Sreyk getmonotime(&con->se_tv_last); 74430791b79Sbluhm cre->timedout = 0; 745fd1841a3Sreyk 746a15b848eSreyk size = EVBUFFER_LENGTH(src); 747cb8b0e56Sreyk DPRINTF("%s: session %d: size %lu, to read %lld", __func__, 748cb8b0e56Sreyk con->se_id, size, cre->toread); 749a15b848eSreyk if (!size) 750a15b848eSreyk return; 751c58e32e8Sbluhm if (relay_spliceadjust(cre) == -1) 752c58e32e8Sbluhm goto fail; 753a9feb0c6Sbluhm 754a9feb0c6Sbluhm if (cre->toread > 0) { 755a9feb0c6Sbluhm /* Read content data */ 756a9feb0c6Sbluhm if ((off_t)size > cre->toread) { 757a9feb0c6Sbluhm size = cre->toread; 758a9feb0c6Sbluhm if (relay_bufferevent_write_chunk(cre->dst, src, size) 759a9feb0c6Sbluhm == -1) 760a9feb0c6Sbluhm goto fail; 761a9feb0c6Sbluhm cre->toread = 0; 762a9feb0c6Sbluhm } else { 763a15b848eSreyk if (relay_bufferevent_write_buffer(cre->dst, src) == -1) 764a15b848eSreyk goto fail; 765a15b848eSreyk cre->toread -= size; 766a9feb0c6Sbluhm } 7670ccd53b9Sguenther DPRINTF("%s: done, size %lu, to read %lld", __func__, 768a15b848eSreyk size, cre->toread); 769a9feb0c6Sbluhm } 770a9feb0c6Sbluhm if (cre->toread == 0) { 771a9feb0c6Sbluhm cre->toread = TOREAD_HTTP_HEADER; 772a9feb0c6Sbluhm bev->readcb = relay_read_http; 773a9feb0c6Sbluhm } 774a15b848eSreyk if (con->se_done) 775a15b848eSreyk goto done; 77670d03dadSbluhm bufferevent_enable(bev, EV_READ); 777807a0a46Sbenno 778807a0a46Sbenno if (cre->dst->bev && EVBUFFER_LENGTH(EVBUFFER_OUTPUT(cre->dst->bev)) > 779807a0a46Sbenno (size_t)RELAY_MAX_PREFETCH * proto->tcpbufsiz) 780807a0a46Sbenno bufferevent_disable(cre->bev, EV_READ); 781807a0a46Sbenno 782a15b848eSreyk if (bev->readcb != relay_read_httpcontent) 783a15b848eSreyk bev->readcb(bev, arg); 78470d03dadSbluhm /* The callback readcb() might have freed the session. */ 785a15b848eSreyk return; 786a15b848eSreyk done: 7870be9d00aSbenno relay_close(con, "last http content read", 0); 788a15b848eSreyk return; 789a15b848eSreyk fail: 7900be9d00aSbenno relay_close(con, strerror(errno), 1); 791a15b848eSreyk } 792a15b848eSreyk 793a15b848eSreyk void 794a15b848eSreyk relay_read_httpchunks(struct bufferevent *bev, void *arg) 795a15b848eSreyk { 79648240b8fSbluhm struct ctl_relay_event *cre = arg; 797a15b848eSreyk struct rsession *con = cre->con; 798807a0a46Sbenno struct protocol *proto = con->se_relay->rl_proto; 799a15b848eSreyk struct evbuffer *src = EVBUFFER_INPUT(bev); 8001c543edcSmillert char *line, *ep; 801222e5e8aSreyk long long llval; 802536becf6Sbluhm size_t size, linelen; 803a15b848eSreyk 804fd1841a3Sreyk getmonotime(&con->se_tv_last); 80530791b79Sbluhm cre->timedout = 0; 806fd1841a3Sreyk 807a15b848eSreyk size = EVBUFFER_LENGTH(src); 808cb8b0e56Sreyk DPRINTF("%s: session %d: size %lu, to read %lld", __func__, 809cb8b0e56Sreyk con->se_id, size, cre->toread); 810a15b848eSreyk if (!size) 811a15b848eSreyk return; 812c58e32e8Sbluhm if (relay_spliceadjust(cre) == -1) 813c58e32e8Sbluhm goto fail; 814a15b848eSreyk 815a9feb0c6Sbluhm if (cre->toread > 0) { 816a9feb0c6Sbluhm /* Read chunk data */ 817a9feb0c6Sbluhm if ((off_t)size > cre->toread) { 818a9feb0c6Sbluhm size = cre->toread; 819a9feb0c6Sbluhm if (relay_bufferevent_write_chunk(cre->dst, src, size) 820a9feb0c6Sbluhm == -1) 821a9feb0c6Sbluhm goto fail; 822a9feb0c6Sbluhm cre->toread = 0; 823a9feb0c6Sbluhm } else { 824a9feb0c6Sbluhm if (relay_bufferevent_write_buffer(cre->dst, src) == -1) 825a9feb0c6Sbluhm goto fail; 826a9feb0c6Sbluhm cre->toread -= size; 827a9feb0c6Sbluhm } 828a9feb0c6Sbluhm DPRINTF("%s: done, size %lu, to read %lld", __func__, 829a9feb0c6Sbluhm size, cre->toread); 830a9feb0c6Sbluhm } 831a9feb0c6Sbluhm switch (cre->toread) { 832a9feb0c6Sbluhm case TOREAD_HTTP_CHUNK_LENGTH: 833536becf6Sbluhm line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF); 834a15b848eSreyk if (line == NULL) { 835a15b848eSreyk /* Ignore empty line, continue */ 836a15b848eSreyk bufferevent_enable(bev, EV_READ); 837a15b848eSreyk return; 838a15b848eSreyk } 839536becf6Sbluhm if (linelen == 0) { 840a15b848eSreyk free(line); 841a15b848eSreyk goto next; 842a15b848eSreyk } 843a15b848eSreyk 844222e5e8aSreyk /* 8451c543edcSmillert * Read prepended chunk size in hex without leading +0[Xx]. 846222e5e8aSreyk * The returned signed value must not be negative. 847222e5e8aSreyk */ 8481c543edcSmillert if (line[0] == '+' || line[0] == '-' || 8491c543edcSmillert (line[0] == '0' && (line[1] == 'x' || line[1] == 'X'))) { 8501c543edcSmillert /* Reject values like 0xdead and 0XBEEF or +FEED. */ 8511c543edcSmillert ep = line; 8521c543edcSmillert } else { 8531c543edcSmillert errno = 0; 8541c543edcSmillert llval = strtoll(line, &ep, 16); 8551c543edcSmillert } 8561c543edcSmillert if (ep == line || *ep != '\0' || llval < 0 || 8571c543edcSmillert (errno == ERANGE && llval == LLONG_MAX)) { 858a15b848eSreyk free(line); 8590be9d00aSbenno relay_close(con, "invalid chunk size", 1); 860a15b848eSreyk return; 861a15b848eSreyk } 862a15b848eSreyk 863a15b848eSreyk if (relay_bufferevent_print(cre->dst, line) == -1 || 864a15b848eSreyk relay_bufferevent_print(cre->dst, "\r\n") == -1) { 865a15b848eSreyk free(line); 866a15b848eSreyk goto fail; 867a15b848eSreyk } 868a15b848eSreyk free(line); 869a15b848eSreyk 870222e5e8aSreyk if ((cre->toread = llval) == 0) { 871a15b848eSreyk DPRINTF("%s: last chunk", __func__); 872a9feb0c6Sbluhm cre->toread = TOREAD_HTTP_CHUNK_TRAILER; 873a9feb0c6Sbluhm } 874a9feb0c6Sbluhm break; 875a9feb0c6Sbluhm case TOREAD_HTTP_CHUNK_TRAILER: 876a9feb0c6Sbluhm /* Last chunk is 0 bytes followed by trailer and empty line */ 877536becf6Sbluhm line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF); 878a15b848eSreyk if (line == NULL) { 879a9feb0c6Sbluhm /* Ignore empty line, continue */ 880a9feb0c6Sbluhm bufferevent_enable(bev, EV_READ); 881a15b848eSreyk return; 882a15b848eSreyk } 883a9feb0c6Sbluhm if (relay_bufferevent_print(cre->dst, line) == -1 || 884a9feb0c6Sbluhm relay_bufferevent_print(cre->dst, "\r\n") == -1) { 885a15b848eSreyk free(line); 886a15b848eSreyk goto fail; 887a9feb0c6Sbluhm } 888536becf6Sbluhm if (linelen == 0) { 889a15b848eSreyk /* Switch to HTTP header mode */ 890a9feb0c6Sbluhm cre->toread = TOREAD_HTTP_HEADER; 891a15b848eSreyk bev->readcb = relay_read_http; 892a15b848eSreyk } 893a9feb0c6Sbluhm free(line); 894a9feb0c6Sbluhm break; 895a9feb0c6Sbluhm case 0: 896a9feb0c6Sbluhm /* Chunk is terminated by an empty newline */ 897536becf6Sbluhm line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF); 898a15b848eSreyk free(line); 8998104d8e4Sreyk if (relay_bufferevent_print(cre->dst, "\r\n") == -1) 900a15b848eSreyk goto fail; 901a9feb0c6Sbluhm cre->toread = TOREAD_HTTP_CHUNK_LENGTH; 902a9feb0c6Sbluhm break; 903a15b848eSreyk } 904a15b848eSreyk 905a15b848eSreyk next: 906a15b848eSreyk if (con->se_done) 907a15b848eSreyk goto done; 90870d03dadSbluhm bufferevent_enable(bev, EV_READ); 909807a0a46Sbenno 910807a0a46Sbenno if (cre->dst->bev && EVBUFFER_LENGTH(EVBUFFER_OUTPUT(cre->dst->bev)) > 911807a0a46Sbenno (size_t)RELAY_MAX_PREFETCH * proto->tcpbufsiz) 912807a0a46Sbenno bufferevent_disable(cre->bev, EV_READ); 913807a0a46Sbenno 914a15b848eSreyk if (EVBUFFER_LENGTH(src)) 915a15b848eSreyk bev->readcb(bev, arg); 91670d03dadSbluhm /* The callback readcb() might have freed the session. */ 917a15b848eSreyk return; 918a15b848eSreyk 919a15b848eSreyk done: 9200be9d00aSbenno relay_close(con, "last http chunk read (done)", 0); 921a15b848eSreyk return; 922a15b848eSreyk fail: 9230be9d00aSbenno relay_close(con, strerror(errno), 1); 924a15b848eSreyk } 925a15b848eSreyk 926a15b848eSreyk void 927cb8b0e56Sreyk relay_reset_http(struct ctl_relay_event *cre) 928a15b848eSreyk { 929cb8b0e56Sreyk struct http_descriptor *desc = cre->desc; 930a15b848eSreyk 931cb8b0e56Sreyk relay_httpdesc_free(desc); 932cb8b0e56Sreyk desc->http_method = 0; 933cb8b0e56Sreyk desc->http_chunked = 0; 934d6db3fedSreyk cre->headerlen = 0; 935a15b848eSreyk cre->line = 0; 936a15b848eSreyk cre->done = 0; 937a15b848eSreyk } 938a15b848eSreyk 939a15b848eSreyk static int 940a15b848eSreyk _relay_lookup_url(struct ctl_relay_event *cre, char *host, char *path, 941cb8b0e56Sreyk char *query, struct kv *kv) 942a15b848eSreyk { 943a15b848eSreyk struct rsession *con = cre->con; 944a15b848eSreyk char *val, *md = NULL; 945cb8b0e56Sreyk int ret = RES_FAIL; 946cb8b0e56Sreyk const char *str = NULL; 947a15b848eSreyk 948a15b848eSreyk if (asprintf(&val, "%s%s%s%s", 949a15b848eSreyk host, path, 950a15b848eSreyk query == NULL ? "" : "?", 951a15b848eSreyk query == NULL ? "" : query) == -1) { 952a15b848eSreyk relay_abort_http(con, 500, "failed to allocate URL", 0); 953cb8b0e56Sreyk return (RES_FAIL); 954a15b848eSreyk } 955a15b848eSreyk 956cb8b0e56Sreyk switch (kv->kv_digest) { 957a15b848eSreyk case DIGEST_SHA1: 958a15b848eSreyk case DIGEST_MD5: 959cb8b0e56Sreyk if ((md = digeststr(kv->kv_digest, 960cb8b0e56Sreyk val, strlen(val), NULL)) == NULL) { 961a15b848eSreyk relay_abort_http(con, 500, 962a15b848eSreyk "failed to allocate digest", 0); 963a15b848eSreyk goto fail; 964a15b848eSreyk } 965cb8b0e56Sreyk str = md; 966a15b848eSreyk break; 967a15b848eSreyk case DIGEST_NONE: 968cb8b0e56Sreyk str = val; 969a15b848eSreyk break; 970a15b848eSreyk } 971a15b848eSreyk 972cb8b0e56Sreyk DPRINTF("%s: session %d: %s, %s: %d", __func__, con->se_id, 973cb8b0e56Sreyk str, kv->kv_key, strcasecmp(kv->kv_key, str)); 974a15b848eSreyk 975cb8b0e56Sreyk if (strcasecmp(kv->kv_key, str) == 0) { 976cb8b0e56Sreyk ret = RES_DROP; 977a15b848eSreyk goto fail; 978a15b848eSreyk } 979a15b848eSreyk 980cb8b0e56Sreyk ret = RES_PASS; 981a15b848eSreyk fail: 982a15b848eSreyk free(md); 983a15b848eSreyk free(val); 984a15b848eSreyk return (ret); 985a15b848eSreyk } 986a15b848eSreyk 987a15b848eSreyk int 988cb8b0e56Sreyk relay_lookup_url(struct ctl_relay_event *cre, const char *host, struct kv *kv) 989a15b848eSreyk { 990cb8b0e56Sreyk struct http_descriptor *desc = (struct http_descriptor *)cre->desc; 991a15b848eSreyk int i, j, dots; 992a15b848eSreyk char *hi[RELAY_MAXLOOKUPLEVELS], *p, *pp, *c, ch; 993e2318a52Sderaadt char ph[HOST_NAME_MAX+1]; 994a15b848eSreyk int ret; 995a15b848eSreyk 996cb8b0e56Sreyk if (desc->http_path == NULL) 997cb8b0e56Sreyk return (RES_PASS); 998a15b848eSreyk 999a15b848eSreyk /* 1000a15b848eSreyk * This is an URL lookup algorithm inspired by 1001a15b848eSreyk * http://code.google.com/apis/safebrowsing/ 1002a15b848eSreyk * developers_guide.html#PerformingLookups 1003a15b848eSreyk */ 1004a15b848eSreyk 10059357f4aaSbenno DPRINTF("%s: host '%s', path '%s', query '%s'", 10069357f4aaSbenno __func__, host, desc->http_path, 1007cb8b0e56Sreyk desc->http_query == NULL ? "" : desc->http_query); 1008a15b848eSreyk 1009cb8b0e56Sreyk if (canonicalize_host(host, ph, sizeof(ph)) == NULL) { 10109357f4aaSbenno return (RES_BAD); 1011a15b848eSreyk } 1012a15b848eSreyk 1013a15b848eSreyk bzero(hi, sizeof(hi)); 1014a15b848eSreyk for (dots = -1, i = strlen(ph) - 1; i > 0; i--) { 1015a15b848eSreyk if (ph[i] == '.' && ++dots) 1016a15b848eSreyk hi[dots - 1] = &ph[i + 1]; 1017a15b848eSreyk if (dots > (RELAY_MAXLOOKUPLEVELS - 2)) 1018a15b848eSreyk break; 1019a15b848eSreyk } 1020a15b848eSreyk if (dots == -1) 1021a15b848eSreyk dots = 0; 1022a15b848eSreyk hi[dots] = ph; 1023a15b848eSreyk 1024cb8b0e56Sreyk if ((pp = strdup(desc->http_path)) == NULL) { 10259357f4aaSbenno return (RES_INTERNAL); 1026a15b848eSreyk } 1027a15b848eSreyk for (i = (RELAY_MAXLOOKUPLEVELS - 1); i >= 0; i--) { 1028a15b848eSreyk if (hi[i] == NULL) 1029a15b848eSreyk continue; 1030a15b848eSreyk 1031a15b848eSreyk /* 1. complete path with query */ 1032cb8b0e56Sreyk if (desc->http_query != NULL) 1033a15b848eSreyk if ((ret = _relay_lookup_url(cre, hi[i], 1034cb8b0e56Sreyk pp, desc->http_query, kv)) != RES_PASS) 1035a15b848eSreyk goto done; 1036a15b848eSreyk 1037a15b848eSreyk /* 2. complete path without query */ 1038a15b848eSreyk if ((ret = _relay_lookup_url(cre, hi[i], 1039cb8b0e56Sreyk pp, NULL, kv)) != RES_PASS) 1040a15b848eSreyk goto done; 1041a15b848eSreyk 1042a15b848eSreyk /* 3. traverse path */ 1043a15b848eSreyk for (j = 0, p = strchr(pp, '/'); 1044a15b848eSreyk p != NULL; p = strchr(p, '/'), j++) { 1045cb8b0e56Sreyk if (j > (RELAY_MAXLOOKUPLEVELS - 2) || *(++p) == '\0') 1046a15b848eSreyk break; 1047a15b848eSreyk c = &pp[p - pp]; 1048a15b848eSreyk ch = *c; 1049a15b848eSreyk *c = '\0'; 1050a15b848eSreyk if ((ret = _relay_lookup_url(cre, hi[i], 1051cb8b0e56Sreyk pp, NULL, kv)) != RES_PASS) 1052a15b848eSreyk goto done; 1053a15b848eSreyk *c = ch; 1054a15b848eSreyk } 1055a15b848eSreyk } 1056a15b848eSreyk 1057cb8b0e56Sreyk ret = RES_PASS; 1058a15b848eSreyk done: 1059a15b848eSreyk free(pp); 1060a15b848eSreyk return (ret); 1061a15b848eSreyk } 1062a15b848eSreyk 1063a15b848eSreyk int 1064cb8b0e56Sreyk relay_lookup_cookie(struct ctl_relay_event *cre, const char *str, 1065cb8b0e56Sreyk struct kv *kv) 1066a15b848eSreyk { 1067cb8b0e56Sreyk char *val, *ptr, *key, *value; 1068a15b848eSreyk int ret; 1069a15b848eSreyk 1070a15b848eSreyk if ((val = strdup(str)) == NULL) { 10719357f4aaSbenno return (RES_INTERNAL); 1072a15b848eSreyk } 1073a15b848eSreyk 1074a15b848eSreyk for (ptr = val; ptr != NULL && strlen(ptr);) { 1075a15b848eSreyk if (*ptr == ' ') 1076a15b848eSreyk *ptr++ = '\0'; 1077cb8b0e56Sreyk key = ptr; 1078a15b848eSreyk if ((ptr = strchr(ptr, ';')) != NULL) 1079a15b848eSreyk *ptr++ = '\0'; 1080a15b848eSreyk /* 1081a15b848eSreyk * XXX We do not handle attributes 1082a15b848eSreyk * ($Path, $Domain, or $Port) 1083a15b848eSreyk */ 1084cb8b0e56Sreyk if (*key == '$') 1085a15b848eSreyk continue; 1086a15b848eSreyk 1087cb8b0e56Sreyk if ((value = 1088cb8b0e56Sreyk strchr(key, '=')) == NULL || 1089cb8b0e56Sreyk strlen(value) < 1) 1090a15b848eSreyk continue; 1091cb8b0e56Sreyk *value++ = '\0'; 1092cb8b0e56Sreyk if (*value == '"') 1093cb8b0e56Sreyk *value++ = '\0'; 1094cb8b0e56Sreyk if (value[strlen(value) - 1] == '"') 1095cb8b0e56Sreyk value[strlen(value) - 1] = '\0'; 1096cb8b0e56Sreyk 10979357f4aaSbenno DPRINTF("%s: key %s = %s, %s = %s : %d", 10989357f4aaSbenno __func__, key, value, kv->kv_key, kv->kv_value, 1099cb8b0e56Sreyk strcasecmp(kv->kv_key, key)); 1100cb8b0e56Sreyk 1101cb8b0e56Sreyk if (strcasecmp(kv->kv_key, key) == 0 && 1102cb8b0e56Sreyk ((kv->kv_value == NULL) || 1103cb8b0e56Sreyk (fnmatch(kv->kv_value, value, 1104cb8b0e56Sreyk FNM_CASEFOLD) != FNM_NOMATCH))) { 1105cb8b0e56Sreyk ret = RES_DROP; 1106a15b848eSreyk goto done; 1107a15b848eSreyk } 1108a15b848eSreyk } 1109a15b848eSreyk 1110cb8b0e56Sreyk ret = RES_PASS; 1111cb8b0e56Sreyk 1112a15b848eSreyk done: 1113a15b848eSreyk free(val); 1114a15b848eSreyk return (ret); 1115a15b848eSreyk } 1116a15b848eSreyk 1117cb8b0e56Sreyk int 1118cb8b0e56Sreyk relay_lookup_query(struct ctl_relay_event *cre, struct kv *kv) 1119cb8b0e56Sreyk { 1120cb8b0e56Sreyk struct http_descriptor *desc = cre->desc; 1121cb8b0e56Sreyk struct kv *match = &desc->http_matchquery; 1122cb8b0e56Sreyk char *val, *ptr, *tmpkey = NULL, *tmpval = NULL; 1123cb8b0e56Sreyk int ret = -1; 1124cb8b0e56Sreyk 1125cb8b0e56Sreyk if (desc->http_query == NULL) 1126cb8b0e56Sreyk return (-1); 1127cb8b0e56Sreyk if ((val = strdup(desc->http_query)) == NULL) { 11289357f4aaSbenno return (RES_INTERNAL); 1129cb8b0e56Sreyk } 1130cb8b0e56Sreyk 1131cb8b0e56Sreyk ptr = val; 1132cb8b0e56Sreyk while (ptr != NULL && strlen(ptr)) { 1133cb8b0e56Sreyk tmpkey = ptr; 1134cb8b0e56Sreyk if ((ptr = strchr(ptr, '&')) != NULL) 1135cb8b0e56Sreyk *ptr++ = '\0'; 1136cb8b0e56Sreyk if ((tmpval = strchr(tmpkey, '=')) == NULL || strlen(tmpval) 1137cb8b0e56Sreyk < 1) 1138cb8b0e56Sreyk continue; 1139cb8b0e56Sreyk *tmpval++ = '\0'; 1140cb8b0e56Sreyk 1141cb8b0e56Sreyk if (fnmatch(kv->kv_key, tmpkey, 0) != FNM_NOMATCH && 1142cb8b0e56Sreyk (kv->kv_value == NULL || fnmatch(kv->kv_value, tmpval, 0) 1143cb8b0e56Sreyk != FNM_NOMATCH)) 1144cb8b0e56Sreyk break; 1145cb8b0e56Sreyk else 1146cb8b0e56Sreyk tmpkey = NULL; 1147cb8b0e56Sreyk } 1148cb8b0e56Sreyk 1149cb8b0e56Sreyk if (tmpkey == NULL || tmpval == NULL) 1150cb8b0e56Sreyk goto done; 1151cb8b0e56Sreyk 1152cb8b0e56Sreyk match->kv_key = strdup(tmpkey); 1153cb8b0e56Sreyk if (match->kv_key == NULL) 1154cb8b0e56Sreyk goto done; 1155cb8b0e56Sreyk match->kv_value = strdup(tmpval); 1156cb8b0e56Sreyk if (match->kv_key == NULL) 1157cb8b0e56Sreyk goto done; 1158cb8b0e56Sreyk ret = 0; 1159cb8b0e56Sreyk 1160cb8b0e56Sreyk done: 1161cb8b0e56Sreyk free(val); 1162cb8b0e56Sreyk return (ret); 1163cb8b0e56Sreyk } 1164cb8b0e56Sreyk 11653ad4d242Sreyk ssize_t 11663ad4d242Sreyk relay_http_time(time_t t, char *tmbuf, size_t len) 11673ad4d242Sreyk { 11683ad4d242Sreyk struct tm tm; 11693ad4d242Sreyk 11703ad4d242Sreyk /* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */ 11713ad4d242Sreyk if (t == -1 || gmtime_r(&t, &tm) == NULL) 11723ad4d242Sreyk return (-1); 11733ad4d242Sreyk else 11743ad4d242Sreyk return (strftime(tmbuf, len, "%a, %d %h %Y %T %Z", &tm)); 11753ad4d242Sreyk } 1176cb8b0e56Sreyk 1177a15b848eSreyk void 1178a15b848eSreyk relay_abort_http(struct rsession *con, u_int code, const char *msg, 1179a15b848eSreyk u_int16_t labelid) 1180a15b848eSreyk { 118148240b8fSbluhm struct relay *rlay = con->se_relay; 1182a15b848eSreyk struct bufferevent *bev = con->se_in.bev; 1183543ef491Sreyk const char *httperr = NULL, *text = ""; 11843ad4d242Sreyk char *httpmsg, *body = NULL; 1185a15b848eSreyk char tmbuf[32], hbuf[128]; 1186a15b848eSreyk const char *style, *label = NULL; 11873ad4d242Sreyk int bodylen; 1188a15b848eSreyk 1189543ef491Sreyk if ((httperr = relay_httperror_byid(code)) == NULL) 1190543ef491Sreyk httperr = "Unknown Error"; 1191543ef491Sreyk 1192cb8b0e56Sreyk if (labelid != 0) 1193cb8b0e56Sreyk label = label_id2name(labelid); 1194cb8b0e56Sreyk 1195a15b848eSreyk /* In some cases this function may be called from generic places */ 1196a15b848eSreyk if (rlay->rl_proto->type != RELAY_PROTO_HTTP || 1197a15b848eSreyk (rlay->rl_proto->flags & F_RETURN) == 0) { 11980be9d00aSbenno relay_close(con, msg, 0); 1199a15b848eSreyk return; 1200a15b848eSreyk } 1201a15b848eSreyk 1202a15b848eSreyk if (bev == NULL) 1203a15b848eSreyk goto done; 1204a15b848eSreyk 1205a15b848eSreyk /* Some system information */ 1206a15b848eSreyk if (print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) == NULL) 1207a15b848eSreyk goto done; 1208a15b848eSreyk 12093ad4d242Sreyk if (relay_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0) 12103ad4d242Sreyk goto done; 1211a15b848eSreyk 1212a15b848eSreyk /* Do not send details of the Internal Server Error */ 12133ad4d242Sreyk switch (code) { 12143ad4d242Sreyk case 500: 12153ad4d242Sreyk break; 12163ad4d242Sreyk default: 1217a15b848eSreyk text = msg; 12183ad4d242Sreyk break; 12193ad4d242Sreyk } 1220a15b848eSreyk 1221a15b848eSreyk /* A CSS stylesheet allows minimal customization by the user */ 12223ad4d242Sreyk style = (rlay->rl_proto->style != NULL) ? rlay->rl_proto->style : 12233ad4d242Sreyk "body { background-color: #a00000; color: white; font-family: " 12243ad4d242Sreyk "'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }\n" 12253ad4d242Sreyk "hr { border: 0; border-bottom: 1px dashed; }\n"; 1226a15b848eSreyk 1227a15b848eSreyk /* Generate simple HTTP+HTML error document */ 12283ad4d242Sreyk if ((bodylen = asprintf(&body, 1229f75b1bb6Sreyk "<!DOCTYPE html>\n" 1230a15b848eSreyk "<html>\n" 1231a15b848eSreyk "<head>\n" 1232a15b848eSreyk "<title>%03d %s</title>\n" 1233a15b848eSreyk "<style type=\"text/css\"><!--\n%s\n--></style>\n" 1234a15b848eSreyk "</head>\n" 1235a15b848eSreyk "<body>\n" 1236a15b848eSreyk "<h1>%s</h1>\n" 1237a15b848eSreyk "<div id='m'>%s</div>\n" 1238a15b848eSreyk "<div id='l'>%s</div>\n" 1239a15b848eSreyk "<hr><address>%s at %s port %d</address>\n" 1240a15b848eSreyk "</body>\n" 1241a15b848eSreyk "</html>\n", 1242a15b848eSreyk code, httperr, style, httperr, text, 1243a15b848eSreyk label == NULL ? "" : label, 12443ad4d242Sreyk RELAYD_SERVERNAME, hbuf, ntohs(rlay->rl_conf.port))) == -1) 12453ad4d242Sreyk goto done; 12463ad4d242Sreyk 12473ad4d242Sreyk /* Generate simple HTTP+HTML error document */ 12483ad4d242Sreyk if (asprintf(&httpmsg, 12493ad4d242Sreyk "HTTP/1.0 %03d %s\r\n" 12503ad4d242Sreyk "Date: %s\r\n" 12513ad4d242Sreyk "Server: %s\r\n" 12523ad4d242Sreyk "Connection: close\r\n" 12533ad4d242Sreyk "Content-Type: text/html\r\n" 12543ad4d242Sreyk "Content-Length: %d\r\n" 12553ad4d242Sreyk "\r\n" 12563ad4d242Sreyk "%s", 12573ad4d242Sreyk code, httperr, tmbuf, RELAYD_SERVERNAME, bodylen, body) == -1) 1258a15b848eSreyk goto done; 1259a15b848eSreyk 1260a15b848eSreyk /* Dump the message without checking for success */ 1261a15b848eSreyk relay_dump(&con->se_in, httpmsg, strlen(httpmsg)); 1262a15b848eSreyk free(httpmsg); 1263a15b848eSreyk 1264a15b848eSreyk done: 12653ad4d242Sreyk free(body); 1266a15b848eSreyk if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) 12670be9d00aSbenno relay_close(con, msg, 1); 1268a15b848eSreyk else { 12690be9d00aSbenno relay_close(con, httpmsg, 1); 1270a15b848eSreyk free(httpmsg); 1271a15b848eSreyk } 1272a15b848eSreyk } 1273a15b848eSreyk 1274cb8b0e56Sreyk void 1275cb8b0e56Sreyk relay_close_http(struct rsession *con) 1276cb8b0e56Sreyk { 127753e8df0dSbenno struct http_session *hs = con->se_priv; 127853e8df0dSbenno struct http_method_node *hmn; 127953e8df0dSbenno 128053e8df0dSbenno DPRINTF("%s: session %d http_session %p", __func__, 128153e8df0dSbenno con->se_id, hs); 128253e8df0dSbenno if (hs != NULL) 128353e8df0dSbenno while (!SIMPLEQ_EMPTY(&hs->hs_methods)) { 128453e8df0dSbenno hmn = SIMPLEQ_FIRST(&hs->hs_methods); 128553e8df0dSbenno SIMPLEQ_REMOVE_HEAD(&hs->hs_methods, hmn_entry); 128653e8df0dSbenno DPRINTF("%s: session %d freeing %s", __func__, 128753e8df0dSbenno con->se_id, relay_httpmethod_byid(hmn->hmn_method)); 128853e8df0dSbenno free(hmn); 128953e8df0dSbenno } 1290954d713bSclaudio relay_httpdesc_free(con->se_in.desc); 1291954d713bSclaudio free(con->se_in.desc); 1292954d713bSclaudio relay_httpdesc_free(con->se_out.desc); 1293954d713bSclaudio free(con->se_out.desc); 1294cb8b0e56Sreyk } 1295cb8b0e56Sreyk 1296a15b848eSreyk char * 1297cb8b0e56Sreyk relay_expand_http(struct ctl_relay_event *cre, char *val, char *buf, 1298cb8b0e56Sreyk size_t len) 1299a15b848eSreyk { 1300a15b848eSreyk struct rsession *con = cre->con; 130148240b8fSbluhm struct relay *rlay = con->se_relay; 1302c4ec8a1cSrobert struct http_descriptor *desc = cre->desc; 1303c4ec8a1cSrobert struct kv *host, key; 1304a15b848eSreyk char ibuf[128]; 1305a15b848eSreyk 1306ad587bc9Sreyk if (strlcpy(buf, val, len) >= len) 1307ad587bc9Sreyk return (NULL); 1308a15b848eSreyk 1309c4ec8a1cSrobert if (strstr(val, "$HOST") != NULL) { 1310c4ec8a1cSrobert key.kv_key = "Host"; 1311c4ec8a1cSrobert host = kv_find(&desc->http_headers, &key); 1312c4ec8a1cSrobert if (host) { 1313c4ec8a1cSrobert if (host->kv_value == NULL) 1314c4ec8a1cSrobert return (NULL); 1315c4ec8a1cSrobert snprintf(ibuf, sizeof(ibuf), "%s", host->kv_value); 1316c4ec8a1cSrobert } else { 1317c4ec8a1cSrobert if (print_host(&rlay->rl_conf.ss, 1318c4ec8a1cSrobert ibuf, sizeof(ibuf)) == NULL) 1319c4ec8a1cSrobert return (NULL); 1320c4ec8a1cSrobert } 1321c4ec8a1cSrobert if (expand_string(buf, len, "$HOST", ibuf)) 1322c4ec8a1cSrobert return (NULL); 1323c4ec8a1cSrobert } 1324a15b848eSreyk if (strstr(val, "$REMOTE_") != NULL) { 1325a15b848eSreyk if (strstr(val, "$REMOTE_ADDR") != NULL) { 1326a15b848eSreyk if (print_host(&cre->ss, ibuf, sizeof(ibuf)) == NULL) 1327a15b848eSreyk return (NULL); 1328a15b848eSreyk if (expand_string(buf, len, 1329a15b848eSreyk "$REMOTE_ADDR", ibuf) != 0) 1330a15b848eSreyk return (NULL); 1331a15b848eSreyk } 1332a15b848eSreyk if (strstr(val, "$REMOTE_PORT") != NULL) { 1333a15b848eSreyk snprintf(ibuf, sizeof(ibuf), "%u", ntohs(cre->port)); 1334a15b848eSreyk if (expand_string(buf, len, 1335a15b848eSreyk "$REMOTE_PORT", ibuf) != 0) 1336a15b848eSreyk return (NULL); 1337a15b848eSreyk } 1338a15b848eSreyk } 1339a15b848eSreyk if (strstr(val, "$SERVER_") != NULL) { 1340a15b848eSreyk if (strstr(val, "$SERVER_ADDR") != NULL) { 1341a15b848eSreyk if (print_host(&rlay->rl_conf.ss, 1342a15b848eSreyk ibuf, sizeof(ibuf)) == NULL) 1343a15b848eSreyk return (NULL); 1344a15b848eSreyk if (expand_string(buf, len, 1345a15b848eSreyk "$SERVER_ADDR", ibuf) != 0) 1346a15b848eSreyk return (NULL); 1347a15b848eSreyk } 1348a15b848eSreyk if (strstr(val, "$SERVER_PORT") != NULL) { 1349a15b848eSreyk snprintf(ibuf, sizeof(ibuf), "%u", 1350a15b848eSreyk ntohs(rlay->rl_conf.port)); 1351a15b848eSreyk if (expand_string(buf, len, 1352a15b848eSreyk "$SERVER_PORT", ibuf) != 0) 1353a15b848eSreyk return (NULL); 1354a15b848eSreyk } 1355a15b848eSreyk if (strstr(val, "$SERVER_NAME") != NULL) { 1356a15b848eSreyk if (expand_string(buf, len, 1357a15b848eSreyk "$SERVER_NAME", RELAYD_SERVERNAME) != 0) 1358a15b848eSreyk return (NULL); 1359a15b848eSreyk } 1360a15b848eSreyk } 1361a15b848eSreyk if (strstr(val, "$TIMEOUT") != NULL) { 136279a291a5Sderaadt snprintf(ibuf, sizeof(ibuf), "%lld", 136379a291a5Sderaadt (long long)rlay->rl_conf.timeout.tv_sec); 1364a15b848eSreyk if (expand_string(buf, len, "$TIMEOUT", ibuf) != 0) 1365a15b848eSreyk return (NULL); 1366a15b848eSreyk } 1367a15b848eSreyk 1368a15b848eSreyk return (buf); 1369a15b848eSreyk } 1370a15b848eSreyk 1371a15b848eSreyk int 1372cb8b0e56Sreyk relay_writerequest_http(struct ctl_relay_event *dst, 1373cb8b0e56Sreyk struct ctl_relay_event *cre) 1374a15b848eSreyk { 1375cb8b0e56Sreyk struct http_descriptor *desc = (struct http_descriptor *)cre->desc; 1376cb8b0e56Sreyk const char *name = NULL; 1377a15b848eSreyk 1378cb8b0e56Sreyk if ((name = relay_httpmethod_byid(desc->http_method)) == NULL) 1379cb8b0e56Sreyk return (-1); 1380a15b848eSreyk 1381cb8b0e56Sreyk if (relay_bufferevent_print(dst, name) == -1 || 1382cb8b0e56Sreyk relay_bufferevent_print(dst, " ") == -1 || 1383cb8b0e56Sreyk relay_bufferevent_print(dst, desc->http_path) == -1 || 1384cb8b0e56Sreyk (desc->http_query != NULL && 1385cb8b0e56Sreyk (relay_bufferevent_print(dst, "?") == -1 || 1386cb8b0e56Sreyk relay_bufferevent_print(dst, desc->http_query) == -1)) || 1387cb8b0e56Sreyk relay_bufferevent_print(dst, " ") == -1 || 1388cb8b0e56Sreyk relay_bufferevent_print(dst, desc->http_version) == -1) 1389a15b848eSreyk return (-1); 1390cb8b0e56Sreyk 1391a15b848eSreyk return (0); 1392a15b848eSreyk } 1393a15b848eSreyk 1394a15b848eSreyk int 1395cb8b0e56Sreyk relay_writeresponse_http(struct ctl_relay_event *dst, 1396cb8b0e56Sreyk struct ctl_relay_event *cre) 1397a15b848eSreyk { 1398cb8b0e56Sreyk struct http_descriptor *desc = (struct http_descriptor *)cre->desc; 1399a15b848eSreyk 1400cb8b0e56Sreyk DPRINTF("version: %s rescode: %s resmsg: %s", desc->http_version, 1401cb8b0e56Sreyk desc->http_rescode, desc->http_resmesg); 1402a15b848eSreyk 1403cb8b0e56Sreyk if (relay_bufferevent_print(dst, desc->http_version) == -1 || 1404cb8b0e56Sreyk relay_bufferevent_print(dst, " ") == -1 || 1405cb8b0e56Sreyk relay_bufferevent_print(dst, desc->http_rescode) == -1 || 1406cb8b0e56Sreyk relay_bufferevent_print(dst, " ") == -1 || 1407cb8b0e56Sreyk relay_bufferevent_print(dst, desc->http_resmesg) == -1) 1408cb8b0e56Sreyk return (-1); 1409cb8b0e56Sreyk 1410cb8b0e56Sreyk return (0); 1411cb8b0e56Sreyk } 1412cb8b0e56Sreyk 1413cb8b0e56Sreyk int 1414c307a266Sreyk relay_writeheader_kv(struct ctl_relay_event *dst, struct kv *hdr) 1415cb8b0e56Sreyk { 1416cb8b0e56Sreyk char *ptr; 1417c307a266Sreyk const char *key; 1418cb8b0e56Sreyk 1419cb8b0e56Sreyk if (hdr->kv_flags & KV_FLAG_INVALID) 1420c307a266Sreyk return (0); 1421c307a266Sreyk 1422c307a266Sreyk /* The key might have been updated in the parent */ 1423c307a266Sreyk if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL) 1424c307a266Sreyk key = hdr->kv_parent->kv_key; 1425c307a266Sreyk else 1426c307a266Sreyk key = hdr->kv_key; 1427c307a266Sreyk 1428cb8b0e56Sreyk ptr = hdr->kv_value; 1429c307a266Sreyk if (relay_bufferevent_print(dst, key) == -1 || 1430cb8b0e56Sreyk (ptr != NULL && 1431cb8b0e56Sreyk (relay_bufferevent_print(dst, ": ") == -1 || 1432cb8b0e56Sreyk relay_bufferevent_print(dst, ptr) == -1 || 1433cb8b0e56Sreyk relay_bufferevent_print(dst, "\r\n") == -1))) 1434cb8b0e56Sreyk return (-1); 1435c307a266Sreyk DPRINTF("%s: %s: %s", __func__, key, 1436cb8b0e56Sreyk hdr->kv_value == NULL ? "" : hdr->kv_value); 1437c307a266Sreyk 1438c307a266Sreyk return (0); 1439c307a266Sreyk } 1440c307a266Sreyk 1441c307a266Sreyk int 1442c307a266Sreyk relay_writeheader_http(struct ctl_relay_event *dst, struct ctl_relay_event 1443c307a266Sreyk *cre) 1444c307a266Sreyk { 1445c307a266Sreyk struct kv *hdr, *kv; 1446c307a266Sreyk struct http_descriptor *desc = (struct http_descriptor *)cre->desc; 1447c307a266Sreyk 1448c307a266Sreyk RB_FOREACH(hdr, kvtree, &desc->http_headers) { 1449c307a266Sreyk if (relay_writeheader_kv(dst, hdr) == -1) 1450c307a266Sreyk return (-1); 1451c307a266Sreyk TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) { 1452c307a266Sreyk if (relay_writeheader_kv(dst, kv) == -1) 1453c307a266Sreyk return (-1); 1454c307a266Sreyk } 1455cb8b0e56Sreyk } 1456cb8b0e56Sreyk 1457cb8b0e56Sreyk return (0); 1458cb8b0e56Sreyk } 1459cb8b0e56Sreyk 1460cb8b0e56Sreyk enum httpmethod 1461cb8b0e56Sreyk relay_httpmethod_byname(const char *name) 1462cb8b0e56Sreyk { 1463cb8b0e56Sreyk enum httpmethod id = HTTP_METHOD_NONE; 1464cb8b0e56Sreyk struct http_method method, *res = NULL; 1465cb8b0e56Sreyk 1466cb8b0e56Sreyk /* Set up key */ 1467cb8b0e56Sreyk method.method_name = name; 1468cb8b0e56Sreyk 1469cb8b0e56Sreyk if ((res = bsearch(&method, http_methods, 1470cb8b0e56Sreyk sizeof(http_methods) / sizeof(http_methods[0]) - 1, 1471cb8b0e56Sreyk sizeof(http_methods[0]), relay_httpmethod_cmp)) != NULL) 1472cb8b0e56Sreyk id = res->method_id; 1473cb8b0e56Sreyk 1474cb8b0e56Sreyk return (id); 1475cb8b0e56Sreyk } 1476cb8b0e56Sreyk 1477cb8b0e56Sreyk const char * 1478cb8b0e56Sreyk relay_httpmethod_byid(u_int id) 1479cb8b0e56Sreyk { 1480cb8b0e56Sreyk const char *name = NULL; 1481cb8b0e56Sreyk int i; 1482cb8b0e56Sreyk 1483cb8b0e56Sreyk for (i = 0; http_methods[i].method_name != NULL; i++) { 1484cb8b0e56Sreyk if (http_methods[i].method_id == id) { 1485cb8b0e56Sreyk name = http_methods[i].method_name; 1486cb8b0e56Sreyk break; 1487cb8b0e56Sreyk } 1488cb8b0e56Sreyk } 1489cb8b0e56Sreyk 1490cb8b0e56Sreyk return (name); 1491cb8b0e56Sreyk } 1492cb8b0e56Sreyk 1493cb8b0e56Sreyk static int 1494cb8b0e56Sreyk relay_httpmethod_cmp(const void *a, const void *b) 1495cb8b0e56Sreyk { 1496cb8b0e56Sreyk const struct http_method *ma = a; 1497cb8b0e56Sreyk const struct http_method *mb = b; 14981d89351eSstsp 14991d89351eSstsp /* 15001d89351eSstsp * RFC 2616 section 5.1.1 says that the method is case 15011d89351eSstsp * sensitive so we don't do a strcasecmp here. 15021d89351eSstsp */ 1503cb8b0e56Sreyk return (strcmp(ma->method_name, mb->method_name)); 1504cb8b0e56Sreyk } 1505cb8b0e56Sreyk 1506543ef491Sreyk const char * 1507543ef491Sreyk relay_httperror_byid(u_int id) 1508543ef491Sreyk { 1509543ef491Sreyk struct http_error error, *res = NULL; 1510543ef491Sreyk 1511543ef491Sreyk /* Set up key */ 1512543ef491Sreyk error.error_code = (int)id; 1513543ef491Sreyk 1514543ef491Sreyk res = bsearch(&error, http_errors, 1515543ef491Sreyk sizeof(http_errors) / sizeof(http_errors[0]) - 1, 1516543ef491Sreyk sizeof(http_errors[0]), relay_httperror_cmp); 1517543ef491Sreyk 1518543ef491Sreyk return (res->error_name); 1519543ef491Sreyk } 1520543ef491Sreyk 1521543ef491Sreyk static int 1522543ef491Sreyk relay_httperror_cmp(const void *a, const void *b) 1523543ef491Sreyk { 1524543ef491Sreyk const struct http_error *ea = a; 1525543ef491Sreyk const struct http_error *eb = b; 1526543ef491Sreyk return (ea->error_code - eb->error_code); 1527543ef491Sreyk } 1528543ef491Sreyk 1529cb8b0e56Sreyk int 1530cb8b0e56Sreyk relay_httpquery_test(struct ctl_relay_event *cre, struct relay_rule *rule, 1531cb8b0e56Sreyk struct kvlist *actions) 1532cb8b0e56Sreyk { 1533cb8b0e56Sreyk struct http_descriptor *desc = cre->desc; 1534cb8b0e56Sreyk struct kv *match = &desc->http_matchquery; 1535cb8b0e56Sreyk struct kv *kv = &rule->rule_kv[KEY_TYPE_QUERY]; 15369357f4aaSbenno int res = 0; 1537cb8b0e56Sreyk 1538cb8b0e56Sreyk if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_QUERY) 1539cb8b0e56Sreyk return (0); 1540cb8b0e56Sreyk else if (kv->kv_key == NULL) 1541cb8b0e56Sreyk return (0); 15429357f4aaSbenno else if ((res = relay_lookup_query(cre, kv)) != 0) 15439357f4aaSbenno return (res); 1544cb8b0e56Sreyk 1545cb8b0e56Sreyk relay_match(actions, kv, match, NULL); 1546cb8b0e56Sreyk 1547cb8b0e56Sreyk return (0); 1548cb8b0e56Sreyk } 1549cb8b0e56Sreyk 1550cb8b0e56Sreyk int 1551cb8b0e56Sreyk relay_httpheader_test(struct ctl_relay_event *cre, struct relay_rule *rule, 1552cb8b0e56Sreyk struct kvlist *actions) 1553cb8b0e56Sreyk { 1554cb8b0e56Sreyk struct http_descriptor *desc = cre->desc; 1555cb8b0e56Sreyk struct kv *kv = &rule->rule_kv[KEY_TYPE_HEADER]; 1556cb8b0e56Sreyk struct kv *match; 1557cb8b0e56Sreyk 1558c307a266Sreyk if (kv->kv_type != KEY_TYPE_HEADER) 1559cb8b0e56Sreyk return (0); 1560cb8b0e56Sreyk 1561c307a266Sreyk match = kv_find(&desc->http_headers, kv); 1562c307a266Sreyk 1563cb8b0e56Sreyk if (kv->kv_option == KEY_OPTION_APPEND || 1564cb8b0e56Sreyk kv->kv_option == KEY_OPTION_SET) { 1565cb8b0e56Sreyk /* header can be NULL and will be added later */ 1566cb8b0e56Sreyk } else if (match == NULL) { 1567cb8b0e56Sreyk /* Fail if header doesn't exist */ 1568cb8b0e56Sreyk return (-1); 15692d28367dSbenno } else { 15701ab70d21Sreyk if (fnmatch(kv->kv_key, match->kv_key, 15711ab70d21Sreyk FNM_CASEFOLD) == FNM_NOMATCH) 15722d28367dSbenno return (-1); 15732d28367dSbenno if (kv->kv_value != NULL && 15742d28367dSbenno match->kv_value != NULL && 15752d28367dSbenno fnmatch(kv->kv_value, match->kv_value, 0) == FNM_NOMATCH) 15762d28367dSbenno return (-1); 1577cb8b0e56Sreyk } 1578cb8b0e56Sreyk 1579cb8b0e56Sreyk relay_match(actions, kv, match, &desc->http_headers); 1580cb8b0e56Sreyk 1581cb8b0e56Sreyk return (0); 1582cb8b0e56Sreyk } 1583cb8b0e56Sreyk 1584cb8b0e56Sreyk int 1585cb8b0e56Sreyk relay_httppath_test(struct ctl_relay_event *cre, struct relay_rule *rule, 1586cb8b0e56Sreyk struct kvlist *actions) 1587cb8b0e56Sreyk { 1588cb8b0e56Sreyk struct http_descriptor *desc = cre->desc; 1589cb8b0e56Sreyk struct kv *kv = &rule->rule_kv[KEY_TYPE_PATH]; 1590cb8b0e56Sreyk struct kv *match = &desc->http_pathquery; 1591cb8b0e56Sreyk const char *query; 1592cb8b0e56Sreyk 1593cb8b0e56Sreyk if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_PATH) 1594cb8b0e56Sreyk return (0); 1595eeb1fea4Sdenis else if (kv->kv_option != KEY_OPTION_STRIP) { 1596eeb1fea4Sdenis if (kv->kv_key == NULL) 1597cb8b0e56Sreyk return (0); 1598cb8b0e56Sreyk else if (fnmatch(kv->kv_key, desc->http_path, 0) == FNM_NOMATCH) 1599cb8b0e56Sreyk return (-1); 1600cb8b0e56Sreyk else if (kv->kv_value != NULL && kv->kv_option == KEY_OPTION_NONE) { 1601cb8b0e56Sreyk query = desc->http_query == NULL ? "" : desc->http_query; 1602cb8b0e56Sreyk if (fnmatch(kv->kv_value, query, FNM_CASEFOLD) == FNM_NOMATCH) 1603cb8b0e56Sreyk return (-1); 1604cb8b0e56Sreyk } 1605eeb1fea4Sdenis } 1606cb8b0e56Sreyk 1607cb8b0e56Sreyk relay_match(actions, kv, match, NULL); 1608cb8b0e56Sreyk 1609cb8b0e56Sreyk return (0); 1610cb8b0e56Sreyk } 1611cb8b0e56Sreyk 1612cb8b0e56Sreyk int 1613cb8b0e56Sreyk relay_httpurl_test(struct ctl_relay_event *cre, struct relay_rule *rule, 1614cb8b0e56Sreyk struct kvlist *actions) 1615cb8b0e56Sreyk { 1616cb8b0e56Sreyk struct http_descriptor *desc = cre->desc; 1617c307a266Sreyk struct kv *host, key; 1618cb8b0e56Sreyk struct kv *kv = &rule->rule_kv[KEY_TYPE_URL]; 1619cb8b0e56Sreyk struct kv *match = &desc->http_pathquery; 16209357f4aaSbenno int res; 1621cb8b0e56Sreyk 1622c307a266Sreyk if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_URL || 1623c307a266Sreyk kv->kv_key == NULL) 1624cb8b0e56Sreyk return (0); 1625c307a266Sreyk 1626c307a266Sreyk key.kv_key = "Host"; 1627c307a266Sreyk host = kv_find(&desc->http_headers, &key); 1628c307a266Sreyk 1629c307a266Sreyk if (host == NULL || host->kv_value == NULL) 1630cb8b0e56Sreyk return (0); 1631cb8b0e56Sreyk else if (rule->rule_action != RULE_ACTION_BLOCK && 1632cb8b0e56Sreyk kv->kv_option == KEY_OPTION_LOG && 1633cb8b0e56Sreyk fnmatch(kv->kv_key, match->kv_key, FNM_CASEFOLD) != FNM_NOMATCH) { 1634cb8b0e56Sreyk /* fnmatch url only for logging */ 16359357f4aaSbenno } else if ((res = relay_lookup_url(cre, host->kv_value, kv)) != 0) 16369357f4aaSbenno return (res); 1637cb8b0e56Sreyk relay_match(actions, kv, match, NULL); 1638cb8b0e56Sreyk 1639cb8b0e56Sreyk return (0); 1640cb8b0e56Sreyk } 1641cb8b0e56Sreyk 1642cb8b0e56Sreyk int 1643cb8b0e56Sreyk relay_httpcookie_test(struct ctl_relay_event *cre, struct relay_rule *rule, 1644cb8b0e56Sreyk struct kvlist *actions) 1645cb8b0e56Sreyk { 1646cb8b0e56Sreyk struct http_descriptor *desc = cre->desc; 1647c307a266Sreyk struct kv *kv = &rule->rule_kv[KEY_TYPE_COOKIE], key; 1648cb8b0e56Sreyk struct kv *match = NULL; 16499357f4aaSbenno int res; 1650cb8b0e56Sreyk 1651cb8b0e56Sreyk if (kv->kv_type != KEY_TYPE_COOKIE) 1652cb8b0e56Sreyk return (0); 1653cb8b0e56Sreyk 1654cb8b0e56Sreyk switch (cre->dir) { 1655cb8b0e56Sreyk case RELAY_DIR_REQUEST: 1656c307a266Sreyk key.kv_key = "Cookie"; 1657cb8b0e56Sreyk break; 1658cb8b0e56Sreyk case RELAY_DIR_RESPONSE: 1659c307a266Sreyk key.kv_key = "Set-Cookie"; 1660a15b848eSreyk break; 1661a15b848eSreyk default: 1662cb8b0e56Sreyk return (0); 1663cb8b0e56Sreyk /* NOTREACHED */ 1664a15b848eSreyk break; 1665a15b848eSreyk } 1666c307a266Sreyk 1667cb8b0e56Sreyk if (kv->kv_option == KEY_OPTION_APPEND || 1668cb8b0e56Sreyk kv->kv_option == KEY_OPTION_SET) { 1669cb8b0e56Sreyk /* no cookie, can be NULL and will be added later */ 1670cb8b0e56Sreyk } else { 1671c307a266Sreyk match = kv_find(&desc->http_headers, &key); 1672cb8b0e56Sreyk if (match == NULL) 1673cb8b0e56Sreyk return (-1); 1674cb8b0e56Sreyk if (kv->kv_key == NULL || match->kv_value == NULL) 1675cb8b0e56Sreyk return (0); 16769357f4aaSbenno else if ((res = relay_lookup_cookie(cre, match->kv_value, 16779357f4aaSbenno kv)) != 0) 16789357f4aaSbenno return (res); 1679cb8b0e56Sreyk } 1680a15b848eSreyk 1681cb8b0e56Sreyk relay_match(actions, kv, match, &desc->http_headers); 1682cb8b0e56Sreyk 1683cb8b0e56Sreyk return (0); 1684cb8b0e56Sreyk } 1685cb8b0e56Sreyk 1686cb8b0e56Sreyk int 1687cb8b0e56Sreyk relay_match_actions(struct ctl_relay_event *cre, struct relay_rule *rule, 168865f47834Sreyk struct kvlist *matches, struct kvlist *actions, struct relay_table **tbl) 1689cb8b0e56Sreyk { 1690cb8b0e56Sreyk struct rsession *con = cre->con; 169144c7492eSbket struct kv *kv; 1692cb8b0e56Sreyk 1693a15b848eSreyk /* 1694cb8b0e56Sreyk * Apply the following options instantly (action per match). 1695a15b848eSreyk */ 1696053cc50eSchrisz if (rule->rule_table != NULL) { 169765f47834Sreyk *tbl = rule->rule_table; 1698053cc50eSchrisz con->se_out.ss.ss_family = AF_UNSPEC; 1699053cc50eSchrisz } 1700cb8b0e56Sreyk if (rule->rule_tag != 0) 1701cb8b0e56Sreyk con->se_tag = rule->rule_tag == -1 ? 0 : rule->rule_tag; 1702cb8b0e56Sreyk if (rule->rule_label != 0) 1703cb8b0e56Sreyk con->se_label = rule->rule_label == -1 ? 0 : rule->rule_label; 1704cb8b0e56Sreyk 1705cb8b0e56Sreyk /* 1706cb8b0e56Sreyk * Apply the remaining options once after evaluation. 1707cb8b0e56Sreyk */ 1708cb8b0e56Sreyk if (matches == NULL) { 1709cb8b0e56Sreyk /* 'pass' or 'block' rule */ 171044c7492eSbket TAILQ_CONCAT(actions, &rule->rule_kvlist, kv_rule_entry); 1711cb8b0e56Sreyk } else { 1712cb8b0e56Sreyk /* 'match' rule */ 1713cb8b0e56Sreyk TAILQ_FOREACH(kv, matches, kv_match_entry) { 1714c307a266Sreyk TAILQ_INSERT_TAIL(actions, kv, kv_action_entry); 1715a15b848eSreyk } 1716a15b848eSreyk } 1717cb8b0e56Sreyk 1718cb8b0e56Sreyk return (0); 1719cb8b0e56Sreyk } 1720cb8b0e56Sreyk 1721cb8b0e56Sreyk int 172265f47834Sreyk relay_apply_actions(struct ctl_relay_event *cre, struct kvlist *actions, 172365f47834Sreyk struct relay_table *tbl) 1724cb8b0e56Sreyk { 1725cb8b0e56Sreyk struct rsession *con = cre->con; 1726cb8b0e56Sreyk struct http_descriptor *desc = cre->desc; 1727cb8b0e56Sreyk struct kv *host = NULL; 1728cb8b0e56Sreyk const char *value; 1729c307a266Sreyk struct kv *kv, *match, *kp, *mp, kvcopy, matchcopy, key; 1730eeb1fea4Sdenis int addkv, ret, nstrip; 1731cb8b0e56Sreyk char buf[IBUF_READ_SIZE], *ptr; 1732c84d6099Sbenno char *msg = NULL; 1733c84d6099Sbenno const char *meth = NULL; 1734cb8b0e56Sreyk 1735f8ffcebeSreyk memset(&kvcopy, 0, sizeof(kvcopy)); 1736f8ffcebeSreyk memset(&matchcopy, 0, sizeof(matchcopy)); 1737f8ffcebeSreyk 1738cb8b0e56Sreyk ret = -1; 1739cb8b0e56Sreyk kp = mp = NULL; 1740c307a266Sreyk TAILQ_FOREACH(kv, actions, kv_action_entry) { 1741cb8b0e56Sreyk kp = NULL; 1742cb8b0e56Sreyk match = kv->kv_match; 1743c307a266Sreyk addkv = 0; 1744cb8b0e56Sreyk 1745cb8b0e56Sreyk /* 1746cb8b0e56Sreyk * Although marked as deleted, give a chance to non-critical 1747cb8b0e56Sreyk * actions, ie. log, to be performed 1748cb8b0e56Sreyk */ 1749cb8b0e56Sreyk if (match != NULL && (match->kv_flags & KV_FLAG_INVALID)) 1750cb8b0e56Sreyk goto matchdel; 1751cb8b0e56Sreyk 1752cb8b0e56Sreyk switch (kv->kv_option) { 1753cb8b0e56Sreyk case KEY_OPTION_APPEND: 1754cb8b0e56Sreyk case KEY_OPTION_SET: 1755cb8b0e56Sreyk switch (kv->kv_type) { 1756cb8b0e56Sreyk case KEY_TYPE_PATH: 1757cb8b0e56Sreyk if (kv->kv_option == KEY_OPTION_APPEND) { 1758cb8b0e56Sreyk if (kv_setkey(match, "%s%s", 1759cb8b0e56Sreyk match->kv_key, kv->kv_key) == -1) 1760cb8b0e56Sreyk goto fail; 1761cb8b0e56Sreyk } else { 1762cb8b0e56Sreyk if (kv_setkey(match, "%s", 1763cb8b0e56Sreyk kv->kv_value) == -1) 1764cb8b0e56Sreyk goto fail; 1765cb8b0e56Sreyk } 1766a15b848eSreyk break; 1767cb8b0e56Sreyk case KEY_TYPE_COOKIE: 1768cb8b0e56Sreyk kp = &kvcopy; 1769cb8b0e56Sreyk if (kv_inherit(kp, kv) == NULL) 1770cb8b0e56Sreyk goto fail; 1771cb8b0e56Sreyk if (kv_set(kp, "%s=%s;", kp->kv_key, 1772cb8b0e56Sreyk kp->kv_value) == -1) 1773cb8b0e56Sreyk goto fail; 1774c307a266Sreyk if (kv_setkey(kp, "%s", cre->dir == 1775c307a266Sreyk RELAY_DIR_REQUEST ? 1776c307a266Sreyk "Cookie" : "Set-Cookie") == -1) 1777cb8b0e56Sreyk goto fail; 1778cb8b0e56Sreyk /* FALLTHROUGH cookie is a header */ 1779cb8b0e56Sreyk case KEY_TYPE_HEADER: 1780cb8b0e56Sreyk if (match == NULL) { 1781cb8b0e56Sreyk addkv = 1; 1782cb8b0e56Sreyk break; 1783cb8b0e56Sreyk } 1784cb8b0e56Sreyk if (match->kv_value == NULL || 1785cb8b0e56Sreyk kv->kv_option == KEY_OPTION_SET) { 1786cb8b0e56Sreyk if (kv_set(match, "%s", 1787cb8b0e56Sreyk kv->kv_value) == -1) 1788cb8b0e56Sreyk goto fail; 1789aa334c22Sbenno } else 1790aa334c22Sbenno addkv = 1; 1791cb8b0e56Sreyk break; 1792cb8b0e56Sreyk default: 1793cb8b0e56Sreyk /* query, url not supported */ 1794cb8b0e56Sreyk break; 1795cb8b0e56Sreyk } 1796cb8b0e56Sreyk break; 1797cb8b0e56Sreyk case KEY_OPTION_REMOVE: 1798cb8b0e56Sreyk switch (kv->kv_type) { 1799cb8b0e56Sreyk case KEY_TYPE_PATH: 1800cb8b0e56Sreyk if (kv_setkey(match, "/") == -1) 1801cb8b0e56Sreyk goto fail; 1802cb8b0e56Sreyk break; 1803cb8b0e56Sreyk case KEY_TYPE_COOKIE: 1804cb8b0e56Sreyk case KEY_TYPE_HEADER: 1805c307a266Sreyk if (kv->kv_matchtree != NULL) 1806cb8b0e56Sreyk match->kv_flags |= KV_FLAG_INVALID; 1807cb8b0e56Sreyk else 1808cb8b0e56Sreyk kv_free(match); 1809cb8b0e56Sreyk match = kv->kv_match = NULL; 1810cb8b0e56Sreyk break; 1811cb8b0e56Sreyk default: 1812cb8b0e56Sreyk /* query and url not supported */ 1813cb8b0e56Sreyk break; 1814cb8b0e56Sreyk } 1815cb8b0e56Sreyk break; 1816cb8b0e56Sreyk case KEY_OPTION_HASH: 1817cb8b0e56Sreyk switch (kv->kv_type) { 1818cb8b0e56Sreyk case KEY_TYPE_PATH: 1819cb8b0e56Sreyk value = match->kv_key; 1820cb8b0e56Sreyk break; 1821cb8b0e56Sreyk default: 1822cb8b0e56Sreyk value = match->kv_value; 1823cb8b0e56Sreyk break; 1824cb8b0e56Sreyk } 1825acb89df4Sreyk SipHash24_Update(&con->se_siphashctx, 1826acb89df4Sreyk value, strlen(value)); 1827a15b848eSreyk break; 1828cb8b0e56Sreyk case KEY_OPTION_LOG: 1829cb8b0e56Sreyk /* perform this later */ 1830a15b848eSreyk break; 1831eeb1fea4Sdenis case KEY_OPTION_STRIP: 1832eeb1fea4Sdenis nstrip = strtonum(kv->kv_value, 0, INT_MAX, NULL); 1833eeb1fea4Sdenis if (kv->kv_type == KEY_TYPE_PATH) { 1834e472afa5Sbenno if (kv_setkey(match, "%s", 1835eeb1fea4Sdenis server_root_strip(match->kv_key, 1836eeb1fea4Sdenis nstrip)) == -1) 1837eeb1fea4Sdenis goto fail; 1838eeb1fea4Sdenis } 1839eeb1fea4Sdenis break; 1840cb8b0e56Sreyk default: 1841efc39811Sbenno fatalx("%s: invalid action", __func__); 1842cb8b0e56Sreyk /* NOTREACHED */ 1843a15b848eSreyk } 1844cb8b0e56Sreyk 1845cb8b0e56Sreyk /* from now on, reads from kp writes to kv */ 1846cb8b0e56Sreyk if (kp == NULL) 1847cb8b0e56Sreyk kp = kv; 1848c307a266Sreyk if (addkv && kv->kv_matchtree != NULL) { 1849cb8b0e56Sreyk /* Add new entry to the list (eg. new HTTP header) */ 1850c307a266Sreyk if ((match = kv_add(kv->kv_matchtree, kp->kv_key, 1851c9822c56Sreyk kp->kv_value, 0)) == NULL) 1852a15b848eSreyk goto fail; 1853cb8b0e56Sreyk match->kv_option = kp->kv_option; 1854cb8b0e56Sreyk match->kv_type = kp->kv_type; 1855cb8b0e56Sreyk kv->kv_match = match; 1856cb8b0e56Sreyk } 1857cb8b0e56Sreyk if (match != NULL && kp->kv_flags & KV_FLAG_MACRO) { 1858cb8b0e56Sreyk bzero(buf, sizeof(buf)); 1859cb8b0e56Sreyk if ((ptr = relay_expand_http(cre, kp->kv_value, buf, 1860cb8b0e56Sreyk sizeof(buf))) == NULL) 1861cb8b0e56Sreyk goto fail; 1862e472afa5Sbenno if (kv_set(match, "%s", ptr) == -1) 1863cb8b0e56Sreyk goto fail; 1864cb8b0e56Sreyk } 1865c307a266Sreyk 1866cb8b0e56Sreyk matchdel: 1867cb8b0e56Sreyk switch (kv->kv_option) { 1868cb8b0e56Sreyk case KEY_OPTION_LOG: 1869cb8b0e56Sreyk if (match == NULL) 1870cb8b0e56Sreyk break; 1871cb8b0e56Sreyk mp = &matchcopy; 1872cb8b0e56Sreyk if (kv_inherit(mp, match) == NULL) 1873cb8b0e56Sreyk goto fail; 1874cb8b0e56Sreyk if (mp->kv_flags & KV_FLAG_INVALID) { 18757f9463e5Sbluhm if (kv_set(mp, "%s (removed)", 1876cb8b0e56Sreyk mp->kv_value) == -1) 1877cb8b0e56Sreyk goto fail; 1878cb8b0e56Sreyk } 1879cb8b0e56Sreyk switch (kv->kv_type) { 1880cb8b0e56Sreyk case KEY_TYPE_URL: 1881c307a266Sreyk key.kv_key = "Host"; 1882c307a266Sreyk host = kv_find(&desc->http_headers, &key); 1883cb8b0e56Sreyk switch (kv->kv_digest) { 1884cb8b0e56Sreyk case DIGEST_NONE: 1885cb8b0e56Sreyk if (host == NULL || 1886cb8b0e56Sreyk host->kv_value == NULL) 1887cb8b0e56Sreyk break; 1888cb8b0e56Sreyk if (kv_setkey(mp, "%s%s", 1889cb8b0e56Sreyk host->kv_value, mp->kv_key) == 1890cb8b0e56Sreyk -1) 1891cb8b0e56Sreyk goto fail; 1892cb8b0e56Sreyk break; 1893cb8b0e56Sreyk default: 1894cb8b0e56Sreyk if (kv_setkey(mp, "%s", kv->kv_key) 1895cb8b0e56Sreyk == -1) 1896cb8b0e56Sreyk goto fail; 1897cb8b0e56Sreyk break; 1898cb8b0e56Sreyk } 1899cb8b0e56Sreyk break; 1900cb8b0e56Sreyk default: 1901cb8b0e56Sreyk break; 1902cb8b0e56Sreyk } 1903c84d6099Sbenno if (kv_log(con, mp, con->se_label, cre->dir) 1904c84d6099Sbenno == -1) 1905cb8b0e56Sreyk goto fail; 1906cb8b0e56Sreyk break; 1907cb8b0e56Sreyk default: 1908cb8b0e56Sreyk break; 1909cb8b0e56Sreyk } 1910cb8b0e56Sreyk 1911cb8b0e56Sreyk /* actions applied, cleanup kv */ 1912cb8b0e56Sreyk kv->kv_match = NULL; 1913c307a266Sreyk kv->kv_matchtree = NULL; 1914cb8b0e56Sreyk TAILQ_REMOVE(actions, kv, kv_match_entry); 1915cb8b0e56Sreyk 1916cb8b0e56Sreyk kv_free(&kvcopy); 1917f8ffcebeSreyk kv_free(&matchcopy); 1918cb8b0e56Sreyk } 1919cb8b0e56Sreyk 1920c84d6099Sbenno /* 192165f47834Sreyk * Change the backend if the forward table has been changed. 192265f47834Sreyk * This only works in the request direction. 192365f47834Sreyk */ 192465f47834Sreyk if (cre->dir == RELAY_DIR_REQUEST && con->se_table != tbl) { 192565f47834Sreyk relay_reset_event(con, &con->se_out); 192665f47834Sreyk con->se_table = tbl; 192765f47834Sreyk con->se_haslog = 1; 192865f47834Sreyk } 192965f47834Sreyk 193065f47834Sreyk /* 1931c84d6099Sbenno * log tag for request and response, request method 1932c84d6099Sbenno * and end of request marker "," 1933c84d6099Sbenno */ 1934c84d6099Sbenno if ((con->se_log != NULL) && 1935c84d6099Sbenno ((meth = relay_httpmethod_byid(desc->http_method)) != NULL) && 193665f47834Sreyk (asprintf(&msg, " %s", meth) != -1)) 1937c84d6099Sbenno evbuffer_add(con->se_log, msg, strlen(msg)); 1938c84d6099Sbenno free(msg); 1939c84d6099Sbenno relay_log(con, cre->dir == RELAY_DIR_REQUEST ? "" : ";"); 1940cb8b0e56Sreyk ret = 0; 1941cb8b0e56Sreyk fail: 1942cb8b0e56Sreyk kv_free(&kvcopy); 1943cb8b0e56Sreyk kv_free(&matchcopy); 1944a15b848eSreyk 1945a15b848eSreyk return (ret); 1946cb8b0e56Sreyk } 1947cb8b0e56Sreyk 1948cb8b0e56Sreyk #define RELAY_GET_SKIP_STEP(i) \ 1949cb8b0e56Sreyk do { \ 1950cb8b0e56Sreyk r = r->rule_skip[i]; \ 1951cb8b0e56Sreyk DPRINTF("%s:%d: skip %d rules", __func__, __LINE__, i); \ 1952cb8b0e56Sreyk } while (0) 1953cb8b0e56Sreyk 1954cb8b0e56Sreyk #define RELAY_GET_NEXT_STEP \ 1955cb8b0e56Sreyk do { \ 1956cb8b0e56Sreyk DPRINTF("%s:%d: next rule", __func__, __LINE__); \ 1957cb8b0e56Sreyk goto nextrule; \ 1958cb8b0e56Sreyk } while (0) 1959cb8b0e56Sreyk 1960cb8b0e56Sreyk int 1961cb8b0e56Sreyk relay_test(struct protocol *proto, struct ctl_relay_event *cre) 1962cb8b0e56Sreyk { 1963cb8b0e56Sreyk struct rsession *con; 1964cb8b0e56Sreyk struct http_descriptor *desc = cre->desc; 1965cb8b0e56Sreyk struct relay_rule *r = NULL, *rule = NULL; 196665f47834Sreyk struct relay_table *tbl = NULL; 1967cb8b0e56Sreyk u_int action = RES_PASS; 1968cb8b0e56Sreyk struct kvlist actions, matches; 1969cb8b0e56Sreyk struct kv *kv; 19709357f4aaSbenno int res = 0; 1971cb8b0e56Sreyk 1972cb8b0e56Sreyk con = cre->con; 1973cb8b0e56Sreyk TAILQ_INIT(&actions); 1974cb8b0e56Sreyk 1975cb8b0e56Sreyk r = TAILQ_FIRST(&proto->rules); 1976cb8b0e56Sreyk while (r != NULL) { 1977cb8b0e56Sreyk TAILQ_INIT(&matches); 1978cb8b0e56Sreyk TAILQ_INIT(&r->rule_kvlist); 19799357f4aaSbenno 1980cb8b0e56Sreyk if (r->rule_dir && r->rule_dir != cre->dir) 1981cb8b0e56Sreyk RELAY_GET_SKIP_STEP(RULE_SKIP_DIR); 1982cb8b0e56Sreyk else if (proto->type != r->rule_proto) 1983cb8b0e56Sreyk RELAY_GET_SKIP_STEP(RULE_SKIP_PROTO); 1984860302f3Sreyk else if (RELAY_AF_NEQ(r->rule_af, cre->ss.ss_family) || 1985860302f3Sreyk RELAY_AF_NEQ(r->rule_af, cre->dst->ss.ss_family)) 1986cb8b0e56Sreyk RELAY_GET_SKIP_STEP(RULE_SKIP_AF); 1987cb8b0e56Sreyk else if (RELAY_ADDR_CMP(&r->rule_src, &cre->ss) != 0) 1988cb8b0e56Sreyk RELAY_GET_SKIP_STEP(RULE_SKIP_SRC); 1989860302f3Sreyk else if (RELAY_ADDR_CMP(&r->rule_dst, &con->se_sockname) != 0) 1990cb8b0e56Sreyk RELAY_GET_SKIP_STEP(RULE_SKIP_DST); 1991cb8b0e56Sreyk else if (r->rule_method != HTTP_METHOD_NONE && 1992cb8b0e56Sreyk (desc->http_method == HTTP_METHOD_RESPONSE || 1993cb8b0e56Sreyk desc->http_method != r->rule_method)) 1994cb8b0e56Sreyk RELAY_GET_SKIP_STEP(RULE_SKIP_METHOD); 1995cb8b0e56Sreyk else if (r->rule_tagged && con->se_tag != r->rule_tagged) 1996cb8b0e56Sreyk RELAY_GET_NEXT_STEP; 1997cb8b0e56Sreyk else if (relay_httpheader_test(cre, r, &matches) != 0) 1998cb8b0e56Sreyk RELAY_GET_NEXT_STEP; 19999357f4aaSbenno else if ((res = relay_httpquery_test(cre, r, &matches)) != 0) 2000cb8b0e56Sreyk RELAY_GET_NEXT_STEP; 2001cb8b0e56Sreyk else if (relay_httppath_test(cre, r, &matches) != 0) 2002cb8b0e56Sreyk RELAY_GET_NEXT_STEP; 20039357f4aaSbenno else if ((res = relay_httpurl_test(cre, r, &matches)) != 0) 2004cb8b0e56Sreyk RELAY_GET_NEXT_STEP; 20059357f4aaSbenno else if ((res = relay_httpcookie_test(cre, r, &matches)) != 0) 2006cb8b0e56Sreyk RELAY_GET_NEXT_STEP; 2007cb8b0e56Sreyk else { 2008cb8b0e56Sreyk DPRINTF("%s: session %d: matched rule %d", 2009cb8b0e56Sreyk __func__, con->se_id, r->rule_id); 2010cb8b0e56Sreyk 2011cb8b0e56Sreyk if (r->rule_action == RULE_ACTION_MATCH) { 2012cb8b0e56Sreyk if (relay_match_actions(cre, r, &matches, 201365f47834Sreyk &actions, &tbl) != 0) { 2014cb8b0e56Sreyk /* Something bad happened, drop */ 2015cb8b0e56Sreyk action = RES_DROP; 2016cb8b0e56Sreyk break; 2017cb8b0e56Sreyk } 2018cb8b0e56Sreyk RELAY_GET_NEXT_STEP; 2019cb8b0e56Sreyk } else if (r->rule_action == RULE_ACTION_BLOCK) 2020cb8b0e56Sreyk action = RES_DROP; 2021cb8b0e56Sreyk else if (r->rule_action == RULE_ACTION_PASS) 2022cb8b0e56Sreyk action = RES_PASS; 2023cb8b0e56Sreyk 2024cb8b0e56Sreyk /* Rule matched */ 2025cb8b0e56Sreyk rule = r; 2026cb8b0e56Sreyk 2027cb8b0e56Sreyk /* Temporarily save actions */ 2028cb8b0e56Sreyk TAILQ_FOREACH(kv, &matches, kv_match_entry) { 2029cb8b0e56Sreyk TAILQ_INSERT_TAIL(&rule->rule_kvlist, 2030cb8b0e56Sreyk kv, kv_rule_entry); 2031cb8b0e56Sreyk } 2032cb8b0e56Sreyk 2033cb8b0e56Sreyk if (rule->rule_flags & RULE_FLAG_QUICK) 2034cb8b0e56Sreyk break; 2035cb8b0e56Sreyk 2036cb8b0e56Sreyk nextrule: 2037cb8b0e56Sreyk /* Continue to find last matching policy */ 20389357f4aaSbenno DPRINTF("%s: session %d, res %d", __func__, 20399357f4aaSbenno con->se_id, res); 20409357f4aaSbenno if (res == RES_BAD || res == RES_INTERNAL) 20419357f4aaSbenno return (res); 20429357f4aaSbenno res = 0; 2043cb8b0e56Sreyk r = TAILQ_NEXT(r, rule_entry); 2044cb8b0e56Sreyk } 2045cb8b0e56Sreyk } 2046cb8b0e56Sreyk 204765f47834Sreyk if (rule != NULL && relay_match_actions(cre, rule, NULL, &actions, &tbl) 2048e8e49268Sbenno != 0) { 2049cb8b0e56Sreyk /* Something bad happened, drop */ 2050cb8b0e56Sreyk action = RES_DROP; 2051cb8b0e56Sreyk } 2052cb8b0e56Sreyk 205365f47834Sreyk if (relay_apply_actions(cre, &actions, tbl) != 0) { 2054cb8b0e56Sreyk /* Something bad happened, drop */ 2055cb8b0e56Sreyk action = RES_DROP; 2056cb8b0e56Sreyk } 2057cb8b0e56Sreyk 2058cb8b0e56Sreyk DPRINTF("%s: session %d: action %d", __func__, 2059cb8b0e56Sreyk con->se_id, action); 2060cb8b0e56Sreyk 2061cb8b0e56Sreyk return (action); 2062cb8b0e56Sreyk } 2063cb8b0e56Sreyk 2064cb8b0e56Sreyk #define RELAY_SET_SKIP_STEPS(i) \ 2065cb8b0e56Sreyk do { \ 2066cb8b0e56Sreyk while (head[i] != cur) { \ 2067cb8b0e56Sreyk head[i]->rule_skip[i] = cur; \ 2068cb8b0e56Sreyk head[i] = TAILQ_NEXT(head[i], rule_entry); \ 2069cb8b0e56Sreyk } \ 2070cb8b0e56Sreyk } while (0) 2071cb8b0e56Sreyk 2072cb8b0e56Sreyk /* This code is derived from pf_calc_skip_steps() from pf.c */ 2073cb8b0e56Sreyk void 2074cb8b0e56Sreyk relay_calc_skip_steps(struct relay_rules *rules) 2075cb8b0e56Sreyk { 2076cb8b0e56Sreyk struct relay_rule *head[RULE_SKIP_COUNT], *cur, *prev; 2077cb8b0e56Sreyk int i; 2078cb8b0e56Sreyk 2079cb8b0e56Sreyk cur = TAILQ_FIRST(rules); 2080cb8b0e56Sreyk prev = cur; 2081cb8b0e56Sreyk for (i = 0; i < RULE_SKIP_COUNT; ++i) 2082cb8b0e56Sreyk head[i] = cur; 2083cb8b0e56Sreyk while (cur != NULL) { 2084cb8b0e56Sreyk if (cur->rule_dir != prev->rule_dir) 2085cb8b0e56Sreyk RELAY_SET_SKIP_STEPS(RULE_SKIP_DIR); 2086cb8b0e56Sreyk else if (cur->rule_proto != prev->rule_proto) 2087cb8b0e56Sreyk RELAY_SET_SKIP_STEPS(RULE_SKIP_PROTO); 2088860302f3Sreyk else if (RELAY_AF_NEQ(cur->rule_af, prev->rule_af)) 2089cb8b0e56Sreyk RELAY_SET_SKIP_STEPS(RULE_SKIP_AF); 2090cb8b0e56Sreyk else if (RELAY_ADDR_NEQ(&cur->rule_src, &prev->rule_src)) 2091cb8b0e56Sreyk RELAY_SET_SKIP_STEPS(RULE_SKIP_SRC); 2092cb8b0e56Sreyk else if (RELAY_ADDR_NEQ(&cur->rule_dst, &prev->rule_dst)) 2093cb8b0e56Sreyk RELAY_SET_SKIP_STEPS(RULE_SKIP_DST); 2094cb8b0e56Sreyk else if (cur->rule_method != prev->rule_method) 2095cb8b0e56Sreyk RELAY_SET_SKIP_STEPS(RULE_SKIP_METHOD); 2096cb8b0e56Sreyk 2097cb8b0e56Sreyk prev = cur; 2098cb8b0e56Sreyk cur = TAILQ_NEXT(cur, rule_entry); 2099cb8b0e56Sreyk } 2100cb8b0e56Sreyk for (i = 0; i < RULE_SKIP_COUNT; ++i) 2101cb8b0e56Sreyk RELAY_SET_SKIP_STEPS(i); 2102cb8b0e56Sreyk } 2103cb8b0e56Sreyk 2104cb8b0e56Sreyk void 2105cb8b0e56Sreyk relay_match(struct kvlist *actions, struct kv *kv, struct kv *match, 2106c307a266Sreyk struct kvtree *matchtree) 2107cb8b0e56Sreyk { 2108cb8b0e56Sreyk if (kv->kv_option != KEY_OPTION_NONE) { 2109cb8b0e56Sreyk kv->kv_match = match; 2110c307a266Sreyk kv->kv_matchtree = matchtree; 2111cb8b0e56Sreyk TAILQ_INSERT_TAIL(actions, kv, kv_match_entry); 2112cb8b0e56Sreyk } 2113a15b848eSreyk } 2114eeb1fea4Sdenis 2115eeb1fea4Sdenis char * 2116eeb1fea4Sdenis server_root_strip(char *path, int n) 2117eeb1fea4Sdenis { 2118eeb1fea4Sdenis char *p; 2119eeb1fea4Sdenis 2120eeb1fea4Sdenis /* Strip strip leading directories. Leading '/' is ignored. */ 2121eeb1fea4Sdenis for (; n > 0 && *path != '\0'; n--) 2122eeb1fea4Sdenis if ((p = strchr(++path, '/')) != NULL) 2123eeb1fea4Sdenis path = p; 2124eeb1fea4Sdenis else 2125eeb1fea4Sdenis path--; 2126eeb1fea4Sdenis 2127eeb1fea4Sdenis return (path); 2128eeb1fea4Sdenis } 2129eeb1fea4Sdenis 2130