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"
40 #include "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;
54 static const 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" },
74 { NULL, -1, 0, NULL, NULL }
80 * This hack changes Ascend's wierd port numberings
81 * to standard 0-??? port numbers so that the "+" works
82 * for IP address assignments.
84 static void ascend_nasport_hack(VALUE_PAIR *nas_port, int channels_per_line)
94 if (nas_port->lvalue > 9999) {
95 service = nas_port->lvalue/10000; /* 1=digital 2=analog */
96 line = (nas_port->lvalue - (10000 * service)) / 100;
97 channel = nas_port->lvalue-((10000 * service)+(100 * line));
99 (channel - 1) + (line - 1) * channels_per_line;
105 * This hack strips out Cisco's VSA duplicities in lines
106 * (Cisco not implemented VSA's in standard way.
108 * Cisco sends it's VSA attributes with the attribute name *again*
109 * in the string, like: H323-Attribute = "h323-attribute=value".
110 * This sort of behaviour is nonsense.
112 static void cisco_vsa_hack(VALUE_PAIR *vp)
116 char newattr[MAX_STRING_LEN];
118 for ( ; vp != NULL; vp = vp->next) {
119 vendorcode = (vp->attribute >> 16); /* HACK! */
120 if (!((vendorcode == 9) || (vendorcode == 6618))) continue; /* not a Cisco or Quintum VSA, continue */
122 if (vp->type != PW_TYPE_STRING) continue;
125 * No weird packing. Ignore it.
127 ptr = strchr(vp->strvalue, '='); /* find an '=' */
131 * Cisco-AVPair's get packed as:
133 * Cisco-AVPair = "h323-foo-bar = baz"
134 * Cisco-AVPair = "h323-foo-bar=baz"
136 * which makes sense only if you're a lunatic.
137 * This code looks for the attribute named inside
138 * of the string, and if it exists, adds it as a new
141 if ((vp->attribute & 0xffff) == 1) {
146 gettoken(&p, newattr, sizeof(newattr));
148 if (((dattr = dict_attrbyname(newattr)) != NULL) &&
149 (dattr->type == PW_TYPE_STRING)) {
153 * Make a new attribute.
155 newvp = pairmake(newattr, ptr + 1, T_OP_EQ);
160 } else { /* h322-foo-bar = "h323-foo-bar = baz" */
162 * We strip out the duplicity from the
163 * value field, we use only the value on
164 * the right side of the '=' character.
166 strNcpy(newattr, ptr + 1, sizeof(newattr));
167 strNcpy((char *)vp->strvalue, newattr,
168 sizeof(vp->strvalue));
169 vp->length = strlen((char *)vp->strvalue);
175 * Mangle username if needed, IN PLACE.
177 static void rad_mangle(rlm_preprocess_t *data, REQUEST *request)
179 VALUE_PAIR *namepair;
180 VALUE_PAIR *request_pairs;
184 * Get the username from the request
185 * If it isn't there, then we can't mangle the request.
187 request_pairs = request->packet->vps;
188 namepair = pairfind(request_pairs, PW_USER_NAME);
189 if ((namepair == NULL) ||
190 (namepair->length <= 0)) {
194 if (data->with_ntdomain_hack) {
196 char newname[MAX_STRING_LEN];
199 * Windows NT machines often authenticate themselves as
200 * NT_DOMAIN\username. Try to be smart about this.
202 * FIXME: should we handle this as a REALM ?
204 if ((ptr = strchr(namepair->strvalue, '\\')) != NULL) {
205 strNcpy(newname, ptr + 1, sizeof(newname));
207 strcpy(namepair->strvalue, newname);
208 namepair->length = strlen(newname);
212 if (data->with_specialix_jetstream_hack) {
216 * Specialix Jetstream 8500 24 port access server.
217 * If the user name is 10 characters or longer, a "/"
218 * and the excess characters after the 10th are
219 * appended to the user name.
221 * Reported by Lucas Heise <root@laonet.net>
223 if ((strlen((char *)namepair->strvalue) > 10) &&
224 (namepair->strvalue[10] == '/')) {
225 for (ptr = (char *)namepair->strvalue + 11; *ptr; ptr++)
228 namepair->length = strlen((char *)namepair->strvalue);
233 * Small check: if Framed-Protocol present but Service-Type
234 * is missing, add Service-Type = Framed-User.
236 if (pairfind(request_pairs, PW_FRAMED_PROTOCOL) != NULL &&
237 pairfind(request_pairs, PW_SERVICE_TYPE) == NULL) {
238 tmp = paircreate(PW_SERVICE_TYPE, PW_TYPE_INTEGER);
240 tmp->lvalue = PW_FRAMED_USER;
241 pairmove(&request_pairs, &tmp);
247 * Compare the request with the "reply" part in the
248 * huntgroup, which normally only contains username or group.
249 * At least one of the "reply" items has to match.
251 static int hunt_paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check)
253 VALUE_PAIR *check_item = check;
257 if (check == NULL) return 0;
259 while (result != 0 && check_item != NULL) {
261 tmp = check_item->next;
262 check_item->next = NULL;
264 result = paircmp(req, request, check_item, NULL);
266 check_item->next = tmp;
267 check_item = check_item->next;
275 * Add hints to the info sent by the terminal server
276 * based on the pattern of the username, and other attributes.
278 static int hints_setup(PAIR_LIST *hints, REQUEST *request)
284 VALUE_PAIR *request_pairs;
286 request_pairs = request->packet->vps;
288 if (hints == NULL || request_pairs == NULL)
289 return RLM_MODULE_NOOP;
292 * Check for valid input, zero length names not permitted
294 if ((tmp = pairfind(request_pairs, PW_USER_NAME)) == NULL)
297 name = (char *)tmp->strvalue;
299 if (name == NULL || name[0] == 0)
301 * No name, nothing to do.
303 return RLM_MODULE_NOOP;
305 for (i = hints; i; i = i->next) {
307 * Use "paircmp", which is a little more general...
309 if (paircmp(request, request_pairs, i->check, NULL) == 0) {
310 DEBUG2(" hints: Matched %s at %d",
316 if (i == NULL) return RLM_MODULE_NOOP;
318 add = paircopy(i->reply);
321 * Now add all attributes to the request list,
322 * except the PW_STRIP_USER_NAME one, and
325 pairdelete(&add, PW_STRIP_USER_NAME);
326 pairxlatmove(request, &request->packet->vps, &add);
329 return RLM_MODULE_UPDATED;
333 * See if we have access to the huntgroup.
335 static int huntgroup_access(REQUEST *request,
336 PAIR_LIST *huntgroups, VALUE_PAIR *request_pairs)
339 int r = RLM_MODULE_OK;
342 * We're not controlling access by huntgroups:
345 if (huntgroups == NULL)
346 return RLM_MODULE_OK;
348 for(i = huntgroups; i; i = i->next) {
350 * See if this entry matches.
352 if (paircmp(request, request_pairs, i->check, NULL) != 0)
356 * Now check for access.
358 r = RLM_MODULE_REJECT;
359 if (hunt_paircmp(request, request_pairs, i->reply) == 0) {
363 * We've matched the huntgroup, so add it in
364 * to the list of request pairs.
366 vp = pairfind(request_pairs, PW_HUNTGROUP_NAME);
368 vp = paircreate(PW_HUNTGROUP_NAME,
371 radlog(L_ERR, "No memory");
375 strNcpy(vp->strvalue, i->name,
376 sizeof(vp->strvalue));
377 vp->length = strlen(vp->strvalue);
379 pairadd(&request_pairs, vp);
390 * If the NAS wasn't smart enought to add a NAS-IP-Address
391 * to the request, then add it ourselves.
393 static int add_nas_attr(REQUEST *request)
397 switch (request->packet->src_ipaddr.af) {
399 nas = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS);
401 nas = paircreate(PW_NAS_IP_ADDRESS, PW_TYPE_IPADDR);
403 radlog(L_ERR, "No memory");
407 ip_ntoh(&request->packet->src_ipaddr,
408 nas->strvalue, sizeof(nas->strvalue));
409 nas->lvalue = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr;
410 pairadd(&request->packet->vps, nas);
415 nas = pairfind(request->packet->vps, PW_NAS_IPV6_ADDRESS);
417 nas = paircreate(PW_NAS_IPV6_ADDRESS, PW_TYPE_IPV6ADDR);
419 radlog(L_ERR, "No memory");
423 memcpy(nas->strvalue,
424 &request->packet->src_ipaddr.ipaddr,
425 sizeof(request->packet->src_ipaddr.ipaddr));
426 pairadd(&request->packet->vps, nas);
431 radlog(L_ERR, "Unknown address family for packet");
437 * Add in a Client-IP-Address, to tell the user
438 * the source IP of the request. That is, the client,
440 * Note that this MAY BE different from the NAS-IP-Address,
441 * especially if the request is being proxied.
443 * Note also that this is a server configuration item,
444 * and will NOT make it to any packets being sent from
447 nas = paircreate(PW_CLIENT_IP_ADDRESS, PW_TYPE_IPADDR);
449 radlog(L_ERR, "No memory");
452 nas->lvalue = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr;
453 ip_ntoh(&request->packet->src_ipaddr,
454 nas->strvalue, sizeof(nas->strvalue));
455 pairadd(&request->packet->vps, nas);
463 static int preprocess_instantiate(CONF_SECTION *conf, void **instance)
466 rlm_preprocess_t *data;
469 * Allocate room to put the module's instantiation data.
471 data = (rlm_preprocess_t *) rad_malloc(sizeof(*data));
472 memset(data, 0, sizeof(*data));
475 * Read this modules configuration data.
477 if (cf_section_parse(conf, data, module_config) < 0) {
482 data->huntgroups = NULL;
486 * Read the huntgroups file.
488 rcode = pairlist_read(data->huntgroup_file, &(data->huntgroups), 0);
490 radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
491 data->huntgroup_file);
496 * Read the hints file.
498 rcode = pairlist_read(data->hints_file, &(data->hints), 0);
500 radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
506 * Save the instantiation data for later.
514 * Preprocess a request.
516 static int preprocess_authorize(void *instance, REQUEST *request)
519 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
522 * Mangle the username, to get rid of stupid implementation
525 rad_mangle(data, request);
527 if (data->with_ascend_hack) {
529 * If we're using Ascend systems, hack the NAS-Port-Id
530 * in place, to go from Ascend's weird values to something
531 * approaching rationality.
533 ascend_nasport_hack(pairfind(request->packet->vps,
535 data->ascend_channels_per_line);
538 if (data->with_cisco_vsa_hack) {
540 * We need to run this hack because the h323-conf-id
541 * attribute should be used.
543 cisco_vsa_hack(request->packet->vps);
547 * Note that we add the Request-Src-IP-Address to the request
548 * structure BEFORE checking huntgroup access. This allows
549 * the Request-Src-IP-Address to be used for huntgroup
552 if (add_nas_attr(request) < 0) {
553 return RLM_MODULE_FAIL;
556 hints_setup(data->hints, request);
559 * If there is a PW_CHAP_PASSWORD attribute but there
560 * is PW_CHAP_CHALLENGE we need to add it so that other
561 * modules can use it as a normal attribute.
563 if (pairfind(request->packet->vps, PW_CHAP_PASSWORD) &&
564 pairfind(request->packet->vps, PW_CHAP_CHALLENGE) == NULL) {
566 vp = paircreate(PW_CHAP_CHALLENGE, PW_TYPE_OCTETS);
568 radlog(L_ERR|L_CONS, "no memory");
569 return RLM_MODULE_FAIL;
571 vp->length = AUTH_VECTOR_LEN;
572 memcpy(vp->strvalue, request->packet->vector, AUTH_VECTOR_LEN);
573 pairadd(&request->packet->vps, vp);
576 if ((r = huntgroup_access(request, data->huntgroups,
577 request->packet->vps)) != RLM_MODULE_OK) {
579 radlog(L_AUTH, "No huntgroup access: [%s] (%s)",
580 request->username->strvalue,
581 auth_name(buf, sizeof(buf), request, 1));
585 return RLM_MODULE_OK; /* Meaning: try next authorization module */
589 * Preprocess a request before accounting
591 static int preprocess_preaccounting(void *instance, REQUEST *request)
594 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
597 * Ensure that we have the SAME user name for both
598 * authentication && accounting.
600 rad_mangle(data, request);
602 if (data->with_cisco_vsa_hack) {
604 * We need to run this hack because the h323-conf-id
605 * attribute should be used.
607 cisco_vsa_hack(request->packet->vps);
611 * Ensure that we log the NAS IP Address in the packet.
613 if (add_nas_attr(request) < 0) {
614 return RLM_MODULE_FAIL;
617 r = hints_setup(data->hints, request);
619 if ((r = huntgroup_access(request, data->huntgroups,
620 request->packet->vps)) != RLM_MODULE_OK) {
622 radlog(L_INFO, "No huntgroup access: [%s] (%s)",
623 request->username->strvalue,
624 auth_name(buf, sizeof(buf), request, 1));
632 * Clean up the module's instance.
634 static int preprocess_detach(void *instance)
636 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
638 pairlist_free(&(data->huntgroups));
639 pairlist_free(&(data->hints));
641 free(data->huntgroup_file);
642 free(data->hints_file);
648 /* globally exported name */
649 module_t rlm_preprocess = {
651 0, /* type: reserved */
652 NULL, /* initialization */
653 preprocess_instantiate, /* instantiation */
655 NULL, /* authentication */
656 preprocess_authorize, /* authorization */
657 preprocess_preaccounting, /* pre-accounting */
658 NULL, /* accounting */
659 NULL, /* checksimul */
660 NULL, /* pre-proxy */
661 NULL, /* post-proxy */
664 preprocess_detach, /* detach */