All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
detail.c
Go to the documentation of this file.
1 /*
2  * detail.c Process the detail file
3  *
4  * Version: $Id: 8b2e2c35e84cebd7a7d9715e3db784e05f60600a $
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 2007 The FreeRADIUS server project
21  * Copyright 2007 Alan DeKok <aland@deployingradius.com>
22  */
23 
24 RCSID("$Id: 8b2e2c35e84cebd7a7d9715e3db784e05f60600a $")
25 
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <freeradius-devel/detail.h>
29 #include <freeradius-devel/process.h>
30 #include <freeradius-devel/rad_assert.h>
31 
32 #ifdef HAVE_SYS_STAT_H
33 #include <sys/stat.h>
34 #endif
35 
36 #ifdef HAVE_GLOB_H
37 #include <glob.h>
38 #endif
39 
40 #include <fcntl.h>
41 
42 #ifdef WITH_DETAIL
43 
44 #define USEC (1000000)
45 
47  { "unopened", STATE_UNOPENED },
48  { "unlocked", STATE_UNLOCKED },
49  { "header", STATE_HEADER },
50  { "reading", STATE_READING },
51  { "queued", STATE_QUEUED },
52  { "running", STATE_RUNNING },
53  { "no-reply", STATE_NO_REPLY },
54  { "replied", STATE_REPLIED },
55 
56  { NULL, 0 }
57 };
58 
59 
60 /*
61  * If we're limiting outstanding packets, then mark the response
62  * as being sent.
63  */
64 int detail_send(rad_listen_t *listener, REQUEST *request)
65 {
66 #ifdef WITH_DETAIL_THREAD
67  char c = 0;
68 #endif
69  listen_detail_t *data = listener->data;
70 
71  rad_assert(request->listener == listener);
72  rad_assert(listener->send == detail_send);
73 
74  /*
75  * This request timed out. Remember that, and tell the
76  * caller it's OK to read more "detail" file stuff.
77  */
78  if (request->reply->code == 0) {
79  data->delay_time = data->retry_interval * USEC;
80  data->signal = 1;
81  data->state = STATE_NO_REPLY;
82 
83  RDEBUG("detail (%s): No response to request. Will retry in %d seconds",
84  data->name, data->retry_interval);
85  } else {
86  int rtt;
87  struct timeval now;
88  /*
89  * We call gettimeofday a lot. But it should be OK,
90  * because there's nothing else to do.
91  */
92  gettimeofday(&now, NULL);
93 
94  /*
95  * If we haven't sent a packet in the last second, reset
96  * the RTT.
97  */
98  now.tv_sec -= 1;
99  if (timercmp(&data->last_packet, &now, <)) {
100  data->has_rtt = false;
101  }
102  now.tv_sec += 1;
103 
104  /*
105  * Only one detail packet may be outstanding at a time,
106  * so it's safe to update some entries in the detail
107  * structure.
108  *
109  * We keep smoothed round trip time (SRTT), but not round
110  * trip timeout (RTO). We use SRTT to calculate a rough
111  * load factor.
112  */
113  rtt = now.tv_sec - request->packet->timestamp.tv_sec;
114  rtt *= USEC;
115  rtt += now.tv_usec;
116  rtt -= request->packet->timestamp.tv_usec;
117 
118  /*
119  * If we're proxying, the RTT is our processing time,
120  * plus the network delay there and back, plus the time
121  * on the other end to process the packet. Ideally, we
122  * should remove the network delays from the RTT, but we
123  * don't know what they are.
124  *
125  * So, to be safe, we over-estimate the total cost of
126  * processing the packet.
127  */
128  if (!data->has_rtt) {
129  data->has_rtt = true;
130  data->srtt = rtt;
131  data->rttvar = rtt / 2;
132 
133  } else {
134  data->rttvar -= data->rttvar >> 2;
135  data->rttvar += (data->srtt - rtt);
136  data->srtt -= data->srtt >> 3;
137  data->srtt += rtt >> 3;
138  }
139 
140  /*
141  * Calculate the time we wait before sending the next
142  * packet.
143  *
144  * rtt / (rtt + delay) = load_factor / 100
145  */
146  data->delay_time = (data->srtt * (100 - data->load_factor)) / (data->load_factor);
147 
148  /*
149  * Cap delay at no less than 4 packets/s. If the
150  * end system can't handle this, then it's very
151  * broken.
152  */
153  if (data->delay_time > (USEC / 4)) data->delay_time= USEC / 4;
154 
155  RDEBUG3("detail (%s): Received response for request %d. Will read the next packet in %d seconds",
156  data->name, request->number, data->delay_time / USEC);
157 
158  data->last_packet = now;
159  data->signal = 1;
160  data->state = STATE_REPLIED;
161  data->counter++;
162  }
163 
164 #ifdef WITH_DETAIL_THREAD
165  if (write(data->child_pipe[1], &c, 1) < 0) {
166  RERROR("detail (%s): Failed writing ack to reader thread: %s", data->name, fr_syserror(errno));
167  }
168 #else
170 #endif
171 
172  return 0;
173 }
174 
175 
176 /*
177  * Open the detail file, if we can.
178  *
179  * FIXME: create it, if it's not already there, so that the main
180  * server select() will wake us up if there's anything to read.
181  */
182 static int detail_open(rad_listen_t *this)
183 {
184  struct stat st;
185  listen_detail_t *data = this->data;
186 
187  rad_assert(data->state == STATE_UNOPENED);
188  data->delay_time = USEC;
189 
190  /*
191  * Open detail.work first, so we don't lose
192  * accounting packets. It's probably better to
193  * duplicate them than to lose them.
194  *
195  * Note that we're not writing to the file, but
196  * we've got to open it for writing in order to
197  * establish the lock, to prevent rlm_detail from
198  * writing to it.
199  *
200  * This also means that if we're doing globbing,
201  * this file will be read && processed before the
202  * file globbing is done.
203  */
204  data->fp = NULL;
205  data->work_fd = open(data->filename_work, O_RDWR);
206 
207  /*
208  * Couldn't open it for a reason OTHER than "it doesn't
209  * exist". Complain and tell the admin.
210  */
211  if ((data->work_fd < 0) && (errno != ENOENT)) {
212  ERROR("Failed opening detail file %s: %s",
213  data->filename_work, fr_syserror(errno));
214  return 0;
215  }
216 
217  /*
218  * The file doesn't exist. Poll for it again.
219  */
220  if (data->work_fd < 0) {
221 #ifndef HAVE_GLOB_H
222  return 0;
223 #else
224  unsigned int i;
225  int found;
226  time_t chtime;
227  char const *filename;
228  glob_t files;
229 
230  DEBUG2("detail (%s): Polling for detail file", data->name);
231 
232  memset(&files, 0, sizeof(files));
233  if (glob(data->filename, 0, NULL, &files) != 0) {
234  noop:
235  globfree(&files);
236  return 0;
237  }
238 
239  /*
240  * Loop over the glob'd files, looking for the
241  * oldest one.
242  */
243  chtime = 0;
244  found = -1;
245  for (i = 0; i < files.gl_pathc; i++) {
246  if (stat(files.gl_pathv[i], &st) < 0) continue;
247 
248  if ((i == 0) || (st.st_ctime < chtime)) {
249  chtime = st.st_ctime;
250  found = i;
251  }
252  }
253 
254  if (found < 0) goto noop;
255 
256  /*
257  * Rename detail to detail.work
258  */
259  filename = files.gl_pathv[found];
260 
261  DEBUG("detail (%s): Renaming %s -> %s", data->name, filename, data->filename_work);
262  if (rename(filename, data->filename_work) < 0) {
263  ERROR("detail (%s): Failed renaming %s to %s: %s",
264  data->name, filename, data->filename_work, fr_syserror(errno));
265  goto noop;
266  }
267 
268  globfree(&files); /* Shouldn't be using anything in files now */
269 
270  /*
271  * And try to open the filename.
272  */
273  data->work_fd = open(data->filename_work, O_RDWR);
274  if (data->work_fd < 0) {
275  ERROR("detail (%s): Failed opening %s: %s",
276  data->name, data->filename_work, fr_syserror(errno));
277  return 0;
278  }
279 #endif
280  } /* else detail.work existed, and we opened it */
281 
282  rad_assert(data->vps == NULL);
283  rad_assert(data->fp == NULL);
284 
285  data->state = STATE_UNLOCKED;
286 
287  data->client_ip.af = AF_UNSPEC;
288  data->timestamp = 0;
289  data->offset = data->last_offset = data->timestamp_offset = 0;
290  data->packets = 0;
291  data->tries = 0;
292  data->done_entry = false;
293 
294  return 1;
295 }
296 
297 
298 /*
299  * FIXME: add a configuration "exit when done" so that the detail
300  * file reader can be used as a one-off tool to update stuff.
301  *
302  * The time sequence for reading from the detail file is:
303  *
304  * t_0 signalled that the server is idle, and we
305  * can read from the detail file.
306  *
307  * t_rtt the packet has been processed successfully,
308  * wait for t_delay to enforce load factor.
309  *
310  * t_rtt + t_delay wait for signal that the server is idle.
311  *
312  */
313 #ifndef WITH_DETAIL_THREAD
314 static RADIUS_PACKET *detail_poll(rad_listen_t *listener);
315 
316 int detail_recv(rad_listen_t *listener)
317 {
318  RADIUS_PACKET *packet;
319  listen_detail_t *data = listener->data;
320  RAD_REQUEST_FUNP fun = NULL;
321 
322  /*
323  * We may be in the main thread. It needs to update the
324  * timers before we try to read from the file again.
325  */
326  if (data->signal) return 0;
327 
328  packet = detail_poll(listener);
329  if (!packet) return -1;
330 
331  if (DEBUG_ENABLED2) {
332  VALUE_PAIR *vp;
333  vp_cursor_t cursor;
334 
335  DEBUG2("detail (%s): Read packet from %s", data->name, data->filename_work);
336 
337  for (vp = fr_cursor_init(&cursor, &packet->vps);
338  vp;
339  vp = fr_cursor_next(&cursor)) {
340  debug_pair(vp);
341  }
342  }
343 
344  switch (packet->code) {
346  fun = rad_accounting;
347  break;
348 
349  case PW_CODE_COA_REQUEST:
351  fun = rad_coa_recv;
352  break;
353 
354  default:
355  fr_radius_free(&packet);
356  data->state = STATE_REPLIED;
357  return 0;
358  }
359 
360  /*
361  * Don't bother doing limit checks, etc.
362  */
363  if (!request_receive(NULL, listener, packet, &data->detail_client, fun)) {
364  fr_radius_free(&packet);
365  data->state = STATE_NO_REPLY; /* try again later */
366  return 0;
367  }
368 
369  return 1;
370 }
371 #else
372 int detail_recv(rad_listen_t *listener)
373 {
374  char c = 0;
375  ssize_t rcode;
376  RADIUS_PACKET *packet;
377  listen_detail_t *data = listener->data;
378  RAD_REQUEST_FUNP fun = NULL;
379 
380  /*
381  * Block until there's a packet ready.
382  */
383  rcode = read(data->master_pipe[0], &packet, sizeof(packet));
384  if (rcode <= 0) return rcode;
385 
386  if (DEBUG_ENABLED2) {
387  VALUE_PAIR *vp;
388  vp_cursor_t cursor;
389 
390  DEBUG2("detail (%s): Read packet from %s", data->name, data->filename_work);
391  for (vp = fr_cursor_init(&cursor, &packet->vps);
392  vp;
393  vp = fr_cursor_next(&cursor)) {
394  debug_pair(vp);
395  }
396  }
397  rad_assert(packet != NULL);
398 
399  switch (packet->code) {
401  fun = rad_accounting;
402  break;
403 
404  case PW_CODE_COA_REQUEST:
406  fun = rad_coa_recv;
407  break;
408 
409  default:
410  data->state = STATE_REPLIED;
411  goto signal_thread;
412  }
413 
414  if (!request_receive(NULL, listener, packet, &data->detail_client, fun)) {
415  data->state = STATE_NO_REPLY; /* try again later */
416 
417  signal_thread:
418  fr_radius_free(&packet);
419  if (write(data->child_pipe[1], &c, 1) < 0) {
420  ERROR("detail (%s): Failed writing ack to reader thread: %s", data->name,
421  fr_syserror(errno));
422  }
423  }
424 
425  /*
426  * Wait for the child thread to write an answer to the pipe
427  */
428  return 0;
429 }
430 #endif
431 
433 {
434  char key[256], op[8], value[1024];
435  vp_cursor_t cursor;
436  VALUE_PAIR *vp;
437  RADIUS_PACKET *packet;
438  char buffer[2048];
439  listen_detail_t *data = listener->data;
440 
441  switch (data->state) {
442  case STATE_UNOPENED:
443 open_file:
444  rad_assert(data->work_fd < 0);
445 
446  if (!detail_open(listener)) return NULL;
447 
448  rad_assert(data->state == STATE_UNLOCKED);
449  rad_assert(data->work_fd >= 0);
450 
451  /* FALL-THROUGH */
452 
453  /*
454  * Try to lock fd. If we can't, return.
455  * If we can, continue. This means that
456  * the server doesn't block while waiting
457  * for the lock to open...
458  */
459  case STATE_UNLOCKED:
460  /*
461  * Note that we do NOT block waiting for
462  * the lock. We've re-named the file
463  * above, so we've already guaranteed
464  * that any *new* detail writer will not
465  * be opening this file. The only
466  * purpose of the lock is to catch a race
467  * condition where the execution
468  * "ping-pongs" between radiusd &
469  * radrelay.
470  */
471  if (rad_lockfd_nonblock(data->work_fd, 0) < 0) {
472  /*
473  * Close the FD. The main loop
474  * will wake up in a second and
475  * try again.
476  */
477  close(data->work_fd);
478  data->fp = NULL;
479  data->work_fd = -1;
480  data->state = STATE_UNOPENED;
481  return NULL;
482  }
483 
484  /*
485  * Only open for writing if we're
486  * marking requests as completed.
487  */
488  data->fp = fdopen(data->work_fd, data->track ? "r+" : "r");
489  if (!data->fp) {
490  ERROR("detail (%s): FATAL: Failed to re-open detail file: %s",
491  data->name, fr_syserror(errno));
492  fr_exit(1);
493  }
494 
495  /*
496  * Look for the header
497  */
498  data->state = STATE_HEADER;
499  data->delay_time = USEC;
500  data->vps = NULL;
501 
502  /* FALL-THROUGH */
503 
504  case STATE_HEADER:
505  do_header:
506  data->done_entry = false;
507  data->timestamp_offset = 0;
508 
509  data->tries = 0;
510  if (!data->fp) {
511  data->state = STATE_UNOPENED;
512  goto open_file;
513  }
514 
515  {
516  struct stat buf;
517 
518  if (fstat(data->work_fd, &buf) < 0) {
519  ERROR("detail (%s): Failed to stat detail file: %s",
520  data->name, fr_syserror(errno));
521 
522  goto cleanup;
523  }
524  if (((off_t) ftell(data->fp)) == buf.st_size) {
525  goto cleanup;
526  }
527  }
528 
529  /*
530  * End of file. Delete it, and re-set
531  * everything.
532  */
533  if (feof(data->fp)) {
534  cleanup:
535  DEBUG("detail (%s): Unlinking %s", data->name, data->filename_work);
536  unlink(data->filename_work);
537  if (data->fp) fclose(data->fp);
538  data->fp = NULL;
539  data->work_fd = -1;
540  data->state = STATE_UNOPENED;
541  rad_assert(data->vps == NULL);
542 
543  if (data->one_shot) {
544  INFO("detail (%s): Finished reading \"one shot\" detail file - Exiting", data->name);
546  }
547 
548  return NULL;
549  }
550 
551  /*
552  * Else go read something.
553  */
554  break;
555 
556  /*
557  * Read more value-pair's, unless we're
558  * at EOF. In that case, queue whatever
559  * we have.
560  */
561  case STATE_READING:
562  if (data->fp && !feof(data->fp)) break;
563  data->state = STATE_QUEUED;
564 
565  /* FALL-THROUGH */
566 
567  case STATE_QUEUED:
568  goto alloc_packet;
569 
570  /*
571  * Periodically check what's going on.
572  * If the request is taking too long,
573  * retry it.
574  */
575  case STATE_RUNNING:
576  if (time(NULL) < (data->running + (int)data->retry_interval)) {
577  return NULL;
578  }
579 
580  DEBUG("detail (%s): No response to detail request. Retrying", data->name);
581  /* FALL-THROUGH */
582 
583  /*
584  * If there's no reply, keep
585  * retransmitting the current packet
586  * forever.
587  */
588  case STATE_NO_REPLY:
589  data->state = STATE_QUEUED;
590  goto alloc_packet;
591 
592  /*
593  * We have a reply. Clean up the old
594  * request, and go read another one.
595  */
596  case STATE_REPLIED:
597  if (data->track) {
598  rad_assert(data->fp != NULL);
599 
600  if (fseek(data->fp, data->timestamp_offset, SEEK_SET) < 0) {
601  WARN("detail (%s): Failed seeking to timestamp offset: %s",
602  data->name, fr_syserror(errno));
603  } else if (fwrite("\tDone", 1, 5, data->fp) < 5) {
604  WARN("detail (%s): Failed marking request as done: %s",
605  data->name, fr_syserror(errno));
606  } else if (fflush(data->fp) != 0) {
607  WARN("detail (%s): Failed flushing marked detail file to disk: %s",
608  data->name, fr_syserror(errno));
609  }
610 
611  if (fseek(data->fp, data->offset, SEEK_SET) < 0) {
612  WARN("detail (%s): Failed seeking to next detail request: %s",
613  data->name, fr_syserror(errno));
614  }
615  }
616 
617  fr_pair_list_free(&data->vps);
618  data->state = STATE_HEADER;
619  goto do_header;
620  }
621 
622  fr_cursor_init(&cursor, &data->vps);
623 
624  /*
625  * Read a header, OR a value-pair.
626  */
627  while (fgets(buffer, sizeof(buffer), data->fp)) {
628  data->last_offset = data->offset;
629  data->offset = ftell(data->fp); /* for statistics */
630 
631  /*
632  * Badly formatted file: delete it.
633  *
634  * FIXME: Maybe flag an error?
635  */
636  if (!strchr(buffer, '\n')) {
637  fr_pair_list_free(&data->vps);
638  goto cleanup;
639  }
640 
641  /*
642  * We're reading VP's, and got a blank line.
643  * Queue the packet.
644  */
645  if ((data->state == STATE_READING) &&
646  (buffer[0] == '\n')) {
647  data->state = STATE_QUEUED;
648  break;
649  }
650 
651  /*
652  * Look for date/time header, and read VP's if
653  * found. If not, keep reading lines until we
654  * find one.
655  */
656  if (data->state == STATE_HEADER) {
657  int y;
658 
659  if (sscanf(buffer, "%*s %*s %*d %*d:%*d:%*d %d", &y)) {
660  data->state = STATE_READING;
661  }
662  continue;
663  }
664 
665  /*
666  * We have a full "attribute = value" line.
667  * If it doesn't look reasonable, skip it.
668  *
669  * FIXME: print an error for badly formatted attributes?
670  */
671  if (sscanf(buffer, "%255s %7s %1023s", key, op, value) != 3) {
672  WARN("detail (%s): Skipping badly formatted line %s", data->name, buffer);
673  continue;
674  }
675 
676  /*
677  * Should be =, :=, +=, ...
678  */
679  if (!strchr(op, '=')) continue;
680 
681  /*
682  * Skip non-protocol attributes.
683  */
684  if (!strcasecmp(key, "Request-Authenticator")) continue;
685 
686  /*
687  * Set the original client IP address, based on
688  * what's in the detail file.
689  *
690  * Hmm... we don't set the server IP address.
691  * or port. Oh well.
692  */
693  if (!strcasecmp(key, "Client-IP-Address")) {
694  data->client_ip.af = AF_INET;
695  if (fr_inet_hton(&data->client_ip, AF_INET, value, false) < 0) {
696  ERROR("detail (%s): Failed parsing Client-IP-Address", data->name);
697 
698  fr_pair_list_free(&data->vps);
699  goto cleanup;
700  }
701  continue;
702  }
703 
704  /*
705  * The original time at which we received the
706  * packet. We need this to properly calculate
707  * Acct-Delay-Time.
708  */
709  if (!strcasecmp(key, "Timestamp")) {
710  data->timestamp = atoi(value);
711  data->timestamp_offset = data->last_offset;
712 
713  vp = fr_pair_afrom_num(data, 0, PW_PACKET_ORIGINAL_TIMESTAMP);
714  if (vp) {
715  vp->vp_date = (uint32_t) data->timestamp;
716  vp->type = VT_DATA;
717  fr_cursor_insert(&cursor, vp);
718  }
719  continue;
720  }
721 
722  if (!strcasecmp(key, "Donestamp")) {
723  data->timestamp = atoi(value);
724  data->done_entry = true;
725  continue;
726  }
727 
728  /*
729  * Read one VP.
730  *
731  * FIXME: do we want to check for non-protocol
732  * attributes like radsqlrelay does?
733  */
734  vp = NULL;
735  if ((fr_pair_list_afrom_str(data, buffer, &vp) > 0) &&
736  (vp != NULL)) {
737  fr_cursor_merge(&cursor, vp);
738  }
739  }
740 
741  /*
742  * Some kind of error.
743  *
744  * FIXME: Leave the file in-place, and warn the
745  * administrator?
746  */
747  if (ferror(data->fp)) goto cleanup;
748 
749  data->tries = 0;
750  data->packets++;
751 
752  /*
753  * Process the packet.
754  */
755  alloc_packet:
756  if (data->done_entry) {
757  DEBUG2("detail (%s): Skipping record for timestamp %lu", data->name, data->timestamp);
758  fr_pair_list_free(&data->vps);
759  data->state = STATE_HEADER;
760  goto do_header;
761  }
762 
763  data->tries++;
764 
765  /*
766  * The writer doesn't check that the record was
767  * completely written. If the disk is full, this can
768  * result in a truncated record. When that happens,
769  * treat it as EOF.
770  */
771  if (data->state != STATE_QUEUED) {
772  ERROR("detail (%s): Truncated record: treating it as EOF for detail file %s",
773  data->name, data->filename_work);
774  fr_pair_list_free(&data->vps);
775  goto cleanup;
776  }
777 
778  /*
779  * We're done reading the file, but we didn't read
780  * anything. Clean up, and don't return anything.
781  */
782  if (!data->vps) {
783  data->state = STATE_HEADER;
784  if (!data->fp || feof(data->fp)) goto cleanup;
785  return NULL;
786  }
787 
788  /*
789  * Allocate the packet. If we fail, it's a serious
790  * problem.
791  */
792  packet = fr_radius_alloc(NULL, true);
793  if (!packet) {
794  ERROR("detail (%s): FATAL: Failed allocating memory for detail", data->name);
795  fr_exit(1);
796  }
797 
798  memset(packet, 0, sizeof(*packet));
799  packet->sockfd = -1;
800  packet->src_ipaddr.af = AF_INET;
801  packet->src_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_NONE);
802 
803  /*
804  * If everything's OK, this is a waste of memory.
805  * Otherwise, it lets us re-send the original packet
806  * contents, unmolested.
807  */
808  packet->vps = fr_pair_list_copy(packet, data->vps);
809 
811  vp = fr_pair_find_by_num(packet->vps, 0, PW_PACKET_TYPE, TAG_ANY);
812  if (vp) packet->code = vp->vp_integer;
813 
814  gettimeofday(&packet->timestamp, NULL);
815 
816  /*
817  * Remember where it came from, so that we don't
818  * proxy it to the place it came from...
819  */
820  if (data->client_ip.af != AF_UNSPEC) {
821  packet->src_ipaddr = data->client_ip;
822  }
823 
824  vp = fr_pair_find_by_num(packet->vps, 0, PW_PACKET_SRC_IP_ADDRESS, TAG_ANY);
825  if (vp) {
826  packet->src_ipaddr.af = AF_INET;
827  packet->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
828  packet->src_ipaddr.prefix = 32;
829  } else {
830  vp = fr_pair_find_by_num(packet->vps, 0, PW_PACKET_SRC_IPV6_ADDRESS, TAG_ANY);
831  if (vp) {
832  packet->src_ipaddr.af = AF_INET6;
833  memcpy(&packet->src_ipaddr.ipaddr.ip6addr,
834  &vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
835  packet->src_ipaddr.prefix = 128;
836  }
837  }
838 
839  vp = fr_pair_find_by_num(packet->vps, 0, PW_PACKET_DST_IP_ADDRESS, TAG_ANY);
840  if (vp) {
841  packet->dst_ipaddr.af = AF_INET;
842  packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
843  packet->dst_ipaddr.prefix = 32;
844  } else {
845  vp = fr_pair_find_by_num(packet->vps, 0, PW_PACKET_DST_IPV6_ADDRESS, TAG_ANY);
846  if (vp) {
847  packet->dst_ipaddr.af = AF_INET6;
848  memcpy(&packet->dst_ipaddr.ipaddr.ip6addr,
849  &vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
850  packet->dst_ipaddr.prefix = 128;
851  }
852  }
853 
854  /*
855  * Generate packet ID, ports, IP via a counter.
856  */
857  packet->id = data->counter & 0xff;
858  packet->src_port = 1024 + ((data->counter >> 8) & 0xff);
859  packet->dst_port = 1024 + ((data->counter >> 16) & 0xff);
860 
861  packet->dst_ipaddr.af = AF_INET;
862  packet->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl((INADDR_LOOPBACK & ~0xffffff) | ((data->counter >> 24) & 0xff));
863 
864  /*
865  * Create / update accounting attributes.
866  */
867  if (packet->code == PW_CODE_ACCOUNTING_REQUEST) {
868  /*
869  * Prefer the Event-Timestamp in the packet, if it
870  * exists. That is when the event occurred, whereas the
871  * "Timestamp" field is when we wrote the packet to the
872  * detail file, which could have been much later.
873  */
874  vp = fr_pair_find_by_num(packet->vps, 0, PW_EVENT_TIMESTAMP, TAG_ANY);
875  if (vp) {
876  data->timestamp = vp->vp_integer;
877  }
878 
879  /*
880  * Look for Acct-Delay-Time, and update
881  * based on Acct-Delay-Time += (time(NULL) - timestamp)
882  */
883  vp = fr_pair_find_by_num(packet->vps, 0, PW_ACCT_DELAY_TIME, TAG_ANY);
884  if (!vp) {
885  vp = fr_pair_afrom_num(packet, 0, PW_ACCT_DELAY_TIME);
886  rad_assert(vp != NULL);
887  fr_pair_add(&packet->vps, vp);
888  }
889  if (data->timestamp != 0) {
890  vp->vp_integer += time(NULL) - data->timestamp;
891  }
892  }
893 
894  /*
895  * Set the transmission count.
896  */
897  vp = fr_pair_find_by_num(packet->vps, 0, PW_PACKET_TRANSMIT_COUNTER, TAG_ANY);
898  if (!vp) {
899  vp = fr_pair_afrom_num(packet, 0, PW_PACKET_TRANSMIT_COUNTER);
900  rad_assert(vp != NULL);
901  fr_pair_add(&packet->vps, vp);
902  }
903  vp->vp_integer = data->tries;
904 
905  data->state = STATE_RUNNING;
906  data->running = packet->timestamp.tv_sec;
907 
908  return packet;
909 }
910 
911 /*
912  * Free detail-specific stuff.
913  */
915 {
916  listen_detail_t *data = this->data;
917 
918 #ifdef WITH_DETAIL_THREAD
919  if (!check_config) {
920  ssize_t ret;
921  void *arg = NULL;
922 
923  /*
924  * Mark the child pipes as unusable
925  */
926  close(data->child_pipe[0]);
927  close(data->child_pipe[1]);
928  data->child_pipe[0] = -1;
929 
930  /*
931  * Tell it to stop (interrupting its sleep)
932  */
933  pthread_kill(data->pthread_id, SIGTERM);
934 
935  /*
936  * Wait for it to acknowledge that it's stopped.
937  */
938  ret = read(data->master_pipe[0], &arg, sizeof(arg));
939  if (ret < 0) {
940  ERROR("detail (%s): Reader thread exited without informing the master: %s",
941  data->name, fr_syserror(errno));
942  } else if (ret != sizeof(arg)) {
943  ERROR("detail (%s): Invalid thread pointer received from reader thread during exit",
944  data->name);
945  ERROR("detail (%s): Expected %zu bytes, got %zi bytes", data->name, sizeof(arg), ret);
946  }
947 
948  close(data->master_pipe[0]);
949  close(data->master_pipe[1]);
950 
951  if (arg) pthread_join(data->pthread_id, &arg);
952  }
953 #endif
954 
955  if (data->fp != NULL) {
956  fclose(data->fp);
957  data->fp = NULL;
958  }
959 }
960 
961 
962 int detail_print(rad_listen_t const *this, char *buffer, size_t bufsize)
963 {
964  if (!this->server) {
965  return snprintf(buffer, bufsize, "%s",
966  ((listen_detail_t *)(this->data))->filename);
967  }
968 
969  return snprintf(buffer, bufsize, "detail file %s as server %s",
970  ((listen_detail_t *)(this->data))->filename,
971  this->server);
972 }
973 
974 
975 /*
976  * Delay while waiting for a file to be ready
977  */
978 static int detail_delay(listen_detail_t *data)
979 {
980  int delay = (data->poll_interval - 1) * USEC;
981 
982  /*
983  * Add +/- 0.25s of jitter
984  */
985  delay += (USEC * 3) / 4;
986  delay += fr_rand() % (USEC / 2);
987 
988  DEBUG2("detail (%s): Detail listener state %s waiting %d.%06d sec",
989  data->name,
990  fr_int2str(state_names, data->state, "?"),
991  (delay / USEC), delay % USEC);
992 
993  return delay;
994 }
995 
996 /*
997  * Overloaded to return delay times.
998  */
1000 {
1001 #ifdef WITH_DETAIL_THREAD
1002  return 0;
1003 #else
1004  listen_detail_t *data = this->data;
1005 
1006  /*
1007  * We haven't sent a packet... delay things a bit.
1008  */
1009  if (!data->signal) return detail_delay(data);
1010 
1011  data->signal = 0;
1012 
1013  DEBUG2("detail (%s): Detail listener state %s signalled %d waiting %d.%06d sec",
1014  data->name,
1015  fr_int2str(state_names, data->state, "?"),
1016  data->signal,
1017  data->delay_time / USEC,
1018  data->delay_time % USEC);
1019 
1020  return data->delay_time;
1021 #endif
1022 }
1023 
1024 /*
1025  * Overloaded to return "should we fix delay times"
1026  */
1028 {
1029 #ifdef WITH_DETAIL_THREAD
1030  return 0;
1031 #else
1032  listen_detail_t *data = this->data;
1033 
1034  return data->signal;
1035 #endif
1036 }
1037 
1038 
1039 #ifdef WITH_DETAIL_THREAD
1040 static void *detail_handler_thread(void *arg)
1041 {
1042  char c;
1043  rad_listen_t *this = arg;
1044  listen_detail_t *data = this->data;
1045 
1046  while (true) {
1047  RADIUS_PACKET *packet;
1048 
1049  while ((packet = detail_poll(this)) == NULL) {
1050  usleep(detail_delay(data));
1051 
1052  /*
1053  * If we're supposed to exit then tell
1054  * the master thread we've exited.
1055  */
1056  if (data->child_pipe[0] < 0) {
1057  packet = NULL;
1058  if (write(data->master_pipe[1], &packet, sizeof(packet)) < 0) {
1059  ERROR("detail (%s): Failed writing exit status to master: %s",
1060  data->name, fr_syserror(errno));
1061  }
1062  return NULL;
1063  }
1064  }
1065 
1066  /*
1067  * Keep retrying forever.
1068  *
1069  * FIXME: cap the retries.
1070  */
1071  do {
1072  if (write(data->master_pipe[1], &packet, sizeof(packet)) < 0) {
1073  ERROR("detail (%s): Failed passing detail packet pointer to master: %s",
1074  data->name, fr_syserror(errno));
1075  }
1076 
1077  if (read(data->child_pipe[0], &c, 1) < 0) {
1078  ERROR("detail (%s): Failed getting detail packet ack from master: %s",
1079  data->name, fr_syserror(errno));
1080  break;
1081  }
1082 
1083  if (data->delay_time > 0) usleep(data->delay_time);
1084 
1085  packet = detail_poll(this);
1086  if (!packet) break;
1087  } while (data->state != STATE_REPLIED);
1088  }
1089 
1090  return NULL;
1091 }
1092 #endif
1093 
1094 
1095 static const CONF_PARSER detail_config[] = {
1098  { FR_CONF_OFFSET("load_factor", PW_TYPE_INTEGER, listen_detail_t, load_factor), .dflt = STRINGIFY(10) },
1099  { FR_CONF_OFFSET("poll_interval", PW_TYPE_INTEGER, listen_detail_t, poll_interval), .dflt = STRINGIFY(1) },
1100  { FR_CONF_OFFSET("retry_interval", PW_TYPE_INTEGER, listen_detail_t, retry_interval), .dflt = STRINGIFY(30) },
1101  { FR_CONF_OFFSET("one_shot", PW_TYPE_BOOLEAN, listen_detail_t, one_shot), .dflt = "no" },
1102  { FR_CONF_OFFSET("track", PW_TYPE_BOOLEAN, listen_detail_t, track), .dflt = "no" },
1104 };
1105 
1106 /*
1107  * Parse a detail section.
1108  */
1110 {
1111  int rcode;
1113  RADCLIENT *client;
1114  char buffer[2048];
1115 
1116  data = this->data;
1117 
1118  rcode = cf_section_parse(cs, data, detail_config);
1119  if (rcode < 0) {
1120  cf_log_err_cs(cs, "Failed parsing listen section");
1121  return -1;
1122  }
1123 
1124  data->name = cf_section_name2(cs);
1125  if (!data->name) data->name = data->filename;
1126 
1127  /*
1128  * We don't do duplicate detection for "detail" sockets.
1129  */
1130  this->nodup = true;
1131  this->synchronous = false;
1132 
1133  if (!data->filename) {
1134  cf_log_err_cs(cs, "No detail file specified in listen section");
1135  return -1;
1136  }
1137 
1138  FR_INTEGER_BOUND_CHECK("load_factor", data->load_factor, >=, 1);
1139  FR_INTEGER_BOUND_CHECK("load_factor", data->load_factor, <=, 100);
1140 
1141  FR_INTEGER_BOUND_CHECK("poll_interval", data->poll_interval, >=, 1);
1142  FR_INTEGER_BOUND_CHECK("poll_interval", data->poll_interval, <=, 60);
1143 
1144  FR_INTEGER_BOUND_CHECK("retry_interval", data->retry_interval, >=, 4);
1145  FR_INTEGER_BOUND_CHECK("retry_interval", data->retry_interval, <=, 3600);
1146 
1147  /*
1148  * Only checking the config. Don't start threads or anything else.
1149  */
1150  if (check_config) return 0;
1151 
1152  /*
1153  * If the filename is a glob, use "detail.work" as the
1154  * work file name.
1155  */
1156  if ((strchr(data->filename, '*') != NULL) ||
1157  (strchr(data->filename, '[') != NULL)) {
1158  char *p;
1159 
1160 #ifndef HAVE_GLOB_H
1161  WARN("detail (%s): File \"%s\" appears to use file globbing, but it is not supported on this system",
1162  data->name, data->filename);
1163 #endif
1164  strlcpy(buffer, data->filename, sizeof(buffer));
1165  p = strrchr(buffer, FR_DIR_SEP);
1166  if (p) {
1167  p[1] = '\0';
1168  } else {
1169  buffer[0] = '\0';
1170  }
1171 
1172  /*
1173  * Globbing cannot be done across directories.
1174  */
1175  if ((strchr(buffer, '*') != NULL) ||
1176  (strchr(buffer, '[') != NULL)) {
1177  cf_log_err_cs(cs, "Wildcard directories are not supported");
1178  return -1;
1179  }
1180 
1181  strlcat(buffer, "detail.work",
1182  sizeof(buffer) - strlen(buffer));
1183 
1184  } else {
1185  snprintf(buffer, sizeof(buffer), "%s.work", data->filename);
1186  }
1187 
1188  data->filename_work = talloc_strdup(data, buffer);
1189 
1190  data->work_fd = -1;
1191  data->vps = NULL;
1192  data->fp = NULL;
1193  data->state = STATE_UNOPENED;
1194  data->delay_time = data->poll_interval * USEC;
1195  data->signal = 1;
1196 
1197  /*
1198  * Initialize the fake client.
1199  */
1200  client = &data->detail_client;
1201  memset(client, 0, sizeof(*client));
1202  client->ipaddr.af = AF_INET;
1203  client->ipaddr.ipaddr.ip4addr.s_addr = INADDR_NONE;
1204  client->ipaddr.prefix = 0;
1205  client->longname = client->shortname = data->filename;
1206  client->secret = client->shortname;
1207  client->nas_type = talloc_strdup(data, "none"); /* Part of 'data' not dynamically allocated */
1208 
1209  return 0;
1210 }
1211 
1212 #ifdef WITH_DETAIL_THREAD
1213 /*
1214  * Open detail files
1215  */
1217 {
1219 
1220  data = this->data;
1221 
1222  /*
1223  * Create the communication pipes.
1224  */
1225  if (pipe(data->master_pipe) < 0) {
1226  ERROR("detail (%s): Error opening internal pipe: %s", data->name, fr_syserror(errno));
1227  fr_exit(1);
1228  }
1229 
1230  if (pipe(data->child_pipe) < 0) {
1231  ERROR("detail (%s): Error opening internal pipe: %s", data->name, fr_syserror(errno));
1232  fr_exit(1);
1233  }
1234 
1235  pthread_create(&data->pthread_id, NULL, detail_handler_thread, this);
1236 
1237  this->fd = data->master_pipe[0];
1238 
1239  return 0;
1240 }
1241 #else
1243 {
1244  return 0;
1245 }
1246 #endif /* WITH_DETAIL_THREAD */
1247 #endif /* WITH_DETAIL */
void fr_pair_list_free(VALUE_PAIR **)
Free memory used by a valuepair list.
Definition: pair.c:544
VALUE_PAIR has a single value.
Definition: pair.h:101
time_t running
Definition: detail.h:74
int sockfd
Socket this packet was read from.
Definition: libradius.h:147
off_t offset
Definition: detail.h:71
int id
Packet ID (used to link requests/responses).
Definition: libradius.h:154
struct timeval timestamp
When we received the packet.
Definition: libradius.h:159
#define RERROR(fmt,...)
Definition: log.h:207
uint32_t counter
Definition: detail.h:94
int detail_print(rad_listen_t const *this, char *buffer, size_t bufsize)
Definition: detail.c:962
char const * nas_type
Type of client (arbitrary).
Definition: clients.h:47
int fr_inet_hton(fr_ipaddr_t *out, int af, char const *hostname, bool fallback)
Wrappers for IPv4/IPv6 host to IP address lookup.
Definition: inet.c:127
int rad_accounting(REQUEST *)
Definition: acct.c:38
VALUE_PAIR * fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int vendor, unsigned int attr)
Create a new valuepair.
Definition: pair.c:106
uint32_t fr_rand(void)
Return a 32-bit random number.
Definition: radius.c:1621
VALUE_PAIR * vps
Definition: detail.h:61
fr_ipaddr_t src_ipaddr
Src IP address of packet.
Definition: libradius.h:149
uint8_t prefix
Prefix length - Between 0-32 for IPv4 and 0-128 for IPv6.
Definition: inet.h:47
#define INFO(fmt,...)
Definition: log.h:143
#define debug_pair(vp)
Definition: pair.h:184
static FR_NAME_NUMBER state_names[]
Definition: detail.c:46
#define USEC
Definition: detail.c:44
#define UNUSED
Definition: libradius.h:134
char const * filename
Definition: detail.h:59
VALUE_PAIR * vps
Result of decoding the packet into VALUE_PAIRs.
Definition: libradius.h:162
#define CONF_PARSER_TERMINATOR
Definition: conffile.h:289
char const * secret
Secret PSK.
Definition: clients.h:43
VALUE_PAIR * fr_cursor_init(vp_cursor_t *cursor, VALUE_PAIR *const *node)
Setup a cursor to iterate over attribute pairs.
Definition: cursor.c:60
time_t timestamp
Definition: detail.h:73
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition: snprintf.c:686
rad_listen_t * listener
The listener that received the request.
Definition: radiusd.h:218
#define PW_TYPE_DEPRECATED
If a matching CONF_PAIR is found, error out with a deprecated message.
Definition: conffile.h:199
#define FR_INTEGER_BOUND_CHECK(_name, _var, _op, _bound)
Definition: conffile.h:222
unsigned int number
Monotonically increasing request number. Reset on server restart.
Definition: radiusd.h:213
rad_listen_send_t send
Definition: listen.h:96
uint16_t dst_port
DST Port of packet.
Definition: libradius.h:152
uint16_t src_port
Src port of packet.
Definition: libradius.h:151
fr_ipaddr_t dst_ipaddr
Dst IP address of packet.
Definition: libradius.h:150
fr_ipaddr_t client_ip
Definition: detail.h:75
off_t timestamp_offset
Definition: detail.h:78
Defines a CONF_PAIR to C data type mapping.
Definition: conffile.h:267
Abstraction to allow iterating over different configurations of VALUE_PAIRs.
Definition: pair.h:144
void detail_free(rad_listen_t *this)
Definition: detail.c:914
int detail_decode(UNUSED rad_listen_t *this, UNUSED REQUEST *request)
Definition: detail.c:1027
uint32_t retry_interval
Definition: detail.h:84
int af
Address family.
Definition: inet.h:42
#define rad_assert(expr)
Definition: rad_assert.h:38
RADCLIENT detail_client
Definition: detail.h:96
uint32_t poll_interval
Definition: detail.h:83
int detail_parse(CONF_SECTION *cs, rad_listen_t *this)
Definition: detail.c:1109
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition: log.c:238
VALUE_PAIR * fr_pair_list_copy(TALLOC_CTX *ctx, VALUE_PAIR *from)
Copy a pairlist.
Definition: pair.c:1394
int rad_coa_recv(REQUEST *request)
Definition: listen.c:2045
#define DEBUG(fmt,...)
Definition: log.h:175
int(* RAD_REQUEST_FUNP)(REQUEST *)
Definition: process.h:51
void fr_cursor_merge(vp_cursor_t *cursor, VALUE_PAIR *vp)
Merges multiple VALUE_PAIR into the cursor.
Definition: cursor.c:394
void fr_cursor_insert(vp_cursor_t *cursor, VALUE_PAIR *vp)
Insert a single VALUE_PAIR at the end of the list.
Definition: cursor.c:321
void fr_pair_add(VALUE_PAIR **head, VALUE_PAIR *vp)
Add a VP to the end of the list.
Definition: pair.c:659
#define DEBUG2(fmt,...)
Definition: log.h:176
RFC2866 - Accounting-Request.
Definition: radius.h:95
#define STRINGIFY(x)
Definition: build.h:34
RADIUS_PACKET * fr_radius_alloc(TALLOC_CTX *ctx, bool new_vector)
Allocate a new RADIUS_PACKET.
Definition: radius.c:1651
int cf_section_parse(CONF_SECTION *, void *base, CONF_PARSER const *variables)
Parse a configuration section into user-supplied variables.
Definition: conffile.c:2234
FILE * fp
Definition: detail.h:70
int detail_socket_open(UNUSED CONF_SECTION *cs, UNUSED rad_listen_t *this)
Definition: detail.c:1242
static RADIUS_PACKET * detail_poll(rad_listen_t *listener)
Definition: detail.c:432
union fr_ipaddr_t::@1 ipaddr
unsigned int code
Packet code (type).
Definition: libradius.h:155
fr_ipaddr_t ipaddr
IPv4/IPv6 address of the host.
Definition: clients.h:36
int detail_encode(UNUSED rad_listen_t *this, UNUSED REQUEST *request)
Definition: detail.c:999
Stores an attribute, a value and various bits of other data.
Definition: pair.h:112
RADIUS_PACKET * reply
Outgoing response.
Definition: radiusd.h:225
void void cf_log_err_cs(CONF_SECTION const *cs, char const *fmt,...) CC_HINT(format(printf
struct timeval last_packet
Definition: detail.h:95
A truth value.
Definition: radius.h:56
32 Bit unsigned integer.
Definition: radius.h:34
bool track
Do we track progress through the file?
Definition: detail.h:80
RFC3575/RFC5176 - CoA-Request.
Definition: radius.h:108
static int detail_delay(listen_detail_t *data)
Definition: detail.c:978
int strcasecmp(char *s1, char *s2)
Definition: missing.c:73
static bool cleanup
Definition: radsniff.c:53
int request_receive(TALLOC_CTX *ctx, rad_listen_t *listener, RADIUS_PACKET *packet, RADCLIENT *client, RAD_REQUEST_FUNP fun)
Definition: process.c:1523
Describes a host allowed to send packets to the server.
Definition: clients.h:35
uint8_t data[]
Definition: eap_pwd.h:625
#define TAG_ANY
Definition: pair.h:191
value_type_t type
Type of pointer in value union.
Definition: pair.h:132
char const * filename_work
Definition: detail.h:60
#define PW_TYPE_FILE_OUTPUT
File matching value must exist, and must be writeable.
Definition: conffile.h:205
#define FR_CONF_OFFSET(_n, _t, _s, _f)
Definition: conffile.h:168
VALUE_PAIR * fr_cursor_next(vp_cursor_t *cursor)
Advanced the cursor to the next VALUE_PAIR.
Definition: cursor.c:263
void fr_radius_free(RADIUS_PACKET **)
Free a RADIUS_PACKET.
Definition: radius.c:1727
void radius_signal_self(int flag)
Definition: process.c:5132
int detail_send(rad_listen_t *listener, REQUEST *request)
Definition: detail.c:64
RADIUS_PACKET * packet
Incoming request.
Definition: radiusd.h:221
off_t last_offset
Definition: detail.h:77
char const * name
Identifier used in log messages.
Definition: detail.h:57
bool one_shot
Definition: detail.h:89
#define WARN(fmt,...)
Definition: log.h:144
uint32_t load_factor
Definition: detail.h:82
int detail_recv(rad_listen_t *listener)
Definition: detail.c:316
static const CONF_PARSER detail_config[]
Definition: detail.c:1095
bool check_config
Definition: conffile.c:45
#define PW_TYPE_REQUIRED
Error out if no matching CONF_PAIR is found, and no dflt value is set.
Definition: conffile.h:200
int delay_time
Definition: detail.h:58
VALUE_PAIR * fr_pair_find_by_num(VALUE_PAIR *head, unsigned int vendor, unsigned int attr, int8_t tag)
Find the pair with the matching attribute.
Definition: pair.c:639
char const * longname
Client identifier.
Definition: clients.h:40
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition: strlcpy.c:38
bool done_entry
Are we done reading this entry?
Definition: detail.h:79
char const * fr_int2str(FR_NAME_NUMBER const *table, int number, char const *def)
Definition: token.c:506
int rad_lockfd_nonblock(int fd, int lock_len)
Definition: misc.c:166
void * data
Definition: listen.h:103
detail_state_t state
Definition: detail.h:72
static int detail_open(rad_listen_t *this)
Definition: detail.c:182
FR_TOKEN fr_pair_list_afrom_str(TALLOC_CTX *ctx, char const *buffer, VALUE_PAIR **head)
Read one line of attribute/value pairs into a list.
Definition: pair.c:1261
#define RCSID(id)
Definition: build.h:135
#define RDEBUG(fmt,...)
Definition: log.h:243
#define fr_exit(_x)
Definition: libradius.h:508
#define ERROR(fmt,...)
Definition: log.h:145
char const * cf_section_name2(CONF_SECTION const *cs)
Definition: conffile.c:3601
char const * shortname
Client nickname.
Definition: clients.h:41
#define DEBUG_ENABLED2
True if global debug level 1-2 messages are enabled.
Definition: log.h:170
RFC3575/RFC5176 - Disconnect-Request.
Definition: radius.h:105
#define RDEBUG3(fmt,...)
Definition: log.h:245
size_t strlcat(char *dst, char const *src, size_t siz)
Definition: strlcat.c:40