2 * radmin.c RADIUS Administration tool.
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.
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.
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
20 * Copyright 2012 The FreeRADIUS server project
21 * Copyright 2012 Alan DeKok <aland@deployingradius.com>
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/md5.h>
32 # define SUN_LEN(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
40 #ifdef HAVE_SYS_STAT_H
41 # include <sys/stat.h>
44 #ifdef HAVE_LIBREADLINE
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) */
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)
60 # define USE_READLINE_HISTORY (1)
61 #endif /* defined(HAVE_READLINE_HISTORY_H) */
63 #endif /* HAVE_READLINE_HISTORY */
65 #endif /* HAVE_LIBREADLINE */
68 * For configuration file stuff.
70 char const *progname = "radmin";
71 static char const *radmin_version = "radmin version " RADIUSD_VERSION_STRING
72 #ifdef RADIUSD_VERSION_COMMIT
73 " (git #" STRINGIFY(RADIUSD_VERSION_COMMIT) ")"
75 ", built on " __DATE__ " at " __TIME__;
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".
83 log_lvl_t debug_flag = 0;
84 struct main_config_t main_config;
86 bool check_config = false;
88 static FILE *outputfp = NULL;
89 static bool echo = false;
90 static char const *secret = "testing123";
99 pid_t rad_waitpid(pid_t pid, int *status)
101 return waitpid(pid, status, 0);
105 static void NEVER_RETURNS usage(int status)
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");
123 static int fr_domain_socket(char const *path)
129 struct sockaddr_un saremote;
132 if (len >= sizeof(saremote.sun_path)) {
133 fprintf(stderr, "%s: Path too long in filename\n", progname);
137 if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
138 fprintf(stderr, "%s: Failed creating socket: %s\n",
139 progname, fr_syserror(errno));
143 saremote.sun_family = AF_UNIX;
144 memcpy(saremote.sun_path, path, len + 1); /* SUN_LEN does strlen */
146 socklen = SUN_LEN(&saremote);
148 if (connect(sockfd, (struct sockaddr *)&saremote, socklen) < 0) {
152 fprintf(stderr, "%s: Failed connecting to %s: %s\n",
153 progname, path, fr_syserror(errno));
156 * The file doesn't exist. Tell the user how to
159 if ((stat(path, &buf) < 0) &&
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");
171 if ((flags = fcntl(sockfd, F_GETFL, NULL)) < 0) {
172 fprintf(stderr, "%s: Failure getting socket flags: %s",
173 progname, fr_syserror(errno));
179 if( fcntl(sockfd, F_SETFL, flags) < 0) {
180 fprintf(stderr, "%s: Failure setting socket flags: %s",
181 progname, fr_syserror(errno));
191 static int client_socket(char const *server)
196 char *p, buffer[1024];
198 strlcpy(buffer, server, sizeof(buffer));
200 p = strchr(buffer, ':');
202 port = PW_RADMIN_PORT;
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));
214 sockfd = fr_tcp_client_socket(NULL, &ipaddr, port);
216 fprintf(stderr, "%s: Failed opening socket %s: %s\n",
217 progname, server, fr_syserror(errno));
224 static void do_challenge(int sockfd)
228 uint8_t challenge[16];
230 for (total = 0; total < sizeof(challenge); ) {
231 r = read(sockfd, challenge + total, sizeof(challenge) - total);
236 if (errno == ECONNRESET) {
237 fprintf(stderr, "%s: Connection reset",
242 if (errno == EINTR) continue;
244 fprintf(stderr, "%s: Failed reading data: %s\n",
245 progname, fr_syserror(errno));
252 fr_hmac_md5(challenge, (uint8_t const *) secret, strlen(secret),
253 challenge, sizeof(challenge));
255 if (write(sockfd, challenge, sizeof(challenge)) < 0) {
256 fprintf(stderr, "%s: Failed writing challenge data: %s\n",
257 progname, fr_syserror(errno));
261 static ssize_t run_command(int sockfd, char const *command,
262 char *buffer, size_t bufsize)
268 fprintf(outputfp, "%s\n", command);
272 * Write the text to the socket.
274 if (write(sockfd, command, strlen(command)) < 0) return -1;
275 if (write(sockfd, "\r\n", 2) < 0) return -1;
283 memset(buffer, 0, bufsize);
290 FD_SET(sockfd, &readfds);
292 rcode = select(sockfd + 1, &readfds, NULL, NULL, NULL);
294 if (errno == EINTR) continue;
296 fprintf(stderr, "%s: Failed selecting: %s\n",
297 progname, fr_syserror(errno));
302 fprintf(stderr, "%s: Server closed the connection.\n",
308 len = recv(sockfd, buffer + size,
309 bufsize - size - 1, MSG_DONTWAIT);
312 * Read one byte at a time (ugh)
314 len = recv(sockfd, buffer + size, 1, 0);
318 * No data: keep looping
320 if ((errno == EAGAIN) || (errno == EINTR)) {
324 fprintf(stderr, "%s: Error reading socket: %s\n",
325 progname, fr_syserror(errno));
328 if (len == 0) return 0; /* clean exit */
334 * There really is a better way of doing this.
336 p = strstr(buffer, "radmin> ");
343 if (p[-1] == '\n') p[-1] = '\0';
349 * Blank prompt. Go get another command.
351 if (!buffer[0]) return 1;
353 buffer[size] = '\0'; /* this is at least right */
358 static int do_connect(int *out, char const *file, char const *server)
364 uint32_t magic, needed;
367 * Close stale file descriptors
376 * FIXME: Get destination from command line, if possible?
378 sockfd = fr_domain_socket(file);
379 if (sockfd < 0) return -1;
381 sockfd = client_socket(server);
385 * Only works for BSD, but Linux allows us
386 * to mask SIGPIPE, so that's fine.
392 setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
397 * Read initial magic && version information.
399 for (size = 0; size < 8; size += len) {
400 len = read(sockfd, buffer + size, 8 - size);
402 fprintf(stderr, "%s: Error reading initial data from socket: %s\n",
403 progname, fr_syserror(errno));
409 memcpy(&magic, buffer, 4);
410 magic = ntohl(magic);
411 if (magic != 0xf7eead15) {
412 fprintf(stderr, "%s: Socket %s is not FreeRADIUS administration socket\n", progname, file);
417 memcpy(&magic, buffer + 4, 4);
418 magic = ntohl(magic);
426 if (magic != needed) {
427 fprintf(stderr, "%s: Socket version mismatch: Need %d, got %d\n",
428 progname, needed, magic);
433 if (server && secret) do_challenge(sockfd);
440 #define MAX_COMMANDS (4)
442 int main(int argc, char **argv)
449 char const *file = NULL;
450 char const *name = "radiusd";
451 char *p, buffer[65536];
452 char const *input_file = NULL;
453 FILE *inputfp = stdin;
454 char const *output_file = NULL;
455 char const *server = NULL;
457 char const *radius_dir = RADIUS_DIR;
458 char const *dict_dir = DICTDIR;
460 char *commands[MAX_COMMANDS];
461 int num_commands = -1;
464 if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) {
470 talloc_set_log_stderr();
472 outputfp = stdout; /* stdout is not a constant value... */
474 if ((progname = strrchr(argv[0], FR_DIR_SEP)) == NULL) {
480 while ((argval = getopt(argc, argv, "d:D:hi:e:Ef:n:o:qs:S")) != EOF) {
484 fprintf(stderr, "%s: -d and -f cannot be used together.\n", progname);
488 fprintf(stderr, "%s: -d and -s cannot be used together.\n", progname);
499 num_commands++; /* starts at -1 */
500 if (num_commands >= MAX_COMMANDS) {
501 fprintf(stderr, "%s: Too many '-e'\n",
505 commands[num_commands] = optarg;
519 usage(0); /* never returns */
522 if (strcmp(optarg, "-") != 0) {
533 if (strcmp(optarg, "-") != 0) {
534 output_file = optarg;
545 fprintf(stderr, "%s: -s and -f cannot be used together.\n", progname);
559 * Mismatch between the binary and the libraries it depends on
561 if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
568 CONF_SECTION *cs, *subcs;
570 file = NULL; /* MUST read it from the conffile now */
572 snprintf(buffer, sizeof(buffer), "%s/%s.conf", radius_dir, name);
575 * Need to read in the dictionaries, else we may get
576 * validation errors when we try and parse the config.
578 if (dict_init(dict_dir, RADIUS_DICTIONARY) < 0) {
583 if (dict_read(radius_dir, RADIUS_DICTIONARY) == -1) {
588 cs = cf_section_alloc(NULL, "main", NULL);
591 if (cf_file_read(cs, buffer) < 0) {
592 fprintf(stderr, "%s: Errors reading or parsing %s\n", progname, buffer);
597 while ((subcs = cf_subsection_find_next(cs, subcs, "listen")) != NULL) {
599 CONF_PAIR *cp = cf_pair_find(subcs, "type");
603 value = cf_pair_value(cp);
604 if (!value) continue;
606 if (strcmp(value, "control") != 0) continue;
609 * Now find the socket name (sigh)
611 rcode = cf_item_parse(subcs, "socket", FR_ITEM_POINTER(PW_TYPE_STRING, &file), NULL);
613 fprintf(stderr, "%s: Failed parsing listen section\n", progname);
618 fprintf(stderr, "%s: No path given for socket\n", progname);
625 fprintf(stderr, "%s: Could not find control socket in %s\n", progname, buffer);
631 inputfp = fopen(input_file, "r");
633 fprintf(stderr, "%s: Failed opening %s: %s\n", progname, input_file, fr_syserror(errno));
639 outputfp = fopen(output_file, "w");
641 fprintf(stderr, "%s: Failed creating %s: %s\n", progname, output_file, fr_syserror(errno));
646 if (!file && !server) {
647 fprintf(stderr, "%s: Must use one of '-d' or '-f' or '-s'\n",
653 * Check if stdin is a TTY only if input is from stdin
655 if (input_file && !quiet && !isatty(STDIN_FILENO)) quiet = true;
659 #ifdef USE_READLINE_HISTORY
662 rl_bind_key('\t', rl_insert);
667 * Prevent SIGPIPEs from terminating the process
669 signal(SIGPIPE, SIG_IGN);
671 if (do_connect(&sockfd, file, server) < 0) exit(1);
676 if (num_commands >= 0) {
679 for (i = 0; i <= num_commands; i++) {
680 len = run_command(sockfd, commands[i], buffer, sizeof(buffer));
681 if (len < 0) exit(1);
684 fputs(buffer, outputfp);
685 fprintf(outputfp, "\n");
693 printf("%s - FreeRADIUS Server administration tool.\n", radmin_version);
694 printf("Copyright (C) 2008-2015 The FreeRADIUS server project and contributors.\n");
695 printf("There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n");
696 printf("PARTICULAR PURPOSE.\n");
697 printf("You may redistribute copies of FreeRADIUS under the terms of the\n");
698 printf("GNU General Public License v2.\n");
713 line = readline("radmin> ");
722 #ifdef USE_READLINE_HISTORY
725 } else /* quiet, or no readline */
728 line = fgets(buffer, sizeof(buffer), inputfp);
731 p = strchr(buffer, '\n');
733 fprintf(stderr, "%s: Input line too long\n",
741 * Strip off leading spaces.
743 for (p = line; *p != '\0'; p++) {
759 * Comments: keep going.
766 for (p = line; *p != '\0'; p++) {
767 if ((p[0] == '\r') ||
775 if (strcmp(line, "reconnect") == 0) {
776 if (do_connect(&sockfd, file, server) < 0) exit(1);
781 if (memcmp(line, "secret ", 7) == 0) {
784 do_challenge(sockfd);
793 if ((strcmp(line, "exit") == 0) ||
794 (strcmp(line, "quit") == 0)) {
798 if (server && !secret) {
799 fprintf(stderr, "ERROR: You must enter 'secret <SECRET>' before running any commands\n");
804 len = run_command(sockfd, line, buffer, sizeof(buffer));
805 if ((len < 0) && (do_connect(&sockfd, file, server) < 0)) {
806 fprintf(stderr, "Reconnecting...");
808 } else if (len == 0) break;
809 else if (len == 1) continue; /* no output. */
811 fputs(buffer, outputfp);
813 fprintf(outputfp, "\n");
816 fprintf(outputfp, "\n");