xref: /openbsd-src/usr.sbin/relayd/relay_http.c (revision 79db477be4e393b3377af541ed18a9654fda1df1)
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