blob: 8927b5bf4650707ad54c517f7740e3638d49036c [file] [log] [blame]
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +02001/*
2 * Modsecurity wrapper for haproxy
3 *
4 * This file contains the wrapper which sends data in ModSecurity
5 * and returns the verdict.
6 *
7 * Copyright 2016 OZON, Thierry Fournier <thierry.fournier@ozon.io>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version
12 * 2 of the License, or (at your option) any later version.
13 *
14 */
15#include <limits.h>
16#include <stdio.h>
17#include <stdarg.h>
18
19#include <common/time.h>
20
21#include <types/global.h>
22#include <types/stream.h>
23
24#include <proto/arg.h>
25#include <proto/hdr_idx.h>
26#include <proto/hlua.h>
27#include <proto/log.h>
28#include <proto/proto_http.h>
29#include <proto/spoe.h>
30
31#include <api.h>
32
33#include "modsec_wrapper.h"
34#include "spoa.h"
35
36static char host_name[60];
37
38/* Note: The document and the code of "apr_table_make" considers
39 * that this function doesn't fails. The Apache APR code says
40 * other thing. If the system doesn't have any more memory, a
41 * a segfault occurs :(. Be carrefull with this module.
42 */
43
44struct directory_config *modsec_config = NULL;
45static server_rec *modsec_server = NULL;
46
47struct apr_bucket_haproxy {
48 apr_bucket_refcount refcount;
49 char *buffer;
50 size_t length;
51};
52
53static void haproxy_bucket_destroy(void *data)
54{
55 struct apr_bucket_haproxy *bucket = data;
56
57 if (apr_bucket_shared_destroy(bucket))
58 apr_bucket_free(bucket);
59}
60
61static apr_status_t haproxy_bucket_read(apr_bucket *bucket, const char **str,
62 apr_size_t *len, apr_read_type_e block)
63{
64 struct apr_bucket_haproxy *data = bucket->data;
65
66 if (bucket->start) {
67 *str = NULL;
68 *len = 0;
69 return APR_SUCCESS;
70 }
71
72 *str = data->buffer;
73 *len = data->length;
74 bucket->start = 1; /* Just a flag to say that the read is started */
75
76 return APR_SUCCESS;
77}
78
79static const apr_bucket_type_t apr_bucket_type_haproxy = {
80 "HAProxy", 7, APR_BUCKET_DATA,
81 haproxy_bucket_destroy,
82 haproxy_bucket_read,
83 apr_bucket_setaside_noop,
84 apr_bucket_shared_split,
85 apr_bucket_shared_copy
86};
87
88static char *chunk_strdup(struct request_rec *req, const char *str, size_t len)
89{
90 char *out;
91
92 out = apr_pcalloc(req->pool, len + 1);
93 if (!out)
94 return NULL;
95 memcpy(out, str, len);
96 out[len] = '\0';
97 return out;
98}
99
100static char *printf_dup(struct request_rec *req, char *fmt, ...)
101{
102 char *out;
103 va_list ap;
104 int len;
105
106 va_start(ap, fmt);
107 len = vsnprintf(NULL, 0, fmt, ap);
108 if (len == -1)
109 return NULL;
110 va_end(ap);
111
112 out = apr_pcalloc(req->pool, len + 1);
113 if (!out)
114 return NULL;
115
116 va_start(ap, fmt);
117 len = vsnprintf(out, len + 1, fmt, ap);
118 if (len == -1)
119 return NULL;
120 va_end(ap);
121
122 return out;
123}
124
125/* This function send logs. For now, it do nothing. */
126static void modsec_log(void *obj, int level, char *str)
127{
128 LOG(&null_worker, "%s", str);
129}
130
131/* This fucntion load the ModSecurity file. It returns -1 if the
132 * initialisation fails.
133 */
134int modsecurity_load(const char *file)
135{
136 const char *msg;
137 char cwd[128];
138
139 /* Initialises modsecurity. */
140
141 modsec_server = modsecInit();
142 if (modsec_server == NULL) {
143 LOG(&null_worker, "ModSecurity initilisation failed.\n");
144 return -1;
145 }
146
147 modsecSetLogHook(NULL, modsec_log);
148
149 gethostname(host_name, 60);
150 modsec_server->server_hostname = host_name;
151
152 modsecStartConfig();
153
154 modsec_config = modsecGetDefaultConfig();
155 if (modsec_config == NULL) {
156 LOG(&null_worker, "ModSecurity default configuration initilisation failed.\n");
157 return -1;
158 }
159
160 msg = modsecProcessConfig(modsec_config, file, getcwd(cwd, 128));
161 if (msg != NULL) {
162 LOG(&null_worker, "ModSecurity load configuration failed.\n");
163 return -1;
164 }
165
166 modsecFinalizeConfig();
167
168 modsecInitProcess();
169
170 return 1;
171}
172
173struct modsec_hdr {
174 const char *name;
175 uint64_t name_len;
176 const char *value;
177 uint64_t value_len;
178};
179
180int modsecurity_process(struct worker *worker, struct modsecurity_parameters *params)
181{
182 struct conn_rec *cr;
183 struct request_rec *req;
184 struct apr_bucket_brigade *brigade;
185 struct apr_bucket *link_bucket;
186 struct apr_bucket_haproxy *data_bucket;
187 struct apr_bucket *last_bucket;
188 int i;
189 long clength;
190 char *err;
191 int fail;
192 const char *lang;
193 char *name, *value;
194 // int body_partial;
195 struct timeval now;
196 int ret;
197 char *buf;
198 char *end;
199 const char *uniqueid;
200 uint64_t uniqueid_len;
201 const char *meth;
202 uint64_t meth_len;
203 const char *path;
204 uint64_t path_len;
205 const char *qs;
206 uint64_t qs_len;
207 const char *vers;
208 uint64_t vers_len;
209 const char *body;
210 uint64_t body_len;
211 uint64_t body_exposed_len;
212 uint64_t hdr_nb;
213 struct modsec_hdr hdrs[255];
214 struct modsec_hdr hdr;
215 int status;
216 int return_code = -1;
217
218 /* Decode uniqueid. */
219 uniqueid = params->uniqueid.data.u.str.str;
220 uniqueid_len = params->uniqueid.data.u.str.len;
221
222 /* Decode method. */
223 meth = params->method.data.u.str.str;
224 meth_len = params->method.data.u.str.len;
225
226 /* Decode path. */
227 path = params->path.data.u.str.str;
228 path_len = params->path.data.u.str.len;
229
230 /* Decode query string. */
231 qs = params->query.data.u.str.str;
232 qs_len = params->query.data.u.str.len;
233
234 /* Decode version. */
235 vers = params->vers.data.u.str.str;
236 vers_len = params->vers.data.u.str.len;
237
238 /* Decode header binary block. */
239 buf = params->hdrs_bin.data.u.str.str;
240 end = buf + params->hdrs_bin.data.u.str.len;
241
242 /* Decode each header. */
243 hdr_nb = 0;
244 while (1) {
245
246 /* Initialise the storage struct. It is useless
247 * because the process fail if the struct is not
248 * fully filled. This init is just does in order
249 * to prevent bug after some improvements.
250 */
251 memset(&hdr, 0, sizeof(hdr));
252
253 /* Decode header name. */
254 ret = decode_varint(&buf, end, &hdr.name_len);
255 if (ret == -1)
256 return -1;
257 hdr.name = buf;
258 buf += hdr.name_len;
259 if (buf > end)
260 return -1;
261
262 /* Decode header value. */
263 ret = decode_varint(&buf, end, &hdr.value_len);
264 if (ret == -1)
265 return -1;
266 hdr.value = buf;
267 buf += hdr.value_len;
268 if (buf > end)
269 return -1;
270
271 /* Detect the end of the headers. */
272 if (hdr.name_len == 0 && hdr.value_len == 0)
273 break;
274
275 /* Store the header. */
276 if (hdr_nb < 255) {
277 memcpy(&hdrs[hdr_nb], &hdr, sizeof(hdr));
278 hdr_nb++;
279 }
280 }
281
282 /* Decode body length. Note that the following control
283 * is just set for avoifing a gcc warning.
284 */
285 body_exposed_len = (uint64_t)params->body_length.data.u.sint;
286 if (body_exposed_len < 0)
287 return -1;
288
289 /* Decode body. */
290 body = params->body.data.u.str.str;
291 body_len = params->body.data.u.str.len;
292
293 fail = 1;
294
295 /* Init processing */
296
297 cr = modsecNewConnection();
298 req = modsecNewRequest(cr, modsec_config);
299
300 /* Load request. */
301
302 req->proxyreq = PROXYREQ_NONE;
303 req->header_only = 0; /* May modified later */
304
305 /* Copy header list. */
306
307 for (i = 0; i < hdr_nb; i++) {
308 name = chunk_strdup(req, hdrs[i].name, hdrs[i].name_len);
309 if (!name) {
310 errno = ENOMEM;
311 goto fail;
312 }
313 value = chunk_strdup(req, hdrs[i].value, hdrs[i].value_len);
314 if (!value) {
315 errno = ENOMEM;
316 goto fail;
317 }
318 apr_table_setn(req->headers_in, name, value);
319 }
320
321 /* Process special headers. */
322 req->range = apr_table_get(req->headers_in, "Range");
323 req->content_type = apr_table_get(req->headers_in, "Content-Type");
324 req->content_encoding = apr_table_get(req->headers_in, "Content-Encoding");
325 req->hostname = apr_table_get(req->headers_in, "Host");
326 req->parsed_uri.hostname = chunk_strdup(req, req->hostname, strlen(req->hostname));
327
328 lang = apr_table_get(req->headers_in, "Content-Languages");
329 if (lang != NULL) {
330 req->content_languages = apr_array_make(req->pool, 1, sizeof(const char *));
331 *(const char **)apr_array_push(req->content_languages) = lang;
332 }
333
334 lang = apr_table_get(req->headers_in, "Content-Length");
335 if (lang) {
336 errno = 0;
337 clength = strtol(lang, &err, 10);
338 if (*err != '\0' || errno != 0 || clength < 0 || clength > INT_MAX) {
339 errno = ERANGE;
340 goto fail;
341 }
342 req->clength = clength;
343 }
344
345 /* Copy the first line of the request. */
346 req->the_request = printf_dup(req, "%.*s %.*s%s%.*s %.*s",
347 meth_len, meth,
348 path_len, path,
349 qs_len > 0 ? "?" : "",
350 qs_len, qs,
351 vers_len, vers);
352 if (!req->the_request) {
353 errno = ENOMEM;
354 goto fail;
355 }
356
357 /* Copy the method. */
358 req->method = chunk_strdup(req, meth, meth_len);
359 if (!req->method) {
360 errno = ENOMEM;
361 goto fail;
362 }
363
364 /* Set the method number. */
365 if (meth_len < 3) {
366 errno = EINVAL;
367 goto fail;
368 }
369
370 /* Detect the method */
371 switch (meth_len) {
372 case 3:
373 if (strncmp(req->method, "GET", 3) == 0)
374 req->method_number = M_GET;
375 else if (strncmp(req->method, "PUT", 3) == 0)
376 req->method_number = M_PUT;
377 else {
378 errno = EINVAL;
379 goto fail;
380 }
381 break;
382 case 4:
383 if (strncmp(req->method, "POST", 4) == 0)
384 req->method_number = M_POST;
385 else if (strncmp(req->method, "HEAD", 4) == 0) {
386 req->method_number = M_GET;
387 req->header_only = 1;
388 }
389 else if (strncmp(req->method, "COPY", 4) == 0)
390 req->method_number = M_COPY;
391 else if (strncmp(req->method, "MOVE", 4) == 0)
392 req->method_number = M_MOVE;
393 else if (strncmp(req->method, "LOCK", 4) == 0)
394 req->method_number = M_LOCK;
395 else {
396 errno = EINVAL;
397 goto fail;
398 }
399 break;
400 case 5:
401 if (strncmp(req->method, "TRACE", 5) == 0)
402 req->method_number = M_TRACE;
403 else if (strncmp(req->method, "PATCH", 5) == 0)
404 req->method_number = M_PATCH;
405 else if (strncmp(req->method, "MKCOL", 5) == 0)
406 req->method_number = M_MKCOL;
407 else if (strncmp(req->method, "MERGE", 5) == 0)
408 req->method_number = M_MERGE;
409 else if (strncmp(req->method, "LABEL", 5) == 0)
410 req->method_number = M_LABEL;
411 else {
412 errno = EINVAL;
413 goto fail;
414 }
415 break;
416 case 6:
417 if (strncmp(req->method, "DELETE", 6) == 0)
418 req->method_number = M_DELETE;
419 else if (strncmp(req->method, "REPORT", 6) == 0)
420 req->method_number = M_REPORT;
421 else if (strncmp(req->method, "UPDATE", 6) == 0)
422 req->method_number = M_UPDATE;
423 else if (strncmp(req->method, "UNLOCK", 6) == 0)
424 req->method_number = M_UNLOCK;
425 else {
426 errno = EINVAL;
427 goto fail;
428 }
429 break;
430 case 7:
431 if (strncmp(req->method, "CHECKIN", 7) == 0)
432 req->method_number = M_CHECKIN;
433 else if (strncmp(req->method, "INVALID", 7) == 0)
434 req->method_number = M_INVALID;
435 else if (strncmp(req->method, "CONNECT", 7) == 0)
436 req->method_number = M_CONNECT;
437 else if (strncmp(req->method, "OPTIONS", 7) == 0)
438 req->method_number = M_OPTIONS;
439 else {
440 errno = EINVAL;
441 goto fail;
442 }
443 break;
444 case 8:
445 if (strncmp(req->method, "PROPFIND", 8) == 0)
446 req->method_number = M_PROPFIND;
447 else if (strncmp(req->method, "CHECKOUT", 8) == 0)
448 req->method_number = M_CHECKOUT;
449 else {
450 errno = EINVAL;
451 goto fail;
452 }
453 break;
454 case 9:
455 if (strncmp(req->method, "PROPPATCH", 9) == 0)
456 req->method_number = M_PROPPATCH;
457 else {
458 errno = EINVAL;
459 goto fail;
460 }
461 break;
462 case 10:
463 if (strncmp(req->method, "MKACTIVITY", 10) == 0)
464 req->method_number = M_MKACTIVITY;
465 else if (strncmp(req->method, "UNCHECKOUT", 10) == 0)
466 req->method_number = M_UNCHECKOUT;
467 else {
468 errno = EINVAL;
469 goto fail;
470 }
471 break;
472 case 11:
473 if (strncmp(req->method, "MKWORKSPACE", 11) == 0)
474 req->method_number = M_MKWORKSPACE;
475 else {
476 errno = EINVAL;
477 goto fail;
478 }
479 break;
480 case 15:
481 if (strncmp(req->method, "VERSION_CONTROL", 15) == 0)
482 req->method_number = M_VERSION_CONTROL;
483 else {
484 errno = EINVAL;
485 goto fail;
486 }
487 break;
488 case 16:
489 if (strncmp(req->method, "BASELINE_CONTROL", 16) == 0)
490 req->method_number = M_BASELINE_CONTROL;
491 else {
492 errno = EINVAL;
493 goto fail;
494 }
495 break;
496 default:
497 errno = EINVAL;
498 goto fail;
499 }
500
501 /* Copy the protocol. */
502 req->protocol = chunk_strdup(req, vers, vers_len);
503 if (!req->protocol) {
504 errno = ENOMEM;
505 goto fail;
506 }
507
508 /* Compute the protocol number. */
509 if (vers_len >= 8)
510 req->proto_num = 1000 + !!(vers[7] == '1');
511
512 /* The request time. */
513 gettimeofday(&now, NULL);
514 req->request_time = apr_time_make(now.tv_sec, now.tv_usec / 1000);
515
516 /* No status line. */
517 req->status_line = NULL;
518 req->status = 0;
519
520 /* Copy path. */
521 req->parsed_uri.path = chunk_strdup(req, path, path_len);
522 if (!req->parsed_uri.path) {
523 errno = ENOMEM;
524 goto fail;
525 }
526
527 /* Copy args (query string). */
528 req->args = chunk_strdup(req, qs, qs_len);
529 if (!req->args) {
530 errno = ENOMEM;
531 goto fail;
532 }
533
534 /* Set parsed_uri */
535
536 req->parsed_uri.scheme = "http";
537
538 if (req->hostname && req->parsed_uri.scheme && req->parsed_uri.path) {
539 i = snprintf(NULL, 0, "%s://%s%s",
540 req->parsed_uri.scheme, req->hostname, req->parsed_uri.path);
541 req->uri = apr_pcalloc(req->pool, i + 1);
542 if (!req->uri) {
543 errno = ENOMEM;
544 goto fail;
545 }
546 i = snprintf(req->uri, i + 1, "%s://%s%s",
547 req->parsed_uri.scheme, req->hostname, req->parsed_uri.path);
548 }
549
550 req->filename = req->parsed_uri.path;
551
552 /* Set unique id */
553
554 apr_table_setn(req->subprocess_env, "UNIQUE_ID", chunk_strdup(req, uniqueid, uniqueid_len));
555
556 /*
557 *
558 * Load body.
559 *
560 */
561
562 /* Create an empty bucket brigade */
563 brigade = apr_brigade_create(req->pool, req->connection->bucket_alloc);
564 if (!brigade) {
565 errno = ENOMEM;
566 goto fail;
567 }
568
569 /* Stores HTTP body avalaible data in a bucket */
570 data_bucket = apr_bucket_alloc(sizeof(*data_bucket), req->connection->bucket_alloc);
571 if (!data_bucket) {
572 errno = ENOMEM;
573 goto fail;
574 }
575 data_bucket->buffer = (char *)body;
576 data_bucket->length = body_len;
577
578 /* Create linked bucket */
579 link_bucket = apr_bucket_alloc(sizeof(*link_bucket), req->connection->bucket_alloc);
580 if (!link_bucket) {
581 errno = ENOMEM;
582 goto fail;
583 }
584 APR_BUCKET_INIT(link_bucket); /* link */
585 link_bucket->free = apr_bucket_free;
586 link_bucket->list = req->connection->bucket_alloc;
587 link_bucket = apr_bucket_shared_make(link_bucket, data_bucket, 0, body_len);
588 link_bucket->type = &apr_bucket_type_haproxy;
589
590 /* Insert the bucket at the end of the brigade. */
591 APR_BRIGADE_INSERT_TAIL(brigade, link_bucket);
592
593 /* Insert the last bucket. */
594 last_bucket = apr_bucket_eos_create(req->connection->bucket_alloc);
595 APR_BRIGADE_INSERT_TAIL(brigade, last_bucket);
596
597 /* Declares the bucket brigade in modsecurity */
598 modsecSetBodyBrigade(req, brigade);
599
600 /*
601 *
602 * Process analysis.
603 *
604 */
605
606 /* Process request headers analysis. */
607 status = modsecProcessRequestHeaders(req);
608 if (status != DECLINED && status != DONE)
609 return_code = status;
610
611 /* Process request body analysis. */
612 status = modsecProcessRequestBody(req);
613 if (status != DECLINED && status != DONE)
614 return_code = status;
615
616 /* End processing. */
617
618 fail = 0;
619 if (return_code == -1)
620 return_code = 0;
621
622fail:
623
624 modsecFinishRequest(req);
625 modsecFinishConnection(cr);
626
627 if (fail) {
628
629 /* errno == ERANGE / ENOMEM / EINVAL */
630 switch (errno) {
631 case ERANGE: LOG(worker, "Invalid range");
632 case ENOMEM: LOG(worker, "Out of memory error");
633 case EINVAL: LOG(worker, "Invalid value");
634 default: LOG(worker, "Unknown error");
635 }
636 }
637
638 return return_code;
639}