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/autoconf.h>
38 #include <freeradius-devel/radiusd.h>
39 #include <freeradius-devel/modules.h>
40 #include <freeradius-devel/rad_assert.h>
42 typedef struct rlm_preprocess_t {
45 PAIR_LIST *huntgroups;
48 int ascend_channels_per_line;
49 int with_ntdomain_hack;
50 int with_specialix_jetstream_hack;
51 int with_cisco_vsa_hack;
52 int with_alvarion_vsa_hack;
55 static const CONF_PARSER module_config[] = {
56 { "huntgroups", PW_TYPE_FILENAME,
57 offsetof(rlm_preprocess_t,huntgroup_file), NULL,
58 "${raddbdir}/huntgroups" },
59 { "hints", PW_TYPE_FILENAME,
60 offsetof(rlm_preprocess_t,hints_file), NULL,
61 "${raddbdir}/hints" },
62 { "with_ascend_hack", PW_TYPE_BOOLEAN,
63 offsetof(rlm_preprocess_t,with_ascend_hack), NULL, "no" },
64 { "ascend_channels_per_line", PW_TYPE_INTEGER,
65 offsetof(rlm_preprocess_t,ascend_channels_per_line), NULL, "23" },
67 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
68 offsetof(rlm_preprocess_t,with_ntdomain_hack), NULL, "no" },
69 { "with_specialix_jetstream_hack", PW_TYPE_BOOLEAN,
70 offsetof(rlm_preprocess_t,with_specialix_jetstream_hack), NULL,
72 { "with_cisco_vsa_hack", PW_TYPE_BOOLEAN,
73 offsetof(rlm_preprocess_t,with_cisco_vsa_hack), NULL, "no" },
74 { "with_alvarion_vsa_hack", PW_TYPE_BOOLEAN,
75 offsetof(rlm_preprocess_t,with_alvarion_vsa_hack), NULL, "no" },
77 { NULL, -1, 0, NULL, NULL }
83 * This hack changes Ascend's wierd port numberings
84 * to standard 0-??? port numbers so that the "+" works
85 * for IP address assignments.
87 static void ascend_nasport_hack(VALUE_PAIR *nas_port, int channels_per_line)
97 if (nas_port->lvalue > 9999) {
98 service = nas_port->lvalue/10000; /* 1=digital 2=analog */
99 line = (nas_port->lvalue - (10000 * service)) / 100;
100 channel = nas_port->lvalue-((10000 * service)+(100 * line));
102 (channel - 1) + (line - 1) * channels_per_line;
108 * This hack strips out Cisco's VSA duplicities in lines
109 * (Cisco not implemented VSA's in standard way.
111 * Cisco sends it's VSA attributes with the attribute name *again*
112 * in the string, like: H323-Attribute = "h323-attribute=value".
113 * This sort of behaviour is nonsense.
115 static void cisco_vsa_hack(VALUE_PAIR *vp)
119 char newattr[MAX_STRING_LEN];
121 for ( ; vp != NULL; vp = vp->next) {
122 vendorcode = VENDOR(vp->attribute);
123 if (!((vendorcode == 9) || (vendorcode == 6618))) continue; /* not a Cisco or Quintum VSA, continue */
125 if (vp->type != PW_TYPE_STRING) continue;
128 * No weird packing. Ignore it.
130 ptr = strchr(vp->vp_strvalue, '='); /* find an '=' */
134 * Cisco-AVPair's get packed as:
136 * Cisco-AVPair = "h323-foo-bar = baz"
137 * Cisco-AVPair = "h323-foo-bar=baz"
139 * which makes sense only if you're a lunatic.
140 * This code looks for the attribute named inside
141 * of the string, and if it exists, adds it as a new
144 if ((vp->attribute & 0xffff) == 1) {
149 gettoken(&p, newattr, sizeof(newattr));
151 if (((dattr = dict_attrbyname(newattr)) != NULL) &&
152 (dattr->type == PW_TYPE_STRING)) {
156 * Make a new attribute.
158 newvp = pairmake(newattr, ptr + 1, T_OP_EQ);
163 } else { /* h322-foo-bar = "h323-foo-bar = baz" */
165 * We strip out the duplicity from the
166 * value field, we use only the value on
167 * the right side of the '=' character.
169 strlcpy(newattr, ptr + 1, sizeof(newattr));
170 strlcpy((char *)vp->vp_strvalue, newattr,
171 sizeof(vp->vp_strvalue));
172 vp->length = strlen((char *)vp->vp_strvalue);
179 * Don't even ask what this is doing...
181 static void alvarion_vsa_hack(VALUE_PAIR *vp)
186 for ( ; vp != NULL; vp = vp->next) {
187 vendorcode = VENDOR(vp->attribute);
188 if (vendorcode != 12394) continue;
189 if (vp->type != PW_TYPE_STRING) continue;
191 vp->attribute = number | (12394 << 16);
192 snprintf(vp->name, sizeof(vp->name),
193 "Breezecom-Attr%d", number++);
198 * Mangle username if needed, IN PLACE.
200 static void rad_mangle(rlm_preprocess_t *data, REQUEST *request)
202 VALUE_PAIR *namepair;
203 VALUE_PAIR *request_pairs;
207 * Get the username from the request
208 * If it isn't there, then we can't mangle the request.
210 request_pairs = request->packet->vps;
211 namepair = pairfind(request_pairs, PW_USER_NAME);
212 if ((namepair == NULL) ||
213 (namepair->length <= 0)) {
217 if (data->with_ntdomain_hack) {
219 char newname[MAX_STRING_LEN];
222 * Windows NT machines often authenticate themselves as
223 * NT_DOMAIN\username. Try to be smart about this.
225 * FIXME: should we handle this as a REALM ?
227 if ((ptr = strchr(namepair->vp_strvalue, '\\')) != NULL) {
228 strlcpy(newname, ptr + 1, sizeof(newname));
230 strcpy(namepair->vp_strvalue, newname);
231 namepair->length = strlen(newname);
235 if (data->with_specialix_jetstream_hack) {
239 * Specialix Jetstream 8500 24 port access server.
240 * If the user name is 10 characters or longer, a "/"
241 * and the excess characters after the 10th are
242 * appended to the user name.
244 * Reported by Lucas Heise <root@laonet.net>
246 if ((strlen((char *)namepair->vp_strvalue) > 10) &&
247 (namepair->vp_strvalue[10] == '/')) {
248 for (ptr = (char *)namepair->vp_strvalue + 11; *ptr; ptr++)
251 namepair->length = strlen((char *)namepair->vp_strvalue);
256 * Small check: if Framed-Protocol present but Service-Type
257 * is missing, add Service-Type = Framed-User.
259 if (pairfind(request_pairs, PW_FRAMED_PROTOCOL) != NULL &&
260 pairfind(request_pairs, PW_SERVICE_TYPE) == NULL) {
261 tmp = paircreate(PW_SERVICE_TYPE, PW_TYPE_INTEGER);
263 tmp->lvalue = PW_FRAMED_USER;
264 pairmove(&request_pairs, &tmp);
270 * Compare the request with the "reply" part in the
271 * huntgroup, which normally only contains username or group.
272 * At least one of the "reply" items has to match.
274 static int hunt_paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check)
276 VALUE_PAIR *check_item = check;
280 if (check == NULL) return 0;
282 while (result != 0 && check_item != NULL) {
284 tmp = check_item->next;
285 check_item->next = NULL;
287 result = paircompare(req, request, check_item, NULL);
289 check_item->next = tmp;
290 check_item = check_item->next;
298 * Add hints to the info sent by the terminal server
299 * based on the pattern of the username, and other attributes.
301 static int hints_setup(PAIR_LIST *hints, REQUEST *request)
307 VALUE_PAIR *request_pairs;
309 request_pairs = request->packet->vps;
311 if (hints == NULL || request_pairs == NULL)
312 return RLM_MODULE_NOOP;
315 * Check for valid input, zero length names not permitted
317 if ((tmp = pairfind(request_pairs, PW_USER_NAME)) == NULL)
320 name = (char *)tmp->vp_strvalue;
322 if (name == NULL || name[0] == 0)
324 * No name, nothing to do.
326 return RLM_MODULE_NOOP;
328 for (i = hints; i; i = i->next) {
330 * Use "paircompare", which is a little more general...
332 if (paircompare(request, request_pairs, i->check, NULL) == 0) {
333 DEBUG2(" hints: Matched %s at %d",
339 if (i == NULL) return RLM_MODULE_NOOP;
341 add = paircopy(i->reply);
344 * Now add all attributes to the request list,
345 * except the PW_STRIP_USER_NAME one, and
348 pairdelete(&add, PW_STRIP_USER_NAME);
349 pairxlatmove(request, &request->packet->vps, &add);
352 return RLM_MODULE_UPDATED;
356 * See if we have access to the huntgroup.
358 static int huntgroup_access(REQUEST *request,
359 PAIR_LIST *huntgroups, VALUE_PAIR *request_pairs)
362 int r = RLM_MODULE_OK;
365 * We're not controlling access by huntgroups:
368 if (huntgroups == NULL)
369 return RLM_MODULE_OK;
371 for(i = huntgroups; i; i = i->next) {
373 * See if this entry matches.
375 if (paircompare(request, request_pairs, i->check, NULL) != 0)
379 * Now check for access.
381 r = RLM_MODULE_REJECT;
382 if (hunt_paircmp(request, request_pairs, i->reply) == 0) {
386 * We've matched the huntgroup, so add it in
387 * to the list of request pairs.
389 vp = pairfind(request_pairs, PW_HUNTGROUP_NAME);
391 vp = paircreate(PW_HUNTGROUP_NAME,
394 radlog(L_ERR, "No memory");
398 strlcpy(vp->vp_strvalue, i->name,
399 sizeof(vp->vp_strvalue));
400 vp->length = strlen(vp->vp_strvalue);
402 pairadd(&request_pairs, vp);
413 * If the NAS wasn't smart enought to add a NAS-IP-Address
414 * to the request, then add it ourselves.
416 static int add_nas_attr(REQUEST *request)
420 switch (request->packet->src_ipaddr.af) {
422 nas = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS);
424 nas = paircreate(PW_NAS_IP_ADDRESS, PW_TYPE_IPADDR);
426 radlog(L_ERR, "No memory");
429 nas->lvalue = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr;
430 pairadd(&request->packet->vps, nas);
435 nas = pairfind(request->packet->vps, PW_NAS_IPV6_ADDRESS);
437 nas = paircreate(PW_NAS_IPV6_ADDRESS, PW_TYPE_IPV6ADDR);
439 radlog(L_ERR, "No memory");
443 memcpy(nas->vp_strvalue,
444 &request->packet->src_ipaddr.ipaddr,
445 sizeof(request->packet->src_ipaddr.ipaddr));
446 pairadd(&request->packet->vps, nas);
451 radlog(L_ERR, "Unknown address family for packet");
462 static int preprocess_instantiate(CONF_SECTION *conf, void **instance)
465 rlm_preprocess_t *data;
468 * Allocate room to put the module's instantiation data.
470 data = (rlm_preprocess_t *) rad_malloc(sizeof(*data));
471 memset(data, 0, sizeof(*data));
474 * Read this modules configuration data.
476 if (cf_section_parse(conf, data, module_config) < 0) {
481 data->huntgroups = NULL;
485 * Read the huntgroups file.
487 rcode = pairlist_read(data->huntgroup_file, &(data->huntgroups), 0);
489 radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
490 data->huntgroup_file);
495 * Read the hints file.
497 rcode = pairlist_read(data->hints_file, &(data->hints), 0);
499 radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
505 * Save the instantiation data for later.
513 * Preprocess a request.
515 static int preprocess_authorize(void *instance, REQUEST *request)
518 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
521 * Mangle the username, to get rid of stupid implementation
524 rad_mangle(data, request);
526 if (data->with_ascend_hack) {
528 * If we're using Ascend systems, hack the NAS-Port-Id
529 * in place, to go from Ascend's weird values to something
530 * approaching rationality.
532 ascend_nasport_hack(pairfind(request->packet->vps,
534 data->ascend_channels_per_line);
537 if (data->with_cisco_vsa_hack) {
539 * We need to run this hack because the h323-conf-id
540 * attribute should be used.
542 cisco_vsa_hack(request->packet->vps);
545 if (data->with_alvarion_vsa_hack) {
547 * We need to run this hack because the Alvarion
550 alvarion_vsa_hack(request->packet->vps);
554 * Note that we add the Request-Src-IP-Address to the request
555 * structure BEFORE checking huntgroup access. This allows
556 * the Request-Src-IP-Address to be used for huntgroup
559 if (add_nas_attr(request) < 0) {
560 return RLM_MODULE_FAIL;
563 hints_setup(data->hints, request);
566 * If there is a PW_CHAP_PASSWORD attribute but there
567 * is PW_CHAP_CHALLENGE we need to add it so that other
568 * modules can use it as a normal attribute.
570 if (pairfind(request->packet->vps, PW_CHAP_PASSWORD) &&
571 pairfind(request->packet->vps, PW_CHAP_CHALLENGE) == NULL) {
573 vp = paircreate(PW_CHAP_CHALLENGE, PW_TYPE_OCTETS);
575 radlog(L_ERR|L_CONS, "no memory");
576 return RLM_MODULE_FAIL;
578 vp->length = AUTH_VECTOR_LEN;
579 memcpy(vp->vp_strvalue, request->packet->vector, AUTH_VECTOR_LEN);
580 pairadd(&request->packet->vps, vp);
583 if ((r = huntgroup_access(request, data->huntgroups,
584 request->packet->vps)) != RLM_MODULE_OK) {
586 radlog(L_AUTH, "No huntgroup access: [%s] (%s)",
587 request->username ? request->username->vp_strvalue : "<NO User-Name>",
588 auth_name(buf, sizeof(buf), request, 1));
592 return RLM_MODULE_OK; /* Meaning: try next authorization module */
596 * Preprocess a request before accounting
598 static int preprocess_preaccounting(void *instance, REQUEST *request)
601 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
604 * Ensure that we have the SAME user name for both
605 * authentication && accounting.
607 rad_mangle(data, request);
609 if (data->with_cisco_vsa_hack) {
611 * We need to run this hack because the h323-conf-id
612 * attribute should be used.
614 cisco_vsa_hack(request->packet->vps);
617 if (data->with_alvarion_vsa_hack) {
619 * We need to run this hack because the Alvarion
622 alvarion_vsa_hack(request->packet->vps);
626 * Ensure that we log the NAS IP Address in the packet.
628 if (add_nas_attr(request) < 0) {
629 return RLM_MODULE_FAIL;
632 r = hints_setup(data->hints, request);
634 if ((r = huntgroup_access(request, data->huntgroups,
635 request->packet->vps)) != RLM_MODULE_OK) {
637 radlog(L_INFO, "No huntgroup access: [%s] (%s)",
638 request->username ? request->username->vp_strvalue : "<NO User-Name>",
639 auth_name(buf, sizeof(buf), request, 1));
647 * Clean up the module's instance.
649 static int preprocess_detach(void *instance)
651 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
653 pairlist_free(&(data->huntgroups));
654 pairlist_free(&(data->hints));
661 /* globally exported name */
662 module_t rlm_preprocess = {
665 0, /* type: reserved */
666 preprocess_instantiate, /* instantiation */
667 preprocess_detach, /* detach */
669 NULL, /* authentication */
670 preprocess_authorize, /* authorization */
671 preprocess_preaccounting, /* pre-accounting */
672 NULL, /* accounting */
673 NULL, /* checksimul */
674 NULL, /* pre-proxy */
675 NULL, /* post-proxy */