diff --git a/raddb/mods-available/always b/raddb/mods-available/always index de3f13089d89..92e892768c61 100644 --- a/raddb/mods-available/always +++ b/raddb/mods-available/always @@ -32,6 +32,26 @@ # # mpp = # +# An xlat based on the instance name can be called to change the status +# returned by the instance. +# +# Force the module status to be alive or dead: +# +# %{db_status:alive} +# %{db_status:dead} +# +# Update the rcode returned by an alive module (a dead module returns fail): +# +# %{db_status:ok} +# %{db_status:fail} +# %{db_status:notfound} +# ... +# +# The above xlats expand to the current status of the module. To fetch the +# current status without affecting it call the xlat with an empty argument: +# +# %{db_status:} +# always reject { rcode = reject } diff --git a/src/modules/rlm_always/rlm_always.c b/src/modules/rlm_always/rlm_always.c index 6a75164a89db..228bff971062 100644 --- a/src/modules/rlm_always/rlm_always.c +++ b/src/modules/rlm_always/rlm_always.c @@ -26,6 +26,7 @@ RCSID("$Id$") #include #include +#include #include /* @@ -52,12 +53,94 @@ static const CONF_PARSER module_config[] = { CONF_PARSER_TERMINATOR }; +/** Set module status or rcode + * + * Look ma, no locks... + * + * Example: "%{db_status:dead}" + */ +static ssize_t always_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen) +{ + CONF_SECTION *cs; + module_instance_t *mi; + rlm_always_t *inst = instance; + char const *status = fmt; + char const *p; + size_t len; + + cs = cf_section_find("modules"); + if (!cs) return -1; + + mi = module_find(cs, inst->name); + if (!mi) { + RERROR("Can't find the module that registered this xlat: %s", inst->name); + return -1; + } + + /* + * Expand to the existing status + */ + p = "alive"; + if (mi->force) { + p = fr_int2str(mod_rcode_table, mi->code, ""); + } + + len = strlen(p); + if (outlen < len) { + RWARN("Output is too short!"); + *out = '\0'; + } else { + strncpy(out, p, outlen); + } + + if (*fmt == '\0') goto done; + + /* + * Set the module status + */ + if (strcmp(status, "alive") == 0) { + mi->force = false; + + } else if (strcmp(status, "dead") == 0) { + mi->code = RLM_MODULE_FAIL; + mi->force = true; + + } else { + int rcode; + + rcode = fr_str2int(mod_rcode_table, status, -1); + if (rcode < 0) { + RWARN("Unknown status \"%s\"", status); + return -1; + } + + mi->code = rcode; + mi->force = true; + + } + +done: + return strlen(out); +} + +static int mod_bootstrap(CONF_SECTION *conf, void *instance) +{ + rlm_always_t *inst = instance; + + inst->name = cf_section_name2(conf); + if (!inst->name) { + inst->name = cf_section_name1(conf); + } + + xlat_register(inst->name, always_xlat, NULL, inst); + + return 0; +} + static int mod_instantiate(CONF_SECTION *conf, void *instance) { rlm_always_t *inst = instance; - inst->name = cf_section_name1(conf); - if (!inst->name) inst->name = cf_section_name2(conf); /* * Convert the rcode string to an int */ @@ -130,6 +213,7 @@ module_t rlm_always = { .type = RLM_TYPE_HUP_SAFE, .inst_size = sizeof(rlm_always_t), .config = module_config, + .bootstrap = mod_bootstrap, .instantiate = mod_instantiate, .methods = { [MOD_AUTHENTICATE] = mod_always_return, diff --git a/src/tests/modules/always/module.conf b/src/tests/modules/always/module.conf index ce784b16ee81..39995e501ac9 100644 --- a/src/tests/modules/always/module.conf +++ b/src/tests/modules/always/module.conf @@ -1,3 +1,7 @@ always my_reject { rcode = reject -} \ No newline at end of file +} + +always db_status { + rcode = ok +} diff --git a/src/tests/modules/always/set_rcode.unlang b/src/tests/modules/always/set_rcode.unlang new file mode 100644 index 000000000000..faaed287c597 --- /dev/null +++ b/src/tests/modules/always/set_rcode.unlang @@ -0,0 +1,44 @@ +# +# Set status to "notfound". xlat should expand to previous status, "alive" +# +if ("%{db_status:notfound}" != "alive") { + update reply { + Filter-Id += "failed" + } +} + + +# +# Verify that the status was changed +# +db_status +if (!notfound) { + update reply { + Filter-Id += "failed" + } +} + + +# +# Fetch status using xlat without setting the status +# +if ("%{db_status:}" != "notfound") { + update reply { + Filter-Id += "failed" + } +} + + +# +# Verify that the status did not change +# +db_status +if (notfound) { + update reply { + Filter-Id += "success" + } +} + +update control { + Cleartext-Password := "hello" +} diff --git a/src/tests/modules/always/set_status_dead.unlang b/src/tests/modules/always/set_status_dead.unlang new file mode 100644 index 000000000000..6b29edea8648 --- /dev/null +++ b/src/tests/modules/always/set_status_dead.unlang @@ -0,0 +1,18 @@ +# +# Set the module status to dead, call it and check that it fails +# +%{db_status:dead} + +db_status { + fail = 1 +} + +if (fail) { + update reply { + Filter-Id := "success" + } +} + +update control { + Cleartext-Password := "hello" +} diff --git a/src/tests/modules/always/set_status_revive.unlang b/src/tests/modules/always/set_status_revive.unlang new file mode 100644 index 000000000000..3e71d3977f45 --- /dev/null +++ b/src/tests/modules/always/set_status_revive.unlang @@ -0,0 +1,28 @@ +# +# Fail a module... +# +%{db_status:dead} +db_status { + fail = 1 +} +if (!fail) { + update reply { + Filter-Id += "failed" + } +} + + +# +# ... Now revive it +# +%{db_status:alive} +db_status +if (ok) { + update reply { + Filter-Id += "success" + } +} + +update control { + Cleartext-Password := "hello" +}