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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * Copyright 2000 The FreeRADIUS server project
23 static const char rcsid[] = "$Id$";
26 #include "libradius.h"
29 #include <sys/select.h>
40 static const char *packet_codes[] = {
46 "Accounting-Response",
56 struct detail_instance {
60 /* detail file permissions */
63 /* directory permissions */
66 /* last made directory */
67 char *last_made_directory;
69 /* if we want file locking */
72 lrad_hash_table_t *ht;
75 static CONF_PARSER module_config[] = {
76 { "detailfile", PW_TYPE_STRING_PTR,
77 offsetof(struct detail_instance,detailfile), NULL, "%A/%{Client-IP-Address}/detail" },
78 { "detailperm", PW_TYPE_INTEGER,
79 offsetof(struct detail_instance,detailperm), NULL, "0600" },
80 { "dirperm", PW_TYPE_INTEGER,
81 offsetof(struct detail_instance,dirperm), NULL, "0755" },
82 { "locking", PW_TYPE_BOOLEAN,
83 offsetof(struct detail_instance,locking), NULL, "no" },
84 { NULL, -1, 0, NULL, NULL }
91 static int detail_detach(void *instance)
93 struct detail_instance *inst = instance;
94 free((char *) inst->detailfile);
96 free((char*) inst->last_made_directory);
98 if (inst->ht) lrad_hash_table_free(inst->ht);
106 * Hash callback functions. Copied from src/lib/dict.c
108 static uint32_t dict_attr_value_hash(const void *data)
110 return lrad_hash(&((const DICT_ATTR *)data)->attr,
111 sizeof(((const DICT_ATTR *)data)->attr));
114 static int dict_attr_value_cmp(const void *one, const void *two)
116 const DICT_ATTR *a = one;
117 const DICT_ATTR *b = two;
119 return a->attr - b->attr;
124 * (Re-)read radiusd.conf into memory.
126 static int detail_instantiate(CONF_SECTION *conf, void **instance)
128 struct detail_instance *inst;
131 inst = rad_malloc(sizeof(*inst));
135 memset(inst, 0, sizeof(*inst));
137 if (cf_section_parse(conf, inst, module_config) < 0) {
142 inst->last_made_directory = NULL;
145 * Suppress certain attributes.
147 cs = cf_section_sub_find(conf, "suppress");
151 inst->ht = lrad_hash_table_create(dict_attr_value_hash,
155 for (ci = cf_item_find_next(cs, NULL);
157 ci = cf_item_find_next(cs, ci)) {
161 if (!cf_item_is_pair(ci)) continue;
163 attr = cf_pair_attr(cf_itemtopair(ci));
164 if (!attr) continue; /* pair-anoia */
166 da = dict_attrbyname(attr);
168 radlog(L_INFO, "rlm_detail: WARNING: No such attribute %s: Cannot suppress printing it.", attr);
172 if (!lrad_hash_table_insert(inst->ht, da)) {
173 radlog(L_ERR, "rlm_detail: Failed trying to remember %s", attr);
186 * Do detail, compatible with old accounting
188 static int do_detail(void *instance, REQUEST *request, RADIUS_PACKET *packet,
200 char proxy_buffer[16];
201 VALUE_PAIR *pair = packet->vps;
203 struct detail_instance *inst = instance;
206 * Nothing to log: don't do anything.
209 return RLM_MODULE_NOOP;
213 * Create a directory for this nas.
215 * Generate the path for the detail file. Use the
216 * same format, but truncate at the last /. Then
217 * feed it through radius_xlat() to expand the
220 radius_xlat(buffer, sizeof(buffer), inst->detailfile, request, NULL);
221 DEBUG2("rlm_detail: %s expands to %s", inst->detailfile, buffer);
224 * Grab the last directory delimiter.
226 p = strrchr(buffer,'/');
229 * There WAS a directory delimiter there, and
230 * the file doesn't exist, so
231 * we prolly must create it the dir(s)
233 if ((p) && (stat(buffer, &st) < 0)) {
236 * NO previously cached directory name, so we've
237 * got to create a new one.
239 * OR the new directory name is different than the old,
240 * so we've got to create a new one.
242 * OR the cached directory has somehow gotten removed,
243 * so we've got to create a new one.
245 if ((inst->last_made_directory == NULL) ||
246 (strcmp(inst->last_made_directory, buffer) != 0)) {
249 * Free any previously cached name.
251 if (inst->last_made_directory != NULL) {
252 free((char *) inst->last_made_directory);
253 inst->last_made_directory = NULL;
256 inst->last_made_directory = strdup(buffer);
260 * stat the directory, and don't do anything if
261 * it exists. If it doesn't exist, create it.
263 * This also catches the case where some idiot
264 * deleted a directory that the server was using.
266 if (rad_mkdir(inst->last_made_directory, inst->dirperm) < 0) {
267 radlog(L_ERR, "rlm_detail: Failed to create directory %s: %s", inst->last_made_directory, strerror(errno));
268 return RLM_MODULE_FAIL;
272 } /* else there was no directory delimiter. */
275 * Open & create the file, with the given permissions.
277 if ((outfd = open(buffer, O_WRONLY | O_APPEND | O_CREAT,
278 inst->detailperm)) < 0) {
279 radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
280 buffer, strerror(errno));
281 return RLM_MODULE_FAIL;
285 * If we're not using locking, we'll just pass straight though
287 * If we fail to aquire the filelock in 80 tries (approximately
288 * two seconds) we bail out.
294 lseek(outfd, 0L, SEEK_SET);
295 if (rad_lockfd_nonblock(outfd, 0) < 0) {
299 select(0, NULL, NULL, NULL, &tv);
302 DEBUG("rlm_detail: Acquired filelock, tried %d time(s)",
307 } while (!locked && inst->locking && lock_count < 80);
309 if (!locked && inst->locking && lock_count >= 80) {
310 radlog(L_ERR, "rlm_detail: Failed to aquire filelock for %s, giving up",
312 return RLM_MODULE_FAIL;
316 * Convert the FD to FP. The FD is no longer valid
317 * after this operation.
319 if ((outfp = fdopen(outfd, "a")) == NULL) {
320 radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
321 buffer, strerror(errno));
323 lseek(outfd, 0L, SEEK_SET);
324 rad_unlockfd(outfd, 0);
325 DEBUG("rlm_detail: Released filelock");
329 return RLM_MODULE_FAIL;
333 * Write the information to the file.
337 * Print out names, if they're OK.
340 if ((packet->code > 0) &&
341 (packet->code <= PW_ACCESS_CHALLENGE)) {
342 fprintf(outfp, "Packet-Type = %s\n",
343 packet_codes[packet->code]);
345 fprintf(outfp, "Packet-Type = %d\n", packet->code);
352 fseek(outfp, 0L, SEEK_END);
353 fputs(CTIME_R(&request->timestamp, buffer, DIRLEN), outfp);
355 /* Write each attribute/value to the log file */
356 for (; pair != NULL; pair = pair->next) {
358 lrad_hash_table_finddata(inst->ht, pair)) continue;
361 * Don't print passwords in old format...
363 if (compat && (pair->attribute == PW_PASSWORD)) continue;
366 * Print all of the attributes.
369 vp_print(outfp, pair);
374 * Add non-protocol attibutes.
377 if ((pair = pairfind(request->config_items,
378 PW_PROXY_TO_REALM)) != NULL) {
379 proxy_realm = realm_find(pair->strvalue, TRUE);
381 memset((char *) proxy_buffer, 0, 16);
382 ip_ntoa(proxy_buffer, proxy_realm->acct_ipaddr);
383 fprintf(outfp, "\tFreeradius-Proxied-To = %s\n",
385 DEBUG("rlm_detail: Freeradius-Proxied-To set to %s",
389 fprintf(outfp, "\tTimestamp = %ld\n",
390 (unsigned long) request->timestamp);
392 if (request->packet->verified == 2)
393 fputs("\tRequest-Authenticator = Verified\n", outfp);
394 else if (request->packet->verified == 1)
395 fputs("\tRequest-Authenticator = None\n", outfp);
402 lseek(outfd, 0L, SEEK_SET);
403 rad_unlockfd(outfd, 0);
404 DEBUG("rlm_detail: Released filelock");
410 * And everything is fine.
412 return RLM_MODULE_OK;
416 * Accounting - write the detail files.
418 static int detail_accounting(void *instance, REQUEST *request)
421 return do_detail(instance,request,request->packet, TRUE);
425 * Incoming Access Request - write the detail files.
427 static int detail_authorize(void *instance, REQUEST *request)
429 return do_detail(instance,request,request->packet, FALSE);
433 * Outgoing Access-Request Reply - write the detail files.
435 static int detail_postauth(void *instance, REQUEST *request)
437 return do_detail(instance,request,request->reply, FALSE);
442 * Outgoing Access-Request to home server - write the detail files.
444 static int detail_pre_proxy(void *instance, REQUEST *request)
446 if (request->proxy &&
447 request->proxy->vps) {
448 return do_detail(instance,request,request->proxy, FALSE);
451 return RLM_MODULE_NOOP;
456 * Outgoing Access-Request Reply - write the detail files.
458 static int detail_post_proxy(void *instance, REQUEST *request)
460 if (request->proxy_reply &&
461 request->proxy_reply->vps) {
462 return do_detail(instance,request,request->proxy_reply, FALSE);
465 return RLM_MODULE_NOOP;
469 /* globally exported name */
470 module_t rlm_detail = {
472 RLM_TYPE_THREAD_UNSAFE, /* type: reserved */
473 NULL, /* initialization */
474 detail_instantiate, /* instantiation */
476 NULL, /* authentication */
477 detail_authorize, /* authorization */
478 NULL, /* preaccounting */
479 detail_accounting, /* accounting */
480 NULL, /* checksimul */
481 detail_pre_proxy, /* pre-proxy */
482 detail_post_proxy, /* post-proxy */
483 detail_postauth /* post-auth */
485 detail_detach, /* detach */