2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2 if the
4 * License as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
18 * @file rlm_preprocess.c
19 * @brief Fixes up requests, and processes huntgroups/hints files.
21 * @copyright 2000,2006 The FreeRADIUS server project
22 * @copyright 2000 Alan DeKok <aland@ox.org>
24 #include <freeradius-devel/ident.h>
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29 #include <freeradius-devel/rad_assert.h>
33 typedef struct rlm_preprocess_t {
36 PAIR_LIST *huntgroups;
39 int ascend_channels_per_line;
40 int with_ntdomain_hack;
41 int with_specialix_jetstream_hack;
42 int with_cisco_vsa_hack;
43 int with_alvarion_vsa_hack;
44 int with_cablelabs_vsa_hack;
47 static const CONF_PARSER module_config[] = {
48 { "huntgroups", PW_TYPE_FILENAME,
49 offsetof(rlm_preprocess_t,huntgroup_file), NULL, NULL },
50 { "hints", PW_TYPE_FILENAME,
51 offsetof(rlm_preprocess_t,hints_file), NULL, NULL },
52 { "with_ascend_hack", PW_TYPE_BOOLEAN,
53 offsetof(rlm_preprocess_t,with_ascend_hack), NULL, "no" },
54 { "ascend_channels_per_line", PW_TYPE_INTEGER,
55 offsetof(rlm_preprocess_t,ascend_channels_per_line), NULL, "23" },
57 { "with_ntdomain_hack", PW_TYPE_BOOLEAN,
58 offsetof(rlm_preprocess_t,with_ntdomain_hack), NULL, "no" },
59 { "with_specialix_jetstream_hack", PW_TYPE_BOOLEAN,
60 offsetof(rlm_preprocess_t,with_specialix_jetstream_hack), NULL,
62 { "with_cisco_vsa_hack", PW_TYPE_BOOLEAN,
63 offsetof(rlm_preprocess_t,with_cisco_vsa_hack), NULL, "no" },
64 { "with_alvarion_vsa_hack", PW_TYPE_BOOLEAN,
65 offsetof(rlm_preprocess_t,with_alvarion_vsa_hack), NULL, "no" },
66 { "with_cablelabs_vsa_hack", PW_TYPE_BOOLEAN,
67 offsetof(rlm_preprocess_t,with_cablelabs_vsa_hack), NULL, NULL },
69 { NULL, -1, 0, NULL, NULL }
73 * See if a VALUE_PAIR list contains Fall-Through = Yes
75 static int fallthrough(VALUE_PAIR *vp)
78 tmp = pairfind(vp, PW_FALL_THROUGH, 0, TAG_ANY);
80 return tmp ? tmp->vp_integer : 0;
85 * This hack changes Ascend's wierd port numberings
86 * to standard 0-??? port numbers so that the "+" works
87 * for IP address assignments.
89 static void ascend_nasport_hack(VALUE_PAIR *nas_port, int channels_per_line)
99 if (nas_port->vp_integer > 9999) {
100 service = nas_port->vp_integer/10000; /* 1=digital 2=analog */
101 line = (nas_port->vp_integer - (10000 * service)) / 100;
102 channel = nas_port->vp_integer-((10000 * service)+(100 * line));
103 nas_port->vp_integer =
104 (channel - 1) + (line - 1) * channels_per_line;
110 * This hack strips out Cisco's VSA duplicities in lines
111 * (Cisco not implemented VSA's in standard way.
113 * Cisco sends it's VSA attributes with the attribute name *again*
114 * in the string, like: H323-Attribute = "h323-attribute=value".
115 * This sort of behaviour is nonsense.
117 static void cisco_vsa_hack(VALUE_PAIR *vp)
121 char newattr[MAX_STRING_LEN];
123 for ( ; vp != NULL; vp = vp->next) {
124 vendorcode = vp->da->vendor;
125 if (!((vendorcode == 9) || (vendorcode == 6618))) continue; /* not a Cisco or Quintum VSA, continue */
127 if (vp->da->type != PW_TYPE_STRING) continue;
130 * No weird packing. Ignore it.
132 ptr = strchr(vp->vp_strvalue, '='); /* find an '=' */
136 * Cisco-AVPair's get packed as:
138 * Cisco-AVPair = "h323-foo-bar = baz"
139 * Cisco-AVPair = "h323-foo-bar=baz"
141 * which makes sense only if you're a lunatic.
142 * This code looks for the attribute named inside
143 * of the string, and if it exists, adds it as a new
146 if (vp->da->attr == 1) {
150 gettoken(&p, newattr, sizeof(newattr));
152 if (dict_attrbyname(newattr) != NULL) {
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)
185 for ( ; vp != NULL; vp = vp->next) {
188 if (vp->da->vendor != 12394) continue;
189 if (vp->da->type != PW_TYPE_STRING) continue;
191 da = dict_attrbyvalue(number, 12394);
201 * Cablelabs magic, taken from:
203 * http://www.cablelabs.com/packetcable/downloads/specs/PKT-SP-EM-I12-05812.pdf
207 * 0x0001d2d2026d30310000000000003030
208 * 3130303030000e812333000100033031
209 * 00000000000030303130303030000000
210 * 00063230313230313331303630323231
211 * 2e3633390000000081000500
214 typedef struct cl_timezone_t {
222 typedef struct cl_bcid_t {
224 uint8_t element_id[8];
225 cl_timezone_t timezone;
226 uint32_t event_counter;
229 typedef struct cl_em_hdr_t {
232 uint16_t message_type;
233 uint16_t element_type;
234 uint8_t element_id[8];
235 cl_timezone_t time_zone;
236 uint32_t sequence_number;
237 uint8_t event_time[18];
240 uint16_t attr_count; /* of normal Cablelabs VSAs */
241 uint8_t event_object;
245 static void cablelabs_vsa_hack(VALUE_PAIR **list)
249 ev = pairfind(*list, 1, 4491, TAG_ANY); /* Cablelabs-Event-Message */
253 * FIXME: write 100's of lines of code to decode
254 * each data structure above.
261 * Mangle username if needed, IN PLACE.
263 static void rad_mangle(rlm_preprocess_t *data, REQUEST *request)
266 VALUE_PAIR *namepair;
267 VALUE_PAIR *request_pairs;
271 * Get the username from the request
272 * If it isn't there, then we can't mangle the request.
274 request_pairs = request->packet->vps;
275 namepair = pairfind(request_pairs, PW_USER_NAME, 0, TAG_ANY);
276 if ((namepair == NULL) ||
277 (namepair->length <= 0)) {
281 if (data->with_ntdomain_hack) {
283 char newname[MAX_STRING_LEN];
286 * Windows NT machines often authenticate themselves as
287 * NT_DOMAIN\username. Try to be smart about this.
289 * FIXME: should we handle this as a REALM ?
291 if ((ptr = strchr(namepair->vp_strvalue, '\\')) != NULL) {
292 strlcpy(newname, ptr + 1, sizeof(newname));
294 strcpy(namepair->vp_strvalue, newname);
295 namepair->length = strlen(newname);
299 if (data->with_specialix_jetstream_hack) {
303 * Specialix Jetstream 8500 24 port access server.
304 * If the user name is 10 characters or longer, a "/"
305 * and the excess characters after the 10th are
306 * appended to the user name.
308 * Reported by Lucas Heise <root@laonet.net>
310 if ((strlen((char *)namepair->vp_strvalue) > 10) &&
311 (namepair->vp_strvalue[10] == '/')) {
312 for (ptr = (char *)namepair->vp_strvalue + 11; *ptr; ptr++)
315 namepair->length = strlen((char *)namepair->vp_strvalue);
320 * Small check: if Framed-Protocol present but Service-Type
321 * is missing, add Service-Type = Framed-User.
323 if (pairfind(request_pairs, PW_FRAMED_PROTOCOL, 0, TAG_ANY) != NULL &&
324 pairfind(request_pairs, PW_SERVICE_TYPE, 0, TAG_ANY) == NULL) {
325 tmp = radius_paircreate(request, &request->packet->vps,
327 tmp->vp_integer = PW_FRAMED_USER;
331 for (tmp = request->packet->vps; tmp != NULL; tmp = tmp->next) {
332 if (tmp->da->vendor != 0) continue;
333 if (tmp->da->attr != PW_PROXY_STATE) continue;
338 if (num_proxy_state > 10) {
339 RDEBUGW("There are more than 10 Proxy-State attributes in the request.");
340 RDEBUGW("You have likely configured an infinite proxy loop.");
345 * Compare the request with the "reply" part in the
346 * huntgroup, which normally only contains username or group.
347 * At least one of the "reply" items has to match.
349 static int hunt_paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check)
351 VALUE_PAIR *check_item = check;
355 if (check == NULL) return 0;
357 while (result != 0 && check_item != NULL) {
359 tmp = check_item->next;
360 check_item->next = NULL;
362 result = paircompare(req, request, check_item, NULL);
364 check_item->next = tmp;
365 check_item = check_item->next;
373 * Add hints to the info sent by the terminal server
374 * based on the pattern of the username, and other attributes.
376 static int hints_setup(PAIR_LIST *hints, REQUEST *request)
382 VALUE_PAIR *request_pairs;
386 request_pairs = request->packet->vps;
388 if (hints == NULL || request_pairs == NULL)
389 return RLM_MODULE_NOOP;
392 * Check for valid input, zero length names not permitted
394 if ((tmp = pairfind(request_pairs, PW_USER_NAME, 0, TAG_ANY)) == NULL)
397 name = (char *)tmp->vp_strvalue;
399 if (name == NULL || name[0] == 0)
401 * No name, nothing to do.
403 return RLM_MODULE_NOOP;
405 for (i = hints; i; i = i->next) {
407 * Use "paircompare", which is a little more general...
409 if (((strcmp(i->name, "DEFAULT") == 0) ||
410 (strcmp(i->name, name) == 0)) &&
411 (paircompare(request, request_pairs, i->check, NULL) == 0)) {
412 RDEBUG2(" hints: Matched %s at %d",
415 * Now add all attributes to the request list,
416 * except PW_STRIP_USER_NAME and PW_FALL_THROUGH
419 add = paircopy(i->reply);
420 ft = fallthrough(add);
421 pairdelete(&add, PW_STRIP_USER_NAME, 0, TAG_ANY);
422 pairdelete(&add, PW_FALL_THROUGH, 0, TAG_ANY);
423 radius_xlat_move(request, &request->packet->vps, &add);
430 if (updated == 0) return RLM_MODULE_NOOP;
432 return RLM_MODULE_UPDATED;
436 * See if we have access to the huntgroup.
438 static int huntgroup_access(REQUEST *request, PAIR_LIST *huntgroups)
441 int r = RLM_MODULE_OK;
442 VALUE_PAIR *request_pairs = request->packet->vps;
445 * We're not controlling access by huntgroups:
448 if (huntgroups == NULL)
449 return RLM_MODULE_OK;
451 for(i = huntgroups; i; i = i->next) {
453 * See if this entry matches.
455 if (paircompare(request, request_pairs, i->check, NULL) != 0)
459 * Now check for access.
461 r = RLM_MODULE_REJECT;
462 if (hunt_paircmp(request, request_pairs, i->reply) == 0) {
466 * We've matched the huntgroup, so add it in
467 * to the list of request pairs.
469 vp = pairfind(request_pairs, PW_HUNTGROUP_NAME, 0, TAG_ANY);
471 vp = radius_paircreate(request,
472 &request->packet->vps,
473 PW_HUNTGROUP_NAME, 0);
474 strlcpy(vp->vp_strvalue, i->name,
475 sizeof(vp->vp_strvalue));
476 vp->length = strlen(vp->vp_strvalue);
487 * If the NAS wasn't smart enought to add a NAS-IP-Address
488 * to the request, then add it ourselves.
490 static int add_nas_attr(REQUEST *request)
494 switch (request->packet->src_ipaddr.af) {
496 nas = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS, 0, TAG_ANY);
498 nas = radius_paircreate(request, &request->packet->vps,
499 PW_NAS_IP_ADDRESS, 0);
500 nas->vp_ipaddr = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr;
505 nas = pairfind(request->packet->vps, PW_NAS_IPV6_ADDRESS, 0, TAG_ANY);
507 nas = radius_paircreate(request, &request->packet->vps,
508 PW_NAS_IPV6_ADDRESS, 0);
509 memcpy(nas->vp_strvalue,
510 &request->packet->src_ipaddr.ipaddr,
511 sizeof(request->packet->src_ipaddr.ipaddr));
516 radlog(L_ERR, "Unknown address family for packet");
527 static int preprocess_instantiate(CONF_SECTION *conf, void **instance)
530 rlm_preprocess_t *data;
533 * Allocate room to put the module's instantiation data.
535 *instance = data = talloc_zero(conf, rlm_preprocess_t);
536 if (!data) return -1;
539 * Read this modules configuration data.
541 if (cf_section_parse(conf, data, module_config) < 0) {
545 data->huntgroups = NULL;
549 * Read the huntgroups file.
551 if (data->huntgroup_file) {
552 rcode = pairlist_read(data->huntgroup_file,
553 &(data->huntgroups), 0);
555 radlog(L_ERR, "rlm_preprocess: Error reading %s",
556 data->huntgroup_file);
562 * Read the hints file.
564 if (data->hints_file) {
565 rcode = pairlist_read(data->hints_file, &(data->hints), 0);
567 radlog(L_ERR, "rlm_preprocess: Error reading %s",
577 * Preprocess a request.
579 static rlm_rcode_t preprocess_authorize(void *instance, REQUEST *request)
582 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
585 * Mangle the username, to get rid of stupid implementation
588 rad_mangle(data, request);
590 if (data->with_ascend_hack) {
592 * If we're using Ascend systems, hack the NAS-Port-Id
593 * in place, to go from Ascend's weird values to something
594 * approaching rationality.
596 ascend_nasport_hack(pairfind(request->packet->vps, PW_NAS_PORT, 0, TAG_ANY),
597 data->ascend_channels_per_line);
600 if (data->with_cisco_vsa_hack) {
602 * We need to run this hack because the h323-conf-id
603 * attribute should be used.
605 cisco_vsa_hack(request->packet->vps);
608 if (data->with_alvarion_vsa_hack) {
610 * We need to run this hack because the Alvarion
613 alvarion_vsa_hack(request->packet->vps);
616 if (data->with_cablelabs_vsa_hack) {
618 * We need to run this hack because the Cablelabs
621 cablelabs_vsa_hack(&request->packet->vps);
625 * Note that we add the Request-Src-IP-Address to the request
626 * structure BEFORE checking huntgroup access. This allows
627 * the Request-Src-IP-Address to be used for huntgroup
630 if (add_nas_attr(request) < 0) {
631 return RLM_MODULE_FAIL;
634 hints_setup(data->hints, request);
637 * If there is a PW_CHAP_PASSWORD attribute but there
638 * is PW_CHAP_CHALLENGE we need to add it so that other
639 * modules can use it as a normal attribute.
641 if (pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY) &&
642 pairfind(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY) == NULL) {
645 vp = radius_paircreate(request, &request->packet->vps,
646 PW_CHAP_CHALLENGE, 0);
647 vp->length = AUTH_VECTOR_LEN;
648 memcpy(vp->vp_strvalue, request->packet->vector, AUTH_VECTOR_LEN);
651 if ((r = huntgroup_access(request,
652 data->huntgroups)) != RLM_MODULE_OK) {
654 radlog_request(L_AUTH, 0, request, "No huntgroup access: [%s] (%s)",
655 request->username ? request->username->vp_strvalue : "<NO User-Name>",
656 auth_name(buf, sizeof(buf), request, 1));
660 return RLM_MODULE_OK; /* Meaning: try next authorization module */
664 * Preprocess a request before accounting
666 static rlm_rcode_t preprocess_preaccounting(void *instance, REQUEST *request)
670 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
673 * Ensure that we have the SAME user name for both
674 * authentication && accounting.
676 rad_mangle(data, request);
678 if (data->with_cisco_vsa_hack) {
680 * We need to run this hack because the h323-conf-id
681 * attribute should be used.
683 cisco_vsa_hack(request->packet->vps);
686 if (data->with_alvarion_vsa_hack) {
688 * We need to run this hack because the Alvarion
691 alvarion_vsa_hack(request->packet->vps);
694 if (data->with_cablelabs_vsa_hack) {
696 * We need to run this hack because the Cablelabs
699 cablelabs_vsa_hack(&request->packet->vps);
703 * Ensure that we log the NAS IP Address in the packet.
705 if (add_nas_attr(request) < 0) {
706 return RLM_MODULE_FAIL;
709 hints_setup(data->hints, request);
712 * Add an event timestamp. This means that the rest of
713 * the server can use it, rather than various error-prone
714 * manual calculations.
716 vp = pairfind(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY);
720 vp = radius_paircreate(request, &request->packet->vps,
721 PW_EVENT_TIMESTAMP, 0);
722 vp->vp_date = request->packet->timestamp.tv_sec;
723 delay = pairfind(request->packet->vps, PW_ACCT_DELAY_TIME, 0, TAG_ANY);
724 if (delay) vp->vp_date -= delay->vp_integer;
727 if ((r = huntgroup_access(request,
728 data->huntgroups)) != RLM_MODULE_OK) {
730 radlog_request(L_INFO, 0, request, "No huntgroup access: [%s] (%s)",
731 request->username ? request->username->vp_strvalue : "<NO User-Name>",
732 auth_name(buf, sizeof(buf), request, 1));
740 * Clean up the module's instance.
742 static int preprocess_detach(void *instance)
744 rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
746 pairlist_free(&(data->huntgroups));
747 pairlist_free(&(data->hints));
752 /* globally exported name */
753 module_t rlm_preprocess = {
756 RLM_TYPE_CHECK_CONFIG_SAFE, /* type */
757 preprocess_instantiate, /* instantiation */
758 preprocess_detach, /* detach */
760 NULL, /* authentication */
761 preprocess_authorize, /* authorization */
762 preprocess_preaccounting, /* pre-accounting */
763 NULL, /* accounting */
764 NULL, /* checksimul */
765 NULL, /* pre-proxy */
766 NULL, /* post-proxy */