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