This is the bigger part of the modular session framework. It is not ready to
[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  */
9
10 static const char rcsid[] = "$Id$";
11
12 #include        "autoconf.h"
13
14 #include        <sys/types.h>
15 #include        <sys/time.h>
16 #include        <sys/stat.h>
17
18 #include        <stdio.h>
19 #include        <stdlib.h>
20 #include        <string.h>
21 #include        <time.h>
22 #include        <ctype.h>
23
24 #if HAVE_MALLOC_H
25 #  include      <malloc.h>
26 #endif
27
28 #include        "radiusd.h"
29 #include        "modules.h"
30
31
32 /* FIXME: should this stuff be instance data? */
33 static PAIR_LIST        *huntgroups;
34 static PAIR_LIST        *hints;
35
36 #ifdef WITH_ASCEND_HACK
37 /*
38  *      dgreer --
39  *      This hack changes Ascend's wierd port numberings
40  *      to standard 0-??? port numbers so that the "+" works
41  *      for IP address assignments.
42  */
43 static void ascend_nasport_hack(VALUE_PAIR *nas_port)
44 {
45         int service;
46         int line;
47         int channel;
48
49         if (!nas_port) {
50                 return;
51         }
52
53         if (nas_port->lvalue > 9999) {
54                 service = nas_port->lvalue/10000; /* 1=digital 2=analog */
55                 line = (nas_port->lvalue - (10000 * service)) / 100;
56                 channel = nas_port->lvalue-((10000 * service)+(100 * line));
57                 nas_port->lvalue =
58                         (channel - 1) + (line - 1) * ASCEND_CHANNELS_PER_LINE;
59         }
60 }
61 #endif
62
63 /*
64  *      Mangle username if needed, IN PLACE.
65  */
66 static void rad_mangle(REQUEST *request)
67 {
68         VALUE_PAIR      *namepair;
69         VALUE_PAIR      *request_pairs;
70         VALUE_PAIR      *tmp;
71 #ifdef WITH_NTDOMAIN_HACK
72         char            newname[MAX_STRING_LEN];
73 #endif
74 #if defined(WITH_NTDOMAIN_HACK) || defined(WITH_SPECIALIX_JETSTREAM_HACK)
75         char            *ptr;
76 #endif
77
78         /*
79          *      Get the username from the request
80          *      If it isn't there, then we can't mangle the request.
81          */
82         request_pairs = request->packet->vps;
83         namepair = pairfind(request_pairs, PW_USER_NAME);
84         if ((namepair == NULL) || 
85             (namepair->length <= 0)) {
86           return;
87         }
88
89 #ifdef WITH_NTDOMAIN_HACK
90         /*
91          *      Windows NT machines often authenticate themselves as
92          *      NT_DOMAIN\username. Try to be smart about this.
93          *
94          *      FIXME: should we handle this as a REALM ?
95          */
96         if ((ptr = strchr(namepair->strvalue, '\\')) != NULL) {
97                 strNcpy(newname, ptr + 1, sizeof(newname));
98                 /* Same size */
99                 strcpy(namepair->strvalue, newname);
100                 namepair->length = strlen(newname);
101         }
102 #endif /* WITH_NTDOMAIN_HACK */
103
104 #ifdef WITH_SPECIALIX_JETSTREAM_HACK
105         /*
106          *      Specialix Jetstream 8500 24 port access server.
107          *      If the user name is 10 characters or longer, a "/"
108          *      and the excess characters after the 10th are
109          *      appended to the user name.
110          *
111          *      Reported by Lucas Heise <root@laonet.net>
112          */
113         if (strlen(namepair->strvalue) > 10 && namepair->strvalue[10] == '/') {
114                 for (ptr = namepair->strvalue + 11; *ptr; ptr++)
115                         *(ptr - 1) = *ptr;
116                 *(ptr - 1) = 0;
117                 namepair->length = strlen(namepair->strvalue);
118         }
119 #endif
120
121         /*
122          *      Small check: if Framed-Protocol present but Service-Type
123          *      is missing, add Service-Type = Framed-User.
124          */
125         if (pairfind(request_pairs, PW_FRAMED_PROTOCOL) != NULL &&
126             pairfind(request_pairs, PW_SERVICE_TYPE) == NULL) {
127                 tmp = paircreate(PW_SERVICE_TYPE, PW_TYPE_INTEGER);
128                 if (tmp) {
129                         tmp->lvalue = PW_FRAMED_USER;
130                         pairmove(&request_pairs, &tmp);
131                 }
132         }
133
134 #if 0
135         /*
136          *      FIXME: find some substitute for this, or
137          *      drop the log_auth_detail option all together.
138          */
139         if (log_auth_detail)
140                 rad_accounting_orig(request, -1, "detail.auth");
141 #endif
142 }
143
144 /*
145  *      Compare the request with the "reply" part in the
146  *      huntgroup, which normally only contains username or group.
147  *      At least one of the "reply" items has to match.
148  */
149 static int hunt_paircmp(VALUE_PAIR *request, VALUE_PAIR *check)
150 {
151         VALUE_PAIR      *check_item = check;
152         VALUE_PAIR      *tmp;
153         int             result = -1;
154
155         if (check == NULL) return 0;
156
157         while (result != 0 && check_item != NULL) {
158
159                 tmp = check_item->next;
160                 check_item->next = NULL;
161
162                 result = paircmp(request, check_item, NULL);
163
164                 check_item->next = tmp;
165                 check_item = check_item->next;
166         }
167
168         return result;
169 }
170
171
172 /*
173  *      Compare prefix/suffix
174  */
175 static int presufcmp(VALUE_PAIR *check, char *name, char *rest)
176 {
177         int             len, namelen;
178         int             ret = -1;
179
180 #if 0 /* DEBUG */
181         printf("Comparing %s and %s, check->attr is %d\n",
182                 name, check->strvalue, check->attribute);
183 #endif
184
185         len = strlen((char *)check->strvalue);
186         switch (check->attribute) {
187                 case PW_PREFIX:
188                         ret = strncmp(name, (char *)check->strvalue, len);
189                         if (ret == 0 && rest)
190                                 strcpy(rest, name + len);
191                         break;
192                 case PW_SUFFIX:
193                         namelen = strlen(name);
194                         if (namelen < len)
195                                 break;
196                         ret = strcmp(name + namelen - len,
197                                      (char *)check->strvalue);
198                         if (ret == 0 && rest) {
199                                 strncpy(rest, name, namelen - len);
200                                 rest[namelen - len] = 0;
201                         }
202                         break;
203         }
204
205         return ret;
206 }
207
208 /*
209  *      Match a username with a wildcard expression.
210  *      Is very limited for now.
211  */
212 static int matches(char *name, PAIR_LIST *pl, char *matchpart)
213 {
214         int len, wlen;
215         int ret = 0;
216         char *wild = pl->name;
217         VALUE_PAIR *tmp;
218
219         /*
220          *      We now support both:
221          *
222          *              DEFAULT Prefix = "P"
223          *
224          *      and
225          *              P*
226          */
227         if ((tmp = pairfind(pl->check, PW_PREFIX)) != NULL ||
228             (tmp = pairfind(pl->check, PW_SUFFIX)) != NULL) {
229
230                 if (strncmp(pl->name, "DEFAULT", 7) == 0 ||
231                     strcmp(pl->name, name) == 0)
232                         return !presufcmp(tmp, name, matchpart);
233         }
234
235         /*
236          *      Shortcut if there's no '*' in pl->name.
237          */
238         if (strchr(pl->name, '*') == NULL &&
239             (strncmp(pl->name, "DEFAULT", 7) == 0 ||
240              strcmp(pl->name, name) == 0)) {
241                 strcpy(matchpart, name);
242                 return 1;
243         }
244
245         /*
246          *      Normally, we should return 0 here, but we
247          *      support the old * stuff.
248          */
249         len = strlen(name);
250         wlen = strlen(wild);
251
252         if (len == 0 || wlen == 0) return 0;
253
254         if (wild[0] == '*') {
255                 wild++;
256                 wlen--;
257                 if (wlen <= len && strcmp(name + (len - wlen), wild) == 0) {
258                         strcpy(matchpart, name);
259                         matchpart[len - wlen] = 0;
260                         ret = 1;
261                 }
262         } else if (wild[wlen - 1] == '*') {
263                 if (wlen <= len && strncmp(name, wild, wlen - 1) == 0) {
264                         strcpy(matchpart, name + wlen - 1);
265                         ret = 1;
266                 }
267         }
268
269         return ret;
270 }
271
272
273 /*
274  *      Add hints to the info sent by the terminal server
275  *      based on the pattern of the username.
276  */
277 static int hints_setup(REQUEST *request)
278 {
279         char            newname[MAX_STRING_LEN];
280         char            *name;
281         VALUE_PAIR      *add;
282         VALUE_PAIR      *last;
283         VALUE_PAIR      *tmp;
284         PAIR_LIST       *i;
285         int             do_strip;
286         VALUE_PAIR *request_pairs;
287
288         request_pairs = request->packet->vps;
289
290         if (hints == NULL || request_pairs == NULL)
291                 return RLM_MODULE_NOOP;
292
293         /* 
294          *      Check for valid input, zero length names not permitted 
295          */
296         if ((tmp = pairfind(request_pairs, PW_USER_NAME)) == NULL)
297                 name = NULL;
298         else
299                 name = (char *)tmp->strvalue;
300
301         if (name == NULL || name[0] == 0)
302                 /*
303                  *      No name, nothing to do.
304                  */
305                 return RLM_MODULE_NOOP;
306
307         for (i = hints; i; i = i->next) {
308                 if (matches(name, i, newname)) {
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 #if 0 /* DEBUG */
320         printf("In hints_setup, newname is %s\n", newname);
321 #endif
322
323         /*
324          *      See if we need to adjust the name.
325          */
326         do_strip = 1;
327         if ((tmp = pairfind(i->reply, PW_STRIP_USER_NAME)) != NULL
328              && tmp->lvalue == 0)
329                 do_strip = 0;
330         if ((tmp = pairfind(i->check, PW_STRIP_USER_NAME)) != NULL
331              && tmp->lvalue == 0)
332                 do_strip = 0;
333
334         if (do_strip) {
335                 tmp = pairfind(request_pairs, PW_STRIPPED_USER_NAME);
336                 if (tmp) {
337                         strcpy((char *)tmp->strvalue, newname);
338                         tmp->length = strlen((char *)tmp->strvalue);
339                 } else {
340                         /*
341                          *      No Stripped-User-Name exists: add one.
342                          */
343                         tmp = paircreate(PW_STRIPPED_USER_NAME, PW_TYPE_STRING);
344                         if (!tmp) {
345                                 radlog(L_ERR|L_CONS, "no memory");
346                                 exit(1);
347                         }
348                         strcpy((char *)tmp->strvalue, newname);
349                         tmp->length = strlen((char *)tmp->strvalue);
350                         pairadd(&request_pairs, tmp);
351                 }
352                 request->username = tmp;
353         }
354
355         /*
356          *      Now add all attributes to the request list,
357          *      except the PW_STRIP_USER_NAME one.
358          */
359         pairdelete(&add, PW_STRIP_USER_NAME);
360         for(last = request_pairs; last && last->next; last = last->next)
361                 ;
362         if (last) last->next = add;
363
364         return RLM_MODULE_UPDATED;
365 }
366
367 /*
368  *      See if the huntgroup matches. This function is
369  *      tied to the "Huntgroup" keyword.
370  */
371 static int huntgroup_cmp(VALUE_PAIR *request, VALUE_PAIR *check,
372         VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
373 {
374         PAIR_LIST       *i;
375         char            *huntgroup;
376
377         check_pairs = check_pairs; /* shut the compiler up */
378         reply_pairs = reply_pairs;
379
380         huntgroup = (char *)check->strvalue;
381
382         for (i = huntgroups; i; i = i->next) {
383                 if (strcmp(i->name, huntgroup) != 0)
384                         continue;
385                 if (paircmp(request, i->check, NULL) == 0) {
386                         DEBUG2("  huntgroups: Matched %s at %d",
387                                i->name, i->lineno);
388                         break;
389                 }
390         }
391
392         /*
393          *      paircmp() expects to see zero on match, so let's
394          *      keep it happy.
395          */
396         if (i == NULL) {
397                 return -1;
398         }
399         return 0;
400 }
401
402
403 /*
404  *      See if we have access to the huntgroup.
405  */
406 static int huntgroup_access(VALUE_PAIR *request_pairs)
407 {
408         PAIR_LIST       *i;
409         int             r = RLM_MODULE_OK;
410
411         /*
412          *      We're not controlling access by huntgroups:
413          *      Allow them in.
414          */
415         if (huntgroups == NULL)
416                 return RLM_MODULE_OK;
417
418         for(i = huntgroups; i; i = i->next) {
419                 /*
420                  *      See if this entry matches.
421                  */
422                 if (paircmp(request_pairs, i->check, NULL) != 0)
423                         continue;
424
425                 /*
426                  *      Now check for access.
427                  */
428                 r = RLM_MODULE_REJECT;
429                 if (hunt_paircmp(request_pairs, i->reply) == 0) {
430                         r = RLM_MODULE_OK;
431                 }
432                 break;
433         }
434
435         return r;
436 }
437
438 /*
439  *      If the NAS wasn't smart enought to add a NAS-IP-Address
440  *      to the request, then add it ourselves.
441  */
442 static void add_nas_attr(REQUEST *request)
443 {
444         VALUE_PAIR *nas;
445
446         nas = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS);
447         if (!nas) {
448                 nas = paircreate(PW_NAS_IP_ADDRESS, PW_TYPE_IPADDR);
449                 if (!nas) {
450                         radlog(L_ERR, "No memory");
451                         exit(1);
452                 }
453                 nas->lvalue = request->packet->src_ipaddr;
454                 pairadd(&request->packet->vps, nas);
455         }
456
457         /*
458          *      Add in a Request-Src-IP-Address, to tell the user
459          *      the source IP of the request.  That is, the client,
460          *      but Client-IP-Address is too close to the old
461          *      Client-FOO names, which I KNOW would confuse a lot
462          *      of people.
463          *
464          *      Note that this MAY BE different from the NAS-IP-Address,
465          *      especially if the request is being proxied.
466          *
467          *      Note also that this is a server configuration item,
468          *      and will NOT make it to any packets being sent from
469          *      the server.
470          */
471         nas = paircreate(PW_REQUEST_SRC_IP_ADDRESS, PW_TYPE_IPADDR);
472         if (!nas) {
473           radlog(L_ERR, "No memory");
474           exit(1);
475         }
476         nas->lvalue = request->packet->src_ipaddr;
477         pairadd(&request->packet->vps, nas);
478 }
479
480
481 /*
482  *      Initialize.
483  */
484 static int preprocess_init(void)
485 {
486         int     rcode;
487         char    buffer[256];
488
489         pairlist_free(&huntgroups);
490         pairlist_free(&hints);
491
492         sprintf(buffer, "%s/%s", radius_dir, RADIUS_HUNTGROUPS);
493         rcode = pairlist_read(buffer, &huntgroups, 0);
494         if (rcode < 0) {
495                 return -1;
496         }
497
498         sprintf(buffer, "%s/%s", radius_dir, RADIUS_HINTS);
499         rcode = pairlist_read(buffer, &hints, 0);
500         if (rcode < 0) {
501                 return -1;
502         }
503
504         paircompare_register(PW_HUNTGROUP_NAME, 0, huntgroup_cmp);
505
506         return 0;
507 }
508
509 /*
510  *      Preprocess a request.
511  */
512 static int preprocess_authorize(void *instance, REQUEST *request)
513 {
514         char buf[1024];
515
516         instance = instance;
517
518         /*
519          *      Mangle the username, to get rid of stupid implementation
520          *      bugs.
521          */
522         rad_mangle(request);
523
524 #ifdef WITH_ASCEND_HACK
525         /*
526          *      If we're using Ascend systems, hack the NAS-Port-Id
527          *      in place, to go from Ascend's weird values to something
528          *      approaching rationality.
529          */
530         ascend_nasport_hack(pairfind(request->packet->vps, PW_NAS_PORT_ID));
531 #endif
532
533         hints_setup(request);
534         
535         /*
536          *      Note that we add the Request-Src-IP-Address to the request
537          *      structure BEFORE checking huntgroup access.  This allows
538          *      the Request-Src-IP-Address to be used for huntgroup
539          *      comparisons.
540          */
541         add_nas_attr(request);
542
543         if (huntgroup_access(request->packet->vps) != RLM_MODULE_OK) {
544                 radlog(L_AUTH, "No huntgroup access: [%s] (%s)",
545                     request->username->strvalue,
546                     auth_name(buf, sizeof(buf), request, 1));
547                 return RLM_MODULE_REJECT;
548         }
549
550         return RLM_MODULE_OK; /* Meaning: try next authorization module */
551 }
552
553 /*
554  *      Preprocess a request before accounting
555  */
556 static int preprocess_preaccounting(void *instance, REQUEST *request)
557 {
558         int r;
559
560         instance = instance;
561         /*
562          *  Ensure that we have the SAME user name for both
563          *  authentication && accounting.
564          */
565         rad_mangle(request);
566         r = hints_setup(request);
567
568         /*
569          *  Ensure that we log the NAS IP Address in the packet.
570          */
571         add_nas_attr(request);
572
573         return r;
574 }
575
576 /*
577  *      Clean up.
578  */
579 static int preprocess_destroy(void)
580 {
581         paircompare_unregister(PW_HUNTGROUP_NAME, huntgroup_cmp);
582         pairlist_free(&huntgroups);
583         pairlist_free(&hints);
584
585         return 0;
586 }
587
588 /* globally exported name */
589 module_t rlm_preprocess = {
590         "preprocess",
591         0,                              /* type: reserved */
592         preprocess_init,                /* initialization */
593         NULL,                           /* instantiation */
594         preprocess_authorize,           /* authorization */
595         NULL,                           /* authentication */
596         preprocess_preaccounting,       /* pre-accounting */
597         NULL,                           /* accounting */
598         NULL,                           /* checksimul */
599         NULL,                           /* detach */
600         preprocess_destroy,             /* destroy */
601 };
602