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