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>
41 struct detail_instance {
45 /* detail file permissions */
48 /* directory permissions */
51 /* timestamp & stuff */
54 /* if we want file locking */
57 /* log src/dst information */
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 }
83 static int detail_detach(void *instance)
85 struct detail_instance *inst = instance;
86 if (inst->ht) fr_hash_table_free(inst->ht);
93 static uint32_t detail_hash(const void *data)
95 const DICT_ATTR *da = data;
96 return fr_hash(&(da->attr), sizeof(da->attr));
99 static int detail_cmp(const void *a, const void *b)
101 return ((const DICT_ATTR *)a)->attr - ((const DICT_ATTR *)b)->attr;
106 * (Re-)read radiusd.conf into memory.
108 static int detail_instantiate(CONF_SECTION *conf, void **instance)
110 struct detail_instance *inst;
113 inst = rad_malloc(sizeof(*inst));
117 memset(inst, 0, sizeof(*inst));
119 if (cf_section_parse(conf, inst, module_config) < 0) {
125 * Suppress certain attributes.
127 cs = cf_section_sub_find(conf, "suppress");
131 inst->ht = fr_hash_table_create(detail_hash, detail_cmp,
134 for (ci = cf_item_find_next(cs, NULL);
136 ci = cf_item_find_next(cs, ci)) {
140 if (!cf_item_is_pair(ci)) continue;
142 attr = cf_pair_attr(cf_itemtopair(ci));
143 if (!attr) continue; /* pair-anoia */
145 da = dict_attrbyname(attr);
147 radlog(L_INFO, "rlm_detail: WARNING: No such attribute %s: Cannot suppress printing it.", attr);
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.
157 if (!fr_hash_table_insert(inst->ht, da)) {
158 radlog(L_ERR, "rlm_detail: Failed trying to remember %s", attr);
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.
174 static int checked_write(REQUEST *request, off_t *bytes_accum, int fd,
175 const char *format, ...)
178 int buf_used, written;
181 va_start(args, format);
182 buf_used = vsnprintf(buf, sizeof(buf), format, args);
185 if (buf_used == 0) return 0;
187 if (buf_used >= (int) sizeof(buf)) {
188 radlog_request(L_ERR, 0, request, "Truncated vsnprintf");
192 written = write(fd, buf, buf_used);
194 *bytes_accum += written;
196 if (written != buf_used) {
198 * Don't worry if the truncate fails, since the
199 * detail reader ignores partial entries.
201 ftruncate(fd, lseek(fd, 0, SEEK_CUR) - *bytes_accum);
204 radlog_request(L_ERR, 0, request, "Truncated write (wanted %d, wrote %d)",
212 static int checked_write_vp(REQUEST *request, off_t *bytes_accum, int fd,
217 vp_prints(buffer, sizeof(buffer), vp);
219 if (checked_write(request, bytes_accum, fd, "\t%s\n", buffer) < 0) {
227 * Do detail, compatible with old accounting
229 static int do_detail(void *instance, REQUEST *request, RADIUS_PACKET *packet,
239 off_t bytes_accum = 0;
243 struct detail_instance *inst = instance;
245 rad_assert(request != NULL);
248 * Nothing to log: don't do anything.
251 return RLM_MODULE_NOOP;
255 * Create a directory for this nas.
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
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",
265 return RLM_MODULE_FAIL;
267 RDEBUG2("%s expands to %s", inst->detailfile, buffer);
269 #ifdef HAVE_FNMATCH_H
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
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;
285 * Grab the last directory delimiter.
287 p = strrchr(buffer,'/');
290 * There WAS a directory delimiter there, and the file
291 * doesn't exist, so we must create it the directories..
295 * Always try to create the directory. If it
296 * exists, rad_mkdir() will check via stat(), and
297 * return immediately.
299 * This catches the case where some idiot deleted
300 * a directory that the server was using.
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;
308 } /* else there was no directory delimiter. */
314 * Open & create the file, with the given
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;
325 * If we fail to aquire the filelock in 80 tries
326 * (approximately two seconds) we bail out.
329 lseek(outfd, 0L, SEEK_SET);
330 if (rad_lockfd_nonblock(outfd, 0) < 0) {
334 select(0, NULL, NULL, NULL, &tv);
340 * The file might have been deleted by
341 * radrelay while we tried to acquire
342 * the lock (race condition)
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));
348 return RLM_MODULE_FAIL;
350 if (st.st_nlink == 0) {
351 RDEBUG2("File %s removed by another program, retrying",
358 RDEBUG2("Acquired filelock, tried %d time(s)",
362 } while (inst->locking && !locked && lock_count < 80);
364 if (inst->locking && !locked) {
366 radlog_request(L_ERR, 0, request, "rlm_detail: Failed to acquire filelock for %s, giving up",
368 return RLM_MODULE_FAIL;
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",
378 return RLM_MODULE_FAIL;
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",
384 return RLM_MODULE_FAIL;
386 if (checked_write(request, &bytes_accum, outfd,
387 "%s\n", timestamp) < 0) {
388 return RLM_MODULE_FAIL;
392 * Write the information to the file.
396 * Print out names, if they're OK.
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;
407 if (checked_write(request, &bytes_accum, outfd,
408 "\tPacket-Type = %d\n", packet->code) == -1) {
409 return RLM_MODULE_FAIL;
414 if (inst->log_srcdst) {
415 VALUE_PAIR src_vp, dst_vp;
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;
421 switch (packet->src_ipaddr.af) {
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;
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));
446 if (checked_write_vp(request, &bytes_accum,
447 outfd, &src_vp) < 0) {
448 return RLM_MODULE_FAIL;
450 if (checked_write_vp(request, &bytes_accum,
451 outfd, &dst_vp) < 0) {
452 return RLM_MODULE_FAIL;
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;
462 if (checked_write_vp(request, &bytes_accum,
463 outfd, &src_vp) < 0) {
464 return RLM_MODULE_FAIL;
466 if (checked_write_vp(request, &bytes_accum,
467 outfd, &dst_vp) < 0) {
468 return RLM_MODULE_FAIL;
472 /* Write each attribute/value to the log file */
473 for (pair = packet->vps; pair != NULL; pair = pair->next) {
475 da.attr = pair->attribute;
478 fr_hash_table_finddata(inst->ht, &da)) continue;
481 * Don't print passwords in old format...
483 if (compat && (pair->attribute == PW_USER_PASSWORD)) continue;
486 * Print all of the attributes.
488 if (checked_write_vp(request, &bytes_accum,
490 return RLM_MODULE_FAIL;
495 * Add non-protocol attibutes.
498 if (request->proxy) {
499 char proxy_buffer[128];
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",
507 return RLM_MODULE_FAIL;
509 RDEBUG("Freeradius-Proxied-To = %s",
513 if (checked_write(request, &bytes_accum, outfd,
514 "\tTimestamp = %ld\n",
515 (unsigned long) request->timestamp) < 0) {
516 return RLM_MODULE_FAIL;
520 if (checked_write(request, &bytes_accum, outfd, "\n") < 0) {
521 return RLM_MODULE_FAIL;
525 lseek(outfd, 0L, SEEK_SET);
526 rad_unlockfd(outfd, 0);
527 RDEBUG2("Released filelock");
533 * And everything is fine.
535 return RLM_MODULE_OK;
539 * Accounting - write the detail files.
541 static int detail_accounting(void *instance, REQUEST *request)
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;
550 return do_detail(instance,request,request->packet, TRUE);
554 * Incoming Access Request - write the detail files.
556 static int detail_authorize(void *instance, REQUEST *request)
558 return do_detail(instance,request,request->packet, FALSE);
562 * Outgoing Access-Request Reply - write the detail files.
564 static int detail_postauth(void *instance, REQUEST *request)
566 return do_detail(instance,request,request->reply, FALSE);
571 * Incoming CoA - write the detail files.
573 static int detail_recv_coa(void *instance, REQUEST *request)
575 return do_detail(instance,request,request->packet, FALSE);
579 * Outgoing CoA - write the detail files.
581 static int detail_send_coa(void *instance, REQUEST *request)
583 return do_detail(instance,request,request->reply, FALSE);
588 * Outgoing Access-Request to home server - write the detail files.
590 static int detail_pre_proxy(void *instance, REQUEST *request)
592 if (request->proxy &&
593 request->proxy->vps) {
594 return do_detail(instance,request,request->proxy, FALSE);
597 return RLM_MODULE_NOOP;
602 * Outgoing Access-Request Reply - write the detail files.
604 static int detail_post_proxy(void *instance, REQUEST *request)
606 if (request->proxy_reply &&
607 request->proxy_reply->vps) {
608 return do_detail(instance,request,request->proxy_reply, FALSE);
612 * No reply: we must be doing Post-Proxy-Type = Fail.
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.
618 if (!request->proxy_reply) {
621 rcode = detail_accounting(instance, request);
622 if (rcode == RLM_MODULE_OK) {
623 request->reply->code = PW_ACCOUNTING_RESPONSE;
628 return RLM_MODULE_NOOP;
632 /* globally exported name */
633 module_t rlm_detail = {
636 RLM_TYPE_THREAD_UNSAFE | RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,
637 detail_instantiate, /* instantiation */
638 detail_detach, /* detach */
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 */