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