Imported Upstream version 2.5.11
[libapache-mod-security.git] / apache2 / apache2_config.c
1 /*
2  * ModSecurity for Apache 2.x, http://www.modsecurity.org/
3  * Copyright (c) 2004-2009 Breach Security, Inc. (http://www.breach.com/)
4  *
5  * This product is released under the terms of the General Public Licence,
6  * version 2 (GPLv2). Please refer to the file LICENSE (included with this
7  * distribution) which contains the complete text of the licence.
8  *
9  * There are special exceptions to the terms and conditions of the GPL
10  * as it is applied to this software. View the full text of the exception in
11  * file MODSECURITY_LICENSING_EXCEPTION in the directory of this software
12  * distribution.
13  *
14  * If any of the files related to licensing are missing or if you have any
15  * other questions related to licensing please contact Breach Security, Inc.
16  * directly using the email address support@breach.com.
17  *
18  */
19 #include <limits.h>
20
21 #include "modsecurity.h"
22 #include "msc_logging.h"
23 #include "msc_util.h"
24 #include "pdf_protect.h"
25 #include "http_log.h"
26
27 #if defined(WITH_LUA)
28 #include "msc_lua.h"
29 #endif
30
31
32 /* -- Directory context creation and initialisation -- */
33
34 /**
35  * Creates a fresh directory configuration.
36  */
37 void *create_directory_config(apr_pool_t *mp, char *path) {
38     directory_config *dcfg = (directory_config *)apr_pcalloc(mp, sizeof(directory_config));
39     if (dcfg == NULL) return NULL;
40
41     #ifdef DEBUG_CONF
42     ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Created directory config %pp path %s", dcfg, path);
43     #endif
44
45     dcfg->mp = mp;
46     dcfg->is_enabled = NOT_SET;
47
48     dcfg->reqbody_access = NOT_SET;
49     dcfg->reqbody_buffering = NOT_SET;
50     dcfg->reqbody_inmemory_limit = NOT_SET;
51     dcfg->reqbody_limit = NOT_SET;
52     dcfg->reqbody_no_files_limit = NOT_SET;
53     dcfg->resbody_access = NOT_SET;
54
55     dcfg->debuglog_name = NOT_SET_P;
56     dcfg->debuglog_level = NOT_SET;
57     dcfg->debuglog_fd = NOT_SET_P;
58
59     dcfg->of_limit = NOT_SET;
60     dcfg->of_limit_action = NOT_SET;
61     dcfg->of_mime_types = NOT_SET_P;
62     dcfg->of_mime_types_cleared = NOT_SET;
63
64     dcfg->cookie_format = NOT_SET;
65     dcfg->argument_separator = NOT_SET;
66
67     dcfg->rule_inheritance = NOT_SET;
68     dcfg->rule_exceptions = apr_array_make(mp, 16, sizeof(rule_exception *));
69
70     /* audit log variables */
71     dcfg->auditlog_flag = NOT_SET;
72     dcfg->auditlog_type = NOT_SET;
73     dcfg->auditlog_dirperms = NOT_SET;
74     dcfg->auditlog_fileperms = NOT_SET;
75     dcfg->auditlog_name = NOT_SET_P;
76     dcfg->auditlog2_name = NOT_SET_P;
77     dcfg->auditlog_fd = NOT_SET_P;
78     dcfg->auditlog2_fd = NOT_SET_P;
79     dcfg->auditlog_storage_dir = NOT_SET_P;
80     dcfg->auditlog_parts = NOT_SET_P;
81     dcfg->auditlog_relevant_regex = NOT_SET_P;
82
83     dcfg->ruleset = NULL;
84
85     /* Upload */
86     dcfg->tmp_dir = NOT_SET_P;
87     dcfg->upload_dir = NOT_SET_P;
88     dcfg->upload_keep_files = NOT_SET;
89     dcfg->upload_validates_files = NOT_SET;
90     dcfg->upload_filemode = NOT_SET;
91
92     /* These are only used during the configuration process. */
93     dcfg->tmp_chain_starter = NULL;
94     dcfg->tmp_default_actionset = NULL;
95     dcfg->tmp_rule_placeholders = NULL;
96
97     /* Misc */
98     dcfg->data_dir = NOT_SET_P;
99     dcfg->webappid = NOT_SET_P;
100
101     /* Content injection. */
102     dcfg->content_injection_enabled = NOT_SET;
103
104     /* PDF XSS protection. */
105     dcfg->pdfp_enabled = NOT_SET;
106     dcfg->pdfp_secret = NOT_SET_P;
107     dcfg->pdfp_timeout = NOT_SET;
108     dcfg->pdfp_token_name = NOT_SET_P;
109     dcfg->pdfp_only_get = NOT_SET;
110     dcfg->pdfp_method = NOT_SET;
111
112     /* Geo Lookups */
113     dcfg->geo = NOT_SET_P;
114
115     /* Cache */
116     dcfg->cache_trans = NOT_SET;
117     dcfg->cache_trans_incremental = NOT_SET;
118     dcfg->cache_trans_min = NOT_SET;
119     dcfg->cache_trans_max = NOT_SET;
120     dcfg->cache_trans_maxitems = NOT_SET;
121
122     dcfg->component_signatures = apr_array_make(mp, 16, sizeof(char *));
123
124     dcfg->request_encoding = NOT_SET_P;
125
126     return dcfg;
127 }
128
129 /**
130  * Copies rules between one phase of two configuration contexts,
131  * taking exceptions into account.
132  */
133 static void copy_rules_phase(apr_pool_t *mp, apr_array_header_t *parent_phase_arr,
134     apr_array_header_t *child_phase_arr, apr_array_header_t *exceptions_arr)
135 {
136     rule_exception **exceptions;
137     msre_rule **rules;
138     int i, j;
139     int mode = 0;
140
141     rules = (msre_rule **)parent_phase_arr->elts;
142     for(i = 0; i < parent_phase_arr->nelts; i++) {
143         msre_rule *rule = (msre_rule *)rules[i];
144         int copy = 1;
145
146         if (mode == 0) {
147             /* First rule in the chain. */
148             exceptions = (rule_exception **)exceptions_arr->elts;
149             for(j = 0; j < exceptions_arr->nelts; j++) {
150
151                 /* Process exceptions. */
152                 switch(exceptions[j]->type) {
153                     case RULE_EXCEPTION_REMOVE_ID :
154                         if ((rule->actionset != NULL)&&(rule->actionset->id != NULL)) {
155                             int ruleid = atoi(rule->actionset->id);
156                             if (rule_id_in_range(ruleid, exceptions[j]->param)) copy--;
157                         }
158                         break;
159                     case RULE_EXCEPTION_REMOVE_MSG :
160                         if ((rule->actionset != NULL)&&(rule->actionset->msg != NULL)) {
161                             char *my_error_msg = NULL;
162
163                             int rc = msc_regexec(exceptions[j]->param_data,
164                                 rule->actionset->msg, strlen(rule->actionset->msg),
165                                 &my_error_msg);
166                             if (rc >= 0) copy--;
167                         }
168                         break;
169                 }
170             }
171
172             if (copy > 0) {
173                 #ifdef DEBUG_CONF
174                 ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Copy rule %pp [id \"%s\"]", rule, rule->actionset->id);
175                 #endif
176
177                 /* Copy the rule. */
178                 *(msre_rule **)apr_array_push(child_phase_arr) = rule;
179                 if (rule->actionset->is_chained) mode = 2;
180             } else {
181                 if (rule->actionset->is_chained) mode = 1;
182             }
183         } else {
184             if (mode == 2) {
185                 #ifdef DEBUG_CONF
186                 ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Copy chain %pp for rule %pp [id \"%s\"]", rule, rule->chain_starter, rule->chain_starter->actionset->id);
187                 #endif
188
189                 /* Copy the rule (it belongs to the chain we want to include. */
190                 *(msre_rule **)apr_array_push(child_phase_arr) = rule;
191             }
192
193             if ((rule->actionset == NULL)||(rule->actionset->is_chained == 0)) mode = 0;
194         }
195     }
196 }
197
198 /**
199  * Copies rules between two configuration contexts,
200  * taking exceptions into account.
201  */
202 static int copy_rules(apr_pool_t *mp, msre_ruleset *parent_ruleset, msre_ruleset *child_ruleset,
203     apr_array_header_t *exceptions_arr)
204 {
205     copy_rules_phase(mp, parent_ruleset->phase_request_headers,
206         child_ruleset->phase_request_headers, exceptions_arr);
207     copy_rules_phase(mp, parent_ruleset->phase_request_body,
208         child_ruleset->phase_request_body, exceptions_arr);
209     copy_rules_phase(mp, parent_ruleset->phase_response_headers,
210         child_ruleset->phase_response_headers, exceptions_arr);
211     copy_rules_phase(mp, parent_ruleset->phase_response_body,
212         child_ruleset->phase_response_body, exceptions_arr);
213     copy_rules_phase(mp, parent_ruleset->phase_logging,
214         child_ruleset->phase_logging, exceptions_arr);
215
216     return 1;
217 }
218
219 /**
220  * Merges two directory configurations.
221  */
222 void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) {
223     directory_config *parent = (directory_config *)_parent;
224     directory_config *child = (directory_config *)_child;
225     directory_config *merged = create_directory_config(mp, NULL);
226
227     #ifdef DEBUG_CONF
228     ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Merge parent %pp child %pp RESULT %pp", _parent, _child, merged);
229     #endif
230
231     if (merged == NULL) return NULL;
232
233     /* Use values from the child configuration where possible,
234      * otherwise use the parent's.
235      */
236
237     merged->is_enabled = (child->is_enabled == NOT_SET
238         ? parent->is_enabled : child->is_enabled);
239
240     /* IO parameters */
241     merged->reqbody_access = (child->reqbody_access == NOT_SET
242         ? parent->reqbody_access : child->reqbody_access);
243     merged->reqbody_buffering = (child->reqbody_buffering == NOT_SET
244         ? parent->reqbody_buffering : child->reqbody_buffering);
245     merged->reqbody_inmemory_limit = (child->reqbody_inmemory_limit == NOT_SET
246         ? parent->reqbody_inmemory_limit : child->reqbody_inmemory_limit);
247     merged->reqbody_limit = (child->reqbody_limit == NOT_SET
248         ? parent->reqbody_limit : child->reqbody_limit);
249     merged->reqbody_no_files_limit = (child->reqbody_no_files_limit == NOT_SET
250         ? parent->reqbody_no_files_limit : child->reqbody_no_files_limit);
251     merged->resbody_access = (child->resbody_access == NOT_SET
252         ? parent->resbody_access : child->resbody_access);
253
254     merged->of_limit = (child->of_limit == NOT_SET
255         ? parent->of_limit : child->of_limit);
256     merged->of_limit_action = (child->of_limit_action == NOT_SET
257         ? parent->of_limit_action : child->of_limit_action);
258
259     if (child->of_mime_types != NOT_SET_P) {
260         /* Child added to the table */
261
262         if (child->of_mime_types_cleared == 1) {
263             /* The list of MIME types was cleared in the child,
264              * which means the parent's MIME types went away and
265              * we should not take them into consideration here.
266              */
267             merged->of_mime_types = child->of_mime_types;
268             merged->of_mime_types_cleared = 1;
269         } else {
270             /* Add MIME types defined in the child to those
271              * defined in the parent context.
272              */
273             if (parent->of_mime_types == NOT_SET_P) {
274                 merged->of_mime_types = child->of_mime_types;
275                 merged->of_mime_types_cleared = NOT_SET;
276             } else {
277                 merged->of_mime_types = apr_table_overlay(mp, parent->of_mime_types,
278                     child->of_mime_types);
279                 if (merged->of_mime_types == NULL) return NULL;
280             }
281         }
282     } else {
283         /* Child did not add to the table */
284
285         if (child->of_mime_types_cleared == 1) {
286             merged->of_mime_types_cleared = 1;
287         } else {
288             merged->of_mime_types = parent->of_mime_types;
289             merged->of_mime_types_cleared = parent->of_mime_types_cleared;
290         }
291     }
292
293     /* debug log */
294     if (child->debuglog_fd == NOT_SET_P) {
295         merged->debuglog_name = parent->debuglog_name;
296         merged->debuglog_fd = parent->debuglog_fd;
297     } else {
298         merged->debuglog_name = child->debuglog_name;
299         merged->debuglog_fd = child->debuglog_fd;
300     }
301
302     merged->debuglog_level = (child->debuglog_level == NOT_SET
303         ? parent->debuglog_level : child->debuglog_level);
304
305     merged->cookie_format = (child->cookie_format == NOT_SET
306         ? parent->cookie_format : child->cookie_format);
307     merged->argument_separator = (child->argument_separator == NOT_SET
308         ? parent->argument_separator : child->argument_separator);
309
310
311     /* rule inheritance */
312     if ((child->rule_inheritance == NOT_SET)||(child->rule_inheritance == 1)) {
313         merged->rule_inheritance = parent->rule_inheritance;
314         if ((child->ruleset == NULL)&&(parent->ruleset == NULL)) {
315             #ifdef DEBUG_CONF
316             ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "No rules in this context.");
317             #endif
318
319             /* Do nothing, there are no rules in either context. */
320         } else
321         if (child->ruleset == NULL) {
322             #ifdef DEBUG_CONF
323             ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Using parent rules in this context.");
324             #endif
325
326             /* Copy the rules from the parent context. */
327             merged->ruleset = msre_ruleset_create(parent->ruleset->engine, mp);
328             copy_rules(mp, parent->ruleset, merged->ruleset, child->rule_exceptions);
329         } else
330         if (parent->ruleset == NULL) {
331             #ifdef DEBUG_CONF
332             ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Using child rules in this context.");
333             #endif
334
335             /* Copy child rules. */
336             merged->ruleset = msre_ruleset_create(child->ruleset->engine, mp);
337             merged->ruleset->phase_request_headers = apr_array_copy(mp,
338                 child->ruleset->phase_request_headers);
339             merged->ruleset->phase_request_body = apr_array_copy(mp,
340                 child->ruleset->phase_request_body);
341             merged->ruleset->phase_response_headers = apr_array_copy(mp,
342                 child->ruleset->phase_response_headers);
343             merged->ruleset->phase_response_body = apr_array_copy(mp,
344                 child->ruleset->phase_response_body);
345             merged->ruleset->phase_logging = apr_array_copy(mp,
346                 child->ruleset->phase_logging);
347         } else {
348             #ifdef DEBUG_CONF
349             ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Using parent then child rules in this context.");
350             #endif
351
352             /* Copy parent rules, then add child rules to it. */
353             merged->ruleset = msre_ruleset_create(parent->ruleset->engine, mp);
354             copy_rules(mp, parent->ruleset, merged->ruleset, child->rule_exceptions);
355
356             apr_array_cat(merged->ruleset->phase_request_headers,
357                 child->ruleset->phase_request_headers);
358             apr_array_cat(merged->ruleset->phase_request_body,
359                 child->ruleset->phase_request_body);
360             apr_array_cat(merged->ruleset->phase_response_headers,
361                 child->ruleset->phase_response_headers);
362             apr_array_cat(merged->ruleset->phase_response_body,
363                 child->ruleset->phase_response_body);
364             apr_array_cat(merged->ruleset->phase_logging,
365                 child->ruleset->phase_logging);
366         }
367     } else {
368         merged->rule_inheritance = 0;
369         if (child->ruleset != NULL) {
370             /* Copy child rules. */
371             merged->ruleset = msre_ruleset_create(child->ruleset->engine, mp);
372             merged->ruleset->phase_request_headers = apr_array_copy(mp,
373                 child->ruleset->phase_request_headers);
374             merged->ruleset->phase_request_body = apr_array_copy(mp,
375                 child->ruleset->phase_request_body);
376             merged->ruleset->phase_response_headers = apr_array_copy(mp,
377                 child->ruleset->phase_response_headers);
378             merged->ruleset->phase_response_body = apr_array_copy(mp,
379                 child->ruleset->phase_response_body);
380             merged->ruleset->phase_logging = apr_array_copy(mp,
381                 child->ruleset->phase_logging);
382         }
383     }
384
385     /* Merge rule exceptions. */
386     merged->rule_exceptions = apr_array_append(mp, parent->rule_exceptions,
387         child->rule_exceptions);
388
389     /* audit log variables */
390     merged->auditlog_flag = (child->auditlog_flag == NOT_SET
391         ? parent->auditlog_flag : child->auditlog_flag);
392     merged->auditlog_type = (child->auditlog_type == NOT_SET
393         ? parent->auditlog_type : child->auditlog_type);
394     merged->auditlog_dirperms = (child->auditlog_dirperms == NOT_SET
395         ? parent->auditlog_dirperms : child->auditlog_dirperms);
396     merged->auditlog_fileperms = (child->auditlog_fileperms == NOT_SET
397         ? parent->auditlog_fileperms : child->auditlog_fileperms);
398     if (child->auditlog_fd != NOT_SET_P) {
399         merged->auditlog_fd = child->auditlog_fd;
400         merged->auditlog_name = child->auditlog_name;
401     } else {
402         merged->auditlog_fd = parent->auditlog_fd;
403         merged->auditlog_name = parent->auditlog_name;
404     }
405     if (child->auditlog2_fd != NOT_SET_P) {
406         merged->auditlog2_fd = child->auditlog2_fd;
407         merged->auditlog2_name = child->auditlog2_name;
408     } else {
409         merged->auditlog2_fd = parent->auditlog2_fd;
410         merged->auditlog2_name = parent->auditlog2_name;
411     }
412     merged->auditlog_storage_dir = (child->auditlog_storage_dir == NOT_SET_P
413         ? parent->auditlog_storage_dir : child->auditlog_storage_dir);
414     merged->auditlog_parts = (child->auditlog_parts == NOT_SET_P
415         ? parent->auditlog_parts : child->auditlog_parts);
416     merged->auditlog_relevant_regex = (child->auditlog_relevant_regex == NOT_SET_P
417         ? parent->auditlog_relevant_regex : child->auditlog_relevant_regex);
418
419     /* Upload */
420     merged->tmp_dir = (child->tmp_dir == NOT_SET_P
421         ? parent->tmp_dir : child->tmp_dir);
422     merged->upload_dir = (child->upload_dir == NOT_SET_P
423         ? parent->upload_dir : child->upload_dir);
424     merged->upload_keep_files = (child->upload_keep_files == NOT_SET
425         ? parent->upload_keep_files : child->upload_keep_files);
426     merged->upload_validates_files = (child->upload_validates_files == NOT_SET
427         ? parent->upload_validates_files : child->upload_validates_files);
428     merged->upload_filemode = (child->upload_filemode == NOT_SET
429         ? parent->upload_filemode : child->upload_filemode);
430
431     /* Misc */
432     merged->data_dir = (child->data_dir == NOT_SET_P
433         ? parent->data_dir : child->data_dir);
434     merged->webappid = (child->webappid == NOT_SET_P
435         ? parent->webappid : child->webappid);
436
437     /* Content injection. */
438     merged->content_injection_enabled = (child->content_injection_enabled == NOT_SET
439         ? parent->content_injection_enabled : child->content_injection_enabled);
440
441     /* PDF XSS protection. */
442     merged->pdfp_enabled = (child->pdfp_enabled == NOT_SET
443         ? parent->pdfp_enabled : child->pdfp_enabled);
444     merged->pdfp_secret = (child->pdfp_secret == NOT_SET_P
445         ? parent->pdfp_secret : child->pdfp_secret);
446     merged->pdfp_timeout = (child->pdfp_timeout == NOT_SET
447         ? parent->pdfp_timeout : child->pdfp_timeout);
448     merged->pdfp_token_name = (child->pdfp_token_name == NOT_SET_P
449         ? parent->pdfp_token_name : child->pdfp_token_name);
450     merged->pdfp_only_get = (child->pdfp_only_get == NOT_SET
451         ? parent->pdfp_only_get : child->pdfp_only_get);
452     merged->pdfp_method = (child->pdfp_method == NOT_SET
453         ? parent->pdfp_method : child->pdfp_method);
454
455     /* Geo Lookup */
456     merged->geo = (child->geo == NOT_SET_P
457         ? parent->geo : child->geo);
458
459     /* Cache */
460     merged->cache_trans = (child->cache_trans == NOT_SET
461         ? parent->cache_trans : child->cache_trans);
462     merged->cache_trans_incremental = (child->cache_trans_incremental == NOT_SET
463         ? parent->cache_trans_incremental : child->cache_trans_incremental);
464     merged->cache_trans_min = (child->cache_trans_min == (apr_size_t)NOT_SET
465         ? parent->cache_trans_min : child->cache_trans_min);
466     merged->cache_trans_max = (child->cache_trans_max == (apr_size_t)NOT_SET
467         ? parent->cache_trans_max : child->cache_trans_max);
468     merged->cache_trans_maxitems = (child->cache_trans_maxitems == (apr_size_t)NOT_SET
469         ? parent->cache_trans_maxitems : child->cache_trans_maxitems);
470
471     /* Merge component signatures. */
472     merged->component_signatures = apr_array_append(mp, parent->component_signatures,
473         child->component_signatures);
474
475     merged->request_encoding = (child->request_encoding == NOT_SET_P
476         ? parent->request_encoding : child->request_encoding);
477
478     return merged;
479 }
480
481 /**
482  * Initialise directory configuration. This function is *not* meant
483  * to be called for directory configuration instances created during
484  * the configuration phase. It can only be called on copies of those
485  * (created fresh for every transaction).
486  */
487 void init_directory_config(directory_config *dcfg) {
488     if (dcfg == NULL) return;
489
490     if (dcfg->is_enabled == NOT_SET) dcfg->is_enabled = 0;
491
492     if (dcfg->reqbody_access == NOT_SET) dcfg->reqbody_access = 0;
493     if (dcfg->reqbody_buffering == NOT_SET) dcfg->reqbody_buffering = REQUEST_BODY_FORCEBUF_OFF;
494     if (dcfg->reqbody_inmemory_limit == NOT_SET)
495         dcfg->reqbody_inmemory_limit = REQUEST_BODY_DEFAULT_INMEMORY_LIMIT;
496     if (dcfg->reqbody_limit == NOT_SET) dcfg->reqbody_limit = REQUEST_BODY_DEFAULT_LIMIT;
497     if (dcfg->reqbody_no_files_limit == NOT_SET) dcfg->reqbody_no_files_limit = REQUEST_BODY_NO_FILES_DEFAULT_LIMIT;
498     if (dcfg->resbody_access == NOT_SET) dcfg->resbody_access = 0;
499     if (dcfg->of_limit == NOT_SET) dcfg->of_limit = RESPONSE_BODY_DEFAULT_LIMIT;
500     if (dcfg->of_limit_action == NOT_SET) dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_REJECT;
501
502     if (dcfg->of_mime_types == NOT_SET_P) {
503         dcfg->of_mime_types = apr_table_make(dcfg->mp, 3);
504         if (dcfg->of_mime_types_cleared != 1) {
505             apr_table_setn(dcfg->of_mime_types, "text/plain", "1");
506             apr_table_setn(dcfg->of_mime_types, "text/html", "1");
507         }
508     }
509
510     if (dcfg->debuglog_fd == NOT_SET_P) dcfg->debuglog_fd = NULL;
511     if (dcfg->debuglog_name == NOT_SET_P) dcfg->debuglog_name = NULL;
512     if (dcfg->debuglog_level == NOT_SET) dcfg->debuglog_level = 0;
513
514     if (dcfg->cookie_format == NOT_SET) dcfg->cookie_format = 0;
515     if (dcfg->argument_separator == NOT_SET) dcfg->argument_separator = '&';
516
517     if (dcfg->rule_inheritance == NOT_SET) dcfg->rule_inheritance = 1;
518
519     /* audit log variables */
520     if (dcfg->auditlog_flag == NOT_SET) dcfg->auditlog_flag = 0;
521     if (dcfg->auditlog_type == NOT_SET) dcfg->auditlog_type = AUDITLOG_SERIAL;
522     if (dcfg->auditlog_dirperms == NOT_SET) dcfg->auditlog_dirperms = CREATEMODE_DIR;
523     if (dcfg->auditlog_fileperms == NOT_SET) dcfg->auditlog_fileperms = CREATEMODE;
524     if (dcfg->auditlog_fd == NOT_SET_P) dcfg->auditlog_fd = NULL;
525     if (dcfg->auditlog2_fd == NOT_SET_P) dcfg->auditlog2_fd = NULL;
526     if (dcfg->auditlog_name == NOT_SET_P) dcfg->auditlog_name = NULL;
527     if (dcfg->auditlog2_name == NOT_SET_P) dcfg->auditlog2_name = NULL;
528     if (dcfg->auditlog_storage_dir == NOT_SET_P) dcfg->auditlog_storage_dir = NULL;
529     if (dcfg->auditlog_parts == NOT_SET_P) dcfg->auditlog_parts = "ABCFHZ";
530     if (dcfg->auditlog_relevant_regex == NOT_SET_P) dcfg->auditlog_relevant_regex = NULL;
531
532     /* Upload */
533     if (dcfg->tmp_dir == NOT_SET_P) dcfg->tmp_dir = guess_tmp_dir(dcfg->mp);
534     if (dcfg->upload_dir == NOT_SET_P) dcfg->upload_dir = NULL;
535     if (dcfg->upload_keep_files == NOT_SET) dcfg->upload_keep_files = KEEP_FILES_OFF;
536     if (dcfg->upload_validates_files == NOT_SET) dcfg->upload_validates_files = 0;
537     if (dcfg->upload_filemode == NOT_SET) dcfg->upload_filemode = mode2fileperms(0600);
538
539     /* Misc */
540     if (dcfg->data_dir == NOT_SET_P) dcfg->data_dir = NULL;
541     if (dcfg->webappid == NOT_SET_P) dcfg->webappid = "default";
542
543     /* Content injection. */
544     if (dcfg->content_injection_enabled == NOT_SET) dcfg->content_injection_enabled = 0;
545
546     /* PDF XSS protection. */
547     if (dcfg->pdfp_enabled == NOT_SET) dcfg->pdfp_enabled = 0;
548     if (dcfg->pdfp_secret == NOT_SET_P) dcfg->pdfp_secret = NULL;
549     if (dcfg->pdfp_timeout == NOT_SET) dcfg->pdfp_timeout = 10;
550     if (dcfg->pdfp_token_name == NOT_SET_P) dcfg->pdfp_token_name = "PDFPTOKEN";
551     if (dcfg->pdfp_only_get == NOT_SET) dcfg->pdfp_only_get = 1;
552     if (dcfg->pdfp_method == NOT_SET) dcfg->pdfp_method = PDF_PROTECT_METHOD_TOKEN_REDIRECTION;
553
554     /* Geo Lookup */
555     if (dcfg->geo == NOT_SET_P) dcfg->geo = NULL;
556
557     /* Cache */
558     if (dcfg->cache_trans == NOT_SET) dcfg->cache_trans = MODSEC_CACHE_DISABLED;
559     if (dcfg->cache_trans_incremental == NOT_SET) dcfg->cache_trans_incremental = 0;
560     if (dcfg->cache_trans_min == (apr_size_t)NOT_SET) dcfg->cache_trans_min = 32;
561     if (dcfg->cache_trans_max == (apr_size_t)NOT_SET) dcfg->cache_trans_max = 1024;
562     if (dcfg->cache_trans_maxitems == (apr_size_t)NOT_SET) dcfg->cache_trans_maxitems = 512;
563
564     if (dcfg->request_encoding == NOT_SET_P) dcfg->request_encoding = NULL;
565 }
566
567 /**
568  *
569  */
570 static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, int type,
571     const char *p1, const char *p2, const char *p3)
572 {
573     char *my_error_msg = NULL;
574     msre_rule *rule = NULL;
575     extern msc_engine *modsecurity;
576
577     #ifdef DEBUG_CONF
578     ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
579         "Rule: type=%d p1='%s' p2='%s' p3='%s'", type, p1, p2, p3);
580     #endif
581
582     /* Create a ruleset if one does not exist. */
583     if ((dcfg->ruleset == NULL)||(dcfg->ruleset == NOT_SET_P)) {
584         dcfg->ruleset = msre_ruleset_create(modsecurity->msre, cmd->pool);
585         if (dcfg->ruleset == NULL) return FATAL_ERROR;
586     }
587
588     /* Create the rule now. */
589     switch(type) {
590         #if defined(WITH_LUA)
591         case RULE_TYPE_LUA :
592             rule = msre_rule_lua_create(dcfg->ruleset, cmd->directive->filename,
593                 cmd->directive->line_num, p1, p2, &my_error_msg);
594             break;
595         #endif
596         default :
597             rule = msre_rule_create(dcfg->ruleset, type, cmd->directive->filename,
598                 cmd->directive->line_num, p1, p2, p3, &my_error_msg);
599             break;
600     }
601
602     if (rule == NULL) {
603         return my_error_msg;
604     }
605
606     /* Create default actionset if one does not already exist. */
607     if (dcfg->tmp_default_actionset == NULL) {
608         dcfg->tmp_default_actionset = msre_actionset_create_default(modsecurity->msre);
609         if (dcfg->tmp_default_actionset == NULL) return FATAL_ERROR;
610     }
611
612     /* Check some cases prior to merging so we know where it came from */
613
614     /* Check syntax for chained rules */
615     if ((rule->actionset != NULL) && (dcfg->tmp_chain_starter != NULL)) {
616         /* Must NOT specify a disruptive action. */
617         if (rule->actionset->intercept_action != NOT_SET) {
618             return apr_psprintf(cmd->pool, "ModSecurity: Disruptive actions can only "
619                 "be specified by chain starter rules.");
620         }
621
622         /* Must NOT specify a phase. */
623         if (rule->actionset->phase != NOT_SET) {
624             return apr_psprintf(cmd->pool, "ModSecurity: Execution phases can only be "
625                 "specified by chain starter rules.");
626         }
627
628         /* Must NOT use metadata actions. */
629         /* ENH: loop through to check for tags */
630         if ((rule->actionset->id != NOT_SET_P)
631             ||(rule->actionset->rev != NOT_SET_P)
632             ||(rule->actionset->msg != NOT_SET_P)
633             ||(rule->actionset->severity != NOT_SET)
634             ||(rule->actionset->logdata != NOT_SET_P))
635         {
636             return apr_psprintf(cmd->pool, "ModSecurity: Metadata actions (id, rev, msg, tag, severity, logdata) "
637                 " can only be specified by chain starter rules.");
638         }
639
640         /* Must NOT use skip. */
641         if (rule->actionset->skip_count != NOT_SET) {
642             return apr_psprintf(cmd->pool, "ModSecurity: The skip action can only be used "
643                 " by chain starter rules. ");
644         }
645     }
646
647     /* Merge actions with the parent.
648      *
649      * ENH Probably do not want this done fully for chained rules.
650      */
651     rule->actionset = msre_actionset_merge(modsecurity->msre, dcfg->tmp_default_actionset,
652         rule->actionset, 1);
653
654     /* Keep track of the parent action for "block" */
655     rule->actionset->parent_intercept_action_rec = dcfg->tmp_default_actionset->intercept_action_rec;
656     rule->actionset->parent_intercept_action = dcfg->tmp_default_actionset->intercept_action;
657
658     /* Must NOT specify a disruptive action in logging phase. */
659     if ((rule->actionset != NULL)
660         && (rule->actionset->phase == PHASE_LOGGING)
661         && (rule->actionset->intercept_action != ACTION_ALLOW)
662         && (rule->actionset->intercept_action != ACTION_ALLOW_REQUEST)
663         && (rule->actionset->intercept_action != ACTION_NONE)
664     ) {
665         return apr_psprintf(cmd->pool, "ModSecurity: Disruptive actions "
666             "cannot be specified in the logging phase.");
667     }
668
669     if (dcfg->tmp_chain_starter != NULL) {
670         rule->chain_starter = dcfg->tmp_chain_starter;
671         rule->actionset->phase = rule->chain_starter->actionset->phase;
672     }
673
674     if (rule->actionset->is_chained != 1) {
675         /* If this rule is part of the chain but does
676          * not want more rules to follow in the chain
677          * then cut it (the chain).
678          */
679         dcfg->tmp_chain_starter = NULL;
680     } else {
681         /* On the other hand, if this rule wants other
682          * rules to follow it, then start a new chain
683          * if there isn't one already.
684          */
685         if (dcfg->tmp_chain_starter == NULL) {
686             dcfg->tmp_chain_starter = rule;
687         }
688     }
689
690     /* Optimisation */
691     if ((rule->op_name != NULL)&&(strcasecmp(rule->op_name, "inspectFile") == 0)) {
692         dcfg->upload_validates_files = 1;
693     }
694
695     /* Create skip table if one does not already exist. */
696     if (dcfg->tmp_rule_placeholders == NULL) {
697         dcfg->tmp_rule_placeholders = apr_table_make(cmd->pool, 10);
698         if (dcfg->tmp_rule_placeholders == NULL) return FATAL_ERROR;
699     }
700
701     /* Keep track of any rule IDs we need to skip after */
702     if (rule->actionset->skip_after != NOT_SET_P) {
703         char *tmp_id = apr_pstrdup(cmd->pool, rule->actionset->skip_after);
704         apr_table_setn(dcfg->tmp_rule_placeholders, tmp_id, tmp_id);
705
706         #ifdef DEBUG_CONF
707         ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
708             "Watching for skipafter target rule id=\"%s\".", tmp_id);
709         #endif
710
711     }
712
713     #ifdef DEBUG_CONF
714     ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
715         "Adding rule %pp phase=%d id=\"%s\".", rule, rule->actionset->phase, (rule->actionset->id == NOT_SET_P
716         ? "(none)" : rule->actionset->id));
717     #endif
718
719     /* Add rule to the recipe. */
720     if (msre_ruleset_rule_add(dcfg->ruleset, rule, rule->actionset->phase) < 0) {
721         return "Internal Error: Failed to add rule to the ruleset.";
722     }
723
724     /* Add an additional placeholder if this rule ID is on the list */
725     if ((rule->actionset->id != NULL) && apr_table_get(dcfg->tmp_rule_placeholders, rule->actionset->id)) {
726         msre_rule *phrule = apr_palloc(rule->ruleset->mp, sizeof(msre_rule));
727         if (phrule == NULL) {
728             return FATAL_ERROR;
729         }
730
731         #ifdef DEBUG_CONF
732         ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
733             "Adding placeholder %pp for rule %pp id=\"%s\".", phrule, rule, rule->actionset->id);
734         #endif
735
736         /* shallow copy of original rule with placeholder marked as target */
737         memcpy(phrule, rule, sizeof(msre_rule));
738         phrule->placeholder = RULE_PH_SKIPAFTER;
739
740         /* Add placeholder. */
741         if (msre_ruleset_rule_add(dcfg->ruleset, phrule, phrule->actionset->phase) < 0) {
742             return "Internal Error: Failed to add placeholder to the ruleset.";
743         }
744
745         /* No longer need to search for the ID */
746         apr_table_unset(dcfg->tmp_rule_placeholders, rule->actionset->id);
747     }
748
749     /* Update the unparsed rule */
750     rule->unparsed = msre_rule_generate_unparsed(dcfg->ruleset->mp, rule, NULL, NULL, NULL);
751
752     return NULL;
753 }
754
755 /**
756  *
757  */
758 static const char *add_marker(cmd_parms *cmd, directory_config *dcfg, const char *p1,
759     const char *p2, const char *p3)
760 {
761     char *my_error_msg = NULL;
762     msre_rule *rule = NULL;
763     extern msc_engine *modsecurity;
764     int p;
765
766     #ifdef DEBUG_CONF
767     ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
768         "Rule: type=%d p1='%s' p2='%s' p3='%s'", RULE_TYPE_MARKER, p1, p2, p3);
769     #endif
770
771     /* Create a ruleset if one does not exist. */
772     if ((dcfg->ruleset == NULL)||(dcfg->ruleset == NOT_SET_P)) {
773         dcfg->ruleset = msre_ruleset_create(modsecurity->msre, cmd->pool);
774         if (dcfg->ruleset == NULL) return FATAL_ERROR;
775     }
776
777     /* Create the rule now. */
778     rule = msre_rule_create(dcfg->ruleset, RULE_TYPE_MARKER, cmd->directive->filename, cmd->directive->line_num, p1, p2, p3, &my_error_msg);
779     if (rule == NULL) {
780         return my_error_msg;
781     }
782
783     /* This is a marker */
784     rule->placeholder = RULE_PH_MARKER;
785
786     /* Add placeholder to each phase */
787     for (p = PHASE_FIRST; p <= PHASE_LAST; p++) {
788         #ifdef DEBUG_CONF
789         ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
790             "Adding marker %pp phase=%d id=\"%s\".", rule, p, (rule->actionset->id == NOT_SET_P
791             ? "(none)" : rule->actionset->id));
792         #endif
793
794         if (msre_ruleset_rule_add(dcfg->ruleset, rule, p) < 0) {
795             return "Internal Error: Failed to add marker to the ruleset.";
796         }
797     }
798
799     /* No longer need to search for the ID */
800     if (dcfg->tmp_rule_placeholders != NULL) {
801         apr_table_unset(dcfg->tmp_rule_placeholders, rule->actionset->id);
802     }
803
804     return NULL;
805 }
806
807 /**
808  *
809  */
810 static const char *update_rule_action(cmd_parms *cmd, directory_config *dcfg,
811     const char *p1, const char *p2)
812 {
813     char *my_error_msg = NULL;
814     msre_rule *rule = NULL;
815     msre_actionset *new_actionset = NULL;
816     msre_ruleset *ruleset = dcfg->ruleset;
817     extern msc_engine *modsecurity;
818
819     /* Get the ruleset if one exists */
820     if ((ruleset == NULL)||(ruleset == NOT_SET_P)) {
821         return NULL;
822     }
823
824     #ifdef DEBUG_CONF
825     ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
826         "Update rule id=\"%s\" with action \"%s\".", p1, p2);
827     #endif
828
829     /* Fetch the rule */
830     rule = msre_ruleset_fetch_rule(ruleset, p1);
831     if (rule == NULL) {
832         #ifdef DEBUG_CONF
833         ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
834             "Update rule id=\"%s\" with action \"%s\" failed: Rule not found.", p1, p2);
835         #endif
836         return NULL;
837     }
838
839     /* Check the rule actionset */
840     /* ENH: Can this happen? */
841     if (rule->actionset == NULL) {
842         return apr_psprintf(cmd->pool, "ModSecurity: Attempt to update action for rule \"%s\" failed: Rule does not have an actionset.", p1);
843     }
844
845     /* Create a new actionset */
846     new_actionset = msre_actionset_create(modsecurity->msre, p2, &my_error_msg);
847     if (new_actionset == NULL) return FATAL_ERROR;
848     if (my_error_msg != NULL) return my_error_msg;
849
850     /* Must NOT change an id */
851     if ((new_actionset->id != NOT_SET_P) && (rule->actionset->id != NULL) && (strcmp(rule->actionset->id, new_actionset->id) != 0)) {
852         return apr_psprintf(cmd->pool, "ModSecurity: Rule IDs cannot be updated via SecRuleUpdateActionById.");
853     }
854
855     /* Must NOT alter the phase */
856     if ((new_actionset->phase != NOT_SET) && (rule->actionset->phase != new_actionset->phase)) {
857         return apr_psprintf(cmd->pool, "ModSecurity: Rule phases cannot be updated via SecRuleUpdateActionById.");
858     }
859
860     #ifdef DEBUG_CONF
861     {
862         char *actions = msre_actionset_generate_action_string(ruleset->mp, rule->actionset);
863         ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
864             "Update rule %pp id=\"%s\" old action: \"%s\"",
865             rule,
866             (rule->actionset->id == NOT_SET_P ? "(none)" : rule->actionset->id),
867             actions);
868     }
869     #endif
870
871     /* Merge new actions with the rule */
872     /* ENH: Will this leak the old actionset? */
873     rule->actionset = msre_actionset_merge(modsecurity->msre, rule->actionset,
874         new_actionset, 1);
875     msre_actionset_set_defaults(rule->actionset);
876
877     /* Update the unparsed rule */
878     rule->unparsed = msre_rule_generate_unparsed(ruleset->mp, rule, NULL, NULL, NULL);
879
880     #ifdef DEBUG_CONF
881     {
882         char *actions = msre_actionset_generate_action_string(ruleset->mp, rule->actionset);
883         ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool,
884             "Update rule %pp id=\"%s\" new action: \"%s\"",
885             rule,
886             (rule->actionset->id == NOT_SET_P ? "(none)" : rule->actionset->id),
887             actions);
888     }
889     #endif
890
891     return NULL;
892 }
893
894 /* -- Configuration directives -- */
895
896 static const char *cmd_action(cmd_parms *cmd, void *_dcfg, const char *p1) {
897     return add_rule(cmd, (directory_config *)_dcfg, RULE_TYPE_ACTION, SECACTION_TARGETS, SECACTION_ARGS, p1);
898 }
899
900 static const char *cmd_marker(cmd_parms *cmd, void *_dcfg, const char *p1) {
901     directory_config *dcfg = (directory_config *)_dcfg;
902     const char *action = apr_pstrcat(dcfg->mp, SECMARKER_BASE_ACTIONS, p1, NULL);
903     return add_marker(cmd, (directory_config *)_dcfg, SECMARKER_TARGETS, SECMARKER_ARGS, action);
904 }
905
906 static const char *cmd_argument_separator(cmd_parms *cmd, void *_dcfg, const char *p1) {
907     directory_config *dcfg = (directory_config *)_dcfg;
908
909     if (strlen(p1) != 1) {
910         return apr_psprintf(cmd->pool, "ModSecurity: Invalid argument separator: %s", p1);
911     }
912
913     dcfg->argument_separator = p1[0];
914
915     return NULL;
916 }
917
918 static const char *cmd_audit_engine(cmd_parms *cmd, void *_dcfg, const char *p1) {
919     directory_config *dcfg = _dcfg;
920
921     if (strcasecmp(p1, "On") == 0) dcfg->auditlog_flag = AUDITLOG_ON;
922     else
923     if (strcasecmp(p1, "Off") == 0) dcfg->auditlog_flag = AUDITLOG_OFF;
924     else
925     if (strcasecmp(p1, "RelevantOnly") == 0) dcfg->auditlog_flag = AUDITLOG_RELEVANT;
926     else
927     return (const char *)apr_psprintf(cmd->pool,
928         "ModSecurity: Unrecognised parameter value for SecAuditEngine: %s", p1);
929
930     return NULL;
931 }
932
933 static const char *cmd_audit_log(cmd_parms *cmd, void *_dcfg, const char *p1) {
934     directory_config *dcfg = _dcfg;
935
936     dcfg->auditlog_name = (char *)p1;
937
938     if (dcfg->auditlog_name[0] == '|') {
939         const char *pipe_name = ap_server_root_relative(cmd->pool, dcfg->auditlog_name + 1);
940         piped_log *pipe_log;
941
942         pipe_log = ap_open_piped_log(cmd->pool, pipe_name);
943         if (pipe_log == NULL) {
944             return apr_psprintf(cmd->pool, "ModSecurity: Failed to open the audit log pipe: %s",
945                 pipe_name);
946         }
947         dcfg->auditlog_fd = ap_piped_log_write_fd(pipe_log);
948     }
949     else {
950         const char *file_name = ap_server_root_relative(cmd->pool, dcfg->auditlog_name);
951         apr_status_t rc;
952
953         rc = apr_file_open(&dcfg->auditlog_fd, file_name,
954             APR_WRITE | APR_APPEND | APR_CREATE | APR_BINARY,
955             CREATEMODE, cmd->pool);
956
957         if (rc != APR_SUCCESS) {
958             return apr_psprintf(cmd->pool, "ModSecurity: Failed to open the audit log file: %s",
959                 file_name);
960         }
961     }
962
963     return NULL;
964 }
965
966 static const char *cmd_audit_log2(cmd_parms *cmd, void *_dcfg, const char *p1) {
967     directory_config *dcfg = _dcfg;
968
969     if (dcfg->auditlog_name == NOT_SET_P) {
970         return apr_psprintf(cmd->pool, "ModSecurity: Cannot configure a secondary audit log without a primary defined: %s", p1);
971     }
972
973     dcfg->auditlog2_name = (char *)p1;
974
975     if (dcfg->auditlog2_name[0] == '|') {
976         const char *pipe_name = ap_server_root_relative(cmd->pool, dcfg->auditlog2_name + 1);
977         piped_log *pipe_log;
978
979         pipe_log = ap_open_piped_log(cmd->pool, pipe_name);
980         if (pipe_log == NULL) {
981             return apr_psprintf(cmd->pool, "ModSecurity: Failed to open the secondary audit log pipe: %s",
982                 pipe_name);
983         }
984         dcfg->auditlog2_fd = ap_piped_log_write_fd(pipe_log);
985     }
986     else {
987         const char *file_name = ap_server_root_relative(cmd->pool, dcfg->auditlog2_name);
988         apr_status_t rc;
989
990         rc = apr_file_open(&dcfg->auditlog2_fd, file_name,
991             APR_WRITE | APR_APPEND | APR_CREATE | APR_BINARY,
992             CREATEMODE, cmd->pool);
993
994         if (rc != APR_SUCCESS) {
995             return apr_psprintf(cmd->pool, "ModSecurity: Failed to open the secondary audit log file: %s",
996                 file_name);
997         }
998     }
999
1000     return NULL;
1001 }
1002
1003 static const char *cmd_audit_log_parts(cmd_parms *cmd, void *_dcfg, const char *p1) {
1004     directory_config *dcfg = _dcfg;
1005
1006     if (is_valid_parts_specification((char *)p1) != 1) {
1007         return apr_psprintf(cmd->pool, "Invalid parts specification for SecAuditLogParts: %s", p1);
1008     }
1009
1010     dcfg->auditlog_parts = (char *)p1;
1011     return NULL;
1012 }
1013
1014 static const char *cmd_audit_log_relevant_status(cmd_parms *cmd, void *_dcfg, const char *p1) {
1015     directory_config *dcfg = _dcfg;
1016
1017     dcfg->auditlog_relevant_regex = msc_pregcomp(cmd->pool, p1, PCRE_DOTALL, NULL, NULL);
1018     if (dcfg->auditlog_relevant_regex == NULL) {
1019         return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p1);
1020     }
1021
1022     return NULL;
1023 }
1024
1025 static const char *cmd_audit_log_type(cmd_parms *cmd, void *_dcfg, const char *p1) {
1026     directory_config *dcfg = _dcfg;
1027
1028     if (strcasecmp(p1, "Serial") == 0) dcfg->auditlog_type = AUDITLOG_SERIAL;
1029     else
1030     if (strcasecmp(p1, "Concurrent") == 0) dcfg->auditlog_type = AUDITLOG_CONCURRENT;
1031     else
1032     return (const char *)apr_psprintf(cmd->pool,
1033         "ModSecurity: Unrecognised parameter value for SecAuditLogType: %s", p1);
1034
1035     return NULL;
1036 }
1037
1038 static const char *cmd_audit_log_dirmode(cmd_parms *cmd, void *_dcfg, const char *p1) {
1039     directory_config *dcfg = (directory_config *)_dcfg;
1040
1041     if (dcfg == NULL) return NULL;
1042
1043     if (strcasecmp(p1, "default") == 0) {
1044         dcfg->auditlog_dirperms = NOT_SET;
1045     }
1046     else {
1047         long int mode = strtol(p1, NULL, 8); /* expects octal mode */
1048         if ((mode == LONG_MAX)||(mode == LONG_MIN)||(mode <= 0)||(mode > 07777)) {
1049             return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecAuditLogDirMode: %s", p1);
1050         }
1051
1052         dcfg->auditlog_dirperms = mode2fileperms(mode);
1053     }
1054
1055     return NULL;
1056 }
1057
1058 static const char *cmd_audit_log_filemode(cmd_parms *cmd, void *_dcfg, const char *p1) {
1059     directory_config *dcfg = (directory_config *)_dcfg;
1060
1061     if (dcfg == NULL) return NULL;
1062
1063     if (strcasecmp(p1, "default") == 0) {
1064         dcfg->auditlog_fileperms = NOT_SET;
1065     }
1066     else {
1067         long int mode = strtol(p1, NULL, 8); /* expects octal mode */
1068         if ((mode == LONG_MAX)||(mode == LONG_MIN)||(mode <= 0)||(mode > 07777)) {
1069             return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecAuditLogFileMode: %s", p1);
1070         }
1071
1072         dcfg->auditlog_fileperms = mode2fileperms(mode);
1073     }
1074
1075     return NULL;
1076 }
1077
1078 static const char *cmd_audit_log_storage_dir(cmd_parms *cmd, void *_dcfg, const char *p1) {
1079     directory_config *dcfg = _dcfg;
1080
1081     dcfg->auditlog_storage_dir = ap_server_root_relative(cmd->pool, p1);
1082
1083     return NULL;
1084 }
1085
1086 static const char *cmd_cookie_format(cmd_parms *cmd, void *_dcfg, const char *p1) {
1087     directory_config *dcfg = (directory_config *)_dcfg;
1088
1089     if (strcmp(p1, "0") == 0) dcfg->cookie_format = COOKIES_V0;
1090     else
1091     if (strcmp(p1, "1") == 0) dcfg->cookie_format = COOKIES_V1;
1092     else {
1093         return apr_psprintf(cmd->pool, "ModSecurity: Invalid cookie format: %s", p1);
1094     }
1095
1096     return NULL;
1097 }
1098
1099 static const char *cmd_chroot_dir(cmd_parms *cmd, void *_dcfg, const char *p1) {
1100     char cwd[1025] = "";
1101
1102     if (cmd->server->is_virtual) {
1103         return "ModSecurity: SecChrootDir not allowed in VirtualHost";
1104     }
1105
1106     chroot_dir = (char *)p1;
1107
1108     if (getcwd(cwd, 1024) == NULL) {
1109         return "ModSecurity: Failed to get the current working directory";
1110     }
1111
1112     if (chdir(chroot_dir) < 0) {
1113         return apr_psprintf(cmd->pool, "ModSecurity: Failed to chdir to %s, errno=%d (%s)",
1114             chroot_dir, errno, strerror(errno));
1115     }
1116
1117     if (chdir(cwd) < 0) {
1118         return apr_psprintf(cmd->pool, "ModSecurity: Failed to chdir to %s, errno=%d (%s)",
1119             cwd, errno, strerror(errno));
1120     }
1121
1122     return NULL;
1123 }
1124
1125 /**
1126  * Adds component signature to the list of signatures kept in configuration.
1127  */
1128 static const char *cmd_component_signature(cmd_parms *cmd, void *_dcfg, const char *p1) {
1129     directory_config *dcfg = (directory_config *)_dcfg;
1130
1131     /* ENH Enforce "Name/VersionX.Y.Z (comment)" format. */
1132     *(char **)apr_array_push(dcfg->component_signatures) = (char *)p1;
1133
1134     return NULL;
1135 }
1136
1137 static const char *cmd_content_injection(cmd_parms *cmd, void *_dcfg, int flag) {
1138     directory_config *dcfg = (directory_config *)_dcfg;
1139     if (dcfg == NULL) return NULL;
1140     dcfg->content_injection_enabled = flag;
1141     return NULL;
1142 }
1143
1144 static const char *cmd_data_dir(cmd_parms *cmd, void *_dcfg, const char *p1) {
1145     directory_config *dcfg = (directory_config *)_dcfg;
1146
1147     if (cmd->server->is_virtual) {
1148         return "ModSecurity: SecDataDir not allowed in VirtualHost.";
1149     }
1150
1151     dcfg->data_dir = ap_server_root_relative(cmd->pool, p1);
1152
1153     return NULL;
1154 }
1155
1156 static const char *cmd_debug_log(cmd_parms *cmd, void *_dcfg, const char *p1) {
1157     directory_config *dcfg = (directory_config *)_dcfg;
1158     apr_status_t rc;
1159
1160     dcfg->debuglog_name = ap_server_root_relative(cmd->pool, p1);
1161
1162     rc = apr_file_open(&dcfg->debuglog_fd, dcfg->debuglog_name,
1163                    APR_WRITE | APR_APPEND | APR_CREATE | APR_BINARY,
1164                    CREATEMODE, cmd->pool);
1165
1166     if (rc != APR_SUCCESS) {
1167         return apr_psprintf(cmd->pool, "ModSecurity: Failed to open debug log file: %s",
1168             dcfg->debuglog_name);
1169     }
1170
1171     return NULL;
1172 }
1173
1174 static const char *cmd_debug_log_level(cmd_parms *cmd, void *_dcfg, const char *p1) {
1175     directory_config *dcfg = (directory_config *)_dcfg;
1176
1177     dcfg->debuglog_level = atoi(p1);
1178     if ((dcfg->debuglog_level >= 0)&&(dcfg->debuglog_level <= 9)) return NULL;
1179
1180     return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecDebugLogLevel: %s", p1);
1181 }
1182
1183 static const char *cmd_default_action(cmd_parms *cmd, void *_dcfg, const char *p1) {
1184     directory_config *dcfg = (directory_config *)_dcfg;
1185     extern msc_engine *modsecurity;
1186     char *my_error_msg = NULL;
1187
1188     dcfg->tmp_default_actionset = msre_actionset_create(modsecurity->msre, p1, &my_error_msg);
1189     if (dcfg->tmp_default_actionset == NULL) {
1190         if (my_error_msg != NULL) return my_error_msg;
1191         else return FATAL_ERROR;
1192     }
1193
1194     /* Must specify a disruptive action. */
1195     /* ENH: Remove this requirement? */
1196     if (dcfg->tmp_default_actionset->intercept_action == NOT_SET) {
1197         return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must specify a disruptive action.");
1198     }
1199
1200     /* Must specify a phase. */
1201     /* ENH: Remove this requirement? */
1202     if (dcfg->tmp_default_actionset->phase == NOT_SET) {
1203         return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must specify a phase.");
1204     }
1205
1206     /* Must not use metadata actions. */
1207     /* ENH: loop through to check for tags */
1208     if ((dcfg->tmp_default_actionset->id != NOT_SET_P)
1209         ||(dcfg->tmp_default_actionset->rev != NOT_SET_P)
1210         ||(dcfg->tmp_default_actionset->msg != NOT_SET_P))
1211     {
1212         return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must not "
1213             "contain any metadata actions (id, rev, msg, tag, severity, logdata).");
1214     }
1215     /* These are just a warning for now. */
1216     if ((dcfg->tmp_default_actionset->severity != NOT_SET)
1217         ||(dcfg->tmp_default_actionset->logdata != NOT_SET_P))
1218     {
1219         ap_log_perror(APLOG_MARK,
1220             APLOG_STARTUP|APLOG_WARNING|APLOG_NOERRNO, 0, cmd->pool,
1221             "ModSecurity: WARNING Using \"severity\" or \"logdata\" in "
1222             "SecDefaultAction is deprecated (%s:%d).",
1223             cmd->directive->filename, cmd->directive->line_num);
1224     }
1225
1226     /* Must not use chain. */
1227     if (dcfg->tmp_default_actionset->is_chained != NOT_SET) {
1228         return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must not "
1229             "contain a chain action.");
1230     }
1231
1232     /* Must not use skip. */
1233     if (dcfg->tmp_default_actionset->skip_count != NOT_SET) {
1234         return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must not "
1235             "contain a skip action.");
1236     }
1237
1238     /* Must not use skipAfter. */
1239     if (dcfg->tmp_default_actionset->skip_after != NOT_SET_P) {
1240         return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must not "
1241             "contain a skipAfter action.");
1242     }
1243
1244     return NULL;
1245 }
1246
1247 static const char *cmd_guardian_log(cmd_parms *cmd, void *_dcfg, const char *p1, const char *p2) {
1248     extern char *guardianlog_name;
1249     extern apr_file_t *guardianlog_fd;
1250     extern char *guardianlog_condition;
1251
1252     if (cmd->server->is_virtual) {
1253         return "ModSecurity: SecGuardianLog not allowed in VirtualHost";
1254     }
1255
1256     if (p2 != NULL) {
1257         if (strncmp(p2, "env=", 4) != 0) {
1258             return "ModSecurity: Error in condition clause";
1259         }
1260         if ( (p2[4] == '\0') || ((p2[4] == '!')&&(p2[5] == '\0')) ) {
1261             return "ModSecurity: Missing variable name";
1262         }
1263         guardianlog_condition = apr_pstrdup(cmd->pool, p2 + 4);
1264     }
1265
1266     guardianlog_name = (char *)p1;
1267
1268     if (guardianlog_name[0] == '|') {
1269         const char *pipe_name = ap_server_root_relative(cmd->pool, guardianlog_name + 1);
1270         piped_log *pipe_log;
1271
1272         pipe_log = ap_open_piped_log(cmd->pool, pipe_name);
1273         if (pipe_log == NULL) {
1274             return apr_psprintf(cmd->pool, "ModSecurity: Failed to open the guardian log pipe: %s",
1275                 pipe_name);
1276         }
1277         guardianlog_fd = ap_piped_log_write_fd(pipe_log);
1278     }
1279     else {
1280         const char *file_name = ap_server_root_relative(cmd->pool, guardianlog_name);
1281         apr_status_t rc;
1282
1283         rc = apr_file_open(&guardianlog_fd, file_name,
1284             APR_WRITE | APR_APPEND | APR_CREATE | APR_BINARY,
1285             CREATEMODE, cmd->pool);
1286
1287         if (rc != APR_SUCCESS) {
1288             return apr_psprintf(cmd->pool, "ModSecurity: Failed to open the guardian log file: %s",
1289                 file_name);
1290         }
1291     }
1292
1293     return NULL;
1294 }
1295
1296 static const char *cmd_request_body_inmemory_limit(cmd_parms *cmd, void *_dcfg, const char *p1) {
1297     directory_config *dcfg = (directory_config *)_dcfg;
1298     long int limit;
1299
1300     if (dcfg == NULL) return NULL;
1301
1302     limit = strtol(p1, NULL, 10);
1303     if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) {
1304         return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRequestBodyInMemoryLimit: %s", p1);
1305     }
1306
1307     dcfg->reqbody_inmemory_limit = limit;
1308
1309     return NULL;
1310 }
1311
1312 static const char *cmd_request_body_limit(cmd_parms *cmd, void *_dcfg, const char *p1) {
1313     directory_config *dcfg = (directory_config *)_dcfg;
1314     long int limit;
1315
1316     if (dcfg == NULL) return NULL;
1317
1318     limit = strtol(p1, NULL, 10);
1319     if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) {
1320         return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRequestBodyLimit: %s", p1);
1321     }
1322
1323     dcfg->reqbody_limit = limit;
1324
1325     return NULL;
1326 }
1327
1328 static const char *cmd_request_body_no_files_limit(cmd_parms *cmd, void *_dcfg, const char *p1) {
1329     directory_config *dcfg = (directory_config *)_dcfg;
1330     long int limit;
1331
1332     if (dcfg == NULL) return NULL;
1333
1334     limit = strtol(p1, NULL, 10);
1335     if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) {
1336         return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRequestBodyNoFilesLimit: %s", p1);
1337     }
1338
1339     dcfg->reqbody_no_files_limit = limit;
1340
1341     return NULL;
1342 }
1343
1344 static const char *cmd_request_body_access(cmd_parms *cmd, void *_dcfg, const char *p1) {
1345     directory_config *dcfg = (directory_config *)_dcfg;
1346     if (dcfg == NULL) return NULL;
1347
1348     if (strcasecmp(p1, "on") == 0) dcfg->reqbody_access = 1;
1349     else
1350     if (strcasecmp(p1, "off") == 0) dcfg->reqbody_access = 0;
1351     else
1352     return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRequestBodyAccess: %s", p1);
1353
1354     return NULL;
1355 }
1356
1357 static const char *cmd_request_encoding(cmd_parms *cmd, void *_dcfg, const char *p1) {
1358     directory_config *dcfg = (directory_config *)_dcfg;
1359     if (dcfg == NULL) return NULL;
1360
1361     /* ENH Validate encoding */
1362
1363     dcfg->request_encoding = p1;
1364
1365     return NULL;
1366 }
1367
1368 static const char *cmd_response_body_access(cmd_parms *cmd, void *_dcfg, const char *p1) {
1369     directory_config *dcfg = (directory_config *)_dcfg;
1370     if (dcfg == NULL) return NULL;
1371
1372     if (strcasecmp(p1, "on") == 0) dcfg->resbody_access = 1;
1373     else
1374     if (strcasecmp(p1, "off") == 0) dcfg->resbody_access = 0;
1375     else
1376     return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecResponseBodyAccess: %s", p1);
1377
1378     return NULL;
1379 }
1380
1381 static const char *cmd_response_body_limit(cmd_parms *cmd, void *_dcfg, const char *p1) {
1382     directory_config *dcfg = (directory_config *)_dcfg;
1383     long int limit;
1384
1385     limit = strtol(p1, NULL, 10);
1386     if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) {
1387         return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecResponseBodyLimit: %s", p1);
1388     }
1389
1390     if (limit > RESPONSE_BODY_HARD_LIMIT) {
1391         return apr_psprintf(cmd->pool, "ModSecurity: Response size limit can not exceed the hard limit: %li", RESPONSE_BODY_HARD_LIMIT);
1392     }
1393
1394     dcfg->of_limit = limit;
1395
1396     return NULL;
1397 }
1398
1399 static const char *cmd_response_body_limit_action(cmd_parms *cmd, void *_dcfg, const char *p1) {
1400     directory_config *dcfg = (directory_config *)_dcfg;
1401     if (dcfg == NULL) return NULL;
1402
1403     if (strcasecmp(p1, "ProcessPartial") == 0) dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_PARTIAL;
1404     else
1405     if (strcasecmp(p1, "Reject") == 0) dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_REJECT;
1406     else
1407     return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecResponseBodyLimitAction: %s", p1);
1408
1409     return NULL;
1410 }
1411
1412 static const char *cmd_response_body_mime_type(cmd_parms *cmd, void *_dcfg, const char *_p1) {
1413     directory_config *dcfg = (directory_config *)_dcfg;
1414     char *p1 = apr_pstrdup(cmd->pool, _p1);
1415
1416     /* TODO check whether the parameter is a valid MIME type of "???" */
1417
1418     if ((dcfg->of_mime_types == NULL)||(dcfg->of_mime_types == NOT_SET_P)) {
1419         dcfg->of_mime_types = apr_table_make(cmd->pool, 10);
1420     }
1421
1422     strtolower_inplace((unsigned char *)p1);
1423     apr_table_setn(dcfg->of_mime_types, p1, "1");
1424
1425     return NULL;
1426 }
1427
1428 static const char *cmd_response_body_mime_types_clear(cmd_parms *cmd, void *_dcfg) {
1429     directory_config *dcfg = (directory_config *)_dcfg;
1430     if (dcfg == NULL) return NULL;
1431
1432     dcfg->of_mime_types_cleared = 1;
1433
1434     if ((dcfg->of_mime_types != NULL)&&(dcfg->of_mime_types != NOT_SET_P)) {
1435         apr_table_clear(dcfg->of_mime_types);
1436     }
1437
1438     return NULL;
1439 }
1440
1441 static const char *cmd_rule(cmd_parms *cmd, void *_dcfg, const char *p1,
1442     const char *p2, const char *p3)
1443 {
1444     return add_rule(cmd, (directory_config *)_dcfg, RULE_TYPE_NORMAL, p1, p2, p3);
1445 }
1446
1447 static const char *cmd_rule_engine(cmd_parms *cmd, void *_dcfg, const char *p1) {
1448     directory_config *dcfg = (directory_config *)_dcfg;
1449     if (dcfg == NULL) return NULL;
1450
1451     if (strcasecmp(p1, "on") == 0) dcfg->is_enabled = MODSEC_ENABLED;
1452     else
1453     if (strcasecmp(p1, "off") == 0) dcfg->is_enabled = MODSEC_DISABLED;
1454     else
1455     if (strcasecmp(p1, "detectiononly") == 0) dcfg->is_enabled = MODSEC_DETECTION_ONLY;
1456     else
1457     return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRuleEngine: %s", p1);
1458
1459     return NULL;
1460 }
1461
1462 /*
1463 static const char *cmd_rule_import_by_id(cmd_parms *cmd, void *_dcfg, const char *p1) {
1464     directory_config *dcfg = (directory_config *)_dcfg;
1465     rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception));
1466     if (dcfg == NULL) return NULL;
1467
1468     re->type = RULE_EXCEPTION_IMPORT_ID;
1469     // TODO verify p1
1470     re->param = p1;
1471        *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re;
1472
1473     return NULL;
1474 }
1475
1476 static const char *cmd_rule_import_by_msg(cmd_parms *cmd, void *_dcfg, const char *p1) {
1477     directory_config *dcfg = (directory_config *)_dcfg;
1478     rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception));
1479     if (dcfg == NULL) return NULL;
1480
1481     re->type = RULE_EXCEPTION_IMPORT_MSG;
1482     // TODO verify p1
1483     re->param = p1;
1484     *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re;
1485
1486     return NULL;
1487 }
1488 */
1489
1490 static const char *cmd_rule_inheritance(cmd_parms *cmd, void *_dcfg, int flag) {
1491     directory_config *dcfg = (directory_config *)_dcfg;
1492     if (dcfg == NULL) return NULL;
1493     dcfg->rule_inheritance = flag;
1494     return NULL;
1495 }
1496
1497 static const char *cmd_rule_script(cmd_parms *cmd, void *_dcfg, const char *p1,
1498     const char *p2)
1499 {
1500     #if defined(WITH_LUA)
1501     const char *filename = resolve_relative_path(cmd->pool, cmd->directive->filename, p1);
1502     return add_rule(cmd, (directory_config *)_dcfg, RULE_TYPE_LUA, filename, p2, NULL);
1503     #else
1504     ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, "Ignoring SecRuleScript \"%s\" directive (%s:%d): No Lua scripting support.", p1, cmd->directive->filename, cmd->directive->line_num);
1505     return NULL;
1506     #endif
1507 }
1508
1509 static const char *cmd_rule_remove_by_id(cmd_parms *cmd, void *_dcfg, const char *p1) {
1510     directory_config *dcfg = (directory_config *)_dcfg;
1511     rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception));
1512     if (dcfg == NULL) return NULL;
1513
1514     re->type = RULE_EXCEPTION_REMOVE_ID;
1515     re->param = p1;
1516     *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re;
1517
1518     /* Remove the corresponding rules from the context straight away. */
1519     msre_ruleset_rule_remove_with_exception(dcfg->ruleset, re);
1520
1521     return NULL;
1522 }
1523
1524 static const char *cmd_rule_remove_by_msg(cmd_parms *cmd, void *_dcfg, const char *p1) {
1525     directory_config *dcfg = (directory_config *)_dcfg;
1526     rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception));
1527     if (dcfg == NULL) return NULL;
1528
1529     re->type = RULE_EXCEPTION_REMOVE_MSG;
1530     re->param = p1;
1531     re->param_data = msc_pregcomp(cmd->pool, p1, 0, NULL, NULL);
1532     if (re->param_data == NULL) {
1533         return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p1);
1534     }
1535     *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re;
1536
1537     /* Remove the corresponding rules from the context straight away. */
1538     msre_ruleset_rule_remove_with_exception(dcfg->ruleset, re);
1539
1540     #ifdef DEBUG_CONF
1541     ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, "Added exception %pp (%d %s) to dcfg %pp.", re, re->type, re->param, dcfg);
1542     #endif
1543
1544     return NULL;
1545 }
1546
1547 static const char *cmd_rule_update_action_by_id(cmd_parms *cmd, void *_dcfg,
1548     const char *p1, const char *p2)
1549 {
1550     return update_rule_action(cmd, (directory_config *)_dcfg, p1, p2);
1551 }
1552
1553 static const char *cmd_server_signature(cmd_parms *cmd, void *_dcfg, const char *p1) {
1554     if (cmd->server->is_virtual) {
1555         return "ModSecurity: SecServerSignature not allowed in VirtualHost";
1556     }
1557     new_server_signature = (char *)p1;
1558     return NULL;
1559 }
1560
1561 static const char *cmd_tmp_dir(cmd_parms *cmd, void *_dcfg, const char *p1) {
1562     directory_config *dcfg = (directory_config *)_dcfg;
1563
1564     if (dcfg == NULL) return NULL;
1565
1566     if (strcasecmp(p1, "none") == 0) dcfg->tmp_dir = NULL;
1567     else dcfg->tmp_dir = ap_server_root_relative(cmd->pool, p1);
1568
1569     return NULL;
1570 }
1571
1572 static const char *cmd_upload_dir(cmd_parms *cmd, void *_dcfg, const char *p1) {
1573     directory_config *dcfg = (directory_config *)_dcfg;
1574
1575     if (dcfg == NULL) return NULL;
1576
1577     if (strcasecmp(p1, "none") == 0) dcfg->upload_dir = NULL;
1578     else dcfg->upload_dir = ap_server_root_relative(cmd->pool, p1);
1579
1580     return NULL;
1581 }
1582
1583 static const char *cmd_upload_filemode(cmd_parms *cmd, void *_dcfg, const char *p1) {
1584     directory_config *dcfg = (directory_config *)_dcfg;
1585
1586     if (dcfg == NULL) return NULL;
1587
1588     if (strcasecmp(p1, "default") == 0) {
1589         dcfg->upload_filemode = NOT_SET;
1590     }
1591     else {
1592         long int mode = strtol(p1, NULL, 8); /* expects octal mode */
1593         if ((mode == LONG_MAX)||(mode == LONG_MIN)||(mode <= 0)||(mode > 07777)) {
1594             return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecUploadFileMode: %s", p1);
1595         }
1596
1597         dcfg->upload_filemode = (int)mode;
1598     }
1599
1600     return NULL;
1601 }
1602
1603 static const char *cmd_upload_keep_files(cmd_parms *cmd, void *_dcfg, const char *p1) {
1604     directory_config *dcfg = (directory_config *)_dcfg;
1605
1606     if (dcfg == NULL) return NULL;
1607
1608     if (strcasecmp(p1, "on") == 0) {
1609         dcfg->upload_keep_files = KEEP_FILES_ON;
1610     } else
1611     if (strcasecmp(p1, "off") == 0) {
1612         dcfg->upload_keep_files = KEEP_FILES_OFF;
1613     } else
1614     if (strcasecmp(p1, "relevantonly") == 0) {
1615         dcfg->upload_keep_files = KEEP_FILES_RELEVANT_ONLY;
1616     } else {
1617         return apr_psprintf(cmd->pool, "ModSecurity: Invalid setting for SecUploadKeepFiles: %s",
1618             p1);
1619     }
1620     return NULL;
1621 }
1622
1623 static const char *cmd_web_app_id(cmd_parms *cmd, void *_dcfg, const char *p1) {
1624     directory_config *dcfg = (directory_config *)_dcfg;
1625
1626     /* ENH enforce format (letters, digits, ., _, -) */
1627     dcfg->webappid = p1;
1628
1629     return NULL;
1630 }
1631
1632 /* -- PDF Protection configuration -- */
1633
1634 static const char *cmd_pdf_protect(cmd_parms *cmd, void *_dcfg, int flag) {
1635     directory_config *dcfg = (directory_config *)_dcfg;
1636     if (dcfg == NULL) return NULL;
1637
1638     dcfg->pdfp_enabled = flag;
1639
1640     return NULL;
1641 }
1642
1643 static const char *cmd_pdf_protect_secret(cmd_parms *cmd, void *_dcfg,
1644     const char *p1)
1645 {
1646     directory_config *dcfg = (directory_config *)_dcfg;
1647     if (dcfg == NULL) return NULL;
1648
1649     dcfg->pdfp_secret = p1;
1650
1651     return NULL;
1652 }
1653
1654 static const char *cmd_pdf_protect_timeout(cmd_parms *cmd, void *_dcfg,
1655     const char *p1)
1656 {
1657     directory_config *dcfg = (directory_config *)_dcfg;
1658     if (dcfg == NULL) return NULL;
1659
1660     dcfg->pdfp_timeout = atoi(p1);
1661
1662     return NULL;
1663 }
1664
1665 static const char *cmd_pdf_protect_token_name(cmd_parms *cmd, void *_dcfg,
1666     const char *p1)
1667 {
1668     directory_config *dcfg = (directory_config *)_dcfg;
1669     if (dcfg == NULL) return NULL;
1670
1671     dcfg->pdfp_token_name = p1;
1672
1673     return NULL;
1674 }
1675
1676 static const char *cmd_pdf_protect_intercept_get_only(cmd_parms *cmd, void *_dcfg,
1677     int flag)
1678 {
1679     directory_config *dcfg = (directory_config *)_dcfg;
1680     if (dcfg == NULL) return NULL;
1681
1682     dcfg->pdfp_only_get = flag;
1683
1684     return NULL;
1685 }
1686
1687 static const char *cmd_pdf_protect_method(cmd_parms *cmd, void *_dcfg,
1688     const char *p1)
1689 {
1690     directory_config *dcfg = (directory_config *)_dcfg;
1691     if (dcfg == NULL) return NULL;
1692
1693     if (strcasecmp(p1, "TokenRedirection") == 0) {
1694         dcfg->pdfp_method = PDF_PROTECT_METHOD_TOKEN_REDIRECTION;
1695     } else
1696     if (strcasecmp(p1, "ForcedDownload") == 0) {
1697         dcfg->pdfp_method = PDF_PROTECT_METHOD_FORCED_DOWNLOAD;
1698     } else {
1699         return (const char *)apr_psprintf(cmd->pool,
1700             "ModSecurity: Unrecognised parameter value for SecPdfProtectMethod: %s", p1);
1701     }
1702
1703     return NULL;
1704 }
1705
1706 /* -- Geo Lookup configuration -- */
1707
1708 static const char *cmd_geo_lookup_db(cmd_parms *cmd, void *_dcfg,
1709     const char *p1)
1710 {
1711     const char *filename = resolve_relative_path(cmd->pool, cmd->directive->filename, p1);
1712     char *error_msg;
1713     directory_config *dcfg = (directory_config *)_dcfg;
1714     if (dcfg == NULL) return NULL;
1715
1716     if (geo_init(dcfg, filename, &error_msg) <= 0) {
1717         return error_msg;
1718     }
1719
1720     return NULL;
1721 }
1722
1723
1724 /* -- Cache -- */
1725
1726 static const char *cmd_cache_transformations(cmd_parms *cmd, void *_dcfg, const char *p1, const char *p2) {
1727     directory_config *dcfg = (directory_config *)_dcfg;
1728
1729     if (dcfg == NULL) return NULL;
1730
1731     if (strcasecmp(p1, "on") == 0)
1732         dcfg->cache_trans = MODSEC_CACHE_ENABLED;
1733     else if (strcasecmp(p1, "off") == 0)
1734         dcfg->cache_trans = MODSEC_CACHE_DISABLED;
1735     else
1736         return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecCacheTransformations: %s", p1);
1737
1738     /* Process options */
1739     if (p2 != NULL) {
1740         apr_table_t *vartable = apr_table_make(cmd->pool, 4);
1741         apr_status_t rc;
1742         char *error_msg = NULL;
1743         const char *charval = NULL;
1744         apr_int64_t intval = 0;
1745
1746         if (vartable == NULL) {
1747             return apr_psprintf(cmd->pool, "ModSecurity: Unable to process options for SecCacheTransformations");
1748         }
1749         rc = msre_parse_generic(cmd->pool, p2, vartable, &error_msg);
1750         if (rc < 0) {
1751             return apr_psprintf(cmd->pool, "ModSecurity: Unable to parse options for SecCacheTransformations: %s", error_msg);
1752         }
1753
1754         /* incremental */
1755         charval = apr_table_get(vartable, "incremental");
1756         if (charval != NULL) {
1757             if (strcasecmp(charval, "on") == 0)
1758                 dcfg->cache_trans_incremental = 1;
1759             else if (strcasecmp(charval, "off") == 0)
1760                 dcfg->cache_trans_incremental = 0;
1761             else
1762                 return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations invalid incremental value: %s", charval);
1763         }
1764
1765         /* minlen */
1766         charval = apr_table_get(vartable, "minlen");
1767         if (charval != NULL) {
1768             intval = apr_atoi64(charval);
1769             if (errno == ERANGE) {
1770                 return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen out of range: %s", charval);
1771             }
1772             if (intval < 0) {
1773                 return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen must be positive: %s", charval);
1774             }
1775
1776             /* The NOT_SET indicator is -1, a signed long, and therfore
1777              * we cannot be >= the unsigned value of NOT_SET.
1778              */
1779             if ((unsigned long)intval >= (unsigned long)NOT_SET) {
1780                 return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen must be less than: %lu", (unsigned long)NOT_SET);
1781             }
1782             dcfg->cache_trans_min = (apr_size_t)intval;
1783         }
1784
1785         /* maxlen */
1786         charval = apr_table_get(vartable, "maxlen");
1787         if (charval != NULL) {
1788             intval = apr_atoi64(charval);
1789             if (errno == ERANGE) {
1790                 return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen out of range: %s", charval);
1791             }
1792             if (intval < 0) {
1793                 return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen must be positive: %s", charval);
1794             }
1795
1796             /* The NOT_SET indicator is -1, a signed long, and therfore
1797              * we cannot be >= the unsigned value of NOT_SET.
1798              */
1799             if ((unsigned long)intval >= (unsigned long)NOT_SET) {
1800                 return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen must be less than: %lu", (unsigned long)NOT_SET);
1801             }
1802             if ((intval != 0) && ((apr_size_t)intval < dcfg->cache_trans_min)) {
1803                 return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen must not be less than minlen: %lu < %" APR_SIZE_T_FMT, (unsigned long)intval, dcfg->cache_trans_min);
1804             }
1805             dcfg->cache_trans_max = (apr_size_t)intval;
1806
1807         }
1808
1809         /* maxitems */
1810         charval = apr_table_get(vartable, "maxitems");
1811         if (charval != NULL) {
1812             intval = apr_atoi64(charval);
1813             if (errno == ERANGE) {
1814                 return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxitems out of range: %s", charval);
1815             }
1816             if (intval < 0) {
1817                 return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxitems must be positive: %s", charval);
1818             }
1819             dcfg->cache_trans_maxitems = (apr_size_t)intval;
1820         }
1821     }
1822
1823     return NULL;
1824 }
1825
1826
1827 /* -- Configuration directives definitions -- */
1828
1829 #define CMD_SCOPE_MAIN  (RSRC_CONF)
1830 #define CMD_SCOPE_ANY   (RSRC_CONF | ACCESS_CONF)
1831
1832 const command_rec module_directives[] = {
1833
1834     AP_INIT_TAKE1 (
1835         "SecAction",
1836         cmd_action,
1837         NULL,
1838         CMD_SCOPE_ANY,
1839         "an action list"
1840     ),
1841
1842     AP_INIT_TAKE1 (
1843         "SecArgumentSeparator",
1844         cmd_argument_separator,
1845         NULL,
1846         CMD_SCOPE_MAIN,
1847         "character that will be used as separator when parsing application/x-www-form-urlencoded content."
1848     ),
1849
1850     AP_INIT_TAKE1 (
1851         "SecAuditEngine",
1852         cmd_audit_engine,
1853         NULL,
1854         CMD_SCOPE_ANY,
1855         "On, Off or RelevantOnly to determine the level of audit logging"
1856     ),
1857
1858     AP_INIT_TAKE1 (
1859         "SecAuditLog",
1860         cmd_audit_log,
1861         NULL,
1862         CMD_SCOPE_ANY,
1863         "filename of the primary audit log file"
1864     ),
1865
1866     AP_INIT_TAKE1 (
1867         "SecAuditLog2",
1868         cmd_audit_log2,
1869         NULL,
1870         CMD_SCOPE_ANY,
1871         "filename of the secondary audit log file"
1872     ),
1873
1874     AP_INIT_TAKE1 (
1875         "SecAuditLogParts",
1876         cmd_audit_log_parts,
1877         NULL,
1878         CMD_SCOPE_ANY,
1879         "list of audit log parts that go into the log."
1880     ),
1881
1882     AP_INIT_TAKE1 (
1883         "SecAuditLogRelevantStatus",
1884         cmd_audit_log_relevant_status,
1885         NULL,
1886         CMD_SCOPE_ANY,
1887         "regular expression that will be used to determine if the response status is relevant for audit logging"
1888     ),
1889
1890     AP_INIT_TAKE1 (
1891         "SecAuditLogType",
1892         cmd_audit_log_type,
1893         NULL,
1894         CMD_SCOPE_ANY,
1895         "whether to use the old audit log format (Serial) or new (Concurrent)"
1896     ),
1897
1898     AP_INIT_TAKE1 (
1899         "SecAuditLogStorageDir",
1900         cmd_audit_log_storage_dir,
1901         NULL,
1902         CMD_SCOPE_ANY,
1903         "path to the audit log storage area; absolute, or relative to the root of the server"
1904     ),
1905
1906     AP_INIT_TAKE1 (
1907         "SecAuditLogDirMode",
1908         cmd_audit_log_dirmode,
1909         NULL,
1910         CMD_SCOPE_ANY,
1911         "octal permissions mode for concurrent audit log directories"
1912     ),
1913
1914     AP_INIT_TAKE1 (
1915         "SecAuditLogFileMode",
1916         cmd_audit_log_filemode,
1917         NULL,
1918         CMD_SCOPE_ANY,
1919         "octal permissions mode for concurrent audit log files"
1920     ),
1921
1922     AP_INIT_TAKE12 (
1923         "SecCacheTransformations",
1924         cmd_cache_transformations,
1925         NULL,
1926         CMD_SCOPE_ANY,
1927         "whether or not to cache transformations. Defaults to true."
1928     ),
1929
1930     AP_INIT_TAKE1 (
1931         "SecChrootDir",
1932         cmd_chroot_dir,
1933         NULL,
1934         CMD_SCOPE_MAIN,
1935         "path of the directory to which server will be chrooted"
1936     ),
1937
1938     AP_INIT_TAKE1 (
1939         "SecComponentSignature",
1940         cmd_component_signature,
1941         NULL,
1942         CMD_SCOPE_MAIN,
1943         "component signature to add to ModSecurity signature."
1944     ),
1945
1946     AP_INIT_FLAG (
1947         "SecContentInjection",
1948         cmd_content_injection,
1949         NULL,
1950         CMD_SCOPE_ANY,
1951         "On or Off"
1952     ),
1953
1954     AP_INIT_TAKE1 (
1955         "SecCookieFormat",
1956         cmd_cookie_format,
1957         NULL,
1958         CMD_SCOPE_ANY,
1959         "version of the Cookie specification to use for parsing. Possible values are 0 and 1."
1960     ),
1961
1962     AP_INIT_TAKE1 (
1963         "SecDataDir",
1964         cmd_data_dir,
1965         NULL,
1966         CMD_SCOPE_MAIN,
1967         "path to the persistent data storage area" // TODO
1968     ),
1969
1970     AP_INIT_TAKE1 (
1971         "SecDebugLog",
1972         cmd_debug_log,
1973         NULL,
1974         CMD_SCOPE_ANY,
1975         "path to the debug log file"
1976     ),
1977
1978     AP_INIT_TAKE1 (
1979         "SecDebugLogLevel",
1980         cmd_debug_log_level,
1981         NULL,
1982         CMD_SCOPE_ANY,
1983         "debug log level, which controls the verbosity of logging."
1984         " Use values from 0 (no logging) to 9 (a *lot* of logging)."
1985     ),
1986
1987     AP_INIT_TAKE1 (
1988         "SecDefaultAction",
1989         cmd_default_action,
1990         NULL,
1991         CMD_SCOPE_ANY,
1992         "default action list"
1993     ),
1994
1995     AP_INIT_TAKE1 (
1996         "SecGeoLookupDB",
1997         cmd_geo_lookup_db,
1998         NULL,
1999         RSRC_CONF,
2000         "database for geographical lookups module."
2001     ),
2002
2003     AP_INIT_TAKE12 (
2004         "SecGuardianLog",
2005         cmd_guardian_log,
2006         NULL,
2007         CMD_SCOPE_MAIN,
2008         "The filename of the filter debugging log file"
2009     ),
2010
2011     AP_INIT_TAKE1 (
2012         "SecMarker",
2013         cmd_marker,
2014         NULL,
2015         CMD_SCOPE_ANY,
2016         "marker for a skipAfter target"
2017     ),
2018
2019     AP_INIT_FLAG (
2020         "SecPdfProtect",
2021         cmd_pdf_protect,
2022         NULL,
2023         RSRC_CONF,
2024         "enable PDF protection module."
2025     ),
2026
2027     AP_INIT_TAKE1 (
2028         "SecPdfProtectSecret",
2029         cmd_pdf_protect_secret,
2030         NULL,
2031         RSRC_CONF,
2032         "secret that will be used to construct protection tokens."
2033     ),
2034
2035     AP_INIT_TAKE1 (
2036         "SecPdfProtectTimeout",
2037         cmd_pdf_protect_timeout,
2038         NULL,
2039         RSRC_CONF,
2040         "duration for which protection tokens will be valid."
2041     ),
2042
2043     AP_INIT_TAKE1 (
2044         "SecPdfProtectTokenName",
2045         cmd_pdf_protect_token_name,
2046         NULL,
2047         RSRC_CONF,
2048         "name of the protection token. The name 'PDFTOKEN' is used by default."
2049     ),
2050
2051     AP_INIT_FLAG (
2052         "SecPdfProtectInterceptGETOnly",
2053         cmd_pdf_protect_intercept_get_only,
2054         NULL,
2055         RSRC_CONF,
2056         "whether or not to intercept only GET and HEAD requess. Defaults to true."
2057     ),
2058
2059     AP_INIT_TAKE1 (
2060         "SecPdfProtectMethod",
2061         cmd_pdf_protect_method,
2062         NULL,
2063         RSRC_CONF,
2064         "protection method to use. Can be 'TokenRedirection' (default) or 'ForcedDownload'"
2065     ),
2066
2067     AP_INIT_TAKE1 (
2068         "SecRequestBodyAccess",
2069         cmd_request_body_access,
2070         NULL,
2071         CMD_SCOPE_ANY,
2072         "On or Off"
2073     ),
2074
2075     AP_INIT_TAKE1 (
2076         "SecRequestBodyInMemoryLimit",
2077         cmd_request_body_inmemory_limit,
2078         NULL,
2079         CMD_SCOPE_ANY,
2080         "maximum request body size that will be placed in memory (except for POST urlencoded requests)."
2081     ),
2082
2083     AP_INIT_TAKE1 (
2084         "SecRequestBodyLimit",
2085         cmd_request_body_limit,
2086         NULL,
2087         CMD_SCOPE_ANY,
2088         "maximum request body size ModSecurity will accept."
2089     ),
2090
2091     AP_INIT_TAKE1 (
2092         "SecRequestBodyNoFilesLimit",
2093         cmd_request_body_no_files_limit,
2094         NULL,
2095         CMD_SCOPE_ANY,
2096         "maximum request body size ModSecurity will accept, but excluding the size of uploaded files."
2097     ),
2098
2099     AP_INIT_TAKE1 (
2100         "SecRequestEncoding",
2101         cmd_request_encoding,
2102         NULL,
2103         CMD_SCOPE_ANY,
2104         "character encoding used in request."
2105     ),
2106
2107     AP_INIT_TAKE1 (
2108         "SecResponseBodyAccess",
2109         cmd_response_body_access,
2110         NULL,
2111         CMD_SCOPE_ANY,
2112         "On or Off"
2113     ),
2114
2115     AP_INIT_TAKE1 (
2116         "SecResponseBodyLimit",
2117         cmd_response_body_limit,
2118         NULL,
2119         CMD_SCOPE_ANY,
2120         "byte limit for response body"
2121     ),
2122
2123     AP_INIT_TAKE1 (
2124         "SecResponseBodyLimitAction",
2125         cmd_response_body_limit_action,
2126         NULL,
2127         CMD_SCOPE_ANY,
2128         "what happens when the response body limit is reached"
2129     ),
2130
2131     AP_INIT_ITERATE (
2132         "SecResponseBodyMimeType",
2133         cmd_response_body_mime_type,
2134         NULL,
2135         CMD_SCOPE_ANY,
2136         "adds given MIME types to the list of types that will be buffered on output"
2137     ),
2138
2139     AP_INIT_NO_ARGS (
2140         "SecResponseBodyMimeTypesClear",
2141         cmd_response_body_mime_types_clear,
2142         NULL,
2143         CMD_SCOPE_ANY,
2144         "clears the list of MIME types that will be buffered on output"
2145     ),
2146
2147     AP_INIT_TAKE23 (
2148         "SecRule",
2149         cmd_rule,
2150         NULL,
2151         CMD_SCOPE_ANY,
2152         "rule target, operator and optional action list"
2153     ),
2154
2155     AP_INIT_TAKE1 (
2156         "SecRuleEngine",
2157         cmd_rule_engine,
2158         NULL,
2159         CMD_SCOPE_ANY,
2160         "On or Off"
2161     ),
2162
2163     AP_INIT_FLAG (
2164         "SecRuleInheritance",
2165         cmd_rule_inheritance,
2166         NULL,
2167         CMD_SCOPE_ANY,
2168         "On or Off"
2169     ),
2170
2171     AP_INIT_TAKE12 (
2172         "SecRuleScript",
2173         cmd_rule_script,
2174         NULL,
2175         CMD_SCOPE_ANY,
2176         "rule script and optional actionlist"
2177     ),
2178
2179     AP_INIT_ITERATE (
2180         "SecRuleRemoveById",
2181         cmd_rule_remove_by_id,
2182         NULL,
2183         CMD_SCOPE_ANY,
2184         "rule ID for removal"
2185     ),
2186
2187     AP_INIT_ITERATE (
2188         "SecRuleRemoveByMsg",
2189         cmd_rule_remove_by_msg,
2190         NULL,
2191         CMD_SCOPE_ANY,
2192         "rule message for removal"
2193     ),
2194
2195     AP_INIT_TAKE2 (
2196         "SecRuleUpdateActionById",
2197         cmd_rule_update_action_by_id,
2198         NULL,
2199         CMD_SCOPE_ANY,
2200         "updated action list"
2201     ),
2202
2203     AP_INIT_TAKE1 (
2204         "SecServerSignature",
2205         cmd_server_signature,
2206         NULL,
2207         CMD_SCOPE_MAIN,
2208         "the new signature of the server"
2209     ),
2210
2211     AP_INIT_TAKE1 (
2212         "SecTmpDir",
2213         cmd_tmp_dir,
2214         NULL,
2215         CMD_SCOPE_ANY,
2216         "path to the temporary storage area"
2217     ),
2218
2219     AP_INIT_TAKE1 (
2220         "SecUploadDir",
2221         cmd_upload_dir,
2222         NULL,
2223         CMD_SCOPE_ANY,
2224         "path to the file upload area"
2225     ),
2226
2227     AP_INIT_TAKE1 (
2228         "SecUploadFileMode",
2229         cmd_upload_filemode,
2230         NULL,
2231         CMD_SCOPE_ANY,
2232         "octal permissions mode for uploaded files"
2233     ),
2234
2235     AP_INIT_TAKE1 (
2236         "SecUploadKeepFiles",
2237         cmd_upload_keep_files,
2238         NULL,
2239         CMD_SCOPE_ANY,
2240         "On or Off"
2241     ),
2242
2243     AP_INIT_TAKE1 (
2244         "SecWebAppId",
2245         cmd_web_app_id,
2246         NULL,
2247         CMD_SCOPE_ANY,
2248         "id"
2249     ),
2250
2251     { NULL }
2252 };