2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @file rlm_preprocess.c
20 * @brief Fixes up requests, and processes huntgroups/hints files.
22 * @copyright 2000,2006 The FreeRADIUS server project
23 * @copyright 2000 Alan DeKok <aland@ox.org>
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29 #include <freeradius-devel/rad_assert.h>
33 typedef struct rlm_preprocess_t {
34 char const *huntgroup_file;
35 char const *hints_file;
36 PAIR_LIST *huntgroups;
38 bool with_ascend_hack;
39 uint32_t ascend_channels_per_line;
40 bool with_ntdomain_hack;
41 bool with_specialix_jetstream_hack;
42 bool with_cisco_vsa_hack;
43 bool with_alvarion_vsa_hack;
44 bool with_cablelabs_vsa_hack;
47 static const CONF_PARSER module_config[] = {
48 { "huntgroups", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_preprocess_t, huntgroup_file), NULL },
49 { "hints", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_preprocess_t, hints_file), NULL },
50 { "with_ascend_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_ascend_hack), "no" },
51 { "ascend_channels_per_line", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_preprocess_t, ascend_channels_per_line), "23" },
53 { "with_ntdomain_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_ntdomain_hack), "no" },
54 { "with_specialix_jetstream_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_specialix_jetstream_hack), "no" },
55 { "with_cisco_vsa_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_cisco_vsa_hack), "no" },
56 { "with_alvarion_vsa_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_alvarion_vsa_hack), "no" },
58 { "with_cablelabs_vsa_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_cablelabs_vsa_hack), NULL },
60 CONF_PARSER_TERMINATOR
64 * See if a VALUE_PAIR list contains Fall-Through = Yes
66 static int fall_through(VALUE_PAIR *vp)
69 tmp = fr_pair_find_by_num(vp, PW_FALL_THROUGH, 0, TAG_ANY);
71 return tmp ? tmp->vp_integer : 0;
75 * This hack changes Ascend's wierd port numberings
76 * to standard 0-??? port numbers so that the "+" works
77 * for IP address assignments.
79 static void ascend_nasport_hack(VALUE_PAIR *nas_port, int channels_per_line)
89 if (nas_port->vp_integer > 9999) {
90 service = nas_port->vp_integer/10000; /* 1=digital 2=analog */
91 line = (nas_port->vp_integer - (10000 * service)) / 100;
92 channel = nas_port->vp_integer - ((10000 * service) + (100 * line));
93 nas_port->vp_integer = (channel - 1) + ((line - 1) * channels_per_line);
98 * This hack strips out Cisco's VSA duplicities in lines
99 * (Cisco not implemented VSA's in standard way.
101 * Cisco sends it's VSA attributes with the attribute name *again*
102 * in the string, like: H323-Attribute = "h323-attribute=value".
103 * This sort of behaviour is nonsense.
105 static void cisco_vsa_hack(REQUEST *request)
109 char newattr[MAX_STRING_LEN];
112 for (vp = fr_cursor_init(&cursor, &request->packet->vps);
114 vp = fr_cursor_next(&cursor)) {
115 vendorcode = vp->da->vendor;
116 if (!((vendorcode == 9) || (vendorcode == 6618))) {
117 continue; /* not a Cisco or Quintum VSA, continue */
120 if (vp->da->type != PW_TYPE_STRING) {
125 * No weird packing. Ignore it.
127 ptr = strchr(vp->vp_strvalue, '='); /* find an '=' */
133 * Cisco-AVPair's get packed as:
135 * Cisco-AVPair = "h323-foo-bar = baz"
136 * Cisco-AVPair = "h323-foo-bar=baz"
138 * which makes sense only if you're a lunatic.
139 * This code looks for the attribute named inside
140 * of the string, and if it exists, adds it as a new
143 if (vp->da->attr == 1) {
147 gettoken(&p, newattr, sizeof(newattr), false);
149 if (dict_attrbyname(newattr) != NULL) {
150 pair_make_request(newattr, ptr + 1, T_OP_EQ);
152 } else { /* h322-foo-bar = "h323-foo-bar = baz" */
154 * We strip out the duplicity from the
155 * value field, we use only the value on
156 * the right side of the '=' character.
158 fr_pair_value_strcpy(vp, ptr + 1);
165 * Don't even ask what this is doing...
167 static void alvarion_vsa_hack(VALUE_PAIR *vp)
172 for (vp = fr_cursor_init(&cursor, &vp);
174 vp = fr_cursor_next(&cursor)) {
177 if (vp->da->vendor != 12394) {
181 if (vp->da->type != PW_TYPE_STRING) {
185 da = dict_attrbyvalue(number, 12394);
197 * Cablelabs magic, taken from:
199 * http://www.cablelabs.com/packetcable/downloads/specs/PKT-SP-EM-I12-05812.pdf
203 * 0x0001d2d2026d30310000000000003030
204 * 3130303030000e812333000100033031
205 * 00000000000030303130303030000000
206 * 00063230313230313331303630323231
207 * 2e3633390000000081000500
210 typedef struct cl_timezone_t {
218 typedef struct cl_bcid_t {
220 uint8_t element_id[8];
221 cl_timezone_t timezone;
222 uint32_t event_counter;
225 typedef struct cl_em_hdr_t {
228 uint16_t message_type;
229 uint16_t element_type;
230 uint8_t element_id[8];
231 cl_timezone_t time_zone;
232 uint32_t sequence_number;
233 uint8_t event_time[18];
236 uint16_t attr_count; /* of normal Cablelabs VSAs */
237 uint8_t event_object;
241 static void cablelabs_vsa_hack(VALUE_PAIR **list)
245 ev = fr_pair_find_by_num(*list, 1, 4491, TAG_ANY); /* Cablelabs-Event-Message */
251 * FIXME: write 100's of lines of code to decode
252 * each data structure above.
257 * Mangle username if needed, IN PLACE.
259 static void rad_mangle(rlm_preprocess_t *inst, REQUEST *request)
262 VALUE_PAIR *namepair;
263 VALUE_PAIR *request_pairs;
268 * Get the username from the request
269 * If it isn't there, then we can't mangle the request.
271 request_pairs = request->packet->vps;
272 namepair = fr_pair_find_by_num(request_pairs, PW_USER_NAME, 0, TAG_ANY);
273 if (!namepair || (namepair->vp_length == 0)) {
277 if (inst->with_ntdomain_hack) {
279 char newname[MAX_STRING_LEN];
282 * Windows NT machines often authenticate themselves as
283 * NT_DOMAIN\username. Try to be smart about this.
285 * FIXME: should we handle this as a REALM ?
287 if ((ptr = strchr(namepair->vp_strvalue, '\\')) != NULL) {
288 strlcpy(newname, ptr + 1, sizeof(newname));
290 fr_pair_value_strcpy(namepair, newname);
294 if (inst->with_specialix_jetstream_hack) {
296 * Specialix Jetstream 8500 24 port access server.
297 * If the user name is 10 characters or longer, a "/"
298 * and the excess characters after the 10th are
299 * appended to the user name.
301 * Reported by Lucas Heise <root@laonet.net>
303 if ((strlen(namepair->vp_strvalue) > 10) &&
304 (namepair->vp_strvalue[10] == '/')) {
305 fr_pair_value_strcpy(namepair, namepair->vp_strvalue + 11);
310 * Small check: if Framed-Protocol present but Service-Type
311 * is missing, add Service-Type = Framed-User.
313 if (fr_pair_find_by_num(request_pairs, PW_FRAMED_PROTOCOL, 0, TAG_ANY) != NULL &&
314 fr_pair_find_by_num(request_pairs, PW_SERVICE_TYPE, 0, TAG_ANY) == NULL) {
315 tmp = radius_pair_create(request->packet, &request->packet->vps, PW_SERVICE_TYPE, 0);
316 tmp->vp_integer = PW_FRAMED_USER;
320 for (tmp = fr_cursor_init(&cursor, &request->packet->vps);
322 tmp = fr_cursor_next(&cursor)) {
323 if (tmp->da->vendor != 0) {
327 if (tmp->da->attr != PW_PROXY_STATE) {
334 if (num_proxy_state > 10) {
335 RWDEBUG("There are more than 10 Proxy-State attributes in the request");
336 RWDEBUG("You have likely configured an infinite proxy loop");
341 * Compare the request with the "reply" part in the
342 * huntgroup, which normally only contains username or group.
343 * At least one of the "reply" items has to match.
345 static int hunt_paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check)
348 VALUE_PAIR *check_item;
352 if (!check) return 0;
354 for (check_item = fr_cursor_init(&cursor, &check);
355 check_item && (result != 0);
356 check_item = fr_cursor_next(&cursor)) {
357 /* FIXME: fr_pair_list_copy should be removed once VALUE_PAIRs are no longer in linked lists */
358 tmp = fr_pair_copy(request, check_item);
359 tmp->op = check_item->op;
360 result = paircompare(req, request, check_item, NULL);
361 fr_pair_list_free(&tmp);
369 * Add hints to the info sent by the terminal server
370 * based on the pattern of the username, and other attributes.
372 static int hints_setup(PAIR_LIST *hints, REQUEST *request)
378 VALUE_PAIR *request_pairs;
381 request_pairs = request->packet->vps;
383 if (!hints || !request_pairs)
384 return RLM_MODULE_NOOP;
387 * Check for valid input, zero length names not permitted
389 name = (tmp = fr_pair_find_by_num(request_pairs, PW_USER_NAME, 0, TAG_ANY)) ?
390 tmp->vp_strvalue : NULL;
391 if (!name || name[0] == 0) {
393 * No name, nothing to do.
395 return RLM_MODULE_NOOP;
398 for (i = hints; i; i = i->next) {
400 * Use "paircompare", which is a little more general...
402 if (((strcmp(i->name, "DEFAULT") == 0) || (strcmp(i->name, name) == 0)) &&
403 (paircompare(request, request_pairs, i->check, NULL) == 0)) {
404 RDEBUG2("hints: Matched %s at %d", i->name, i->lineno);
406 * Now add all attributes to the request list,
407 * except PW_STRIP_USER_NAME and PW_FALL_THROUGH
410 add = fr_pair_list_copy(request->packet, i->reply);
411 ft = fall_through(add);
413 fr_pair_delete_by_num(&add, PW_STRIP_USER_NAME, 0, TAG_ANY);
414 fr_pair_delete_by_num(&add, PW_FALL_THROUGH, 0, TAG_ANY);
415 radius_pairmove(request, &request->packet->vps, add, true);
425 return RLM_MODULE_NOOP;
428 return RLM_MODULE_UPDATED;
432 * See if we have access to the huntgroup.
434 static int huntgroup_access(REQUEST *request, PAIR_LIST *huntgroups)
437 int r = RLM_MODULE_OK;
438 VALUE_PAIR *request_pairs = request->packet->vps;
441 * We're not controlling access by huntgroups:
445 return RLM_MODULE_OK;
448 for (i = huntgroups; i; i = i->next) {
450 * See if this entry matches.
452 if (paircompare(request, request_pairs, i->check, NULL) != 0) {
457 * Now check for access.
459 r = RLM_MODULE_REJECT;
460 if (hunt_paircmp(request, request_pairs, i->reply) == 0) {
464 * We've matched the huntgroup, so add it in
465 * to the list of request pairs.
467 vp = fr_pair_find_by_num(request_pairs, PW_HUNTGROUP_NAME, 0, TAG_ANY);
469 vp = radius_pair_create(request->packet, &request->packet->vps, PW_HUNTGROUP_NAME, 0);
470 fr_pair_value_strcpy(vp, i->name);
481 * If the NAS wasn't smart enought to add a NAS-IP-Address
482 * to the request, then add it ourselves.
484 static int add_nas_attr(REQUEST *request)
488 switch (request->packet->src_ipaddr.af) {
490 nas = fr_pair_find_by_num(request->packet->vps, PW_NAS_IP_ADDRESS, 0, TAG_ANY);
492 nas = radius_pair_create(request->packet, &request->packet->vps, PW_NAS_IP_ADDRESS, 0);
493 nas->vp_ipaddr = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr;
498 nas = fr_pair_find_by_num(request->packet->vps, PW_NAS_IPV6_ADDRESS, 0, TAG_ANY);
500 nas = radius_pair_create(request->packet, &request->packet->vps, PW_NAS_IPV6_ADDRESS, 0);
501 memcpy(&nas->vp_ipv6addr, &request->packet->src_ipaddr.ipaddr,
502 sizeof(request->packet->src_ipaddr.ipaddr));
507 ERROR("Unknown address family for packet");
518 static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
521 rlm_preprocess_t *inst = instance;
524 * Read the huntgroups file.
526 if (inst->huntgroup_file) {
527 ret = pairlist_read(inst, inst->huntgroup_file, &(inst->huntgroups), 0);
529 ERROR("rlm_preprocess: Error reading %s", inst->huntgroup_file);
536 * Read the hints file.
538 if (inst->hints_file) {
539 ret = pairlist_read(inst, inst->hints_file, &(inst->hints), 0);
541 ERROR("rlm_preprocess: Error reading %s", inst->hints_file);
551 * Preprocess a request.
553 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
556 rlm_preprocess_t *inst = instance;
561 * Mangle the username, to get rid of stupid implementation
564 rad_mangle(inst, request);
566 if (inst->with_ascend_hack) {
568 * If we're using Ascend systems, hack the NAS-Port-Id
569 * in place, to go from Ascend's weird values to something
570 * approaching rationality.
572 ascend_nasport_hack(fr_pair_find_by_num(request->packet->vps, PW_NAS_PORT, 0, TAG_ANY),
573 inst->ascend_channels_per_line);
576 if (inst->with_cisco_vsa_hack) {
578 * We need to run this hack because the h323-conf-id
579 * attribute should be used.
581 cisco_vsa_hack(request);
584 if (inst->with_alvarion_vsa_hack) {
586 * We need to run this hack because the Alvarion
589 alvarion_vsa_hack(request->packet->vps);
592 if (inst->with_cablelabs_vsa_hack) {
594 * We need to run this hack because the Cablelabs
597 cablelabs_vsa_hack(&request->packet->vps);
601 * Add an event timestamp. Means Event-Timestamp can be used
602 * consistently instead of one letter expansions.
604 vp = fr_pair_find_by_num(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY);
606 vp = radius_pair_create(request->packet, &request->packet->vps, PW_EVENT_TIMESTAMP, 0);
607 vp->vp_date = request->packet->timestamp.tv_sec;
611 * Note that we add the Request-Src-IP-Address to the request
612 * structure BEFORE checking huntgroup access. This allows
613 * the Request-Src-IP-Address to be used for huntgroup
616 if (add_nas_attr(request) < 0) {
617 return RLM_MODULE_FAIL;
620 hints_setup(inst->hints, request);
623 * If there is a PW_CHAP_PASSWORD attribute but there
624 * is PW_CHAP_CHALLENGE we need to add it so that other
625 * modules can use it as a normal attribute.
627 if (fr_pair_find_by_num(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY) &&
628 fr_pair_find_by_num(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY) == NULL) {
629 vp = radius_pair_create(request->packet, &request->packet->vps, PW_CHAP_CHALLENGE, 0);
630 fr_pair_value_memcpy(vp, request->packet->vector, AUTH_VECTOR_LEN);
633 if ((r = huntgroup_access(request, inst->huntgroups)) != RLM_MODULE_OK) {
635 RIDEBUG("No huntgroup access: [%s] (%s)",
636 request->username ? request->username->vp_strvalue : "<NO User-Name>",
637 auth_name(buf, sizeof(buf), request, 1));
642 return RLM_MODULE_OK; /* Meaning: try next authorization module */
646 * Preprocess a request before accounting
648 static rlm_rcode_t CC_HINT(nonnull) mod_preaccounting(void *instance, REQUEST *request)
652 rlm_preprocess_t *inst = instance;
655 * Ensure that we have the SAME user name for both
656 * authentication && accounting.
658 rad_mangle(inst, request);
660 if (inst->with_cisco_vsa_hack) {
662 * We need to run this hack because the h323-conf-id
663 * attribute should be used.
665 cisco_vsa_hack(request);
668 if (inst->with_alvarion_vsa_hack) {
670 * We need to run this hack because the Alvarion
673 alvarion_vsa_hack(request->packet->vps);
676 if (inst->with_cablelabs_vsa_hack) {
678 * We need to run this hack because the Cablelabs
681 cablelabs_vsa_hack(&request->packet->vps);
685 * Ensure that we log the NAS IP Address in the packet.
687 if (add_nas_attr(request) < 0) {
688 return RLM_MODULE_FAIL;
691 hints_setup(inst->hints, request);
694 * Add an event timestamp. This means that the rest of
695 * the server can use it, rather than various error-prone
696 * manual calculations.
698 vp = fr_pair_find_by_num(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY);
702 vp = radius_pair_create(request->packet, &request->packet->vps, PW_EVENT_TIMESTAMP, 0);
703 vp->vp_date = request->packet->timestamp.tv_sec;
705 delay = fr_pair_find_by_num(request->packet->vps, PW_ACCT_DELAY_TIME, 0, TAG_ANY);
707 if ((delay->vp_integer >= vp->vp_date) || (delay->vp_integer == UINT32_MAX)) {
708 RWARN("Ignoring invalid Acct-Delay-time of %u seconds", delay->vp_integer);
710 vp->vp_date -= delay->vp_integer;
715 if ((r = huntgroup_access(request, inst->huntgroups)) != RLM_MODULE_OK) {
717 RIDEBUG("No huntgroup access: [%s] (%s)",
718 request->username ? request->username->vp_strvalue : "<NO User-Name>",
719 auth_name(buf, sizeof(buf), request, 1));
726 /* globally exported name */
727 extern module_t rlm_preprocess;
728 module_t rlm_preprocess = {
729 .magic = RLM_MODULE_INIT,
730 .name = "preprocess",
731 .inst_size = sizeof(rlm_preprocess_t),
732 .config = module_config,
733 .instantiate = mod_instantiate,
735 [MOD_AUTHORIZE] = mod_authorize,
736 [MOD_PREACCT] = mod_preaccounting