From: Sebastian Harl Date: Tue, 9 Dec 2014 14:19:47 +0000 (+0100) Subject: frontend: Get the username of the remote peer from a UNIX socket. X-Git-Tag: sysdb-0.7.0~113 X-Git-Url: https://git.tokkee.org/?p=sysdb.git;a=commitdiff_plain;h=f8809a7d245d9147bba60d907dd11a398941d868 frontend: Get the username of the remote peer from a UNIX socket. Then, only allow that user to authenticate against the daemon. That is, use the same as PostgreSQL's "peer" authentication for all connections. --- diff --git a/configure.ac b/configure.ac index 088a4b5..baf1137 100644 --- a/configure.ac +++ b/configure.ac @@ -344,6 +344,46 @@ AC_SUBST([PROFILING_LDFLAGS]) m4_divert_once([HELP_ENABLE], [ Build dependencies:]) +AC_CHECK_HEADERS([ucred.h]) +dnl On OpenBSD, sys/param.h is required for sys/ucred.h. +AC_CHECK_HEADERS([sys/ucred.h], [], [], + [[ #include ]]) + +AC_CHECK_TYPES([struct ucred], + [have_struct_ucred="yes"], [have_struct_ucred="no"], + [[ +#include +#include +#if HAVE_UCRED_H +# include +#endif + ]]) + +if test "x$have_struct_ucred" != "xyes"; then + AC_MSG_CHECKING([for struct ucred when using _GNU_SOURCE]) + orig_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -D_GNU_SOURCE" + dnl Don't reuse AC_CHECK_HEADERS; for one it'll use the cached value + dnl but also, it will print the "checking for" message a second time. + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[ +#include +#include +#if HAVE_UCRED_H +# include +#endif + ]], + [if (sizeof(struct ucred)) return 0;] + )], + [have_struct_ucred="yes"], [have_struct_ucred="no"]) + CFLAGS="$orig_CFLAGS" + if test "x$have_struct_ucred" = "xyes"; then + AC_DEFINE([_GNU_SOURCE], 1, [Define to enable GNU features.]) + fi + AC_MSG_RESULT([$have_struct_ucred]) +fi + dnl Testing. PKG_CHECK_MODULES([CHECK], [check >= 0.9.4], [unit_tests="yes"], [unit_tests="no"]) @@ -482,11 +522,11 @@ if test "x$have_libfacter" = "xyes"; then AC_MSG_CHECKING([for facter::facts::collection in -lfacter]) AC_LINK_IFELSE( [AC_LANG_PROGRAM( - [[[ #include ]]], - [[[ + [[ #include ]], + [[ facter::facts::collection facts; facts.add_default_facts(); - ]]] + ]] )], [TEST_LIBS=$TEST_LIBS -lfacter], [have_libfacter="yes"], diff --git a/src/client/sock.c b/src/client/sock.c index ef35166..533e9e3 100644 --- a/src/client/sock.c +++ b/src/client/sock.c @@ -194,7 +194,8 @@ sdb_client_connect(sdb_client_t *client, const char *username) "for server response"); if (rstatus == SDB_CONNECTION_ERROR) { - sdb_log(SDB_LOG_ERR, "Access denied for user '%s'", username); + sdb_log(SDB_LOG_ERR, "Access denied for user '%s': %s", + username, sdb_strbuf_string(buf)); status = -((int)rstatus); } else if (rstatus != SDB_CONNECTION_OK) { diff --git a/src/frontend/connection.c b/src/frontend/connection.c index e384baf..b27b3dd 100644 --- a/src/frontend/connection.c +++ b/src/frontend/connection.c @@ -25,6 +25,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + #include "sysdb.h" #include "core/object.h" #include "core/plugin.h" @@ -42,6 +46,19 @@ #include #include +#include +#include +#include + +#ifdef HAVE_UCRED_H +# include +#endif +#ifdef HAVE_SYS_UCRED_H +# include +#endif + +#include + #include /* @@ -59,6 +76,36 @@ static bool conn_ctx_key_initialized = 0; #define CONN_FD_PREFIX "conn#" #define CONN_FD_PLACEHOLDER "XXXXXXX" +/* XXX: only supports UNIX sockets so far */ +static char * +peer(int sockfd) +{ + uid_t uid; + + struct passwd pw_entry; + struct passwd *result = NULL; + char buf[1024]; + +#ifdef SO_PEERCRED + struct ucred cred; + socklen_t len = sizeof(cred); + + if (getsockopt(sockfd, SOL_SOCKET, SO_PEERCRED, &cred, &len) + || (len != sizeof(cred))) + return NULL; + uid = cred.uid; +#else /* SO_PEERCRED */ + errno = ENOSYS; + return NULL; +#endif + + memset(&pw_entry, 0, sizeof(pw_entry)); + if (getpwuid_r(uid, &pw_entry, buf, sizeof(buf), &result) + || (! result)) + return NULL; + return strdup(result->pw_name); +} /* peer */ + static int connection_init(sdb_object_t *obj, va_list ap) { @@ -111,6 +158,16 @@ connection_init(sdb_object_t *obj, va_list ap) return -1; } + conn->username = peer(conn->fd); + if (! conn->username) { + char buf[1024]; + sdb_log(SDB_LOG_ERR, "frontend: Failed to retrieve peer for " + "connection conn#%i: %s", conn->fd, + sdb_strerror(errno, buf, sizeof(buf))); + return -1; + } + conn->ready = 0; + sdb_log(SDB_LOG_DEBUG, "frontend: Accepted connection on fd=%i", conn->fd); diff --git a/src/frontend/session.c b/src/frontend/session.c index b0fc780..ab45cdd 100644 --- a/src/frontend/session.c +++ b/src/frontend/session.c @@ -42,26 +42,33 @@ int sdb_fe_session_start(sdb_conn_t *conn) { - const char *username; + char username[sdb_strbuf_len(conn->buf) + 1]; + const char *tmp; - if ((! conn) || (conn->username)) + if ((! conn) || (conn->cmd != SDB_CONNECTION_STARTUP)) return -1; - if (conn->cmd != SDB_CONNECTION_STARTUP) - return -1; - - username = sdb_strbuf_string(conn->buf); - if ((! username) || (! conn->cmd_len) || (! *username)) { + tmp = sdb_strbuf_string(conn->buf); + if ((! tmp) || (! conn->cmd_len) || (! *tmp)) { sdb_strbuf_sprintf(conn->errbuf, "Invalid empty username"); return -1; } + strncpy(username, tmp, conn->cmd_len); + username[conn->cmd_len] = '\0'; - /* XXX: for now, simply accept all connections */ - conn->username = strndup(username, conn->cmd_len); if (! conn->username) { - sdb_strbuf_sprintf(conn->errbuf, "Authentication failed"); + /* We couldn't determine the remote peer when setting up the + * connection; TODO: add support for password authentication */ + sdb_strbuf_sprintf(conn->errbuf, "Password authentication " + "not supported"); return -1; } + if (strcmp(conn->username, username)) { + sdb_strbuf_sprintf(conn->errbuf, "%s cannot act on behalf of %s", + conn->username, username); + return -1; + } + sdb_connection_send(conn, SDB_CONNECTION_OK, 0, NULL); conn->ready = 1; return 0; diff --git a/t/integration/query.sh b/t/integration/query.sh index 39522cf..5536c70 100755 --- a/t/integration/query.sh +++ b/t/integration/query.sh @@ -48,6 +48,11 @@ run_sysdbd -D -C "$SYSDBD_CONF" wait_for_sysdbd sleep 3 +# Invalid user. +output="$( run_sysdb_nouser -H "$SOCKET_FILE" \ + -U $SYSDB_USER-invalid -c 'LIST hosts' 2>&1 )" && exit 1 +echo "$output" | grep -F 'Access denied' + # On parse errors, expect a non-zero exit code. output="$( run_sysdb -H "$SOCKET_FILE" -c INVALID )" && exit 1 echo "$output" | grep "Failed to parse query 'INVALID'" diff --git a/t/integration/test_lib.sh b/t/integration/test_lib.sh index 0cc1931..f6898e4 100644 --- a/t/integration/test_lib.sh +++ b/t/integration/test_lib.sh @@ -50,8 +50,14 @@ SYSDBD_CONF="$TESTDIR/sysdbd.conf" SOCKET_FILE="$TESTDIR/sock" PLUGIN_DIR="$TESTDIR" +SYSDB_USER="$( id -un )" + function run_sysdb() { - $MEMCHECK "$TESTDIR/sysdb" -U mockuser "$@" + $MEMCHECK "$TESTDIR/sysdb" -U $SYSDB_USER "$@" +} + +function run_sysdb_nouser() { + $MEMCHECK "$TESTDIR/sysdb" "$@" } function run_sysdbd() { diff --git a/t/unit/frontend/connection_test.c b/t/unit/frontend/connection_test.c index 77828e6..13ae221 100644 --- a/t/unit/frontend/connection_test.c +++ b/t/unit/frontend/connection_test.c @@ -32,10 +32,12 @@ #include "frontend/connection.h" #include "frontend/connection-private.h" #include "utils/proto.h" +#include "utils/os.h" #include "libsysdb_test.h" #include "utils/strbuf.h" +#include #include #include @@ -48,6 +50,8 @@ #include #include +static char username[1024]; + /* * private helper functions */ @@ -99,6 +103,9 @@ mock_conn_create(void) unlink(tmp_file); + conn->username = strdup(username); + assert(conn->username); + conn->cmd = SDB_CONNECTION_IDLE; conn->cmd_len = 0; return conn; @@ -174,12 +181,12 @@ connection_startup(sdb_conn_t *conn) { ssize_t check, expected; - expected = 2 * sizeof(uint32_t) + strlen("fakeuser"); + expected = 2 * sizeof(uint32_t) + strlen(username); check = sdb_connection_send(conn, SDB_CONNECTION_STARTUP, - (uint32_t)strlen("fakeuser"), "fakeuser"); + (uint32_t)strlen(username), username); fail_unless(check == expected, - "sdb_connection_send(STARTUP, fakeuser) = %zi; expected: %zi", - check, expected); + "sdb_connection_send(STARTUP, %s) = %zi; expected: %zi", + username, check, expected); mock_conn_rewind(conn); check = sdb_connection_read(conn); @@ -189,7 +196,8 @@ connection_startup(sdb_conn_t *conn) fail_unless(sdb_strbuf_len(conn->errbuf) == 0, "sdb_connection_read() left %zu bytes in the error " - "buffer; expected: 0", sdb_strbuf_len(conn->errbuf)); + "buffer (%s); expected: 0", sdb_strbuf_len(conn->errbuf), + sdb_strbuf_string(conn->errbuf)); mock_conn_truncate(conn); } /* connection_startup */ @@ -244,7 +252,7 @@ START_TEST(test_conn_setup) { UINT32_MAX, NULL, NULL }, { SDB_CONNECTION_IDLE, "fakedata", "Authentication required" }, { SDB_CONNECTION_PING, NULL, "Authentication required" }, - { SDB_CONNECTION_STARTUP, "fakeuser", NULL }, + { SDB_CONNECTION_STARTUP, username, NULL }, { SDB_CONNECTION_PING, NULL, NULL }, { SDB_CONNECTION_IDLE, NULL, "Invalid command 0" }, { SDB_CONNECTION_PING, "fakedata", NULL }, @@ -292,7 +300,8 @@ START_TEST(test_conn_setup) else fail_unless(sdb_strbuf_len(conn->errbuf) == 0, "sdb_connection_read() left %zu bytes in the error " - "buffer; expected: 0", sdb_strbuf_len(conn->errbuf)); + "buffer (%s); expected: 0", sdb_strbuf_len(conn->errbuf), + sdb_strbuf_string(conn->errbuf)); } mock_conn_destroy(conn); @@ -418,6 +427,11 @@ fe_conn_suite(void) Suite *s = suite_create("frontend::connection"); TCase *tc; + char *tmp = sdb_get_current_user(); + assert(tmp); + strcpy(username, tmp); + free(tmp); + tc = tcase_create("core"); tcase_add_test(tc, test_conn_accept); tcase_add_test(tc, test_conn_setup); diff --git a/t/unit/utils/unixsock_test.c b/t/unit/utils/unixsock_test.c index c28781e..82a1daf 100644 --- a/t/unit/utils/unixsock_test.c +++ b/t/unit/utils/unixsock_test.c @@ -30,7 +30,9 @@ #endif /* HAVE_CONFIG_H */ /* required for fopencookie support */ -#define _GNU_SOURCE +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif #include "utils/unixsock.h" #include "libsysdb_test.h"