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