From: Alan T. DeKok Date: Tue, 23 Jan 2024 18:55:55 +0000 (-0500) Subject: move to using bios for at least part of the control socket X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=71f36d0ded14aba6b0308c95447a456ba9813547;p=thirdparty%2Ffreeradius-server.git move to using bios for at least part of the control socket --- diff --git a/raddb/sites-available/control-socket b/raddb/sites-available/control-socket index 118d9d988d5..0c5001b932d 100644 --- a/raddb/sites-available/control-socket +++ b/raddb/sites-available/control-socket @@ -60,32 +60,25 @@ server control { filename = ${run_dir}/control/${name}.sock # - # peercred:: It is enabled by default, and offers an additional layer - # of security. When enabled FreeRADIUS will check the euid and - # egid of the process connecting to the control socket. + # uid:: Name of user who owns the control socket. # - # The client process is allowed to connect if any of the following - # are true: + # If this is not set, then the "security.user" configuration is used. # - # - The client processes' euid is 0 (root). - # - The client processes' euid matches FreeRADIUS' euid. - # - gid is set (below), and the client processes' egid matches the - # configured gid. + # If that isn't set, then the uid of the current user running radiusd is used. # - # NOTE: With peercred enabled, auxiliary groups of the client process - # are not considered. If you have multiple users and need to control - # control socket authorization via group membership, you should set - # `peercred = no`, and rely on filesystem permissions for enforcement. + # Note that the control socket cannot have uid of 0. # -# peercred = no +# uid = radius # - # uid:: Name of user who is allowed to connect to the control socket. + # gid:: Name of group which owns the control socket. # -# uid = radius - + # If this is not set, then the "security.group" configuration is used. # - # gid:: Name of group that is allowed to connect to the control socket. + # If that isn't set, then the primary group (gid) of the current user running radiusd + # is used. + # + # Note that the control socket cannot have gid of 0. # # gid = radius @@ -99,8 +92,16 @@ server control { # rw = read/write access. # mode = rw - } - # @todo - add "limit" section + # + # As an additional check, the connecting process can be limited to a certain UID and + # GID. Either or both can be set. If one is set, it is checked and enforced. + # + peercred { +# uid = something +# gid = something + } + + } } } diff --git a/src/listen/control/proto_control_unix.c b/src/listen/control/proto_control_unix.c index 49d867a7966..287b2879936 100644 --- a/src/listen/control/proto_control_unix.c +++ b/src/listen/control/proto_control_unix.c @@ -22,6 +22,8 @@ * @copyright 2018 The FreeRADIUS server project. * @copyright 2018 Alan DeKok (aland@deployingradius.com) */ +#include + #include #include #include @@ -29,10 +31,13 @@ #include #include #include + #include #include "proto_control.h" +#include + #ifdef HAVE_SYS_STAT_H #endif @@ -44,6 +49,8 @@ typedef struct { int sockfd; + fr_bio_t *fd_bio; + fr_stats_t stats; //!< statistics for this socket fr_io_address_t *connection; //!< for connected sockets. @@ -78,21 +85,33 @@ typedef struct { bool recv_buff_is_set; //!< Whether we were provided with a receive //!< buffer value. - bool peercred; //!< whether we use peercred or not + char const *peer_uid_name; //!< name of UID to require + char const *peer_gid_name; //!< name of GID to require + uid_t peer_uid; //!< UID value + gid_t peer_gid; //!< GID value + } proto_control_unix_t; +static const conf_parser_t peercred_config[] = { + { FR_CONF_OFFSET("uid", proto_control_unix_t, peer_uid_name) }, + { FR_CONF_OFFSET("gid", proto_control_unix_t, peer_gid_name) }, + + CONF_PARSER_TERMINATOR +}; + static const conf_parser_t unix_listen_config[] = { { FR_CONF_OFFSET_FLAGS("filename", CONF_FLAG_REQUIRED, proto_control_unix_t, filename), .dflt = "${run_dir}/radiusd.sock}" }, { FR_CONF_OFFSET("uid", proto_control_unix_t, uid_name) }, { FR_CONF_OFFSET("gid", proto_control_unix_t, gid_name) }, { FR_CONF_OFFSET("mode", proto_control_unix_t, mode_name) }, - { FR_CONF_OFFSET("peercred", proto_control_unix_t, peercred), .dflt = "yes" }, { FR_CONF_OFFSET_IS_SET("recv_buff", FR_TYPE_UINT32, 0, proto_control_unix_t, recv_buff) }, { FR_CONF_OFFSET("max_packet_size", proto_control_unix_t, max_packet_size), .dflt = "4096" } , + { FR_CONF_POINTER("peercred", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) peercred_config }, + CONF_PARSER_TERMINATOR }; @@ -362,575 +381,6 @@ static void mod_network_get(UNUSED void *instance, int *ipproto, bool *dynamic_c *trie = NULL; } -/** Initialise a socket for use with peercred authentication - * - * This function initialises a socket and path in a way suitable for use with - * peercred. - * - * @param path to socket. - * @param uid that should own the socket (linux only). - * @param gid that should own the socket (linux only). - * @return 0 on success -1 on failure. - */ -#ifdef __linux__ -static int fr_server_domain_socket_peercred(char const *path, uid_t uid, gid_t gid) -#else -static int fr_server_domain_socket_peercred(char const *path, uid_t UNUSED uid, UNUSED gid_t gid) -#endif -{ - int sockfd; - int dirfd; - char const *r; - size_t len; - socklen_t socklen; - struct sockaddr_un salocal; - struct stat buf; - - if (!path) { - fr_strerror_const("No path provided, was NULL"); - return -1; - } - - len = strlen(path); - if (len >= sizeof(salocal.sun_path)) { - fr_strerror_const("Path too long in socket filename"); - 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); - - /* - * Find and open the directory containing path so we can use the "at" - * functions to avoid time of check/time of use insecurities. - */ - if (fr_dirfd(&dirfd, &r, path) < 0) { - close(sockfd); - fr_strerror_printf("Failed to open directory containing %s", path); - return -1; - } - - /* - * Check the path. - */ - if (fstatat(dirfd, r, &buf, 0) < 0) { - if (errno != ENOENT) { - fr_strerror_printf("Failed to stat %s: %s", path, fr_syserror(errno)); - close(sockfd); - if (dirfd != AT_FDCWD) close(dirfd); - return -1; - } - - /* - * FIXME: Check the enclosing directory? - */ - } else { /* it exists */ - int client_fd; - - 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); - if (dirfd != AT_FDCWD) close(dirfd); - 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); - if (dirfd != AT_FDCWD) close(dirfd); - return -1; - } - - /* - * 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); - close(sockfd); - if (dirfd != AT_FDCWD) close(dirfd); - return -1; - } - - if (unlinkat(dirfd, r, 0) < 0) { - fr_strerror_printf("Failed to delete %s: %s", path, fr_syserror(errno)); - close(sockfd); - if (dirfd != AT_FDCWD) close(dirfd); - return -1; - } - } - - if (bind(sockfd, (struct sockaddr *)&salocal, socklen) < 0) { - fr_strerror_printf("Failed binding to %s: %s", path, fr_syserror(errno)); - close(sockfd); - if (dirfd != AT_FDCWD) close(dirfd); - return -1; - } - - /* - * FIXME: There's a race condition here. But Linux - * doesn't seem to permit fchmod on domain sockets. - */ - if (fchmodat(dirfd, r, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, 0) < 0) { - fr_strerror_printf("Failed setting permissions on %s: %s", path, fr_syserror(errno)); - close(sockfd); - if (dirfd != AT_FDCWD) close(dirfd); - return -1; - } - - if (dirfd != AT_FDCWD) close(dirfd); - - 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 - - /* - * Changing socket permissions only works on linux. - * BSDs ignore socket permissions. - */ -#ifdef __linux__ - /* - * Don't chown it from (possibly) non-root to root. - * Do chown it from (possibly) root to non-root. - */ - if ((uid != (uid_t) -1) || (gid != (gid_t) -1)) { - /* - * Don't do chown if it's already owned by us. - */ - if (fstat(sockfd, &buf) < 0) { - fr_strerror_printf("Failed reading %s: %s", path, fr_syserror(errno)); - close(sockfd); - return -1; - } - - if ((buf.st_uid != uid) || (buf.st_gid != gid)) { - rad_suid_up(); - if (fchown(sockfd, uid, gid) < 0) { - fr_strerror_printf("Failed setting ownership of %s to (%d, %d): %s", - path, uid, gid, fr_syserror(errno)); - rad_suid_down(); - close(sockfd); - return -1; - } - rad_suid_down(); - } - } -#endif - - return sockfd; -} - -#if !defined(HAVE_OPENAT) || !defined(HAVE_MKDIRAT) || !defined(HAVE_UNLINKAT) -static int fr_server_domain_socket_perm(UNUSED char const *path, UNUSED uid_t uid, UNUSED gid_t gid) -{ - fr_strerror_printf("Unable to initialise control socket. Set peercred = yes or update to " - "POSIX-2008 compliant libc"); - return -1; -} -#else -/** Alternative function for creating Unix domain sockets and enforcing permissions - * - * Unlike fr_server_unix_socket which is intended to be used with peercred auth - * this function relies on the file system to enforce access. - * - * 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[in] path where domain socket should be created. - * @param[in] uid Owner of the socket. - * @param[in] gid Group of the socket. - * @return a file descriptor for the bound socket on success, -1 on failure. - */ -static int fr_server_domain_socket_perm(char const *path, uid_t uid, gid_t gid) -{ - int dir_fd = -1, sock_fd = -1, parent_fd = -1; - char const *name; - char *buff = NULL, *dir = NULL, *p; - - uid_t euid; - gid_t egid; - - mode_t perm = 0; - struct stat st; - - - size_t len; - - socklen_t socklen; - struct sockaddr_un salocal; - - fr_assert(path); - - euid = geteuid(); - egid = getegid(); - - /* - * Determine the correct permissions for the socket, or its - * containing directory. - */ - perm |= S_IREAD | S_IWRITE | S_IEXEC; - if (gid != (gid_t) -1) perm |= S_IRGRP | S_IWGRP | S_IXGRP; - - buff = talloc_strdup(NULL, path); - if (!buff) 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); - if (!dir) return -1; - talloc_free(buff); - } - - p = strrchr(dir, FR_DIR_SEP); - if (!p) { - fr_strerror_const("Failed determining parent directory"); - error: - talloc_free(dir); - if (sock_fd >= 0) close(sock_fd); - if (dir_fd >= 0) close(dir_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. - */ - parent_fd = open(dir, O_DIRECTORY); - if (parent_fd < 0) { - struct passwd *user; - struct group *group; - - if (fr_perm_getpwuid(NULL, &user, euid) < 0) goto error; - if (fr_perm_getgrgid(NULL, &group, egid) < 0) { - talloc_free(user); - goto error; - } - fr_strerror_printf("Can't open directory \"%s\": %s. Must be created manually, or modified, " - "with permissions that allow writing by user %s or group %s", dir, - user->pw_name, group->gr_name, fr_syserror(errno)); - 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) { - fr_strerror_printf("Failed opening control socket directory: %s", fr_syserror(errno)); - goto error; - } - - /* - * This fails if the radius user can't write - * to the parent directory. - */ - if (mkdirat(parent_fd, p + 1, 0700) < 0) { - fr_strerror_printf("Failed creating control socket directory: %s", fr_syserror(errno)); - 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; - } - if (fchmod(dir_fd, perm) < 0) { - fr_strerror_printf("Failed setting permissions on control socket directory: %s", - fr_syserror(errno)); - goto error; - } - - rad_suid_up(); - if ((uid != (uid_t)-1) || (gid != (gid_t)-1)) ret = fchown(dir_fd, uid, gid); - rad_suid_down(); - if (ret < 0) { - fr_strerror_printf("Failed changing ownership 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 ((uid != (uid_t)-1) && (st.st_uid != uid)) { - struct passwd *need_user, *have_user; - - if (fr_perm_getpwuid(NULL, &need_user, uid) < 0) goto error; - if (fr_perm_getpwuid(NULL, &have_user, st.st_uid) < 0) { - talloc_free(need_user); - goto error; - } - fr_strerror_printf("Control socket directory must be owned by user %s, " - "currently owned by %s", 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)) { - struct group *need_group, *have_group; - - if (fr_perm_getgrgid(NULL, &need_group, gid) < 0) goto error; - if (fr_perm_getgrgid(NULL, &have_group, st.st_gid) < 0) { - talloc_free(need_group); - goto error; - } - fr_strerror_printf("Control socket directory \"%s\" must be owned by group %s, " - "currently owned by %s", dir, need_group->gr_name, have_group->gr_name); - talloc_free(need_group); - talloc_free(have_group); - goto error; - } - - if ((perm & 0x0c) != (st.st_mode & 0x0c)) { - char str_need[10], oct_need[5]; - char str_have[10], oct_have[5]; - - fr_perm_mode_to_str(str_need, perm); - fr_perm_mode_to_oct(oct_need, perm); - fr_perm_mode_to_str(str_have, st.st_mode); - fr_perm_mode_to_oct(oct_have, st.st_mode); - fr_strerror_printf("Control socket directory must have permissions %s (%s), current " - "permissions are %s (%s)", str_need, oct_need, str_have, oct_have); - 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; - } - } - - name = strrchr(path, FR_DIR_SEP); - if (!name) { - fr_strerror_const("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. - */ - if ((uid != (uid_t)-1) && (rad_seuid(uid) < 0)) goto error; - if ((gid != (gid_t)-1) && (rad_segid(gid) < 0)) { - rad_seuid(euid); - 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: - if (uid != (uid_t)-1) rad_seuid(euid); - if (gid != (gid_t)-1) rad_segid(egid); - - 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 creating 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_const("Path too long in socket filename"); - goto 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); - - /* - * Direct socket permissions are only useful on Linux which - * actually enforces them. BSDs don't. They also need to be - * set before binding the socket to a file. - */ -#ifdef __linux__ - if (fchmod(sock_fd, perm) < 0) { - char str_need[10], oct_need[5]; - - fr_perm_mode_to_str(str_need, perm); - fr_perm_mode_to_oct(oct_need, perm); - fr_strerror_printf("Failed changing socket permissions to %s (%s)", str_need, oct_need); - - goto sock_error; - } - - if (fchown(sock_fd, uid, gid) < 0) { - struct passwd *user; - struct group *group; - - if (fr_perm_getpwuid(NULL, &user, uid) < 0) goto sock_error; - if (fr_perm_getgrgid(NULL, &group, gid) < 0) { - talloc_free(user); - goto sock_error; - } - - fr_strerror_printf("Failed changing ownership of socket to %s:%s", user->pw_name, group->gr_name); - talloc_free(user); - talloc_free(group); - goto sock_error; - } -#endif - /* - * 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; - } - - if (listen(sock_fd, 8) < 0) { - fr_strerror_printf("Failed listening on socket: %s", fr_syserror(errno)); - goto sock_error; - } - -#ifdef O_NONBLOCK - { - int flags; - - flags = fcntl(sock_fd, F_GETFL, NULL); - if (flags < 0) { - fr_strerror_printf("Failed getting socket flags: %s", fr_syserror(errno)); - goto sock_error; - } - - flags |= O_NONBLOCK; - if (fcntl(sock_fd, F_SETFL, flags) < 0) { - fr_strerror_printf("Failed setting nonblocking socket flag: %s", fr_syserror(errno)); - goto sock_error; - } - } -#endif - - if (uid != (uid_t)-1) rad_seuid(euid); - if (gid != (gid_t)-1) rad_segid(egid); - - if (dir_fd >= 0) close(dir_fd); - if (parent_fd >= 0) close(parent_fd); - - return sock_fd; -} -#endif - /** Open a UNIX listener for control sockets * */ @@ -939,29 +389,34 @@ static int mod_open(fr_listen_t *li) proto_control_unix_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_control_unix_t); proto_control_unix_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_control_unix_thread_t); - int sockfd ; CONF_ITEM *ci; CONF_SECTION *server_cs; - fr_assert(!thread->connection); - - if (inst->peercred) { - sockfd = fr_server_domain_socket_peercred(inst->filename, inst->uid, inst->gid); - } else { - uid_t uid = inst->uid; - gid_t gid = inst->gid; + fr_bio_fd_info_t const *info; + fr_bio_fd_config_t cfg; - if (uid == ((uid_t)-1)) uid = 0; - if (gid == ((gid_t)-1)) gid = 0; + fr_assert(!thread->connection); - sockfd = fr_server_domain_socket_perm(inst->filename, uid, gid); - } - if (sockfd < 0) { - PERROR("Failed opening UNIX path %s", inst->filename); + cfg = (fr_bio_fd_config_t) { + .type = FR_BIO_FD_ACCEPT, + .socket_type = SOCK_STREAM, + .path = inst->filename, + .uid = inst->uid, + .gid = inst->gid, + .perm = 0x700, + .async = true, + }; + + thread->fd_bio = fr_bio_fd_alloc(thread, NULL, &cfg, 0); + if (!thread->fd_bio) { + PERROR("Failed allocating UNIX path %s", inst->filename); return -1; } - li->fd = thread->sockfd = sockfd; + info = fr_bio_fd_info(thread->fd_bio); + fr_assert(info != NULL); + + li->fd = thread->sockfd = info->socket.fd; ci = cf_parent(inst->cs); /* listen { ... } */ fr_assert(ci != NULL); @@ -986,7 +441,6 @@ static int mod_open(fr_listen_t *li) return 0; } - #if !defined(HAVE_GETPEEREID) && defined(SO_PEERCRED) static int getpeereid(int s, uid_t *euid, gid_t *egid) { @@ -1026,19 +480,21 @@ static int mod_fd_set(fr_listen_t *li, int fd) cookie_io_functions_t io; - thread->name = NULL; - #ifdef HAVE_GETPEEREID /* * Perform user authentication. + * + * @todo - this really belongs in the accept() callback, + * so that we don't create an entirely new listener and + * then close it. */ - if (inst->peercred) { + if (inst->peer_uid || inst->peer_gid) { uid_t uid; gid_t gid; if (getpeereid(fd, &uid, &gid) < 0) { ERROR("Failed getting peer credentials for %s: %s", - inst->filename, fr_syserror(errno)); + inst->filename, fr_syserror(errno)); return -1; } @@ -1048,33 +504,34 @@ static int mod_fd_set(fr_listen_t *li, int fd) * we might as well let them do anything. */ if (uid != 0) do { - /* - * Allow entry if UID or GID matches. - */ - if (inst->uid_name && (inst->uid == uid)) break; - if (inst->gid_name && (inst->gid == gid)) break; - - if (inst->uid_name && (inst->uid != uid)) { - ERROR("Unauthorized connection to %s from uid %ld", - inst->filename, (long int) uid); - return -1; - } - - if (inst->gid_name && (inst->gid != gid)) { - ERROR("Unauthorized connection to %s from gid %ld", - inst->filename, (long int) gid); - return -1; - } - - } while (0); + /* + * Allow entry if UID or GID matches. + */ + if (inst->peer_uid_name && (inst->peer_uid == uid)) break; + if (inst->peer_gid_name && (inst->peer_gid == gid)) break; + + if (inst->peer_uid_name && (inst->peer_uid != uid)) { + ERROR("Unauthorized connection to %s from uid %ld", + inst->filename, (long int) uid); + return -1; + } + + if (inst->peer_gid_name && (inst->peer_gid != gid)) { + ERROR("Unauthorized connection to %s from gid %ld", + inst->filename, (long int) gid); + return -1; + } + + } while (0); thread->name = talloc_typed_asprintf(thread, "proto unix filename %s from peer UID %u GID %u", inst->filename, (unsigned int) uid, (unsigned int) gid); - } + } else #endif - if (!thread->name) thread->name = talloc_typed_asprintf(thread, "proto unix filename %s", inst->filename); + + thread->name = talloc_typed_asprintf(thread, "proto unix filename %s", inst->filename); thread->sockfd = fd; thread->read = mod_read_init; @@ -1133,13 +590,6 @@ static int mod_bootstrap(module_inst_ctx_t const *mctx) FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, <=, INT_MAX); } -#ifndef HAVE_GETPEEREID - if (inst->peercred && (inst->uid_name || inst->gid_name)) { - ERROR("System does not support uid or gid authentication for sockets"); - return -1; - } -#endif - if (inst->uid_name) { struct passwd *pwd; @@ -1149,8 +599,16 @@ static int mod_bootstrap(module_inst_ctx_t const *mctx) } inst->uid = pwd->pw_uid; talloc_free(pwd); + + } else if (main_config->server_uid) { + inst->uid = main_config->server_uid; + } else { - inst->uid = -1; + inst->uid = getuid(); + } + if (!inst->uid) { + ERROR("Cannot open domain sockets as UID root. uid must be set"); + return -1; } if (inst->gid_name) { @@ -1158,8 +616,37 @@ static int mod_bootstrap(module_inst_ctx_t const *mctx) PERROR("Failed getting gid for %s", inst->gid_name); return -1; } + + } else if (main_config->server_gid) { + inst->gid = main_config->server_gid; + } else { - inst->gid = -1; + inst->gid = getgid(); + } + if (!inst->gid) { + ERROR("Cannot open domain sockets as GID root. gid must be set"); + return -1; + } + + /* + * And for peer creds + */ + if (inst->peer_uid_name) { + struct passwd *pwd; + + if (fr_perm_getpwnam(conf, &pwd, inst->peer_uid_name) < 0) { + PERROR("Failed getting peer uid for %s", inst->peer_uid_name); + return -1; + } + inst->peer_uid = pwd->pw_uid; + talloc_free(pwd); + } + + if (inst->peer_gid_name) { + if (fr_perm_gid_from_str(conf, &inst->peer_gid, inst->peer_gid_name) < 0) { + PERROR("Failed getting peer gid for %s", inst->peer_gid_name); + return -1; + } } if (!inst->mode_name) { diff --git a/src/listen/control/proto_control_unix.mk b/src/listen/control/proto_control_unix.mk index a5ff0557d48..66e34598cb7 100644 --- a/src/listen/control/proto_control_unix.mk +++ b/src/listen/control/proto_control_unix.mk @@ -6,4 +6,4 @@ endif SOURCES := proto_control_unix.c -TGT_PREREQS := libfreeradius-control$(L) +TGT_PREREQS := libfreeradius-control$(L) libfreeradius-bio$(L)