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