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