]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
allow "catch" to have multiple rcodes
authorAlan T. DeKok <aland@freeradius.org>
Fri, 15 Dec 2023 02:20:24 +0000 (21:20 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Fri, 15 Dec 2023 02:20:24 +0000 (21:20 -0500)
doc/antora/modules/reference/pages/unlang/catch.adoc
src/lib/server/cf_file.c
src/lib/unlang/compile.c
src/tests/keywords/try

index f5c41a60eb78cdbec7533799039e29bb305b89ac..6f8714bd5a938ac9f6c3984b13bc02413189b2ef 100644 (file)
@@ -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.
index 9eee414887874b6c375e6676fa04bd3b649bc7d3..9dd759e265260c8f24f215c986bef69245a7508f 100644 (file)
@@ -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 },
index 7b9188c4fd66673ea39ab15af149d7bb61d4d440..68aa03f988b153f65db1b9c9ededa78732bc6109 100644 (file)
@@ -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;
+                       }
+               }
        }
 
        /*
index 48d5ce377ff84f33778c7f0f9b8da2d966eb7766..d4e20c841e572872c8b2d8ab16138c51be3d4132 100644 (file)
@@ -11,7 +11,10 @@ try {
 
        &bar := "nope"
 }
-catch {
+catch disallow {
+       test_fail
+}
+catch ok reject fail {
        if &foo != "hello" {
                test_fail
        }