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