>From eb0f97525eba8e37533ec00a44622db29dda9338 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Fri, 9 May 2014 20:03:02 +0200
Subject: [PATCH 06/11] SBUS: Add an async request to retrieve the caller ID

Adds an async request sbus_get_sender_id_{send,recv} that allows
retrieval of UID based on "sender" as returned by
dbus_message_get_sender().

The UID is an int64_t to be able to use "-1" to as a fallback value for
uknown or error cases.

The unit test is added as a standalone one, not part of the sbus_tests
because the request, and by extension the unit test relies on being
connected to the system bus, which is very unlikely to work in a build
system.
---
 Makefile.am                            |  22 +++
 src/sbus/sssd_dbus.h                   |   2 +
 src/sbus/sssd_dbus_connection.c        |   7 +
 src/sbus/sssd_dbus_private.h           |  11 ++
 src/sbus/sssd_dbus_request.c           | 201 +++++++++++++++++++++++++
 src/tests/cmocka/sbus_internal_tests.c | 268 +++++++++++++++++++++++++++++++++
 src/util/util_errors.c                 |   2 +
 src/util/util_errors.h                 |   2 +
 8 files changed, 515 insertions(+)
 create mode 100644 src/tests/cmocka/sbus_internal_tests.c

diff --git a/Makefile.am b/Makefile.am
index 4f9c11f47ce49751e585a174b15d8f30d14e1877..d3a6b5c2f80bdd6378dcaaa47b859269129cd839 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -175,6 +175,7 @@ if HAVE_CMOCKA
         ad_common_tests \
         dp_opt_tests \
         responder-get-domains-tests \
+        sbus-internal-tests \
         test_search_bases
 endif
 
@@ -1465,6 +1466,27 @@ responder_get_domains_tests_LDADD = \
     $(SSSD_INTERNAL_LTLIBS) \
     libsss_test_common.la
 
+sbus_internal_tests_SOURCES = \
+    src/tests/cmocka/sbus_internal_tests.c \
+    src/sbus/sssd_dbus_request.c
+sbus_internal_tests_CFLAGS = \
+    $(AM_CFLAGS)
+sbus_internal_tests_LDFLAGS = \
+    -Wl,-wrap,dbus_bus_get \
+    -Wl,-wrap,dbus_pending_call_steal_reply \
+    -Wl,-wrap,dbus_pending_call_unref \
+    -Wl,-wrap,dbus_message_unref \
+    -Wl,-wrap,dbus_connection_unref \
+    -Wl,-wrap,dbus_connection_set_exit_on_disconnect \
+    -Wl,-wrap,hash_lookup
+sbus_internal_tests_LDADD = \
+    $(CMOCKA_LIBS) \
+    $(SSSD_LIBS) \
+    libsss_util.la \
+    libsss_crypt.la \
+    libsss_debug.la \
+    libsss_test_common.la
+
 test_find_uid_SOURCES = \
     src/tests/cmocka/test_find_uid.c \
     src/util/find_uid.c \
diff --git a/src/sbus/sssd_dbus.h b/src/sbus/sssd_dbus.h
index 8ba108ee324e6cf01a8ebc6abf40a9e61f7658c9..15ca521184f82c7424549907b7afa912f891c3ad 100644
--- a/src/sbus/sssd_dbus.h
+++ b/src/sbus/sssd_dbus.h
@@ -27,6 +27,7 @@ struct sbus_interface;
 struct sbus_request;
 
 #include <dbus/dbus.h>
