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