Print timestamp as an unsigned long, which works a little better
[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
73 static CONF_PARSER module_config[] = {
74         { "detailfile",    PW_TYPE_STRING_PTR,
75           offsetof(struct detail_instance,detailfile), NULL, "%A/%{Client-IP-Address}/detail" },
76         { "detailperm",    PW_TYPE_INTEGER,
77           offsetof(struct detail_instance,detailperm), NULL, "0600" },
78         { "dirperm",       PW_TYPE_INTEGER,
79           offsetof(struct detail_instance,dirperm),    NULL, "0755" },
80         { "locking",       PW_TYPE_BOOLEAN,
81           offsetof(struct detail_instance,locking),    NULL, "no" },
82         { NULL, -1, 0, NULL, NULL }
83 };
84
85 /*
86  *      (Re-)read radiusd.conf into memory.
87  */
88 static int detail_instantiate(CONF_SECTION *conf, void **instance)
89 {
90         struct detail_instance *inst;
91
92         inst = rad_malloc(sizeof(*inst));
93         if (!inst) {
94                 return -1;
95         }
96         memset(inst, 0, sizeof(*inst));
97
98         if (cf_section_parse(conf, inst, module_config) < 0) {
99                 free(inst);
100                 return -1;
101         }
102
103         inst->last_made_directory = NULL;
104
105         *instance = inst;
106         return 0;
107 }
108
109 /*
110  *      Do detail, compatible with old accounting
111  */
112 static int do_detail(void *instance, REQUEST *request, RADIUS_PACKET *packet,
113                      int compat)
114 {
115         int             outfd;
116         FILE            *outfp;
117         char            buffer[DIRLEN];
118         char            *p;
119         struct stat     st;
120         int             locked;
121         int             lock_count;
122         struct timeval  tv;
123         REALM           *proxy_realm;
124         char            proxy_buffer[16];
125         VALUE_PAIR      *pair = packet->vps;
126
127         struct detail_instance *inst = instance;
128
129         /*
130          *      Nothing to log: don't do anything.
131          */
132         if (!packet) {
133                 return RLM_MODULE_NOOP;
134         }
135
136         /*
137          *      Create a directory for this nas.
138          *
139          *      Generate the path for the detail file.  Use the
140          *      same format, but truncate at the last /.  Then
141          *      feed it through radius_xlat() to expand the
142          *      variables.
143          */
144         radius_xlat(buffer, sizeof(buffer), inst->detailfile, request, NULL);
145         DEBUG2("rlm_detail: %s expands to %s", inst->detailfile, buffer);
146
147         /*
148          *      Grab the last directory delimiter.
149          */
150         p = strrchr(buffer,'/');
151
152         /*
153          *      There WAS a directory delimiter there, and
154          *      the file doesn't exist, so
155          *      we prolly must create it the dir(s)
156          */
157         if ((p) && (stat(buffer, &st) < 0)) {
158                 *p = '\0';      
159                 /*
160                  *      NO previously cached directory name, so we've
161                  *      got to create a new one.
162                  *
163                  *      OR the new directory name is different than the old,
164                  *      so we've got to create a new one.
165                  *
166                  *      OR the cached directory has somehow gotten removed,
167                  *      so we've got to create a new one.
168                  */
169                 if ((inst->last_made_directory == NULL) ||
170                     (strcmp(inst->last_made_directory, buffer) != 0)) { 
171                         
172                         /*
173                          *      Free any previously cached name.
174                          */
175                         if (inst->last_made_directory != NULL) {
176                                 free((char *) inst->last_made_directory);
177                                 inst->last_made_directory = NULL;
178                         }
179                         
180                         /*
181                          *      Go create possibly multiple directories.
182                          */
183                         if (rad_mkdir(buffer, inst->dirperm) < 0) {
184                                 radlog(L_ERR, "rlm_detail: Failed to create directory %s: %s", buffer, strerror(errno));
185                                 return RLM_MODULE_FAIL;
186                         }
187                         inst->last_made_directory = strdup(buffer);
188                 }
189                 
190                 *p = '/';       
191         } /* else there was no directory delimiter. */
192
193         /*
194          *      Open & create the file, with the given permissions.
195          */
196         if ((outfd = open(buffer, O_WRONLY | O_APPEND | O_CREAT,
197                           inst->detailperm)) < 0) {
198                 radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
199                        buffer, strerror(errno));
200                 return RLM_MODULE_FAIL;
201         }
202
203         /*
204          *      If we're not using locking, we'll just pass straight though
205          *      the while loop.
206          *      If we fail to aquire the filelock in 80 tries (approximately
207          *      two seconds) we bail out.
208          */
209         locked = 0;
210         lock_count = 0;
211         do {
212                 if (inst->locking) {
213                         lseek(outfd, 0L, SEEK_SET);
214                         if (rad_lockfd_nonblock(outfd, 0) < 0) {
215                                 close(outfd);
216                                 tv.tv_sec = 0;
217                                 tv.tv_usec = 25000;
218                                 select(0, NULL, NULL, NULL, &tv);
219                                 lock_count++;
220                         } else {
221                                 DEBUG("rlm_detail: Acquired filelock, tried %d time(s)",
222                                       lock_count + 1);
223                                 locked = 1;
224                         }
225                 }
226         } while (!locked && inst->locking && lock_count < 80);
227
228         if (!locked && inst->locking && lock_count >= 80) {
229                 radlog(L_ERR, "rlm_detail: Failed to aquire filelock for %s, giving up",
230                        buffer);
231                 return RLM_MODULE_FAIL;
232         }
233
234         /*
235          *      Convert the FD to FP.  The FD is no longer valid
236          *      after this operation.
237          */
238         if ((outfp = fdopen(outfd, "a")) == NULL) {
239                 radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
240                        buffer, strerror(errno));
241                 if (inst->locking) {
242                         lseek(outfd, 0L, SEEK_SET);
243                         rad_unlockfd(outfd, 0);
244                         DEBUG("rlm_detail: Released filelock");
245                 }
246                 close(outfd);
247
248                 return RLM_MODULE_FAIL;
249         }
250
251         /*
252          *      Write the information to the file.
253          */
254         if (!compat) {
255                 /*
256                  *      Print out names, if they're OK.
257                  *      Numbers, if not.
258                  */
259                 if ((packet->code > 0) &&
260                     (packet->code <= PW_ACCESS_CHALLENGE)) {
261                         fprintf(outfp, "Packet-Type = %s\n",
262                                 packet_codes[packet->code]);
263                 } else {
264                         fprintf(outfp, "Packet-Type = %d\n", packet->code);
265                 }
266         }
267
268         /*
269          *      Post a timestamp
270          */
271         fseek(outfp, 0L, SEEK_END);
272         fputs(CTIME_R(&request->timestamp, buffer, DIRLEN), outfp);
273         
274         /* Write each attribute/value to the log file */
275         while (pair) {
276                 /*
277                  *      Don't print passwords in old format...
278                  */
279                 if (compat && (pair->attribute == PW_PASSWORD)) {
280                         pair = pair->next;
281                         continue;
282                 }
283
284                 /*
285                  *      Print all of the attributes.
286                  */
287                 fputs("\t", outfp);
288                 vp_print(outfp, pair);
289                 fputs("\n", outfp);
290                 pair = pair->next;
291         }
292
293         /*
294          *      Add non-protocol attibutes.
295          */
296         if (compat) {
297                 if ((pair = pairfind(request->config_items,
298                                      PW_PROXY_TO_REALM)) != NULL) {
299                         proxy_realm = realm_find(pair->strvalue, TRUE);
300                         if (proxy_realm) {
301                                 memset((char *) proxy_buffer, 0, 16);
302                                 ip_ntoa(proxy_buffer, proxy_realm->acct_ipaddr);
303                                 fprintf(outfp, "\tFreeradius-Proxied-To = %s\n",
304                                         proxy_buffer);
305                                 DEBUG("rlm_detail: Freeradius-Proxied-To set to %s",
306                                       proxy_buffer);
307                         }
308                 }
309                 fprintf(outfp, "\tTimestamp = %ld\n",
310                         (unsigned long) request->timestamp);
311
312                 if (request->packet->verified == 2)
313                         fputs("\tRequest-Authenticator = Verified\n", outfp);
314                 else if (request->packet->verified == 1)
315                         fputs("\tRequest-Authenticator = None\n", outfp);
316         }
317         
318         fputs("\n", outfp);
319
320         if (inst->locking) {
321                 fflush(outfp);
322                 lseek(outfd, 0L, SEEK_SET);
323                 rad_unlockfd(outfd, 0);
324                 DEBUG("rlm_detail: Released filelock");
325         }
326         
327         fclose(outfp);
328
329         /*
330          *      And everything is fine.
331          */
332         return RLM_MODULE_OK;
333 }
334
335 /*
336  *      Accounting - write the detail files.
337  */
338 static int detail_accounting(void *instance, REQUEST *request)
339 {
340
341         return do_detail(instance,request,request->packet, TRUE);
342 }
343
344 /*
345  *      Incoming Access Request - write the detail files.
346  */
347 static int detail_authorize(void *instance, REQUEST *request)
348 {
349         return do_detail(instance,request,request->packet, FALSE);
350 }
351
352 /*
353  *      Outgoing Access-Request Reply - write the detail files.
354  */
355 static int detail_postauth(void *instance, REQUEST *request)
356 {
357         return do_detail(instance,request,request->reply, FALSE);
358 }
359
360
361 /*
362  *      Outgoing Access-Request to home server - write the detail files.
363  */
364 static int detail_pre_proxy(void *instance, REQUEST *request)
365 {
366         if (request->proxy &&
367             request->proxy->vps) {
368                 return do_detail(instance,request,request->proxy, FALSE);
369         }
370
371         return RLM_MODULE_NOOP;
372 }
373
374
375 /*
376  *      Outgoing Access-Request Reply - write the detail files.
377  */
378 static int detail_post_proxy(void *instance, REQUEST *request)
379 {
380         if (request->proxy_reply &&
381             request->proxy_reply->vps) {
382                 return do_detail(instance,request,request->proxy_reply, FALSE);
383         }
384
385         return RLM_MODULE_NOOP;
386 }
387
388
389 /*
390  *      Clean up.
391  */
392 static int detail_detach(void *instance)
393 {
394         struct detail_instance *inst = instance;
395         free((char *) inst->detailfile);
396
397         if (inst->last_made_directory)
398                 free((char*) inst->last_made_directory);
399         free(inst);
400         return 0;
401 }
402
403
404 /* globally exported name */
405 module_t rlm_detail = {
406         "detail",
407         RLM_TYPE_THREAD_UNSAFE,        /* type: reserved */
408         NULL,                           /* initialization */
409         detail_instantiate,             /* instantiation */
410         {
411                 NULL,                   /* authentication */
412                 detail_authorize,       /* authorization */
413                 NULL,                   /* preaccounting */
414                 detail_accounting,      /* accounting */
415                 NULL,                   /* checksimul */
416                 detail_pre_proxy,       /* pre-proxy */
417                 detail_post_proxy,      /* post-proxy */
418                 detail_postauth         /* post-auth */
419         },
420         detail_detach,                  /* detach */
421         NULL                            /* destroy */
422 };
423