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