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