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