summaryrefslogtreecommitdiff
path: root/src/sp_config.c
diff options
context:
space:
mode:
authorBen Fuhrmannek2021-08-16 15:47:01 +0200
committerBen Fuhrmannek2021-08-16 15:47:01 +0200
commit5148ded7268b569fd5e720f90b44645c83ac3e9e (patch)
tree9d5c3035a7a85ffc27de7c32b441994a21a6347a /src/sp_config.c
parent9dc6b23a2219e809e665bac7d82567533751d39d (diff)
fincy new scanner/parser for config rules + fixed a few bugs along the way + fixed related unittests
Diffstat (limited to 'src/sp_config.c')
-rw-r--r--src/sp_config.c305
1 files changed, 159 insertions, 146 deletions
diff --git a/src/sp_config.c b/src/sp_config.c
index 37c749b..4d96bbe 100644
--- a/src/sp_config.c
+++ b/src/sp_config.c
@@ -4,102 +4,140 @@
4 4
5#include "php_snuffleupagus.h" 5#include "php_snuffleupagus.h"
6 6
7size_t sp_line_no;
8 7
9static sp_config_tokens const sp_func[] = { 8static zend_result sp_process_config_root(sp_parsed_keyword *parsed_rule) {
10 {.func = parse_unserialize, .token = SP_TOKEN_UNSERIALIZE_HMAC}, 9 sp_config_keyword sp_func[] = {
11 {.func = parse_random, .token = SP_TOKEN_HARDEN_RANDOM}, 10 {parse_unserialize, SP_TOKEN_UNSERIALIZE_HMAC, SNUFFLEUPAGUS_G(config).config_unserialize},
12 {.func = parse_log_media, .token = SP_TOKEN_LOG_MEDIA}, 11 {parse_enable, SP_TOKEN_HARDEN_RANDOM, &(SNUFFLEUPAGUS_G(config).config_random->enable)},
13 {.func = parse_disabled_functions, .token = SP_TOKEN_DISABLE_FUNC}, 12 {parse_log_media, SP_TOKEN_LOG_MEDIA, &(SNUFFLEUPAGUS_G(config).log_media)},
14 {.func = parse_readonly_exec, .token = SP_TOKEN_READONLY_EXEC}, 13 {parse_disabled_functions, SP_TOKEN_DISABLE_FUNC, NULL},
15 {.func = parse_global_strict, .token = SP_TOKEN_GLOBAL_STRICT}, 14 {parse_readonly_exec, SP_TOKEN_READONLY_EXEC, SNUFFLEUPAGUS_G(config).config_readonly_exec},
16 {.func = parse_upload_validation, .token = SP_TOKEN_UPLOAD_VALIDATION}, 15 {parse_enable, SP_TOKEN_GLOBAL_STRICT, &(SNUFFLEUPAGUS_G(config).config_global_strict->enable)},
17 {.func = parse_cookie, .token = SP_TOKEN_COOKIE_ENCRYPTION}, 16 {parse_upload_validation, SP_TOKEN_UPLOAD_VALIDATION, SNUFFLEUPAGUS_G(config).config_upload_validation},
18 {.func = parse_global, .token = SP_TOKEN_GLOBAL}, 17 {parse_cookie, SP_TOKEN_COOKIE_ENCRYPTION, NULL},
19 {.func = parse_auto_cookie_secure, .token = SP_TOKEN_AUTO_COOKIE_SECURE}, 18 {parse_global, SP_TOKEN_GLOBAL, NULL},
20 {.func = parse_disable_xxe, .token = SP_TOKEN_DISABLE_XXE}, 19 {parse_enable, SP_TOKEN_AUTO_COOKIE_SECURE, &(SNUFFLEUPAGUS_G(config).config_auto_cookie_secure->enable)},
21 {.func = parse_eval_blacklist, .token = SP_TOKEN_EVAL_BLACKLIST}, 20 {parse_enable, SP_TOKEN_DISABLE_XXE, &(SNUFFLEUPAGUS_G(config).config_disable_xxe->enable)},
22 {.func = parse_eval_whitelist, .token = SP_TOKEN_EVAL_WHITELIST}, 21 {parse_eval_filter_conf, SP_TOKEN_EVAL_BLACKLIST, &(SNUFFLEUPAGUS_G(config).config_eval->blacklist)},
23 {.func = parse_session, .token = SP_TOKEN_SESSION_ENCRYPTION}, 22 {parse_eval_filter_conf, SP_TOKEN_EVAL_WHITELIST, &(SNUFFLEUPAGUS_G(config).config_eval->whitelist)},
24 {.func = parse_sloppy_comparison, .token = SP_TOKEN_SLOPPY_COMPARISON}, 23 {parse_session, SP_TOKEN_SESSION_ENCRYPTION, SNUFFLEUPAGUS_G(config).config_session},
25 {.func = parse_wrapper_whitelist, .token = SP_TOKEN_ALLOW_WRAPPERS}, 24 {parse_enable, SP_TOKEN_SLOPPY_COMPARISON, &(SNUFFLEUPAGUS_G(config).config_sloppy->enable)},
26 {.func = parse_ini_protection, .token = ".ini_protection"}, 25 {parse_wrapper_whitelist, SP_TOKEN_ALLOW_WRAPPERS, SNUFFLEUPAGUS_G(config).config_wrapper},
27 {.func = parse_ini_entry, .token = ".ini"}, 26 {parse_ini_protection, SP_TOKEN_INI_PROTECTION, SNUFFLEUPAGUS_G(config).config_ini},
28 {NULL, NULL}}; 27 {parse_ini_entry, SP_TOKEN_INI, SNUFFLEUPAGUS_G(config).config_unserialize},
28 {NULL, NULL, NULL}};
29 return sp_process_rule(parsed_rule, sp_func);
30}
29 31
30/* Top level keyword parsing */ 32zend_result sp_parse_config(const char *filename) {
33 FILE *fd = fopen(filename, "rb");
34 if (fd == NULL) {
35 sp_log_err("config", "Could not open configuration file %s : %s", filename, strerror(errno));
36 return FAILURE;
37 }
31 38
32static int parse_line(char *line) { 39 size_t step = 8192;
33 char *ptr = line; 40 size_t max_len = step, len = 0;
41 zend_string *data = zend_string_alloc(max_len, 0);
42 char *ptr = ZSTR_VAL(data);
34 43
35 while (*ptr == ' ' || *ptr == '\t') { 44 size_t bytes;
36 ++ptr; 45 while ((bytes = fread(ptr, 1, max_len - len, fd))) {
46 len += bytes;
47 if (max_len - len <= 0) {
48 max_len += step;
49 data = zend_string_extend(data, max_len, 0);
50 ptr = ZSTR_VAL(data) + len;
51 } else {
52 ptr += bytes;
53 }
37 } 54 }
55 fclose(fd);
38 56
39 if (!*ptr || *ptr == '#' || *ptr == ';') { 57 data = zend_string_truncate(data, len, 0);
40 return 0; 58 ZSTR_VAL(data)[len] = 0;
41 } 59
60 int ret = sp_config_scan(ZSTR_VAL(data), sp_process_config_root);
61
62 zend_string_release_ex(data, 0);
63
64 return ret;
65}
42 66
43 if (strncmp(ptr, SP_TOKEN_BASE, strlen(SP_TOKEN_BASE))) {
44 sp_log_err("config", "Invalid configuration prefix for '%s' on line %zu",
45 line, sp_line_no);
46 return -1;
47 }
48 ptr += strlen(SP_TOKEN_BASE);
49 67
50 for (size_t i = 0; sp_func[i].func; i++) { 68zend_result sp_process_rule(sp_parsed_keyword *parsed_rule, sp_config_keyword *config_keywords) {
51 if (!strncmp(sp_func[i].token, ptr, strlen(sp_func[i].token))) { 69 for (sp_parsed_keyword *kw = parsed_rule; kw->kw; kw++) {
52 return sp_func[i].func(ptr + strlen(sp_func[i].token)); 70 bool found_kw = false;
71 for (sp_config_keyword *ckw = config_keywords; ckw->func; ckw++) {
72 if (kw->kwlen == strlen(ckw->token) && !strncmp(kw->kw, ckw->token, kw->kwlen)) {
73 if (ckw->func) {
74 int ret = ckw->func(ckw->token, kw, ckw->retval);
75 switch (ret) {
76 case SP_PARSER_SUCCESS:
77 break;
78 case SP_PARSER_ERROR:
79 return FAILURE;
80 case SP_PARSER_STOP:
81 return SUCCESS;
82 }
83 }
84 found_kw = true;
85 break;
86 }
87 }
88
89 if (!found_kw) {
90 zend_string *kwname = zend_string_init(kw->kw, kw->kwlen, 0);
91 sp_log_err("config", "Unexpected keyword '%s' on line %d", ZSTR_VAL(kwname), kw->lineno);
92 zend_string_release_ex(kwname, 0);
93 return FAILURE;
53 } 94 }
54 } 95 }
55 sp_log_err("config", "Invalid configuration section '%s' on line %zu", line, 96 return SUCCESS;
56 sp_line_no);
57 return -1;
58} 97}
59 98
60/* keyword parsing */
61#define CHECK_DUPLICATE_KEYWORD(retval) \ 99#define CHECK_DUPLICATE_KEYWORD(retval) \
62 if (*(void**)(retval)) { \ 100 if (*(void**)(retval)) { \
63 sp_log_err("config", "duplicate %s) on line %zu near `%s`", keyword, sp_line_no, line); \ 101 sp_log_err("config", "duplicate keyword '%s' on line %zu", token, kw->lineno); \
64 return -1; } 102 return SP_PARSER_ERROR; }
65 103
66 104
67int parse_empty(char *restrict line, char *restrict keyword, void *retval) { 105SP_PARSEKW_FN(parse_empty) {
106 if (kw->arglen) {
107 sp_log_err("config", "Unexpected argument for keyword '%s' - it should be '%s()' on line %zu", token, token, kw->lineno);
108 return SP_PARSER_ERROR;
109 }
110 if (kw->argtype != SP_ARGTYPE_EMPTY) {
111 sp_log_err("config", "Missing paranthesis for keyword '%s' - it should be '%s()' on line %zu", token, token, kw->lineno);
112 return SP_PARSER_ERROR;
113 }
68 *(bool *)retval = true; 114 *(bool *)retval = true;
69 return 0; 115 return SP_PARSER_SUCCESS;
70} 116}
71 117
72int parse_list(char *restrict line, char *restrict keyword, void *list_ptr) { 118SP_PARSEKW_FN(parse_list) {
73 CHECK_DUPLICATE_KEYWORD(list_ptr); 119 CHECK_DUPLICATE_KEYWORD(retval);
74 zend_string *value = NULL;
75 sp_list_node **list = list_ptr;
76 char *token, *tmp;
77 120
78 size_t consumed = 0; 121 sp_list_node **list = retval;
79 value = get_param(&consumed, line, SP_TYPE_STR, keyword); 122 char *tok, *tmp;
80 if (!value) { 123
81 return -1; 124 SP_PARSE_ARG(value);
82 }
83 125
84 tmp = ZSTR_VAL(value); 126 tmp = ZSTR_VAL(value);
85 while (1) { 127 while (1) {
86 token = strsep(&tmp, ","); 128 tok = strsep(&tmp, ",");
87 if (token == NULL) { 129 if (tok == NULL) {
88 break; 130 break;
89 } 131 }
90 *list = sp_list_insert(*list, zend_string_init(token, strlen(token), 1)); 132 *list = sp_list_insert(*list, zend_string_init(tok, strlen(tok), 1));
91 } 133 }
134 zend_string_release(value);
92 135
93 pefree(value, 1); 136 return SP_PARSER_SUCCESS;
94 return consumed;
95} 137}
96 138
97int parse_php_type(char *restrict line, char *restrict keyword, void *retval) { 139SP_PARSEKW_FN(parse_php_type) {
98 size_t consumed = 0; 140 SP_PARSE_ARG(value);
99 zend_string *value = get_param(&consumed, line, SP_TYPE_STR, keyword);
100 if (!value) {
101 return -1;
102 }
103 141
104 if (zend_string_equals_literal_ci(value, "undef")) { 142 if (zend_string_equals_literal_ci(value, "undef")) {
105 *(sp_php_type *)retval = SP_PHP_TYPE_UNDEF; 143 *(sp_php_type *)retval = SP_PHP_TYPE_UNDEF;
@@ -124,113 +162,88 @@ int parse_php_type(char *restrict line, char *restrict keyword, void *retval) {
124 } else if (zend_string_equals_literal_ci(value, "reference")) { 162 } else if (zend_string_equals_literal_ci(value, "reference")) {
125 *(sp_php_type *)retval = SP_PHP_TYPE_REFERENCE; 163 *(sp_php_type *)retval = SP_PHP_TYPE_REFERENCE;
126 } else { 164 } else {
127 pefree(value, 1); 165 zend_string_release(value);
128 sp_log_err("error", 166 sp_log_err("error", ".%s() is expecting a valid php type ('false', 'true',"
129 "%s) is expecting a valid php type ('false', 'true',"
130 " 'array'. 'object', 'long', 'double', 'null', 'resource', " 167 " 'array'. 'object', 'long', 'double', 'null', 'resource', "
131 "'reference', 'undef') on line %zu", 168 "'reference', 'undef') on line %zu", token, kw->lineno);
132 keyword, sp_line_no); 169 return SP_PARSER_ERROR;
133 return -1;
134 } 170 }
135 pefree(value, 1); 171 zend_string_release(value);
136 return consumed; 172 return SP_PARSER_SUCCESS;
137} 173}
138 174
139int parse_str(char *restrict line, char *restrict keyword, void *retval) { 175
176SP_PARSEKW_FN(parse_str) {
140 CHECK_DUPLICATE_KEYWORD(retval); 177 CHECK_DUPLICATE_KEYWORD(retval);
141 zend_string *value = NULL; 178 SP_PARSE_ARG(value);
142 179
143 size_t consumed = 0; 180 *(zend_string **)retval = value;
144 value = get_param(&consumed, line, SP_TYPE_STR, keyword); 181
145 if (value) { 182 return SP_PARSER_SUCCESS;
146 *(zend_string **)retval = value;
147 return consumed;
148 }
149 return -1;
150} 183}
151 184
152int parse_cidr(char *restrict line, char *restrict keyword, void *retval) { 185SP_PARSEKW_FN(parse_int) {
153 CHECK_DUPLICATE_KEYWORD(retval); 186 int ret = SP_PARSER_SUCCESS;
187 SP_PARSE_ARG(value);
188
189 char *endptr;
190 errno = 0;
191 *(int*)retval = (int)strtoimax(ZSTR_VAL(value), &endptr, 10);
192 if (errno != 0 || !endptr || endptr == ZSTR_VAL(value)) {
193 sp_log_err("config", "Failed to parse arg '%s' of `%s` on line %zu", ZSTR_VAL(value), token, kw->lineno);
194 ret = SP_PARSER_ERROR;
195 }
196 zend_string_release(value);
197 return ret;
198}
154 199
155 size_t consumed = 0; 200SP_PARSEKW_FN(parse_ulong) {
156 zend_string *value = get_param(&consumed, line, SP_TYPE_STR, keyword); 201 int ret = SP_PARSER_SUCCESS;
202 SP_PARSE_ARG(value);
157 203
158 if (!value) { 204 char *endptr;
159 sp_log_err("config", "%s doesn't contain a valid cidr on line %zu", line, sp_line_no); 205 errno = 0;
160 return -1; 206 *(u_long*)retval = (u_long)strtoul(ZSTR_VAL(value), &endptr, 10);
207 if (errno != 0 || !endptr || endptr == ZSTR_VAL(value)) {
208 sp_log_err("config", "Failed to parse arg '%s' of `%s` on line %zu", ZSTR_VAL(value), token, kw->lineno);
209 ret = SP_PARSER_ERROR;
161 } 210 }
211 zend_string_release(value);
212 return ret;
213}
214
215SP_PARSEKW_FN(parse_cidr) {
216 CHECK_DUPLICATE_KEYWORD(retval);
217 SP_PARSE_ARG(value);
162 218
163 sp_cidr *cidr = pecalloc(sizeof(sp_cidr), 1, 1); 219 sp_cidr *cidr = pecalloc(sizeof(sp_cidr), 1, 1);
164 220
165 if (0 != get_ip_and_cidr(ZSTR_VAL(value), cidr)) { 221 if (0 != get_ip_and_cidr(ZSTR_VAL(value), cidr)) {
166 pefree(cidr, 1); 222 pefree(cidr, 1);
167 *(sp_cidr **)retval = NULL; 223 cidr = NULL;
168 return -1;
169 } 224 }
170 225
171 *(sp_cidr **)retval = cidr; 226 *(sp_cidr **)retval = cidr;
172 return consumed; 227 return cidr ? SP_PARSER_SUCCESS : SP_PARSER_ERROR;
173} 228}
174 229
175int parse_regexp(char *restrict line, char *restrict keyword, void *retval) { 230SP_PARSEKW_FN(parse_regexp) {
176 /* TODO: Do we want to use pcre_study? 231 /* TODO: Do we want to use pcre_study?
177 * (http://www.pcre.org/original/doc/html/pcre_study.html) 232 * (http://www.pcre.org/original/doc/html/pcre_study.html)
178 * maybe not: http://sljit.sourceforge.net/pcre.html*/ 233 * maybe not: http://sljit.sourceforge.net/pcre.html*/
179 CHECK_DUPLICATE_KEYWORD(retval); 234 CHECK_DUPLICATE_KEYWORD(retval);
235 SP_PARSE_ARG(value);
180 236
181 size_t consumed = 0; 237 sp_pcre *compiled_re = sp_pcre_compile(ZSTR_VAL(value));
182 zend_string *value = get_param(&consumed, line, SP_TYPE_STR, keyword); 238 if (!compiled_re) {
183 239 sp_log_err("config", "Invalid regexp '%s' for '.%s()' on line %zu", ZSTR_VAL(value), token, kw->lineno);
184 if (value) { 240 zend_string_release_ex(value, 1);
185 sp_pcre *compiled_re = sp_pcre_compile(ZSTR_VAL(value)); 241 return SP_PARSER_ERROR;
186 if (NULL != compiled_re) {
187 *(sp_pcre **)retval = compiled_re;
188 return consumed;
189 }
190 }
191 char *closing_paren = strchr(line, ')');
192 if (NULL != closing_paren) {
193 closing_paren[0] = '\0';
194 } 242 }
195 sp_log_err("config",
196 "'%s)' is expecting a valid regexp, and not '%s' on line %zu",
197 keyword, line, sp_line_no);
198 return -1;
199}
200 243
201int sp_parse_config(const char *conf_file) { 244 *(sp_pcre **)retval = compiled_re;
202 FILE *fd = fopen(conf_file, "r");
203 char *lineptr = NULL;
204 size_t n = 0;
205 sp_line_no = 1;
206 245
207 if (fd == NULL) { 246 return SP_PARSER_SUCCESS;
208 sp_log_err("config", "Could not open configuration file %s : %s", conf_file,
209 strerror(errno));
210 return FAILURE;
211 }
212
213 while (getline(&lineptr, &n, fd) > 0) {
214 /* We trash the terminal `\n`. This simplify the display of logs. */
215 if (lineptr[strlen(lineptr) - 1] == '\n') {
216 if (strlen(lineptr) >= 2 && lineptr[strlen(lineptr) - 2] == '\r') {
217 lineptr[strlen(lineptr) - 2] = '\0';
218 } else {
219 lineptr[strlen(lineptr) - 1] = '\0';
220 }
221 }
222 if (parse_line(lineptr) == -1) {
223 fclose(fd);
224 free(lineptr);
225 return FAILURE;
226 }
227 free(lineptr);
228 lineptr = NULL;
229 n = 0;
230 sp_line_no++;
231 }
232 fclose(fd);
233 return SUCCESS;
234} 247}
235 248
236void sp_free_disabled_function(void *data) { 249void sp_free_disabled_function(void *data) {
@@ -292,4 +305,4 @@ void sp_free_ini_entry(void *data) {
292 sp_pcre_free(entry->regexp); 305 sp_pcre_free(entry->regexp);
293 sp_free_zstr(entry->msg); 306 sp_free_zstr(entry->msg);
294 sp_free_zstr(entry->set); 307 sp_free_zstr(entry->set);
295} \ No newline at end of file 308}