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