#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/rad_assert.h>
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#include <ctype.h>
typedef enum conf_type {
CONF_ITEM_INVALID = 0,
CONF_ITEM_PAIR,
CONF_ITEM_SECTION,
CONF_ITEM_DATA
} CONF_ITEM_TYPE;
struct conf_item {
struct conf_item *next;
struct conf_part *parent;
int lineno;
const char *filename;
CONF_ITEM_TYPE type;
};
struct conf_pair {
CONF_ITEM item;
const char *attr;
const char *value;
FR_TOKEN operator;
FR_TOKEN value_type;
};
struct conf_part {
CONF_ITEM item;
const char *name1;
const char *name2;
struct conf_item *children;
struct conf_item *tail;
CONF_SECTION *template;
rbtree_t *pair_tree;
rbtree_t *section_tree;
rbtree_t *name2_tree;
rbtree_t *data_tree;
void *base;
int depth;
const CONF_PARSER *variables;
};
struct conf_data {
CONF_ITEM item;
const char *name;
int flag;
void *data;
void (*free)(void *);
};
static int cf_data_add_internal(CONF_SECTION *cs, const char *name,
void *data, void (*data_free)(void *),
int flag);
static void *cf_data_find_internal(CONF_SECTION *cs, const char *name,
int flag);
int cf_log_config = 1;
int cf_log_modules = 1;
CONF_PAIR *cf_itemtopair(CONF_ITEM *ci)
{
if (ci == NULL)
return NULL;
rad_assert(ci->type == CONF_ITEM_PAIR);
return (CONF_PAIR *)ci;
}
CONF_SECTION *cf_itemtosection(CONF_ITEM *ci)
{
if (ci == NULL)
return NULL;
rad_assert(ci->type == CONF_ITEM_SECTION);
return (CONF_SECTION *)ci;
}
CONF_ITEM *cf_pairtoitem(CONF_PAIR *cp)
{
if (cp == NULL)
return NULL;
return (CONF_ITEM *)cp;
}
CONF_ITEM *cf_sectiontoitem(CONF_SECTION *cs)
{
if (cs == NULL)
return NULL;
return (CONF_ITEM *)cs;
}
static CONF_DATA *cf_itemtodata(CONF_ITEM *ci)
{
if (ci == NULL)
return NULL;
rad_assert(ci->type == CONF_ITEM_DATA);
return (CONF_DATA *)ci;
}
static CONF_ITEM *cf_datatoitem(CONF_DATA *cd)
{
if (cd == NULL)
return NULL;
return (CONF_ITEM *)cd;
}
static CONF_PAIR *cf_pair_alloc(const char *attr, const char *value,
FR_TOKEN operator, FR_TOKEN value_type,
CONF_SECTION *parent)
{
char *p;
size_t attr_len, value_len = 0;
CONF_PAIR *cp;
if (!attr) return NULL;
attr_len = strlen(attr) + 1;
if (value) value_len = strlen(value) + 1;
p = rad_malloc(sizeof(*cp) + attr_len + value_len);
cp = (CONF_PAIR *) p;
memset(cp, 0, sizeof(*cp));
cp->item.type = CONF_ITEM_PAIR;
cp->item.parent = parent;
p += sizeof(*cp);
memcpy(p, attr, attr_len);
cp->attr = p;
if (value) {
p += attr_len;
memcpy(p, value, value_len);
cp->value = p;
}
cp->value_type = value_type;
cp->operator = operator;
return cp;
}
void cf_pair_free(CONF_PAIR **cp)
{
if (!cp || !*cp) return;
#ifndef NDEBUG
memset(*cp, 0, sizeof(*cp));
#endif
free(*cp);
*cp = NULL;
}
static void cf_data_free(CONF_DATA **cd)
{
if (!cd || !*cd) return;
if (!(*cd)->free) {
free((*cd)->data);
} else {
((*cd)->free)((*cd)->data);
}
#ifndef NDEBUG
memset(*cd, 0, sizeof(*cd));
#endif
free(*cd);
*cd = NULL;
}
static int pair_cmp(const void *a, const void *b)
{
const CONF_PAIR *one = a;
const CONF_PAIR *two = b;
return strcmp(one->attr, two->attr);
}
static int section_cmp(const void *a, const void *b)
{
const CONF_SECTION *one = a;
const CONF_SECTION *two = b;
return strcmp(one->name1, two->name1);
}
static int name2_cmp(const void *a, const void *b)
{
const CONF_SECTION *one = a;
const CONF_SECTION *two = b;
rad_assert(strcmp(one->name1, two->name1) == 0);
if (!one->name2 && !two->name2) return 0;
if (!one->name2) return -1;
if (!two->name2) return +1;
return strcmp(one->name2, two->name2);
}
static int data_cmp(const void *a, const void *b)
{
int rcode;
const CONF_DATA *one = a;
const CONF_DATA *two = b;
rcode = one->flag - two->flag;
if (rcode != 0) return rcode;
return strcmp(one->name, two->name);
}
void cf_section_parse_free(CONF_SECTION *cs, void *base)
{
int i;
const CONF_PARSER *variables = cs->variables;
if (!variables) return;
for (i = 0; variables[i].name != NULL; i++) {
char **p;
if ((variables[i].type != PW_TYPE_STRING_PTR) &&
(variables[i].type != PW_TYPE_FILENAME)) {
continue;
}
if (!base) {
if (!variables[i].data) {
continue;
}
p = (char **) variables[i].data;;
} else if (variables[i].data) {
p = (char **) variables[i].data;;
} else {
p = (char **) (((char *)base) + variables[i].offset);
}
free(*p);
*p = NULL;
}
}
void cf_section_free(CONF_SECTION **cs)
{
CONF_ITEM *ci, *next;
if (!cs || !*cs) return;
cf_section_parse_free(*cs, (*cs)->base);
for (ci = (*cs)->children; ci; ci = next) {
next = ci->next;
switch (ci->type) {
case CONF_ITEM_PAIR: {
CONF_PAIR *pair = cf_itemtopair(ci);
cf_pair_free(&pair);
}
break;
case CONF_ITEM_SECTION: {
CONF_SECTION *section = cf_itemtosection(ci);
cf_section_free(§ion);
}
break;
case CONF_ITEM_DATA: {
CONF_DATA *data = cf_itemtodata(ci);
cf_data_free(&data);
}
break;
default:
break;
}
}
if ((*cs)->pair_tree)
rbtree_free((*cs)->pair_tree);
if ((*cs)->section_tree)
rbtree_free((*cs)->section_tree);
if ((*cs)->name2_tree)
rbtree_free((*cs)->name2_tree);
if ((*cs)->data_tree)
rbtree_free((*cs)->data_tree);
#ifndef NDEBUG
memset(*cs, 0, sizeof(*cs));
#endif
free(*cs);
*cs = NULL;
}
static CONF_SECTION *cf_section_alloc(const char *name1, const char *name2,
CONF_SECTION *parent)
{
size_t name1_len, name2_len = 0;
char *p;
CONF_SECTION *cs;
if (!name1) return NULL;
name1_len = strlen(name1) + 1;
if (name2) name2_len = strlen(name2) + 1;
p = rad_malloc(sizeof(*cs) + name1_len + name2_len);
cs = (CONF_SECTION *) p;
memset(cs, 0, sizeof(*cs));
cs->item.type = CONF_ITEM_SECTION;
cs->item.parent = parent;
p += sizeof(*cs);
memcpy(p, name1, name1_len);
cs->name1 = p;
if (name2 && *name2) {
p += name1_len;
memcpy(p, name2, name2_len);
cs->name2 = p;
}
cs->pair_tree = rbtree_create(pair_cmp, NULL, 0);
if (!cs->pair_tree) {
cf_section_free(&cs);
return NULL;
}
if (parent) cs->depth = parent->depth + 1;
return cs;
}
int cf_pair_replace(CONF_SECTION *cs, CONF_PAIR *cp, const char *value)
{
CONF_PAIR *newp;
CONF_ITEM *ci, *cn, **last;
newp = cf_pair_alloc(cp->attr, value, cp->operator, cp->value_type,
cs);
if (!newp) return -1;
ci = cf_pairtoitem(cp);
cn = cf_pairtoitem(newp);
for (last = &cs->children; (*last) != NULL; last = &(*last)->next) {
if (*last == ci) {
cn->next = (*last)->next;
*last = cn;
ci->next = NULL;
break;
}
}
rbtree_deletebydata(cs->pair_tree, ci);
rbtree_insert(cs->pair_tree, cn);
return 0;
}
static void cf_item_add(CONF_SECTION *cs, CONF_ITEM *ci)
{
if (!cs->children) {
rad_assert(cs->tail == NULL);
cs->children = ci;
} else {
rad_assert(cs->tail != NULL);
cs->tail->next = ci;
}
for (; ci != NULL; ci = ci->next) {
cs->tail = ci;
switch (ci->type) {
case CONF_ITEM_PAIR:
rbtree_insert(cs->pair_tree, ci);
break;
case CONF_ITEM_SECTION: {
CONF_SECTION *cs_new = cf_itemtosection(ci);
if (!cs->section_tree) {
cs->section_tree = rbtree_create(section_cmp, NULL, 0);
if (!cs->section_tree) {
radlog(L_ERR, "Out of memory");
_exit(1);
}
}
rbtree_insert(cs->section_tree, cs_new);
{
CONF_SECTION *old_cs;
old_cs = rbtree_finddata(cs->section_tree, cs_new);
if (!old_cs) return;
if (!old_cs->name2_tree) {
old_cs->name2_tree = rbtree_create(name2_cmp,
NULL, 0);
}
if (old_cs->name2_tree) {
rbtree_insert(old_cs->name2_tree, cs_new);
}
}
break;
}
case CONF_ITEM_DATA:
if (!cs->data_tree) {
cs->data_tree = rbtree_create(data_cmp, NULL, 0);
}
if (cs->data_tree) {
rbtree_insert(cs->data_tree, ci);
}
break;
default:
break;
}
}
}
CONF_ITEM *cf_reference_item(const CONF_SECTION *parentcs,
CONF_SECTION *outercs,
const char *ptr)
{
CONF_PAIR *cp;
CONF_SECTION *next;
const CONF_SECTION *cs = outercs;
char name[8192];
char *p;
strlcpy(name, ptr, sizeof(name));
p = name;
if (*p == '.') {
p++;
while (*p == '.') {
if (cs->item.parent)
cs = cs->item.parent;
p++;
}
} else if (strchr(p, '.') != NULL) {
if (!parentcs) goto no_such_item;
cs = parentcs;
}
while (*p) {
char *q, *r;
r = strchr(p, '[');
q = strchr(p, '.');
if (!r && !q) break;
if (r && q > r) q = NULL;
if (q && q < r) r = NULL;
if (r) {
q = strchr(r + 1, ']');
if (!q) return NULL;
if (q[1] && q[1] != '.') goto no_such_item;
*r = '\0';
*q = '\0';
next = cf_section_sub_find_name2(cs, p, r + 1);
*r = '[';
*q = ']';
if (!q[1]) {
if (!next) goto no_such_item;
return cf_sectiontoitem(next);
}
q++;
} else {
*q = '\0';
next = cf_section_sub_find(cs, p);
*q = '.';
}
if (!next) break;
cs = next;
p = q + 1;
}
if (!*p) goto no_such_item;
retry:
cp = cf_pair_find(cs, p);
if (cp) return cf_pairtoitem(cp);
next = cf_section_sub_find(cs, p);
if (next) return cf_sectiontoitem(next);
if ((p == name) && (parentcs != NULL) && (cs != parentcs)) {
cs = parentcs;
goto retry;
}
no_such_item:
DEBUG2("WARNING: No such configuration item %s", ptr);
return NULL;
}
CONF_SECTION *cf_top_section(CONF_SECTION *cs)
{
while (cs->item.parent != NULL) {
cs = cs->item.parent;
}
return cs;
}
static const char *cf_expand_variables(const char *cf, int *lineno,
CONF_SECTION *outercs,
char *output, const char *input)
{
char *p;
const char *end, *ptr;
const CONF_SECTION *parentcs;
char name[8192];
parentcs = cf_top_section(outercs);
p = output;
ptr = input;
while (*ptr) {
if ((*ptr == '$') && (ptr[1] == '{')) {
CONF_ITEM *ci;
CONF_PAIR *cp;
end = strchr(ptr, '}');
if (end == NULL) {
*p = '\0';
radlog(L_INFO, "%s[%d]: Variable expansion missing }",
cf, *lineno);
return NULL;
}
ptr += 2;
if ((size_t) (end - ptr) >= sizeof(name)) {
radlog(L_ERR, "%s[%d]: Reference string is too large",
cf, *lineno);
return NULL;
}
memcpy(name, ptr, end - ptr);
name[end - ptr] = '\0';
ci = cf_reference_item(parentcs, outercs, name);
if (!ci || (ci->type != CONF_ITEM_PAIR)) {
radlog(L_ERR, "%s[%d]: Reference \"%s\" not found",
cf, *lineno, input);
return NULL;
}
cp = cf_itemtopair(ci);
if (!cp->value) {
radlog(L_ERR, "%s[%d]: Reference \"%s\" has no value",
cf, *lineno, input);
return NULL;
}
strcpy(p, cp->value);
p += strlen(p);
ptr = end + 1;
} else if (memcmp(ptr, "$ENV{", 5) == 0) {
char *env;
ptr += 5;
end = strchr(ptr, '}');
if (end == NULL) {
*p = '\0';
radlog(L_INFO, "%s[%d]: Environment variable expansion missing }",
cf, *lineno);
return NULL;
}
if ((size_t) (end - ptr) >= sizeof(name)) {
radlog(L_ERR, "%s[%d]: Environment variable name is too large",
cf, *lineno);
return NULL;
}
memcpy(name, ptr, end - ptr);
name[end - ptr] = '\0';
env = getenv(name);
if (env == NULL) {
*name = '\0';
env = name;
}
strcpy(p, env);
p += strlen(p);
ptr = end + 1;
} else {
*(p++) = *(ptr++);
}
}
*p = '\0';
return output;
}
int cf_item_parse(CONF_SECTION *cs, const char *name,
int type, void *data, const char *dflt)
{
int rcode = 0;
char **q;
const char *value;
fr_ipaddr_t ipaddr;
const CONF_PAIR *cp = NULL;
char ipbuf[128];
if (cs) cp = cf_pair_find(cs, name);
if (cp) {
value = cp->value;
} else if (!dflt) {
return 1;
} else {
rcode = 1;
value = dflt;
}
if (!value) {
return 0;
}
switch (type) {
case PW_TYPE_BOOLEAN:
if ((strcasecmp(value, "yes") == 0) ||
(strcasecmp(value, "on") == 0)) {
*(int *)data = 1;
} else if ((strcasecmp(value, "no") == 0) ||
(strcasecmp(value, "off") == 0)) {
*(int *)data = 0;
} else {
*(int *)data = 0;
radlog(L_ERR, "Bad value \"%s\" for boolean variable %s", value, name);
return -1;
}
cf_log_info(cs, "\t%s = %s", name, value);
break;
case PW_TYPE_INTEGER:
*(int *)data = strtol(value, 0, 0);
cf_log_info(cs, "\t%s = %d", name, *(int *)data);
break;
case PW_TYPE_STRING_PTR:
q = (char **) data;
if (*q != NULL) {
free(*q);
}
if (value == dflt) {
char buffer[8192];
int lineno = 0;
if (cs) lineno = cs->item.lineno;
value = cf_expand_variables("?",
&lineno,
cs, buffer, value);
if (!value) return -1;
}
cf_log_info(cs, "\t%s = \"%s\"", name, value ? value : "(null)");
*q = value ? strdup(value) : NULL;
break;
case PW_TYPE_FILENAME:
q = (char **) data;
if (*q != NULL) {
free(*q);
}
if (value == dflt) {
char buffer[8192];
int lineno = 0;
if (cs) lineno = cs->item.lineno;
value = cf_expand_variables("?",
&lineno,
cs, buffer, value);
if (!value) return -1;
}
cf_log_info(cs, "\t%s = \"%s\"", name, value ? value : "(null)");
*q = value ? strdup(value) : NULL;
if (0 && *q) {
struct stat buf;
if (stat(*q, &buf) == 0) {
time_t *mtime;
mtime = rad_malloc(sizeof(*mtime));
*mtime = buf.st_mtime;
cf_data_add_internal(cs, *q, mtime, free,
PW_TYPE_FILENAME);
}
}
break;
case PW_TYPE_IPADDR:
if (strcmp(value, "*") == 0) {
*(uint32_t *) data = htonl(INADDR_ANY);
cf_log_info(cs, "\t%s = *", name);
break;
}
if (ip_hton(value, AF_INET, &ipaddr) < 0) {
radlog(L_ERR, "Can't find IP address for host %s", value);
return -1;
}
if (strspn(value, "0123456789.") == strlen(value)) {
cf_log_info(cs, "\t%s = %s", name, value);
} else {
cf_log_info(cs, "\t%s = %s IP address [%s]", name, value,
ip_ntoh(&ipaddr, ipbuf, sizeof(ipbuf)));
}
*(uint32_t *) data = ipaddr.ipaddr.ip4addr.s_addr;
break;
case PW_TYPE_IPV6ADDR:
if (ip_hton(value, AF_INET6, &ipaddr) < 0) {
radlog(L_ERR, "Can't find IPv6 address for host %s", value);
return -1;
}
cf_log_info(cs, "\t%s = %s IPv6 address [%s]", name, value,
ip_ntoh(&ipaddr, ipbuf, sizeof(ipbuf)));
memcpy(data, &ipaddr.ipaddr.ip6addr,
sizeof(ipaddr.ipaddr.ip6addr));
break;
default:
radlog(L_ERR, "type %d not supported yet", type);
return -1;
break;
}
return rcode;
}
static const char *parse_spaces = " ";
int cf_section_parse(CONF_SECTION *cs, void *base,
const CONF_PARSER *variables)
{
int i;
void *data;
cs->variables = variables;
if (!cs->name2) {
cf_log_info(cs, "%.*s%s {", cs->depth, parse_spaces,
cs->name1);
} else {
cf_log_info(cs, "%.*s%s %s {", cs->depth, parse_spaces,
cs->name1, cs->name2);
}
for (i = 0; variables[i].name != NULL; i++) {
if (variables[i].type == PW_TYPE_SUBSECTION) {
CONF_SECTION *subcs;
subcs = cf_section_sub_find(cs, variables[i].name);
if (!subcs) continue;
if (!variables[i].dflt) {
DEBUG2("Internal sanity check 1 failed in cf_section_parse");
goto error;
}
if (cf_section_parse(subcs, base,
(const CONF_PARSER *) variables[i].dflt) < 0) {
goto error;
}
continue;
}
if (variables[i].data) {
data = variables[i].data;
} else if (base) {
data = ((char *)base) + variables[i].offset;
} else {
DEBUG2("Internal sanity check 2 failed in cf_section_parse");
goto error;
}
if (cf_item_parse(cs, variables[i].name, variables[i].type,
data, variables[i].dflt) < 0) {
goto error;
}
}
cf_log_info(cs, "%.*s}", cs->depth, parse_spaces);
cs->base = base;
return 0;
error:
cf_log_info(cs, "%.*s}", cs->depth, parse_spaces);
cf_section_parse_free(cs, base);
return -1;
}
static int condition_looks_ok(const char **ptr)
{
int num_braces = 1;
int quote = 0;
const char *p = *ptr;
while (*p) {
if (quote) {
if (*p == quote) {
p++;
quote = 0;
continue;
}
if (*p == '\\') {
if (!p[1]) {
return 0;
}
p += 2;
continue;
}
p++;
continue;
}
switch (*p) {
case '\\':
if (!p[1]) {
return 0;
}
p += 2;
continue;
case '(':
num_braces++;
p++;
continue;
case ')':
if (num_braces == 1) {
const char *q = p + 1;
while ((*q == ' ') || (*q == '\t')) q++;
if (*q != '{') {
DEBUG2("Expected open brace '{' after condition at %s", p);
return 0;
}
*ptr = p + 1;
return 1;
}
num_braces--;
p++;
continue;
case '"':
case '\'':
case '/':
case '`':
quote = *p;
default:
p++;
break;
}
}
DEBUG3("Unexpected error");
return 0;
}
static const char *cf_local_file(CONF_SECTION *cs, const char *local,
char *buffer, size_t bufsize)
{
size_t dirsize;
const char *p;
CONF_SECTION *parentcs = cf_top_section(cs);
p = strrchr(parentcs->item.filename, FR_DIR_SEP);
if (!p) return local;
dirsize = (p - parentcs->item.filename) + 1;
if ((dirsize + strlen(local)) >= bufsize) {
return NULL;
}
memcpy(buffer, parentcs->item.filename, dirsize);
strlcpy(buffer + dirsize, local, bufsize - dirsize);
return buffer;
}
static int cf_section_read(const char *filename, int *lineno, FILE *fp,
CONF_SECTION *current)
{
CONF_SECTION *this, *css;
CONF_PAIR *cpn;
const char *ptr;
const char *value;
char buf[8192];
char buf1[8192];
char buf2[8192];
char buf3[8192];
int t1, t2, t3;
char *cbuf = buf;
size_t len;
this = current;
for (;;) {
int at_eof;
at_eof = (fgets(cbuf, sizeof(buf) - (cbuf - buf), fp) == NULL);
(*lineno)++;
len = strlen(cbuf);
if ((cbuf + len + 1) >= (buf + sizeof(buf))) {
radlog(L_ERR, "%s[%d]: Line too long",
filename, *lineno);
return -1;
}
if (cbuf == buf) {
if (at_eof) break;
ptr = buf;
while (*ptr && isspace((int) *ptr)) ptr++;
if (!*ptr || (*ptr == '#')) continue;
} else if (at_eof || (len == 0)) {
radlog(L_ERR, "%s[%d]: Continuation at EOF is illegal",
filename, *lineno);
return -1;
}
while ((len > 0) &&
((cbuf[len - 1] == '\n') || (cbuf[len - 1] == '\r'))) {
len--;
cbuf[len] = '\0';
}
if ((len > 0) && (cbuf[len - 1] == '\\')) {
cbuf[len - 1] = '\0';
cbuf += len - 1;
continue;
}
ptr = cbuf = buf;
while ((*ptr == ' ') || (*ptr == '\t')) ptr++;
if (((ptr[0] == '%') && (ptr[1] == '{')) ||
(ptr[0] == '`')) {
int hack;
if (ptr[0] == '%') {
hack = rad_copy_variable(buf1, ptr);
} else {
hack = rad_copy_string(buf1, ptr);
}
if (hack < 0) {
radlog(L_ERR, "%s[%d]: Invalid expansion: %s",
filename, *lineno, ptr);
return -1;
}
t1 = T_BARE_WORD;
ptr += hack;
t2 = gettoken(&ptr, buf2, sizeof(buf2));
switch (t2) {
case T_EOL:
case T_HASH:
goto do_bare_word;
default:
radlog(L_ERR, "%s[%d]: Invalid expansion: %s",
filename, *lineno, ptr);
return -1;
}
} else {
t1 = gettoken(&ptr, buf1, sizeof(buf1));
}
if (t1 == T_RCBRACE) {
if (this == current) {
radlog(L_ERR, "%s[%d]: Too many closing braces",
filename, *lineno);
return -1;
}
this = this->item.parent;
continue;
}
if ((strcasecmp(buf1, "$INCLUDE") == 0) ||
(strcasecmp(buf1, "$-INCLUDE") == 0)) {
int relative = 1;
t2 = getword(&ptr, buf2, sizeof(buf2));
if (buf2[0] == '$') relative = 0;
value = cf_expand_variables(filename, lineno, this, buf, buf2);
if (!value) return -1;
if (!FR_DIR_IS_RELATIVE(value)) relative = 0;
if (relative) {
value = cf_local_file(current, value, buf3,
sizeof(buf3));
if (!value) {
radlog(L_ERR, "%s[%d]: Directories too deep.",
filename, *lineno);
return -1;
}
}
#ifdef HAVE_DIRENT_H
if (value[strlen(value) - 1] == '/') {
DIR *dir;
struct dirent *dp;
struct stat stat_buf;
DEBUG2("including files in directory %s", value );
dir = opendir(value);
if (!dir) {
radlog(L_ERR, "%s[%d]: Error reading directory %s: %s",
filename, *lineno, value,
strerror(errno));
return -1;
}
while ((dp = readdir(dir)) != NULL) {
const char *p;
if (dp->d_name[0] == '.') continue;
for (p = dp->d_name; *p != '\0'; p++) {
if (isalpha((int)*p) ||
isdigit((int)*p) ||
(*p == '-') ||
(*p == '_') ||
(*p == '.')) continue;
break;
}
if (*p != '\0') continue;
snprintf(buf2, sizeof(buf2), "%s%s",
value, dp->d_name);
if ((stat(buf2, &stat_buf) != 0) ||
S_ISDIR(stat_buf.st_mode)) continue;
if (cf_file_include(buf2, this) < 0) {
closedir(dir);
return -1;
}
}
closedir(dir);
} else
#endif
{
if (buf1[1] == '-') {
struct stat statbuf;
if (stat(value, &statbuf) < 0) {
DEBUG("WARNING: Not including file %s: %s", value, strerror(errno));
continue;
}
}
if (cf_file_include(value, this) < 0) {
return -1;
}
}
continue;
}
if (strcasecmp(buf1, "$template") == 0) {
CONF_ITEM *ci;
CONF_SECTION *parentcs, *templatecs;
t2 = getword(&ptr, buf2, sizeof(buf2));
parentcs = cf_top_section(current);
templatecs = cf_section_sub_find(parentcs, "templates");
if (!templatecs) {
radlog(L_ERR, "%s[%d]: No \"templates\" section for reference \"%s\"",
filename, *lineno, buf2);
return -1;
}
ci = cf_reference_item(parentcs, templatecs, buf2);
if (!ci || (ci->type != CONF_ITEM_SECTION)) {
radlog(L_ERR, "%s[%d]: Reference \"%s\" not found",
filename, *lineno, buf2);
return -1;
}
if (this->template) {
radlog(L_ERR, "%s[%d]: Section already has a template",
filename, *lineno);
return -1;
}
this->template = cf_itemtosection(ci);
continue;
}
if (buf1[0] == '_') {
radlog(L_ERR, "%s[%d]: Illegal configuration pair name \"%s\"",
filename, *lineno, buf1);
return -1;
}
t2 = gettoken(&ptr, buf2, sizeof(buf2));
switch (t2) {
case T_EOL:
case T_HASH:
do_bare_word:
t3 = t2;
t2 = T_OP_EQ;
value = NULL;
goto do_set;
case T_OP_ADD:
case T_OP_CMP_EQ:
case T_OP_SUB:
case T_OP_LE:
case T_OP_GE:
case T_OP_CMP_FALSE:
if (!this || (strcmp(this->name1, "update") != 0)) {
radlog(L_ERR, "%s[%d]: Invalid operator in assignment",
filename, *lineno);
return -1;
}
case T_OP_EQ:
case T_OP_SET:
t3 = getstring(&ptr, buf3, sizeof(buf3));
if (t3 == T_OP_INVALID) {
radlog(L_ERR, "%s[%d]: Parse error: %s",
filename, *lineno,
fr_strerror());
return -1;
}
if ((t3 == T_BARE_WORD) ||
(t3 == T_DOUBLE_QUOTED_STRING)) {
value = cf_expand_variables(filename, lineno, this,
buf, buf3);
if (!value) return -1;
} else if ((t3 == T_EOL) ||
(t3 == T_HASH)) {
value = NULL;
} else {
value = buf3;
}
do_set:
cpn = cf_pair_alloc(buf1, value, t2, t3, this);
cpn->item.filename = filename;
cpn->item.lineno = *lineno;
cf_item_add(this, cf_pairtoitem(cpn));
continue;
case T_LBRACE:
if ((strcmp(buf1, "if") == 0) ||
(strcmp(buf1, "elsif") == 0)) {
const char *end = ptr;
CONF_SECTION *server;
if (!condition_looks_ok(&end)) {
radlog(L_ERR, "%s[%d]: Parse error in condition at: %s",
filename, *lineno, ptr);
return -1;
}
if ((size_t) (end - ptr) >= (sizeof(buf2) - 1)) {
radlog(L_ERR, "%s[%d]: Statement too complicated after \"%s\"",
filename, *lineno, buf1);
return -1;
}
server = this;
while (server) {
if (strcmp(server->name1, "server") == 0) break;
server = server->item.parent;
}
if (0 && !server) {
radlog(L_ERR, "%s[%d]: Processing directives such as \"%s\" cannot be used here.",
filename, *lineno, buf1);
return -1;
}
buf2[0] = '(';
memcpy(buf2 + 1, ptr, end - ptr);
buf2[end - ptr + 1] = '\0';
ptr = end + 1;
t2 = T_BARE_WORD;
goto section_alloc;
} else {
radlog(L_ERR, "%s[%d]: Parse error after \"%s\"",
filename, *lineno, buf1);
return -1;
}
case T_BARE_WORD:
case T_DOUBLE_QUOTED_STRING:
case T_SINGLE_QUOTED_STRING:
t3 = gettoken(&ptr, buf3, sizeof(buf3));
if (t3 != T_LCBRACE) {
radlog(L_ERR, "%s[%d]: Expecting section start brace '{' after \"%s %s\"",
filename, *lineno, buf1, buf2);
return -1;
}
case T_LCBRACE:
section_alloc:
css = cf_section_alloc(buf1,
t2 == T_LCBRACE ? NULL : buf2,
this);
if (!css) {
radlog(L_ERR, "%s[%d]: Failed allocating memory for section",
filename, *lineno);
return -1;
}
cf_item_add(this, cf_sectiontoitem(css));
css->item.filename = filename;
css->item.lineno = *lineno;
this = css;
continue;
default:
radlog(L_ERR, "%s[%d]: Parse error after \"%s\"",
filename, *lineno, buf1);
return -1;
}
}
if (feof(fp) && (this != current)) {
radlog(L_ERR, "%s[%d]: EOF reached without closing brace for section %s starting at line %d",
filename, *lineno,
cf_section_name1(this), cf_section_lineno(this));
return -1;
}
return 0;
}
int cf_file_include(const char *filename, CONF_SECTION *cs)
{
FILE *fp;
int lineno = 0;
struct stat statbuf;
time_t *mtime;
CONF_DATA *cd;
DEBUG2( "including configuration file %s", filename);
if (stat(filename, &statbuf) == 0) {
#ifdef S_IWOTH
if ((statbuf.st_mode & S_IWOTH) != 0) {
radlog(L_ERR|L_CONS, "Configuration file %s is globally writable. Refusing to start due to insecure configuration.",
filename);
return -1;
}
#endif
#ifdef S_IROTH
if (0 && (statbuf.st_mode & S_IROTH) != 0) {
radlog(L_ERR|L_CONS, "Configuration file %s is globally readable. Refusing to start due to insecure configuration.",
filename);
return -1;
}
#endif
}
fp = fopen(filename, "r");
if (!fp) {
radlog(L_ERR|L_CONS, "Unable to open file \"%s\": %s",
filename, strerror(errno));
return -1;
}
if (cf_data_find_internal(cs, filename, PW_TYPE_FILENAME)) {
fclose(fp);
radlog(L_ERR, "Cannot include the same file twice: \"%s\"",
filename);
return -1;
}
mtime = rad_malloc(sizeof(*mtime));
*mtime = statbuf.st_mtime;
if (cf_data_add_internal(cs, filename, mtime, free,
PW_TYPE_FILENAME) < 0) {
fclose(fp);
radlog(L_ERR|L_CONS, "Internal error opening file \"%s\"",
filename);
return -1;
}
cd = cf_data_find_internal(cs, filename, PW_TYPE_FILENAME);
if (!cd) {
fclose(fp);
radlog(L_ERR|L_CONS, "Internal error opening file \"%s\"",
filename);
return -1;
}
if (!cs->item.filename) cs->item.filename = filename;
if (cf_section_read(cd->name, &lineno, fp, cs) < 0) {
fclose(fp);
return -1;
}
fclose(fp);
return 0;
}
CONF_SECTION *cf_file_read(const char *filename)
{
char *p;
CONF_PAIR *cp;
CONF_SECTION *cs;
cs = cf_section_alloc("main", NULL, NULL);
if (!cs) return NULL;
cp = cf_pair_alloc("confdir", filename, T_OP_SET, T_BARE_WORD, cs);
if (!cp) return NULL;
p = strrchr(cp->value, FR_DIR_SEP);
if (p) *p = '\0';
cp->item.filename = "internal";
cp->item.lineno = 0;
cf_item_add(cs, cf_pairtoitem(cp));
if (cf_file_include(filename, cs) < 0) {
cf_section_free(&cs);
return NULL;
}
return cs;
}
CONF_PAIR *cf_pair_find(const CONF_SECTION *cs, const char *name)
{
CONF_ITEM *ci;
CONF_PAIR *cp = NULL;
if (!cs) return NULL;
if (name) {
CONF_PAIR mycp;
mycp.attr = name;
cp = rbtree_finddata(cs->pair_tree, &mycp);
} else {
for (ci = cs->children; ci; ci = ci->next) {
if (ci->type == CONF_ITEM_PAIR) {
return cf_itemtopair(ci);
}
}
}
if (cp || !cs->template) return cp;
return cf_pair_find(cs->template, name);
}
const char *cf_pair_attr(CONF_PAIR *pair)
{
return (pair ? pair->attr : NULL);
}
const char *cf_pair_value(CONF_PAIR *pair)
{
return (pair ? pair->value : NULL);
}
extern void fr_strerror_printf(const char *, ...);
VALUE_PAIR *cf_pairtovp(CONF_PAIR *pair)
{
VALUE_PAIR *vp;
if (!pair) {
fr_strerror_printf("Internal error");
return NULL;
}
if (!pair->value) {
fr_strerror_printf("No value given for attribute %s", pair->attr);
return NULL;
}
vp = pairmake(pair->attr, NULL, pair->operator);
if (!vp) {
return NULL;
}
if (pair->value_type == T_BARE_WORD) {
if ((vp->type == PW_TYPE_STRING) &&
(pair->value[0] == '0') && (pair->value[1] == 'x')) {
vp->type = PW_TYPE_OCTETS;
}
if (!pairparsevalue(vp, pair->value)) {
pairfree(&vp);
return NULL;
}
vp->flags.do_xlat = 0;
} else if (pair->value_type == T_SINGLE_QUOTED_STRING) {
if (!pairparsevalue(vp, pair->value)) {
pairfree(&vp);
return NULL;
}
vp->flags.do_xlat = 0;
} else {
vp->flags.do_xlat = 1;
}
return vp;
}
const char *cf_section_name1(const CONF_SECTION *cs)
{
return (cs ? cs->name1 : NULL);
}
const char *cf_section_name2(const CONF_SECTION *cs)
{
return (cs ? cs->name2 : NULL);
}
const char *cf_section_value_find(const CONF_SECTION *cs, const char *attr)
{
CONF_PAIR *cp;
cp = cf_pair_find(cs, attr);
return (cp ? cp->value : NULL);
}
CONF_PAIR *cf_pair_find_next(const CONF_SECTION *cs,
CONF_PAIR *pair, const char *attr)
{
CONF_ITEM *ci;
if (pair == NULL){
return cf_pair_find(cs, attr);
}
ci = cf_pairtoitem(pair)->next;
for (; ci; ci = ci->next) {
if (ci->type != CONF_ITEM_PAIR)
continue;
if (attr == NULL || strcmp(cf_itemtopair(ci)->attr, attr) == 0)
break;
}
return cf_itemtopair(ci);
}
CONF_SECTION *cf_section_find(const char *name)
{
if (name)
return cf_section_sub_find(mainconfig.config, name);
else
return mainconfig.config;
}
CONF_SECTION *cf_section_sub_find(const CONF_SECTION *cs, const char *name)
{
CONF_ITEM *ci;
if (!name) return NULL;
if (cs->section_tree) {
CONF_SECTION mycs;
mycs.name1 = name;
mycs.name2 = NULL;
return rbtree_finddata(cs->section_tree, &mycs);
}
for (ci = cs->children; ci; ci = ci->next) {
if (ci->type != CONF_ITEM_SECTION)
continue;
if (strcmp(cf_itemtosection(ci)->name1, name) == 0)
break;
}
return cf_itemtosection(ci);
}
CONF_SECTION *cf_section_sub_find_name2(const CONF_SECTION *cs,
const char *name1, const char *name2)
{
CONF_ITEM *ci;
if (!cs) cs = mainconfig.config;
if (name1 && (cs->section_tree)) {
CONF_SECTION mycs, *master_cs;
mycs.name1 = name1;
mycs.name2 = name2;
master_cs = rbtree_finddata(cs->section_tree, &mycs);
if (master_cs) {
return rbtree_finddata(master_cs->name2_tree, &mycs);
}
}
for (ci = cs->children; ci; ci = ci->next) {
CONF_SECTION *subcs;
if (ci->type != CONF_ITEM_SECTION)
continue;
subcs = cf_itemtosection(ci);
if (!name1) {
if (!subcs->name2) {
if (strcmp(subcs->name1, name2) == 0) break;
} else {
if (strcmp(subcs->name2, name2) == 0) break;
}
continue;
}
if ((strcmp(subcs->name1, name1) == 0) &&
(subcs->name2 != NULL) &&
(strcmp(subcs->name2, name2) == 0))
break;
}
return cf_itemtosection(ci);
}
CONF_SECTION *cf_subsection_find_next(CONF_SECTION *section,
CONF_SECTION *subsection,
const char *name1)
{
CONF_ITEM *ci;
if (subsection == NULL){
ci = section->children;
} else {
ci = cf_sectiontoitem(subsection)->next;
}
for (; ci; ci = ci->next) {
if (ci->type != CONF_ITEM_SECTION)
continue;
if ((name1 == NULL) ||
(strcmp(cf_itemtosection(ci)->name1, name1) == 0))
break;
}
return cf_itemtosection(ci);
}
CONF_SECTION *cf_section_find_next(CONF_SECTION *section,
CONF_SECTION *subsection,
const char *name1)
{
if (!section->item.parent) return NULL;
return cf_subsection_find_next(section->item.parent, subsection, name1);
}
CONF_ITEM *cf_item_find_next(CONF_SECTION *section, CONF_ITEM *item)
{
if (item == NULL) {
return section->children;
} else {
return item->next;
}
}
int cf_section_lineno(CONF_SECTION *section)
{
return cf_sectiontoitem(section)->lineno;
}
const char *cf_pair_filename(CONF_PAIR *pair)
{
return cf_pairtoitem(pair)->filename;
}
const char *cf_section_filename(CONF_SECTION *section)
{
return cf_sectiontoitem(section)->filename;
}
int cf_pair_lineno(CONF_PAIR *pair)
{
return cf_pairtoitem(pair)->lineno;
}
int cf_item_is_section(CONF_ITEM *item)
{
return item->type == CONF_ITEM_SECTION;
}
int cf_item_is_pair(CONF_ITEM *item)
{
return item->type == CONF_ITEM_PAIR;
}
static CONF_DATA *cf_data_alloc(CONF_SECTION *parent, const char *name,
void *data, void (*data_free)(void *))
{
char *p;
size_t name_len;
CONF_DATA *cd;
name_len = strlen(name) + 1;
p = rad_malloc(sizeof(*cd) + name_len);
cd = (CONF_DATA *) p;
memset(cd, 0, sizeof(*cd));
cd->item.type = CONF_ITEM_DATA;
cd->item.parent = parent;
cd->data = data;
cd->free = data_free;
p += sizeof(*cd);
memcpy(p, name, name_len);
cd->name = p;
return cd;
}
static void *cf_data_find_internal(CONF_SECTION *cs, const char *name,
int flag)
{
if (!cs || !name) return NULL;
if (cs->data_tree) {
CONF_DATA mycd;
mycd.name = name;
mycd.flag = flag;
return rbtree_finddata(cs->data_tree, &mycd);
}
return NULL;
}
void *cf_data_find(CONF_SECTION *cs, const char *name)
{
CONF_DATA *cd = cf_data_find_internal(cs, name, 0);
if (cd) return cd->data;
return NULL;
}
static int cf_data_add_internal(CONF_SECTION *cs, const char *name,
void *data, void (*data_free)(void *),
int flag)
{
CONF_DATA *cd;
if (!cs || !name) return -1;
if (cf_data_find_internal(cs, name, flag) != NULL) return -1;
cd = cf_data_alloc(cs, name, data, data_free);
if (!cd) return -1;
cd->flag = flag;
cf_item_add(cs, cf_datatoitem(cd));
return 0;
}
int cf_data_add(CONF_SECTION *cs, const char *name,
void *data, void (*data_free)(void *))
{
return cf_data_add_internal(cs, name, data, data_free, 0);
}
#if 0
static void cf_section_copy_data(CONF_SECTION *s, CONF_SECTION *d)
{
CONF_ITEM *cd, *next, **last;
rad_assert(d->data_tree == NULL);
d->data_tree = s->data_tree;
s->data_tree = NULL;
last = &(s->children);
for (cd = s->children; cd != NULL; cd = next) {
next = cd->next;
if (cd->type == CONF_ITEM_SECTION) {
CONF_SECTION *s1, *d1;
s1 = cf_itemtosection(cd);
d1 = cf_section_sub_find_name2(d, s1->name1, s1->name2);
if (d1) {
cf_section_copy_data(s1, d1);
}
last = &(cd->next);
continue;
}
if (cd->type != CONF_ITEM_DATA) {
last = &(cd->next);
continue;
}
*last = cd->next;
cd->next = NULL;
if (!d->children) {
rad_assert(d->tail == NULL);
d->children = cd;
} else {
rad_assert(d->tail != NULL);
d->tail->next = cd;
}
d->tail = cd;
}
}
static int filename_stat(void *context, void *data)
{
struct stat buf;
CONF_DATA *cd = data;
context = context;
if (cd->flag != PW_TYPE_FILENAME) return 0;
if (stat(cd->name, &buf) < 0) return -1;
if (buf.st_mtime != *(time_t *) cd->data) return -1;
return 0;
}
static int cf_section_cmp(CONF_SECTION *a, CONF_SECTION *b)
{
CONF_ITEM *ca = a->children;
CONF_ITEM *cb = b->children;
while (1) {
CONF_PAIR *pa, *pb;
if (!ca && !cb) break;
if (ca && ca->type == CONF_ITEM_DATA) {
ca = ca->next;
continue;
}
if (cb && cb->type == CONF_ITEM_DATA) {
cb = cb->next;
continue;
}
if (!ca || !cb) return 0;
if (ca->type != cb->type) return 0;
if (ca->type == CONF_ITEM_SECTION) {
CONF_SECTION *sa = cf_itemtosection(ca);
CONF_SECTION *sb = cf_itemtosection(cb);
if (!cf_section_cmp(sa, sb)) return 0;
goto next;
}
rad_assert(ca->type == CONF_ITEM_PAIR);
pa = cf_itemtopair(ca);
pb = cf_itemtopair(cb);
if ((strcmp(pa->attr, pb->attr) != 0) ||
(strcmp(pa->value, pb->value) != 0)) return 0;
next:
ca = ca->next;
cb = cb->next;
}
if (a->data_tree &&
(rbtree_walk(a->data_tree, InOrder, filename_stat, NULL) != 0)) {
return 0;
}
return 1;
}
int cf_section_migrate(CONF_SECTION *dst, CONF_SECTION *src)
{
CONF_ITEM *ci;
CONF_SECTION *s, *d;
for (ci = src->children; ci != NULL; ci = ci->next) {
if (ci->type != CONF_ITEM_SECTION)
continue;
s = cf_itemtosection(ci);
d = cf_section_sub_find_name2(dst, s->name1, s->name2);
if (!d) continue;
if (cf_section_cmp(s, d)) {
cf_section_copy_data(s, d);
}
}
return 1;
}
#endif
int cf_section_template(CONF_SECTION *cs, CONF_SECTION *template)
{
if (!cs || !template || cs->template || template->template) return -1;
cs->template = template;
return 0;
}
void cf_log_err(CONF_ITEM *ci, const char *fmt, ...)
{
va_list ap;
char buffer[256];
va_start(ap, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, ap);
va_end(ap);
radlog(L_ERR, "%s[%d]: %s", ci->filename, ci->lineno, buffer);
}
void cf_log_info(CONF_SECTION *cs, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (debug_flag > 1 && cf_log_config && cs) vradlog(L_DBG, fmt, ap);
va_end(ap);
}
void cf_log_module(CONF_SECTION *cs, const char *fmt, ...)
{
va_list ap;
char buffer[256];
va_start(ap, fmt);
if (debug_flag > 1 && cf_log_modules && cs) {
vsnprintf(buffer, sizeof(buffer), fmt, ap);
radlog(L_DBG, " Module: %s", buffer);
}
va_end(ap);
}
const CONF_PARSER *cf_section_parse_table(CONF_SECTION *cs)
{
if (!cs) return NULL;
return cs->variables;
}
#if 0
static int dump_config_section(CONF_SECTION *cs, int indent)
{
CONF_SECTION *scs;
CONF_PAIR *cp;
CONF_ITEM *ci;
for (ci = cs->children; ci; ci = ci->next) {
switch (ci->type) {
case CONF_ITEM_PAIR:
cp=cf_itemtopair(ci);
DEBUG("%.*s%s = %s",
indent, "\t\t\t\t\t\t\t\t\t\t\t",
cp->attr, cp->value);
break;
case CONF_ITEM_SECTION:
scs=cf_itemtosection(ci);
DEBUG("%.*s%s %s%s{",
indent, "\t\t\t\t\t\t\t\t\t\t\t",
scs->name1,
scs->name2 ? scs->name2 : "",
scs->name2 ? " " : "");
dump_config_section(scs, indent+1);
DEBUG("%.*s}",
indent, "\t\t\t\t\t\t\t\t\t\t\t");
break;
default:
break;
}
}
return 0;
}
int dump_config(CONF_SECTION *cs)
{
return dump_config_section(cs, 0);
}
#endif
static const char *cf_pair_print_value(const CONF_PAIR *cp,
char *buffer, size_t buflen)
{
char *p;
if (!cp->value) return "";
switch (cp->value_type) {
default:
case T_BARE_WORD:
snprintf(buffer, buflen, "%s", cp->value);
break;
case T_SINGLE_QUOTED_STRING:
snprintf(buffer, buflen, "'%s'", cp->value);
break;
case T_DOUBLE_QUOTED_STRING:
buffer[0] = '"';
fr_print_string(cp->value, strlen(cp->value),
buffer + 1, buflen - 3);
p = buffer + strlen(buffer);
p[0] = '"';
p[1] = '\0';
break;
}
return buffer;
}
int cf_pair2xml(FILE *fp, const CONF_PAIR *cp)
{
fprintf(fp, "<%s>", cp->attr);
if (cp->value) {
char buffer[2048];
char *p = buffer;
const char *q = cp->value;
while (*q && (p < (buffer + sizeof(buffer) - 1))) {
if (q[0] == '&') {
memcpy(p, "&", 4);
p += 5;
} else if (q[0] == '<') {
memcpy(p, "<", 4);
p += 4;
} else if (q[0] == '>') {
memcpy(p, ">", 4);
p += 4;
} else {
*(p++) = *q;
}
q++;
}
*p = '\0';
fprintf(fp, "%s", buffer);
}
fprintf(fp, "</%s>\n", cp->attr);
return 1;
}
int cf_section2xml(FILE *fp, const CONF_SECTION *cs)
{
CONF_ITEM *ci, *next;
fprintf(fp, "<%s>\n", cs->name1);
if (cs->name2) {
fprintf(fp, "<_name2>%s</_name2>\n", cs->name2);
}
for (ci = cs->children; ci; ci = next) {
next = ci->next;
switch (ci->type) {
case CONF_ITEM_PAIR:
if (!cf_pair2xml(fp, (CONF_PAIR *) ci)) return 0;
break;
case CONF_ITEM_SECTION:
if (!cf_section2xml(fp, (CONF_SECTION *) ci)) return 0;
break;
default:
break;
}
}
fprintf(fp, "</%s>\n", cs->name1);
return 1;
}
int cf_pair2file(FILE *fp, const CONF_PAIR *cp)
{
char buffer[2048];
fprintf(fp, "\t%s = %s\n", cp->attr,
cf_pair_print_value(cp, buffer, sizeof(buffer)));
return 1;
}
int cf_section2file(FILE *fp, const CONF_SECTION *cs)
{
const CONF_ITEM *ci, *next;
if (!cs->name2) {
fprintf(fp, "%s {\n", cs->name1);
} else {
fprintf(fp, "%s %s {\n",
cs->name1, cs->name2);
}
for (ci = cs->children; ci; ci = next) {
next = ci->next;
switch (ci->type) {
case CONF_ITEM_PAIR:
if (!cf_pair2file(fp, (const CONF_PAIR *) ci)) return 0;
break;
case CONF_ITEM_SECTION:
if (!cf_section2file(fp, (const CONF_SECTION *) ci)) return 0;
break;
default:
break;
}
}
fprintf(fp, "}\n");
return 1;
}