diff options
author | Pavel Březina <pbrezina@redhat.com> | 2016-01-15 13:00:45 +0100 |
---|---|---|
committer | Jakub Hrozek <jhrozek@redhat.com> | 2016-06-20 14:48:47 +0200 |
commit | d3dee2a07f1a8ee9ae6f94e149ced754ef76c248 (patch) | |
tree | dcb92cf97dd70a4183d05258b9db0414b91d60a8 /src | |
parent | 565b9955cc439ade58cc24a98168060a60f33e7a (diff) | |
download | sssd-d3dee2a07f1a8ee9ae6f94e149ced754ef76c248.tar.gz sssd-d3dee2a07f1a8ee9ae6f94e149ced754ef76c248.tar.xz sssd-d3dee2a07f1a8ee9ae6f94e149ced754ef76c248.zip |
DP: Introduce new interface for backend
Terminology:
* Backend: Implemenation of domain
* Data Provider: interface between backend and responders
* Module: ldap/ipa/ad/... dlopened library that implements dp interface
* Target: id/autofs/sudo/... functionality of module
Benefits over current code:
* data provider is a black box completely separated from backend
* method handlers are just simple tevent requests on backend side
* no need of spy on be_client
* simplified and error proof adding of new responders
* simplified adding of new methods
* reply to D-Bus message is completely handled by DP code
* each target can have several methods defined
* properties can be added on objects
* each method can have output parameters
* modules now support constructor
* improved debugging
* clear memory hierarchy
* ability to chain requests
* type safe private data
Reviewed-by: Sumit Bose <sbose@redhat.com>
Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
Reviewed-by: Lukáš Slebodník <lslebodn@redhat.com>
Diffstat (limited to 'src')
23 files changed, 3628 insertions, 0 deletions
diff --git a/src/providers/backend.h b/src/providers/backend.h index cfb9044b5..e91ed9c3e 100644 --- a/src/providers/backend.h +++ b/src/providers/backend.h @@ -25,6 +25,7 @@ #include "providers/data_provider.h" #include "providers/fail_over.h" #include "providers/be_refresh.h" +#include "providers/data_provider/dp.h" #include "util/child_common.h" #include "db/sysdb.h" @@ -148,6 +149,8 @@ struct be_ctx { /* List of ongoing requests */ struct be_req *active_requests; + + struct data_provider *provider; }; struct bet_ops { diff --git a/src/providers/data_provider/dp.c b/src/providers/data_provider/dp.c new file mode 100644 index 000000000..c4590e315 --- /dev/null +++ b/src/providers/data_provider/dp.c @@ -0,0 +1,129 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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 <talloc.h> + +#include "config.h" +#include "providers/data_provider/dp.h" +#include "providers/data_provider/dp_private.h" +#include "providers/backend.h" +#include "util/util.h" + +static errno_t dp_init_dbus_server(struct data_provider *provider) +{ + const char *domain; + char *sbus_address; + errno_t ret; + + domain = provider->be_ctx->domain->name; + ret = dp_get_sbus_address(NULL, &sbus_address, domain); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not get sbus backend address.\n"); + return ret; + } + + ret = sbus_new_server(provider, provider->ev, sbus_address, + provider->uid, provider->gid, true, + &provider->srv_conn, + dp_client_init, provider); + talloc_free(sbus_address); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not set up sbus server.\n"); + return ret; + } + + return EOK; +} + +static int dp_destructor(struct data_provider *provider) +{ + enum dp_clients client; + + provider->terminating = true; + + dp_terminate_active_requests(provider); + + for (client = 0; client != DP_CLIENT_SENTINEL; client++) { + talloc_zfree(provider->clients[client]); + } + + return 0; +} + +errno_t dp_init(struct tevent_context *ev, + struct be_ctx *be_ctx, + uid_t uid, + gid_t gid) +{ + struct data_provider *provider; + + errno_t ret; + + provider = talloc_zero(be_ctx, struct data_provider); + if (provider == NULL) { + return ENOMEM; + } + + provider->ev = ev; + provider->uid = uid; + provider->gid = gid; + provider->be_ctx = be_ctx; + + ret = dp_req_table_init(provider, &provider->requests.reply_table); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize request table " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = dp_init_modules(provider, &provider->modules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize DP modules " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = dp_init_targets(provider, be_ctx, provider, provider->modules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize DP targets " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + talloc_set_destructor(provider, dp_destructor); + + ret = dp_init_dbus_server(provider); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to setup service bus [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + be_ctx->provider = provider; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(provider); + } + + return ret; +} diff --git a/src/providers/data_provider/dp.h b/src/providers/data_provider/dp.h new file mode 100644 index 000000000..8cdbd7768 --- /dev/null +++ b/src/providers/data_provider/dp.h @@ -0,0 +1,162 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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/>. +*/ + +#ifndef _DP_H_ +#define _DP_H_ + +#include <stdint.h> + +#include "sbus/sssd_dbus.h" +#include "providers/backend.h" +#include "providers/data_provider/dp_request.h" +#include "providers/data_provider/dp_custom_data.h" +#include "providers/data_provider/dp_flags.h" + +struct data_provider; +struct dp_method; + +/** + * Module constructor. + * + * It is possible to create a module data that is passed into all + * target initialization functions. + */ +typedef errno_t (*dp_module_init_fn)(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + const char *module_name, + void **_module_data); + +/** + * Target initialization function. + * + * Pointer to dp_method is unique for all targets. Make sure that + * dp_set_method is called in all targets even if you are reusing + * some existing context or initialization function. + */ +typedef errno_t (*dp_target_init_fn)(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods); + +enum dp_targets { + DPT_ID, + DPT_AUTH, + DPT_ACCESS, + DPT_CHPASS, + DPT_SUDO, + DPT_AUTOFS, + DPT_SELINUX, + DPT_HOSTID, + DPT_SUBDOMAINS, + + DP_TARGET_SENTINEL +}; + +enum dp_methods { + DPM_CHECK_ONLINE, + DPM_ACCOUNT_HANDLER, + DPM_AUTH_HANDLER, + DPM_ACCESS_HANDLER, + DPM_SELINUX_HANDLER, + DPM_SUDO_HANDLER, + DPM_AUTOFS_HANDLER, + DPM_HOSTID_HANDLER, + DPM_DOMAINS_HANDLER, + + DP_METHOD_SENTINEL +}; + +/* Method handler. */ + +struct dp_req_params { + struct tevent_context *ev; + struct be_ctx *be_ctx; + struct sss_domain_info *domain; + enum dp_targets target; + enum dp_methods method; +}; + +typedef struct tevent_req * +(*dp_req_send_fn)(TALLOC_CTX *mem_ctx, void *method_data, void *request_data, + struct dp_req_params *params); + +typedef errno_t +(*dp_req_recv_fn)(TALLOC_CTX *mem_ctx, struct tevent_req *req, void *data); + +/* Data provider initialization. */ + +errno_t dp_init(struct tevent_context *ev, + struct be_ctx *be_ctx, + uid_t uid, + gid_t gid); + +bool _dp_target_enabled(struct data_provider *provider, + const char *module_name, + ...); + +#define dp_target_enabled(provider, module_name, ...) \ + _dp_target_enabled(provider, module_name, ##__VA_ARGS__, DP_TARGET_SENTINEL) + +struct dp_module *dp_target_module(struct data_provider *provider, + enum dp_targets target); + +void _dp_set_method(struct dp_method *methods, + enum dp_methods method, + dp_req_send_fn send_fn, + dp_req_recv_fn recv_fn, + void *method_data, + const char *method_dtype, + const char *request_dtype, + const char *output_dtype, + uint32_t output_size); + +/* We check function headers on compile time and data types on run time. This + * check requires that both method and request private data are talloc-created + * with talloc name set to data type name (which is done by talloc unless + * you use _size variations of talloc functions. + * + * This way we ensure that we always pass correct data and we can access them + * directly in request handler without the need to cast them explicitly + * from void pointer. */ +#define dp_set_method(methods, method, send_fn, recv_fn, method_data, \ + method_dtype, req_dtype, output_dtype) \ + do { \ + /* Check _send function parameter types. */ \ + struct tevent_req *(*__send_fn)(TALLOC_CTX *, method_dtype *, \ + req_dtype *, struct dp_req_params *params) = (send_fn); \ + \ + /* Check _recv function parameter types. */ \ + errno_t (*__recv_fn)(TALLOC_CTX *, struct tevent_req *, \ + output_dtype *) = (recv_fn); \ + _dp_set_method(methods, method, (dp_req_send_fn)__send_fn, \ + (dp_req_recv_fn)__recv_fn, method_data, \ + #method_dtype, #req_dtype, \ + #output_dtype, sizeof(output_dtype)); \ + } while (0) + +bool dp_method_enabled(struct data_provider *provider, + enum dp_targets target, + enum dp_methods method); + +void dp_terminate_domain_requests(struct data_provider *provider, + const char *domain); + +#endif /* _DP_H_ */ diff --git a/src/providers/data_provider/dp_builtin.c b/src/providers/data_provider/dp_builtin.c new file mode 100644 index 000000000..01d7f6deb --- /dev/null +++ b/src/providers/data_provider/dp_builtin.c @@ -0,0 +1,118 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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 <security/pam_modules.h> +#include <talloc.h> +#include "config.h" +#include "providers/data_provider/dp.h" +#include "providers/backend.h" +#include "util/util.h" + +struct dp_access_permit_handler_state { + struct pam_data *pd; +}; + +struct tevent_req * +dp_access_permit_handler_send(TALLOC_CTX *mem_ctx, + void *data, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct dp_access_permit_handler_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct dp_access_permit_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + DEBUG(SSSDBG_TRACE_ALL, "Access permit, returning PAM_SUCCESS.\n"); + state->pd->pam_status = PAM_SUCCESS; + + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +errno_t +dp_access_permit_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct dp_access_permit_handler_state *state = NULL; + + state = tevent_req_data(req, struct dp_access_permit_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} + +struct dp_access_deny_handler_state { + struct pam_data *pd; +}; + +struct tevent_req * +dp_access_deny_handler_send(TALLOC_CTX *mem_ctx, + void *data, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct dp_access_deny_handler_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct dp_access_deny_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + DEBUG(SSSDBG_TRACE_ALL, "Access deny, returning PAM_PERM_DENIED.\n"); + state->pd->pam_status = PAM_PERM_DENIED; + + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +errno_t +dp_access_deny_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct dp_access_deny_handler_state *state = NULL; + + state = tevent_req_data(req, struct dp_access_deny_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/data_provider/dp_builtin.h b/src/providers/data_provider/dp_builtin.h new file mode 100644 index 000000000..6bd032990 --- /dev/null +++ b/src/providers/data_provider/dp_builtin.h @@ -0,0 +1,50 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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/>. +*/ + +#ifndef _DP_SPECIAL_H_ +#define _DP_SPECIAL_H_ + +#include <talloc.h> +#include <tevent.h> +#include "providers/data_provider/dp.h" + +struct tevent_req * +dp_access_permit_handler_send(TALLOC_CTX *mem_ctx, + void *data, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +dp_access_permit_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +struct tevent_req * +dp_access_deny_handler_send(TALLOC_CTX *mem_ctx, + void *data, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +dp_access_deny_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +#endif /* _DP_SPECIAL_H_ */ diff --git a/src/providers/data_provider/dp_client.c b/src/providers/data_provider/dp_client.c new file mode 100644 index 000000000..7009a5c38 --- /dev/null +++ b/src/providers/data_provider/dp_client.c @@ -0,0 +1,249 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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 "providers/backend.h" +#include "providers/data_provider/dp_iface_generated.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_iface.h" +#include "providers/data_provider/dp.h" +#include "sbus/sssd_dbus.h" +#include "sbus/sssd_dbus_errors.h" +#include "util/util.h" + +struct dp_client { + struct data_provider *provider; + struct sbus_connection *conn; + struct tevent_timer *timeout; + const char *name; + bool initialized; +}; + +const char *dp_client_to_string(enum dp_clients client) +{ + switch (client) { + case DPC_NSS: + return "NSS"; + case DPC_PAM: + return "PAM"; + case DPC_IFP: + return "InfoPipe"; + case DPC_PAC: + return "PAC"; + case DPC_SUDO: + return "SUDO"; + case DPC_HOST: + return "SSH"; + case DPC_AUTOFS: + return "autofs"; + case DP_CLIENT_SENTINEL: + return "Invalid"; + } + + return "Invalid"; +} + +static int dp_client_destructor(struct dp_client *dp_cli) +{ + struct data_provider *provider; + enum dp_clients client; + + if (dp_cli->provider == NULL) { + return 0; + } + + provider = dp_cli->provider; + + for (client = 0; client != DP_CLIENT_SENTINEL; client++) { + if (provider->clients[client] == dp_cli) { + provider->clients[client] = NULL; + DEBUG(SSSDBG_TRACE_FUNC, "Removed %s client\n", + dp_client_to_string(client)); + break; + } + } + + if (client == DP_CLIENT_SENTINEL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown client removed...\n"); + } + + return 0; +} + +static int +dp_client_register(struct sbus_request *sbus_req, + void *data, + const char *client_name) +{ + struct data_provider *provider; + struct dp_client *dp_cli; + struct DBusError *error; + enum dp_clients client; + errno_t ret; + + dp_cli = talloc_get_type(data, struct dp_client); + if (dp_cli == NULL) { + /* Do not send D-Bus error here. */ + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: dp_cli is NULL\n"); + return EINVAL; + } + + provider = dp_cli->provider; + dp_cli->name = talloc_strdup(dp_cli, client_name); + if (dp_cli->name == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Cancel DP ID timeout [%p]\n", dp_cli->timeout); + talloc_zfree(dp_cli->timeout); + + for (client = 0; client != DP_CLIENT_SENTINEL; client++) { + if (strcasecmp(client_name, dp_client_to_string(client)) == 0) { + provider->clients[client] = dp_cli; + break; + } + } + + if (client == DP_CLIENT_SENTINEL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown client! [%s]\n", client_name); + error = sbus_error_new(sbus_req, SBUS_ERROR_NOT_FOUND, + "Unknown client [%s]", client_name); + + /* Kill this client. */ + talloc_free(dp_cli); + return sbus_request_fail_and_finish(sbus_req, error); + } + + talloc_set_destructor(dp_cli, dp_client_destructor); + + ret = iface_dp_client_Register_finish(sbus_req); + if (ret != EOK) { + DEBUG(SSSDBG_CONF_SETTINGS, "Unable to send ack to the client [%s], " + "disconnecting...\n", client_name); + sbus_disconnect(sbus_req->conn); + return ret; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Added Frontend client [%s]\n", client_name); + + dp_cli->initialized = true; + return EOK; +} + +static void +dp_client_handshake_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, + void *ptr) +{ + struct dp_client *dp_cli; + + DEBUG(SSSDBG_OP_FAILURE, + "Client timed out before identification [%p]!\n", te); + + dp_cli = talloc_get_type(ptr, struct dp_client); + + sbus_disconnect(dp_cli->conn); + talloc_zfree(dp_cli); +} + +errno_t dp_client_init(struct sbus_connection *conn, void *data) +{ + struct data_provider *provider; + struct dp_client *dp_cli; + struct timeval tv; + errno_t ret; + + static struct iface_dp_client iface_dp_client = { + { &iface_dp_client_meta, 0 }, + + .Register = dp_client_register, + }; + + provider = talloc_get_type(data, struct data_provider); + + /* When connection is lost we also free the client. */ + dp_cli = talloc_zero(conn, struct dp_client); + if (dp_cli == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory, killing connection.\n"); + talloc_free(conn); + return ENOMEM; + } + + dp_cli->provider = provider; + dp_cli->conn = conn; + dp_cli->initialized = false; + dp_cli->timeout = NULL; + + /* Allow access from the SSSD user. */ + sbus_allow_uid(conn, &provider->uid); + + /* Setup timeout in case client fails to register himself in time. */ + tv = tevent_timeval_current_ofs(5, 0); + dp_cli->timeout = tevent_add_timer(provider->ev, dp_cli, tv, + dp_client_handshake_timeout, dp_cli); + if (dp_cli->timeout == NULL) { + /* Connection is closed in the caller. */ + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory, killing connection\n"); + return ENOMEM; + } + + DEBUG(SSSDBG_CONF_SETTINGS, + "Set-up Backend ID timeout [%p]\n", dp_cli->timeout); + + /* Setup D-Bus interfaces and methods. */ + ret = sbus_conn_register_iface(conn, &iface_dp_client.vtable, + DP_PATH, dp_cli); + if (ret != EOK) { + /* Connection is closed in the caller. */ + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to register D-Bus interface, " + "killing connection [%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + ret = dp_register_sbus_interface(conn, dp_cli); + if (ret != EOK) { + /* Connection is closed in the caller. */ + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to register D-Bus interface, " + "killing connection [%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + return ret; +} + +struct data_provider * +dp_client_provider(struct dp_client *dp_cli) +{ + if (dp_cli == NULL) { + return NULL; + } + + return dp_cli->provider; +} + +struct sbus_connection * +dp_client_conn(struct dp_client *dp_cli) +{ + if (dp_cli == NULL) { + return NULL; + } + + return dp_cli->conn; +} diff --git a/src/providers/data_provider/dp_custom_data.h b/src/providers/data_provider/dp_custom_data.h new file mode 100644 index 000000000..4c03dc93a --- /dev/null +++ b/src/providers/data_provider/dp_custom_data.h @@ -0,0 +1,32 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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/>. +*/ + +#ifndef _DP_CUSTOM_DATA_H_ +#define _DP_CUSTOM_DATA_H_ + +#include "providers/data_provider/dp.h" + +/* Request handler private data. */ + +/* Reply private data. */ + +/* Reply callbacks. */ + +#endif /* _DP_CUSTOM_DATA_H_ */ diff --git a/src/providers/data_provider/dp_flags.h b/src/providers/data_provider/dp_flags.h new file mode 100644 index 000000000..52e666d0c --- /dev/null +++ b/src/providers/data_provider/dp_flags.h @@ -0,0 +1,29 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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/>. +*/ + +#ifndef _DP_FLAGS_H_ +#define _DP_FLAGS_H_ + +/** + * If backend is offline, respond with ERR_OFFLINE immediately. + */ +#define DP_FAST_REPLY 0x0001 + +#endif /* _DP_FLAGS_H_ */ diff --git a/src/providers/data_provider/dp_iface.c b/src/providers/data_provider/dp_iface.c new file mode 100644 index 000000000..5eaf7e2a3 --- /dev/null +++ b/src/providers/data_provider/dp_iface.c @@ -0,0 +1,49 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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 <dbus/dbus.h> + +#include "sbus/sssd_dbus.h" +#include "providers/data_provider/dp_iface_generated.h" +#include "providers/data_provider/dp_iface.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp.h" + +struct iface_dp iface_dp = { + {&iface_dp_meta, 0}, + .pamHandler = NULL, + .sudoHandler = NULL, + .autofsHandler = NULL, + .hostHandler = NULL, + .getDomains = NULL, + .getAccountInfo = NULL +}; + +static struct sbus_iface_map dp_map[] = { + { DP_PATH, &iface_dp.vtable }, + { NULL, NULL } +}; + +errno_t +dp_register_sbus_interface(struct sbus_connection *conn, + struct dp_client *pvt) +{ + return sbus_conn_register_iface_map(conn, dp_map, pvt); +} diff --git a/src/providers/data_provider/dp_iface.h b/src/providers/data_provider/dp_iface.h new file mode 100644 index 000000000..2991bf7aa --- /dev/null +++ b/src/providers/data_provider/dp_iface.h @@ -0,0 +1,34 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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/>. +*/ + +#ifndef DP_IFACE_H_ +#define DP_IFACE_H_ + +#include "sbus/sssd_dbus.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_responder_iface.h" +#include "providers/data_provider/dp.h" + +#define DP_PATH "/org/freedesktop/sssd/dataprovider" + +errno_t dp_register_sbus_interface(struct sbus_connection *conn, + struct dp_client *pvt); + +#endif /* DP_IFACE_H_ */ diff --git a/src/providers/data_provider/dp_iface.xml b/src/providers/data_provider/dp_iface.xml new file mode 100644 index 000000000..f508968c8 --- /dev/null +++ b/src/providers/data_provider/dp_iface.xml @@ -0,0 +1,54 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node> + <interface name="org.freedesktop.sssd.DataProvider.Client"> + <annotation value="iface_dp_client" name="org.freedesktop.DBus.GLib.CSymbol"/> + <method name="Register"> + <arg name="Name" type="s" direction="in" /> + </method> + </interface> + + <interface name="org.freedesktop.sssd.dataprovider"> + <annotation value="iface_dp" name="org.freedesktop.DBus.GLib.CSymbol"/> + <method name="pamHandler"> + <!-- arguments parsed manually, raw handler --> + <annotation name="org.freedesktop.sssd.RawHandler" value="true"/> + </method> + <method name="sudoHandler"> + <!-- arguments parsed manually, raw handler --> + <annotation name="org.freedesktop.sssd.RawHandler" value="true"/> + </method> + <method name="autofsHandler"> + <arg name="dp_flags" type="u" direction="in" /> + <arg name="mapname" type="s" direction="in" /> + <arg name="dp_error" type="q" direction="out" /> + <arg name="error" type="u" direction="out" /> + <arg name="error_message" type="s" direction="out" /> + </method> + <method name="hostHandler"> + <arg name="dp_flags" type="u" direction="in" /> + <arg name="name" type="s" direction="in" /> + <arg name="alias" type="s" direction="in" /> + <arg name="dp_error" type="q" direction="out" /> + <arg name="error" type="u" direction="out" /> + <arg name="error_message" type="s" direction="out" /> + </method> + <method name="getDomains"> + <arg name="domain_hint" type="s" direction="in" /> + <arg name="dp_error" type="q" direction="out" /> + <arg name="error" type="u" direction="out" /> + <arg name="error_message" type="s" direction="out" /> + </method> + <method name="getAccountInfo"> + <arg name="dp_flags" type="u" direction="in" /> + <arg name="entry_type" type="u" direction="in" /> + <arg name="attr_type" type="u" direction="in" /> + <arg name="filter" type="s" direction="in" /> + <arg name="domain" type="s" direction="in" /> + <arg name="extra" type="s" direction="in" /> + <arg name="dp_error" type="q" direction="out" /> + <arg name="error" type="u" direction="out" /> + <arg name="error_message" type="s" direction="out" /> + </method> + </interface> +</node> diff --git a/src/providers/data_provider/dp_iface_generated.c b/src/providers/data_provider/dp_iface_generated.c new file mode 100644 index 000000000..7fee5326d --- /dev/null +++ b/src/providers/data_provider/dp_iface_generated.c @@ -0,0 +1,296 @@ +/* The following definitions are auto-generated from dp_iface.xml */ + +#include "util/util.h" +#include "sbus/sssd_dbus.h" +#include "sbus/sssd_dbus_meta.h" +#include "sbus/sssd_dbus_invokers.h" +#include "dp_iface_generated.h" + +/* invokes a handler with a 's' DBus signature */ +static int invoke_s_method(struct sbus_request *dbus_req, void *function_ptr); + +/* invokes a handler with a 'us' DBus signature */ +static int invoke_us_method(struct sbus_request *dbus_req, void *function_ptr); + +/* invokes a handler with a 'uss' DBus signature */ +static int invoke_uss_method(struct sbus_request *dbus_req, void *function_ptr); + +/* invokes a handler with a 'uuusss' DBus signature */ +static int invoke_uuusss_method(struct sbus_request *dbus_req, void *function_ptr); + +/* arguments for org.freedesktop.sssd.DataProvider.Client.Register */ +const struct sbus_arg_meta iface_dp_client_Register__in[] = { + { "Name", "s" }, + { NULL, } +}; + +int iface_dp_client_Register_finish(struct sbus_request *req) +{ + return sbus_request_return_and_finish(req, + DBUS_TYPE_INVALID); +} + +/* methods for org.freedesktop.sssd.DataProvider.Client */ +const struct sbus_method_meta iface_dp_client__methods[] = { + { + "Register", /* name */ + iface_dp_client_Register__in, + NULL, /* no out_args */ + offsetof(struct iface_dp_client, Register), + invoke_s_method, + }, + { NULL, } +}; + +/* interface info for org.freedesktop.sssd.DataProvider.Client */ +const struct sbus_interface_meta iface_dp_client_meta = { + "org.freedesktop.sssd.DataProvider.Client", /* name */ + iface_dp_client__methods, + NULL, /* no signals */ + NULL, /* no properties */ + sbus_invoke_get_all, /* GetAll invoker */ +}; + +/* arguments for org.freedesktop.sssd.dataprovider.autofsHandler */ +const struct sbus_arg_meta iface_dp_autofsHandler__in[] = { + { "dp_flags", "u" }, + { "mapname", "s" }, + { NULL, } +}; + +/* arguments for org.freedesktop.sssd.dataprovider.autofsHandler */ +const struct sbus_arg_meta iface_dp_autofsHandler__out[] = { + { "dp_error", "q" }, + { "error", "u" }, + { "error_message", "s" }, + { NULL, } +}; + +int iface_dp_autofsHandler_finish(struct sbus_request *req, uint16_t arg_dp_error, uint32_t arg_error, const char *arg_error_message) +{ + return sbus_request_return_and_finish(req, + DBUS_TYPE_UINT16, &arg_dp_error, + DBUS_TYPE_UINT32, &arg_error, + DBUS_TYPE_STRING, &arg_error_message, + DBUS_TYPE_INVALID); +} + +/* arguments for org.freedesktop.sssd.dataprovider.hostHandler */ +const struct sbus_arg_meta iface_dp_hostHandler__in[] = { + { "dp_flags", "u" }, + { "name", "s" }, + { "alias", "s" }, + { NULL, } +}; + +/* arguments for org.freedesktop.sssd.dataprovider.hostHandler */ +const struct sbus_arg_meta iface_dp_hostHandler__out[] = { + { "dp_error", "q" }, + { "error", "u" }, + { "error_message", "s" }, + { NULL, } +}; + +int iface_dp_hostHandler_finish(struct sbus_request *req, uint16_t arg_dp_error, uint32_t arg_error, const char *arg_error_message) +{ + return sbus_request_return_and_finish(req, + DBUS_TYPE_UINT16, &arg_dp_error, + DBUS_TYPE_UINT32, &arg_error, + DBUS_TYPE_STRING, &arg_error_message, + DBUS_TYPE_INVALID); +} + +/* arguments for org.freedesktop.sssd.dataprovider.getDomains */ +const struct sbus_arg_meta iface_dp_getDomains__in[] = { + { "domain_hint", "s" }, + { NULL, } +}; + +/* arguments for org.freedesktop.sssd.dataprovider.getDomains */ +const struct sbus_arg_meta iface_dp_getDomains__out[] = { + { "dp_error", "q" }, + { "error", "u" }, + { "error_message", "s" }, + { NULL, } +}; + +int iface_dp_getDomains_finish(struct sbus_request *req, uint16_t arg_dp_error, uint32_t arg_error, const char *arg_error_message) +{ + return sbus_request_return_and_finish(req, + DBUS_TYPE_UINT16, &arg_dp_error, + DBUS_TYPE_UINT32, &arg_error, + DBUS_TYPE_STRING, &arg_error_message, + DBUS_TYPE_INVALID); +} + +/* arguments for org.freedesktop.sssd.dataprovider.getAccountInfo */ +const struct sbus_arg_meta iface_dp_getAccountInfo__in[] = { + { "dp_flags", "u" }, + { "entry_type", "u" }, + { "attr_type", "u" }, + { "filter", "s" }, + { "domain", "s" }, + { "extra", "s" }, + { NULL, } +}; + +/* arguments for org.freedesktop.sssd.dataprovider.getAccountInfo */ +const struct sbus_arg_meta iface_dp_getAccountInfo__out[] = { + { "dp_error", "q" }, + { "error", "u" }, + { "error_message", "s" }, + { NULL, } +}; + +int iface_dp_getAccountInfo_finish(struct sbus_request *req, uint16_t arg_dp_error, uint32_t arg_error, const char *arg_error_message) +{ + return sbus_request_return_and_finish(req, + DBUS_TYPE_UINT16, &arg_dp_error, + DBUS_TYPE_UINT32, &arg_error, + DBUS_TYPE_STRING, &arg_error_message, + DBUS_TYPE_INVALID); +} + +/* methods for org.freedesktop.sssd.dataprovider */ +const struct sbus_method_meta iface_dp__methods[] = { + { + "pamHandler", /* name */ + NULL, /* no in_args */ + NULL, /* no out_args */ + offsetof(struct iface_dp, pamHandler), + NULL, /* no invoker */ + }, + { + "sudoHandler", /* name */ + NULL, /* no in_args */ + NULL, /* no out_args */ + offsetof(struct iface_dp, sudoHandler), + NULL, /* no invoker */ + }, + { + "autofsHandler", /* name */ + iface_dp_autofsHandler__in, + iface_dp_autofsHandler__out, + offsetof(struct iface_dp, autofsHandler), + invoke_us_method, + }, + { + "hostHandler", /* name */ + iface_dp_hostHandler__in, + iface_dp_hostHandler__out, + offsetof(struct iface_dp, hostHandler), + invoke_uss_method, + }, + { + "getDomains", /* name */ + iface_dp_getDomains__in, + iface_dp_getDomains__out, + offsetof(struct iface_dp, getDomains), + invoke_s_method, + }, + { + "getAccountInfo", /* name */ + iface_dp_getAccountInfo__in, + iface_dp_getAccountInfo__out, + offsetof(struct iface_dp, getAccountInfo), + invoke_uuusss_method, + }, + { NULL, } +}; + +/* interface info for org.freedesktop.sssd.dataprovider */ +const struct sbus_interface_meta iface_dp_meta = { + "org.freedesktop.sssd.dataprovider", /* name */ + iface_dp__methods, + NULL, /* no signals */ + NULL, /* no properties */ + sbus_invoke_get_all, /* GetAll invoker */ +}; + +/* invokes a handler with a 's' DBus signature */ +static int invoke_s_method(struct sbus_request *dbus_req, void *function_ptr) +{ + const char * arg_0; + int (*handler)(struct sbus_request *, void *, const char *) = function_ptr; + + if (!sbus_request_parse_or_finish(dbus_req, + DBUS_TYPE_STRING, &arg_0, + DBUS_TYPE_INVALID)) { + return EOK; /* request handled */ + } + + return (handler)(dbus_req, dbus_req->intf->handler_data, + arg_0); +} + +/* invokes a handler with a 'uss' DBus signature */ +static int invoke_uss_method(struct sbus_request *dbus_req, void *function_ptr) +{ + uint32_t arg_0; + const char * arg_1; + const char * arg_2; + int (*handler)(struct sbus_request *, void *, uint32_t, const char *, const char *) = function_ptr; + + if (!sbus_request_parse_or_finish(dbus_req, + DBUS_TYPE_UINT32, &arg_0, + DBUS_TYPE_STRING, &arg_1, + DBUS_TYPE_STRING, &arg_2, + DBUS_TYPE_INVALID)) { + return EOK; /* request handled */ + } + + return (handler)(dbus_req, dbus_req->intf->handler_data, + arg_0, + arg_1, + arg_2); +} + +/* invokes a handler with a 'uuusss' DBus signature */ +static int invoke_uuusss_method(struct sbus_request *dbus_req, void *function_ptr) +{ + uint32_t arg_0; + uint32_t arg_1; + uint32_t arg_2; + const char * arg_3; + const char * arg_4; + const char * arg_5; + int (*handler)(struct sbus_request *, void *, uint32_t, uint32_t, uint32_t, const char *, const char *, const char *) = function_ptr; + + if (!sbus_request_parse_or_finish(dbus_req, + DBUS_TYPE_UINT32, &arg_0, + DBUS_TYPE_UINT32, &arg_1, + DBUS_TYPE_UINT32, &arg_2, + DBUS_TYPE_STRING, &arg_3, + DBUS_TYPE_STRING, &arg_4, + DBUS_TYPE_STRING, &arg_5, + DBUS_TYPE_INVALID)) { + return EOK; /* request handled */ + } + + return (handler)(dbus_req, dbus_req->intf->handler_data, + arg_0, + arg_1, + arg_2, + arg_3, + arg_4, + arg_5); +} + +/* invokes a handler with a 'us' DBus signature */ +static int invoke_us_method(struct sbus_request *dbus_req, void *function_ptr) +{ + uint32_t arg_0; + const char * arg_1; + int (*handler)(struct sbus_request *, void *, uint32_t, const char *) = function_ptr; + + if (!sbus_request_parse_or_finish(dbus_req, + DBUS_TYPE_UINT32, &arg_0, + DBUS_TYPE_STRING, &arg_1, + DBUS_TYPE_INVALID)) { + return EOK; /* request handled */ + } + + return (handler)(dbus_req, dbus_req->intf->handler_data, + arg_0, + arg_1); +} diff --git a/src/providers/data_provider/dp_iface_generated.h b/src/providers/data_provider/dp_iface_generated.h new file mode 100644 index 000000000..f1bcf17d0 --- /dev/null +++ b/src/providers/data_provider/dp_iface_generated.h @@ -0,0 +1,93 @@ +/* The following declarations are auto-generated from dp_iface.xml */ + +#ifndef __DP_IFACE_XML__ +#define __DP_IFACE_XML__ + +#include "sbus/sssd_dbus.h" + +/* ------------------------------------------------------------------------ + * DBus Constants + * + * Various constants of interface and method names mostly for use by clients + */ + +/* constants for org.freedesktop.sssd.DataProvider.Client */ +#define IFACE_DP_CLIENT "org.freedesktop.sssd.DataProvider.Client" +#define IFACE_DP_CLIENT_REGISTER "Register" + +/* constants for org.freedesktop.sssd.dataprovider */ +#define IFACE_DP "org.freedesktop.sssd.dataprovider" +#define IFACE_DP_PAMHANDLER "pamHandler" +#define IFACE_DP_SUDOHANDLER "sudoHandler" +#define IFACE_DP_AUTOFSHANDLER "autofsHandler" +#define IFACE_DP_HOSTHANDLER "hostHandler" +#define IFACE_DP_GETDOMAINS "getDomains" +#define IFACE_DP_GETACCOUNTINFO "getAccountInfo" + +/* ------------------------------------------------------------------------ + * DBus handlers + * + * These structures are filled in by implementors of the different + * dbus interfaces to handle method calls. + * + * Handler functions of type sbus_msg_handler_fn accept raw messages, + * other handlers are typed appropriately. If a handler that is + * set to NULL is invoked it will result in a + * org.freedesktop.DBus.Error.NotSupported error for the caller. + * + * Handlers have a matching xxx_finish() function (unless the method has + * accepts raw messages). These finish functions the + * sbus_request_return_and_finish() with the appropriate arguments to + * construct a valid reply. Once a finish function has been called, the + * @dbus_req it was called with is freed and no longer valid. + */ + +/* vtable for org.freedesktop.sssd.DataProvider.Client */ +struct iface_dp_client { + struct sbus_vtable vtable; /* derive from sbus_vtable */ + int (*Register)(struct sbus_request *req, void *data, const char *arg_Name); +}; + +/* finish function for Register */ +int iface_dp_client_Register_finish(struct sbus_request *req); + +/* vtable for org.freedesktop.sssd.dataprovider */ +struct iface_dp { + struct sbus_vtable vtable; /* derive from sbus_vtable */ + sbus_msg_handler_fn pamHandler; + sbus_msg_handler_fn sudoHandler; + int (*autofsHandler)(struct sbus_request *req, void *data, uint32_t arg_dp_flags, const char *arg_mapname); + int (*hostHandler)(struct sbus_request *req, void *data, uint32_t arg_dp_flags, const char *arg_name, const char *arg_alias); + int (*getDomains)(struct sbus_request *req, void *data, const char *arg_domain_hint); + int (*getAccountInfo)(struct sbus_request *req, void *data, uint32_t arg_dp_flags, uint32_t arg_entry_type, uint32_t arg_attr_type, const char *arg_filter, const char *arg_domain, const char *arg_extra); +}; + +/* finish function for autofsHandler */ +int iface_dp_autofsHandler_finish(struct sbus_request *req, uint16_t arg_dp_error, uint32_t arg_error, const char *arg_error_message); + +/* finish function for hostHandler */ +int iface_dp_hostHandler_finish(struct sbus_request *req, uint16_t arg_dp_error, uint32_t arg_error, const char *arg_error_message); + +/* finish function for getDomains */ +int iface_dp_getDomains_finish(struct sbus_request *req, uint16_t arg_dp_error, uint32_t arg_error, const char *arg_error_message); + +/* finish function for getAccountInfo */ +int iface_dp_getAccountInfo_finish(struct sbus_request *req, uint16_t arg_dp_error, uint32_t arg_error, const char *arg_error_message); + +/* ------------------------------------------------------------------------ + * DBus Interface Metadata + * + * These structure definitions are filled in with the information about + * the interfaces, methods, properties and so on. + * + * The actual definitions are found in the accompanying C file next + * to this header. + */ + +/* interface info for org.freedesktop.sssd.DataProvider.Client */ +extern const struct sbus_interface_meta iface_dp_client_meta; + +/* interface info for org.freedesktop.sssd.dataprovider */ +extern const struct sbus_interface_meta iface_dp_meta; + +#endif /* __DP_IFACE_XML__ */ diff --git a/src/providers/data_provider/dp_methods.c b/src/providers/data_provider/dp_methods.c new file mode 100644 index 000000000..e4290beee --- /dev/null +++ b/src/providers/data_provider/dp_methods.c @@ -0,0 +1,122 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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 <talloc.h> + +#include "config.h" +#include "providers/data_provider/dp.h" +#include "providers/data_provider/dp_private.h" +#include "providers/backend.h" +#include "util/util.h" + +void _dp_set_method(struct dp_method *methods, + enum dp_methods method, + dp_req_send_fn send_fn, + dp_req_recv_fn recv_fn, + void *method_data, + const char *method_dtype, + const char *request_dtype, + const char *output_dtype, + uint32_t output_size) +{ + if (method >= DP_METHOD_SENTINEL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: invalid method %d\n", method); + return; + } + + /* Each method can be set only once, if we attempt to set it twice it + * is a bug in module initialization. */ + if (methods[method].send_fn != NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: method %d is already set!\n", method); + return; + } + + if (send_fn == NULL || recv_fn == NULL || method_dtype == NULL + || request_dtype == NULL || output_dtype == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: one or more required parameter was " + "not provided for method %d\n", method); + return; + } + + methods[method].send_fn = send_fn; + methods[method].recv_fn = recv_fn; + methods[method].method_data = method_data; + + methods[method].method_dtype = method_dtype; + methods[method].request_dtype = request_dtype; + methods[method].output_dtype = output_dtype; + methods[method].output_size = output_size; +} + +bool dp_method_enabled(struct data_provider *provider, + enum dp_targets target, + enum dp_methods method) +{ + struct dp_target *dp_target; + + if (target >= DP_TARGET_SENTINEL || method >= DP_METHOD_SENTINEL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: Invalid target or method ID\n"); + return false; + } + + dp_target = provider->targets[target]; + if (dp_target == NULL || dp_target->initialized == false) { + DEBUG(SSSDBG_TRACE_FUNC, "Target %s is not configured\n", + dp_target_to_string(target)); + return false; + } + + if (dp_target->methods[method].send_fn == NULL) { + return false; + } + + return true; +} + +errno_t dp_find_method(struct data_provider *provider, + enum dp_targets target, + enum dp_methods method, + struct dp_method **_execute) +{ + struct dp_method *execute; + + if (target >= DP_TARGET_SENTINEL || method >= DP_METHOD_SENTINEL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: Invalid target or method ID\n"); + return ERR_INTERNAL; + } + + if (!dp_target_initialized(provider->targets, target)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Target [%s] is not initialized\n", + dp_target_to_string(target)); + return ERR_MISSING_DP_TARGET; + } + + execute = &provider->targets[target]->methods[method]; + if (execute->send_fn == NULL || execute->recv_fn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Bug: Invalid combination of target [%s] and method [%d]\n", + dp_target_to_string(target), method); + return ERR_INTERNAL; + } + + *_execute = execute; + + return EOK; +} diff --git a/src/providers/data_provider/dp_modules.c b/src/providers/data_provider/dp_modules.c new file mode 100644 index 000000000..2e6e33ddb --- /dev/null +++ b/src/providers/data_provider/dp_modules.c @@ -0,0 +1,224 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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 <dlfcn.h> +#include "config.h" +#include "providers/data_provider/dp.h" +#include "providers/data_provider/dp_private.h" +#include "providers/backend.h" +#include "util/util.h" + +/* There can be at most the same number of different modules loaded at + * one time as the maximum number of defined targets. */ +#define DP_MAX_MODULES DP_TARGET_SENTINEL + +#define DP_MODULE_PATH DATA_PROVIDER_PLUGINS_PATH "/libsss_%s.so" +#define DP_MODULE_INIT_FN "sssm_%s_init" + +static errno_t dp_module_open_lib(struct dp_module *module) +{ + char *libpath = NULL; + errno_t ret; + + libpath = talloc_asprintf(module, DP_MODULE_PATH, module->name); + if (libpath == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Loading module [%s] with path [%s]\n", + module->name, libpath); + + module->libhandle = dlopen(libpath, RTLD_NOW); + if (module->libhandle == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to load module [%s] with path " + "[%s]: %s\n", module->name, libpath, dlerror()); + ret = ELIBACC; + goto done; + } + + ret = EOK; + +done: + talloc_free(libpath); + return ret; +} + +static errno_t dp_module_run_constructor(struct dp_module *module, + struct be_ctx *be_ctx, + struct data_provider *provider) +{ + char *fn_name; + dp_module_init_fn fn; + errno_t ret; + + fn_name = talloc_asprintf(module, DP_MODULE_INIT_FN, module->name); + if (fn_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + return ENOMEM; + } + + fn = (dp_module_init_fn)dlsym(module->libhandle, fn_name); + if (fn != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Executing module [%s] constructor.\n", + module->name); + + ret = fn(module, be_ctx, provider, module->name, &module->module_data); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Module [%s] constructor failed " + "[%d]: %s\n", module->name, ret, sss_strerror(ret)); + goto done; + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, "No constructor found for module [%s].\n", + module->name); + module->module_data = NULL; + ret = EOK; + goto done; + } + + ret = EOK; + +done: + talloc_free(fn_name); + return ret; +} + +static errno_t dp_module_find(struct dp_module **modules, + const char *name, + struct dp_module **_module, + unsigned int *_slot) +{ + unsigned int slot; + + for (slot = 0; modules[slot] != NULL; slot++) { + if (strcmp(modules[slot]->name, name) == 0) { + *_module = modules[slot]; + *_slot = slot; + + return EOK; + } + } + + if (slot == DP_MAX_MODULES) { + /* This should not happen. */ + DEBUG(SSSDBG_CRIT_FAILURE, "All module slots are taken.\n"); + + return ERR_INTERNAL; + } + + *_module = NULL; + *_slot = slot; + + return EOK; +} + +static struct dp_module *dp_module_create(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + const char *name) +{ + struct dp_module *module; + errno_t ret; + + module = talloc_zero(mem_ctx, struct dp_module); + if (module == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed\n"); + ret = ENOMEM; + goto done; + } + + module->name = talloc_strdup(module, name); + if (module->name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = dp_module_open_lib(module); + if (ret != EOK) { + goto done; + } + + ret = dp_module_run_constructor(module, be_ctx, provider); + if (ret != EOK) { + goto done; + } + + module->initialized = true; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(module); + return NULL; + } + + return module; +} + +struct dp_module *dp_load_module(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + struct dp_module **modules, + const char *name) +{ + struct dp_module *module; + unsigned int free_slot; + errno_t ret; + + ret = dp_module_find(modules, name, &module, &free_slot); + if (ret != EOK) { + return NULL; + } + + if (module != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Module [%s] is already loaded.\n", name); + return module; + } + + DEBUG(SSSDBG_TRACE_FUNC, "About to load module [%s].\n", name); + + module = dp_module_create(mem_ctx, be_ctx, provider, name); + if (module == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create DP module.\n"); + return NULL; + } + + modules[free_slot] = module; + + return module; +} + +errno_t dp_init_modules(TALLOC_CTX *mem_ctx, struct dp_module ***_modules) +{ + struct dp_module **modules; + + modules = talloc_zero_array(mem_ctx, struct dp_module *, + DP_MAX_MODULES + 1); + if (modules == NULL) { + return ENOMEM; + } + + *_modules = modules; + + return EOK; +} diff --git a/src/providers/data_provider/dp_private.h b/src/providers/data_provider/dp_private.h new file mode 100644 index 000000000..ece60e7a4 --- /dev/null +++ b/src/providers/data_provider/dp_private.h @@ -0,0 +1,247 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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/>. +*/ + +#ifndef _DP_PRIVATE_H_ +#define _DP_PRIVATE_H_ + +#include <tevent.h> +#include <dhash.h> +#include "sbus/sssd_dbus.h" +#include "providers/data_provider/dp.h" +#include "util/util.h" + +#define DP_REQ_DEBUG(level, name, fmt, ...) \ + DEBUG(level, "DP Request [%s]: " fmt "\n", (name ?: "Unknown"), ##__VA_ARGS__) + +enum dp_clients { + DPC_NSS, + DPC_PAM, + DPC_IFP, + DPC_PAC, + DPC_SUDO, + DPC_HOST, + DPC_AUTOFS, + + DP_CLIENT_SENTINEL +}; + +struct dp_req; +struct dp_client; + +struct dp_module { + bool initialized; + const char *name; + void *module_data; + void *libhandle; +}; + +struct dp_target { + const char *name; + const char *module_name; + bool explicitly_configured; + + bool initialized; + enum dp_targets target; + struct dp_module *module; + struct dp_method *methods; +}; + +struct dp_method { + dp_req_send_fn send_fn; + dp_req_recv_fn recv_fn; + void *method_data; + const char *method_dtype; + const char *request_dtype; + const char *output_dtype; + uint32_t output_size; +}; + +struct data_provider { + uid_t uid; + gid_t gid; + struct be_ctx *be_ctx; + struct tevent_context *ev; + struct sbus_connection *srv_conn; + struct dp_client *clients[DP_CLIENT_SENTINEL]; + bool terminating; + + struct { + /* Numeric identificator that will be assigned to next request. */ + uint32_t index; + + /* List of all ongoing requests. */ + uint32_t num_active; + struct dp_req *active; + + /* Table containing list of sbus_requests where DP sends reply when + * a request is finished. Value of this table is pair + * <tevent_req, list of sbus_request> + */ + hash_table_t *reply_table; + } requests; + + struct dp_module **modules; + struct dp_target **targets; +}; + +errno_t dp_find_method(struct data_provider *provider, + enum dp_targets target, + enum dp_methods method, + struct dp_method **_execute); + +struct dp_module *dp_load_module(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + struct dp_module **modules, + const char *name); + +errno_t dp_init_modules(TALLOC_CTX *mem_ctx, struct dp_module ***_modules); + +const char *dp_target_to_string(enum dp_targets target); + +bool dp_target_initialized(struct dp_target **targets, enum dp_targets type); + +errno_t dp_init_targets(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + struct dp_module **modules); + +/* Reply callbacks. */ + +typedef void (*dp_req_post_fn)(const char *req_name, + struct data_provider *provider, + void *post_data, + void *reply_data); + +typedef void (*dp_req_reply_fn)(const char *req_name, + struct sbus_request *sbus_req, + void *data); + +void dp_req_reply_default(const char *req_name, + struct sbus_request *sbus_req, + void *data); + +/* Data provider request table. */ + +struct dp_sbus_req_item; + +struct dp_table_value { + hash_table_t *table; + const char *key; + + struct tevent_req *req; + struct dp_sbus_req_item *list; +}; + +struct dp_sbus_req_item { + struct dp_table_value *parent; + struct sbus_request *sbus_req; + + struct dp_sbus_req_item *prev; + struct dp_sbus_req_item *next; +}; + +char *dp_req_table_key(TALLOC_CTX *mem_ctx, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + const char *custom_part); + +errno_t dp_req_table_init(TALLOC_CTX *mem_ctx, hash_table_t **_table); + +struct dp_table_value *dp_req_table_lookup(hash_table_t *table, + const char *key); + +errno_t dp_req_table_add(hash_table_t *table, + const char *key, + struct tevent_req *req, + struct sbus_request *sbus_req); + +void dp_req_table_del(hash_table_t *table, + const char *key); + +void dp_req_table_del_and_free(hash_table_t *table, + const char *key); + +bool dp_req_table_has_key(hash_table_t *table, + const char *key); + +/* Data provider request. */ + +void dp_terminate_active_requests(struct data_provider *provider); + +void dp_req_reply_error(struct sbus_request *sbus_req, + const char *req_name, + errno_t ret); + +void _dp_req_with_reply(struct dp_client *dp_cli, + const char *domain, + const char *request_name, + const char *custom_key, + struct sbus_request *sbus_req, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + dp_req_post_fn postprocess_fn, + void *postprocess_data, + dp_req_reply_fn reply_fn, + const char *output_dtype); + +/** + * If @domain is NULL, be_ctx->domain is used. + * If req_key is NULL, address of sbus_req is used. + * + * If @pp_fn (post process function) is set it is call on a successful + * DP request before reply is sent. + */ +#define dp_req_with_reply_pp(dp_cli, domain, req_name, req_key, sbus_req, \ + target, method, dp_flags, req_data, pp_fn, \ + pp_data, pp_dtype, reply_fn, output_dtype) \ + do { \ + /* Check postprocess function parameter types. */ \ + void (*__pp_fn)(const char *, struct data_provider *, \ + pp_dtype *, output_dtype *) = (pp_fn); \ + pp_dtype *__pp_data = (pp_data); \ + \ + /* Check reply function parameter types. */ \ + void (*__reply_fn)(const char *, struct sbus_request *, \ + output_dtype *) = (reply_fn); \ + \ + _dp_req_with_reply(dp_cli, domain, req_name, req_key, sbus_req, \ + target, method, dp_flags, req_data, \ + (dp_req_post_fn)__pp_fn, __pp_data, \ + (dp_req_reply_fn)__reply_fn, #output_dtype); \ + } while(0) + +#define dp_req_with_reply(dp_cli, domain, req_name, req_key, sbus_req, target,\ + method, dp_flags, req_data, reply_fn, \ + output_dtype) \ + dp_req_with_reply_pp(dp_cli, domain, req_name, req_key, sbus_req, target, \ + method, dp_flags, req_data, NULL, NULL, void, \ + reply_fn, output_dtype) + +/* Client shared functions. */ + +errno_t dp_client_init(struct sbus_connection *conn, void *data); +struct data_provider *dp_client_provider(struct dp_client *dp_cli); +struct sbus_connection *dp_client_conn(struct dp_client *dp_cli); + +#endif /* _DP_PRIVATE_H_ */ diff --git a/src/providers/data_provider/dp_request.c b/src/providers/data_provider/dp_request.c new file mode 100644 index 000000000..6c0a0b72d --- /dev/null +++ b/src/providers/data_provider/dp_request.c @@ -0,0 +1,460 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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 <talloc.h> +#include <tevent.h> +#include <dbus/dbus.h> + +#include "sbus/sssd_dbus_errors.h" +#include "providers/data_provider/dp_private.h" +#include "providers/backend.h" +#include "util/dlinklist.h" +#include "util/util.h" + +struct dp_req { + struct data_provider *provider; + struct dp_client *client; + uint32_t dp_flags; + + struct sss_domain_info *domain; + + enum dp_targets target; + enum dp_methods method; + struct dp_method *execute; + const char *name; + uint32_t num; + + struct tevent_req *req; + struct tevent_req *handler_req; + void *request_data; + + /* Active request list. */ + struct dp_req *prev; + struct dp_req *next; +}; + +static bool check_data_type(const char *expected, + const char *description, + void *ptr) +{ + void *tmp; + + /* If ptr is NULL we still return true since it is valid case. */ + tmp = talloc_check_name(ptr, expected); + if (tmp != NULL || ptr == NULL) { + return true; + } + + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid %s data type provided. Expected [%s], " + "got [%s].\n", description, expected, talloc_get_name(ptr)); + + return false; +} + +static bool check_method_data(struct dp_method *method, + void *request_data) +{ + if (!check_data_type(method->method_dtype, "method", method->method_data)) { + return false; + } + + if (!check_data_type(method->request_dtype, "request", request_data)) { + return false; + } + + return true; +} + +static int dp_req_destructor(struct dp_req *dp_req) +{ + DLIST_REMOVE(dp_req->provider->requests.active, dp_req); + + if (dp_req->provider->requests.num_active == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: there are no active requests!\n"); + return 0; + } + + dp_req->provider->requests.num_active--; + + DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, dp_req->name, "Request removed."); + + DEBUG(SSSDBG_TRACE_FUNC, "Number of active DP request: %u\n", + dp_req->provider->requests.num_active); + + return 0; +} + +static errno_t dp_attach_req(struct dp_req *dp_req, + struct data_provider *provider, + const char *name, + uint32_t dp_flags) +{ + /* If we run out of numbers we simply overflow. */ + dp_req->num = provider->requests.index++; + dp_req->name = talloc_asprintf(dp_req, "%s #%u", name, dp_req->num); + if (dp_req->name == NULL) { + return ENOMEM; + } + + /* Attach this request to active request list. */ + DLIST_ADD(provider->requests.active, dp_req); + provider->requests.num_active++; + + talloc_set_destructor(dp_req, dp_req_destructor); + + DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, dp_req->name, + "New request. Flags [%#.4x].", dp_flags); + + DEBUG(SSSDBG_TRACE_FUNC, "Number of active DP request: %u\n", + provider->requests.num_active); + + return EOK; +} + +static errno_t +dp_req_new(TALLOC_CTX *mem_ctx, + struct data_provider *provider, + struct dp_client *dp_cli, + const char *domainname, + const char *name, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + struct tevent_req *req, + struct dp_req **_dp_req) +{ + struct dp_req *dp_req; + struct be_ctx *be_ctx; + errno_t ret; + + /* We set output even for error to simplify code flow in the caller. */ + *_dp_req = NULL; + + dp_req = talloc_zero(mem_ctx, struct dp_req); + if (dp_req == NULL) { + return ENOMEM; + } + + dp_req->provider = provider; + dp_req->client = dp_cli; + dp_req->dp_flags = dp_flags; + dp_req->target = target; + dp_req->method = method; + dp_req->request_data = request_data; + dp_req->req = req; + + ret = dp_attach_req(dp_req, provider, name, dp_flags); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create DP request " + "[%s] [%d]: %s\n", name, ret, sss_strerror(ret)); + talloc_free(dp_req); + return ret; + } + + /* Now the request is created. We will return it even in case of error + * so we can get better debug messages. */ + + talloc_steal(dp_req, dp_req->request_data); + *_dp_req = dp_req; + + be_ctx = provider->be_ctx; + dp_req->domain = be_ctx->domain; + if (domainname != NULL) { + dp_req->domain = find_domain_by_name(be_ctx->domain, domainname, true); + if (dp_req->domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown domain: %s\n", domainname); + return ERR_DOMAIN_NOT_FOUND; + } + } + + ret = dp_find_method(provider, target, method, &dp_req->execute); + + return ret; +} + +static errno_t +file_dp_request(TALLOC_CTX *mem_ctx, + struct data_provider *provider, + struct dp_client *dp_cli, + const char *domainname, + const char *name, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + struct tevent_req *req, + struct dp_req **_dp_req) +{ + struct dp_req_params *dp_params; + dp_req_send_fn send_fn; + struct dp_req *dp_req; + struct be_ctx *be_ctx; + errno_t ret; + + be_ctx = provider->be_ctx; + + ret = dp_req_new(mem_ctx, provider, dp_cli, domainname, name, target, + method, dp_flags, request_data, req, &dp_req); + if (ret != EOK) { + *_dp_req = dp_req; + goto done; + } + + /* DP request is already created. We will always return it to get nice + * debug messages. */ + *_dp_req = dp_req; + + /* Check that provided data are of correct type. */ + + if (!check_method_data(dp_req->execute, dp_req->request_data)) { + ret = ERR_INVALID_DATA_TYPE; + goto done; + } + + /* Process data provider flags */ + + if (dp_flags & DP_FAST_REPLY && be_is_offline(be_ctx)) { + ret = ERR_OFFLINE; + goto done; + } + + /* File request */ + + dp_params = talloc_zero(dp_req, struct dp_req_params); + if (dp_params == NULL) { + ret = ENOMEM; + goto done; + } + + dp_params->ev = provider->ev; + dp_params->be_ctx = be_ctx; + dp_params->domain = dp_req->domain; + dp_params->target = dp_req->target; + dp_params->method = dp_req->method; + + send_fn = dp_req->execute->send_fn; + dp_req->handler_req = send_fn(dp_req, dp_req->execute->method_data, + dp_req->request_data, dp_params); + if (dp_req->handler_req == NULL) { + ret = ENOMEM; + goto done; + } + + *_dp_req = dp_req; + + ret = EOK; + +done: + return ret; +} + +struct dp_req_state { + struct dp_req *dp_req; + dp_req_recv_fn recv_fn; + void *output_data; +}; + +static void dp_req_done(struct tevent_req *subreq); + +struct tevent_req *dp_req_send(TALLOC_CTX *mem_ctx, + struct data_provider *provider, + struct dp_client *dp_cli, + const char *domain, + const char *name, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + const char **_request_name) +{ + struct dp_req_state *state; + const char *request_name; + struct tevent_req *req; + struct dp_req *dp_req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct dp_req_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + ret = file_dp_request(state, provider, dp_cli, domain, name, target, + method, dp_flags, request_data, req, &dp_req); + + if (dp_req == NULL) { + /* An error ocurred before request could be created. */ + if (_request_name != NULL) { + *_request_name = "Request Not Yet Created"; + } + + goto immediately; + } + + state->dp_req = dp_req; + if (_request_name != NULL) { + request_name = talloc_strdup(mem_ctx, dp_req->name); + if (request_name == NULL) { + *_request_name = "Request Not Yet Created"; + ret = ENOMEM; + goto immediately; + } + *_request_name = request_name; + } + + if (ret != EOK) { + goto immediately; + } + + state->recv_fn = dp_req->execute->recv_fn; + state->output_data = talloc_zero_size(state, dp_req->execute->output_size); + if (state->output_data == NULL) { + ret = ENOMEM; + goto immediately; + } + + talloc_set_name_const(state->output_data, dp_req->execute->output_dtype); + + tevent_req_set_callback(dp_req->handler_req, dp_req_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, provider->ev); + + return req; +} + +static void dp_req_done(struct tevent_req *subreq) +{ + struct dp_req_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_req_state); + + ret = state->recv_fn(state->output_data, subreq, state->output_data); + + /* subreq is the same as dp_req->handler_req */ + talloc_zfree(subreq); + state->dp_req->handler_req = NULL; + + DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->dp_req->name, + "Request handler finished [%d]: %s", ret, sss_strerror(ret)); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t _dp_req_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char *output_dtype, + void **_output_data) +{ + struct dp_req_state *state; + + state = tevent_req_data(req, struct dp_req_state); + + if (state->dp_req != NULL) { + DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->dp_req->name, + "Receiving request data."); + } else { + /* dp_req may be NULL in case we error when filing request */ + DEBUG(SSSDBG_TRACE_FUNC, + "Receiving data of prematurely interrupted request!\n"); + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (!check_data_type(output_dtype, "output", state->output_data)) { + return ERR_INVALID_DATA_TYPE; + } + + *_output_data = talloc_steal(mem_ctx, state->output_data); + + return EOK; +} + +static void dp_terminate_request(struct dp_req *dp_req) +{ + if (dp_req->handler_req == NULL) { + /* This may occur when the handler already finished but the caller + * of dp request did not yet recieved data/free dp_req. We just + * return here. */ + return; + } + + /* We will end the handler request and mark dp request as terminated. */ + + DP_REQ_DEBUG(SSSDBG_TRACE_ALL, dp_req->name, "Terminating."); + + talloc_zfree(dp_req->handler_req); + tevent_req_error(dp_req->req, ERR_TERMINATED); +} + +static void dp_terminate_request_list(struct data_provider *provider, + const char *domain) +{ + struct dp_req *next; + struct dp_req *cur; + + if (provider == NULL || provider->requests.active == NULL) { + return; + } + + for (cur = provider->requests.active; cur != NULL; cur = next) { + next = cur->next; + if (domain == NULL || strcmp(cur->domain->name, domain) == 0) { + dp_terminate_request(cur); + } + } +} + +void dp_terminate_active_requests(struct data_provider *provider) +{ + DEBUG(SSSDBG_TRACE_FUNC, "Terminating active data provider requests\n"); + + dp_terminate_request_list(provider, NULL); +} + +void dp_terminate_domain_requests(struct data_provider *provider, + const char *domain) +{ + DEBUG(SSSDBG_TRACE_FUNC, "Terminating active data provider requests " + "for domain [%s]\n", domain); + + if (domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: domain is NULL!\n"); + return; + } + + dp_terminate_request_list(provider, domain); +} diff --git a/src/providers/data_provider/dp_request.h b/src/providers/data_provider/dp_request.h new file mode 100644 index 000000000..361ab2536 --- /dev/null +++ b/src/providers/data_provider/dp_request.h @@ -0,0 +1,77 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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/>. +*/ + +#ifndef _DP_REQUEST_H_ +#define _DP_REQUEST_H_ + +#include <talloc.h> + +#include "providers/data_provider/dp.h" + +struct data_provider; +struct dp_client; +enum dp_targets; +enum dp_methods; + +struct tevent_req *dp_req_send(TALLOC_CTX *mem_ctx, + struct data_provider *provider, + struct dp_client *dp_cli, + const char *domain, + const char *name, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + const char **_request_name); + +errno_t _dp_req_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char *data_type, + void **_data); + +/** + * Returns value of output data. + * + * @example + * struct dp_reply_std reply; + * ret = dp_req_recv(mem_ctx, req, struct dp_reply_std, &reply); + */ +#define dp_req_recv(mem_ctx, req, data_type, _data) \ +({ \ + data_type *__value = NULL; \ + errno_t __ret; \ + __ret = _dp_req_recv(mem_ctx, req, #data_type, (void**)&__value); \ + if (__ret == EOK) { \ + *(_data) = *__value; \ + } \ + __ret; \ +}) + +/** + * Returns pointer to output data type. + * + * @example + * struct dp_reply_std *reply; + * ret = dp_req_recv_ptr(mem_ctx, req, struct dp_reply_std, &reply); + */ +#define dp_req_recv_ptr(mem_ctx, req, data_type, _data) \ + _dp_req_recv(mem_ctx, req, #data_type, (void**)_data) + +#endif /* _DP_REQUEST_H_ */ diff --git a/src/providers/data_provider/dp_request_reply.c b/src/providers/data_provider/dp_request_reply.c new file mode 100644 index 000000000..27d9654ba --- /dev/null +++ b/src/providers/data_provider/dp_request_reply.c @@ -0,0 +1,372 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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 <talloc.h> +#include <tevent.h> +#include <dbus/dbus.h> + +#include "sbus/sssd_dbus_errors.h" +#include "providers/data_provider/dp_private.h" +#include "providers/backend.h" +#include "util/dlinklist.h" +#include "util/sss_utf8.h" +#include "util/util.h" + +void dp_req_reply_default(const char *req_name, + struct sbus_request *sbus_req, + void *data) +{ + DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, req_name, "Replying with empty message"); + + sbus_request_return_and_finish(sbus_req, DBUS_TYPE_INVALID); +} + +static DBusError *dp_req_reply_gen_error(TALLOC_CTX *mem_ctx, + const char *req_name, + errno_t ret) +{ + DBusError *error; + + switch (ret) { + case EOK: + DP_REQ_DEBUG(SSSDBG_CRIT_FAILURE, req_name, + "Bug: Success case must be handled by custom handler."); + error = sbus_error_new(mem_ctx, SBUS_ERROR_INTERNAL, + "Operation succeeded but result was not handled"); + break; + case ERR_OFFLINE: + DP_REQ_DEBUG(SSSDBG_MINOR_FAILURE, req_name, + "Finished. Backend is currently offline."); + + error = sbus_error_new(mem_ctx, SBUS_ERROR_DP_OFFLINE, + "Backend is currently offline"); + break; + case ERR_MISSING_DP_TARGET: + DP_REQ_DEBUG(SSSDBG_MINOR_FAILURE, req_name, + "Finished. Target is not supported " + "with this configuration."); + + error = sbus_error_new(mem_ctx, SBUS_ERROR_DP_NOTSUP, + "Target is not supported."); + break; + default: + DP_REQ_DEBUG(SSSDBG_CRIT_FAILURE, req_name, + "Finished. Error [%d]: %s", ret, sss_strerror(ret)); + + error = sbus_error_new(mem_ctx, SBUS_ERROR_DP_FATAL, + "An error occurred [%d]: %s", ret, sss_strerror(ret)); + break; + } + + return error; +} + +void dp_req_reply_error(struct sbus_request *sbus_req, + const char *req_name, + errno_t ret) +{ + DBusError *error; + + error = dp_req_reply_gen_error(sbus_req, req_name, ret); + if (error == NULL) { + DP_REQ_DEBUG(SSSDBG_CRIT_FAILURE, req_name, + "Out of memory, killing request..."); + talloc_free(sbus_req); + return; + } + + sbus_request_fail_and_finish(sbus_req, error); +} + +static void dp_req_reply_list_error(struct dp_sbus_req_item *list, + const char *req_name, + errno_t ret) +{ + struct dp_sbus_req_item *next_item; + struct dp_sbus_req_item *item; + DBusError *error; + + error = dp_req_reply_gen_error(NULL, req_name, ret); + if (error == NULL) { + DP_REQ_DEBUG(SSSDBG_CRIT_FAILURE, req_name, + "Out of memory, killing request..."); + + for (item = list; item != NULL; item = next_item) { + next_item = item->next; + talloc_free(item->sbus_req); + } + + return; + } + + for (item = list; item != NULL; item = next_item) { + next_item = item->next; + sbus_request_fail_and_finish(item->sbus_req, error); + } + + talloc_free(error); + return; +} + +static void dp_req_reply_list_success(struct dp_sbus_req_item *list, + dp_req_reply_fn reply_fn, + const char *request_name, + void *output_data) +{ + struct dp_sbus_req_item *next_item; + struct dp_sbus_req_item *item; + + DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, request_name, "Finished. Success."); + + for (item = list; item != NULL; item = next_item) { + next_item = item->next; + reply_fn(request_name, item->sbus_req, output_data); + } +} + +struct dp_req_with_reply_state { + struct data_provider *provider; + + void *postprocess_data; + dp_req_post_fn postprocess_fn; + + const char *output_dtype; + dp_req_reply_fn reply_fn; + const char *key; + const char *name; +}; + +static errno_t dp_req_with_reply_step(struct data_provider *provider, + struct dp_client *dp_cli, + const char *domain, + const char *request_name, + const char *custom_key, + struct sbus_request *sbus_req, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + dp_req_post_fn postprocess_fn, + void *postprocess_data, + dp_req_reply_fn reply_fn, + const char *output_dtype); + +static void dp_req_with_reply_done(struct tevent_req *req); + +void _dp_req_with_reply(struct dp_client *dp_cli, + const char *domain, + const char *request_name, + const char *custom_key, + struct sbus_request *sbus_req, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + dp_req_post_fn postprocess_fn, + void *postprocess_data, + dp_req_reply_fn reply_fn, + const char *output_dtype) +{ + TALLOC_CTX *tmp_ctx; + struct data_provider *provider; + const char *key; + bool has_key; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + provider = dp_client_provider(dp_cli); + + if (custom_key == NULL) { + /* It may not be always possible or desirable to have a meaningful key + * to chain sbus request. In such cases, we generate a unique key from + * sbus_req address that allows us to use the same code but the + * chaining is logically disabled. */ + custom_key = talloc_asprintf(tmp_ctx, "%p", sbus_req); + if (custom_key == NULL) { + ret = ENOMEM; + goto done; + } + } + + key = dp_req_table_key(tmp_ctx, target, method, dp_flags, custom_key); + if (key == NULL) { + ret = ENOMEM; + goto done; + } + + has_key = dp_req_table_has_key(provider->requests.reply_table, key); + if (has_key) { + ret = dp_req_table_add(provider->requests.reply_table, + key, NULL, sbus_req); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to attach sbus request to " + "existing data provider request [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Attaching to DP request: %s\n", key); + + ret = EOK; + goto done; + } + + ret = dp_req_with_reply_step(provider, dp_cli, domain, request_name, key, + sbus_req, target, method, dp_flags, + request_data, postprocess_fn, postprocess_data, + reply_fn, output_dtype); + +done: + if (ret == ENOMEM) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to allocate memory for " + "new DP request, killing D-Bus request...\n"); + talloc_zfree(sbus_req); + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to initialize " + "DP request [%d: %s], killing D-Bus request...\n", + ret, sss_strerror(ret)); + talloc_zfree(sbus_req); + } + + talloc_free(tmp_ctx); +} + +static errno_t dp_req_with_reply_step(struct data_provider *provider, + struct dp_client *dp_cli, + const char *domain, + const char *request_name, + const char *custom_key, + struct sbus_request *sbus_req, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + dp_req_post_fn postprocess_fn, + void *postprocess_data, + dp_req_reply_fn reply_fn, + const char *output_dtype) +{ + TALLOC_CTX *tmp_ctx; + struct dp_req_with_reply_state *state; + struct tevent_req *req; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + state = talloc_zero(tmp_ctx, struct dp_req_with_reply_state); + if (state == NULL) { + ret = ENOMEM; + goto done; + } + + state->provider = provider; + state->reply_fn = reply_fn; + state->key = talloc_strdup(state, custom_key); + if (state->key == NULL) { + ret = ENOMEM; + goto done; + } + + if (postprocess_fn != NULL) { + state->postprocess_data = postprocess_data; + state->postprocess_fn = postprocess_fn; + } + + state->output_dtype = talloc_strdup(state, output_dtype); + if (state->output_dtype == NULL) { + ret = ENOMEM; + goto done; + } + + req = dp_req_send(tmp_ctx, provider, dp_cli, domain, request_name, target, + method, dp_flags, request_data, &state->name); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + ret = dp_req_table_add(provider->requests.reply_table, + custom_key, req, sbus_req); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add request to table " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, dp_req_with_reply_done, state); + + talloc_steal(provider, req); + talloc_steal(req, state); + talloc_steal(state, state->name); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static void dp_req_with_reply_done(struct tevent_req *req) +{ + struct dp_req_with_reply_state *state; + struct dp_table_value *value; + void *output_data; + errno_t ret; + + state = tevent_req_callback_data(req, struct dp_req_with_reply_state); + + value = dp_req_table_lookup(state->provider->requests.reply_table, + state->key); + if (value == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup table!\n"); + return; + } + + ret = _dp_req_recv(state, req, state->output_dtype, &output_data); + if (ret != EOK) { + dp_req_reply_list_error(value->list, state->name, ret); + goto done; + } + + /* Run postprocess function if any. */ + if (state->postprocess_fn != NULL) { + state->postprocess_fn(state->name, + state->provider, + state->postprocess_data, + output_data); + } + + /* Reply with data. */ + dp_req_reply_list_success(value->list, state->reply_fn, + state->name, output_data); + +done: + /* Freeing value will remove it from the table as well. */ + talloc_free(value); + talloc_free(req); +} diff --git a/src/providers/data_provider/dp_request_table.c b/src/providers/data_provider/dp_request_table.c new file mode 100644 index 000000000..970679a91 --- /dev/null +++ b/src/providers/data_provider/dp_request_table.c @@ -0,0 +1,265 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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 <talloc.h> +#include <tevent.h> +#include <dhash.h> + +#include "sbus/sssd_dbus.h" +#include "providers/data_provider/dp_private.h" +#include "providers/backend.h" +#include "util/dlinklist.h" +#include "util/sss_utf8.h" +#include "util/util.h" + +static int +dp_sbus_req_item_destructor(struct dp_sbus_req_item *item) +{ + DLIST_REMOVE(item->parent->list, item); + + return 0; +} + +static int +dp_table_value_destructor(struct dp_table_value *value) +{ + struct dp_sbus_req_item *next_item; + struct dp_sbus_req_item *item; + + DEBUG(SSSDBG_TRACE_FUNC, "Removing [%s] from reply table\n", value->key); + + dp_req_table_del(value->table, value->key); + + for (item = value->list; item != NULL; item = next_item) { + next_item = item->next; + talloc_free(item); + } + + return 0; +} + +static struct dp_sbus_req_item * +dp_sbus_req_item_new(struct dp_table_value *value, + struct sbus_request *sbus_req) +{ + struct dp_sbus_req_item *item; + + /* Attach to sbus_request so we ensure that this sbus_req is removed + * from the list when it is unexpectedly freed, for example when + * client connection is dropped. */ + item = talloc_zero(sbus_req, struct dp_sbus_req_item); + if (item == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed\n"); + return NULL; + } + + item->parent = value; + item->sbus_req = sbus_req; + + talloc_set_destructor(item, dp_sbus_req_item_destructor); + + return item; +} + +char *dp_req_table_key(TALLOC_CTX *mem_ctx, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + const char *custom_part) +{ + const char *str = custom_part == NULL ? "(null)" : custom_part; + + return talloc_asprintf(mem_ctx, "%u:%u:%#.4x:%s", + target, method, dp_flags, str); +} + +errno_t dp_req_table_init(TALLOC_CTX *mem_ctx, hash_table_t **_table) +{ + return sss_hash_create(mem_ctx, 100, _table); +} + +struct dp_table_value *dp_req_table_lookup(hash_table_t *table, + const char *key) +{ + hash_key_t hkey; + hash_value_t hvalue; + int hret; + + hkey.type = HASH_KEY_STRING; + hkey.str = discard_const_p(char, key); + + hret = hash_lookup(table, &hkey, &hvalue); + if (hret == HASH_ERROR_KEY_NOT_FOUND) { + return NULL; + } else if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to search hash table [%d]\n", hret); + return NULL; + } + + return hvalue.ptr; +} + +static errno_t dp_req_table_new_item(hash_table_t *table, + const char *key, + struct tevent_req *req, + struct sbus_request *sbus_req) +{ + hash_key_t hkey; + hash_value_t hvalue; + struct dp_table_value *table_value; + errno_t ret; + int hret; + + /* Attach it to request. */ + table_value = talloc_zero(req, struct dp_table_value); + if (table_value == NULL) { + return ENOMEM; + } + + table_value->table = table; + table_value->key = talloc_strdup(table_value, key); + if (table_value->key == NULL) { + ret = ENOMEM; + goto done; + } + + table_value->req = req; + table_value->list = dp_sbus_req_item_new(table_value, sbus_req); + if (table_value->list == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_set_destructor(table_value, dp_table_value_destructor); + + hkey.type = HASH_KEY_STRING; + hkey.str = discard_const_p(char, key); + + hvalue.type = HASH_VALUE_PTR; + hvalue.ptr = table_value; + + hret = hash_enter(table, &hkey, &hvalue); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to enter value into hash table " + "[%d]\n", hret); + ret = EIO; + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(table_value); + } + + return ret; +} + +static errno_t dp_req_table_mod_item(hash_table_t *table, + struct dp_table_value *table_value, + struct sbus_request *sbus_req) +{ + struct dp_sbus_req_item *item; + + item = dp_sbus_req_item_new(table_value, sbus_req); + if (item == NULL) { + return ENOMEM; + } + + DLIST_ADD(table_value->list, item); + + return EOK; +} + +errno_t dp_req_table_add(hash_table_t *table, + const char *key, + struct tevent_req *req, + struct sbus_request *sbus_req) +{ + struct dp_table_value *table_value; + + if (sbus_req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "SBUS request cannot be NULL\n"); + return EINVAL; + } + + table_value = dp_req_table_lookup(table, key); + if (table_value == NULL) { + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Tevent request cannot be NULL\n"); + return EINVAL; + } + + return dp_req_table_new_item(table, key, req, sbus_req); + } + + return dp_req_table_mod_item(table, table_value, sbus_req); +} + +void dp_req_table_del(hash_table_t *table, + const char *key) +{ + hash_key_t hkey; + int hret; + + if (table == NULL || key == NULL) { + return; + } + + hkey.type = HASH_KEY_STRING; + hkey.str = discard_const_p(char, key); + + hret = hash_delete(table, &hkey); + if (hret != HASH_SUCCESS && hret != HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to remove key from table [%d]\n", + hret); + } + + return; +} + +void dp_req_table_del_and_free(hash_table_t *table, + const char *key) +{ + struct dp_table_value *value; + + value = dp_req_table_lookup(table, key); + if (value == NULL) { + /* We're done here. */ + return; + } + + dp_req_table_del(table, key); + talloc_free(value); + + return; +} + +bool dp_req_table_has_key(hash_table_t *table, + const char *key) +{ + hash_key_t hkey; + + hkey.type = HASH_KEY_STRING; + hkey.str = discard_const_p(char, key); + + return hash_has_key(table, &hkey); +} diff --git a/src/providers/data_provider/dp_responder_iface.h b/src/providers/data_provider/dp_responder_iface.h new file mode 100644 index 000000000..7fe6e0d4e --- /dev/null +++ b/src/providers/data_provider/dp_responder_iface.h @@ -0,0 +1,29 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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/>. +*/ + +#ifndef DP_RESPONDER_IFACE_H_ +#define DP_RESPONDER_IFACE_H_ + +#include "providers/data_provider/dp_iface_generated.h" +#include "providers/data_provider/dp_flags.h" + +#define DP_PATH "/org/freedesktop/sssd/dataprovider" + +#endif /* DP_RESPONDER_IFACE_H_ */ diff --git a/src/providers/dp_sbus.c b/src/providers/data_provider/dp_sbus.c index 623d7dd9c..623d7dd9c 100644 --- a/src/providers/dp_sbus.c +++ b/src/providers/data_provider/dp_sbus.c diff --git a/src/providers/data_provider/dp_targets.c b/src/providers/data_provider/dp_targets.c new file mode 100644 index 000000000..e19cf93a3 --- /dev/null +++ b/src/providers/data_provider/dp_targets.c @@ -0,0 +1,534 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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 <talloc.h> +#include <dlfcn.h> + +#include "config.h" +#include "providers/data_provider/dp.h" +#include "providers/data_provider/dp_private.h" +#include "providers/data_provider/dp_builtin.h" +#include "providers/backend.h" +#include "util/util.h" + +#define DP_TARGET_INIT_FN "sssm_%s_%s_init" + +#define DP_PROVIDER_OPT "%s_provider" +#define DP_ACCESS_PERMIT "permit" +#define DP_ACCESS_DENY "deny" +#define DP_NO_PROVIDER "none" + +bool _dp_target_enabled(struct data_provider *provider, + const char *module_name, + ...) +{ + struct dp_target *target; + enum dp_targets type; + va_list ap; + bool bret; + + if (provider == NULL || provider->targets == NULL) { + return false; + } + + bret = false; + va_start(ap, module_name); + while ((type = va_arg(ap, enum dp_targets)) != DP_TARGET_SENTINEL) { + target = provider->targets[type]; + if (target == NULL || target->module_name == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Uninitialized target %s\n", + dp_target_to_string(type)); + continue; + } + + if (module_name == NULL) { + bret = true; + goto done; + } + + if (strcmp(target->module_name, module_name) == 0) { + bret = true; + goto done; + } + } + +done: + va_end(ap); + return bret; +} + +struct dp_module *dp_target_module(struct data_provider *provider, + enum dp_targets target) +{ + if (provider == NULL || provider->targets == NULL) { + return NULL; + } + + if (target >= DP_TARGET_SENTINEL || provider->targets[target] == NULL) { + return NULL; + } + + return provider->targets[target]->module; +} + +const char *dp_target_to_string(enum dp_targets target) +{ + switch (target) { + case DPT_ID: + return "id"; + case DPT_AUTH: + return "auth"; + case DPT_ACCESS: + return "access"; + case DPT_CHPASS: + return "chpass"; + case DPT_SUDO: + return "sudo"; + case DPT_AUTOFS: + return "autofs"; + case DPT_SELINUX: + return "selinux"; + case DPT_HOSTID: + return "hostid"; + case DPT_SUBDOMAINS: + return "subdomains"; + case DP_TARGET_SENTINEL: + return NULL; + } + + return NULL; +} + +bool dp_target_initialized(struct dp_target **targets, enum dp_targets type) +{ + if (targets == NULL || targets[type] == NULL) { + return false; + } + + return targets[type]->initialized; +} + +static bool dp_target_sudo_enabled(struct be_ctx *be_ctx) +{ + TALLOC_CTX *tmp_ctx; + char **services; + char *module; + bool responder_enabled; + bool enable; + errno_t ret; + int i; + + /* Do not disable it in case of error. */ + enable = true; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return enable; + } + + ret = confdb_get_string_as_list(be_ctx->cdb, tmp_ctx, + CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_ACTIVE_SERVICES, &services); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to read from confdb [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + responder_enabled = false; + for (i = 0; services[i] != NULL; i++) { + if (strcmp(services[i], "sudo") == 0) { + responder_enabled = true; + break; + } + } + + ret = confdb_get_string(be_ctx->cdb, tmp_ctx, be_ctx->conf_path, + CONFDB_DOMAIN_SUDO_PROVIDER, NULL, &module); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to read from confdb [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (!responder_enabled) { + if (module == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "SUDO is not listed in services, " + "disabling SUDO module.\n"); + enable = false; + goto done; + } else if (strcmp(module, DP_NO_PROVIDER) != 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "SUDO provider is set, but it is not " + "listed in active services. SUDO support will not work!\n"); + enable = true; + goto done; + } + } + + enable = true; + +done: + talloc_free(tmp_ctx); + return enable; +} + +static const char *dp_target_module_name(struct dp_target **targets, + enum dp_targets type) +{ + if (targets[type] == NULL) { + return NULL; + } + + return targets[type]->module_name; +} + +static const char *dp_target_default_module(struct dp_target **targets, + enum dp_targets target) +{ + switch (target) { + case DPT_ID: + return NULL; + case DPT_ACCESS: + return "permit"; + case DPT_CHPASS: + return dp_target_module_name(targets, DPT_AUTH); + case DP_TARGET_SENTINEL: + return NULL; + default: + return dp_target_module_name(targets, DPT_ID); + } +} + +static errno_t dp_target_run_constructor(struct dp_target *target, + struct be_ctx *be_ctx) +{ + char *fn_name = NULL; + dp_target_init_fn fn; + char *error; + errno_t ret; + + fn_name = talloc_asprintf(target, DP_TARGET_INIT_FN, + target->module->name, target->name); + if (fn_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + return ENOMEM; + } + + dlerror(); /* clear any error */ + fn = (dp_target_init_fn)dlsym(target->module->libhandle, fn_name); + if (fn != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "Executing target [%s] constructor\n", + target->name); + + ret = fn(target, be_ctx, target->module->module_data, target->methods); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Target [%s] constructor failed " + "[%d]: %s\n", target->name, ret, sss_strerror(ret)); + goto done; + } + } else { + error = dlerror(); + if (error == NULL || !target->explicitly_configured) { + /* Not found. */ + ret = ELIBBAD; + goto done; + } else { + /* Error. */ + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to load target [%s] " + "constructor: %s\n", target->name, error); + ret = ELIBBAD; + goto done; + } + } + + target->initialized = true; + ret = EOK; + +done: + talloc_free(fn_name); + return ret; +} + +static errno_t dp_target_special(struct be_ctx *be_ctx, + struct dp_target *target, + const char *module_name) +{ + if (strcasecmp(module_name, DP_NO_PROVIDER) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Target [%s] is explicitly disabled.\n", + target->name); + target->initialized = false; + target->module = NULL; + return EOK; + } + + if (target->target == DPT_ACCESS) { + if (strcmp(module_name, DP_ACCESS_PERMIT) == 0) { + dp_set_method(target->methods, DPM_ACCESS_HANDLER, + dp_access_permit_handler_send, dp_access_permit_handler_recv, NULL, + void, struct pam_data, struct pam_data *); + target->module = NULL; + target->initialized = true; + return EOK; + } + + if (strcmp(module_name, DP_ACCESS_DENY) == 0) { + dp_set_method(target->methods, DPM_ACCESS_HANDLER, + dp_access_deny_handler_send, dp_access_deny_handler_recv, NULL, + void, struct pam_data, struct pam_data *); + target->module = NULL; + target->initialized = true; + return EOK; + } + } + + if (target->target == DPT_SUDO) { + if (dp_target_sudo_enabled(be_ctx)) { + return EAGAIN; + } else { + target->module = NULL; + target->initialized = false; + return EOK; + } + } + + return EAGAIN; +} + +static errno_t dp_target_init(struct be_ctx *be_ctx, + struct data_provider *provider, + struct dp_module **modules, + struct dp_target *target) +{ + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Initializing target [%s] with module [%s]\n", + target->name, target->module_name); + + /* We have already name, module name and target set. We just load + * the module and initialize it. */ + + target->methods = talloc_zero_array(target, struct dp_method, + DP_METHOD_SENTINEL + 1); + if (target->methods == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + ret = ENOMEM; + goto done; + } + + /* Handle special cases that do not require opening a module. */ + ret = dp_target_special(be_ctx, target, target->module_name); + if (ret == EOK || ret != EAGAIN) { + goto done; + } + + /* Load module first. Memory context is modules, not target here. */ + target->module = dp_load_module(modules, be_ctx, provider, modules, + target->module_name); + if (target->module == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to load module %s\n", + target->module_name); + ret = ELIBBAD; + goto done; + } + + /* Run constructor. */ + ret = dp_target_run_constructor(target, be_ctx); + if (!target->explicitly_configured && (ret == ELIBBAD || ret == ENOTSUP)) { + /* Target not found but it wasn't explicitly + * configured so we shall just continue. */ + DEBUG(SSSDBG_CRIT_FAILURE, "Target [%s] is not supported by " + "module [%s].\n", target->name, target->module_name); + ret = EOK; + goto done; + } else if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(target->methods); + } + + return ret; +} + +static char *dp_get_module_name(TALLOC_CTX *mem_ctx, + struct confdb_ctx *confdb_ctx, + const char *conf_path, + struct dp_target **targets, + enum dp_targets type, + bool *_is_default) +{ + const char *name; + const char *default_module; + char *module; + char *option; + errno_t ret; + + name = dp_target_to_string(type); + if (name == NULL) { + return NULL; + } + + option = talloc_asprintf(mem_ctx, DP_PROVIDER_OPT, name); + if (option == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + return NULL; + } + + ret = confdb_get_string(confdb_ctx, mem_ctx, conf_path, + option, NULL, &module); + talloc_free(option); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to read provider value " + "[%d]: %s\n", ret, sss_strerror(ret)); + return NULL; + } + + if (module != NULL) { + *_is_default = false; + return module; + } + + *_is_default = true; + default_module = dp_target_default_module(targets, type); + + return talloc_strdup(mem_ctx, default_module); +} + +static errno_t dp_load_configuration(struct confdb_ctx *cdb, + const char *conf_path, + struct dp_target **targets) +{ + enum dp_targets type; + const char *name; + bool is_default; + char *module; + errno_t ret; + + for (type = 0; type < DP_TARGET_SENTINEL; type++) { + name = dp_target_to_string(type); + if (name == NULL) { + ret = ERR_INTERNAL; + goto done; + } + + module = dp_get_module_name(NULL, cdb, conf_path, targets, + type, &is_default); + if (module == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No provider is specified for" + " [%s]\n", name); + continue; + } else { + DEBUG(SSSDBG_CONF_SETTINGS, "Using [%s] provider for [%s]\n", + module, name); + } + + targets[type]->explicitly_configured = is_default == false; + targets[type]->name = name; + targets[type]->target = type; + targets[type]->module_name = talloc_steal(targets[type], module); + } + + ret = EOK; + +done: + return ret; +} + +static errno_t dp_load_targets(struct be_ctx *be_ctx, + struct data_provider *provider, + struct dp_target **targets, + struct dp_module **modules) +{ + enum dp_targets type; + errno_t ret; + + /* We load the configuration first and store module name to each target. + * This way we ensure that we have this information available during + * module initialization. */ + + ret = dp_load_configuration(be_ctx->cdb, be_ctx->conf_path, targets); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to load DP configuration " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + for (type = 0; type < DP_TARGET_SENTINEL; type++) { + ret = dp_target_init(be_ctx, provider, modules, targets[type]); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to load target [%s] " + "[%d]: %s.\n", targets[type]->name, ret, sss_strerror(ret)); + ret = ERR_INTERNAL; + goto done; + } + } + + ret = EOK; + +done: + return ret; +} + +errno_t dp_init_targets(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + struct dp_module **modules) +{ + struct dp_target **targets; + enum dp_targets type; + errno_t ret; + + /* Even though we know the exact number of targets we will allocate + * them all dynamically so we can have correct talloc hierarchy where + * all private data are attached to the target they belong to. */ + + targets = talloc_zero_array(mem_ctx, struct dp_target *, + DP_TARGET_SENTINEL + 1); + if (targets == NULL) { + ret = ENOMEM; + goto done; + } + + for (type = 0; type != DP_TARGET_SENTINEL; type++) { + targets[type] = talloc_zero(targets, struct dp_target); + if (targets[type] == NULL) { + ret = ENOMEM; + goto done; + } + } + + /* We want this to be already available. */ + provider->targets = targets; + + ret = dp_load_targets(be_ctx, provider, targets, modules); + +done: + if (ret != EOK) { + provider->targets = NULL; + talloc_free(targets); + } + + return ret; +} |