Free allocated 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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2000,2006  The FreeRADIUS server project
21  */
22
23 #include        <freeradius-devel/ident.h>
24 RCSID("$Id$")
25
26 #include        <freeradius-devel/autoconf.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        <freeradius-devel/radiusd.h>
37 #include        <freeradius-devel/modules.h>
38 #include        <freeradius-devel/rad_assert.h>
39
40 #define         DIRLEN  8192
41
42 static const char *packet_codes[] = {
43   "",
44   "Access-Request",
45   "Access-Accept",
46   "Access-Reject",
47   "Accounting-Request",
48   "Accounting-Response",
49   "Accounting-Status",
50   "Password-Request",
51   "Password-Accept",
52   "Password-Reject",
53   "Accounting-Message",
54   "Access-Challenge"
55 };
56
57
58 struct detail_instance {
59         /* detail file */
60         char *detailfile;
61
62         /* detail file permissions */
63         int detailperm;
64
65         /* directory permissions */
66         int dirperm;
67
68         /* last made directory */
69         char *last_made_directory;
70
71         /* timestamp & stuff */
72         char *header;
73
74         /* if we want file locking */
75         int locking;
76
77         /* log src/dst information */
78         int log_srcdst;
79
80         lrad_hash_table_t *ht;
81 };
82
83 static const CONF_PARSER module_config[] = {
84         { "detailfile",    PW_TYPE_STRING_PTR,
85           offsetof(struct detail_instance,detailfile), NULL, "%A/%{Client-IP-Address}/detail" },
86         { "header",    PW_TYPE_STRING_PTR,
87           offsetof(struct detail_instance,header), NULL, "%t" },
88         { "detailperm",    PW_TYPE_INTEGER,
89           offsetof(struct detail_instance,detailperm), NULL, "0600" },
90         { "dirperm",       PW_TYPE_INTEGER,
91           offsetof(struct detail_instance,dirperm),    NULL, "0755" },
92         { "locking",       PW_TYPE_BOOLEAN,
93           offsetof(struct detail_instance,locking),    NULL, "no" },
94         { "log_packet_header",       PW_TYPE_BOOLEAN,
95           offsetof(struct detail_instance,log_srcdst),    NULL, "no" },
96         { NULL, -1, 0, NULL, NULL }
97 };
98
99
100 /*
101  *      Clean up.
102  */
103 static int detail_detach(void *instance)
104 {
105         struct detail_instance *inst = instance;
106         free((char *) inst->detailfile);
107
108         free((char*) inst->last_made_directory);
109
110         free((char*) inst->header);
111
112         if (inst->ht) lrad_hash_table_free(inst->ht);
113
114         free(inst);
115         return 0;
116 }
117
118
119 static uint32_t detail_hash(const void *data)
120 {
121         const DICT_ATTR *da = data;
122         return lrad_hash(&(da->attr), sizeof(da->attr));
123 }
124
125 static int detail_cmp(const void *a, const void *b)
126 {
127         return ((const DICT_ATTR *)a)->attr - ((const DICT_ATTR *)b)->attr;
128 }
129
130
131 /*
132  *      (Re-)read radiusd.conf into memory.
133  */
134 static int detail_instantiate(CONF_SECTION *conf, void **instance)
135 {
136         struct detail_instance *inst;
137         CONF_SECTION    *cs;
138
139         inst = rad_malloc(sizeof(*inst));
140         if (!inst) {
141                 return -1;
142         }
143         memset(inst, 0, sizeof(*inst));
144
145         if (cf_section_parse(conf, inst, module_config) < 0) {
146                 detail_detach(inst);
147                 return -1;
148         }
149
150         inst->last_made_directory = NULL;
151
152         /*
153          *      Suppress certain attributes.
154          */
155         cs = cf_section_sub_find(conf, "suppress");
156         if (cs) {
157                 CONF_ITEM       *ci;
158
159                 inst->ht = lrad_hash_table_create(detail_hash, detail_cmp,
160                                                   NULL);
161
162                 for (ci = cf_item_find_next(cs, NULL);
163                      ci != NULL;
164                      ci = cf_item_find_next(cs, ci)) {
165                         const char      *attr;
166                         DICT_ATTR       *da;
167
168                         if (!cf_item_is_pair(ci)) continue;
169                                                 
170                         attr = cf_pair_attr(cf_itemtopair(ci));
171                         if (!attr) continue; /* pair-anoia */
172
173                         da = dict_attrbyname(attr);
174                         if (!da) {
175                                 radlog(L_INFO, "rlm_detail: WARNING: No such attribute %s: Cannot suppress printing it.", attr);
176                                 continue;
177                         }
178
179                         /*
180                          *      For better distribution we should really
181                          *      hash the attribute number or name.  But
182                          *      since the suppression list will usually
183                          *      be small, it doesn't matter.
184                          */
185                         if (!lrad_hash_table_insert(inst->ht, da)) {
186                                 radlog(L_ERR, "rlm_detail: Failed trying to remember %s", attr);
187                                 detail_detach(inst);
188                                 return -1;
189                         }
190                 }
191         }
192
193
194         *instance = inst;
195         return 0;
196 }
197
198 /*
199  *      Do detail, compatible with old accounting
200  */
201 static int do_detail(void *instance, REQUEST *request, RADIUS_PACKET *packet,
202                      int compat)
203 {
204         int             outfd;
205         FILE            *outfp;
206         char            timestamp[256];
207         char            buffer[DIRLEN];
208         char            *p;
209         struct stat     st;
210         int             locked;
211         int             lock_count;
212         struct timeval  tv;
213         REALM           *proxy_realm;
214         char            proxy_buffer[16];
215         VALUE_PAIR      *pair = packet->vps;
216
217         struct detail_instance *inst = instance;
218
219         /*
220          *      Nothing to log: don't do anything.
221          */
222         if (!packet) {
223                 return RLM_MODULE_NOOP;
224         }
225
226         /*
227          *      Create a directory for this nas.
228          *
229          *      Generate the path for the detail file.  Use the
230          *      same format, but truncate at the last /.  Then
231          *      feed it through radius_xlat() to expand the
232          *      variables.
233          */
234         radius_xlat(buffer, sizeof(buffer), inst->detailfile, request, NULL);
235         DEBUG2("rlm_detail: %s expands to %s", inst->detailfile, buffer);
236
237         /*
238          *      Grab the last directory delimiter.
239          */
240         p = strrchr(buffer,'/');
241
242         /*
243          *      There WAS a directory delimiter there, and
244          *      the file doesn't exist, so
245          *      we prolly must create it the dir(s)
246          */
247         if ((p) && (stat(buffer, &st) < 0)) {
248                 *p = '\0';
249                 /*
250                  *      NO previously cached directory name, so we've
251                  *      got to create a new one.
252                  *
253                  *      OR the new directory name is different than the old,
254                  *      so we've got to create a new one.
255                  *
256                  *      OR the cached directory has somehow gotten removed,
257                  *      so we've got to create a new one.
258                  */
259                 if ((inst->last_made_directory == NULL) ||
260                     (strcmp(inst->last_made_directory, buffer) != 0)) {
261                         free((char *) inst->last_made_directory);
262                         inst->last_made_directory = strdup(buffer);
263                 }
264
265                 /*
266                  *      stat the directory, and don't do anything if
267                  *      it exists.  If it doesn't exist, create it.
268                  *
269                  *      This also catches the case where some idiot
270                  *      deleted a directory that the server was using.
271                  */
272                 if (rad_mkdir(inst->last_made_directory, inst->dirperm) < 0) {
273                         radlog(L_ERR, "rlm_detail: Failed to create directory %s: %s", inst->last_made_directory, strerror(errno));
274                         return RLM_MODULE_FAIL;
275                 }
276
277                 *p = '/';
278         } /* else there was no directory delimiter. */
279
280         locked = 0;
281         lock_count = 0;
282         do {
283                 /*
284                  *      Open & create the file, with the given
285                  *      permissions.
286                  */
287                 if ((outfd = open(buffer, O_WRONLY | O_APPEND | O_CREAT,
288                                   inst->detailperm)) < 0) {
289                         radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
290                                buffer, strerror(errno));
291                         return RLM_MODULE_FAIL;
292                 }
293
294                 /*
295                  *      If we fail to aquire the filelock in 80 tries
296                  *      (approximately two seconds) we bail out.
297                  */
298                 if (inst->locking) {
299                         lseek(outfd, 0L, SEEK_SET);
300                         if (rad_lockfd_nonblock(outfd, 0) < 0) {
301                                 close(outfd);
302                                 tv.tv_sec = 0;
303                                 tv.tv_usec = 25000;
304                                 select(0, NULL, NULL, NULL, &tv);
305                                 lock_count++;
306                                 continue;
307                         }
308
309                         /*
310                          *      The file might have been deleted by
311                          *      radrelay while we tried to acquire
312                          *      the lock (race condition)
313                          */
314                         if (fstat(outfd, &st) != 0) {
315                                 radlog(L_ERR, "rlm_detail: Couldn't stat file %s: %s",
316                                        buffer, strerror(errno));
317                                 close(outfd);
318                                 return RLM_MODULE_FAIL;
319                         }
320                         if (st.st_nlink == 0) {
321                                 DEBUG("rlm_detail: File %s removed by another program, retrying",
322                                       buffer);
323                                 close(outfd);
324                                 lock_count = 0;
325                                 continue;
326                         }
327
328                         DEBUG("rlm_detail: Acquired filelock, tried %d time(s)",
329                               lock_count + 1);
330                         locked = 1;
331                 }
332         } while (inst->locking && !locked && lock_count < 80);
333
334         if (inst->locking && !locked) {
335                 close(outfd);
336                 radlog(L_ERR, "rlm_detail: Failed to aquire filelock for %s, giving up",
337                        buffer);
338                 return RLM_MODULE_FAIL;
339         }
340
341         /*
342          *      Convert the FD to FP.  The FD is no longer valid
343          *      after this operation.
344          */
345         if ((outfp = fdopen(outfd, "a")) == NULL) {
346                 radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
347                        buffer, strerror(errno));
348                 if (inst->locking) {
349                         lseek(outfd, 0L, SEEK_SET);
350                         rad_unlockfd(outfd, 0);
351                         DEBUG("rlm_detail: Released filelock");
352                 }
353                 close(outfd);   /* automatically releases the lock */
354
355                 return RLM_MODULE_FAIL;
356         }
357
358         /*
359          *      Post a timestamp
360          */
361         fseek(outfp, 0L, SEEK_END);
362         radius_xlat(timestamp, sizeof(timestamp), inst->header, request, NULL);
363         fprintf(outfp, "%s\n", timestamp);
364
365         /*
366          *      Write the information to the file.
367          */
368         if (!compat) {
369                 /*
370                  *      Print out names, if they're OK.
371                  *      Numbers, if not.
372                  */
373                 if ((packet->code > 0) &&
374                     (packet->code <= PW_ACCESS_CHALLENGE)) {
375                         fprintf(outfp, "\tPacket-Type = %s\n",
376                                 packet_codes[packet->code]);
377                 } else {
378                         fprintf(outfp, "\tPacket-Type = %d\n", packet->code);
379                 }
380         }
381
382         if (inst->log_srcdst) {
383                 VALUE_PAIR src_vp, dst_vp;
384
385                 src_vp.name[0] = dst_vp.name[0] = '\0'; /* for vp_prints() */
386                 src_vp.operator = dst_vp.operator = T_OP_EQ;
387
388                 switch (packet->src_ipaddr.af) {
389                 case AF_INET:
390                         src_vp.type = PW_TYPE_IPADDR;
391                         src_vp.attribute = PW_PACKET_SRC_IP_ADDRESS;
392                         src_vp.lvalue = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
393                         dst_vp.type = PW_TYPE_IPADDR;
394                         dst_vp.attribute = PW_PACKET_DST_IP_ADDRESS;
395                         dst_vp.lvalue = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
396                         break;
397                 case AF_INET6:
398                         src_vp.type = PW_TYPE_IPV6ADDR; 
399                         src_vp.attribute = PW_PACKET_SRC_IPV6_ADDRESS;
400                         memcpy(src_vp.vp_strvalue,
401                                &packet->src_ipaddr.ipaddr.ip6addr,
402                                sizeof(packet->src_ipaddr.ipaddr.ip6addr));
403                         dst_vp.type = PW_TYPE_IPV6ADDR; 
404                         dst_vp.attribute = PW_PACKET_DST_IPV6_ADDRESS;
405                         memcpy(dst_vp.vp_strvalue,
406                                &packet->dst_ipaddr.ipaddr.ip6addr,
407                                sizeof(packet->dst_ipaddr.ipaddr.ip6addr));
408                         break;
409                 default:
410                         break;
411                 }
412
413                 fputs("\t", outfp);
414                 vp_print(outfp, &src_vp);
415                 fputs("\n", outfp);
416                 fputs("\t", outfp);
417                 vp_print(outfp, &dst_vp);
418                 fputs("\n", outfp);
419
420                 src_vp.attribute = PW_PACKET_SRC_PORT;
421                 src_vp.type = PW_TYPE_INTEGER;
422                 src_vp.lvalue = packet->src_port;
423                 dst_vp.attribute = PW_PACKET_DST_PORT;
424                 dst_vp.type = PW_TYPE_INTEGER;
425                 dst_vp.lvalue = packet->dst_port;
426
427                 fputs("\t", outfp);
428                 vp_print(outfp, &src_vp);
429                 fputs("\n", outfp);
430                 fputs("\t", outfp);
431                 vp_print(outfp, &dst_vp);
432                 fputs("\n", outfp);
433         }
434
435         /* Write each attribute/value to the log file */
436         for (; pair != NULL; pair = pair->next) {
437                 DICT_ATTR da;
438                 da.attr = pair->attribute;
439
440                 if (inst->ht &&
441                     lrad_hash_table_finddata(inst->ht, &da)) continue;
442
443                 /*
444                  *      Don't print passwords in old format...
445                  */
446                 if (compat && (pair->attribute == PW_USER_PASSWORD)) continue;
447
448                 /*
449                  *      Print all of the attributes.
450                  */
451                 fputs("\t", outfp);
452                 vp_print(outfp, pair);
453                 fputs("\n", outfp);
454         }
455
456         /*
457          *      Add non-protocol attibutes.
458          */
459         if (compat) {
460                 if ((pair = pairfind(request->config_items,
461                                      PW_PROXY_TO_REALM)) != NULL) {
462                         proxy_realm = realm_find(pair->vp_strvalue, TRUE);
463                         if (proxy_realm) {
464                                 memset((char *) proxy_buffer, 0, 16);
465
466                                 rad_assert(proxy_realm->acct_ipaddr.af == AF_INET);
467
468                                 inet_ntop(proxy_realm->acct_ipaddr.af,
469                                           &proxy_realm->acct_ipaddr.ipaddr,
470                                           proxy_buffer, sizeof(proxy_buffer));
471                                 fprintf(outfp, "\tFreeradius-Proxied-To = %s\n",
472                                         proxy_buffer);
473                                 DEBUG("rlm_detail: Freeradius-Proxied-To set to %s",
474                                       proxy_buffer);
475                         }
476                 }
477                 fprintf(outfp, "\tTimestamp = %ld\n",
478                         (unsigned long) request->timestamp);
479
480                 if (request->packet->verified == 2)
481                         fputs("\tRequest-Authenticator = Verified\n", outfp);
482                 else if (request->packet->verified == 1)
483                         fputs("\tRequest-Authenticator = None\n", outfp);
484         }
485
486         fputs("\n", outfp);
487
488         if (inst->locking) {
489                 fflush(outfp);
490                 lseek(outfd, 0L, SEEK_SET);
491                 rad_unlockfd(outfd, 0);
492                 DEBUG("rlm_detail: Released filelock");
493         }
494
495         fclose(outfp);
496
497         /*
498          *      And everything is fine.
499          */
500         return RLM_MODULE_OK;
501 }
502
503 /*
504  *      Accounting - write the detail files.
505  */
506 static int detail_accounting(void *instance, REQUEST *request)
507 {
508
509         return do_detail(instance,request,request->packet, TRUE);
510 }
511
512 /*
513  *      Incoming Access Request - write the detail files.
514  */
515 static int detail_authorize(void *instance, REQUEST *request)
516 {
517         return do_detail(instance,request,request->packet, FALSE);
518 }
519
520 /*
521  *      Outgoing Access-Request Reply - write the detail files.
522  */
523 static int detail_postauth(void *instance, REQUEST *request)
524 {
525         return do_detail(instance,request,request->reply, FALSE);
526 }
527
528
529 /*
530  *      Outgoing Access-Request to home server - write the detail files.
531  */
532 static int detail_pre_proxy(void *instance, REQUEST *request)
533 {
534         if (request->proxy &&
535             request->proxy->vps) {
536                 return do_detail(instance,request,request->proxy, FALSE);
537         }
538
539         return RLM_MODULE_NOOP;
540 }
541
542
543 /*
544  *      Outgoing Access-Request Reply - write the detail files.
545  */
546 static int detail_post_proxy(void *instance, REQUEST *request)
547 {
548         if (request->proxy_reply &&
549             request->proxy_reply->vps) {
550                 return do_detail(instance,request,request->proxy_reply, FALSE);
551         }
552
553         return RLM_MODULE_NOOP;
554 }
555
556
557 /* globally exported name */
558 module_t rlm_detail = {
559         RLM_MODULE_INIT,
560         "detail",
561         RLM_TYPE_THREAD_UNSAFE,        /* type: reserved */
562         detail_instantiate,             /* instantiation */
563         detail_detach,                  /* detach */
564         {
565                 NULL,                   /* authentication */
566                 detail_authorize,       /* authorization */
567                 NULL,                   /* preaccounting */
568                 detail_accounting,      /* accounting */
569                 NULL,                   /* checksimul */
570                 detail_pre_proxy,       /* pre-proxy */
571                 detail_post_proxy,      /* post-proxy */
572                 detail_postauth         /* post-auth */
573         },
574 };
575