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