diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
commit | af754e596a8dbb05ed8580c342e7fe02e08b28e0 (patch) | |
tree | b2f334c2b55ede42081aa6710a72da784547d8ea /src/modules/rlm_preprocess | |
parent | Initial commit. (diff) | |
download | freeradius-af754e596a8dbb05ed8580c342e7fe02e08b28e0.tar.xz freeradius-af754e596a8dbb05ed8580c342e7fe02e08b28e0.zip |
Adding upstream version 3.2.3+dfsg.upstream/3.2.3+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/modules/rlm_preprocess')
-rw-r--r-- | src/modules/rlm_preprocess/README.md | 11 | ||||
-rw-r--r-- | src/modules/rlm_preprocess/all.mk | 2 | ||||
-rw-r--r-- | src/modules/rlm_preprocess/rlm_preprocess.c | 736 |
3 files changed, 749 insertions, 0 deletions
diff --git a/src/modules/rlm_preprocess/README.md b/src/modules/rlm_preprocess/README.md new file mode 100644 index 0000000..f3a6fc5 --- /dev/null +++ b/src/modules/rlm_preprocess/README.md @@ -0,0 +1,11 @@ +# rlm_preprocess +## Metadata +<dl> + <dt>category</dt><dd>policy</dd> +</dl> + +## Summary + +Helper module to pre-process incoming packets. This processes +'huntgroups' and 'hints' files, as well as fixing up a number of +NAS attribute issues. diff --git a/src/modules/rlm_preprocess/all.mk b/src/modules/rlm_preprocess/all.mk new file mode 100644 index 0000000..6b18994 --- /dev/null +++ b/src/modules/rlm_preprocess/all.mk @@ -0,0 +1,2 @@ +TARGET := rlm_preprocess.a +SOURCES := rlm_preprocess.c diff --git a/src/modules/rlm_preprocess/rlm_preprocess.c b/src/modules/rlm_preprocess/rlm_preprocess.c new file mode 100644 index 0000000..e6e12d4 --- /dev/null +++ b/src/modules/rlm_preprocess/rlm_preprocess.c @@ -0,0 +1,736 @@ +/* + * This program is 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 + */ + +/** + * $Id$ + * @file rlm_preprocess.c + * @brief Fixes up requests, and processes huntgroups/hints files. + * + * @copyright 2000,2006 The FreeRADIUS server project + * @copyright 2000 Alan DeKok <aland@ox.org> + */ +RCSID("$Id$") + +#include <freeradius-devel/radiusd.h> +#include <freeradius-devel/modules.h> +#include <freeradius-devel/rad_assert.h> + +#include <ctype.h> + +typedef struct rlm_preprocess_t { + char const *huntgroup_file; + char const *hints_file; + PAIR_LIST *huntgroups; + PAIR_LIST *hints; + bool with_ascend_hack; + uint32_t ascend_channels_per_line; + bool with_ntdomain_hack; + bool with_specialix_jetstream_hack; + bool with_cisco_vsa_hack; + bool with_alvarion_vsa_hack; + bool with_cablelabs_vsa_hack; +} rlm_preprocess_t; + +static const CONF_PARSER module_config[] = { + { "huntgroups", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_preprocess_t, huntgroup_file), NULL }, + { "hints", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_preprocess_t, hints_file), NULL }, + { "with_ascend_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_ascend_hack), "no" }, + { "ascend_channels_per_line", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_preprocess_t, ascend_channels_per_line), "23" }, + + { "with_ntdomain_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_ntdomain_hack), "no" }, + { "with_specialix_jetstream_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_specialix_jetstream_hack), "no" }, + { "with_cisco_vsa_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_cisco_vsa_hack), "no" }, + { "with_alvarion_vsa_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_alvarion_vsa_hack), "no" }, +#if 0 + { "with_cablelabs_vsa_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_cablelabs_vsa_hack), NULL }, +#endif + CONF_PARSER_TERMINATOR +}; + +/* + * See if a VALUE_PAIR list contains Fall-Through = Yes + */ +static int fall_through(VALUE_PAIR *vp) +{ + VALUE_PAIR *tmp; + tmp = fr_pair_find_by_num(vp, PW_FALL_THROUGH, 0, TAG_ANY); + + return tmp ? tmp->vp_integer : 0; +} + +/* + * This hack changes Ascend's wierd port numberings + * to standard 0-??? port numbers so that the "+" works + * for IP address assignments. + */ +static void ascend_nasport_hack(VALUE_PAIR *nas_port, int channels_per_line) +{ + int service; + int line; + int channel; + + if (!nas_port) { + return; + } + + if (nas_port->vp_integer > 9999) { + service = nas_port->vp_integer/10000; /* 1=digital 2=analog */ + line = (nas_port->vp_integer - (10000 * service)) / 100; + channel = nas_port->vp_integer - ((10000 * service) + (100 * line)); + nas_port->vp_integer = (channel - 1) + ((line - 1) * channels_per_line); + } +} + +/* + * This hack strips out Cisco's VSA duplicities in lines + * (Cisco not implemented VSA's in standard way. + * + * Cisco sends it's VSA attributes with the attribute name *again* + * in the string, like: H323-Attribute = "h323-attribute=value". + * This sort of behaviour is nonsense. + */ +static void cisco_vsa_hack(REQUEST *request) +{ + int vendorcode; + char *ptr; + char newattr[MAX_STRING_LEN]; + VALUE_PAIR *vp; + vp_cursor_t cursor; + for (vp = fr_cursor_init(&cursor, &request->packet->vps); + vp; + vp = fr_cursor_next(&cursor)) { + vendorcode = vp->da->vendor; + if (!((vendorcode == 9) || (vendorcode == 6618) || (vendorcode == 35265))) { + continue; /* not a Cisco, Quintum or Eltex VSA, continue */ + } + + if (vp->da->type != PW_TYPE_STRING) { + continue; + } + + /* + * No weird packing. Ignore it. + */ + ptr = strchr(vp->vp_strvalue, '='); /* find an '=' */ + if (!ptr) { + continue; + } + + /* + * Cisco-AVPair's get packed as: + * + * Cisco-AVPair = "h323-foo-bar = baz" + * Cisco-AVPair = "h323-foo-bar=baz" + * + * which makes sense only if you're a lunatic. + * This code looks for the attribute named inside + * of the string, and if it exists, adds it as a new + * attribute. + */ + if (vp->da->attr == 1) { + char const *p; + + p = vp->vp_strvalue; + gettoken(&p, newattr, sizeof(newattr), false); + + if (dict_attrbyname(newattr) != NULL) { + pair_make_request(newattr, ptr + 1, T_OP_EQ); + } + } else { /* h322-foo-bar = "h323-foo-bar = baz" */ + /* + * We strip out the duplicity from the + * value field, we use only the value on + * the right side of the '=' character. + */ + fr_pair_value_strcpy(vp, ptr + 1); + } + } +} + + +/* + * Don't even ask what this is doing... + */ +static void alvarion_vsa_hack(VALUE_PAIR *vp) +{ + int number = 1; + vp_cursor_t cursor; + + for (vp = fr_cursor_init(&cursor, &vp); + vp; + vp = fr_cursor_next(&cursor)) { + DICT_ATTR const *da; + + if (vp->da->vendor != 12394) { + continue; + } + + if (vp->da->type != PW_TYPE_STRING) { + continue; + } + + da = dict_attrbyvalue(number, 12394); + if (!da) { + continue; + } + + vp->da = da; + + number++; + } +} + +/* + * Cablelabs magic, taken from: + * + * http://www.cablelabs.com/packetcable/downloads/specs/PKT-SP-EM-I12-05812.pdf + * + * Sample data is: + * + * 0x0001d2d2026d30310000000000003030 + * 3130303030000e812333000100033031 + * 00000000000030303130303030000000 + * 00063230313230313331303630323231 + * 2e3633390000000081000500 + */ + +typedef struct cl_timezone_t { + uint8_t dst; + uint8_t sign; + uint8_t hh[2]; + uint8_t mm[2]; + uint8_t ss[2]; +} cl_timezone_t; + +typedef struct cl_bcid_t { + uint32_t timestamp; + uint8_t element_id[8]; + cl_timezone_t timezone; + uint32_t event_counter; +} cl_bcid_t; + +typedef struct cl_em_hdr_t { + uint16_t version; + cl_bcid_t bcid; + uint16_t message_type; + uint16_t element_type; + uint8_t element_id[8]; + cl_timezone_t time_zone; + uint32_t sequence_number; + uint8_t event_time[18]; + uint8_t status[4]; + uint8_t priority; + uint16_t attr_count; /* of normal Cablelabs VSAs */ + uint8_t event_object; +} cl_em_hdr_t; + + +static void cablelabs_vsa_hack(VALUE_PAIR **list) +{ + VALUE_PAIR *ev; + + ev = fr_pair_find_by_num(*list, 1, 4491, TAG_ANY); /* Cablelabs-Event-Message */ + if (!ev) { + return; + } + + /* + * FIXME: write 100's of lines of code to decode + * each data structure above. + */ +} + +/* + * Mangle username if needed, IN PLACE. + */ +static void rad_mangle(rlm_preprocess_t *inst, REQUEST *request) +{ + int num_proxy_state; + VALUE_PAIR *namepair; + VALUE_PAIR *request_pairs; + VALUE_PAIR *tmp; + vp_cursor_t cursor; + + /* + * Get the username from the request + * If it isn't there, then we can't mangle the request. + */ + request_pairs = request->packet->vps; + namepair = fr_pair_find_by_num(request_pairs, PW_USER_NAME, 0, TAG_ANY); + if (!namepair || (namepair->vp_length == 0)) { + return; + } + + if (inst->with_ntdomain_hack) { + char *ptr; + char newname[MAX_STRING_LEN]; + + /* + * Windows NT machines often authenticate themselves as + * NT_DOMAIN\username. Try to be smart about this. + * + * FIXME: should we handle this as a REALM ? + */ + if ((ptr = strchr(namepair->vp_strvalue, '\\')) != NULL) { + strlcpy(newname, ptr + 1, sizeof(newname)); + /* Same size */ + fr_pair_value_strcpy(namepair, newname); + } + } + + if (inst->with_specialix_jetstream_hack) { + /* + * Specialix Jetstream 8500 24 port access server. + * If the user name is 10 characters or longer, a "/" + * and the excess characters after the 10th are + * appended to the user name. + * + * Reported by Lucas Heise <root@laonet.net> + */ + if ((strlen(namepair->vp_strvalue) > 10) && + (namepair->vp_strvalue[10] == '/')) { + fr_pair_value_strcpy(namepair, namepair->vp_strvalue + 11); + } + } + + /* + * Small check: if Framed-Protocol present but Service-Type + * is missing, add Service-Type = Framed-User. + */ + if (fr_pair_find_by_num(request_pairs, PW_FRAMED_PROTOCOL, 0, TAG_ANY) != NULL && + fr_pair_find_by_num(request_pairs, PW_SERVICE_TYPE, 0, TAG_ANY) == NULL) { + tmp = radius_pair_create(request->packet, &request->packet->vps, PW_SERVICE_TYPE, 0); + tmp->vp_integer = PW_FRAMED_USER; + } + + num_proxy_state = 0; + for (tmp = fr_cursor_init(&cursor, &request->packet->vps); + tmp; + tmp = fr_cursor_next(&cursor)) { + if (tmp->da->vendor != 0) { + continue; + } + + if (tmp->da->attr != PW_PROXY_STATE) { + continue; + } + + num_proxy_state++; + } + + if (num_proxy_state > 10) { + RWDEBUG("There are more than 10 Proxy-State attributes in the request"); + RWDEBUG("You have likely configured an infinite proxy loop"); + } +} + +/* + * Compare the request with the "reply" part in the + * huntgroup, which normally only contains username or group. + * At least one of the "reply" items has to match. + */ +static int hunt_paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check) +{ + vp_cursor_t cursor; + VALUE_PAIR *check_item; + VALUE_PAIR *tmp; + int result = -1; + + if (!check) return 0; + + for (check_item = fr_cursor_init(&cursor, &check); + check_item && (result != 0); + check_item = fr_cursor_next(&cursor)) { + /* FIXME: fr_pair_list_copy should be removed once VALUE_PAIRs are no longer in linked lists */ + tmp = fr_pair_copy(request, check_item); + tmp->op = check_item->op; + result = paircompare(req, request, tmp, NULL); + fr_pair_list_free(&tmp); + } + + return result; +} + + +/* + * Add hints to the info sent by the terminal server + * based on the pattern of the username, and other attributes. + */ +static int hints_setup(PAIR_LIST *hints, REQUEST *request) +{ + char const *name; + VALUE_PAIR *add; + VALUE_PAIR *tmp; + PAIR_LIST *i; + int updated = 0, ft; + + if (!hints || !request->packet->vps) + return RLM_MODULE_NOOP; + + /* + * Check for valid input, zero length names not permitted + */ + name = (tmp = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY)) ? + tmp->vp_strvalue : NULL; + if (!name || name[0] == 0) { + /* + * No name, nothing to do. + */ + return RLM_MODULE_NOOP; + } + + for (i = hints; i; i = i->next) { + /* + * Use "paircompare", which is a little more general... + */ + if (((strcmp(i->name, "DEFAULT") == 0) || (strcmp(i->name, name) == 0)) && + (paircompare(request, request->packet->vps, i->check, NULL) == 0)) { + RDEBUG2("hints: Matched %s at %d", i->name, i->lineno); + /* + * Now add all attributes to the request list, + * except PW_STRIP_USER_NAME and PW_FALL_THROUGH + * and xlat them. + */ + add = fr_pair_list_copy(request->packet, i->reply); + ft = fall_through(add); + + fr_pair_delete_by_num(&add, PW_STRIP_USER_NAME, 0, TAG_ANY); + fr_pair_delete_by_num(&add, PW_FALL_THROUGH, 0, TAG_ANY); + radius_pairmove(request, &request->packet->vps, add, true); + + updated = 1; + if (!ft) { + break; + } + } + } + + if (updated == 0) { + return RLM_MODULE_NOOP; + } + + return RLM_MODULE_UPDATED; +} + +/* + * See if we have access to the huntgroup. + */ +static int huntgroup_access(REQUEST *request, PAIR_LIST *huntgroups) +{ + PAIR_LIST *i; + int r = RLM_MODULE_OK; + VALUE_PAIR *request_pairs = request->packet->vps; + + /* + * We're not controlling access by huntgroups: + * Allow them in. + */ + if (!huntgroups) { + return RLM_MODULE_OK; + } + + for (i = huntgroups; i; i = i->next) { + /* + * See if this entry matches. + */ + if (paircompare(request, request_pairs, i->check, NULL) != 0) { + continue; + } + + /* + * Now check for access. + */ + r = RLM_MODULE_REJECT; + if (hunt_paircmp(request, request_pairs, i->reply) == 0) { + VALUE_PAIR *vp; + + /* + * We've matched the huntgroup, so add it in + * to the list of request pairs. + */ + vp = fr_pair_find_by_num(request_pairs, PW_HUNTGROUP_NAME, 0, TAG_ANY); + if (!vp) { + vp = radius_pair_create(request->packet, &request->packet->vps, PW_HUNTGROUP_NAME, 0); + fr_pair_value_strcpy(vp, i->name); + } + r = RLM_MODULE_OK; + } + break; + } + + return r; +} + +/* + * If the NAS wasn't smart enought to add a NAS-IP-Address + * to the request, then add it ourselves. + */ +static int add_nas_attr(REQUEST *request) +{ + VALUE_PAIR *nas; + + switch (request->packet->src_ipaddr.af) { + case AF_INET: + nas = fr_pair_find_by_num(request->packet->vps, PW_NAS_IP_ADDRESS, 0, TAG_ANY); + if (!nas) { + nas = radius_pair_create(request->packet, &request->packet->vps, PW_NAS_IP_ADDRESS, 0); + nas->vp_ipaddr = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr; + } + break; + + case AF_INET6: + nas = fr_pair_find_by_num(request->packet->vps, PW_NAS_IPV6_ADDRESS, 0, TAG_ANY); + if (!nas) { + nas = radius_pair_create(request->packet, &request->packet->vps, PW_NAS_IPV6_ADDRESS, 0); + memcpy(&nas->vp_ipv6addr, &request->packet->src_ipaddr.ipaddr, + sizeof(request->packet->src_ipaddr.ipaddr)); + } + break; + + default: + ERROR("Unknown address family for packet"); + return -1; + } + + return 0; +} + + +/* + * Initialize. + */ +static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance) +{ + int ret; + rlm_preprocess_t *inst = instance; + + /* + * Read the huntgroups file. + */ + if (inst->huntgroup_file) { + ret = pairlist_read(inst, inst->huntgroup_file, &(inst->huntgroups), 0); + if (ret < 0) { + ERROR("rlm_preprocess: Error reading %s", inst->huntgroup_file); + + return -1; + } + } + + /* + * Read the hints file. + */ + if (inst->hints_file) { + ret = pairlist_read(inst, inst->hints_file, &(inst->hints), 0); + if (ret < 0) { + ERROR("rlm_preprocess: Error reading %s", inst->hints_file); + + return -1; + } + } + + return 0; +} + +/* + * Preprocess a request. + */ +static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) +{ + int r; + rlm_preprocess_t *inst = instance; + + VALUE_PAIR *vp; + + /* + * Mangle the username, to get rid of stupid implementation + * bugs. + */ + rad_mangle(inst, request); + + if (inst->with_ascend_hack) { + /* + * If we're using Ascend systems, hack the NAS-Port-Id + * in place, to go from Ascend's weird values to something + * approaching rationality. + */ + ascend_nasport_hack(fr_pair_find_by_num(request->packet->vps, PW_NAS_PORT, 0, TAG_ANY), + inst->ascend_channels_per_line); + } + + if (inst->with_cisco_vsa_hack) { + /* + * We need to run this hack because the h323-conf-id + * attribute should be used. + */ + cisco_vsa_hack(request); + } + + if (inst->with_alvarion_vsa_hack) { + /* + * We need to run this hack because the Alvarion + * people are crazy. + */ + alvarion_vsa_hack(request->packet->vps); + } + + if (inst->with_cablelabs_vsa_hack) { + /* + * We need to run this hack because the Cablelabs + * people are crazy. + */ + cablelabs_vsa_hack(&request->packet->vps); + } + + /* + * Add an event timestamp. Means Event-Timestamp can be used + * consistently instead of one letter expansions. + */ + vp = fr_pair_find_by_num(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY); + if (!vp) { + vp = radius_pair_create(request->packet, &request->packet->vps, PW_EVENT_TIMESTAMP, 0); + vp->vp_date = request->packet->timestamp.tv_sec; + } + + /* + * Note that we add the Request-Src-IP-Address to the request + * structure BEFORE checking huntgroup access. This allows + * the Request-Src-IP-Address to be used for huntgroup + * comparisons. + */ + if (add_nas_attr(request) < 0) { + return RLM_MODULE_FAIL; + } + + hints_setup(inst->hints, request); + + /* + * If there is a PW_CHAP_PASSWORD attribute but there + * is PW_CHAP_CHALLENGE we need to add it so that other + * modules can use it as a normal attribute. + */ + if (fr_pair_find_by_num(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY) && + fr_pair_find_by_num(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY) == NULL) { + vp = radius_pair_create(request->packet, &request->packet->vps, PW_CHAP_CHALLENGE, 0); + fr_pair_value_memcpy(vp, request->packet->vector, AUTH_VECTOR_LEN); + } + + if ((r = huntgroup_access(request, inst->huntgroups)) != RLM_MODULE_OK) { + char buf[1024]; + RIDEBUG("No huntgroup access: [%s] (%s)", + request->username ? request->username->vp_strvalue : "<NO User-Name>", + auth_name(buf, sizeof(buf), request, 1)); + + return r; + } + + return RLM_MODULE_OK; /* Meaning: try next authorization module */ +} + +/* + * Preprocess a request before accounting + */ +static rlm_rcode_t CC_HINT(nonnull) mod_preaccounting(void *instance, REQUEST *request) +{ + int r; + VALUE_PAIR *vp; + rlm_preprocess_t *inst = instance; + + /* + * Ensure that we have the SAME user name for both + * authentication && accounting. + */ + rad_mangle(inst, request); + + if (inst->with_cisco_vsa_hack) { + /* + * We need to run this hack because the h323-conf-id + * attribute should be used. + */ + cisco_vsa_hack(request); + } + + if (inst->with_alvarion_vsa_hack) { + /* + * We need to run this hack because the Alvarion + * people are crazy. + */ + alvarion_vsa_hack(request->packet->vps); + } + + if (inst->with_cablelabs_vsa_hack) { + /* + * We need to run this hack because the Cablelabs + * people are crazy. + */ + cablelabs_vsa_hack(&request->packet->vps); + } + + /* + * Ensure that we log the NAS IP Address in the packet. + */ + if (add_nas_attr(request) < 0) { + return RLM_MODULE_FAIL; + } + + hints_setup(inst->hints, request); + + /* + * Add an event timestamp. This means that the rest of + * the server can use it, rather than various error-prone + * manual calculations. + */ + vp = fr_pair_find_by_num(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY); + if (!vp) { + VALUE_PAIR *delay; + + vp = radius_pair_create(request->packet, &request->packet->vps, PW_EVENT_TIMESTAMP, 0); + vp->vp_date = request->packet->timestamp.tv_sec; + + delay = fr_pair_find_by_num(request->packet->vps, PW_ACCT_DELAY_TIME, 0, TAG_ANY); + if (delay) { + if ((delay->vp_integer >= vp->vp_date) || (delay->vp_integer == UINT32_MAX)) { + RWARN("Ignoring invalid Acct-Delay-time of %u seconds", delay->vp_integer); + } else { + vp->vp_date -= delay->vp_integer; + } + } + } + + if ((r = huntgroup_access(request, inst->huntgroups)) != RLM_MODULE_OK) { + char buf[1024]; + RIDEBUG("No huntgroup access: [%s] (%s)", + request->username ? request->username->vp_strvalue : "<NO User-Name>", + auth_name(buf, sizeof(buf), request, 1)); + return r; + } + + return r; +} + +/* globally exported name */ +extern module_t rlm_preprocess; +module_t rlm_preprocess = { + .magic = RLM_MODULE_INIT, + .name = "preprocess", + .inst_size = sizeof(rlm_preprocess_t), + .config = module_config, + .instantiate = mod_instantiate, + .methods = { + [MOD_AUTHORIZE] = mod_authorize, + [MOD_PREACCT] = mod_preaccounting + }, +}; + |