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>
28 #include <freeradius-devel/channel.h>
36 # define SUN_LEN(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
44 #ifdef HAVE_SYS_STAT_H
45 # include <sys/stat.h>
48 #ifdef HAVE_LIBREADLINE
50 #if defined(HAVE_READLINE_READLINE_H)
51 # include <readline/readline.h>
52 # define USE_READLINE (1)
53 #elif defined(HAVE_READLINE_H)
54 # include <readline.h>
55 # define USE_READLINE (1)
56 #endif /* !defined(HAVE_READLINE_H) */
58 #ifdef HAVE_READLINE_HISTORY
59 # if defined(HAVE_READLINE_HISTORY_H)
60 # include <readline/history.h>
61 # define USE_READLINE_HISTORY (1)
62 # elif defined(HAVE_HISTORY_H)
64 # define USE_READLINE_HISTORY (1)
65 #endif /* defined(HAVE_READLINE_HISTORY_H) */
67 #endif /* HAVE_READLINE_HISTORY */
69 #endif /* HAVE_LIBREADLINE */
72 * For configuration file stuff.
74 char const *progname = "radmin";
75 static char const *radmin_version = "radmin version " RADIUSD_VERSION_STRING
76 #ifdef RADIUSD_VERSION_COMMIT
77 " (git #" STRINGIFY(RADIUSD_VERSION_COMMIT) ")"
79 ", built on " __DATE__ " at " __TIME__;
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".
87 log_lvl_t debug_flag = 0;
88 struct main_config_t main_config;
90 bool check_config = false;
92 static bool echo = false;
93 static char const *secret = "testing123";
101 #ifdef HAVE_PTHREAD_H
102 pid_t rad_waitpid(pid_t pid, int *status)
104 return waitpid(pid, status, 0);
108 static void NEVER_RETURNS usage(int status)
110 FILE *output = status ? stderr : stdout;
111 fprintf(output, "Usage: %s [ args ]\n", progname);
112 fprintf(output, " -d raddb_dir Configuration files are in \"raddbdir/*\".\n");
113 fprintf(stderr, " -D <dictdir> Set main dictionary directory (defaults to " DICTDIR ").\n");
114 fprintf(output, " -e command Execute 'command' and then exit.\n");
115 fprintf(output, " -E Echo commands as they are being executed.\n");
116 fprintf(output, " -f socket_file Open socket_file directly, without reading radius.conf\n");
117 fprintf(output, " -h Print usage help information.\n");
118 fprintf(output, " -i input_file Read commands from 'input_file'.\n");
119 fprintf(output, " -n name Read raddb/name.conf instead of raddb/radiusd.conf\n");
120 fprintf(output, " -q Quiet mode.\n");
125 static int fr_domain_socket(char const *path)
131 struct sockaddr_un saremote;
134 if (len >= sizeof(saremote.sun_path)) {
135 fprintf(stderr, "%s: Path too long in filename\n", progname);
139 if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
140 fprintf(stderr, "%s: Failed creating socket: %s\n",
141 progname, fr_syserror(errno));
145 saremote.sun_family = AF_UNIX;
146 memcpy(saremote.sun_path, path, len + 1); /* SUN_LEN does strlen */
148 socklen = SUN_LEN(&saremote);
150 if (connect(sockfd, (struct sockaddr *)&saremote, socklen) < 0) {
154 fprintf(stderr, "%s: Failed connecting to %s: %s\n",
155 progname, path, fr_syserror(errno));
158 * The file doesn't exist. Tell the user how to
161 if ((stat(path, &buf) < 0) &&
163 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");
172 static int client_socket(char const *server)
177 char *p, buffer[1024];
179 strlcpy(buffer, server, sizeof(buffer));
181 p = strchr(buffer, ':');
183 port = PW_RADMIN_PORT;
189 if (ip_hton(&ipaddr, AF_INET, buffer, false) < 0) {
190 fprintf(stderr, "%s: Failed looking up host %s: %s\n",
191 progname, buffer, fr_syserror(errno));
195 sockfd = fr_tcp_client_socket(NULL, &ipaddr, port);
197 fprintf(stderr, "%s: Failed opening socket %s: %s\n",
198 progname, server, fr_syserror(errno));
205 static ssize_t do_challenge(int sockfd)
208 fr_channel_type_t channel;
209 uint8_t challenge[16];
214 * When connecting over a socket, the server challenges us.
216 r = fr_channel_read(sockfd, &channel, challenge, sizeof(challenge));
217 if (r <= 0) return r;
219 if ((r != 16) || (channel != FR_CHANNEL_AUTH_CHALLENGE)) {
220 fprintf(stderr, "%s: Failed to read challenge.\n",
225 fr_hmac_md5(challenge, (uint8_t const *) secret, strlen(secret),
226 challenge, sizeof(challenge));
228 r = fr_channel_write(sockfd, FR_CHANNEL_AUTH_RESPONSE, challenge, sizeof(challenge));
229 if (r <= 0) return r;
232 * If the server doesn't like us, he just closes the
233 * socket. So we don't look for an ACK.
241 * Returns -1 on error. 0 on connection failed. +1 on OK.
243 static ssize_t run_command(int sockfd, char const *command,
244 char *buffer, size_t bufsize)
248 fr_channel_type_t channel;
251 fprintf(stdout, "%s\n", command);
255 * Write the text to the socket.
257 r = fr_channel_write(sockfd, FR_CHANNEL_STDIN, command, strlen(command));
258 if (r <= 0) return r;
261 r = fr_channel_read(sockfd, &channel, buffer, bufsize - 1);
262 if (r <= 0) return r;
264 buffer[r] = '\0'; /* for C strings */
267 case FR_CHANNEL_STDOUT:
268 fprintf(stdout, "%s", buffer);
271 case FR_CHANNEL_STDERR:
272 fprintf(stderr, "ERROR: %s", buffer);
275 case FR_CHANNEL_CMD_STATUS:
278 memcpy(&status, buffer, sizeof(status));
279 status = ntohl(status);
280 // set the status from the data
284 fprintf(stderr, "Unexpected response\n");
289 /* never gets here */
292 static int do_connect(int *out, char const *file, char const *server)
296 fr_channel_type_t channel;
302 * Close stale file descriptors
311 * FIXME: Get destination from command line, if possible?
313 sockfd = fr_domain_socket(file);
314 if (sockfd < 0) return -1;
316 sockfd = client_socket(server);
320 * Only works for BSD, but Linux allows us
321 * to mask SIGPIPE, so that's fine.
327 setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
332 * Set up the initial header data.
335 magic = htonl(magic);
336 memcpy(buffer, &magic, sizeof(magic));
337 memset(buffer + sizeof(magic), 0, sizeof(magic));
339 r = fr_channel_write(sockfd, FR_CHANNEL_INIT_ACK, buffer, 8);
342 fprintf(stderr, "%s: Error in socket: %s\n",
343 progname, fr_syserror(errno));
348 r = fr_channel_read(sockfd, &channel, buffer + 8, 8);
349 if (r <= 0) goto do_close;
351 if ((r != 8) || (channel != FR_CHANNEL_INIT_ACK) ||
352 (memcmp(buffer, buffer + 8, 8) != 0)) {
353 fprintf(stderr, "%s: Incompatible versions\n", progname);
358 if (server && secret) {
359 r = do_challenge(sockfd);
360 if (r <= 0) goto do_close;
368 #define MAX_COMMANDS (4)
370 int main(int argc, char **argv)
377 char const *file = NULL;
378 char const *name = "radiusd";
379 char *p, buffer[65536];
380 char const *input_file = NULL;
381 FILE *inputfp = stdin;
382 char const *server = NULL;
384 char const *radius_dir = RADIUS_DIR;
385 char const *dict_dir = DICTDIR;
387 char *commands[MAX_COMMANDS];
388 int num_commands = -1;
390 int exit_status = EXIT_SUCCESS;
393 if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) {
399 talloc_set_log_stderr();
401 if ((progname = strrchr(argv[0], FR_DIR_SEP)) == NULL) {
407 while ((argval = getopt(argc, argv, "d:D:hi:e:Ef:n:qs:S")) != EOF) {
411 fprintf(stderr, "%s: -d and -f cannot be used together.\n", progname);
415 fprintf(stderr, "%s: -d and -s cannot be used together.\n", progname);
426 num_commands++; /* starts at -1 */
427 if (num_commands >= MAX_COMMANDS) {
428 fprintf(stderr, "%s: Too many '-e'\n",
433 commands[num_commands] = optarg;
447 usage(0); /* never returns */
450 if (strcmp(optarg, "-") != 0) {
466 fprintf(stderr, "%s: -s and -f cannot be used together.\n", progname);
480 * Mismatch between the binary and the libraries it depends on
482 if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
489 CONF_SECTION *cs, *subcs;
492 char const *uid_name = NULL;
493 char const *gid_name = NULL;
497 file = NULL; /* MUST read it from the conffile now */
499 snprintf(buffer, sizeof(buffer), "%s/%s.conf", radius_dir, name);
502 * Need to read in the dictionaries, else we may get
503 * validation errors when we try and parse the config.
505 if (dict_init(dict_dir, RADIUS_DICTIONARY) < 0) {
510 if (dict_read(radius_dir, RADIUS_DICTIONARY) == -1) {
515 cs = cf_section_alloc(NULL, "main", NULL);
518 if (cf_file_read(cs, buffer) < 0) {
519 fprintf(stderr, "%s: Errors reading or parsing %s\n", progname, buffer);
527 while ((subcs = cf_subsection_find_next(cs, subcs, "listen")) != NULL) {
529 CONF_PAIR *cp = cf_pair_find(subcs, "type");
533 value = cf_pair_value(cp);
534 if (!value) continue;
536 if (strcmp(value, "control") != 0) continue;
539 * Now find the socket name (sigh)
541 rcode = cf_item_parse(subcs, "socket", FR_ITEM_POINTER(PW_TYPE_STRING, &file), NULL);
543 fprintf(stderr, "%s: Failed parsing listen section 'socket'\n", progname);
548 fprintf(stderr, "%s: No path given for socket\n", progname);
553 * If we're root, just use the first one we gind
560 rcode = cf_item_parse(subcs, "uid", FR_ITEM_POINTER(PW_TYPE_STRING, &uid_name), NULL);
562 fprintf(stderr, "%s: Failed parsing listen section 'uid'\n", progname);
566 if (!uid_name) break;
568 pwd = getpwnam(uid_name);
570 fprintf(stderr, "%s: Failed getting UID for user %s: %s\n", progname, uid_name, strerror(errno));
574 if (uid != pwd->pw_uid) continue;
576 rcode = cf_item_parse(subcs, "gid", FR_ITEM_POINTER(PW_TYPE_STRING, &gid_name), NULL);
578 fprintf(stderr, "%s: Failed parsing listen section 'gid'\n", progname);
582 if (!gid_name) break;
584 grp = getgrnam(gid_name);
586 fprintf(stderr, "%s: Failed getting GID for group %s: %s\n", progname, gid_name, strerror(errno));
590 if (gid != grp->gr_gid) continue;
596 fprintf(stderr, "%s: Could not find control socket in %s\n", progname, buffer);
602 inputfp = fopen(input_file, "r");
604 fprintf(stderr, "%s: Failed opening %s: %s\n", progname, input_file, fr_syserror(errno));
609 if (!file && !server) {
610 fprintf(stderr, "%s: Must use one of '-d' or '-f' or '-s'\n",
616 * Check if stdin is a TTY only if input is from stdin
618 if (input_file && !quiet && !isatty(STDIN_FILENO)) quiet = true;
622 #ifdef USE_READLINE_HISTORY
625 rl_bind_key('\t', rl_insert);
630 * Prevent SIGPIPEs from terminating the process
632 signal(SIGPIPE, SIG_IGN);
634 if (do_connect(&sockfd, file, server) < 0) exit(1);
639 if (num_commands >= 0) {
642 for (i = 0; i <= num_commands; i++) {
643 len = run_command(sockfd, commands[i], buffer, sizeof(buffer));
644 if (len < 0) exit(1);
646 if (len == 1) exit_status = EXIT_FAILURE;
652 printf("%s - FreeRADIUS Server administration tool.\n", radmin_version);
653 printf("Copyright (C) 2008-2015 The FreeRADIUS server project and contributors.\n");
654 printf("There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n");
655 printf("PARTICULAR PURPOSE.\n");
656 printf("You may redistribute copies of FreeRADIUS under the terms of the\n");
657 printf("GNU General Public License v2.\n");
672 line = readline("radmin> ");
681 #ifdef USE_READLINE_HISTORY
684 } else /* quiet, or no readline */
687 line = fgets(buffer, sizeof(buffer), inputfp);
690 p = strchr(buffer, '\n');
692 fprintf(stderr, "%s: Input line too long\n",
700 * Strip off leading spaces.
702 for (p = line; *p != '\0'; p++) {
718 * Comments: keep going.
725 for (p = line; *p != '\0'; p++) {
726 if ((p[0] == '\r') ||
734 if (strcmp(line, "reconnect") == 0) {
735 if (do_connect(&sockfd, file, server) < 0) exit(1);
740 if (memcmp(line, "secret ", 7) == 0) {
743 do_challenge(sockfd);
752 if ((strcmp(line, "exit") == 0) ||
753 (strcmp(line, "quit") == 0)) {
757 if (server && !secret) {
758 fprintf(stderr, "ERROR: You must enter 'secret <SECRET>' before running any commands\n");
763 len = run_command(sockfd, line, buffer, sizeof(buffer));
764 if ((len < 0) && (do_connect(&sockfd, file, server) < 0)) {
765 fprintf(stderr, "Reconnecting...");
768 } else if (len == 0) {
770 } else if (len == 1) {
771 exit_status = EXIT_FAILURE;
775 fprintf(stdout, "\n");
777 if (inputfp != stdin) fclose(inputfp);