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