4f01ddeb47a685f0665cc738677cf46fa8f4186a
[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, 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, check->strvalue);
197                         if (ret == 0 && rest) {
198                                 strncpy(rest, name, namelen - len);
199                                 rest[namelen - len] = 0;
200                         }
201                         break;
202         }
203
204         return ret;
205 }
206
207 /*
208  *      Match a username with a wildcard expression.
209  *      Is very limited for now.
210  */
211 static int matches(char *name, PAIR_LIST *pl, char *matchpart)
212 {
213         int len, wlen;
214         int ret = 0;
215         char *wild = pl->name;
216         VALUE_PAIR *tmp;
217
218         /*
219          *      We now support both:
220          *
221          *              DEFAULT Prefix = "P"
222          *
223          *      and
224          *              P*
225          */
226         if ((tmp = pairfind(pl->check, PW_PREFIX)) != NULL ||
227             (tmp = pairfind(pl->check, PW_SUFFIX)) != NULL) {
228
229                 if (strncmp(pl->name, "DEFAULT", 7) == 0 ||
230                     strcmp(pl->name, name) == 0)
231                         return !presufcmp(tmp, name, matchpart);
232         }
233
234         /*
235          *      Shortcut if there's no '*' in pl->name.
236          */
237         if (strchr(pl->name, '*') == NULL &&
238             (strncmp(pl->name, "DEFAULT", 7) == 0 ||
239              strcmp(pl->name, name) == 0)) {
240                 strcpy(matchpart, name);
241                 return 1;
242         }
243
244         /*
245          *      Normally, we should return 0 here, but we
246          *      support the old * stuff.
247          */
248         len = strlen(name);
249         wlen = strlen(wild);
250
251         if (len == 0 || wlen == 0) return 0;
252
253         if (wild[0] == '*') {
254                 wild++;
255                 wlen--;
256                 if (wlen <= len && strcmp(name + (len - wlen), wild) == 0) {
257                         strcpy(matchpart, name);
258                         matchpart[len - wlen] = 0;
259                         ret = 1;
260                 }
261         } else if (wild[wlen - 1] == '*') {
262                 if (wlen <= len && strncmp(name, wild, wlen - 1) == 0) {
263                         strcpy(matchpart, name + wlen - 1);
264                         ret = 1;
265                 }
266         }
267
268         return ret;
269 }
270
271
272 /*
273  *      Add hints to the info sent by the terminal server
274  *      based on the pattern of the username.
275  */
276 static int hints_setup(REQUEST *request)
277 {
278         char            newname[MAX_STRING_LEN];
279         char            *name;
280         VALUE_PAIR      *add;
281         VALUE_PAIR      *last;
282         VALUE_PAIR      *tmp;
283         PAIR_LIST       *i;
284         int             do_strip;
285         VALUE_PAIR *request_pairs;
286
287         request_pairs = request->packet->vps;
288
289         if (hints == NULL || request_pairs == NULL)
290                 return RLM_MODULE_NOOP;
291
292         /* 
293          *      Check for valid input, zero length names not permitted 
294          */
295         if ((tmp = pairfind(request_pairs, PW_USER_NAME)) == NULL)
296                 name = NULL;
297         else
298                 name = (char *)tmp->strvalue;
299
300         if (name == NULL || name[0] == 0)
301                 /*
302                  *      No name, nothing to do.
303                  */
304                 return RLM_MODULE_NOOP;
305
306         for (i = hints; i; i = i->next) {
307                 if (matches(name, i, newname)) {
308                         DEBUG2("  hints: Matched %s at %d",
309                                i->name, i->lineno);
310                         break;
311                 }
312         }
313
314         if (i == NULL) return RLM_MODULE_NOOP;
315
316         add = paircopy(i->reply);
317
318 #if 0 /* DEBUG */
319         printf("In hints_setup, newname is %s\n", newname);
320 #endif
321
322         /*
323          *      See if we need to adjust the name.
324          */
325         do_strip = 1;
326         if ((tmp = pairfind(i->reply, PW_STRIP_USER_NAME)) != NULL
327              && tmp->lvalue == 0)
328                 do_strip = 0;
329         if ((tmp = pairfind(i->check, PW_STRIP_USER_NAME)) != NULL
330              && tmp->lvalue == 0)
331                 do_strip = 0;
332
333         if (do_strip) {
334                 tmp = pairfind(request_pairs, PW_STRIPPED_USER_NAME);
335                 if (tmp) {
336                         strcpy(tmp->strvalue, newname);
337                         tmp->length = strlen((char *)tmp->strvalue);
338                 } else {
339                         /*
340                          *      No Stripped-User-Name exists: add one.
341                          */
342                         tmp = paircreate(PW_STRIPPED_USER_NAME, PW_TYPE_STRING);
343                         if (!tmp) {
344                                 radlog(L_ERR|L_CONS, "no memory");
345                                 exit(1);
346                         }
347                         strcpy(tmp->strvalue, newname);
348                         tmp->length = strlen((char *)tmp->strvalue);
349                         pairadd(&request_pairs, tmp);
350                 }
351                 request->username = tmp;
352         }
353
354         /*
355          *      Now add all attributes to the request list,
356          *      except the PW_STRIP_USER_NAME one.
357          */
358         pairdelete(&add, PW_STRIP_USER_NAME);
359         for(last = request_pairs; last && last->next; last = last->next)
360                 ;
361         if (last) last->next = add;
362
363         return RLM_MODULE_UPDATED;
364 }
365
366 /*
367  *      See if the huntgroup matches. This function is
368  *      tied to the "Huntgroup" keyword.
369  */
370 static int huntgroup_cmp(VALUE_PAIR *request, VALUE_PAIR *check,
371         VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
372 {
373         PAIR_LIST       *i;
374         char            *huntgroup;
375
376         check_pairs = check_pairs; /* shut the compiler up */
377         reply_pairs = reply_pairs;
378
379         huntgroup = (char *)check->strvalue;
380
381         for (i = huntgroups; i; i = i->next) {
382                 if (strcmp(i->name, huntgroup) != 0)
383                         continue;
384                 if (paircmp(request, i->check, NULL) == 0) {
385                         DEBUG2("  huntgroups: Matched %s at %d",
386                                i->name, i->lineno);
387                         break;
388                 }
389         }
390
391         /*
392          *      paircmp() expects to see zero on match, so let's
393          *      keep it happy.
394          */
395         if (i == NULL) {
396                 return -1;
397         }
398         return 0;
399 }
400
401
402 /*
403  *      See if we have access to the huntgroup.
404  */
405 static int huntgroup_access(VALUE_PAIR *request_pairs)
406 {
407         PAIR_LIST       *i;
408         int             r = RLM_MODULE_OK;
409
410         /*
411          *      We're not controlling access by huntgroups:
412          *      Allow them in.
413          */
414         if (huntgroups == NULL)
415                 return RLM_MODULE_OK;
416
417         for(i = huntgroups; i; i = i->next) {
418                 /*
419                  *      See if this entry matches.
420                  */
421                 if (paircmp(request_pairs, i->check, NULL) != 0)
422                         continue;
423
424                 /*
425                  *      Now check for access.
426                  */
427                 r = RLM_MODULE_REJECT;
428                 if (hunt_paircmp(request_pairs, i->reply) == 0) {
429                         r = RLM_MODULE_OK;
430                 }
431                 break;
432         }
433
434         return r;
435 }
436
437 /*
438  *      If the NAS wasn't smart enought to add a NAS-IP-Address
439  *      to the request, then add it ourselves.
440  */
441 static void add_nas_attr(REQUEST *request)
442 {
443         VALUE_PAIR *nas;
444
445         nas = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS);
446         if (!nas) {
447                 nas = paircreate(PW_NAS_IP_ADDRESS, PW_TYPE_IPADDR);
448                 if (!nas) {
449                         radlog(L_ERR, "No memory");
450                         exit(1);
451                 }
452                 nas->lvalue = request->packet->src_ipaddr;
453                 pairadd(&request->packet->vps, nas);
454         }
455
456         /*
457          *      Add in a Request-Src-IP-Address, to tell the user
458          *      the source IP of the request.  That is, the client,
459          *      but Client-IP-Address is too close to the old
460          *      Client-FOO names, which I KNOW would confuse a lot
461          *      of people.
462          *
463          *      Note that this MAY BE different from the NAS-IP-Address,
464          *      especially if the request is being proxied.
465          *
466          *      Note also that this is a server configuration item,
467          *      and will NOT make it to any packets being sent from
468          *      the server.
469          */
470         nas = paircreate(PW_REQUEST_SRC_IP_ADDRESS, PW_TYPE_IPADDR);
471         if (!nas) {
472           radlog(L_ERR, "No memory");
473           exit(1);
474         }
475         nas->lvalue = request->packet->src_ipaddr;
476         pairadd(&request->packet->vps, nas);
477 }
478
479
480 /*
481  *      Initialize.
482  */
483 static int preprocess_init(void)
484 {
485         int     rcode;
486         char    buffer[256];
487
488         pairlist_free(&huntgroups);
489         pairlist_free(&hints);
490
491         sprintf(buffer, "%s/%s", radius_dir, RADIUS_HUNTGROUPS);
492         rcode = pairlist_read(buffer, &huntgroups, 0);
493         if (rcode < 0) {
494                 return -1;
495         }
496
497         sprintf(buffer, "%s/%s", radius_dir, RADIUS_HINTS);
498         rcode = pairlist_read(buffer, &hints, 0);
499         if (rcode < 0) {
500                 return -1;
501         }
502
503         paircompare_register(PW_HUNTGROUP_NAME, 0, huntgroup_cmp);
504
505         return 0;
506 }
507
508 /*
509  *      Preprocess a request.
510  */
511 static int preprocess_authorize(void *instance, REQUEST *request)
512 {
513         char buf[1024];
514
515         instance = instance;
516
517         /*
518          *      Mangle the username, to get rid of stupid implementation
519          *      bugs.
520          */
521         rad_mangle(request);
522
523 #ifdef WITH_ASCEND_HACK
524         /*
525          *      If we're using Ascend systems, hack the NAS-Port-Id
526          *      in place, to go from Ascend's weird values to something
527          *      approaching rationality.
528          */
529         ascend_nasport_hack(pairfind(request->packet->vps, PW_NAS_PORT_ID));
530 #endif
531
532         hints_setup(request);
533         
534         /*
535          *      Note that we add the Request-Src-IP-Address to the request
536          *      structure BEFORE checking huntgroup access.  This allows
537          *      the Request-Src-IP-Address to be used for huntgroup
538          *      comparisons.
539          */
540         add_nas_attr(request);
541
542         if (huntgroup_access(request->packet->vps) != RLM_MODULE_OK) {
543                 radlog(L_AUTH, "No huntgroup access: [%s] (%s)",
544                     request->username->strvalue,
545                     auth_name(buf, sizeof(buf), request, 1));
546                 return RLM_MODULE_REJECT;
547         }
548
549         return RLM_MODULE_OK; /* Meaning: try next authorization module */
550 }
551
552 /*
553  *      Preprocess a request before accounting
554  */
555 static int preprocess_preaccounting(void *instance, REQUEST *request)
556 {
557         int r;
558
559         instance = instance;
560         /*
561          *  Ensure that we have the SAME user name for both
562          *  authentication && accounting.
563          */
564         rad_mangle(request);
565         r = hints_setup(request);
566
567         /*
568          *  Ensure that we log the NAS IP Address in the packet.
569          */
570         add_nas_attr(request);
571
572         return r;
573 }
574
575 /*
576  *      Clean up.
577  */
578 static int preprocess_destroy(void)
579 {
580         paircompare_unregister(PW_HUNTGROUP_NAME, huntgroup_cmp);
581         pairlist_free(&huntgroups);
582         pairlist_free(&hints);
583
584         return 0;
585 }
586
587 /* globally exported name */
588 module_t rlm_preprocess = {
589         "preprocess",
590         0,                              /* type: reserved */
591         preprocess_init,                /* initialization */
592         NULL,                           /* instantiation */
593         preprocess_authorize,           /* authorization */
594         NULL,                           /* authentication */
595         preprocess_preaccounting,       /* pre-accounting */
596         NULL,                           /* accounting */
597         NULL,                           /* detach */
598         preprocess_destroy,             /* destroy */
599 };
600