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