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