Bug Summary

File:src/usr.sbin/relayd/relay_http.c
Warning:line 1721, column 22
Access to field 'kv_flags' results in a dereference of a null pointer (loaded from variable 'match')

Annotated Source Code

Press '?' to see keyboard shortcuts

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