1 #include "php_snuffleupagus.h"
2
parse_enable(char * line,bool * restrict retval,bool * restrict simulation)3 static int parse_enable(char *line, bool *restrict retval,
4 bool *restrict simulation) {
5 bool enable = false, disable = false;
6 sp_config_functions sp_config_funcs[] = {
7 {parse_empty, SP_TOKEN_ENABLE, &(enable)},
8 {parse_empty, SP_TOKEN_DISABLE, &(disable)},
9 {parse_empty, SP_TOKEN_SIMULATION, simulation},
10 {0, 0, 0}};
11
12 int ret = parse_keywords(sp_config_funcs, line);
13
14 if (0 != ret) {
15 return ret;
16 }
17
18 if (!(enable ^ disable)) {
19 sp_log_err("config", "A rule can't be enabled and disabled on line %zu",
20 sp_line_no);
21 return -1;
22 }
23
24 *retval = enable;
25
26 return ret;
27 }
28
parse_session(char * line)29 int parse_session(char *line) {
30 sp_config_session *session = pecalloc(sizeof(sp_config_session), 1, 0);
31
32 sp_config_functions sp_config_funcs_session_encryption[] = {
33 {parse_empty, SP_TOKEN_ENCRYPT, &(session->encrypt)},
34 {parse_empty, SP_TOKEN_SIMULATION, &(session->simulation)},
35 {0, 0, 0}};
36 int ret = parse_keywords(sp_config_funcs_session_encryption, line);
37 if (0 != ret) {
38 return ret;
39 }
40
41 #if (!HAVE_PHP_SESSION || defined(COMPILE_DL_SESSION))
42 sp_log_err(
43 "config",
44 "You're trying to use the session cookie encryption feature "
45 "on line %zu without having session support statically built into PHP. "
46 "This isn't supported, see "
47 "https://github.com/jvoisin/snuffleupagus/issues/278 for details.",
48 sp_line_no);
49 pefree(session, 0);
50 return -1;
51 #endif
52
53 if (session->encrypt) {
54 if (0 == (SNUFFLEUPAGUS_G(config).config_snuffleupagus->cookies_env_var)) {
55 sp_log_err(
56 "config",
57 "You're trying to use the session cookie encryption feature "
58 "on line %zu without having set the `.cookie_env_var` option in"
59 "`sp.global`: please set it first",
60 sp_line_no);
61 pefree(session, 0);
62 return -1;
63 } else if (0 ==
64 (SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key)) {
65 sp_log_err("config",
66 "You're trying to use the session cookie encryption feature "
67 "on line %zu without having set the `.secret_key` option in"
68 "`sp.global`: please set it first",
69 sp_line_no);
70 pefree(session, 0);
71 return -1;
72 }
73 }
74
75 SNUFFLEUPAGUS_G(config).config_session->encrypt = session->encrypt;
76 SNUFFLEUPAGUS_G(config).config_session->simulation = session->simulation;
77 pefree(session, 0);
78 return ret;
79 }
80
parse_random(char * line)81 int parse_random(char *line) {
82 return parse_enable(line, &(SNUFFLEUPAGUS_G(config).config_random->enable),
83 NULL);
84 }
85
parse_log_media(char * line)86 int parse_log_media(char *line) {
87 size_t consumed = 0;
88 zend_string *value =
89 get_param(&consumed, line, SP_TYPE_STR, SP_TOKEN_LOG_MEDIA);
90
91 if (value) {
92 if (!strcmp(ZSTR_VAL(value), "php")) {
93 SNUFFLEUPAGUS_G(config).log_media = SP_ZEND;
94 return 0;
95 } else if (!strcmp(ZSTR_VAL(value), "syslog")) {
96 SNUFFLEUPAGUS_G(config).log_media = SP_SYSLOG;
97 return 0;
98 }
99 }
100 sp_log_err("config", "%s) only supports 'syslog' or 'php', on line %zu",
101 SP_TOKEN_LOG_MEDIA, sp_line_no);
102 return -1;
103 }
104
parse_sloppy_comparison(char * line)105 int parse_sloppy_comparison(char *line) {
106 return parse_enable(line, &(SNUFFLEUPAGUS_G(config).config_sloppy->enable),
107 NULL);
108 }
109
parse_disable_xxe(char * line)110 int parse_disable_xxe(char *line) {
111 return parse_enable(
112 line, &(SNUFFLEUPAGUS_G(config).config_disable_xxe->enable), NULL);
113 }
114
parse_auto_cookie_secure(char * line)115 int parse_auto_cookie_secure(char *line) {
116 return parse_enable(
117 line, &(SNUFFLEUPAGUS_G(config).config_auto_cookie_secure->enable), NULL);
118 }
119
parse_global_strict(char * line)120 int parse_global_strict(char *line) {
121 return parse_enable(
122 line, &(SNUFFLEUPAGUS_G(config).config_global_strict->enable), NULL);
123 }
124
parse_unserialize(char * line)125 int parse_unserialize(char *line) {
126 bool enable = false, disable = false;
127 sp_config_unserialize *unserialize =
128 SNUFFLEUPAGUS_G(config).config_unserialize;
129
130 sp_config_functions sp_config_funcs[] = {
131 {parse_empty, SP_TOKEN_ENABLE, &(enable)},
132 {parse_empty, SP_TOKEN_DISABLE, &(disable)},
133 {parse_empty, SP_TOKEN_SIMULATION, &(unserialize->simulation)},
134 {parse_str, SP_TOKEN_DUMP, &(unserialize->dump)},
135 {0, 0, 0}};
136
137 unserialize->textual_representation = zend_string_init(line, strlen(line), 1);
138
139 int ret = parse_keywords(sp_config_funcs, line);
140 if (0 != ret) {
141 return ret;
142 }
143
144 if (!(enable ^ disable)) {
145 sp_log_err("config", "A rule can't be enabled and disabled on line %zu",
146 sp_line_no);
147 return -1;
148 }
149
150 SNUFFLEUPAGUS_G(config).config_unserialize->enable = enable;
151
152 return ret;
153 }
154
parse_readonly_exec(char * line)155 int parse_readonly_exec(char *line) {
156 bool enable = false, disable = false;
157 sp_config_readonly_exec *readonly_exec =
158 SNUFFLEUPAGUS_G(config).config_readonly_exec;
159
160 sp_config_functions sp_config_funcs[] = {
161 {parse_empty, SP_TOKEN_ENABLE, &(enable)},
162 {parse_empty, SP_TOKEN_DISABLE, &(disable)},
163 {parse_empty, SP_TOKEN_SIMULATION, &(readonly_exec->simulation)},
164 {parse_str, SP_TOKEN_DUMP, &(readonly_exec->dump)},
165 {0, 0, 0}};
166
167 readonly_exec->textual_representation =
168 zend_string_init(line, strlen(line), 1);
169 int ret = parse_keywords(sp_config_funcs, line);
170
171 if (0 != ret) {
172 return ret;
173 }
174
175 if (!(enable ^ disable)) {
176 sp_log_err("config", "A rule can't be enabled and disabled on line %zu",
177 sp_line_no);
178 return -1;
179 }
180
181 SNUFFLEUPAGUS_G(config).config_readonly_exec->enable = enable;
182
183 return ret;
184 }
185
parse_global(char * line)186 int parse_global(char *line) {
187 sp_config_functions sp_config_funcs_global[] = {
188 {parse_str, SP_TOKEN_ENCRYPTION_KEY,
189 &(SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key)},
190 {parse_str, SP_TOKEN_ENV_VAR,
191 &(SNUFFLEUPAGUS_G(config).config_snuffleupagus->cookies_env_var)},
192 {0, 0, 0}};
193 return parse_keywords(sp_config_funcs_global, line);
194 }
195
parse_eval_filter_conf(char * line,sp_list_node ** list)196 static int parse_eval_filter_conf(char *line, sp_list_node **list) {
197 sp_config_eval *eval = SNUFFLEUPAGUS_G(config).config_eval;
198
199 sp_config_functions sp_config_funcs[] = {
200 {parse_list, SP_TOKEN_LIST, list},
201 {parse_empty, SP_TOKEN_SIMULATION,
202 &(SNUFFLEUPAGUS_G(config).config_eval->simulation)},
203 {parse_str, SP_TOKEN_DUMP, &(SNUFFLEUPAGUS_G(config).config_eval->dump)},
204 {0, 0, 0}};
205
206 eval->textual_representation = zend_string_init(line, strlen(line), 1);
207
208 int ret = parse_keywords(sp_config_funcs, line);
209 if (0 != ret) {
210 return ret;
211 }
212
213 return SUCCESS;
214 }
215
parse_wrapper_whitelist(char * line)216 int parse_wrapper_whitelist(char *line) {
217 SNUFFLEUPAGUS_G(config).config_wrapper->enabled = true;
218 sp_config_functions sp_config_funcs[] = {
219 {parse_list, SP_TOKEN_LIST,
220 &SNUFFLEUPAGUS_G(config).config_wrapper->whitelist},
221 {0, 0, 0}};
222 int ret = parse_keywords(sp_config_funcs, line);
223 if (0 != ret) {
224 return ret;
225 }
226 return SUCCESS;
227 }
228
parse_eval_blacklist(char * line)229 int parse_eval_blacklist(char *line) {
230 return parse_eval_filter_conf(
231 line, &SNUFFLEUPAGUS_G(config).config_eval->blacklist);
232 }
233
parse_eval_whitelist(char * line)234 int parse_eval_whitelist(char *line) {
235 return parse_eval_filter_conf(
236 line, &SNUFFLEUPAGUS_G(config).config_eval->whitelist);
237 }
238
parse_cookie(char * line)239 int parse_cookie(char *line) {
240 int ret = 0;
241 zend_string *samesite = NULL;
242 sp_cookie *cookie = pecalloc(sizeof(sp_cookie), 1, 1);
243
244 sp_config_functions sp_config_funcs_cookie_encryption[] = {
245 {parse_str, SP_TOKEN_NAME, &(cookie->name)},
246 {parse_regexp, SP_TOKEN_NAME_REGEXP, &(cookie->name_r)},
247 {parse_str, SP_TOKEN_SAMESITE, &samesite},
248 {parse_empty, SP_TOKEN_ENCRYPT, &cookie->encrypt},
249 {parse_empty, SP_TOKEN_SIMULATION, &cookie->simulation},
250 {0, 0, 0}};
251
252 ret = parse_keywords(sp_config_funcs_cookie_encryption, line);
253 if (0 != ret) {
254 return ret;
255 }
256
257 if (cookie->encrypt) {
258 if (0 == (SNUFFLEUPAGUS_G(config).config_snuffleupagus->cookies_env_var)) {
259 sp_log_err(
260 "config",
261 "You're trying to use the cookie encryption feature"
262 "on line %zu without having set the `.cookie_env_var` option in"
263 "`sp.global`: please set it first",
264 sp_line_no);
265 return -1;
266 } else if (0 ==
267 (SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key)) {
268 sp_log_err(
269 "config",
270 "You're trying to use the cookie encryption feature"
271 "on line %zu without having set the `.encryption_key` option in"
272 "`sp.global`: please set it first",
273 sp_line_no);
274 return -1;
275 }
276 } else if (!samesite) {
277 sp_log_err("config",
278 "You must specify a at least one action to a cookie on line "
279 "%zu",
280 sp_line_no);
281 return -1;
282 }
283 if ((!cookie->name || 0 == ZSTR_LEN(cookie->name)) && !cookie->name_r) {
284 sp_log_err("config",
285 "You must specify a cookie name/regexp on line "
286 "%zu",
287 sp_line_no);
288 return -1;
289 }
290 if (cookie->name && cookie->name_r) {
291 sp_log_err("config",
292 "name and name_r are mutually exclusive on line "
293 "%zu",
294 sp_line_no);
295 return -1;
296 }
297 if (samesite) {
298 if (zend_string_equals_literal_ci(samesite, SP_TOKEN_SAMESITE_LAX)) {
299 cookie->samesite = lax;
300 } else if (zend_string_equals_literal_ci(samesite,
301 SP_TOKEN_SAMESITE_STRICT)) {
302 cookie->samesite = strict;
303 } else {
304 sp_log_err(
305 "config",
306 "%s is an invalid value to samesite (expected %s or %s) on line "
307 "%zu",
308 ZSTR_VAL(samesite), SP_TOKEN_SAMESITE_LAX, SP_TOKEN_SAMESITE_STRICT,
309 sp_line_no);
310 return -1;
311 }
312 }
313 SNUFFLEUPAGUS_G(config).config_cookie->cookies =
314 sp_list_insert(SNUFFLEUPAGUS_G(config).config_cookie->cookies, cookie);
315 return SUCCESS;
316 }
317
add_df_to_hashtable(HashTable * ht,sp_disabled_function * df)318 int add_df_to_hashtable(HashTable *ht, sp_disabled_function *df) {
319 zval *list = zend_hash_find(ht, df->function);
320
321 if (NULL == list) {
322 zend_hash_add_ptr(ht, df->function, sp_list_insert(NULL, df));
323 } else {
324 Z_PTR_P(list) = sp_list_insert(Z_PTR_P(list), df);
325 }
326 return SUCCESS;
327 }
328
parse_disabled_functions(char * line)329 int parse_disabled_functions(char *line) {
330 int ret = 0;
331 bool enable = true, disable = false, allow = false, drop = false;
332 zend_string *pos = NULL, *var = NULL, *param = NULL;
333 zend_string *line_number = NULL;
334 sp_disabled_function *df = pecalloc(sizeof(*df), 1, 1);
335 df->pos = -1;
336
337 sp_config_functions sp_config_funcs_disabled_functions[] = {
338 {parse_empty, SP_TOKEN_ENABLE, &(enable)},
339 {parse_empty, SP_TOKEN_DISABLE, &(disable)},
340 {parse_str, SP_TOKEN_ALIAS, &(df->alias)},
341 {parse_empty, SP_TOKEN_SIMULATION, &(df->simulation)},
342 {parse_str, SP_TOKEN_FILENAME, &(df->filename)},
343 {parse_regexp, SP_TOKEN_FILENAME_REGEXP, &(df->r_filename)},
344 {parse_str, SP_TOKEN_FUNCTION, &(df->function)},
345 {parse_regexp, SP_TOKEN_FUNCTION_REGEXP, &(df->r_function)},
346 {parse_str, SP_TOKEN_DUMP, &(df->dump)},
347 {parse_empty, SP_TOKEN_ALLOW, &(allow)},
348 {parse_empty, SP_TOKEN_DROP, &(drop)},
349 {parse_str, SP_TOKEN_HASH, &(df->hash)},
350 {parse_str, SP_TOKEN_PARAM, &(param)},
351 {parse_regexp, SP_TOKEN_VALUE_REGEXP, &(df->r_value)},
352 {parse_str, SP_TOKEN_VALUE, &(df->value)},
353 {parse_str, SP_TOKEN_KEY, &(df->key)},
354 {parse_regexp, SP_TOKEN_KEY_REGEXP, &(df->r_key)},
355 {parse_regexp, SP_TOKEN_PARAM_REGEXP, &(df->r_param)},
356 {parse_php_type, SP_TOKEN_PARAM_TYPE, &(df->param_type)},
357 {parse_str, SP_TOKEN_RET, &(df->ret)},
358 {parse_cidr, SP_TOKEN_CIDR, &(df->cidr)},
359 {parse_regexp, SP_TOKEN_RET_REGEXP, &(df->r_ret)},
360 {parse_php_type, SP_TOKEN_RET_TYPE, &(df->ret_type)},
361 {parse_str, SP_TOKEN_LOCAL_VAR, &(var)},
362 {parse_str, SP_TOKEN_VALUE_ARG_POS, &(pos)},
363 {parse_str, SP_TOKEN_LINE_NUMBER, &(line_number)},
364 {0, 0, 0}};
365
366 ret = parse_keywords(sp_config_funcs_disabled_functions, line);
367
368 if (0 != ret) {
369 return ret;
370 }
371
372 #define MUTUALLY_EXCLUSIVE(X, Y, STR1, STR2) \
373 if (X && Y) { \
374 sp_log_err("config", \
375 "Invalid configuration line: 'sp.disabled_functions%s': " \
376 "'.%s' and '.%s' are mutually exclusive on line %zu", \
377 line, STR1, STR2, sp_line_no); \
378 return -1; \
379 }
380
381 MUTUALLY_EXCLUSIVE(df->r_value, df->value, "r_value", "value");
382 MUTUALLY_EXCLUSIVE(df->r_function, df->function, "r_function", "function");
383 MUTUALLY_EXCLUSIVE(df->r_filename, df->filename, "r_filename", "filename");
384 MUTUALLY_EXCLUSIVE(df->r_ret, df->ret, "r_ret", "ret");
385 MUTUALLY_EXCLUSIVE(df->r_key, df->key, "r_key", "key");
386 #undef MUTUALLY_EXCLUSIVE
387
388 if (1 <
389 ((df->r_param ? 1 : 0) + (param ? 1 : 0) + ((-1 != df->pos) ? 1 : 0))) {
390 sp_log_err(
391 "config",
392 "Invalid configuration line: 'sp.disabled_functions%s':"
393 "'.r_param', '.param' and '.pos' are mutually exclusive on line %zu",
394 line, sp_line_no);
395 return -1;
396 } else if ((df->r_key || df->key) && (df->r_value || df->value)) {
397 sp_log_err("config",
398 "Invalid configuration line: 'sp.disabled_functions%s':"
399 "`key` and `value` are mutually exclusive on line %zu",
400 line, sp_line_no);
401 return -1;
402 } else if ((df->r_ret || df->ret || df->ret_type) && (df->r_param || param)) {
403 sp_log_err("config",
404 "Invalid configuration line: 'sp.disabled_functions%s':"
405 "`ret` and `param` are mutually exclusive on line %zu",
406 line, sp_line_no);
407 return -1;
408 } else if ((df->r_ret || df->ret || df->ret_type) && (var)) {
409 sp_log_err("config",
410 "Invalid configuration line: 'sp.disabled_functions%s':"
411 "`ret` and `var` are mutually exclusive on line %zu",
412 line, sp_line_no);
413 return -1;
414 } else if ((df->r_ret || df->ret || df->ret_type) &&
415 (df->value || df->r_value)) {
416 sp_log_err("config",
417 "Invalid configuration line: 'sp.disabled_functions%s':"
418 "`ret` and `value` are mutually exclusive on line %zu",
419 line, sp_line_no);
420 return -1;
421 } else if (!(df->r_function || df->function)) {
422 sp_log_err("config",
423 "Invalid configuration line: 'sp.disabled_functions%s':"
424 " must take a function name on line %zu",
425 line, sp_line_no);
426 return -1;
427 } else if (df->filename && (*ZSTR_VAL(df->filename) != '/') &&
428 (0 !=
429 strncmp(ZSTR_VAL(df->filename), "phar://", strlen("phar://")))) {
430 sp_log_err(
431 "config",
432 "Invalid configuration line: 'sp.disabled_functions%s':"
433 "'.filename' must be an absolute path or a phar archive on line %zu",
434 line, sp_line_no);
435 return -1;
436 } else if (!(allow ^ drop)) {
437 sp_log_err("config",
438 "Invalid configuration line: 'sp.disabled_functions%s': The "
439 "rule must either be a `drop` or `allow` one on line %zu",
440 line, sp_line_no);
441 return -1;
442 }
443
444 if (pos) {
445 errno = 0;
446 char *endptr;
447 df->pos = (int)strtol(ZSTR_VAL(pos), &endptr, 10);
448 if (errno != 0 || endptr == ZSTR_VAL(pos)) {
449 sp_log_err("config", "Failed to parse arg '%s' of `pos` on line %zu",
450 ZSTR_VAL(pos), sp_line_no);
451 return -1;
452 }
453 }
454
455 if (line_number) {
456 errno = 0;
457 char *endptr;
458 df->line = (unsigned int)strtoul(ZSTR_VAL(line_number), &endptr, 10);
459 if (errno != 0 || endptr == ZSTR_VAL(line_number)) {
460 sp_log_err("config", "Failed to parse arg '%s' of `line` on line %zu",
461 ZSTR_VAL(line_number), sp_line_no);
462 return -1;
463 }
464 }
465 df->allow = allow;
466 df->textual_representation = zend_string_init(line, strlen(line), 1);
467
468 if (df->function) {
469 df->functions_list = parse_functions_list(ZSTR_VAL(df->function));
470 }
471
472 if (param) {
473 if (ZSTR_LEN(param) > 0 && ZSTR_VAL(param)[0] != '$') {
474 /* This is an ugly hack. We're prefixing with a `$` because otherwise
475 * the parser treats this as a constant.
476 * FIXME: Remove this, and improve our (weird) parser. */
477 char *new = pecalloc(ZSTR_LEN(param) + 2, 1, 1);
478 new[0] = '$';
479 memcpy(new + 1, ZSTR_VAL(param), ZSTR_LEN(param));
480 df->param = sp_parse_var(new);
481 free(new);
482 } else {
483 df->param = sp_parse_var(ZSTR_VAL(param));
484 }
485 if (!df->param) {
486 sp_log_err("config", "Invalid value '%s' for `param` on line %zu",
487 ZSTR_VAL(param), sp_line_no);
488 return -1;
489 }
490 }
491
492 if (var) {
493 if (ZSTR_LEN(var)) {
494 df->var = sp_parse_var(ZSTR_VAL(var));
495 if (!df->var) {
496 sp_log_err("config", "Invalid value '%s' for `var` on line %zu",
497 ZSTR_VAL(var), sp_line_no);
498 return -1;
499 }
500 } else {
501 sp_log_err("config", "Empty value in `var` on line %zu", sp_line_no);
502 return -1;
503 }
504 }
505
506 if (true == disable) {
507 return ret;
508 }
509
510 if (df->function && zend_string_equals_literal(df->function, "print")) {
511 zend_string_release(df->function);
512 df->function = zend_string_init("echo", sizeof("echo") - 1, 1);
513 }
514
515 if (df->function && !df->functions_list) {
516 if (df->ret || df->r_ret || df->ret_type) {
517 add_df_to_hashtable(SNUFFLEUPAGUS_G(config).config_disabled_functions_ret,
518 df);
519 } else {
520 add_df_to_hashtable(SNUFFLEUPAGUS_G(config).config_disabled_functions,
521 df);
522 }
523 } else {
524 if (df->ret || df->r_ret || df->ret_type) {
525 SNUFFLEUPAGUS_G(config)
526 .config_disabled_functions_reg_ret->disabled_functions =
527 sp_list_insert(
528 SNUFFLEUPAGUS_G(config)
529 .config_disabled_functions_reg_ret->disabled_functions,
530 df);
531 } else {
532 SNUFFLEUPAGUS_G(config)
533 .config_disabled_functions_reg->disabled_functions =
534 sp_list_insert(SNUFFLEUPAGUS_G(config)
535 .config_disabled_functions_reg->disabled_functions,
536 df);
537 }
538 }
539 return ret;
540 }
541
parse_upload_validation(char * line)542 int parse_upload_validation(char *line) {
543 bool disable = false, enable = false;
544 sp_config_functions sp_config_funcs_upload_validation[] = {
545 {parse_str, SP_TOKEN_UPLOAD_SCRIPT,
546 &(SNUFFLEUPAGUS_G(config).config_upload_validation->script)},
547 {parse_empty, SP_TOKEN_SIMULATION,
548 &(SNUFFLEUPAGUS_G(config).config_upload_validation->simulation)},
549 {parse_empty, SP_TOKEN_ENABLE, &(enable)},
550 {parse_empty, SP_TOKEN_DISABLE, &(disable)},
551 {0, 0, 0}};
552
553 int ret = parse_keywords(sp_config_funcs_upload_validation, line);
554
555 if (0 != ret) {
556 return ret;
557 }
558
559 if (!(enable ^ disable)) {
560 sp_log_err("config", "A rule can't be enabled and disabled on line %zu",
561 sp_line_no);
562 return -1;
563 }
564 SNUFFLEUPAGUS_G(config).config_upload_validation->enable = enable;
565
566 zend_string const *script =
567 SNUFFLEUPAGUS_G(config).config_upload_validation->script;
568
569 if (!script) {
570 sp_log_err("config",
571 "The `script` directive is mandatory in '%s' on line %zu", line,
572 sp_line_no);
573 return -1;
574 } else if (-1 == access(ZSTR_VAL(script), F_OK)) {
575 sp_log_err("config", "The `script` (%s) doesn't exist on line %zu",
576 ZSTR_VAL(script), sp_line_no);
577 return -1;
578 } else if (-1 == access(ZSTR_VAL(script), X_OK)) {
579 sp_log_err("config", "The `script` (%s) isn't executable on line %zu",
580 ZSTR_VAL(script), sp_line_no);
581 return -1;
582 }
583
584 return ret;
585 }
586