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