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