Merge branch 'sam'
[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         int             with_cablelabs_vsa_hack;
47 } rlm_preprocess_t;
48
49 static const CONF_PARSER module_config[] = {
50         { "huntgroups",                 PW_TYPE_FILENAME,
51           offsetof(rlm_preprocess_t,huntgroup_file), NULL,
52           "${raddbdir}/huntgroups" },
53         { "hints",                      PW_TYPE_FILENAME,
54           offsetof(rlm_preprocess_t,hints_file), NULL,
55           "${raddbdir}/hints" },
56         { "with_ascend_hack",           PW_TYPE_BOOLEAN,
57           offsetof(rlm_preprocess_t,with_ascend_hack), NULL, "no" },
58         { "ascend_channels_per_line",   PW_TYPE_INTEGER,
59           offsetof(rlm_preprocess_t,ascend_channels_per_line), NULL, "23" },
60
61         { "with_ntdomain_hack",         PW_TYPE_BOOLEAN,
62           offsetof(rlm_preprocess_t,with_ntdomain_hack), NULL, "no" },
63         { "with_specialix_jetstream_hack",  PW_TYPE_BOOLEAN,
64           offsetof(rlm_preprocess_t,with_specialix_jetstream_hack), NULL,
65           "no" },
66         { "with_cisco_vsa_hack",        PW_TYPE_BOOLEAN,
67           offsetof(rlm_preprocess_t,with_cisco_vsa_hack), NULL, "no" },
68         { "with_alvarion_vsa_hack",        PW_TYPE_BOOLEAN,
69           offsetof(rlm_preprocess_t,with_alvarion_vsa_hack), NULL, "no" },
70         { "with_cablelabs_vsa_hack",        PW_TYPE_BOOLEAN,
71           offsetof(rlm_preprocess_t,with_cablelabs_vsa_hack), NULL, NULL },
72
73         { NULL, -1, 0, NULL, NULL }
74 };
75
76 /*
77  *     See if a VALUE_PAIR list contains Fall-Through = Yes
78  */
79 static int fallthrough(VALUE_PAIR *vp)
80 {
81         VALUE_PAIR *tmp;
82         tmp = pairfind(vp, PW_FALL_THROUGH, 0);
83
84         return tmp ? tmp->vp_integer : 0;
85 }
86
87 /*
88  *      dgreer --
89  *      This hack changes Ascend's wierd port numberings
90  *      to standard 0-??? port numbers so that the "+" works
91  *      for IP address assignments.
92  */
93 static void ascend_nasport_hack(VALUE_PAIR *nas_port, int channels_per_line)
94 {
95         int service;
96         int line;
97         int channel;
98
99         if (!nas_port) {
100                 return;
101         }
102
103         if (nas_port->vp_integer > 9999) {
104                 service = nas_port->vp_integer/10000; /* 1=digital 2=analog */
105                 line = (nas_port->vp_integer - (10000 * service)) / 100;
106                 channel = nas_port->vp_integer-((10000 * service)+(100 * line));
107                 nas_port->vp_integer =
108                         (channel - 1) + (line - 1) * channels_per_line;
109         }
110 }
111
112 /*
113  *      ThomasJ --
114  *      This hack strips out Cisco's VSA duplicities in lines
115  *      (Cisco not implemented VSA's in standard way.
116  *
117  *      Cisco sends it's VSA attributes with the attribute name *again*
118  *      in the string, like:  H323-Attribute = "h323-attribute=value".
119  *      This sort of behaviour is nonsense.
120  */
121 static void cisco_vsa_hack(VALUE_PAIR *vp)
122 {
123         int             vendorcode;
124         char            *ptr;
125         char            newattr[MAX_STRING_LEN];
126
127         for ( ; vp != NULL; vp = vp->next) {
128                 vendorcode = vp->vendor;
129                 if (!((vendorcode == 9) || (vendorcode == 6618))) continue; /* not a Cisco or Quintum VSA, continue */
130
131                 if (vp->type != PW_TYPE_STRING) continue;
132
133                 /*
134                  *  No weird packing.  Ignore it.
135                  */
136                 ptr = strchr(vp->vp_strvalue, '='); /* find an '=' */
137                 if (!ptr) continue;
138
139                 /*
140                  *      Cisco-AVPair's get packed as:
141                  *
142                  *      Cisco-AVPair = "h323-foo-bar = baz"
143                  *      Cisco-AVPair = "h323-foo-bar=baz"
144                  *
145                  *      which makes sense only if you're a lunatic.
146                  *      This code looks for the attribute named inside
147                  *      of the string, and if it exists, adds it as a new
148                  *      attribute.
149                  */
150                 if (vp->attribute == 1) {
151                         const char *p;
152                         DICT_ATTR       *dattr;
153
154                         p = vp->vp_strvalue;
155                         gettoken(&p, newattr, sizeof(newattr));
156
157                         if ((dattr = dict_attrbyname(newattr)) != NULL) {
158                                 VALUE_PAIR *newvp;
159
160                                 /*
161                                  *  Make a new attribute.
162                                  */
163                                 newvp = pairmake(newattr, ptr + 1, T_OP_EQ);
164                                 if (newvp) {
165                                         pairadd(&vp, newvp);
166                                 }
167                         }
168                 } else {        /* h322-foo-bar = "h323-foo-bar = baz" */
169                         /*
170                          *      We strip out the duplicity from the
171                          *      value field, we use only the value on
172                          *      the right side of the '=' character.
173                          */
174                         strlcpy(newattr, ptr + 1, sizeof(newattr));
175                         strlcpy((char *)vp->vp_strvalue, newattr,
176                                 sizeof(vp->vp_strvalue));
177                         vp->length = strlen((char *)vp->vp_strvalue);
178                 }
179         }
180 }
181
182
183 /*
184  *      Don't even ask what this is doing...
185  */
186 static void alvarion_vsa_hack(VALUE_PAIR *vp)
187 {
188         int             number = 1;
189
190         for ( ; vp != NULL; vp = vp->next) {
191                 DICT_ATTR *da;
192
193                 if (vp->vendor != 12394) continue;
194                 if (vp->type != PW_TYPE_STRING) continue;
195
196                 da = dict_attrbyvalue(number, 12394);
197                 if (!da) continue;
198
199                 vp->attribute = da->attr;
200                 vp->name = da->name;
201
202                 number++;
203         }
204 }
205
206 /*
207  *      Cablelabs magic, taken from:
208  *
209  * http://www.cablelabs.com/packetcable/downloads/specs/PKT-SP-EM-I12-05812.pdf
210  *
211  *      Sample data is:
212  *
213  *      0x0001d2d2026d30310000000000003030
214  *        3130303030000e812333000100033031
215  *        00000000000030303130303030000000
216  *        00063230313230313331303630323231
217  *        2e3633390000000081000500
218  */
219
220 typedef struct cl_timezone_t {
221         uint8_t         dst;
222         uint8_t         sign;
223         uint8_t         hh[2];
224         uint8_t         mm[2];
225         uint8_t         ss[2];
226 } cl_timezone_t;
227
228 typedef struct cl_bcid_t {
229         uint32_t        timestamp;
230         uint8_t         element_id[8];
231         cl_timezone_t   timezone;
232         uint32_t        event_counter;
233 } cl_bcid_t;
234
235 typedef struct cl_em_hdr_t {
236         uint16_t        version;
237         cl_bcid_t       bcid;
238         uint16_t        message_type;
239         uint16_t        element_type;
240         uint8_t         element_id[8];
241         cl_timezone_t   time_zone;
242         uint32_t        sequence_number;
243         uint8_t         event_time[18];
244         uint8_t         status[4];
245         uint8_t         priority;
246         uint16_t        attr_count; /* of normal Cablelabs VSAs */
247         uint8_t         event_object;
248 } cl_em_hdr_t;
249
250
251 static void cablelabs_vsa_hack(VALUE_PAIR **list)
252 {
253         VALUE_PAIR *ev, *vp;
254
255         ev = pairfind(&list, 1, 4491); /* Cablelabs-Event-Message */
256         if (!ev) return;
257
258         /*
259          *      FIXME: write 100's of lines of code to decode
260          *      each data structure above.
261          */
262 }
263
264
265
266 /*
267  *      Mangle username if needed, IN PLACE.
268  */
269 static void rad_mangle(rlm_preprocess_t *data, REQUEST *request)
270 {
271         int             num_proxy_state;
272         VALUE_PAIR      *namepair;
273         VALUE_PAIR      *request_pairs;
274         VALUE_PAIR      *tmp;
275
276         /*
277          *      Get the username from the request
278          *      If it isn't there, then we can't mangle the request.
279          */
280         request_pairs = request->packet->vps;
281         namepair = pairfind(request_pairs, PW_USER_NAME, 0);
282         if ((namepair == NULL) ||
283             (namepair->length <= 0)) {
284           return;
285         }
286
287         if (data->with_ntdomain_hack) {
288                 char            *ptr;
289                 char            newname[MAX_STRING_LEN];
290
291                 /*
292                  *      Windows NT machines often authenticate themselves as
293                  *      NT_DOMAIN\username. Try to be smart about this.
294                  *
295                  *      FIXME: should we handle this as a REALM ?
296                  */
297                 if ((ptr = strchr(namepair->vp_strvalue, '\\')) != NULL) {
298                         strlcpy(newname, ptr + 1, sizeof(newname));
299                         /* Same size */
300                         strcpy(namepair->vp_strvalue, newname);
301                         namepair->length = strlen(newname);
302                 }
303         }
304
305         if (data->with_specialix_jetstream_hack) {
306                 char            *ptr;
307
308                 /*
309                  *      Specialix Jetstream 8500 24 port access server.
310                  *      If the user name is 10 characters or longer, a "/"
311                  *      and the excess characters after the 10th are
312                  *      appended to the user name.
313                  *
314                  *      Reported by Lucas Heise <root@laonet.net>
315                  */
316                 if ((strlen((char *)namepair->vp_strvalue) > 10) &&
317                     (namepair->vp_strvalue[10] == '/')) {
318                         for (ptr = (char *)namepair->vp_strvalue + 11; *ptr; ptr++)
319                                 *(ptr - 1) = *ptr;
320                         *(ptr - 1) = 0;
321                         namepair->length = strlen((char *)namepair->vp_strvalue);
322                 }
323         }
324
325         /*
326          *      Small check: if Framed-Protocol present but Service-Type
327          *      is missing, add Service-Type = Framed-User.
328          */
329         if (pairfind(request_pairs, PW_FRAMED_PROTOCOL, 0) != NULL &&
330             pairfind(request_pairs, PW_SERVICE_TYPE, 0) == NULL) {
331                 tmp = radius_paircreate(request, &request->packet->vps,
332                                         PW_SERVICE_TYPE, 0, PW_TYPE_INTEGER);
333                 tmp->vp_integer = PW_FRAMED_USER;
334         }
335
336         num_proxy_state = 0;
337         for (tmp = request->packet->vps; tmp != NULL; tmp = tmp->next) {
338                 if (tmp->vendor != 0) continue;
339                 if (tmp->attribute != PW_PROXY_STATE) continue;
340
341                 num_proxy_state++;
342         }
343
344         if (num_proxy_state > 10) {
345                 DEBUG("WARNING: There are more than 10 Proxy-State attributes in the request.");
346                 DEBUG("WARNING: You have likely configured an infinite proxy loop.");
347         }
348 }
349
350 /*
351  *      Compare the request with the "reply" part in the
352  *      huntgroup, which normally only contains username or group.
353  *      At least one of the "reply" items has to match.
354  */
355 static int hunt_paircmp(REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check)
356 {
357         VALUE_PAIR      *check_item = check;
358         VALUE_PAIR      *tmp;
359         int             result = -1;
360
361         if (check == NULL) return 0;
362
363         while (result != 0 && check_item != NULL) {
364
365                 tmp = check_item->next;
366                 check_item->next = NULL;
367
368                 result = paircompare(req, request, check_item, NULL);
369
370                 check_item->next = tmp;
371                 check_item = check_item->next;
372         }
373
374         return result;
375 }
376
377
378 /*
379  *      Add hints to the info sent by the terminal server
380  *      based on the pattern of the username, and other attributes.
381  */
382 static int hints_setup(PAIR_LIST *hints, REQUEST *request)
383 {
384         char            *name;
385         VALUE_PAIR      *add;
386         VALUE_PAIR      *tmp;
387         PAIR_LIST       *i;
388         VALUE_PAIR *request_pairs;
389         int             updated = 0, ft;
390
391
392         request_pairs = request->packet->vps;
393
394         if (hints == NULL || request_pairs == NULL)
395                 return RLM_MODULE_NOOP;
396
397         /*
398          *      Check for valid input, zero length names not permitted
399          */
400         if ((tmp = pairfind(request_pairs, PW_USER_NAME, 0)) == NULL)
401                 name = NULL;
402         else
403                 name = (char *)tmp->vp_strvalue;
404
405         if (name == NULL || name[0] == 0)
406                 /*
407                  *      No name, nothing to do.
408                  */
409                 return RLM_MODULE_NOOP;
410
411         for (i = hints; i; i = i->next) {
412                 /*
413                  *      Use "paircompare", which is a little more general...
414                  */
415                 if (((strcmp(i->name, "DEFAULT") == 0) ||
416                      (strcmp(i->name, name) == 0)) &&
417                     (paircompare(request, request_pairs, i->check, NULL) == 0)) {
418                         RDEBUG2("  hints: Matched %s at %d",
419                                i->name, i->lineno);
420                         /*
421                          *      Now add all attributes to the request list,
422                          *      except PW_STRIP_USER_NAME and PW_FALL_THROUGH
423                          *      and xlat them.
424                          */
425                         add = paircopy(i->reply);
426                         ft = fallthrough(add);
427                         pairdelete(&add, PW_STRIP_USER_NAME, 0);
428                         pairdelete(&add, PW_FALL_THROUGH, 0);
429                         pairxlatmove(request, &request->packet->vps, &add);
430                         pairfree(&add);
431                         updated = 1;
432                         if (!ft) break;
433                 }
434         }
435
436         if (updated == 0) return RLM_MODULE_NOOP;
437
438         return RLM_MODULE_UPDATED;
439 }
440
441 /*
442  *      See if we have access to the huntgroup.
443  */
444 static int huntgroup_access(REQUEST *request, PAIR_LIST *huntgroups)
445 {
446         PAIR_LIST       *i;
447         int             r = RLM_MODULE_OK;
448         VALUE_PAIR      *request_pairs = request->packet->vps;
449
450         /*
451          *      We're not controlling access by huntgroups:
452          *      Allow them in.
453          */
454         if (huntgroups == NULL)
455                 return RLM_MODULE_OK;
456
457         for(i = huntgroups; i; i = i->next) {
458                 /*
459                  *      See if this entry matches.
460                  */
461                 if (paircompare(request, request_pairs, i->check, NULL) != 0)
462                         continue;
463
464                 /*
465                  *      Now check for access.
466                  */
467                 r = RLM_MODULE_REJECT;
468                 if (hunt_paircmp(request, request_pairs, i->reply) == 0) {
469                         VALUE_PAIR *vp;
470
471                         /*
472                          *  We've matched the huntgroup, so add it in
473                          *  to the list of request pairs.
474                          */
475                         vp = pairfind(request_pairs, PW_HUNTGROUP_NAME, 0);
476                         if (!vp) {
477                                 vp = radius_paircreate(request,
478                                                        &request->packet->vps,
479                                                        PW_HUNTGROUP_NAME, 0,
480                                                        PW_TYPE_STRING);
481                                 strlcpy(vp->vp_strvalue, i->name,
482                                         sizeof(vp->vp_strvalue));
483                                 vp->length = strlen(vp->vp_strvalue);
484                         }
485                         r = RLM_MODULE_OK;
486                 }
487                 break;
488         }
489
490         return r;
491 }
492
493 /*
494  *      If the NAS wasn't smart enought to add a NAS-IP-Address
495  *      to the request, then add it ourselves.
496  */
497 static int add_nas_attr(REQUEST *request)
498 {
499         VALUE_PAIR *nas;
500
501         switch (request->packet->src_ipaddr.af) {
502         case AF_INET:
503                 nas = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS, 0);
504                 if (!nas) {
505                         nas = radius_paircreate(request, &request->packet->vps,
506                                                 PW_NAS_IP_ADDRESS, 0,
507                                                 PW_TYPE_IPADDR);
508                         nas->vp_ipaddr = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr;
509                 }
510                 break;
511
512         case AF_INET6:
513                 nas = pairfind(request->packet->vps, PW_NAS_IPV6_ADDRESS, 0);
514                 if (!nas) {
515                         nas = radius_paircreate(request, &request->packet->vps,
516                                                 PW_NAS_IPV6_ADDRESS, 0,
517                                                 PW_TYPE_IPV6ADDR);
518                         memcpy(nas->vp_strvalue,
519                                &request->packet->src_ipaddr.ipaddr,
520                                sizeof(request->packet->src_ipaddr.ipaddr));
521                 }
522                 break;
523
524         default:
525                 radlog(L_ERR, "Unknown address family for packet");
526                 return -1;
527         }
528
529         return 0;
530 }
531
532
533 /*
534  *      Initialize.
535  */
536 static int preprocess_instantiate(CONF_SECTION *conf, void **instance)
537 {
538         int     rcode;
539         rlm_preprocess_t *data;
540
541         /*
542          *      Allocate room to put the module's instantiation data.
543          */
544         data = (rlm_preprocess_t *) rad_malloc(sizeof(*data));
545         memset(data, 0, sizeof(*data));
546
547         /*
548          *      Read this modules configuration data.
549          */
550         if (cf_section_parse(conf, data, module_config) < 0) {
551                 free(data);
552                 return -1;
553         }
554
555         data->huntgroups = NULL;
556         data->hints = NULL;
557
558         /*
559          *      Read the huntgroups file.
560          */
561         if (data->huntgroup_file) {
562                 rcode = pairlist_read(data->huntgroup_file,
563                                       &(data->huntgroups), 0);
564                 if (rcode < 0) {
565                         radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
566                                data->huntgroup_file);
567                         return -1;
568                 }
569         }
570
571         /*
572          *      Read the hints file.
573          */
574         if (data->hints_file) {
575                 rcode = pairlist_read(data->hints_file, &(data->hints), 0);
576                 if (rcode < 0) {
577                         radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
578                                data->hints_file);
579                         return -1;
580                 }
581         }
582
583         /*
584          *      Save the instantiation data for later.
585          */
586         *instance = data;
587
588         return 0;
589 }
590
591 /*
592  *      Preprocess a request.
593  */
594 static int preprocess_authorize(void *instance, REQUEST *request)
595 {
596         int r;
597         rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
598
599         /*
600          *      Mangle the username, to get rid of stupid implementation
601          *      bugs.
602          */
603         rad_mangle(data, request);
604
605         if (data->with_ascend_hack) {
606                 /*
607                  *      If we're using Ascend systems, hack the NAS-Port-Id
608                  *      in place, to go from Ascend's weird values to something
609                  *      approaching rationality.
610                  */
611                 ascend_nasport_hack(pairfind(request->packet->vps,
612                                              PW_NAS_PORT, 0),
613                                     data->ascend_channels_per_line);
614         }
615
616         if (data->with_cisco_vsa_hack) {
617                 /*
618                  *      We need to run this hack because the h323-conf-id
619                  *      attribute should be used.
620                  */
621                 cisco_vsa_hack(request->packet->vps);
622         }
623
624         if (data->with_alvarion_vsa_hack) {
625                 /*
626                  *      We need to run this hack because the Alvarion
627                  *      people are crazy.
628                  */
629                 alvarion_vsa_hack(request->packet->vps);
630         }
631
632         if (data->with_cablelabs_vsa_hack) {
633                 /*
634                  *      We need to run this hack because the Cablelabs
635                  *      people are crazy.
636                  */
637                 cablelabs_vsa_hack(&request->packet->vps);
638         }
639
640         /*
641          *      Note that we add the Request-Src-IP-Address to the request
642          *      structure BEFORE checking huntgroup access.  This allows
643          *      the Request-Src-IP-Address to be used for huntgroup
644          *      comparisons.
645          */
646         if (add_nas_attr(request) < 0) {
647                 return RLM_MODULE_FAIL;
648         }
649
650         hints_setup(data->hints, request);
651
652         /*
653          *      If there is a PW_CHAP_PASSWORD attribute but there
654          *      is PW_CHAP_CHALLENGE we need to add it so that other
655          *      modules can use it as a normal attribute.
656          */
657         if (pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0) &&
658             pairfind(request->packet->vps, PW_CHAP_CHALLENGE, 0) == NULL) {
659                 VALUE_PAIR *vp;
660
661                 vp = radius_paircreate(request, &request->packet->vps,
662                                        PW_CHAP_CHALLENGE, 0, PW_TYPE_OCTETS);
663                 vp->length = AUTH_VECTOR_LEN;
664                 memcpy(vp->vp_strvalue, request->packet->vector, AUTH_VECTOR_LEN);
665         }
666
667         if ((r = huntgroup_access(request,
668                                   data->huntgroups)) != RLM_MODULE_OK) {
669                 char buf[1024];
670                 radlog_request(L_AUTH, 0, request, "No huntgroup access: [%s] (%s)",
671                        request->username ? request->username->vp_strvalue : "<NO User-Name>",
672                        auth_name(buf, sizeof(buf), request, 1));
673                 return r;
674         }
675
676         return RLM_MODULE_OK; /* Meaning: try next authorization module */
677 }
678
679 /*
680  *      Preprocess a request before accounting
681  */
682 static int preprocess_preaccounting(void *instance, REQUEST *request)
683 {
684         int r;
685         rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
686
687         /*
688          *  Ensure that we have the SAME user name for both
689          *  authentication && accounting.
690          */
691         rad_mangle(data, request);
692
693         if (data->with_cisco_vsa_hack) {
694                 /*
695                  *      We need to run this hack because the h323-conf-id
696                  *      attribute should be used.
697                  */
698                 cisco_vsa_hack(request->packet->vps);
699         }
700
701         if (data->with_alvarion_vsa_hack) {
702                 /*
703                  *      We need to run this hack because the Alvarion
704                  *      people are crazy.
705                  */
706                 alvarion_vsa_hack(request->packet->vps);
707         }
708
709         if (data->with_cablelabs_vsa_hack) {
710                 /*
711                  *      We need to run this hack because the Cablelabs
712                  *      people are crazy.
713                  */
714                 cablelabs_vsa_hack(&request->packet->vps);
715         }
716
717         /*
718          *  Ensure that we log the NAS IP Address in the packet.
719          */
720         if (add_nas_attr(request) < 0) {
721                 return RLM_MODULE_FAIL;
722         }
723
724         hints_setup(data->hints, request);
725
726         if ((r = huntgroup_access(request,
727                                   data->huntgroups)) != RLM_MODULE_OK) {
728                 char buf[1024];
729                 radlog_request(L_INFO, 0, request, "No huntgroup access: [%s] (%s)",
730                        request->username ? request->username->vp_strvalue : "<NO User-Name>",
731                        auth_name(buf, sizeof(buf), request, 1));
732                 return r;
733         }
734
735         return r;
736 }
737
738 /*
739  *      Clean up the module's instance.
740  */
741 static int preprocess_detach(void *instance)
742 {
743         rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
744
745         pairlist_free(&(data->huntgroups));
746         pairlist_free(&(data->hints));
747
748         free(data);
749
750         return 0;
751 }
752
753 /* globally exported name */
754 module_t rlm_preprocess = {
755         RLM_MODULE_INIT,
756         "preprocess",
757         RLM_TYPE_CHECK_CONFIG_SAFE,     /* type */
758         preprocess_instantiate, /* instantiation */
759         preprocess_detach,      /* detach */
760         {
761                 NULL,                   /* authentication */
762                 preprocess_authorize,   /* authorization */
763                 preprocess_preaccounting, /* pre-accounting */
764                 NULL,                   /* accounting */
765                 NULL,                   /* checksimul */
766                 NULL,                   /* pre-proxy */
767                 NULL,                   /* post-proxy */
768                 NULL                    /* post-auth */
769         },
770 };
771