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/autoconf.h>
29 #include <sys/select.h>
36 #include <freeradius-devel/radiusd.h>
37 #include <freeradius-devel/modules.h>
38 #include <freeradius-devel/rad_assert.h>
42 static const char *packet_codes[] = {
48 "Accounting-Response",
58 struct detail_instance {
62 /* detail file permissions */
65 /* directory permissions */
68 /* last made directory */
69 char *last_made_directory;
71 /* timestamp & stuff */
74 /* if we want file locking */
77 /* log src/dst information */
80 lrad_hash_table_t *ht;
83 static const CONF_PARSER module_config[] = {
84 { "detailfile", PW_TYPE_STRING_PTR,
85 offsetof(struct detail_instance,detailfile), NULL, "%A/%{Client-IP-Address}/detail" },
86 { "header", PW_TYPE_STRING_PTR,
87 offsetof(struct detail_instance,header), NULL, "%t" },
88 { "detailperm", PW_TYPE_INTEGER,
89 offsetof(struct detail_instance,detailperm), NULL, "0600" },
90 { "dirperm", PW_TYPE_INTEGER,
91 offsetof(struct detail_instance,dirperm), NULL, "0755" },
92 { "locking", PW_TYPE_BOOLEAN,
93 offsetof(struct detail_instance,locking), NULL, "no" },
94 { "log_packet_header", PW_TYPE_BOOLEAN,
95 offsetof(struct detail_instance,log_srcdst), NULL, "no" },
96 { NULL, -1, 0, NULL, NULL }
103 static int detail_detach(void *instance)
105 struct detail_instance *inst = instance;
106 free((char *) inst->detailfile);
108 free((char*) inst->last_made_directory);
110 free((char*) inst->header);
112 if (inst->ht) lrad_hash_table_free(inst->ht);
119 static uint32_t detail_hash(const void *data)
121 const DICT_ATTR *da = data;
122 return lrad_hash(&(da->attr), sizeof(da->attr));
125 static int detail_cmp(const void *a, const void *b)
127 return ((const DICT_ATTR *)a)->attr - ((const DICT_ATTR *)b)->attr;
132 * (Re-)read radiusd.conf into memory.
134 static int detail_instantiate(CONF_SECTION *conf, void **instance)
136 struct detail_instance *inst;
139 inst = rad_malloc(sizeof(*inst));
143 memset(inst, 0, sizeof(*inst));
145 if (cf_section_parse(conf, inst, module_config) < 0) {
150 inst->last_made_directory = NULL;
153 * Suppress certain attributes.
155 cs = cf_section_sub_find(conf, "suppress");
159 inst->ht = lrad_hash_table_create(detail_hash, detail_cmp,
162 for (ci = cf_item_find_next(cs, NULL);
164 ci = cf_item_find_next(cs, ci)) {
168 if (!cf_item_is_pair(ci)) continue;
170 attr = cf_pair_attr(cf_itemtopair(ci));
171 if (!attr) continue; /* pair-anoia */
173 da = dict_attrbyname(attr);
175 radlog(L_INFO, "rlm_detail: WARNING: No such attribute %s: Cannot suppress printing it.", attr);
180 * For better distribution we should really
181 * hash the attribute number or name. But
182 * since the suppression list will usually
183 * be small, it doesn't matter.
185 if (!lrad_hash_table_insert(inst->ht, da)) {
186 radlog(L_ERR, "rlm_detail: Failed trying to remember %s", attr);
199 * Do detail, compatible with old accounting
201 static int do_detail(void *instance, REQUEST *request, RADIUS_PACKET *packet,
214 char proxy_buffer[16];
215 VALUE_PAIR *pair = packet->vps;
217 struct detail_instance *inst = instance;
220 * Nothing to log: don't do anything.
223 return RLM_MODULE_NOOP;
227 * Create a directory for this nas.
229 * Generate the path for the detail file. Use the
230 * same format, but truncate at the last /. Then
231 * feed it through radius_xlat() to expand the
234 radius_xlat(buffer, sizeof(buffer), inst->detailfile, request, NULL);
235 DEBUG2("rlm_detail: %s expands to %s", inst->detailfile, buffer);
238 * Grab the last directory delimiter.
240 p = strrchr(buffer,'/');
243 * There WAS a directory delimiter there, and
244 * the file doesn't exist, so
245 * we prolly must create it the dir(s)
247 if ((p) && (stat(buffer, &st) < 0)) {
250 * NO previously cached directory name, so we've
251 * got to create a new one.
253 * OR the new directory name is different than the old,
254 * so we've got to create a new one.
256 * OR the cached directory has somehow gotten removed,
257 * so we've got to create a new one.
259 if ((inst->last_made_directory == NULL) ||
260 (strcmp(inst->last_made_directory, buffer) != 0)) {
261 free((char *) inst->last_made_directory);
262 inst->last_made_directory = strdup(buffer);
266 * stat the directory, and don't do anything if
267 * it exists. If it doesn't exist, create it.
269 * This also catches the case where some idiot
270 * deleted a directory that the server was using.
272 if (rad_mkdir(inst->last_made_directory, inst->dirperm) < 0) {
273 radlog(L_ERR, "rlm_detail: Failed to create directory %s: %s", inst->last_made_directory, strerror(errno));
274 return RLM_MODULE_FAIL;
278 } /* else there was no directory delimiter. */
284 * Open & create the file, with the given
287 if ((outfd = open(buffer, O_WRONLY | O_APPEND | O_CREAT,
288 inst->detailperm)) < 0) {
289 radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
290 buffer, strerror(errno));
291 return RLM_MODULE_FAIL;
295 * If we fail to aquire the filelock in 80 tries
296 * (approximately two seconds) we bail out.
299 lseek(outfd, 0L, SEEK_SET);
300 if (rad_lockfd_nonblock(outfd, 0) < 0) {
304 select(0, NULL, NULL, NULL, &tv);
310 * The file might have been deleted by
311 * radrelay while we tried to acquire
312 * the lock (race condition)
314 if (fstat(outfd, &st) != 0) {
315 radlog(L_ERR, "rlm_detail: Couldn't stat file %s: %s",
316 buffer, strerror(errno));
318 return RLM_MODULE_FAIL;
320 if (st.st_nlink == 0) {
321 DEBUG("rlm_detail: File %s removed by another program, retrying",
328 DEBUG("rlm_detail: Acquired filelock, tried %d time(s)",
332 } while (inst->locking && !locked && lock_count < 80);
334 if (inst->locking && !locked) {
336 radlog(L_ERR, "rlm_detail: Failed to aquire filelock for %s, giving up",
338 return RLM_MODULE_FAIL;
342 * Convert the FD to FP. The FD is no longer valid
343 * after this operation.
345 if ((outfp = fdopen(outfd, "a")) == NULL) {
346 radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
347 buffer, strerror(errno));
349 lseek(outfd, 0L, SEEK_SET);
350 rad_unlockfd(outfd, 0);
351 DEBUG("rlm_detail: Released filelock");
353 close(outfd); /* automatically releases the lock */
355 return RLM_MODULE_FAIL;
361 fseek(outfp, 0L, SEEK_END);
362 radius_xlat(timestamp, sizeof(timestamp), inst->header, request, NULL);
363 fprintf(outfp, "%s\n", timestamp);
366 * Write the information to the file.
370 * Print out names, if they're OK.
373 if ((packet->code > 0) &&
374 (packet->code <= PW_ACCESS_CHALLENGE)) {
375 fprintf(outfp, "\tPacket-Type = %s\n",
376 packet_codes[packet->code]);
378 fprintf(outfp, "\tPacket-Type = %d\n", packet->code);
382 if (inst->log_srcdst) {
383 VALUE_PAIR src_vp, dst_vp;
385 src_vp.name[0] = dst_vp.name[0] = '\0'; /* for vp_prints() */
386 src_vp.operator = dst_vp.operator = T_OP_EQ;
388 switch (packet->src_ipaddr.af) {
390 src_vp.type = PW_TYPE_IPADDR;
391 src_vp.attribute = PW_PACKET_SRC_IP_ADDRESS;
392 src_vp.lvalue = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
393 dst_vp.type = PW_TYPE_IPADDR;
394 dst_vp.attribute = PW_PACKET_DST_IP_ADDRESS;
395 dst_vp.lvalue = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
398 src_vp.type = PW_TYPE_IPV6ADDR;
399 src_vp.attribute = PW_PACKET_SRC_IPV6_ADDRESS;
400 memcpy(src_vp.vp_strvalue,
401 &packet->src_ipaddr.ipaddr.ip6addr,
402 sizeof(packet->src_ipaddr.ipaddr.ip6addr));
403 dst_vp.type = PW_TYPE_IPV6ADDR;
404 dst_vp.attribute = PW_PACKET_DST_IPV6_ADDRESS;
405 memcpy(dst_vp.vp_strvalue,
406 &packet->dst_ipaddr.ipaddr.ip6addr,
407 sizeof(packet->dst_ipaddr.ipaddr.ip6addr));
414 vp_print(outfp, &src_vp);
417 vp_print(outfp, &dst_vp);
420 src_vp.attribute = PW_PACKET_SRC_PORT;
421 src_vp.type = PW_TYPE_INTEGER;
422 src_vp.lvalue = packet->src_port;
423 dst_vp.attribute = PW_PACKET_DST_PORT;
424 dst_vp.type = PW_TYPE_INTEGER;
425 dst_vp.lvalue = packet->dst_port;
428 vp_print(outfp, &src_vp);
431 vp_print(outfp, &dst_vp);
435 /* Write each attribute/value to the log file */
436 for (; pair != NULL; pair = pair->next) {
438 da.attr = pair->attribute;
441 lrad_hash_table_finddata(inst->ht, &da)) continue;
444 * Don't print passwords in old format...
446 if (compat && (pair->attribute == PW_USER_PASSWORD)) continue;
449 * Print all of the attributes.
452 vp_print(outfp, pair);
457 * Add non-protocol attibutes.
460 if ((pair = pairfind(request->config_items,
461 PW_PROXY_TO_REALM)) != NULL) {
462 proxy_realm = realm_find(pair->vp_strvalue, TRUE);
464 memset((char *) proxy_buffer, 0, 16);
466 rad_assert(proxy_realm->acct_ipaddr.af == AF_INET);
468 inet_ntop(proxy_realm->acct_ipaddr.af,
469 &proxy_realm->acct_ipaddr.ipaddr,
470 proxy_buffer, sizeof(proxy_buffer));
471 fprintf(outfp, "\tFreeradius-Proxied-To = %s\n",
473 DEBUG("rlm_detail: Freeradius-Proxied-To set to %s",
477 fprintf(outfp, "\tTimestamp = %ld\n",
478 (unsigned long) request->timestamp);
480 if (request->packet->verified == 2)
481 fputs("\tRequest-Authenticator = Verified\n", outfp);
482 else if (request->packet->verified == 1)
483 fputs("\tRequest-Authenticator = None\n", outfp);
490 lseek(outfd, 0L, SEEK_SET);
491 rad_unlockfd(outfd, 0);
492 DEBUG("rlm_detail: Released filelock");
498 * And everything is fine.
500 return RLM_MODULE_OK;
504 * Accounting - write the detail files.
506 static int detail_accounting(void *instance, REQUEST *request)
509 return do_detail(instance,request,request->packet, TRUE);
513 * Incoming Access Request - write the detail files.
515 static int detail_authorize(void *instance, REQUEST *request)
517 return do_detail(instance,request,request->packet, FALSE);
521 * Outgoing Access-Request Reply - write the detail files.
523 static int detail_postauth(void *instance, REQUEST *request)
525 return do_detail(instance,request,request->reply, FALSE);
530 * Outgoing Access-Request to home server - write the detail files.
532 static int detail_pre_proxy(void *instance, REQUEST *request)
534 if (request->proxy &&
535 request->proxy->vps) {
536 return do_detail(instance,request,request->proxy, FALSE);
539 return RLM_MODULE_NOOP;
544 * Outgoing Access-Request Reply - write the detail files.
546 static int detail_post_proxy(void *instance, REQUEST *request)
548 if (request->proxy_reply &&
549 request->proxy_reply->vps) {
550 return do_detail(instance,request,request->proxy_reply, FALSE);
553 return RLM_MODULE_NOOP;
557 /* globally exported name */
558 module_t rlm_detail = {
561 RLM_TYPE_THREAD_UNSAFE, /* type: reserved */
562 detail_instantiate, /* instantiation */
563 detail_detach, /* detach */
565 NULL, /* authentication */
566 detail_authorize, /* authorization */
567 NULL, /* preaccounting */
568 detail_accounting, /* accounting */
569 NULL, /* checksimul */
570 detail_pre_proxy, /* pre-proxy */
571 detail_post_proxy, /* post-proxy */
572 detail_postauth /* post-auth */