3 * Contains the functions for the "huntgroups" and "hints"
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 * Copyright 2000,2006 The FreeRADIUS server project
23 * Copyright 2000 Alan DeKok <aland@ox.org>
26 #include <freeradius-devel/ident.h>
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/modules.h>
31 #include <freeradius-devel/rad_assert.h>
35 typedef struct rlm_preprocess_t {
38 PAIR_LIST *huntgroups;
41 int ascend_channels_per_line;
42 int with_ntdomain_hack;
43 int with_specialix_jetstream_hack;
44 int with_cisco_vsa_hack;
45 int with_alvarion_vsa_hack;
46 int with_cablelabs_vsa_hack;
49 static const CONF_PARSER module_config[] = {
50 { "huntgroups", PW_TYPE_FILENAME,
51 offsetof(rlm_preprocess_t,huntgroup_file), NULL,
52 "${raddbdir}/huntgroups" },
53 { "hints", PW_TYPE_FILENAME,
54 offsetof(rlm_preprocess_t,hints_file), NULL,
55 "${raddbdir}/hints" },
56 { "with_ascend_hack", PW_TYPE_BOOLEAN,
57 offsetof(rlm_preprocess_t,with_ascend_hack), NULL, "no" },
58 { "ascend_channels_per_line", PW_TYPE_INTEGER,
59 offsetof(rlm_preprocess_t,ascend_channels_per_line), NULL, "23" },
61 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
62 offsetof(rlm_preprocess_t,with_ntdomain_hack), NULL, "no" },
63 { "with_specialix_jetstream_hack", PW_TYPE_BOOLEAN,
64 offsetof(rlm_preprocess_t,with_specialix_jetstream_hack), NULL,
66 { "with_cisco_vsa_hack", PW_TYPE_BOOLEAN,
67 offsetof(rlm_preprocess_t,with_cisco_vsa_hack), NULL, "no" },
68 { "with_alvarion_vsa_hack", PW_TYPE_BOOLEAN,
69 offsetof(rlm_preprocess_t,with_alvarion_vsa_hack), NULL, "no" },
70 { "with_cablelabs_vsa_hack", PW_TYPE_BOOLEAN,
71 offsetof(rlm_preprocess_t,with_cablelabs_vsa_hack), NULL, NULL },
73 { NULL, -1, 0, NULL, NULL }
77 * See if a VALUE_PAIR list contains Fall-Through = Yes
79 static int fallthrough(VALUE_PAIR *vp)
82 tmp = pairfind(vp, PW_FALL_THROUGH, 0);
84 return tmp ? tmp->vp_integer : 0;
89 * This hack changes Ascend's wierd port numberings
90 * to standard 0-??? port numbers so that the "+" works
91 * for IP address assignments.
93 static void ascend_nasport_hack(VALUE_PAIR *nas_port, int channels_per_line)
103 if (nas_port->vp_integer > 9999) {
104 service = nas_port->vp_integer/10000; /* 1=digital 2=analog */
105 line = (nas_port->vp_integer - (10000 * service)) / 100;
106 channel = nas_port->vp_integer-((10000 * service)+(100 * line));
107 nas_port->vp_integer =
108 (channel - 1) + (line - 1) * channels_per_line;
114 * This hack strips out Cisco's VSA duplicities in lines
115 * (Cisco not implemented VSA's in standard way.
117 * Cisco sends it's VSA attributes with the attribute name *again*
118 * in the string, like: H323-Attribute = "h323-attribute=value".
119 * This sort of behaviour is nonsense.
121 static void cisco_vsa_hack(VALUE_PAIR *vp)
125 char newattr[MAX_STRING_LEN];
127 for ( ; vp != NULL; vp = vp->next) {
128 vendorcode = vp->vendor;
129 if (!((vendorcode == 9) || (vendorcode == 6618))) continue; /* not a Cisco or Quintum VSA, continue */
131 if (vp->type != PW_TYPE_STRING) continue;
134 * No weird packing. Ignore it.
136 ptr = strchr(vp->vp_strvalue, '='); /* find an '=' */
140 * Cisco-AVPair's get packed as:
142 * Cisco-AVPair = "h323-foo-bar = baz"
143 * Cisco-AVPair = "h323-foo-bar=baz"
145 * which makes sense only if you're a lunatic.
146 * This code looks for the attribute named inside
147 * of the string, and if it exists, adds it as a new
150 if (vp->attribute == 1) {
155 gettoken(&p, newattr, sizeof(newattr));
157 if ((dattr = dict_attrbyname(newattr)) != NULL) {
161 * Make a new attribute.
163 newvp = pairmake(newattr, ptr + 1, T_OP_EQ);
168 } else { /* h322-foo-bar = "h323-foo-bar = baz" */
170 * We strip out the duplicity from the
171 * value field, we use only the value on
172 * the right side of the '=' character.
174 strlcpy(newattr, ptr + 1, sizeof(newattr));
175 strlcpy((char *)vp->vp_strvalue, newattr,
176 sizeof(vp->vp_strvalue));
177 vp->length = strlen((char *)vp->vp_strvalue);
184 * Don't even ask what this is doing...
186 static void alvarion_vsa_hack(VALUE_PAIR *vp)
190 for ( ; vp != NULL; vp = vp->next) {
193 if (vp->vendor != 12394) continue;
194 if (vp->type != PW_TYPE_STRING) continue;
196 da = dict_attrbyvalue(number, 12394);
199 vp->attribute = da->attr;
207 * Cablelabs magic, taken from:
209 * http://www.cablelabs.com/packetcable/downloads/specs/PKT-SP-EM-I12-05812.pdf
213 * 0x0001d2d2026d30310000000000003030
214 * 3130303030000e812333000100033031
215 * 00000000000030303130303030000000
216 * 00063230313230313331303630323231
217 * 2e3633390000000081000500
220 typedef struct cl_timezone_t {
228 typedef struct cl_bcid_t {
230 uint8_t element_id[8];
231 cl_timezone_t timezone;
232 uint32_t event_counter;
235 typedef struct cl_em_hdr_t {
238 uint16_t message_type;
239 uint16_t element_type;
240 uint8_t element_id[8];
241 cl_timezone_t time_zone;
242 uint32_t sequence_number;
243 uint8_t event_time[18];
246 uint16_t attr_count; /* of normal Cablelabs VSAs */
247 uint8_t event_object;
251 static void cablelabs_vsa_hack(VALUE_PAIR **list)
255 ev = pairfind(&list, 1, 4491); /* Cablelabs-Event-Message */
259 * FIXME: write 100's of lines of code to decode
260 * each data structure above.
267 * Mangle username if needed, IN PLACE.
269 static void rad_mangle(rlm_preprocess_t *data, REQUEST *request)
272 VALUE_PAIR *namepair;
273 VALUE_PAIR *request_pairs;
277 * Get the username from the request
278 * If it isn't there, then we can't mangle the request.
280 request_pairs = request->packet->vps;
281 namepair = pairfind(request_pairs, PW_USER_NAME, 0);
282 if ((namepair == NULL) ||
283 (namepair->length <= 0)) {
287 if (data->with_ntdomain_hack) {
289 char newname[MAX_STRING_LEN];
292 * Windows NT machines often authenticate themselves as
293 * NT_DOMAIN\username. Try to be smart about this.
295 * FIXME: should we handle this as a REALM ?
297 if ((ptr = strchr(namepair->vp_strvalue, '\\')) != NULL) {
298 strlcpy(newname, ptr + 1, sizeof(newname));
300 strcpy(namepair->vp_strvalue, newname);
301 namepair->length = strlen(newname);
305 if (data->with_specialix_jetstream_hack) {
309 * Specialix Jetstream 8500 24 port access server.
310 * If the user name is 10 characters or longer, a "/"
311 * and the excess characters after the 10th are
312 * appended to the user name.
314 * Reported by Lucas Heise <root@laonet.net>
316 if ((strlen((char *)namepair->vp_strvalue) > 10) &&
317 (namepair->vp_strvalue[10] == '/')) {
318 for (ptr = (char *)namepair->vp_strvalue + 11; *ptr; ptr++)
321 namepair->length = strlen((char *)namepair->vp_strvalue);
326 * Small check: if Framed-Protocol present but Service-Type
327 * is missing, add Service-Type = Framed-User.
329 if (pairfind(request_pairs, PW_FRAMED_PROTOCOL, 0) != NULL &&
330 pairfind(request_pairs, PW_SERVICE_TYPE, 0) == NULL) {
331 tmp = radius_paircreate(request, &request->packet->vps,
332 PW_SERVICE_TYPE, 0, PW_TYPE_INTEGER);
333 tmp->vp_integer = PW_FRAMED_USER;
337 for (tmp = request->packet->vps; tmp != NULL; tmp = tmp->next) {
338 if (tmp->vendor != 0) continue;
339 if (tmp->attribute != PW_PROXY_STATE) continue;
344 if (num_proxy_state > 10) {
345 DEBUG("WARNING: There are more than 10 Proxy-State attributes in the request.");
346 DEBUG("WARNING: You have likely configured an infinite proxy loop.");
351 * Compare the request with the "reply" part in the
352 * huntgroup, which normally only contains username or group.
353 * At least one of the "reply" items has to match.
355 static int hunt_paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check)
357 VALUE_PAIR *check_item = check;
361 if (check == NULL) return 0;
363 while (result != 0 && check_item != NULL) {
365 tmp = check_item->next;
366 check_item->next = NULL;
368 result = paircompare(req, request, check_item, NULL);
370 check_item->next = tmp;
371 check_item = check_item->next;
379 * Add hints to the info sent by the terminal server
380 * based on the pattern of the username, and other attributes.
382 static int hints_setup(PAIR_LIST *hints, REQUEST *request)
388 VALUE_PAIR *request_pairs;
392 request_pairs = request->packet->vps;
394 if (hints == NULL || request_pairs == NULL)
395 return RLM_MODULE_NOOP;
398 * Check for valid input, zero length names not permitted
400 if ((tmp = pairfind(request_pairs, PW_USER_NAME, 0)) == NULL)
403 name = (char *)tmp->vp_strvalue;
405 if (name == NULL || name[0] == 0)
407 * No name, nothing to do.
409 return RLM_MODULE_NOOP;
411 for (i = hints; i; i = i->next) {
413 * Use "paircompare", which is a little more general...
415 if (((strcmp(i->name, "DEFAULT") == 0) ||
416 (strcmp(i->name, name) == 0)) &&
417 (paircompare(request, request_pairs, i->check, NULL) == 0)) {
418 RDEBUG2(" hints: Matched %s at %d",
421 * Now add all attributes to the request list,
422 * except PW_STRIP_USER_NAME and PW_FALL_THROUGH
425 add = paircopy(i->reply);
426 ft = fallthrough(add);
427 pairdelete(&add, PW_STRIP_USER_NAME, 0);
428 pairdelete(&add, PW_FALL_THROUGH, 0);
429 pairxlatmove(request, &request->packet->vps, &add);
436 if (updated == 0) return RLM_MODULE_NOOP;
438 return RLM_MODULE_UPDATED;
442 * See if we have access to the huntgroup.
444 static int huntgroup_access(REQUEST *request, PAIR_LIST *huntgroups)
447 int r = RLM_MODULE_OK;
448 VALUE_PAIR *request_pairs = request->packet->vps;
451 * We're not controlling access by huntgroups:
454 if (huntgroups == NULL)
455 return RLM_MODULE_OK;
457 for(i = huntgroups; i; i = i->next) {
459 * See if this entry matches.
461 if (paircompare(request, request_pairs, i->check, NULL) != 0)
465 * Now check for access.
467 r = RLM_MODULE_REJECT;
468 if (hunt_paircmp(request, request_pairs, i->reply) == 0) {
472 * We've matched the huntgroup, so add it in
473 * to the list of request pairs.
475 vp = pairfind(request_pairs, PW_HUNTGROUP_NAME, 0);
477 vp = radius_paircreate(request,
478 &request->packet->vps,
479 PW_HUNTGROUP_NAME, 0,
481 strlcpy(vp->vp_strvalue, i->name,
482 sizeof(vp->vp_strvalue));
483 vp->length = strlen(vp->vp_strvalue);
494 * If the NAS wasn't smart enought to add a NAS-IP-Address
495 * to the request, then add it ourselves.
497 static int add_nas_attr(REQUEST *request)
501 switch (request->packet->src_ipaddr.af) {
503 nas = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS, 0);
505 nas = radius_paircreate(request, &request->packet->vps,
506 PW_NAS_IP_ADDRESS, 0,
508 nas->vp_ipaddr = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr;
513 nas = pairfind(request->packet->vps, PW_NAS_IPV6_ADDRESS, 0);
515 nas = radius_paircreate(request, &request->packet->vps,
516 PW_NAS_IPV6_ADDRESS, 0,
518 memcpy(nas->vp_strvalue,
519 &request->packet->src_ipaddr.ipaddr,
520 sizeof(request->packet->src_ipaddr.ipaddr));
525 radlog(L_ERR, "Unknown address family for packet");
536 static int preprocess_instantiate(CONF_SECTION *conf, void **instance)
539 rlm_preprocess_t *data;
542 * Allocate room to put the module's instantiation data.
544 data = (rlm_preprocess_t *) rad_malloc(sizeof(*data));
545 memset(data, 0, sizeof(*data));
548 * Read this modules configuration data.
550 if (cf_section_parse(conf, data, module_config) < 0) {
555 data->huntgroups = NULL;
559 * Read the huntgroups file.
561 if (data->huntgroup_file) {
562 rcode = pairlist_read(data->huntgroup_file,
563 &(data->huntgroups), 0);
565 radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
566 data->huntgroup_file);
572 * Read the hints file.
574 if (data->hints_file) {
575 rcode = pairlist_read(data->hints_file, &(data->hints), 0);
577 radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
584 * Save the instantiation data for later.
592 * Preprocess a request.
594 static int preprocess_authorize(void *instance, REQUEST *request)
597 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
600 * Mangle the username, to get rid of stupid implementation
603 rad_mangle(data, request);
605 if (data->with_ascend_hack) {
607 * If we're using Ascend systems, hack the NAS-Port-Id
608 * in place, to go from Ascend's weird values to something
609 * approaching rationality.
611 ascend_nasport_hack(pairfind(request->packet->vps,
613 data->ascend_channels_per_line);
616 if (data->with_cisco_vsa_hack) {
618 * We need to run this hack because the h323-conf-id
619 * attribute should be used.
621 cisco_vsa_hack(request->packet->vps);
624 if (data->with_alvarion_vsa_hack) {
626 * We need to run this hack because the Alvarion
629 alvarion_vsa_hack(request->packet->vps);
632 if (data->with_cablelabs_vsa_hack) {
634 * We need to run this hack because the Cablelabs
637 cablelabs_vsa_hack(&request->packet->vps);
641 * Note that we add the Request-Src-IP-Address to the request
642 * structure BEFORE checking huntgroup access. This allows
643 * the Request-Src-IP-Address to be used for huntgroup
646 if (add_nas_attr(request) < 0) {
647 return RLM_MODULE_FAIL;
650 hints_setup(data->hints, request);
653 * If there is a PW_CHAP_PASSWORD attribute but there
654 * is PW_CHAP_CHALLENGE we need to add it so that other
655 * modules can use it as a normal attribute.
657 if (pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0) &&
658 pairfind(request->packet->vps, PW_CHAP_CHALLENGE, 0) == NULL) {
661 vp = radius_paircreate(request, &request->packet->vps,
662 PW_CHAP_CHALLENGE, 0, PW_TYPE_OCTETS);
663 vp->length = AUTH_VECTOR_LEN;
664 memcpy(vp->vp_strvalue, request->packet->vector, AUTH_VECTOR_LEN);
667 if ((r = huntgroup_access(request,
668 data->huntgroups)) != RLM_MODULE_OK) {
670 radlog_request(L_AUTH, 0, request, "No huntgroup access: [%s] (%s)",
671 request->username ? request->username->vp_strvalue : "<NO User-Name>",
672 auth_name(buf, sizeof(buf), request, 1));
676 return RLM_MODULE_OK; /* Meaning: try next authorization module */
680 * Preprocess a request before accounting
682 static int preprocess_preaccounting(void *instance, REQUEST *request)
685 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
688 * Ensure that we have the SAME user name for both
689 * authentication && accounting.
691 rad_mangle(data, request);
693 if (data->with_cisco_vsa_hack) {
695 * We need to run this hack because the h323-conf-id
696 * attribute should be used.
698 cisco_vsa_hack(request->packet->vps);
701 if (data->with_alvarion_vsa_hack) {
703 * We need to run this hack because the Alvarion
706 alvarion_vsa_hack(request->packet->vps);
709 if (data->with_cablelabs_vsa_hack) {
711 * We need to run this hack because the Cablelabs
714 cablelabs_vsa_hack(&request->packet->vps);
718 * Ensure that we log the NAS IP Address in the packet.
720 if (add_nas_attr(request) < 0) {
721 return RLM_MODULE_FAIL;
724 hints_setup(data->hints, request);
726 if ((r = huntgroup_access(request,
727 data->huntgroups)) != RLM_MODULE_OK) {
729 radlog_request(L_INFO, 0, request, "No huntgroup access: [%s] (%s)",
730 request->username ? request->username->vp_strvalue : "<NO User-Name>",
731 auth_name(buf, sizeof(buf), request, 1));
739 * Clean up the module's instance.
741 static int preprocess_detach(void *instance)
743 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
745 pairlist_free(&(data->huntgroups));
746 pairlist_free(&(data->hints));
753 /* globally exported name */
754 module_t rlm_preprocess = {
757 RLM_TYPE_CHECK_CONFIG_SAFE, /* type */
758 preprocess_instantiate, /* instantiation */
759 preprocess_detach, /* detach */
761 NULL, /* authentication */
762 preprocess_authorize, /* authorization */
763 preprocess_preaccounting, /* pre-accounting */
764 NULL, /* accounting */
765 NULL, /* checksimul */
766 NULL, /* pre-proxy */
767 NULL, /* post-proxy */