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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 * Copyright 2000 The FreeRADIUS server project
23 * Copyright 2000 Alan DeKok <aland@ox.org>
26 static const char rcsid[] = "$Id$";
29 #include "libradius.h"
41 typedef struct rlm_preprocess_t {
44 PAIR_LIST *huntgroups;
47 int ascend_channels_per_line;
48 int with_ntdomain_hack;
49 int with_specialix_jetstream_hack;
50 int with_cisco_vsa_hack;
51 int with_alvarion_vsa_hack;
54 static CONF_PARSER module_config[] = {
55 { "huntgroups", PW_TYPE_STRING_PTR,
56 offsetof(rlm_preprocess_t,huntgroup_file), NULL,
57 "${raddbdir}/huntgroups" },
58 { "hints", PW_TYPE_STRING_PTR,
59 offsetof(rlm_preprocess_t,hints_file), NULL,
60 "${raddbdir}/hints" },
61 { "with_ascend_hack", PW_TYPE_BOOLEAN,
62 offsetof(rlm_preprocess_t,with_ascend_hack), NULL, "no" },
63 { "ascend_channels_per_line", PW_TYPE_INTEGER,
64 offsetof(rlm_preprocess_t,ascend_channels_per_line), NULL, "23" },
66 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
67 offsetof(rlm_preprocess_t,with_ntdomain_hack), NULL, "no" },
68 { "with_specialix_jetstream_hack", PW_TYPE_BOOLEAN,
69 offsetof(rlm_preprocess_t,with_specialix_jetstream_hack), NULL,
71 { "with_cisco_vsa_hack", PW_TYPE_BOOLEAN,
72 offsetof(rlm_preprocess_t,with_cisco_vsa_hack), NULL, "no" },
73 { "with_alvarion_vsa_hack", PW_TYPE_BOOLEAN,
74 offsetof(rlm_preprocess_t,with_alvarion_vsa_hack), NULL, "no" },
76 { NULL, -1, 0, NULL, NULL }
82 * This hack changes Ascend's wierd port numberings
83 * to standard 0-??? port numbers so that the "+" works
84 * for IP address assignments.
86 static void ascend_nasport_hack(VALUE_PAIR *nas_port, int channels_per_line)
96 if (nas_port->lvalue > 9999) {
97 service = nas_port->lvalue/10000; /* 1=digital 2=analog */
98 line = (nas_port->lvalue - (10000 * service)) / 100;
99 channel = nas_port->lvalue-((10000 * service)+(100 * line));
101 (channel - 1) + (line - 1) * channels_per_line;
107 * This hack strips out Cisco's VSA duplicities in lines
108 * (Cisco not implemented VSA's in standard way.
110 * Cisco sends it's VSA attributes with the attribute name *again*
111 * in the string, like: H323-Attribute = "h323-attribute=value".
112 * This sort of behaviour is nonsense.
114 static void cisco_vsa_hack(VALUE_PAIR *vp)
118 char newattr[MAX_STRING_LEN];
120 for ( ; vp != NULL; vp = vp->next) {
121 vendorcode = VENDOR(vp->attribute);
122 if (!((vendorcode == 9) || (vendorcode == 6618))) continue; /* not a Cisco or Quintum VSA, continue */
124 if (vp->type != PW_TYPE_STRING) continue;
127 * No weird packing. Ignore it.
129 ptr = strchr(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->attribute & 0xffff) == 1) {
148 gettoken(&p, newattr, sizeof(newattr));
150 if (((dattr = dict_attrbyname(newattr)) != NULL) &&
151 (dattr->type == PW_TYPE_STRING)) {
155 * Make a new attribute.
157 newvp = pairmake(newattr, ptr + 1, T_OP_EQ);
162 } else { /* h322-foo-bar = "h323-foo-bar = baz" */
164 * We strip out the duplicity from the
165 * value field, we use only the value on
166 * the right side of the '=' character.
168 strNcpy(newattr, ptr + 1, sizeof(newattr));
169 strNcpy((char *)vp->strvalue, newattr,
170 sizeof(vp->strvalue));
171 vp->length = strlen((char *)vp->strvalue);
178 * Don't even ask what this is doing...
180 static void alvarion_vsa_hack(VALUE_PAIR *vp)
185 for ( ; vp != NULL; vp = vp->next) {
186 vendorcode = VENDOR(vp->attribute);
187 if (vendorcode != 12394) continue;
188 if (vp->type != PW_TYPE_STRING) continue;
190 vp->attribute = number | (12394 << 16);
191 snprintf(vp->name, sizeof(vp->name),
192 "Breezecom-Attr%d", number++);
197 * Mangle username if needed, IN PLACE.
199 static void rad_mangle(rlm_preprocess_t *data, REQUEST *request)
201 VALUE_PAIR *namepair;
202 VALUE_PAIR *request_pairs;
206 * Get the username from the request
207 * If it isn't there, then we can't mangle the request.
209 request_pairs = request->packet->vps;
210 namepair = pairfind(request_pairs, PW_USER_NAME);
211 if ((namepair == NULL) ||
212 (namepair->length <= 0)) {
216 if (data->with_ntdomain_hack) {
218 char newname[MAX_STRING_LEN];
221 * Windows NT machines often authenticate themselves as
222 * NT_DOMAIN\username. Try to be smart about this.
224 * FIXME: should we handle this as a REALM ?
226 if ((ptr = strchr(namepair->strvalue, '\\')) != NULL) {
227 strNcpy(newname, ptr + 1, sizeof(newname));
229 strcpy(namepair->strvalue, newname);
230 namepair->length = strlen(newname);
234 if (data->with_specialix_jetstream_hack) {
238 * Specialix Jetstream 8500 24 port access server.
239 * If the user name is 10 characters or longer, a "/"
240 * and the excess characters after the 10th are
241 * appended to the user name.
243 * Reported by Lucas Heise <root@laonet.net>
245 if ((strlen((char *)namepair->strvalue) > 10) &&
246 (namepair->strvalue[10] == '/')) {
247 for (ptr = (char *)namepair->strvalue + 11; *ptr; ptr++)
250 namepair->length = strlen((char *)namepair->strvalue);
255 * Small check: if Framed-Protocol present but Service-Type
256 * is missing, add Service-Type = Framed-User.
258 if (pairfind(request_pairs, PW_FRAMED_PROTOCOL) != NULL &&
259 pairfind(request_pairs, PW_SERVICE_TYPE) == NULL) {
260 tmp = paircreate(PW_SERVICE_TYPE, PW_TYPE_INTEGER);
262 tmp->lvalue = PW_FRAMED_USER;
263 pairmove(&request_pairs, &tmp);
269 * Compare the request with the "reply" part in the
270 * huntgroup, which normally only contains username or group.
271 * At least one of the "reply" items has to match.
273 static int hunt_paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check)
275 VALUE_PAIR *check_item = check;
279 if (check == NULL) return 0;
281 while (result != 0 && check_item != NULL) {
283 tmp = check_item->next;
284 check_item->next = NULL;
286 result = paircmp(req, request, check_item, NULL);
288 check_item->next = tmp;
289 check_item = check_item->next;
297 * Add hints to the info sent by the terminal server
298 * based on the pattern of the username, and other attributes.
300 static int hints_setup(PAIR_LIST *hints, REQUEST *request)
306 VALUE_PAIR *request_pairs;
308 request_pairs = request->packet->vps;
310 if (hints == NULL || request_pairs == NULL)
311 return RLM_MODULE_NOOP;
314 * Check for valid input, zero length names not permitted
316 if ((tmp = pairfind(request_pairs, PW_USER_NAME)) == NULL)
319 name = (char *)tmp->strvalue;
321 if (name == NULL || name[0] == 0)
323 * No name, nothing to do.
325 return RLM_MODULE_NOOP;
327 for (i = hints; i; i = i->next) {
329 * Use "paircmp", which is a little more general...
331 if (paircmp(request, request_pairs, i->check, NULL) == 0) {
332 DEBUG2(" hints: Matched %s at %d",
338 if (i == NULL) return RLM_MODULE_NOOP;
340 add = paircopy(i->reply);
343 * Now add all attributes to the request list,
344 * except the PW_STRIP_USER_NAME one, and
347 pairdelete(&add, PW_STRIP_USER_NAME);
348 pairxlatmove(request, &request->packet->vps, &add);
351 return RLM_MODULE_UPDATED;
355 * See if we have access to the huntgroup.
357 static int huntgroup_access(REQUEST *request,
358 PAIR_LIST *huntgroups, VALUE_PAIR *request_pairs)
361 int r = RLM_MODULE_OK;
364 * We're not controlling access by huntgroups:
367 if (huntgroups == NULL)
368 return RLM_MODULE_OK;
370 for(i = huntgroups; i; i = i->next) {
372 * See if this entry matches.
374 if (paircmp(request, request_pairs, i->check, NULL) != 0)
378 * Now check for access.
380 r = RLM_MODULE_REJECT;
381 if (hunt_paircmp(request, request_pairs, i->reply) == 0) {
385 * We've matched the huntgroup, so add it in
386 * to the list of request pairs.
388 vp = pairfind(request_pairs, PW_HUNTGROUP_NAME);
390 vp = paircreate(PW_HUNTGROUP_NAME,
393 radlog(L_ERR, "No memory");
397 strNcpy(vp->strvalue, i->name,
398 sizeof(vp->strvalue));
399 vp->length = strlen(vp->strvalue);
401 pairadd(&request_pairs, vp);
412 * If the NAS wasn't smart enought to add a NAS-IP-Address
413 * to the request, then add it ourselves.
415 static int add_nas_attr(REQUEST *request)
419 nas = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS);
421 nas = paircreate(PW_NAS_IP_ADDRESS, PW_TYPE_IPADDR);
423 radlog(L_ERR, "No memory");
426 nas->lvalue = request->packet->src_ipaddr;
427 ip_hostname(nas->strvalue, sizeof(nas->strvalue), nas->lvalue);
428 pairadd(&request->packet->vps, nas);
432 * Add in a Client-IP-Address, to tell the user
433 * the source IP of the request. That is, the client,
435 * Note that this MAY BE different from the NAS-IP-Address,
436 * especially if the request is being proxied.
438 * Note also that this is a server configuration item,
439 * and will NOT make it to any packets being sent from
442 nas = paircreate(PW_CLIENT_IP_ADDRESS, PW_TYPE_IPADDR);
444 radlog(L_ERR, "No memory");
447 nas->lvalue = request->packet->src_ipaddr;
448 ip_hostname(nas->strvalue, sizeof(nas->strvalue), nas->lvalue);
449 pairadd(&request->packet->vps, nas);
457 static int preprocess_instantiate(CONF_SECTION *conf, void **instance)
460 rlm_preprocess_t *data;
463 * Allocate room to put the module's instantiation data.
465 data = (rlm_preprocess_t *) rad_malloc(sizeof(*data));
466 memset(data, 0, sizeof(*data));
469 * Read this modules configuration data.
471 if (cf_section_parse(conf, data, module_config) < 0) {
476 data->huntgroups = NULL;
480 * Read the huntgroups file.
482 rcode = pairlist_read(data->huntgroup_file, &(data->huntgroups), 0);
484 radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
485 data->huntgroup_file);
490 * Read the hints file.
492 rcode = pairlist_read(data->hints_file, &(data->hints), 0);
494 radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
500 * Save the instantiation data for later.
508 * Preprocess a request.
510 static int preprocess_authorize(void *instance, REQUEST *request)
514 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
517 * Mangle the username, to get rid of stupid implementation
520 rad_mangle(data, request);
522 if (data->with_ascend_hack) {
524 * If we're using Ascend systems, hack the NAS-Port-Id
525 * in place, to go from Ascend's weird values to something
526 * approaching rationality.
528 ascend_nasport_hack(pairfind(request->packet->vps,
530 data->ascend_channels_per_line);
533 if (data->with_cisco_vsa_hack) {
535 * We need to run this hack because the h323-conf-id
536 * attribute should be used.
538 cisco_vsa_hack(request->packet->vps);
541 if (data->with_alvarion_vsa_hack) {
543 * We need to run this hack because the Alvarion
546 alvarion_vsa_hack(request->packet->vps);
550 * Note that we add the Request-Src-IP-Address to the request
551 * structure BEFORE checking huntgroup access. This allows
552 * the Request-Src-IP-Address to be used for huntgroup
555 if (add_nas_attr(request) < 0) {
556 return RLM_MODULE_FAIL;
559 hints_setup(data->hints, request);
562 * If there is a PW_CHAP_PASSWORD attribute but there
563 * is PW_CHAP_CHALLENGE we need to add it so that other
564 * modules can use it as a normal attribute.
566 if (pairfind(request->packet->vps, PW_CHAP_PASSWORD) &&
567 pairfind(request->packet->vps, PW_CHAP_CHALLENGE) == NULL) {
569 vp = paircreate(PW_CHAP_CHALLENGE, PW_TYPE_OCTETS);
571 radlog(L_ERR|L_CONS, "no memory");
572 return RLM_MODULE_FAIL;
574 vp->length = AUTH_VECTOR_LEN;
575 memcpy(vp->strvalue, request->packet->vector, AUTH_VECTOR_LEN);
576 pairadd(&request->packet->vps, vp);
579 if ((r = huntgroup_access(request, data->huntgroups,
580 request->packet->vps)) != RLM_MODULE_OK) {
581 radlog(L_AUTH, "No huntgroup access: [%s] (%s)",
582 request->username ? request->username->strvalue : "<No User-Name>",
583 auth_name(buf, sizeof(buf), request, 1));
587 return RLM_MODULE_OK; /* Meaning: try next authorization module */
591 * Preprocess a request before accounting
593 static int preprocess_preaccounting(void *instance, REQUEST *request)
596 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
599 * Ensure that we have the SAME user name for both
600 * authentication && accounting.
602 rad_mangle(data, request);
604 if (data->with_cisco_vsa_hack) {
606 * We need to run this hack because the h323-conf-id
607 * attribute should be used.
609 cisco_vsa_hack(request->packet->vps);
612 if (data->with_alvarion_vsa_hack) {
614 * We need to run this hack because the Alvarion
617 alvarion_vsa_hack(request->packet->vps);
621 * Ensure that we log the NAS IP Address in the packet.
623 if (add_nas_attr(request) < 0) {
624 return RLM_MODULE_FAIL;
627 r = hints_setup(data->hints, request);
633 * Clean up the module's instance.
635 static int preprocess_detach(void *instance)
637 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
639 pairlist_free(&(data->huntgroups));
640 pairlist_free(&(data->hints));
642 free(data->huntgroup_file);
643 free(data->hints_file);
649 /* globally exported name */
650 module_t rlm_preprocess = {
652 0, /* type: reserved */
653 NULL, /* initialization */
654 preprocess_instantiate, /* instantiation */
656 NULL, /* authentication */
657 preprocess_authorize, /* authorization */
658 preprocess_preaccounting, /* pre-accounting */
659 NULL, /* accounting */
660 NULL, /* checksimul */
661 NULL, /* pre-proxy */
662 NULL, /* post-proxy */
665 preprocess_detach, /* detach */