41e200e158b13d9deaed25f8ebc23d255b53881d
[freeradius.git] / src / modules / rlm_preprocess / rlm_preprocess.c
1 /*
2  * rlm_preprocess.c
3  *              Contains the functions for the "huntgroups" and "hints"
4  *              files.
5  *
6  * Version:     $Id$
7  *
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.
12  *
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.
17  *
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
21  *
22  * Copyright 2000,2006  The FreeRADIUS server project
23  * Copyright 2000  Alan DeKok <aland@ox.org>
24  */
25
26 #include        <freeradius-devel/ident.h>
27 RCSID("$Id$")
28
29 #include        <freeradius-devel/radiusd.h>
30 #include        <freeradius-devel/modules.h>
31 #include        <freeradius-devel/rad_assert.h>
32
33 #include        <ctype.h>
34
35 typedef struct rlm_preprocess_t {
36         char            *huntgroup_file;
37         char            *hints_file;
38         PAIR_LIST       *huntgroups;
39         PAIR_LIST       *hints;
40         int             with_ascend_hack;
41         int             ascend_channels_per_line;
42         int             with_ntdomain_hack;
43         int             with_specialix_jetstream_hack;
44         int             with_cisco_vsa_hack;
45         int             with_alvarion_vsa_hack;
46 } rlm_preprocess_t;
47
48 static const CONF_PARSER module_config[] = {
49         { "huntgroups",                 PW_TYPE_FILENAME,
50           offsetof(rlm_preprocess_t,huntgroup_file), NULL,
51           "${raddbdir}/huntgroups" },
52         { "hints",                      PW_TYPE_FILENAME,
53           offsetof(rlm_preprocess_t,hints_file), NULL,
54           "${raddbdir}/hints" },
55         { "with_ascend_hack",           PW_TYPE_BOOLEAN,
56           offsetof(rlm_preprocess_t,with_ascend_hack), NULL, "no" },
57         { "ascend_channels_per_line",   PW_TYPE_INTEGER,
58           offsetof(rlm_preprocess_t,ascend_channels_per_line), NULL, "23" },
59
60         { "with_ntdomain_hack",         PW_TYPE_BOOLEAN,
61           offsetof(rlm_preprocess_t,with_ntdomain_hack), NULL, "no" },
62         { "with_specialix_jetstream_hack",  PW_TYPE_BOOLEAN,
63           offsetof(rlm_preprocess_t,with_specialix_jetstream_hack), NULL,
64           "no" },
65         { "with_cisco_vsa_hack",        PW_TYPE_BOOLEAN,
66           offsetof(rlm_preprocess_t,with_cisco_vsa_hack), NULL, "no" },
67         { "with_alvarion_vsa_hack",        PW_TYPE_BOOLEAN,
68           offsetof(rlm_preprocess_t,with_alvarion_vsa_hack), NULL, "no" },
69
70         { NULL, -1, 0, NULL, NULL }
71 };
72
73 /*
74  *     See if a VALUE_PAIR list contains Fall-Through = Yes
75  */
76 static int fallthrough(VALUE_PAIR *vp)
77 {
78         VALUE_PAIR *tmp;
79         tmp = pairfind(vp, PW_FALL_THROUGH, 0);
80
81         return tmp ? tmp->vp_integer : 0;
82 }
83
84 /*
85  *      dgreer --
86  *      This hack changes Ascend's wierd port numberings
87  *      to standard 0-??? port numbers so that the "+" works
88  *      for IP address assignments.
89  */
90 static void ascend_nasport_hack(VALUE_PAIR *nas_port, int channels_per_line)
91 {
92         int service;
93         int line;
94         int channel;
95
96         if (!nas_port) {
97                 return;
98         }
99
100         if (nas_port->vp_integer > 9999) {
101                 service = nas_port->vp_integer/10000; /* 1=digital 2=analog */
102                 line = (nas_port->vp_integer - (10000 * service)) / 100;
103                 channel = nas_port->vp_integer-((10000 * service)+(100 * line));
104                 nas_port->vp_integer =
105                         (channel - 1) + (line - 1) * channels_per_line;
106         }
107 }
108
109 /*
110  *      ThomasJ --
111  *      This hack strips out Cisco's VSA duplicities in lines
112  *      (Cisco not implemented VSA's in standard way.
113  *
114  *      Cisco sends it's VSA attributes with the attribute name *again*
115  *      in the string, like:  H323-Attribute = "h323-attribute=value".
116  *      This sort of behaviour is nonsense.
117  */
118 static void cisco_vsa_hack(VALUE_PAIR *vp)
119 {
120         int             vendorcode;
121         char            *ptr;
122         char            newattr[MAX_STRING_LEN];
123
124         for ( ; vp != NULL; vp = vp->next) {
125                 vendorcode = vp->vendor;
126                 if (!((vendorcode == 9) || (vendorcode == 6618))) continue; /* not a Cisco or Quintum VSA, continue */
127
128                 if (vp->type != PW_TYPE_STRING) continue;
129
130                 /*
131                  *  No weird packing.  Ignore it.
132                  */
133                 ptr = strchr(vp->vp_strvalue, '='); /* find an '=' */
134                 if (!ptr) continue;
135
136                 /*
137                  *      Cisco-AVPair's get packed as:
138                  *
139                  *      Cisco-AVPair = "h323-foo-bar = baz"
140                  *      Cisco-AVPair = "h323-foo-bar=baz"
141                  *
142                  *      which makes sense only if you're a lunatic.
143                  *      This code looks for the attribute named inside
144                  *      of the string, and if it exists, adds it as a new
145                  *      attribute.
146                  */
147                 if (vp->attribute == 1) {
148                         const char *p;
149                         DICT_ATTR       *dattr;
150
151                         p = vp->vp_strvalue;
152                         gettoken(&p, newattr, sizeof(newattr));
153
154                         if ((dattr = dict_attrbyname(newattr)) != NULL) {
155                                 VALUE_PAIR *newvp;
156
157                                 /*
158                                  *  Make a new attribute.
159                                  */
160                                 newvp = pairmake(newattr, ptr + 1, T_OP_EQ);
161                                 if (newvp) {
162                                         pairadd(&vp, newvp);
163                                 }
164                         }
165                 } else {        /* h322-foo-bar = "h323-foo-bar = baz" */
166                         /*
167                          *      We strip out the duplicity from the
168                          *      value field, we use only the value on
169                          *      the right side of the '=' character.
170                          */
171                         strlcpy(newattr, ptr + 1, sizeof(newattr));
172                         strlcpy((char *)vp->vp_strvalue, newattr,
173                                 sizeof(vp->vp_strvalue));
174                         vp->length = strlen((char *)vp->vp_strvalue);
175                 }
176         }
177 }
178
179
180 /*
181  *      Don't even ask what this is doing...
182  */
183 static void alvarion_vsa_hack(VALUE_PAIR *vp)
184 {
185         int             number = 1;
186
187         for ( ; vp != NULL; vp = vp->next) {
188                 DICT_ATTR *da;
189
190                 if (vp->vendor != 12394) continue;
191                 if (vp->type != PW_TYPE_STRING) continue;
192
193                 da = dict_attrbyvalue(number, 12394);
194                 if (!da) continue;
195
196                 vp->attribute = da->attr;
197                 vp->name = da->name;
198
199                 number++;
200         }
201 }
202
203 /*
204  *      Mangle username if needed, IN PLACE.
205  */
206 static void rad_mangle(rlm_preprocess_t *data, REQUEST *request)
207 {
208         int             num_proxy_state;
209         VALUE_PAIR      *namepair;
210         VALUE_PAIR      *request_pairs;
211         VALUE_PAIR      *tmp;
212
213         /*
214          *      Get the username from the request
215          *      If it isn't there, then we can't mangle the request.
216          */
217         request_pairs = request->packet->vps;
218         namepair = pairfind(request_pairs, PW_USER_NAME, 0);
219         if ((namepair == NULL) ||
220             (namepair->length <= 0)) {
221           return;
222         }
223
224         if (data->with_ntdomain_hack) {
225                 char            *ptr;
226                 char            newname[MAX_STRING_LEN];
227
228                 /*
229                  *      Windows NT machines often authenticate themselves as
230                  *      NT_DOMAIN\username. Try to be smart about this.
231                  *
232                  *      FIXME: should we handle this as a REALM ?
233                  */
234                 if ((ptr = strchr(namepair->vp_strvalue, '\\')) != NULL) {
235                         strlcpy(newname, ptr + 1, sizeof(newname));
236                         /* Same size */
237                         strcpy(namepair->vp_strvalue, newname);
238                         namepair->length = strlen(newname);
239                 }
240         }
241
242         if (data->with_specialix_jetstream_hack) {
243                 char            *ptr;
244
245                 /*
246                  *      Specialix Jetstream 8500 24 port access server.
247                  *      If the user name is 10 characters or longer, a "/"
248                  *      and the excess characters after the 10th are
249                  *      appended to the user name.
250                  *
251                  *      Reported by Lucas Heise <root@laonet.net>
252                  */
253                 if ((strlen((char *)namepair->vp_strvalue) > 10) &&
254                     (namepair->vp_strvalue[10] == '/')) {
255                         for (ptr = (char *)namepair->vp_strvalue + 11; *ptr; ptr++)
256                                 *(ptr - 1) = *ptr;
257                         *(ptr - 1) = 0;
258                         namepair->length = strlen((char *)namepair->vp_strvalue);
259                 }
260         }
261
262         /*
263          *      Small check: if Framed-Protocol present but Service-Type
264          *      is missing, add Service-Type = Framed-User.
265          */
266         if (pairfind(request_pairs, PW_FRAMED_PROTOCOL, 0) != NULL &&
267             pairfind(request_pairs, PW_SERVICE_TYPE, 0) == NULL) {
268                 tmp = radius_paircreate(request, &request->packet->vps,
269                                         PW_SERVICE_TYPE, 0, PW_TYPE_INTEGER);
270                 tmp->vp_integer = PW_FRAMED_USER;
271         }
272
273         num_proxy_state = 0;
274         for (tmp = request->packet->vps; tmp != NULL; tmp = tmp->next) {
275                 if (tmp->vendor != 0) continue;
276                 if (tmp->attribute != PW_PROXY_STATE) continue;
277
278                 num_proxy_state++;
279         }
280
281         if (num_proxy_state > 10) {
282                 DEBUG("WARNING: There are more than 10 Proxy-State attributes in the request.");
283                 DEBUG("WARNING: You have likely configured an infinite proxy loop.");
284         }
285 }
286
287 /*
288  *      Compare the request with the "reply" part in the
289  *      huntgroup, which normally only contains username or group.
290  *      At least one of the "reply" items has to match.
291  */
292 static int hunt_paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check)
293 {
294         VALUE_PAIR      *check_item = check;
295         VALUE_PAIR      *tmp;
296         int             result = -1;
297
298         if (check == NULL) return 0;
299
300         while (result != 0 && check_item != NULL) {
301
302                 tmp = check_item->next;
303                 check_item->next = NULL;
304
305                 result = paircompare(req, request, check_item, NULL);
306
307                 check_item->next = tmp;
308                 check_item = check_item->next;
309         }
310
311         return result;
312 }
313
314
315 /*
316  *      Add hints to the info sent by the terminal server
317  *      based on the pattern of the username, and other attributes.
318  */
319 static int hints_setup(PAIR_LIST *hints, REQUEST *request)
320 {
321         char            *name;
322         VALUE_PAIR      *add;
323         VALUE_PAIR      *tmp;
324         PAIR_LIST       *i;
325         VALUE_PAIR *request_pairs;
326         int             updated = 0, ft;
327
328
329         request_pairs = request->packet->vps;
330
331         if (hints == NULL || request_pairs == NULL)
332                 return RLM_MODULE_NOOP;
333
334         /*
335          *      Check for valid input, zero length names not permitted
336          */
337         if ((tmp = pairfind(request_pairs, PW_USER_NAME, 0)) == NULL)
338                 name = NULL;
339         else
340                 name = (char *)tmp->vp_strvalue;
341
342         if (name == NULL || name[0] == 0)
343                 /*
344                  *      No name, nothing to do.
345                  */
346                 return RLM_MODULE_NOOP;
347
348         for (i = hints; i; i = i->next) {
349                 /*
350                  *      Use "paircompare", which is a little more general...
351                  */
352                 if (((strcmp(i->name, "DEFAULT") == 0) ||
353                      (strcmp(i->name, name) == 0)) &&
354                     (paircompare(request, request_pairs, i->check, NULL) == 0)) {
355                         RDEBUG2("  hints: Matched %s at %d",
356                                i->name, i->lineno);
357                         /*
358                          *      Now add all attributes to the request list,
359                          *      except PW_STRIP_USER_NAME and PW_FALL_THROUGH
360                          *      and xlat them.
361                          */
362                         add = paircopy(i->reply);
363                         ft = fallthrough(add);
364                         pairdelete(&add, PW_STRIP_USER_NAME, 0);
365                         pairdelete(&add, PW_FALL_THROUGH, 0);
366                         pairxlatmove(request, &request->packet->vps, &add);
367                         pairfree(&add);
368                         updated = 1;
369                         if (!ft) break;
370                 }
371         }
372
373         if (updated == 0) return RLM_MODULE_NOOP;
374
375         return RLM_MODULE_UPDATED;
376 }
377
378 /*
379  *      See if we have access to the huntgroup.
380  */
381 static int huntgroup_access(REQUEST *request, PAIR_LIST *huntgroups)
382 {
383         PAIR_LIST       *i;
384         int             r = RLM_MODULE_OK;
385         VALUE_PAIR      *request_pairs = request->packet->vps;
386
387         /*
388          *      We're not controlling access by huntgroups:
389          *      Allow them in.
390          */
391         if (huntgroups == NULL)
392                 return RLM_MODULE_OK;
393
394         for(i = huntgroups; i; i = i->next) {
395                 /*
396                  *      See if this entry matches.
397                  */
398                 if (paircompare(request, request_pairs, i->check, NULL) != 0)
399                         continue;
400
401                 /*
402                  *      Now check for access.
403                  */
404                 r = RLM_MODULE_REJECT;
405                 if (hunt_paircmp(request, request_pairs, i->reply) == 0) {
406                         VALUE_PAIR *vp;
407
408                         /*
409                          *  We've matched the huntgroup, so add it in
410                          *  to the list of request pairs.
411                          */
412                         vp = pairfind(request_pairs, PW_HUNTGROUP_NAME, 0);
413                         if (!vp) {
414                                 vp = radius_paircreate(request,
415                                                        &request->packet->vps,
416                                                        PW_HUNTGROUP_NAME, 0,
417                                                        PW_TYPE_STRING);
418                                 strlcpy(vp->vp_strvalue, i->name,
419                                         sizeof(vp->vp_strvalue));
420                                 vp->length = strlen(vp->vp_strvalue);
421                         }
422                         r = RLM_MODULE_OK;
423                 }
424                 break;
425         }
426
427         return r;
428 }
429
430 /*
431  *      If the NAS wasn't smart enought to add a NAS-IP-Address
432  *      to the request, then add it ourselves.
433  */
434 static int add_nas_attr(REQUEST *request)
435 {
436         VALUE_PAIR *nas;
437
438         switch (request->packet->src_ipaddr.af) {
439         case AF_INET:
440                 nas = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS, 0);
441                 if (!nas) {
442                         nas = radius_paircreate(request, &request->packet->vps,
443                                                 PW_NAS_IP_ADDRESS, 0,
444                                                 PW_TYPE_IPADDR);
445                         nas->vp_ipaddr = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr;
446                 }
447                 break;
448
449         case AF_INET6:
450                 nas = pairfind(request->packet->vps, PW_NAS_IPV6_ADDRESS, 0);
451                 if (!nas) {
452                         nas = radius_paircreate(request, &request->packet->vps,
453                                                 PW_NAS_IPV6_ADDRESS, 0,
454                                                 PW_TYPE_IPV6ADDR);
455                         memcpy(nas->vp_strvalue,
456                                &request->packet->src_ipaddr.ipaddr,
457                                sizeof(request->packet->src_ipaddr.ipaddr));
458                 }
459                 break;
460
461         default:
462                 radlog(L_ERR, "Unknown address family for packet");
463                 return -1;
464         }
465
466         return 0;
467 }
468
469
470 /*
471  *      Initialize.
472  */
473 static int preprocess_instantiate(CONF_SECTION *conf, void **instance)
474 {
475         int     rcode;
476         rlm_preprocess_t *data;
477
478         /*
479          *      Allocate room to put the module's instantiation data.
480          */
481         data = (rlm_preprocess_t *) rad_malloc(sizeof(*data));
482         memset(data, 0, sizeof(*data));
483
484         /*
485          *      Read this modules configuration data.
486          */
487         if (cf_section_parse(conf, data, module_config) < 0) {
488                 free(data);
489                 return -1;
490         }
491
492         data->huntgroups = NULL;
493         data->hints = NULL;
494
495         /*
496          *      Read the huntgroups file.
497          */
498         if (data->huntgroup_file) {
499                 rcode = pairlist_read(data->huntgroup_file,
500                                       &(data->huntgroups), 0);
501                 if (rcode < 0) {
502                         radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
503                                data->huntgroup_file);
504                         return -1;
505                 }
506         }
507
508         /*
509          *      Read the hints file.
510          */
511         if (data->hints_file) {
512                 rcode = pairlist_read(data->hints_file, &(data->hints), 0);
513                 if (rcode < 0) {
514                         radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
515                                data->hints_file);
516                         return -1;
517                 }
518         }
519
520         /*
521          *      Save the instantiation data for later.
522          */
523         *instance = data;
524
525         return 0;
526 }
527
528 /*
529  *      Preprocess a request.
530  */
531 static int preprocess_authorize(void *instance, REQUEST *request)
532 {
533         int r;
534         rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
535
536         /*
537          *      Mangle the username, to get rid of stupid implementation
538          *      bugs.
539          */
540         rad_mangle(data, request);
541
542         if (data->with_ascend_hack) {
543                 /*
544                  *      If we're using Ascend systems, hack the NAS-Port-Id
545                  *      in place, to go from Ascend's weird values to something
546                  *      approaching rationality.
547                  */
548                 ascend_nasport_hack(pairfind(request->packet->vps,
549                                              PW_NAS_PORT, 0),
550                                     data->ascend_channels_per_line);
551         }
552
553         if (data->with_cisco_vsa_hack) {
554                 /*
555                  *      We need to run this hack because the h323-conf-id
556                  *      attribute should be used.
557                  */
558                 cisco_vsa_hack(request->packet->vps);
559         }
560
561         if (data->with_alvarion_vsa_hack) {
562                 /*
563                  *      We need to run this hack because the Alvarion
564                  *      people are crazy.
565                  */
566                 alvarion_vsa_hack(request->packet->vps);
567         }
568
569         /*
570          *      Note that we add the Request-Src-IP-Address to the request
571          *      structure BEFORE checking huntgroup access.  This allows
572          *      the Request-Src-IP-Address to be used for huntgroup
573          *      comparisons.
574          */
575         if (add_nas_attr(request) < 0) {
576                 return RLM_MODULE_FAIL;
577         }
578
579         hints_setup(data->hints, request);
580
581         /*
582          *      If there is a PW_CHAP_PASSWORD attribute but there
583          *      is PW_CHAP_CHALLENGE we need to add it so that other
584          *      modules can use it as a normal attribute.
585          */
586         if (pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0) &&
587             pairfind(request->packet->vps, PW_CHAP_CHALLENGE, 0) == NULL) {
588                 VALUE_PAIR *vp;
589
590                 vp = radius_paircreate(request, &request->packet->vps,
591                                        PW_CHAP_CHALLENGE, 0, PW_TYPE_OCTETS);
592                 vp->length = AUTH_VECTOR_LEN;
593                 memcpy(vp->vp_strvalue, request->packet->vector, AUTH_VECTOR_LEN);
594         }
595
596         if ((r = huntgroup_access(request,
597                                   data->huntgroups)) != RLM_MODULE_OK) {
598                 char buf[1024];
599                 radlog_request(L_AUTH, 0, request, "No huntgroup access: [%s] (%s)",
600                        request->username ? request->username->vp_strvalue : "<NO User-Name>",
601                        auth_name(buf, sizeof(buf), request, 1));
602                 return r;
603         }
604
605         return RLM_MODULE_OK; /* Meaning: try next authorization module */
606 }
607
608 /*
609  *      Preprocess a request before accounting
610  */
611 static int preprocess_preaccounting(void *instance, REQUEST *request)
612 {
613         int r;
614         rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
615
616         /*
617          *  Ensure that we have the SAME user name for both
618          *  authentication && accounting.
619          */
620         rad_mangle(data, request);
621
622         if (data->with_cisco_vsa_hack) {
623                 /*
624                  *      We need to run this hack because the h323-conf-id
625                  *      attribute should be used.
626                  */
627                 cisco_vsa_hack(request->packet->vps);
628         }
629
630         if (data->with_alvarion_vsa_hack) {
631                 /*
632                  *      We need to run this hack because the Alvarion
633                  *      people are crazy.
634                  */
635                 alvarion_vsa_hack(request->packet->vps);
636         }
637
638         /*
639          *  Ensure that we log the NAS IP Address in the packet.
640          */
641         if (add_nas_attr(request) < 0) {
642                 return RLM_MODULE_FAIL;
643         }
644
645         hints_setup(data->hints, request);
646
647         if ((r = huntgroup_access(request,
648                                   data->huntgroups)) != RLM_MODULE_OK) {
649                 char buf[1024];
650                 radlog_request(L_INFO, 0, request, "No huntgroup access: [%s] (%s)",
651                        request->username ? request->username->vp_strvalue : "<NO User-Name>",
652                        auth_name(buf, sizeof(buf), request, 1));
653                 return r;
654         }
655
656         return r;
657 }
658
659 /*
660  *      Clean up the module's instance.
661  */
662 static int preprocess_detach(void *instance)
663 {
664         rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
665
666         pairlist_free(&(data->huntgroups));
667         pairlist_free(&(data->hints));
668
669         free(data);
670
671         return 0;
672 }
673
674 /* globally exported name */
675 module_t rlm_preprocess = {
676         RLM_MODULE_INIT,
677         "preprocess",
678         RLM_TYPE_CHECK_CONFIG_SAFE,     /* type */
679         preprocess_instantiate, /* instantiation */
680         preprocess_detach,      /* detach */
681         {
682                 NULL,                   /* authentication */
683                 preprocess_authorize,   /* authorization */
684                 preprocess_preaccounting, /* pre-accounting */
685                 NULL,                   /* accounting */
686                 NULL,                   /* checksimul */
687                 NULL,                   /* pre-proxy */
688                 NULL,                   /* post-proxy */
689                 NULL                    /* post-auth */
690         },
691 };
692