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, version 2 if the
4 * License as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
18 * @file rlm_preprocess.c
19 * @brief Fixes up requests, and processes huntgroups/hints files.
21 * @copyright 2000,2006 The FreeRADIUS server project
22 * @copyright 2000 Alan DeKok <aland@ox.org>
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <freeradius-devel/rad_assert.h>
32 typedef struct rlm_preprocess_t {
33 char const *huntgroup_file;
34 char const *hints_file;
35 PAIR_LIST *huntgroups;
37 bool with_ascend_hack;
38 uint32_t ascend_channels_per_line;
39 bool with_ntdomain_hack;
40 bool with_specialix_jetstream_hack;
41 bool with_cisco_vsa_hack;
42 bool with_alvarion_vsa_hack;
43 bool with_cablelabs_vsa_hack;
46 static const CONF_PARSER module_config[] = {
47 { "huntgroups", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_preprocess_t, huntgroup_file), NULL },
48 { "hints", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_preprocess_t, hints_file), NULL },
49 { "with_ascend_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_ascend_hack), "no" },
50 { "ascend_channels_per_line", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_preprocess_t, ascend_channels_per_line), "23" },
52 { "with_ntdomain_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_ntdomain_hack), "no" },
53 { "with_specialix_jetstream_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_specialix_jetstream_hack), "no" },
54 { "with_cisco_vsa_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_cisco_vsa_hack), "no" },
55 { "with_alvarion_vsa_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_alvarion_vsa_hack), "no" },
57 { "with_cablelabs_vsa_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_preprocess_t, with_cablelabs_vsa_hack), NULL },
59 { NULL, -1, 0, NULL, NULL }
63 * See if a VALUE_PAIR list contains Fall-Through = Yes
65 static int fallthrough(VALUE_PAIR *vp)
68 tmp = pairfind(vp, PW_FALL_THROUGH, 0, TAG_ANY);
70 return tmp ? tmp->vp_integer : 0;
74 * This hack changes Ascend's wierd port numberings
75 * to standard 0-??? port numbers so that the "+" works
76 * for IP address assignments.
78 static void ascend_nasport_hack(VALUE_PAIR *nas_port, int channels_per_line)
88 if (nas_port->vp_integer > 9999) {
89 service = nas_port->vp_integer/10000; /* 1=digital 2=analog */
90 line = (nas_port->vp_integer - (10000 * service)) / 100;
91 channel = nas_port->vp_integer - ((10000 * service) + (100 * line));
92 nas_port->vp_integer = (channel - 1) + ((line - 1) * channels_per_line);
97 * This hack strips out Cisco's VSA duplicities in lines
98 * (Cisco not implemented VSA's in standard way.
100 * Cisco sends it's VSA attributes with the attribute name *again*
101 * in the string, like: H323-Attribute = "h323-attribute=value".
102 * This sort of behaviour is nonsense.
104 static void cisco_vsa_hack(REQUEST *request)
108 char newattr[MAX_STRING_LEN];
111 for (vp = fr_cursor_init(&cursor, &request->packet->vps);
113 vp = fr_cursor_next(&cursor)) {
114 vendorcode = vp->da->vendor;
115 if (!((vendorcode == 9) || (vendorcode == 6618))) {
116 continue; /* not a Cisco or Quintum VSA, continue */
119 if (vp->da->type != PW_TYPE_STRING) {
124 * No weird packing. Ignore it.
126 ptr = strchr(vp->vp_strvalue, '='); /* find an '=' */
132 * Cisco-AVPair's get packed as:
134 * Cisco-AVPair = "h323-foo-bar = baz"
135 * Cisco-AVPair = "h323-foo-bar=baz"
137 * which makes sense only if you're a lunatic.
138 * This code looks for the attribute named inside
139 * of the string, and if it exists, adds it as a new
142 if (vp->da->attr == 1) {
146 gettoken(&p, newattr, sizeof(newattr), false);
148 if (dict_attrbyname(newattr) != NULL) {
149 pairmake_packet(newattr, ptr + 1, T_OP_EQ);
151 } else { /* h322-foo-bar = "h323-foo-bar = baz" */
153 * We strip out the duplicity from the
154 * value field, we use only the value on
155 * the right side of the '=' character.
157 pairstrcpy(vp, ptr + 1);
164 * Don't even ask what this is doing...
166 static void alvarion_vsa_hack(VALUE_PAIR *vp)
171 for (vp = fr_cursor_init(&cursor, &vp);
173 vp = fr_cursor_next(&cursor)) {
176 if (vp->da->vendor != 12394) {
180 if (vp->da->type != PW_TYPE_STRING) {
184 da = dict_attrbyvalue(number, 12394);
196 * Cablelabs magic, taken from:
198 * http://www.cablelabs.com/packetcable/downloads/specs/PKT-SP-EM-I12-05812.pdf
202 * 0x0001d2d2026d30310000000000003030
203 * 3130303030000e812333000100033031
204 * 00000000000030303130303030000000
205 * 00063230313230313331303630323231
206 * 2e3633390000000081000500
209 typedef struct cl_timezone_t {
217 typedef struct cl_bcid_t {
219 uint8_t element_id[8];
220 cl_timezone_t timezone;
221 uint32_t event_counter;
224 typedef struct cl_em_hdr_t {
227 uint16_t message_type;
228 uint16_t element_type;
229 uint8_t element_id[8];
230 cl_timezone_t time_zone;
231 uint32_t sequence_number;
232 uint8_t event_time[18];
235 uint16_t attr_count; /* of normal Cablelabs VSAs */
236 uint8_t event_object;
240 static void cablelabs_vsa_hack(VALUE_PAIR **list)
244 ev = pairfind(*list, 1, 4491, TAG_ANY); /* Cablelabs-Event-Message */
250 * FIXME: write 100's of lines of code to decode
251 * each data structure above.
256 * Mangle username if needed, IN PLACE.
258 static void rad_mangle(rlm_preprocess_t *inst, REQUEST *request)
261 VALUE_PAIR *namepair;
262 VALUE_PAIR *request_pairs;
267 * Get the username from the request
268 * If it isn't there, then we can't mangle the request.
270 request_pairs = request->packet->vps;
271 namepair = pairfind(request_pairs, PW_USER_NAME, 0, TAG_ANY);
272 if (!namepair || (namepair->length == 0)) {
276 if (inst->with_ntdomain_hack) {
278 char newname[MAX_STRING_LEN];
281 * Windows NT machines often authenticate themselves as
282 * NT_DOMAIN\username. Try to be smart about this.
284 * FIXME: should we handle this as a REALM ?
286 if ((ptr = strchr(namepair->vp_strvalue, '\\')) != NULL) {
287 strlcpy(newname, ptr + 1, sizeof(newname));
289 pairstrcpy(namepair, newname);
293 if (inst->with_specialix_jetstream_hack) {
295 * Specialix Jetstream 8500 24 port access server.
296 * If the user name is 10 characters or longer, a "/"
297 * and the excess characters after the 10th are
298 * appended to the user name.
300 * Reported by Lucas Heise <root@laonet.net>
302 if ((strlen(namepair->vp_strvalue) > 10) &&
303 (namepair->vp_strvalue[10] == '/')) {
304 pairstrcpy(namepair, namepair->vp_strvalue + 11);
309 * Small check: if Framed-Protocol present but Service-Type
310 * is missing, add Service-Type = Framed-User.
312 if (pairfind(request_pairs, PW_FRAMED_PROTOCOL, 0, TAG_ANY) != NULL &&
313 pairfind(request_pairs, PW_SERVICE_TYPE, 0, TAG_ANY) == NULL) {
314 tmp = radius_paircreate(request->packet, &request->packet->vps, PW_SERVICE_TYPE, 0);
315 tmp->vp_integer = PW_FRAMED_USER;
319 for (tmp = fr_cursor_init(&cursor, &request->packet->vps);
321 tmp = fr_cursor_next(&cursor)) {
322 if (tmp->da->vendor != 0) {
326 if (tmp->da->attr != PW_PROXY_STATE) {
333 if (num_proxy_state > 10) {
334 RWDEBUG("There are more than 10 Proxy-State attributes in the request");
335 RWDEBUG("You have likely configured an infinite proxy loop");
340 * Compare the request with the "reply" part in the
341 * huntgroup, which normally only contains username or group.
342 * At least one of the "reply" items has to match.
344 static int hunt_paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check)
347 VALUE_PAIR *check_item;
351 if (!check) return 0;
353 for (check_item = fr_cursor_init(&cursor, &check);
354 check_item && (result != 0);
355 check_item = fr_cursor_next(&cursor)) {
356 /* FIXME: paircopy should be removed once VALUE_PAIRs are no longer in linked lists */
357 tmp = paircopyvp(request, check_item);
358 tmp->op = check_item->op;
359 result = paircompare(req, request, check_item, NULL);
368 * Add hints to the info sent by the terminal server
369 * based on the pattern of the username, and other attributes.
371 static int hints_setup(PAIR_LIST *hints, REQUEST *request)
377 VALUE_PAIR *request_pairs;
380 request_pairs = request->packet->vps;
382 if (!hints || !request_pairs)
383 return RLM_MODULE_NOOP;
386 * Check for valid input, zero length names not permitted
388 name = (tmp = pairfind(request_pairs, PW_USER_NAME, 0, TAG_ANY)) ?
389 tmp->vp_strvalue : NULL;
390 if (!name || name[0] == 0) {
392 * No name, nothing to do.
394 return RLM_MODULE_NOOP;
397 for (i = hints; i; i = i->next) {
399 * Use "paircompare", which is a little more general...
401 if (((strcmp(i->name, "DEFAULT") == 0) || (strcmp(i->name, name) == 0)) &&
402 (paircompare(request, request_pairs, i->check, NULL) == 0)) {
403 RDEBUG2("hints: Matched %s at %d", i->name, i->lineno);
405 * Now add all attributes to the request list,
406 * except PW_STRIP_USER_NAME and PW_FALL_THROUGH
409 add = paircopy(request->packet, i->reply);
410 ft = fallthrough(add);
412 pairdelete(&add, PW_STRIP_USER_NAME, 0, TAG_ANY);
413 pairdelete(&add, PW_FALL_THROUGH, 0, TAG_ANY);
414 radius_pairmove(request, &request->packet->vps, add, true);
424 return RLM_MODULE_NOOP;
427 return RLM_MODULE_UPDATED;
431 * See if we have access to the huntgroup.
433 static int huntgroup_access(REQUEST *request, PAIR_LIST *huntgroups)
436 int r = RLM_MODULE_OK;
437 VALUE_PAIR *request_pairs = request->packet->vps;
440 * We're not controlling access by huntgroups:
444 return RLM_MODULE_OK;
447 for (i = huntgroups; i; i = i->next) {
449 * See if this entry matches.
451 if (paircompare(request, request_pairs, i->check, NULL) != 0) {
456 * Now check for access.
458 r = RLM_MODULE_REJECT;
459 if (hunt_paircmp(request, request_pairs, i->reply) == 0) {
463 * We've matched the huntgroup, so add it in
464 * to the list of request pairs.
466 vp = pairfind(request_pairs, PW_HUNTGROUP_NAME, 0, TAG_ANY);
468 vp = radius_paircreate(request->packet, &request->packet->vps, PW_HUNTGROUP_NAME, 0);
469 pairstrcpy(vp, i->name);
480 * If the NAS wasn't smart enought to add a NAS-IP-Address
481 * to the request, then add it ourselves.
483 static int add_nas_attr(REQUEST *request)
487 switch (request->packet->src_ipaddr.af) {
489 nas = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS, 0, TAG_ANY);
491 nas = radius_paircreate(request->packet, &request->packet->vps, PW_NAS_IP_ADDRESS, 0);
492 nas->vp_ipaddr = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr;
497 nas = pairfind(request->packet->vps, PW_NAS_IPV6_ADDRESS, 0, TAG_ANY);
499 nas = radius_paircreate(request->packet, &request->packet->vps, PW_NAS_IPV6_ADDRESS, 0);
500 memcpy(&nas->vp_ipv6addr, &request->packet->src_ipaddr.ipaddr,
501 sizeof(request->packet->src_ipaddr.ipaddr));
506 ERROR("Unknown address family for packet");
517 static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
520 rlm_preprocess_t *inst = instance;
523 * Read the huntgroups file.
525 if (inst->huntgroup_file) {
526 ret = pairlist_read(inst, inst->huntgroup_file, &(inst->huntgroups), 0);
528 ERROR("rlm_preprocess: Error reading %s", inst->huntgroup_file);
535 * Read the hints file.
537 if (inst->hints_file) {
538 ret = pairlist_read(inst, inst->hints_file, &(inst->hints), 0);
540 ERROR("rlm_preprocess: Error reading %s", inst->hints_file);
550 * Preprocess a request.
552 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
555 rlm_preprocess_t *inst = instance;
558 * Mangle the username, to get rid of stupid implementation
561 rad_mangle(inst, request);
563 if (inst->with_ascend_hack) {
565 * If we're using Ascend systems, hack the NAS-Port-Id
566 * in place, to go from Ascend's weird values to something
567 * approaching rationality.
569 ascend_nasport_hack(pairfind(request->packet->vps, PW_NAS_PORT, 0, TAG_ANY),
570 inst->ascend_channels_per_line);
573 if (inst->with_cisco_vsa_hack) {
575 * We need to run this hack because the h323-conf-id
576 * attribute should be used.
578 cisco_vsa_hack(request);
581 if (inst->with_alvarion_vsa_hack) {
583 * We need to run this hack because the Alvarion
586 alvarion_vsa_hack(request->packet->vps);
589 if (inst->with_cablelabs_vsa_hack) {
591 * We need to run this hack because the Cablelabs
594 cablelabs_vsa_hack(&request->packet->vps);
598 * Note that we add the Request-Src-IP-Address to the request
599 * structure BEFORE checking huntgroup access. This allows
600 * the Request-Src-IP-Address to be used for huntgroup
603 if (add_nas_attr(request) < 0) {
604 return RLM_MODULE_FAIL;
607 hints_setup(inst->hints, request);
610 * If there is a PW_CHAP_PASSWORD attribute but there
611 * is PW_CHAP_CHALLENGE we need to add it so that other
612 * modules can use it as a normal attribute.
614 if (pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY) &&
615 pairfind(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY) == NULL) {
618 vp = radius_paircreate(request->packet, &request->packet->vps, PW_CHAP_CHALLENGE, 0);
619 pairmemcpy(vp, request->packet->vector, AUTH_VECTOR_LEN);
622 if ((r = huntgroup_access(request, inst->huntgroups)) != RLM_MODULE_OK) {
624 RIDEBUG("No huntgroup access: [%s] (%s)",
625 request->username ? request->username->vp_strvalue : "<NO User-Name>",
626 auth_name(buf, sizeof(buf), request, 1));
631 return RLM_MODULE_OK; /* Meaning: try next authorization module */
635 * Preprocess a request before accounting
637 static rlm_rcode_t CC_HINT(nonnull) mod_preaccounting(void *instance, REQUEST *request)
641 rlm_preprocess_t *inst = instance;
644 * Ensure that we have the SAME user name for both
645 * authentication && accounting.
647 rad_mangle(inst, request);
649 if (inst->with_cisco_vsa_hack) {
651 * We need to run this hack because the h323-conf-id
652 * attribute should be used.
654 cisco_vsa_hack(request);
657 if (inst->with_alvarion_vsa_hack) {
659 * We need to run this hack because the Alvarion
662 alvarion_vsa_hack(request->packet->vps);
665 if (inst->with_cablelabs_vsa_hack) {
667 * We need to run this hack because the Cablelabs
670 cablelabs_vsa_hack(&request->packet->vps);
674 * Ensure that we log the NAS IP Address in the packet.
676 if (add_nas_attr(request) < 0) {
677 return RLM_MODULE_FAIL;
680 hints_setup(inst->hints, request);
683 * Add an event timestamp. This means that the rest of
684 * the server can use it, rather than various error-prone
685 * manual calculations.
687 vp = pairfind(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY);
691 vp = radius_paircreate(request->packet, &request->packet->vps, PW_EVENT_TIMESTAMP, 0);
692 vp->vp_date = request->packet->timestamp.tv_sec;
694 delay = pairfind(request->packet->vps, PW_ACCT_DELAY_TIME, 0, TAG_ANY);
696 vp->vp_date -= delay->vp_integer;
700 if ((r = huntgroup_access(request, inst->huntgroups)) != RLM_MODULE_OK) {
702 RIDEBUG("No huntgroup access: [%s] (%s)",
703 request->username ? request->username->vp_strvalue : "<NO User-Name>",
704 auth_name(buf, sizeof(buf), request, 1));
711 /* globally exported name */
712 module_t rlm_preprocess = {
716 sizeof(rlm_preprocess_t),
718 mod_instantiate, /* instantiation */
721 NULL, /* authentication */
722 mod_authorize, /* authorization */
723 mod_preaccounting, /* pre-accounting */
724 NULL, /* accounting */
725 NULL, /* checksimul */
726 NULL, /* pre-proxy */
727 NULL, /* post-proxy */