Remove redundant file from freeradius-abfab list.
[freeradius.git] / src / main / unittest.c
1 /*
2  * unittest.c   Unit test wrapper for the RADIUS daemon.
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-2013  The FreeRADIUS server project
21  * Copyright 2013  Alan DeKok <aland@ox.org>
22  */
23
24 RCSID("$Id$")
25
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <freeradius-devel/state.h>
29 #include <freeradius-devel/rad_assert.h>
30
31 #ifdef HAVE_GETOPT_H
32 #       include <getopt.h>
33 #endif
34
35 #include <ctype.h>
36
37 /*
38  *  Global variables.
39  */
40 char const *progname = NULL;
41 char const *radacct_dir = NULL;
42 char const *radlog_dir = NULL;
43 char const *radlib_dir = NULL;
44 bool log_stripped_names = false;
45
46 static bool memory_report = false;
47 static bool filedone = false;
48
49 char const *radiusd_version = "FreeRADIUS Version " RADIUSD_VERSION_STRING
50 #ifdef RADIUSD_VERSION_COMMIT
51 " (git #" STRINGIFY(RADIUSD_VERSION_COMMIT) ")"
52 #endif
53 ", for host " HOSTINFO ", built on " __DATE__ " at " __TIME__;
54
55 /*
56  *      Static functions.
57  */
58 static void usage(int);
59
60 void listen_free(UNUSED rad_listen_t **head)
61 {
62         /* do nothing */
63 }
64
65
66 static rad_listen_t *listen_alloc(void *ctx)
67 {
68         rad_listen_t *this;
69
70         this = talloc_zero(ctx, rad_listen_t);
71         if (!this) return NULL;
72
73         this->type = RAD_LISTEN_AUTH;
74         this->recv = NULL;
75         this->send = NULL;
76         this->print = NULL;
77         this->encode = NULL;
78         this->decode = NULL;
79
80         /*
81          *      We probably don't care about this.  We can always add
82          *      fields later.
83          */
84         this->data = talloc_zero(this, listen_socket_t);
85         if (!this->data) {
86                 talloc_free(this);
87                 return NULL;
88         }
89
90         return this;
91 }
92
93 static RADCLIENT *client_alloc(void *ctx)
94 {
95         RADCLIENT *client;
96
97         client = talloc_zero(ctx, RADCLIENT);
98         if (!client) return NULL;
99
100         return client;
101 }
102
103 static REQUEST *request_setup(FILE *fp)
104 {
105         VALUE_PAIR      *vp;
106         REQUEST         *request;
107         vp_cursor_t     cursor;
108         struct timeval  now;
109
110         /*
111          *      Create and initialize the new request.
112          */
113         request = request_alloc(NULL);
114         gettimeofday(&now, NULL);
115         request->timestamp = now.tv_sec;
116
117         request->packet = rad_alloc(request, false);
118         if (!request->packet) {
119                 ERROR("No memory");
120                 talloc_free(request);
121                 return NULL;
122         }
123         request->packet->timestamp = now;
124
125         request->reply = rad_alloc(request, false);
126         if (!request->reply) {
127                 ERROR("No memory");
128                 talloc_free(request);
129                 return NULL;
130         }
131
132         request->listener = listen_alloc(request);
133         request->client = client_alloc(request);
134
135         request->number = 0;
136
137         request->master_state = REQUEST_ACTIVE;
138         request->child_state = REQUEST_RUNNING;
139         request->handle = NULL;
140         request->server = talloc_typed_strdup(request, "default");
141
142         request->root = &main_config;
143
144         /*
145          *      Read packet from fp
146          */
147         if (fr_pair_list_afrom_file(request->packet, &request->packet->vps, fp, &filedone) < 0) {
148                 fr_perror("unittest");
149                 talloc_free(request);
150                 return NULL;
151         }
152
153         /*
154          *      Set the defaults for IPs, etc.
155          */
156         request->packet->code = PW_CODE_ACCESS_REQUEST;
157
158         request->packet->src_ipaddr.af = AF_INET;
159         request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_LOOPBACK);
160         request->packet->src_port = 18120;
161
162         request->packet->dst_ipaddr.af = AF_INET;
163         request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_LOOPBACK);
164         request->packet->dst_port = 1812;
165
166         /*
167          *      Copied from radclient
168          *
169          *      Fix up Digest-Attributes issues
170          */
171         for (vp = fr_cursor_init(&cursor, &request->packet->vps);
172              vp;
173              vp = fr_cursor_next(&cursor)) {
174                 /*
175                  *      Double quoted strings get marked up as xlat expansions,
176                  *      but we don't support that here.
177                  */
178                 if (vp->type == VT_XLAT) {
179                         vp->vp_strvalue = vp->value.xlat;
180                         vp->value.xlat = NULL;
181                         vp->type = VT_DATA;
182                 }
183
184                 if (!vp->da->vendor) switch (vp->da->attr) {
185                 default:
186                         break;
187
188                         /*
189                          *      Allow it to set the packet type in
190                          *      the attributes read from the file.
191                          */
192                 case PW_PACKET_TYPE:
193                         request->packet->code = vp->vp_integer;
194                         break;
195
196                 case PW_PACKET_DST_PORT:
197                         request->packet->dst_port = (vp->vp_integer & 0xffff);
198                         break;
199
200                 case PW_PACKET_DST_IP_ADDRESS:
201                         request->packet->dst_ipaddr.af = AF_INET;
202                         request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
203                         request->packet->dst_ipaddr.prefix = 32;
204                         break;
205
206                 case PW_PACKET_DST_IPV6_ADDRESS:
207                         request->packet->dst_ipaddr.af = AF_INET6;
208                         request->packet->dst_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
209                         request->packet->dst_ipaddr.prefix = 128;
210                         break;
211
212                 case PW_PACKET_SRC_PORT:
213                         request->packet->src_port = (vp->vp_integer & 0xffff);
214                         break;
215
216                 case PW_PACKET_SRC_IP_ADDRESS:
217                         request->packet->src_ipaddr.af = AF_INET;
218                         request->packet->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
219                         request->packet->src_ipaddr.prefix = 32;
220                         break;
221
222                 case PW_PACKET_SRC_IPV6_ADDRESS:
223                         request->packet->src_ipaddr.af = AF_INET6;
224                         request->packet->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
225                         request->packet->src_ipaddr.prefix = 128;
226                         break;
227
228                 case PW_CHAP_PASSWORD: {
229                         int i, already_hex = 0;
230
231                         /*
232                          *      If it's 17 octets, it *might* be already encoded.
233                          *      Or, it might just be a 17-character password (maybe UTF-8)
234                          *      Check it for non-printable characters.  The odds of ALL
235                          *      of the characters being 32..255 is (1-7/8)^17, or (1/8)^17,
236                          *      or 1/(2^51), which is pretty much zero.
237                          */
238                         if (vp->vp_length == 17) {
239                                 for (i = 0; i < 17; i++) {
240                                         if (vp->vp_octets[i] < 32) {
241                                                 already_hex = 1;
242                                                 break;
243                                         }
244                                 }
245                         }
246
247                         /*
248                          *      Allow the user to specify ASCII or hex CHAP-Password
249                          */
250                         if (!already_hex) {
251                                 uint8_t *p;
252                                 size_t len, len2;
253
254                                 len = len2 = vp->vp_length;
255                                 if (len2 < 17) len2 = 17;
256
257                                 p = talloc_zero_array(vp, uint8_t, len2);
258
259                                 memcpy(p, vp->vp_strvalue, len);
260
261                                 rad_chap_encode(request->packet,
262                                                 p,
263                                                 fr_rand() & 0xff, vp);
264                                 vp->vp_octets = p;
265                                 vp->vp_length = 17;
266                         }
267                 }
268                         break;
269
270                 case PW_DIGEST_REALM:
271                 case PW_DIGEST_NONCE:
272                 case PW_DIGEST_METHOD:
273                 case PW_DIGEST_URI:
274                 case PW_DIGEST_QOP:
275                 case PW_DIGEST_ALGORITHM:
276                 case PW_DIGEST_BODY_DIGEST:
277                 case PW_DIGEST_CNONCE:
278                 case PW_DIGEST_NONCE_COUNT:
279                 case PW_DIGEST_USER_NAME:
280                         /* overlapping! */
281                 {
282                         DICT_ATTR const *da;
283                         uint8_t *p, *q;
284
285                         p = talloc_array(vp, uint8_t, vp->vp_length + 2);
286
287                         memcpy(p + 2, vp->vp_octets, vp->vp_length);
288                         p[0] = vp->da->attr - PW_DIGEST_REALM + 1;
289                         vp->vp_length += 2;
290                         p[1] = vp->vp_length;
291
292                         da = dict_attrbyvalue(PW_DIGEST_ATTRIBUTES, 0);
293                         rad_assert(da != NULL);
294                         vp->da = da;
295
296                         /*
297                          *      Re-do fr_pair_value_memsteal ourselves,
298                          *      because we play games with
299                          *      vp->da, and fr_pair_value_memsteal goes
300                          *      to GREAT lengths to sanitize
301                          *      and fix and change and
302                          *      double-check the various
303                          *      fields.
304                          */
305                         memcpy(&q, &vp->vp_octets, sizeof(q));
306                         talloc_free(q);
307
308                         vp->vp_octets = talloc_steal(vp, p);
309                         vp->type = VT_DATA;
310
311                         VERIFY_VP(vp);
312                 }
313
314                 break;
315                 }
316         } /* loop over the VP's we read in */
317
318         if (rad_debug_lvl) {
319                 for (vp = fr_cursor_init(&cursor, &request->packet->vps);
320                      vp;
321                      vp = fr_cursor_next(&cursor)) {
322                         /*
323                          *      Take this opportunity to verify all the VALUE_PAIRs are still valid.
324                          */
325                         if (!talloc_get_type(vp, VALUE_PAIR)) {
326                                 ERROR("Expected VALUE_PAIR pointer got \"%s\"", talloc_get_name(vp));
327
328                                 fr_log_talloc_report(vp);
329                                 rad_assert(0);
330                         }
331
332                         vp_print(fr_log_fp, vp);
333                 }
334                 fflush(fr_log_fp);
335         }
336
337         /*
338          *      Build the reply template from the request.
339          */
340         request->reply->sockfd = request->packet->sockfd;
341         request->reply->dst_ipaddr = request->packet->src_ipaddr;
342         request->reply->src_ipaddr = request->packet->dst_ipaddr;
343         request->reply->dst_port = request->packet->src_port;
344         request->reply->src_port = request->packet->dst_port;
345         request->reply->id = request->packet->id;
346         request->reply->code = 0; /* UNKNOWN code */
347         memcpy(request->reply->vector, request->packet->vector,
348                sizeof(request->reply->vector));
349         request->reply->vps = NULL;
350         request->reply->data = NULL;
351         request->reply->data_len = 0;
352
353         /*
354          *      Debugging
355          */
356         request->log.lvl = rad_debug_lvl;
357         request->log.func = vradlog_request;
358
359         request->username = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
360         request->password = fr_pair_find_by_num(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY);
361
362         return request;
363 }
364
365
366 static void print_packet(FILE *fp, RADIUS_PACKET *packet)
367 {
368         VALUE_PAIR *vp;
369         vp_cursor_t cursor;
370
371         if (!packet) {
372                 fprintf(fp, "\n");
373                 return;
374         }
375
376         fprintf(fp, "%s\n", fr_packet_codes[packet->code]);
377
378         for (vp = fr_cursor_init(&cursor, &packet->vps);
379              vp;
380              vp = fr_cursor_next(&cursor)) {
381                 /*
382                  *      Take this opportunity to verify all the VALUE_PAIRs are still valid.
383                  */
384                 if (!talloc_get_type(vp, VALUE_PAIR)) {
385                         ERROR("Expected VALUE_PAIR pointer got \"%s\"", talloc_get_name(vp));
386
387                         fr_log_talloc_report(vp);
388                         rad_assert(0);
389                 }
390
391                 vp_print(fp, vp);
392         }
393         fflush(fp);
394 }
395
396
397 #include <freeradius-devel/modpriv.h>
398
399 /*
400  *      %{poke:sql.foo=bar}
401  */
402 static ssize_t xlat_poke(UNUSED void *instance, REQUEST *request,
403                          char const *fmt, char *out, size_t outlen)
404 {
405         int i;
406         void *data, *base;
407         char *p, *q;
408         module_instance_t *mi;
409         char *buffer;
410         CONF_SECTION *modules;
411         CONF_PAIR *cp;
412         CONF_PARSER const *variables;
413         size_t len;
414
415         rad_assert(outlen > 1);
416         rad_assert(request != NULL);
417         rad_assert(fmt != NULL);
418         rad_assert(out != NULL);
419
420         *out = '\0';
421
422         modules = cf_section_sub_find(request->root->config, "modules");
423         if (!modules) return 0;
424
425         buffer = talloc_strdup(request, fmt);
426         if (!buffer) return 0;
427
428         p = strchr(buffer, '.');
429         if (!p) return 0;
430
431         *(p++) = '\0';
432
433         mi = module_find(modules, buffer);
434         if (!mi) {
435                 RDEBUG("Failed finding module '%s'", buffer);
436         fail:
437                 talloc_free(buffer);
438                 return 0;
439         }
440
441         q = strchr(p, '=');
442         if (!q) {
443                 RDEBUG("Failed finding '=' in string '%s'", fmt);
444                 goto fail;
445         }
446
447         *(q++) = '\0';
448
449         if (strchr(p, '.') != NULL) {
450                 RDEBUG("Can't do sub-sections right now");
451                 goto fail;
452         }
453
454         cp = cf_pair_find(mi->cs, p);
455         if (!cp) {
456                 RDEBUG("No such item '%s'", p);
457                 goto fail;
458         }
459
460         /*
461          *      Copy the old value to the output buffer, that way
462          *      tests can restore it later, if they need to.
463          */
464         len = strlcpy(out, cf_pair_value(cp), outlen);
465
466         if (cf_pair_replace(mi->cs, cp, q) < 0) {
467                 RDEBUG("Failed replacing pair");
468                 goto fail;
469         }
470
471         base = mi->insthandle;
472         variables = mi->entry->module->config;
473
474         /*
475          *      Handle the known configuration parameters.
476          */
477         for (i = 0; variables[i].name != NULL; i++) {
478                 int ret;
479
480                 if (variables[i].type == PW_TYPE_SUBSECTION) continue;
481                 /* else it's a CONF_PAIR */
482
483                 /*
484                  *      Not the pair we want.  Skip it.
485                  */
486                 if (strcmp(variables[i].name, p) != 0) continue;
487
488                 if (variables[i].data) {
489                         data = variables[i].data; /* prefer this. */
490                 } else if (base) {
491                         data = ((char *)base) + variables[i].offset;
492                 } else {
493                         DEBUG2("Internal sanity check 2 failed in cf_section_parse");
494                         goto fail;
495                 }
496
497                 /*
498                  *      Parse the pair we found, or a default value.
499                  */
500                 ret = cf_item_parse(mi->cs, variables[i].name, variables[i].type, data, variables[i].dflt);
501                 if (ret < 0) {
502                         DEBUG2("Failed inserting new value into module instance data");
503                         goto fail;
504                 }
505                 break;          /* we found it, don't do any more */
506         }
507
508         talloc_free(buffer);
509
510         return len;
511 }
512
513
514 /*
515  *      Read a file compose of xlat's and expected results
516  */
517 static bool do_xlats(char const *filename, FILE *fp)
518 {
519         int             lineno = 0;
520         ssize_t         len;
521         char            *p;
522         char            input[8192];
523         char            output[8192];
524         REQUEST         *request;
525         struct timeval  now;
526
527         /*
528          *      Create and initialize the new request.
529          */
530         request = request_alloc(NULL);
531         gettimeofday(&now, NULL);
532         request->timestamp = now.tv_sec;
533
534         request->log.lvl = rad_debug_lvl;
535         request->log.func = vradlog_request;
536
537         output[0] = '\0';
538
539         while (fgets(input, sizeof(input), fp) != NULL) {
540                 lineno++;
541
542                 /*
543                  *      Ignore blank lines and comments
544                  */
545                 p = input;
546                 while (isspace((int) *p)) p++;
547
548                 if (*p < ' ') continue;
549                 if (*p == '#') continue;
550
551                 p = strchr(p, '\n');
552                 if (!p) {
553                         if (!feof(fp)) {
554                                 fprintf(stderr, "Line %d too long in %s\n",
555                                         lineno, filename);
556                                 TALLOC_FREE(request);
557                                 return false;
558                         }
559                 } else {
560                         *p = '\0';
561                 }
562
563                 /*
564                  *      Look for "xlat"
565                  */
566                 if (strncmp(input, "xlat ", 5) == 0) {
567                         ssize_t slen;
568                         char const *error = NULL;
569                         char *fmt = talloc_typed_strdup(NULL, input + 5);
570                         xlat_exp_t *head;
571
572                         slen = xlat_tokenize(fmt, fmt, &head, &error);
573                         if (slen <= 0) {
574                                 talloc_free(fmt);
575                                 snprintf(output, sizeof(output), "ERROR offset %d '%s'", (int) -slen, error);
576                                 continue;
577                         }
578
579                         if (input[slen + 5] != '\0') {
580                                 talloc_free(fmt);
581                                 snprintf(output, sizeof(output), "ERROR offset %d 'Too much text' ::%s::", (int) slen, input + slen + 5);
582                                 continue;
583                         }
584
585                         len = radius_xlat_struct(output, sizeof(output), request, head, NULL, NULL);
586                         if (len < 0) {
587                                 snprintf(output, sizeof(output), "ERROR expanding xlat: %s", fr_strerror());
588                                 continue;
589                         }
590
591                         TALLOC_FREE(fmt); /* also frees 'head' */
592                         continue;
593                 }
594
595                 /*
596                  *      Look for "data".
597                  */
598                 if (strncmp(input, "data ", 5) == 0) {
599                         if (strcmp(input + 5, output) != 0) {
600                                 fprintf(stderr, "Mismatch at line %d of %s\n\tgot      : %s\n\texpected : %s\n",
601                                         lineno, filename, output, input + 5);
602                                 TALLOC_FREE(request);
603                                 return false;
604                         }
605                         continue;
606                 }
607
608                 fprintf(stderr, "Unknown keyword in %s[%d]\n", filename, lineno);
609                 TALLOC_FREE(request);
610                 return false;
611         }
612
613         TALLOC_FREE(request);
614         return true;
615 }
616
617
618 /*
619  *      The main guy.
620  */
621 int main(int argc, char *argv[])
622 {
623         int rcode = EXIT_SUCCESS;
624         int argval;
625         const char *input_file = NULL;
626         const char *output_file = NULL;
627         const char *filter_file = NULL;
628         FILE *fp;
629         REQUEST *request = NULL;
630         VALUE_PAIR *vp;
631         VALUE_PAIR *filter_vps = NULL;
632         bool xlat_only = false;
633         fr_state_t *state = NULL;
634
635         fr_talloc_fault_setup();
636
637         /*
638          *      If the server was built with debugging enabled always install
639          *      the basic fatal signal handlers.
640          */
641 #ifndef NDEBUG
642         if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) {
643                 fr_perror("unittest");
644                 exit(EXIT_FAILURE);
645         }
646 #endif
647
648         if ((progname = strrchr(argv[0], FR_DIR_SEP)) == NULL)
649                 progname = argv[0];
650         else
651                 progname++;
652
653         rad_debug_lvl = 0;
654         set_radius_dir(NULL, RADIUS_DIR);
655
656         /*
657          *      Ensure that the configuration is initialized.
658          */
659         memset(&main_config, 0, sizeof(main_config));
660         main_config.myip.af = AF_UNSPEC;
661         main_config.port = 0;
662         main_config.name = "radiusd";
663
664         /*
665          *      The tests should have only IPs, not host names.
666          */
667         fr_hostname_lookups = false;
668
669         /*
670          *      We always log to stdout.
671          */
672         fr_log_fp = stdout;
673         default_log.dst = L_DST_STDOUT;
674         default_log.fd = STDOUT_FILENO;
675
676         /*  Process the options.  */
677         while ((argval = getopt(argc, argv, "d:D:f:hi:mMn:o:O:xX")) != EOF) {
678
679                 switch (argval) {
680                         case 'd':
681                                 set_radius_dir(NULL, optarg);
682                                 break;
683
684                         case 'D':
685                                 main_config.dictionary_dir = talloc_typed_strdup(NULL, optarg);
686                                 break;
687
688                         case 'f':
689                                 filter_file = optarg;
690                                 break;
691
692                         case 'h':
693                                 usage(0);
694                                 break;
695
696                         case 'i':
697                                 input_file = optarg;
698                                 break;
699
700                         case 'm':
701                                 main_config.debug_memory = true;
702                                 break;
703
704                         case 'M':
705                                 memory_report = true;
706                                 main_config.debug_memory = true;
707                                 break;
708
709                         case 'n':
710                                 main_config.name = optarg;
711                                 break;
712
713                         case 'o':
714                                 output_file = optarg;
715                                 break;
716
717                         case 'O':
718                                 if (strcmp(optarg, "xlat_only") == 0) {
719                                         xlat_only = true;
720                                         break;
721                                 }
722
723                                 fprintf(stderr, "Unknown option '%s'\n", optarg);
724                                 exit(EXIT_FAILURE);
725
726                         case 'X':
727                                 rad_debug_lvl += 2;
728                                 main_config.log_auth = true;
729                                 main_config.log_auth_badpass = true;
730                                 main_config.log_auth_goodpass = true;
731                                 break;
732
733                         case 'x':
734                                 rad_debug_lvl++;
735                                 break;
736
737                         default:
738                                 usage(1);
739                                 break;
740                 }
741         }
742
743         if (rad_debug_lvl) version_print();
744         fr_debug_lvl = rad_debug_lvl;
745
746         /*
747          *      Mismatch between the binary and the libraries it depends on
748          */
749         if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
750                 fr_perror("radiusd");
751                 exit(EXIT_FAILURE);
752         }
753
754         /*
755          *  Initialising OpenSSL once, here, is safer than having individual modules do it.
756          */
757 #ifdef HAVE_OPENSSL_CRYPTO_H
758         tls_global_init();
759 #endif
760
761         if (xlat_register("poke", xlat_poke, NULL, NULL) < 0) {
762                 rcode = EXIT_FAILURE;
763                 goto finish;
764         }
765
766         /*  Read the configuration files, BEFORE doing anything else.  */
767         if (main_config_init() < 0) {
768                 rcode = EXIT_FAILURE;
769                 goto finish;
770         }
771
772         /*
773          *  Load the modules
774          */
775         if (modules_init(main_config.config) < 0) {
776                 rcode = EXIT_FAILURE;
777                 goto finish;
778         }
779
780         state =fr_state_init(NULL);
781
782         /*
783          *  Set the panic action (if required)
784          */
785         {
786                 char const *panic_action = NULL;
787
788                 panic_action = getenv("PANIC_ACTION");
789                 if (!panic_action) panic_action = main_config.panic_action;
790
791                 if (panic_action && (fr_fault_setup(panic_action, argv[0]) < 0)) {
792                         fr_perror("radiusd");
793                         exit(EXIT_FAILURE);
794                 }
795         }
796
797         setlinebuf(stdout); /* unbuffered output */
798
799         if (!input_file || (strcmp(input_file, "-") == 0)) {
800                 fp = stdin;
801         } else {
802                 fp = fopen(input_file, "r");
803                 if (!fp) {
804                         fprintf(stderr, "Failed reading %s: %s\n",
805                                 input_file, fr_syserror(errno));
806                         goto finish;
807                 }
808         }
809
810         /*
811          *      For simplicity, read xlat's.
812          */
813         if (xlat_only) {
814                 if (!do_xlats(input_file, fp)) rcode = EXIT_FAILURE;
815                 if (input_file) fclose(fp);
816                 goto finish;
817         }
818
819         /*
820          *      Grab the VPs from stdin, or from the file.
821          */
822         request = request_setup(fp);
823         if (!request) {
824                 fprintf(stderr, "Failed reading input: %s\n", fr_strerror());
825                 rcode = EXIT_FAILURE;
826                 goto finish;
827         }
828
829         /*
830          *      No filter file, OR there's no more input, OR we're
831          *      reading from a file, and it's different from the
832          *      filter file.
833          */
834         if (!filter_file || filedone ||
835             ((input_file != NULL) && (strcmp(filter_file, input_file) != 0))) {
836                 if (output_file) {
837                         fclose(fp);
838                         fp = NULL;
839                 }
840                 filedone = false;
841         }
842
843         /*
844          *      There is a filter file.  If necessary, open it.  If we
845          *      already are reading it via "input_file", then we don't
846          *      need to re-open it.
847          */
848         if (filter_file) {
849                 if (!fp) {
850                         fp = fopen(filter_file, "r");
851                         if (!fp) {
852                                 fprintf(stderr, "Failed reading %s: %s\n", filter_file, strerror(errno));
853                                 rcode = EXIT_FAILURE;
854                                 goto finish;
855                         }
856                 }
857
858
859                 if (fr_pair_list_afrom_file(request, &filter_vps, fp, &filedone) < 0) {
860                         fprintf(stderr, "Failed reading attributes from %s: %s\n",
861                                 filter_file, fr_strerror());
862                         rcode = EXIT_FAILURE;
863                         goto finish;
864                 }
865
866                 /*
867                  *      FIXME: loop over input packets.
868                  */
869                 fclose(fp);
870         }
871
872         rad_virtual_server(request);
873
874         if (!output_file || (strcmp(output_file, "-") == 0)) {
875                 fp = stdout;
876         } else {
877                 fp = fopen(output_file, "w");
878                 if (!fp) {
879                         fprintf(stderr, "Failed writing %s: %s\n",
880                                 output_file, fr_syserror(errno));
881                         exit(EXIT_FAILURE);
882                 }
883         }
884
885         print_packet(fp, request->reply);
886
887         if (output_file) fclose(fp);
888
889         /*
890          *      Update the list with the response type.
891          */
892         vp = radius_pair_create(request->reply, &request->reply->vps,
893                                PW_RESPONSE_PACKET_TYPE, 0);
894         vp->vp_integer = request->reply->code;
895
896         {
897                 VALUE_PAIR const *failed[2];
898
899                 if (filter_vps && !fr_pair_validate(failed, filter_vps, request->reply->vps)) {
900                         fr_pair_validate_debug(request, failed);
901                         fr_perror("Output file %s does not match attributes in filter %s (%s)",
902                                   output_file ? output_file : input_file, filter_file, fr_strerror());
903                         rcode = EXIT_FAILURE;
904                         goto finish;
905                 }
906         }
907
908         INFO("Exiting normally");
909
910 finish:
911         talloc_free(request);
912
913         /*
914          *      Detach any modules.
915          */
916         modules_free();
917
918         xlat_unregister("poke", xlat_poke, NULL);
919
920         xlat_free();            /* modules may have xlat's */
921
922         fr_state_delete(state);
923
924         /*
925          *      Free the configuration items.
926          */
927         main_config_free();
928
929         if (memory_report) {
930                 INFO("Allocated memory at time of report:");
931                 fr_log_talloc_report(NULL);
932         }
933
934         return rcode;
935 }
936
937
938 /*
939  *  Display the syntax for starting this program.
940  */
941 static void NEVER_RETURNS usage(int status)
942 {
943         FILE *output = status?stderr:stdout;
944
945         fprintf(output, "Usage: %s [options]\n", progname);
946         fprintf(output, "Options:\n");
947         fprintf(output, "  -d raddb_dir  Configuration files are in \"raddb_dir/*\".\n");
948         fprintf(output, "  -D dict_dir   Dictionary files are in \"dict_dir/*\".\n");
949         fprintf(output, "  -f file       Filter reply against attributes in 'file'.\n");
950         fprintf(output, "  -h            Print this help message.\n");
951         fprintf(output, "  -i file       File containing request attributes.\n");
952         fprintf(output, "  -m            On SIGINT or SIGQUIT exit cleanly instead of immediately.\n");
953         fprintf(output, "  -n name       Read raddb/name.conf instead of raddb/radiusd.conf.\n");
954         fprintf(output, "  -X            Turn on full debugging.\n");
955         fprintf(output, "  -x            Turn on additional debugging. (-xx gives more debugging).\n");
956         exit(status);
957 }