Bug Summary

File:src/usr.sbin/relayd/relay_http.c
Warning:line 444, column 25
The left operand of '==' is a garbage value

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.4 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name relay_http.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/usr.sbin/relayd/obj -resource-dir /usr/local/llvm16/lib/clang/16 -I /usr/src/usr.sbin/relayd -internal-isystem /usr/local/llvm16/lib/clang/16/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.sbin/relayd/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fcf-protection=branch -fno-jump-tables -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/scan/2024-01-11-140451-98009-1 -x c /usr/src/usr.sbin/relayd/relay_http.c
1/* $OpenBSD: relay_http.c,v 1.87 2023/12/01 16:48:40 millert Exp $ */
2
3/*
4 * Copyright (c) 2006 - 2016 Reyk Floeter <reyk@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/types.h>
20#include <sys/queue.h>
21#include <sys/time.h>
22#include <sys/socket.h>
23#include <sys/tree.h>
24
25#include <netinet/in.h>
26#include <arpa/inet.h>
27
28#include <limits.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <errno(*__errno()).h>
32#include <string.h>
33#include <time.h>
34#include <event.h>
35#include <fnmatch.h>
36#include <siphash.h>
37#include <imsg.h>
38#include <unistd.h>
39
40#include "relayd.h"
41#include "http.h"
42
43static int _relay_lookup_url(struct ctl_relay_event *, char *, char *,
44 char *, struct kv *);
45int relay_lookup_url(struct ctl_relay_event *,
46 const char *, struct kv *);
47int relay_lookup_query(struct ctl_relay_event *, struct kv *);
48int relay_lookup_cookie(struct ctl_relay_event *, const char *,
49 struct kv *);
50void relay_read_httpcontent(struct bufferevent *, void *);
51void relay_read_httpchunks(struct bufferevent *, void *);
52char *relay_expand_http(struct ctl_relay_event *, char *,
53 char *, size_t);
54int relay_writeheader_kv(struct ctl_relay_event *, struct kv *);
55int relay_writeheader_http(struct ctl_relay_event *,
56 struct ctl_relay_event *);
57int relay_writerequest_http(struct ctl_relay_event *,
58 struct ctl_relay_event *);
59int relay_writeresponse_http(struct ctl_relay_event *,
60 struct ctl_relay_event *);
61void relay_reset_http(struct ctl_relay_event *);
62static int relay_httpmethod_cmp(const void *, const void *);
63static int relay_httperror_cmp(const void *, const void *);
64int relay_httpquery_test(struct ctl_relay_event *,
65 struct relay_rule *, struct kvlist *);
66int relay_httpheader_test(struct ctl_relay_event *,
67 struct relay_rule *, struct kvlist *);
68int relay_httppath_test(struct ctl_relay_event *,
69 struct relay_rule *, struct kvlist *);
70int relay_httpurl_test(struct ctl_relay_event *,
71 struct relay_rule *, struct kvlist *);
72int relay_httpcookie_test(struct ctl_relay_event *,
73 struct relay_rule *, struct kvlist *);
74int relay_apply_actions(struct ctl_relay_event *, struct kvlist *,
75 struct relay_table *);
76int relay_match_actions(struct ctl_relay_event *,
77 struct relay_rule *, struct kvlist *, struct kvlist *,
78 struct relay_table **);
79void relay_httpdesc_free(struct http_descriptor *);
80char * server_root_strip(char *, int);
81
82static struct relayd *env = NULL((void *)0);
83
84static struct http_method http_methods[] = HTTP_METHODS{ { HTTP_METHOD_GET, "GET" }, { HTTP_METHOD_HEAD, "HEAD" }, {
HTTP_METHOD_POST, "POST" }, { HTTP_METHOD_PUT, "PUT" }, { HTTP_METHOD_DELETE
, "DELETE" }, { HTTP_METHOD_OPTIONS, "OPTIONS" }, { HTTP_METHOD_TRACE
, "TRACE" }, { HTTP_METHOD_CONNECT, "CONNECT" }, { HTTP_METHOD_PROPFIND
, "PROPFIND" }, { HTTP_METHOD_PROPPATCH, "PROPPATCH" }, { HTTP_METHOD_MKCOL
, "MKCOL" }, { HTTP_METHOD_COPY, "COPY" }, { HTTP_METHOD_MOVE
, "MOVE" }, { HTTP_METHOD_LOCK, "LOCK" }, { HTTP_METHOD_UNLOCK
, "UNLOCK" }, { HTTP_METHOD_VERSION_CONTROL, "VERSION-CONTROL"
}, { HTTP_METHOD_REPORT, "REPORT" }, { HTTP_METHOD_CHECKOUT,
"CHECKOUT" }, { HTTP_METHOD_CHECKIN, "CHECKIN" }, { HTTP_METHOD_UNCHECKOUT
, "UNCHECKOUT" }, { HTTP_METHOD_MKWORKSPACE, "MKWORKSPACE" },
{ HTTP_METHOD_UPDATE, "UPDATE" }, { HTTP_METHOD_LABEL, "LABEL"
}, { HTTP_METHOD_MERGE, "MERGE" }, { HTTP_METHOD_BASELINE_CONTROL
, "BASELINE-CONTROL" }, { HTTP_METHOD_MKACTIVITY, "MKACTIVITY"
}, { HTTP_METHOD_ORDERPATCH, "ORDERPATCH" }, { HTTP_METHOD_ACL
, "ACL" }, { HTTP_METHOD_MKREDIRECTREF, "MKREDIRECTREF" }, { HTTP_METHOD_UPDATEREDIRECTREF
, "UPDATEREDIRECTREF" }, { HTTP_METHOD_SEARCH, "SEARCH" }, { HTTP_METHOD_PATCH
, "PATCH" }, { HTTP_METHOD_NONE, ((void *)0) } }
;
85static struct http_error http_errors[] = HTTP_ERRORS{ { 100, "Continue" }, { 101, "Switching Protocols" }, { 102,
"Processing" }, { 200, "OK" }, { 201, "Created" }, { 202, "Accepted"
}, { 203, "Non-Authoritative Information" }, { 204, "No Content"
}, { 205, "Reset Content" }, { 206, "Partial Content" }, { 207
, "Multi-Status" }, { 208, "Already Reported" }, { 226, "IM Used"
}, { 300, "Multiple Choices" }, { 301, "Moved Permanently" }
, { 302, "Found" }, { 303, "See Other" }, { 304, "Not Modified"
}, { 305, "Use Proxy" }, { 306, "Switch Proxy" }, { 307, "Temporary Redirect"
}, { 308, "Permanent Redirect" }, { 400, "Bad Request" }, { 401
, "Unauthorized" }, { 402, "Payment Required" }, { 403, "Forbidden"
}, { 404, "Not Found" }, { 405, "Method Not Allowed" }, { 406
, "Not Acceptable" }, { 407, "Proxy Authentication Required" }
, { 408, "Request Timeout" }, { 409, "Conflict" }, { 410, "Gone"
}, { 411, "Length Required" }, { 412, "Precondition Failed" }
, { 413, "Payload Too Large" }, { 414, "URI Too Long" }, { 415
, "Unsupported Media Type" }, { 416, "Range Not Satisfiable" }
, { 417, "Expectation Failed" }, { 418, "I'm a teapot" }, { 420
, "Enhance Your Calm" }, { 422, "Unprocessable Entity" }, { 423
, "Locked" }, { 424, "Failed Dependency" }, { 426, "Upgrade Required"
}, { 428, "Precondition Required" }, { 429, "Too Many Requests"
}, { 431, "Request Header Fields Too Large" }, { 451, "Unavailable For Legal Reasons"
}, { 500, "Internal Server Error" }, { 501, "Not Implemented"
}, { 502, "Bad Gateway" }, { 503, "Service Unavailable" }, {
504, "Gateway Timeout" }, { 505, "HTTP Version Not Supported"
}, { 506, "Variant Also Negotiates" }, { 507, "Insufficient Storage"
}, { 508, "Loop Detected" }, { 510, "Not Extended" }, { 511,
"Network Authentication Required" }, { 0, ((void *)0) } }
;
86
87void
88relay_http(struct relayd *x_env)
89{
90 if (x_env != NULL((void *)0))
91 env = x_env;
92
93 DPRINTF("%s: sorting lookup tables, pid %d", __func__, getpid())do {} while(0);
94
95 /* Sort the HTTP lookup arrays */
96 qsort(http_methods, sizeof(http_methods) /
97 sizeof(http_methods[0]) - 1,
98 sizeof(http_methods[0]), relay_httpmethod_cmp);
99 qsort(http_errors, sizeof(http_errors) /
100 sizeof(http_errors[0]) - 1,
101 sizeof(http_errors[0]), relay_httperror_cmp);
102}
103
104void
105relay_http_init(struct relay *rlay)
106{
107 rlay->rl_proto->close = relay_close_http;
108
109 relay_http(NULL((void *)0));
110
111 /* Calculate skip step for the filter rules (may take a while) */
112 relay_calc_skip_steps(&rlay->rl_proto->rules);
113}
114
115int
116relay_http_priv_init(struct rsession *con)
117{
118
119 struct http_session *hs;
120
121 if ((hs = calloc(1, sizeof(*hs))) == NULL((void *)0))
122 return (-1);
123 SIMPLEQ_INIT(&hs->hs_methods)do { (&hs->hs_methods)->sqh_first = ((void *)0); (&
hs->hs_methods)->sqh_last = &(&hs->hs_methods
)->sqh_first; } while (0)
;
124 DPRINTF("%s: session %d http_session %p", __func__,do {} while(0)
125 con->se_id, hs)do {} while(0);
126 con->se_priv = hs;
127 return (relay_httpdesc_init(&con->se_in));
128}
129
130int
131relay_httpdesc_init(struct ctl_relay_event *cre)
132{
133 struct http_descriptor *desc;
134
135 if ((desc = calloc(1, sizeof(*desc))) == NULL((void *)0))
136 return (-1);
137
138 RB_INIT(&desc->http_headers)do { (&desc->http_headers)->rbh_root = ((void *)0);
} while (0)
;
139 cre->desc = desc;
140
141 return (0);
142}
143
144void
145relay_httpdesc_free(struct http_descriptor *desc)
146{
147 if (desc == NULL((void *)0))
148 return;
149
150 free(desc->http_pathhttp_pathquery.kv_key);
151 desc->http_pathhttp_pathquery.kv_key = NULL((void *)0);
152 free(desc->http_queryhttp_pathquery.kv_value);
153 desc->http_queryhttp_pathquery.kv_value = NULL((void *)0);
154 free(desc->http_version);
155 desc->http_version = NULL((void *)0);
156 free(desc->query_keyhttp_matchquery.kv_key);
157 desc->query_keyhttp_matchquery.kv_key = NULL((void *)0);
158 free(desc->query_valhttp_matchquery.kv_value);
159 desc->query_valhttp_matchquery.kv_value = NULL((void *)0);
160 kv_purge(&desc->http_headers);
161 desc->http_lastheader = NULL((void *)0);
162}
163
164static int
165relay_http_header_name_valid(const char *name)
166{
167 /*
168 * RFC 9110 specifies that only the following characters are
169 * permitted within HTTP header field names.
170 */
171 const char token_chars[] = "!#$%&'*+-.^_`|~0123456789"
172 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
173 const size_t len = strspn(name, token_chars);
174
175 return (name[len] == '\0');
176}
177
178void
179relay_read_http(struct bufferevent *bev, void *arg)
180{
181 struct ctl_relay_event *cre = arg;
182 struct http_descriptor *desc = cre->desc;
183 struct rsession *con = cre->con;
184 struct relay *rlay = con->se_relay;
185 struct protocol *proto = rlay->rl_proto;
186 struct evbuffer *src = EVBUFFER_INPUT(bev)(bev)->input;
187 char *line = NULL((void *)0), *key, *value;
188 char *urlproto, *host, *path;
189 int action, unique, ret;
190 const char *errstr;
191 size_t size, linelen;
192 struct kv *hdr = NULL((void *)0);
193 struct kv *upgrade = NULL((void *)0), *upgrade_ws = NULL((void *)0);
194 struct kv *connection_close = NULL((void *)0);
195 int ws_response = 0;
196 struct http_method_node *hmn;
197 struct http_session *hs;
198 enum httpmethod request_method;
1
'request_method' declared without an initial value
199
200 getmonotime(&con->se_tv_last);
201 cre->timedout = 0;
202
203 size = EVBUFFER_LENGTH(src)(src)->off;
204 DPRINTF("%s: session %d: size %lu, to read %lld",do {} while(0)
2
Loop condition is false. Exiting loop
205 __func__, con->se_id, size, cre->toread)do {} while(0);
206 if (size == 0) {
3
Assuming 'size' is not equal to 0
4
Taking false branch
207 if (cre->dir == RELAY_DIR_RESPONSE)
208 return;
209 cre->toread = TOREAD_HTTP_HEADER;
210 goto done;
211 }
212
213 for (;;) {
5
Loop condition is true. Entering loop body
214 line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF);
215 if (line == NULL((void *)0)) {
6
Assuming 'line' is not equal to NULL
7
Taking false branch
216 /*
217 * We do not process the last header on premature
218 * EOF as it may not be complete.
219 */
220 break;
221 }
222
223 /*
224 * An empty line indicates the end of the request.
225 * libevent already stripped the \r\n for us.
226 */
227 if (linelen == 0) {
8
Assuming 'linelen' is equal to 0
9
Taking true branch
228 cre->done = 1;
229 free(line);
230 line = NULL((void *)0);
231 if (cre->line > 1) {
10
Assuming field 'line' is > 1
11
Taking true branch
232 /* Process last (complete) header line. */
233 goto last_header;
12
Control jumps to line 407
234 }
235 break;
236 }
237
238 /* Limit the total header length minus \r\n */
239 cre->headerlen += linelen;
240 if (cre->headerlen > proto->httpheaderlen) {
241 relay_abort_http(con, 413,
242 "request headers too large", 0);
243 goto abort;
244 }
245
246 /* Reject requests with an embedded NUL byte. */
247 if (memchr(line, '\0', linelen) != NULL((void *)0)) {
248 relay_abort_http(con, 400, "malformed", 0);
249 goto abort;
250 }
251
252 hs = con->se_priv;
253 DPRINTF("%s: session %d http_session %p", __func__,do {} while(0)
254 con->se_id, hs)do {} while(0);
255
256 /*
257 * The first line is the GET/POST/PUT/... request,
258 * subsequent lines are HTTP headers.
259 */
260 if (++cre->line == 1) {
261 key = line;
262 if ((value = strchr(key, ' ')) == NULL((void *)0)) {
263 relay_abort_http(con, 400, "malformed", 0);
264 goto abort;
265 }
266 *value++ = '\0';
267
268 if (cre->dir == RELAY_DIR_RESPONSE) {
269 desc->http_method = HTTP_METHOD_RESPONSE;
270 hmn = SIMPLEQ_FIRST(&hs->hs_methods)((&hs->hs_methods)->sqh_first);
271
272 /*
273 * There is nothing preventing the relay from
274 * sending an unbalanced response. Be prepared.
275 */
276 if (hmn == NULL((void *)0)) {
277 request_method = HTTP_METHOD_NONE;
278 DPRINTF("%s: session %d unbalanced "do {} while(0)
279 "response", __func__, con->se_id)do {} while(0);
280 } else {
281 SIMPLEQ_REMOVE_HEAD(&hs->hs_methods,do { if (((&hs->hs_methods)->sqh_first = (&hs->
hs_methods)->sqh_first->hmn_entry.sqe_next) == ((void *
)0)) (&hs->hs_methods)->sqh_last = &(&hs->
hs_methods)->sqh_first; } while (0)
282 hmn_entry)do { if (((&hs->hs_methods)->sqh_first = (&hs->
hs_methods)->sqh_first->hmn_entry.sqe_next) == ((void *
)0)) (&hs->hs_methods)->sqh_last = &(&hs->
hs_methods)->sqh_first; } while (0)
;
283 request_method = hmn->hmn_method;
284 DPRINTF("%s: session %d dequeuing %s",do {} while(0)
285 __func__, con->se_id,do {} while(0)
286 relay_httpmethod_byid(request_method))do {} while(0);
287 free(hmn);
288 }
289
290 /*
291 * Decode response path and query
292 */
293 desc->http_version = strdup(key);
294 if (desc->http_version == NULL((void *)0)) {
295 free(line);
296 goto fail;
297 }
298 desc->http_rescodehttp_pathquery.kv_key = strdup(value);
299 if (desc->http_rescodehttp_pathquery.kv_key == NULL((void *)0)) {
300 free(line);
301 goto fail;
302 }
303 desc->http_resmesghttp_pathquery.kv_value = strchr(desc->http_rescodehttp_pathquery.kv_key,
304 ' ');
305 if (desc->http_resmesghttp_pathquery.kv_value == NULL((void *)0)) {
306 free(line);
307 goto fail;
308 }
309 *desc->http_resmesghttp_pathquery.kv_value++ = '\0';
310 desc->http_resmesghttp_pathquery.kv_value = strdup(desc->http_resmesghttp_pathquery.kv_value);
311 if (desc->http_resmesghttp_pathquery.kv_value == NULL((void *)0)) {
312 free(line);
313 goto fail;
314 }
315 desc->http_status = strtonum(desc->http_rescodehttp_pathquery.kv_key,
316 100, 599, &errstr);
317 if (errstr) {
318 DPRINTF(do {} while(0)
319 "%s: http_status %s: errno %d, %s",do {} while(0)
320 __func__, desc->http_rescode, errno,do {} while(0)
321 errstr)do {} while(0);
322 free(line);
323 goto fail;
324 }
325 DPRINTF("http_version %s http_rescode %s "do {} while(0)
326 "http_resmesg %s", desc->http_version,do {} while(0)
327 desc->http_rescode, desc->http_resmesg)do {} while(0);
328 } else if (cre->dir == RELAY_DIR_REQUEST) {
329 desc->http_method =
330 relay_httpmethod_byname(key);
331 if (desc->http_method == HTTP_METHOD_NONE) {
332 free(line);
333 goto fail;
334 }
335 if ((hmn = calloc(1, sizeof *hmn)) == NULL((void *)0)) {
336 free(line);
337 goto fail;
338 }
339 hmn->hmn_method = desc->http_method;
340 DPRINTF("%s: session %d enqueuing %s",do {} while(0)
341 __func__, con->se_id,do {} while(0)
342 relay_httpmethod_byid(hmn->hmn_method))do {} while(0);
343 SIMPLEQ_INSERT_TAIL(&hs->hs_methods, hmn,do { (hmn)->hmn_entry.sqe_next = ((void *)0); *(&hs->
hs_methods)->sqh_last = (hmn); (&hs->hs_methods)->
sqh_last = &(hmn)->hmn_entry.sqe_next; } while (0)
344 hmn_entry)do { (hmn)->hmn_entry.sqe_next = ((void *)0); *(&hs->
hs_methods)->sqh_last = (hmn); (&hs->hs_methods)->
sqh_last = &(hmn)->hmn_entry.sqe_next; } while (0)
;
345 /*
346 * Decode request path and query
347 */
348 desc->http_pathhttp_pathquery.kv_key = strdup(value);
349 if (desc->http_pathhttp_pathquery.kv_key == NULL((void *)0)) {
350 free(line);
351 goto fail;
352 }
353 desc->http_version = strchr(desc->http_pathhttp_pathquery.kv_key,
354 ' ');
355 if (desc->http_version == NULL((void *)0)) {
356 free(line);
357 goto fail;
358 }
359 *desc->http_version++ = '\0';
360 desc->http_queryhttp_pathquery.kv_value = strchr(desc->http_pathhttp_pathquery.kv_key, '?');
361 if (desc->http_queryhttp_pathquery.kv_value != NULL((void *)0))
362 *desc->http_queryhttp_pathquery.kv_value++ = '\0';
363
364 /*
365 * Have to allocate the strings because they
366 * could be changed independently by the
367 * filters later.
368 */
369 if ((desc->http_version =
370 strdup(desc->http_version)) == NULL((void *)0)) {
371 free(line);
372 goto fail;
373 }
374 if (desc->http_queryhttp_pathquery.kv_value != NULL((void *)0) &&
375 (desc->http_queryhttp_pathquery.kv_value =
376 strdup(desc->http_queryhttp_pathquery.kv_value)) == NULL((void *)0)) {
377 free(line);
378 goto fail;
379 }
380 }
381
382 free(line);
383 continue;
384 }
385
386 /* Multiline headers wrap with a space or tab. */
387 if (*line == ' ' || *line == '\t') {
388 if (cre->line == 2) {
389 /* First header line cannot start with space. */
390 relay_abort_http(con, 400, "malformed", 0);
391 goto abort;
392 }
393
394 /* Append line to the last header, if present */
395 if (kv_extend(&desc->http_headers,
396 desc->http_lastheader, line) == NULL((void *)0)) {
397 free(line);
398 goto fail;
399 }
400
401 free(line);
402 continue;
403 }
404
405 /* Process the last complete header line. */
406 last_header:
407 if (desc->http_lastheader != NULL((void *)0)) {
13
Assuming field 'http_lastheader' is not equal to NULL
14
Taking true branch
408 key = desc->http_lastheader->kv_key;
409 value = desc->http_lastheader->kv_value;
410
411 DPRINTF("%s: session %d: header '%s: %s'", __func__,do {} while(0)
412 con->se_id, key, value)do {} while(0);
413
414 if (desc->http_method != HTTP_METHOD_NONE &&
15
Assuming field 'http_method' is not equal to HTTP_METHOD_NONE
16
Taking true branch
415 strcasecmp("Content-Length", key) == 0) {
416 switch (desc->http_method) {
17
Control jumps to 'case HTTP_METHOD_RESPONSE:' at line 438
417 case HTTP_METHOD_TRACE:
418 case HTTP_METHOD_CONNECT:
419 /*
420 * These methods should not have a body
421 * and thus no Content-Length header.
422 */
423 relay_abort_http(con, 400, "malformed",
424 0);
425 goto abort;
426 case HTTP_METHOD_GET:
427 case HTTP_METHOD_HEAD:
428 case HTTP_METHOD_COPY:
429 case HTTP_METHOD_MOVE:
430 /*
431 * We strip the body (if present) from
432 * the GET, HEAD, COPY and MOVE methods
433 * so strip Content-Length too.
434 */
435 kv_delete(&desc->http_headers,
436 desc->http_lastheader);
437 break;
438 case HTTP_METHOD_RESPONSE:
439 /*
440 * Strip Content-Length header from
441 * HEAD responses since there is no
442 * actual payload in the response.
443 */
444 if (request_method == HTTP_METHOD_HEAD) {
18
The left operand of '==' is a garbage value
445 kv_delete(&desc->http_headers,
446 desc->http_lastheader);
447 break;
448 }
449 /* FALLTHROUGH */
450 default:
451 /*
452 * Need to read data from the client
453 * after the HTTP header.
454 * XXX What about non-standard clients
455 * not using the carriage return? And
456 * some browsers seem to include the
457 * line length in the content-length.
458 */
459 if (*value == '+' || *value == '-') {
460 errstr = "invalid";
461 } else {
462 cre->toread = strtonum(value, 0,
463 LLONG_MAX0x7fffffffffffffffLL, &errstr);
464 }
465 if (errstr) {
466 relay_abort_http(con, 500,
467 errstr, 0);
468 goto abort;
469 }
470 break;
471 }
472 /*
473 * Response with a status code of 1xx
474 * (Informational) or 204 (No Content) MUST
475 * not have a Content-Length (rfc 7230 3.3.3)
476 * Instead we check for value != 0 because there
477 * are servers that do not follow the rfc and
478 * send Content-Length: 0.
479 */
480 if (desc->http_method == HTTP_METHOD_RESPONSE &&
481 (((desc->http_status >= 100 &&
482 desc->http_status < 200) ||
483 desc->http_status == 204)) &&
484 cre->toread != 0) {
485 relay_abort_http(con, 502,
486 "Bad Gateway", 0);
487 goto abort;
488 }
489 }
490 if (strcasecmp("Transfer-Encoding", key) == 0) {
491 /* We don't support other encodings. */
492 if (strcasecmp("chunked", value) != 0) {
493 relay_abort_http(con, 400,
494 "malformed", 0);
495 goto abort;
496 }
497 desc->http_chunked = 1;
498 }
499
500 if (strcasecmp("Host", key) == 0) {
501 /*
502 * The path may contain a URL. The host in the
503 * URL has to match the Host: value.
504 */
505 if (parse_url(desc->http_pathhttp_pathquery.kv_key,
506 &urlproto, &host, &path) == 0) {
507 ret = strcasecmp(host, value);
508 free(urlproto);
509 free(host);
510 free(path);
511 if (ret != 0) {
512 relay_abort_http(con, 400,
513 "malformed host", 0);
514 goto abort;
515 }
516 }
517 }
518 }
519
520 if (cre->done)
521 break;
522
523 /* Validate header field name and check for missing value. */
524 key = line;
525 if ((value = strchr(line, ':')) == NULL((void *)0)) {
526 relay_abort_http(con, 400, "malformed", 0);
527 goto abort;
528 }
529 *value++ = '\0';
530 value += strspn(value, " \t\r\n");
531
532 if (!relay_http_header_name_valid(key)) {
533 relay_abort_http(con, 400, "malformed", 0);
534 goto abort;
535 }
536
537 /* The "Host" header must only occur once. */
538 unique = strcasecmp("Host", key) == 0;
539
540 if ((hdr = kv_add(&desc->http_headers, key,
541 value, unique)) == NULL((void *)0)) {
542 relay_abort_http(con, 400, "malformed header", 0);
543 goto abort;
544 }
545 desc->http_lastheader = hdr;
546
547 free(line);
548 }
549
550 if (cre->done) {
551 if (desc->http_method == HTTP_METHOD_NONE) {
552 relay_abort_http(con, 406, "no method", 0);
553 return;
554 }
555
556 action = relay_test(proto, cre);
557 switch (action) {
558 case RES_FAIL:
559 relay_close(con, "filter rule failed", 1);
560 return;
561 case RES_BAD:
562 relay_abort_http(con, 400, "Bad Request",
563 con->se_label);
564 return;
565 case RES_INTERNAL:
566 relay_abort_http(con, 500, "Internal Server Error",
567 con->se_label);
568 return;
569 }
570 if (action != RES_PASS) {
571 relay_abort_http(con, 403, "Forbidden", con->se_label);
572 return;
573 }
574
575 /*
576 * HTTP 101 Switching Protocols
577 */
578
579 upgrade = kv_find_value(&desc->http_headers,
580 "Connection", "upgrade", ",");
581 upgrade_ws = kv_find_value(&desc->http_headers,
582 "Upgrade", "websocket", ",");
583 ws_response = 0;
584 if (cre->dir == RELAY_DIR_REQUEST && upgrade_ws != NULL((void *)0)) {
585 if ((proto->httpflags & HTTPFLAG_WEBSOCKETS0x01) == 0) {
586 relay_abort_http(con, 403,
587 "Websocket Forbidden", 0);
588 return;
589 } else if (upgrade == NULL((void *)0)) {
590 relay_abort_http(con, 400,
591 "Bad Websocket Request", 0);
592 return;
593 } else if (desc->http_method != HTTP_METHOD_GET) {
594 relay_abort_http(con, 405,
595 "Websocket Method Not Allowed", 0);
596 return;
597 }
598 } else if (cre->dir == RELAY_DIR_RESPONSE &&
599 desc->http_status == 101) {
600 if (upgrade_ws != NULL((void *)0) && upgrade != NULL((void *)0) &&
601 (proto->httpflags & HTTPFLAG_WEBSOCKETS0x01)) {
602 ws_response = 1;
603 cre->dst->toread = TOREAD_UNLIMITED;
604 cre->dst->bev->readcb = relay_read;
605 } else {
606 relay_abort_http(con, 502,
607 "Bad Websocket Gateway", 0);
608 return;
609 }
610 }
611
612 connection_close = kv_find_value(&desc->http_headers,
613 "Connection", "close", ",");
614
615 switch (desc->http_method) {
616 case HTTP_METHOD_CONNECT:
617 /* Data stream */
618 cre->toread = TOREAD_UNLIMITED;
619 bev->readcb = relay_read;
620 break;
621 case HTTP_METHOD_GET:
622 case HTTP_METHOD_HEAD:
623 /* WebDAV methods */
624 case HTTP_METHOD_COPY:
625 case HTTP_METHOD_MOVE:
626 cre->toread = 0;
627 break;
628 case HTTP_METHOD_DELETE:
629 case HTTP_METHOD_OPTIONS:
630 case HTTP_METHOD_POST:
631 case HTTP_METHOD_PUT:
632 case HTTP_METHOD_RESPONSE:
633 /* WebDAV methods */
634 case HTTP_METHOD_PROPFIND:
635 case HTTP_METHOD_PROPPATCH:
636 case HTTP_METHOD_MKCOL:
637 case HTTP_METHOD_LOCK:
638 case HTTP_METHOD_UNLOCK:
639 case HTTP_METHOD_VERSION_CONTROL:
640 case HTTP_METHOD_REPORT:
641 case HTTP_METHOD_CHECKOUT:
642 case HTTP_METHOD_CHECKIN:
643 case HTTP_METHOD_UNCHECKOUT:
644 case HTTP_METHOD_MKWORKSPACE:
645 case HTTP_METHOD_UPDATE:
646 case HTTP_METHOD_LABEL:
647 case HTTP_METHOD_MERGE:
648 case HTTP_METHOD_BASELINE_CONTROL:
649 case HTTP_METHOD_MKACTIVITY:
650 case HTTP_METHOD_ORDERPATCH:
651 case HTTP_METHOD_ACL:
652 case HTTP_METHOD_MKREDIRECTREF:
653 case HTTP_METHOD_UPDATEREDIRECTREF:
654 case HTTP_METHOD_SEARCH:
655 case HTTP_METHOD_PATCH:
656 /* HTTP request payload */
657 if (cre->toread > 0) {
658 bev->readcb = relay_read_httpcontent;
659 }
660
661 /* Single-pass HTTP body */
662 if (cre->toread < 0) {
663 cre->toread = TOREAD_UNLIMITED;
664 bev->readcb = relay_read;
665 }
666 break;
667 default:
668 /* HTTP handler */
669 cre->toread = TOREAD_HTTP_HEADER;
670 bev->readcb = relay_read_http;
671 break;
672 }
673 if (desc->http_chunked) {
674 /* Chunked transfer encoding */
675 cre->toread = TOREAD_HTTP_CHUNK_LENGTH;
676 bev->readcb = relay_read_httpchunks;
677 }
678
679 /*
680 * Ask the server to close the connection after this request
681 * since we don't read any further request headers. Only add
682 * this header if it does not already exist or if this is a
683 * outbound websocket upgrade response.
684 */
685 if (cre->toread == TOREAD_UNLIMITED &&
686 connection_close == NULL((void *)0) && !ws_response)
687 if (kv_add(&desc->http_headers, "Connection",
688 "close", 0) == NULL((void *)0))
689 goto fail;
690
691 if (cre->dir == RELAY_DIR_REQUEST) {
692 if (relay_writerequest_http(cre->dst, cre) == -1)
693 goto fail;
694 } else {
695 if (relay_writeresponse_http(cre->dst, cre) == -1)
696 goto fail;
697 }
698 if (relay_bufferevent_print(cre->dst, "\r\n") == -1 ||
699 relay_writeheader_http(cre->dst, cre) == -1 ||
700 relay_bufferevent_print(cre->dst, "\r\n") == -1)
701 goto fail;
702
703 relay_reset_http(cre);
704 done:
705 if (cre->dir == RELAY_DIR_REQUEST && cre->toread <= 0 &&
706 cre->dst->state != STATE_CONNECTED) {
707 if (rlay->rl_conf.fwdmode == FWD_TRANS) {
708 relay_bindanyreq(con, 0, IPPROTO_TCP6);
709 return;
710 }
711 if (relay_connect(con) == -1) {
712 relay_abort_http(con, 502, "session failed", 0);
713 return;
714 }
715 }
716 }
717 if (con->se_done) {
718 relay_close(con, "last http read (done)", 0);
719 return;
720 }
721 switch (relay_splice(cre)) {
722 case -1:
723 relay_close(con, strerror(errno(*__errno())), 1);
724 case 1:
725 return;
726 case 0:
727 break;
728 }
729 bufferevent_enable(bev, EV_READ0x02);
730 if (EVBUFFER_LENGTH(src)(src)->off && bev->readcb != relay_read_http)
731 bev->readcb(bev, arg);
732 /* The callback readcb() might have freed the session. */
733 return;
734 fail:
735 relay_abort_http(con, 500, strerror(errno(*__errno())), 0);
736 return;
737 abort:
738 free(line);
739}
740
741void
742relay_read_httpcontent(struct bufferevent *bev, void *arg)
743{
744 struct ctl_relay_event *cre = arg;
745 struct rsession *con = cre->con;
746 struct protocol *proto = con->se_relay->rl_proto;
747
748 struct evbuffer *src = EVBUFFER_INPUT(bev)(bev)->input;
749 size_t size;
750
751 getmonotime(&con->se_tv_last);
752 cre->timedout = 0;
753
754 size = EVBUFFER_LENGTH(src)(src)->off;
755 DPRINTF("%s: session %d: size %lu, to read %lld", __func__,do {} while(0)
756 con->se_id, size, cre->toread)do {} while(0);
757 if (!size)
758 return;
759 if (relay_spliceadjust(cre) == -1)
760 goto fail;
761
762 if (cre->toread > 0) {
763 /* Read content data */
764 if ((off_t)size > cre->toread) {
765 size = cre->toread;
766 if (relay_bufferevent_write_chunk(cre->dst, src, size)
767 == -1)
768 goto fail;
769 cre->toread = 0;
770 } else {
771 if (relay_bufferevent_write_buffer(cre->dst, src) == -1)
772 goto fail;
773 cre->toread -= size;
774 }
775 DPRINTF("%s: done, size %lu, to read %lld", __func__,do {} while(0)
776 size, cre->toread)do {} while(0);
777 }
778 if (cre->toread == 0) {
779 cre->toread = TOREAD_HTTP_HEADER;
780 bev->readcb = relay_read_http;
781 }
782 if (con->se_done)
783 goto done;
784 bufferevent_enable(bev, EV_READ0x02);
785
786 if (cre->dst->bev && EVBUFFER_LENGTH(EVBUFFER_OUTPUT(cre->dst->bev))((cre->dst->bev)->output)->off >
787 (size_t)RELAY_MAX_PREFETCH256 * proto->tcpbufsiz)
788 bufferevent_disable(cre->bev, EV_READ0x02);
789
790 if (bev->readcb != relay_read_httpcontent)
791 bev->readcb(bev, arg);
792 /* The callback readcb() might have freed the session. */
793 return;
794 done:
795 relay_close(con, "last http content read", 0);
796 return;
797 fail:
798 relay_close(con, strerror(errno(*__errno())), 1);
799}
800
801void
802relay_read_httpchunks(struct bufferevent *bev, void *arg)
803{
804 struct ctl_relay_event *cre = arg;
805 struct rsession *con = cre->con;
806 struct protocol *proto = con->se_relay->rl_proto;
807 struct evbuffer *src = EVBUFFER_INPUT(bev)(bev)->input;
808 char *line, *ep;
809 long long llval;
810 size_t size, linelen;
811
812 getmonotime(&con->se_tv_last);
813 cre->timedout = 0;
814
815 size = EVBUFFER_LENGTH(src)(src)->off;
816 DPRINTF("%s: session %d: size %lu, to read %lld", __func__,do {} while(0)
817 con->se_id, size, cre->toread)do {} while(0);
818 if (!size)
819 return;
820 if (relay_spliceadjust(cre) == -1)
821 goto fail;
822
823 if (cre->toread > 0) {
824 /* Read chunk data */
825 if ((off_t)size > cre->toread) {
826 size = cre->toread;
827 if (relay_bufferevent_write_chunk(cre->dst, src, size)
828 == -1)
829 goto fail;
830 cre->toread = 0;
831 } else {
832 if (relay_bufferevent_write_buffer(cre->dst, src) == -1)
833 goto fail;
834 cre->toread -= size;
835 }
836 DPRINTF("%s: done, size %lu, to read %lld", __func__,do {} while(0)
837 size, cre->toread)do {} while(0);
838 }
839 switch (cre->toread) {
840 case TOREAD_HTTP_CHUNK_LENGTH:
841 line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF);
842 if (line == NULL((void *)0)) {
843 /* Ignore empty line, continue */
844 bufferevent_enable(bev, EV_READ0x02);
845 return;
846 }
847 if (linelen == 0) {
848 free(line);
849 goto next;
850 }
851
852 /*
853 * Read prepended chunk size in hex without leading +0[Xx].
854 * The returned signed value must not be negative.
855 */
856 if (line[0] == '+' || line[0] == '-' ||
857 (line[0] == '0' && (line[1] == 'x' || line[1] == 'X'))) {
858 /* Reject values like 0xdead and 0XBEEF or +FEED. */
859 ep = line;
860 } else {
861 errno(*__errno()) = 0;
862 llval = strtoll(line, &ep, 16);
863 }
864 if (ep == line || *ep != '\0' || llval < 0 ||
865 (errno(*__errno()) == ERANGE34 && llval == LLONG_MAX0x7fffffffffffffffLL)) {
866 free(line);
867 relay_close(con, "invalid chunk size", 1);
868 return;
869 }
870
871 if (relay_bufferevent_print(cre->dst, line) == -1 ||
872 relay_bufferevent_print(cre->dst, "\r\n") == -1) {
873 free(line);
874 goto fail;
875 }
876 free(line);
877
878 if ((cre->toread = llval) == 0) {
879 DPRINTF("%s: last chunk", __func__)do {} while(0);
880 cre->toread = TOREAD_HTTP_CHUNK_TRAILER;
881 }
882 break;
883 case TOREAD_HTTP_CHUNK_TRAILER:
884 /* Last chunk is 0 bytes followed by trailer and empty line */
885 line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF);
886 if (line == NULL((void *)0)) {
887 /* Ignore empty line, continue */
888 bufferevent_enable(bev, EV_READ0x02);
889 return;
890 }
891 if (relay_bufferevent_print(cre->dst, line) == -1 ||
892 relay_bufferevent_print(cre->dst, "\r\n") == -1) {
893 free(line);
894 goto fail;
895 }
896 if (linelen == 0) {
897 /* Switch to HTTP header mode */
898 cre->toread = TOREAD_HTTP_HEADER;
899 bev->readcb = relay_read_http;
900 }
901 free(line);
902 break;
903 case 0:
904 /* Chunk is terminated by an empty newline */
905 line = evbuffer_readln(src, &linelen, EVBUFFER_EOL_CRLF);
906 free(line);
907 if (relay_bufferevent_print(cre->dst, "\r\n") == -1)
908 goto fail;
909 cre->toread = TOREAD_HTTP_CHUNK_LENGTH;
910 break;
911 }
912
913 next:
914 if (con->se_done)
915 goto done;
916 bufferevent_enable(bev, EV_READ0x02);
917
918 if (cre->dst->bev && EVBUFFER_LENGTH(EVBUFFER_OUTPUT(cre->dst->bev))((cre->dst->bev)->output)->off >
919 (size_t)RELAY_MAX_PREFETCH256 * proto->tcpbufsiz)
920 bufferevent_disable(cre->bev, EV_READ0x02);
921
922 if (EVBUFFER_LENGTH(src)(src)->off)
923 bev->readcb(bev, arg);
924 /* The callback readcb() might have freed the session. */
925 return;
926
927 done:
928 relay_close(con, "last http chunk read (done)", 0);
929 return;
930 fail:
931 relay_close(con, strerror(errno(*__errno())), 1);
932}
933
934void
935relay_reset_http(struct ctl_relay_event *cre)
936{
937 struct http_descriptor *desc = cre->desc;
938
939 relay_httpdesc_free(desc);
940 desc->http_method = 0;
941 desc->http_chunked = 0;
942 cre->headerlen = 0;
943 cre->line = 0;
944 cre->done = 0;
945}
946
947static int
948_relay_lookup_url(struct ctl_relay_event *cre, char *host, char *path,
949 char *query, struct kv *kv)
950{
951 struct rsession *con = cre->con;
952 char *val, *md = NULL((void *)0);
953 int ret = RES_FAIL;
954 const char *str = NULL((void *)0);
955
956 if (asprintf(&val, "%s%s%s%s",
957 host, path,
958 query == NULL((void *)0) ? "" : "?",
959 query == NULL((void *)0) ? "" : query) == -1) {
960 relay_abort_http(con, 500, "failed to allocate URL", 0);
961 return (RES_FAIL);
962 }
963
964 switch (kv->kv_digest) {
965 case DIGEST_SHA1:
966 case DIGEST_MD5:
967 if ((md = digeststr(kv->kv_digest,
968 val, strlen(val), NULL((void *)0))) == NULL((void *)0)) {
969 relay_abort_http(con, 500,
970 "failed to allocate digest", 0);
971 goto fail;
972 }
973 str = md;
974 break;
975 case DIGEST_NONE:
976 str = val;
977 break;
978 }
979
980 DPRINTF("%s: session %d: %s, %s: %d", __func__, con->se_id,do {} while(0)
981 str, kv->kv_key, strcasecmp(kv->kv_key, str))do {} while(0);
982
983 if (strcasecmp(kv->kv_key, str) == 0) {
984 ret = RES_DROP;
985 goto fail;
986 }
987
988 ret = RES_PASS;
989 fail:
990 free(md);
991 free(val);
992 return (ret);
993}
994
995int
996relay_lookup_url(struct ctl_relay_event *cre, const char *host, struct kv *kv)
997{
998 struct http_descriptor *desc = (struct http_descriptor *)cre->desc;
999 int i, j, dots;
1000 char *hi[RELAY_MAXLOOKUPLEVELS5], *p, *pp, *c, ch;
1001 char ph[HOST_NAME_MAX255+1];
1002 int ret;
1003
1004 if (desc->http_pathhttp_pathquery.kv_key == NULL((void *)0))
1005 return (RES_PASS);
1006
1007 /*
1008 * This is an URL lookup algorithm inspired by
1009 * http://code.google.com/apis/safebrowsing/
1010 * developers_guide.html#PerformingLookups
1011 */
1012
1013 DPRINTF("%s: host '%s', path '%s', query '%s'",do {} while(0)
1014 __func__, host, desc->http_path,do {} while(0)
1015 desc->http_query == NULL ? "" : desc->http_query)do {} while(0);
1016
1017 if (canonicalize_host(host, ph, sizeof(ph)) == NULL((void *)0)) {
1018 return (RES_BAD);
1019 }
1020
1021 bzero(hi, sizeof(hi));
1022 for (dots = -1, i = strlen(ph) - 1; i > 0; i--) {
1023 if (ph[i] == '.' && ++dots)
1024 hi[dots - 1] = &ph[i + 1];
1025 if (dots > (RELAY_MAXLOOKUPLEVELS5 - 2))
1026 break;
1027 }
1028 if (dots == -1)
1029 dots = 0;
1030 hi[dots] = ph;
1031
1032 if ((pp = strdup(desc->http_pathhttp_pathquery.kv_key)) == NULL((void *)0)) {
1033 return (RES_INTERNAL);
1034 }
1035 for (i = (RELAY_MAXLOOKUPLEVELS5 - 1); i >= 0; i--) {
1036 if (hi[i] == NULL((void *)0))
1037 continue;
1038
1039 /* 1. complete path with query */
1040 if (desc->http_queryhttp_pathquery.kv_value != NULL((void *)0))
1041 if ((ret = _relay_lookup_url(cre, hi[i],
1042 pp, desc->http_queryhttp_pathquery.kv_value, kv)) != RES_PASS)
1043 goto done;
1044
1045 /* 2. complete path without query */
1046 if ((ret = _relay_lookup_url(cre, hi[i],
1047 pp, NULL((void *)0), kv)) != RES_PASS)
1048 goto done;
1049
1050 /* 3. traverse path */
1051 for (j = 0, p = strchr(pp, '/');
1052 p != NULL((void *)0); p = strchr(p, '/'), j++) {
1053 if (j > (RELAY_MAXLOOKUPLEVELS5 - 2) || *(++p) == '\0')
1054 break;
1055 c = &pp[p - pp];
1056 ch = *c;
1057 *c = '\0';
1058 if ((ret = _relay_lookup_url(cre, hi[i],
1059 pp, NULL((void *)0), kv)) != RES_PASS)
1060 goto done;
1061 *c = ch;
1062 }
1063 }
1064
1065 ret = RES_PASS;
1066 done:
1067 free(pp);
1068 return (ret);
1069}
1070
1071int
1072relay_lookup_cookie(struct ctl_relay_event *cre, const char *str,
1073 struct kv *kv)
1074{
1075 char *val, *ptr, *key, *value;
1076 int ret;
1077
1078 if ((val = strdup(str)) == NULL((void *)0)) {
1079 return (RES_INTERNAL);
1080 }
1081
1082 for (ptr = val; ptr != NULL((void *)0) && strlen(ptr);) {
1083 if (*ptr == ' ')
1084 *ptr++ = '\0';
1085 key = ptr;
1086 if ((ptr = strchr(ptr, ';')) != NULL((void *)0))
1087 *ptr++ = '\0';
1088 /*
1089 * XXX We do not handle attributes
1090 * ($Path, $Domain, or $Port)
1091 */
1092 if (*key == '$')
1093 continue;
1094
1095 if ((value =
1096 strchr(key, '=')) == NULL((void *)0) ||
1097 strlen(value) < 1)
1098 continue;
1099 *value++ = '\0';
1100 if (*value == '"')
1101 *value++ = '\0';
1102 if (value[strlen(value) - 1] == '"')
1103 value[strlen(value) - 1] = '\0';
1104
1105 DPRINTF("%s: key %s = %s, %s = %s : %d",do {} while(0)
1106 __func__, key, value, kv->kv_key, kv->kv_value,do {} while(0)
1107 strcasecmp(kv->kv_key, key))do {} while(0);
1108
1109 if (strcasecmp(kv->kv_key, key) == 0 &&
1110 ((kv->kv_value == NULL((void *)0)) ||
1111 (fnmatch(kv->kv_value, value,
1112 FNM_CASEFOLD0x10) != FNM_NOMATCH1))) {
1113 ret = RES_DROP;
1114 goto done;
1115 }
1116 }
1117
1118 ret = RES_PASS;
1119
1120 done:
1121 free(val);
1122 return (ret);
1123}
1124
1125int
1126relay_lookup_query(struct ctl_relay_event *cre, struct kv *kv)
1127{
1128 struct http_descriptor *desc = cre->desc;
1129 struct kv *match = &desc->http_matchquery;
1130 char *val, *ptr, *tmpkey = NULL((void *)0), *tmpval = NULL((void *)0);
1131 int ret = -1;
1132
1133 if (desc->http_queryhttp_pathquery.kv_value == NULL((void *)0))
1134 return (-1);
1135 if ((val = strdup(desc->http_queryhttp_pathquery.kv_value)) == NULL((void *)0)) {
1136 return (RES_INTERNAL);
1137 }
1138
1139 ptr = val;
1140 while (ptr != NULL((void *)0) && strlen(ptr)) {
1141 tmpkey = ptr;
1142 if ((ptr = strchr(ptr, '&')) != NULL((void *)0))
1143 *ptr++ = '\0';
1144 if ((tmpval = strchr(tmpkey, '=')) == NULL((void *)0) || strlen(tmpval)
1145 < 1)
1146 continue;
1147 *tmpval++ = '\0';
1148
1149 if (fnmatch(kv->kv_key, tmpkey, 0) != FNM_NOMATCH1 &&
1150 (kv->kv_value == NULL((void *)0) || fnmatch(kv->kv_value, tmpval, 0)
1151 != FNM_NOMATCH1))
1152 break;
1153 else
1154 tmpkey = NULL((void *)0);
1155 }
1156
1157 if (tmpkey == NULL((void *)0) || tmpval == NULL((void *)0))
1158 goto done;
1159
1160 match->kv_key = strdup(tmpkey);
1161 if (match->kv_key == NULL((void *)0))
1162 goto done;
1163 match->kv_value = strdup(tmpval);
1164 if (match->kv_key == NULL((void *)0))
1165 goto done;
1166 ret = 0;
1167
1168 done:
1169 free(val);
1170 return (ret);
1171}
1172
1173ssize_t
1174relay_http_time(time_t t, char *tmbuf, size_t len)
1175{
1176 struct tm tm;
1177
1178 /* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */
1179 if (t == -1 || gmtime_r(&t, &tm) == NULL((void *)0))
1180 return (-1);
1181 else
1182 return (strftime(tmbuf, len, "%a, %d %h %Y %T %Z", &tm));
1183}
1184
1185void
1186relay_abort_http(struct rsession *con, u_int code, const char *msg,
1187 u_int16_t labelid)
1188{
1189 struct relay *rlay = con->se_relay;
1190 struct bufferevent *bev = con->se_in.bev;
1191 const char *httperr = NULL((void *)0), *text = "";
1192 char *httpmsg, *body = NULL((void *)0);
1193 char tmbuf[32], hbuf[128];
1194 const char *style, *label = NULL((void *)0);
1195 int bodylen;
1196
1197 if ((httperr = relay_httperror_byid(code)) == NULL((void *)0))
1198 httperr = "Unknown Error";
1199
1200 if (labelid != 0)
1201 label = label_id2name(labelid);
1202
1203 /* In some cases this function may be called from generic places */
1204 if (rlay->rl_proto->type != RELAY_PROTO_HTTP ||
1205 (rlay->rl_proto->flags & F_RETURN0x00020000) == 0) {
1206 relay_close(con, msg, 0);
1207 return;
1208 }
1209
1210 if (bev == NULL((void *)0))
1211 goto done;
1212
1213 /* Some system information */
1214 if (print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) == NULL((void *)0))
1215 goto done;
1216
1217 if (relay_http_time(time(NULL((void *)0)), tmbuf, sizeof(tmbuf)) <= 0)
1218 goto done;
1219
1220 /* Do not send details of the Internal Server Error */
1221 switch (code) {
1222 case 500:
1223 break;
1224 default:
1225 text = msg;
1226 break;
1227 }
1228
1229 /* A CSS stylesheet allows minimal customization by the user */
1230 style = (rlay->rl_proto->style != NULL((void *)0)) ? rlay->rl_proto->style :
1231 "body { background-color: #a00000; color: white; font-family: "
1232 "'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }\n"
1233 "hr { border: 0; border-bottom: 1px dashed; }\n";
1234
1235 /* Generate simple HTTP+HTML error document */
1236 if ((bodylen = asprintf(&body,
1237 "<!DOCTYPE html>\n"
1238 "<html>\n"
1239 "<head>\n"
1240 "<title>%03d %s</title>\n"
1241 "<style type=\"text/css\"><!--\n%s\n--></style>\n"
1242 "</head>\n"
1243 "<body>\n"
1244 "<h1>%s</h1>\n"
1245 "<div id='m'>%s</div>\n"
1246 "<div id='l'>%s</div>\n"
1247 "<hr><address>%s at %s port %d</address>\n"
1248 "</body>\n"
1249 "</html>\n",
1250 code, httperr, style, httperr, text,
1251 label == NULL((void *)0) ? "" : label,
1252 RELAYD_SERVERNAME"OpenBSD relayd", hbuf, ntohs(rlay->rl_conf.port)(__uint16_t)(__builtin_constant_p(rlay->rl_conf.port) ? (__uint16_t
)(((__uint16_t)(rlay->rl_conf.port) & 0xffU) << 8
| ((__uint16_t)(rlay->rl_conf.port) & 0xff00U) >>
8) : __swap16md(rlay->rl_conf.port))
)) == -1)
1253 goto done;
1254
1255 /* Generate simple HTTP+HTML error document */
1256 if (asprintf(&httpmsg,
1257 "HTTP/1.0 %03d %s\r\n"
1258 "Date: %s\r\n"
1259 "Server: %s\r\n"
1260 "Connection: close\r\n"
1261 "Content-Type: text/html\r\n"
1262 "Content-Length: %d\r\n"
1263 "\r\n"
1264 "%s",
1265 code, httperr, tmbuf, RELAYD_SERVERNAME"OpenBSD relayd", bodylen, body) == -1)
1266 goto done;
1267
1268 /* Dump the message without checking for success */
1269 relay_dump(&con->se_in, httpmsg, strlen(httpmsg));
1270 free(httpmsg);
1271
1272 done:
1273 free(body);
1274 if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1)
1275 relay_close(con, msg, 1);
1276 else {
1277 relay_close(con, httpmsg, 1);
1278 free(httpmsg);
1279 }
1280}
1281
1282void
1283relay_close_http(struct rsession *con)
1284{
1285 struct http_session *hs = con->se_priv;
1286 struct http_method_node *hmn;
1287
1288 DPRINTF("%s: session %d http_session %p", __func__,do {} while(0)
1289 con->se_id, hs)do {} while(0);
1290 if (hs != NULL((void *)0))
1291 while (!SIMPLEQ_EMPTY(&hs->hs_methods)(((&hs->hs_methods)->sqh_first) == ((void *)0))) {
1292 hmn = SIMPLEQ_FIRST(&hs->hs_methods)((&hs->hs_methods)->sqh_first);
1293 SIMPLEQ_REMOVE_HEAD(&hs->hs_methods, hmn_entry)do { if (((&hs->hs_methods)->sqh_first = (&hs->
hs_methods)->sqh_first->hmn_entry.sqe_next) == ((void *
)0)) (&hs->hs_methods)->sqh_last = &(&hs->
hs_methods)->sqh_first; } while (0)
;
1294 DPRINTF("%s: session %d freeing %s", __func__,do {} while(0)
1295 con->se_id, relay_httpmethod_byid(hmn->hmn_method))do {} while(0);
1296 free(hmn);
1297 }
1298 relay_httpdesc_free(con->se_in.desc);
1299 free(con->se_in.desc);
1300 relay_httpdesc_free(con->se_out.desc);
1301 free(con->se_out.desc);
1302}
1303
1304char *
1305relay_expand_http(struct ctl_relay_event *cre, char *val, char *buf,
1306 size_t len)
1307{
1308 struct rsession *con = cre->con;
1309 struct relay *rlay = con->se_relay;
1310 struct http_descriptor *desc = cre->desc;
1311 struct kv *host, key;
1312 char ibuf[128];
1313
1314 if (strlcpy(buf, val, len) >= len)
1315 return (NULL((void *)0));
1316
1317 if (strstr(val, "$HOST") != NULL((void *)0)) {
1318 key.kv_key = "Host";
1319 host = kv_find(&desc->http_headers, &key);
1320 if (host) {
1321 if (host->kv_value == NULL((void *)0))
1322 return (NULL((void *)0));
1323 snprintf(ibuf, sizeof(ibuf), "%s", host->kv_value);
1324 } else {
1325 if (print_host(&rlay->rl_conf.ss,
1326 ibuf, sizeof(ibuf)) == NULL((void *)0))
1327 return (NULL((void *)0));
1328 }
1329 if (expand_string(buf, len, "$HOST", ibuf))
1330 return (NULL((void *)0));
1331 }
1332 if (strstr(val, "$REMOTE_") != NULL((void *)0)) {
1333 if (strstr(val, "$REMOTE_ADDR") != NULL((void *)0)) {
1334 if (print_host(&cre->ss, ibuf, sizeof(ibuf)) == NULL((void *)0))
1335 return (NULL((void *)0));
1336 if (expand_string(buf, len,
1337 "$REMOTE_ADDR", ibuf) != 0)
1338 return (NULL((void *)0));
1339 }
1340 if (strstr(val, "$REMOTE_PORT") != NULL((void *)0)) {
1341 snprintf(ibuf, sizeof(ibuf), "%u", ntohs(cre->port)(__uint16_t)(__builtin_constant_p(cre->port) ? (__uint16_t
)(((__uint16_t)(cre->port) & 0xffU) << 8 | ((__uint16_t
)(cre->port) & 0xff00U) >> 8) : __swap16md(cre->
port))
);
1342 if (expand_string(buf, len,
1343 "$REMOTE_PORT", ibuf) != 0)
1344 return (NULL((void *)0));
1345 }
1346 }
1347 if (strstr(val, "$SERVER_") != NULL((void *)0)) {
1348 if (strstr(val, "$SERVER_ADDR") != NULL((void *)0)) {
1349 if (print_host(&rlay->rl_conf.ss,
1350 ibuf, sizeof(ibuf)) == NULL((void *)0))
1351 return (NULL((void *)0));
1352 if (expand_string(buf, len,
1353 "$SERVER_ADDR", ibuf) != 0)
1354 return (NULL((void *)0));
1355 }
1356 if (strstr(val, "$SERVER_PORT") != NULL((void *)0)) {
1357 snprintf(ibuf, sizeof(ibuf), "%u",
1358 ntohs(rlay->rl_conf.port)(__uint16_t)(__builtin_constant_p(rlay->rl_conf.port) ? (__uint16_t
)(((__uint16_t)(rlay->rl_conf.port) & 0xffU) << 8
| ((__uint16_t)(rlay->rl_conf.port) & 0xff00U) >>
8) : __swap16md(rlay->rl_conf.port))
);
1359 if (expand_string(buf, len,
1360 "$SERVER_PORT", ibuf) != 0)
1361 return (NULL((void *)0));
1362 }
1363 if (strstr(val, "$SERVER_NAME") != NULL((void *)0)) {
1364 if (expand_string(buf, len,
1365 "$SERVER_NAME", RELAYD_SERVERNAME"OpenBSD relayd") != 0)
1366 return (NULL((void *)0));
1367 }
1368 }
1369 if (strstr(val, "$TIMEOUT") != NULL((void *)0)) {
1370 snprintf(ibuf, sizeof(ibuf), "%lld",
1371 (long long)rlay->rl_conf.timeout.tv_sec);
1372 if (expand_string(buf, len, "$TIMEOUT", ibuf) != 0)
1373 return (NULL((void *)0));
1374 }
1375
1376 return (buf);
1377}
1378
1379int
1380relay_writerequest_http(struct ctl_relay_event *dst,
1381 struct ctl_relay_event *cre)
1382{
1383 struct http_descriptor *desc = (struct http_descriptor *)cre->desc;
1384 const char *name = NULL((void *)0);
1385
1386 if ((name = relay_httpmethod_byid(desc->http_method)) == NULL((void *)0))
1387 return (-1);
1388
1389 if (relay_bufferevent_print(dst, name) == -1 ||
1390 relay_bufferevent_print(dst, " ") == -1 ||
1391 relay_bufferevent_print(dst, desc->http_pathhttp_pathquery.kv_key) == -1 ||
1392 (desc->http_queryhttp_pathquery.kv_value != NULL((void *)0) &&
1393 (relay_bufferevent_print(dst, "?") == -1 ||
1394 relay_bufferevent_print(dst, desc->http_queryhttp_pathquery.kv_value) == -1)) ||
1395 relay_bufferevent_print(dst, " ") == -1 ||
1396 relay_bufferevent_print(dst, desc->http_version) == -1)
1397 return (-1);
1398
1399 return (0);
1400}
1401
1402int
1403relay_writeresponse_http(struct ctl_relay_event *dst,
1404 struct ctl_relay_event *cre)
1405{
1406 struct http_descriptor *desc = (struct http_descriptor *)cre->desc;
1407
1408 DPRINTF("version: %s rescode: %s resmsg: %s", desc->http_version,do {} while(0)
1409 desc->http_rescode, desc->http_resmesg)do {} while(0);
1410
1411 if (relay_bufferevent_print(dst, desc->http_version) == -1 ||
1412 relay_bufferevent_print(dst, " ") == -1 ||
1413 relay_bufferevent_print(dst, desc->http_rescodehttp_pathquery.kv_key) == -1 ||
1414 relay_bufferevent_print(dst, " ") == -1 ||
1415 relay_bufferevent_print(dst, desc->http_resmesghttp_pathquery.kv_value) == -1)
1416 return (-1);
1417
1418 return (0);
1419}
1420
1421int
1422relay_writeheader_kv(struct ctl_relay_event *dst, struct kv *hdr)
1423{
1424 char *ptr;
1425 const char *key;
1426
1427 if (hdr->kv_flags & KV_FLAG_INVALID0x02)
1428 return (0);
1429
1430 /* The key might have been updated in the parent */
1431 if (hdr->kv_parent != NULL((void *)0) && hdr->kv_parent->kv_key != NULL((void *)0))
1432 key = hdr->kv_parent->kv_key;
1433 else
1434 key = hdr->kv_key;
1435
1436 ptr = hdr->kv_value;
1437 if (relay_bufferevent_print(dst, key) == -1 ||
1438 (ptr != NULL((void *)0) &&
1439 (relay_bufferevent_print(dst, ": ") == -1 ||
1440 relay_bufferevent_print(dst, ptr) == -1 ||
1441 relay_bufferevent_print(dst, "\r\n") == -1)))
1442 return (-1);
1443 DPRINTF("%s: %s: %s", __func__, key,do {} while(0)
1444 hdr->kv_value == NULL ? "" : hdr->kv_value)do {} while(0);
1445
1446 return (0);
1447}
1448
1449int
1450relay_writeheader_http(struct ctl_relay_event *dst, struct ctl_relay_event
1451 *cre)
1452{
1453 struct kv *hdr, *kv;
1454 struct http_descriptor *desc = (struct http_descriptor *)cre->desc;
1455
1456 RB_FOREACH(hdr, kvtree, &desc->http_headers)for ((hdr) = kvtree_RB_MINMAX(&desc->http_headers, -1)
; (hdr) != ((void *)0); (hdr) = kvtree_RB_NEXT(hdr))
{
1457 if (relay_writeheader_kv(dst, hdr) == -1)
1458 return (-1);
1459 TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry)for((kv) = ((&hdr->kv_children)->tqh_first); (kv) !=
((void *)0); (kv) = ((kv)->kv_entry.tqe_next))
{
1460 if (relay_writeheader_kv(dst, kv) == -1)
1461 return (-1);
1462 }
1463 }
1464
1465 return (0);
1466}
1467
1468enum httpmethod
1469relay_httpmethod_byname(const char *name)
1470{
1471 enum httpmethod id = HTTP_METHOD_NONE;
1472 struct http_method method, *res = NULL((void *)0);
1473
1474 /* Set up key */
1475 method.method_name = name;
1476
1477 if ((res = bsearch(&method, http_methods,
1478 sizeof(http_methods) / sizeof(http_methods[0]) - 1,
1479 sizeof(http_methods[0]), relay_httpmethod_cmp)) != NULL((void *)0))
1480 id = res->method_id;
1481
1482 return (id);
1483}
1484
1485const char *
1486relay_httpmethod_byid(u_int id)
1487{
1488 const char *name = NULL((void *)0);
1489 int i;
1490
1491 for (i = 0; http_methods[i].method_name != NULL((void *)0); i++) {
1492 if (http_methods[i].method_id == id) {
1493 name = http_methods[i].method_name;
1494 break;
1495 }
1496 }
1497
1498 return (name);
1499}
1500
1501static int
1502relay_httpmethod_cmp(const void *a, const void *b)
1503{
1504 const struct http_method *ma = a;
1505 const struct http_method *mb = b;
1506
1507 /*
1508 * RFC 2616 section 5.1.1 says that the method is case
1509 * sensitive so we don't do a strcasecmp here.
1510 */
1511 return (strcmp(ma->method_name, mb->method_name));
1512}
1513
1514const char *
1515relay_httperror_byid(u_int id)
1516{
1517 struct http_error error, *res = NULL((void *)0);
1518
1519 /* Set up key */
1520 error.error_code = (int)id;
1521
1522 res = bsearch(&error, http_errors,
1523 sizeof(http_errors) / sizeof(http_errors[0]) - 1,
1524 sizeof(http_errors[0]), relay_httperror_cmp);
1525
1526 return (res->error_name);
1527}
1528
1529static int
1530relay_httperror_cmp(const void *a, const void *b)
1531{
1532 const struct http_error *ea = a;
1533 const struct http_error *eb = b;
1534 return (ea->error_code - eb->error_code);
1535}
1536
1537int
1538relay_httpquery_test(struct ctl_relay_event *cre, struct relay_rule *rule,
1539 struct kvlist *actions)
1540{
1541 struct http_descriptor *desc = cre->desc;
1542 struct kv *match = &desc->http_matchquery;
1543 struct kv *kv = &rule->rule_kv[KEY_TYPE_QUERY];
1544 int res = 0;
1545
1546 if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_QUERY)
1547 return (0);
1548 else if (kv->kv_key == NULL((void *)0))
1549 return (0);
1550 else if ((res = relay_lookup_query(cre, kv)) != 0)
1551 return (res);
1552
1553 relay_match(actions, kv, match, NULL((void *)0));
1554
1555 return (0);
1556}
1557
1558int
1559relay_httpheader_test(struct ctl_relay_event *cre, struct relay_rule *rule,
1560 struct kvlist *actions)
1561{
1562 struct http_descriptor *desc = cre->desc;
1563 struct kv *kv = &rule->rule_kv[KEY_TYPE_HEADER];
1564 struct kv *match;
1565
1566 if (kv->kv_type != KEY_TYPE_HEADER)
1567 return (0);
1568
1569 match = kv_find(&desc->http_headers, kv);
1570
1571 if (kv->kv_option == KEY_OPTION_APPEND ||
1572 kv->kv_option == KEY_OPTION_SET) {
1573 /* header can be NULL and will be added later */
1574 } else if (match == NULL((void *)0)) {
1575 /* Fail if header doesn't exist */
1576 return (-1);
1577 } else {
1578 if (fnmatch(kv->kv_key, match->kv_key,
1579 FNM_CASEFOLD0x10) == FNM_NOMATCH1)
1580 return (-1);
1581 if (kv->kv_value != NULL((void *)0) &&
1582 match->kv_value != NULL((void *)0) &&
1583 fnmatch(kv->kv_value, match->kv_value, 0) == FNM_NOMATCH1)
1584 return (-1);
1585 }
1586
1587 relay_match(actions, kv, match, &desc->http_headers);
1588
1589 return (0);
1590}
1591
1592int
1593relay_httppath_test(struct ctl_relay_event *cre, struct relay_rule *rule,
1594 struct kvlist *actions)
1595{
1596 struct http_descriptor *desc = cre->desc;
1597 struct kv *kv = &rule->rule_kv[KEY_TYPE_PATH];
1598 struct kv *match = &desc->http_pathquery;
1599 const char *query;
1600
1601 if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_PATH)
1602 return (0);
1603 else if (kv->kv_option != KEY_OPTION_STRIP) {
1604 if (kv->kv_key == NULL((void *)0))
1605 return (0);
1606 else if (fnmatch(kv->kv_key, desc->http_pathhttp_pathquery.kv_key, 0) == FNM_NOMATCH1)
1607 return (-1);
1608 else if (kv->kv_value != NULL((void *)0) && kv->kv_option == KEY_OPTION_NONE) {
1609 query = desc->http_queryhttp_pathquery.kv_value == NULL((void *)0) ? "" : desc->http_queryhttp_pathquery.kv_value;
1610 if (fnmatch(kv->kv_value, query, FNM_CASEFOLD0x10) == FNM_NOMATCH1)
1611 return (-1);
1612 }
1613 }
1614
1615 relay_match(actions, kv, match, NULL((void *)0));
1616
1617 return (0);
1618}
1619
1620int
1621relay_httpurl_test(struct ctl_relay_event *cre, struct relay_rule *rule,
1622 struct kvlist *actions)
1623{
1624 struct http_descriptor *desc = cre->desc;
1625 struct kv *host, key;
1626 struct kv *kv = &rule->rule_kv[KEY_TYPE_URL];
1627 struct kv *match = &desc->http_pathquery;
1628 int res;
1629
1630 if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_URL ||
1631 kv->kv_key == NULL((void *)0))
1632 return (0);
1633
1634 key.kv_key = "Host";
1635 host = kv_find(&desc->http_headers, &key);
1636
1637 if (host == NULL((void *)0) || host->kv_value == NULL((void *)0))
1638 return (0);
1639 else if (rule->rule_action != RULE_ACTION_BLOCK &&
1640 kv->kv_option == KEY_OPTION_LOG &&
1641 fnmatch(kv->kv_key, match->kv_key, FNM_CASEFOLD0x10) != FNM_NOMATCH1) {
1642 /* fnmatch url only for logging */
1643 } else if ((res = relay_lookup_url(cre, host->kv_value, kv)) != 0)
1644 return (res);
1645 relay_match(actions, kv, match, NULL((void *)0));
1646
1647 return (0);
1648}
1649
1650int
1651relay_httpcookie_test(struct ctl_relay_event *cre, struct relay_rule *rule,
1652 struct kvlist *actions)
1653{
1654 struct http_descriptor *desc = cre->desc;
1655 struct kv *kv = &rule->rule_kv[KEY_TYPE_COOKIE], key;
1656 struct kv *match = NULL((void *)0);
1657 int res;
1658
1659 if (kv->kv_type != KEY_TYPE_COOKIE)
1660 return (0);
1661
1662 switch (cre->dir) {
1663 case RELAY_DIR_REQUEST:
1664 key.kv_key = "Cookie";
1665 break;
1666 case RELAY_DIR_RESPONSE:
1667 key.kv_key = "Set-Cookie";
1668 break;
1669 default:
1670 return (0);
1671 /* NOTREACHED */
1672 break;
1673 }
1674
1675 if (kv->kv_option == KEY_OPTION_APPEND ||
1676 kv->kv_option == KEY_OPTION_SET) {
1677 /* no cookie, can be NULL and will be added later */
1678 } else {
1679 match = kv_find(&desc->http_headers, &key);
1680 if (match == NULL((void *)0))
1681 return (-1);
1682 if (kv->kv_key == NULL((void *)0) || match->kv_value == NULL((void *)0))
1683 return (0);
1684 else if ((res = relay_lookup_cookie(cre, match->kv_value,
1685 kv)) != 0)
1686 return (res);
1687 }
1688
1689 relay_match(actions, kv, match, &desc->http_headers);
1690
1691 return (0);
1692}
1693
1694int
1695relay_match_actions(struct ctl_relay_event *cre, struct relay_rule *rule,
1696 struct kvlist *matches, struct kvlist *actions, struct relay_table **tbl)
1697{
1698 struct rsession *con = cre->con;
1699 struct kv *kv;
1700
1701 /*
1702 * Apply the following options instantly (action per match).
1703 */
1704 if (rule->rule_table != NULL((void *)0)) {
1705 *tbl = rule->rule_table;
1706 con->se_out.ss.ss_family = AF_UNSPEC0;
1707 }
1708 if (rule->rule_tag != 0)
1709 con->se_tag = rule->rule_tag == -1 ? 0 : rule->rule_tag;
1710 if (rule->rule_label != 0)
1711 con->se_label = rule->rule_label == -1 ? 0 : rule->rule_label;
1712
1713 /*
1714 * Apply the remaining options once after evaluation.
1715 */
1716 if (matches == NULL((void *)0)) {
1717 /* 'pass' or 'block' rule */
1718 TAILQ_CONCAT(actions, &rule->rule_kvlist, kv_rule_entry)do { if (!(((&rule->rule_kvlist)->tqh_first) == ((void
*)0))) { *(actions)->tqh_last = (&rule->rule_kvlist
)->tqh_first; (&rule->rule_kvlist)->tqh_first->
kv_rule_entry.tqe_prev = (actions)->tqh_last; (actions)->
tqh_last = (&rule->rule_kvlist)->tqh_last; do { ((&
rule->rule_kvlist))->tqh_first = ((void *)0); ((&rule
->rule_kvlist))->tqh_last = &((&rule->rule_kvlist
))->tqh_first; } while (0); } } while (0)
;
1719 } else {
1720 /* 'match' rule */
1721 TAILQ_FOREACH(kv, matches, kv_match_entry)for((kv) = ((matches)->tqh_first); (kv) != ((void *)0); (kv
) = ((kv)->kv_match_entry.tqe_next))
{
1722 TAILQ_INSERT_TAIL(actions, kv, kv_action_entry)do { (kv)->kv_action_entry.tqe_next = ((void *)0); (kv)->
kv_action_entry.tqe_prev = (actions)->tqh_last; *(actions)
->tqh_last = (kv); (actions)->tqh_last = &(kv)->
kv_action_entry.tqe_next; } while (0)
;
1723 }
1724 }
1725
1726 return (0);
1727}
1728
1729int
1730relay_apply_actions(struct ctl_relay_event *cre, struct kvlist *actions,
1731 struct relay_table *tbl)
1732{
1733 struct rsession *con = cre->con;
1734 struct http_descriptor *desc = cre->desc;
1735 struct kv *host = NULL((void *)0);
1736 const char *value;
1737 struct kv *kv, *match, *kp, *mp, kvcopy, matchcopy, key;
1738 int addkv, ret, nstrip;
1739 char buf[IBUF_READ_SIZE65535], *ptr;
1740 char *msg = NULL((void *)0);
1741 const char *meth = NULL((void *)0);
1742
1743 memset(&kvcopy, 0, sizeof(kvcopy));
1744 memset(&matchcopy, 0, sizeof(matchcopy));
1745
1746 ret = -1;
1747 kp = mp = NULL((void *)0);
1748 TAILQ_FOREACH(kv, actions, kv_action_entry)for((kv) = ((actions)->tqh_first); (kv) != ((void *)0); (kv
) = ((kv)->kv_action_entry.tqe_next))
{
1749 kp = NULL((void *)0);
1750 match = kv->kv_match;
1751 addkv = 0;
1752
1753 /*
1754 * Although marked as deleted, give a chance to non-critical
1755 * actions, ie. log, to be performed
1756 */
1757 if (match != NULL((void *)0) && (match->kv_flags & KV_FLAG_INVALID0x02))
1758 goto matchdel;
1759
1760 switch (kv->kv_option) {
1761 case KEY_OPTION_APPEND:
1762 case KEY_OPTION_SET:
1763 switch (kv->kv_type) {
1764 case KEY_TYPE_PATH:
1765 if (kv->kv_option == KEY_OPTION_APPEND) {
1766 if (kv_setkey(match, "%s%s",
1767 match->kv_key, kv->kv_key) == -1)
1768 goto fail;
1769 } else {
1770 if (kv_setkey(match, "%s",
1771 kv->kv_value) == -1)
1772 goto fail;
1773 }
1774 break;
1775 case KEY_TYPE_COOKIE:
1776 kp = &kvcopy;
1777 if (kv_inherit(kp, kv) == NULL((void *)0))
1778 goto fail;
1779 if (kv_set(kp, "%s=%s;", kp->kv_key,
1780 kp->kv_value) == -1)
1781 goto fail;
1782 if (kv_setkey(kp, "%s", cre->dir ==
1783 RELAY_DIR_REQUEST ?
1784 "Cookie" : "Set-Cookie") == -1)
1785 goto fail;
1786 /* FALLTHROUGH cookie is a header */
1787 case KEY_TYPE_HEADER:
1788 if (match == NULL((void *)0)) {
1789 addkv = 1;
1790 break;
1791 }
1792 if (match->kv_value == NULL((void *)0) ||
1793 kv->kv_option == KEY_OPTION_SET) {
1794 if (kv_set(match, "%s",
1795 kv->kv_value) == -1)
1796 goto fail;
1797 } else
1798 addkv = 1;
1799 break;
1800 default:
1801 /* query, url not supported */
1802 break;
1803 }
1804 break;
1805 case KEY_OPTION_REMOVE:
1806 switch (kv->kv_type) {
1807 case KEY_TYPE_PATH:
1808 if (kv_setkey(match, "/") == -1)
1809 goto fail;
1810 break;
1811 case KEY_TYPE_COOKIE:
1812 case KEY_TYPE_HEADER:
1813 if (kv->kv_matchtree != NULL((void *)0))
1814 match->kv_flags |= KV_FLAG_INVALID0x02;
1815 else
1816 kv_free(match);
1817 match = kv->kv_match = NULL((void *)0);
1818 break;
1819 default:
1820 /* query and url not supported */
1821 break;
1822 }
1823 break;
1824 case KEY_OPTION_HASH:
1825 switch (kv->kv_type) {
1826 case KEY_TYPE_PATH:
1827 value = match->kv_key;
1828 break;
1829 default:
1830 value = match->kv_value;
1831 break;
1832 }
1833 SipHash24_Update(&con->se_siphashctx,SipHash_Update((&con->se_siphashctx), 2, 4, (value), (
strlen(value)))
1834 value, strlen(value))SipHash_Update((&con->se_siphashctx), 2, 4, (value), (
strlen(value)))
;
1835 break;
1836 case KEY_OPTION_LOG:
1837 /* perform this later */
1838 break;
1839 case KEY_OPTION_STRIP:
1840 nstrip = strtonum(kv->kv_value, 0, INT_MAX0x7fffffff, NULL((void *)0));
1841 if (kv->kv_type == KEY_TYPE_PATH) {
1842 if (kv_setkey(match, "%s",
1843 server_root_strip(match->kv_key,
1844 nstrip)) == -1)
1845 goto fail;
1846 }
1847 break;
1848 default:
1849 fatalx("%s: invalid action", __func__);
1850 /* NOTREACHED */
1851 }
1852
1853 /* from now on, reads from kp writes to kv */
1854 if (kp == NULL((void *)0))
1855 kp = kv;
1856 if (addkv && kv->kv_matchtree != NULL((void *)0)) {
1857 /* Add new entry to the list (eg. new HTTP header) */
1858 if ((match = kv_add(kv->kv_matchtree, kp->kv_key,
1859 kp->kv_value, 0)) == NULL((void *)0))
1860 goto fail;
1861 match->kv_option = kp->kv_option;
1862 match->kv_type = kp->kv_type;
1863 kv->kv_match = match;
1864 }
1865 if (match != NULL((void *)0) && kp->kv_flags & KV_FLAG_MACRO0x01) {
1866 bzero(buf, sizeof(buf));
1867 if ((ptr = relay_expand_http(cre, kp->kv_value, buf,
1868 sizeof(buf))) == NULL((void *)0))
1869 goto fail;
1870 if (kv_set(match, "%s", ptr) == -1)
1871 goto fail;
1872 }
1873
1874 matchdel:
1875 switch (kv->kv_option) {
1876 case KEY_OPTION_LOG:
1877 if (match == NULL((void *)0))
1878 break;
1879 mp = &matchcopy;
1880 if (kv_inherit(mp, match) == NULL((void *)0))
1881 goto fail;
1882 if (mp->kv_flags & KV_FLAG_INVALID0x02) {
1883 if (kv_set(mp, "%s (removed)",
1884 mp->kv_value) == -1)
1885 goto fail;
1886 }
1887 switch (kv->kv_type) {
1888 case KEY_TYPE_URL:
1889 key.kv_key = "Host";
1890 host = kv_find(&desc->http_headers, &key);
1891 switch (kv->kv_digest) {
1892 case DIGEST_NONE:
1893 if (host == NULL((void *)0) ||
1894 host->kv_value == NULL((void *)0))
1895 break;
1896 if (kv_setkey(mp, "%s%s",
1897 host->kv_value, mp->kv_key) ==
1898 -1)
1899 goto fail;
1900 break;
1901 default:
1902 if (kv_setkey(mp, "%s", kv->kv_key)
1903 == -1)
1904 goto fail;
1905 break;
1906 }
1907 break;
1908 default:
1909 break;
1910 }
1911 if (kv_log(con, mp, con->se_label, cre->dir)
1912 == -1)
1913 goto fail;
1914 break;
1915 default:
1916 break;
1917 }
1918
1919 /* actions applied, cleanup kv */
1920 kv->kv_match = NULL((void *)0);
1921 kv->kv_matchtree = NULL((void *)0);
1922 TAILQ_REMOVE(actions, kv, kv_match_entry)do { if (((kv)->kv_match_entry.tqe_next) != ((void *)0)) (
kv)->kv_match_entry.tqe_next->kv_match_entry.tqe_prev =
(kv)->kv_match_entry.tqe_prev; else (actions)->tqh_last
= (kv)->kv_match_entry.tqe_prev; *(kv)->kv_match_entry
.tqe_prev = (kv)->kv_match_entry.tqe_next; ; ; } while (0)
;
1923
1924 kv_free(&kvcopy);
1925 kv_free(&matchcopy);
1926 }
1927
1928 /*
1929 * Change the backend if the forward table has been changed.
1930 * This only works in the request direction.
1931 */
1932 if (cre->dir == RELAY_DIR_REQUEST && con->se_table != tbl) {
1933 relay_reset_event(con, &con->se_out);
1934 con->se_table = tbl;
1935 con->se_haslog = 1;
1936 }
1937
1938 /*
1939 * log tag for request and response, request method
1940 * and end of request marker ","
1941 */
1942 if ((con->se_log != NULL((void *)0)) &&
1943 ((meth = relay_httpmethod_byid(desc->http_method)) != NULL((void *)0)) &&
1944 (asprintf(&msg, " %s", meth) != -1))
1945 evbuffer_add(con->se_log, msg, strlen(msg));
1946 free(msg);
1947 relay_log(con, cre->dir == RELAY_DIR_REQUEST ? "" : ";");
1948 ret = 0;
1949 fail:
1950 kv_free(&kvcopy);
1951 kv_free(&matchcopy);
1952
1953 return (ret);
1954}
1955
1956#define RELAY_GET_SKIP_STEP(i)do { r = r->rule_skip[i]; do {} while(0); } while (0) \
1957 do { \
1958 r = r->rule_skip[i]; \
1959 DPRINTF("%s:%d: skip %d rules", __func__, __LINE__, i)do {} while(0); \
1960 } while (0)
1961
1962#define RELAY_GET_NEXT_STEPdo { do {} while(0); goto nextrule; } while (0) \
1963 do { \
1964 DPRINTF("%s:%d: next rule", __func__, __LINE__)do {} while(0); \
1965 goto nextrule; \
1966 } while (0)
1967
1968int
1969relay_test(struct protocol *proto, struct ctl_relay_event *cre)
1970{
1971 struct rsession *con;
1972 struct http_descriptor *desc = cre->desc;
1973 struct relay_rule *r = NULL((void *)0), *rule = NULL((void *)0);
1974 struct relay_table *tbl = NULL((void *)0);
1975 u_int cnt = 0;
1976 u_int action = RES_PASS;
1977 struct kvlist actions, matches;
1978 struct kv *kv;
1979 int res = 0;
1980
1981 con = cre->con;
1982 TAILQ_INIT(&actions)do { (&actions)->tqh_first = ((void *)0); (&actions
)->tqh_last = &(&actions)->tqh_first; } while (
0)
;
1983
1984 r = TAILQ_FIRST(&proto->rules)((&proto->rules)->tqh_first);
1985 while (r != NULL((void *)0)) {
1986 cnt++;
1987
1988 TAILQ_INIT(&matches)do { (&matches)->tqh_first = ((void *)0); (&matches
)->tqh_last = &(&matches)->tqh_first; } while (
0)
;
1989 TAILQ_INIT(&r->rule_kvlist)do { (&r->rule_kvlist)->tqh_first = ((void *)0); (&
r->rule_kvlist)->tqh_last = &(&r->rule_kvlist
)->tqh_first; } while (0)
;
1990
1991 if (r->rule_dir && r->rule_dir != cre->dir)
1992 RELAY_GET_SKIP_STEP(RULE_SKIP_DIR)do { r = r->rule_skip[1]; do {} while(0); } while (0);
1993 else if (proto->type != r->rule_proto)
1994 RELAY_GET_SKIP_STEP(RULE_SKIP_PROTO)do { r = r->rule_skip[0]; do {} while(0); } while (0);
1995 else if (RELAY_AF_NEQ(r->rule_af, cre->ss.ss_family)(((r->rule_af) != 0) && ((cre->ss.ss_family) !=
0) && ((r->rule_af) != (cre->ss.ss_family)))
||
1996 RELAY_AF_NEQ(r->rule_af, cre->dst->ss.ss_family)(((r->rule_af) != 0) && ((cre->dst->ss.ss_family
) != 0) && ((r->rule_af) != (cre->dst->ss.ss_family
)))
)
1997 RELAY_GET_SKIP_STEP(RULE_SKIP_AF)do { r = r->rule_skip[2]; do {} while(0); } while (0);
1998 else if (RELAY_ADDR_CMP(&r->rule_src, &cre->ss)sockaddr_cmp((struct sockaddr *)&(&r->rule_src)->
addr, (struct sockaddr *)(&cre->ss), (&r->rule_src
)->addr_mask)
!= 0)
1999 RELAY_GET_SKIP_STEP(RULE_SKIP_SRC)do { r = r->rule_skip[3]; do {} while(0); } while (0);
2000 else if (RELAY_ADDR_CMP(&r->rule_dst, &con->se_sockname)sockaddr_cmp((struct sockaddr *)&(&r->rule_dst)->
addr, (struct sockaddr *)(&con->se_sockname), (&r->
rule_dst)->addr_mask)
!= 0)
2001 RELAY_GET_SKIP_STEP(RULE_SKIP_DST)do { r = r->rule_skip[4]; do {} while(0); } while (0);
2002 else if (r->rule_method != HTTP_METHOD_NONE &&
2003 (desc->http_method == HTTP_METHOD_RESPONSE ||
2004 desc->http_method != r->rule_method))
2005 RELAY_GET_SKIP_STEP(RULE_SKIP_METHOD)do { r = r->rule_skip[5]; do {} while(0); } while (0);
2006 else if (r->rule_tagged && con->se_tag != r->rule_tagged)
2007 RELAY_GET_NEXT_STEPdo { do {} while(0); goto nextrule; } while (0);
2008 else if (relay_httpheader_test(cre, r, &matches) != 0)
2009 RELAY_GET_NEXT_STEPdo { do {} while(0); goto nextrule; } while (0);
2010 else if ((res = relay_httpquery_test(cre, r, &matches)) != 0)
2011 RELAY_GET_NEXT_STEPdo { do {} while(0); goto nextrule; } while (0);
2012 else if (relay_httppath_test(cre, r, &matches) != 0)
2013 RELAY_GET_NEXT_STEPdo { do {} while(0); goto nextrule; } while (0);
2014 else if ((res = relay_httpurl_test(cre, r, &matches)) != 0)
2015 RELAY_GET_NEXT_STEPdo { do {} while(0); goto nextrule; } while (0);
2016 else if ((res = relay_httpcookie_test(cre, r, &matches)) != 0)
2017 RELAY_GET_NEXT_STEPdo { do {} while(0); goto nextrule; } while (0);
2018 else {
2019 DPRINTF("%s: session %d: matched rule %d",do {} while(0)
2020 __func__, con->se_id, r->rule_id)do {} while(0);
2021
2022 if (r->rule_action == RULE_ACTION_MATCH) {
2023 if (relay_match_actions(cre, r, &matches,
2024 &actions, &tbl) != 0) {
2025 /* Something bad happened, drop */
2026 action = RES_DROP;
2027 break;
2028 }
2029 RELAY_GET_NEXT_STEPdo { do {} while(0); goto nextrule; } while (0);
2030 } else if (r->rule_action == RULE_ACTION_BLOCK)
2031 action = RES_DROP;
2032 else if (r->rule_action == RULE_ACTION_PASS)
2033 action = RES_PASS;
2034
2035 /* Rule matched */
2036 rule = r;
2037
2038 /* Temporarily save actions */
2039 TAILQ_FOREACH(kv, &matches, kv_match_entry)for((kv) = ((&matches)->tqh_first); (kv) != ((void *)0
); (kv) = ((kv)->kv_match_entry.tqe_next))
{
2040 TAILQ_INSERT_TAIL(&rule->rule_kvlist,do { (kv)->kv_rule_entry.tqe_next = ((void *)0); (kv)->
kv_rule_entry.tqe_prev = (&rule->rule_kvlist)->tqh_last
; *(&rule->rule_kvlist)->tqh_last = (kv); (&rule
->rule_kvlist)->tqh_last = &(kv)->kv_rule_entry.
tqe_next; } while (0)
2041 kv, kv_rule_entry)do { (kv)->kv_rule_entry.tqe_next = ((void *)0); (kv)->
kv_rule_entry.tqe_prev = (&rule->rule_kvlist)->tqh_last
; *(&rule->rule_kvlist)->tqh_last = (kv); (&rule
->rule_kvlist)->tqh_last = &(kv)->kv_rule_entry.
tqe_next; } while (0)
;
2042 }
2043
2044 if (rule->rule_flags & RULE_FLAG_QUICK0x01)
2045 break;
2046
2047 nextrule:
2048 /* Continue to find last matching policy */
2049 DPRINTF("%s: session %d, res %d", __func__,do {} while(0)
2050 con->se_id, res)do {} while(0);
2051 if (res == RES_BAD || res == RES_INTERNAL)
2052 return (res);
2053 res = 0;
2054 r = TAILQ_NEXT(r, rule_entry)((r)->rule_entry.tqe_next);
2055 }
2056 }
2057
2058 if (rule != NULL((void *)0) && relay_match_actions(cre, rule, NULL((void *)0), &actions, &tbl)
2059 != 0) {
2060 /* Something bad happened, drop */
2061 action = RES_DROP;
2062 }
2063
2064 if (relay_apply_actions(cre, &actions, tbl) != 0) {
2065 /* Something bad happened, drop */
2066 action = RES_DROP;
2067 }
2068
2069 DPRINTF("%s: session %d: action %d", __func__,do {} while(0)
2070 con->se_id, action)do {} while(0);
2071
2072 return (action);
2073}
2074
2075#define RELAY_SET_SKIP_STEPS(i)do { while (head[i] != cur) { head[i]->rule_skip[i] = cur;
head[i] = ((head[i])->rule_entry.tqe_next); } } while (0)
\
2076 do { \
2077 while (head[i] != cur) { \
2078 head[i]->rule_skip[i] = cur; \
2079 head[i] = TAILQ_NEXT(head[i], rule_entry)((head[i])->rule_entry.tqe_next); \
2080 } \
2081 } while (0)
2082
2083/* This code is derived from pf_calc_skip_steps() from pf.c */
2084void
2085relay_calc_skip_steps(struct relay_rules *rules)
2086{
2087 struct relay_rule *head[RULE_SKIP_COUNT6], *cur, *prev;
2088 int i;
2089
2090 cur = TAILQ_FIRST(rules)((rules)->tqh_first);
2091 prev = cur;
2092 for (i = 0; i < RULE_SKIP_COUNT6; ++i)
2093 head[i] = cur;
2094 while (cur != NULL((void *)0)) {
2095 if (cur->rule_dir != prev->rule_dir)
2096 RELAY_SET_SKIP_STEPS(RULE_SKIP_DIR)do { while (head[1] != cur) { head[1]->rule_skip[1] = cur;
head[1] = ((head[1])->rule_entry.tqe_next); } } while (0)
;
2097 else if (cur->rule_proto != prev->rule_proto)
2098 RELAY_SET_SKIP_STEPS(RULE_SKIP_PROTO)do { while (head[0] != cur) { head[0]->rule_skip[0] = cur;
head[0] = ((head[0])->rule_entry.tqe_next); } } while (0)
;
2099 else if (RELAY_AF_NEQ(cur->rule_af, prev->rule_af)(((cur->rule_af) != 0) && ((prev->rule_af) != 0
) && ((cur->rule_af) != (prev->rule_af)))
)
2100 RELAY_SET_SKIP_STEPS(RULE_SKIP_AF)do { while (head[2] != cur) { head[2]->rule_skip[2] = cur;
head[2] = ((head[2])->rule_entry.tqe_next); } } while (0)
;
2101 else if (RELAY_ADDR_NEQ(&cur->rule_src, &prev->rule_src)((&cur->rule_src)->addr_mask != (&prev->rule_src
)->addr_mask || sockaddr_cmp((struct sockaddr *)&(&
cur->rule_src)->addr, (struct sockaddr *)&(&prev
->rule_src)->addr, (&cur->rule_src)->addr_mask
) != 0)
)
2102 RELAY_SET_SKIP_STEPS(RULE_SKIP_SRC)do { while (head[3] != cur) { head[3]->rule_skip[3] = cur;
head[3] = ((head[3])->rule_entry.tqe_next); } } while (0)
;
2103 else if (RELAY_ADDR_NEQ(&cur->rule_dst, &prev->rule_dst)((&cur->rule_dst)->addr_mask != (&prev->rule_dst
)->addr_mask || sockaddr_cmp((struct sockaddr *)&(&
cur->rule_dst)->addr, (struct sockaddr *)&(&prev
->rule_dst)->addr, (&cur->rule_dst)->addr_mask
) != 0)
)
2104 RELAY_SET_SKIP_STEPS(RULE_SKIP_DST)do { while (head[4] != cur) { head[4]->rule_skip[4] = cur;
head[4] = ((head[4])->rule_entry.tqe_next); } } while (0)
;
2105 else if (cur->rule_method != prev->rule_method)
2106 RELAY_SET_SKIP_STEPS(RULE_SKIP_METHOD)do { while (head[5] != cur) { head[5]->rule_skip[5] = cur;
head[5] = ((head[5])->rule_entry.tqe_next); } } while (0)
;
2107
2108 prev = cur;
2109 cur = TAILQ_NEXT(cur, rule_entry)((cur)->rule_entry.tqe_next);
2110 }
2111 for (i = 0; i < RULE_SKIP_COUNT6; ++i)
2112 RELAY_SET_SKIP_STEPS(i)do { while (head[i] != cur) { head[i]->rule_skip[i] = cur;
head[i] = ((head[i])->rule_entry.tqe_next); } } while (0)
;
2113}
2114
2115void
2116relay_match(struct kvlist *actions, struct kv *kv, struct kv *match,
2117 struct kvtree *matchtree)
2118{
2119 if (kv->kv_option != KEY_OPTION_NONE) {
2120 kv->kv_match = match;
2121 kv->kv_matchtree = matchtree;
2122 TAILQ_INSERT_TAIL(actions, kv, kv_match_entry)do { (kv)->kv_match_entry.tqe_next = ((void *)0); (kv)->
kv_match_entry.tqe_prev = (actions)->tqh_last; *(actions)->
tqh_last = (kv); (actions)->tqh_last = &(kv)->kv_match_entry
.tqe_next; } while (0)
;
2123 }
2124}
2125
2126char *
2127server_root_strip(char *path, int n)
2128{
2129 char *p;
2130
2131 /* Strip strip leading directories. Leading '/' is ignored. */
2132 for (; n > 0 && *path != '\0'; n--)
2133 if ((p = strchr(++path, '/')) != NULL((void *)0))
2134 path = p;
2135 else
2136 path--;
2137
2138 return (path);
2139}
2140