From c5651f39ef1660150ffdaed6f35ea14708327adc Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Fri, 9 May 2014 20:03:02 +0200 Subject: SBUS: Add an async request to retrieve the caller ID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. Reviewed-by: Pavel Březina Reviewed-by: Stef Walter (cherry picked from commit 0161a3c5637a0c0092bf54c436bb3d6508d7df26) Conflicts: Makefile.am --- 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 0dfd6003d..65595aa06 100644 --- a/Makefile.am +++ b/Makefile.am @@ -163,6 +163,7 @@ if HAVE_CMOCKA ad_common_tests \ dp_opt_tests \ responder-get-domains-tests \ + sbus-internal-tests \ test_search_bases endif @@ -1411,6 +1412,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_DEPENDENCIES = \ $(ldblib_LTLIBRARIES) test_find_uid_SOURCES = \ diff --git a/src/sbus/sssd_dbus.h b/src/sbus/sssd_dbus.h index 8ba108ee3..15ca52118 100644 --- a/src/sbus/sssd_dbus.h +++ b/src/sbus/sssd_dbus.h @@ -27,6 +27,7 @@ struct sbus_interface; struct sbus_request; #include +#include #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 33d5b1a38..b42020bc2 100644 --- a/src/sbus/sssd_dbus_connection.c +++ b/src/sbus/sssd_dbus_connection.c @@ -173,6 +173,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 58b385b71..65189b5ff 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 + #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 0021ce0e9..2d23931aa 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 000000000..6100037ea --- /dev/null +++ b/src/tests/cmocka/sbus_internal_tests.c @@ -0,0 +1,268 @@ +/* + Authors: + Jakub Hrozek + + 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 . +*/ + +#include +#include + +#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 d27d20b0a..90faa3e42 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 f03fc16b1..4d9f16c0a 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 */ }; -- cgit