blob: e798a2275256991b1cbc3d2801769d365739db3e [file] [log] [blame]
Dragan Dosen59bb97a2017-06-02 12:03:16 +02001/*
2 * Mod Defender for HAProxy
3 *
4 * Copyright 2017 HAProxy Technologies, Dragan Dosen <ddosen@haproxy.com>
5 *
6 * Mod Defender
7 * Copyright (c) 2017 Annihil (https://github.com/VultureProject/mod_defender)
8 *
9 * Parts of code based on Apache HTTP Server source
10 * Copyright 2015 The Apache Software Foundation (http://www.apache.org/)
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version
15 * 3 of the License, or (at your option) any later version.
16 *
17 */
Dragan Dosen59bb97a2017-06-02 12:03:16 +020018#include <stdio.h>
19#include <stdarg.h>
20
Willy Tarreau4c7e4b72020-05-27 12:58:42 +020021#include <haproxy/api.h>
Willy Tarreauc13ed532020-06-02 10:22:45 +020022#include <haproxy/chunk.h>
Willy Tarreau6c58ab02020-06-04 22:35:49 +020023#include <haproxy/spoe.h>
Willy Tarreau92b4f132020-06-01 11:05:15 +020024#include <haproxy/time.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020025#include <haproxy/tools.h>
Dragan Dosen59bb97a2017-06-02 12:03:16 +020026
Dragan Dosen59bb97a2017-06-02 12:03:16 +020027#include <http_core.h>
28#include <http_main.h>
29#include <http_log.h>
30#include <http_request.h>
31
32#include <apr_pools.h>
33#include <apr_strings.h>
34
35#include "spoa.h"
36#include "standalone.h"
37#include "defender.h"
38
39#define DEFENDER_NAME "defender"
40#define DEFENDER_INPUT_FILTER "DEFENDER_IN"
41#define DEFENDER_DEFAULT_UNIQUE_ID "unique_id"
42#define DEFENDER_BRIGADE_REQUEST "defender-brigade-request"
43
44extern module AP_MODULE_DECLARE_DATA defender_module;
45
46DECLARE_HOOK(int,post_config,(apr_pool_t *pconf,apr_pool_t *plog, apr_pool_t *ptemp,server_rec *s))
47DECLARE_HOOK(int,fixups,(request_rec *r))
48DECLARE_HOOK(int,header_parser,(request_rec *r))
49
50char *defender_name = DEFENDER_NAME;
51const char *defender_argv[] = { DEFENDER_NAME, NULL };
52const char *defender_unknown_hostname = "";
53
54void *defender_module_config = NULL;
55static server_rec *server = NULL;
56apr_pool_t *defender_pool = NULL;
57
58char hostname[MAX_HOSTNAME_LEN];
59char defender_cwd[MAXPATHLEN];
60
61static apr_status_t defender_bucket_read(apr_bucket *b, const char **str,
62 apr_size_t *len, apr_read_type_e block);
63static void defender_bucket_destroy(void *data);
64
65static const apr_bucket_type_t apr_bucket_type_defender = {
66 "defender", 8, APR_BUCKET_DATA,
67 defender_bucket_destroy,
68 defender_bucket_read,
69 apr_bucket_setaside_noop,
70 apr_bucket_shared_split,
71 apr_bucket_shared_copy
72};
73
74struct apr_bucket_defender {
75 apr_bucket_refcount refcount;
Willy Tarreau83061a82018-07-13 11:56:34 +020076 struct buffer buf;
Dragan Dosen59bb97a2017-06-02 12:03:16 +020077};
78
79static apr_status_t defender_bucket_read(apr_bucket *b, const char **str,
80 apr_size_t *len, apr_read_type_e block)
81{
82 struct apr_bucket_defender *d = b->data;
83
Willy Tarreau843b7cb2018-07-13 10:54:26 +020084 *str = d->buf.area;
85 *len = d->buf.data;
Dragan Dosen59bb97a2017-06-02 12:03:16 +020086
87 return APR_SUCCESS;
88}
89
90static void defender_bucket_destroy(void *data)
91{
92 struct apr_bucket_defender *d = data;
93
94 if (apr_bucket_shared_destroy(d))
95 apr_bucket_free(d);
96}
97
Willy Tarreau83061a82018-07-13 11:56:34 +020098static apr_bucket *defender_bucket_make(apr_bucket *b,
99 const struct buffer *buf)
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200100{
101 struct apr_bucket_defender *d;
102
103 d = apr_bucket_alloc(sizeof(*d), b->list);
104
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200105 d->buf.area = buf->area;
106 d->buf.data = buf->data;
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200107 d->buf.size = 0;
108
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200109 b = apr_bucket_shared_make(b, d, 0, buf->data);
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200110 b->type = &apr_bucket_type_defender;
111 return b;
112}
113
Willy Tarreau83061a82018-07-13 11:56:34 +0200114static apr_bucket *defender_bucket_create(const struct buffer *buf,
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200115 apr_bucket_alloc_t *list)
116{
117 apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
118
119 APR_BUCKET_INIT(b);
120 b->free = apr_bucket_free;
121 b->list = list;
122 return defender_bucket_make(b, buf);
123}
124
125static void defender_logger(int level, char *str)
126{
127 LOG(&null_worker, "%s", str);
128}
129
130static char *defender_strdup(apr_pool_t *pool, const char *src, uint64_t len)
131{
132 char *dst;
133
134 if (!(dst = apr_pcalloc(pool, len + 1)))
135 return NULL;
136
137 memcpy(dst, src, len);
138 dst[len] = '\0';
139
140 return dst;
141}
142
143static char *defender_printf(apr_pool_t *pool, const char *fmt, ...)
144{
145 va_list argp;
146 char *dst;
147 int len;
148
149 va_start(argp, fmt);
150 len = vsnprintf(NULL, 0, fmt, argp);
Dragan Dosenccf61002017-09-18 08:20:33 +0200151 va_end(argp);
152
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200153 if (len < 0)
154 return NULL;
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200155
156 if (!(dst = apr_pcalloc(pool, len + 1)))
157 return NULL;
158
159 va_start(argp, fmt);
160 len = vsnprintf(dst, len + 1, fmt, argp);
161 va_end(argp);
162
163 return dst;
164}
165
166static char *defender_addr2str(apr_pool_t *pool, struct sample *addr)
167{
168 sa_family_t family;
169 const void *src;
170 char *dst;
171
172 switch (addr->data.type) {
173 case SMP_T_IPV4:
174 src = &addr->data.u.ipv4;
175 family = AF_INET;
176 break;
177 case SMP_T_IPV6:
178 src = &addr->data.u.ipv6;
179 family = AF_INET6;
180 break;
181 default:
182 return NULL;
183 }
184
185 if (!(dst = apr_pcalloc(pool, INET6_ADDRSTRLEN + 1)))
186 return NULL;
187
188 if (inet_ntop(family, src, dst, INET6_ADDRSTRLEN))
189 return dst;
190
191 return NULL;
192}
193
194static void defender_pre_config()
195{
196 apr_pool_t *ptemp = NULL;
197
198 defender_module.module_index = 0;
199 defender_module.register_hooks(defender_pool);
200
201 apr_pool_create(&ptemp, defender_pool);
202 run_ap_hook_post_config(defender_pool, defender_pool, ptemp, server);
203 apr_pool_destroy(ptemp);
204}
205
206static const char *defender_read_config(const char *file)
207{
208 apr_pool_t *ptemp = NULL;
209 const char *err;
210 const char *fullname;
211
212 defender_module_config = defender_module.create_dir_config(defender_pool, "/");
213 if (defender_module_config == NULL) {
214 return "cannot allocate space for the configuration structure";
215 }
216
217 apr_pool_create(&ptemp, defender_pool);
218
219 fullname = ap_server_root_relative(ptemp, file);
220
221 err = read_module_config(server, defender_module_config,
222 defender_module.cmds,
223 defender_pool, ptemp, fullname);
224
225 apr_pool_destroy(ptemp);
226
227 return err;
228}
229
230static void defender_post_config()
231{
232 apr_pool_t *ptemp = NULL;
233
234 apr_pool_create(&ptemp, defender_pool);
235 run_ap_hook_post_config(defender_pool, defender_pool, ptemp, server);
236 apr_pool_destroy(ptemp);
237}
238
239static const char *defender_set_logger(const char *file)
240{
241 char *logname;
242
243 logger = defender_logger;
244
245 if (file == NULL)
246 return NULL;
247
248 logname = ap_server_root_relative(defender_pool, file);
249
250 if (apr_file_open(&server->error_log, logname,
251 APR_APPEND | APR_WRITE | APR_CREATE | APR_LARGEFILE,
252 APR_OS_DEFAULT, defender_pool) != APR_SUCCESS) {
253 return apr_pstrcat(defender_pool, "Cannot open log file, ",
254 logname, NULL);
255 }
256 server->error_fname = logname;
257
258 return NULL;
259}
260
261static apr_status_t defender_input_filter(ap_filter_t *f,
262 apr_bucket_brigade *new_bb,
263 ap_input_mode_t mode,
264 apr_read_type_e block,
265 apr_off_t readbytes)
266{
267 apr_bucket_brigade *bb = NULL;
268 apr_bucket *b = NULL, *a = NULL;
269 apr_status_t rv;
270
271 bb = (apr_bucket_brigade *)apr_table_get(f->r->notes, DEFENDER_BRIGADE_REQUEST);
272
273 if (bb == NULL || (bb && !APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb)))) {
274 b = apr_bucket_eos_create(f->c->bucket_alloc);
275 APR_BRIGADE_INSERT_TAIL(new_bb, b);
276 if (bb == NULL)
277 return APR_SUCCESS;
278 }
279
280 rv = apr_brigade_partition(bb, readbytes, &a);
281 if (rv != APR_SUCCESS && rv != APR_INCOMPLETE)
282 return rv;
283
284 b = APR_BRIGADE_FIRST(bb);
285
286 while (b != a) {
287 if (APR_BUCKET_IS_EOS(b))
288 ap_remove_input_filter(f);
289
290 APR_BUCKET_REMOVE(b);
291 APR_BRIGADE_INSERT_TAIL(new_bb, b);
292 b = APR_BRIGADE_FIRST(bb);
293 }
294
295 return APR_SUCCESS;
296}
297
298static conn_rec *defender_create_conn()
299{
300 conn_rec *c = NULL;
301 apr_pool_t *ptrans = NULL;
302
303 apr_pool_create(&ptrans, defender_pool);
304
305 c = apr_pcalloc(ptrans, sizeof(conn_rec));
306
307 c->pool = ptrans;
308 c->local_ip = "127.0.0.1";
309 c->local_addr = server->addrs->host_addr;
310 c->local_host = defender_name;
311 c->client_addr = server->addrs->host_addr;
312 c->remote_host = defender_name;
313
314 c->id = 1;
315 c->base_server = server;
316 c->bucket_alloc = apr_bucket_alloc_create(ptrans);
317
318 return c;
319}
320
321static request_rec *defender_create_request(conn_rec *conn)
322{
323 request_rec *r = NULL;
324 apr_pool_t *p = NULL;
325 struct ap_logconf *l;
326
327 apr_pool_create(&p, conn->pool);
328
329 r = apr_pcalloc(p, sizeof(request_rec));
330
331 r->pool = p;
332 r->connection = conn;
333 r->server = conn->base_server;
334
335 r->headers_in = apr_table_make(p, 25);
336 r->headers_out = apr_table_make(p, 12);
337 r->subprocess_env = apr_table_make(p, 25);
338 r->err_headers_out = apr_table_make(p, 5);
339 r->notes = apr_table_make(p, 5);
340
341 r->request_config = apr_palloc(p, sizeof(void *));
342 r->per_dir_config = apr_palloc(p, sizeof(void *));
343 ((void **)r->per_dir_config)[0] = defender_module_config;
344
345 r->handler = defender_name;
346
347 r->parsed_uri.scheme = "http";
348 r->parsed_uri.is_initialized = 1;
349 r->parsed_uri.port = 80;
350 r->parsed_uri.port_str = "80";
351 r->parsed_uri.fragment = "";
352
353 r->input_filters = NULL;
354 r->output_filters = NULL;
355
356 l = apr_pcalloc(p, sizeof(struct ap_logconf));
357 l->level = APLOG_DEBUG;
358 r->log = l;
359
360 return r;
361}
362
363static int defender_process_headers(request_rec *r)
364{
365 return run_ap_hook_header_parser(r);
366}
367
368static int defender_process_body(request_rec *r)
369{
370 ap_add_input_filter(DEFENDER_INPUT_FILTER, NULL, r, r->connection);
371 return run_ap_hook_fixups(r);
372}
373
374int defender_init(const char *config_file, const char *log_file)
375{
376 apr_status_t rv;
377 const char *msg;
378
379 if (!config_file) {
380 LOG(&null_worker, "Mod Defender configuration file not specified.\n");
381 return 0;
382 }
383
384 apr_initialize();
385 apr_pool_create(&defender_pool, NULL);
386 apr_hook_global_pool = defender_pool;
387
388 ap_server_root = getcwd(defender_cwd, APR_PATH_MAX);
389
390 server = (server_rec *) apr_palloc(defender_pool, sizeof(server_rec));
391 server->process = apr_palloc(defender_pool, sizeof(process_rec));
392 server->process->argc = 1;
393 server->process->argv = defender_argv;
394 server->process->short_name = defender_name;
395 server->process->pconf = defender_pool;
396 server->process->pool = defender_pool;
397
398 server->addrs = apr_palloc(defender_pool, sizeof(server_addr_rec));
399 rv = apr_sockaddr_info_get(&server->addrs->host_addr,
400 "127.0.0.1", APR_UNSPEC, 0, 0,
401 defender_pool);
402 if (rv != APR_SUCCESS) {
403 LOG(&null_worker, "Mod Defender getaddrinfo failed.\n");
404 return 0;
405 }
406
407 server->path = "/";
408 server->pathlen = strlen(server->path);
409 server->port = 0;
410 server->server_admin = defender_name;
411 server->server_scheme = "";
412 server->error_fname = NULL;
413 server->error_log = NULL;
414 server->limit_req_line = DEFAULT_LIMIT_REQUEST_LINE;
415 server->limit_req_fieldsize = DEFAULT_LIMIT_REQUEST_FIELDSIZE;
416 server->limit_req_fields = DEFAULT_LIMIT_REQUEST_FIELDS;
417 server->timeout = apr_time_from_sec(DEFAULT_TIMEOUT);
418
419 memset(hostname, 0, sizeof(hostname));
420 gethostname(hostname, sizeof(hostname) - 1);
421 server->server_hostname = hostname;
422
423 server->addrs->host_port = 0;
424 server->names = server->wild_names = NULL;
425 server->is_virtual = 0;
426
427 server->lookup_defaults = NULL;
428 server->module_config = NULL;
429
430 msg = defender_set_logger(log_file);
431 if (msg != NULL) {
432 LOG(&null_worker, "Mod Defender init failed: %s\n", msg);
433 return 0;
434 }
435
436 ap_register_input_filter(DEFENDER_INPUT_FILTER, defender_input_filter,
437 NULL, AP_FTYPE_RESOURCE);
438
439 defender_pre_config();
440
441 msg = defender_read_config(config_file);
442 if (msg != NULL) {
443 LOG(&null_worker, "Mod Defender configuration failed: %s\n", msg);
444 return 0;
445 }
446
447 defender_post_config();
448
449 return 1;
450}
451
452int defender_process_request(struct worker *worker, struct defender_request *request)
453{
454 struct conn_rec *c = NULL;
455 struct request_rec *r = NULL;
456
457 struct apr_bucket_brigade *bb = NULL;
458 struct apr_bucket *d = NULL, *e = NULL;
459
Willy Tarreau83061a82018-07-13 11:56:34 +0200460 struct buffer *method;
461 struct buffer *path;
462 struct buffer *query;
463 struct buffer *version;
464 struct buffer *body;
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200465
466 struct defender_header hdr;
467 char *hdr_ptr, *hdr_end;
468
469 const char *ptr;
470
471 int status = DECLINED;
472
473 if (!(c = defender_create_conn()))
474 goto out;
475
476 if (!(r = defender_create_request(c)))
477 goto out;
478
479 /* request */
480 r->request_time = apr_time_now();
481
482 if (request->clientip.data.type != SMP_T_IPV4 &&
483 request->clientip.data.type != SMP_T_IPV6)
484 goto out;
485
486 if (!(r->useragent_ip = defender_addr2str(r->pool, &request->clientip)))
487 goto out;
488
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200489 if (request->id.data.u.str.area && request->id.data.u.str.data > 0) {
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200490 apr_table_setn(r->subprocess_env, "UNIQUE_ID",
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200491 defender_strdup(r->pool, request->id.data.u.str.area,
492 request->id.data.u.str.data));
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200493 }
494 else {
495 apr_table_setn(r->subprocess_env, "UNIQUE_ID",
496 DEFENDER_DEFAULT_UNIQUE_ID);
497 }
498
499 method = &request->method.data.u.str;
500 path = &request->path.data.u.str;
501 query = &request->query.data.u.str;
502 version = &request->version.data.u.str;
503
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200504 r->method_number = lookup_builtin_method(method->area, method->data);
505 if (!(r->method = defender_strdup(r->pool, method->area, method->data)))
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200506 goto out;
507
508 r->unparsed_uri = defender_printf(r->pool, "%.*s%s%.*s",
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200509 path->data, path->area,
510 query->data > 0 ? "?" : "",
511 query->data, query->area);
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200512 if (!r->unparsed_uri)
513 goto out;
514
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200515 if (!(r->uri = defender_strdup(r->pool, path->area, path->data)))
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200516 goto out;
517
518 r->parsed_uri.path = r->filename = r->uri;
519
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200520 if (!(r->args = defender_strdup(r->pool, query->area, query->data)))
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200521 goto out;
522
523 r->parsed_uri.query = r->args;
524
525 r->protocol = defender_printf(r->pool, "%s%.*s",
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200526 version->data > 0 ? "HTTP/" : "",
527 version->data, version->area);
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200528 if (!r->protocol)
529 goto out;
530
531 r->the_request = defender_printf(r->pool, "%.*s %s%s%s",
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200532 method->data, method->area,
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200533 r->unparsed_uri,
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200534 version->data > 0 ? " " : "",
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200535 r->protocol);
536 if (!r->the_request)
537 goto out;
538
539 /* headers */
540 if (request->headers.data.type != SMP_T_BIN)
541 goto misc;
542
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200543 hdr_ptr = request->headers.data.u.str.area;
544 hdr_end = hdr_ptr + request->headers.data.u.str.data;
Dragan Dosen59bb97a2017-06-02 12:03:16 +0200545
546 while (1) {
547 memset(&hdr, 0, sizeof(hdr));
548
549 if (decode_varint(&hdr_ptr, hdr_end, &hdr.name.len) == -1)
550 goto out;
551 if (!(hdr.name.str = defender_strdup(r->pool, hdr_ptr, hdr.name.len)))
552 goto out;
553
554 hdr_ptr += hdr.name.len;
555 if (hdr_ptr > hdr_end)
556 goto out;
557
558 if (decode_varint(&hdr_ptr, hdr_end, &hdr.value.len) == -1)
559 goto out;
560 if (!(hdr.value.str = defender_strdup(r->pool, hdr_ptr, hdr.value.len)))
561 goto out;
562
563 hdr_ptr += hdr.value.len;
564 if (hdr_ptr > hdr_end)
565 goto out;
566
567 if (!hdr.name.len && !hdr.value.len)
568 break;
569
570 apr_table_setn(r->headers_in, hdr.name.str, hdr.value.str);
571 }
572
573misc:
574
575 r->hostname = apr_table_get(r->headers_in, "Host");
576 if (!r->hostname)
577 r->hostname = defender_unknown_hostname;
578 r->parsed_uri.hostname = (char *)r->hostname;
579
580 r->content_type = apr_table_get(r->headers_in, "Content-Type");
581 r->content_encoding = apr_table_get(r->headers_in, "Content-Encoding");
582 ptr = apr_table_get(r->headers_in, "Content-Length");
583 if (ptr)
584 r->clength = strtol(ptr, NULL, 10);
585
586 /* body */
587 body = &request->body.data.u.str;
588
589 bb = apr_brigade_create(r->pool, c->bucket_alloc);
590 if (bb == NULL)
591 goto out;
592
593 d = defender_bucket_create(body, c->bucket_alloc);
594 if (d == NULL)
595 goto out;
596
597 APR_BRIGADE_INSERT_TAIL(bb, d);
598
599 e = apr_bucket_eos_create(c->bucket_alloc);
600 APR_BRIGADE_INSERT_TAIL(bb, e);
601
602 apr_table_setn(r->notes, DEFENDER_BRIGADE_REQUEST, (char *)bb);
603
604 /* process */
605 status = defender_process_headers(r);
606
607 if (status == DECLINED)
608 status = defender_process_body(r);
609
610 apr_brigade_cleanup(bb);
611
612 /* success */
613 if (status == DECLINED)
614 status = OK;
615
616out:
617
618 if (r && r->pool) {
619 apr_table_clear(r->headers_in);
620 apr_table_clear(r->headers_out);
621 apr_table_clear(r->subprocess_env);
622 apr_table_clear(r->err_headers_out);
623 apr_table_clear(r->notes);
624 apr_pool_destroy(r->pool);
625 }
626
627 if (c && c->pool) {
628 apr_bucket_alloc_destroy(c->bucket_alloc);
629 apr_pool_destroy(c->pool);
630 }
631
632 return status;
633}