Add more NULL's to module data structures, in preparation for
[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        "autoconf.h"
29 #include        "libradius.h"
30
31 #include        <sys/stat.h>
32
33 #include        <stdio.h>
34 #include        <stdlib.h>
35 #include        <string.h>
36 #include        <ctype.h>
37
38 #include        "radiusd.h"
39 #include        "modules.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 CONF_PARSER module_config[] = {
54         { "huntgroups",                 PW_TYPE_STRING_PTR,
55           offsetof(rlm_preprocess_t,huntgroup_file), NULL,
56           "${raddbdir}/huntgroups" },
57         { "hints",                      PW_TYPE_STRING_PTR,
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             vendorpec, 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 == 0) continue;  /* ignore non-VSA attributes */
120
121                 vendorpec  = dict_vendorpec(vendorcode);
122                 if (vendorpec == 0) continue; /* ignore unknown VSA's */
123
124                 if (vendorpec != 9) continue; /* not a Cisco VSA, continue */
125
126                 if ((vp->attribute & 0xffff) == 1) continue; /* Cisco-AVPair */
127
128                 /*
129                  *  We strip out the duplicity from the value field,
130                  *  we use only the value on the right side of = character.
131                  */
132                 if ((ptr = strchr(vp->strvalue, '=')) != NULL) {
133                         strNcpy(newattr, ptr + 1, sizeof(newattr));
134                         strNcpy((char *)vp->strvalue, newattr,
135                                 sizeof(vp->strvalue));
136                         vp->length = strlen((char *)vp->strvalue);
137                 }
138         }
139 }
140
141 /*
142  *      Mangle username if needed, IN PLACE.
143  */
144 static void rad_mangle(rlm_preprocess_t *data, REQUEST *request)
145 {
146         VALUE_PAIR      *namepair;
147         VALUE_PAIR      *request_pairs;
148         VALUE_PAIR      *tmp;
149
150         /*
151          *      Get the username from the request
152          *      If it isn't there, then we can't mangle the request.
153          */
154         request_pairs = request->packet->vps;
155         namepair = pairfind(request_pairs, PW_USER_NAME);
156         if ((namepair == NULL) || 
157             (namepair->length <= 0)) {
158           return;
159         }
160
161         if (data->with_ntdomain_hack) {
162                 char            *ptr;
163                 char            newname[MAX_STRING_LEN];
164
165                 /*
166                  *      Windows NT machines often authenticate themselves as
167                  *      NT_DOMAIN\username. Try to be smart about this.
168                  *
169                  *      FIXME: should we handle this as a REALM ?
170                  */
171                 if ((ptr = strchr(namepair->strvalue, '\\')) != NULL) {
172                         strNcpy(newname, ptr + 1, sizeof(newname));
173                         /* Same size */
174                         strcpy(namepair->strvalue, newname);
175                         namepair->length = strlen(newname);
176                 }
177         }
178
179         if (data->with_specialix_jetstream_hack) {
180                 char            *ptr;
181
182                 /*
183                  *      Specialix Jetstream 8500 24 port access server.
184                  *      If the user name is 10 characters or longer, a "/"
185                  *      and the excess characters after the 10th are
186                  *      appended to the user name.
187                  *
188                  *      Reported by Lucas Heise <root@laonet.net>
189                  */
190                 if ((strlen((char *)namepair->strvalue) > 10) &&
191                     (namepair->strvalue[10] == '/')) {
192                         for (ptr = (char *)namepair->strvalue + 11; *ptr; ptr++)
193                                 *(ptr - 1) = *ptr;
194                         *(ptr - 1) = 0;
195                         namepair->length = strlen((char *)namepair->strvalue);
196                 }
197         }
198
199         /*
200          *      Small check: if Framed-Protocol present but Service-Type
201          *      is missing, add Service-Type = Framed-User.
202          */
203         if (pairfind(request_pairs, PW_FRAMED_PROTOCOL) != NULL &&
204             pairfind(request_pairs, PW_SERVICE_TYPE) == NULL) {
205                 tmp = paircreate(PW_SERVICE_TYPE, PW_TYPE_INTEGER);
206                 if (tmp) {
207                         tmp->lvalue = PW_FRAMED_USER;
208                         pairmove(&request_pairs, &tmp);
209                 }
210         }
211 }
212
213 /*
214  *      Compare the request with the "reply" part in the
215  *      huntgroup, which normally only contains username or group.
216  *      At least one of the "reply" items has to match.
217  */
218 static int hunt_paircmp(VALUE_PAIR *request, VALUE_PAIR *check)
219 {
220         VALUE_PAIR      *check_item = check;
221         VALUE_PAIR      *tmp;
222         int             result = -1;
223
224         if (check == NULL) return 0;
225
226         while (result != 0 && check_item != NULL) {
227
228                 tmp = check_item->next;
229                 check_item->next = NULL;
230
231                 result = paircmp(NULL, request, check_item, NULL);
232
233                 check_item->next = tmp;
234                 check_item = check_item->next;
235         }
236
237         return result;
238 }
239
240
241 /*
242  *      Compare prefix/suffix
243  */
244 static int presufcmp(VALUE_PAIR *check, char *name, char *rest)
245 {
246         int             len, namelen;
247         int             ret = -1;
248
249 #if 0 /* DEBUG */
250         printf("Comparing %s and %s, check->attr is %d\n",
251                 name, check->strvalue, check->attribute);
252 #endif
253
254         len = strlen((char *)check->strvalue);
255         switch (check->attribute) {
256                 case PW_PREFIX:
257                         ret = strncmp(name, (char *)check->strvalue, len);
258                         if (ret == 0 && rest)
259                                 strcpy(rest, name + len);
260                         break;
261                 case PW_SUFFIX:
262                         namelen = strlen(name);
263                         if (namelen < len)
264                                 break;
265                         ret = strcmp(name + namelen - len,
266                                      (char *)check->strvalue);
267                         if (ret == 0 && rest) {
268                                 strncpy(rest, name, namelen - len);
269                                 rest[namelen - len] = 0;
270                         }
271                         break;
272         }
273
274         return ret;
275 }
276
277 /*
278  *      Match a username with a wildcard expression.
279  *      Is very limited for now.
280  */
281 static int matches(char *name, PAIR_LIST *pl, char *matchpart)
282 {
283         int len, wlen;
284         int ret = 0;
285         char *wild = pl->name;
286         VALUE_PAIR *tmp;
287
288         /*
289          *      We now support both:
290          *
291          *              DEFAULT Prefix = "P"
292          *
293          *      and
294          *              P*
295          */
296         if ((tmp = pairfind(pl->check, PW_PREFIX)) != NULL ||
297             (tmp = pairfind(pl->check, PW_SUFFIX)) != NULL) {
298
299                 if (strncmp(pl->name, "DEFAULT", 7) == 0 ||
300                     strcmp(pl->name, name) == 0)
301                         return !presufcmp(tmp, name, matchpart);
302         }
303
304         /*
305          *      Shortcut if there's no '*' in pl->name.
306          */
307         if (strchr(pl->name, '*') == NULL &&
308             (strncmp(pl->name, "DEFAULT", 7) == 0 ||
309              strcmp(pl->name, name) == 0)) {
310                 strcpy(matchpart, name);
311                 return 1;
312         }
313
314         /*
315          *      Normally, we should return 0 here, but we
316          *      support the old * stuff.
317          */
318         len = strlen(name);
319         wlen = strlen(wild);
320
321         if (len == 0 || wlen == 0) return 0;
322
323         if (wild[0] == '*') {
324                 wild++;
325                 wlen--;
326                 if (wlen <= len && strcmp(name + (len - wlen), wild) == 0) {
327                         strcpy(matchpart, name);
328                         matchpart[len - wlen] = 0;
329                         ret = 1;
330                 }
331         } else if (wild[wlen - 1] == '*') {
332                 if (wlen <= len && strncmp(name, wild, wlen - 1) == 0) {
333                         strcpy(matchpart, name + wlen - 1);
334                         ret = 1;
335                 }
336         }
337
338         return ret;
339 }
340
341
342 /*
343  *      Add hints to the info sent by the terminal server
344  *      based on the pattern of the username.
345  */
346 static int hints_setup(PAIR_LIST *hints, REQUEST *request)
347 {
348         char            newname[MAX_STRING_LEN];
349         char            *name;
350         VALUE_PAIR      *add;
351         VALUE_PAIR      *last;
352         VALUE_PAIR      *tmp;
353         PAIR_LIST       *i;
354         int             do_strip;
355         VALUE_PAIR *request_pairs;
356
357         request_pairs = request->packet->vps;
358
359         if (hints == NULL || request_pairs == NULL)
360                 return RLM_MODULE_NOOP;
361
362         /* 
363          *      Check for valid input, zero length names not permitted 
364          */
365         if ((tmp = pairfind(request_pairs, PW_USER_NAME)) == NULL)
366                 name = NULL;
367         else
368                 name = (char *)tmp->strvalue;
369
370         if (name == NULL || name[0] == 0)
371                 /*
372                  *      No name, nothing to do.
373                  */
374                 return RLM_MODULE_NOOP;
375
376         for (i = hints; i; i = i->next) {
377                 if (matches(name, i, newname)) {
378                         DEBUG2("  hints: Matched %s at %d",
379                                i->name, i->lineno);
380                         break;
381                 }
382         }
383
384         if (i == NULL) return RLM_MODULE_NOOP;
385
386         add = paircopy(i->reply);
387
388 #if 0 /* DEBUG */
389         printf("In hints_setup, newname is %s\n", newname);
390 #endif
391
392         /*
393          *      See if we need to adjust the name.
394          */
395         do_strip = 1;
396         if ((tmp = pairfind(i->reply, PW_STRIP_USER_NAME)) != NULL
397              && tmp->lvalue == 0)
398                 do_strip = 0;
399         if ((tmp = pairfind(i->check, PW_STRIP_USER_NAME)) != NULL
400              && tmp->lvalue == 0)
401                 do_strip = 0;
402
403         if (do_strip) {
404                 tmp = pairfind(request_pairs, PW_STRIPPED_USER_NAME);
405                 if (tmp) {
406                         strcpy((char *)tmp->strvalue, newname);
407                         tmp->length = strlen((char *)tmp->strvalue);
408                 } else {
409                         /*
410                          *      No Stripped-User-Name exists: add one.
411                          */
412                         tmp = paircreate(PW_STRIPPED_USER_NAME, PW_TYPE_STRING);
413                         if (!tmp) {
414                                 radlog(L_ERR|L_CONS, "no memory");
415                                 exit(1);
416                         }
417                         strcpy((char *)tmp->strvalue, newname);
418                         tmp->length = strlen((char *)tmp->strvalue);
419                         pairadd(&request_pairs, tmp);
420                 }
421                 request->username = tmp;
422         }
423
424         /*
425          *      Now add all attributes to the request list,
426          *      except the PW_STRIP_USER_NAME one.
427          */
428         pairdelete(&add, PW_STRIP_USER_NAME);
429         for(last = request_pairs; last && last->next; last = last->next)
430                 ;
431         if (last) last->next = add;
432
433         return RLM_MODULE_UPDATED;
434 }
435
436 /*
437  *      See if the huntgroup matches. This function is
438  *      tied to the "Huntgroup" keyword.
439  */
440 static int huntgroup_cmp(void *instance, REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
441                          VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
442 {
443         PAIR_LIST       *i;
444         char            *huntgroup;
445         rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
446
447         check_pairs = check_pairs; /* shut the compiler up */
448         reply_pairs = reply_pairs;
449
450         huntgroup = (char *)check->strvalue;
451
452         for (i = data->huntgroups; i; i = i->next) {
453                 if (strcmp(i->name, huntgroup) != 0)
454                         continue;
455                 if (paircmp(req, request, i->check, NULL) == 0) {
456                         DEBUG2("  huntgroups: Matched %s at %d",
457                                i->name, i->lineno);
458                         break;
459                 }
460         }
461
462         /*
463          *      paircmp() expects to see zero on match, so let's
464          *      keep it happy.
465          */
466         if (i == NULL) {
467                 return -1;
468         }
469         return 0;
470 }
471
472
473 /*
474  *      See if we have access to the huntgroup.
475  */
476 static int huntgroup_access(PAIR_LIST *huntgroups, VALUE_PAIR *request_pairs)
477 {
478         PAIR_LIST       *i;
479         int             r = RLM_MODULE_OK;
480
481         /*
482          *      We're not controlling access by huntgroups:
483          *      Allow them in.
484          */
485         if (huntgroups == NULL)
486                 return RLM_MODULE_OK;
487
488         for(i = huntgroups; i; i = i->next) {
489                 /*
490                  *      See if this entry matches.
491                  */
492                 if (paircmp(NULL, request_pairs, i->check, NULL) != 0)
493                         continue;
494
495                 /*
496                  *      Now check for access.
497                  */
498                 r = RLM_MODULE_REJECT;
499                 if (hunt_paircmp(request_pairs, i->reply) == 0) {
500                         VALUE_PAIR *vp;
501
502                         /*
503                          *  We've matched the huntgroup, so add it in
504                          *  to the list of request pairs.
505                          */
506                         vp = pairfind(request_pairs, PW_HUNTGROUP_NAME);
507                         if (!vp) {
508                                 vp = paircreate(PW_HUNTGROUP_NAME,
509                                                 PW_TYPE_STRING);
510                                 if (!vp) {
511                                         radlog(L_ERR, "No memory");
512                                         exit(1);
513                                 }
514                                 
515                                 strNcpy(vp->strvalue, i->name,
516                                         sizeof(vp->strvalue));
517                                 vp->length = strlen(vp->strvalue);
518
519                                 pairadd(&request_pairs, vp);
520                         }
521                         r = RLM_MODULE_OK;
522                 }
523                 break;
524         }
525
526         return r;
527 }
528
529 /*
530  *      If the NAS wasn't smart enought to add a NAS-IP-Address
531  *      to the request, then add it ourselves.
532  */
533 static void add_nas_attr(REQUEST *request)
534 {
535         VALUE_PAIR *nas;
536
537         nas = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS);
538         if (!nas) {
539                 nas = paircreate(PW_NAS_IP_ADDRESS, PW_TYPE_IPADDR);
540                 if (!nas) {
541                         radlog(L_ERR, "No memory");
542                         exit(1);
543                 }
544                 nas->lvalue = request->packet->src_ipaddr;
545                 pairadd(&request->packet->vps, nas);
546         }
547
548         /*
549          *      Add in a Client-IP-Address, to tell the user
550          *      the source IP of the request.  That is, the client,
551          *
552          *      Note that this MAY BE different from the NAS-IP-Address,
553          *      especially if the request is being proxied.
554          *
555          *      Note also that this is a server configuration item,
556          *      and will NOT make it to any packets being sent from
557          *      the server.
558          */
559         nas = paircreate(PW_CLIENT_IP_ADDRESS, PW_TYPE_IPADDR);
560         if (!nas) {
561           radlog(L_ERR, "No memory");
562           exit(1);
563         }
564         nas->lvalue = request->packet->src_ipaddr;
565         ip_hostname(nas->strvalue, sizeof(nas->strvalue), nas->lvalue);
566         pairadd(&request->packet->vps, nas);
567 }
568
569
570 /*
571  *      Initialize.
572  */
573 static int preprocess_instantiate(CONF_SECTION *conf, void **instance)
574 {
575         int     rcode;
576         rlm_preprocess_t *data;
577
578         /*
579          *      Allocate room to put the module's instantiation data.
580          */
581         data = (rlm_preprocess_t *) rad_malloc(sizeof(*data));
582
583         /*
584          *      Read this modules configuration data.
585          */
586         if (cf_section_parse(conf, data, module_config) < 0) {
587                 free(data);
588                 return -1;
589         }
590
591         data->huntgroups = NULL;
592         data->hints = NULL;
593
594         /*
595          *      Read the huntgroups file.
596          */
597         rcode = pairlist_read(data->huntgroup_file, &(data->huntgroups), 0);
598         if (rcode < 0) {
599                 radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
600                        data->huntgroup_file);
601                 return -1;
602         }
603
604         /*
605          *      Read the hints file.
606          */
607         rcode = pairlist_read(data->hints_file, &(data->hints), 0);
608         if (rcode < 0) {
609                 radlog(L_ERR|L_CONS, "rlm_preprocess: Error reading %s",
610                        data->hints_file);
611                 return -1;
612         }
613
614         /*
615          *      Register the huntgroup comparison operation.
616          */
617         paircompare_register(PW_HUNTGROUP_NAME, 0, huntgroup_cmp, data);
618
619         /*
620          *      Save the instantiation data for later.
621          */
622         *instance = data;
623
624         return 0;
625 }
626
627 /*
628  *      Preprocess a request.
629  */
630 static int preprocess_authorize(void *instance, REQUEST *request)
631 {
632         char buf[1024];
633         rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
634
635         /*
636          *      Mangle the username, to get rid of stupid implementation
637          *      bugs.
638          */
639         rad_mangle(data, request);
640
641         if (data->with_ascend_hack) {
642                 /*
643                  *      If we're using Ascend systems, hack the NAS-Port-Id
644                  *      in place, to go from Ascend's weird values to something
645                  *      approaching rationality.
646                  */
647                 ascend_nasport_hack(pairfind(request->packet->vps,
648                                              PW_NAS_PORT_ID),
649                                     data->ascend_channels_per_line);
650         }
651
652         if (data->with_cisco_vsa_hack) {
653                 /*
654                  *      We need to run this hack because the h323-conf-id
655                  *      attribute should be used.
656                  */
657                 cisco_vsa_hack(request->packet->vps);
658         }
659
660         /*
661          *      Note that we add the Request-Src-IP-Address to the request
662          *      structure BEFORE checking huntgroup access.  This allows
663          *      the Request-Src-IP-Address to be used for huntgroup
664          *      comparisons.
665          */
666         add_nas_attr(request);
667
668         hints_setup(data->hints, request);
669
670         /*
671          *      If there is a PW_CHAP_PASSWORD attribute but there
672          *      is PW_CHAP_CHALLENGE we need to add it so that other
673          *      modules can use it as a normal attribute.
674          */
675         if (pairfind(request->packet->vps, PW_CHAP_PASSWORD) &&
676             pairfind(request->packet->vps, PW_CHAP_CHALLENGE) == NULL) {
677                 VALUE_PAIR *vp;
678                 vp = paircreate(PW_CHAP_CHALLENGE, PW_TYPE_OCTETS);
679                 if (!vp) {
680                         radlog(L_ERR|L_CONS, "no memory");
681                         exit(1);
682                 }
683                 vp->length = AUTH_VECTOR_LEN;
684                 memcpy(vp->strvalue, request->packet->vector, AUTH_VECTOR_LEN);
685                 pairadd(&request->packet->vps, vp);
686         }
687
688         if (huntgroup_access(data->huntgroups, request->packet->vps) != RLM_MODULE_OK) {
689                 radlog(L_AUTH, "No huntgroup access: [%s] (%s)",
690                     request->username->strvalue,
691                     auth_name(buf, sizeof(buf), request, 1));
692                 return RLM_MODULE_REJECT;
693         }
694
695         return RLM_MODULE_OK; /* Meaning: try next authorization module */
696 }
697
698 /*
699  *      Preprocess a request before accounting
700  */
701 static int preprocess_preaccounting(void *instance, REQUEST *request)
702 {
703         int r;
704         rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
705
706         /*
707          *  Ensure that we have the SAME user name for both
708          *  authentication && accounting.
709          */
710         rad_mangle(data, request);
711
712         if (data->with_cisco_vsa_hack) {
713                 /*
714                  *      We need to run this hack because the h323-conf-id
715                  *      attribute should be used.
716                  */
717                 cisco_vsa_hack(request->packet->vps);
718         }
719
720         /*
721          *  Ensure that we log the NAS IP Address in the packet.
722          */
723         add_nas_attr(request);
724
725         r = hints_setup(data->hints, request);
726
727         return r;
728 }
729
730 /*
731  *      Clean up the module's instance.
732  */
733 static int preprocess_detach(void *instance)
734 {
735         rlm_preprocess_t *data = (rlm_preprocess_t *) instance;
736
737         paircompare_unregister(PW_HUNTGROUP_NAME, huntgroup_cmp);
738         pairlist_free(&(data->huntgroups));
739         pairlist_free(&(data->hints));
740
741         free(data->huntgroup_file);
742         free(data->hints_file);
743         free(data);
744
745         return 0;
746 }
747
748 /* globally exported name */
749 module_t rlm_preprocess = {
750         "preprocess",
751         0,                      /* type: reserved */
752         NULL,                   /* initialization */
753         preprocess_instantiate, /* instantiation */
754         {
755                 NULL,                   /* authentication */
756                 preprocess_authorize,   /* authorization */
757                 preprocess_preaccounting, /* pre-accounting */
758                 NULL,                   /* accounting */
759                 NULL,                   /* checksimul */
760                 NULL,                   /* pre-proxy */
761                 NULL,                   /* post-proxy */
762                 NULL                    /* post-auth */
763         },
764         preprocess_detach,      /* detach */
765         NULL,                   /* destroy */
766 };
767