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