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