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