From: Alan T. DeKok Date: Fri, 15 Dec 2023 02:20:24 +0000 (-0500) Subject: allow "catch" to have multiple rcodes X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f06e1a62f6527727e5da2e3e341ef61e678431b5;p=thirdparty%2Ffreeradius-server.git allow "catch" to have multiple rcodes --- diff --git a/doc/antora/modules/reference/pages/unlang/catch.adoc b/doc/antora/modules/reference/pages/unlang/catch.adoc index f5c41a60eb7..6f8714bd5a9 100644 --- a/doc/antora/modules/reference/pages/unlang/catch.adoc +++ b/doc/antora/modules/reference/pages/unlang/catch.adoc @@ -6,16 +6,22 @@ try { ... } -catch { +catch [ rcodes ] { [ statements ] } ---- The `catch` statement runs a series of substatements in a block, but only if the previous xref:unlang/try.adoc[try] failed. +[ rcodes ]:: Zero or more module return codes. The return codes can be `disallow`, `fail`, `invalid`, or `reject`. + [ statements ]:: The `unlang` commands which will be executed. A `catch` block can be empty. +If no rcode is given the `catch` statement matches all of the codes listed above. Otherwise, the `catch` statement matches one of the listed rcodes. + +Multiple `catch` statements cam ne given + .Example [source,unlang] @@ -28,7 +34,12 @@ catch { } ---- -There is some overlap in functionality between `try` / xref:unlang/catch.adoc[catch] and xref:unlang/redundant.adoc[redundant]. The main difference (TODO) is that a xref:unlang/catch.adoc[catch] statement can catch specific actions. +There is some overlap in functionality between `try` / xref:unlang/catch.adoc[catch] and xref:unlang/redundant.adoc[redundant]. The main difference is that a xref:unlang/catch.adoc[catch] statement can catch specific failure codes. + +The xref:unlang/redundant.adoc[redundant] statement should be used to run +one of many similar modules. For example, the xref:unlang/redundant.adoc[redundant] statement could be used to choose one of four different `sql` modules in a fail-over fashion. + +In contrast, the `try` / `catch` statements should be used for more complex policies, when the intention is to run one policy, and then do something completely different if a failure occurs. // Copyright (C) 2023 Network RADIUS SAS. Licenced under CC-by-NC 4.0. // This documentation was developed by Network RADIUS SAS. diff --git a/src/lib/server/cf_file.c b/src/lib/server/cf_file.c index 9eee4148878..9dd759e2652 100644 --- a/src/lib/server/cf_file.c +++ b/src/lib/server/cf_file.c @@ -1897,6 +1897,98 @@ alloc_section: return cf_section_to_item(css); } +static CONF_ITEM *process_catch(cf_stack_t *stack) +{ + CONF_SECTION *css; + int argc = 0; + char const *ptr = stack->ptr; + cf_stack_frame_t *frame = &stack->frame[stack->depth]; + CONF_SECTION *parent = frame->current; + char *name2 = NULL; + char *argv[RLM_MODULE_NUMCODES]; + + while (true) { + char const *p; + size_t len; + + fr_skip_whitespace(ptr); + + /* + * We have an open bracket, it's the end of the "catch" statement. + */ + if (*ptr == '{') { + ptr++; + break; + } + + /* + * The arguments have to be short, unquoted words. + */ + p = ptr; + while (isalpha((uint8_t) *ptr)) ptr++; + + len = ptr - p; + if (len > 16) { + ERROR("%s[%d]: Invalid syntax for 'catch' - unknown rcode '%s'", + frame->filename, frame->lineno, p); + return NULL; + } + + if ((*ptr != '{') && !isspace((uint8_t) *ptr)) { + ERROR("%s[%d]: Invalid syntax for 'catch' - unexpected text at '%s'", + frame->filename, frame->lineno, ptr); + return NULL; + } + + if (!name2) { + name2 = talloc_strndup(NULL, p, len); + continue; + } + + if (argc > RLM_MODULE_NUMCODES) { + ERROR("%s[%d]: Invalid syntax for 'catch' - too many arguments at'%s'", + frame->filename, frame->lineno, ptr); + return NULL; + } + + argv[argc++] = talloc_strndup(name2, p, len); + } + + css = cf_section_alloc(parent, parent, "catch", name2); + if (!css) { + talloc_free(name2); + ERROR("%s[%d]: Failed allocating memory for section", + frame->filename, frame->lineno); + return NULL; + } + cf_filename_set(css, frame->filename); + cf_lineno_set(css, frame->lineno); + css->name2_quote = T_BARE_WORD; + css->allow_unlang = 1; + + css->argc = argc; + if (argc) { + int i; + + css->argv = talloc_array(css, char const *, argc); + css->argv_quote = talloc_array(css, fr_token_t, argc); + css->argc = argc; + + for (i = 0; i < argc; i++) { + css->argv[i] = talloc_typed_strdup(css->argv, argv[i]); + css->argv_quote[i] = T_BARE_WORD; + } + + css->argv[argc] = NULL; + } + talloc_free(name2); + + stack->ptr = ptr; + frame->special = css; + + return cf_section_to_item(css); +} + static int add_section_pair(CONF_SECTION **parent, char const **attr, char const *dot, char *buffer, size_t buffer_len, char const *filename, int lineno) { CONF_SECTION *cs; @@ -2035,6 +2127,7 @@ static int add_pair(CONF_SECTION *parent, char const *attr, char const *value, } static fr_table_ptr_sorted_t unlang_keywords[] = { + { L("catch"), (void *) process_catch }, { L("elsif"), (void *) process_if }, { L("if"), (void *) process_if }, { L("map"), (void *) process_map }, diff --git a/src/lib/unlang/compile.c b/src/lib/unlang/compile.c index 7b9188c4fd6..68aa03f988b 100644 --- a/src/lib/unlang/compile.c +++ b/src/lib/unlang/compile.c @@ -2559,10 +2559,16 @@ static unlang_t *compile_transaction(unlang_t *parent, unlang_compile_t *unlang_ .type_name = "unlang_transaction_t", }; + if (cf_section_name2(cs) != NULL) { + cf_log_err(cs, "Unexpected argument to 'transaction' section"); + return NULL; + } + /* * The transaction is empty, ignore it. */ if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE; + if (!transaction_ok(cs)) return NULL; /* @@ -2616,6 +2622,11 @@ static unlang_t *compile_try(unlang_t *parent, unlang_compile_t *unlang_ctx, CON return NULL; } + if (cf_section_name2(cs) != NULL) { + cf_log_err(cs, "Unexpected argument to 'try' section"); + return NULL; + } + next = cf_section_next(cf_item_to_section(cf_parent(cs)), cs); if (!next || (strcmp(cf_section_name1(next), "catch") != 0)) { cf_log_err(cs, "'try' sections must be followed by a 'catch'"); @@ -2631,10 +2642,31 @@ static unlang_t *compile_try(unlang_t *parent, unlang_compile_t *unlang_ctx, CON return compile_children(g, unlang_ctx, true); } +static int catch_argv(CONF_SECTION *cs, unlang_catch_t *ca, char const *name) +{ + int rcode; + + rcode = fr_table_value_by_str(mod_rcode_table, name, -1); + if (rcode < 0) { + cf_log_err(cs, "Unknown rcode '%s'.", name); + return -1; + } + + if (ca->catching[rcode]) { + cf_log_err(cs, "Duplicate rcode '%s'.", name); + return -1; + } + + ca->catching[rcode] = true; + + return 0; +} + static unlang_t *compile_catch(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_SECTION *cs) { unlang_group_t *g; unlang_t *c; + unlang_catch_t *ca; static unlang_ext_t const ext = { .type = UNLANG_TYPE_CATCH, @@ -2648,16 +2680,32 @@ static unlang_t *compile_catch(unlang_t *parent, unlang_compile_t *unlang_ctx, C c = unlang_group_to_generic(g); c->debug_name = c->name = cf_section_name1(cs); + ca = unlang_group_to_catch(g); + /* * No arg2: catch all errors */ if (!cf_section_name2(cs)) { - unlang_catch_t *ca = unlang_group_to_catch(g); - ca->catching[RLM_MODULE_REJECT] = true; ca->catching[RLM_MODULE_FAIL] = true; ca->catching[RLM_MODULE_INVALID] = true; ca->catching[RLM_MODULE_DISALLOW] = true; + + } else { + int i; + char const *name = cf_section_name2(cs); + + if (catch_argv(cs, ca, name) < 0) { + talloc_free(c); + return NULL; + } + + for (i = 0; (name = cf_section_argv(cs, i)) != NULL; i++) { + if (catch_argv(cs, ca, name) < 0) { + talloc_free(c); + return NULL; + } + } } /* diff --git a/src/tests/keywords/try b/src/tests/keywords/try index 48d5ce377ff..d4e20c841e5 100644 --- a/src/tests/keywords/try +++ b/src/tests/keywords/try @@ -11,7 +11,10 @@ try { &bar := "nope" } -catch { +catch disallow { + test_fail +} +catch ok reject fail { if &foo != "hello" { test_fail }