import from branch_1_1:
[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2000  The FreeRADIUS server project
21  */
22
23 static const char rcsid[] = "$Id$";
24
25 #include        <freeradius-devel/autoconf.h>
26
27 #include        <sys/stat.h>
28 #include        <sys/select.h>
29
30 #include        <stdlib.h>
31 #include        <string.h>
32 #include        <ctype.h>
33 #include        <fcntl.h>
34
35 #include        <freeradius-devel/radiusd.h>
36 #include        <freeradius-devel/modules.h>
37 #include        <freeradius-devel/rad_assert.h>
38
39 #define         DIRLEN  8192
40
41 static const char *packet_codes[] = {
42   "",
43   "Access-Request",
44   "Access-Accept",
45   "Access-Reject",
46   "Accounting-Request",
47   "Accounting-Response",
48   "Accounting-Status",
49   "Password-Request",
50   "Password-Accept",
51   "Password-Reject",
52   "Accounting-Message",
53   "Access-Challenge"
54 };
55
56
57 struct detail_instance {
58         /* detail file */
59         char *detailfile;
60
61         /* detail file permissions */
62         int detailperm;
63
64         /* directory permissions */
65         int dirperm;
66
67         /* last made directory */
68         char *last_made_directory;
69
70         /* timestamp & stuff */
71         char *header;
72
73         /* if we want file locking */
74         int locking;
75
76         lrad_hash_table_t *ht;
77 };
78
79 static const CONF_PARSER module_config[] = {
80         { "detailfile",    PW_TYPE_STRING_PTR,
81           offsetof(struct detail_instance,detailfile), NULL, "%A/%{Client-IP-Address}/detail" },
82         { "header",    PW_TYPE_STRING_PTR,
83           offsetof(struct detail_instance,header), NULL, "%t" },
84         { "detailperm",    PW_TYPE_INTEGER,
85           offsetof(struct detail_instance,detailperm), NULL, "0600" },
86         { "dirperm",       PW_TYPE_INTEGER,
87           offsetof(struct detail_instance,dirperm),    NULL, "0755" },
88         { "locking",       PW_TYPE_BOOLEAN,
89           offsetof(struct detail_instance,locking),    NULL, "no" },
90         { NULL, -1, 0, NULL, NULL }
91 };
92
93
94 /*
95  *      Clean up.
96  */
97 static int detail_detach(void *instance)
98 {
99         struct detail_instance *inst = instance;
100         free((char *) inst->detailfile);
101
102         free((char*) inst->last_made_directory);
103
104         if (inst->ht) lrad_hash_table_free(inst->ht);
105
106         free(inst);
107         return 0;
108 }
109
110
111 static uint32_t detail_hash(const void *data)
112 {
113         const DICT_ATTR *da = data;
114         return lrad_hash(&(da->attr), sizeof(da->attr));
115 }
116
117 static int detail_cmp(const void *a, const void *b)
118 {
119         return ((const DICT_ATTR *)a)->attr - ((const DICT_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(detail_hash, detail_cmp,
152                                                   NULL);
153
154                 for (ci = cf_item_find_next(cs, NULL);
155                      ci != NULL;
156                      ci = cf_item_find_next(cs, ci)) {
157                         const char      *attr;
158                         DICT_ATTR       *da;
159
160                         if (!cf_item_is_pair(ci)) continue;
161                                                 
162                         attr = cf_pair_attr(cf_itemtopair(ci));
163                         if (!attr) continue; /* pair-anoia */
164
165                         da = dict_attrbyname(attr);
166                         if (!da) {
167                                 radlog(L_INFO, "rlm_detail: WARNING: No such attribute %s: Cannot suppress printing it.", attr);
168                                 continue;
169                         }
170
171                         /*
172                          *      For better distribution we should really
173                          *      hash the attribute number or name.  But
174                          *      since the suppression list will usually
175                          *      be small, it doesn't matter.
176                          */
177                         if (!lrad_hash_table_insert(inst->ht, da)) {
178                                 radlog(L_ERR, "rlm_detail: Failed trying to remember %s", attr);
179                                 detail_detach(inst);
180                                 return -1;
181                         }
182                 }
183         }
184
185
186         *instance = inst;
187         return 0;
188 }
189
190 /*
191  *      Do detail, compatible with old accounting
192  */
193 static int do_detail(void *instance, REQUEST *request, RADIUS_PACKET *packet,
194                      int compat)
195 {
196         int             outfd;
197         FILE            *outfp;
198         char            timestamp[256];
199         char            buffer[DIRLEN];
200         char            *p;
201         struct stat     st;
202         int             locked;
203         int             lock_count;
204         struct timeval  tv;
205         REALM           *proxy_realm;
206         char            proxy_buffer[16];
207         VALUE_PAIR      *pair = packet->vps;
208
209         struct detail_instance *inst = instance;
210
211         /*
212          *      Nothing to log: don't do anything.
213          */
214         if (!packet) {
215                 return RLM_MODULE_NOOP;
216         }
217
218         /*
219          *      Create a directory for this nas.
220          *
221          *      Generate the path for the detail file.  Use the
222          *      same format, but truncate at the last /.  Then
223          *      feed it through radius_xlat() to expand the
224          *      variables.
225          */
226         radius_xlat(buffer, sizeof(buffer), inst->detailfile, request, NULL);
227         DEBUG2("rlm_detail: %s expands to %s", inst->detailfile, buffer);
228
229         /*
230          *      Grab the last directory delimiter.
231          */
232         p = strrchr(buffer,'/');
233
234         /*
235          *      There WAS a directory delimiter there, and
236          *      the file doesn't exist, so
237          *      we prolly must create it the dir(s)
238          */
239         if ((p) && (stat(buffer, &st) < 0)) {
240                 *p = '\0';
241                 /*
242                  *      NO previously cached directory name, so we've
243                  *      got to create a new one.
244                  *
245                  *      OR the new directory name is different than the old,
246                  *      so we've got to create a new one.
247                  *
248                  *      OR the cached directory has somehow gotten removed,
249                  *      so we've got to create a new one.
250                  */
251                 if ((inst->last_made_directory == NULL) ||
252                     (strcmp(inst->last_made_directory, buffer) != 0)) {
253                         free((char *) inst->last_made_directory);
254                         inst->last_made_directory = strdup(buffer);
255                 }
256
257                 /*
258                  *      stat the directory, and don't do anything if
259                  *      it exists.  If it doesn't exist, create it.
260                  *
261                  *      This also catches the case where some idiot
262                  *      deleted a directory that the server was using.
263                  */
264                 if (rad_mkdir(inst->last_made_directory, inst->dirperm) < 0) {
265                         radlog(L_ERR, "rlm_detail: Failed to create directory %s: %s", inst->last_made_directory, strerror(errno));
266                         return RLM_MODULE_FAIL;
267                 }
268
269                 *p = '/';
270         } /* else there was no directory delimiter. */
271
272         locked = 0;
273         lock_count = 0;
274         do {
275                 /*
276                  *      Open & create the file, with the given
277                  *      permissions.
278                  */
279                 if ((outfd = open(buffer, O_WRONLY | O_APPEND | O_CREAT,
280                                   inst->detailperm)) < 0) {
281                         radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
282                                buffer, strerror(errno));
283                         return RLM_MODULE_FAIL;
284                 }
285
286                 /*
287                  *      If we fail to aquire the filelock in 80 tries
288                  *      (approximately two seconds) we bail out.
289                  */
290                 if (inst->locking) {
291                         lseek(outfd, 0L, SEEK_SET);
292                         if (rad_lockfd_nonblock(outfd, 0) < 0) {
293                                 close(outfd);
294                                 tv.tv_sec = 0;
295                                 tv.tv_usec = 25000;
296                                 select(0, NULL, NULL, NULL, &tv);
297                                 lock_count++;
298                                 continue;
299                         }
300
301                         /*
302                          *      The file might have been deleted by
303                          *      radrelay while we tried to acquire
304                          *      the lock (race condition)
305                          */
306                         if (fstat(outfd, &st) != 0) {
307                                 radlog(L_ERR, "rlm_detail: Couldn't stat file %s: %s",
308                                        buffer, strerror(errno));
309                                 close(outfd);
310                                 return RLM_MODULE_FAIL;
311                         }
312                         if (st.st_nlink == 0) {
313                                 DEBUG("rlm_detail: File %s removed by another program, retrying",
314                                       buffer);
315                                 close(outfd);
316                                 lock_count = 0;
317                                 continue;
318                         }
319
320                         DEBUG("rlm_detail: Acquired filelock, tried %d time(s)",
321                               lock_count + 1);
322                         locked = 1;
323                 }
324         } while (inst->locking && !locked && lock_count < 80);
325
326         if (inst->locking && !locked) {
327                 close(outfd);
328                 radlog(L_ERR, "rlm_detail: Failed to aquire filelock for %s, giving up",
329                        buffer);
330                 return RLM_MODULE_FAIL;
331         }
332
333         /*
334          *      Convert the FD to FP.  The FD is no longer valid
335          *      after this operation.
336          */
337         if ((outfp = fdopen(outfd, "a")) == NULL) {
338                 radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
339                        buffer, strerror(errno));
340                 if (inst->locking) {
341                         lseek(outfd, 0L, SEEK_SET);
342                         rad_unlockfd(outfd, 0);
343                         DEBUG("rlm_detail: Released filelock");
344                 }
345                 close(outfd);   /* automatically releases the lock */
346
347                 return RLM_MODULE_FAIL;
348         }
349
350         /*
351          *      Write the information to the file.
352          */
353         if (!compat) {
354                 /*
355                  *      Print out names, if they're OK.
356                  *      Numbers, if not.
357                  */
358                 if ((packet->code > 0) &&
359                     (packet->code <= PW_ACCESS_CHALLENGE)) {
360                         fprintf(outfp, "Packet-Type = %s\n",
361                                 packet_codes[packet->code]);
362                 } else {
363                         fprintf(outfp, "Packet-Type = %d\n", packet->code);
364                 }
365         }
366
367         /*
368          *      Post a timestamp
369          */
370         fseek(outfp, 0L, SEEK_END);
371         radius_xlat(timestamp, sizeof(timestamp), inst->header, request, NULL);
372         fprintf(outfp, "%s\n", timestamp);
373
374         /* Write each attribute/value to the log file */
375         for (; pair != NULL; pair = pair->next) {
376                 DICT_ATTR da;
377                 da.attr = pair->attribute;
378
379                 if (inst->ht &&
380                     lrad_hash_table_finddata(inst->ht, &da)) continue;
381
382                 /*
383                  *      Don't print passwords in old format...
384                  */
385                 if (compat && (pair->attribute == PW_PASSWORD)) continue;
386
387                 /*
388                  *      Print all of the attributes.
389                  */
390                 fputs("\t", outfp);
391                 vp_print(outfp, pair);
392                 fputs("\n", outfp);
393         }
394
395         /*
396          *      Add non-protocol attibutes.
397          */
398         if (compat) {
399                 if ((pair = pairfind(request->config_items,
400                                      PW_PROXY_TO_REALM)) != NULL) {
401                         proxy_realm = realm_find(pair->vp_strvalue, TRUE);
402                         if (proxy_realm) {
403                                 memset((char *) proxy_buffer, 0, 16);
404
405                                 rad_assert(proxy_realm->acct_ipaddr.af == AF_INET);
406
407                                 inet_ntop(proxy_realm->acct_ipaddr.af,
408                                           &proxy_realm->acct_ipaddr.ipaddr,
409                                           proxy_buffer, sizeof(proxy_buffer));
410                                 fprintf(outfp, "\tFreeradius-Proxied-To = %s\n",
411                                         proxy_buffer);
412                                 DEBUG("rlm_detail: Freeradius-Proxied-To set to %s",
413                                       proxy_buffer);
414                         }
415                 }
416                 fprintf(outfp, "\tTimestamp = %ld\n",
417                         (unsigned long) request->timestamp);
418
419                 if (request->packet->verified == 2)
420                         fputs("\tRequest-Authenticator = Verified\n", outfp);
421                 else if (request->packet->verified == 1)
422                         fputs("\tRequest-Authenticator = None\n", outfp);
423         }
424
425         fputs("\n", outfp);
426
427         if (inst->locking) {
428                 fflush(outfp);
429                 lseek(outfd, 0L, SEEK_SET);
430                 rad_unlockfd(outfd, 0);
431                 DEBUG("rlm_detail: Released filelock");
432         }
433
434         fclose(outfp);
435
436         /*
437          *      And everything is fine.
438          */
439         return RLM_MODULE_OK;
440 }
441
442 /*
443  *      Accounting - write the detail files.
444  */
445 static int detail_accounting(void *instance, REQUEST *request)
446 {
447
448         return do_detail(instance,request,request->packet, TRUE);
449 }
450
451 /*
452  *      Incoming Access Request - write the detail files.
453  */
454 static int detail_authorize(void *instance, REQUEST *request)
455 {
456         return do_detail(instance,request,request->packet, FALSE);
457 }
458
459 /*
460  *      Outgoing Access-Request Reply - write the detail files.
461  */
462 static int detail_postauth(void *instance, REQUEST *request)
463 {
464         return do_detail(instance,request,request->reply, FALSE);
465 }
466
467
468 /*
469  *      Outgoing Access-Request to home server - write the detail files.
470  */
471 static int detail_pre_proxy(void *instance, REQUEST *request)
472 {
473         if (request->proxy &&
474             request->proxy->vps) {
475                 return do_detail(instance,request,request->proxy, FALSE);
476         }
477
478         return RLM_MODULE_NOOP;
479 }
480
481
482 /*
483  *      Outgoing Access-Request Reply - write the detail files.
484  */
485 static int detail_post_proxy(void *instance, REQUEST *request)
486 {
487         if (request->proxy_reply &&
488             request->proxy_reply->vps) {
489                 return do_detail(instance,request,request->proxy_reply, FALSE);
490         }
491
492         return RLM_MODULE_NOOP;
493 }
494
495
496 /* globally exported name */
497 module_t rlm_detail = {
498         RLM_MODULE_INIT,
499         "detail",
500         RLM_TYPE_THREAD_UNSAFE,        /* type: reserved */
501         detail_instantiate,             /* instantiation */
502         detail_detach,                  /* detach */
503         {
504                 NULL,                   /* authentication */
505                 detail_authorize,       /* authorization */
506                 NULL,                   /* preaccounting */
507                 detail_accounting,      /* accounting */
508                 NULL,                   /* checksimul */
509                 detail_pre_proxy,       /* pre-proxy */
510                 detail_post_proxy,      /* post-proxy */
511                 detail_postauth         /* post-auth */
512         },
513 };
514