From: Arran Cudbard-Bell Date: Thu, 1 Jan 2026 18:23:35 +0000 (+0000) Subject: Add support for backticked expansions in legacy file formats, in certain situations X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2c1baeee54c7fabda45cdc751779c3ad0be074ba;p=thirdparty%2Ffreeradius-server.git Add support for backticked expansions in legacy file formats, in certain situations This lets us add dynamic values in certain situations. Output from exec is limited to a single line (for now). --- diff --git a/src/bin/dhcpclient.c b/src/bin/dhcpclient.c index 31f49372f8f..289b5bb5c69 100644 --- a/src/bin/dhcpclient.c +++ b/src/bin/dhcpclient.c @@ -159,7 +159,7 @@ static int request_init(fr_packet_t **out, fr_pair_list_t *packet_vps, char cons /* * Read the VP's. */ - if (fr_pair_list_afrom_file(packet, dict_dhcpv4, packet_vps, fp, &filedone) < 0) { + if (fr_pair_list_afrom_file(packet, dict_dhcpv4, packet_vps, fp, &filedone, true) < 0) { fr_perror("dhcpclient"); fr_packet_free(&packet); if (fp != stdin) fclose(fp); diff --git a/src/bin/radclient-ng.c b/src/bin/radclient-ng.c index 645d89cc913..377499c0222 100644 --- a/src/bin/radclient-ng.c +++ b/src/bin/radclient-ng.c @@ -422,7 +422,7 @@ static int coa_init(rc_request_t *parent, FILE *coa_reply, char const *reply_fil * Read the reply VP's. */ if (fr_pair_list_afrom_file(request, dict_radius, - &request->reply_pairs, coa_reply, coa_reply_done) < 0) { + &request->reply_pairs, coa_reply, coa_reply_done, true) < 0) { REDEBUG("Error parsing \"%s\"", reply_filename); error: talloc_free(request); @@ -440,7 +440,7 @@ static int coa_init(rc_request_t *parent, FILE *coa_reply, char const *reply_fil */ if (coa_filter) { if (fr_pair_list_afrom_file(request, dict_radius, - &request->filter, coa_filter, coa_filter_done) < 0) { + &request->filter, coa_filter, coa_filter_done, true) < 0) { REDEBUG("Error parsing \"%s\"", filter_filename); goto error; } @@ -577,7 +577,7 @@ static int radclient_init(TALLOC_CTX *ctx, rc_file_pair_t *files) * Read the request VP's. */ if (fr_pair_list_afrom_file(request, dict_radius, - &request->request_pairs, packets, &packets_done) < 0) { + &request->request_pairs, packets, &packets_done, true) < 0) { char const *input; if ((files->packets[0] == '-') && (files->packets[1] == '\0')) { @@ -606,7 +606,7 @@ static int radclient_init(TALLOC_CTX *ctx, rc_file_pair_t *files) bool filters_done; if (fr_pair_list_afrom_file(request, dict_radius, - &request->filter, filters, &filters_done) < 0) { + &request->filter, filters, &filters_done, true) < 0) { REDEBUG("Error parsing \"%s\"", files->filters); goto error; } diff --git a/src/bin/radclient.c b/src/bin/radclient.c index 33c3f489a11..e68a38be0cb 100644 --- a/src/bin/radclient.c +++ b/src/bin/radclient.c @@ -406,7 +406,7 @@ static int coa_init(rc_request_t *parent, FILE *coa_reply, char const *reply_fil * Read the reply VP's. */ if (fr_pair_list_afrom_file(request, dict_radius, - &request->reply_pairs, coa_reply, coa_reply_done) < 0) { + &request->reply_pairs, coa_reply, coa_reply_done, true) < 0) { REDEBUG("Error parsing \"%s\"", reply_filename); error: talloc_free(request); @@ -424,7 +424,7 @@ static int coa_init(rc_request_t *parent, FILE *coa_reply, char const *reply_fil */ if (coa_filter) { if (fr_pair_list_afrom_file(request, dict_radius, - &request->filter, coa_filter, coa_filter_done) < 0) { + &request->filter, coa_filter, coa_filter_done, true) < 0) { REDEBUG("Error parsing \"%s\"", filter_filename); goto error; } @@ -563,7 +563,7 @@ static int radclient_init(TALLOC_CTX *ctx, rc_file_pair_t *files) * Read the request VP's. */ if (fr_pair_list_afrom_file(request, dict_radius, - &request->request_pairs, packets, &packets_done) < 0) { + &request->request_pairs, packets, &packets_done, true) < 0) { char const *input; if ((files->packets[0] == '-') && (files->packets[1] == '\0')) { @@ -592,7 +592,7 @@ static int radclient_init(TALLOC_CTX *ctx, rc_file_pair_t *files) bool filters_done; if (fr_pair_list_afrom_file(request, dict_radius, - &request->filter, filters, &filters_done) < 0) { + &request->filter, filters, &filters_done, true) < 0) { REDEBUG("Error parsing \"%s\"", files->filters); goto error; } diff --git a/src/bin/radsniff.c b/src/bin/radsniff.c index da9c465c279..0b9616b78f0 100644 --- a/src/bin/radsniff.c +++ b/src/bin/radsniff.c @@ -2108,6 +2108,7 @@ static int rs_build_filter(fr_pair_list_t *out, char const *filter) .dict = dict_radius, .internal = fr_dict_internal(), .allow_compare = true, + .allow_exec = true }; relative = (fr_pair_parse_t) { }; diff --git a/src/bin/unit_test_attribute.c b/src/bin/unit_test_attribute.c index 912364efc29..6ee306a9edd 100644 --- a/src/bin/unit_test_attribute.c +++ b/src/bin/unit_test_attribute.c @@ -1260,7 +1260,7 @@ static size_t command_allow_unresolved(command_result_t *result, command_file_ct static size_t command_attr_children(command_result_t *result, command_file_ctx_t *cc, UNUSED char *data, UNUSED size_t data_used, char *in, size_t inlen) { - fr_hash_table_t *namespace; + fr_hash_table_t *namespace; fr_hash_iter_t iter; fr_dict_attr_t const *ref; fr_sbuff_t out = FR_SBUFF_OUT(data, COMMAND_OUTPUT_MAX); @@ -2141,6 +2141,7 @@ static size_t command_encode_pair(command_result_t *result, command_file_ctx_t * .list = &head, .dict = cc->tmpl_rules.attr.namespace->dict, .internal = fr_dict_internal(), + .allow_exec = true }; relative = (fr_pair_parse_t) { }; @@ -2295,7 +2296,7 @@ static size_t command_read_file(command_result_t *result, command_file_ctx_t *cc } fr_pair_list_init(&head); - slen = fr_pair_list_afrom_file(cc->tmp_ctx, cc->tmpl_rules.attr.dict_def, &head, fp, &done); + slen = fr_pair_list_afrom_file(cc->tmp_ctx, cc->tmpl_rules.attr.dict_def, &head, fp, &done, true); fclose(fp); if (slen < 0) { RETURN_OK_WITH_ERROR(); @@ -2362,6 +2363,7 @@ static size_t command_encode_proto(command_result_t *result, command_file_ctx_t .list = &head, .dict = cc->tmpl_rules.attr.namespace->dict, .internal = fr_dict_internal(), + .allow_exec = true }; relative = (fr_pair_parse_t) { }; @@ -2717,6 +2719,7 @@ static size_t command_pair_common(command_result_t *result, command_file_ctx_t * .dict = dict, .internal = fr_dict_internal(), .allow_compare = allow_compare, + .allow_exec = true }; relative = (fr_pair_parse_t) { }; diff --git a/src/bin/unit_test_module.c b/src/bin/unit_test_module.c index 4852133281e..284ce63c7af 100644 --- a/src/bin/unit_test_module.c +++ b/src/bin/unit_test_module.c @@ -225,7 +225,7 @@ static request_t *request_from_file(TALLOC_CTX *ctx, FILE *fp, fr_client_t *clie /* * Read packet from fp */ - if (fr_pair_list_afrom_file(request->request_ctx, dict_protocol, &request->request_pairs, fp, &filedone) < 0) { + if (fr_pair_list_afrom_file(request->request_ctx, dict_protocol, &request->request_pairs, fp, &filedone, true) < 0) { goto error; } @@ -1126,7 +1126,7 @@ int main(int argc, char *argv[]) } } - if (fr_pair_list_afrom_file(request->request_ctx, dict_protocol, &filter_vps, fp, &filedone) < 0) { + if (fr_pair_list_afrom_file(request->request_ctx, dict_protocol, &filter_vps, fp, &filedone, true) < 0) { fr_perror("Failed reading attributes from %s", filter_file); EXIT_WITH_FAILURE; } diff --git a/src/lib/util/pair_legacy.c b/src/lib/util/pair_legacy.c index a4f6db8b9fe..5fa3304fc5c 100644 --- a/src/lib/util/pair_legacy.c +++ b/src/lib/util/pair_legacy.c @@ -20,14 +20,18 @@ * * @copyright 2000,2006,2015 The FreeRADIUS server project */ - RCSID("$Id$") +#include + #include #include #include #include #include +#include +#include +#include #include #include @@ -98,46 +102,149 @@ static fr_sbuff_parse_rules_t const bareword_unquoted = { }; -static ssize_t fr_pair_value_from_substr(fr_pair_t *vp, fr_sbuff_t *in, UNUSED bool tainted) +static ssize_t fr_pair_value_from_substr(fr_pair_parse_t const *conf, fr_pair_t *vp, fr_sbuff_t *in) { - char quote; - ssize_t slen; - fr_sbuff_parse_rules_t const *rules; + fr_sbuff_t our_in = FR_SBUFF(in); + char quote; + ssize_t slen; + fr_sbuff_parse_rules_t const *rules; - if (fr_sbuff_next_if_char(in, '"')) { + if (fr_sbuff_next_if_char(&our_in, '"')) { rules = &value_parse_rules_double_quoted; quote = '"'; - - } else if (fr_sbuff_next_if_char(in, '\'')) { + parse: + slen = fr_value_box_from_substr(vp, &vp->data, vp->da->type, vp->da, &our_in, rules); + } else if (fr_sbuff_next_if_char(&our_in, '\'')) { rules = &value_parse_rules_single_quoted; quote = '\''; + goto parse; + } else if (!fr_sbuff_next_if_char(&our_in, '`')) { + quote = '\0'; + rules = &bareword_unquoted; + goto parse; + /* + * We _sometimes_ support backticks, depending on the + * source of the data. This should ONLY be used on + * trusted input, like config files. + * + * We don't impose arbitrary limits on exec input or + * output, as AGAIN this should only be used on trusted + * input. + * + * Only the first line of output from the process is used, + * and no escape sequences in the output are processed. + */ + } else { + fr_sbuff_t *exec_in; + size_t exec_out_buff_len = 0; + ssize_t exec_out_len; + char *exec_out = NULL; + FILE *fp; + int ret; + + if (!conf->allow_exec) { + fr_strerror_const("Backticks are not supported here"); + return 0; + } /* - * We don't support backticks here. + * Should only be used for trusted resources, so no artificial limits */ - } else if (fr_sbuff_is_char(in, '\'')) { - fr_strerror_const("Backticks are not supported here"); - return 0; + FR_SBUFF_TALLOC_THREAD_LOCAL(&exec_in, 1024, SIZE_MAX); + (void)fr_sbuff_out_unescape_until(exec_in, &our_in, SIZE_MAX, &FR_SBUFF_TERMS(L("`")), &fr_value_unescape_backtick); + /* + * Don't exec if we know we're going to fail + */ + if (!fr_sbuff_is_char(&our_in, '`')) { + fr_strerror_const("Unterminated backtick string"); + return 0; + } - } else { - rules = &bareword_unquoted; - quote = '\0'; + fp = popen(fr_sbuff_start(exec_in), "r"); + if (!fp) { + fr_strerror_printf("Cannot execute command `%pV`: %s", + fr_box_strvalue_len(fr_sbuff_start(exec_in), fr_sbuff_used(exec_in)), + fr_syserror(errno)); + return 0; + } + + errno = 0; /* If we get EOF immediately, we don't want to emit spurious errors */ + exec_out_len = getline(&exec_out, &exec_out_buff_len, fp); + if ((exec_out_len < 0) || (exec_out == NULL)) { /* defensive */ + fr_strerror_printf("Cannot read output from command `%pV`: %s", + fr_box_strvalue_len(fr_sbuff_start(exec_in), fr_sbuff_used(exec_in)), + fr_syserror(errno)); + pclose(fp); + return 0; + } + + /* + * Protect against child writing too much data to stdout, + * blocking, and never exiting. + * + * This is likely overly cautious for this particular use + * case, but it doesn't hurt. + */ + { + char buffer[128]; + + while (fread(buffer, 1, sizeof(buffer), fp) > 0) { /* discard */ } + } + + errno = 0; /* ensure we don't have stale errno */ + ret = pclose(fp); + if (ret < 0) { + fr_strerror_printf("Error waiting for command `%pV` to finish: %s", + fr_box_strvalue_len(fr_sbuff_start(exec_in), fr_sbuff_used(exec_in)), + fr_syserror(errno)); + pclose_error: + free(exec_out); + return 0; + } else if (ret != 0) { + if (WIFEXITED(ret)) { + fr_strerror_printf("Command `%pV` exited with status %d", + fr_box_strvalue_len(fr_sbuff_start(exec_in), fr_sbuff_used(exec_in)), + WEXITSTATUS(ret)); + } else if (WIFSIGNALED(ret)) { + fr_strerror_printf("Command `%pV` terminated by signal %d", + fr_box_strvalue_len(fr_sbuff_start(exec_in), fr_sbuff_used(exec_in)), + WTERMSIG(ret)); + } else { + fr_strerror_printf("Command `%pV` terminated abnormally", + fr_box_strvalue_len(fr_sbuff_start(exec_in), fr_sbuff_used(exec_in))); + } + goto pclose_error; + } + + /* + * Trim line endings + */ + if (exec_out_len > 0 && exec_out[exec_out_len - 1] == '\n') exec_out[--exec_out_len] = '\0'; + if (exec_out_len > 0 && exec_out[exec_out_len - 1] == '\r') exec_out[--exec_out_len] = '\0'; + + slen = fr_value_box_from_substr(vp, &vp->data, vp->da->type, vp->da, + &FR_SBUFF_IN(exec_out, exec_out_len), &value_parse_rules_single_quoted); + free(exec_out); + if (unlikely(slen < 0)) { + return 0; /* slen is parse position in the exec output*/ + } + + quote = '`'; } - slen = fr_value_box_from_substr(vp, &vp->data, vp->da->type, vp->da, in, rules); if (slen < 0) { fr_assert(slen >= -((ssize_t) 1 << 20)); return slen - (quote != 0); } - if (quote && !fr_sbuff_next_if_char(in, quote)) { + if (quote && !fr_sbuff_next_if_char(&our_in, quote)) { fr_strerror_const("Unterminated string"); return 0; } fr_assert(slen <= ((ssize_t) 1 << 20)); - return slen + ((quote != 0) << 1); + FR_SBUFF_SET_RETURN(in, &our_in); } /** Our version of a DA stack. @@ -1008,7 +1115,7 @@ redo: *relative = my; } else { - slen = fr_pair_value_from_substr(vp, &our_in, relative->tainted); + slen = fr_pair_value_from_substr(root, vp, &our_in); if (slen <= 0) goto error; fr_pair_append(my.list, vp); @@ -1141,11 +1248,12 @@ done: * @param[in,out] out where the parsed fr_pair_ts will be appended. * @param[in] fp to read valuepairs from. * @param[out] pfiledone true if file parsing complete; + * @param[in] allow_exec Whether we allow `backtick` expansions. * @return * - 0 on success * - -1 on error */ -int fr_pair_list_afrom_file(TALLOC_CTX *ctx, fr_dict_t const *dict, fr_pair_list_t *out, FILE *fp, bool *pfiledone) +int fr_pair_list_afrom_file(TALLOC_CTX *ctx, fr_dict_t const *dict, fr_pair_list_t *out, FILE *fp, bool *pfiledone, bool allow_exec) { fr_pair_list_t tmp_list; fr_pair_parse_t root, relative; @@ -1167,6 +1275,7 @@ int fr_pair_list_afrom_file(TALLOC_CTX *ctx, fr_dict_t const *dict, fr_pair_list .internal = fr_dict_internal(), .allow_crlf = true, .allow_compare = true, + .allow_exec = allow_exec }; relative = (fr_pair_parse_t) { }; diff --git a/src/lib/util/pair_legacy.h b/src/lib/util/pair_legacy.h index fb449f694ea..9d922f1ed32 100644 --- a/src/lib/util/pair_legacy.h +++ b/src/lib/util/pair_legacy.h @@ -35,11 +35,11 @@ extern "C" { #endif int fr_pair_list_afrom_file(TALLOC_CTX *ctx, fr_dict_t const *dict, - fr_pair_list_t *out, FILE *fp, bool *pfiledone); + fr_pair_list_t *out, FILE *fp, bool *pfiledone, bool allow_exec); void fr_pair_list_move_op(fr_pair_list_t *to, fr_pair_list_t *from, fr_token_t op); -typedef struct fr_pair_parse_s { +typedef struct { TALLOC_CTX *ctx; fr_dict_attr_t const *da; //!< root da to start parsing from fr_pair_list_t *list; //!< list where output is placed @@ -50,6 +50,11 @@ typedef struct fr_pair_parse_s { bool allow_compare; //!< allow comparison operators bool allow_crlf; //!< allow CRLF, and treat like comma bool allow_zeros; //!< allow '\0' as end of attribute + bool allow_exec; //!< allow `exec` to execute external commands. + ///< This should only be allowed in trusted input, + ///< and on startup only. popen() is used for the + ///< execution, and it has no configurable timeout, + ///< so the calling code will wait indefinitely. bool tainted; //!< source is tainted char last_char; //!< last character we read - ',', '\n', or 0 for EOF bool end_of_list; //!< do we expect an end of list '}' character? diff --git a/src/lib/util/pair_legacy_tests.c b/src/lib/util/pair_legacy_tests.c index 3db64a303db..6c8b81f5bee 100644 --- a/src/lib/util/pair_legacy_tests.c +++ b/src/lib/util/pair_legacy_tests.c @@ -124,6 +124,85 @@ static void test_fr_pair_list_afrom_substr(void) fr_pair_list_free(&list); } + +static void test_fr_pair_list_afrom_substr_exec(void) +{ + fr_pair_t *vp; + ssize_t len; + fr_pair_list_t list; + ssize_t slen; + char const *buffer = "Test-Uint32-0 = 123, Test-String-0 = `echo \"Testing321\"`"; + char const *buffer_multi = "Test-String-0 = `echo \"Testing321\"`, Test-String-0 += 'Testing123'"; + fr_pair_parse_t root, relative; + + root = (fr_pair_parse_t) { + .ctx = autofree, + .da = fr_dict_root(test_dict), + .list = &list, + .dict = test_dict, + .internal = fr_dict_internal(), + .allow_exec = true + }; + relative = (fr_pair_parse_t) { }; + + fr_pair_list_init(&list); + len = strlen(buffer); + + TEST_CASE("Create 'vp' using fr_pair_list_afrom_substr()"); + slen = fr_pair_list_afrom_substr(&root, &relative, &FR_SBUFF_IN(buffer, len)); + TEST_CHECK_SLEN(slen, (ssize_t)len); + TEST_MSG_FAIL("fr_pair_list_afrom_substr(): %s", fr_strerror()); + + TEST_CASE("Looking for Test-Uint32-0"); + TEST_CHECK((vp = fr_pair_find_by_da(&list, NULL, fr_dict_attr_test_uint32)) != NULL); + + TEST_CASE("Validating PAIR_VERIFY()"); + PAIR_VERIFY(vp); + + TEST_CASE("Checking if (Test-Uint32-0 == 123)"); + TEST_CHECK(vp && vp->vp_uint32 == 123); + + TEST_CASE("Looking for Test-String-0"); + TEST_CHECK((vp = fr_pair_find_by_da(&list, NULL, fr_dict_attr_test_string)) != NULL); + + TEST_CASE("Validating PAIR_VERIFY()"); + PAIR_VERIFY(vp); + + TEST_MSG_FAIL("Pair value was: %s", vp->vp_strvalue); + TEST_CASE("Checking if (Test-String-0 == 'Testing321')"); + TEST_CHECK(vp && strcmp(vp->vp_strvalue, "Testing321") == 0); + + fr_pair_list_free(&list); + + len = strlen(buffer_multi); + TEST_CASE("Create 'vp' using fr_pair_list_afrom_substr()"); + slen = fr_pair_list_afrom_substr(&root, &relative, &FR_SBUFF_IN(buffer_multi, len)); + TEST_CHECK_SLEN(slen, (ssize_t)len); + TEST_MSG_FAIL("fr_pair_list_afrom_substr(): %s", fr_strerror()); + + TEST_CASE("Looking for Test-String-0"); + TEST_CHECK((vp = fr_pair_find_by_da(&list, NULL, fr_dict_attr_test_string)) != NULL); + + TEST_CASE("Validating PAIR_VERIFY()"); + PAIR_VERIFY(vp); + + TEST_MSG_FAIL("Pair value was: %s", vp->vp_strvalue); + TEST_CASE("Checking if (Test-String-0 == 'Testing321')"); + TEST_CHECK(vp && strcmp(vp->vp_strvalue, "Testing321") == 0); + + TEST_CASE("Looking for Test-String-0"); + TEST_CHECK((vp = fr_pair_find_by_da(&list, vp, fr_dict_attr_test_string)) != NULL); + + TEST_CASE("Validating PAIR_VERIFY()"); + PAIR_VERIFY(vp); + + TEST_MSG_FAIL("Pair value was: %s", vp->vp_strvalue); + TEST_CASE("Checking if (Test-String-0 == 'Testing123')"); + TEST_CHECK(vp && strcmp(vp->vp_strvalue, "Testing123") == 0); + + fr_pair_list_free(&list); +} + static FILE *open_buffer_as_file(uint8_t const *buffer, size_t buffer_len) { FILE *fp; @@ -147,7 +226,7 @@ static void test_fr_pair_list_afrom_file(void) fr_pair_list_init(&list); TEST_CASE("Create 'vp' using fr_pair_list_afrom_file()"); - TEST_CHECK(fr_pair_list_afrom_file(autofree, test_dict, &list, fp, &pfiledone) == 0); + TEST_CHECK(fr_pair_list_afrom_file(autofree, test_dict, &list, fp, &pfiledone, false) == 0); TEST_CASE("Looking for Test-Uint32-0"); TEST_CHECK((vp = fr_pair_find_by_da(&list, NULL, fr_dict_attr_test_uint32)) != NULL); @@ -184,7 +263,7 @@ static void test_fr_pair_list_move_op(void) fr_pair_list_init(&new_list); TEST_CASE("Create 'vp' using fr_pair_list_afrom_file()"); - TEST_CHECK(fr_pair_list_afrom_file(autofree, test_dict, &old_list, fp, &pfiledone) == 0); + TEST_CHECK(fr_pair_list_afrom_file(autofree, test_dict, &old_list, fp, &pfiledone, false) == 0); TEST_CHECK(pfiledone == true); TEST_CASE("Move pair from 'old_list' to 'new_list' using fr_pair_list_move_op()"); @@ -222,6 +301,7 @@ TEST_LIST = { * Legacy calls */ { "fr_pair_list_afrom_substr", test_fr_pair_list_afrom_substr }, + { "fr_pair_list_afrom_substr_exec", test_fr_pair_list_afrom_substr_exec }, { "fr_pair_list_afrom_file", test_fr_pair_list_afrom_file }, { "fr_pair_list_move_op", test_fr_pair_list_move_op }, diff --git a/src/listen/cron/cron.c b/src/listen/cron/cron.c index 4543efed255..9c8df9160e9 100644 --- a/src/listen/cron/cron.c +++ b/src/listen/cron/cron.c @@ -421,7 +421,7 @@ static int mod_instantiate(void *instance, CONF_SECTION *conf) return -1; } - if (fr_pair_list_afrom_file(inst, inst->dict, &inst->vps, fp, &done) < 0) { + if (fr_pair_list_afrom_file(inst, inst->dict, &inst->vps, fp, &done, true) < 0) { fclose(fp); cf_log_err(conf, "Failed reading %s - %s", inst->filename, fr_strerror()); return -1; diff --git a/src/listen/cron/proto_cron_crontab.c b/src/listen/cron/proto_cron_crontab.c index b2f11508040..ec59ec84564 100644 --- a/src/listen/cron/proto_cron_crontab.c +++ b/src/listen/cron/proto_cron_crontab.c @@ -716,7 +716,7 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) return -1; } - if (fr_pair_list_afrom_file(inst, inst->dict, &inst->pair_list, fp, &done) < 0) { + if (fr_pair_list_afrom_file(inst, inst->dict, &inst->pair_list, fp, &done, true) < 0) { cf_log_perr(conf, "Failed reading %s", inst->filename); fclose(fp); return -1; diff --git a/src/listen/load/proto_load_step.c b/src/listen/load/proto_load_step.c index 1bd4b368dae..6db9eb808ab 100644 --- a/src/listen/load/proto_load_step.c +++ b/src/listen/load/proto_load_step.c @@ -367,7 +367,7 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) return -1; } - if (fr_pair_list_afrom_file(inst, inst->dict, &inst->pair_list, fp, &done) < 0) { + if (fr_pair_list_afrom_file(inst, inst->dict, &inst->pair_list, fp, &done, true) < 0) { cf_log_perr(conf, "Failed reading %s", inst->filename); fclose(fp); return -1;