+++ /dev/null
-/*
- * command.c Command socket processing.
- *
- * Version: $Id$
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- *
- * Copyright 2008 The FreeRADIUS server project
- * Copyright 2008 Alan DeKok <aland@deployingradius.com>
- */
-
-#ifdef WITH_COMMAND_SOCKET
-
-#include <freeradius-devel/parser.h>
-#include <freeradius-devel/md5.h>
-#include <freeradius-devel/conduit.h>
-#include <freeradius-devel/state.h>
-
-#include <libgen.h>
-#ifdef HAVE_INTTYPES_H
-#include <inttypes.h>
-#endif
-
-#ifdef HAVE_SYS_UN_H
-#include <sys/un.h>
-#ifndef SUN_LEN
-#define SUN_LEN(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
-#endif
-#endif
-
-#ifdef HAVE_SYS_STAT_H
-#include <sys/stat.h>
-#endif
-
-#include <pwd.h>
-#include <grp.h>
-
-#ifdef HAVE_GPERFTOOLS_PROFILER_H
-# include <gperftools/profiler.h>
-#endif
-
-typedef struct fr_command_table_t fr_command_table_t;
-
-typedef int (*fr_command_func_t)(rad_listen_t *, int, char *argv[]);
-
-#define FR_READ (1)
-#define FR_WRITE (2)
-
-#define CMD_FAIL FR_CONDUIT_FAIL
-#define CMD_OK FR_CONDUIT_SUCCESS
-
-struct fr_command_table_t {
- char const *command;
- int mode; /* read/write */
- char const *help;
- fr_command_func_t func;
- fr_command_table_t *table;
-};
-
-#define COMMAND_SOCKET_MAGIC (0xffdeadee)
-typedef struct fr_command_socket_t {
- uint32_t magic;
- char const *path;
- char *copy; /* <sigh> */
- gid_t gid; //!< Additional group authorized to connect to socket.
- char const *gid_name; //!< Name of additional group (resolved to gid later).
- char const *mode_name;
- bool peercred;
- bool blocking;
- char user[256];
-
- /*
- * The next few entries handle fake packets injected by
- * the control socket.
- */
- fr_ipaddr_t src_ipaddr; /* src_port is always 0 */
- fr_ipaddr_t dst_ipaddr;
- uint16_t dst_port;
- rad_listen_t *inject_listener;
- RADCLIENT *inject_client;
-
- fr_cs_buffer_t co;
-} fr_command_socket_t;
-
-static const CONF_PARSER command_config[] = {
- { FR_CONF_OFFSET("socket", FR_TYPE_STRING, fr_command_socket_t, path), .dflt = "${run_dir}/radiusd.sock" },
- { FR_CONF_DEPRECATED("uid", FR_TYPE_STRING, fr_command_socket_t, NULL) },
- { FR_CONF_OFFSET("gid", FR_TYPE_STRING, fr_command_socket_t, gid_name) },
- { FR_CONF_OFFSET("mode", FR_TYPE_STRING, fr_command_socket_t, mode_name) },
- { FR_CONF_OFFSET("peercred", FR_TYPE_BOOL, fr_command_socket_t, peercred), .dflt = "yes" },
- { FR_CONF_OFFSET("blocking", FR_TYPE_BOOL, fr_command_socket_t, blocking), },
- CONF_PARSER_TERMINATOR
-};
-
-static FR_NAME_NUMBER mode_names[] = {
- { "ro", FR_READ },
- { "read-only", FR_READ },
- { "read-write", FR_READ | FR_WRITE },
- { "rw", FR_READ | FR_WRITE },
- { NULL, 0 }
-};
-
-static char debug_log_file_buffer[1024];
-extern fr_cond_t *debug_condition;
-extern fr_log_t debug_log;
-
-#if !defined(HAVE_GETPEEREID) && defined(SO_PEERCRED)
-static int getpeereid(int s, uid_t *euid, gid_t *egid)
-{
- struct ucred cr;
- socklen_t cl = sizeof(cr);
-
- if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cl) < 0) {
- return -1;
- }
-
- *euid = cr.uid;
- *egid = cr.gid;
- return 0;
-}
-
-/* we now have getpeereid() in this file */
-#define HAVE_GETPEEREID (1)
-
-#endif /* HAVE_GETPEEREID */
-
-#if 0
-/*
- * Enable this function if you're running on OSX and want to use
- * valgrind. Valgrind doesn't implement openat() etc., so this
- * old / shorter function works.
- */
-static int fr_server_domain_socket(char const *path, UNUSED gid_t gid)
-{
- int sockfd;
- size_t len;
- socklen_t socklen;
- struct sockaddr_un salocal;
- struct stat buf;
-
- len = strlen(path);
- if (len >= sizeof(salocal.sun_path)) {
- fr_strerror_printf("Path length (%zu) exceeds system limit for unix socket paths (%zu)",
- len, sizeof(salocal.sun_path));
- return -1;
- }
-
- if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
- fr_strerror_printf("Failed creating socket: %s",
- fr_syserror(errno));
- return -1;
- }
-
- memset(&salocal, 0, sizeof(salocal));
- salocal.sun_family = AF_UNIX;
- memcpy(salocal.sun_path, path, len + 1); /* SUN_LEN does strlen */
-
- socklen = SUN_LEN(&salocal);
-
- /*
- * Check the path.
- */
- if (stat(path, &buf) < 0) {
- if (errno != ENOENT) {
- fr_strerror_printf("Failed to stat %s: %s",
- path, fr_syserror(errno));
- close(sockfd);
- return -1;
- }
-
- /*
- * FIXME: Check the enclosing directory?
- */
- } else { /* it exists */
- if (!S_ISREG(buf.st_mode)
-#ifdef S_ISSOCK
- && !S_ISSOCK(buf.st_mode)
-#endif
- ) {
- fr_strerror_printf("Cannot turn %s into socket", path);
- close(sockfd);
- return -1;
- }
-
- /*
- * Refuse to open sockets not owned by us.
- */
- if (buf.st_uid != geteuid()) {
- fr_strerror_printf("We do not own %s", path);
- close(sockfd);
- return -1;
- }
-
- if (unlink(path) < 0) {
- fr_strerror_printf("Failed to delete %s: %s",
- path, fr_syserror(errno));
- close(sockfd);
- return -1;
- }
- }
-
- if (bind(sockfd, (struct sockaddr *)&salocal, socklen) < 0) {
- fr_strerror_printf("Failed binding to %s: %s",
- path, fr_syserror(errno));
- close(sockfd);
- return -1;
- }
-
- /*
- * FIXME: There's a race condition here. But Linux
- * doesn't seem to permit fchmod on domain sockets.
- */
- if (chmod(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) < 0) {
- fr_strerror_printf("Failed setting permissions on %s: %s",
- path, fr_syserror(errno));
- close(sockfd);
- return -1;
- }
-
- if (listen(sockfd, 8) < 0) {
- fr_strerror_printf("Failed listening to %s: %s",
- path, fr_syserror(errno));
- close(sockfd);
- return -1;
- }
-
-#ifdef O_NONBLOCK
- {
- int flags;
-
- if ((flags = fcntl(sockfd, F_GETFL, NULL)) < 0) {
- fr_strerror_printf("Failure getting socket flags: %s",
- fr_syserror(errno));
- close(sockfd);
- return -1;
- }
-
- flags |= O_NONBLOCK;
- if( fcntl(sockfd, F_SETFL, flags) < 0) {
- fr_strerror_printf("Failure setting socket flags: %s",
- fr_syserror(errno));
- close(sockfd);
- return -1;
- }
- }
-#endif
-
- return sockfd;
-}
-#else /* OSX VALGRIND */
-
-#if !defined(HAVE_OPENAT) || !defined(HAVE_MKDIRAT) || !defined(HAVE_UNLINKAT) || !defined(HAVE_FCHMODAT) || !defined(HAVE_FCHOWNAT)
-static int fr_server_domain_socket(UNUSED char const *path, UNUSED gid_t gid)
-{
- fr_strerror_printf("Unable to initialise control socket. Upgrade to POSIX-2008 compliant libc");
- return -1;
-}
-#else
-/** Create a unix socket, and enforce permissions using the file system
- *
- * The way it does this depends on the operating system. On Linux systems permissions
- * can be set on the socket directly and the system will enforce them.
- *
- * On most other systems fchown and fchmod fail when called with socket descriptors,
- * and although permissions can be changed in other ways, they're not enforced.
- *
- * For these systems we use the permissions on the parent directory to enforce
- * permissions on the socket. It's not safe to modify these permissions ourselves
- * due to TOCTOU attacks, so if they don't match what we require, we error out and
- * get the user to change them (which arguably isn't any safer, but releases us of
- * the responsibility).
- *
- * @note must be called without effective root permissions (#fr_suid_down).
- *
- * @param path where domain socket should be created.
- * @param gid Alternative group to grant read/write access to the socket.
- * @return
- * - A file descriptor for the bound socket on success.
- * - -1 on failure.
- */
-static int fr_server_domain_socket(char const *path, gid_t gid, bool blocking)
-{
- int dir_fd = -1, sock_fd = -1, parent_fd = -1;
- char const *name;
- char *buff = NULL, *dir = NULL, *p;
-
- uid_t euid, suid;
- gid_t egid;
-
- mode_t perm = 0;
- mode_t dir_perm;
- struct stat st;
-
- size_t len;
-
- socklen_t socklen;
- struct sockaddr_un salocal;
-
- rad_assert(path);
-
- euid = geteuid();
- egid = getegid();
-
- /*
- * In the Linux implementation, sockets which are visible in the
- * filesystem honor the permissions of the directory they are in. Their
- * owner, group, and permissions can be changed. Creation of a new
- * socket will fail if the process does not have write and search
- * (execute) permission on the directory the socket is created in.
- * Connecting to the socket object requires read/write permission.
- */
- perm = (S_IREAD | S_IWRITE);
- dir_perm = (S_IREAD | S_IWRITE | S_IEXEC);
- if (gid != (gid_t) -1) {
- perm |= (S_IRGRP | S_IWGRP);
- dir_perm |= (S_IRGRP | S_IXGRP);
- }
-
- buff = talloc_strdup(NULL, path);
- if (!buff) {
- fr_strerror_printf("Out of memory");
- return -1;
- }
-
- /*
- * Some implementations modify it in place others use internal
- * storage *sigh*. dirname also formats the path else we wouldn't
- * be using it.
- */
- dir = dirname(buff);
- if (dir != buff) {
- dir = talloc_strdup(NULL, dir);
- MEM(dir);
- talloc_free(buff);
- }
-
- p = strrchr(dir, FR_DIR_SEP);
- if (!p) {
- fr_strerror_printf("Failed determining parent directory");
- error:
- talloc_free(dir);
- if (dir_fd >= 0) close(dir_fd);
- if (sock_fd >= 0) close(sock_fd);
- if (parent_fd >= 0) close(parent_fd);
- return -1;
- }
-
- *p = '\0';
-
- /*
- * Ensure the parent of the control socket directory exists,
- * and the euid we're running under has access to it.
- *
- * This must be done suid_down, so we can't be tricked into
- * accessing a directory owned by root.
- */
- parent_fd = open(dir, O_DIRECTORY);
- if (parent_fd < 0) {
- struct passwd *user;
- struct group *group;
-
- if (errno != ENOENT) {
- fr_strerror_printf("Can't open directory \"%s\": %s", dir, fr_syserror(errno));
- goto error;
- }
-
- if (rad_getpwuid(NULL, &user, euid) < 0) {
- fr_strerror_printf("Failed resolving euid to user: %s", fr_strerror());
- goto error;
- }
- if (rad_getgrgid(NULL, &group, egid) < 0) {
- fr_strerror_printf("Failed resolving egid to group: %s", fr_strerror());
- talloc_free(user);
- goto error;
- }
-
- fr_strerror_printf("Can't open directory \"%s\": Create it and allow writing by "
- "user %s or group %s", dir, user->pw_name, group->gr_name);
-
- talloc_free(user);
- talloc_free(group);
- goto error;
- }
-
- *p = FR_DIR_SEP;
-
- dir_fd = openat(parent_fd, p + 1, O_NOFOLLOW | O_DIRECTORY);
- if (dir_fd < 0) {
- int ret = 0;
-
- if (errno != ENOENT) {
- rad_file_error(errno);
- fr_strerror_printf("Failed opening control socket directory \"%s\": %s", dir, fr_strerror());
- goto error;
- }
-
- /*
- * This fails if the radius user can't write
- * to the parent directory.
- */
- if (mkdirat(parent_fd, p + 1, dir_perm) < 0) {
- rad_file_error(errno);
- fr_strerror_printf("Failed creating control socket directory \"%s\": %s", dir, fr_strerror());
- goto error;
- }
-
- dir_fd = openat(parent_fd, p + 1, O_NOFOLLOW | O_DIRECTORY);
- if (dir_fd < 0) {
- fr_strerror_printf("Failed opening the control socket directory we created: %s",
- fr_syserror(errno));
- goto error;
- }
-
- /*
- * Can't set groups other than ones we belong
- * to unless we suid_up.
- */
- rad_suid_up();
- if (gid != (gid_t)-1) ret = fchown(dir_fd, euid, gid);
- rad_suid_down();
- if (ret < 0) {
- fr_strerror_printf("Failed changing group of control socket directory: %s",
- fr_syserror(errno));
- goto error;
- }
- /*
- * Control socket dir already exists, but we still need to
- * check the permissions are what we expect.
- */
- } else {
- int ret;
- int client_fd;
-
- ret = fstat(dir_fd, &st);
- if (ret < 0) {
- fr_strerror_printf("Failed checking permissions of control socket directory: %s",
- fr_syserror(errno));
- goto error;
- }
-
- if (st.st_uid != euid) {
- struct passwd *need_user, *have_user;
-
- if (rad_getpwuid(NULL, &need_user, euid) < 0) {
- fr_strerror_printf("Failed resolving socket dir uid to user: %s", fr_strerror());
- goto error;
- }
- if (rad_getpwuid(NULL, &have_user, st.st_uid) < 0) {
- fr_strerror_printf("Failed resolving socket dir gid to group: %s", fr_strerror());
- talloc_free(need_user);
- goto error;
- }
- fr_strerror_printf("Socket directory \"%s\" must be owned by user %s, currently owned "
- "by user %s", dir, need_user->pw_name, have_user->pw_name);
- talloc_free(need_user);
- talloc_free(have_user);
- goto error;
- }
-
- if ((gid != (gid_t)-1) && (st.st_gid != gid)) {
- /*
- * Can't set groups other than ones we belong
- * to unless we suid_up.
- */
- rad_suid_up();
- if (gid != (gid_t)-1) ret = fchown(dir_fd, euid, gid);
- rad_suid_down();
- if (ret < 0) {
- struct group *need_group, *have_group;
-
- if (rad_getgrgid(NULL, &need_group, gid) < 0) {
- fr_strerror_printf("Failed resolving socket directory uid to user: %s",
- fr_strerror());
- goto error;
- }
- if (rad_getgrgid(NULL, &have_group, st.st_gid) < 0) {
- fr_strerror_printf("Failed resolving socket directory gid to group: %s",
- fr_strerror());
- talloc_free(need_group);
- goto error;
- }
- fr_strerror_printf("Failed changing ownership of socket directory \"%s\" from "
- "group %s, to group %s", dir,
- need_group->gr_name, have_group->gr_name);
- talloc_free(need_group);
- talloc_free(have_group);
-
- goto error;
- }
- }
-
- if ((dir_perm & 0777) != (st.st_mode & 0777) &&
- (fchmod(dir_fd, (st.st_mode & 7000) | dir_perm)) < 0) {
- char str_need[10], oct_need[5];
- char str_have[10], oct_have[5];
-
- rad_mode_to_str(str_need, dir_perm);
- rad_mode_to_oct(oct_need, dir_perm);
- rad_mode_to_str(str_have, st.st_mode);
- rad_mode_to_oct(oct_have, st.st_mode);
- fr_strerror_printf("Failed changing permissions on socket directory \"%s\" from %s "
- "(%s) to %s (%s): %s", dir, str_have, oct_have,
- str_need, oct_need, fr_syserror(errno));
-
- goto error;
- }
-
- /*
- * Check if a server is already listening on the
- * socket?
- */
- client_fd = fr_socket_client_unix(path, false);
- if (client_fd >= 0) {
- fr_strerror_printf("Control socket '%s' is already in use", path);
- close(client_fd);
- goto error;
- }
- fr_strerror(); /* Clear any errors */
- }
-
- name = strrchr(path, FR_DIR_SEP);
- if (!name) {
- fr_strerror_printf("Can't determine socket name");
- goto error;
- }
- name++;
-
- /*
- * We've checked the containing directory has the permissions
- * we expect, and as we have the FD, and aren't following
- * symlinks no one can trick us into changing or creating a
- * file elsewhere.
- *
- * It's possible an attacker may still be able to create hard
- * links, for the socket file. But they would need write
- * access to the directory we just created or verified, so
- * this attack vector is unlikely.
- */
- rad_suid_up(); /* Need to be root to change euid and egid */
- suid = geteuid();
-
- /*
- * Group needs to be changed first, because if we change
- * to a non root user, we can no longer set it.
- */
- if ((gid != (gid_t)-1) && (rad_segid(gid) < 0)) {
- fr_strerror_printf("Failed setting egid: %s", fr_strerror());
- rad_suid_down();
- goto error;
- }
-
- /*
- * Reset euid back to FreeRADIUS user
- */
- if (rad_seuid(euid) < 0) {
- fr_strerror_printf("Failed restoring euid: %s", fr_strerror());
- rad_segid(egid);
- rad_suid_down();
- goto error;
- }
-
- /*
- * The original code, did openat, used fstat to figure out
- * what type the file was and then used unlinkat to unlink
- * it. Except on OSX (at least) openat refuses to open
- * socket files. So we now rely on the fact that unlinkat
- * has sane and consistent behaviour, and will not unlink
- * directories. unlinkat should also fail if the socket user
- * hasn't got permission to modify the socket.
- */
- if ((unlinkat(dir_fd, name, 0) < 0) && (errno != ENOENT)) {
- fr_strerror_printf("Failed removing stale socket: %s", fr_syserror(errno));
- sock_error:
- /*
- * Restore suid to ensure rad_suid_up continues
- * to work correctly.
- */
- rad_seuid(suid);
- if (gid != (gid_t)-1) rad_segid(egid);
- /*
- * Then SUID down, to ensure rad_suid_up/down continues
- * to work correctly.
- */
- rad_suid_down();
- goto error;
- }
-
- /*
- * At this point we should have established a secure directory
- * to house our socket, and cleared out any stale sockets.
- */
- sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if (sock_fd < 0) {
- fr_strerror_printf("Failed opening socket: %s", fr_syserror(errno));
- goto sock_error;
- }
-
-#ifdef HAVE_BINDAT
- len = strlen(name);
-#else
- len = strlen(path);
-#endif
- if (len >= sizeof(salocal.sun_path)) {
- fr_strerror_printf("Path length (%zu) exceeds system limit for unix socket paths (%zu)",
- len, sizeof(salocal.sun_path));
- goto sock_error;
- }
-
- memset(&salocal, 0, sizeof(salocal));
- salocal.sun_family = AF_UNIX;
-
-#ifdef HAVE_BINDAT
- memcpy(salocal.sun_path, name, len + 1); /* SUN_LEN does strlen */
-#else
- memcpy(salocal.sun_path, path, len + 1); /* SUN_LEN does strlen */
-#endif
- socklen = SUN_LEN(&salocal);
-
- /*
- * The correct function to use here is bindat(), but only
- * quite recent versions of FreeBSD actually have it, and
- * it's definitely not POSIX.
- */
-#ifdef HAVE_BINDAT
- if (bindat(dir_fd, sock_fd, (struct sockaddr *)&salocal, socklen) < 0) {
-#else
- if (bind(sock_fd, (struct sockaddr *)&salocal, socklen) < 0) {
-#endif
- fr_strerror_printf("Failed binding socket: %s", fr_syserror(errno));
- goto sock_error;
- }
-
- /*
- * Previous code used fchown to set ownership before the
- * socket was bound. Unfortunately this only seemed to
- * work on Linux, on OSX and FreeBSD this operation would
- * throw an EINVAL error.
- */
- if (fchownat(dir_fd, name, euid, gid, AT_SYMLINK_NOFOLLOW) < 0) {
- struct passwd *user;
- struct group *group;
- int fchown_err = errno;
-
-
- if (rad_getpwuid(NULL, &user, euid) < 0) {
- fr_strerror_printf("Failed resolving socket uid to user: %s", fr_strerror());
- goto sock_error;
- }
- if (rad_getgrgid(NULL, &group, gid) < 0) {
- fr_strerror_printf("Failed resolving socket gid to group: %s", fr_strerror());
- talloc_free(user);
- goto sock_error;
- }
-
- fr_strerror_printf("Failed changing socket ownership to %s:%s: %s", user->pw_name, group->gr_name,
- fr_syserror(fchown_err));
- talloc_free(user);
- talloc_free(group);
- goto sock_error;
- }
-
- /*
- * Direct socket permissions are only useful on Linux which
- * actually enforces them. BSDs may not... or they may...
- * OSX 10.11.x (EL-Capitan) seems to.
- *
- * Previous code used fchmod on sock_fd before the bind,
- * but this didn't always set the correct permissions.
- *
- * fchmodat seems to work more reliably, and has the same
- * resistance against TOCTOU attacks.
- *
- * AT_SYMLINK_NOFOLLOW causes this to fail on Linux.
- */
- if (fchmodat(dir_fd, name, perm, 0) < 0) {
- char str_need[10], oct_need[5];
-
- rad_mode_to_str(str_need, perm);
- rad_mode_to_oct(oct_need, perm);
- fr_strerror_printf("Failed changing socket permissions to %s (%s): %s", str_need, oct_need,
- fr_syserror(errno));
- goto sock_error;
- }
-
- if (listen(sock_fd, 8) < 0) {
- fr_strerror_printf("Failed listening on socket: %s", fr_syserror(errno));
- goto sock_error;
- }
-
- if (!blocking && (fr_nonblock(sock_fd) < 0)) {
- fr_strerror_printf("Failed setting nonblock on socket: %s", fr_strerror());
- goto sock_error;
- }
-
- /*
- * Restore suid to ensure rad_suid_up continues
- * to work correctly.
- */
- rad_seuid(suid);
- if (gid != (gid_t)-1) rad_segid(egid);
- rad_suid_down();
-
- close(dir_fd);
- close(parent_fd);
-
- return sock_fd;
-}
-#endif /* HAVE_OPENAT, etc. */
-#endif /* OSX VALGRIND */
-
-/*
- * Turn off all debugging. But don't touch the debug condition.
- */
-static void command_debug_off(void)
-{
- debug_log.dst = L_DST_NULL;
- debug_log.file = NULL;
- debug_log.cookie = NULL;
- debug_log.cookie_write = NULL;
-}
-
-
-static void command_close_socket(rad_listen_t *this)
-{
- this->status = RAD_LISTEN_STATUS_EOL;
-
- if (debug_log.cookie == this) {
- command_debug_off();
- }
-
- /*
- * This removes the socket from the event fd, so no one
- * will be calling us any more.
- */
- radius_update_listener(this);
-}
-
-
-#if defined(HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN)
-static pthread_mutex_t debug_mutex = PTHREAD_MUTEX_INITIALIZER;
-
-/*
- * Callback from log.c, so that we can write debug output to the
- * radmin socket.
- *
- * We only have one debug condition, so we only need one mutex.
- */
-#ifdef HAVE_FOPENCOOKIE
-static ssize_t command_socket_write(void *cookie, char const *buffer, size_t len)
-#else
-static int command_socket_write(void *cookie, char const *buffer, int len)
-#endif
-{
- ssize_t r;
- rad_listen_t *listener = cookie;
-
- if (listener->status == RAD_LISTEN_STATUS_EOL) return 0;
-
- pthread_mutex_lock(&debug_mutex);
-
- r = fr_conduit_write(listener->fd, FR_CONDUIT_STDOUT, buffer, len);
-
- pthread_mutex_unlock(&debug_mutex);
-
- if (r <= 0) {
- command_close_socket(listener);
- }
-
- return r;
-}
-#endif
-
-static ssize_t CC_HINT(format (printf, 2, 3)) cprintf(rad_listen_t *listener, char const *fmt, ...)
-{
- ssize_t r, len;
- va_list ap;
- char buffer[256];
-
- va_start(ap, fmt);
- len = vsnprintf(buffer, sizeof(buffer), fmt, ap);
- va_end(ap);
-
- if (listener->status == RAD_LISTEN_STATUS_EOL) return 0;
-
- r = fr_conduit_write(listener->fd, FR_CONDUIT_STDOUT, buffer, len);
- if (r <= 0) command_close_socket(listener);
-
- /*
- * FIXME: Keep writing until done?
- */
- return r;
-}
-
-static ssize_t CC_HINT(format (printf, 2, 3)) cprintf_error(rad_listen_t *listener, char const *fmt, ...)
-{
- ssize_t r, len;
- va_list ap;
- char buffer[256];
-
- va_start(ap, fmt);
- len = vsnprintf(buffer, sizeof(buffer), fmt, ap);
- va_end(ap);
-
- if (listener->status == RAD_LISTEN_STATUS_EOL) return 0;
-
- r = fr_conduit_write(listener->fd, FR_CONDUIT_STDERR, buffer, len);
- if (r <= 0) command_close_socket(listener);
-
- /*
- * FIXME: Keep writing until done?
- */
- return r;
-}
-
-static int command_hup(rad_listen_t *listener, int argc, char *argv[])
-{
- CONF_SECTION *cs;
- module_instance_t *instance;
-
- if (argc == 0) {
- radius_signal_self(RADIUS_SIGNAL_SELF_HUP);
- return CMD_OK;
- }
-
- /*
- * Hack a "main" HUP thingy
- */
- if (strcmp(argv[0], "main.log") == 0) {
- hup_logfile();
- return CMD_OK;
- }
-
- cs = cf_section_find(main_config.config, "modules", NULL);
- if (!cs) return CMD_FAIL;
-
- instance = module_find(cs, argv[0]);
- if (!instance) {
- cprintf_error(listener, "No such module \"%s\"\n", argv[0]);
- return CMD_FAIL;
- }
-
- cprintf_error(listener, "HUP - NYI in version 4");
-
- return CMD_FAIL;
-}
-
-static int command_terminate(UNUSED rad_listen_t *listener,
- UNUSED int argc, UNUSED char *argv[])
-{
- radius_signal_self(RADIUS_SIGNAL_SELF_TERM);
-
- return CMD_OK;
-}
-
-static int command_uptime(rad_listen_t *listener,
- UNUSED int argc, UNUSED char *argv[])
-{
- char buffer[128];
-
- CTIME_R(&fr_start_time, buffer, sizeof(buffer));
- cprintf(listener, "Up since %s", buffer); /* no \r\n */
-
- return CMD_OK;
-}
-
-static int command_show_config(rad_listen_t *listener, int argc, char *argv[])
-{
- CONF_ITEM *ci;
- CONF_PAIR *cp;
- char const *value;
-
- if (argc != 1) {
- cprintf_error(listener, "No path was given\n");
- return CMD_FAIL;
- }
-
- ci = cf_reference_item(main_config.config, main_config.config, argv[0]);
- if (!ci) return CMD_FAIL;
-
- if (!cf_item_is_pair(ci)) return CMD_FAIL;
-
- cp = cf_item_to_pair(ci);
- value = cf_pair_value(cp);
- if (!value) return CMD_FAIL;
-
- cprintf(listener, "%s\n", value);
-
- return CMD_OK;
-}
-
-static char const tabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
-
-/*
- * FIXME: Recurse && indent?
- */
-static void cprint_conf_parser(rad_listen_t *listener, int indent, CONF_SECTION *cs,
- void const *base)
-
-{
- int i;
- char const *name1 = cf_section_name1(cs);
- char const *name2 = cf_section_name2(cs);
- CONF_PARSER const *variables = cf_section_parse_table(cs);
-
- if (name2) {
- cprintf(listener, "%.*s%s %s {\n", indent, tabs, name1, name2);
- } else {
- cprintf(listener, "%.*s%s {\n", indent, tabs, name1);
- }
-
- indent++;
-
- /*
- * Print
- */
- if (variables) for (i = 0; variables[i].name != NULL; i++) {
- void const *data;
- char buffer[INET6_ADDRSTRLEN];
-
- /*
- * No base struct offset, data must be the pointer.
- * If data doesn't exist, ignore the entry, there
- * must be something wrong.
- */
- if (!base) {
- if (!variables[i].data) {
- continue;
- }
-
- data = variables[i].data;
-
- } else if (variables[i].data) {
- data = variables[i].data;
-
- } else {
- data = (((char const *)base) + variables[i].offset);
- }
-
- /*
- * Ignore the various flags
- */
- switch (variables[i].type & 0xff) {
- default:
- cprintf(listener, "%.*s%s = ?\n", indent, tabs,
- variables[i].name);
- break;
-
- case FR_TYPE_UINT32:
- cprintf(listener, "%.*s%s = %u\n", indent, tabs,
- variables[i].name, *(int const *) data);
- break;
-
- case FR_TYPE_IPV4_ADDR:
- inet_ntop(AF_INET, data, buffer, sizeof(buffer));
- break;
-
- case FR_TYPE_IPV6_ADDR:
- inet_ntop(AF_INET6, data, buffer, sizeof(buffer));
- break;
-
- case FR_TYPE_BOOL:
- cprintf(listener, "%.*s%s = %s\n", indent, tabs,
- variables[i].name,
- ((*(bool const *) data) == false) ? "no" : "yes");
- break;
-
- case FR_TYPE_STRING:
- case FR_TYPE_FILE_INPUT:
- case FR_TYPE_FILE_OUTPUT:
- /*
- * FIXME: Escape things in the string!
- */
- if (*(char const * const *) data) {
- cprintf(listener, "%.*s%s = \"%s\"\n", indent, tabs,
- variables[i].name, *(char const * const *) data);
- } else {
- cprintf(listener, "%.*s%s = \n", indent, tabs,
- variables[i].name);
- }
-
- break;
- }
- }
-
- indent--;
-
- cprintf(listener, "%.*s}\n", indent, tabs);
-}
-
-static int command_show_module_config(rad_listen_t *listener, int argc, char *argv[])
-{
- CONF_SECTION *cs;
- module_instance_t *instance;
-
- if (argc != 1) {
- cprintf_error(listener, "No module name was given\n");
- return CMD_FAIL;
- }
-
- cs = cf_section_find(main_config.config, "modules", NULL);
- if (!cs) return CMD_FAIL;
-
- instance = module_find(cs, argv[0]);
- if (!instance) {
- cprintf_error(listener, "No such module \"%s\"\n", argv[0]);
- return CMD_FAIL;
- }
-
- cprint_conf_parser(listener, 0, instance->dl_inst->conf, instance->dl_inst->data);
-
- return CMD_OK;
-}
-
-static char const *method_names[MOD_COUNT] = {
- "authenticate",
- "authorize",
- "preacct",
- "accounting",
- "pre-proxy",
- "post-proxy",
- "post-auth"
-};
-
-
-static int command_show_module_methods(rad_listen_t *listener, int argc, char *argv[])
-{
- int i;
- CONF_SECTION *cs;
- module_instance_t const *instance;
-
- if (argc != 1) {
- cprintf_error(listener, "No module name was given\n");
- return CMD_FAIL;
- }
-
- cs = cf_section_find(main_config.config, "modules", NULL);
- if (!cs) return CMD_FAIL;
-
- instance = module_find(cs, argv[0]);
- if (!instance) {
- cprintf_error(listener, "No such module \"%s\"\n", argv[0]);
- return CMD_FAIL;
- }
-
- for (i = 0; i < MOD_COUNT; i++) {
- if (instance->module->methods[i]) cprintf(listener, "%s\n", method_names[i]);
- }
-
- return CMD_OK;
-}
-
-
-static int command_show_module_flags(rad_listen_t *listener, int argc, char *argv[])
-{
- CONF_SECTION *cs;
- module_instance_t const *instance;
-
- if (argc != 1) {
- cprintf_error(listener, "No module name was given\n");
- return CMD_FAIL;
- }
-
- cs = cf_section_find(main_config.config, "modules", NULL);
- if (!cs) return CMD_FAIL;
-
- instance = module_find(cs, argv[0]);
- if (!instance) {
- cprintf_error(listener, "No such module \"%s\"\n", argv[0]);
- return CMD_FAIL;
- }
-
- if ((instance->module->type & RLM_TYPE_THREAD_UNSAFE) != 0) cprintf(listener, "thread-unsafe\n");
-
- return CMD_OK;
-}
-
-static int command_show_module_status(rad_listen_t *listener, int argc, char *argv[])
-{
- CONF_SECTION *cs;
- const module_instance_t *instance;
-
- if (argc != 1) {
- cprintf_error(listener, "No module name was given\n");
- return CMD_FAIL;
- }
-
- cs = cf_section_find(main_config.config, "modules", NULL);
- if (!cs) return CMD_FAIL;
-
- instance = module_find(cs, argv[0]);
- if (!instance) {
- cprintf_error(listener, "No such module \"%s\"\n", argv[0]);
- return CMD_FAIL;
- }
-
- if (!instance->force) {
- cprintf(listener, "alive\n");
- } else {
- cprintf(listener, "%s\n", fr_int2str(mod_rcode_table, instance->code, "<invalid>"));
- }
-
-
- return CMD_OK;
-}
-
-
-/*
- * Show all loaded modules
- */
-static int command_show_modules(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[])
-{
- CONF_SECTION *cs, *subcs;
-
- cs = cf_section_find(main_config.config, "modules", NULL);
- if (!cs) return CMD_FAIL;
-
- subcs = NULL;
- while ((subcs = cf_section_next(cs, subcs)) != NULL) {
- char const *name1 = cf_section_name1(subcs);
- char const *name2 = cf_section_name2(subcs);
-
- module_instance_t *instance;
-
- if (name2) {
- instance = module_find(cs, name2);
- if (!instance) continue;
-
- cprintf(listener, "%s (%s)\n", name2, name1);
- } else {
- instance = module_find(cs, name1);
- if (!instance) continue;
-
- cprintf(listener, "%s\n", name1);
- }
- }
-
- return CMD_OK;
-}
-
-static int command_show_clients(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[])
-{
- int i;
- RADCLIENT *client;
- char buffer[256];
- char ipaddr[256];
-
- for (i = 0; i < 256; i++) {
- client = client_findbynumber(NULL, i);
- if (!client) break;
-
- fr_inet_ntoh(&client->ipaddr, buffer, sizeof(buffer));
-
- if (((client->ipaddr.af == AF_INET) &&
- (client->ipaddr.prefix != 32)) ||
- ((client->ipaddr.af == AF_INET6) &&
- (client->ipaddr.prefix != 128))) {
- snprintf(ipaddr, sizeof(ipaddr), "%s/%d", buffer, client->ipaddr.prefix);
- } else {
- snprintf(ipaddr, sizeof(ipaddr), "%s", buffer);
- }
-
- cprintf(listener, "%s\t%s\t%s\t%s\n", ipaddr,
- client->shortname ? client->shortname : "\t",
- client->nas_type ? client->nas_type : "\t",
- client->server ? client->server : "\t");
- }
-
- return CMD_OK;
-}
-
-
-static int command_show_version(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[])
-{
- cprintf(listener, "%s\n", radiusd_version);
- return CMD_OK;
-}
-
-static int command_debug_level_global(rad_listen_t *listener, int argc, char *argv[])
-{
- int number;
-
- if (argc == 0) {
- cprintf_error(listener, "Must specify <number>\n");
- return -1;
- }
-
- number = atoi(argv[0]);
- if ((number < 0) || (number > 4)) {
- cprintf_error(listener, "<number> must be between 0 and 4\n");
- return -1;
- }
-
- INFO("Global debug level set to %i, was %i", number, fr_debug_lvl);
- fr_debug_lvl = rad_debug_lvl = number;
-
- return CMD_OK;
-}
-
-static int command_debug_level_request(rad_listen_t *listener, int argc, char *argv[])
-{
- int number;
-
- if (argc == 0) {
- cprintf_error(listener, "Must specify <number>\n");
- return -1;
- }
-
- number = atoi(argv[0]);
- if ((number < 0) || (number > 4)) {
- cprintf_error(listener, "<number> must be between 0 and 4\n");
- return -1;
- }
-
- INFO("Request debug level set to %i, was %i", number, req_debug_lvl);
- req_debug_lvl = number;
-
- return CMD_OK;
-}
-
-#ifndef NDEBUG
-static void _command_talloc_report(const void *ptr, int depth, int max_depth, int is_ref, void *_f)
-{
- const char *name = talloc_get_name(ptr);
- rad_listen_t *listener = talloc_get_type_abort(_f, rad_listen_t);
-
- if (is_ref) {
- cprintf(listener, "%*sreference to: %s\n", depth * 4, "", name);
- return;
- }
-
- if (depth == 0) {
- cprintf(listener,
- "%stalloc report on '%s' (total %6lu bytes in %3lu blocks)\n",
- (max_depth < 0 ? "full " :""), name,
- (unsigned long)talloc_total_size(ptr),
- (unsigned long)talloc_total_blocks(ptr));
- return;
- }
-
- cprintf(listener,
- "%*s%-30s contains %6lu bytes in %3lu blocks (ref %d) %p\n",
- depth * 4, "",
- name,
- (unsigned long)talloc_total_size(ptr),
- (unsigned long)talloc_total_blocks(ptr),
- (int)talloc_reference_count(ptr), ptr);
-}
-
-static int command_show_memory_report(rad_listen_t *listener, int argc, UNUSED char *argv[])
-{
- if (argc != 0) {
- cprintf_error(listener, "Command takes no arguments");
- }
-
- if (!main_config.talloc_memory_report) {
- cprintf(listener, "Memory debugging not enabled. To enable, pass -Ms when starting the server\n");
- return CMD_OK;
- }
-
- INFO("Writing talloc memory report to command socket");
-
- talloc_report_depth_cb(talloc_null_ctx(), 0, -1, _command_talloc_report, listener);
-
- return CMD_OK;
-}
-#endif
-
-#if defined(HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN)
-static int command_debug_socket(rad_listen_t *listener, int argc, char *argv[])
-{
- uint32_t notify;
-
- if (rad_debug_lvl && default_log.dst == L_DST_STDOUT) {
- cprintf_error(listener, "Cannot redirect debug logs to a socket when already in debugging mode.\n");
- return -1;
- }
-
- if ((argc == 0) || (strcmp(argv[0], "off") == 0)) {
- notify = htonl(FR_NOTIFY_BUFFERED);
-
- /*
- * Tell radmin to go into buffered mode.
- */
- (void) fr_conduit_write(listener->fd, FR_CONDUIT_NOTIFY, ¬ify, sizeof(notify));
-
- command_debug_off();
- return CMD_OK;
- }
-
- if (strcmp(argv[0], "on") != 0) {
- cprintf_error(listener, "Syntax error: got '%s', expected [on|off]", argv[0]);
- return -1;
- }
-
- /*
- * Don't allow people to stomp on each other.
- */
- if ((debug_log.cookie != NULL) &&
- (debug_log.cookie != listener)) {
- cprintf_error(listener, "ERROR: Someone else is already using the debug socket");
- return -1;
- }
-
- /*
- * Disable logging while we're mucking with the buffer.
- */
- command_debug_off();
-
- debug_log.cookie = listener;
- debug_log.cookie_write = command_socket_write;
- debug_log.dst = L_DST_EXTRA;
-
- notify = htonl(FR_NOTIFY_UNBUFFERED);
-
- /*
- * Tell radmin to go into unbuffered mode.
- */
- (void) fr_conduit_write(listener->fd, FR_CONDUIT_NOTIFY, ¬ify, sizeof(notify));
-
- return CMD_OK;
-}
-#endif
-
-static int command_debug_file(rad_listen_t *listener, int argc, char *argv[])
-{
- if (rad_debug_lvl && default_log.dst == L_DST_STDOUT) {
- cprintf_error(listener, "Cannot redirect debug logs to a file when already in debugging mode.\n");
- return -1;
- }
-
- if ((argc > 0) && (strchr(argv[0], FR_DIR_SEP) != NULL)) {
- cprintf_error(listener, "Cannot direct debug logs to absolute path.\n");
- return -1;
- }
-
- if (argc == 0) {
- command_debug_off();
- return CMD_OK;
- }
-
- /*
- * Disable logging while we're mucking with the buffer.
- */
- command_debug_off();
-
- /*
- * This looks weird, but it's here to avoid locking
- * a mutex for every log message.
- */
- memset(debug_log_file_buffer, 0, sizeof(debug_log_file_buffer));
-
- /*
- * Debug files always go to the logging directory.
- */
- snprintf(debug_log_file_buffer, sizeof(debug_log_file_buffer),
- "%s/%s", radlog_dir, argv[0]);
-
- debug_log.file = &debug_log_file_buffer[0];
- debug_log.dst = L_DST_FILES;
-
- INFO("Global debug log set to \"%s\"", debug_log.file);
-
- return CMD_OK;
-}
-
-static int command_debug_condition(rad_listen_t *listener, int argc, char *argv[])
-{
- int i;
- char const *error;
- ssize_t slen = 0;
- fr_cond_t *new_condition = NULL;
- char *p, buffer[1024];
-
- /*
- * Disable it.
- */
- if (argc == 0) {
- TALLOC_FREE(debug_condition);
- debug_condition = NULL;
- return CMD_OK;
- }
-
- if (!((argc == 1) &&
- ((argv[0][0] == '"') || (argv[0][0] == '\'')))) {
- p = buffer;
- *p = '\0';
- for (i = 0; i < argc; i++) {
- size_t len;
-
- len = strlcpy(p, argv[i], buffer + sizeof(buffer) - p);
- p += len;
- *(p++) = ' ';
- *p = '\0';
- }
-
- } else {
- /*
- * Backwards compatibility. De-escape the string.
- */
- char quote;
- char *q;
-
- p = argv[0];
- q = buffer;
-
- quote = *(p++);
-
- while (true) {
- if (!*p) {
- error = "Unexpected end of string";
- slen = -strlen(argv[0]);
- p = argv[0];
-
- goto parse_error;
- }
-
- if (*p == quote) {
- if (p[1]) {
- error = "Unexpected text after end of string";
- slen = -(p - argv[0]);
- p = argv[0];
-
- goto parse_error;
- }
- *q = '\0';
- break;
- }
-
- if (*p == '\\') {
- *(q++) = p[1];
- p += 2;
- continue;
- }
-
- *(q++) = *(p++);
- }
- }
-
- p = buffer;
-
- slen = fr_cond_tokenize(NULL, NULL, p, &new_condition, &error, FR_COND_ONE_PASS);
- if (slen <= 0) {
- char *spaces, *text;
-
- parse_error:
- fr_canonicalize_error(NULL, &spaces, &text, slen, p);
-
- ERROR("Parse error in condition");
- ERROR("%s", p);
- ERROR("%s^ %s", spaces, error);
-
- cprintf_error(listener, "Parse error in condition \"%s\": %s\n", p, error);
-
- talloc_free(spaces);
- talloc_free(text);
- return CMD_FAIL;
- }
-
- /*
- * Delete old condition.
- *
- * This is thread-safe because the condition is evaluated
- * in the main server thread, along with this code.
- */
- TALLOC_FREE(debug_condition);
- debug_condition = new_condition;
-
- return CMD_OK;
-}
-
-#ifdef HAVE_GPERFTOOLS_PROFILER_H
-static char profiler_log_buffer[1024];
-/** Start the gperftools profiler
- *
- */
-static int command_profiler_cpu_start(rad_listen_t *listener, int argc, char *argv[])
-{
- struct ProfilerState state;
-
- ProfilerGetCurrentState(&state);
-
- if (argc == 0) {
- cprintf_error(listener, "Need filename for profiler to write to.\n");
- return -1;
- }
-
- if ((argc > 0) && (strchr(argv[0], FR_DIR_SEP) != NULL)) {
- cprintf_error(listener, "Profiler file must be a relative path.\n");
- return -1;
- }
-
- /*
- * We get an error if we don't stop the current
- * profiler first.
- */
- if (state.enabled) {
- ProfilerFlush();
- ProfilerStop();
- }
-
- /*
- * Profiler files always go to the logging directory.
- */
- snprintf(profiler_log_buffer, sizeof(profiler_log_buffer),
- "%s/%s", radlog_dir, argv[0]);
-
- errno = 0;
- if (ProfilerStart(profiler_log_buffer) == 0) {
- cprintf_error(listener, "Failed enabling profiler: %s\n",
- errno ? fr_syserror(errno) : "unknown error");
- return -1;
- }
-
- return CMD_OK;
-}
-
-/** Stop the gperftools cpu profiler
- *
- */
-static int command_profiler_cpu_stop(UNUSED rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[])
-{
- ProfilerFlush();
- ProfilerStop();
-
- return CMD_OK;
-}
-
-/** Show gperftools cpu profiler output file
- *
- */
-static int command_profiler_cpu_show_file(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[])
-{
- struct ProfilerState state;
-
- ProfilerGetCurrentState(&state);
-
- if (!state.enabled) {
- cprintf_error(listener, "Profiler not enabled.\n");
- return -1;
- }
-
- cprintf(listener, "%s\n", state.profile_name);
-
- return CMD_OK;
-}
-
-/** Show gperftools cpu profiler samples collected
- *
- */
-static int command_profiler_cpu_show_samples(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[])
-{
- struct ProfilerState state;
-
- ProfilerGetCurrentState(&state);
-
- if (!state.enabled) {
- cprintf_error(listener, "Profiler not enabled.\n");
- return -1;
- }
-
- cprintf(listener, "%i\n", state.samples_gathered);
-
- return CMD_OK;
-}
-
-/** Show gperftools cpu profiler start_time
- *
- */
-static int command_profiler_cpu_show_start_time(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[])
-{
- char buffer[128];
- struct ProfilerState state;
-
- ProfilerGetCurrentState(&state);
-
- if (!state.enabled) {
- cprintf_error(listener, "Profiler not enabled.\n");
- return -1;
- }
-
- CTIME_R(&state.start_time, buffer, sizeof(buffer));
- cprintf(listener, "%s", buffer);
-
- return CMD_OK;
-}
-
-/** Show gperftools cpu profiler status
- *
- */
-static int command_profiler_cpu_show_status(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[])
-{
- struct ProfilerState state;
-
- ProfilerGetCurrentState(&state);
-
- if (state.enabled) {
- cprintf(listener, "running\n");
- } else {
- cprintf(listener, "stopped\n");
- }
-
- return CMD_OK;
-}
-#endif
-
-static int command_show_debug_condition(rad_listen_t *listener,
- UNUSED int argc, UNUSED char *argv[])
-{
- char buffer[1024];
-
- if (!debug_condition) {
- cprintf(listener, "\n");
- return CMD_OK;
- }
-
- cond_snprint(buffer, sizeof(buffer), debug_condition);
-
- cprintf(listener, "%s\n", buffer);
- return CMD_OK;
-}
-
-
-static int command_show_debug_file(rad_listen_t *listener,
- UNUSED int argc, UNUSED char *argv[])
-{
- if (!debug_log.file) return CMD_FAIL;
-
- cprintf(listener, "%s\n", debug_log.file);
- return CMD_OK;
-}
-
-
-static int command_show_debug_level_global(rad_listen_t *listener,
- UNUSED int argc, UNUSED char *argv[])
-{
- cprintf(listener, "%d\n", rad_debug_lvl);
- return CMD_OK;
-}
-
-static int command_show_debug_level_request(rad_listen_t *listener,
- UNUSED int argc, UNUSED char *argv[])
-{
- cprintf(listener, "%d\n", req_debug_lvl);
- return CMD_OK;
-}
-
-static RADCLIENT *get_client(rad_listen_t *listener, int argc, char *argv[])
-{
- RADCLIENT *client;
- fr_ipaddr_t ipaddr;
- int myarg;
- int proto = IPPROTO_UDP;
- RADCLIENT_LIST *list = NULL;
-
- if (argc < 1) {
- cprintf_error(listener, "Must specify <ipaddr>\n");
- return NULL;
- }
-
- /*
- * First arg is IP address.
- */
- if (fr_inet_hton(&ipaddr, AF_UNSPEC, argv[0], false) < 0) {
- cprintf_error(listener, "Failed parsing IP address; %s\n",
- fr_strerror());
- return NULL;
- }
- myarg = 1;
-
- while (myarg < argc) {
- if (strcmp(argv[myarg], "udp") == 0) {
- proto = IPPROTO_UDP;
- myarg++;
- continue;
- }
-
-#ifdef WITH_TCP
- if (strcmp(argv[myarg], "tcp") == 0) {
- proto = IPPROTO_TCP;
- myarg++;
- continue;
- }
-#endif
-
- if (strcmp(argv[myarg], "listen") == 0) {
- uint16_t server_port;
- fr_ipaddr_t server_ipaddr;
-
- if ((argc - myarg) < 2) {
- cprintf_error(listener, "Must specify listen <ipaddr> <port>\n");
- return NULL;
- }
-
- if (fr_inet_hton(&server_ipaddr, ipaddr.af, argv[myarg + 1], false) < 0) {
- cprintf_error(listener, "Failed parsing IP address; %s\n",
- fr_strerror());
- return NULL;
- }
-
- server_port = atoi(argv[myarg + 2]);
-
- list = listener_find_client_list(&server_ipaddr, server_port, proto);
- if (!list) {
- cprintf_error(listener, "No such listener %s %s\n", argv[myarg + 1], argv[myarg + 2]);
- return NULL;
- }
- myarg += 3;
- continue;
- }
-
- cprintf_error(listener, "Unknown argument %s.\n", argv[myarg]);
- return NULL;
- }
-
- client = client_find(list, &ipaddr, proto);
- if (!client) {
- cprintf_error(listener, "No such client\n");
- return NULL;
- }
-
- return client;
-}
-
-static int command_show_listener_enabled(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[])
-{
- if (main_config.drop_requests) {
- cprintf(listener, "no\n");
- } else {
- cprintf(listener, "yes\n");
- }
-
- return CMD_OK;
-}
-
-/*
- * For encode/decode stuff
- */
-static int null_socket_dencode(UNUSED rad_listen_t *listener, UNUSED REQUEST *request)
-{
- return 0;
-}
-
-static int null_socket_send(UNUSED rad_listen_t *listener, REQUEST *request)
-{
- fr_cursor_t cursor;
- char *output_file;
- FILE *fp;
-
- output_file = request_data_reference(request, (void *)null_socket_send, 0);
- if (!output_file) {
- ERROR("No output file for injected packet %" PRIu64 "", request->number);
- return 0;
- }
-
- fp = fopen(output_file, "w");
- if (!fp) {
- ERROR("Failed to send injected file to %s: %s", output_file, fr_syserror(errno));
- return 0;
- }
-
- if (request->reply->code != 0) {
- char const *what = "reply";
- VALUE_PAIR *vp;
- char buffer[1024];
-
- if (request->reply->code < FR_MAX_PACKET_CODE) {
- what = fr_packet_codes[request->reply->code];
- }
-
- fprintf(fp, "%s\n", what);
-
- if (rad_debug_lvl) {
- RDEBUG("Injected %s packet to host %s port 0 code=%d, id=%d", what,
- inet_ntop(request->reply->src_ipaddr.af,
- &request->reply->src_ipaddr.addr,
- buffer, sizeof(buffer)),
- request->reply->code, request->reply->id);
- }
-
- RINDENT();
- for (vp = fr_cursor_init(&cursor, &request->reply->vps);
- vp;
- vp = fr_cursor_next(&cursor)) {
- fr_pair_snprint(buffer, sizeof(buffer), vp);
- fprintf(fp, "%s\n", buffer);
- RDEBUG("%s", buffer);
- }
- REXDENT();
- }
- fclose(fp);
-
- return 0;
-}
-
-static rad_listen_t *get_socket(rad_listen_t *listener, int argc,
- char *argv[], int *last)
-{
- rad_listen_t *sock;
- uint16_t port;
- int proto = IPPROTO_UDP;
- fr_ipaddr_t ipaddr;
-
- if (argc < 2) {
- cprintf_error(listener, "Must specify <ipaddr> <port> [udp|tcp]\n");
- return NULL;
- }
-
- if (fr_inet_hton(&ipaddr, AF_UNSPEC, argv[0], false) < 0) {
- cprintf_error(listener, "Failed parsing IP address; %s\n",
- fr_strerror());
- return NULL;
- }
-
- port = atoi(argv[1]);
-
- if (last) *last = 2;
- if (argc > 2) {
- if (strcmp(argv[2], "udp") == 0) {
- proto = IPPROTO_UDP;
- if (last) *last = 3;
- }
-#ifdef WITH_TCP
- if (strcmp(argv[2], "tcp") == 0) {
- proto = IPPROTO_TCP;
- if (last) *last = 3;
- }
-#endif
- }
-
- sock = listener_find_byipaddr(&ipaddr, port, proto);
- if (!sock) {
- cprintf_error(listener, "No such listen section\n");
- return NULL;
- }
-
- return sock;
-}
-
-
-static int command_inject_to(rad_listen_t *listener, int argc, char *argv[])
-{
- fr_command_socket_t *sock = listener->data;
- listen_socket_t *data;
- rad_listen_t *found;
-
- found = get_socket(listener, argc, argv, NULL);
- if (!found) {
- return 0;
- }
-
- data = found->data;
- sock->inject_listener = found;
- sock->dst_ipaddr = data->my_ipaddr;
- sock->dst_port = data->my_port;
-
- return CMD_OK;
-}
-
-static int command_inject_from(rad_listen_t *listener, int argc, char *argv[])
-{
- RADCLIENT *client;
- fr_command_socket_t *sock = listener->data;
-
- if (argc < 1) {
- cprintf_error(listener, "No <ipaddr> was given\n");
- return 0;
- }
-
- if (!sock->inject_listener) {
- cprintf_error(listener, "You must specify \"inject to\" before using \"inject from\"\n");
- return 0;
- }
-
- sock->src_ipaddr.af = AF_UNSPEC;
- if (fr_inet_hton(&sock->src_ipaddr, AF_UNSPEC, argv[0], false) < 0) {
- cprintf_error(listener, "Failed parsing IP address; %s\n",
- fr_strerror());
- return 0;
- }
-
- client = client_listener_find(sock->inject_listener, &sock->src_ipaddr,
- 0);
- if (!client) {
- cprintf_error(listener, "No such client %s\n", argv[0]);
- return 0;
- }
- sock->inject_client = client;
-
- return CMD_OK;
-}
-
-static int command_inject_file(rad_listen_t *listener, int argc, char *argv[])
-{
- static int inject_id = 0;
- int ret;
- bool filedone;
- fr_command_socket_t *sock = listener->data;
- rad_listen_t *fake;
- RADIUS_PACKET *packet;
- vp_cursor_t cursor;
- VALUE_PAIR *vp;
- FILE *fp;
- RAD_REQUEST_FUNP fun = NULL;
- char buffer[2048];
-
- if (argc < 2) {
- cprintf_error(listener, "You must specify <input-file> <output-file>\n");
- return 0;
- }
-
- if (!sock->inject_listener) {
- cprintf_error(listener, "You must specify \"inject to\" before using \"inject file\"\n");
- return 0;
- }
-
- if (!sock->inject_client) {
- cprintf_error(listener, "You must specify \"inject from\" before using \"inject file\"\n");
- return 0;
- }
-
- /*
- * Output files always go to the logging directory.
- */
- snprintf(buffer, sizeof(buffer), "%s/%s", radlog_dir, argv[1]);
-
- fp = fopen(argv[0], "r");
- if (!fp ) {
- cprintf_error(listener, "Failed opening %s: %s\n",
- argv[0], fr_syserror(errno));
- return 0;
- }
-
- ret = fr_pair_list_afrom_file(NULL, &vp, fp, &filedone);
- fclose(fp);
- if (ret < 0) {
- cprintf_error(listener, "Failed reading attributes from %s: %s\n",
- argv[0], fr_strerror());
- return 0;
- }
-
- fake = talloc(NULL, rad_listen_t);
- memcpy(fake, sock->inject_listener, sizeof(*fake));
-
- /*
- * Re-write the IO for the listener.
- */
- fake->encode = null_socket_dencode;
- fake->decode = null_socket_dencode;
- fake->send = null_socket_send;
-
- packet = fr_radius_alloc(NULL, false);
- packet->src_ipaddr = sock->src_ipaddr;
- packet->src_port = 0;
-
- packet->dst_ipaddr = sock->dst_ipaddr;
- packet->dst_port = sock->dst_port;
- packet->vps = vp;
- packet->id = inject_id++;
-
- if (fake->type == RAD_LISTEN_AUTH) {
- packet->code = FR_CODE_ACCESS_REQUEST;
- fun = rad_authenticate;
-
- } else {
-#ifdef WITH_ACCOUNTING
- packet->code = FR_CODE_ACCOUNTING_REQUEST;
- fun = rad_accounting;
-#else
- cprintf_error(listener, "This server was built without accounting support.\n");
- fr_radius_free(&packet);
- talloc_free(fake);
- return 0;
-#endif
- }
-
- if (rad_debug_lvl) {
- DEBUG("Injecting %s packet from host %s port 0 code=%d, id=%d",
- fr_packet_codes[packet->code],
- inet_ntop(packet->src_ipaddr.af,
- &packet->src_ipaddr.addr,
- buffer, sizeof(buffer)),
- packet->code, packet->id);
-
- for (vp = fr_cursor_init(&cursor, &packet->vps);
- vp;
- vp = fr_cursor_next(&cursor)) {
- fr_pair_snprint(buffer, sizeof(buffer), vp);
- DEBUG("\t%s", buffer);
- }
-
- WARN("INJECTION IS LEAKING MEMORY!");
- }
-
- if (!request_receive(NULL, fake, packet, sock->inject_client, fun)) {
- cprintf_error(listener, "Failed to inject request. See log file for details\n");
- fr_radius_free(&packet);
- talloc_free(fake);
- return 0;
- }
-
-#if 0
- /*
- * Remember what the output file is, and remember to
- * delete the fake listener when done.
- */
- request_data_add(request, null_socket_send, 0, talloc_typed_strdup(NULL, buffer), true, false, false);
- request_data_add(request, null_socket_send, 1, fake, true, false, false);
-
-#endif
-
- return CMD_OK;
-}
-
-
-static fr_command_table_t command_table_inject[] = {
- { "to", FR_WRITE,
- "inject to <ipaddr> <port> - Inject packets to the destination IP and port.",
- command_inject_to, NULL },
-
- { "from", FR_WRITE,
- "inject from <ipaddr> - Inject packets as if they came from <ipaddr>",
- command_inject_from, NULL },
-
- { "file", FR_WRITE,
- "inject file <input-file> <output-file> - Inject packet from <input-file>, with results sent to <output-file>",
- command_inject_file, NULL },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-static fr_command_table_t command_table_debug_level[] = {
- { "global", FR_WRITE,
- "debug level global <number> - Set debug level for global server events and requests written to the main server log.",
- command_debug_level_global, NULL },
-
- { "request", FR_WRITE,
- "debug level request <number> - Set debug level for requests written to debug file or debug socket.",
- command_debug_level_request, NULL },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-static fr_command_table_t command_table_debug[] = {
- { "condition", FR_WRITE,
- "debug condition [condition] - Enable debugging for requests matching [condition]",
- command_debug_condition, NULL },
-
- { "file", FR_WRITE,
- "debug file [filename] - Send all request debug output to [filename]",
- command_debug_file, NULL },
-
-#if defined(HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN)
- { "socket", FR_WRITE,
- "debug socket [on|off] - Send all request debug output to radmin socket.",
- command_debug_socket, NULL },
-#endif
-
- { "level", FR_READ,
- "debug level <command> - Set debug levels",
- NULL, command_table_debug_level },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-#ifdef HAVE_GPERFTOOLS_PROFILER_H
-/** Commands to control the gperftools profiler
- *
- */
-static fr_command_table_t command_table_profiler_cpu[] = {
- { "start", FR_WRITE,
- "profiler cpu start <filename> - Start gperftools cpu profiler, writing output to filename",
- command_profiler_cpu_start, NULL },
-
- { "stop", FR_WRITE,
- "profiler cpu stop - Stop gperftools cpu profiler, and flush results to disk",
- command_profiler_cpu_stop, NULL },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-static fr_command_table_t command_table_profiler[] = {
- { "cpu", FR_WRITE,
- "profiler cpu <command> do sub-command of cpu profiler",
- NULL, command_table_profiler_cpu },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-#endif
-
-static fr_command_table_t command_table_show_debug_level[] = {
- { "global", FR_WRITE,
- "show debug level global - Show debug level for global server events and requests written to the main server log. Higher is more debugging.",
- command_show_debug_level_global, NULL },
-
- { "request", FR_WRITE,
- "show debug level request - Show debug level for requests written to debug file or debug socket. Higher is more debugging",
- command_show_debug_level_request, NULL },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-
-static fr_command_table_t command_table_show_debug[] = {
- { "condition", FR_READ,
- "show debug condition - Shows current debugging condition.",
- command_show_debug_condition, NULL },
-
- { "file", FR_READ,
- "show debug file - Shows current debugging file.",
- command_show_debug_file, NULL },
-
- { "level", FR_READ,
- "show debug level <command> - Shows current global or request debug level.",
- NULL, command_table_show_debug_level },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-static fr_command_table_t command_table_show_module[] = {
- { "config", FR_READ,
- "show module config <module> - show configuration for given module",
- command_show_module_config, NULL },
- { "flags", FR_READ,
- "show module flags <module> - show other module properties",
- command_show_module_flags, NULL },
- { "list", FR_READ,
- "show module list - shows list of loaded modules",
- command_show_modules, NULL },
- { "methods", FR_READ,
- "show module methods <module> - show sections where <module> may be used",
- command_show_module_methods, NULL },
- { "status", FR_READ,
- "show module status <module> - show the module status",
- command_show_module_status, NULL },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-static fr_command_table_t command_table_show_client[] = {
- { "list", FR_READ,
- "show client list - shows list of global clients",
- command_show_clients, NULL },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-static fr_command_table_t command_table_show_listener[] = {
- { "enabled", FR_READ,
- "show listener all enabled - shows whether the server is configured to accept packets",
- command_show_listener_enabled, NULL},
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-static fr_command_table_t command_table_show_listeners[] = {
- { "all", FR_READ,
- "show listener all - shows global listener state",
- NULL, command_table_show_listener },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-#ifdef HAVE_GPERFTOOLS_PROFILER_H
-static fr_command_table_t command_table_show_profiler_cpu[] = {
- { "file", FR_WRITE,
- "show profiler cpu file - show where profile data is being written",
- command_profiler_cpu_show_file, NULL },
-
- { "samples", FR_WRITE,
- "show profiler cpu samples - show how many profiler samples have been collected",
- command_profiler_cpu_show_samples, NULL },
-
- { "start_time", FR_WRITE,
- "show profiler cpu start_time - show when profiling last started",
- command_profiler_cpu_show_start_time, NULL },
-
- { "status", FR_WRITE,
- "show profiler cpu status - show the current profiler state (running or stopped)",
- command_profiler_cpu_show_status, NULL },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-static fr_command_table_t command_table_show_profiler[] = {
- { "cpu", FR_WRITE,
- "show profiler cpu <command> do sub-command of cpu profiler",
- NULL, command_table_show_profiler_cpu },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-#endif
-
-static fr_command_table_t command_table_show[] = {
- { "client", FR_READ,
- "show client <command> - do sub-command of client",
- NULL, command_table_show_client },
- { "config", FR_READ,
- "show config <path> - shows the value of configuration option <path>",
- command_show_config, NULL },
- { "debug", FR_READ,
- "show debug <command> - show debug properties",
- NULL, command_table_show_debug },
- { "listener", FR_READ,
- "show listener <command> - do sub-command of listener",
- NULL, command_table_show_listeners },
-
-#ifndef NDEBUG
- { "memory-report", FR_READ,
- "show memory-report - show currently talloced memory",
- command_show_memory_report, NULL },
-#endif
-
- { "module", FR_READ,
- "show module <command> - do sub-command of module",
- NULL, command_table_show_module },
-
-#ifdef HAVE_GPERFTOOLS_PROFILER_H
- { "profiler", FR_READ,
- "show profiler <command> - do sub-command of profiler",
- NULL, command_table_show_profiler },
-#endif
-
- { "uptime", FR_READ,
- "show uptime - shows time at which server started",
- command_uptime, NULL },
- { "version", FR_READ,
- "show version - Prints version of the running server",
- command_show_version, NULL },
- { NULL, 0, NULL, NULL, NULL }
-};
-
-static int command_set_module_config(rad_listen_t *listener, int argc, char *argv[])
-{
- int i, rcode;
- CONF_PAIR *cp;
- CONF_SECTION *cs;
- module_instance_t *instance;
- CONF_PARSER const *variables;
- void *data;
-
- if (argc < 3) {
- cprintf_error(listener, "No module name or variable was given\n");
- return 0;
- }
-
- cs = cf_section_find(main_config.config, "modules", NULL);
- if (!cs) return 0;
-
- instance = module_find(cs, argv[0]);
- if (!instance) {
- cprintf_error(listener, "No such module \"%s\"\n", argv[0]);
- return 0;
- }
-
- variables = cf_section_parse_table(instance->dl_inst->conf);
- if (!variables) {
- cprintf_error(listener, "Cannot find configuration for module\n");
- return 0;
- }
-
- rcode = -1;
- for (i = 0; variables[i].name != NULL; i++) {
- /*
- * FIXME: Recurse into sub-types somehow...
- */
- if (FR_BASE_TYPE(variables[i].type) == FR_TYPE_SUBSECTION) continue;
-
- if (strcmp(variables[i].name, argv[1]) == 0) {
- rcode = i;
- break;
- }
- }
-
- if (rcode < 0) {
- cprintf_error(listener, "No such variable \"%s\"\n", argv[1]);
- return 0;
- }
-
- i = rcode; /* just to be safe */
-
- /*
- * It's not part of the dynamic configuration. The module
- * needs to re-parse && validate things.
- */
- if (variables[i].data) {
- cprintf_error(listener, "Variable cannot be dynamically updated\n");
- return 0;
- }
-
- data = ((char *) instance->dl_inst->data) + variables[i].offset;
-
- cp = cf_pair_find(instance->dl_inst->conf, argv[1]);
- if (!cp) return 0;
-
- /*
- * Replace the OLD value in the configuration file with
- * the NEW value.
- *
- * FIXME: Parse argv[2] depending on it's data type!
- * If it's a string, look for leading single/double quotes,
- * end then call tokenize functions???
- */
- cf_pair_replace(instance->dl_inst->conf, cp, argv[2]);
-
- rcode = cf_pair_parse(NULL, instance->dl_inst->conf, argv[1], variables[i].type, data, argv[2], T_DOUBLE_QUOTED_STRING);
- if (rcode < 0) {
- cprintf_error(listener, "Failed to parse value\n");
- return 0;
- }
-
- return CMD_OK;
-}
-
-static int command_set_module_status(rad_listen_t *listener, int argc, char *argv[])
-{
- CONF_SECTION *cs;
- module_instance_t *instance;
-
- if (argc < 2) {
- cprintf_error(listener, "No module name or status was given\n");
- return 0;
- }
-
- cs = cf_section_find(main_config.config, "modules", NULL);
- if (!cs) return 0;
-
- instance = module_find(cs, argv[0]);
- if (!instance) {
- cprintf_error(listener, "No such module \"%s\"\n", argv[0]);
- return 0;
- }
-
-
- if (strcmp(argv[1], "alive") == 0) {
- instance->force = false;
-
- } else if (strcmp(argv[1], "dead") == 0) {
- instance->code = RLM_MODULE_FAIL;
- instance->force = true;
-
- } else {
- int rcode;
-
- rcode = fr_str2int(mod_rcode_table, argv[1], -1);
- if (rcode < 0) {
- cprintf_error(listener, "Unknown status \"%s\"\n", argv[1]);
- return 0;
- }
-
- instance->code = rcode;
- instance->force = true;
- }
-
- return CMD_OK;
-}
-
-static int command_set_listener_enabled(rad_listen_t *listener, int argc, char *argv[])
-{
- if (argc < 1) {
- cprintf_error(listener, "No value was provided\n");
- return 0;
- }
-
- if ((strcmp(argv[0], "yes") == 0) || (strcmp(argv[0], "true") == 0)) {
- INFO("Server has resumed accepting requests");
- main_config.drop_requests = false;
- } else if ((strcmp(argv[0], "no") == 0) || (strcmp(argv[0], "false") == 0)) {
- INFO("Server is no longer accepting new requests");
- main_config.drop_requests = true;
- }
-
- return CMD_OK;
-}
-
-#ifdef WITH_STATS
-static char const *elapsed_names[8] = {
- "1us", "10us", "100us", "1ms", "10ms", "100ms", "1s", "10s"
-};
-
-#undef PU
-#ifdef WITH_STATS_64BIT
-#ifdef PRIu64
-#define PU "%" PRIu64
-#else
-#define PU "%lu"
-#endif
-#else
-#ifdef PRIu32
-#define PU "%" PRIu32
-#else
-#define PU "%u"
-#endif
-#endif
-
-static int command_print_stats(rad_listen_t *listener, fr_stats_t *stats,
- int auth, int server)
-{
- int i;
-
- cprintf(listener, "requests\t" PU "\n", stats->total_requests);
- cprintf(listener, "responses\t" PU "\n", stats->total_responses);
-
- if (auth) {
- cprintf(listener, "accepts\t\t" PU "\n",
- stats->total_access_accepts);
- cprintf(listener, "rejects\t\t" PU "\n",
- stats->total_access_rejects);
- cprintf(listener, "challenges\t" PU "\n",
- stats->total_access_challenges);
- }
-
- cprintf(listener, "dup\t\t" PU "\n", stats->total_dup_requests);
- cprintf(listener, "invalid\t\t" PU "\n", stats->total_invalid_requests);
- cprintf(listener, "malformed\t" PU "\n", stats->total_malformed_requests);
- cprintf(listener, "bad_authenticator\t" PU "\n", stats->total_bad_authenticators);
- cprintf(listener, "dropped\t\t" PU "\n", stats->total_packets_dropped);
- cprintf(listener, "unknown_types\t" PU "\n", stats->total_unknown_types);
-
- if (server) {
- cprintf(listener, "timeouts\t" PU "\n", stats->total_timeouts);
- }
-
- cprintf(listener, "last_packet\t%" PRId64 "\n", (int64_t) stats->last_packet);
- for (i = 0; i < 8; i++) {
- cprintf(listener, "elapsed.%s\t%u\n",
- elapsed_names[i], stats->elapsed[i]);
- }
-
- return CMD_OK;
-}
-
-static int command_stats_state(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[])
-{
- cprintf(listener, "states_created\t\t%" PRIu64 "\n", fr_state_entries_created(global_state));
- cprintf(listener, "states_timeout\t\t%" PRIu64 "\n", fr_state_entries_timeout(global_state));
- cprintf(listener, "states_tracked\t\t%" PRIu32 "\n", fr_state_entries_tracked(global_state));
-
- return CMD_OK;
-}
-
-#ifndef NDEBUG
-static int command_stats_memory(rad_listen_t *listener, int argc, char *argv[])
-{
- if (!main_config.talloc_memory_report) {
- cprintf(listener, "Memory debugging not enabled. To enable, pass -Ms when starting the server\n");
- return CMD_OK;
- }
-
- if (argc == 0) goto fail;
-
- if (strcmp(argv[0], "total") == 0) {
- cprintf(listener, "%zd\n", talloc_total_size(NULL));
- return CMD_OK;
- }
-
- if (strcmp(argv[0], "blocks") == 0) {
- cprintf(listener, "%zd\n", talloc_total_blocks(NULL));
- return CMD_OK;
- }
-
-fail:
- cprintf_error(listener, "Must use 'stats memory [blocks|total]'\n");
- return CMD_FAIL;
-}
-#endif
-
-#ifdef WITH_DETAIL
-static FR_NAME_NUMBER state_names[] = {
- { "unopened", STATE_UNOPENED },
- { "unlocked", STATE_UNLOCKED },
- { "processing", STATE_PROCESSING },
-
- { "header", STATE_HEADER },
- { "vps", STATE_VPS },
- { "queued", STATE_QUEUED },
- { "running", STATE_RUNNING },
- { "no-reply", STATE_NO_REPLY },
- { "replied", STATE_REPLIED },
-
- { NULL, 0 }
-};
-
-static int command_stats_detail(rad_listen_t *listener, int argc, char *argv[])
-{
- rad_listen_t *this;
- listen_detail_t *data, *needle;
- struct stat buf;
-
- if (argc == 0) {
- cprintf_error(listener, "Must specify <filename>\n");
- return 0;
- }
-
- data = NULL;
- for (this = main_config.listen; this != NULL; this = this->next) {
- if (this->type != RAD_LISTEN_DETAIL) continue;
-
- needle = this->data;
- if (!strcmp(argv[0], needle->filename)) {
- data = needle;
- break;
- }
- }
-
- if (!data) {
- cprintf_error(listener, "No detail file listener\n");
- return 0;
- }
-
- cprintf(listener, "state\t%s\n",
- fr_int2str(state_names, data->file_state, "?"));
-
- if ((data->file_state == STATE_UNOPENED) ||
- (data->file_state == STATE_UNLOCKED)) {
- return CMD_OK;
- }
-
- /*
- * Race conditions: file might not exist.
- */
- if (stat(data->filename_work, &buf) < 0) {
- cprintf(listener, "packets\t0\n");
- cprintf(listener, "tries\t0\n");
- cprintf(listener, "offset\t0\n");
- cprintf(listener, "size\t0\n");
- return CMD_OK;
- }
-
- cprintf(listener, "packets\t%d\n", data->packets);
- cprintf(listener, "tries\t%d\n", data->tries);
- cprintf(listener, "offset\t%u\n", (unsigned int) data->offset);
- cprintf(listener, "size\t%u\n", (unsigned int) buf.st_size);
-
- return CMD_OK;
-}
-#endif
-
-static int command_stats_client(rad_listen_t *listener, int argc, char *argv[])
-{
- bool auth = true;
- fr_stats_t *stats;
- RADCLIENT *client, fake;
-
- if (argc < 1) {
- cprintf_error(listener, "Must specify [auth/acct]\n");
- return 0;
- }
-
- if (argc == 1) {
- /*
- * Global statistics.
- */
- fake.auth = radius_auth_stats;
-#ifdef WITH_ACCOUNTING
- fake.acct = radius_acct_stats;
-#endif
-#ifdef WITH_COA
- fake.coa = radius_coa_stats;
- fake.dsc = radius_dsc_stats;
-#endif
- client = &fake;
-
- } else {
- /*
- * Per-client statistics.
- */
- client = get_client(listener, argc - 1, argv + 1);
- if (!client) return 0;
- }
-
- if (strcmp(argv[0], "auth") == 0) {
- auth = true;
- stats = &client->auth;
-
- } else if (strcmp(argv[0], "acct") == 0) {
-#ifdef WITH_ACCOUNTING
- auth = false;
- stats = &client->acct;
-#else
- cprintf_error(listener, "This server was built without accounting support.\n");
- return 0;
-#endif
-
- } else if (strcmp(argv[0], "coa") == 0) {
-#ifdef WITH_COA
- auth = false;
- stats = &client->coa;
-#else
- cprintf_error(listener, "This server was built without CoA support.\n");
- return 0;
-#endif
-
- } else if (strcmp(argv[0], "disconnect") == 0) {
-#ifdef WITH_COA
- auth = false;
- stats = &client->dsc;
-#else
- cprintf_error(listener, "This server was built without CoA support.\n");
- return 0;
-#endif
-
- } else {
- cprintf_error(listener, "Unknown statistics type\n");
- return 0;
- }
-
- /*
- * Global results for all client.
- */
- if (argc == 1) {
-#ifdef WITH_ACCOUNTING
- if (!auth) {
- return command_print_stats(listener,
- &radius_acct_stats, auth, 0);
- }
-#endif
- return command_print_stats(listener, &radius_auth_stats, auth, 0);
- }
-
- return command_print_stats(listener, stats, auth, 0);
-}
-
-
-static int command_stats_socket(rad_listen_t *listener, int argc, char *argv[])
-{
- bool auth = true;
- rad_listen_t *sock;
-
- sock = get_socket(listener, argc, argv, NULL);
- if (!sock) return 0;
-
- if (sock->type != RAD_LISTEN_AUTH) auth = false;
-
- return command_print_stats(listener, &sock->stats, auth, 0);
-}
-#endif /* WITH_STATS */
-
-
-#ifdef WITH_DYNAMIC_CLIENTS
-static int command_add_client_file(rad_listen_t *listener, int argc, char *argv[])
-{
- RADCLIENT *c;
-
- if (argc < 1) {
- cprintf_error(listener, "<file> is required\n");
- return 0;
- }
-
- /*
- * Read the file and generate the client.
- */
- c = client_read(argv[0], NULL, false);
- if (!c) {
- cprintf_error(listener, "Unknown error reading client file.\n");
- return 0;
- }
-
- if (!client_add(NULL, c)) {
- cprintf_error(listener, "Unknown error inserting new client.\n");
- client_free(c);
- return 0;
- }
-
- return CMD_OK;
-}
-
-
-static int command_del_client(rad_listen_t *listener, int argc, char *argv[])
-{
- RADCLIENT *client;
-
- client = get_client(listener, argc, argv);
- if (!client) return 0;
-
- if (!client->dynamic) {
- cprintf_error(listener, "Client %s was not dynamically defined.\n", argv[0]);
- return 0;
- }
-
- /*
- * DON'T delete it. Instead, mark it as "dead now". The
- * next time we receive a packet for the client, it will
- * be deleted.
- *
- * If we don't receive a packet from it, the client
- * structure will stick around for a while. Oh well...
- */
- client->lifetime = 1;
-
- return CMD_OK;
-}
-
-
-static fr_command_table_t command_table_del_client[] = {
- { "ipaddr", FR_WRITE,
- "del client ipaddr <ipaddr> [udp|tcp] [listen <ipaddr> <port>] - Delete a dynamically created client",
- command_del_client, NULL },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-
-static fr_command_table_t command_table_del[] = {
- { "client", FR_WRITE,
- "del client <command> - Delete client configuration commands",
- NULL, command_table_del_client },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-
-static fr_command_table_t command_table_add_client[] = {
- { "file", FR_WRITE,
- "add client file <filename> - Add new client definition from <filename>",
- command_add_client_file, NULL },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-
-static fr_command_table_t command_table_add[] = {
- { "client", FR_WRITE,
- "add client <command> - Add client configuration commands",
- NULL, command_table_add_client },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-#endif
-
-static fr_command_table_t command_table_set_module[] = {
- { "config", FR_WRITE,
- "set module config <module> variable value - set configuration for <module>",
- command_set_module_config, NULL },
-
- { "status", FR_WRITE,
- "set module status <module> [alive|...] - set the module status to be alive (operating normally), or force a particular code (ok,fail, etc.)",
- command_set_module_status, NULL },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-static fr_command_table_t command_table_set_listener[] = {
- { "enabled", FR_WRITE,
- "set listener all enabled <bool> - enable or disable all listeners",
- command_set_listener_enabled, NULL },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-static fr_command_table_t command_table_set_listeners[] = {
- { "all", FR_WRITE,
- "set listener all - change state of all listeners",
- NULL, command_table_set_listener },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-static fr_command_table_t command_table_set[] = {
- { "module", FR_WRITE,
- "set module <command> - set module commands",
- NULL, command_table_set_module },
- { "listener", FR_WRITE,
- "set listener <command> - set listener commands",
- NULL, command_table_set_listeners },
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-
-#ifdef WITH_STATS
-static fr_command_table_t command_table_stats[] = {
- { "client", FR_READ,
- "stats client [auth/acct] <ipaddr> [udp|tcp] [listen <ipaddr> <port>] "
- "- show statistics for given client, or for all clients (auth or acct)",
- command_stats_client, NULL },
-
-#ifdef WITH_DETAIL
- { "detail", FR_READ,
- "stats detail <filename> - show statistics for the given detail file",
- command_stats_detail, NULL },
-#endif
-
- { "state", FR_READ,
- "stats state - show statistics for states",
- command_stats_state, NULL },
-
- { "socket", FR_READ,
- "stats socket <ipaddr> <port> [udp|tcp] "
- "- show statistics for given socket",
- command_stats_socket, NULL },
-
-#ifndef NDEBUG
- { "memory", FR_READ,
- "stats memory [blocks|total] - show statistics on used memory",
- command_stats_memory, NULL },
-#endif
-
- { NULL, 0, NULL, NULL, NULL }
-};
-#endif
-
-static fr_command_table_t command_table[] = {
-#ifdef WITH_DYNAMIC_CLIENTS
- { "add", FR_WRITE, NULL, NULL, command_table_add },
-#endif
- { "debug", FR_WRITE,
- "debug <command> - debugging commands",
- NULL, command_table_debug },
-#ifdef WITH_DYNAMIC_CLIENTS
- { "del", FR_WRITE, NULL, NULL, command_table_del },
-#endif
- { "hup", FR_WRITE,
- "hup [module] - sends a HUP signal to the server, or optionally to one module",
- command_hup, NULL },
- { "inject", FR_WRITE,
- "inject <command> - commands to inject packets into a running server",
- NULL, command_table_inject },
-
-#ifdef HAVE_GPERFTOOLS_PROFILER_H
- { "profiler", FR_WRITE,
- "profiler <command> - commands to alter the state of the gperftools profiler",
- NULL, command_table_profiler },
-#endif
- { "reconnect", FR_READ,
- "reconnect - reconnect to a running server",
- NULL, NULL }, /* just here for "help" */
- { "terminate", FR_WRITE,
- "terminate - terminates the server, and cause it to exit",
- command_terminate, NULL },
- { "set", FR_WRITE, NULL, NULL, command_table_set },
- { "show", FR_READ, NULL, NULL, command_table_show },
-#ifdef WITH_STATS
- { "stats", FR_READ, NULL, NULL, command_table_stats },
-#endif
-
- { NULL, 0, NULL, NULL, NULL }
-};
-
-
-static int _command_socket_free(fr_command_socket_t *cmd)
-{
- /*
- * If it's a TCP socket, don't do anything.
- */
- if (cmd->magic != COMMAND_SOCKET_MAGIC) return 0;
-
- if (!cmd->copy) return 0;
- unlink(cmd->copy);
-
- return 0;
-}
-
-
-/*
- * Parse the unix domain sockets.
- *
- * FIXME: TCP + SSL, after RadSec is in.
- */
-static int command_socket_parse_unix(CONF_SECTION *cs, rad_listen_t *this)
-{
- fr_command_socket_t *sock;
-
- sock = this->data;
- talloc_set_destructor(sock, _command_socket_free);
-
- if (cf_section_rules_push(cs, command_config) < 0) return -1;
- if (cf_section_parse(sock, sock, cs) < 0) return -1;
-
- /*
- * Can't get uid or gid of connecting user, so can't do
- * peercred authentication.
- */
-#ifndef HAVE_GETPEEREID
- if (sock->peercred && (sock->uid_name || sock->gid_name)) {
- ERROR("System does not support uid or gid authentication for sockets");
- return -1;
- }
-#endif
-
- sock->magic = COMMAND_SOCKET_MAGIC;
- sock->copy = NULL;
- if (sock->path) sock->copy = talloc_typed_strdup(sock, sock->path);
-
- if (sock->gid_name) {
- if (rad_getgid(cs, &sock->gid, sock->gid_name) < 0) {
- ERROR("Failed resolving gid of group %s: %s", sock->gid_name, fr_strerror());
- return -1;
- }
- } else {
- sock->gid = -1;
- }
-
- if (!sock->mode_name) {
- sock->co.mode = FR_READ;
- } else {
- sock->co.mode = fr_str2int(mode_names, sock->mode_name, 0);
- if (!sock->co.mode) {
- ERROR("Invalid mode name \"%s\"", sock->mode_name);
- return -1;
- }
- }
-
- return 0;
-}
-
-static int command_socket_open_unix(UNUSED CONF_SECTION *cs, rad_listen_t *this)
-{
- fr_command_socket_t *sock;
-
- sock = this->data;
-
- this->fd = fr_server_domain_socket(sock->path, sock->gid, sock->blocking);
- if (this->fd < 0) {
- ERROR("%s", fr_strerror());
- if (sock->copy) TALLOC_FREE(sock->copy);
- return -1;
- }
-
- return 0;
-}
-
-static int command_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
-{
- int rcode;
- CONF_PAIR const *cp;
- listen_socket_t *sock;
-
- cp = cf_pair_find(cs, "socket");
- if (cp) return command_socket_parse_unix(cs, this);
-
- rcode = common_socket_parse(cs, this);
- if (rcode < 0) return -1;
-
-#ifdef WITH_TLS
- if (this->tls) {
- cf_log_err(cs,
- "TLS is not supported for control sockets");
- return -1;
- }
-#endif
-
- sock = this->data;
- if (sock->proto != IPPROTO_TCP) {
- cf_log_err(cs,
- "UDP is not supported for control sockets");
- return -1;
- }
-
- return 0;
-}
-
-static int command_socket_open(CONF_SECTION *cs, rad_listen_t *this)
-{
- CONF_PAIR const *cp;
-
- cp = cf_pair_find(cs, "socket");
- if (cp) return command_socket_open_unix(cs, this);
-
- return common_socket_open(cs, this);
-}
-
-static int command_socket_print(rad_listen_t const *this, char *buffer, size_t bufsize)
-{
- fr_command_socket_t *sock = this->data;
-
- if (sock->magic != COMMAND_SOCKET_MAGIC) {
- return common_socket_print(this, buffer, bufsize);
- }
-
- snprintf(buffer, bufsize, "command file %s", sock->path);
- return 1;
-}
-
-
-/*
- * String split routine. Splits an input string IN PLACE
- * into pieces, based on spaces.
- */
-static int dict_str_to_argvX(char *str, char **argv, int max_argc)
-{
- int argc = 0;
-
- while (*str) {
- if (argc >= max_argc) return argc;
-
- /*
- * Chop out comments early.
- */
- if (*str == '#') {
- *str = '\0';
- break;
- }
-
- while ((*str == ' ') ||
- (*str == '\t') ||
- (*str == '\r') ||
- (*str == '\n')) *(str++) = '\0';
-
- if (!*str) return argc;
-
- argv[argc++] = str;
-
- if ((*str == '\'') || (*str == '"')) {
- char quote = *str;
- char *p = str + 1;
-
- while (true) {
- if (!*p) return -1;
-
- if (*p == quote) {
- str = p + 1;
- break;
- }
-
- /*
- * Handle \" and nothing else.
- */
- if (*p == '\\') {
- p += 2;
- continue;
- }
-
- p++;
- }
- }
-
- while (*str &&
- (*str != ' ') &&
- (*str != '\t') &&
- (*str != '\r') &&
- (*str != '\n')) str++;
- }
-
- return argc;
-}
-
-static void print_help(rad_listen_t *listener, int argc, char *argv[],
- fr_command_table_t *table, int recursive)
-{
- int i;
-
- /* this should never happen, but if it does then just return gracefully */
- if (!table) return;
-
- for (i = 0; table[i].command != NULL; i++) {
- if (argc > 0) {
- if (strcmp(table[i].command, argv[0]) == 0) {
- if (table[i].table) {
- print_help(listener, argc - 1, argv + 1, table[i].table, recursive);
- } else {
- if (table[i].help) {
- cprintf(listener, "%s\n", table[i].help);
- }
- }
- return;
- }
-
- continue;
- }
-
- if (table[i].help) {
- cprintf(listener, "%s\n",
- table[i].help);
- } else {
- cprintf(listener, "%s <command> - do sub-command of %s\n",
- table[i].command, table[i].command);
- }
-
- if (recursive && table[i].table) {
- print_help(listener, 0, NULL, table[i].table, recursive);
- }
- }
-}
-
-#define MAX_ARGV (16)
-
-/*
- * Check if an incoming request is "ok"
- *
- * It takes packets, not requests. It sees if the packet looks
- * OK. If so, it does a number of sanity checks on it.
- */
-static int command_domain_recv_co(rad_listen_t *listener, fr_cs_buffer_t *co)
-{
- int i;
- uint32_t status;
- ssize_t r, len;
- int argc;
- fr_conduit_type_t conduit;
- char *my_argv[MAX_ARGV], **argv;
- fr_command_table_t *table;
- uint8_t *command;
-
- r = fr_conduit_drain(listener->fd, &conduit, co->buffer, sizeof(co->buffer) - 1, &command, &co->offset);
- if ((r < 0) && ((errno == EINTR) || (errno == EAGAIN))) return 0;
-
- if (r <= 0) {
- do_close:
- command_close_socket(listener);
- return 0;
- }
-
- /*
- * We need more data. Go read it.
- */
- if (conduit == FR_CONDUIT_WANT_MORE) {
- return 0;
- }
-
- status = 0;
- command[r] = '\0';
- DEBUG("radmin> %s", command);
-
- argc = dict_str_to_argvX((char *) command, my_argv, MAX_ARGV);
- if (argc == 0) goto do_next; /* empty strings are OK */
-
- if (argc < 0) {
- cprintf_error(listener, "Failed parsing command '%s'.\n",
- command);
- goto do_next;
- }
-
- argv = my_argv;
-
- for (len = 0; len <= co->offset; len++) {
- if (command[len] < 0x20) {
- command[len] = '\0';
- break;
- }
- }
-
- /*
- * Hard-code exit && quit.
- */
- if ((strcmp(argv[0], "exit") == 0) ||
- (strcmp(argv[0], "quit") == 0)) goto do_close;
-
- table = command_table;
- retry:
- len = 0;
- for (i = 0; table[i].command != NULL; i++) {
- if (strcmp(table[i].command, argv[0]) == 0) {
- /*
- * Check permissions.
- */
- if (((co->mode & FR_WRITE) == 0) &&
- ((table[i].mode & FR_WRITE) != 0)) {
- cprintf_error(listener, "You do not have write permission. See \"mode = rw\" in the \"listen\" section for this socket.\n");
- goto do_next;
- }
-
- if (table[i].table) {
- /*
- * This is the last argument, but
- * there's a sub-table. Print help.
- *
- */
- if (argc == 1) {
- table = table[i].table;
- goto do_help;
- }
-
- argc--;
- argv++;
- table = table[i].table;
- goto retry;
- }
-
- if ((argc == 2) && (strcmp(argv[1], "?") == 0)) goto do_help;
-
- if (!table[i].func) {
- cprintf_error(listener, "Invalid command\n");
- goto do_next;
- }
-
- status = table[i].func(listener, argc - 1, argv + 1);
- goto do_next;
- }
- }
-
- /*
- * No such command
- */
- if (!len) {
- if ((strcmp(argv[0], "help") == 0) ||
- (strcmp(argv[0], "?") == 0)) {
- int recursive;
-
- do_help:
- if ((argc > 1) && (strcmp(argv[1], "-r") == 0)) {
- recursive = true;
- argc--;
- argv++;
- } else {
- recursive = false;
- }
-
- print_help(listener, argc - 1, argv + 1, table, recursive);
- goto do_next;
- }
-
- cprintf_error(listener, "Unknown command \"%s\"\n",
- argv[0]);
- }
-
- do_next:
- /*
- * Reset offset now that command has been fully read
- */
- co->offset = 0;
-
- r = fr_conduit_write(listener->fd, FR_CONDUIT_CMD_STATUS, &status, sizeof(status));
- if (r <= 0) goto do_close;
-
- return 0;
-}
-
-
-static int command_tcp_recv(rad_listen_t *this)
-{
- ssize_t r;
- listen_socket_t *sock = this->data;
- fr_cs_buffer_t *co = (void *) sock->packet;
- fr_conduit_type_t conduit;
-
- if (!co) {
- do_close:
- command_close_socket(this);
- return 0;
- }
-
- if (!co->auth) {
- uint8_t *data;
- uint8_t expected[16];
-
- r = fr_conduit_drain(this->fd, &conduit, co->buffer, sizeof(co->buffer) - 1, &data, &co->offset);
- if ((r < 0) && ((errno == EINTR) || (errno == EAGAIN))) return 0;
-
- if (r <= 0) goto do_close;
-
- /*
- * We need more data. Go read it.
- */
- if (conduit == FR_CONDUIT_WANT_MORE) {
- return 0;
- }
-
- if ((r != sizeof(expected)) || (conduit != FR_CONDUIT_AUTH_RESPONSE)) goto do_close;
-
- fr_hmac_md5(expected, (void const *) sock->client->secret,
- strlen(sock->client->secret),
- data, sizeof(expected));
-
- if (fr_digest_cmp(expected, data + sizeof(expected), sizeof(expected)) != 0) {
- ERROR("radmin failed challenge: Closing socket");
- goto do_close;
- }
-
- co->auth = true;
- co->offset = 0;
- }
-
- return command_domain_recv_co(this, co);
-}
-
-/*
- * Should never be called. The functions should just call write().
- */
-static int command_tcp_send(UNUSED rad_listen_t *listener, UNUSED REQUEST *request)
-{
- return 0;
-}
-
-static int command_domain_recv(rad_listen_t *listener)
-{
- fr_command_socket_t *sock = listener->data;
-
- return command_domain_recv_co(listener, &sock->co);
-}
-
-/*
- * Write 32-bit magic number && version information.
- */
-static int command_magic_recv(rad_listen_t *this, fr_cs_buffer_t *co, bool challenge)
-{
- int i;
- ssize_t r;
- uint32_t magic;
- fr_conduit_type_t conduit;
- uint8_t *data;
-
- /*
- * Start off by reading 4 bytes of magic, followed by 4 bytes of zero.
- */
- r = fr_conduit_drain(this->fd, &conduit, co->buffer, sizeof(co->buffer) - 1, &data, &co->offset);
- if ((r < 0) && ((errno == EINTR) || (errno == EAGAIN))) return 0;
-
- if (r <= 0) {
- ERROR("Failed reading magic: %s", fr_syserror(errno));
-
- do_close:
- command_close_socket(this);
- return 0;
- }
-
- /*
- * We need more data. Go read it.
- */
- if (conduit == FR_CONDUIT_WANT_MORE) {
- return 0;
- }
-
- if ((r != 8) || (conduit != FR_CONDUIT_INIT_ACK)) goto do_close;
-
- magic = htonl(0xf7eead16);
- if (memcmp(&magic, data, sizeof(magic)) != 0) {
- ERROR("Incompatible versions");
- goto do_close;
- }
-
- /*
- * Ack the magic + 4 bytes of zero back.
- */
- r = fr_conduit_write(this->fd, FR_CONDUIT_INIT_ACK, data, 8);
- if (r <= 0) {
- ERROR("Failed writing magic: %s", fr_syserror(errno));
- goto do_close;
- }
-
- if (challenge) {
- for (i = 0; i < 16; i++) {
- co->buffer[i] = fr_rand();
- }
-
- r = fr_conduit_write(this->fd, FR_CONDUIT_AUTH_CHALLENGE, co->buffer, 16);
- if (r <= 0) {
- ERROR("Failed writing auth challenge: %s", fr_syserror(errno));
- goto do_close;
- }
-
- }
-
- /*
- * Reset offset now that version has been fully read
- */
- co->offset = 0;
-
- return 1;
-}
-
-static int command_init_recv(rad_listen_t *this)
-{
- int rcode;
- fr_command_socket_t *sock = this->data;
-
- if (sock->magic == COMMAND_SOCKET_MAGIC) {
- rcode = command_magic_recv(this, &sock->co, false);
- if (rcode <= 0) return rcode;
-
- this->recv = command_domain_recv;
- } else {
- listen_socket_t *sock2 = this->data;
-
- rcode = command_magic_recv(this, (fr_cs_buffer_t *) sock2->packet, true);
- if (rcode <= 0) return rcode;
-
- this->recv = command_tcp_recv;
- }
-
- return 0;
-}
-
-
-static int command_domain_accept(rad_listen_t *listener)
-{
- int newfd;
- rad_listen_t *this;
- socklen_t salen;
- struct sockaddr_storage src;
- fr_command_socket_t *sock = listener->data;
-
- salen = sizeof(src);
-
- DEBUG2(" ... new connection request on command socket");
-
- newfd = accept(listener->fd, (struct sockaddr *) &src, &salen);
- if (newfd < 0) {
- /*
- * Non-blocking sockets must handle this.
- */
- if (errno == EWOULDBLOCK) {
- return 0;
- }
-
- DEBUG2(" ... failed to accept connection");
- return 0;
- }
-
- /*
- * Is likely redundant as newfd should inherit blocking
- * from listener->fd. But better to be safe.
- */
- if (!sock->blocking) fr_nonblock(newfd);
-
-#ifdef HAVE_GETPEEREID
- /*
- * Perform user authentication.
- */
- if (sock->peercred) {
- uid_t uid;
- gid_t gid;
-
- if (getpeereid(newfd, &uid, &gid) < 0) {
- ERROR("Failed getting peer credentials for %s: %s",
- sock->path, fr_syserror(errno));
- close(newfd);
- return 0;
- }
-
- /*
- * Only do UID checking if the caller is
- * non-root. The superuser can do anything, so
- * we might as well let them.
- */
- if ((uid != 0) && (uid != geteuid()) && (sock->gid_name && (sock->gid != gid))) {
- ERROR("Unauthorized connection to %s from uid %ld, gid %ld",
- sock->path, (long int) uid, (long int) gid);
- close(newfd);
- return 0;
- }
- }
-#endif
-
- /*
- * Add the new listener.
- */
- this = listen_alloc(listener, listener->type, listener->proto);
- if (!this) return 0;
-
- /*
- * Copy everything, including the pointer to the socket
- * information.
- */
- sock = this->data;
- memcpy(this, listener, sizeof(*this));
- this->status = RAD_LISTEN_STATUS_INIT;
- this->next = NULL;
- this->data = sock; /* fix it back */
-
- sock->magic = COMMAND_SOCKET_MAGIC;
- sock->user[0] = '\0';
- sock->path = ((fr_command_socket_t *) listener->data)->path;
- sock->co.offset = 0;
- sock->co.mode = ((fr_command_socket_t *) listener->data)->co.mode;
-
- this->fd = newfd;
-
- /*
- * Start off by sending the magic handshake.
- */
- this->recv = command_init_recv;
-
- /*
- * Tell the event loop that we have a new FD
- */
- radius_update_listener(this);
-
- return 0;
-}
-
-
-/*
- * Send an authentication response packet
- */
-static int command_domain_send(UNUSED rad_listen_t *listener,
- UNUSED REQUEST *request)
-{
- return 0;
-}
-
-
-static int command_socket_encode(UNUSED rad_listen_t *listener,
- UNUSED REQUEST *request)
-{
- return 0;
-}
-
-
-static int command_socket_decode(UNUSED rad_listen_t *listener,
- UNUSED REQUEST *request)
-{
- return 0;
-}
-
-#endif /* WITH_COMMAND_SOCKET */
+++ /dev/null
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-/**
- * $Id$
- *
- * @file tls_listen.c
- * @brief TLS server/client socket functions.
- *
- * @copyright 2001 hereUare Communications, Inc. <raghud@hereuare.com>
- * @copyright 2003 Alan DeKok <aland@freeradius.org>
- * @copyright 2006-2016 The FreeRADIUS server project
- */
-RCSID("$Id$")
-USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
-
-#ifdef WITH_TLS
-#define LOG_PREFIX "tls - "
-
-#include <freeradius-devel/radiusd.h>
-#include <freeradius-devel/process.h>
-#include <freeradius-devel/rad_assert.h>
-
-static void tls_socket_close(rad_listen_t *listener)
-{
- listen_socket_t *sock = listener->data;
-
- SSL_shutdown(sock->tls_session->ssl);
-
- listener->status = RAD_LISTEN_STATUS_EOL;
- listener->tls = NULL; /* parent owns this! */
-
- /*
- * Tell the event handler that an FD has disappeared.
- */
- DEBUG("Client has closed connection");
- radius_update_listener(listener);
-
- /*
- * Do NOT free the listener here. It's in use by
- * a request, and will need to hang around until
- * all of the requests are done.
- *
- * It is instead free'd in remove_from_request_hash()
- */
-}
-
-static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener, REQUEST *request)
-{
- uint8_t *p;
- ssize_t rcode;
- listen_socket_t *sock = listener->data;
-
- p = sock->tls_session->dirty_out.data;
-
- while (p < (sock->tls_session->dirty_out.data + sock->tls_session->dirty_out.used)) {
- RDEBUG3("Writing to socket %d", request->packet->sockfd);
- rcode = write(request->packet->sockfd, p,
- (sock->tls_session->dirty_out.data + sock->tls_session->dirty_out.used) - p);
- if (rcode <= 0) {
- RDEBUG("Error writing to TLS socket: %s", fr_syserror(errno));
-
- tls_socket_close(listener);
- return 0;
- }
- p += rcode;
- }
-
- sock->tls_session->dirty_out.used = 0;
-
- return 1;
-}
-
-static int tls_socket_recv(rad_listen_t *listener)
-{
- bool doing_init = false;
- ssize_t rcode;
- RADIUS_PACKET *packet;
- REQUEST *request;
- listen_socket_t *sock = listener->data;
- int ret;
- RADCLIENT *client = sock->client;
-
- if (!sock->packet) {
- sock->packet = fr_radius_alloc(sock, false);
- if (!sock->packet) return 0;
-
- sock->packet->sockfd = listener->fd;
- sock->packet->src_ipaddr = sock->other_ipaddr;
- sock->packet->src_port = sock->other_port;
- sock->packet->dst_ipaddr = sock->my_ipaddr;
- sock->packet->dst_port = sock->my_port;
-
- if (sock->request) sock->request->packet = talloc_steal(sock->request, sock->packet);
- }
-
- /*
- * Allocate a REQUEST for debugging, and initialize the TLS session.
- */
- if (!sock->request) {
- sock->request = request = request_alloc(sock);
- if (!sock->request) {
- ERROR("Out of memory");
- return 0;
- }
-
- rad_assert(request->packet == NULL);
- rad_assert(sock->packet != NULL);
- request->packet = talloc_steal(request, sock->packet);
-
- request->component = "<tls-connect>";
-
- request->reply = fr_radius_alloc(request, false);
- if (!request->reply) return 0;
-
- rad_assert(sock->tls_session == NULL);
-
- sock->tls_session = tls_session_init_server(sock, listener->tls, sock->request,
- listener->tls->require_client_cert);
- if (!sock->tls_session) {
- TALLOC_FREE(sock->request);
- sock->packet = NULL;
- return 0;
- }
-
- SSL_set_ex_data(sock->tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, (void *)request);
-
- doing_init = true;
- }
-
- rad_assert(sock->request != NULL);
- rad_assert(sock->request->packet != NULL);
- rad_assert(sock->packet != NULL);
- rad_assert(sock->tls_session != NULL);
-
- request = sock->request;
-
- RDEBUG3("Reading from socket %d", request->packet->sockfd);
- pthread_mutex_lock(&sock->mutex);
- rcode = read(request->packet->sockfd,
- sock->tls_session->dirty_in.data,
- sizeof(sock->tls_session->dirty_in.data));
- if ((rcode < 0) && (errno == ECONNRESET)) {
- do_close:
- pthread_mutex_unlock(&sock->mutex);
- DEBUG("Closing TLS socket from client port %u", sock->other_port);
- tls_socket_close(listener);
- pthread_mutex_unlock(&sock->mutex);
- return 0;
- }
-
- if (rcode < 0) {
- RDEBUG("Error reading TLS socket: %s", fr_syserror(errno));
- goto do_close;
- }
-
- /*
- * Normal socket close.
- */
- if (rcode == 0) goto do_close;
-
- sock->tls_session->dirty_in.used = rcode;
-
- RDEBUG2("Encrypted TLS data in (%zu bytes)", sock->tls_session->dirty_in.used);
- radlog_request_hex(L_DBG, L_DBG_LVL_3,
- request, sock->tls_session->dirty_in.data, sock->tls_session->dirty_in.used);
-
- /*
- * Catch attempts to use non-SSL.
- */
- if (doing_init && (sock->tls_session->dirty_in.data[0] != handshake)) {
- RDEBUG("Non-TLS data sent to TLS socket: closing");
- goto do_close;
- }
-
- /*
- * If we need to do more initialization, do that here.
- */
- if (!SSL_is_init_finished(sock->tls_session->ssl)) {
- if (!tls_session_handshake(request, sock->tls_session)) {
- RDEBUG("FAILED in TLS handshake receive");
- goto do_close;
- }
-
- /*
- * More ACK data to send. Do so.
- */
- if (sock->tls_session->dirty_out.used > 0) {
- tls_socket_write(listener, request);
- pthread_mutex_unlock(&sock->mutex);
- return 0;
- }
-
- /*
- * FIXME: Run the request through a virtual
- * server in order to see if we like the
- * certificate presented by the client.
- */
- }
-
- /*
- * Try to get application data.
- */
- ret = tls_session_recv(request, sock->tls_session);
- switch (ret) {
- case 0: /* Record decrypted */
- break;
-
- case 1: /* Need more record data */
- pthread_mutex_unlock(&sock->mutex);
- return 0;
-
- default: /* error */
- goto do_close;
- }
-
- if (sock->tls_session->clean_out.used == 0) {
- pthread_mutex_unlock(&sock->mutex);
- return 0;
- }
-
- /*
- * If the packet is a complete RADIUS packet, return it to
- * the caller. Otherwise...
- */
- if ((sock->tls_session->clean_out.used < 20) ||
- (((sock->tls_session->clean_out.data[2] << 8) | sock->tls_session->clean_out.data[3]) != (int) sock->tls_session->clean_out.used)) {
- RDEBUG("Received bad packet: Length %zd contents %d",
- sock->tls_session->clean_out.used,
- (sock->tls_session->clean_out.data[2] << 8) | sock->tls_session->clean_out.data[3]);
- goto do_close;
- }
-
- packet = sock->packet;
- packet->data = talloc_array(packet, uint8_t, sock->tls_session->clean_out.used);
- packet->data_len = sock->tls_session->clean_out.used;
- sock->tls_session->record_to_buff(&sock->tls_session->clean_out, packet->data, packet->data_len);
- packet->vps = NULL;
- pthread_mutex_unlock(&sock->mutex);
-
- if (!fr_radius_packet_ok(packet, 0, NULL)) {
- if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror());
- DEBUG("Closing TLS socket from client");
- pthread_mutex_lock(&sock->mutex);
- tls_socket_close(listener);
- pthread_mutex_unlock(&sock->mutex);
- return 0; /* do_close unlocks the mutex */
- }
-
- /*
- * Copied from src/lib/radius.c, fr_radius_packet_recv();
- */
- if (fr_debug_lvl) {
- char host_ipaddr[INET6_ADDRSTRLEN];
-
- if (is_radius_code(packet->code)) {
- RDEBUG("tls_recv: %s packet from host %s port %d, id=%d, length=%d",
- fr_packet_codes[packet->code],
- inet_ntop(packet->src_ipaddr.af,
- &packet->src_ipaddr.addr,
- host_ipaddr, sizeof(host_ipaddr)),
- packet->src_port,
- packet->id, (int) packet->data_len);
- } else {
- RDEBUG("tls_recv: Packet from host %s port %d code=%d, id=%d, length=%d",
- inet_ntop(packet->src_ipaddr.af,
- &packet->src_ipaddr.addr,
- host_ipaddr, sizeof(host_ipaddr)),
- packet->src_port,
- packet->code,
- packet->id, (int) packet->data_len);
- }
- }
-
- FR_STATS_INC(auth, total_requests);
-
- return 1;
-}
-
-int dual_tls_recv(rad_listen_t *listener)
-{
- RADIUS_PACKET *packet;
- RAD_REQUEST_FUNP fun = NULL;
- listen_socket_t *sock = listener->data;
- RADCLIENT *client = sock->client;
-
- if (!rad_cond_assert(client != NULL)) return 0;
- if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0;
- if (!tls_socket_recv(listener)) return 0;
-
- rad_assert(sock->packet != NULL);
- rad_assert(sock->tls_session != NULL);
- rad_assert(client != NULL);
-
- packet = talloc_steal(NULL, sock->packet);
- sock->packet = NULL;
-
- gettimeofday(&packet->timestamp, NULL);
-
- /*
- * Some sanity checks, based on the packet code.
- *
- * "auth+acct" are marked as "auth", with the "dual" flag
- * set.
- */
- switch (packet->code) {
- case FR_CODE_ACCESS_REQUEST:
- if (listener->type != RAD_LISTEN_AUTH) goto bad_packet;
- FR_STATS_INC(auth, total_requests);
- fun = rad_authenticate;
- break;
-
-#ifdef WITH_ACCOUNTING
- case FR_CODE_ACCOUNTING_REQUEST:
- if (listener->type != RAD_LISTEN_ACCT) {
- /*
- * Allow auth + dual. Disallow
- * everything else.
- */
- if (!((listener->type == RAD_LISTEN_AUTH) &&
- (listener->dual))) {
- goto bad_packet;
- }
- }
- FR_STATS_INC(acct, total_requests);
- fun = rad_accounting;
- break;
-#endif
-
- case FR_CODE_STATUS_SERVER:
- if (!main_config.status_server) {
- FR_STATS_INC(auth, total_unknown_types);
- WARN("Ignoring Status-Server request due to security configuration");
- fr_radius_free(&packet);
- return 0;
- }
- fun = rad_status_server;
- break;
-
- default:
- bad_packet:
- FR_STATS_INC(auth, total_unknown_types);
-
- DEBUG("Invalid packet code %d sent from client %s port %d : IGNORED",
- packet->code, client->shortname, packet->src_port);
- fr_radius_free(&packet);
- return 0;
- } /* switch over packet types */
-
- if (!request_receive(NULL, listener, packet, client, fun)) {
- FR_STATS_INC(auth, total_packets_dropped);
- fr_radius_free(&packet);
- return 0;
- }
-
- return 1;
-}
-
-/*
- * Send a response packet
- */
-int dual_tls_send(rad_listen_t *listener, REQUEST *request)
-{
- listen_socket_t *sock = listener->data;
-
- REQUEST_VERIFY(request);
-
- rad_assert(request->listener == listener);
- rad_assert(listener->send == dual_tls_send);
-
- if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0;
-
- /*
- * Accounting reject's are silently dropped.
- *
- * We do it here to avoid polluting the rest of the
- * code with this knowledge
- */
- if (request->reply->code == 0) return 0;
-
- /*
- * Pack the VPs
- */
- if (fr_radius_packet_encode(request->reply, request->packet,
- request->client->secret) < 0) {
- RPERROR("Failed encoding packet");
- return 0;
- }
-
- if (request->reply->data_len > (MAX_PACKET_LEN - 100)) {
- RWARN("Packet is large, and possibly truncated - %zd vs max %d",
- request->reply->data_len, MAX_PACKET_LEN);
- }
-
- /*
- * Sign the packet.
- */
- if (fr_radius_packet_sign(request->reply, request->packet,
- request->client->secret) < 0) {
- RPERROR("Failed signing packet");
- return 0;
- }
-
- pthread_mutex_lock(&sock->mutex);
-
- /*
- * Write the packet to the SSL buffers.
- */
- sock->tls_session->record_from_buff(&sock->tls_session->clean_in,
- request->reply->data, request->reply->data_len);
-
- /*
- * Do SSL magic to get encrypted data.
- */
- tls_session_send(request, sock->tls_session);
-
- /*
- * And finally write the data to the socket.
- */
- if (sock->tls_session->dirty_out.used > 0) {
- RDEBUG2("Encrypted TLS data out (%zu bytes)", sock->tls_session->dirty_out.used);
- radlog_request_hex(L_DBG, L_DBG_LVL_3,
- request, sock->tls_session->dirty_out.data, sock->tls_session->dirty_out.used);
-
- tls_socket_write(listener, request);
- }
- pthread_mutex_unlock(&sock->mutex);
-
- return 0;
-}
-
-#ifdef WITH_PROXY
-/*
- * Read from the SSL socket. Safe with either blocking or
- * non-blocking IO. This level of complexity is probably not
- * necessary, as each packet gets put into one SSL application
- * record. When SSL has a full record, we should be able to read
- * the entire packet via one SSL_read().
- *
- * When SSL has a partial record, SSL_read() will return
- * WANT_READ or WANT_WRITE, and zero application data.
- *
- * Called with the mutex held.
- */
-static ssize_t proxy_tls_read(rad_listen_t *listener)
-{
- int rcode;
- size_t length;
- uint8_t *data;
- listen_socket_t *sock = listener->data;
-
- /*
- * Get the maximum size of data to receive.
- */
- if (!sock->data) sock->data = talloc_array(sock, uint8_t,
- sock->tls_session->mtu);
-
- data = sock->data;
-
- if (sock->partial < 4) {
- rcode = SSL_read(sock->tls_session->ssl, data + sock->partial,
- 4 - sock->partial);
- if (rcode <= 0) {
- int err = SSL_get_error(sock->tls_session->ssl, rcode);
- switch (err) {
- case SSL_ERROR_WANT_READ:
- case SSL_ERROR_WANT_WRITE:
- return 0; /* do some more work later */
-
- case SSL_ERROR_ZERO_RETURN:
- /* remote end sent close_notify, send one back */
- SSL_shutdown(sock->tls_session->ssl);
-
- case SSL_ERROR_SYSCALL:
- do_close:
- return -1;
-
- default:
- tls_log_error(NULL, "Failed in proxy receive");
-
- goto do_close;
- }
- }
-
- sock->partial = rcode;
- } /* try reading the packet header */
-
- if (sock->partial < 4) return 0; /* read more data */
-
- length = (data[2] << 8) | data[3];
-
- /*
- * Do these checks only once, when we read the header.
- */
- if (sock->partial == 4) {
- DEBUG3("Proxy received header saying we have a packet of %u bytes",
- (unsigned int) length);
-
- /*
- * FIXME: allocate a RADIUS_PACKET, and set
- * "data" to be as large as necessary.
- */
- if (length > sock->tls_session->mtu) {
- INFO("Received packet will be too large! Set \"fragment_size = %u\"",
- (data[2] << 8) | data[3]);
- goto do_close;
- }
- }
-
- /*
- * Try to read some more.
- */
- if (sock->partial < length) {
- rcode = SSL_read(sock->tls_session->ssl, data + sock->partial,
- length - sock->partial);
- if (rcode <= 0) {
- switch (SSL_get_error(sock->tls_session->ssl, rcode)) {
- case SSL_ERROR_WANT_READ:
- case SSL_ERROR_WANT_WRITE:
- return 0;
-
- case SSL_ERROR_ZERO_RETURN:
- /* remote end sent close_notify, send one back */
- SSL_shutdown(sock->tls_session->ssl);
- goto do_close;
- default:
- goto do_close;
- }
- }
-
- sock->partial += rcode;
- }
-
- /*
- * If we're not done, say so.
- *
- * Otherwise, reset the partially read data flag, and say
- * we have a packet.
- */
- if (sock->partial < length) {
- return 0;
- }
-
- sock->partial = 0; /* we've now read the packet */
- return length;
-}
-
-int proxy_tls_recv(rad_listen_t *listener)
-{
- listen_socket_t *sock = listener->data;
- char buffer[256];
- RADIUS_PACKET *packet;
- uint8_t *data;
- ssize_t data_len;
-
- if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0;
-
- DEBUG3("Proxy SSL socket has data to read");
- pthread_mutex_lock(&sock->mutex);
- data_len = proxy_tls_read(listener);
- pthread_mutex_unlock(&sock->mutex);
-
- if (data_len < 0) {
- DEBUG("Closing TLS socket to home server");
- pthread_mutex_lock(&sock->mutex);
- tls_socket_close(listener);
- pthread_mutex_unlock(&sock->mutex);
- return 0;
- }
-
- if (data_len == 0) return 0; /* not done yet */
-
- data = sock->data;
-
- packet = fr_radius_alloc(sock, false);
- packet->sockfd = listener->fd;
- packet->src_ipaddr = sock->other_ipaddr;
- packet->src_port = sock->other_port;
- packet->dst_ipaddr = sock->my_ipaddr;
- packet->dst_port = sock->my_port;
- packet->code = data[0];
- packet->id = data[1];
- packet->data_len = data_len;
- packet->data = talloc_array(packet, uint8_t, packet->data_len);
- memcpy(packet->data, data, packet->data_len);
- memcpy(packet->vector, packet->data + 4, 16);
-
- /*
- * FIXME: Client MIB updates?
- */
- switch (packet->code) {
- case FR_CODE_ACCESS_ACCEPT:
- case FR_CODE_ACCESS_CHALLENGE:
- case FR_CODE_ACCESS_REJECT:
- break;
-
-#ifdef WITH_ACCOUNTING
- case FR_CODE_ACCOUNTING_RESPONSE:
- break;
-#endif
-
- default:
- /*
- * FIXME: Update MIB for packet types?
- */
- ERROR("Invalid packet code %d sent to a proxy port "
- "from home server %s port %d - ID %d : IGNORED",
- packet->code,
- fr_inet_ntoh(&packet->src_ipaddr, buffer, sizeof(buffer)),
- packet->src_port, packet->id);
- fr_radius_free(&packet);
- return 0;
- }
-
- fr_radius_free(&packet);
- return 0;
-}
-
-int proxy_tls_send(rad_listen_t *listener, REQUEST *request)
-{
- int rcode;
- listen_socket_t *sock = listener->data;
-
- REQUEST_VERIFY(request);
-
- if ((listener->status != RAD_LISTEN_STATUS_INIT) &&
- (listener->status != RAD_LISTEN_STATUS_KNOWN)) return 0;
-
- /*
- * Normal proxying calls us with the data already
- * encoded. The "ping home server" code does not. So,
- * if there's no packet, encode it here.
- */
- if (!request->proxy->packet->data) {
- request->proxy->listener->encode(request->proxy->listener,
- request);
- }
-
- DEBUG3("Proxy is writing %u bytes to SSL",
- (unsigned int) request->proxy->packet->data_len);
- pthread_mutex_lock(&sock->mutex);
- rcode = SSL_write(sock->tls_session->ssl, request->proxy->packet->data,
- request->proxy->packet->data_len);
- if (rcode < 0) {
- int err;
-
- err = ERR_get_error();
- switch (err) {
- case SSL_ERROR_NONE:
- case SSL_ERROR_WANT_READ:
- case SSL_ERROR_WANT_WRITE:
- break; /* let someone else retry */
-
- default:
- tls_log_error(NULL, "Failed in proxy send");
- DEBUG("Closing TLS socket to home server");
- tls_socket_close(listener);
- pthread_mutex_unlock(&sock->mutex);
- return 0;
- }
- }
- pthread_mutex_unlock(&sock->mutex);
-
- return 1;
-}
-#endif /* WITH_PROXY */
-#endif /* WITH_TLS */