解析配置文件库
主节点:
cs = cf_section_alloc(NULL, "main", NULL);
cf_pair_alloc(this, buf1, value, t2, t1, t3);
cf_section_alloc(this, buf1, t2 == T_LCBRACE ? NULL : buf2);
modules = cf_section_sub_find(config, "modules");
for (ci = cf_item_find_next(modules, NULL); ci != NULL; ci = next) {
char const *name1;
CONF_SECTION *subcs;
module_instance_t *node;
next = cf_item_find_next(modules, ci);
if (!cf_item_is_section(ci)) continue;
subcs = cf_item_to_section(ci);
node = module_bootstrap(subcs);
if (!node) return -1;
if (!next || !cf_item_is_section(next)) continue;
}
struct conf_item {
struct conf_item *next; //!< Sibling.
struct conf_part *parent; //!< Parent.
int lineno; //!< The line number the config item began on.
char const *filename; //!< The file the config item was parsed from.
CONF_ITEM_TYPE type; //!< Whether the config item is a config_pair, conf_section or conf_data.
};
//typedef struct conf_part CONF_SECTION;
//!< #CONF_ITEM used to group multiple #CONF_PAIR and #CONF_SECTION, together.
struct conf_part {
CONF_ITEM item;
char const *name1; //!< First name token. Given ``foo bar {}`` would be ``foo``.
char const *name2; //!< Second name token. Given ``foo bar {}`` would be ``bar``.
FR_TOKEN name2_type; //!< The type of quoting around name2.
CONF_ITEM *children;
CONF_ITEM *tail; //!< For speed.
CONF_SECTION *template;
rbtree_t *pair_tree; //!< and a partridge..
rbtree_t *section_tree; //!< no jokes here.
rbtree_t *name2_tree; //!< for sections of the same name2
rbtree_t *data_tree;
void *base;
int depth;
CONF_PARSER const *variables;
};
/** Configuration AVP similar to a VALUE_PAIR
*
*/
struct conf_pair {
CONF_ITEM item;
char const *attr; //!< Attribute name
char const *value; //!< Attribute value
FR_TOKEN op; //!< Operator e.g. =, :=
FR_TOKEN lhs_type; //!< Name quoting style T_(DOUBLE|SINGLE|BACK)_QUOTE_STRING or T_BARE_WORD.
FR_TOKEN rhs_type; //!< Value Quoting style T_(DOUBLE|SINGLE|BACK)_QUOTE_STRING or T_BARE_WORD.
bool pass2; //!< do expansion in pass2.
bool parsed; //!< Was this item used during parsing?
};
/** Internal data that is associated with a configuration section
*
*/
struct conf_data {
CONF_ITEM item;
char const *name;
int flag;
void *data; //!< User data
void (*free)(void *); //!< Free user data function
};
Thread ID:0 [main_config_init@954]cf_section feature : n2
Thread ID:0 [main_config_init@954]cf_section version : n2
Thread ID:0 [main_config_init@954]cf_section log : n2
Thread ID:0 [main_config_init@954]cf_section ENV : n2
Thread ID:0 [main_config_init@954]cf_section security : n2
Thread ID:0 [main_config_init@954]cf_section proxy : server
Thread ID:0 [main_config_init@954]cf_section home_server : localhost
Thread ID:0 [main_config_init@954]cf_section home_server_pool : my_auth_failover
Thread ID:0 [main_config_init@954]cf_section realm : example.com
Thread ID:0 [main_config_init@954]cf_section realm : LOCAL
Thread ID:0 [main_config_init@954]cf_section client : localhost
Thread ID:0 [main_config_init@954]cf_section client : cloud_radius_clients
Thread ID:0 [main_config_init@954]cf_section thread : pool
Thread ID:0 [main_config_init@954]cf_section modules : n2
Thread ID:0 [main_config_init@954]cf_section instantiate : n2
Thread ID:0 [main_config_init@954]cf_section policy : n2
Thread ID:0 [main_config_init@954]cf_section server : check-by-controller
Thread ID:0 [main_config_init@954]cf_section server : default
Thread ID:0 [main_config_init@954]cf_section server : inner-tunnel
Thread ID:0 [main_config_init@967]cf_pair_attr confdir :/usr/local/etc/raddb
Thread ID:0 [main_config_init@967]cf_pair_attr prefix :/usr/local
Thread ID:0 [main_config_init@967]cf_pair_attr exec_prefix :/usr/local
Thread ID:0 [main_config_init@967]cf_pair_attr sysconfdir :/usr/local/etc
Thread ID:0 [main_config_init@967]cf_pair_attr localstatedir :/usr/local/var
Thread ID:0 [main_config_init@967]cf_pair_attr sbindir :/usr/local/sbin
Thread ID:0 [main_config_init@967]cf_pair_attr logdir :/usr/local/var/log/radius
Thread ID:0 [main_config_init@967]cf_pair_attr raddbdir :/usr/local/etc/raddb
Thread ID:0 [main_config_init@967]cf_pair_attr radacctdir :/usr/local/var/log/radius/radacct
Thread ID:0 [main_config_init@967]cf_pair_attr name :radiusd
Thread ID:0 [main_config_init@967]cf_pair_attr confdir :/usr/local/etc/raddb
Thread ID:0 [main_config_init@967]cf_pair_attr modconfdir :/usr/local/etc/raddb/mods-config
Thread ID:0 [main_config_init@967]cf_pair_attr certdir :/usr/local/etc/raddb/certs
Thread ID:0 [main_config_init@967]cf_pair_attr cadir :/usr/local/etc/raddb/certs
Thread ID:0 [main_config_init@967]cf_pair_attr run_dir :/usr/local/var/run/radiusd
Thread ID:0 [main_config_init@967]cf_pair_attr db_dir :/usr/local/etc/raddb
Thread ID:0 [main_config_init@967]cf_pair_attr libdir :/usr/local/lib
Thread ID:0 [main_config_init@967]cf_pair_attr pidfile :/usr/local/var/run/radiusd/radiusd.pid
Thread ID:0 [main_config_init@967]cf_pair_attr max_request_time :30
Thread ID:0 [main_config_init@967]cf_pair_attr cleanup_delay :5
Thread ID:0 [main_config_init@967]cf_pair_attr max_requests :16384
Thread ID:0 [main_config_init@967]cf_pair_attr hostname_lookups :no
Thread ID:0 [main_config_init@967]cf_pair_attr checkrad :/usr/local/sbin/checkrad
Thread ID:0 [main_config_init@967]cf_pair_attr proxy_requests :no
root_config = main_config.config = cs;
//解析radiusd.conf的结果
根据log分析:
一开始解析radiud.conf文件,创建main section,parse conf文件以main section 为root config section创建如下section
Thread ID:0 [main_config_init@954]cf_section feature : n2
Thread ID:0 [main_config_init@954]cf_section version : n2
Thread ID:0 [main_config_init@954]cf_section log : n2
Thread ID:0 [main_config_init@954]cf_section ENV : n2
Thread ID:0 [main_config_init@954]cf_section security : n2
Thread ID:0 [main_config_init@954]cf_section proxy : server
Thread ID:0 [main_config_init@954]cf_section home_server : localhost
Thread ID:0 [main_config_init@954]cf_section home_server_pool : my_auth_failover
Thread ID:0 [main_config_init@954]cf_section realm : example.com
Thread ID:0 [main_config_init@954]cf_section realm : LOCAL
Thread ID:0 [main_config_init@954]cf_section client : localhost
Thread ID:0 [main_config_init@954]cf_section client : cloud_radius_clients
Thread ID:0 [main_config_init@954]cf_section thread : pool
Thread ID:0 [main_config_init@954]cf_section modules : n2
Thread ID:0 [main_config_init@954]cf_section instantiate : n2
Thread ID:0 [main_config_init@954]cf_section policy : n2
Thread ID:0 [main_config_init@954]cf_section server : check-by-controller
Thread ID:0 [main_config_init@954]cf_section server : default
Thread ID:0 [main_config_init@954]cf_section server : inner-tunnel
同时创建cf_pair_attr 如下: key value 键值对如下:
Thread ID:0 [main_config_init@967]cf_pair_attr confdir :/usr/local/etc/raddbThread ID:0 [main_config_init@967]cf_pair_attr prefix :/usr/local
Thread ID:0 [main_config_init@967]cf_pair_attr exec_prefix :/usr/local
Thread ID:0 [main_config_init@967]cf_pair_attr sysconfdir :/usr/local/etc
Thread ID:0 [main_config_init@967]cf_pair_attr localstatedir :/usr/local/var
Thread ID:0 [main_config_init@967]cf_pair_attr sbindir :/usr/local/sbin
Thread ID:0 [main_config_init@967]cf_pair_attr logdir :/usr/local/var/log/radius
Thread ID:0 [main_config_init@967]cf_pair_attr raddbdir :/usr/local/etc/raddb
Thread ID:0 [main_config_init@967]cf_pair_attr radacctdir :/usr/local/var/log/radius/radacct
Thread ID:0 [main_config_init@967]cf_pair_attr name :radiusd
Thread ID:0 [main_config_init@967]cf_pair_attr confdir :/usr/local/etc/raddb
Thread ID:0 [main_config_init@967]cf_pair_attr modconfdir :/usr/local/etc/raddb/mods-config
Thread ID:0 [main_config_init@967]cf_pair_attr certdir :/usr/local/etc/raddb/certs
Thread ID:0 [main_config_init@967]cf_pair_attr cadir :/usr/local/etc/raddb/certs
Thread ID:0 [main_config_init@967]cf_pair_attr run_dir :/usr/local/var/run/radiusd
Thread ID:0 [main_config_init@967]cf_pair_attr db_dir :/usr/local/etc/raddb
Thread ID:0 [main_config_init@967]cf_pair_attr libdir :/usr/local/lib
Thread ID:0 [main_config_init@967]cf_pair_attr pidfile :/usr/local/var/run/radiusd/radiusd.pid
Thread ID:0 [main_config_init@967]cf_pair_attr max_request_time :30
Thread ID:0 [main_config_init@967]cf_pair_attr cleanup_delay :5
Thread ID:0 [main_config_init@967]cf_pair_attr max_requests :16384
Thread ID:0 [main_config_init@967]cf_pair_attr hostname_lookups :no
Thread ID:0 [main_config_init@967]cf_pair_attr checkrad :/usr/local/sbin/checkrad
Thread ID:0 [main_config_init@967]cf_pair_attr proxy_requests :no
以解析/usr/local/etc/raddb/mods-enabled/eap文件为例:
Thread ID:0 [cf_file_open@336]including configuration file /usr/local/etc/raddb/mods-enabled/eap
Thread ID:0 [cf_file_include@3025]xxxxxxx include /usr/local/etc/raddb/mods-enabled/eap :0
Thread ID:0 [cf_section_read@2946] CONF_SECTION add /usr/local/etc/raddb/mods-enabled/eap[1]: key:'eap'---value:NULL, parent name1:modules name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[2]: key:'default_eap_type'---value:md5, parent name1:eap name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[3]: key:'timer_expire'---value:60, parent name1:eap name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[4]: key:'ignore_unknown_eap_types'---value:no, parent name1:eap name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[5]: key:'cisco_accounting_username_bug'---value:no, parent name1:eap name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[6]: key:'max_sessions'---value:16384, parent name1:eap name2:null
Thread ID:0 [cf_section_read@2946] CONF_SECTION add /usr/local/etc/raddb/mods-enabled/eap[7]: key:'md5'---value:NULL, parent name1:eap name2:null
Thread ID:0 [cf_section_read@2946] CONF_SECTION add /usr/local/etc/raddb/mods-enabled/eap[9]: key:'gtc'---value:NULL, parent name1:eap name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[10]: key:'auth_type'---value:PAP, parent name1:gtc name2:null
Thread ID:0 [cf_section_read@2946] CONF_SECTION add /usr/local/etc/raddb/mods-enabled/eap[12]: key:'tls-config'---value:tls-common, parent name1:eap name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[13]: key:'private_key_password'---value:123456, parent name1:tls-config name2:tls-common
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[14]: key:'private_key_file'---value:/usr/local/etc/raddb/certs/server.pem, parent name1:tls-config name2:tls-common
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[15]: key:'certificate_file'---value:/usr/local/etc/raddb/certs/server.pem, parent name1:tls-config name2:tls-common
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[16]: key:'ca_file'---value:/usr/local/etc/raddb/certs/ca.pem, parent name1:tls-config name2:tls-common
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[17]: key:'ca_path'---value:/usr/local/etc/raddb/certs, parent name1:tls-config name2:tls-common
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[18]: key:'cipher_list'---value:DEFAULT, parent name1:tls-config name2:tls-common
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[19]: key:'cipher_server_preference'---value:no, parent name1:tls-config name2:tls-common
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[20]: key:'tls_min_version'---value:1.0, parent name1:tls-config name2:tls-common
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[21]: key:'tls_max_version'---value:1.2, parent name1:tls-config name2:tls-common
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[22]: key:'ecdh_curve'---value:, parent name1:tls-config name2:tls-common
Thread ID:0 [cf_section_read@2946] CONF_SECTION add /usr/local/etc/raddb/mods-enabled/eap[23]: key:'cache'---value:NULL, parent name1:tls-config name2:tls-common
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[24]: key:'enable'---value:no, parent name1:cache name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[25]: key:'lifetime'---value:24, parent name1:cache name2:null
Thread ID:0 [cf_section_read@2946] CONF_SECTION add /usr/local/etc/raddb/mods-enabled/eap[26]: key:'store'---value:NULL, parent name1:cache name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[27]: key:'Tunnel-Private-Group-Id'---value:(null), parent name1:store name2:null
Thread ID:0 [cf_section_read@2946] CONF_SECTION add /usr/local/etc/raddb/mods-enabled/eap[30]: key:'verify'---value:NULL, parent name1:tls-config name2:tls-common
Thread ID:0 [cf_section_read@2946] CONF_SECTION add /usr/local/etc/raddb/mods-enabled/eap[32]: key:'ocsp'---value:NULL, parent name1:tls-config name2:tls-common
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[33]: key:'enable'---value:no, parent name1:ocsp name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[34]: key:'override_cert_url'---value:yes, parent name1:ocsp name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[35]: key:'url'---value:http://127.0.0.1/ocsp/, parent name1:ocsp name2:null
Thread ID:0 [cf_section_read@2946] CONF_SECTION add /usr/local/etc/raddb/mods-enabled/eap[38]: key:'tls'---value:NULL, parent name1:eap name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[39]: key:'tls'---value:tls-common, parent name1:tls name2:null
Thread ID:0 [cf_section_read@2946] CONF_SECTION add /usr/local/etc/raddb/mods-enabled/eap[41]: key:'ttls'---value:NULL, parent name1:eap name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[42]: key:'tls'---value:tls-common, parent name1:ttls name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[43]: key:'default_eap_type'---value:md5, parent name1:ttls name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[44]: key:'copy_request_to_tunnel'---value:no, parent name1:ttls name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[45]: key:'use_tunneled_reply'---value:no, parent name1:ttls name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[46]: key:'virtual_server'---value:inner-tunnel, parent name1:ttls name2:null
Thread ID:0 [cf_section_read@2946] CONF_SECTION add /usr/local/etc/raddb/mods-enabled/eap[48]: key:'peap'---value:NULL, parent name1:eap name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[49]: key:'tls'---value:tls-common, parent name1:peap name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[50]: key:'default_eap_type'---value:mschapv2, parent name1:peap name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[51]: key:'copy_request_to_tunnel'---value:yes, parent name1:peap name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[52]: key:'use_tunneled_reply'---value:yes, parent name1:peap name2:null
Thread ID:0 [cf_section_read@2897] CONF_PAIR add /usr/local/etc/raddb/mods-enabled/eap[53]: key:'virtual_server'---value:inner-tunnel, parent name1:peap name2:null
Thread ID:0 [cf_section_read@2946] CONF_SECTION add /usr/local/etc/raddb/mods-enabled/eap[55]: key:'mschapv2'---value:NULL, parent name1:eap name2:null
eap {
default_eap_type = md5
timer_expire = 60
ignore_unknown_eap_types = no
cisco_accounting_username_bug = no
max_sessions = ${max_requests}
md5 {
}
gtc {
auth_type = PAP
}
tls-config tls-common {
private_key_password = 123456
private_key_file = ${certdir}/server.pem
certificate_file = ${certdir}/server.pem
ca_file = ${cadir}/ca.pem
ca_path = ${cadir}
cipher_list = "DEFAULT"
cipher_server_preference = no
tls_min_version = "1.0"
tls_max_version = "1.2"
ecdh_curve = ""
cache {
enable = no
lifetime = 24 # hours
store {
Tunnel-Private-Group-Id
}
}
verify {
}
ocsp {
enable = no
override_cert_url = yes
url = "http://127.0.0.1/ocsp/"
}
}
tls {
tls = tls-common
}
ttls {
tls = tls-common
default_eap_type = md5
copy_request_to_tunnel = no
use_tunneled_reply = no
virtual_server = "inner-tunnel"
}
peap {
tls = tls-common
default_eap_type = mschapv2
copy_request_to_tunnel = yes
use_tunneled_reply = yes
virtual_server = "inner-tunnel"
}
mschapv2 {
}
}
对于配置文件的解析conffile.c 文件, 当做代码库后续使用
查看代码
/*
* conffile.c Read the radiusd.conf file.
*
* Yep I should learn to use lex & yacc, or at least
* write a decent parser. I know how to do that, really :)
* miquels@cistron.nl
*
* Version: $Id: 7fe658711ffcc665bcc777d860c3e66d21d3856f $
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* Copyright 2000,2006 The FreeRADIUS server project
* Copyright 2000 Miquel van Smoorenburg <miquels@cistron.nl>
* Copyright 2000 Alan DeKok <aland@ox.org>
*/
RCSID("$Id: 7fe658711ffcc665bcc777d860c3e66d21d3856f $")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/parser.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>
//#define ERROR(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__)
bool check_config = false;
typedef enum conf_property {
CONF_PROPERTY_INVALID = 0,
CONF_PROPERTY_NAME,
CONF_PROPERTY_INSTANCE,
} CONF_PROPERTY;
static const FR_NAME_NUMBER conf_property_name[] = {
{ "name", CONF_PROPERTY_NAME},
{ "instance", CONF_PROPERTY_INSTANCE},
{ NULL , -1 }
};
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; //!< Sibling.
struct conf_part *parent; //!< Parent.
int lineno; //!< The line number the config item began on.
char const *filename; //!< The file the config item was parsed from.
CONF_ITEM_TYPE type; //!< Whether the config item is a config_pair, conf_section or conf_data.
};
/** Configuration AVP similar to a VALUE_PAIR
*
*/
struct conf_pair {
CONF_ITEM item;
char const *attr; //!< Attribute name
char const *value; //!< Attribute value
FR_TOKEN op; //!< Operator e.g. =, :=
FR_TOKEN lhs_type; //!< Name quoting style T_(DOUBLE|SINGLE|BACK)_QUOTE_STRING or T_BARE_WORD.
FR_TOKEN rhs_type; //!< Value Quoting style T_(DOUBLE|SINGLE|BACK)_QUOTE_STRING or T_BARE_WORD.
bool pass2; //!< do expansion in pass2.
bool parsed; //!< Was this item used during parsing?
};
/** Internal data that is associated with a configuration section
*
*/
struct conf_data {
CONF_ITEM item;
char const *name;
int flag;
void *data; //!< User data
void (*free)(void *); //!< Free user data function
};
struct conf_part {
CONF_ITEM item;
char const *name1; //!< First name token. Given ``foo bar {}`` would be ``foo``.
char const *name2; //!< Second name token. Given ``foo bar {}`` would be ``bar``.
FR_TOKEN name2_type; //!< The type of quoting around name2.
CONF_ITEM *children;
CONF_ITEM *tail; //!< For speed.
CONF_SECTION *template;
rbtree_t *pair_tree; //!< and a partridge..
rbtree_t *section_tree; //!< no jokes here.
rbtree_t *name2_tree; //!< for sections of the same name2
rbtree_t *data_tree;
void *base;
int depth;
CONF_PARSER const *variables;
};
typedef struct cf_file_t {
char const *filename;
CONF_SECTION *cs;
struct stat buf;
bool from_dir;
} cf_file_t;
CONF_SECTION *root_config = NULL;
bool cf_new_escape = true;
static int cf_data_add_internal(CONF_SECTION *cs, char const *name, void *data,
void (*data_free)(void *), int flag);
static void *cf_data_find_internal(CONF_SECTION const *cs, char const *name, int flag);
static char const *cf_expand_variables(char const *cf, int *lineno,
CONF_SECTION *outercs,
char *output, size_t outsize,
char const *input, bool *soft_fail);
static int cf_file_include(CONF_SECTION *cs, char const *filename_in, bool from_dir);
/*
* Isolate the scary casts in these tiny provably-safe functions
*/
/** Cast a CONF_ITEM to a CONF_PAIR
*
*/
CONF_PAIR *cf_item_to_pair(CONF_ITEM const *ci)
{
CONF_PAIR *out;
if (ci == NULL) return NULL;
rad_assert(ci->type == CONF_ITEM_PAIR);
memcpy(&out, &ci, sizeof(out));
return out;
}
/** Cast a CONF_ITEM to a CONF_SECTION
*
*/
CONF_SECTION *cf_item_to_section(CONF_ITEM const *ci)
{
CONF_SECTION *out;
if (ci == NULL) return NULL;
rad_assert(ci->type == CONF_ITEM_SECTION);
memcpy(&out, &ci, sizeof(out));
return out;
}
/** Cast a CONF_PAIR to a CONF_ITEM
*
*/
CONF_ITEM *cf_pair_to_item(CONF_PAIR const *cp)
{
CONF_ITEM *out;
if (cp == NULL) return NULL;
memcpy(&out, &cp, sizeof(out));
return out;
}
/** Cast a CONF_SECTION to a CONF_ITEM
*
*/
CONF_ITEM *cf_section_to_item(CONF_SECTION const *cs)
{
CONF_ITEM *out;
if (cs == NULL) return NULL;
memcpy(&out, &cs, sizeof(out));
return out;
}
/** Cast CONF_DATA to a CONF_ITEM
*
*/
static CONF_ITEM *cf_data_to_item(CONF_DATA const *cd)
{
CONF_ITEM *out;
if (cd == NULL) {
return NULL;
}
memcpy(&out, &cd, sizeof(out));
return out;
}
static int _cf_data_free(CONF_DATA *cd)
{
if (cd->free) cd->free(cd->data);
return 0;
}
/*
* rbtree callback function
*/
static int pair_cmp(void const *a, void const *b)
{
CONF_PAIR const *one = a;
CONF_PAIR const *two = b;
return strcmp(one->attr, two->attr);
}
/*
* rbtree callback function
*/
static int section_cmp(void const *a, void const *b)
{
CONF_SECTION const *one = a;
CONF_SECTION const *two = b;
return strcmp(one->name1, two->name1);
}
/*
* rbtree callback function
*/
static int name2_cmp(void const *a, void const *b)
{
CONF_SECTION const *one = a;
CONF_SECTION const *two = b;
rad_assert(strcmp(one->name1, two->name1) == 0);
if (!one->name2 && !two->name2) return 0;
if (one->name2 && !two->name2) return -1;
if (!one->name2 && two->name2) return +1;
return strcmp(one->name2, two->name2);
}
/*
* rbtree callback function
*/
static int data_cmp(void const *a, void const *b)
{
int rcode;
CONF_DATA const *one = a;
CONF_DATA const *two = b;
rcode = one->flag - two->flag;
if (rcode != 0) return rcode;
return strcmp(one->name, two->name);
}
/*
* Functions for tracking filenames.
*/
static int filename_cmp(void const *a, void const *b)
{
cf_file_t const *one = a;
cf_file_t const *two = b;
if (one->buf.st_dev < two->buf.st_dev) return -1;
if (one->buf.st_dev > two->buf.st_dev) return +1;
if (one->buf.st_ino < two->buf.st_ino) return -1;
if (one->buf.st_ino > two->buf.st_ino) return +1;
return 0;
}
static int cf_file_open(CONF_SECTION *cs, char const *filename, bool from_dir, FILE **fp_p)
{
cf_file_t *file;
CONF_DATA *cd;
CONF_SECTION *top;
rbtree_t *tree;
int fd;
FILE *fp;
top = cf_top_section(cs);
cd = cf_data_find_internal(top, "filename", 0);
if (!cd) return -1;
tree = cd->data;
/*
* If we're including a wildcard directory, then ignore
* any files the users has already explicitly loaded in
* that directory.
*/
if (from_dir) {
cf_file_t my_file;
my_file.cs = cs;
my_file.filename = filename;
if (stat(filename, &my_file.buf) < 0) goto error;
file = rbtree_finddata(tree, &my_file);
if (file && !file->from_dir) return 0;
}
DEBUG2("including configuration file %s", filename);
fp = fopen(filename, "r");
if (!fp) {
error:
ERROR("Unable to open file \"%s\": %s",
filename, fr_syserror(errno));
return -1;
}
fd = fileno(fp);
file = talloc(tree, cf_file_t);
if (!file) {
fclose(fp);
return -1;
}
file->filename = filename;
file->cs = cs;
if (fstat(fd, &file->buf) == 0) {
#ifdef S_IWOTH
if ((file->buf.st_mode & S_IWOTH) != 0) {
ERROR("Configuration file %s is globally writable. "
"Refusing to start due to insecure configuration.", filename);
fclose(fp);
talloc_free(file);
return -1;
}
#endif
}
/*
* We can include the same file twice. e.g. when it
* contains common definitions, such as for SQL.
*
* Though the admin should really use templates for that.
*/
if (!rbtree_insert(tree, file)) {
talloc_free(file);
}
*fp_p = fp;
return 1;
}
/*
* Do some checks on the file
*/
static bool cf_file_check(CONF_SECTION *cs, char const *filename, bool check_perms)
{
cf_file_t *file;
CONF_DATA *cd;
CONF_SECTION *top;
rbtree_t *tree;
top = cf_top_section(cs);
cd = cf_data_find_internal(top, "filename", 0);
if (!cd) return false;
tree = cd->data;
file = talloc(tree, cf_file_t);
if (!file) return false;
file->filename = filename;
file->cs = cs;
if (stat(filename, &file->buf) < 0) {
ERROR("Unable to check file \"%s\": %s", filename, fr_syserror(errno));
talloc_free(file);
return false;
}
if (!check_perms) {
talloc_free(file);
return true;
}
#ifdef S_IWOTH
if ((file->buf.st_mode & S_IWOTH) != 0) {
ERROR("Configuration file %s is globally writable. "
"Refusing to start due to insecure configuration.", filename);
talloc_free(file);
return false;
}
#endif
/*
* It's OK to include the same file twice...
*/
if (!rbtree_insert(tree, file)) {
talloc_free(file);
}
return true;
}
typedef struct cf_file_callback_t {
int rcode;
rb_walker_t callback;
CONF_SECTION *modules;
} cf_file_callback_t;
/*
* Return 0 for keep going, 1 for stop.
*/
static int file_callback(void *ctx, void *data)
{
cf_file_callback_t *cb = ctx;
cf_file_t *file = data;
struct stat buf;
/*
* The file doesn't exist or we can no longer read it.
*/
if (stat(file->filename, &buf) < 0) {
cb->rcode = CF_FILE_ERROR;
return 1;
}
/*
* The file changed, we'll need to re-read it.
*/
if (file->buf.st_mtime != buf.st_mtime) {
if (cb->callback(cb->modules, file->cs)) {
cb->rcode |= CF_FILE_MODULE;
DEBUG3("HUP: Changed module file %s", file->filename);
} else {
DEBUG3("HUP: Changed config file %s", file->filename);
cb->rcode |= CF_FILE_CONFIG;
}
/*
* Presume that the file will be immediately
* re-read, so we update the mtime appropriately.
*/
file->buf.st_mtime = buf.st_mtime;
}
return 0;
}
/*
* See if any of the files have changed.
*/
int cf_file_changed(CONF_SECTION *cs, rb_walker_t callback)
{
CONF_DATA *cd;
CONF_SECTION *top;
cf_file_callback_t cb;
rbtree_t *tree;
top = cf_top_section(cs);
cd = cf_data_find_internal(top, "filename", 0);
if (!cd) return true;
tree = cd->data;
cb.rcode = CF_FILE_NONE;
cb.callback = callback;
cb.modules = cf_section_sub_find(cs, "modules");
(void) rbtree_walk(tree, RBTREE_IN_ORDER, file_callback, &cb);
return cb.rcode;
}
static int _cf_section_free(CONF_SECTION *cs)
{
/*
* Name1 and name2 are allocated contiguous with
* cs.
*/
if (cs->pair_tree) {
rbtree_free(cs->pair_tree);
cs->pair_tree = NULL;
}
if (cs->section_tree) {
rbtree_free(cs->section_tree);
cs->section_tree = NULL;
}
if (cs->name2_tree) {
rbtree_free(cs->name2_tree);
cs->name2_tree = NULL;
}
if (cs->data_tree) {
rbtree_free(cs->data_tree);
cs->data_tree = NULL;
}
return 0;
}
/** Allocate a CONF_PAIR
*
* @param parent CONF_SECTION to hang this CONF_PAIR off of.
* @param attr name.
* @param value of CONF_PAIR.
* @param op T_OP_EQ, T_OP_SET etc.
* @param lhs_type T_BARE_WORD, T_DOUBLE_QUOTED_STRING, T_BACK_QUOTED_STRING
* @param rhs_type T_BARE_WORD, T_DOUBLE_QUOTED_STRING, T_BACK_QUOTED_STRING
* @return NULL on error, else a new CONF_SECTION parented by parent.
*/
CONF_PAIR *cf_pair_alloc(CONF_SECTION *parent, char const *attr, char const *value,
FR_TOKEN op, FR_TOKEN lhs_type, FR_TOKEN rhs_type)
{
CONF_PAIR *cp;
rad_assert(fr_equality_op[op] || fr_assignment_op[op]);
if (!attr) return NULL;
cp = talloc_zero(parent, CONF_PAIR);
if (!cp) return NULL;
cp->item.type = CONF_ITEM_PAIR;
cp->item.parent = parent;
cp->lhs_type = lhs_type;
cp->rhs_type = rhs_type;
cp->op = op;
cp->attr = talloc_typed_strdup(cp, attr);
if (!cp->attr) {
error:
talloc_free(cp);
return NULL;
}
if (value) {
cp->value = talloc_typed_strdup(cp, value);
if (!cp->value) goto error;
}
return cp;
}
/** Duplicate a CONF_PAIR
*
* @param parent to allocate new pair in.
* @param cp to duplicate.
* @return NULL on error, else a duplicate of the input pair.
*/
CONF_PAIR *cf_pair_dup(CONF_SECTION *parent, CONF_PAIR *cp)
{
CONF_PAIR *new;
rad_assert(parent);
rad_assert(cp);
new = cf_pair_alloc(parent, cp->attr, cf_pair_value(cp),
cp->op, cp->lhs_type, cp->rhs_type);
if (!new) return NULL;
new->parsed = cp->parsed;
new->item.lineno = cp->item.lineno;
/*
* Avoid mallocs if possible.
*/
if (!cp->item.filename || (parent->item.filename && !strcmp(parent->item.filename, cp->item.filename))) {
new->item.filename = parent->item.filename;
} else {
new->item.filename = talloc_strdup(new, cp->item.filename);
}
return new;
}
/** Add a configuration pair to a section
*
* @param parent section to add pair to.
* @param cp to add.
*/
void cf_pair_add(CONF_SECTION *parent, CONF_PAIR *cp)
{
cf_item_add(parent, cf_pair_to_item(cp));
}
/** Allocate a CONF_SECTION
*
* @param parent CONF_SECTION to hang this CONF_SECTION off of.
* @param name1 Primary name.
* @param name2 Secondary name.
* @return NULL on error, else a new CONF_SECTION parented by parent.
*/
CONF_SECTION *cf_section_alloc(CONF_SECTION *parent, char const *name1, char const *name2)
{
CONF_SECTION *cs;
char buffer[1024];
if (!name1) return NULL;
if (name2 && parent) {
if (strchr(name2, '$')) {
name2 = cf_expand_variables(parent->item.filename,
&parent->item.lineno,
parent,
buffer, sizeof(buffer), name2, NULL);
if (!name2) {
ERROR("Failed expanding section name");
return NULL;
}
}
}
cs = talloc_zero(parent, CONF_SECTION);
if (!cs) return NULL;
cs->item.type = CONF_ITEM_SECTION;
cs->item.parent = parent;
cs->name1 = talloc_typed_strdup(cs, name1);
if (!cs->name1) {
error:
talloc_free(cs);
return NULL;
}
if (name2) {
cs->name2 = talloc_typed_strdup(cs, name2);
if (!cs->name2) goto error;
}
cs->pair_tree = rbtree_create(cs, pair_cmp, NULL, 0);
if (!cs->pair_tree) goto error;
talloc_set_destructor(cs, _cf_section_free);
/*
* Don't create a data tree, it may not be needed.
*/
/*
* Don't create the section tree here, it may not
* be needed.
*/
if (parent) cs->depth = parent->depth + 1;
return cs;
}
/** Duplicate a configuration section
*
* @note recursively duplicates any child sections.
* @note does not duplicate any data associated with a section, or its child sections.
*
* @param parent section (may be NULL).
* @param cs to duplicate.
* @param name1 of new section.
* @param name2 of new section.
* @param copy_meta Copy additional meta data for a section (like template, base, depth and variables).
* @return a duplicate of the existing section, or NULL on error.
*/
CONF_SECTION *cf_section_dup(CONF_SECTION *parent, CONF_SECTION const *cs,
char const *name1, char const *name2, bool copy_meta)
{
CONF_SECTION *new, *subcs;
CONF_PAIR *cp;
CONF_ITEM *ci;
new = cf_section_alloc(parent, name1, name2);
if (copy_meta) {
new->template = cs->template;
new->base = cs->base;
new->depth = cs->depth;
new->variables = cs->variables;
}
new->item.lineno = cs->item.lineno;
if (!cs->item.filename || (parent && (strcmp(parent->item.filename, cs->item.filename) == 0))) {
new->item.filename = parent->item.filename;
} else {
new->item.filename = talloc_strdup(new, cs->item.filename);
}
for (ci = cs->children; ci; ci = ci->next) {
switch (ci->type) {
case CONF_ITEM_SECTION:
subcs = cf_item_to_section(ci);
subcs = cf_section_dup(new, subcs,
cf_section_name1(subcs), cf_section_name2(subcs),
copy_meta);
if (!subcs) {
talloc_free(new);
return NULL;
}
cf_section_add(new, subcs);
break;
case CONF_ITEM_PAIR:
cp = cf_pair_dup(new, cf_item_to_pair(ci));
if (!cp) {
talloc_free(new);
return NULL;
}
cf_pair_add(new, cp);
break;
case CONF_ITEM_DATA: /* Skip data */
break;
case CONF_ITEM_INVALID:
rad_assert(0);
}
}
return new;
}
void cf_section_add(CONF_SECTION *parent, CONF_SECTION *cs)
{
cf_item_add(parent, &(cs->item));
}
/** Replace pair in a given section with a new pair, of the given value.
*
* @param cs to replace pair in.
* @param cp to replace.
* @param value New value to assign to cp.
* @return 0 on success, -1 on failure.
*/
int cf_pair_replace(CONF_SECTION *cs, CONF_PAIR *cp, char const *value)
{
CONF_PAIR *newp;
CONF_ITEM *ci, *cn, **last;
newp = cf_pair_alloc(cs, cp->attr, value, cp->op, cp->lhs_type, cp->rhs_type);
if (!newp) return -1;
ci = &(cp->item);
cn = &(newp->item);
/*
* Find the old one from the linked list, and replace it
* with the new one.
*/
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;
}
/*
* Add an item to a configuration section.
*/
void cf_item_add(CONF_SECTION *cs, CONF_ITEM *ci)
{
#ifndef NDEBUG
CONF_ITEM *first = ci;
#endif
rad_assert((void *)cs != (void *)ci);
if (!cs || !ci) return;
if (!cs->children) {
rad_assert(cs->tail == NULL);
cs->children = ci;
} else {
rad_assert(cs->tail != NULL);
cs->tail->next = ci;
}
/*
* Update the trees (and tail) for each item added.
*/
for (/* nothing */; ci != NULL; ci = ci->next) {
rad_assert(ci->next != first); /* simple cycle detection */
cs->tail = ci;
/*
* For fast lookups, pairs and sections get
* added to rbtree's.
*/
switch (ci->type) {
case CONF_ITEM_PAIR:
if (!rbtree_insert(cs->pair_tree, ci)) {
CONF_PAIR *cp = cf_item_to_pair(ci);
if (strcmp(cp->attr, "confdir") == 0) break;
if (!cp->value) break; /* module name, "ok", etc. */
}
break;
case CONF_ITEM_SECTION: {
CONF_SECTION *cs_new = cf_item_to_section(ci);
CONF_SECTION *name1_cs;
if (!cs->section_tree) {
cs->section_tree = rbtree_create(cs, section_cmp, NULL, 0);
if (!cs->section_tree) {
ERROR("Out of memory");
fr_exit_now(1);
}
}
name1_cs = rbtree_finddata(cs->section_tree, cs_new);
if (!name1_cs) {
if (!rbtree_insert(cs->section_tree, cs_new)) {
ERROR("Failed inserting section into tree");
fr_exit_now(1);
}
break;
}
/*
* We already have a section of
* this "name1". Add a new
* sub-section based on name2.
*/
if (!name1_cs->name2_tree) {
name1_cs->name2_tree = rbtree_create(name1_cs, name2_cmp, NULL, 0);
if (!name1_cs->name2_tree) {
ERROR("Out of memory");
fr_exit_now(1);
}
}
/*
* We don't care if this fails.
* If the user tries to create
* two sections of the same
* name1/name2, the duplicate
* section is just silently
* ignored.
*/
rbtree_insert(name1_cs->name2_tree, cs_new);
break;
} /* was a section */
case CONF_ITEM_DATA:
if (!cs->data_tree) {
cs->data_tree = rbtree_create(cs, data_cmp, NULL, 0);
}
if (cs->data_tree) {
rbtree_insert(cs->data_tree, ci);
}
break;
default: /* FIXME: assert & error! */
break;
} /* switch over conf types */
} /* loop over ci */
}
CONF_ITEM *cf_reference_item(CONF_SECTION const *parentcs,
CONF_SECTION *outercs,
char const *ptr)
{
CONF_PAIR *cp;
CONF_SECTION *next;
CONF_SECTION const *cs = outercs;
char name[8192];
char *p;
if (!cs) goto no_such_item;
strlcpy(name, ptr, sizeof(name));
p = name;
/*
* ".foo" means "foo from the current section"
*/
if (*p == '.') {
p++;
/*
* Just '.' means the current section
*/
if (*p == '\0') {
return cf_section_to_item(cs);
}
/*
* ..foo means "foo from the section
* enclosing this section" (etc.)
*/
while (*p == '.') {
if (cs->item.parent) {
cs = cs->item.parent;
}
/*
* .. means the section
* enclosing this section
*/
if (!*++p) {
return cf_section_to_item(cs);
}
}
/*
* "foo.bar.baz" means "from the root"
*/
} 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;
/*
* Split off name2.
*/
if (r) {
q = strchr(r + 1, ']');
if (!q) return NULL; /* parse error */
/*
* Points to foo[bar]xx: parse error,
* it should be foo[bar] or foo[bar].baz
*/
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 = ']';
/*
* Points to a named instance of a section.
*/
if (!q[1]) {
if (!next) goto no_such_item;
return &(next->item);
}
q++; /* ensure we skip the ']' and '.' */
} else {
*q = '\0';
next = cf_section_sub_find(cs, p);
*q = '.';
}
if (!next) break; /* it MAY be a pair in this section! */
cs = next;
p = q + 1;
}
if (!*p) goto no_such_item;
retry:
/*
* Find it in the current referenced
* section.
*/
cp = cf_pair_find(cs, p);
if (cp) {
cp->parsed = true; /* conf pairs which are referenced count as parsed */
return &(cp->item);
}
next = cf_section_sub_find(cs, p);
if (next) return &(next->item);
/*
* "foo" is "in the current section, OR in main".
*/
if ((p == name) && (parentcs != NULL) && (cs != parentcs)) {
cs = parentcs;
goto retry;
}
no_such_item:
return NULL;
}
CONF_SECTION *cf_top_section(CONF_SECTION *cs)
{
if (!cs) return NULL;
while (cs->item.parent != NULL) {
cs = cs->item.parent;
}
return cs;
}
/*
* Expand the variables in an input string.
*/
static char const *cf_expand_variables(char const *cf, int *lineno,
CONF_SECTION *outercs,
char *output, size_t outsize,
char const *input, bool *soft_fail)
{
char *p;
char const *end, *ptr;
CONF_SECTION const *parentcs;
char name[8192];
if (soft_fail) *soft_fail = false;
/*
* Find the master parent conf section.
* We can't use main_config.config, because we're in the
* process of re-building it, and it isn't set up yet...
*/
parentcs = cf_top_section(outercs);
p = output;
ptr = input;
while (*ptr) {
/*
* Ignore anything other than "${"
*/
if ((*ptr == '$') && (ptr[1] == '{')) {
CONF_ITEM *ci;
CONF_PAIR *cp;
char *q;
/*
* FIXME: Add support for ${foo:-bar},
* like in xlat.c
*/
/*
* Look for trailing '}', and log a
* warning for anything that doesn't match,
* and exit with a fatal error.
*/
end = strchr(ptr, '}');
if (end == NULL) {
*p = '\0';
ERROR("%s[%d]: Variable expansion missing }",
cf, *lineno);
return NULL;
}
ptr += 2;
/*
* Can't really happen because input lines are
* capped at 8k, which is sizeof(name)
*/
if ((size_t) (end - ptr) >= sizeof(name)) {
ERROR("%s[%d]: Reference string is too large",
cf, *lineno);
return NULL;
}
memcpy(name, ptr, end - ptr);
name[end - ptr] = '\0';
q = strchr(name, ':');
if (q) {
*(q++) = '\0';
}
ci = cf_reference_item(parentcs, outercs, name);
if (!ci) {
if (soft_fail) *soft_fail = true;
ERROR("%s[%d]: Reference \"${%s}\" not found", cf, *lineno, name);
return NULL;
}
/*
* The expansion doesn't refer to another item or section
* it's the property of a section.
*/
if (q) {
CONF_SECTION *mycs = cf_item_to_section(ci);
if (ci->type != CONF_ITEM_SECTION) {
ERROR("%s[%d]: Can only reference properties of sections", cf, *lineno);
return NULL;
}
switch (fr_str2int(conf_property_name, q, CONF_PROPERTY_INVALID)) {
case CONF_PROPERTY_NAME:
strcpy(p, mycs->name1);
break;
case CONF_PROPERTY_INSTANCE:
strcpy(p, mycs->name2 ? mycs->name2 : mycs->name1);
break;
default:
ERROR("%s[%d]: Invalid property '%s'", cf, *lineno, q);
return NULL;
}
p += strlen(p);
ptr = end + 1;
} else if (ci->type == CONF_ITEM_PAIR) {
/*
* Substitute the value of the variable.
*/
cp = cf_item_to_pair(ci);
/*
* If the thing we reference is
* marked up as being expanded in
* pass2, don't expand it now.
* Let it be expanded in pass2.
*/
if (cp->pass2) {
if (soft_fail) *soft_fail = true;
ERROR("%s[%d]: Reference \"%s\" points to a variable which has not been expanded.",
cf, *lineno, input);
return NULL;
}
/*
* Might as well make
* non-existent string be the
* empty string.
*/
if (!cp->value) {
*p = '\0';
goto skip_value;
}
if (p + strlen(cp->value) >= output + outsize) {
ERROR("%s[%d]: Reference \"%s\" is too long",
cf, *lineno, input);
return NULL;
}
strcpy(p, cp->value);
p += strlen(p);
skip_value:
ptr = end + 1;
} else if (ci->type == CONF_ITEM_SECTION) {
CONF_SECTION *subcs;
/*
* Adding an entry again to a
* section is wrong. We don't
* want an infinite loop.
*/
if (ci->parent == outercs) {
ERROR("%s[%d]: Cannot reference different item in same section", cf, *lineno);
return NULL;
}
/*
* Copy the section instead of
* referencing it.
*/
subcs = cf_item_to_section(ci);
subcs = cf_section_dup(outercs, subcs,
cf_section_name1(subcs), cf_section_name2(subcs),
false);
if (!subcs) {
ERROR("%s[%d]: Failed copying reference %s", cf, *lineno, name);
return NULL;
}
subcs->item.filename = ci->filename;
subcs->item.lineno = ci->lineno;
cf_item_add(outercs, &(subcs->item));
ptr = end + 1;
} else {
ERROR("%s[%d]: Reference \"%s\" type is invalid", cf, *lineno, input);
return NULL;
}
} else if (strncmp(ptr, "$ENV{", 5) == 0) {
char *env;
ptr += 5;
/*
* Look for trailing '}', and log a
* warning for anything that doesn't match,
* and exit with a fatal error.
*/
end = strchr(ptr, '}');
if (end == NULL) {
*p = '\0';
ERROR("%s[%d]: Environment variable expansion missing }",
cf, *lineno);
return NULL;
}
/*
* Can't really happen because input lines are
* capped at 8k, which is sizeof(name)
*/
if ((size_t) (end - ptr) >= sizeof(name)) {
ERROR("%s[%d]: Environment variable name is too large",
cf, *lineno);
return NULL;
}
memcpy(name, ptr, end - ptr);
name[end - ptr] = '\0';
/*
* Get the environment variable.
* If none exists, then make it an empty string.
*/
env = getenv(name);
if (env == NULL) {
*name = '\0';
env = name;
}
if (p + strlen(env) >= output + outsize) {
ERROR("%s[%d]: Reference \"%s\" is too long",
cf, *lineno, input);
return NULL;
}
strcpy(p, env);
p += strlen(p);
ptr = end + 1;
} else {
/*
* Copy it over verbatim.
*/
*(p++) = *(ptr++);
}
if (p >= (output + outsize)) {
ERROR("%s[%d]: Reference \"%s\" is too long",
cf, *lineno, input);
return NULL;
}
} /* loop over all of the input string. */
*p = '\0';
return output;
}
static char const parse_spaces[] = " ";
/** Validation function for ipaddr conffile types
*
*/
static inline int fr_item_validate_ipaddr(CONF_SECTION *cs, char const *name, PW_TYPE type, char const *value,
fr_ipaddr_t *ipaddr)
{
char ipbuf[128];
if (strcmp(value, "*") == 0) {
cf_log_info(cs, "%.*s\t%s = *", cs->depth, parse_spaces, name);
} else if (strspn(value, ".0123456789abdefABCDEF:%[]/") == strlen(value)) {
cf_log_info(cs, "%.*s\t%s = %s", cs->depth, parse_spaces, name, value);
} else {
cf_log_info(cs, "%.*s\t%s = %s IPv%s address [%s]", cs->depth, parse_spaces, name, value,
(ipaddr->af == AF_INET ? "4" : " 6"), ip_ntoh(ipaddr, ipbuf, sizeof(ipbuf)));
}
switch (type) {
case PW_TYPE_IPV4_ADDR:
case PW_TYPE_IPV6_ADDR:
case PW_TYPE_COMBO_IP_ADDR:
switch (ipaddr->af) {
case AF_INET:
if (ipaddr->prefix == 32) return 0;
cf_log_err(&(cs->item), "Invalid IPv4 mask length \"/%i\". Only \"/32\" permitted for non-prefix types",
ipaddr->prefix);
break;
case AF_INET6:
if (ipaddr->prefix == 128) return 0;
cf_log_err(&(cs->item), "Invalid IPv6 mask length \"/%i\". Only \"/128\" permitted for non-prefix types",
ipaddr->prefix);
break;
default:
cf_log_err(&(cs->item), "Unknown address (%d) family passed for parsing IP address.", ipaddr->af);
break;
}
return -1;
default:
break;
}
return 0;
}
/** Parses a #CONF_PAIR into a C data type, with a default value.
*
* Takes fields from a #CONF_PARSER struct and uses them to parse the string value
* of a #CONF_PAIR into a C data type matching the type argument.
*
* The format of the types are the same as #value_data_t types.
*
* @note The dflt value will only be used if no matching #CONF_PAIR is found. Empty strings will not
* result in the dflt value being used.
*
* **PW_TYPE to data type mappings**
* | PW_TYPE | Data type | Dynamically allocated |
* | ----------------------- | ------------------ | ---------------------- |
* | PW_TYPE_TMPL | ``vp_tmpl_t`` | Yes |
* | PW_TYPE_BOOLEAN | ``bool`` | No |
* | PW_TYPE_INTEGER | ``uint32_t`` | No |
* | PW_TYPE_SHORT | ``uint16_t`` | No |
* | PW_TYPE_INTEGER64 | ``uint64_t`` | No |
* | PW_TYPE_SIGNED | ``int32_t`` | No |
* | PW_TYPE_STRING | ``char const *`` | Yes |
* | PW_TYPE_IPV4_ADDR | ``fr_ipaddr_t`` | No |
* | PW_TYPE_IPV4_PREFIX | ``fr_ipaddr_t`` | No |
* | PW_TYPE_IPV6_ADDR | ``fr_ipaddr_t`` | No |
* | PW_TYPE_IPV6_PREFIX | ``fr_ipaddr_t`` | No |
* | PW_TYPE_COMBO_IP_ADDR | ``fr_ipaddr_t`` | No |
* | PW_TYPE_COMBO_IP_PREFIX | ``fr_ipaddr_t`` | No |
* | PW_TYPE_TIMEVAL | ``struct timeval`` | No |
*
* @param cs to search for matching #CONF_PAIR in.
* @param name of #CONF_PAIR to search for.
* @param type Data type to parse #CONF_PAIR value as.
* Should be one of the following ``data`` types, and one or more of the following ``flag`` types or'd together:
* - ``data`` #PW_TYPE_TMPL - @copybrief PW_TYPE_TMPL
* Feeds the value into #tmpl_afrom_str. Value can be
* obtained when processing requests, with #tmpl_expand or #tmpl_aexpand.
* - ``data`` #PW_TYPE_BOOLEAN - @copybrief PW_TYPE_BOOLEAN
* - ``data`` #PW_TYPE_INTEGER - @copybrief PW_TYPE_INTEGER
* - ``data`` #PW_TYPE_SHORT - @copybrief PW_TYPE_SHORT
* - ``data`` #PW_TYPE_INTEGER64 - @copybrief PW_TYPE_INTEGER64
* - ``data`` #PW_TYPE_SIGNED - @copybrief PW_TYPE_SIGNED
* - ``data`` #PW_TYPE_STRING - @copybrief PW_TYPE_STRING
* - ``data`` #PW_TYPE_IPV4_ADDR - @copybrief PW_TYPE_IPV4_ADDR (IPv4 address with prefix 32).
* - ``data`` #PW_TYPE_IPV4_PREFIX - @copybrief PW_TYPE_IPV4_PREFIX (IPv4 address with variable prefix).
* - ``data`` #PW_TYPE_IPV6_ADDR - @copybrief PW_TYPE_IPV6_ADDR (IPv6 address with prefix 128).
* - ``data`` #PW_TYPE_IPV6_PREFIX - @copybrief PW_TYPE_IPV6_PREFIX (IPv6 address with variable prefix).
* - ``data`` #PW_TYPE_COMBO_IP_ADDR - @copybrief PW_TYPE_COMBO_IP_ADDR (IPv4/IPv6 address with
* prefix 32/128).
* - ``data`` #PW_TYPE_COMBO_IP_PREFIX - @copybrief PW_TYPE_COMBO_IP_PREFIX (IPv4/IPv6 address with
* variable prefix).
* - ``data`` #PW_TYPE_TIMEVAL - @copybrief PW_TYPE_TIMEVAL
* - ``flag`` #PW_TYPE_DEPRECATED - @copybrief PW_TYPE_DEPRECATED
* - ``flag`` #PW_TYPE_REQUIRED - @copybrief PW_TYPE_REQUIRED
* - ``flag`` #PW_TYPE_ATTRIBUTE - @copybrief PW_TYPE_ATTRIBUTE
* - ``flag`` #PW_TYPE_SECRET - @copybrief PW_TYPE_SECRET
* - ``flag`` #PW_TYPE_FILE_INPUT - @copybrief PW_TYPE_FILE_INPUT
* - ``flag`` #PW_TYPE_NOT_EMPTY - @copybrief PW_TYPE_NOT_EMPTY
* @param data Pointer to a global variable, or pointer to a field in the struct being populated with values.
* @param dflt value to use, if no #CONF_PAIR is found.
* @return
* - 1 if default value was used.
* - 0 on success.
* - -1 on error.
* - -2 if deprecated.
*/
int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *data, char const *dflt)
{
int rcode;
bool deprecated, required, attribute, secret, file_input, cant_be_empty, tmpl, multi, file_exists;
char **q;
char const *value;
CONF_PAIR *cp = NULL;
fr_ipaddr_t *ipaddr;
char buffer[8192];
CONF_ITEM *c_item;
if (!cs) {
cf_log_err(&(cs->item), "No enclosing section for configuration item \"%s\"", name);
return -1;
}
c_item = &cs->item;
deprecated = (type & PW_TYPE_DEPRECATED);
required = (type & PW_TYPE_REQUIRED);
attribute = (type & PW_TYPE_ATTRIBUTE);
secret = (type & PW_TYPE_SECRET);
file_input = (type == PW_TYPE_FILE_INPUT); /* check, not and */
file_exists = (type == PW_TYPE_FILE_EXISTS); /* check, not and */
cant_be_empty = (type & PW_TYPE_NOT_EMPTY);
tmpl = (type & PW_TYPE_TMPL);
multi = (type & PW_TYPE_MULTI);
if (attribute) required = true;
if (required) cant_be_empty = true; /* May want to review this in the future... */
/*
* Everything except templates must have a base type.
*/
if (!(type & 0xff) && !tmpl) {
cf_log_err(c_item, "Configuration item \"%s\" must have a data type", name);
return -1;
}
type &= 0xff; /* normal types are small */
rcode = 0;
cp = cf_pair_find(cs, name);
/*
* No pairs match the configuration item name in the current
* section, use the default value.
*/
if (!cp) {
if (deprecated) return 0; /* Don't set the default value */
rcode = 1;
value = dflt;
/*
* Something matched, used the CONF_PAIR value.
*/
} else {
CONF_PAIR *next = cp;
value = cp->value;
cp->parsed = true;
c_item = &cp->item;
if (deprecated) {
cf_log_err(c_item, "Configuration item \"%s\" is deprecated", name);
return -2;
}
/*
* A quick check to see if the next item is the same.
*/
if (!multi && cp->item.next && (cp->item.next->type == CONF_ITEM_PAIR)) {
next = cf_item_to_pair(cp->item.next);
if (strcmp(next->attr, name) == 0) {
WARN("%s[%d]: Ignoring duplicate configuration item '%s'",
next->item.filename ? next->item.filename : "unknown",
next->item.lineno, name);
}
}
if (multi) {
while ((next = cf_pair_find_next(cs, next, name)) != NULL) {
/*
* @fixme We should actually validate
* the value of the pairs too
*/
next->parsed = true;
};
}
}
if (!value) {
if (required) {
cf_log_err(c_item, "Configuration item \"%s\" must have a value", name);
return -1;
}
return rcode;
}
if ((value[0] == '\0') && cant_be_empty) {
cant_be_empty:
cf_log_err(c_item, "Configuration item \"%s\" must not be empty (zero length)", name);
if (!required) cf_log_err(c_item, "Comment item to silence this message");
return -1;
}
/*
* Process a value as a LITERAL template. Once all of
* the attrs and xlats are defined, the pass2 code
* converts it to the appropriate type.
*/
if (tmpl) {
vp_tmpl_t *vpt;
if (!value) {
*(vp_tmpl_t **)data = NULL;
return 0;
}
rad_assert(!attribute);
vpt = tmpl_alloc(cs, TMPL_TYPE_LITERAL, value, strlen(value));
*(vp_tmpl_t **)data = vpt;
return 0;
}
switch (type) {
case PW_TYPE_BOOLEAN:
/*
* Allow yes/no, true/false, and on/off
*/
if ((strcasecmp(value, "yes") == 0) ||
(strcasecmp(value, "true") == 0) ||
(strcasecmp(value, "on") == 0)) {
*(bool *)data = true;
} else if ((strcasecmp(value, "no") == 0) ||
(strcasecmp(value, "false") == 0) ||
(strcasecmp(value, "off") == 0)) {
*(bool *)data = false;
} else {
*(bool *)data = false;
cf_log_err(&(cs->item), "Invalid value \"%s\" for boolean "
"variable %s", value, name);
return -1;
}
cf_log_info(cs, "%.*s\t%s = %s",
cs->depth, parse_spaces, name, value);
break;
case PW_TYPE_INTEGER:
{
unsigned long v = strtoul(value, 0, 0);
/*
* Restrict integer values to 0-INT32_MAX, this means
* it will always be safe to cast them to a signed type
* for comparisons, and imposes the same range limit as
* before we switched to using an unsigned type to
* represent config item integers.
*/
if (v > INT32_MAX) {
cf_log_err(&(cs->item), "Invalid value \"%s\" for variable %s, must be between 0-%u", value,
name, INT32_MAX);
return -1;
}
*(uint32_t *)data = v;
cf_log_info(cs, "%.*s\t%s = %u", cs->depth, parse_spaces, name, *(uint32_t *)data);
}
break;
case PW_TYPE_BYTE:
{
unsigned long v = strtoul(value, 0, 0);
if (v > UINT8_MAX) {
cf_log_err(&(cs->item), "Invalid value \"%s\" for variable %s, must be between 0-%u", value,
name, UINT8_MAX);
return -1;
}
*(uint8_t *)data = (uint8_t) v;
cf_log_info(cs, "%.*s\t%s = %u", cs->depth, parse_spaces, name, *(uint8_t *)data);
}
break;
case PW_TYPE_SHORT:
{
unsigned long v = strtoul(value, 0, 0);
if (v > UINT16_MAX) {
cf_log_err(&(cs->item), "Invalid value \"%s\" for variable %s, must be between 0-%u", value,
name, UINT16_MAX);
return -1;
}
*(uint16_t *)data = (uint16_t) v;
cf_log_info(cs, "%.*s\t%s = %u", cs->depth, parse_spaces, name, *(uint16_t *)data);
}
break;
case PW_TYPE_INTEGER64:
*(uint64_t *)data = strtoull(value, 0, 0);
cf_log_info(cs, "%.*s\t%s = %" PRIu64, cs->depth, parse_spaces, name, *(uint64_t *)data);
break;
case PW_TYPE_SIGNED:
*(int32_t *)data = strtol(value, 0, 0);
cf_log_info(cs, "%.*s\t%s = %d", cs->depth, parse_spaces, name, *(int32_t *)data);
break;
case PW_TYPE_STRING:
q = (char **) data;
if (*q != NULL) {
talloc_free(*q);
}
/*
* Expand variables which haven't already been
* expanded automagically when the configuration
* file was read.
*/
if (value == dflt) {
int lineno = 0;
lineno = cs->item.lineno;
value = cf_expand_variables("<internal>",
&lineno,
cs, buffer, sizeof(buffer),
value, NULL);
if (!value) {
cf_log_err(&(cs->item),"Failed expanding variable %s", name);
return -1;
}
}
if (cant_be_empty && (value[0] == '\0')) goto cant_be_empty;
if (attribute) {
if (!dict_attrbyname(value)) {
if (!cp) {
cf_log_err(&(cs->item), "No such attribute '%s' for configuration '%s'",
value, name);
} else {
cf_log_err(&(cp->item), "No such attribute '%s'", value);
}
return -1;
}
}
/*
* Hide secrets when using "radiusd -X".
*/
if (secret && (rad_debug_lvl <= 2)) {
cf_log_info(cs, "%.*s\t%s = <<< secret >>>",
cs->depth, parse_spaces, name);
} else {
cf_log_info(cs, "%.*s\t%s = \"%s\"",
cs->depth, parse_spaces, name, value ? value : "(null)");
}
*q = value ? talloc_typed_strdup(cs, value) : NULL;
/*
* If there's data AND it's an input file, check
* that we can read it. This check allows errors
* to be caught as early as possible, during
* server startup.
*/
if (*q && file_input && !cf_file_check(cs, *q, true)) {
cf_log_err(&(cs->item), "Failed parsing configuration item \"%s\"", name);
return -1;
}
if (*q && file_exists && !cf_file_check(cs, *q, false)) {
cf_log_err(&(cs->item), "Failed parsing configuration item \"%s\"", name);
return -1;
}
break;
case PW_TYPE_IPV4_ADDR:
case PW_TYPE_IPV4_PREFIX:
ipaddr = data;
if (fr_pton4(ipaddr, value, -1, true, false) < 0) {
failed:
cf_log_err(&(cs->item), "Failed parsing configuration item \"%s\" - %s", name, fr_strerror());
return -1;
}
if (fr_item_validate_ipaddr(cs, name, type, value, ipaddr) < 0) return -1;
break;
case PW_TYPE_IPV6_ADDR:
case PW_TYPE_IPV6_PREFIX:
ipaddr = data;
if (fr_pton6(ipaddr, value, -1, true, false) < 0) goto failed;
if (fr_item_validate_ipaddr(cs, name, type, value, ipaddr) < 0) return -1;
break;
case PW_TYPE_COMBO_IP_ADDR:
case PW_TYPE_COMBO_IP_PREFIX:
ipaddr = data;
if (fr_pton(ipaddr, value, -1, AF_UNSPEC, true) < 0) goto failed;
if (fr_item_validate_ipaddr(cs, name, type, value, ipaddr) < 0) return -1;
break;
case PW_TYPE_TIMEVAL: {
int sec;
char *end;
struct timeval tv;
sec = strtoul(value, &end, 10);
tv.tv_sec = sec;
tv.tv_usec = 0;
if (*end == '.') {
size_t len;
len = strlen(end + 1);
if (len > 6) {
cf_log_err(&(cs->item), "Too much precision for timeval");
return -1;
}
/*
* If they write "0.1", that means
* "10000" microseconds.
*/
sec = strtoul(end + 1, NULL, 10);
while (len < 6) {
sec *= 10;
len++;
}
tv.tv_usec = sec;
}
cf_log_info(cs, "%.*s\t%s = %d.%06d",
cs->depth, parse_spaces, name, (int) tv.tv_sec, (int) tv.tv_usec);
memcpy(data, &tv, sizeof(tv));
}
break;
default:
/*
* If we get here, it's a sanity check error.
* It's not an error parsing the configuration
* file.
*/
rad_assert(type > PW_TYPE_INVALID);
rad_assert(type < PW_TYPE_MAX);
cf_log_err(&(cs->item), "type '%s' is not supported in the configuration files",
fr_int2str(dict_attr_types, type, "?Unknown?"));
return -1;
} /* switch over variable type */
if (!cp) {
CONF_PAIR *cpn;
cpn = cf_pair_alloc(cs, name, value, T_OP_SET, T_BARE_WORD, T_BARE_WORD);
if (!cpn) return -1;
cpn->parsed = true;
cpn->item.filename = "<internal>";
cpn->item.lineno = 0;
cf_item_add(cs, &(cpn->item));
}
return rcode;
}
/*
* A copy of cf_section_parse that initializes pointers before
* parsing them.
*/
static void cf_section_parse_init(CONF_SECTION *cs, void *base,
CONF_PARSER const *variables)
{
int i;
for (i = 0; variables[i].name != NULL; i++) {
if (variables[i].type == PW_TYPE_SUBSECTION) {
CONF_SECTION *subcs;
if (!variables[i].dflt) continue;
subcs = cf_section_sub_find(cs, variables[i].name);
/*
* If there's no subsection in the
* config, BUT the CONF_PARSER wants one,
* then create an empty one. This is so
* that we can track the strings,
* etc. allocated in the subsection.
*/
if (!subcs) {
subcs = cf_section_alloc(cs, variables[i].name, NULL);
if (!subcs) return;
subcs->item.filename = cs->item.filename;
subcs->item.lineno = cs->item.lineno;
cf_item_add(cs, &(subcs->item));
}
cf_section_parse_init(subcs, (uint8_t *)base + variables[i].offset,
(CONF_PARSER const *) variables[i].dflt);
continue;
}
if ((variables[i].type != PW_TYPE_STRING) &&
(variables[i].type != PW_TYPE_FILE_INPUT) &&
(variables[i].type != PW_TYPE_FILE_OUTPUT)) {
continue;
}
if (variables[i].data) {
*(char **) variables[i].data = NULL;
} else if (base) {
*(char **) (((char *)base) + variables[i].offset) = NULL;
} else {
continue;
}
} /* for all variables in the configuration section */
}
static void cf_section_parse_warn(CONF_SECTION *cs)
{
CONF_ITEM *ci;
for (ci = cs->children; ci; ci = ci->next) {
/*
* Don't recurse on sections. We can only safely
* check conf pairs at the same level as the
* section that was just parsed.
*/
if (ci->type == CONF_ITEM_SECTION) continue;
if (ci->type == CONF_ITEM_PAIR) {
CONF_PAIR *cp;
cp = cf_item_to_pair(ci);
if (cp->parsed) continue;
WARN("%s[%d]: The item '%s' is defined, but is unused by the configuration",
cp->item.filename ? cp->item.filename : "unknown",
cp->item.lineno ? cp->item.lineno : 0,
cp->attr);
}
/*
* Skip everything else.
*/
}
}
/** Parse a configuration section into user-supplied variables
*
* @param cs to parse.
* @param base pointer to a struct to fill with data. Any buffers will also be talloced
* using this parent as a pointer.
* @param variables mappings between struct fields and #CONF_ITEM s.
* @return
* - 0 on success.
* - -1 on general error.
* - -2 if a deprecated #CONF_ITEM was found.
*/
int cf_section_parse(CONF_SECTION *cs, void *base, CONF_PARSER const *variables)
{
int ret = 0;
int i;
void *data;
cs->variables = variables; /* this doesn't hurt anything */
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);
}
cf_section_parse_init(cs, base, variables);
/*
* Handle the known configuration parameters.
*/
for (i = 0; variables[i].name != NULL; i++) {
/*
* Handle subsections specially
*/
if (variables[i].type == PW_TYPE_SUBSECTION) {
CONF_SECTION *subcs;
subcs = cf_section_sub_find(cs, variables[i].name);
/*
* Default in this case is overloaded to mean a pointer
* to the CONF_PARSER struct for the subsection.
*/
if (!variables[i].dflt || !subcs) {
ERROR("Internal sanity check 1 failed in cf_section_parse %s", variables[i].name);
ret = -1;
goto finish;
}
ret = cf_section_parse(subcs, (uint8_t *)base + variables[i].offset,
(CONF_PARSER const *) variables[i].dflt);
if (ret < 0) goto finish;
continue;
} /* else it's a CONF_PAIR */
if (variables[i].data) {
data = variables[i].data; /* prefer this. */
} else if (base) {
data = ((char *)base) + variables[i].offset;
} else {
ERROR("Internal sanity check 2 failed in cf_section_parse");
ret = -1;
goto finish;
}
/*
* Parse the pair we found, or a default value.
*/
ret = cf_item_parse(cs, variables[i].name, variables[i].type, data, variables[i].dflt);
switch (ret) {
case 1: /* Used default */
ret = 0;
break;
case 0: /* OK */
break;
case -1: /* Parse error */
goto finish;
case -2: /* Deprecated CONF ITEM */
if ((variables[i + 1].offset == variables[i].offset) &&
(variables[i + 1].data == variables[i].data)) {
cf_log_err(&(cs->item), "Replace \"%s\" with \"%s\"", variables[i].name,
variables[i + 1].name);
} else {
cf_log_err(&(cs->item), "Cannot use deprecated configuration item \"%s\"", variables[i].name);
}
goto finish;
}
} /* for all variables in the configuration section */
/*
* Ensure we have a proper terminator, type so we catch
* missing terminators reliably
*/
rad_assert(variables[i].type == -1);
/*
* Warn about items in the configuration which weren't
* checked during parsing.
*/
if (rad_debug_lvl >= 3) cf_section_parse_warn(cs);
cs->base = base;
cf_log_info(cs, "%.*s}", cs->depth, parse_spaces);
finish:
return ret;
}
/*
* Check XLAT things in pass 2. But don't cache the xlat stuff anywhere.
*/
int cf_section_parse_pass2(CONF_SECTION *cs, void *base, CONF_PARSER const *variables)
{
int i;
ssize_t slen;
char const *error;
char *value = NULL;
xlat_exp_t *xlat;
/*
* Handle the known configuration parameters.
*/
for (i = 0; variables[i].name != NULL; i++) {
CONF_PAIR *cp;
void *data;
/*
* Handle subsections specially
*/
if (variables[i].type == PW_TYPE_SUBSECTION) {
CONF_SECTION *subcs;
subcs = cf_section_sub_find(cs, variables[i].name);
if (cf_section_parse_pass2(subcs, (uint8_t *)base + variables[i].offset,
(CONF_PARSER const *) variables[i].dflt) < 0) {
return -1;
}
continue;
} /* else it's a CONF_PAIR */
/*
* Figure out which data we need to fix.
*/
if (variables[i].data) {
data = variables[i].data; /* prefer this. */
} else if (base) {
data = ((char *)base) + variables[i].offset;
} else {
data = NULL;
}
cp = cf_pair_find(cs, variables[i].name);
xlat = NULL;
redo:
if (!cp || !cp->value || !data) continue;
if ((cp->rhs_type != T_DOUBLE_QUOTED_STRING) &&
(cp->rhs_type != T_BARE_WORD)) continue;
/*
* Non-xlat expansions shouldn't have xlat!
*/
if (((variables[i].type & PW_TYPE_XLAT) == 0) &&
((variables[i].type & PW_TYPE_TMPL) == 0)) {
/*
* Ignore %{... in shared secrets.
* They're never dynamically expanded.
*/
if ((variables[i].type & PW_TYPE_SECRET) != 0) continue;
if (strstr(cp->value, "%{") != NULL) {
WARN("%s[%d]: Found dynamic expansion in string which will not be dynamically expanded",
cp->item.filename ? cp->item.filename : "unknown",
cp->item.lineno ? cp->item.lineno : 0);
}
continue;
}
/*
* Parse (and throw away) the xlat string.
*
* FIXME: All of these should be converted from PW_TYPE_XLAT
* to PW_TYPE_TMPL.
*/
if ((variables[i].type & PW_TYPE_XLAT) != 0) {
/*
* xlat expansions should be parseable.
*/
value = talloc_strdup(cs, cp->value); /* modified by xlat_tokenize */
xlat = NULL;
slen = xlat_tokenize(cs, value, &xlat, &error);
if (slen < 0) {
char *spaces, *text;
error:
fr_canonicalize_error(cs, &spaces, &text, slen, cp->value);
cf_log_err(&cp->item, "Failed parsing expanded string:");
cf_log_err(&cp->item, "%s", text);
cf_log_err(&cp->item, "%s^ %s", spaces, error);
talloc_free(spaces);
talloc_free(text);
talloc_free(value);
talloc_free(xlat);
return -1;
}
talloc_free(value);
talloc_free(xlat);
}
/*
* Convert the LITERAL template to the actual
* type.
*/
if ((variables[i].type & PW_TYPE_TMPL) != 0) {
vp_tmpl_t *vpt;
slen = tmpl_afrom_str(cs, &vpt, cp->value, talloc_array_length(cp->value) - 1,
cp->rhs_type,
REQUEST_CURRENT, PAIR_LIST_REQUEST, true);
if (slen < 0) {
error = fr_strerror();
goto error;
}
/*
* Sanity check
*
* Don't add default - update with new types.
*/
switch (vpt->type) {
/*
* All attributes should have been defined by this point.
*/
case TMPL_TYPE_ATTR_UNDEFINED:
cf_log_err(&cp->item, "Unknown attribute '%s'", vpt->tmpl_unknown_name);
return -1;
case TMPL_TYPE_LITERAL:
case TMPL_TYPE_ATTR:
case TMPL_TYPE_LIST:
case TMPL_TYPE_DATA:
case TMPL_TYPE_EXEC:
case TMPL_TYPE_XLAT:
case TMPL_TYPE_XLAT_STRUCT:
break;
case TMPL_TYPE_UNKNOWN:
case TMPL_TYPE_REGEX:
case TMPL_TYPE_REGEX_STRUCT:
case TMPL_TYPE_NULL:
rad_assert(0);
}
talloc_free(*(vp_tmpl_t **)data);
*(vp_tmpl_t **)data = vpt;
}
/*
* If the "multi" flag is set, check all of them.
*/
if ((variables[i].type & PW_TYPE_MULTI) != 0) {
cp = cf_pair_find_next(cs, cp, cp->attr);
goto redo;
}
} /* for all variables in the configuration section */
return 0;
}
/*
* Merge the template so everyting else "just works".
*/
static bool cf_template_merge(CONF_SECTION *cs, CONF_SECTION const *template)
{
CONF_ITEM *ci;
if (!cs || !template) return true;
cs->template = NULL;
/*
* Walk over the template, adding its' entries to the
* current section. But only if the entries don't
* already exist in the current section.
*/
for (ci = template->children; ci; ci = ci->next) {
if (ci->type == CONF_ITEM_PAIR) {
CONF_PAIR *cp1, *cp2;
/*
* It exists, don't over-write it.
*/
cp1 = cf_item_to_pair(ci);
if (cf_pair_find(cs, cp1->attr)) {
continue;
}
/*
* Create a new pair with all of the data
* of the old one.
*/
cp2 = cf_pair_dup(cs, cp1);
if (!cp2) return false;
cp2->item.filename = cp1->item.filename;
cp2->item.lineno = cp1->item.lineno;
cf_item_add(cs, &(cp2->item));
continue;
}
if (ci->type == CONF_ITEM_SECTION) {
CONF_SECTION *subcs1, *subcs2;
subcs1 = cf_item_to_section(ci);
rad_assert(subcs1 != NULL);
subcs2 = cf_section_sub_find_name2(cs, subcs1->name1, subcs1->name2);
if (subcs2) {
/*
* sub-sections get merged.
*/
if (!cf_template_merge(subcs2, subcs1)) {
return false;
}
continue;
}
/*
* Our section doesn't have a matching
* sub-section. Copy it verbatim from
* the template.
*/
subcs2 = cf_section_dup(cs, subcs1,
cf_section_name1(subcs1), cf_section_name2(subcs1),
false);
if (!subcs2) return false;
subcs2->item.filename = subcs1->item.filename;
subcs2->item.lineno = subcs1->item.lineno;
cf_item_add(cs, &(subcs2->item));
continue;
}
/* ignore everything else */
}
return true;
}
static char const *cf_local_file(char const *base, char const *filename,
char *buffer, size_t bufsize)
{
size_t dirsize;
char *p;
strlcpy(buffer, base, bufsize);
p = strrchr(buffer, FR_DIR_SEP);
if (!p) return filename;
if (p[1]) { /* ./foo */
p[1] = '\0';
}
dirsize = (p - buffer) + 1;
if ((dirsize + strlen(filename)) >= bufsize) {
return NULL;
}
strlcpy(p + 1, filename, bufsize - dirsize);
return buffer;
}
/*
* Read a part of the config file.
*/
static int cf_section_read(char const *filename, int *lineno, FILE *fp,
CONF_SECTION *current)
{
CONF_SECTION *this, *css;
CONF_PAIR *cpn;
char const *ptr;
char const *value;
char buf[8192];
char buf1[8192];
char buf2[8192];
char buf3[8192];
char buf4[8192];
FR_TOKEN t1 = T_INVALID, t2, t3;
bool has_spaces = false;
bool pass2;
char *cbuf = buf;
size_t len;
this = current; /* add items here */
/*
* Read, checking for line continuations ('\\' at EOL)
*/
for (;;) {
int at_eof;
css = NULL;
/*
* Get data, and remember if we are at EOF.
*/
at_eof = (fgets(cbuf, sizeof(buf) - (cbuf - buf), fp) == NULL);
(*lineno)++;
/*
* We read the entire 8k worth of data: complain.
* Note that we don't care if the last character
* is \n: it's still forbidden. This means that
* the maximum allowed length of text is 8k-1, which
* should be plenty.
*/
len = strlen(cbuf);
if ((cbuf + len + 1) >= (buf + sizeof(buf))) {
ERROR("%s[%d]: Line too long",
filename, *lineno);
return -1;
}
if (has_spaces) {
ptr = cbuf;
while (isspace((int) *ptr)) ptr++;
if (ptr > cbuf) {
memmove(cbuf, ptr, len - (ptr - cbuf));
len -= (ptr - cbuf);
}
}
/*
* Not doing continuations: check for edge
* conditions.
*/
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)) {
ERROR("%s[%d]: Continuation at EOF is illegal",
filename, *lineno);
return -1;
}
/*
* See if there's a continuation.
*/
while ((len > 0) &&
((cbuf[len - 1] == '\n') || (cbuf[len - 1] == '\r'))) {
len--;
cbuf[len] = '\0';
}
if ((len > 0) && (cbuf[len - 1] == '\\')) {
/*
* Check for "suppress spaces" magic.
*/
if (!has_spaces && (len > 2) && (cbuf[len - 2] == '"')) {
has_spaces = true;
}
cbuf[len - 1] = '\0';
cbuf += len - 1;
continue;
}
ptr = cbuf = buf;
has_spaces = false;
get_more:
pass2 = false;
/*
* The parser is getting to be evil.
*/
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) {
ERROR("%s[%d]: Invalid expansion: %s",
filename, *lineno, ptr);
return -1;
}
ptr += hack;
t2 = gettoken(&ptr, buf2, sizeof(buf2), true);
switch (t2) {
case T_EOL:
case T_HASH:
goto do_bare_word;
default:
ERROR("%s[%d]: Invalid expansion: %s",
filename, *lineno, ptr);
return -1;
}
} else {
t1 = gettoken(&ptr, buf1, sizeof(buf1), true);
}
/*
* The caller eats "name1 name2 {", and calls us
* for the data inside of the section. So if we
* receive a closing brace, then it must mean the
* end of the section.
*/
if (t1 == T_RCBRACE) {
if (this == current) {
ERROR("%s[%d]: Too many closing braces",
filename, *lineno);
return -1;
}
/*
* Merge the template into the existing
* section. This uses more memory, but
* means that templates now work with
* sub-sections, etc.
*/
if (!cf_template_merge(this, this->template)) {
return -1;
}
this = this->item.parent;
goto check_for_more;
}
if (t1 != T_BARE_WORD) goto skip_keywords;
/*
* Allow for $INCLUDE files
*
* This *SHOULD* work for any level include.
* I really really really hate this file. -cparker
*/
if ((strcasecmp(buf1, "$INCLUDE") == 0) ||
(strcasecmp(buf1, "$-INCLUDE") == 0)) {
bool relative = true;
t2 = getword(&ptr, buf2, sizeof(buf2), true);
if (t2 != T_EOL) {
ERROR("%s[%d]: Unexpected text after $INCLUDE",
filename, *lineno);
return -1;
}
if (buf2[0] == '$') relative = false;
value = cf_expand_variables(filename, lineno, this, buf4, sizeof(buf4), buf2, NULL);
if (!value) return -1;
if (!FR_DIR_IS_RELATIVE(value)) relative = false;
if (relative) {
value = cf_local_file(filename, value, buf3,
sizeof(buf3));
if (!value) {
ERROR("%s[%d]: Directories too deep.",
filename, *lineno);
return -1;
}
}
#ifdef HAVE_DIRENT_H
/*
* $INCLUDE foo/
*
* Include ALL non-"dot" files in the directory.
* careful!
*/
if (value[strlen(value) - 1] == '/') {
DIR *dir;
struct dirent *dp;
struct stat stat_buf;
DEBUG2("including files in directory %s", value );
#ifdef S_IWOTH
/*
* Security checks.
*/
if (stat(value, &stat_buf) < 0) {
ERROR("%s[%d]: Failed reading directory %s: %s",
filename, *lineno,
value, fr_syserror(errno));
return -1;
}
if ((stat_buf.st_mode & S_IWOTH) != 0) {
ERROR("%s[%d]: Directory %s is globally writable. Refusing to start due to "
"insecure configuration", filename, *lineno, value);
return -1;
}
#endif
dir = opendir(value);
if (!dir) {
ERROR("%s[%d]: Error reading directory %s: %s",
filename, *lineno, value,
fr_syserror(errno));
return -1;
}
/*
* Read the directory, ignoring "." files.
*/
while ((dp = readdir(dir)) != NULL) {
char const *p;
int slen;
if (dp->d_name[0] == '.') continue;
/*
* Check for valid characters
*/
for (p = dp->d_name; *p != '\0'; p++) {
if (isalpha((int)*p) ||
isdigit((int)*p) ||
(*p == '-') ||
(*p == '_') ||
(*p == '.')) continue;
break;
}
if (*p != '\0') continue;
slen = snprintf(buf2, sizeof(buf2), "%s%s",
value, dp->d_name);
if (slen >= (int) sizeof(buf2) || slen < 0) {
ERROR("%s: Full file path is too long.", dp->d_name);
return -1;
}
if ((stat(buf2, &stat_buf) != 0) ||
S_ISDIR(stat_buf.st_mode)) continue;
/*
* Read the file into the current
* configuration section.
*/
if (cf_file_include(this, buf2, true) < 0) {
closedir(dir);
return -1;
}
}
closedir(dir);
} else
#endif
{ /* it was a normal file */
if (buf1[1] == '-') {
struct stat statbuf;
if (stat(value, &statbuf) < 0) {
WARN("Not including file %s: %s", value, fr_syserror(errno));
continue;
}
}
if (cf_file_include(this, value, false) < 0) {
return -1;
}
}
continue;
} /* we were in an include */
if (strcasecmp(buf1, "$template") == 0) {
CONF_ITEM *ci;
CONF_SECTION *parentcs, *templatecs;
t2 = getword(&ptr, buf2, sizeof(buf2), true);
if (t2 != T_EOL) {
ERROR("%s[%d]: Unexpected text after $TEMPLATE", filename, *lineno);
return -1;
}
parentcs = cf_top_section(current);
templatecs = cf_section_sub_find(parentcs, "templates");
if (!templatecs) {
ERROR("%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)) {
ERROR("%s[%d]: Reference \"%s\" not found", filename, *lineno, buf2);
return -1;
}
if (!this) {
ERROR("%s[%d]: Internal sanity check error in template reference", filename, *lineno);
return -1;
}
if (this->template) {
ERROR("%s[%d]: Section already has a template", filename, *lineno);
return -1;
}
this->template = cf_item_to_section(ci);
continue;
}
/*
* Ensure that the user can't add CONF_PAIRs
* with 'internal' names;
*/
if (buf1[0] == '_') {
ERROR("%s[%d]: Illegal configuration pair name \"%s\"", filename, *lineno, buf1);
return -1;
}
/*
* Handle if/elsif specially.
*/
if ((strcmp(buf1, "if") == 0) || (strcmp(buf1, "elsif") == 0)) {
ssize_t slen;
char const *error = NULL;
char *p;
CONF_SECTION *server;
fr_cond_t *cond = NULL;
/*
* if / elsif MUST be inside of a
* processing section, which MUST in turn
* be inside of a "server" directive.
*/
if (!this->item.parent) {
invalid_location:
ERROR("%s[%d]: Invalid location for '%s'",
filename, *lineno, buf1);
return -1;
}
/*
* Can only have "if" in 3 named sections.
*/
server = this->item.parent;
while (server &&
(strcmp(server->name1, "server") != 0) &&
(strcmp(server->name1, "policy") != 0) &&
(strcmp(server->name1, "instantiate") != 0)) {
server = server->item.parent;
if (!server) goto invalid_location;
}
/*
* Skip (...) to find the {
*/
slen = fr_condition_tokenize(this, cf_section_to_item(this), ptr, &cond,
&error, FR_COND_TWO_PASS);
memcpy(&p, &ptr, sizeof(p));
if (slen < 0) {
if (p[-slen] != '{') goto cond_error;
slen = -slen;
}
TALLOC_FREE(cond);
/*
* This hack is so that the NEXT stage
* doesn't go "too far" in expanding the
* variable. We can parse the conditions
* without expanding the ${...} stuff.
* BUT we don't want to expand all of the
* stuff AFTER the condition. So we do
* two passes.
*
* The first pass is to discover the end
* of the condition. We then expand THAT
* string, and do a second pass parsing
* the expanded condition.
*/
p += slen;
*p = '\0';
/*
* If there's a ${...}. If so, expand it.
*/
if (strchr(ptr, '$') != NULL) {
ptr = cf_expand_variables(filename, lineno,
this,
buf3, sizeof(buf3),
ptr, NULL);
if (!ptr) {
ERROR("%s[%d]: Parse error expanding ${...} in condition",
filename, *lineno);
return -1;
}
} /* else leave it alone */
css = cf_section_alloc(this, buf1, ptr);
if (!css) {
ERROR("%s[%d]: Failed allocating memory for section",
filename, *lineno);
return -1;
}
css->item.filename = filename;
css->item.lineno = *lineno;
slen = fr_condition_tokenize(css, cf_section_to_item(css), ptr, &cond,
&error, FR_COND_TWO_PASS);
*p = '{'; /* put it back */
cond_error:
if (slen < 0) {
char *spaces, *text;
fr_canonicalize_error(this, &spaces, &text, slen, ptr);
ERROR("%s[%d]: Parse error in condition",
filename, *lineno);
ERROR("%s[%d]: %s", filename, *lineno, text);
ERROR("%s[%d]: %s^ %s", filename, *lineno, spaces, error);
talloc_free(spaces);
talloc_free(text);
talloc_free(css);
return -1;
}
if ((size_t) slen >= (sizeof(buf2) - 1)) {
talloc_free(css);
ERROR("%s[%d]: Condition is too large after \"%s\"",
filename, *lineno, buf1);
return -1;
}
/*
* Copy the expanded and parsed condition
* into buf2. Then, parse the text after
* the condition, which now MUST be a '{.
*
* If it wasn't '{' it would have been
* caught in the first pass of
* conditional parsing, above.
*/
memcpy(buf2, ptr, slen);
buf2[slen] = '\0';
ptr = p;
if ((t3 = gettoken(&ptr, buf3, sizeof(buf3), true)) != T_LCBRACE) {
talloc_free(css);
ERROR("%s[%d]: Expected '{' %d",
filename, *lineno, t3);
return -1;
}
/*
* Swap the condition with trailing stuff for
* the final condition.
*/
memcpy(&p, &css->name2, sizeof(css->name2));
talloc_free(p);
css->name2 = talloc_typed_strdup(css, buf2);
cf_item_add(this, &(css->item));
cf_data_add_internal(css, "if", cond, NULL, false);
/*
* The current section is now the child section.
*/
this = css;
css = NULL;
goto check_for_more;
}
skip_keywords:
/*
* Grab the next token.
*/
t2 = gettoken(&ptr, buf2, sizeof(buf2), !cf_new_escape);
switch (t2) {
case T_EOL:
case T_HASH:
case T_COMMA:
do_bare_word:
t3 = t2;
t2 = T_OP_EQ;
value = NULL;
goto do_set;
case T_OP_INCRM:
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)) {
ERROR("%s[%d]: Invalid operator in assignment",
filename, *lineno);
return -1;
}
/* FALL-THROUGH */
case T_OP_EQ:
case T_OP_SET:
case T_OP_PREPEND:
while (isspace((int) *ptr)) ptr++;
/*
* Be a little more forgiving.
*/
if (*ptr == '#') {
t3 = T_HASH;
} else
/*
* New parser: non-quoted strings are
* bare words, and we parse everything
* until the next newline, or the next
* comma. If they have { or } in a bare
* word, well... too bad.
*/
if (cf_new_escape && (*ptr != '"') && (*ptr != '\'')
&& (*ptr != '`') && (*ptr != '/')) {
const char *q = ptr;
t3 = T_BARE_WORD;
while (*q && (*q >= ' ') && (*q != ',') &&
!isspace(*q)) q++;
if ((size_t) (q - ptr) >= sizeof(buf3)) {
ERROR("%s[%d]: Parse error: value too long",
filename, *lineno);
return -1;
}
memcpy(buf3, ptr, (q - ptr));
buf3[q - ptr] = '\0';
ptr = q;
} else {
t3 = getstring(&ptr, buf3, sizeof(buf3), !cf_new_escape);
}
if (t3 == T_INVALID) {
ERROR("%s[%d]: Parse error: %s",
filename, *lineno,
fr_strerror());
return -1;
}
/*
* Allow "foo" by itself, or "foo = bar"
*/
switch (t3) {
bool soft_fail;
case T_BARE_WORD:
case T_DOUBLE_QUOTED_STRING:
case T_BACK_QUOTED_STRING:
value = cf_expand_variables(filename, lineno, this, buf4, sizeof(buf4), buf3, &soft_fail);
if (!value) {
if (!soft_fail) return -1;
/*
* References an item which doesn't exist,
* or which is already marked up as being
* expanded in pass2. Wait for pass2 to
* do the expansions.
*/
pass2 = true;
value = buf3;
}
break;
case T_EOL:
case T_HASH:
value = NULL;
break;
default:
value = buf3;
break;
}
/*
* Add this CONF_PAIR to our CONF_SECTION
*/
do_set:
cpn = cf_pair_alloc(this, buf1, value, t2, t1, t3);
if (!cpn) return -1;
cpn->item.filename = filename;
cpn->item.lineno = *lineno;
cpn->pass2 = pass2;
cf_item_add(this, &(cpn->item));
/*
* Require a comma, unless there's a comment.
*/
while (isspace(*ptr)) ptr++;
if (*ptr == ',') {
ptr++;
break;
}
/*
* module # stuff!
* foo = bar # other stuff
*/
if ((t3 == T_HASH) || (t3 == T_COMMA) || (t3 == T_EOL) || (*ptr == '#')) continue;
if (!*ptr || (*ptr == '}')) break;
ERROR("%s[%d]: Syntax error: Expected comma after '%s': %s",
filename, *lineno, value, ptr);
return -1;
/*
* No '=', must be a section or sub-section.
*/
case T_BARE_WORD:
case T_DOUBLE_QUOTED_STRING:
case T_SINGLE_QUOTED_STRING:
t3 = gettoken(&ptr, buf3, sizeof(buf3), true);
if (t3 != T_LCBRACE) {
ERROR("%s[%d]: Expecting section start brace '{' after \"%s %s\"",
filename, *lineno, buf1, buf2);
return -1;
}
/* FALL-THROUGH */
case T_LCBRACE:
css = cf_section_alloc(this, buf1,
t2 == T_LCBRACE ? NULL : buf2);
if (!css) {
ERROR("%s[%d]: Failed allocating memory for section",
filename, *lineno);
return -1;
}
css->item.filename = filename;
css->item.lineno = *lineno;
cf_item_add(this, &(css->item));
/*
* There may not be a name2
*/
css->name2_type = (t2 == T_LCBRACE) ? T_INVALID : t2;
/*
* The current section is now the child section.
*/
this = css;
break;
case T_INVALID:
ERROR("%s[%d]: Syntax error in '%s': %s", filename, *lineno, ptr, fr_strerror());
return -1;
default:
ERROR("%s[%d]: Parse error after \"%s\": unexpected token \"%s\"",
filename, *lineno, buf1, fr_int2str(fr_tokens, t2, "<INVALID>"));
return -1;
}
check_for_more:
/*
* Done parsing one thing. Skip to EOL if possible.
*/
while (isspace(*ptr)) ptr++;
if (*ptr == '#') continue;
if (*ptr) {
goto get_more;
}
}
/*
* See if EOF was unexpected ..
*/
if (feof(fp) && (this != current)) {
ERROR("%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;
}
/*
* Include one config file in another.
*/
static int cf_file_include(CONF_SECTION *cs, char const *filename_in, bool from_dir)
{
FILE *fp;
int rcode;
int lineno = 0;
char const *filename;
/*
* So we only need to do this once.
*/
filename = talloc_strdup(cs, filename_in);
/*
* This may return "0" if we already loaded the file.
*/
rcode = cf_file_open(cs, filename, from_dir, &fp);
if (rcode <= 0) return rcode;
if (!cs->item.filename) cs->item.filename = filename;
/*
* Read the section. It's OK to have EOF without a
* matching close brace.
*/
if (cf_section_read(filename, &lineno, fp, cs) < 0) {
fclose(fp);
return -1;
}
fclose(fp);
return 0;
}
/*
* Do variable expansion in pass2.
*
* This is a breadth-first expansion. "deep
*/
static int cf_section_pass2(CONF_SECTION *cs)
{
CONF_ITEM *ci;
for (ci = cs->children; ci; ci = ci->next) {
char const *value;
CONF_PAIR *cp;
char buffer[8192];
if (ci->type != CONF_ITEM_PAIR) continue;
cp = cf_item_to_pair(ci);
if (!cp->value || !cp->pass2) continue;
rad_assert((cp->rhs_type == T_BARE_WORD) ||
(cp->rhs_type == T_DOUBLE_QUOTED_STRING) ||
(cp->rhs_type == T_BACK_QUOTED_STRING));
value = cf_expand_variables(ci->filename, &ci->lineno, cs, buffer, sizeof(buffer), cp->value, NULL);
if (!value) return -1;
rad_const_free(cp->value);
cp->value = talloc_typed_strdup(cp, value);
}
for (ci = cs->children; ci; ci = ci->next) {
if (ci->type != CONF_ITEM_SECTION) continue;
if (cf_section_pass2(cf_item_to_section(ci)) < 0) return -1;
}
return 0;
}
/*
* Bootstrap a config file.
*/
int cf_file_read(CONF_SECTION *cs, char const *filename)
{
char *p;
CONF_PAIR *cp;
rbtree_t *tree;
cp = cf_pair_alloc(cs, "confdir", filename, T_OP_SET, T_BARE_WORD, T_SINGLE_QUOTED_STRING);
if (!cp) return -1;
p = strrchr(cp->value, FR_DIR_SEP);
if (p) *p = '\0';
cp->item.filename = "<internal>";
cp->item.lineno = -1;
cf_item_add(cs, &(cp->item));
tree = rbtree_create(cs, filename_cmp, NULL, 0);
if (!tree) return -1;
//??/
cf_data_add_internal(cs, "filename", tree, NULL, 0);
if (cf_file_include(cs, filename, false) < 0) return -1;
/*
* Now that we've read the file, go back through it and
* expand the variables.
*/
if (cf_section_pass2(cs) < 0) return -1;
return 0;
}
void cf_file_free(CONF_SECTION *cs)
{
talloc_free(cs);
}
/*
* Return a CONF_PAIR within a CONF_SECTION.
*/
CONF_PAIR *cf_pair_find(CONF_SECTION const *cs, char const *name)
{
CONF_PAIR *cp, mycp;
if (!cs || !name) return NULL;
mycp.attr = name;
cp = rbtree_finddata(cs->pair_tree, &mycp);
if (cp) return cp;
if (!cs->template) return NULL;
return rbtree_finddata(cs->template->pair_tree, &mycp);
}
/*
* Return the attr of a CONF_PAIR
*/
char const *cf_pair_attr(CONF_PAIR const *pair)
{
return (pair ? pair->attr : NULL);
}
/*
* Return the value of a CONF_PAIR
*/
char const *cf_pair_value(CONF_PAIR const *pair)
{
return (pair ? pair->value : NULL);
}
FR_TOKEN cf_pair_operator(CONF_PAIR const *pair)
{
return (pair ? pair->op : T_INVALID);
}
/** Return the value (lhs) type
*
* @param pair to extract value type from.
* @return one of T_BARE_WORD, T_SINGLE_QUOTED_STRING, T_BACK_QUOTED_STRING
* T_DOUBLE_QUOTED_STRING or T_INVALID if the pair is NULL.
*/
FR_TOKEN cf_pair_attr_type(CONF_PAIR const *pair)
{
return (pair ? pair->lhs_type : T_INVALID);
}
/** Return the value (rhs) type
*
* @param pair to extract value type from.
* @return one of T_BARE_WORD, T_SINGLE_QUOTED_STRING, T_BACK_QUOTED_STRING
* T_DOUBLE_QUOTED_STRING or T_INVALID if the pair is NULL.
*/
FR_TOKEN cf_pair_value_type(CONF_PAIR const *pair)
{
return (pair ? pair->rhs_type : T_INVALID);
}
/*
* Turn a CONF_PAIR into a VALUE_PAIR
* For now, ignore the "value_type" field...
*/
VALUE_PAIR *cf_pairtovp(CONF_PAIR *pair)
{
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;
}
/*
* false comparisons never match. BUT if it's a "string"
* or `string`, then remember to expand it later.
*/
if ((pair->op != T_OP_CMP_FALSE) &&
((pair->rhs_type == T_DOUBLE_QUOTED_STRING) ||
(pair->rhs_type == T_BACK_QUOTED_STRING))) {
VALUE_PAIR *vp;
vp = fr_pair_make(pair, NULL, pair->attr, NULL, pair->op);
if (!vp) {
return NULL;
}
if (fr_pair_mark_xlat(vp, pair->value) < 0) {
talloc_free(vp);
return NULL;
}
return vp;
}
return fr_pair_make(pair, NULL, pair->attr, pair->value, pair->op);
}
/*
* Return the first label of a CONF_SECTION
*/
char const *cf_section_name1(CONF_SECTION const *cs)
{
return (cs ? cs->name1 : NULL);
}
/*
* Return the second label of a CONF_SECTION
*/
char const *cf_section_name2(CONF_SECTION const *cs)
{
return (cs ? cs->name2 : NULL);
}
/** Return name2 if set, else name1
*
*/
char const *cf_section_name(CONF_SECTION const *cs)
{
char const *name;
name = cf_section_name2(cs);
if (name) return name;
return cf_section_name1(cs);
}
/*
* Find a value in a CONF_SECTION
*/
char const *cf_section_value_find(CONF_SECTION const *cs, char const *attr)
{
CONF_PAIR *cp;
cp = cf_pair_find(cs, attr);
return (cp ? cp->value : NULL);
}
CONF_SECTION *cf_section_find_name2(CONF_SECTION const *cs,
char const *name1, char const *name2)
{
char const *their2;
CONF_ITEM const *ci;
if (!cs || !name1) return NULL;
for (ci = &(cs->item); ci; ci = ci->next) {
if (ci->type != CONF_ITEM_SECTION)
continue;
if (strcmp(cf_item_to_section(ci)->name1, name1) != 0) {
continue;
}
their2 = cf_item_to_section(ci)->name2;
if ((!name2 && !their2) ||
(name2 && their2 && (strcmp(name2, their2) == 0))) {
return cf_item_to_section(ci);
}
}
return NULL;
}
/** Find a pair with a name matching attr, after specified pair.
*
* @param cs to search in.
* @param pair to search from (may be NULL).
* @param attr to find (may be NULL in which case any attribute matches).
* @return the next matching CONF_PAIR or NULL if none matched.
*/
CONF_PAIR *cf_pair_find_next(CONF_SECTION const *cs,
CONF_PAIR const *pair, char const *attr)
{
CONF_ITEM *ci;
if (!cs) return NULL;
/*
* If pair is NULL and we're trying to find a specific
* attribute this must be a first time run.
*
* Find the pair with correct name.
*/
if (!pair && attr) return cf_pair_find(cs, attr);
/*
* Start searching from the next child, or from the head
* of the list of children (if no pair was provided).
*/
for (ci = pair ? pair->item.next : cs->children;
ci;
ci = ci->next) {
if (ci->type != CONF_ITEM_PAIR) continue;
if (!attr || strcmp(cf_item_to_pair(ci)->attr, attr) == 0) break;
}
return cf_item_to_pair(ci);
}
/*
* Find a CONF_SECTION, or return the root if name is NULL
*/
CONF_SECTION *cf_section_find(char const *name)
{
if (name)
return cf_section_sub_find(root_config, name);
else
return root_config;
}
/** Find a sub-section in a section
*
* This finds ANY section having the same first name.
* The second name is ignored.
*/
CONF_SECTION *cf_section_sub_find(CONF_SECTION const *cs, char const *name)
{
CONF_SECTION mycs;
if (!cs || !name) return NULL; /* can't find an un-named section */
/*
* No sub-sections have been defined, so none exist.
*/
if (!cs->section_tree) return NULL;
mycs.name1 = name;
mycs.name2 = NULL;
return rbtree_finddata(cs->section_tree, &mycs);
}
/** Find a CONF_SECTION with both names.
*
*/
CONF_SECTION *cf_section_sub_find_name2(CONF_SECTION const *cs,
char const *name1, char const *name2)
{
CONF_ITEM *ci;
if (!cs) cs = root_config;
if (!cs) return NULL;
if (name1) {
CONF_SECTION mycs, *master_cs;
if (!cs->section_tree) return NULL;
mycs.name1 = name1;
mycs.name2 = name2;
master_cs = rbtree_finddata(cs->section_tree, &mycs);
if (!master_cs) return NULL;
/*
* Look it up in the name2 tree. If it's there,
* return it.
*/
if (master_cs->name2_tree) {
CONF_SECTION *subcs;
subcs = rbtree_finddata(master_cs->name2_tree, &mycs);
if (subcs) return subcs;
}
/*
* We don't insert ourselves into the name2 tree.
* So if there's nothing in the name2 tree, maybe
* *we* are the answer.
*/
if (!master_cs->name2 && name2) return NULL;
if (master_cs->name2 && !name2) return NULL;
if (!master_cs->name2 && !name2) return master_cs;
if (strcmp(master_cs->name2, name2) == 0) {
return master_cs;
}
return NULL;
}
/*
* Else do it the old-fashioned way.
*/
for (ci = cs->children; ci; ci = ci->next) {
CONF_SECTION *subcs;
if (ci->type != CONF_ITEM_SECTION)
continue;
subcs = cf_item_to_section(ci);
if (!subcs->name2) {
if (strcmp(subcs->name1, name2) == 0) break;
} else {
if (strcmp(subcs->name2, name2) == 0) break;
}
}
return cf_item_to_section(ci);
}
/*
* Return the next subsection after a CONF_SECTION
* with a certain name1 (char *name1). If the requested
* name1 is NULL, any name1 matches.
*/
CONF_SECTION *cf_subsection_find_next(CONF_SECTION const *section,
CONF_SECTION const *subsection,
char const *name1)
{
CONF_ITEM *ci;
if (!section) return NULL;
/*
* If subsection is NULL this must be a first time run
* Find the subsection with correct name
*/
if (!subsection) {
ci = section->children;
} else {
ci = subsection->item.next;
}
for (; ci; ci = ci->next) {
if (ci->type != CONF_ITEM_SECTION)
continue;
if ((name1 == NULL) ||
(strcmp(cf_item_to_section(ci)->name1, name1) == 0))
break;
}
return cf_item_to_section(ci);
}
/*
* Return the next section after a CONF_SECTION
* with a certain name1 (char *name1). If the requested
* name1 is NULL, any name1 matches.
*/
CONF_SECTION *cf_section_find_next(CONF_SECTION const *section,
CONF_SECTION const *subsection,
char const *name1)
{
if (!section) return NULL;
if (!section->item.parent) return NULL;
return cf_subsection_find_next(section->item.parent, subsection, name1);
}
/** Return the next item after a CONF_ITEM.
*
*/
CONF_ITEM *cf_item_find_next(CONF_SECTION const *section, CONF_ITEM const *item)
{
if (!section) return NULL;
/*
* If item is NULL this must be a first time run
* Return the first item
*/
if (item == NULL) {
return section->children;
} else {
return item->next;
}
}
static void _pair_count(int *count, CONF_SECTION const *cs)
{
CONF_ITEM const *ci;
for (ci = cf_item_find_next(cs, NULL);
ci != NULL;
ci = cf_item_find_next(cs, ci)) {
if (cf_item_is_section(ci)) {
_pair_count(count, cf_item_to_section(ci));
continue;
}
(*count)++;
}
}
/** Count the number of conf pairs beneath a section
*
* @param[in] cs to search for items in.
* @return number of pairs nested within section.
*/
int cf_pair_count(CONF_SECTION const *cs)
{
int count = 0;
_pair_count(&count, cs);
return count;
}
CONF_SECTION *cf_item_parent(CONF_ITEM const *ci)
{
if (!ci) return NULL;
return ci->parent;
}
int cf_section_lineno(CONF_SECTION const *section)
{
return section->item.lineno;
}
char const *cf_pair_filename(CONF_PAIR const *pair)
{
return pair->item.filename;
}
char const *cf_section_filename(CONF_SECTION const *section)
{
return section->item.filename;
}
int cf_pair_lineno(CONF_PAIR const *pair)
{
return pair->item.lineno;
}
bool cf_item_is_section(CONF_ITEM const *item)
{
return item->type == CONF_ITEM_SECTION;
}
bool cf_item_is_pair(CONF_ITEM const *item)
{
return item->type == CONF_ITEM_PAIR;
}
bool cf_item_is_data(CONF_ITEM const *item)
{
return item->type == CONF_ITEM_DATA;
}
static CONF_DATA *cf_data_alloc(CONF_SECTION *parent, char const *name,
void *data, void (*data_free)(void *))
{
CONF_DATA *cd;
cd = talloc_zero(parent, CONF_DATA);
if (!cd) return NULL;
cd->item.type = CONF_ITEM_DATA;
cd->item.parent = parent;
cd->name = talloc_typed_strdup(cd, name);
if (!cd->name) {
talloc_free(cd);
return NULL;
}
cd->data = data;
cd->free = data_free;
if (cd->free) {
talloc_set_destructor(cd, _cf_data_free);
}
return cd;
}
static void *cf_data_find_internal(CONF_SECTION const *cs, char const *name, int flag)
{
if (!cs || !name) return NULL;
/*
* Find the name in the tree, for speed.
*/
if (cs->data_tree) {
CONF_DATA mycd;
mycd.name = name;
mycd.flag = flag;
return rbtree_finddata(cs->data_tree, &mycd);
}
return NULL;
}
/*
* Find data from a particular section.
*/
void *cf_data_find(CONF_SECTION const *cs, char const *name)
{
CONF_DATA *cd = cf_data_find_internal(cs, name, 0);
if (cd) return cd->data;
return NULL;
}
/*
* Add named data to a configuration section.
*/
static int cf_data_add_internal(CONF_SECTION *cs, char const *name,
void *data, void (*data_free)(void *),
int flag)
{
CONF_DATA *cd;
if (!cs || !name) return -1;
/*
* Already exists. Can't add it.
*/
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_data_to_item(cd));
return 0;
}
/*
* Add named data to a configuration section.
*/
int cf_data_add(CONF_SECTION *cs, char const *name,
void *data, void (*data_free)(void *))
{
return cf_data_add_internal(cs, name, data, data_free, 0);
}
/** Remove named data from a configuration section
*
*/
void *cf_data_remove(CONF_SECTION *cs, char const *name)
{
CONF_DATA mycd;
CONF_DATA *cd;
CONF_ITEM *ci, *it;
void *data;
if (!cs || !name) return NULL;
if (!cs->data_tree) return NULL;
/*
* Find the name in the tree, for speed.
*/
mycd.name = name;
mycd.flag = 0;
cd = rbtree_finddata(cs->data_tree, &mycd);
if (!cd) return NULL;
ci = cf_data_to_item(cd);
if (cs->children == ci) {
cs->children = ci->next;
if (cs->tail == ci) cs->tail = NULL;
} else {
for (it = cs->children; it; it = it->next) {
if (it->next == ci) {
it->next = ci->next;
if (cs->tail == ci) cs->tail = it;
break;
}
}
}
talloc_set_destructor(cd, NULL); /* Disarm the destructor */
rbtree_deletebydata(cs->data_tree, &mycd);
data = cd->data;
talloc_free(cd);
return data;
}
/*
* This is here to make the rest of the code easier to read. It
* ties conffile.c to log.c, but it means we don't have to
* pollute every other function with the knowledge of the
* configuration internals.
*/
void cf_log_err(CONF_ITEM const *ci, char const *fmt, ...)
{
va_list ap;
char buffer[256];
va_start(ap, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, ap);
va_end(ap);
if (ci) {
ERROR("%s[%d]: %s",
ci->filename ? ci->filename : "unknown",
ci->lineno ? ci->lineno : 0,
buffer);
} else {
ERROR("<unknown>[*]: %s", buffer);
}
}
void cf_log_err_cs(CONF_SECTION const *cs, char const *fmt, ...)
{
va_list ap;
char buffer[256];
va_start(ap, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, ap);
va_end(ap);
rad_assert(cs != NULL);
ERROR("%s[%d]: %s",
cs->item.filename ? cs->item.filename : "unknown",
cs->item.lineno ? cs->item.lineno : 0,
buffer);
}
void cf_log_err_cp(CONF_PAIR const *cp, char const *fmt, ...)
{
va_list ap;
char buffer[256];
va_start(ap, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, ap);
va_end(ap);
rad_assert(cp != NULL);
ERROR("%s[%d]: %s",
cp->item.filename ? cp->item.filename : "unknown",
cp->item.lineno ? cp->item.lineno : 0,
buffer);
}
void cf_log_info(CONF_SECTION const *cs, char const *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if ((rad_debug_lvl > 1) && cs) vradlog(L_DBG, fmt, ap);
va_end(ap);
}
/*
* Wrapper to simplify the code.
*/
void cf_log_module(CONF_SECTION const *cs, char const *fmt, ...)
{
va_list ap;
char buffer[256];
va_start(ap, fmt);
if (rad_debug_lvl > 1 && cs) {
vsnprintf(buffer, sizeof(buffer), fmt, ap);
DEBUG("%.*s# %s", cs->depth, parse_spaces, buffer);
}
va_end(ap);
}
const CONF_PARSER *cf_section_parse_table(CONF_SECTION *cs)
{
if (!cs) return NULL;
return cs->variables;
}
/*
* For "switch" and "case" statements.
*/
FR_TOKEN cf_section_name2_type(CONF_SECTION const *cs)
{
if (!cs) return T_INVALID;
return cs->name2_type;
}
来看下dict:
const section_type_value_t section_type_value[MOD_COUNT] = {
{ "authenticate", "Auth-Type", PW_AUTH_TYPE },
{ "authorize", "Autz-Type", PW_AUTZ_TYPE },
{ "preacct", "Pre-Acct-Type", PW_PRE_ACCT_TYPE },
{ "accounting", "Acct-Type", PW_ACCT_TYPE },
{ "session", "Session-Type", PW_SESSION_TYPE },
{ "pre-proxy", "Pre-Proxy-Type", PW_PRE_PROXY_TYPE },
{ "post-proxy", "Post-Proxy-Type", PW_POST_PROXY_TYPE },
{ "post-auth", "Post-Auth-Type", PW_POST_AUTH_TYPE }
#ifdef WITH_COA
,
{ "recv-coa", "Recv-CoA-Type", PW_RECV_COA_TYPE },
{ "send-coa", "Send-CoA-Type", PW_SEND_COA_TYPE }
#endif
};
/** Map a section name, to a section typename, to an attribute number
*
* Used by modules.c to define the mappings between names, types and control
* attributes.
*/
typedef struct section_type_value_t {
char const *section; //!< Section name e.g. "Authorize".
char const *typename; //!< Type name e.g. "Auth-Type".
int attr; //!< Attribute number.
} section_type_value_t;
目前radius中有各种属性以及对应的values值;?
其怎样解析 查找?目前其解析到dict里面去
values_byname = fr_hash_table_create(dict_value_name_hash,
dict_value_name_cmp,
fr_pool_free);
values_byvalue = fr_hash_table_create(dict_value_value_hash,
dict_value_value_cmp,
fr_pool_free);
// hash name vendor attr
static uint32_t dict_value_name_hash(void const *data)
{
uint32_t hash;
DICT_VALUE const *dval = data;
hash = dict_hashname(dval->name);
hash = fr_hash_update(&dval->vendor, sizeof(dval->vendor), hash);
return fr_hash_update(&dval->attr, sizeof(dval->attr), hash);
}
// comp attr vendor name
static int dict_value_name_cmp(void const *one, void const *two)
{
int rcode;
DICT_VALUE const *a = one;
DICT_VALUE const *b = two;
rcode = a->attr - b->attr;
if (rcode != 0) return rcode;
rcode = a->vendor - b->vendor;
if (rcode != 0) return rcode;
return strcasecmp(a->name, b->name);
}
// hash attr vendor value
static uint32_t dict_value_value_hash(void const *data)
{
uint32_t hash;
DICT_VALUE const *dval = data;
hash = fr_hash(&dval->attr, sizeof(dval->attr));
hash = fr_hash_update(&dval->vendor, sizeof(dval->vendor), hash);
return fr_hash_update(&dval->value, sizeof(dval->value), hash);
}
// comp vendor attr value
static int dict_value_value_cmp(void const *one, void const *two)
{
int rcode;
DICT_VALUE const *a = one;
DICT_VALUE const *b = two;
if (a->vendor < b->vendor) return -1;
if (a->vendor > b->vendor) return +1;
rcode = a->attr - b->attr;
if (rcode != 0) return rcode;
return a->value - b->value;
}
/*
* Create the table of attributes by name. There MAY NOT
* be multiple attributes of the same name.
*
* Each attribute is malloc'd, so the free function is free.
*/
attributes_byname = fr_hash_table_create(dict_attr_name_hash,
dict_attr_name_cmp,
fr_pool_free);
/*
* Create the table of attributes by value. There MAY
* be attributes of the same value. If there are, we
* pick the latest one.
*/
attributes_byvalue = fr_hash_table_create(dict_attr_value_hash,
dict_attr_value_cmp,
fr_pool_free);
/*
* Hash callback functions.
*/
static uint32_t dict_attr_name_hash(void const *data)
{
return dict_hashname(((DICT_ATTR const *)data)->name);
}
static int dict_attr_name_cmp(void const *one, void const *two)
{
DICT_ATTR const *a = one;
DICT_ATTR const *b = two;
return strcasecmp(a->name, b->name);
}
static uint32_t dict_attr_value_hash(void const *data)
{
uint32_t hash;
DICT_ATTR const *attr = data;
hash = fr_hash(&attr->vendor, sizeof(attr->vendor));
return fr_hash_update(&attr->attr, sizeof(attr->attr), hash);
}
static int dict_attr_value_cmp(void const *one, void const *two)
{
DICT_ATTR const *a = one;
DICT_ATTR const *b = two;
if (a->vendor < b->vendor) return -1;
if (a->vendor > b->vendor) return +1;
return a->attr - b->attr;
}
解析server 里面的authenticate时;
da = dict_attrbyvalue(section_type_value[comp].attr, 0);
//等同于下面
da = dict_attrbyvalue(PW_AUTH_TYPE, 0); //#define PW_AUTH_TYPE 1000
//在 dictionary.freeradius.internal:35 文件有 ATTRIBUTE Auth-Type 1000 integer
执行:
if (section_type_value[comp].attr == PW_AUTH_TYPE) {
CONF_ITEM *modref;
for (modref = cf_item_find_next(subcs, NULL);
modref != NULL;
modref = cf_item_find_next(subcs, modref)) {
CONF_PAIR *cp;
if (!cf_item_is_pair(modref)) continue;
cp = cf_item_to_pair(modref);
if (!define_type(cs, da, cf_pair_attr(cp))) {
return false;
}
static int define_type(CONF_SECTION *cs, DICT_ATTR const *da, char const *name)
{
uint32_t value;
DICT_VALUE *dval;
/*
* Allow for conditionally loaded types
*/
if (*name == '-') name++;
/*
* If the value already exists, don't
* create it again.
*/ // attr Auth-Type value da->atrr以及vendor value:eap
dval = dict_valbyname(da->attr, da->vendor, name);
if (dval) {
if (dval->value == 0) {
ERROR("The dictionaries must not define VALUE %s %s 0",
da->name, name);
return 0;
}
return 1;
}
/*
* Create a new unique value with a
* meaningless number. You can't look at
* it from outside of this code, so it
* doesn't matter. The only requirement
* is that it's unique.
*/
do {
value = (fr_rand() & 0x00ffffff) + 1;
} while (dict_valbyattr(da->attr, da->vendor, value));
cf_log_module(cs, "Creating %s = %s", da->name, name);
if (dict_addvalue(name, da->name, value) < 0) {
ERROR("%s", fr_strerror());
return 0;
}
return 1;
}
dict_addvalue (namestr=0x947060 "sql", attrstr=0x855bbc "Auth-Type", value=6701470) at src/lib/dict.c:133s
shez 设置 attr 的"Auth-Type" 的value = sql 添加进去
ATTRIBUTE Auth-Type 1000 integer
VALUE Auth-Type Local 1
VALUE Auth-Type Reject 4
#
# FreeRADIUS extensions (most originally from Cistron)
#
VALUE Auth-Type Accept 254
然后再定义
dict_addvalue (namestr=0x950260 "mschap", attrstr=0x855bbc "Auth-Type", value=5222969) at src/lib/dict.c:1340
VALUE Auth-Type mschap xxxx (随机数和以前的值不一样)
VALUE Auth-Type sql xxx(随机数)(并且是不存在的value)
问题?? auth_type 对应的value 去调用对应MOD_AUTHENTICATE的回调 这是怎样关联??
DICT_VALUE *dval = dict_valbyname(PW_AUTH_TYPE, 0, "sql");
int idx = dval->value;
rc = process_authenticate(idx, fake);
rlm_rcode_t process_authenticate(int auth_type, REQUEST *request)
{
return indexed_modcall(MOD_AUTHENTICATE, auth_type, request);
}
rlm_rcode_t indexed_modcall(rlm_components_t comp, int idx, REQUEST *request)
{
rlm_rcode_t rcode;
modcallable *list = NULL;
virtual_server_t *server;
/*
* Hack to find the correct virtual server.
*/
server = virtual_server_find(request->server);
if (!server) {
RDEBUG("No such virtual server \"%s\"", request->server);
return RLM_MODULE_FAIL;
}
if (idx == 0) {
list = server->mc[comp];
if (!list) {
if (server->name) {
RDEBUG3("Empty %s section in virtual server \"%s\". Using default return values.",
section_type_value[comp].section, server->name);
} else {
RDEBUG3("Empty %s section. Using default return values.", section_type_value[comp].section);
}
}
} else {
indexed_modcallable *this;
this = lookup_by_index(server->components, comp, idx);
if (this) {
list = this->modulelist;
} else {
RDEBUG2("%s sub-section not found. Ignoring.", section_type_value[comp].typename);
}
}
if (server->subcs[comp]) {
if (idx == 0) {
RDEBUG("# Executing section %s from file %s",
section_type_value[comp].section,
cf_section_filename(server->subcs[comp]));
} else {
RDEBUG("# Executing group from file %s",
cf_section_filename(server->subcs[comp]));
}
}
request->component = section_type_value[comp].section;
rcode = modcall(comp, list, request);
request->component = "<core>";
return rcode;
}
可以看到 authenticate 等MOD 是和server 绑定在一起的:
解析server的时候 调用
load_component_section(subcs, components, comp)
modref = cf_item_find_next(cs, NULL)
cp = cf_item_to_pair(modref);
modrefname = cf_pair_attr(cp);
dval = dict_valbyname(PW_AUTH_TYPE, 0, modrefname);
idx = dval->value;
subcomp = new_sublist(cs, components, comp, idx);
this = compile_modsingle(subcomp, &subcomp->modulelist, comp, modref, &modname);
server->subcs[comp] = subcs;
逻辑实现数据结构相互管理 有点多!!!
components----> rbtree 节点root
comp ----> mod_authenticate mod_authoxxx
idx---->auth_type 对应的value 值
不看了 !! 只需要了解大概数据结构。
目前修改代码也就是直接查找authenticate 中的mod_call
DICT_VALUE *dval = dict_valbyname(PW_AUTH_TYPE, 0, "sql");
int idx = dval->value;
rc = process_authenticate(idx, fake);
直接调用sql 模块的authenticate
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南