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