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;
53 static CONF_PARSER module_config[] = {
54 { "huntgroups", PW_TYPE_STRING_PTR,
55 offsetof(rlm_preprocess_t,huntgroup_file), NULL,
56 "${raddbdir}/huntgroups" },
57 { "hints", PW_TYPE_STRING_PTR,
58 offsetof(rlm_preprocess_t,hints_file), NULL,
59 "${raddbdir}/hints" },
60 { "with_ascend_hack", PW_TYPE_BOOLEAN,
61 offsetof(rlm_preprocess_t,with_ascend_hack), NULL, "no" },
62 { "ascend_channels_per_line", PW_TYPE_INTEGER,
63 offsetof(rlm_preprocess_t,ascend_channels_per_line), NULL, "23" },
65 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
66 offsetof(rlm_preprocess_t,with_ntdomain_hack), NULL, "no" },
67 { "with_specialix_jetstream_hack", PW_TYPE_BOOLEAN,
68 offsetof(rlm_preprocess_t,with_specialix_jetstream_hack), NULL,
70 { "with_cisco_vsa_hack", PW_TYPE_BOOLEAN,
71 offsetof(rlm_preprocess_t,with_cisco_vsa_hack), NULL, "no" },
73 { NULL, -1, 0, NULL, NULL }
79 * This hack changes Ascend's wierd port numberings
80 * to standard 0-??? port numbers so that the "+" works
81 * for IP address assignments.
83 static void ascend_nasport_hack(VALUE_PAIR *nas_port, int channels_per_line)
93 if (nas_port->lvalue > 9999) {
94 service = nas_port->lvalue/10000; /* 1=digital 2=analog */
95 line = (nas_port->lvalue - (10000 * service)) / 100;
96 channel = nas_port->lvalue-((10000 * service)+(100 * line));
98 (channel - 1) + (line - 1) * channels_per_line;
104 * This hack strips out Cisco's VSA duplicities in lines
105 * (Cisco not implemented VSA's in standard way.
107 * Cisco sends it's VSA attributes with the attribute name *again*
108 * in the string, like: H323-Attribute = "h323-attribute=value".
109 * This sort of behaviour is nonsense.
111 static void cisco_vsa_hack(VALUE_PAIR *vp)
113 int vendorpec, vendorcode;
115 char newattr[MAX_STRING_LEN];
117 for ( ; vp != NULL; vp = vp->next) {
118 vendorcode = (vp->attribute >> 16); /* HACK! */
119 if (vendorcode == 0) continue; /* ignore non-VSA attributes */
121 vendorpec = dict_vendorpec(vendorcode);
122 if (vendorpec == 0) continue; /* ignore unknown VSA's */
124 if (vendorpec != 9) continue; /* not a Cisco VSA, continue */
126 if ((vp->attribute & 0xffff) == 1) continue; /* Cisco-AVPair */
129 * We strip out the duplicity from the value field,
130 * we use only the value on the right side of = character.
132 if ((ptr = strchr(vp->strvalue, '=')) != NULL) {
133 strNcpy(newattr, ptr + 1, sizeof(newattr));
134 strNcpy((char *)vp->strvalue, newattr,
135 sizeof(vp->strvalue));
136 vp->length = strlen((char *)vp->strvalue);
142 * Mangle username if needed, IN PLACE.
144 static void rad_mangle(rlm_preprocess_t *data, REQUEST *request)
146 VALUE_PAIR *namepair;
147 VALUE_PAIR *request_pairs;
151 * Get the username from the request
152 * If it isn't there, then we can't mangle the request.
154 request_pairs = request->packet->vps;
155 namepair = pairfind(request_pairs, PW_USER_NAME);
156 if ((namepair == NULL) ||
157 (namepair->length <= 0)) {
161 if (data->with_ntdomain_hack) {
163 char newname[MAX_STRING_LEN];
166 * Windows NT machines often authenticate themselves as
167 * NT_DOMAIN\username. Try to be smart about this.
169 * FIXME: should we handle this as a REALM ?
171 if ((ptr = strchr(namepair->strvalue, '\\')) != NULL) {
172 strNcpy(newname, ptr + 1, sizeof(newname));
174 strcpy(namepair->strvalue, newname);
175 namepair->length = strlen(newname);
179 if (data->with_specialix_jetstream_hack) {
183 * Specialix Jetstream 8500 24 port access server.
184 * If the user name is 10 characters or longer, a "/"
185 * and the excess characters after the 10th are
186 * appended to the user name.
188 * Reported by Lucas Heise <root@laonet.net>
190 if ((strlen((char *)namepair->strvalue) > 10) &&
191 (namepair->strvalue[10] == '/')) {
192 for (ptr = (char *)namepair->strvalue + 11; *ptr; ptr++)
195 namepair->length = strlen((char *)namepair->strvalue);
200 * Small check: if Framed-Protocol present but Service-Type
201 * is missing, add Service-Type = Framed-User.
203 if (pairfind(request_pairs, PW_FRAMED_PROTOCOL) != NULL &&
204 pairfind(request_pairs, PW_SERVICE_TYPE) == NULL) {
205 tmp = paircreate(PW_SERVICE_TYPE, PW_TYPE_INTEGER);
207 tmp->lvalue = PW_FRAMED_USER;
208 pairmove(&request_pairs, &tmp);
214 * Compare the request with the "reply" part in the
215 * huntgroup, which normally only contains username or group.
216 * At least one of the "reply" items has to match.
218 static int hunt_paircmp(VALUE_PAIR *request, VALUE_PAIR *check)
220 VALUE_PAIR *check_item = check;
224 if (check == NULL) return 0;
226 while (result != 0 && check_item != NULL) {
228 tmp = check_item->next;
229 check_item->next = NULL;
231 result = paircmp(NULL, request, check_item, NULL);
233 check_item->next = tmp;
234 check_item = check_item->next;
242 * Compare prefix/suffix
244 static int presufcmp(VALUE_PAIR *check, char *name, char *rest)
250 printf("Comparing %s and %s, check->attr is %d\n",
251 name, check->strvalue, check->attribute);
254 len = strlen((char *)check->strvalue);
255 switch (check->attribute) {
257 ret = strncmp(name, (char *)check->strvalue, len);
258 if (ret == 0 && rest)
259 strcpy(rest, name + len);
262 namelen = strlen(name);
265 ret = strcmp(name + namelen - len,
266 (char *)check->strvalue);
267 if (ret == 0 && rest) {
268 strncpy(rest, name, namelen - len);
269 rest[namelen - len] = 0;
278 * Match a username with a wildcard expression.
279 * Is very limited for now.
281 static int matches(char *name, PAIR_LIST *pl, char *matchpart)
285 char *wild = pl->name;
289 * We now support both:
291 * DEFAULT Prefix = "P"
296 if ((tmp = pairfind(pl->check, PW_PREFIX)) != NULL ||
297 (tmp = pairfind(pl->check, PW_SUFFIX)) != NULL) {
299 if (strncmp(pl->name, "DEFAULT", 7) == 0 ||
300 strcmp(pl->name, name) == 0)
301 return !presufcmp(tmp, name, matchpart);
305 * Shortcut if there's no '*' in pl->name.
307 if (strchr(pl->name, '*') == NULL &&
308 (strncmp(pl->name, "DEFAULT", 7) == 0 ||
309 strcmp(pl->name, name) == 0)) {
310 strcpy(matchpart, name);
315 * Normally, we should return 0 here, but we
316 * support the old * stuff.
321 if (len == 0 || wlen == 0) return 0;
323 if (wild[0] == '*') {
326 if (wlen <= len && strcmp(name + (len - wlen), wild) == 0) {
327 strcpy(matchpart, name);
328 matchpart[len - wlen] = 0;
331 } else if (wild[wlen - 1] == '*') {
332 if (wlen <= len && strncmp(name, wild, wlen - 1) == 0) {
333 strcpy(matchpart, name + wlen - 1);
343 * Add hints to the info sent by the terminal server
344 * based on the pattern of the username.
346 static int hints_setup(PAIR_LIST *hints, REQUEST *request)
348 char newname[MAX_STRING_LEN];
355 VALUE_PAIR *request_pairs;
357 request_pairs = request->packet->vps;
359 if (hints == NULL || request_pairs == NULL)
360 return RLM_MODULE_NOOP;
363 * Check for valid input, zero length names not permitted
365 if ((tmp = pairfind(request_pairs, PW_USER_NAME)) == NULL)
368 name = (char *)tmp->strvalue;
370 if (name == NULL || name[0] == 0)
372 * No name, nothing to do.
374 return RLM_MODULE_NOOP;
376 for (i = hints; i; i = i->next) {
377 if (matches(name, i, newname)) {
378 DEBUG2(" hints: Matched %s at %d",
384 if (i == NULL) return RLM_MODULE_NOOP;
386 add = paircopy(i->reply);
389 printf("In hints_setup, newname is %s\n", newname);
393 * See if we need to adjust the name.
396 if ((tmp = pairfind(i->reply, PW_STRIP_USER_NAME)) != NULL
399 if ((tmp = pairfind(i->check, PW_STRIP_USER_NAME)) != NULL
404 tmp = pairfind(request_pairs, PW_STRIPPED_USER_NAME);
406 strcpy((char *)tmp->strvalue, newname);
407 tmp->length = strlen((char *)tmp->strvalue);
410 * No Stripped-User-Name exists: add one.
412 tmp = paircreate(PW_STRIPPED_USER_NAME, PW_TYPE_STRING);
414 radlog(L_ERR|L_CONS, "no memory");
417 strcpy((char *)tmp->strvalue, newname);
418 tmp->length = strlen((char *)tmp->strvalue);
419 pairadd(&request_pairs, tmp);
421 request->username = tmp;
425 * Now add all attributes to the request list,
426 * except the PW_STRIP_USER_NAME one.
428 pairdelete(&add, PW_STRIP_USER_NAME);
429 for(last = request_pairs; last && last->next; last = last->next)
431 if (last) last->next = add;
433 return RLM_MODULE_UPDATED;
437 * See if the huntgroup matches. This function is
438 * tied to the "Huntgroup" keyword.
440 static int huntgroup_cmp(void *instance, REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
441 VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
445 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
447 check_pairs = check_pairs; /* shut the compiler up */
448 reply_pairs = reply_pairs;
450 huntgroup = (char *)check->strvalue;
452 for (i = data->huntgroups; i; i = i->next) {
453 if (strcmp(i->name, huntgroup) != 0)
455 if (paircmp(req, request, i->check, NULL) == 0) {
456 DEBUG2(" huntgroups: Matched %s at %d",
463 * paircmp() expects to see zero on match, so let's
474 * See if we have access to the huntgroup.
476 static int huntgroup_access(PAIR_LIST *huntgroups, VALUE_PAIR *request_pairs)
479 int r = RLM_MODULE_OK;
482 * We're not controlling access by huntgroups:
485 if (huntgroups == NULL)
486 return RLM_MODULE_OK;
488 for(i = huntgroups; i; i = i->next) {
490 * See if this entry matches.
492 if (paircmp(NULL, request_pairs, i->check, NULL) != 0)
496 * Now check for access.
498 r = RLM_MODULE_REJECT;
499 if (hunt_paircmp(request_pairs, i->reply) == 0) {
503 * We've matched the huntgroup, so add it in
504 * to the list of request pairs.
506 vp = pairfind(request_pairs, PW_HUNTGROUP_NAME);
508 vp = paircreate(PW_HUNTGROUP_NAME,
511 radlog(L_ERR, "No memory");
515 strNcpy(vp->strvalue, i->name,
516 sizeof(vp->strvalue));
517 vp->length = strlen(vp->strvalue);
519 pairadd(&request_pairs, vp);
530 * If the NAS wasn't smart enought to add a NAS-IP-Address
531 * to the request, then add it ourselves.
533 static void add_nas_attr(REQUEST *request)
537 nas = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS);
539 nas = paircreate(PW_NAS_IP_ADDRESS, PW_TYPE_IPADDR);
541 radlog(L_ERR, "No memory");
544 nas->lvalue = request->packet->src_ipaddr;
545 pairadd(&request->packet->vps, nas);
549 * Add in a Client-IP-Address, to tell the user
550 * the source IP of the request. That is, the client,
552 * Note that this MAY BE different from the NAS-IP-Address,
553 * especially if the request is being proxied.
555 * Note also that this is a server configuration item,
556 * and will NOT make it to any packets being sent from
559 nas = paircreate(PW_CLIENT_IP_ADDRESS, PW_TYPE_IPADDR);
561 radlog(L_ERR, "No memory");
564 nas->lvalue = request->packet->src_ipaddr;
565 ip_hostname(nas->strvalue, sizeof(nas->strvalue), nas->lvalue);
566 pairadd(&request->packet->vps, nas);
573 static int preprocess_instantiate(CONF_SECTION *conf, void **instance)
576 rlm_preprocess_t *data;
579 * Allocate room to put the module's instantiation data.
581 data = (rlm_preprocess_t *) rad_malloc(sizeof(*data));
584 * Read this modules configuration data.
586 if (cf_section_parse(conf, data, module_config) < 0) {
591 data->huntgroups = NULL;
595 * Read the huntgroups file.
597 rcode = pairlist_read(data->huntgroup_file, &(data->huntgroups), 0);
599 radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
600 data->huntgroup_file);
605 * Read the hints file.
607 rcode = pairlist_read(data->hints_file, &(data->hints), 0);
609 radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
615 * Register the huntgroup comparison operation.
617 paircompare_register(PW_HUNTGROUP_NAME, 0, huntgroup_cmp, data);
620 * Save the instantiation data for later.
628 * Preprocess a request.
630 static int preprocess_authorize(void *instance, REQUEST *request)
633 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
636 * Mangle the username, to get rid of stupid implementation
639 rad_mangle(data, request);
641 if (data->with_ascend_hack) {
643 * If we're using Ascend systems, hack the NAS-Port-Id
644 * in place, to go from Ascend's weird values to something
645 * approaching rationality.
647 ascend_nasport_hack(pairfind(request->packet->vps,
649 data->ascend_channels_per_line);
652 if (data->with_cisco_vsa_hack) {
654 * We need to run this hack because the h323-conf-id
655 * attribute should be used.
657 cisco_vsa_hack(request->packet->vps);
661 * Note that we add the Request-Src-IP-Address to the request
662 * structure BEFORE checking huntgroup access. This allows
663 * the Request-Src-IP-Address to be used for huntgroup
666 add_nas_attr(request);
668 hints_setup(data->hints, request);
671 * If there is a PW_CHAP_PASSWORD attribute but there
672 * is PW_CHAP_CHALLENGE we need to add it so that other
673 * modules can use it as a normal attribute.
675 if (pairfind(request->packet->vps, PW_CHAP_PASSWORD) &&
676 pairfind(request->packet->vps, PW_CHAP_CHALLENGE) == NULL) {
678 vp = paircreate(PW_CHAP_CHALLENGE, PW_TYPE_OCTETS);
680 radlog(L_ERR|L_CONS, "no memory");
683 vp->length = AUTH_VECTOR_LEN;
684 memcpy(vp->strvalue, request->packet->vector, AUTH_VECTOR_LEN);
685 pairadd(&request->packet->vps, vp);
688 if (huntgroup_access(data->huntgroups, request->packet->vps) != RLM_MODULE_OK) {
689 radlog(L_AUTH, "No huntgroup access: [%s] (%s)",
690 request->username->strvalue,
691 auth_name(buf, sizeof(buf), request, 1));
692 return RLM_MODULE_REJECT;
695 return RLM_MODULE_OK; /* Meaning: try next authorization module */
699 * Preprocess a request before accounting
701 static int preprocess_preaccounting(void *instance, REQUEST *request)
704 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
707 * Ensure that we have the SAME user name for both
708 * authentication && accounting.
710 rad_mangle(data, request);
712 if (data->with_cisco_vsa_hack) {
714 * We need to run this hack because the h323-conf-id
715 * attribute should be used.
717 cisco_vsa_hack(request->packet->vps);
721 * Ensure that we log the NAS IP Address in the packet.
723 add_nas_attr(request);
725 r = hints_setup(data->hints, request);
731 * Clean up the module's instance.
733 static int preprocess_detach(void *instance)
735 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
737 paircompare_unregister(PW_HUNTGROUP_NAME, huntgroup_cmp);
738 pairlist_free(&(data->huntgroups));
739 pairlist_free(&(data->hints));
741 free(data->huntgroup_file);
742 free(data->hints_file);
748 /* globally exported name */
749 module_t rlm_preprocess = {
751 0, /* type: reserved */
752 NULL, /* initialization */
753 preprocess_instantiate, /* instantiation */
755 NULL, /* authentication */
756 preprocess_authorize, /* authorization */
757 preprocess_preaccounting, /* pre-accounting */
758 NULL, /* accounting */
759 NULL, /* checksimul */
760 NULL, /* pre-proxy */
761 NULL, /* post-proxy */
764 preprocess_detach, /* detach */