import from HEAD:
[freeradius.git] / src / modules / rlm_detail / rlm_detail.c
1 /*
2  * rlm_detail.c accounting:    Write the "detail" files.
3  *
4  * Version:     $Id$
5  *
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.
10  *
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.
15  *
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
19  *
20  * Copyright 2000  The FreeRADIUS server project
21  */
22
23 static const char rcsid[] = "$Id$";
24
25 #include        "autoconf.h"
26 #include        "libradius.h"
27
28 #include        <sys/stat.h>
29 #include        <sys/select.h>
30
31 #include        <stdlib.h>
32 #include        <string.h>
33 #include        <ctype.h>
34 #include        <fcntl.h>
35
36 #include        "radiusd.h"
37 #include        "modules.h"
38 #define         DIRLEN  8192
39
40 static const char *packet_codes[] = {
41   "",
42   "Access-Request",
43   "Access-Accept",
44   "Access-Reject",
45   "Accounting-Request",
46   "Accounting-Response",
47   "Accounting-Status",
48   "Password-Request",
49   "Password-Accept",
50   "Password-Reject",
51   "Accounting-Message",
52   "Access-Challenge"
53 };
54
55
56 struct detail_instance {
57         /* detail file */
58         char *detailfile;
59
60         /* detail file permissions */
61         int detailperm;
62
63         /* directory permissions */
64         int dirperm;
65
66         /* last made directory */
67         char *last_made_directory;
68
69         /* if we want file locking */
70         int locking;
71
72         lrad_hash_table_t *ht;
73 };
74
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 }
85 };
86
87
88 /*
89  *      Clean up.
90  */
91 static int detail_detach(void *instance)
92 {
93         struct detail_instance *inst = instance;
94         free((char *) inst->detailfile);
95
96         free((char*) inst->last_made_directory);
97
98         if (inst->ht) lrad_hash_table_free(inst->ht);
99
100         free(inst);
101         return 0;
102 }
103
104
105 /*
106  *      Hash callback functions.  Copied from src/lib/dict.c
107  */
108 static uint32_t dict_attr_value_hash(const void *data)
109 {
110         return lrad_hash(&((const DICT_ATTR *)data)->attr,
111                          sizeof(((const DICT_ATTR *)data)->attr));
112 }
113
114 static int dict_attr_value_cmp(const void *one, const void *two)
115 {
116         const DICT_ATTR *a = one;
117         const DICT_ATTR *b = two;
118
119         return a->attr - b->attr;
120 }
121
122
123 /*
124  *      (Re-)read radiusd.conf into memory.
125  */
126 static int detail_instantiate(CONF_SECTION *conf, void **instance)
127 {
128         struct detail_instance *inst;
129         CONF_SECTION    *cs;
130
131         inst = rad_malloc(sizeof(*inst));
132         if (!inst) {
133                 return -1;
134         }
135         memset(inst, 0, sizeof(*inst));
136
137         if (cf_section_parse(conf, inst, module_config) < 0) {
138                 detail_detach(inst);
139                 return -1;
140         }
141
142         inst->last_made_directory = NULL;
143
144         /*
145          *      Suppress certain attributes.
146          */
147         cs = cf_section_sub_find(conf, "suppress");
148         if (cs) {
149                 CONF_ITEM       *ci;
150
151                 inst->ht = lrad_hash_table_create(dict_attr_value_hash,
152                                                   dict_attr_value_cmp,
153                                                   NULL);
154
155                 for (ci = cf_item_find_next(cs, NULL);
156                      ci != NULL;
157                      ci = cf_item_find_next(cs, ci)) {
158                         const char      *attr;
159                         DICT_ATTR       *da;
160
161                         if (!cf_item_is_pair(ci)) continue;
162                                                 
163                         attr = cf_pair_attr(cf_itemtopair(ci));
164                         if (!attr) continue; /* pair-anoia */
165
166                         da = dict_attrbyname(attr);
167                         if (!da) {
168                                 radlog(L_INFO, "rlm_detail: WARNING: No such attribute %s: Cannot suppress printing it.", attr);
169                                 continue;
170                         }
171
172                         if (!lrad_hash_table_insert(inst->ht, da)) {
173                                 radlog(L_ERR, "rlm_detail: Failed trying to remember %s", attr);
174                                 detail_detach(inst);
175                                 return -1;
176                         }
177                 }
178         }
179
180
181         *instance = inst;
182         return 0;
183 }
184
185 /*
186  *      Do detail, compatible with old accounting
187  */
188 static int do_detail(void *instance, REQUEST *request, RADIUS_PACKET *packet,
189                      int compat)
190 {
191         int             outfd;
192         FILE            *outfp;
193         char            buffer[DIRLEN];
194         char            *p;
195         struct stat     st;
196         int             locked;
197         int             lock_count;
198         struct timeval  tv;
199         REALM           *proxy_realm;
200         char            proxy_buffer[16];
201         VALUE_PAIR      *pair = packet->vps;
202
203         struct detail_instance *inst = instance;
204
205         /*
206          *      Nothing to log: don't do anything.
207          */
208         if (!packet) {
209                 return RLM_MODULE_NOOP;
210         }
211
212         /*
213          *      Create a directory for this nas.
214          *
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
218          *      variables.
219          */
220         radius_xlat(buffer, sizeof(buffer), inst->detailfile, request, NULL);
221         DEBUG2("rlm_detail: %s expands to %s", inst->detailfile, buffer);
222
223         /*
224          *      Grab the last directory delimiter.
225          */
226         p = strrchr(buffer,'/');
227
228         /*
229          *      There WAS a directory delimiter there, and
230          *      the file doesn't exist, so
231          *      we prolly must create it the dir(s)
232          */
233         if ((p) && (stat(buffer, &st) < 0)) {
234                 *p = '\0';
235                 /*
236                  *      NO previously cached directory name, so we've
237                  *      got to create a new one.
238                  *
239                  *      OR the new directory name is different than the old,
240                  *      so we've got to create a new one.
241                  *
242                  *      OR the cached directory has somehow gotten removed,
243                  *      so we've got to create a new one.
244                  */
245                 if ((inst->last_made_directory == NULL) ||
246                     (strcmp(inst->last_made_directory, buffer) != 0)) {
247
248                         /*
249                          *      Free any previously cached name.
250                          */
251                         if (inst->last_made_directory != NULL) {
252                                 free((char *) inst->last_made_directory);
253                                 inst->last_made_directory = NULL;
254                         }
255
256                         inst->last_made_directory = strdup(buffer);
257                 }
258
259                 /*
260                  *      stat the directory, and don't do anything if
261                  *      it exists.  If it doesn't exist, create it.
262                  *
263                  *      This also catches the case where some idiot
264                  *      deleted a directory that the server was using.
265                  */
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;
269                 }
270
271                 *p = '/';
272         } /* else there was no directory delimiter. */
273
274         /*
275          *      Open & create the file, with the given permissions.
276          */
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;
282         }
283
284         /*
285          *      If we're not using locking, we'll just pass straight though
286          *      the while loop.
287          *      If we fail to aquire the filelock in 80 tries (approximately
288          *      two seconds) we bail out.
289          */
290         locked = 0;
291         lock_count = 0;
292         do {
293                 if (inst->locking) {
294                         lseek(outfd, 0L, SEEK_SET);
295                         if (rad_lockfd_nonblock(outfd, 0) < 0) {
296                                 close(outfd);
297                                 tv.tv_sec = 0;
298                                 tv.tv_usec = 25000;
299                                 select(0, NULL, NULL, NULL, &tv);
300                                 lock_count++;
301                         } else {
302                                 DEBUG("rlm_detail: Acquired filelock, tried %d time(s)",
303                                       lock_count + 1);
304                                 locked = 1;
305                         }
306                 }
307         } while (!locked && inst->locking && lock_count < 80);
308
309         if (!locked && inst->locking && lock_count >= 80) {
310                 radlog(L_ERR, "rlm_detail: Failed to aquire filelock for %s, giving up",
311                        buffer);
312                 return RLM_MODULE_FAIL;
313         }
314
315         /*
316          *      Convert the FD to FP.  The FD is no longer valid
317          *      after this operation.
318          */
319         if ((outfp = fdopen(outfd, "a")) == NULL) {
320                 radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
321                        buffer, strerror(errno));
322                 if (inst->locking) {
323                         lseek(outfd, 0L, SEEK_SET);
324                         rad_unlockfd(outfd, 0);
325                         DEBUG("rlm_detail: Released filelock");
326                 }
327                 close(outfd);
328
329                 return RLM_MODULE_FAIL;
330         }
331
332         /*
333          *      Write the information to the file.
334          */
335         if (!compat) {
336                 /*
337                  *      Print out names, if they're OK.
338                  *      Numbers, if not.
339                  */
340                 if ((packet->code > 0) &&
341                     (packet->code <= PW_ACCESS_CHALLENGE)) {
342                         fprintf(outfp, "Packet-Type = %s\n",
343                                 packet_codes[packet->code]);
344                 } else {
345                         fprintf(outfp, "Packet-Type = %d\n", packet->code);
346                 }
347         }
348
349         /*
350          *      Post a timestamp
351          */
352         fseek(outfp, 0L, SEEK_END);
353         fputs(CTIME_R(&request->timestamp, buffer, DIRLEN), outfp);
354
355         /* Write each attribute/value to the log file */
356         for (; pair != NULL; pair = pair->next) {
357                 if (inst->ht &&
358                     lrad_hash_table_finddata(inst->ht, pair)) continue;
359
360                 /*
361                  *      Don't print passwords in old format...
362                  */
363                 if (compat && (pair->attribute == PW_PASSWORD)) continue;
364
365                 /*
366                  *      Print all of the attributes.
367                  */
368                 fputs("\t", outfp);
369                 vp_print(outfp, pair);
370                 fputs("\n", outfp);
371         }
372
373         /*
374          *      Add non-protocol attibutes.
375          */
376         if (compat) {
377                 if ((pair = pairfind(request->config_items,
378                                      PW_PROXY_TO_REALM)) != NULL) {
379                         proxy_realm = realm_find(pair->strvalue, TRUE);
380                         if (proxy_realm) {
381                                 memset((char *) proxy_buffer, 0, 16);
382                                 ip_ntoa(proxy_buffer, proxy_realm->acct_ipaddr);
383                                 fprintf(outfp, "\tFreeradius-Proxied-To = %s\n",
384                                         proxy_buffer);
385                                 DEBUG("rlm_detail: Freeradius-Proxied-To set to %s",
386                                       proxy_buffer);
387                         }
388                 }
389                 fprintf(outfp, "\tTimestamp = %ld\n",
390                         (unsigned long) request->timestamp);
391
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);
396         }
397
398         fputs("\n", outfp);
399
400         if (inst->locking) {
401                 fflush(outfp);
402                 lseek(outfd, 0L, SEEK_SET);
403                 rad_unlockfd(outfd, 0);
404                 DEBUG("rlm_detail: Released filelock");
405         }
406
407         fclose(outfp);
408
409         /*
410          *      And everything is fine.
411          */
412         return RLM_MODULE_OK;
413 }
414
415 /*
416  *      Accounting - write the detail files.
417  */
418 static int detail_accounting(void *instance, REQUEST *request)
419 {
420
421         return do_detail(instance,request,request->packet, TRUE);
422 }
423
424 /*
425  *      Incoming Access Request - write the detail files.
426  */
427 static int detail_authorize(void *instance, REQUEST *request)
428 {
429         return do_detail(instance,request,request->packet, FALSE);
430 }
431
432 /*
433  *      Outgoing Access-Request Reply - write the detail files.
434  */
435 static int detail_postauth(void *instance, REQUEST *request)
436 {
437         return do_detail(instance,request,request->reply, FALSE);
438 }
439
440
441 /*
442  *      Outgoing Access-Request to home server - write the detail files.
443  */
444 static int detail_pre_proxy(void *instance, REQUEST *request)
445 {
446         if (request->proxy &&
447             request->proxy->vps) {
448                 return do_detail(instance,request,request->proxy, FALSE);
449         }
450
451         return RLM_MODULE_NOOP;
452 }
453
454
455 /*
456  *      Outgoing Access-Request Reply - write the detail files.
457  */
458 static int detail_post_proxy(void *instance, REQUEST *request)
459 {
460         if (request->proxy_reply &&
461             request->proxy_reply->vps) {
462                 return do_detail(instance,request,request->proxy_reply, FALSE);
463         }
464
465         return RLM_MODULE_NOOP;
466 }
467
468
469 /* globally exported name */
470 module_t rlm_detail = {
471         "detail",
472         RLM_TYPE_THREAD_UNSAFE,        /* type: reserved */
473         NULL,                           /* initialization */
474         detail_instantiate,             /* instantiation */
475         {
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 */
484         },
485         detail_detach,                  /* detach */
486         NULL                            /* destroy */
487 };
488