Will set variable `blogs` to the string `this bar is bug`.
+=== Environment Variables
+
+In addition to variable expansions as described above, it is also
+possible to refer to environment variables via the following syntax:
+
+.Loading value from an environment Variable
+====
+----
+blogs = "this $ENV{FOO}"
+----
+====
+
+If an environment variable does not exist, the server will exit with
+an error. The name of the environment variable is a fixed string, and
+cannot be another reference.
+
+The expansion will copy the value of the environment variable exactly.
+
+=== Value from a file
+
+In addition to variable expansions as described above, it is also
+possible to refer to read a value from a file via the following syntax:
+
+.Loading value from a file
+====
+----
+blogs = "this $VALUE{foo.txt}"
+----
+====
+
+If the file does not exist, the server will exit with an error. The
+filename is a fixed string, and cannot be another reference. Both
+absolute and relative filenames are supported. If the filename does
+not begin with '/', then it is taken as relative to the current file.
+
+The file must contain one line. A file with multiple lines will return an error.
+
+The expansion will copy the contents of the file, and will remove any
+trailing CR / LF.
+
+=== Data from a file
+
+In addition to variable expansions as described above, it is also
+possible to refer to read an entire file via the following syntax:
+
+.Loading value from a file
+====
+----
+blogs = "this $FILE{foo.txt}"
+----
+====
+
+If the file does not exist, the server will exit with an error. The
+filename is a fixed string, and cannot be another reference. Both
+absolute and relative filenames are supported. If the filename does
+not begin with '/', then it is taken as relative to the current file.
+
+The file can be binary, multiple lines, etc.
+
+The expansion will copy all of the contents of the file exactly,
+without changes.
+
// Copyright (C) 2026 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
// This documentation was developed by Network RADIUS SAS.
cf_stack_frame_t frame[MAX_STACK]; //!< stack frames
} cf_stack_t;
+/*
+ * Open and read a file.
+ */
+static int cf_expand_file(char const *cf, int lineno, char name[static PATH_MAX],
+ char **p_p, char const **ptr_p, char *output, size_t outsize,
+ bool raw)
+{
+ int fd;
+ size_t room;
+ ssize_t len;
+ char *p, *next;
+ char const *ptr;
+
+ /*
+ * Note that we do NOT recursively expand the value. It has to be a hard-coded string.
+ *
+ * We do NOT do any sanity checks on the value. i.e. filenames beginning with '/' are allowed,
+ * as are files with "../../". The value here comes from the configuration files, and only the
+ * administrator has write access to them.
+ */
+ strlcpy(name, cf, PATH_MAX);
+ p = strrchr(name, '/');
+ if (p) {
+ p++;
+ } else {
+ p = name;
+ }
+
+ ptr = *ptr_p;
+
+ /*
+ * Look for trailing '}', and log a
+ * warning for anything that doesn't match,
+ * and exit with a fatal error.
+ */
+ next = strchr(ptr, '}');
+ if (next == NULL) {
+ *p = '\0';
+ ERROR("%s[%d]: File expansion missing }",
+ cf, lineno);
+ return -1;
+ }
+
+ /*
+ * Can't really happen because input lines are
+ * capped at 8k, which is sizeof(name)
+ */
+ if ((next - ptr) >= (name + PATH_MAX - p)) {
+ ERROR("%s[%d]: File name is too large",
+ cf, lineno);
+ return -1;
+ }
+
+ memcpy(p, ptr, next - ptr);
+ p[next - ptr] = '\0';
+
+ fd = open(name, O_RDONLY);
+ if (fd < 0) {
+ ERROR("%s[%d]: Failed opening %s: %s",
+ cf, lineno, name, strerror(errno));
+ return -1;
+ }
+
+ p = *p_p;
+ room = (output + outsize) - p;
+ fr_assert(room > 0);
+
+ /*
+ * Read the raw data.
+ */
+ len = read(fd, p, room);
+ if (len < 0) {
+ ERROR("%s[%d]: Failed reading %s: %s",
+ cf, lineno, name, strerror(errno));
+ close(fd);
+ return -1;
+ }
+ close(fd);
+
+ if (!len) {
+ ERROR("%s[%d]: Failed reading %s: the file is empty",
+ cf, lineno, name);
+ return -1;
+ }
+
+ /*
+ * We don't know whether or not it was
+ * truncated, so we just error out.
+ */
+ if ((size_t) len >= room) {
+ ERROR("%s[%d]: Too much data in %s: did not read the entire file",
+ cf, lineno, name);
+ return -1;
+ }
+
+ /*
+ * If we're not reading the raw file, return only the first line.
+ */
+ if (!raw) {
+ char *q, *end = p + len;
+
+ while (p < end) {
+ if (*p >= ' ') {
+ p++;
+ continue;
+ }
+
+ break;
+ }
+
+ /*
+ * Strip trailing CR/LF.
+ */
+ for (q = p; q < end; q++) {
+ if (*q >= ' ') break;
+
+ *q = '\0';
+ }
+
+ if (q != end) {
+ ERROR("%s[%d]: Too much data in %s: expected one line of text, found multiple lines in the file",
+ cf, lineno, name);
+ return -1;
+ }
+ } else {
+ p += len;
+ }
+
+ *ptr_p = next + 1;
+ *p_p = p;
+
+ return 0;
+}
+
/*
* Expand the variables in an input string.
*
char *p;
char const *end, *next, *ptr;
CONF_SECTION const *parent_cs;
- char name[8192];
+ char name[PATH_MAX];
if (soft_fail) *soft_fail = false;
p += strlen(p);
ptr = next + 1;
+ } else if (strncmp(ptr, "$VALUE{", 7) == 0) {
+ ptr += 7;
+
+ if (cf_expand_file(cf, lineno, name, &p, &ptr, output, outsize, false) < 0) return NULL;
+
+ } else if (strncmp(ptr, "$FILE{", 6) == 0) {
+ ptr += 6;
+
+ if (cf_expand_file(cf, lineno, name, &p, &ptr, output, outsize, true) < 0) return NULL;
+
} else {
/*
* Copy it over verbatim.