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