Enable -D, and document it in -h
[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 mainconfig;
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, port;
193         fr_ipaddr_t ipaddr;
194         char *p, buffer[1024];
195
196         strlcpy(buffer, server, sizeof(buffer));
197
198         p = strchr(buffer, ':');
199         if (!p) {
200                 port = PW_RADMIN_PORT;
201         } else {
202                 port = atoi(p + 1);
203                 *p = '\0';
204         }
205
206         if (ip_hton(buffer, AF_INET, &ipaddr) < 0) {
207                 fprintf(stderr, "%s: Failed looking up host %s: %s\n",
208                         progname, buffer, fr_syserror(errno));
209                 exit(1);
210         }
211
212         sockfd = fr_tcp_client_socket(NULL, &ipaddr, port);
213         if (sockfd < 0) {
214                 fprintf(stderr, "%s: Failed opening socket %s: %s\n",
215                         progname, server, fr_syserror(errno));
216                 exit(1);
217         }
218
219         return sockfd;
220 }
221
222 static void do_challenge(int sockfd)
223 {
224         size_t total;
225         ssize_t r;
226         uint8_t challenge[16];
227
228         for (total = 0; total < sizeof(challenge); ) {
229                 r = read(sockfd, challenge + total, sizeof(challenge) - total);
230                 if (r == 0) exit(1);
231
232                 if (r < 0) {
233 #ifdef ECONNRESET
234                         if (errno == ECONNRESET) {
235                                 fprintf(stderr, "%s: Connection reset",
236                                         progname);
237                                 exit(1);
238                         }
239 #endif
240                         if (errno == EINTR) continue;
241
242                         fprintf(stderr, "%s: Failed reading data: %s\n",
243                                 progname, fr_syserror(errno));
244                         exit(1);
245                 }
246                 total += r;
247                 fflush(stdout);
248         }
249
250         fr_hmac_md5((uint8_t const *) secret, strlen(secret),
251                     challenge, sizeof(challenge), challenge);
252
253         if (write(sockfd, challenge, sizeof(challenge)) < 0) {
254                 fprintf(stderr, "%s: Failed writing challenge data: %s\n",
255                         progname, fr_syserror(errno));
256         }
257 }
258
259 static ssize_t run_command(int sockfd, char const *command,
260                            char *buffer, size_t bufsize)
261 {
262         char *p;
263         ssize_t size, len;
264
265         if (echo) {
266                 fprintf(outputfp, "%s\n", command);
267         }
268
269         /*
270          *      Write the text to the socket.
271          */
272         if (write(sockfd, command, strlen(command)) < 0) return -1;
273         if (write(sockfd, "\r\n", 2) < 0) return -1;
274
275         /*
276          *      Read the response
277          */
278         size = 0;
279         buffer[0] = '\0';
280
281         memset(buffer, 0, bufsize);
282
283         while (1) {
284                 int rcode;
285                 fd_set readfds;
286
287                 FD_ZERO(&readfds);
288                 FD_SET(sockfd, &readfds);
289
290                 rcode = select(sockfd + 1, &readfds, NULL, NULL, NULL);
291                 if (rcode < 0) {
292                         if (errno == EINTR) continue;
293
294                         fprintf(stderr, "%s: Failed selecting: %s\n",
295                                 progname, fr_syserror(errno));
296                         exit(1);
297                 }
298
299                 if (rcode == 0) {
300                         fprintf(stderr, "%s: Server closed the connection.\n",
301                                 progname);
302                         exit(1);
303                 }
304
305 #ifdef MSG_DONTWAIT
306                 len = recv(sockfd, buffer + size,
307                            bufsize - size - 1, MSG_DONTWAIT);
308 #else
309                 /*
310                  *      Read one byte at a time (ugh)
311                  */
312                 len = recv(sockfd, buffer + size, 1, 0);
313 #endif
314                 if (len < 0) {
315                         /*
316                          *      No data: keep looping
317                          */
318                         if ((errno == EAGAIN) || (errno == EINTR)) {
319                                 continue;
320                         }
321
322                         fprintf(stderr, "%s: Error reading socket: %s\n",
323                                 progname, fr_syserror(errno));
324                         exit(1);
325                 }
326                 if (len == 0) return 0; /* clean exit */
327
328                 size += len;
329                 buffer[size] = '\0';
330
331                 /*
332                  *      There really is a better way of doing this.
333                  */
334                 p = strstr(buffer, "radmin> ");
335                 if (p &&
336                     ((p == buffer) ||
337                      (p[-1] == '\n') ||
338                      (p[-1] == '\r'))) {
339                         *p = '\0';
340
341                         if (p[-1] == '\n') p[-1] = '\0';
342                         break;
343                 }
344         }
345
346         /*
347          *      Blank prompt.  Go get another command.
348          */
349         if (!buffer[0]) return 1;
350
351         buffer[size] = '\0'; /* this is at least right */
352
353         return 2;
354 }
355
356 #define MAX_COMMANDS (4)
357
358 int main(int argc, char **argv)
359 {
360         int             argval;
361         bool            quiet = false;
362         bool            done_license = false;
363         int             sockfd;
364         uint32_t        magic, needed;
365         char            *line = NULL;
366         ssize_t         len, size;
367         char const      *file = NULL;
368         char const      *name = "radiusd";
369         char            *p, buffer[65536];
370         char const      *input_file = NULL;
371         FILE            *inputfp = stdin;
372         char const      *output_file = NULL;
373         char const      *server = NULL;
374
375         char const      *radius_dir = RADIUS_DIR;
376         char const      *dict_dir = DICTDIR;
377
378         char *commands[MAX_COMMANDS];
379         int num_commands = -1;
380
381 #ifndef NDEBUG
382         fr_fault_setup(getenv("PANIC_ACTION"), argv[0]);
383 #endif
384
385         talloc_set_log_stderr();
386
387         outputfp = stdout;      /* stdout is not a constant value... */
388
389         if ((progname = strrchr(argv[0], FR_DIR_SEP)) == NULL) {
390                 progname = argv[0];
391         } else {
392                 progname++;
393         }
394
395         while ((argval = getopt(argc, argv, "d:D:hi:e:Ef:n:o:qs:S")) != EOF) {
396                 switch(argval) {
397                 case 'd':
398                         if (file) {
399                                 fprintf(stderr, "%s: -d and -f cannot be used together.\n", progname);
400                                 exit(1);
401                         }
402                         if (server) {
403                                 fprintf(stderr, "%s: -d and -s cannot be used together.\n", progname);
404                                 exit(1);
405                         }
406                         radius_dir = optarg;
407                         break;
408
409                 case 'D':
410                         dict_dir = optarg;
411                         break;
412
413                 case 'e':
414                         num_commands++; /* starts at -1 */
415                         if (num_commands >= MAX_COMMANDS) {
416                                 fprintf(stderr, "%s: Too many '-e'\n",
417                                         progname);
418                                 exit(1);
419                         }
420                         commands[num_commands] = optarg;
421                         break;
422
423                 case 'E':
424                         echo = true;
425                         break;
426
427                 case 'f':
428                         radius_dir = NULL;
429                         file = optarg;
430                         break;
431
432                 default:
433                 case 'h':
434                         usage(0);
435                         break;
436
437                 case 'i':
438                         if (strcmp(optarg, "-") != 0) {
439                                 input_file = optarg;
440                         }
441                         quiet = true;
442                         break;
443
444                 case 'n':
445                         name = optarg;
446                         break;
447
448                 case 'o':
449                         if (strcmp(optarg, "-") != 0) {
450                                 output_file = optarg;
451                         }
452                         quiet = true;
453                         break;
454
455                 case 'q':
456                         quiet = true;
457                         break;
458
459                 case 's':
460                         if (file) {
461                                 fprintf(stderr, "%s: -s and -f cannot be used together.\n", progname);
462                                 usage(1);
463                         }
464                         radius_dir = NULL;
465                         server = optarg;
466                         break;
467
468                 case 'S':
469                         secret = NULL;
470                         break;
471                 }
472         }
473
474         /*
475          *      Mismatch between the binary and the libraries it depends on
476          */
477         if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
478                 fr_perror("radmin");
479                 exit(1);
480         }
481
482         if (radius_dir) {
483                 int rcode;
484                 CONF_SECTION *cs, *subcs;
485
486                 file = NULL;    /* MUST read it from the conffile now */
487
488                 snprintf(buffer, sizeof(buffer), "%s/%s.conf", radius_dir, name);
489
490                 /*
491                  *      Need to read in the dictionaries, else we may get
492                  *      validation errors when we try and parse the config.
493                  */
494                 if (dict_init(dict_dir, RADIUS_DICTIONARY) < 0) {
495                         fr_perror("radmin");
496                         exit(64);
497                 }
498
499                 if (dict_read(radius_dir, RADIUS_DICTIONARY) == -1) {
500                         fr_perror("radmin");
501                         exit(64);
502                 }
503
504                 cs = cf_file_read(buffer);
505                 if (!cs) {
506                         fprintf(stderr, "%s: Errors reading or parsing %s\n", progname, buffer);
507                         usage(1);
508                 }
509
510                 subcs = NULL;
511                 while ((subcs = cf_subsection_find_next(cs, subcs, "listen")) != NULL) {
512                         char const *value;
513                         CONF_PAIR *cp = cf_pair_find(subcs, "type");
514
515                         if (!cp) continue;
516
517                         value = cf_pair_value(cp);
518                         if (!value) continue;
519
520                         if (strcmp(value, "control") != 0) continue;
521
522                         /*
523                          *      Now find the socket name (sigh)
524                          */
525                         rcode = cf_item_parse(subcs, "socket",
526                                               PW_TYPE_STRING_PTR,
527                                               &file, NULL);
528                         if (rcode < 0) {
529                                 fprintf(stderr, "%s: Failed parsing listen section\n", progname);
530                                 exit(1);
531                         }
532
533                         if (!file) {
534                                 fprintf(stderr, "%s: No path given for socket\n", progname);
535                                 usage(1);
536                         }
537                         break;
538                 }
539
540                 if (!file) {
541                         fprintf(stderr, "%s: Could not find control socket in %s\n", progname, buffer);
542                         exit(1);
543                 }
544         }
545
546         if (input_file) {
547                 inputfp = fopen(input_file, "r");
548                 if (!inputfp) {
549                         fprintf(stderr, "%s: Failed opening %s: %s\n", progname, input_file, fr_syserror(errno));
550                         exit(1);
551                 }
552         }
553
554         if (output_file) {
555                 outputfp = fopen(output_file, "w");
556                 if (!outputfp) {
557                         fprintf(stderr, "%s: Failed creating %s: %s\n", progname, output_file, fr_syserror(errno));
558                         exit(1);
559                 }
560         }
561
562         if (!file && !server) {
563                 fprintf(stderr, "%s: Must use one of '-d' or '-f' or '-s'\n",
564                         progname);
565                 exit(1);
566         }
567
568         /*
569          *      Check if stdin is a TTY only if input is from stdin
570          */
571         if (input_file && !quiet && !isatty(STDIN_FILENO)) quiet = true;
572
573 #ifdef USE_READLINE
574         if (!quiet) {
575 #ifdef USE_READLINE_HISTORY
576                 using_history();
577 #endif
578                 rl_bind_key('\t', rl_insert);
579         }
580 #endif
581
582  reconnect:
583         if (file) {
584                 /*
585                  *      FIXME: Get destination from command line, if possible?
586                  */
587                 sockfd = fr_domain_socket(file);
588                 if (sockfd < 0) {
589                         exit(1);
590                 }
591         } else {
592                 sockfd = client_socket(server);
593         }
594
595         /*
596          *      Read initial magic && version information.
597          */
598         for (size = 0; size < 8; size += len) {
599                 len = read(sockfd, buffer + size, 8 - size);
600                 if (len < 0) {
601                         fprintf(stderr, "%s: Error reading initial data from socket: %s\n",
602                                 progname, fr_syserror(errno));
603                         exit(1);
604                 }
605         }
606
607         memcpy(&magic, buffer, 4);
608         magic = ntohl(magic);
609         if (magic != 0xf7eead15) {
610                 fprintf(stderr, "%s: Socket %s is not FreeRADIUS administration socket\n", progname, file);
611                 exit(1);
612         }
613
614         memcpy(&magic, buffer + 4, 4);
615         magic = ntohl(magic);
616
617         if (!server) {
618                 needed = 1;
619         } else {
620                 needed = 2;
621         }
622
623         if (magic != needed) {
624                 fprintf(stderr, "%s: Socket version mismatch: Need %d, got %d\n",
625                         progname, needed, magic);
626                 exit(1);
627         }
628
629         if (server && secret) do_challenge(sockfd);
630
631         /*
632          *      Run one command.
633          */
634         if (num_commands >= 0) {
635                 int i;
636
637                 for (i = 0; i <= num_commands; i++) {
638                         size = run_command(sockfd, commands[i],
639                                            buffer, sizeof(buffer));
640                         if (size < 0) exit(1);
641
642                         if (buffer[0]) {
643                                 fputs(buffer, outputfp);
644                                 fprintf(outputfp, "\n");
645                                 fflush(outputfp);
646                         }
647                 }
648                 exit(0);
649         }
650
651         if (!done_license && !quiet) {
652                 printf("%s - FreeRADIUS Server administration tool.\n", radmin_version);
653                 printf("Copyright (C) 2008-2014 The FreeRADIUS server project and contributors.\n");
654                 printf("There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n");
655                 printf("PARTICULAR PURPOSE.\n");
656                 printf("You may redistribute copies of FreeRADIUS under the terms of the\n");
657                 printf("GNU General Public License v2.\n");
658
659                 done_license = true;
660         }
661
662         /*
663          *      FIXME: Do login?
664          */
665
666         while (1) {
667 #ifndef USE_READLINE
668                 if (!quiet) {
669                         printf("radmin> ");
670                         fflush(stdout);
671                 }
672 #else
673                 if (!quiet) {
674                         line = readline("radmin> ");
675
676                         if (!line) break;
677
678                         if (!*line) {
679                                 free(line);
680                                 continue;
681                         }
682
683 #ifdef USE_READLINE_HISTORY
684                         add_history(line);
685 #endif
686                 } else          /* quiet, or no readline */
687 #endif
688                 {
689                         line = fgets(buffer, sizeof(buffer), inputfp);
690                         if (!line) break;
691
692                         p = strchr(buffer, '\n');
693                         if (!p) {
694                                 fprintf(stderr, "%s: Input line too long\n",
695                                         progname);
696                                 exit(1);
697                         }
698
699                         *p = '\0';
700
701                         /*
702                          *      Strip off leading spaces.
703                          */
704                         for (p = line; *p != '\0'; p++) {
705                                 if ((p[0] == ' ') ||
706                                     (p[0] == '\t')) {
707                                         line = p + 1;
708                                         continue;
709                                 }
710
711                                 if (p[0] == '#') {
712                                         line = NULL;
713                                         break;
714                                 }
715
716                                 break;
717                         }
718
719                         /*
720                          *      Comments: keep going.
721                          */
722                         if (!line) continue;
723
724                         /*
725                          *      Strip off CR / LF
726                          */
727                         for (p = line; *p != '\0'; p++) {
728                                 if ((p[0] == '\r') ||
729                                     (p[0] == '\n')) {
730                                         p[0] = '\0';
731                                         break;
732                                 }
733                         }
734                 }
735
736                 if (strcmp(line, "reconnect") == 0) {
737                         close(sockfd);
738                         line = NULL;
739                         goto reconnect;
740                 }
741
742                 if (memcmp(line, "secret ", 7) == 0) {
743                         if (!secret) {
744                                 secret = line + 7;
745                                 do_challenge(sockfd);
746                         }
747                         line = NULL;
748                         continue;
749                 }
750
751                 /*
752                  *      Exit, done, etc.
753                  */
754                 if ((strcmp(line, "exit") == 0) ||
755                     (strcmp(line, "quit") == 0)) {
756                         break;
757                 }
758
759                 if (server && !secret) {
760                         fprintf(stderr, "ERROR: You must enter 'secret <SECRET>' before running any commands\n");
761                         line = NULL;
762                         continue;
763                 }
764
765                 size = run_command(sockfd, line, buffer, sizeof(buffer));
766                 if (size <= 0) break; /* error, or clean exit */
767
768                 if (size == 1) continue; /* no output. */
769
770                 fputs(buffer, outputfp);
771                 fflush(outputfp);
772                 fprintf(outputfp, "\n");
773         }
774
775         fprintf(outputfp, "\n");
776
777         return 0;
778 }
779