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