This lets us add dynamic values in certain situations. Output from exec is limited to a single line (for now).
/*
* 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);
* 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);
*/
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;
}
* 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')) {
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;
}
* 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);
*/
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;
}
* 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')) {
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;
}
.dict = dict_radius,
.internal = fr_dict_internal(),
.allow_compare = true,
+ .allow_exec = true
};
relative = (fr_pair_parse_t) { };
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);
.list = &head,
.dict = cc->tmpl_rules.attr.namespace->dict,
.internal = fr_dict_internal(),
+ .allow_exec = true
};
relative = (fr_pair_parse_t) { };
}
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();
.list = &head,
.dict = cc->tmpl_rules.attr.namespace->dict,
.internal = fr_dict_internal(),
+ .allow_exec = true
};
relative = (fr_pair_parse_t) { };
.dict = dict,
.internal = fr_dict_internal(),
.allow_compare = allow_compare,
+ .allow_exec = true
};
relative = (fr_pair_parse_t) { };
/*
* 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;
}
}
}
- 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;
}
*
* @copyright 2000,2006,2015 The FreeRADIUS server project
*/
-
RCSID("$Id$")
+#include <sys/wait.h>
+
#include <freeradius-devel/util/dict.h>
#include <freeradius-devel/util/pair.h>
#include <freeradius-devel/util/pair_legacy.h>
#include <freeradius-devel/util/proto.h>
#include <freeradius-devel/util/regex.h>
+#include <freeradius-devel/util/syserror.h>
+#include <freeradius-devel/util/sbuff.h>
+#include <freeradius-devel/util/value.h>
#include <freeradius-devel/protocol/radius/rfc2865.h>
#include <freeradius-devel/protocol/freeradius/freeradius.internal.h>
};
-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.
*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);
* @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;
.internal = fr_dict_internal(),
.allow_crlf = true,
.allow_compare = true,
+ .allow_exec = allow_exec
};
relative = (fr_pair_parse_t) { };
#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
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?
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;
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);
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()");
* 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 },
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;
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;
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;