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>
36 static const char *packet_codes[] = {
42 "Accounting-Response",
58 "Resource-Free-Request",
59 "Resource-Free-Response",
60 "Resource-Query-Request",
61 "Resource-Query-Response",
62 "Alternate-Resource-Reclaim-Request",
64 "NAS-Reboot-Response",
87 "IP-Address-Allocate",
92 struct detail_instance {
96 /* detail file permissions */
99 /* directory permissions */
102 /* last made directory */
103 char *last_made_directory;
105 /* timestamp & stuff */
108 /* if we want file locking */
111 /* log src/dst information */
117 static const CONF_PARSER module_config[] = {
118 { "detailfile", PW_TYPE_STRING_PTR,
119 offsetof(struct detail_instance,detailfile), NULL, "%A/%{Client-IP-Address}/detail" },
120 { "header", PW_TYPE_STRING_PTR,
121 offsetof(struct detail_instance,header), NULL, "%t" },
122 { "detailperm", PW_TYPE_INTEGER,
123 offsetof(struct detail_instance,detailperm), NULL, "0600" },
124 { "dirperm", PW_TYPE_INTEGER,
125 offsetof(struct detail_instance,dirperm), NULL, "0755" },
126 { "locking", PW_TYPE_BOOLEAN,
127 offsetof(struct detail_instance,locking), NULL, "no" },
128 { "log_packet_header", PW_TYPE_BOOLEAN,
129 offsetof(struct detail_instance,log_srcdst), NULL, "no" },
130 { NULL, -1, 0, NULL, NULL }
137 static int detail_detach(void *instance)
139 struct detail_instance *inst = instance;
140 free((char*) inst->last_made_directory);
141 if (inst->ht) fr_hash_table_free(inst->ht);
148 static uint32_t detail_hash(const void *data)
150 const DICT_ATTR *da = data;
151 return fr_hash(&(da->attr), sizeof(da->attr));
154 static int detail_cmp(const void *a, const void *b)
156 return ((const DICT_ATTR *)a)->attr - ((const DICT_ATTR *)b)->attr;
161 * (Re-)read radiusd.conf into memory.
163 static int detail_instantiate(CONF_SECTION *conf, void **instance)
165 struct detail_instance *inst;
168 inst = rad_malloc(sizeof(*inst));
172 memset(inst, 0, sizeof(*inst));
174 if (cf_section_parse(conf, inst, module_config) < 0) {
179 inst->last_made_directory = NULL;
182 * Suppress certain attributes.
184 cs = cf_section_sub_find(conf, "suppress");
188 inst->ht = fr_hash_table_create(detail_hash, detail_cmp,
191 for (ci = cf_item_find_next(cs, NULL);
193 ci = cf_item_find_next(cs, ci)) {
197 if (!cf_item_is_pair(ci)) continue;
199 attr = cf_pair_attr(cf_itemtopair(ci));
200 if (!attr) continue; /* pair-anoia */
202 da = dict_attrbyname(attr);
204 radlog(L_INFO, "rlm_detail: WARNING: No such attribute %s: Cannot suppress printing it.", attr);
209 * For better distribution we should really
210 * hash the attribute number or name. But
211 * since the suppression list will usually
212 * be small, it doesn't matter.
214 if (!fr_hash_table_insert(inst->ht, da)) {
215 radlog(L_ERR, "rlm_detail: Failed trying to remember %s", attr);
228 * Do detail, compatible with old accounting
230 static int do_detail(void *instance, REQUEST *request, RADIUS_PACKET *packet,
244 struct detail_instance *inst = instance;
246 rad_assert(request != NULL);
249 * Nothing to log: don't do anything.
252 return RLM_MODULE_NOOP;
256 * Create a directory for this nas.
258 * Generate the path for the detail file. Use the
259 * same format, but truncate at the last /. Then
260 * feed it through radius_xlat() to expand the
263 radius_xlat(buffer, sizeof(buffer), inst->detailfile, request, NULL);
264 RDEBUG2("%s expands to %s", inst->detailfile, buffer);
267 * Grab the last directory delimiter.
269 p = strrchr(buffer,'/');
272 * There WAS a directory delimiter there, and
273 * the file doesn't exist, so
274 * we prolly must create it the dir(s)
276 if ((p) && (stat(buffer, &st) < 0)) {
279 * NO previously cached directory name, so we've
280 * got to create a new one.
282 * OR the new directory name is different than the old,
283 * so we've got to create a new one.
285 * OR the cached directory has somehow gotten removed,
286 * so we've got to create a new one.
288 if ((inst->last_made_directory == NULL) ||
289 (strcmp(inst->last_made_directory, buffer) != 0)) {
290 free((char *) inst->last_made_directory);
291 inst->last_made_directory = strdup(buffer);
295 * stat the directory, and don't do anything if
296 * it exists. If it doesn't exist, create it.
298 * This also catches the case where some idiot
299 * deleted a directory that the server was using.
301 if (rad_mkdir(inst->last_made_directory, inst->dirperm) < 0) {
302 radlog_request(L_ERR, 0, request, "rlm_detail: Failed to create directory %s: %s", inst->last_made_directory, strerror(errno));
303 return RLM_MODULE_FAIL;
307 } /* else there was no directory delimiter. */
313 * Open & create the file, with the given
316 if ((outfd = open(buffer, O_WRONLY | O_APPEND | O_CREAT,
317 inst->detailperm)) < 0) {
318 radlog_request(L_ERR, 0, request, "rlm_detail: Couldn't open file %s: %s",
319 buffer, strerror(errno));
320 return RLM_MODULE_FAIL;
324 * If we fail to aquire the filelock in 80 tries
325 * (approximately two seconds) we bail out.
328 lseek(outfd, 0L, SEEK_SET);
329 if (rad_lockfd_nonblock(outfd, 0) < 0) {
333 select(0, NULL, NULL, NULL, &tv);
339 * The file might have been deleted by
340 * radrelay while we tried to acquire
341 * the lock (race condition)
343 if (fstat(outfd, &st) != 0) {
344 radlog_request(L_ERR, 0, request, "rlm_detail: Couldn't stat file %s: %s",
345 buffer, strerror(errno));
347 return RLM_MODULE_FAIL;
349 if (st.st_nlink == 0) {
350 RDEBUG2("File %s removed by another program, retrying",
357 RDEBUG2("Acquired filelock, tried %d time(s)",
361 } while (inst->locking && !locked && lock_count < 80);
363 if (inst->locking && !locked) {
365 radlog_request(L_ERR, 0, request, "rlm_detail: Failed to acquire filelock for %s, giving up",
367 return RLM_MODULE_FAIL;
371 * Convert the FD to FP. The FD is no longer valid
372 * after this operation.
374 if ((outfp = fdopen(outfd, "a")) == NULL) {
375 radlog_request(L_ERR, 0, request, "rlm_detail: Couldn't open file %s: %s",
376 buffer, strerror(errno));
378 lseek(outfd, 0L, SEEK_SET);
379 rad_unlockfd(outfd, 0);
380 RDEBUG2("Released filelock");
382 close(outfd); /* automatically releases the lock */
384 return RLM_MODULE_FAIL;
390 fseek(outfp, 0L, SEEK_END);
391 radius_xlat(timestamp, sizeof(timestamp), inst->header, request, NULL);
392 fprintf(outfp, "%s\n", timestamp);
395 * Write the information to the file.
399 * Print out names, if they're OK.
402 if ((packet->code > 0) &&
403 (packet->code < 52)) {
404 fprintf(outfp, "\tPacket-Type = %s\n",
405 packet_codes[packet->code]);
407 fprintf(outfp, "\tPacket-Type = %d\n", packet->code);
411 if (inst->log_srcdst) {
412 VALUE_PAIR src_vp, dst_vp;
414 memset(&src_vp, 0, sizeof(src_vp));
415 memset(&dst_vp, 0, sizeof(dst_vp));
416 src_vp.operator = dst_vp.operator = T_OP_EQ;
418 switch (packet->src_ipaddr.af) {
420 src_vp.type = PW_TYPE_IPADDR;
421 src_vp.attribute = PW_PACKET_SRC_IP_ADDRESS;
422 src_vp.vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
423 dst_vp.type = PW_TYPE_IPADDR;
424 dst_vp.attribute = PW_PACKET_DST_IP_ADDRESS;
425 dst_vp.vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
428 src_vp.type = PW_TYPE_IPV6ADDR;
429 src_vp.attribute = PW_PACKET_SRC_IPV6_ADDRESS;
430 memcpy(src_vp.vp_strvalue,
431 &packet->src_ipaddr.ipaddr.ip6addr,
432 sizeof(packet->src_ipaddr.ipaddr.ip6addr));
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));
444 vp_print(outfp, &src_vp);
447 vp_print(outfp, &dst_vp);
450 src_vp.attribute = PW_PACKET_SRC_PORT;
451 src_vp.type = PW_TYPE_INTEGER;
452 src_vp.vp_integer = packet->src_port;
453 dst_vp.attribute = PW_PACKET_DST_PORT;
454 dst_vp.type = PW_TYPE_INTEGER;
455 dst_vp.vp_integer = packet->dst_port;
458 vp_print(outfp, &src_vp);
461 vp_print(outfp, &dst_vp);
465 /* Write each attribute/value to the log file */
466 for (pair = packet->vps; pair != NULL; pair = pair->next) {
468 da.attr = pair->attribute;
471 fr_hash_table_finddata(inst->ht, &da)) continue;
474 * Don't print passwords in old format...
476 if (compat && (pair->attribute == PW_USER_PASSWORD)) continue;
479 * Print all of the attributes.
482 vp_print(outfp, pair);
487 * Add non-protocol attibutes.
490 if (request->proxy) {
491 char proxy_buffer[128];
493 inet_ntop(request->proxy->dst_ipaddr.af,
494 &request->proxy->dst_ipaddr.ipaddr,
495 proxy_buffer, sizeof(proxy_buffer));
496 fprintf(outfp, "\tFreeradius-Proxied-To = %s\n",
498 RDEBUG("Freeradius-Proxied-To = %s",
502 fprintf(outfp, "\tTimestamp = %ld\n",
503 (unsigned long) request->timestamp);
506 * We no longer permit Accounting-Request packets
507 * with an authenticator of zero.
509 fputs("\tRequest-Authenticator = Verified\n", outfp);
516 lseek(outfd, 0L, SEEK_SET);
517 rad_unlockfd(outfd, 0);
518 RDEBUG2("Released filelock");
524 * And everything is fine.
526 return RLM_MODULE_OK;
530 * Accounting - write the detail files.
532 static int detail_accounting(void *instance, REQUEST *request)
534 if (request->listener->type == RAD_LISTEN_DETAIL) {
535 RDEBUG("Suppressing writes to detail file as the request was just read from a detail file.");
536 return RLM_MODULE_NOOP;
539 return do_detail(instance,request,request->packet, TRUE);
543 * Incoming Access Request - write the detail files.
545 static int detail_authorize(void *instance, REQUEST *request)
547 return do_detail(instance,request,request->packet, FALSE);
551 * Outgoing Access-Request Reply - write the detail files.
553 static int detail_postauth(void *instance, REQUEST *request)
555 return do_detail(instance,request,request->reply, FALSE);
560 * Incoming CoA - write the detail files.
562 static int detail_recv_coa(void *instance, REQUEST *request)
564 return do_detail(instance,request,request->packet, FALSE);
568 * Outgoing CoA - write the detail files.
570 static int detail_send_coa(void *instance, REQUEST *request)
572 return do_detail(instance,request,request->reply, FALSE);
577 * Outgoing Access-Request to home server - write the detail files.
579 static int detail_pre_proxy(void *instance, REQUEST *request)
581 if (request->proxy &&
582 request->proxy->vps) {
583 return do_detail(instance,request,request->proxy, FALSE);
586 return RLM_MODULE_NOOP;
591 * Outgoing Access-Request Reply - write the detail files.
593 static int detail_post_proxy(void *instance, REQUEST *request)
595 if (request->proxy_reply &&
596 request->proxy_reply->vps) {
597 return do_detail(instance,request,request->proxy_reply, FALSE);
601 * No reply: we must be doing Post-Proxy-Type = Fail.
603 * Note that we just call the normal accounting function,
604 * to minimize the amount of code, and to highlight that
605 * it's doing normal accounting.
607 if (!request->proxy_reply) {
610 rcode = detail_accounting(instance, request);
611 if (rcode == RLM_MODULE_OK) {
612 request->reply->code = PW_ACCOUNTING_RESPONSE;
617 return RLM_MODULE_NOOP;
621 /* globally exported name */
622 module_t rlm_detail = {
625 RLM_TYPE_THREAD_UNSAFE | RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,
626 detail_instantiate, /* instantiation */
627 detail_detach, /* detach */
629 NULL, /* authentication */
630 detail_authorize, /* authorization */
631 NULL, /* preaccounting */
632 detail_accounting, /* accounting */
633 NULL, /* checksimul */
634 detail_pre_proxy, /* pre-proxy */
635 detail_post_proxy, /* post-proxy */
636 detail_postauth /* post-auth */