2 * rlm_detail.c accounting: Write the "detail" files.
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.
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.
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
20 * Copyright 2000,2006 The FreeRADIUS server project
23 #include <freeradius-devel/ident.h>
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>
49 struct detail_instance {
53 /* detail file permissions */
56 /* detail file group */
59 /* directory permissions */
62 /* timestamp & stuff */
65 /* if we want file locking */
68 /* log src/dst information */
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 }
96 static int detail_detach(void *instance)
98 struct detail_instance *inst = instance;
99 if (inst->ht) fr_hash_table_free(inst->ht);
106 static uint32_t detail_hash(const void *data)
108 const DICT_ATTR *da = data;
109 return fr_hash(&(da->attr), sizeof(da->attr));
112 static int detail_cmp(const void *a, const void *b)
114 return ((const DICT_ATTR *)a)->attr - ((const DICT_ATTR *)b)->attr;
119 * (Re-)read radiusd.conf into memory.
121 static int detail_instantiate(CONF_SECTION *conf, void **instance)
123 struct detail_instance *inst;
126 inst = rad_malloc(sizeof(*inst));
130 memset(inst, 0, sizeof(*inst));
132 if (cf_section_parse(conf, inst, module_config) < 0) {
138 * Suppress certain attributes.
140 cs = cf_section_sub_find(conf, "suppress");
144 inst->ht = fr_hash_table_create(detail_hash, detail_cmp,
147 for (ci = cf_item_find_next(cs, NULL);
149 ci = cf_item_find_next(cs, ci)) {
153 if (!cf_item_is_pair(ci)) continue;
155 attr = cf_pair_attr(cf_itemtopair(ci));
156 if (!attr) continue; /* pair-anoia */
158 da = dict_attrbyname(attr);
160 radlog(L_INFO, "rlm_detail: WARNING: No such attribute %s: Cannot suppress printing it.", attr);
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.
170 if (!fr_hash_table_insert(inst->ht, da)) {
171 radlog(L_ERR, "rlm_detail: Failed trying to remember %s", attr);
184 * Do detail, compatible with old accounting
186 static int do_detail(void *instance, REQUEST *request, RADIUS_PACKET *packet,
207 struct detail_instance *inst = instance;
209 rad_assert(request != NULL);
212 * Nothing to log: don't do anything.
215 return RLM_MODULE_NOOP;
219 * Create a directory for this nas.
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
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",
229 return RLM_MODULE_FAIL;
231 RDEBUG2("%s expands to %s", inst->detailfile, buffer);
233 #ifdef HAVE_FNMATCH_H
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
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;
251 * Grab the last directory delimiter.
253 p = strrchr(buffer,'/');
256 * There WAS a directory delimiter there, and the file
257 * doesn't exist, so we must create it the directories..
263 * Always try to create the directory. If it
264 * exists, rad_mkdir() will check via stat(), and
265 * return immediately.
267 * This catches the case where some idiot deleted
268 * a directory that the server was using.
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;
276 } /* else there was no directory delimiter. */
282 * Open & create the file, with the given
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;
293 * If we fail to aquire the filelock in 80 tries
294 * (approximately two seconds) we bail out.
297 lseek(outfd, 0L, SEEK_SET);
298 if (rad_lockfd_nonblock(outfd, 0) < 0) {
302 select(0, NULL, NULL, NULL, &tv);
308 * The file might have been deleted by
309 * radrelay while we tried to acquire
310 * the lock (race condition)
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));
316 return RLM_MODULE_FAIL;
318 if (st.st_nlink == 0) {
319 RDEBUG2("File %s removed by another program, retrying",
326 RDEBUG2("Acquired filelock, tried %d time(s)",
330 } while (inst->locking && !locked && lock_count < 80);
332 if (inst->locking && !locked) {
334 radlog_request(L_ERR, 0, request, "rlm_detail: Failed to acquire filelock for %s, giving up",
336 return RLM_MODULE_FAIL;
341 if (inst->group != NULL) {
342 gid = strtol(inst->group, &endptr, 10);
343 if (*endptr != '\0') {
344 grp = getgrnam(inst->group);
346 RDEBUG2("rlm_detail: Unable to find system group \"%s\"", inst->group);
352 if (chown(buffer, -1, gid) == -1) {
353 RDEBUG2("rlm_detail: Unable to change system group of \"%s\"", buffer);
363 fsize = lseek(outfd, 0L, SEEK_END);
365 radlog_request(L_ERR, 0, request, "rlm_detail: Failed to seek to the end of detail file %s",
368 return RLM_MODULE_FAIL;
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",
375 return RLM_MODULE_FAIL;
379 * Open the FP for buffering.
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));
385 return RLM_MODULE_FAIL;
388 fprintf(fp, "%s\n", timestamp);
391 * Write the information to the file.
395 * Print out names, if they're OK.
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]);
403 fprintf(fp, "\tPacket-Type = %d\n", packet->code);
407 if (inst->log_srcdst) {
408 VALUE_PAIR src_vp, dst_vp;
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;
414 switch (packet->src_ipaddr.af) {
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;
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));
443 vp_print(fp, &src_vp);
444 vp_print(fp, &dst_vp);
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;
455 vp_print(fp, &src_vp);
456 vp_print(fp, &dst_vp);
459 /* Write each attribute/value to the log file */
460 for (pair = packet->vps; pair != NULL; pair = pair->next) {
462 da.attr = pair->attribute;
465 fr_hash_table_finddata(inst->ht, &da)) continue;
468 * Don't print passwords in old format...
470 if (compat && (pair->attribute == PW_USER_PASSWORD)) continue;
473 * Print all of the attributes.
479 * Add non-protocol attibutes.
483 if (request->proxy) {
484 char proxy_buffer[128];
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",
491 RDEBUG("Freeradius-Proxied-To = %s",
496 fprintf(fp, "\tTimestamp = %ld\n",
497 (unsigned long) request->timestamp);
503 * If we can't flush it to disk, truncate the file and
506 if (fflush(fp) != 0) {
507 ftruncate(outfd, fsize); /* ignore errors! */
509 return RLM_MODULE_FAIL;
515 * And everything is fine.
517 return RLM_MODULE_OK;
521 * Accounting - write the detail files.
523 static int detail_accounting(void *instance, REQUEST *request)
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;
534 return do_detail(instance,request,request->packet, TRUE);
538 * Incoming Access Request - write the detail files.
540 static int detail_authorize(void *instance, REQUEST *request)
542 return do_detail(instance,request,request->packet, FALSE);
546 * Outgoing Access-Request Reply - write the detail files.
548 static int detail_postauth(void *instance, REQUEST *request)
550 return do_detail(instance,request,request->reply, FALSE);
555 * Incoming CoA - write the detail files.
557 static int detail_recv_coa(void *instance, REQUEST *request)
559 return do_detail(instance,request,request->packet, FALSE);
563 * Outgoing CoA - write the detail files.
565 static int detail_send_coa(void *instance, REQUEST *request)
567 return do_detail(instance,request,request->reply, FALSE);
572 * Outgoing Access-Request to home server - write the detail files.
575 static int detail_pre_proxy(void *instance, REQUEST *request)
577 if (request->proxy &&
578 request->proxy->vps) {
579 return do_detail(instance,request,request->proxy, FALSE);
582 return RLM_MODULE_NOOP;
587 * Outgoing Access-Request Reply - write the detail files.
589 static int detail_post_proxy(void *instance, REQUEST *request)
591 if (request->proxy_reply &&
592 request->proxy_reply->vps) {
593 return do_detail(instance,request,request->proxy_reply, FALSE);
597 * No reply: we must be doing Post-Proxy-Type = Fail.
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.
603 if (!request->proxy_reply) {
606 rcode = detail_accounting(instance, request);
607 if (rcode == RLM_MODULE_OK) {
608 request->reply->code = PW_ACCOUNTING_RESPONSE;
613 return RLM_MODULE_NOOP;
618 /* globally exported name */
619 module_t rlm_detail = {
622 RLM_TYPE_THREAD_UNSAFE | RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,
623 detail_instantiate, /* instantiation */
624 detail_detach, /* detach */
626 NULL, /* authentication */
627 detail_authorize, /* authorization */
628 NULL, /* preaccounting */
629 detail_accounting, /* accounting */
630 NULL, /* checksimul */
632 detail_pre_proxy, /* pre-proxy */
633 detail_post_proxy, /* post-proxy */
637 detail_postauth /* post-auth */