+#include <sys/types.h>
 #include "util/util.h"
 
 typedef int (*sbus_msg_handler_fn)(struct sbus_request *dbus_req,
@@ -187,6 +188,7 @@ void sbus_conn_send_reply(struct sbus_connection *conn,
  * or sbus_request_fail() functions.
  */
 struct sbus_request {
+    int64_t client;
     struct sbus_connection *conn;
     DBusMessage *message;
     struct sbus_interface *intf;
diff --git a/src/sbus/sssd_dbus_connection.c b/src/sbus/sssd_dbus_connection.c
index 58005c23811c412d6621fadc7cfa8923aea49483..288fa1f498a97f1bd321b547a379bcb8a50e28ac 100644
--- a/src/sbus/sssd_dbus_connection.c
+++ b/src/sbus/sssd_dbus_connection.c
@@ -174,6 +174,13 @@ int sbus_init_connection(TALLOC_CTX *ctx,
     conn->dbus.conn = dbus_conn;
     conn->connection_type = connection_type;
 
+    ret = sss_hash_create(conn, 32, &conn->clients);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create clients hash table\n");
+        talloc_free(conn);
+        return EIO;
+    }
+
     ret = sbus_conn_set_fns(conn);
     if (ret != EOK) {
         talloc_free(conn);
diff --git a/src/sbus/sssd_dbus_private.h b/src/sbus/sssd_dbus_private.h
index 58b385b71d5050a9afbe45232378bc62835eb72b..65189b5ff980e34e653a0bc10760290a223e60b6 100644
--- a/src/sbus/sssd_dbus_private.h
+++ b/src/sbus/sssd_dbus_private.h
@@ -22,6 +22,8 @@
 #ifndef _SSSD_DBUS_PRIVATE_H_
 #define _SSSD_DBUS_PRIVATE_H_
 
+#include <dhash.h>
+
 #include "sssd_dbus_meta.h"
 
 union dbus_conn_pointer {
@@ -60,6 +62,7 @@ struct sbus_connection {
     char *symlink;
     sbus_server_conn_init_fn srv_init_fn;
     void *srv_init_data;
+    hash_table_t *clients;
 
     /* watches list */
     struct sbus_watch_ctx *watch_list;
@@ -126,4 +129,12 @@ int sss_dbus_conn_send(DBusConnection *dbus_conn,
                        void *pvt,
                        DBusPendingCall **pending);
 
+
+/* =Retrieve-conn-credentials=============================================== */
+struct tevent_req *sbus_get_sender_id_send(TALLOC_CTX *mem_ctx,
+                                           struct tevent_context *ev,
+                                           struct sbus_connection *conn,
+                                           const char *sender);
+int sbus_get_sender_id_recv(struct tevent_req *req, int64_t *_uid);
+
 #endif /* _SSSD_DBUS_PRIVATE_H_ */
diff --git a/src/sbus/sssd_dbus_request.c b/src/sbus/sssd_dbus_request.c
index 0021ce0e9e1e102b99c11fea3fc67669c3dac88e..2d23931aa07a83c15c30b31615d5db7cfee11a0a 100644
--- a/src/sbus/sssd_dbus_request.c
+++ b/src/sbus/sssd_dbus_request.c
@@ -276,3 +276,204 @@ sbus_request_parse_or_finish(struct sbus_request *request,
 
     return ret;
 }
+
+struct sbus_get_sender_id_state {
+    struct sbus_connection *conn;
+    DBusConnection *sysbus_conn;
+    char *sender;
+    int64_t uid;
+};
+
+static void sbus_get_sender_id_done(DBusPendingCall *pending, void *ptr);
+
+struct tevent_req *sbus_get_sender_id_send(TALLOC_CTX *mem_ctx,
+                                           struct tevent_context *ev,
+                                           struct sbus_connection *conn,
+                                           const char *sender)
+{
+    struct tevent_req *req;
+    struct sbus_get_sender_id_state *state;
+    DBusError dbus_error;
+    DBusMessage *msg = NULL;
+    dbus_bool_t dbret;
+    errno_t ret;
+    hash_key_t key;
+    hash_value_t value;
+
+    req = tevent_req_create(mem_ctx, &state, struct sbus_get_sender_id_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->conn = conn;
+    state->uid = -1;
+
+    if (conn->connection_type != SBUS_CONN_TYPE_SYSBUS) {
+        DEBUG(SSSDBG_TRACE_INTERNAL, "Not a sysbus message, quit\n");
+        ret = EOK;
+        goto immediate;
+    }
+
+    if (sender == NULL) {
+        ret = ERR_SBUS_NO_SENDER;
+        goto immediate;
+    }
+
+    state->sender = talloc_strdup(state, sender);
+    if (state->sender == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    key.type = HASH_KEY_STRING;
+    key.str = discard_const(sender);
+    ret = hash_lookup(conn->clients, &key, &value);
+    if (ret == HASH_SUCCESS) {
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              "%s already present in the clients table\n", sender);
+        state->uid = (int64_t) value.ul;
+        ret = EOK;
+        goto immediate;
+    } else if (ret != HASH_ERROR_KEY_NOT_FOUND) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to look up %s in the clients table\n", sender);
+        ret = ERR_SBUS_GET_SENDER_ERROR;
+        goto immediate;
+    }
+
+    /* We don't know this sender yet, let's ask the system bus */
+
+    /* Connect to the well-known system bus */
+    dbus_error_init(&dbus_error);
+    state->sysbus_conn = dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error);
+    if (state->sysbus_conn == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to connect to D-BUS system bus.\n");
+        ret = ERR_SBUS_GET_SENDER_ERROR;
+        goto immediate;
+    }
+    dbus_connection_set_exit_on_disconnect(state->sysbus_conn, FALSE);
+
+    /* If we ever need to get the SELinux context or the PID here, we need
+     * to call GetConnectionCredentials instead
+     */
+    msg = dbus_message_new_method_call("org.freedesktop.DBus",  /* bus name */
+                                       "/org/freedesktop/DBus", /* path */
+                                       "org.freedesktop.DBus",  /* interface */
+                                       "GetConnectionUnixUser");
+    if (msg == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory?!\n");
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    dbret = dbus_message_append_args(msg,
+                                     DBUS_TYPE_STRING, &sender,
+                                     DBUS_TYPE_INVALID);
+    if (!dbret) {
+        goto immediate;
+    }
+
+    ret = sss_dbus_conn_send(state->sysbus_conn, msg, 3000,
+                             sbus_get_sender_id_done,
+                             req, NULL);
+    dbus_message_unref(msg);
+    msg = NULL;
+    if (ret != EOK) {
+        goto immediate;
+    }
+
+    return req;
+
+immediate:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        if (msg != NULL) {
+            dbus_message_unref(msg);
+        }
+        if (state->sysbus_conn != NULL) {
+            dbus_connection_unref(state->sysbus_conn);
+        }
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void sbus_get_sender_id_done(DBusPendingCall *pending, void *ptr)
+{
+    struct tevent_req *req;
+    struct sbus_get_sender_id_state *state;
+    DBusMessage *reply;
+    DBusError dbus_error;
+    hash_key_t key;
+    hash_value_t value;
+    dbus_bool_t dbret;
+    int ret;
+    uid_t uid;
+
+    dbus_error_init(&dbus_error);
+
+    req = talloc_get_type(ptr, struct tevent_req);
+    state = tevent_req_data(req, struct sbus_get_sender_id_state);
+
+    reply = dbus_pending_call_steal_reply(pending);
+    if (!reply) {
+        /* reply should never be null. This function shouldn't be called
+         * until reply is valid or timeout has occurred. If reply is NULL
+         * here, something is seriously wrong and we should bail out.
+         */
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Severe error. A reply callback was called but no reply "
+               "was received and no timeout occurred\n");
+
+        ret = EIO;
+        goto done;
+    }
+
+    dbret = dbus_message_get_args(reply,
+                                  &dbus_error,
+                                  DBUS_TYPE_UINT32, &uid,
+                                  DBUS_TYPE_INVALID);
+    if (!dbret) {
+        ret = EIO;
+        goto done;
+    }
+
+    state->uid = uid;
+
+    key.type = HASH_KEY_STRING;
+    key.str = talloc_steal(state->conn->clients, state->sender);
+    value.type = HASH_VALUE_UINT;
+    value.ul = state->uid;
+    ret = hash_enter(state->conn->clients, &key, &value);
+    if (ret != HASH_SUCCESS) {
+        ret = EIO;
+        goto done;
+    }
+
+    ret = EOK;
+done:
+    dbus_pending_call_unref(pending);
+    dbus_message_unref(reply);
+    dbus_connection_unref(state->sysbus_conn);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+    } else {
+        tevent_req_done(req);
+    }
+}
+
+int sbus_get_sender_id_recv(struct tevent_req *req, int64_t *_uid)
+{
+    struct sbus_get_sender_id_state *state = \
+                        tevent_req_data(req, struct sbus_get_sender_id_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    if (_uid) {
+        *_uid = state->uid;
+    }
+
+    return EOK;
+}
diff --git a/src/tests/cmocka/sbus_internal_tests.c b/src/tests/cmocka/sbus_internal_tests.c
new file mode 100644
index 0000000000000000000000000000000000000000..6100037ea09d26e63918ccdaa27c3fcf0353ac59
--- /dev/null
+++ b/src/tests/cmocka/sbus_internal_tests.c
@@ -0,0 +1,268 @@
+/*
+    Authors:
+        Jakub Hrozek <jhrozek@redhat.com>
+
+    Copyright (C) 2014 Red Hat
+
+    SSSD tests: SBUS internals
+
+    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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <popt.h>
+#include <dbus/dbus.h>
+
+#include "util/util.h"
+#include "responder/common/responder.h"
+#include "tests/cmocka/common_mock.h"
+#include "sbus/sssd_dbus_private.h"
+
+struct sbus_get_id_ctx {
+    struct sss_test_ctx *stc;
+    struct sbus_connection *conn;
+
+    DBusPendingCallNotifyFunction reply_handler;
+    void *reply_pvt;
+    int last_hash_lookup;
+
+    int64_t expected;
+};
+
+struct sbus_get_id_ctx *global_test_ctx;
+
+DBusConnection *
+__wrap_dbus_bus_get(DBusBusType  type,
+	            DBusError   *error)
+{
+    /* just don't return NULL */
+    return (DBusConnection *) 0x42;
+}
+
+void
+__wrap_dbus_connection_set_exit_on_disconnect(DBusConnection *connection,
+                                              dbus_bool_t     exit_on_disconnect)
+{
+    return;
+}
+
+void __wrap_dbus_pending_call_unref(DBusPendingCall *pending)
+{
+    return;
+}
+
+void __wrap_dbus_message_unref(DBusMessage *message)
+{
+    return;
+}
+
+void __wrap_dbus_connection_unref(DBusConnection *connection)
+{
+    return;
+}
+
+DBusMessage*
+__wrap_dbus_pending_call_steal_reply(DBusPendingCall *pending)
+{
+    return sss_mock_ptr_type(DBusMessage *);
+}
+
+int __real_hash_lookup(hash_table_t *table, hash_key_t *key, hash_value_t *value);
+
+int __wrap_hash_lookup(hash_table_t *table, hash_key_t *key, hash_value_t *value)
+{
+    global_test_ctx->last_hash_lookup = __real_hash_lookup(table, key, value);
+    return global_test_ctx->last_hash_lookup;
+}
+
+static void fake_sbus_msg_done(struct tevent_context *ev,
+                               struct tevent_immediate *imm,
+                               void *pvt)
+{
+    struct sbus_get_id_ctx *test_ctx = talloc_get_type(pvt,
+                                            struct sbus_get_id_ctx);
+    talloc_free(imm);
+    test_ctx->reply_handler(NULL, test_ctx->reply_pvt);
+}
+
+int sss_dbus_conn_send(DBusConnection *dbus_conn,
+                       DBusMessage *msg,
+                       int timeout_ms,
+                       DBusPendingCallNotifyFunction reply_handler,
+                       void *pvt,
+                       DBusPendingCall **pending)
+{
+    struct tevent_immediate *imm;
+
+    global_test_ctx->reply_pvt = pvt;
+    global_test_ctx->reply_handler = reply_handler;
+
+    imm = tevent_create_immediate(global_test_ctx->stc->ev);
+    assert_non_null(imm);
+    tevent_schedule_immediate(imm, global_test_ctx->stc->ev, fake_sbus_msg_done, global_test_ctx);
+
+    return EOK;
+}
+
+void sbus_get_id_test_setup(void **state)
+{
+    struct sbus_get_id_ctx *test_ctx;
+    int ret;
+
+    test_ctx = talloc(global_talloc_context, struct sbus_get_id_ctx);
+    assert_non_null(test_ctx);
+
+    test_ctx->conn = talloc(test_ctx, struct sbus_connection);
+    assert_non_null(test_ctx->conn);
+    test_ctx->conn->connection_type = SBUS_CONN_TYPE_SYSBUS;
+    ret = sss_hash_create(test_ctx->conn, 32, &test_ctx->conn->clients);
+    assert_int_equal(ret, EOK);
+
+    test_ctx->stc = create_ev_test_ctx(test_ctx);
+    assert_non_null(test_ctx->stc);
+
+    *state = test_ctx;
+    global_test_ctx = test_ctx;
+}
+
+void sbus_int_test_get_uid_done(struct tevent_req *req)
+{
+    errno_t ret;
+    int64_t uid;
+    struct sbus_get_id_ctx *test_ctx = tevent_req_callback_data(req,
+                                            struct sbus_get_id_ctx);
+
+    ret = sbus_get_sender_id_recv(req, &uid);
+    talloc_free(req);
+    assert_int_equal(ret, EOK);
+
+    test_ctx->stc->done = true;
+    assert_int_equal(uid, test_ctx->expected);
+}
+
+void sbus_int_test_get_uid(void **state)
+{
+    errno_t ret;
+    struct tevent_req *req;
+    DBusMessage *reply;
+    struct sbus_get_id_ctx *test_ctx = talloc_get_type(*state,
+                                            struct sbus_get_id_ctx);
+
+    uint32_t uid;
+
+    test_ctx->expected = 42;
+    uid = test_ctx->expected;
+
+    reply = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL);
+    assert_non_null(reply);
+    dbus_message_append_args(reply,
+                             DBUS_TYPE_UINT32, &uid,
+                             DBUS_TYPE_INVALID);
+    will_return(__wrap_dbus_pending_call_steal_reply, reply);
+
+    req = sbus_get_sender_id_send(test_ctx, test_ctx->stc->ev,
+                                  test_ctx->conn, __FILE__);
+    tevent_req_set_callback(req, sbus_int_test_get_uid_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->stc);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(test_ctx->last_hash_lookup, HASH_ERROR_KEY_NOT_FOUND);
+
+    /* Now do the same lookup again, just make sure the result was cached */
+    req = sbus_get_sender_id_send(test_ctx, test_ctx->stc->ev,
+                                  test_ctx->conn, __FILE__);
+    tevent_req_set_callback(req, sbus_int_test_get_uid_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->stc);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(test_ctx->last_hash_lookup, HASH_SUCCESS);
+}
+
+void sbus_int_test_get_uid_no_sender_done(struct tevent_req *req)
+{
+    errno_t ret;
+    int64_t uid;
+    struct sbus_get_id_ctx *test_ctx = tevent_req_callback_data(req,
+                                            struct sbus_get_id_ctx);
+
+    ret = sbus_get_sender_id_recv(req, &uid);
+    talloc_free(req);
+    assert_int_equal(ret, ERR_SBUS_NO_SENDER);
+    test_ctx->stc->done = true;
+}
+
+void sbus_int_test_get_uid_no_sender(void **state)
+{
+    errno_t ret;
+    struct tevent_req *req;
+    struct sbus_get_id_ctx *test_ctx = talloc_get_type(*state,
+                                            struct sbus_get_id_ctx);
+
+    uint32_t uid;
+
+    test_ctx->expected = -1;
+    uid = test_ctx->expected;
+
+    req = sbus_get_sender_id_send(test_ctx, test_ctx->stc->ev,
+                                  test_ctx->conn, NULL);
+    tevent_req_set_callback(req, sbus_int_test_get_uid_no_sender_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->stc);
+    assert_int_equal(ret, EOK);
+}
+
+void sbus_get_id_test_teardown(void **state)
+{
+    struct sbus_get_id_ctx *test_ctx = talloc_get_type(*state,
+                                            struct sbus_get_id_ctx);
+    talloc_free(test_ctx);
+}
+
+int main(int argc, const char *argv[])
+{
+    poptContext pc;
+    int opt;
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        SSSD_DEBUG_OPTS
+        POPT_TABLEEND
+    };
+
+    const UnitTest tests[] = {
+        unit_test_setup_teardown(sbus_int_test_get_uid,
+                                 sbus_get_id_test_setup,
+                                 sbus_get_id_test_teardown),
+        unit_test_setup_teardown(sbus_int_test_get_uid_no_sender,
+                                 sbus_get_id_test_setup,
+                                 sbus_get_id_test_teardown),
+    };
+
+    /* Set debug level to invalid value so we can deside if -d 0 was used. */
+    debug_level = SSSDBG_INVALID;
+
+    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+    while((opt = poptGetNextOpt(pc)) != -1) {
+        switch(opt) {
+        default:
+            fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                    poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            return 1;
+        }
+    }
+    poptFreeContext(pc);
+
+    DEBUG_INIT(debug_level);
+    tests_set_cwd();
+    return run_tests(tests);
+}
diff --git a/src/util/util_errors.c b/src/util/util_errors.c
index d27d20b0a511cfd9090707c62064c7d11ebc68cc..90faa3e4217876a5174193f9e407bf87aef3bce2 100644
--- a/src/util/util_errors.c
+++ b/src/util/util_errors.c
@@ -56,6 +56,8 @@ struct err_string error_to_str[] = {
     { "No POSIX attributes detected" }, /* ERR_NO_POSIX */
     { "Extra attribute is a duplicate" }, /* ERR_DUP_EXTRA_ATTR */
     { "Malformed extra attribute" }, /* ERR_INVALID_EXTRA_ATTR */
+    { "Cannot get bus message sender" }, /* ERR_SBUS_GET_SENDER_ERROR */
+    { "Bus message has no sender" }, /* ERR_SBUS_NO_SENDER */
 };
 
 
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index f03fc16b1444d7e1370c22044b248585f076f32a..4d9f16c0aaea9128b8405048cb32a6d57850e1fc 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -78,6 +78,8 @@ enum sssd_errors {
     ERR_NO_POSIX,
     ERR_DUP_EXTRA_ATTR,
     ERR_INVALID_EXTRA_ATTR,
+    ERR_SBUS_GET_SENDER_ERROR,
+    ERR_SBUS_NO_SENDER,
     ERR_LAST            /* ALWAYS LAST */
 };
 
-- 
1.9.0

