Add compile time checking for config pointers
[freeradius.git] / src / main / radmin.c
1 /*
2  * radmin.c     RADIUS Administration tool.
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 2012   The FreeRADIUS server project
21  * Copyright 2012   Alan DeKok <aland@deployingradius.com>
22  */
23
24 RCSID("$Id$")
25
26 #include <freeradius-devel/radiusd.h>
27
28 #ifdef HAVE_SYS_UN_H
29 #include <sys/un.h>
30 #ifndef SUN_LEN
31 #define SUN_LEN(su)  (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
32 #endif
33 #endif
34
35 #ifdef HAVE_GETOPT_H
36 #include <getopt.h>
37 #endif
38
39 #ifdef HAVE_SYS_STAT_H
40 #include <sys/stat.h>
41 #endif
42
43 #ifdef HAVE_LIBREADLINE
44
45 #if defined(HAVE_READLINE_READLINE_H)
46 #include <readline/readline.h>
47 #define USE_READLINE (1)
48 #elif defined(HAVE_READLINE_H)
49 #include <readline.h>
50 #define USE_READLINE (1)
51 #endif /* !defined(HAVE_READLINE_H) */
52
53 #ifdef HAVE_READLINE_HISTORY
54 #if defined(HAVE_READLINE_HISTORY_H)
55 #include <readline/history.h>
56 #define USE_READLINE_HISTORY (1)
57 #elif defined(HAVE_HISTORY_H)
58 #include <history.h>
59 #define USE_READLINE_HISTORY (1)
60 #endif /* defined(HAVE_READLINE_HISTORY_H) */
61
62 #endif /* HAVE_READLINE_HISTORY */
63
64 #endif /* HAVE_LIBREADLINE */
65
66 /*
67  *      For configuration file stuff.
68  */
69 char const *progname = "radmin";
70 char const *radmin_version = "radmin version " RADIUSD_VERSION_STRING
71 #ifdef RADIUSD_VERSION_COMMIT
72 " (git #" STRINGIFY(RADIUSD_VERSION_COMMIT) ")"
73 #endif
74 ", built on " __DATE__ " at " __TIME__;
75
76
77 /*
78  *      The rest of this is because the conffile.c, etc. assume
79  *      they're running inside of the server.  And we don't (yet)
80  *      have a "libfreeradius-server", or "libfreeradius-util".
81  */
82 log_debug_t debug_flag = 0;
83 struct main_config_t main_config;
84
85 bool check_config = false;
86
87 static FILE *outputfp = NULL;
88 static bool echo = false;
89 static char const *secret = "testing123";
90
91 #include <sys/wait.h>
92 pid_t rad_fork(void)
93 {
94         return fork();
95 }
96
97 #ifdef HAVE_PTHREAD_H
98 pid_t rad_waitpid(pid_t pid, int *status)
99 {
100         return waitpid(pid, status, 0);
101 }
102 #endif
103
104 static void NEVER_RETURNS usage(int status)
105 {
106         FILE *output = status ? stderr : stdout;
107         fprintf(output, "Usage: %s [ args ]\n", progname);
108         fprintf(output, "  -d raddb_dir    Configuration files are in \"raddbdir/*\".\n");
109         fprintf(stderr, "  -D <dictdir>    Set main dictionary directory (defaults to " DICTDIR ").\n");
110         fprintf(output, "  -e command      Execute 'command' and then exit.\n");
111         fprintf(output, "  -E              Echo commands as they are being executed.\n");
112         fprintf(output, "  -f socket_file  Open socket_file directly, without reading radius.conf\n");
113         fprintf(output, "  -h              Print usage help information.\n");
114         fprintf(output, "  -i input_file   Read commands from 'input_file'.\n");
115         fprintf(output, "  -n name         Read raddb/name.conf instead of raddb/radiusd.conf\n");
116         fprintf(output, "  -o output_file  Write commands to 'output_file'.\n");
117         fprintf(output, "  -q              Quiet mode.\n");
118
119         exit(status);
120 }
121
122 static int fr_domain_socket(char const *path)
123 {
124         int sockfd = -1;
125 #ifdef HAVE_SYS_UN_H
126         size_t len;
127         socklen_t socklen;
128         struct sockaddr_un saremote;
129
130         len = strlen(path);
131         if (len >= sizeof(saremote.sun_path)) {
132                 fprintf(stderr, "%s: Path too long in filename\n", progname);
133                 return -1;
134         }
135
136         if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
137                 fprintf(stderr, "%s: Failed creating socket: %s\n",
138                         progname, fr_syserror(errno));
139                 return -1;
140         }
141
142         saremote.sun_family = AF_UNIX;
143         memcpy(saremote.sun_path, path, len + 1); /* SUN_LEN does strlen */
144
145         socklen = SUN_LEN(&saremote);
146
147         if (connect(sockfd, (struct sockaddr *)&saremote, socklen) < 0) {
148                 struct stat buf;
149
150                 close(sockfd);
151                 fprintf(stderr, "%s: Failed connecting to %s: %s\n",
152                         progname, path, fr_syserror(errno));
153
154                 /*
155                  *      The file doesn't exist.  Tell the user how to
156                  *      fix it.
157                  */
158                 if ((stat(path, &buf) < 0) &&
159                     (errno == ENOENT)) {
160                         fprintf(stderr, "  Perhaps you need to run the commands:\n\tcd /etc/raddb\n\tln -s sites-available/control-socket sites-enabled/control-socket\n  and then re-start the server?\n");
161                 }
162
163                 return -1;
164         }
165
166 #ifdef O_NONBLOCK
167         {
168                 int flags;
169
170                 if ((flags = fcntl(sockfd, F_GETFL, NULL)) < 0)  {
171                         fprintf(stderr, "%s: Failure getting socket flags: %s",
172                                 progname, fr_syserror(errno));
173                         close(sockfd);
174                         return -1;
175                 }
176
177                 flags |= O_NONBLOCK;
178                 if( fcntl(sockfd, F_SETFL, flags) < 0) {
179                         fprintf(stderr, "%s: Failure setting socket flags: %s",
180                                 progname, fr_syserror(errno));
181                         close(sockfd);
182                         return -1;
183                 }
184         }
185 #endif
186 #endif
187         return sockfd;
188 }
189
190 static int client_socket(char const *server)
191 {
192         int sockfd;
193         uint16_t port;
194         fr_ipaddr_t ipaddr;
195         char *p, buffer[1024];
196
197         strlcpy(buffer, server, sizeof(buffer));
198
199         p = strchr(buffer, ':');
200         if (!p) {
201                 port = PW_RADMIN_PORT;
202         } else {
203                 port = atoi(p + 1);
204                 *p = '\0';
205         }
206
207         if (ip_hton(buffer, AF_INET, &ipaddr) < 0) {
208                 fprintf(stderr, "%s: Failed looking up host %s: %s\n",
209                         progname, buffer, fr_syserror(errno));
210                 exit(1);
211         }
212
213         sockfd = fr_tcp_client_socket(NULL, &ipaddr, port);
214         if (sockfd < 0) {
215                 fprintf(stderr, "%s: Failed opening socket %s: %s\n",
216                         progname, server, fr_syserror(errno));
217                 exit(1);
218         }
219
220         return sockfd;
221 }
222
223 static void do_challenge(int sockfd)
224 {
225         size_t total;
226         ssize_t r;
227         uint8_t challenge[16];
228
229         for (total = 0; total < sizeof(challenge); ) {
230                 r = read(sockfd, challenge + total, sizeof(challenge) - total);
231                 if (r == 0) exit(1);
232
233                 if (r < 0) {
234 #ifdef ECONNRESET
235                         if (errno == ECONNRESET) {
236                                 fprintf(stderr, "%s: Connection reset",
237                                         progname);
238                                 exit(1);
239                         }
240 #endif
241                         if (errno == EINTR) continue;
242
243                         fprintf(stderr, "%s: Failed reading data: %s\n",
244                                 progname, fr_syserror(errno));
245                         exit(1);
246                 }
247                 total += r;
248                 fflush(stdout);
249         }
250
251         fr_hmac_md5((uint8_t const *) secret, strlen(secret),
252                     challenge, sizeof(challenge), challenge);
253
254         if (write(sockfd, challenge, sizeof(challenge)) < 0) {
255                 fprintf(stderr, "%s: Failed writing challenge data: %s\n",
256                         progname, fr_syserror(errno));
257         }
258 }
259
260 static ssize_t run_command(int sockfd, char const *command,
261                            char *buffer, size_t bufsize)
262 {
263         char *p;
264         ssize_t size, len;
265
266         if (echo) {
267                 fprintf(outputfp, "%s\n", command);
268         }
269
270         /*
271          *      Write the text to the socket.
272          */
273         if (write(sockfd, command, strlen(command)) < 0) return -1;
274         if (write(sockfd, "\r\n", 2) < 0) return -1;
275
276         /*
277          *      Read the response
278          */
279         size = 0;
280         buffer[0] = '\0';
281
282         memset(buffer, 0, bufsize);
283
284         while (1) {
285                 int rcode;
286                 fd_set readfds;
287
288                 FD_ZERO(&readfds);
289                 FD_SET(sockfd, &readfds);
290
291                 rcode = select(sockfd + 1, &readfds, NULL, NULL, NULL);
292                 if (rcode < 0) {
293                         if (errno == EINTR) continue;
294
295                         fprintf(stderr, "%s: Failed selecting: %s\n",
296                                 progname, fr_syserror(errno));
297                         exit(1);
298                 }
299
300                 if (rcode == 0) {
301                         fprintf(stderr, "%s: Server closed the connection.\n",
302                                 progname);
303                         exit(1);
304                 }
305
306 #ifdef MSG_DONTWAIT
307                 len = recv(sockfd, buffer + size,
308                            bufsize - size - 1, MSG_DONTWAIT);
309 #else
310                 /*
311                  *      Read one byte at a time (ugh)
312                  */
313                 len = recv(sockfd, buffer + size, 1, 0);
314 #endif
315                 if (len < 0) {
316                         /*
317                          *      No data: keep looping
318                          */
319                         if ((errno == EAGAIN) || (errno == EINTR)) {
320                                 continue;
321                         }
322
323                         fprintf(stderr, "%s: Error reading socket: %s\n",
324                                 progname, fr_syserror(errno));
325                         exit(1);
326                 }
327                 if (len == 0) return 0; /* clean exit */
328
329                 size += len;
330                 buffer[size] = '\0';
331
332                 /*
333                  *      There really is a better way of doing this.
334                  */
335                 p = strstr(buffer, "radmin> ");
336                 if (p &&
337                     ((p == buffer) ||
338                      (p[-1] == '\n') ||
339                      (p[-1] == '\r'))) {
340                         *p = '\0';
341
342                         if (p[-1] == '\n') p[-1] = '\0';
343                         break;
344                 }
345         }
346
347         /*
348          *      Blank prompt.  Go get another command.
349          */
350         if (!buffer[0]) return 1;
351
352         buffer[size] = '\0'; /* this is at least right */
353
354         return 2;
355 }
356
357 #define MAX_COMMANDS (4)
358
359 int main(int argc, char **argv)
360 {
361         int             argval;
362         bool            quiet = false;
363         bool            done_license = false;
364         int             sockfd;
365         uint32_t        magic, needed;
366         char            *line = NULL;
367         ssize_t         len, size;
368         char const      *file = NULL;
369         char const      *name = "radiusd";
370         char            *p, buffer[65536];
371         char const      *input_file = NULL;
372         FILE            *inputfp = stdin;
373         char const      *output_file = NULL;
374         char const      *server = NULL;
375
376         char const      *radius_dir = RADIUS_DIR;
377         char const      *dict_dir = DICTDIR;
378
379         char *commands[MAX_COMMANDS];
380         int num_commands = -1;
381
382 #ifndef NDEBUG
383         if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) {
384                 fr_perror("radmin");
385                 exit(EXIT_FAILURE);
386         }
387 #endif
388
389         talloc_set_log_stderr();
390
391         outputfp = stdout;      /* stdout is not a constant value... */
392
393         if ((progname = strrchr(argv[0], FR_DIR_SEP)) == NULL) {
394                 progname = argv[0];
395         } else {
396                 progname++;
397         }
398
399         while ((argval = getopt(argc, argv, "d:D:hi:e:Ef:n:o:qs:S")) != EOF) {
400                 switch(argval) {
401                 case 'd':
402                         if (file) {
403                                 fprintf(stderr, "%s: -d and -f cannot be used together.\n", progname);
404                                 exit(1);
405                         }
406                         if (server) {
407                                 fprintf(stderr, "%s: -d and -s cannot be used together.\n", progname);
408                                 exit(1);
409                         }
410                         radius_dir = optarg;
411                         break;
412
413                 case 'D':
414                         dict_dir = optarg;
415                         break;
416
417                 case 'e':
418                         num_commands++; /* starts at -1 */
419                         if (num_commands >= MAX_COMMANDS) {
420                                 fprintf(stderr, "%s: Too many '-e'\n",
421                                         progname);
422                                 exit(1);
423                         }
424                         commands[num_commands] = optarg;
425                         break;
426
427                 case 'E':
428                         echo = true;
429                         break;
430
431                 case 'f':
432                         radius_dir = NULL;
433                         file = optarg;
434                         break;
435
436                 default:
437                 case 'h':
438                         usage(0);
439                         break;
440
441                 case 'i':
442                         if (strcmp(optarg, "-") != 0) {
443                                 input_file = optarg;
444                         }
445                         quiet = true;
446                         break;
447
448                 case 'n':
449                         name = optarg;
450                         break;
451
452                 case 'o':
453                         if (strcmp(optarg, "-") != 0) {
454                                 output_file = optarg;
455                         }
456                         quiet = true;
457                         break;
458
459                 case 'q':
460                         quiet = true;
461                         break;
462
463                 case 's':
464                         if (file) {
465                                 fprintf(stderr, "%s: -s and -f cannot be used together.\n", progname);
466                                 usage(1);
467                         }
468                         radius_dir = NULL;
469                         server = optarg;
470                         break;
471
472                 case 'S':
473                         secret = NULL;
474                         break;
475                 }
476         }
477
478         /*
479          *      Mismatch between the binary and the libraries it depends on
480          */
481         if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
482                 fr_perror("radmin");
483                 exit(1);
484         }
485
486         if (radius_dir) {
487                 int rcode;
488                 CONF_SECTION *cs, *subcs;
489
490                 file = NULL;    /* MUST read it from the conffile now */
491
492                 snprintf(buffer, sizeof(buffer), "%s/%s.conf", radius_dir, name);
493
494                 /*
495                  *      Need to read in the dictionaries, else we may get
496                  *      validation errors when we try and parse the config.
497                  */
498                 if (dict_init(dict_dir, RADIUS_DICTIONARY) < 0) {
499                         fr_perror("radmin");
500                         exit(64);
501                 }
502
503                 if (dict_read(radius_dir, RADIUS_DICTIONARY) == -1) {
504                         fr_perror("radmin");
505                         exit(64);
506                 }
507
508                 cs = cf_file_read(buffer);
509                 if (!cs) {
510                         fprintf(stderr, "%s: Errors reading or parsing %s\n", progname, buffer);
511                         usage(1);
512                 }
513
514                 subcs = NULL;
515                 while ((subcs = cf_subsection_find_next(cs, subcs, "listen")) != NULL) {
516                         char const *value;
517                         CONF_PAIR *cp = cf_pair_find(subcs, "type");
518
519                         if (!cp) continue;
520
521                         value = cf_pair_value(cp);
522                         if (!value) continue;
523
524                         if (strcmp(value, "control") != 0) continue;
525
526                         /*
527                          *      Now find the socket name (sigh)
528                          */
529                         rcode = cf_item_parse(subcs, "socket", FR_ITEM_POINTER(PW_TYPE_STRING, &file), NULL);
530                         if (rcode < 0) {
531                                 fprintf(stderr, "%s: Failed parsing listen section\n", progname);
532                                 exit(1);
533                         }
534
535                         if (!file) {
536                                 fprintf(stderr, "%s: No path given for socket\n", progname);
537                                 usage(1);
538                         }
539                         break;
540                 }
541
542                 if (!file) {
543                         fprintf(stderr, "%s: Could not find control socket in %s\n", progname, buffer);
544                         exit(1);
545                 }
546         }
547
548         if (input_file) {
549                 inputfp = fopen(input_file, "r");
550                 if (!inputfp) {
551                         fprintf(stderr, "%s: Failed opening %s: %s\n", progname, input_file, fr_syserror(errno));
552                         exit(1);
553                 }
554         }
555
556         if (output_file) {
557                 outputfp = fopen(output_file, "w");
558                 if (!outputfp) {
559                         fprintf(stderr, "%s: Failed creating %s: %s\n", progname, output_file, fr_syserror(errno));
560                         exit(1);
561                 }
562         }
563
564         if (!file && !server) {
565                 fprintf(stderr, "%s: Must use one of '-d' or '-f' or '-s'\n",
566                         progname);
567                 exit(1);
568         }
569
570         /*
571          *      Check if stdin is a TTY only if input is from stdin
572          */
573         if (input_file && !quiet && !isatty(STDIN_FILENO)) quiet = true;
574
575 #ifdef USE_READLINE
576         if (!quiet) {
577 #ifdef USE_READLINE_HISTORY
578                 using_history();
579 #endif
580                 rl_bind_key('\t', rl_insert);
581         }
582 #endif
583
584  reconnect:
585         if (file) {
586                 /*
587                  *      FIXME: Get destination from command line, if possible?
588                  */
589                 sockfd = fr_domain_socket(file);
590                 if (sockfd < 0) {
591                         exit(1);
592                 }
593         } else {
594                 sockfd = client_socket(server);
595         }
596
597         /*
598          *      Read initial magic && version information.
599          */
600         for (size = 0; size < 8; size += len) {
601                 len = read(sockfd, buffer + size, 8 - size);
602                 if (len < 0) {
603                         fprintf(stderr, "%s: Error reading initial data from socket: %s\n",
604                                 progname, fr_syserror(errno));
605                         exit(1);
606                 }
607         }
608
609         memcpy(&magic, buffer, 4);
610         magic = ntohl(magic);
611         if (magic != 0xf7eead15) {
612                 fprintf(stderr, "%s: Socket %s is not FreeRADIUS administration socket\n", progname, file);
613                 exit(1);
614         }
615
616         memcpy(&magic, buffer + 4, 4);
617         magic = ntohl(magic);
618
619         if (!server) {
620                 needed = 1;
621         } else {
622                 needed = 2;
623         }
624
625         if (magic != needed) {
626                 fprintf(stderr, "%s: Socket version mismatch: Need %d, got %d\n",
627                         progname, needed, magic);
628                 exit(1);
629         }
630
631         if (server && secret) do_challenge(sockfd);
632
633         /*
634          *      Run one command.
635          */
636         if (num_commands >= 0) {
637                 int i;
638
639                 for (i = 0; i <= num_commands; i++) {
640                         size = run_command(sockfd, commands[i],
641                                            buffer, sizeof(buffer));
642                         if (size < 0) exit(1);
643
644                         if (buffer[0]) {
645                                 fputs(buffer, outputfp);
646                                 fprintf(outputfp, "\n");
647                                 fflush(outputfp);
648                         }
649                 }
650                 exit(0);
651         }
652
653         if (!done_license && !quiet) {
654                 printf("%s - FreeRADIUS Server administration tool.\n", radmin_version);
655                 printf("Copyright (C) 2008-2014 The FreeRADIUS server project and contributors.\n");
656                 printf("There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n");
657                 printf("PARTICULAR PURPOSE.\n");
658                 printf("You may redistribute copies of FreeRADIUS under the terms of the\n");
659                 printf("GNU General Public License v2.\n");
660
661                 done_license = true;
662         }
663
664         /*
665          *      FIXME: Do login?
666          */
667
668         while (1) {
669 #ifndef USE_READLINE
670                 if (!quiet) {
671                         printf("radmin> ");
672                         fflush(stdout);
673                 }
674 #else
675                 if (!quiet) {
676                         line = readline("radmin> ");
677
678                         if (!line) break;
679
680                         if (!*line) {
681                                 free(line);
682                                 continue;
683                         }
684
685 #ifdef USE_READLINE_HISTORY
686                         add_history(line);
687 #endif
688                 } else          /* quiet, or no readline */
689 #endif
690                 {
691                         line = fgets(buffer, sizeof(buffer), inputfp);
692                         if (!line) break;
693
694                         p = strchr(buffer, '\n');
695                         if (!p) {
696                                 fprintf(stderr, "%s: Input line too long\n",
697                                         progname);
698                                 exit(1);
699                         }
700
701                         *p = '\0';
702
703                         /*
704                          *      Strip off leading spaces.
705                          */
706                         for (p = line; *p != '\0'; p++) {
707                                 if ((p[0] == ' ') ||
708                                     (p[0] == '\t')) {
709                                         line = p + 1;
710                                         continue;
711                                 }
712
713                                 if (p[0] == '#') {
714                                         line = NULL;
715                                         break;
716                                 }
717
718                                 break;
719                         }
720
721                         /*
722                          *      Comments: keep going.
723                          */
724                         if (!line) continue;
725
726                         /*
727                          *      Strip off CR / LF
728                          */
729                         for (p = line; *p != '\0'; p++) {
730                                 if ((p[0] == '\r') ||
731                                     (p[0] == '\n')) {
732                                         p[0] = '\0';
733                                         break;
734                                 }
735                         }
736                 }
737
738                 if (strcmp(line, "reconnect") == 0) {
739                         close(sockfd);
740                         line = NULL;
741                         goto reconnect;
742                 }
743
744                 if (memcmp(line, "secret ", 7) == 0) {
745                         if (!secret) {
746                                 secret = line + 7;
747                                 do_challenge(sockfd);
748                         }
749                         line = NULL;
750                         continue;
751                 }
752
753                 /*
754                  *      Exit, done, etc.
755                  */
756                 if ((strcmp(line, "exit") == 0) ||
757                     (strcmp(line, "quit") == 0)) {
758                         break;
759                 }
760
761                 if (server && !secret) {
762                         fprintf(stderr, "ERROR: You must enter 'secret <SECRET>' before running any commands\n");
763                         line = NULL;
764                         continue;
765                 }
766
767                 size = run_command(sockfd, line, buffer, sizeof(buffer));
768                 if (size <= 0) break; /* error, or clean exit */
769
770                 if (size == 1) continue; /* no output. */
771
772                 fputs(buffer, outputfp);
773                 fflush(outputfp);
774                 fprintf(outputfp, "\n");
775         }
776
777         fprintf(outputfp, "\n");
778
779         return 0;
780 }
781