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