Run in authorize and postauth sections also
[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 struct detail_instance {
40         /* detail file */
41         char *detailfile;
42
43         /* detail file permissions */
44         int detailperm;
45
46         /* directory permissions */
47         int dirperm;
48
49         /* last made directory */
50         char *last_made_directory;
51
52         /* if we want file locking */
53         int locking;
54 };
55
56 static CONF_PARSER module_config[] = {
57         { "detailfile",    PW_TYPE_STRING_PTR,
58           offsetof(struct detail_instance,detailfile), NULL, "%A/%{Client-IP-Address}/detail" },
59         { "detailperm",    PW_TYPE_INTEGER,
60           offsetof(struct detail_instance,detailperm), NULL, "0600" },
61         { "dirperm",       PW_TYPE_INTEGER,
62           offsetof(struct detail_instance,dirperm),    NULL, "0755" },
63         { "locking",       PW_TYPE_BOOLEAN,
64           offsetof(struct detail_instance,locking),    NULL, "no" },
65         { NULL, -1, 0, NULL, NULL }
66 };
67
68 /*
69  *      (Re-)read radiusd.conf into memory.
70  */
71 static int detail_instantiate(CONF_SECTION *conf, void **instance)
72 {
73         struct detail_instance *inst;
74
75         inst = rad_malloc(sizeof *inst);
76
77         if (cf_section_parse(conf, inst, module_config) < 0) {
78                 free(inst);
79                 return -1;
80         }
81
82         inst->last_made_directory = NULL;
83
84         *instance = inst;
85         return 0;
86 }
87
88 static int do_detail(void *instance, REQUEST *request, VALUE_PAIR *pair)
89 {
90         int             outfd;
91         FILE            *outfp;
92         char            buffer[DIRLEN];
93         char            *p;
94         int             ret = RLM_MODULE_OK;
95         struct stat     st;
96         int             locked;
97         int             lock_count;
98         struct timeval  tv;
99         REALM           *proxy_realm;
100         char            proxy_buffer[16];
101
102         struct detail_instance *inst = instance;
103
104         /*
105          *      Create a directory for this nas.
106          *
107          *      Generate the path for the detail file.  Use the
108          *      same format, but truncate at the last /.  Then
109          *      feed it through radius_xlat() to expand the
110          *      variables.
111          */
112         radius_xlat(buffer, sizeof(buffer), inst->detailfile, request, NULL);
113         DEBUG2("rlm_detail: %s expands to %s", inst->detailfile, buffer);
114
115         /*
116          *      Grab the last directory delimiter.
117          */
118         p = strrchr(buffer,'/');
119
120         /*
121          *      There WAS a directory delimiter there, and
122          *      the file doesn't exist, so
123          *      we prolly must create it the dir(s)
124          */
125         if ((p) && (stat(buffer, &st) < 0)) {
126                 *p = '\0';      
127                 /*
128                  *      NO previously cached directory name, so we've
129                  *      got to create a new one.
130                  *
131                  *      OR the new directory name is different than the old,
132                  *      so we've got to create a new one.
133                  *
134                  *      OR the cached directory has somehow gotten removed,
135                  *      so we've got to create a new one.
136                  */
137                 if ((inst->last_made_directory == NULL) ||
138                     (strcmp(inst->last_made_directory, buffer) != 0)) { 
139                         
140                         /*
141                          *      Free any previously cached name.
142                          */
143                         if (inst->last_made_directory != NULL) {
144                                 free((char *) inst->last_made_directory);
145                                 inst->last_made_directory = NULL;
146                         }
147                         
148                         /*
149                          *      Go create possibly multiple directories.
150                          */
151                         if (rad_mkdir(buffer, inst->dirperm) < 0) {
152                                 radlog(L_ERR, "rlm_detail: Failed to create directory %s: %s", buffer, strerror(errno));
153                                 return RLM_MODULE_FAIL;
154                         }
155                         inst->last_made_directory = strdup(buffer);
156                 }
157                 
158                 *p = '/';       
159         } /* else there was no directory delimiter. */
160
161         /*
162          *      Open & create the file, with the given permissions.
163          *
164          *      If we're not using locking, we'll just pass straight though
165          *      the while loop.
166          *      If we fail to aquire the filelock in 80 tries (approximately
167          *      two seconds) we bail out.
168          */
169         locked = 0;
170         lock_count = 0;
171         do {
172                 if ((outfd = open(buffer, O_WRONLY | O_APPEND | O_CREAT,
173                                   inst->detailperm)) < 0) {
174                         radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
175                                buffer, strerror(errno));
176                         ret = RLM_MODULE_FAIL;
177                         break;
178                 }
179                 if (inst->locking) {
180                         lseek(outfd, 0L, SEEK_SET);
181                         if (rad_lockfd_nonblock(outfd, 0) < 0) {
182                                 close(outfd);
183                                 tv.tv_sec = 0;
184                                 tv.tv_usec = 25000;
185                                 select(0, NULL, NULL, NULL, &tv);
186                                 lock_count++;
187                         } else {
188                                 DEBUG("rlm_detail: Aquired filelock, tried %d time(s)",
189                                       lock_count + 1);
190                                 locked = 1;
191                         }
192                 }
193         } while (!locked && inst->locking && lock_count < 80);
194
195         if (!locked && inst->locking && lock_count >= 80) {
196                 radlog(L_ERR, "rlm_detail: Failed to aquire filelock for %s, giving up",
197                        buffer);
198                 outfd = -1;
199                 ret = RLM_MODULE_FAIL;
200         }
201
202         outfp = NULL;
203         if (outfd > -1) {
204                 if ((outfp = fdopen(outfd, "a")) == NULL) {
205                         radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
206                                buffer, strerror(errno));
207                         ret = RLM_MODULE_FAIL;
208                         if (inst->locking) {
209                                 lseek(outfd, 0L, SEEK_SET);
210                                 rad_unlockfd(outfd, 0);
211                                 DEBUG("rlm_detail: Released filelock");
212                         }
213                         close(outfd);
214                 }
215         }
216
217         if (outfd > -1 && outfp) {
218                 /* Post a timestamp */
219                 fseek(outfp, 0L, SEEK_END);
220                 fputs(ctime_r(&request->timestamp, buffer), outfp);
221
222                 /* Write each attribute/value to the log file */
223                 while (pair) {
224                         if (pair->attribute != PW_PASSWORD) {
225                                 fputs("\t", outfp);
226                                 vp_print(outfp, pair);
227                                 fputs("\n", outfp);
228                         }
229                         pair = pair->next;
230                 }
231
232                 /*
233                  *      Add non-protocol attibutes.
234                  */
235                 if ((pair = pairfind(request->config_items, PW_PROXY_TO_REALM))
236                     != NULL) {
237                         proxy_realm = realm_find(pair->strvalue, TRUE);
238                         if (proxy_realm) {
239                                 memset((char *) proxy_buffer, 0, 16);
240                                 ip_ntoa(proxy_buffer, proxy_realm->acct_ipaddr);
241                                 fprintf(outfp, "\tFreeradius-Proxied-To = %s\n",
242                                         proxy_buffer);
243                                 DEBUG("rlm_detail: Freeradius-Proxied-To set to %s",
244                                       proxy_buffer);
245                         }
246                 }
247                 fprintf(outfp, "\tTimestamp = %ld\n", request->timestamp);
248                 if (request->packet->verified == 2)
249                         fputs("\tRequest-Authenticator = Verified\n", outfp);
250                 else if (request->packet->verified == 1)
251                         fputs("\tRequest-Authenticator = None\n", outfp);
252                 pair = NULL;
253
254                 fputs("\n", outfp);
255
256                 if (inst->locking) {
257                         fflush(outfp);
258                         lseek(outfd, 0L, SEEK_SET);
259                         rad_unlockfd(outfd, 0);
260                         DEBUG("rlm_detail: Released filelock");
261                 }
262         
263                 fclose(outfp);
264         }
265
266         return ret;
267 }
268
269 /*
270  *      Accounting - write the detail files.
271  */
272 static int detail_accounting(void *instance, REQUEST *request)
273 {
274
275         return do_detail(instance,request,request->packet->vps);
276 }
277
278 /*
279  *      Incoming Access Request - write the detail files.
280  */
281 static int detail_authorize(void *instance, REQUEST *request)
282 {
283         return do_detail(instance,request,request->packet->vps);
284 }
285
286 /*
287  *      Outgoing Access-Request Reply - write the detail files.
288  */
289 static int detail_postauth(void *instance, REQUEST *request)
290 {
291         return do_detail(instance,request,request->reply->vps);
292 }
293
294
295 /*
296  *      Clean up.
297  */
298 static int detail_detach(void *instance)
299 {
300         struct detail_instance *inst = instance;
301         free((char *) inst->detailfile);
302
303         if (inst->last_made_directory)
304                 free((char*) inst->last_made_directory);
305         free(inst);
306         return 0;
307 }
308
309
310 /* globally exported name */
311 module_t rlm_detail = {
312         "detail",
313         0,                              /* type: reserved */
314         NULL,                           /* initialization */
315         detail_instantiate,             /* instantiation */
316         {
317                 NULL,                   /* authentication */
318                 detail_authorize,       /* authorization */
319                 NULL,                   /* preaccounting */
320                 detail_accounting,      /* accounting */
321                 NULL,                   /* checksimul */
322                 NULL,                   /* pre-proxy */
323                 NULL,                   /* post-proxy */
324                 detail_postauth         /* post-auth */
325         },
326         detail_detach,                  /* detach */
327         NULL                            /* destroy */
328 };
329