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