]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
move to using bios for at least part of the control socket
authorAlan T. DeKok <aland@freeradius.org>
Tue, 23 Jan 2024 18:55:55 +0000 (13:55 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Tue, 23 Jan 2024 19:13:45 +0000 (14:13 -0500)
raddb/sites-available/control-socket
src/listen/control/proto_control_unix.c
src/listen/control/proto_control_unix.mk

index 118d9d988d5a7c517c7d91a3ed83dd54aa2768ab..0c5001b932d08c77e6e25a1c5f1b84fec446b2ed 100644 (file)
@@ -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
+                       }
+
+               }
        }
 }
index 49d867a79667abca4eba338c1a5fabec9c322353..287b287993661035dd245205a838651cb0d8cfae 100644 (file)
@@ -22,6 +22,8 @@
  * @copyright 2018 The FreeRADIUS server project.
  * @copyright 2018 Alan DeKok (aland@deployingradius.com)
  */
+#include <freeradius-devel/server/main_config.h>
+
 #include <freeradius-devel/io/application.h>
 #include <freeradius-devel/io/listen.h>
 #include <freeradius-devel/io/schedule.h>
 #include <freeradius-devel/util/perm.h>
 #include <freeradius-devel/util/trie.h>
 #include <freeradius-devel/util/file.h>
+
 #include <netdb.h>
 
 #include "proto_control.h"
 
+#include <freeradius-devel/bio/fd.h>
+
 #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) {
index a5ff0557d4846335ca0b595b2783cdcf278d2014..66e34598cb79fe4e68619ce4e04a363fe765749e 100644 (file)
@@ -6,4 +6,4 @@ endif
 
 SOURCES                := proto_control_unix.c
 
-TGT_PREREQS    := libfreeradius-control$(L)
+TGT_PREREQS    := libfreeradius-control$(L) libfreeradius-bio$(L)