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