Call fnmatch only if the packet was read from the detail file.
[freeradius.git] / src / modules / rlm_detail / rlm_detail.c
1 /*
2  * rlm_detail.c accounting:    Write the "detail" files.
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2000,2006  The FreeRADIUS server project
21  */
22
23 #include        <freeradius-devel/ident.h>
24 RCSID("$Id$")
25
26 #include        <freeradius-devel/radiusd.h>
27 #include        <freeradius-devel/modules.h>
28 #include        <freeradius-devel/rad_assert.h>
29 #include        <freeradius-devel/detail.h>
30
31 #include        <ctype.h>
32 #include        <fcntl.h>
33 #include        <sys/stat.h>
34
35 #ifdef HAVE_FNMATCH_H
36 #include        <fnmatch.h>
37 #endif
38
39 #define         DIRLEN  8192
40
41 struct detail_instance {
42         /* detail file */
43         char *detailfile;
44
45         /* detail file permissions */
46         int detailperm;
47
48         /* directory permissions */
49         int dirperm;
50
51         /* timestamp & stuff */
52         char *header;
53
54         /* if we want file locking */
55         int locking;
56
57         /* log src/dst information */
58         int log_srcdst;
59
60         fr_hash_table_t *ht;
61 };
62
63 static const CONF_PARSER module_config[] = {
64         { "detailfile",    PW_TYPE_STRING_PTR,
65           offsetof(struct detail_instance,detailfile), NULL, "%A/%{Client-IP-Address}/detail" },
66         { "header",    PW_TYPE_STRING_PTR,
67           offsetof(struct detail_instance,header), NULL, "%t" },
68         { "detailperm",    PW_TYPE_INTEGER,
69           offsetof(struct detail_instance,detailperm), NULL, "0600" },
70         { "dirperm",       PW_TYPE_INTEGER,
71           offsetof(struct detail_instance,dirperm),    NULL, "0755" },
72         { "locking",       PW_TYPE_BOOLEAN,
73           offsetof(struct detail_instance,locking),    NULL, "no" },
74         { "log_packet_header",       PW_TYPE_BOOLEAN,
75           offsetof(struct detail_instance,log_srcdst),    NULL, "no" },
76         { NULL, -1, 0, NULL, NULL }
77 };
78
79
80 /*
81  *      Clean up.
82  */
83 static int detail_detach(void *instance)
84 {
85         struct detail_instance *inst = instance;
86         if (inst->ht) fr_hash_table_free(inst->ht);
87
88         free(inst);
89         return 0;
90 }
91
92
93 static uint32_t detail_hash(const void *data)
94 {
95         const DICT_ATTR *da = data;
96         return fr_hash(&(da->attr), sizeof(da->attr));
97 }
98
99 static int detail_cmp(const void *a, const void *b)
100 {
101         return ((const DICT_ATTR *)a)->attr - ((const DICT_ATTR *)b)->attr;
102 }
103
104
105 /*
106  *      (Re-)read radiusd.conf into memory.
107  */
108 static int detail_instantiate(CONF_SECTION *conf, void **instance)
109 {
110         struct detail_instance *inst;
111         CONF_SECTION    *cs;
112
113         inst = rad_malloc(sizeof(*inst));
114         if (!inst) {
115                 return -1;
116         }
117         memset(inst, 0, sizeof(*inst));
118
119         if (cf_section_parse(conf, inst, module_config) < 0) {
120                 detail_detach(inst);
121                 return -1;
122         }
123
124         /*
125          *      Suppress certain attributes.
126          */
127         cs = cf_section_sub_find(conf, "suppress");
128         if (cs) {
129                 CONF_ITEM       *ci;
130
131                 inst->ht = fr_hash_table_create(detail_hash, detail_cmp,
132                                                   NULL);
133
134                 for (ci = cf_item_find_next(cs, NULL);
135                      ci != NULL;
136                      ci = cf_item_find_next(cs, ci)) {
137                         const char      *attr;
138                         DICT_ATTR       *da;
139
140                         if (!cf_item_is_pair(ci)) continue;
141
142                         attr = cf_pair_attr(cf_itemtopair(ci));
143                         if (!attr) continue; /* pair-anoia */
144
145                         da = dict_attrbyname(attr);
146                         if (!da) {
147                                 radlog(L_INFO, "rlm_detail: WARNING: No such attribute %s: Cannot suppress printing it.", attr);
148                                 continue;
149                         }
150
151                         /*
152                          *      For better distribution we should really
153                          *      hash the attribute number or name.  But
154                          *      since the suppression list will usually
155                          *      be small, it doesn't matter.
156                          */
157                         if (!fr_hash_table_insert(inst->ht, da)) {
158                                 radlog(L_ERR, "rlm_detail: Failed trying to remember %s", attr);
159                                 detail_detach(inst);
160                                 return -1;
161                         }
162                 }
163         }
164
165
166         *instance = inst;
167         return 0;
168 }
169
170 /*
171  * Perform a checked write. If the write fails or is not complete, truncate
172  * the file, eliminating the last bytes_accum + current partial write.
173  */
174 static int checked_write(REQUEST *request, off_t *bytes_accum, int fd,
175                          const char *format, ...)
176 {
177         char buf[2048];
178         int buf_used, written;
179         va_list args;
180
181         va_start(args, format);
182         buf_used = vsnprintf(buf, sizeof(buf), format, args);
183         va_end(args);
184
185         if (buf_used == 0) return 0;
186
187         if (buf_used >= (int) sizeof(buf)) {
188                 radlog_request(L_ERR, 0, request, "Truncated vsnprintf");
189                 return -1;
190         }
191
192         written = write(fd, buf, buf_used);
193         if (written > 0) {
194                 *bytes_accum += written;
195         }
196         if (written != buf_used) {
197                 /*
198                  *      Don't worry if the truncate fails, since the
199                  *      detail reader ignores partial entries.
200                  */
201                 ftruncate(fd, lseek(fd, 0, SEEK_CUR) - *bytes_accum);
202                 close(fd);
203
204                 radlog_request(L_ERR, 0, request, "Truncated write (wanted %d, wrote %d)",
205                                buf_used, written);              
206                 return -1;
207         }
208         return written;
209 }
210
211
212 static int checked_write_vp(REQUEST *request, off_t *bytes_accum, int fd,
213                             VALUE_PAIR *vp)
214 {
215         char buffer[1024];
216
217         vp_prints(buffer, sizeof(buffer), vp);
218
219         if (checked_write(request, bytes_accum, fd, "\t%s\n", buffer) < 0) {
220                 return -1;
221         }
222
223         return 0;
224 }
225
226 /*
227  *      Do detail, compatible with old accounting
228  */
229 static int do_detail(void *instance, REQUEST *request, RADIUS_PACKET *packet,
230                      int compat)
231 {
232         int             outfd;
233         char            timestamp[256];
234         char            buffer[DIRLEN];
235         char            *p;
236         struct stat     st;
237         int             locked;
238         int             lock_count;
239         off_t           bytes_accum = 0;
240         struct timeval  tv;
241         VALUE_PAIR      *pair;
242
243         struct detail_instance *inst = instance;
244
245         rad_assert(request != NULL);
246
247         /*
248          *      Nothing to log: don't do anything.
249          */
250         if (!packet) {
251                 return RLM_MODULE_NOOP;
252         }
253
254         /*
255          *      Create a directory for this nas.
256          *
257          *      Generate the path for the detail file.  Use the
258          *      same format, but truncate at the last /.  Then
259          *      feed it through radius_xlat() to expand the
260          *      variables.
261          */
262         if (radius_xlat(buffer, sizeof(buffer), inst->detailfile, request, NULL) == 0) {
263                 radlog_request(L_ERR, 0, request, "rlm_detail: Failed to expand detail file %s",
264                     inst->detailfile);
265             return RLM_MODULE_FAIL;
266         }
267         RDEBUG2("%s expands to %s", inst->detailfile, buffer);
268
269 #ifdef HAVE_FNMATCH_H
270         /*
271          *      If we read it from a detail file, and we're about to
272          *      write it back to the SAME detail file directory, then
273          *      suppress the write.  This check prevents an infinite
274          *      loop.
275          */
276         if ((request->listener == RAD_LISTEN_DETAIL) &&
277             (fnmatch(((listen_detail_t *)request->listener->data)->filename,
278                      buffer, FNM_FILE_NAME | FNM_PERIOD ) == 0)) {
279                 RDEBUG2("WARNING: Suppressing infinite loop.");
280                 return RLM_MODULE_NOOP;
281         }
282 #endif
283
284         /*
285          *      Grab the last directory delimiter.
286          */
287         p = strrchr(buffer,'/');
288
289         /*
290          *      There WAS a directory delimiter there, and the file
291          *      doesn't exist, so we must create it the directories..
292          */
293         if (p) {
294                 /*
295                  *      Always try to create the directory.  If it
296                  *      exists, rad_mkdir() will check via stat(), and
297                  *      return immediately.
298                  *
299                  *      This catches the case where some idiot deleted
300                  *      a directory that the server was using.
301                  */
302                 if (rad_mkdir(buffer, inst->dirperm) < 0) {
303                         radlog_request(L_ERR, 0, request, "rlm_detail: Failed to create directory %s: %s", buffer, strerror(errno));
304                         return RLM_MODULE_FAIL;
305                 }
306                 
307                 *p = '/';
308         } /* else there was no directory delimiter. */
309
310         locked = 0;
311         lock_count = 0;
312         do {
313                 /*
314                  *      Open & create the file, with the given
315                  *      permissions.
316                  */
317                 if ((outfd = open(buffer, O_WRONLY | O_APPEND | O_CREAT,
318                                   inst->detailperm)) < 0) {
319                         radlog_request(L_ERR, 0, request, "rlm_detail: Couldn't open file %s: %s",
320                                buffer, strerror(errno));
321                         return RLM_MODULE_FAIL;
322                 }
323
324                 /*
325                  *      If we fail to aquire the filelock in 80 tries
326                  *      (approximately two seconds) we bail out.
327                  */
328                 if (inst->locking) {
329                         lseek(outfd, 0L, SEEK_SET);
330                         if (rad_lockfd_nonblock(outfd, 0) < 0) {
331                                 close(outfd);
332                                 tv.tv_sec = 0;
333                                 tv.tv_usec = 25000;
334                                 select(0, NULL, NULL, NULL, &tv);
335                                 lock_count++;
336                                 continue;
337                         }
338
339                         /*
340                          *      The file might have been deleted by
341                          *      radrelay while we tried to acquire
342                          *      the lock (race condition)
343                          */
344                         if (fstat(outfd, &st) != 0) {
345                                 radlog_request(L_ERR, 0, request, "rlm_detail: Couldn't stat file %s: %s",
346                                        buffer, strerror(errno));
347                                 close(outfd);
348                                 return RLM_MODULE_FAIL;
349                         }
350                         if (st.st_nlink == 0) {
351                                 RDEBUG2("File %s removed by another program, retrying",
352                                       buffer);
353                                 close(outfd);
354                                 lock_count = 0;
355                                 continue;
356                         }
357
358                         RDEBUG2("Acquired filelock, tried %d time(s)",
359                               lock_count + 1);
360                         locked = 1;
361                 }
362         } while (inst->locking && !locked && lock_count < 80);
363
364         if (inst->locking && !locked) {
365                 close(outfd);
366                 radlog_request(L_ERR, 0, request, "rlm_detail: Failed to acquire filelock for %s, giving up",
367                        buffer);
368                 return RLM_MODULE_FAIL;
369         }
370
371         /*
372          *      Post a timestamp
373          */
374         if (lseek(outfd, 0L, SEEK_END) < 0) {
375                 radlog_request(L_ERR, 0, request, "rlm_detail: Failed to seek to the end of detail file %s",
376                         buffer);
377                 close(outfd);
378                 return RLM_MODULE_FAIL;
379         }
380         if (radius_xlat(timestamp, sizeof(timestamp), inst->header, request, NULL) == 0) {
381                 radlog_request(L_ERR, 0, request, "rlm_detail: Unable to expand detail header format %s",
382                         inst->header);
383                 close(outfd);
384                 return RLM_MODULE_FAIL;
385         }
386         if (checked_write(request, &bytes_accum, outfd,
387                           "%s\n", timestamp) < 0) {
388                 return RLM_MODULE_FAIL;
389         }
390
391         /*
392          *      Write the information to the file.
393          */
394         if (!compat) {
395                 /*
396                  *      Print out names, if they're OK.
397                  *      Numbers, if not.
398                  */
399                 if ((packet->code > 0) &&
400                     (packet->code < FR_MAX_PACKET_CODE)) {
401                         if (checked_write(request, &bytes_accum, outfd,
402                                           "\tPacket-Type = %s\n",
403                                           fr_packet_codes[packet->code]) == -1) {
404                                 return RLM_MODULE_FAIL; 
405                         }
406                 } else {
407                         if (checked_write(request, &bytes_accum, outfd,
408                                           "\tPacket-Type = %d\n", packet->code) == -1) {
409                                 return RLM_MODULE_FAIL;
410                         }
411                 }
412         }
413
414         if (inst->log_srcdst) {
415                 VALUE_PAIR src_vp, dst_vp;
416
417                 memset(&src_vp, 0, sizeof(src_vp));
418                 memset(&dst_vp, 0, sizeof(dst_vp));
419                 src_vp.operator = dst_vp.operator = T_OP_EQ;
420
421                 switch (packet->src_ipaddr.af) {
422                 case AF_INET:
423                         src_vp.type = PW_TYPE_IPADDR;
424                         src_vp.attribute = PW_PACKET_SRC_IP_ADDRESS;
425                         src_vp.vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
426                         dst_vp.type = PW_TYPE_IPADDR;
427                         dst_vp.attribute = PW_PACKET_DST_IP_ADDRESS;
428                         dst_vp.vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
429                         break;
430                 case AF_INET6:
431                         src_vp.type = PW_TYPE_IPV6ADDR;
432                         src_vp.attribute = PW_PACKET_SRC_IPV6_ADDRESS;
433                         memcpy(src_vp.vp_strvalue,
434                                &packet->src_ipaddr.ipaddr.ip6addr,
435                                sizeof(packet->src_ipaddr.ipaddr.ip6addr));
436                         dst_vp.type = PW_TYPE_IPV6ADDR;
437                         dst_vp.attribute = PW_PACKET_DST_IPV6_ADDRESS;
438                         memcpy(dst_vp.vp_strvalue,
439                                &packet->dst_ipaddr.ipaddr.ip6addr,
440                                sizeof(packet->dst_ipaddr.ipaddr.ip6addr));
441                         break;
442                 default:
443                         break;
444                 }
445
446                 if (checked_write_vp(request, &bytes_accum,
447                                      outfd, &src_vp) < 0) {
448                         return RLM_MODULE_FAIL;
449                 }
450                 if (checked_write_vp(request, &bytes_accum,
451                                      outfd, &dst_vp) < 0) {
452                         return RLM_MODULE_FAIL;
453                 }
454
455                 src_vp.attribute = PW_PACKET_SRC_PORT;
456                 src_vp.type = PW_TYPE_INTEGER;
457                 src_vp.vp_integer = packet->src_port;
458                 dst_vp.attribute = PW_PACKET_DST_PORT;
459                 dst_vp.type = PW_TYPE_INTEGER;
460                 dst_vp.vp_integer = packet->dst_port;
461
462                 if (checked_write_vp(request, &bytes_accum,
463                                      outfd, &src_vp) < 0) {
464                         return RLM_MODULE_FAIL;
465                 }
466                 if (checked_write_vp(request, &bytes_accum,
467                                      outfd, &dst_vp) < 0) {
468                         return RLM_MODULE_FAIL;
469                 }
470         }
471
472         /* Write each attribute/value to the log file */
473         for (pair = packet->vps; pair != NULL; pair = pair->next) {
474                 DICT_ATTR da;
475                 da.attr = pair->attribute;
476
477                 if (inst->ht &&
478                     fr_hash_table_finddata(inst->ht, &da)) continue;
479
480                 /*
481                  *      Don't print passwords in old format...
482                  */
483                 if (compat && (pair->attribute == PW_USER_PASSWORD)) continue;
484
485                 /*
486                  *      Print all of the attributes.
487                  */
488                 if (checked_write_vp(request, &bytes_accum,
489                                      outfd, pair) < 0) {
490                         return RLM_MODULE_FAIL;
491                 }
492         }
493
494         /*
495          *      Add non-protocol attibutes.
496          */
497         if (compat) {
498                 if (request->proxy) {
499                         char proxy_buffer[128];
500
501                         inet_ntop(request->proxy->dst_ipaddr.af,
502                                   &request->proxy->dst_ipaddr.ipaddr,
503                                   proxy_buffer, sizeof(proxy_buffer));
504                         if (checked_write(request, &bytes_accum, outfd,
505                                 "\tFreeradius-Proxied-To = %s\n",
506                                 proxy_buffer) < 0) {
507                                 return RLM_MODULE_FAIL;   
508                         }
509                         RDEBUG("Freeradius-Proxied-To = %s",
510                                 proxy_buffer);
511                 }
512
513                 if (checked_write(request, &bytes_accum, outfd,
514                         "\tTimestamp = %ld\n",
515                         (unsigned long) request->timestamp) < 0) {
516                         return RLM_MODULE_FAIL;
517                 }
518         }
519
520         if (checked_write(request, &bytes_accum, outfd, "\n") < 0) {
521                 return RLM_MODULE_FAIL;
522         }
523
524         if (inst->locking) {
525                 lseek(outfd, 0L, SEEK_SET);
526                 rad_unlockfd(outfd, 0);
527                 RDEBUG2("Released filelock");
528         }
529
530         close(outfd);
531
532         /*
533          *      And everything is fine.
534          */
535         return RLM_MODULE_OK;
536 }
537
538 /*
539  *      Accounting - write the detail files.
540  */
541 static int detail_accounting(void *instance, REQUEST *request)
542 {
543         if (request->listener->type == RAD_LISTEN_DETAIL &&
544             strcmp(((struct detail_instance *)instance)->detailfile,
545                    ((listen_detail_t *)request->listener->data)->filename) == 0) {
546                 RDEBUG("Suppressing writes to detail file as the request was just read from a detail file.");
547                 return RLM_MODULE_NOOP;
548         }
549
550         return do_detail(instance,request,request->packet, TRUE);
551 }
552
553 /*
554  *      Incoming Access Request - write the detail files.
555  */
556 static int detail_authorize(void *instance, REQUEST *request)
557 {
558         return do_detail(instance,request,request->packet, FALSE);
559 }
560
561 /*
562  *      Outgoing Access-Request Reply - write the detail files.
563  */
564 static int detail_postauth(void *instance, REQUEST *request)
565 {
566         return do_detail(instance,request,request->reply, FALSE);
567 }
568
569 #ifdef WITH_COA
570 /*
571  *      Incoming CoA - write the detail files.
572  */
573 static int detail_recv_coa(void *instance, REQUEST *request)
574 {
575         return do_detail(instance,request,request->packet, FALSE);
576 }
577
578 /*
579  *      Outgoing CoA - write the detail files.
580  */
581 static int detail_send_coa(void *instance, REQUEST *request)
582 {
583         return do_detail(instance,request,request->reply, FALSE);
584 }
585 #endif
586
587 /*
588  *      Outgoing Access-Request to home server - write the detail files.
589  */
590 static int detail_pre_proxy(void *instance, REQUEST *request)
591 {
592         if (request->proxy &&
593             request->proxy->vps) {
594                 return do_detail(instance,request,request->proxy, FALSE);
595         }
596
597         return RLM_MODULE_NOOP;
598 }
599
600
601 /*
602  *      Outgoing Access-Request Reply - write the detail files.
603  */
604 static int detail_post_proxy(void *instance, REQUEST *request)
605 {
606         if (request->proxy_reply &&
607             request->proxy_reply->vps) {
608                 return do_detail(instance,request,request->proxy_reply, FALSE);
609         }
610
611         /*
612          *      No reply: we must be doing Post-Proxy-Type = Fail.
613          *
614          *      Note that we just call the normal accounting function,
615          *      to minimize the amount of code, and to highlight that
616          *      it's doing normal accounting.
617          */
618         if (!request->proxy_reply) {
619                 int rcode;
620
621                 rcode = detail_accounting(instance, request);
622                 if (rcode == RLM_MODULE_OK) {
623                         request->reply->code = PW_ACCOUNTING_RESPONSE;
624                 }
625                 return rcode;
626         }
627
628         return RLM_MODULE_NOOP;
629 }
630
631
632 /* globally exported name */
633 module_t rlm_detail = {
634         RLM_MODULE_INIT,
635         "detail",
636         RLM_TYPE_THREAD_UNSAFE | RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,
637         detail_instantiate,             /* instantiation */
638         detail_detach,                  /* detach */
639         {
640                 NULL,                   /* authentication */
641                 detail_authorize,       /* authorization */
642                 NULL,                   /* preaccounting */
643                 detail_accounting,      /* accounting */
644                 NULL,                   /* checksimul */
645                 detail_pre_proxy,       /* pre-proxy */
646                 detail_post_proxy,      /* post-proxy */
647                 detail_postauth         /* post-auth */
648 #ifdef WITH_COA
649                 , detail_recv_coa,
650                 detail_send_coa
651 #endif
652         },
653 };
654