diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
commit | 74aa0bc6779af38018a03fd2cf4419fe85917904 (patch) | |
tree | 9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/providers/proxy | |
parent | Initial commit. (diff) | |
download | sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.tar.xz sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.zip |
Adding upstream version 2.9.4.upstream/2.9.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/providers/proxy')
-rw-r--r-- | src/providers/proxy/proxy.h | 187 | ||||
-rw-r--r-- | src/providers/proxy/proxy_auth.c | 877 | ||||
-rw-r--r-- | src/providers/proxy/proxy_certmap.c | 191 | ||||
-rw-r--r-- | src/providers/proxy/proxy_child.c | 606 | ||||
-rw-r--r-- | src/providers/proxy/proxy_client.c | 134 | ||||
-rw-r--r-- | src/providers/proxy/proxy_hosts.c | 768 | ||||
-rw-r--r-- | src/providers/proxy/proxy_id.c | 1962 | ||||
-rw-r--r-- | src/providers/proxy/proxy_init.c | 519 | ||||
-rw-r--r-- | src/providers/proxy/proxy_ipnetworks.c | 628 | ||||
-rw-r--r-- | src/providers/proxy/proxy_netgroup.c | 206 | ||||
-rw-r--r-- | src/providers/proxy/proxy_services.c | 372 |
11 files changed, 6450 insertions, 0 deletions
diff --git a/src/providers/proxy/proxy.h b/src/providers/proxy/proxy.h new file mode 100644 index 0000000..6246eba --- /dev/null +++ b/src/providers/proxy/proxy.h @@ -0,0 +1,187 @@ +/* + SSSD + + Proxy provider, private header file + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2010 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 __PROXY_H__ +#define __PROXY_H__ + +#include <errno.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <security/pam_appl.h> +#include <security/pam_modules.h> + +#include "util/util.h" +#include "util/nss_dl_load.h" +#include "providers/backend.h" +#include "db/sysdb.h" +#include <dhash.h> +#include "sss_iface/sss_iface_async.h" + +#define PROXY_CHILD_PATH "/org/freedesktop/sssd/proxychild" + +struct authtok_conv { + struct sss_auth_token *authtok; + struct sss_auth_token *newauthtok; + + bool sent_old; +}; + +struct proxy_id_ctx { + struct be_ctx *be; + bool fast_alias; + struct sss_nss_ops ops; + struct sss_certmap_ctx *sss_certmap_ctx; +}; + +struct proxy_auth_ctx { + struct be_ctx *be; + char *pam_target; + + uint32_t max_children; + uint32_t running; + uint32_t next_id; + hash_table_t *request_table; + int timeout_ms; +}; + +struct proxy_resolver_ctx { + struct sss_nss_ops ops; +}; + +struct proxy_module_ctx { + struct proxy_id_ctx *id_ctx; + struct proxy_auth_ctx *auth_ctx; + struct proxy_resolver_ctx *resolver_ctx; +}; + +struct proxy_child_ctx { + struct proxy_auth_ctx *auth_ctx; + struct be_req *be_req; + struct pam_data *pd; + + uint32_t id; + pid_t pid; + bool running; + + struct sbus_connection *conn; + struct tevent_timer *timer; + + struct tevent_req *init_req; +}; + +struct pc_init_ctx { + char *command; + pid_t pid; + struct tevent_timer *timeout; + struct tevent_signal *sige; + struct proxy_child_ctx *child_ctx; + struct sbus_connection *conn; +}; + +#define PROXY_CHILD_PIPE "private/proxy_child" +#define DEFAULT_BUFSIZE 4096 +#define MAX_BUF_SIZE 1024*1024 /* max 1MiB */ + +/* From proxy_id.c */ +struct tevent_req * +proxy_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params); + +errno_t proxy_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +/* From proxy_auth.c */ +struct tevent_req * +proxy_pam_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_auth_ctx *proxy_auth_ctx, + struct pam_data *pd, + struct dp_req_params *params); + +errno_t +proxy_pam_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data); + +/* From proxy_netgroup.c */ +errno_t get_netgroup(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *name); + +errno_t get_serv_byname(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *name, + const char *protocol); + +errno_t +get_serv_byport(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *be_filter, + const char *protocol); + +errno_t enum_services(struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom); + +/* From proxy_hosts.c */ +struct tevent_req * +proxy_hosts_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_resolver_ctx *proxy_resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params); + +errno_t +proxy_hosts_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +/* From proxy_ipnetworks.c */ +struct tevent_req * +proxy_nets_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_resolver_ctx *proxy_resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params); + +errno_t +proxy_nets_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data); + +errno_t +proxy_client_init(struct sbus_connection *conn, + struct proxy_auth_ctx *auth_ctx); + +errno_t proxy_init_certmap(TALLOC_CTX *mem_ctx, struct proxy_id_ctx *id_ctx); + + +errno_t proxy_map_cert_to_user(struct proxy_id_ctx *id_ctx, + struct dp_id_data *data); + +int get_pw_name(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *i_name); +#endif /* __PROXY_H__ */ diff --git a/src/providers/proxy/proxy_auth.c b/src/providers/proxy/proxy_auth.c new file mode 100644 index 0000000..7f6f3f2 --- /dev/null +++ b/src/providers/proxy/proxy_auth.c @@ -0,0 +1,877 @@ +/* + SSSD + + proxy_auth.c + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2010 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 <signal.h> + +#include "providers/proxy/proxy.h" +#include "sss_iface/sss_iface_async.h" +#include "util/sss_chain_id.h" + +struct pc_init_ctx; + +static int proxy_child_destructor(TALLOC_CTX *ctx) +{ + struct proxy_child_ctx *child_ctx = + talloc_get_type(ctx, struct proxy_child_ctx); + hash_key_t key; + int hret; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Removing proxy child id [%d]\n", child_ctx->id); + key.type = HASH_KEY_ULONG; + key.ul = child_ctx->id; + hret = hash_delete(child_ctx->auth_ctx->request_table, &key); + if (!(hret == HASH_SUCCESS || + hret == HASH_ERROR_KEY_NOT_FOUND)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Hash error [%d][%s]\n", hret, hash_error_string(hret)); + /* Nothing we can do about this, so just continue */ + } + return 0; +} + +static struct tevent_req *proxy_child_init_send(TALLOC_CTX *mem_ctx, + struct proxy_child_ctx *child_ctx, + struct proxy_auth_ctx *auth_ctx); +static void proxy_child_init_done(struct tevent_req *subreq); +static struct tevent_req *proxy_child_send(TALLOC_CTX *mem_ctx, + struct proxy_auth_ctx *auth_ctx, + struct pam_data *pd) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct proxy_child_ctx *state; + int hret; + hash_key_t key; + hash_value_t value; + uint32_t first; + + req = tevent_req_create(mem_ctx, &state, struct proxy_child_ctx); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->auth_ctx = auth_ctx; + state->pd = pd; + + /* Find an available key */ + key.type = HASH_KEY_ULONG; + key.ul = auth_ctx->next_id; + + first = auth_ctx->next_id; + while (auth_ctx->next_id == 0 || + hash_has_key(auth_ctx->request_table, &key)) { + /* Handle overflow, zero is a reserved value + * Also handle the unlikely case where the next ID + * is still awaiting being run + */ + auth_ctx->next_id++; + key.ul = auth_ctx->next_id; + + if (auth_ctx->next_id == first) { + /* We've looped through all possible integers! */ + DEBUG(SSSDBG_FATAL_FAILURE, "Serious error: queue is too long!\n"); + talloc_zfree(req); + return NULL; + } + } + + state->id = auth_ctx->next_id; + auth_ctx->next_id++; + + value.type = HASH_VALUE_PTR; + value.ptr = req; + DEBUG(SSSDBG_TRACE_INTERNAL, "Queueing request [%lu]\n", key.ul); + hret = hash_enter(auth_ctx->request_table, + &key, &value); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not add request to the queue\n"); + talloc_zfree(req); + return NULL; + } + + talloc_set_destructor((TALLOC_CTX *) state, + proxy_child_destructor); + + if (auth_ctx->running < auth_ctx->max_children) { + /* There's an available slot; start a child + * to handle the request + */ + + auth_ctx->running++; + subreq = proxy_child_init_send(auth_ctx, state, auth_ctx); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not fork child process\n"); + auth_ctx->running--; + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, proxy_child_init_done, req); + + state->running = true; + } + else { + /* If there was no available slot, it will be queued + * until a slot is available + */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "All available child slots are full, queuing request\n"); + } + return req; +} + +static int pc_init_destructor (TALLOC_CTX *ctx) +{ + struct pc_init_ctx *init_ctx = + talloc_get_type(ctx, struct pc_init_ctx); + + /* If the init request has died, forcibly kill the child */ + kill(init_ctx->pid, SIGKILL); + return 0; +} + +static void pc_init_sig_handler(struct tevent_context *ev, + struct tevent_signal *sige, int signum, + int count, void *__siginfo, void *pvt); +static void pc_init_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr); +static struct tevent_req *proxy_child_init_send(TALLOC_CTX *mem_ctx, + struct proxy_child_ctx *child_ctx, + struct proxy_auth_ctx *auth_ctx) +{ + struct tevent_req *req; + struct pc_init_ctx *state; + char **proxy_child_args; + struct timeval tv; + errno_t ret; + pid_t pid; + + req = tevent_req_create(mem_ctx, &state, struct pc_init_ctx); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not create tevent_req\n"); + return NULL; + } + + state->child_ctx = child_ctx; + + state->command = talloc_asprintf(req, + "%s/proxy_child -d %#.4x --debug-timestamps=%d " + "--debug-microseconds=%d --logger=%s --domain %s --id %d " + "--chain-id=%lu", + SSSD_LIBEXEC_PATH, debug_level, debug_timestamps, + debug_microseconds, sss_logger_str[sss_logger], + auth_ctx->be->domain->name, + child_ctx->id, sss_chain_id_get()); + if (state->command == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + return NULL; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Starting proxy child with args [%s]\n", state->command); + + pid = fork(); + if (pid < 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "fork failed [%d][%s].\n", ret, strerror(ret)); + talloc_zfree(req); + return NULL; + } + + if (pid == 0) { /* child */ + proxy_child_args = parse_args(state->command); + execvp(proxy_child_args[0], proxy_child_args); + + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not start proxy child [%s]: [%d][%s].\n", + state->command, ret, strerror(ret)); + + _exit(1); + } + + else { /* parent */ + state->pid = pid; + /* Make sure to kill the child process if we abort */ + talloc_set_destructor((TALLOC_CTX *)state, pc_init_destructor); + + state->sige = tevent_add_signal(auth_ctx->be->ev, req, + SIGCHLD, 0, + pc_init_sig_handler, req); + if (state->sige == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_signal failed.\n"); + talloc_zfree(req); + return NULL; + } + + /* Save the init request to the child context. + * This is technically a layering violation, + * but it's the only sane way to be able to + * identify which client is which when it + * connects to the backend in + * client_registration() + */ + child_ctx->init_req = req; + + /* Wait six seconds for the child to connect + * This is because the connection handler will add + * its own five-second timeout, and we don't want to + * be faster here. + */ + tv = tevent_timeval_current_ofs(6, 0); + state->timeout = tevent_add_timer(auth_ctx->be->ev, req, + tv, pc_init_timeout, req); + + /* processing will continue once the connection is received + * in proxy_client_init() + */ + return req; + } +} + +static void pc_init_sig_handler(struct tevent_context *ev, + struct tevent_signal *sige, int signum, + int count, void *__siginfo, void *pvt) +{ + int ret; + int child_status; + struct tevent_req *req; + struct pc_init_ctx *init_ctx; + + if (count <= 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "SIGCHLD handler called with invalid child count\n"); + return; + } + + req = talloc_get_type(pvt, struct tevent_req); + init_ctx = tevent_req_data(req, struct pc_init_ctx); + + DEBUG(SSSDBG_TRACE_LIBS, "Waiting for child [%d].\n", init_ctx->pid); + + errno = 0; + ret = waitpid(init_ctx->pid, &child_status, WNOHANG); + + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "waitpid failed [%d][%s].\n", ret, strerror(ret)); + } else if (ret == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "waitpid did not find a child with changed status.\n"); + } else { + if (WIFEXITED(child_status)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "child [%d] exited with status [%d].\n", ret, + WEXITSTATUS(child_status)); + tevent_req_error(req, EIO); + } else if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "child [%d] was terminate by signal [%d].\n", ret, + WTERMSIG(child_status)); + tevent_req_error(req, EIO); + } else { + if (WIFSTOPPED(child_status)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "child [%d] was stopped by signal [%d].\n", ret, + WSTOPSIG(child_status)); + } + if (WIFCONTINUED(child_status) == true) { + DEBUG(SSSDBG_CRIT_FAILURE, + "child [%d] was resumed by delivery of SIGCONT.\n", + ret); + } + DEBUG(SSSDBG_CRIT_FAILURE, + "Child is still running, no new child is started.\n"); + return; + } + } +} + +static void pc_init_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct tevent_req *req; + + DEBUG(SSSDBG_OP_FAILURE, "Client timed out before Identification!\n"); + req = talloc_get_type(ptr, struct tevent_req); + tevent_req_error(req, ETIMEDOUT); +} + +static errno_t proxy_child_init_recv(struct tevent_req *req, + pid_t *pid, + struct sbus_connection **conn) +{ + struct pc_init_ctx *state; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + state = tevent_req_data(req, struct pc_init_ctx); + + /* Unset the destructor since we initialized successfully. + * We don't want to kill the child now that it's properly + * set up. + */ + talloc_set_destructor((TALLOC_CTX *)state, NULL); + + *pid = state->pid; + *conn = state->conn; + + return EOK; +} + +struct proxy_child_sig_ctx { + struct proxy_auth_ctx *auth_ctx; + pid_t pid; + struct tevent_req *req; +}; +static void proxy_child_sig_handler(struct tevent_context *ev, + struct tevent_signal *sige, int signum, + int count, void *__siginfo, void *pvt); +static struct tevent_req * +proxy_pam_conv_send(TALLOC_CTX *mem_ctx, struct proxy_auth_ctx *auth_ctx, + struct sbus_connection *conn, + struct proxy_child_sig_ctx *sig_ctx, struct pam_data *pd, + pid_t pid, uint32_t id); +static void proxy_child_init_conv_done(struct tevent_req *subreq); +static void proxy_child_init_done(struct tevent_req *subreq) { + int ret; + struct tevent_signal *sige; + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct proxy_child_ctx *child_ctx = + tevent_req_data(req, struct proxy_child_ctx); + struct proxy_child_sig_ctx *sig_ctx; + + ret = proxy_child_init_recv(subreq, &child_ctx->pid, &child_ctx->conn); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Proxy child init failed [%d]\n", ret); + tevent_req_error(req, ret); + return; + } + + sig_ctx = talloc_zero(child_ctx->auth_ctx, struct proxy_child_sig_ctx); + if (sig_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + + /* An initialized child is available, awaiting the PAM command */ + subreq = proxy_pam_conv_send(req, child_ctx->auth_ctx, + child_ctx->conn, sig_ctx, child_ctx->pd, + child_ctx->pid, child_ctx->id); + if (!subreq) { + talloc_free(sig_ctx); + DEBUG(SSSDBG_CRIT_FAILURE,"Could not start PAM conversation\n"); + tevent_req_error(req, EIO); + return; + } + tevent_req_set_callback(subreq, proxy_child_init_conv_done, req); + + /* Add a signal handler for the child under the auth_ctx, + * that way if the child exits after completion of the + * request, it will still be handled. + */ + sig_ctx->auth_ctx = child_ctx->auth_ctx; + sig_ctx->pid = child_ctx->pid; + + sige = tevent_add_signal(child_ctx->auth_ctx->be->ev, + child_ctx->auth_ctx, + SIGCHLD, 0, + proxy_child_sig_handler, + sig_ctx); + if (sige == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_signal failed.\n"); + tevent_req_error(req, ENOMEM); + return; + } + + /* Steal the signal context onto the signal event + * so that when the signal is freed, the context + * will go with it. + */ + talloc_steal(sige, sig_ctx); +} + +static void remove_sige(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt); +static void run_proxy_child_queue(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt); +static void proxy_child_sig_handler(struct tevent_context *ev, + struct tevent_signal *sige, int signum, + int count, void *__siginfo, void *pvt) +{ + int ret; + int child_status; + struct proxy_child_sig_ctx *sig_ctx; + struct tevent_immediate *imm; + struct tevent_immediate *imm2; + + if (count <= 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "SIGCHLD handler called with invalid child count\n"); + return; + } + + sig_ctx = talloc_get_type(pvt, struct proxy_child_sig_ctx); + DEBUG(SSSDBG_TRACE_LIBS, "Waiting for child [%d].\n", sig_ctx->pid); + + errno = 0; + ret = waitpid(sig_ctx->pid, &child_status, WNOHANG); + + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "waitpid failed [%d][%s].\n", ret, strerror(ret)); + } else if (ret == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "waitpid did not find a child with changed status.\n"); + } else { + if (WIFEXITED(child_status)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "child [%d] exited with status [%d].\n", ret, + WEXITSTATUS(child_status)); + } else if (WIFSIGNALED(child_status) == true) { + DEBUG(SSSDBG_CONF_SETTINGS, + "child [%d] was terminated by signal [%d].\n", ret, + WTERMSIG(child_status)); + } else { + if (WIFSTOPPED(child_status)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "child [%d] was stopped by signal [%d].\n", ret, + WSTOPSIG(child_status)); + } + if (WIFCONTINUED(child_status) == true) { + DEBUG(SSSDBG_CRIT_FAILURE, + "child [%d] was resumed by delivery of SIGCONT.\n", + ret); + } + DEBUG(SSSDBG_CRIT_FAILURE, + "Child is still running, no new child is started.\n"); + return; + } + + /* Free request if it is still running */ + if (sig_ctx->req != NULL) { + tevent_req_error(sig_ctx->req, ERR_PROXY_CHILD_SIGNAL); + } + + imm = tevent_create_immediate(ev); + if (imm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_create_immediate failed.\n"); + return; + } + + tevent_schedule_immediate(imm, ev, run_proxy_child_queue, + sig_ctx->auth_ctx); + + /* schedule another immediate timer to delete the sigchld handler */ + imm2 = tevent_create_immediate(ev); + if (imm2 == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_create_immediate failed.\n"); + return; + } + + tevent_schedule_immediate(imm2, ev, remove_sige, sige); + } + + return; +} + +static void remove_sige(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + talloc_free(pvt); +} + +struct proxy_conv_ctx { + struct proxy_auth_ctx *auth_ctx; + struct sbus_connection *conn; + struct proxy_child_sig_ctx *sig_ctx; + struct pam_data *pd; + pid_t pid; +}; + +static void proxy_pam_conv_done(struct tevent_req *subreq); + +static struct tevent_req * +proxy_pam_conv_send(TALLOC_CTX *mem_ctx, struct proxy_auth_ctx *auth_ctx, + struct sbus_connection *conn, + struct proxy_child_sig_ctx *sig_ctx, struct pam_data *pd, + pid_t pid, uint32_t id) +{ + struct proxy_conv_ctx *state; + struct tevent_req *req; + struct tevent_req *subreq; + char *sbus_cliname; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct proxy_conv_ctx); + if (req == NULL) { + return NULL; + } + + state->auth_ctx = auth_ctx; + state->conn = conn; + state->sig_ctx = sig_ctx; + state->pd = pd; + state->pid = pid; + + sbus_cliname = sss_iface_proxy_bus(state, id); + if (sbus_cliname == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Sending request with the following data:\n"); + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + + subreq = sbus_call_proxy_auth_PAM_send(state, state->conn, sbus_cliname, + SSS_BUS_PATH, pd); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + state->sig_ctx->req = subreq; + + tevent_req_set_callback(subreq, proxy_pam_conv_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_post(req, auth_ctx->be->ev); + tevent_req_error(req, ret); + } + + return req; +} + +static void proxy_pam_conv_done(struct tevent_req *subreq) +{ + struct pam_data *response; + struct response_data *resp; + struct proxy_conv_ctx *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct proxy_conv_ctx); + + state->sig_ctx->req = NULL; + + ret = sbus_call_proxy_auth_PAM_recv(state, subreq, &response); + talloc_zfree(subreq); + + /* Kill the child */ + kill(state->pid, SIGKILL); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get reply from child [%d]: %s\n", + ret, sss_strerror(ret)); + state->pd->pam_status = PAM_SYSTEM_ERR; + tevent_req_error(req, ret); + return; + } + + state->pd->pam_status = response->pam_status; + state->pd->account_locked = response->account_locked; + + for (resp = response->resp_list; resp != NULL; resp = resp->next) { + talloc_steal(state->pd, resp); + + if (resp->next == NULL) { + resp->next = state->pd->resp_list; + state->pd->resp_list = response->resp_list; + break; + } + } + + DEBUG(SSSDBG_CONF_SETTINGS, "received: [%d][%s]\n", + state->pd->pam_status, + state->pd->domain); + + tevent_req_done(req); +} + +static errno_t proxy_pam_conv_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static void proxy_child_init_conv_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + int ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = proxy_pam_conv_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Proxy PAM conversation failed [%d]\n", ret); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int proxy_child_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct pam_data **pd) +{ + struct proxy_child_ctx *ctx; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + ctx = tevent_req_data(req, struct proxy_child_ctx); + *pd = talloc_steal(mem_ctx, ctx->pd); + + return EOK; +} + +static void run_proxy_child_queue(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + struct proxy_auth_ctx *auth_ctx; + struct hash_iter_context_t *iter; + struct hash_entry_t *entry; + struct tevent_req *req = NULL; + struct tevent_req *subreq; + struct proxy_child_ctx *state = NULL; + + auth_ctx = talloc_get_type(pvt, struct proxy_auth_ctx); + + /* Launch next queued request */ + iter = new_hash_iter_context(auth_ctx->request_table); + if (iter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "new_hash_iter_context failed.\n"); + return; + } + + while ((entry = iter->next(iter)) != NULL) { + req = talloc_get_type(entry->value.ptr, struct tevent_req); + state = tevent_req_data(req, struct proxy_child_ctx); + if (!state->running) { + break; + } + } + free(iter); + + if (!entry) { + /* Nothing pending on the queue */ + return; + } + + if (auth_ctx->running < auth_ctx->max_children) { + /* There's an available slot; start a child + * to handle the request + */ + auth_ctx->running++; + subreq = proxy_child_init_send(auth_ctx, state, auth_ctx); + if (!subreq) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not fork child process\n"); + auth_ctx->running--; + talloc_zfree(req); + return; + } + tevent_req_set_callback(subreq, proxy_child_init_done, req); + + state->running = true; + } +} + +struct proxy_pam_handler_state { + struct pam_data *pd; + struct proxy_auth_ctx *auth_ctx; + struct be_ctx *be_ctx; +}; + +static void proxy_pam_handler_done(struct tevent_req *subreq); + +struct tevent_req * +proxy_pam_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_auth_ctx *proxy_auth_ctx, + struct pam_data *pd, + struct dp_req_params *params) +{ + struct proxy_pam_handler_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, struct proxy_pam_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->pd = pd; + state->auth_ctx = proxy_auth_ctx; + state->be_ctx = params->be_ctx; + + /* Tell frontend that we do not support Smartcard authentication */ + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN + || sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_KEYPAD) { + if (pd->cmd == SSS_PAM_PREAUTH) { + /* just return success and let the PAM responder figure out if + * local Smartcard authentication is available. */ + pd->pam_status = PAM_SUCCESS; + } else { + pd->pam_status = PAM_BAD_ITEM; + } + goto immediately; + } + + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + case SSS_PAM_CHAUTHTOK: + case SSS_PAM_CHAUTHTOK_PRELIM: + case SSS_PAM_ACCT_MGMT: + /* Queue the request and spawn a child if there is an available slot. */ + subreq = proxy_child_send(state, proxy_auth_ctx, state->pd); + if (subreq == NULL) { + pd->pam_status = PAM_SYSTEM_ERR; + goto immediately; + } + tevent_req_set_callback(subreq, proxy_pam_handler_done, req); + break; + case SSS_PAM_SETCRED: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + pd->pam_status = PAM_SUCCESS; + goto immediately; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported PAM task %d\n", pd->cmd); + pd->pam_status = PAM_MODULE_UNKNOWN; + goto immediately; + } + + return req; + +immediately: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +static void proxy_pam_handler_done(struct tevent_req *subreq) +{ + struct proxy_pam_handler_state *state; + struct tevent_immediate *imm; + struct tevent_req *req; + const char *password; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct proxy_pam_handler_state); + + ret = proxy_child_recv(subreq, state, &state->pd); + + /* During the proxy_child_send request SIGKILL will be sent to the child + * process unconditionally, so we can assume here that the child process + * is gone even if the request returns an error. */ + state->auth_ctx->running--; + + talloc_zfree(subreq); + if (ret != EOK) { + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + /* Start the next auth in the queue, if any */ + imm = tevent_create_immediate(state->be_ctx->ev); + if (imm == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_create_immediate failed.\n"); + /* We'll still finish the current request, but we're + * likely to have problems if there are queued events + * if we've gotten into this state. + * Hopefully this is impossible, since freeing req + * above should guarantee that we have enough memory + * to create this immediate event. + */ + } else { + tevent_schedule_immediate(imm, state->be_ctx->ev, + run_proxy_child_queue, + state->auth_ctx); + } + + /* Check if we need to save the cached credentials */ + if ((state->pd->cmd == SSS_PAM_AUTHENTICATE || state->pd->cmd == SSS_PAM_CHAUTHTOK) + && (state->pd->pam_status == PAM_SUCCESS) && state->be_ctx->domain->cache_credentials) { + + ret = sss_authtok_get_password(state->pd->authtok, &password, NULL); + if (ret) { + /* password caching failures are not fatal errors */ + DEBUG(SSSDBG_OP_FAILURE, "Failed to cache password\n"); + goto done; + } + + ret = sysdb_cache_password(state->be_ctx->domain, state->pd->user, password); + + /* password caching failures are not fatal errors */ + /* so we just log it any return */ + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to cache password (%d)[%s]!?\n", + ret, sss_strerror(ret)); + } + } + +done: + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); +} + +errno_t +proxy_pam_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct pam_data **_data) +{ + struct proxy_pam_handler_state *state = NULL; + + state = tevent_req_data(req, struct proxy_pam_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_data = talloc_steal(mem_ctx, state->pd); + + return EOK; +} diff --git a/src/providers/proxy/proxy_certmap.c b/src/providers/proxy/proxy_certmap.c new file mode 100644 index 0000000..55fe39f --- /dev/null +++ b/src/providers/proxy/proxy_certmap.c @@ -0,0 +1,191 @@ +/* + SSSD + + Map certificates to users from the proxy provider + + Copyright (C) 2023 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/proxy/proxy.h" +#include "util/util.h" +#include "util/cert.h" +#include "lib/certmap/sss_certmap.h" + +struct priv_sss_debug { + int level; +}; + +static void ext_debug(void *private, const char *file, long line, + const char *function, const char *format, ...) +{ + va_list ap; + struct priv_sss_debug *data = private; + int level = SSSDBG_OP_FAILURE; + + if (data != NULL) { + level = data->level; + } + + va_start(ap, format); + sss_vdebug_fn(file, line, function, level, APPEND_LINE_FEED, format, ap); + va_end(ap); +} + +errno_t proxy_init_certmap(TALLOC_CTX *mem_ctx, struct proxy_id_ctx *id_ctx) +{ + int ret; + bool hint; + struct certmap_info **certmap_list = NULL; + size_t c; + + ret = sysdb_get_certmap(mem_ctx, id_ctx->be->domain->sysdb, + &certmap_list, &hint); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_certmap failed.\n"); + goto done; + } + + if (certmap_list == NULL || *certmap_list == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No certmap data, nothing to do.\n"); + ret = EOK; + goto done; + } + + ret = sss_certmap_init(mem_ctx, ext_debug, NULL, &id_ctx->sss_certmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_init failed.\n"); + goto done; + } + + for (c = 0; certmap_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, "Trying to add rule [%s][%d][%s][%s].\n", + certmap_list[c]->name, + certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule); + + ret = sss_certmap_add_rule(id_ctx->sss_certmap_ctx, + certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule, + certmap_list[c]->domains); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_certmap_add_rule failed for rule [%s] " + "with error [%d][%s], skipping. " + "Please check for typos and if rule syntax is supported.\n", + certmap_list[c]->name, ret, sss_strerror(ret)); + continue; + } + } + + ret = EOK; + +done: + talloc_free(certmap_list); + + return ret; +} + +errno_t proxy_map_cert_to_user(struct proxy_id_ctx *id_ctx, + struct dp_id_data *data) +{ + errno_t ret; + char *filter; + char *user; + struct ldb_message *msg = NULL; + struct sysdb_attrs *attrs = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + ret = sss_cert_derb64_to_ldap_filter(tmp_ctx, data->filter_value, "", + id_ctx->sss_certmap_ctx, + id_ctx->be->domain, &filter); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_cert_derb64_to_ldap_filter failed.\n"); + goto done; + } + if (filter == NULL || filter[0] != '(' + || filter[strlen(filter) - 1] != ')') { + DEBUG(SSSDBG_OP_FAILURE, + "sss_cert_derb64_to_ldap_filter returned bad filter [%s].\n", + filter); + ret = EINVAL; + goto done; + } + + filter[strlen(filter) - 1] = '\0'; + user = sss_create_internal_fqname(tmp_ctx, &filter[1], + id_ctx->be->domain->name); + if (user == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Certificate mapped to user: [%s].\n", user); + + ret = sysdb_search_user_by_name(tmp_ctx, id_ctx->be->domain, user, NULL, &msg); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "Mapped user [%s] not found in cache.\n", user); + ret = get_pw_name(id_ctx, id_ctx->be->domain, user); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_pw_name() failed.\n"); + } + ret = sysdb_search_user_by_name(tmp_ctx, id_ctx->be->domain, user, NULL, &msg); + } + + if (ret == EOK) { + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_base64_blob(attrs, SYSDB_USER_MAPPED_CERT, + data->filter_value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_base64_blob failed.\n"); + goto done; + } + + ret = sysdb_set_entry_attr(id_ctx->be->domain->sysdb, msg->dn, attrs, + SYSDB_MOD_ADD); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_entry_attr failed.\n"); + goto done; + } + } else if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "Mapped user [%s] not found.\n", user); + goto done; + } else { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_name failed.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} diff --git a/src/providers/proxy/proxy_child.c b/src/providers/proxy/proxy_child.c new file mode 100644 index 0000000..dad65e7 --- /dev/null +++ b/src/providers/proxy/proxy_child.c @@ -0,0 +1,606 @@ +/* + SSSD + + Pam Proxy Child + + Authors: + + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2010 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 <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> +#include <sys/time.h> +#include <errno.h> +#include <dlfcn.h> +#include <popt.h> + +#include <security/pam_appl.h> +#include <security/pam_modules.h> + +#include "util/util.h" +#include "confdb/confdb.h" +#include "providers/proxy/proxy.h" +#include "sss_iface/sss_iface_async.h" +#include "util/sss_chain_id.h" + +#include "providers/backend.h" + +struct pc_ctx { + struct tevent_context *ev; + struct confdb_ctx *cdb; + struct sss_domain_info *domain; + const char *identity; + const char *conf_path; + struct sbus_connection *mon_conn; + struct sbus_connection *conn; + const char *pam_target; + uint32_t id; +}; + +static int proxy_internal_conv(int num_msg, const struct pam_message **msgm, + struct pam_response **response, + void *appdata_ptr) { + int i; + struct pam_response *reply; + struct authtok_conv *auth_data; + const char *password; + size_t pwlen; + errno_t ret; + + auth_data = talloc_get_type(appdata_ptr, struct authtok_conv); + + if (num_msg <= 0) return PAM_CONV_ERR; + + reply = (struct pam_response *) calloc(num_msg, + sizeof(struct pam_response)); + if (reply == NULL) return PAM_CONV_ERR; + + for (i=0; i < num_msg; i++) { + switch( msgm[i]->msg_style ) { + case PAM_PROMPT_ECHO_OFF: + DEBUG(SSSDBG_CONF_SETTINGS, + "Conversation message: [%s]\n", msgm[i]->msg); + reply[i].resp_retcode = 0; + + ret = sss_authtok_get_password(auth_data->authtok, + &password, &pwlen); + if (ret) goto failed; + reply[i].resp = calloc(pwlen + 1, sizeof(char)); + if (reply[i].resp == NULL) goto failed; + memcpy(reply[i].resp, password, pwlen + 1); + + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Conversation style %d not supported.\n", + msgm[i]->msg_style); + goto failed; + } + } + + *response = reply; + reply = NULL; + + return PAM_SUCCESS; + +failed: + free(reply); + return PAM_CONV_ERR; +} + +static int proxy_chauthtok_conv(int num_msg, const struct pam_message **msgm, + struct pam_response **response, + void *appdata_ptr) { + int i; + struct pam_response *reply; + struct authtok_conv *auth_data; + const char *password; + size_t pwlen; + errno_t ret; + + auth_data = talloc_get_type(appdata_ptr, struct authtok_conv); + + if (num_msg <= 0) return PAM_CONV_ERR; + + reply = (struct pam_response *) calloc(num_msg, + sizeof(struct pam_response)); + if (reply == NULL) return PAM_CONV_ERR; + + for (i=0; i < num_msg; i++) { + switch( msgm[i]->msg_style ) { + case PAM_PROMPT_ECHO_OFF: + DEBUG(SSSDBG_CONF_SETTINGS, + "Conversation message: [%s]\n", msgm[i]->msg); + + reply[i].resp_retcode = 0; + if (!auth_data->sent_old) { + /* The first prompt will be asking for the old authtok */ + ret = sss_authtok_get_password(auth_data->authtok, + &password, &pwlen); + if (ret) goto failed; + reply[i].resp = calloc(pwlen + 1, sizeof(char)); + if (reply[i].resp == NULL) goto failed; + memcpy(reply[i].resp, password, pwlen + 1); + auth_data->sent_old = true; + } + else { + /* Subsequent prompts are looking for the new authtok */ + ret = sss_authtok_get_password(auth_data->newauthtok, + &password, &pwlen); + if (ret) goto failed; + reply[i].resp = calloc(pwlen + 1, sizeof(char)); + if (reply[i].resp == NULL) goto failed; + memcpy(reply[i].resp, password, pwlen + 1); + auth_data->sent_old = true; + } + + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Conversation style %d not supported.\n", + msgm[i]->msg_style); + goto failed; + } + } + + *response = reply; + reply = NULL; + + return PAM_SUCCESS; + +failed: + free(reply); + return PAM_CONV_ERR; +} + +static errno_t call_pam_stack(const char *pam_target, struct pam_data *pd) +{ + int ret; + int pam_status; + pam_handle_t *pamh=NULL; + struct authtok_conv *auth_data; + struct pam_conv conv; + char *shortname; + + if (pd->cmd == SSS_PAM_CHAUTHTOK) { + conv.conv=proxy_chauthtok_conv; + } + else { + conv.conv=proxy_internal_conv; + } + auth_data = talloc_zero(pd, struct authtok_conv); + if (auth_data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + auth_data->authtok = sss_authtok_new(auth_data); + if (auth_data->authtok == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_authtok_new failed.\n"); + ret = ENOMEM; + goto fail; + } + auth_data->newauthtok = sss_authtok_new(auth_data); + if (auth_data->newauthtok == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_authtok_new failed.\n"); + ret = ENOMEM; + goto fail; + } + + conv.appdata_ptr=auth_data; + + ret = sss_parse_internal_fqname(auth_data, pd->user, &shortname, NULL); + if (ret != EOK) { + goto fail; + } + + ret = pam_start(pam_target, shortname, &conv, &pamh); + if (ret == PAM_SUCCESS) { + DEBUG(SSSDBG_TRACE_LIBS, + "Pam transaction started with service name [%s].\n", + pam_target); + ret = pam_set_item(pamh, PAM_TTY, pd->tty); + if (ret != PAM_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Setting PAM_TTY failed: %s.\n", + pam_strerror(pamh, ret)); + } + ret = pam_set_item(pamh, PAM_RUSER, pd->ruser); + if (ret != PAM_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Setting PAM_RUSER failed: %s.\n", + pam_strerror(pamh, ret)); + } + ret = pam_set_item(pamh, PAM_RHOST, pd->rhost); + if (ret != PAM_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Setting PAM_RHOST failed: %s.\n", + pam_strerror(pamh, ret)); + } + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + sss_authtok_copy(pd->authtok, auth_data->authtok); + pam_status = pam_authenticate(pamh, 0); + break; + case SSS_PAM_SETCRED: + pam_status=pam_setcred(pamh, 0); + break; + case SSS_PAM_ACCT_MGMT: + pam_status=pam_acct_mgmt(pamh, 0); + break; + case SSS_PAM_OPEN_SESSION: + pam_status=pam_open_session(pamh, 0); + break; + case SSS_PAM_CLOSE_SESSION: + pam_status=pam_close_session(pamh, 0); + break; + case SSS_PAM_CHAUTHTOK: + sss_authtok_copy(pd->authtok, auth_data->authtok); + if (pd->priv != 1) { + pam_status = pam_authenticate(pamh, 0); + auth_data->sent_old = false; + if (pam_status != PAM_SUCCESS) break; + } + sss_authtok_copy(pd->newauthtok, auth_data->newauthtok); + pam_status = pam_chauthtok(pamh, 0); + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + if (pd->priv != 1) { + sss_authtok_copy(pd->authtok, auth_data->authtok); + pam_status = pam_authenticate(pamh, 0); + } else { + pam_status = PAM_SUCCESS; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "unknown PAM call %d\n", pd->cmd); + pam_status=PAM_ABORT; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Pam result: [%d][%s]\n", pam_status, + pam_strerror(pamh, pam_status)); + + ret = pam_end(pamh, pam_status); + if (ret != PAM_SUCCESS) { + pamh=NULL; + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot terminate pam transaction.\n"); + } + + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to initialize pam transaction.\n"); + pam_status = PAM_SYSTEM_ERR; + } + + pd->pam_status = pam_status; + + return EOK; +fail: + talloc_free(auth_data); + return ret; +} + +static errno_t +pc_pam_handler(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct pc_ctx *pc_ctx, + struct pam_data *pd, + struct pam_data **_response) +{ + errno_t ret; + + pd->pam_status = PAM_SYSTEM_ERR; + pd->domain = talloc_strdup(pd, pc_ctx->domain->name); + if (pd->domain == NULL) { + exit(ENOMEM); + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Got request with the following data\n"); + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + + ret = call_pam_stack(pc_ctx->pam_target, pd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "call_pam_stack failed.\n"); + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Sending result [%d][%s]\n", + pd->pam_status, pd->domain); + + *_response = pd; + + /* We'll return the message and let the + * parent process kill us. + */ + return ret; +} + +static void proxy_cli_init_done(struct tevent_req *subreq); + +static errno_t +proxy_cli_init(struct pc_ctx *ctx) +{ + TALLOC_CTX *tmp_ctx; + struct tevent_req *subreq; + char *sbus_address; + char *sbus_busname; + char *sbus_cliname; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + SBUS_INTERFACE(iface, + sssd_ProxyChild_Auth, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_ProxyChild_Auth, PAM, pc_pam_handler, ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + struct sbus_path paths[] = { + {SSS_BUS_PATH, &iface}, + {NULL, NULL} + }; + + sbus_address = sss_iface_domain_address(tmp_ctx, ctx->domain); + if (sbus_address == NULL) { + ret = ENOMEM; + goto done; + } + + sbus_busname = sss_iface_domain_bus(tmp_ctx, ctx->domain); + if (sbus_busname == NULL) { + ret = ENOMEM; + goto done; + } + + sbus_cliname = sss_iface_proxy_bus(tmp_ctx, ctx->id); + if (sbus_cliname == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_iface_connect_address(ctx, ctx->ev, sbus_cliname, sbus_address, + NULL, &ctx->conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to connect to %s\n", sbus_address); + goto done; + } + + ret = sbus_connection_add_path_map(ctx->conn, paths); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to add paths [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Sending ID to Proxy Backend: (%"PRIu32")\n", + ctx->id); + + subreq = sbus_call_proxy_client_Register_send(ctx, ctx->conn, sbus_busname, + SSS_BUS_PATH, ctx->id); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, proxy_cli_init_done, NULL); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static void proxy_cli_init_done(struct tevent_req *subreq) +{ + errno_t ret; + + ret = sbus_call_proxy_client_Register_recv(subreq); + talloc_zfree(subreq); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to register with proxy provider " + "[%d]: %s\n", ret, sss_strerror(ret)); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Got id ack from proxy child\n"); +} + +int proxy_child_process_init(TALLOC_CTX *mem_ctx, const char *domain, + struct tevent_context *ev, struct confdb_ctx *cdb, + const char *pam_target, uint32_t id) +{ + struct pc_ctx *ctx; + int ret; + + ctx = talloc_zero(mem_ctx, struct pc_ctx); + if (!ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing pc_ctx\n"); + return ENOMEM; + } + ctx->ev = ev; + ctx->cdb = cdb; + ctx->pam_target = talloc_steal(ctx, pam_target); + ctx->id = id; + ctx->conf_path = talloc_asprintf(ctx, CONFDB_DOMAIN_PATH_TMPL, domain); + if (!ctx->conf_path) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!?\n"); + return ENOMEM; + } + + ret = confdb_get_domain(cdb, domain, &ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "fatal error retrieving domain configuration\n"); + return ret; + } + + ret = proxy_cli_init(ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up server bus\n"); + return ret; + } + + return EOK; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + char *domain = NULL; + char *srv_name = NULL; + char *conf_entry = NULL; + struct main_context *main_ctx; + int ret; + long id = 0; + long chain_id; + char *pam_target = NULL; + uid_t uid = 0; + gid_t gid = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + {"domain", 0, POPT_ARG_STRING, &domain, 0, + _("Domain of the information provider (mandatory)"), NULL }, + {"id", 0, POPT_ARG_LONG, &id, 0, + _("Child identifier (mandatory)"), NULL }, + {"chain-id", 0, POPT_ARG_LONG, &chain_id, 0, + _("Tevent chain ID used for logging purposes"), NULL }, + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + ret = chdir("/"); + if (ret != 0) { + fprintf(stderr, "\nFailed to chdir()\n\n"); + return 1; + } + + ret = clearenv(); + if (ret != 0) { + fprintf(stderr, "\nFailed to clear env.\n\n"); + return 1; + } + + umask(SSS_DFL_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + if (domain == NULL) { + fprintf(stderr, "\nMissing option, " + "--domain is a mandatory option.\n\n"); + poptPrintUsage(pc, stderr, 0); + return 1; + } + if (!is_valid_domain_name(domain)) { + fprintf(stderr, "\nInvalid --domain option.\n\n"); + return 1; + } + + if (id == 0) { + fprintf(stderr, "\nMissing option, " + "--id is a mandatory option.\n\n"); + poptPrintUsage(pc, stderr, 0); + return 1; + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = talloc_asprintf(NULL, "proxy_child_%s", domain); + if (!debug_log_file) return 2; + + sss_chain_id_set((uint64_t)chain_id); + + DEBUG_INIT(debug_level, opt_logger); + + srv_name = talloc_asprintf(NULL, "proxy_child[%s]", domain); + if (!srv_name) return 2; + + conf_entry = talloc_asprintf(NULL, CONFDB_DOMAIN_PATH_TMPL, domain); + if (!conf_entry) return 2; + + ret = server_setup(srv_name, false, 0, 0, 0, conf_entry, &main_ctx, true); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not set up mainloop [%d]\n", ret); + return 2; + } + + ret = confdb_get_string(main_ctx->confdb_ctx, main_ctx, conf_entry, + CONFDB_PROXY_PAM_TARGET, NULL, &pam_target); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) [%s]\n", + ret, strerror(ret)); + return 4; + } + if (pam_target == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing option proxy_pam_target.\n"); + return 4; + } + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, + "Could not set up to exit when parent process does\n"); + } + + ret = proxy_child_process_init(main_ctx, domain, main_ctx->event_ctx, + main_ctx->confdb_ctx, pam_target, + (uint32_t)id); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not initialize proxy child [%d].\n", ret); + return 3; + } + + DEBUG(SSSDBG_IMPORTANT_INFO, + "Proxy child for domain [%s] started!\n", domain); + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} diff --git a/src/providers/proxy/proxy_client.c b/src/providers/proxy/proxy_client.c new file mode 100644 index 0000000..0a4da5c --- /dev/null +++ b/src/providers/proxy/proxy_client.c @@ -0,0 +1,134 @@ +/* + SSSD + + proxy_init.c + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2010 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 "util/util.h" +#include "providers/proxy/proxy.h" +#include "sss_iface/sss_iface_async.h" + +struct proxy_client { + struct proxy_auth_ctx *proxy_auth_ctx; + struct sbus_connection *conn; + struct tevent_timer *timeout; +}; + +errno_t +proxy_client_register(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct proxy_auth_ctx *auth_ctx, + uint32_t cli_id) +{ + struct proxy_client *proxy_cli; + struct proxy_child_ctx *child_ctx; + struct pc_init_ctx *init_ctx; + struct tevent_req *req; + hash_value_t value; + hash_key_t key; + int hret; + struct sbus_connection *cli_conn; + + /* When connection is lost we also free the client. */ + proxy_cli = talloc_zero(sbus_req->conn, struct proxy_client); + if (proxy_cli == NULL) { + return ENOMEM; + } + + proxy_cli->proxy_auth_ctx = auth_ctx; + proxy_cli->conn = sbus_req->conn; + + key.type = HASH_KEY_ULONG; + key.ul = cli_id; + if (!hash_has_key(proxy_cli->proxy_auth_ctx->request_table, &key)) { + talloc_free(proxy_cli); + return EIO; + } + + hret = hash_lookup(proxy_cli->proxy_auth_ctx->request_table, &key, &value); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Hash error [%d]: %s\n", hret, hash_error_string(hret)); + talloc_free(proxy_cli); + return EIO; + } + + /* Signal that the child is up and ready to receive the request */ + req = talloc_get_type(value.ptr, struct tevent_req); + child_ctx = tevent_req_data(req, struct proxy_child_ctx); + + if (!child_ctx->running) { + /* This should hopefully be impossible, but protect + * against it anyway. If we're not marked running, then + * the init_req will be NULL below and things will + * break. + */ + DEBUG(SSSDBG_CRIT_FAILURE, "Client connection from a request " + "that's not marked as running\n"); + talloc_free(proxy_cli); + return EIO; + } + + init_ctx = tevent_req_data(child_ctx->init_req, struct pc_init_ctx); + init_ctx->conn = sbus_req->conn; + tevent_req_done(child_ctx->init_req); + child_ctx->init_req = NULL; + + /* Remove the timeout handler added by dp_client_init() */ + cli_conn = sbus_server_find_connection(dp_sbus_server(auth_ctx->be->provider), + sbus_req->sender->name); + if (cli_conn != NULL) { + dp_client_cancel_timeout(cli_conn); + } else { + DEBUG(SSSDBG_TRACE_ALL, "No connection found for [%s].\n", sbus_req->sender->name); + } + + return EOK; +} + +errno_t +proxy_client_init(struct sbus_connection *conn, + struct proxy_auth_ctx *auth_ctx) +{ + errno_t ret; + + SBUS_INTERFACE(iface, + sssd_ProxyChild_Client, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_ProxyChild_Client, Register, proxy_client_register, auth_ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + struct sbus_path paths[] = { + {SSS_BUS_PATH, &iface}, + {NULL, NULL} + }; + + ret = sbus_connection_add_path_map(conn, paths); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to add paths [%d]: %s\n", + ret, sss_strerror(ret)); + } + + return ret; +} diff --git a/src/providers/proxy/proxy_hosts.c b/src/providers/proxy/proxy_hosts.c new file mode 100644 index 0000000..d224829 --- /dev/null +++ b/src/providers/proxy/proxy_hosts.c @@ -0,0 +1,768 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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/proxy/proxy.h" +#include "db/sysdb_iphosts.h" +#include <resolv.h> +#include <arpa/inet.h> + +static errno_t +nss_status_to_errno(enum nss_status status) +{ + switch (status) { + case NSS_STATUS_SUCCESS: + return EOK; + case NSS_STATUS_TRYAGAIN: + return EAGAIN; + case NSS_STATUS_NOTFOUND: + return ENOENT; + case NSS_STATUS_UNAVAIL: + default: + break; + } + + return EIO; +} + +static errno_t +parse_hostent(TALLOC_CTX *mem_ctx, + struct hostent *result, + bool case_sensitive, + char **out_name, + char ***out_aliases, + char ***out_addresses) +{ + char **addresses = *out_addresses; + char **aliases = *out_aliases; + int i; + errno_t ret; + + /* Parse addresses */ + for (i = 0; result->h_addr_list[i] != NULL; i++) { + size_t len = talloc_array_length(addresses); + char buf[INET6_ADDRSTRLEN]; + const char *addr = NULL; + bool found = false; + int j; + + if (result->h_length == INADDRSZ) { + addr = inet_ntop(AF_INET, result->h_addr_list[i], + buf, INET6_ADDRSTRLEN); + } else if (result->h_length == IN6ADDRSZ) { + addr = inet_ntop(AF_INET6, result->h_addr_list[i], + buf, INET6_ADDRSTRLEN); + } + + if (addr == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to convert host network address of host " + "'%s' to a character string: %s\n", result->h_name, + strerror(errno)); + continue; + } + + /* Skip duplicates */ + for (j = 0; + j < len && addresses != NULL && addresses[j] != NULL; + j++) { + if (strcasecmp(addresses[j], addr) == 0) { + found = true; + break; + } + } + + if (!found) { + ret = add_string_to_list(mem_ctx, addr, &addresses); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Host [%s] has address [%s]\n", + result->h_name, addr); + } + } + + for (i = 0; result->h_aliases[i] != NULL; i++) { + size_t len = talloc_array_length(aliases); + const char *alias = result->h_aliases[i]; + bool found = false; + int j; + + for (j = 0; j < len && aliases != NULL && aliases[j] != NULL; j++) { + if (case_sensitive && strcmp(aliases[j], alias) == 0) { + found = true; + break; + } else if (strcasecmp(aliases[j], alias) == 0) { + found = true; + break; + } + } + + if (!found) { + ret = add_string_to_list(mem_ctx, alias, &aliases); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Host [%s] has alias [%s]\n", + result->h_name, alias); + } + } + + *out_name = talloc_strdup(mem_ctx, result->h_name); + *out_addresses = addresses; + *out_aliases = aliases; + + ret = EOK; +done: + return ret; +} + +static errno_t +proxy_save_host(struct sss_domain_info *domain, + bool lowercase, + uint64_t cache_timeout, + char *name, + char **aliases, + char **addresses) +{ + errno_t ret; + char *cased_name = NULL; + const char **cased_aliases = NULL; + const char **cased_addresses = NULL; + TALLOC_CTX *tmp_ctx; + char *lc_alias = NULL; + time_t now = time(NULL); + + DEBUG(SSSDBG_TRACE_FUNC, "Saving host [%s] into cache, domain [%s]\n", + name, domain->name); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + cased_name = sss_get_cased_name(tmp_ctx, name, + domain->case_preserve); + if (cased_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get cased name.\n"); + ret = ENOMEM; + goto done; + } + + /* Count the aliases */ + ret = sss_get_cased_name_list(tmp_ctx, + (const char * const *) aliases, + !lowercase, &cased_aliases); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get cased aliases.\n"); + goto done; + } + + /* Count the addresses */ + ret = sss_get_cased_name_list(tmp_ctx, + (const char * const *) addresses, + !lowercase, &cased_addresses); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get cased addresses.\n"); + goto done; + } + + if (domain->case_preserve) { + /* Add lowercased alias to allow case-insensitive lookup */ + lc_alias = sss_tc_utf8_str_tolower(tmp_ctx, name); + if (lc_alias == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot convert name to lowercase.\n"); + ret = ENOMEM; + goto done; + } + + ret = add_string_to_list(tmp_ctx, lc_alias, + discard_const_p(char **, &cased_aliases)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add lowercased name alias.\n"); + goto done; + } + } + + ret = sysdb_store_host(domain, cased_name, cased_aliases, cased_addresses, + NULL, NULL, cache_timeout, now); +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +get_host_by_name_internal(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + const char *search_name, int af, + char **out_name, + char ***out_addresses, + char ***out_aliases) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *buffer = NULL; + size_t buflen = DEFAULT_BUFSIZE; + struct hostent *result = NULL; + enum nss_status status; + int err; + int h_err; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Resolving host [%s] [%s]\n", search_name, + af == AF_INET ? "AF_INET" : "AF_INET6"); + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + result = talloc_zero(tmp_ctx, struct hostent); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + /* Ask for IPv4 addresses */ + err = 0; + h_err = 0; + for (status = NSS_STATUS_TRYAGAIN, + err = ERANGE, h_err = 0; + status == NSS_STATUS_TRYAGAIN && err == ERANGE; + buflen *= 2) + { + buffer = talloc_realloc_size(tmp_ctx, buffer, buflen); + if (buffer == NULL) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.gethostbyname2_r(search_name, af, result, + buffer, buflen, + &err, &h_err); + } + + ret = nss_status_to_errno(status); + if (ret != EOK) { + if (ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "gethostbyname2_r (%s) failed for host [%s]: %d, %s, %s.\n", + af == AF_INET ? "AF_INET" : "AF_INET6", + search_name, status, strerror(err), hstrerror(h_err)); + } + + goto done; + } + + ret = parse_hostent(mem_ctx, result, domain->case_sensitive, + out_name, out_aliases, out_addresses); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse hostent [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +get_host_byname(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + const char *search_name) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret_v4; + errno_t ret_v6; + errno_t ret; + char *name = NULL; + char **addresses = NULL; + char **aliases = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, "Processing request for host name [%s]\n", + search_name); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret_v4 = get_host_by_name_internal(ctx, domain, tmp_ctx, search_name, + AF_INET, &name, &addresses, &aliases); + if (ret_v4 != EOK && ret_v4 != ENOENT) { + ret = ret_v4; + goto done; + } + + ret_v6 = get_host_by_name_internal(ctx, domain, tmp_ctx, search_name, + AF_INET6, &name, &addresses, &aliases); + if (ret_v6 != EOK && ret_v6 != ENOENT) { + ret = ret_v6; + goto done; + } + + if (ret_v4 == ENOENT && ret_v6 == ENOENT) { + /* Make sure we remove it from the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Host [%s] not found, removing from " + "cache\n", name); + sysdb_host_delete(domain, search_name, NULL); + ret = ENOENT; + goto done; + } else { + /* Results found. Save them into the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Host [%s] found, saving into " + "cache\n", name); + ret = proxy_save_host(domain, !domain->case_sensitive, + domain->resolver_timeout, + name, aliases, addresses); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store host [%s] [%d]: %s\n", + name, ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +get_host_by_addr_internal(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + const char *addrstr, + char **out_name, + char ***out_addresses, + char ***out_aliases) +{ + TALLOC_CTX *tmp_ctx; + char *buffer = NULL; + size_t buflen = DEFAULT_BUFSIZE; + struct hostent *result = NULL; + enum nss_status status; + int err; + int h_err; + char addrbuf[IN6ADDRSZ]; + socklen_t addrlen = 0; + int af = 0; + errno_t ret; + char *name = NULL; + char **addresses = NULL; + char **aliases = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, "Resolving host [%s]\n", addrstr); + + if (inet_pton(AF_INET, addrstr, addrbuf)) { + af = AF_INET; + addrlen = INADDRSZ; + } else if (inet_pton(AF_INET6, addrstr, addrbuf)) { + af = AF_INET6; + addrlen = IN6ADDRSZ; + } else { + return EINVAL; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + result = talloc_zero(tmp_ctx, struct hostent); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + /* Ask for IPv4 addresses */ + err = 0; + h_err = 0; + for (status = NSS_STATUS_TRYAGAIN, + err = ERANGE, h_err = 0; + status == NSS_STATUS_TRYAGAIN && err == ERANGE; + buflen *= 2) + { + buffer = talloc_realloc_size(tmp_ctx, buffer, buflen); + if (buffer == NULL) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.gethostbyaddr_r(addrbuf, addrlen, af, result, + buffer, buflen, + &err, &h_err); + } + + ret = nss_status_to_errno(status); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "gethostbyaddr_r (%s) failed for host [%s]: %d, %s, %s.\n", + af == AF_INET ? "AF_INET" : "AF_INET6", + addrstr, status, strerror(err), hstrerror(h_err)); + goto done; + } + + if (ret == EOK) { + ret = parse_hostent(tmp_ctx, result, domain->case_sensitive, + &name, &aliases, &addresses); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to parse hostent [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + if (name != NULL) { + *out_name = talloc_steal(mem_ctx, name); + } + if (addresses != NULL) { + *out_addresses = talloc_steal(mem_ctx, addresses); + } + if (aliases != NULL) { + *out_aliases = talloc_steal(mem_ctx, aliases); + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +get_host_byaddr(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + const char *address) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + char *name = NULL; + char **addresses = NULL; + char **aliases = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, "Processing request for host address [%s]\n", + address); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = get_host_by_addr_internal(ctx, domain, tmp_ctx, address, + &name, &addresses, &aliases); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + if (ret == ENOENT) { + /* Make sure we remove it from the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Host [%s] not found, removing from " + "cache\n", address); + sysdb_host_delete(domain, NULL, address); + } else { + /* Results found. Save them into the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Host [%s] found, saving into " + "cache\n", address); + ret = proxy_save_host(domain, !domain->case_sensitive, + domain->resolver_timeout, + name, aliases, addresses); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store host [%s] [%d]: %s\n", + name, ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +gethostent_internal(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + char **out_name, + char ***out_addresses, + char ***out_aliases) + +{ + TALLOC_CTX *tmp_ctx = NULL; + char *buffer = NULL; + size_t buflen = DEFAULT_BUFSIZE; + enum nss_status status; + struct hostent *result = NULL; + char *name = NULL; + char **addresses = NULL; + char **aliases = NULL; + int err; + int h_err; + errno_t ret; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + result = talloc_zero(tmp_ctx, struct hostent); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + for (status = NSS_STATUS_TRYAGAIN, + err = ERANGE, h_err = 0; + status == NSS_STATUS_TRYAGAIN && err == ERANGE; + buflen *= 2) + { + buffer = talloc_realloc_size(tmp_ctx, buffer, buflen); + if (buffer == NULL) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.gethostent_r(result, + buffer, buflen, + &err, &h_err); + } + + ret = nss_status_to_errno(status); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "gethostent_r failed: %d, %s, %s.\n", + status, strerror(err), hstrerror(h_err)); + goto done; + } + + if (ret == EOK) { + ret = parse_hostent(tmp_ctx, result, domain->case_sensitive, + &name, &aliases, &addresses); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to parse hostent [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + if (name != NULL) { + *out_name = talloc_steal(mem_ctx, name); + } + if (addresses != NULL) { + *out_addresses = talloc_steal(mem_ctx, addresses); + } + if (aliases != NULL) { + *out_aliases = talloc_steal(mem_ctx, aliases); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +enum_iphosts(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain) +{ + struct sysdb_ctx *sysdb = domain->sysdb; + TALLOC_CTX *tmp_ctx = NULL; + bool in_transaction = false; + enum nss_status status; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Enumerating iphosts\n"); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + status = ctx->ops.sethostent(); + if (status != NSS_STATUS_SUCCESS) { + ret = EIO; + goto done; + } + + do { + char *name = NULL; + char **addresses = NULL; + char **aliases = NULL; + + ret = gethostent_internal(ctx, domain, tmp_ctx, &name, + &addresses, &aliases); + if (ret == EOK) { + /* Results found. Save them into the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Host [%s] found, saving into " + "cache\n", name); + + proxy_save_host(domain, !domain->case_sensitive, + domain->resolver_timeout, + name, aliases, addresses); + } + + /* Free children to avoid using too much memory */ + talloc_free_children(tmp_ctx); + } while (ret == EOK); + + if (ret == ENOENT) { + /* We are done, commit transaction and stop loop */ + DEBUG(SSSDBG_TRACE_FUNC, "Enumeration completed.\n"); + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "gethostent_r failed [%d]: %s\n", + ret, strerror(ret)); + } + +done: + talloc_free(tmp_ctx); + if (in_transaction) { + errno_t sret; + + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not cancel transaction! [%s]\n", + strerror(sret)); + } + } + ctx->ops.endhostent(); + return ret; +} + +static struct dp_reply_std +proxy_hosts_info(TALLOC_CTX *mem_ctx, + struct proxy_resolver_ctx *ctx, + struct dp_resolver_data *data, + struct be_ctx *be_ctx, + struct sss_domain_info *domain) +{ + struct dp_reply_std reply; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Processing host request, filter type [%d]\n", + data->filter_type); + + switch (data->filter_type) { + case BE_FILTER_NAME: + ret = get_host_byname(ctx, domain, data->filter_value); + break; + + case BE_FILTER_ADDR: + ret = get_host_byaddr(ctx, domain, data->filter_value); + break; + + case BE_FILTER_ENUM: + ret = enum_iphosts(ctx, domain); + break; + + default: + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + + if (ret) { + if (ret == ENXIO) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy returned UNAVAIL error, going offline!\n"); + be_mark_offline(be_ctx); + } + + dp_reply_std_set(&reply, DP_ERR_FATAL, ret, NULL); + return reply; + } + + dp_reply_std_set(&reply, DP_ERR_OK, EOK, NULL); + return reply; +} + +struct proxy_hosts_handler_state { + int dummy; + struct dp_reply_std reply; +}; + +struct tevent_req * +proxy_hosts_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_resolver_ctx *resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params) +{ + struct proxy_hosts_handler_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, struct proxy_hosts_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->reply = proxy_hosts_info(state, resolver_ctx, resolver_data, + params->be_ctx, params->be_ctx->domain); + + tevent_req_done(req); + return tevent_req_post(req, params->ev); +} + +errno_t +proxy_hosts_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct proxy_hosts_handler_state *state; + + state = tevent_req_data(req, struct proxy_hosts_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/proxy/proxy_id.c b/src/providers/proxy/proxy_id.c new file mode 100644 index 0000000..b1d0c22 --- /dev/null +++ b/src/providers/proxy/proxy_id.c @@ -0,0 +1,1962 @@ +/* + SSSD + + proxy_id.c + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2010 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 <dhash.h> +#include "config.h" + +#include "util/sss_format.h" +#include "util/strtonum.h" +#include "providers/proxy/proxy.h" + +/* =Getpwnam-wrapper======================================================*/ + +static int save_user(struct sss_domain_info *domain, + struct passwd *pwd, const char *real_name, + const char *alias); + +static int +handle_getpw_result(enum nss_status status, struct passwd *pwd, + struct sss_domain_info *dom, bool *del_user); + +static int +delete_user(struct sss_domain_info *domain, + const char *name, uid_t uid); + +int get_pw_name(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *i_name) +{ + TALLOC_CTX *tmpctx; + struct passwd *pwd; + enum nss_status status; + char *buffer; + size_t buflen; + int ret; + uid_t uid; + bool del_user; + struct ldb_result *cached_pwd = NULL; + const char *real_name = NULL; + char *shortname_or_alias; + + DEBUG(SSSDBG_TRACE_FUNC, "Searching user by name (%s)\n", i_name); + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmpctx, i_name, &shortname_or_alias, NULL); + if (ret != EOK) { + goto done; + } + + pwd = talloc_zero(tmpctx, struct passwd); + if (!pwd) { + ret = ENOMEM; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + /* FIXME: should we move this call outside the transaction to keep the + * transaction as short as possible? */ + status = ctx->ops.getpwnam_r(shortname_or_alias, pwd, buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwnam failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + if (del_user) { + ret = delete_user(dom, i_name, 0); + goto done; + } + + uid = pwd->pw_uid; + + /* Canonicalize the username in case it was actually an alias */ + + if (ctx->fast_alias == true) { + ret = sysdb_getpwuid(tmpctx, dom, uid, &cached_pwd); + if (ret != EOK) { + /* Non-fatal, attempt to canonicalize online */ + DEBUG(SSSDBG_TRACE_FUNC, "Request to cache failed [%d]: %s\n", + ret, strerror(ret)); + } + + if (ret == EOK && cached_pwd->count == 1) { + real_name = ldb_msg_find_attr_as_string(cached_pwd->msgs[0], + SYSDB_NAME, NULL); + if (!real_name) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cached user has no name?\n"); + } + } + } + + if (real_name == NULL) { + memset(buffer, 0, buflen); + + status = ctx->ops.getpwuid_r(uid, pwd, buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwuid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + real_name = sss_create_internal_fqname(tmpctx, pwd->pw_name, dom->name); + if (real_name == NULL) { + ret = ENOMEM; + goto done; + } + } + + if (del_user) { + ret = delete_user(dom, i_name, uid); + goto done; + } + + /* Both lookups went fine, we can save the user now */ + ret = save_user(dom, pwd, real_name, i_name); + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy -> getpwnam_r failed for '%s' <%d>: %s\n", + i_name, ret, strerror(ret)); + } + return ret; +} + +static int +handle_getpw_result(enum nss_status status, struct passwd *pwd, + struct sss_domain_info *dom, bool *del_user) +{ + int ret = EOK; + + if (!del_user) { + return EINVAL; + } + *del_user = false; + + switch (status) { + case NSS_STATUS_NOTFOUND: + + DEBUG(SSSDBG_TRACE_FUNC, "User not found.\n"); + *del_user = true; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(SSSDBG_TRACE_FUNC, "User found: (%s, %"SPRIuid", %"SPRIgid")\n", + pwd->pw_name, pwd->pw_uid, pwd->pw_gid); + + /* uid=0 or gid=0 are invalid values */ + /* also check that the id is in the valid range for this domain */ + if (OUT_OF_ID_RANGE(pwd->pw_uid, dom->id_min, dom->id_max) || + OUT_OF_ID_RANGE(pwd->pw_gid, dom->id_min, dom->id_max)) { + + DEBUG(SSSDBG_MINOR_FAILURE, + "User filtered out! (id out of range)\n"); + *del_user = true; + break; + } + break; + + case NSS_STATUS_UNAVAIL: + DEBUG(SSSDBG_MINOR_FAILURE, + "Remote back end is not available. Entering offline mode\n"); + ret = ENXIO; + break; + + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown return code %d\n", status); + ret = EIO; + break; + } + + return ret; +} + +static int +delete_user(struct sss_domain_info *domain, + const char *name, uid_t uid) +{ + int ret = EOK; + + if (name != NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "User %s does not exist (or is invalid) on remote server," + " deleting!\n", name); + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "User with UID %"SPRIuid" does not exist (or is invalid) " + "on remote server, deleting!\n", uid); + } + + ret = sysdb_delete_user(domain, name, uid); + if (ret == ENOENT) { + ret = EOK; + } + + return ret; +} + +static int +prepare_attrs_for_saving_ops(TALLOC_CTX *mem_ctx, + bool case_sensitive, + const char *real_name, /* already_qualified */ + const char *alias, /* already qualified */ + struct sysdb_attrs **attrs) +{ + const char *lc_name = NULL; + const char *cased_alias = NULL; + errno_t ret; + + if (!case_sensitive || alias != NULL) { + if (*attrs == NULL) { + *attrs = sysdb_new_attrs(mem_ctx); + if (*attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Allocation error?!\n"); + ret = ENOMEM; + goto done; + } + } + } + + if (!case_sensitive) { + lc_name = sss_tc_utf8_str_tolower(*attrs, real_name); + if (lc_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot convert name to lowercase.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_string(*attrs, SYSDB_NAME_ALIAS, lc_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add name alias\n"); + ret = ENOMEM; + goto done; + } + + } + + if (alias != NULL) { + cased_alias = sss_get_cased_name(*attrs, alias, case_sensitive); + if (cased_alias == NULL) { + ret = ENOMEM; + goto done; + } + + /* Add the alias only if it differs from lowercased pw_name */ + if (lc_name == NULL || strcmp(cased_alias, lc_name) != 0) { + ret = sysdb_attrs_add_string(*attrs, SYSDB_NAME_ALIAS, + cased_alias); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add name alias\n"); + goto done; + } + } + } + + ret = EOK; +done: + return ret; +} + +static int save_user(struct sss_domain_info *domain, + struct passwd *pwd, + const char *real_name, /* already qualified */ + const char *alias) /* already qualified */ +{ + const char *shell; + const char *gecos; + struct sysdb_attrs *attrs = NULL; + errno_t ret; + + if (pwd->pw_shell && pwd->pw_shell[0] != '\0') { + shell = pwd->pw_shell; + } else { + shell = NULL; + } + + if (pwd->pw_gecos && pwd->pw_gecos[0] != '\0') { + gecos = pwd->pw_gecos; + } else { + gecos = NULL; + } + + ret = prepare_attrs_for_saving_ops(NULL, domain->case_sensitive, + real_name, alias, &attrs); + if (ret != EOK) { + goto done; + } + + ret = sysdb_store_user(domain, + real_name, + pwd->pw_passwd, + pwd->pw_uid, + pwd->pw_gid, + gecos, + pwd->pw_dir, + shell, + NULL, + attrs, + NULL, + domain->user_timeout, + 0); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add user to cache\n"); + goto done; + } + +done: + talloc_zfree(attrs); + return ret; +} + +/* =Getpwuid-wrapper======================================================*/ + +static int get_pw_uid(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + uid_t uid) +{ + TALLOC_CTX *tmpctx; + struct passwd *pwd; + enum nss_status status; + char *buffer; + size_t buflen; + bool del_user = false; + int ret; + char *name; + + DEBUG(SSSDBG_TRACE_FUNC, "Searching user by uid (%"SPRIuid")\n", uid); + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + pwd = talloc_zero(tmpctx, struct passwd); + if (!pwd) { + ret = ENOMEM; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getpwuid_r(uid, pwd, buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwuid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + if (del_user) { + ret = delete_user(dom, NULL, uid); + goto done; + } + + name = sss_create_internal_fqname(tmpctx, pwd->pw_name, dom->name); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to qualify name '%s'\n", + pwd->pw_name); + goto done; + } + ret = save_user(dom, pwd, name, NULL); + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "proxy -> getpwuid_r failed for '%"SPRIuid"' <%d>: %s\n", + uid, ret, strerror(ret)); + } + return ret; +} + +/* =Getpwent-wrapper======================================================*/ + +static int enum_users(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom) +{ + TALLOC_CTX *tmpctx; + bool in_transaction = false; + struct passwd *pwd; + enum nss_status status; + size_t buflen; + char *buffer; + char *newbuf; + int ret; + errno_t sret; + bool again; + char *name; + + DEBUG(SSSDBG_TRACE_LIBS, "Enumerating users\n"); + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + pwd = talloc_zero(tmpctx, struct passwd); + if (!pwd) { + ret = ENOMEM; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + status = ctx->ops.setpwent(); + if (status != NSS_STATUS_SUCCESS) { + ret = EIO; + goto done; + } + + do { + again = false; + + /* always zero out the pwd structure */ + memset(pwd, 0, sizeof(struct passwd)); + + /* get entry */ + status = ctx->ops.getpwent_r(pwd, buffer, buflen, &ret); + + switch (status) { + case NSS_STATUS_TRYAGAIN: + /* buffer too small? */ + if (buflen < MAX_BUF_SIZE) { + buflen *= 2; + } + if (buflen > MAX_BUF_SIZE) { + buflen = MAX_BUF_SIZE; + } + newbuf = talloc_realloc_size(tmpctx, buffer, buflen); + if (!newbuf) { + ret = ENOMEM; + goto done; + } + buffer = newbuf; + again = true; + break; + + case NSS_STATUS_NOTFOUND: + + /* we are done here */ + DEBUG(SSSDBG_TRACE_LIBS, "Enumeration completed.\n"); + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(SSSDBG_TRACE_LIBS, + "User found (%s, %"SPRIuid", %"SPRIgid")\n", + pwd->pw_name, pwd->pw_uid, pwd->pw_gid); + + /* uid=0 or gid=0 are invalid values */ + /* also check that the id is in the valid range for this domain + */ + if (OUT_OF_ID_RANGE(pwd->pw_uid, dom->id_min, dom->id_max) || + OUT_OF_ID_RANGE(pwd->pw_gid, dom->id_min, dom->id_max)) { + + DEBUG(SSSDBG_OP_FAILURE, "User [%s] filtered out! (id out" + " of range)\n", pwd->pw_name); + + again = true; + break; + } + + name = sss_create_internal_fqname(tmpctx, pwd->pw_name, dom->name); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "failed to create internal name '%s'\n", + pwd->pw_name); + goto done; + } + ret = save_user(dom, pwd, name, NULL); + if (ret) { + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + DEBUG(SSSDBG_OP_FAILURE, "Failed to store user %s." + " Ignoring.\n", pwd->pw_name); + } + again = true; + break; + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + break; + + default: + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, "proxy -> getpwent_r failed (%d)[%s]" + "\n", ret, strerror(ret)); + break; + } + } while (again); + +done: + talloc_zfree(tmpctx); + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + ctx->ops.endpwent(); + return ret; +} + +/* =Save-group-utilities=================================================*/ +#define DEBUG_GR_MEM(level, grp) \ + do { \ + if (!grp->gr_mem || !grp->gr_mem[0]) { \ + DEBUG(level, "Group %s has no members!\n", \ + grp->gr_name); \ + } else { \ + int i = 0; \ + while (grp->gr_mem[i]) { \ + /* count */ \ + i++; \ + } \ + DEBUG(level, "Group %s has %d members!\n", \ + grp->gr_name, i); \ + } \ + } while(0) + + +static errno_t remove_duplicate_group_members(TALLOC_CTX *mem_ctx, + const struct group *orig_grp, + struct group **_grp) +{ + TALLOC_CTX *tmp_ctx; + hash_table_t *member_tbl = NULL; + struct hash_iter_context_t *iter; + hash_entry_t *entry; + hash_key_t key; + hash_value_t value; + struct group *grp; + size_t orig_member_count= 0; + size_t member_count= 0; + size_t i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + return ENOMEM; + } + + grp = talloc(tmp_ctx, struct group); + if (grp == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto done; + } + + grp->gr_gid = orig_grp->gr_gid; + + grp->gr_name = talloc_strdup(grp, orig_grp->gr_name); + if (grp->gr_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + grp->gr_passwd = talloc_strdup(grp, orig_grp->gr_passwd); + if (grp->gr_passwd == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + if (orig_grp->gr_mem == NULL) { + grp->gr_mem = NULL; + ret = EOK; + goto done; + } + + for (i=0; orig_grp->gr_mem[i] != NULL; ++i) /* no-op: just counting */; + + orig_member_count = i; + + if (orig_member_count == 0) { + grp->gr_mem = talloc_zero_array(grp, char *, 1); + if (grp->gr_mem == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + grp->gr_mem[0] = NULL; + ret = EOK; + goto done; + } + + ret = sss_hash_create(tmp_ctx, orig_member_count, &member_tbl); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create hash table.\n"); + ret = ENOMEM; + goto done; + } + + for (i=0; i < orig_member_count; ++i) { + key.type = HASH_KEY_STRING; + key.str = orig_grp->gr_mem[i]; /* hash_enter() makes copy itself */ + + value.type = HASH_VALUE_PTR; + /* no need to put copy in hash_table since + copy will be created during construction of new grp */ + value.ptr = orig_grp->gr_mem[i]; + + ret = hash_enter(member_tbl, &key, &value); + if (ret != HASH_SUCCESS) { + ret = ENOMEM; + goto done; + } + } + + member_count = hash_count(member_tbl); + if (member_count == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Empty resulting hash table - must be internal bug.\n"); + ret = EINVAL; + goto done; + } + + grp->gr_mem = talloc_zero_array(grp, char *, member_count + 1); + if (grp->gr_mem == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + iter = new_hash_iter_context(member_tbl); + if (iter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "new_hash_iter_context failed.\n"); + ret = EINVAL; + goto done; + } + + i = 0; + while ((entry = iter->next(iter)) != NULL) { + grp->gr_mem[i] = talloc_strdup(grp, entry->key.str); + if (grp->gr_mem[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + i++; + } + grp->gr_mem[i] = NULL; + + ret = EOK; + +done: + if (ret == EOK) { + *_grp = talloc_steal(mem_ctx, grp); + } + talloc_zfree(tmp_ctx); + + return ret; +} + +static errno_t proxy_process_missing_users(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sysdb_attrs *group_attrs, + const char *const*fq_gr_mem, + time_t now); +static int save_group(struct sysdb_ctx *sysdb, struct sss_domain_info *dom, + const struct group *grp, + const char *real_name, /* already qualified */ + const char *alias) /* already qualified */ +{ + errno_t ret, sret; + struct group *ngroup = NULL; + struct sysdb_attrs *attrs = NULL; + TALLOC_CTX *tmp_ctx; + time_t now = time(NULL); + bool in_transaction = false; + char **fq_gr_mem; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = remove_duplicate_group_members(tmp_ctx, grp, &ngroup); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to remove duplicate group members\n"); + goto done; + } + + DEBUG_GR_MEM(SSSDBG_TRACE_LIBS, ngroup); + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + if (ngroup->gr_mem && ngroup->gr_mem[0]) { + attrs = sysdb_new_attrs(tmp_ctx); + if (!attrs) { + DEBUG(SSSDBG_CRIT_FAILURE, "Allocation error?!\n"); + ret = ENOMEM; + goto done; + } + + fq_gr_mem = sss_create_internal_fqname_list( + tmp_ctx, + (const char *const*) ngroup->gr_mem, + dom->name); + if (fq_gr_mem == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_users_from_str_list( + attrs, SYSDB_MEMBER, dom->name, + (const char *const *) fq_gr_mem); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add group members\n"); + goto done; + } + + /* Create ghost users */ + ret = proxy_process_missing_users(sysdb, dom, attrs, + (const char *const*) fq_gr_mem, now); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add missing members\n"); + goto done; + } + } + + ret = prepare_attrs_for_saving_ops(tmp_ctx, dom->case_sensitive, + real_name, alias, &attrs); + if (ret != EOK) { + goto done; + } + + ret = sysdb_store_group(dom, + real_name, + ngroup->gr_gid, + attrs, + dom->group_timeout, + now); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add group to cache\n"); + goto done; + } + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not commit transaction: [%s]\n", + strerror(ret)); + goto done; + } + in_transaction = false; + +done: + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n"); + } + } + talloc_free(tmp_ctx); + return ret; +} + +static errno_t proxy_process_missing_users(struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + struct sysdb_attrs *group_attrs, + const char *const*fq_gr_mem, + time_t now) +{ + errno_t ret; + size_t i; + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_message *msg; + + if (!sysdb || !fq_gr_mem) return EINVAL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + for (i = 0; fq_gr_mem[i]; i++) { + ret = sysdb_search_user_by_name(tmp_ctx, domain, fq_gr_mem[i], + NULL, &msg); + if (ret == EOK) { + /* Member already exists in the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Member [%s] already cached\n", fq_gr_mem[i]); + /* clean up */ + talloc_zfree(msg); + continue; + } else if (ret == ENOENT) { + /* No entry for this user. Create a ghost user */ + DEBUG(SSSDBG_TRACE_LIBS, + "Member [%s] not cached, creating ghost user entry\n", + fq_gr_mem[i]); + + ret = sysdb_attrs_add_string(group_attrs, SYSDB_GHOST, fq_gr_mem[i]); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot store ghost user entry: [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + } else { + /* Unexpected error */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Error searching cache for user [%s]: [%s]\n", + fq_gr_mem[i], strerror(ret)); + goto done; + } + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +/* =Getgrnam-wrapper======================================================*/ +static char * +grow_group_buffer(TALLOC_CTX *mem_ctx, + char **buffer, size_t *buflen) +{ + char *newbuf; + + if (*buflen == 0) { + *buflen = DEFAULT_BUFSIZE; + } + if (*buflen < MAX_BUF_SIZE) { + *buflen *= 2; + } + if (*buflen > MAX_BUF_SIZE) { + *buflen = MAX_BUF_SIZE; + } + + newbuf = talloc_realloc_size(mem_ctx, *buffer, *buflen); + if (!newbuf) { + return NULL; + } + *buffer = newbuf; + + return *buffer; +} + +static errno_t +handle_getgr_result(enum nss_status status, struct group *grp, + struct sss_domain_info *dom, + bool *delete_group) +{ + if (delete_group) { + *delete_group = false; + } + + switch (status) { + case NSS_STATUS_TRYAGAIN: + DEBUG(SSSDBG_MINOR_FAILURE, "Buffer too small\n"); + return EAGAIN; + + case NSS_STATUS_NOTFOUND: + DEBUG(SSSDBG_MINOR_FAILURE, "Group not found.\n"); + if (delete_group) { + *delete_group = true; + } + break; + + case NSS_STATUS_SUCCESS: + DEBUG(SSSDBG_FUNC_DATA, "Group found: (%s, %"SPRIgid")\n", + grp->gr_name, grp->gr_gid); + + /* gid=0 is an invalid value */ + /* also check that the id is in the valid range for this domain */ + if (OUT_OF_ID_RANGE(grp->gr_gid, dom->id_min, dom->id_max)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Group filtered out! (id out of range)\n"); + if (delete_group) { + *delete_group = true; + } + break; + } + break; + + case NSS_STATUS_UNAVAIL: + DEBUG(SSSDBG_MINOR_FAILURE, + "Remote back end is not available. Entering offline mode\n"); + return ENXIO; + + default: + DEBUG(SSSDBG_OP_FAILURE, "Unknown return code %d\n", status); + return EIO; + } + + return EOK; +} + +static int get_gr_name(struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + const char *i_name) +{ + TALLOC_CTX *tmpctx; + struct group *grp; + enum nss_status status; + char *buffer = 0; + size_t buflen = 0; + bool delete_group = false; + int ret; + gid_t gid; + struct ldb_result *cached_grp = NULL; + const char *real_name = NULL; + char *shortname_or_alias; + + DEBUG(SSSDBG_FUNC_DATA, "Searching group by name (%s)\n", i_name); + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmpctx, i_name, &shortname_or_alias, NULL); + if (ret != EOK) { + goto done; + } + + grp = talloc(tmpctx, struct group); + if (!grp) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "talloc() failed\n"); + goto done; + } + + do { + /* always zero out the grp structure */ + memset(grp, 0, sizeof(struct group)); + buffer = grow_group_buffer(tmpctx, &buffer, &buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getgrnam_r(shortname_or_alias, grp, buffer, + buflen, &ret); + ret = handle_getgr_result(status, grp, dom, &delete_group); + } while (ret == EAGAIN); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "getgrnam failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + if (delete_group) { + DEBUG(SSSDBG_TRACE_FUNC, + "Group %s does not exist (or is invalid) on remote server," + " deleting!\n", i_name); + + ret = sysdb_delete_group(dom, i_name, 0); + if (ret == ENOENT) { + ret = EOK; + } + goto done; + } + + gid = grp->gr_gid; + + /* Canonicalize the group name in case it was actually an alias */ + if (ctx->fast_alias == true) { + ret = sysdb_getgrgid(tmpctx, dom, gid, &cached_grp); + if (ret != EOK) { + /* Non-fatal, attempt to canonicalize online */ + DEBUG(SSSDBG_TRACE_FUNC, "Request to cache failed [%d]: %s\n", + ret, strerror(ret)); + } + + if (ret == EOK && cached_grp->count == 1) { + real_name = ldb_msg_find_attr_as_string(cached_grp->msgs[0], + SYSDB_NAME, NULL); + if (!real_name) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cached group has no name?\n"); + } + } + } + + if (real_name == NULL) { + talloc_zfree(buffer); + buflen = 0; + + do { + memset(grp, 0, sizeof(struct group)); + buffer = grow_group_buffer(tmpctx, &buffer, &buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getgrgid_r(gid, grp, buffer, buflen, &ret); + + ret = handle_getgr_result(status, grp, dom, &delete_group); + } while (ret == EAGAIN); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "getgrgid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + real_name = sss_create_internal_fqname(tmpctx, grp->gr_name, dom->name); + if (real_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create fqdn '%s'\n", + grp->gr_name); + ret = ENOMEM; + goto done; + } + } + + if (delete_group) { + DEBUG(SSSDBG_TRACE_FUNC, + "Group %s does not exist (or is invalid) on remote server," + " deleting!\n", i_name); + + ret = sysdb_delete_group(dom, i_name, gid); + if (ret == ENOENT) { + ret = EOK; + } + goto done; + } + + ret = save_group(sysdb, dom, grp, real_name, i_name); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot save group [%d]: %s\n", ret, strerror(ret)); + goto done; + } + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy -> getgrnam_r failed for '%s' <%d>: %s\n", + i_name, ret, strerror(ret)); + } + return ret; +} + +/* =Getgrgid-wrapper======================================================*/ +static int get_gr_gid(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + gid_t gid, + time_t now) +{ + TALLOC_CTX *tmpctx; + struct group *grp; + enum nss_status status; + char *buffer = NULL; + size_t buflen = 0; + bool delete_group = false; + int ret; + char *name; + + DEBUG(SSSDBG_TRACE_FUNC, "Searching group by gid (%"SPRIgid")\n", gid); + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + grp = talloc(tmpctx, struct group); + if (!grp) { + ret = ENOMEM; + goto done; + } + + do { + /* always zero out the grp structure */ + memset(grp, 0, sizeof(struct group)); + buffer = grow_group_buffer(tmpctx, &buffer, &buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getgrgid_r(gid, grp, buffer, buflen, &ret); + + ret = handle_getgr_result(status, grp, dom, &delete_group); + } while (ret == EAGAIN); + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "getgrgid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + if (delete_group) { + DEBUG(SSSDBG_TRACE_FUNC, + "Group %"SPRIgid" does not exist (or is invalid) on remote " + "server, deleting!\n", gid); + + ret = sysdb_delete_group(dom, NULL, gid); + if (ret == ENOENT) { + ret = EOK; + } + goto done; + } + + name = sss_create_internal_fqname(tmpctx, grp->gr_name, dom->name); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + ret = save_group(sysdb, dom, grp, name, NULL); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot save user [%d]: %s\n", ret, strerror(ret)); + goto done; + } + +done: + talloc_zfree(tmpctx); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy -> getgrgid_r failed for '%"SPRIgid"' <%d>: %s\n", + gid, ret, strerror(ret)); + } + return ret; +} + +/* =Getgrent-wrapper======================================================*/ + +static int enum_groups(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom) +{ + TALLOC_CTX *tmpctx; + bool in_transaction = false; + struct group *grp; + enum nss_status status; + size_t buflen; + char *buffer; + char *newbuf; + int ret; + errno_t sret; + bool again; + char *name; + + DEBUG(SSSDBG_TRACE_LIBS, "Enumerating groups\n"); + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + grp = talloc(tmpctx, struct group); + if (!grp) { + ret = ENOMEM; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + status = ctx->ops.setgrent(); + if (status != NSS_STATUS_SUCCESS) { + ret = EIO; + goto done; + } + + do { + again = false; + + /* always zero out the grp structure */ + memset(grp, 0, sizeof(struct group)); + + /* get entry */ + status = ctx->ops.getgrent_r(grp, buffer, buflen, &ret); + + switch (status) { + case NSS_STATUS_TRYAGAIN: + /* buffer too small? */ + if (buflen < MAX_BUF_SIZE) { + buflen *= 2; + } + if (buflen > MAX_BUF_SIZE) { + buflen = MAX_BUF_SIZE; + } + newbuf = talloc_realloc_size(tmpctx, buffer, buflen); + if (!newbuf) { + ret = ENOMEM; + goto done; + } + buffer = newbuf; + again = true; + break; + + case NSS_STATUS_NOTFOUND: + + /* we are done here */ + DEBUG(SSSDBG_TRACE_LIBS, "Enumeration completed.\n"); + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(SSSDBG_OP_FAILURE, "Group found (%s, %"SPRIgid")\n", + grp->gr_name, grp->gr_gid); + + /* gid=0 is an invalid value */ + /* also check that the id is in the valid range for this domain + */ + if (OUT_OF_ID_RANGE(grp->gr_gid, dom->id_min, dom->id_max)) { + + DEBUG(SSSDBG_OP_FAILURE, "Group [%s] filtered out! (id" + "out of range)\n", grp->gr_name); + + again = true; + break; + } + + name = sss_create_internal_fqname(tmpctx, grp->gr_name, + dom->name); + if (name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create internal fqname " + "Ignoring\n"); + ret = ENOMEM; + } + ret = save_group(sysdb, dom, grp, name, NULL); + if (ret) { + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + DEBUG(SSSDBG_OP_FAILURE, "Failed to store group." + "Ignoring\n"); + } + again = true; + break; + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + break; + + default: + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, "proxy -> getgrent_r failed (%d)[%s]" + "\n", ret, strerror(ret)); + break; + } + } while (again); + +done: + talloc_zfree(tmpctx); + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + ctx->ops.endgrent(); + return ret; +} + + +/* =Initgroups-wrapper====================================================*/ + +static int get_initgr_groups_process(TALLOC_CTX *memctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct passwd *pwd); + +static int get_initgr(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + const char *i_name) +{ + TALLOC_CTX *tmpctx; + bool in_transaction = false; + struct passwd *pwd; + enum nss_status status; + char *buffer; + size_t buflen; + int ret; + errno_t sret; + bool del_user; + uid_t uid; + struct ldb_result *cached_pwd = NULL; + const char *real_name = NULL; + char *shortname_or_alias; + + tmpctx = talloc_new(mem_ctx); + if (!tmpctx) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmpctx, i_name, &shortname_or_alias, NULL); + if (ret != EOK) { + goto done; + } + + pwd = talloc_zero(tmpctx, struct passwd); + if (!pwd) { + ret = ENOMEM; + goto fail; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto fail; + } + in_transaction = true; + + /* FIXME: should we move this call outside the transaction to keep the + * transaction as short as possible? */ + status = ctx->ops.getpwnam_r(shortname_or_alias, pwd, + buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwnam failed [%d]: %s\n", ret, strerror(ret)); + goto fail; + } + + if (del_user) { + ret = delete_user(dom, i_name, 0); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not delete user\n"); + goto fail; + } + goto done; + } + + uid = pwd->pw_uid; + + /* Canonicalize the username in case it was actually an alias */ + if (ctx->fast_alias == true) { + ret = sysdb_getpwuid(tmpctx, dom, uid, &cached_pwd); + if (ret != EOK) { + /* Non-fatal, attempt to canonicalize online */ + DEBUG(SSSDBG_TRACE_FUNC, "Request to cache failed [%d]: %s\n", + ret, strerror(ret)); + } + + if (ret == EOK && cached_pwd->count == 1) { + real_name = ldb_msg_find_attr_as_string(cached_pwd->msgs[0], + SYSDB_NAME, NULL); + if (!real_name) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cached user has no name?\n"); + } + } + } + + if (real_name == NULL) { + memset(buffer, 0, buflen); + + status = ctx->ops.getpwuid_r(uid, pwd, buffer, buflen, &ret); + ret = handle_getpw_result(status, pwd, dom, &del_user); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, + "getpwuid failed [%d]: %s\n", ret, strerror(ret)); + goto done; + } + + real_name = sss_create_internal_fqname(tmpctx, pwd->pw_name, dom->name); + if (real_name == NULL) { + ret = ENOMEM; + goto done; + } + } + + if (del_user) { + ret = delete_user(dom, i_name, uid); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not delete user\n"); + goto fail; + } + goto done; + } + + ret = save_user(dom, pwd, real_name, i_name); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not save user\n"); + goto fail; + } + + ret = get_initgr_groups_process(tmpctx, ctx, sysdb, dom, pwd); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not process initgroups\n"); + goto fail; + } + +done: + ret = sysdb_transaction_commit(sysdb); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to commit transaction\n"); + goto fail; + } + in_transaction = false; + +fail: + talloc_zfree(tmpctx); + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n"); + } + } + return ret; +} + +static int remove_group_members(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const struct passwd *pwd, + long int num_gids, + const gid_t *gids, + long int num_cached_gids, + const gid_t *cached_gids) +{ + TALLOC_CTX *tmp_ctx = NULL; + int i = 0, j = 0; + int ret = EOK; + const char *groupname = NULL; + const char *username = NULL; + bool group_found = false; + struct ldb_result *res = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + username = sss_create_internal_fqname(tmp_ctx, pwd->pw_name, dom->name); + if (username == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create fqdn '%s'\n", pwd->pw_name); + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_cached_gids; i++) { + group_found = false; + /* group 0 is the primary group so it can be skipped */ + for (j = 1; j < num_gids; j++) { + if (cached_gids[i] == gids[j]) { + group_found = true; + break; + } + } + + if (!group_found) { + ret = sysdb_getgrgid(tmp_ctx, dom, cached_gids[i], &res); + if (ret != EOK || res->count != 1) { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_getgrgid failed for GID [%d].\n", cached_gids[i]); + continue; + } + + groupname = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_NAME, NULL); + if (groupname == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Attribute is missing but this should never happen!\n"); + continue; + } + + ret = sysdb_remove_group_member(dom, groupname, + username, + SYSDB_MEMBER_USER, false); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not remove member [%s] from group [%s]\n", + username, groupname); + continue; + } + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int get_cached_user_groups(struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + const struct passwd *pwd, + unsigned int *_num_cached_gids, + gid_t **_cached_gids) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret = EOK; + int i = 0, j = 0; + gid_t gid = 0; + gid_t *cached_gids = NULL; + const char *username = NULL; + struct ldb_result *res = NULL; + + if (_num_cached_gids == NULL || _cached_gids == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + goto done; + } + + username = sss_create_internal_fqname(tmp_ctx, pwd->pw_name, dom->name); + if (username == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create fqdn '%s'\n", pwd->pw_name); + ret = ENOMEM; + goto done; + } + + ret = sysdb_initgroups(tmp_ctx, dom, username, &res); + /* the first element is the user itself so it can be skipped */ + if (ret == EOK && res->count > 1) { + cached_gids = talloc_array(tmp_ctx, gid_t, res->count - 1); + + for (i = 1; i < res->count; i++) { + gid = ldb_msg_find_attr_as_uint(res->msgs[i], SYSDB_GIDNUM, 0); + if (gid != 0) { + cached_gids[j] = gid; + j++; + } + } + + *_num_cached_gids = j; + *_cached_gids = talloc_steal(sysdb, cached_gids); + } else if (ret == EOK) { + *_num_cached_gids = 0; + *_cached_gids = NULL; + } else { + goto done; + } + + ret = EOK; + +done: + talloc_zfree(tmp_ctx); + + return ret; +} + +static int get_initgr_groups_process(TALLOC_CTX *memctx, + struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom, + struct passwd *pwd) +{ + enum nss_status status; + long int limit; + long int size; + long int num; + long int num_gids; + gid_t *gids; + int ret; + int i; + time_t now; + gid_t *cached_gids = NULL; + unsigned int num_cached_gids = 0; + + num_gids = 0; + limit = 4096; + num = 4096; + size = num*sizeof(gid_t); + gids = talloc_size(memctx, size); + if (!gids) { + return ENOMEM; + } + + /* nss modules may skip the primary group when we pass it in so always add + * it in advance */ + gids[0] = pwd->pw_gid; + num_gids++; + + /* FIXME: should we move this call outside the transaction to keep the + * transaction as short as possible? */ + do { + status = ctx->ops.initgroups_dyn(pwd->pw_name, pwd->pw_gid, &num_gids, + &num, &gids, limit, &ret); + + if (status == NSS_STATUS_TRYAGAIN) { + /* buffer too small? */ + if (size < MAX_BUF_SIZE) { + num *= 2; + size = num*sizeof(gid_t); + } + if (size > MAX_BUF_SIZE) { + size = MAX_BUF_SIZE; + num = size/sizeof(gid_t); + } + limit = num; + gids = talloc_realloc_size(memctx, gids, size); + if (!gids) { + return ENOMEM; + } + } + } while(status == NSS_STATUS_TRYAGAIN); + + switch (status) { + case NSS_STATUS_NOTFOUND: + DEBUG(SSSDBG_FUNC_DATA, "The initgroups call returned 'NOTFOUND'. " + "Assume the user is only member of its " + "primary group (%"SPRIgid")\n", pwd->pw_gid); + /* fall through */ + SSS_ATTRIBUTE_FALLTHROUGH; + case NSS_STATUS_SUCCESS: + DEBUG(SSSDBG_CONF_SETTINGS, "User [%s] appears to be member of %lu " + "groups\n", pwd->pw_name, num_gids); + + ret = get_cached_user_groups(sysdb, dom, pwd, &num_cached_gids, &cached_gids); + if (ret) { + return ret; + } + ret = remove_group_members(ctx, dom, pwd, num_gids, gids, num_cached_gids, cached_gids); + talloc_free(cached_gids); + if (ret) { + return ret; + } + + now = time(NULL); + for (i = 0; i < num_gids; i++) { + ret = get_gr_gid(memctx, ctx, sysdb, dom, gids[i], now); + if (ret) { + return ret; + } + } + ret = EOK; + + break; + + default: + DEBUG(SSSDBG_OP_FAILURE, "proxy -> initgroups_dyn failed (%d)[%s]\n", + ret, strerror(ret)); + ret = EIO; + break; + } + + return ret; +} + +/* =Proxy_Id-Functions====================================================*/ + +static struct dp_reply_std +proxy_account_info(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *ctx, + struct dp_id_data *data, + struct be_ctx *be_ctx, + struct sss_domain_info *domain) +{ + struct dp_reply_std reply; + struct sysdb_ctx *sysdb; + uid_t uid; + gid_t gid; + errno_t ret; + char *endptr; + + sysdb = domain->sysdb; + + /* Proxy provider does not support security ID lookups. */ + if (data->filter_type == BE_FILTER_SECID) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENOSYS, + "Security lookups are not supported"); + return reply; + } + + switch (data->entry_type & BE_REQ_TYPE_MASK) { + case BE_REQ_USER: /* user */ + switch (data->filter_type) { + case BE_FILTER_ENUM: + ret = enum_users(mem_ctx, ctx, sysdb, domain); + break; + + case BE_FILTER_NAME: + ret = get_pw_name(ctx, domain, data->filter_value); + break; + + case BE_FILTER_IDNUM: + uid = (uid_t) strtouint32(data->filter_value, &endptr, 10); + if (errno || *endptr || (data->filter_value == endptr)) { + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid attr type"); + return reply; + } + ret = get_pw_uid(ctx, domain, uid); + break; + default: + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + break; + + case BE_REQ_GROUP: /* group */ + switch (data->filter_type) { + case BE_FILTER_ENUM: + ret = enum_groups(mem_ctx, ctx, sysdb, domain); + break; + case BE_FILTER_NAME: + ret = get_gr_name(ctx, sysdb, domain, data->filter_value); + break; + case BE_FILTER_IDNUM: + gid = (gid_t) strtouint32(data->filter_value, &endptr, 10); + if (errno || *endptr || (data->filter_value == endptr)) { + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid attr type"); + return reply; + } + ret = get_gr_gid(mem_ctx, ctx, sysdb, domain, gid, 0); + break; + default: + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + break; + + case BE_REQ_INITGROUPS: /* init groups for user */ + if (data->filter_type != BE_FILTER_NAME) { + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + if (ctx->ops.initgroups_dyn == NULL) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Initgroups call not supported"); + return reply; + } + ret = get_initgr(mem_ctx, ctx, sysdb, domain, data->filter_value); + break; + + case BE_REQ_NETGROUP: + if (data->filter_type != BE_FILTER_NAME) { + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + if (ctx->ops.setnetgrent == NULL || ctx->ops.getnetgrent_r == NULL || + ctx->ops.endnetgrent == NULL) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Netgroups are not supported"); + return reply; + } + + ret = get_netgroup(ctx, domain, data->filter_value); + break; + + case BE_REQ_SERVICES: + switch (data->filter_type) { + case BE_FILTER_NAME: + if (ctx->ops.getservbyname_r == NULL) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Services are not supported"); + return reply; + } + ret = get_serv_byname(ctx, domain, + data->filter_value, + data->extra_value); + break; + case BE_FILTER_IDNUM: + if (ctx->ops.getservbyport_r == NULL) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Services are not supported"); + return reply; + } + ret = get_serv_byport(ctx, domain, + data->filter_value, + data->extra_value); + break; + case BE_FILTER_ENUM: + if (!ctx->ops.setservent + || !ctx->ops.getservent_r + || !ctx->ops.endservent) { + dp_reply_std_set(&reply, DP_ERR_FATAL, ENODEV, + "Services are not supported"); + return reply; + } + ret = enum_services(ctx, sysdb, domain); + break; + default: + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + break; + + case BE_REQ_BY_CERT: + if (data->filter_type != BE_FILTER_CERT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected filter type for lookup by cert: %d\n", + data->filter_type); + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Unexpected filter type for lookup by cert"); + return reply; + } + + if (ctx->sss_certmap_ctx == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Certificate mapping not configured.\n"); + ret = EOK; + break; + } + + ret = proxy_map_cert_to_user(ctx, data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "proxy_map_cert_to_user failed\n"); + } + break; + + default: /*fail*/ + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + + if (ret) { + if (ret == ENXIO) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy returned UNAVAIL error, going offline!\n"); + be_mark_offline(be_ctx); + } + + dp_reply_std_set(&reply, DP_ERR_FATAL, ret, NULL); + return reply; + } + + dp_reply_std_set(&reply, DP_ERR_OK, EOK, NULL); + return reply; +} + +struct proxy_account_info_handler_state { + struct dp_reply_std reply; +}; + +struct tevent_req * +proxy_account_info_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_id_ctx *id_ctx, + struct dp_id_data *data, + struct dp_req_params *params) +{ + struct proxy_account_info_handler_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, + struct proxy_account_info_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->reply = proxy_account_info(state, id_ctx, data, params->be_ctx, + params->be_ctx->domain); + + /* TODO For backward compatibility we always return EOK to DP now. */ + tevent_req_done(req); + tevent_req_post(req, params->ev); + + return req; +} + +errno_t proxy_account_info_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct proxy_account_info_handler_state *state = NULL; + + state = tevent_req_data(req, struct proxy_account_info_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/proxy/proxy_init.c b/src/providers/proxy/proxy_init.c new file mode 100644 index 0000000..b3ffad8 --- /dev/null +++ b/src/providers/proxy/proxy_init.c @@ -0,0 +1,519 @@ +/* + SSSD + + proxy_init.c + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2010 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 "config.h" + +#include "util/sss_format.h" +#include "providers/proxy/proxy.h" + +#define OPT_MAX_CHILDREN_DEFAULT 10 + +static errno_t proxy_id_conf(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + char **_libname, + bool *_fast_alias) +{ + TALLOC_CTX *tmp_ctx; + char *libname; + bool fast_alias; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = confdb_get_string(be_ctx->cdb, tmp_ctx, be_ctx->conf_path, + CONFDB_PROXY_LIBNAME, NULL, &libname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to read confdb [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } else if (libname == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No library name given\n"); + ret = ENOENT; + goto done; + } + + ret = confdb_get_bool(be_ctx->cdb, be_ctx->conf_path, + CONFDB_PROXY_FAST_ALIAS, false, &fast_alias); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to read confdb [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + *_libname = talloc_steal(mem_ctx, libname); + *_fast_alias = fast_alias; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +#define LOCAL_AUTH_POLICY_MATCH "match" +#define LOCAL_AUTH_POLICY_ONLY "only" +#define LOCAL_AUTH_POLICY_ENABLE "enable" + +static bool local_auth_enabled(struct be_ctx *be_ctx) +{ + int ret; + char *local_policy = NULL; + bool res; + + ret = confdb_get_string(be_ctx->cdb, NULL, be_ctx->conf_path, + CONFDB_DOMAIN_LOCAL_AUTH_POLICY, + LOCAL_AUTH_POLICY_MATCH, &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get the confdb local_auth_policy\n"); + return false; + } + + res = (strcasecmp(local_policy, LOCAL_AUTH_POLICY_ONLY) == 0 + || strcasestr(local_policy, LOCAL_AUTH_POLICY_ENABLE":") != NULL); + + talloc_free(local_policy); + + return res; +} + +static errno_t proxy_auth_conf(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + char **_pam_target) +{ + char *pam_target; + errno_t ret; + + ret = confdb_get_string(be_ctx->cdb, mem_ctx, be_ctx->conf_path, + CONFDB_PROXY_PAM_TARGET, NULL, &pam_target); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to read confdb [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (pam_target == NULL) { + if (local_auth_enabled(be_ctx)) { + DEBUG(SSSDBG_TRACE_FUNC, + "Option ["CONFDB_PROXY_PAM_TARGET"] is missing but local " \ + "authentication is enabled.\n"); + return EOK; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing option "CONFDB_PROXY_PAM_TARGET" and local " \ + "authentication isn't enable as well.\n"); + return EINVAL; + } + } + + *_pam_target = pam_target; + + return EOK; +} + +static errno_t proxy_resolver_conf(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + char **_libname) +{ + TALLOC_CTX *tmp_ctx; + char *libname; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = confdb_get_string(be_ctx->cdb, tmp_ctx, be_ctx->conf_path, + CONFDB_PROXY_RESOLVER_LIBNAME, NULL, &libname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to read confdb [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } else if (libname == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No resolver library name given\n"); + ret = ENOENT; + goto done; + } + + *_libname = talloc_steal(mem_ctx, libname); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t proxy_init_auth_ctx(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + struct proxy_auth_ctx **_auth_ctx) +{ + struct proxy_auth_ctx *auth_ctx; + errno_t ret; + int hret; + int max_children; + + auth_ctx = talloc_zero(mem_ctx, struct proxy_auth_ctx); + if (auth_ctx == NULL) { + return ENOMEM; + } + + auth_ctx->be = be_ctx; + auth_ctx->timeout_ms = SSS_CLI_SOCKET_TIMEOUT / 4; + auth_ctx->next_id = 1; + + ret = proxy_auth_conf(auth_ctx, be_ctx, &auth_ctx->pam_target); + if (ret != EOK) { + goto done; + } + + ret = proxy_client_init(dp_sbus_conn(be_ctx->provider), auth_ctx); + if (ret != EOK) { + goto done; + } + + /* Set up request hash table */ + ret = confdb_get_int(be_ctx->cdb, be_ctx->conf_path, + CONFDB_PROXY_MAX_CHILDREN, + OPT_MAX_CHILDREN_DEFAULT, + &max_children); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to read confdb [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + if (max_children < 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Option " CONFDB_PROXY_MAX_CHILDREN " must be higher then 0\n"); + ret = EINVAL; + goto done; + } + auth_ctx->max_children = max_children; + + hret = hash_create(auth_ctx->max_children * 2, &auth_ctx->request_table, + NULL, NULL); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not initialize request table\n"); + ret = EIO; + goto done; + } + + *_auth_ctx = auth_ctx; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(auth_ctx); + } + + return ret; +} + +errno_t sssm_proxy_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + struct data_provider *provider, + const char *module_name, + void **_module_data) +{ + struct proxy_module_ctx *module_ctx; + errno_t ret; + + module_ctx = talloc_zero(mem_ctx, struct proxy_module_ctx); + if (module_ctx == NULL) { + return ENOMEM; + } + + if (dp_target_enabled(provider, module_name, + DPT_ACCESS, DPT_AUTH, DPT_CHPASS)) { + /* Initialize auth_ctx since one of the access, auth or chpass is + * set. */ + ret = proxy_init_auth_ctx(module_ctx, be_ctx, provider, + &module_ctx->auth_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create auth context [%d]: %s\n", + ret, sss_strerror(ret)); + talloc_free(module_ctx); + return ret; + } + } + + *_module_data = module_ctx; + + return EOK; +} + +static errno_t proxy_load_nss_symbols(struct sss_nss_ops *ops, + const char *libname) +{ + errno_t ret; + struct sss_nss_symbols syms[] = { + {(void*)&ops->getpwnam_r, true, "getpwnam_r" }, + {(void*)&ops->getpwuid_r, true, "getpwuid_r" }, + {(void*)&ops->setpwent, true, "setpwent" }, + {(void*)&ops->getpwent_r, true, "getpwent_r" }, + {(void*)&ops->endpwent, true, "endpwent" }, + {(void*)&ops->getgrnam_r, true, "getgrnam_r" }, + {(void*)&ops->getgrgid_r, true, "getgrgid_r" }, + {(void*)&ops->setgrent, true, "setgrent" }, + {(void*)&ops->getgrent_r, true, "getgrent_r" }, + {(void*)&ops->endgrent, true, "endgrent" }, + {(void*)&ops->initgroups_dyn, false, "initgroups_dyn" }, + {(void*)&ops->setnetgrent, false, "setnetgrent" }, + {(void*)&ops->getnetgrent_r, false, "getnetgrent_r" }, + {(void*)&ops->endnetgrent, false, "endnetgrent" }, + {(void*)&ops->getservbyname_r, false, "getservbyname_r" }, + {(void*)&ops->getservbyport_r, false, "getservbyport_r" }, + {(void*)&ops->setservent, false, "setservent" }, + {(void*)&ops->getservent_r, false, "getservent_r" }, + {(void*)&ops->endservent, false, "endservent" }, + }; + size_t nsyms = sizeof(syms) / sizeof(struct sss_nss_symbols); + + ret = sss_load_nss_symbols(ops, libname, syms, nsyms); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static errno_t proxy_load_nss_hosts_symbols(struct sss_nss_ops *ops, + const char *libname) +{ + errno_t ret; + struct sss_nss_symbols syms[] = { + {(void*)&ops->gethostbyname_r, true, "gethostbyname_r"}, + {(void*)&ops->gethostbyname2_r, true, "gethostbyname2_r"}, + {(void*)&ops->gethostbyaddr_r, true, "gethostbyaddr_r"}, + {(void*)&ops->sethostent, false, "sethostent"}, + {(void*)&ops->gethostent_r, false, "gethostent_r"}, + {(void*)&ops->endhostent, false, "endhostent"}, + }; + size_t nsyms = sizeof(syms) / sizeof(struct sss_nss_symbols); + + ret = sss_load_nss_symbols(ops, libname, syms, nsyms); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static errno_t proxy_load_nss_nets_symbols(struct sss_nss_ops *ops, + const char *libname) +{ + errno_t ret; + struct sss_nss_symbols syms[] = { + {(void*)&ops->getnetbyname_r, true, "getnetbyname_r"}, + {(void*)&ops->getnetbyaddr_r, true, "getnetbyaddr_r"}, + {(void*)&ops->setnetent, false, "setnetent"}, + {(void*)&ops->getnetent_r, false, "getnetent_r"}, + {(void*)&ops->endnetent, false, "endnetent"}, + }; + size_t nsyms = sizeof(syms) / sizeof(struct sss_nss_symbols); + + ret = sss_load_nss_symbols(ops, libname, syms, nsyms); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t sssm_proxy_id_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct proxy_module_ctx *module_ctx; + char *libname; + errno_t ret; + + module_ctx = talloc_get_type(module_data, struct proxy_module_ctx); + module_ctx->id_ctx = talloc_zero(module_ctx, struct proxy_id_ctx); + if (module_ctx->id_ctx == NULL) { + return ENOMEM; + } + + module_ctx->id_ctx->be = be_ctx; + + ret = proxy_id_conf(module_ctx->id_ctx, be_ctx, &libname, + &module_ctx->id_ctx->fast_alias); + if (ret != EOK) { + goto done; + } + + ret = proxy_load_nss_symbols(&module_ctx->id_ctx->ops, libname); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to load NSS symbols [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_certmap_to_sysdb(be_ctx->cdb, be_ctx->domain, true); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize certificate mapping rules. " + "Authentication with certificates/Smartcards might not work " + "as expected.\n"); + /* not fatal, ignored */ + } else { + ret = proxy_init_certmap(module_ctx->id_ctx, module_ctx->id_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "files_init_certmap failed. " + "Authentication with certificates/Smartcards might not work " + "as expected.\n"); + /* not fatal, ignored */ + } + } + + dp_set_method(dp_methods, DPM_ACCOUNT_HANDLER, + proxy_account_info_handler_send, proxy_account_info_handler_recv, + module_ctx->id_ctx, struct proxy_id_ctx, struct dp_id_data, + struct dp_reply_std); + + dp_set_method(dp_methods, DPM_ACCT_DOMAIN_HANDLER, + default_account_domain_send, default_account_domain_recv, NULL, + void, struct dp_get_acct_domain_data, struct dp_reply_std); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(module_ctx->id_ctx); + } + + return ret; +} + +errno_t sssm_proxy_auth_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct proxy_module_ctx *module_ctx; + + module_ctx = talloc_get_type(module_data, struct proxy_module_ctx); + + dp_set_method(dp_methods, DPM_AUTH_HANDLER, + proxy_pam_handler_send, proxy_pam_handler_recv, + module_ctx->auth_ctx, struct proxy_auth_ctx, + struct pam_data, struct pam_data *); + + return EOK; +} + +errno_t sssm_proxy_chpass_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + return sssm_proxy_auth_init(mem_ctx, be_ctx, module_data, dp_methods); +} + +errno_t sssm_proxy_access_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct proxy_module_ctx *module_ctx; + + module_ctx = talloc_get_type(module_data, struct proxy_module_ctx); + + dp_set_method(dp_methods, DPM_ACCESS_HANDLER, + proxy_pam_handler_send, proxy_pam_handler_recv, + module_ctx->auth_ctx, struct proxy_auth_ctx, + struct pam_data, struct pam_data *); + + return EOK; +} + +errno_t sssm_proxy_resolver_init(TALLOC_CTX *mem_ctx, + struct be_ctx *be_ctx, + void *module_data, + struct dp_method *dp_methods) +{ + struct proxy_module_ctx *module_ctx; + char *libname; + errno_t ret; + + module_ctx = talloc_get_type(module_data, struct proxy_module_ctx); + + module_ctx->resolver_ctx = talloc_zero(mem_ctx, struct proxy_resolver_ctx); + if (module_ctx->resolver_ctx == NULL) { + return ENOMEM; + } + + ret = proxy_resolver_conf(module_ctx->resolver_ctx, be_ctx, &libname); + if (ret == ENOENT) { + ret = ENOTSUP; + goto done; + } else if (ret != EOK) { + goto done; + } + + ret = proxy_load_nss_hosts_symbols(&module_ctx->resolver_ctx->ops, libname); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to load NSS symbols [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = proxy_load_nss_nets_symbols(&module_ctx->resolver_ctx->ops, libname); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to load NSS symbols [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + dp_set_method(dp_methods, DPM_RESOLVER_HOSTS_HANDLER, + proxy_hosts_handler_send, proxy_hosts_handler_recv, + module_ctx->resolver_ctx, struct proxy_resolver_ctx, + struct dp_resolver_data, struct dp_reply_std); + + dp_set_method(dp_methods, DPM_RESOLVER_IP_NETWORK_HANDLER, + proxy_nets_handler_send, proxy_nets_handler_recv, + module_ctx->resolver_ctx, struct proxy_resolver_ctx, + struct dp_resolver_data, struct dp_reply_std); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(module_ctx->resolver_ctx); + } + + return ret; +} diff --git a/src/providers/proxy/proxy_ipnetworks.c b/src/providers/proxy/proxy_ipnetworks.c new file mode 100644 index 0000000..73919b8 --- /dev/null +++ b/src/providers/proxy/proxy_ipnetworks.c @@ -0,0 +1,628 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2020 SUSE LINUX GmbH, Nuernberg, Germany. + + 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/proxy/proxy.h" +#include "db/sysdb_ipnetworks.h" +#include <resolv.h> +#include <arpa/inet.h> + +static errno_t +nss_status_to_errno(enum nss_status status) +{ + switch (status) { + case NSS_STATUS_SUCCESS: + return EOK; + case NSS_STATUS_TRYAGAIN: + return EAGAIN; + case NSS_STATUS_NOTFOUND: + return ENOENT; + case NSS_STATUS_UNAVAIL: + default: + break; + } + + return EIO; +} + +static errno_t +parse_netent(TALLOC_CTX *mem_ctx, + struct netent *result, + bool case_sensitive, + char **out_name, + char ***out_aliases, + char **out_address) +{ + char **aliases = *out_aliases; + char addrbuf[INET_ADDRSTRLEN]; + const char *addr = NULL; + int i; + errno_t ret; + + if (result->n_addrtype == AF_INET) { + /* result->n_net is represented in host byte order, but the NSS + * client, inet_ntop and inet_pton functions expect the address + * in network byte order + */ + uint32_t tmpaddr = htonl(result->n_net); + addr = inet_ntop(AF_INET, &tmpaddr, addrbuf, INET_ADDRSTRLEN); + } + + if (addr == NULL) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to convert address of network '%s' to a character " + "string: %s\n", result->n_name, strerror(ret)); + return ret; + } + + for (i = 0; result->n_aliases[i] != NULL; i++) { + size_t len = talloc_array_length(aliases); + const char *alias = result->n_aliases[i]; + bool found = false; + int j; + + for (j = 0; j < len && aliases != NULL && aliases[j] != NULL; j++) { + if (case_sensitive && strcmp(aliases[j], alias) == 0) { + found = true; + break; + } else if (strcasecmp(aliases[j], alias) == 0) { + found = true; + break; + } + } + + if (!found) { + ret = add_string_to_list(mem_ctx, alias, &aliases); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Network [%s] has alias [%s]\n", + result->n_name, alias); + } + } + + *out_name = talloc_strdup(mem_ctx, result->n_name); + *out_address = talloc_strdup(mem_ctx, addr); + *out_aliases = aliases; + + ret = EOK; +done: + return ret; +} + +static errno_t +proxy_save_ipnetwork(struct sss_domain_info *domain, + bool lowercase, + uint64_t cache_timeout, + char *name, + char **aliases, + char *address) +{ + errno_t ret; + char *cased_name = NULL; + const char **cased_aliases = NULL; + TALLOC_CTX *tmp_ctx; + char *lc_alias = NULL; + time_t now = time(NULL); + + DEBUG(SSSDBG_TRACE_FUNC, "Saving network [%s] into cache, domain [%s]\n", + name, domain->name); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + cased_name = sss_get_cased_name(tmp_ctx, name, + domain->case_preserve); + if (cased_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get cased name.\n"); + ret = ENOMEM; + goto done; + } + + /* Count the aliases */ + ret = sss_get_cased_name_list(tmp_ctx, + (const char * const *) aliases, + !lowercase, &cased_aliases); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get cased aliases.\n"); + goto done; + } + + if (domain->case_preserve) { + /* Add lowercased alias to allow case-insensitive lookup */ + lc_alias = sss_tc_utf8_str_tolower(tmp_ctx, name); + if (lc_alias == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot convert name to lowercase.\n"); + ret = ENOMEM; + goto done; + } + + ret = add_string_to_list(tmp_ctx, lc_alias, + discard_const_p(char **, &cased_aliases)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add lowercased name alias.\n"); + goto done; + } + } + + ret = sysdb_store_ipnetwork(domain, cased_name, cased_aliases, address, + NULL, NULL, cache_timeout, now); +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +get_net_byname(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + const char *search_name) +{ + TALLOC_CTX *tmp_ctx; + struct netent *result = NULL; + char *buffer = NULL; + size_t buflen = DEFAULT_BUFSIZE; + int err = 0; + int h_err = 0; + enum nss_status status; + errno_t ret; + char *name = NULL; + char *address = NULL; + char **aliases = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, "Resolving network [%s]\n", search_name); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + result = talloc_zero(tmp_ctx, struct netent); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + for (status = NSS_STATUS_TRYAGAIN, + err = ERANGE, h_err = 0; + status == NSS_STATUS_TRYAGAIN && err == ERANGE; + buflen *= 2) + { + buffer = talloc_realloc_size(tmp_ctx, buffer, buflen); + if (buffer == NULL) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getnetbyname_r(search_name, result, + buffer, buflen, + &err, &h_err); + } + + ret = nss_status_to_errno(status); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "getnetbyname_r failed for network [%s]: %d, %s, %s.\n", + search_name, status, strerror(err), hstrerror(h_err)); + goto done; + } + + if (ret == ENOENT) { + /* Not found, make sure we remove it from the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Network [%s] not found, removing from " + "cache\n", search_name); + sysdb_ipnetwork_delete(domain, search_name, NULL); + ret = ENOENT; + goto done; + } else { + /* Found, parse result */ + ret = parse_netent(tmp_ctx, result, domain->case_sensitive, + &name, &aliases, &address); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to parse netent [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Save result into the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Network [%s] found as [%s], saving into " + "cache\n", search_name, name); + ret = proxy_save_ipnetwork(domain, !domain->case_sensitive, + domain->resolver_timeout, + name, aliases, address); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store network [%s] [%d]: %s\n", + name, ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +get_net_byaddr(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + const char *search_addrstr) +{ + TALLOC_CTX *tmp_ctx; + struct netent *result = NULL; + char *buffer = NULL; + size_t buflen = DEFAULT_BUFSIZE; + int err = 0; + int h_err = 0; + uint32_t addrbuf; + enum nss_status status; + errno_t ret; + char *name = NULL; + char *address = NULL; + char **aliases = NULL; + + DEBUG(SSSDBG_TRACE_FUNC, "Resolving network [%s]\n", search_addrstr); + + if (inet_pton(AF_INET, search_addrstr, &addrbuf) != 1) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + result = talloc_zero(tmp_ctx, struct netent); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + /* getnetbyaddr_r expects address in host byte order */ + addrbuf = ntohl(addrbuf); + + for (status = NSS_STATUS_TRYAGAIN, + err = ERANGE, h_err = 0; + status == NSS_STATUS_TRYAGAIN && err == ERANGE; + buflen *= 2) + { + buffer = talloc_realloc_size(tmp_ctx, buffer, buflen); + if (buffer == NULL) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getnetbyaddr_r(addrbuf, AF_INET, result, + buffer, buflen, + &err, &h_err); + } + + ret = nss_status_to_errno(status); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "getnetbyname_r failed for network [%s]: %d, %s, %s.\n", + search_addrstr, status, strerror(err), hstrerror(h_err)); + goto done; + } + + if (ret == ENOENT) { + /* Not found, make sure we remove it from the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Network [%s] not found, removing from " + "cache\n", search_addrstr); + sysdb_ipnetwork_delete(domain, NULL, search_addrstr); + ret = ENOENT; + goto done; + } else { + /* Found, parse result */ + ret = parse_netent(tmp_ctx, result, domain->case_sensitive, + &name, &aliases, &address); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to parse netent [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Save result into the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Network [%s] found as [%s], saving into " + "cache\n", search_addrstr, name); + ret = proxy_save_ipnetwork(domain, !domain->case_sensitive, + domain->resolver_timeout, + name, aliases, address); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store network [%s] [%d]: %s\n", + name, ret, sss_strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + + +static errno_t +getnetent_internal(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain, + TALLOC_CTX *mem_ctx, + char **out_name, + char **out_address, + char ***out_aliases) + +{ + TALLOC_CTX *tmp_ctx = NULL; + char *buffer = NULL; + size_t buflen = DEFAULT_BUFSIZE; + enum nss_status status; + struct netent *result = NULL; + char *name = NULL; + char *address = NULL; + char **aliases = NULL; + int err; + int h_err; + errno_t ret; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + result = talloc_zero(tmp_ctx, struct netent); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + for (status = NSS_STATUS_TRYAGAIN, + err = ERANGE, h_err = 0; + status == NSS_STATUS_TRYAGAIN && err == ERANGE; + buflen *= 2) + { + buffer = talloc_realloc_size(tmp_ctx, buffer, buflen); + if (buffer == NULL) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getnetent_r(result, + buffer, buflen, + &err, &h_err); + } + + ret = nss_status_to_errno(status); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "getnetent_r failed: %d, %s, %s.\n", + status, strerror(err), hstrerror(h_err)); + goto done; + } + + if (ret == EOK) { + ret = parse_netent(tmp_ctx, result, domain->case_sensitive, + &name, &aliases, &address); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to parse netent [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + if (name != NULL) { + *out_name = talloc_steal(mem_ctx, name); + } + if (address != NULL) { + *out_address = talloc_steal(mem_ctx, address); + } + if (aliases != NULL) { + *out_aliases = talloc_steal(mem_ctx, aliases); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +enum_ipnetworks(struct proxy_resolver_ctx *ctx, + struct sss_domain_info *domain) +{ + struct sysdb_ctx *sysdb = domain->sysdb; + TALLOC_CTX *tmp_ctx = NULL; + bool in_transaction = false; + enum nss_status status; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Enumerating IP networks\n"); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_transaction_start(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + status = ctx->ops.setnetent(); + if (status != NSS_STATUS_SUCCESS) { + ret = EIO; + goto done; + } + + do { + char *name = NULL; + char *address = NULL; + char **aliases = NULL; + + ret = getnetent_internal(ctx, domain, tmp_ctx, &name, + &address, &aliases); + if (ret == EOK) { + /* Results found. Save them into the cache */ + DEBUG(SSSDBG_TRACE_INTERNAL, "IP network [%s] found, saving into " + "cache\n", name); + + proxy_save_ipnetwork(domain, !domain->case_sensitive, + domain->resolver_timeout, + name, aliases, address); + } + + /* Free children to avoid using too much memory */ + talloc_free_children(tmp_ctx); + } while (ret == EOK); + + if (ret == ENOENT) { + /* We are done, commit transaction and stop loop */ + DEBUG(SSSDBG_TRACE_FUNC, "IP networks enumeration completed.\n"); + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + in_transaction = false; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "getnetent_r failed [%d]: %s\n", + ret, strerror(ret)); + } + +done: + talloc_free(tmp_ctx); + if (in_transaction) { + errno_t sret; + + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not cancel transaction! [%s]\n", + strerror(sret)); + } + } + ctx->ops.endnetent(); + return ret; +} + +static struct dp_reply_std +proxy_nets_info(TALLOC_CTX *mem_ctx, + struct proxy_resolver_ctx *ctx, + struct dp_resolver_data *data, + struct be_ctx *be_ctx, + struct sss_domain_info *domain) +{ + struct dp_reply_std reply; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Processing networks request, filter type [%d]\n", + data->filter_type); + + switch (data->filter_type) { + case BE_FILTER_NAME: + ret = get_net_byname(ctx, domain, data->filter_value); + break; + + case BE_FILTER_ADDR: + ret = get_net_byaddr(ctx, domain, data->filter_value); + break; + + case BE_FILTER_ENUM: + ret = enum_ipnetworks(ctx, domain); + break; + + default: + dp_reply_std_set(&reply, DP_ERR_FATAL, EINVAL, + "Invalid filter type"); + return reply; + } + + if (ret) { + if (ret == ENXIO) { + DEBUG(SSSDBG_OP_FAILURE, + "proxy returned UNAVAIL error, going offline!\n"); + be_mark_offline(be_ctx); + } + + dp_reply_std_set(&reply, DP_ERR_FATAL, ret, NULL); + return reply; + } + + dp_reply_std_set(&reply, DP_ERR_OK, EOK, NULL); + return reply; +} + +struct proxy_nets_handler_state { + struct dp_reply_std reply; +}; + +struct tevent_req * +proxy_nets_handler_send(TALLOC_CTX *mem_ctx, + struct proxy_resolver_ctx *resolver_ctx, + struct dp_resolver_data *resolver_data, + struct dp_req_params *params) +{ + struct proxy_nets_handler_state *state; + struct tevent_req *req; + + req = tevent_req_create(mem_ctx, &state, struct proxy_nets_handler_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->reply = proxy_nets_info(state, resolver_ctx, resolver_data, + params->be_ctx, params->be_ctx->domain); + + tevent_req_done(req); + return tevent_req_post(req, params->ev); +} + +errno_t +proxy_nets_handler_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct dp_reply_std *data) +{ + struct proxy_nets_handler_state *state; + + state = tevent_req_data(req, struct proxy_nets_handler_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *data = state->reply; + + return EOK; +} diff --git a/src/providers/proxy/proxy_netgroup.c b/src/providers/proxy/proxy_netgroup.c new file mode 100644 index 0000000..566af74 --- /dev/null +++ b/src/providers/proxy/proxy_netgroup.c @@ -0,0 +1,206 @@ +/* + SSSD + + Proxy netgroup handler + + Authors: + + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2010 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/proxy/proxy.h" +#include "util/util.h" + +#define BUFLEN 1024 + +#define get_triple_el(s) ((s) ? (s) : "") + +static errno_t make_netgroup_attr(struct __netgrent netgrent, + struct sysdb_attrs *attrs) +{ + int ret; + char *dummy; + + if (netgrent.type == group_val) { + ret =sysdb_attrs_add_string(attrs, SYSDB_NETGROUP_MEMBER, + netgrent.val.group); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_add_string failed.\n"); + return ret; + } + } else if (netgrent.type == triple_val) { + dummy = talloc_asprintf(attrs, "(%s,%s,%s)", + get_triple_el(netgrent.val.triple.host), + get_triple_el(netgrent.val.triple.user), + get_triple_el(netgrent.val.triple.domain)); + if (dummy == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + return ENOMEM; + } + + ret = sysdb_attrs_add_string(attrs, SYSDB_NETGROUP_TRIPLE, dummy); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_attrs_add_string failed.\n"); + return ret; + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unknown netgrent entry type [%d].\n", netgrent.type); + return EINVAL; + } + + return EOK; +} + +static errno_t save_netgroup(struct sss_domain_info *domain, + const char *name, + struct sysdb_attrs *attrs, + bool lowercase, + uint64_t cache_timeout) +{ + errno_t ret; + + if (lowercase) { + ret = sysdb_attrs_add_lc_name_alias(attrs, name); + if (ret) { + DEBUG(SSSDBG_OP_FAILURE, "Could not add name alias\n"); + return ret; + } + } + + ret = sysdb_add_netgroup(domain, name, NULL, attrs, NULL, + cache_timeout, 0); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_add_netgroup failed.\n"); + return ret; + } + + return EOK; +} + +static errno_t handle_error(enum nss_status status, + struct sss_domain_info *domain, const char *name) +{ + errno_t ret; + + switch (status) { + case NSS_STATUS_SUCCESS: + DEBUG(SSSDBG_TRACE_INTERNAL, "Netgroup lookup succeeded\n"); + ret = EOK; + break; + + case NSS_STATUS_NOTFOUND: + DEBUG(SSSDBG_MINOR_FAILURE, "The netgroup was not found\n"); + ret = sysdb_delete_netgroup(domain, name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot delete netgroup: %d\n", ret); + ret = EIO; + } + break; + + case NSS_STATUS_UNAVAIL: + DEBUG(SSSDBG_TRACE_LIBS, + "The proxy target did not respond, going offline\n"); + ret = ENXIO; + break; + + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected error looking up netgroup\n"); + ret = EIO; + break; + } + + return ret; +} + +errno_t get_netgroup(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *name) +{ + struct __netgrent result; + enum nss_status status; + char buffer[BUFLEN]; + int ret; + TALLOC_CTX *tmp_ctx = NULL; + struct sysdb_attrs *attrs; + + memset(&result, 0, sizeof(result)); + status = ctx->ops.setnetgrent(name, &result); + if (status != NSS_STATUS_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "setnetgrent failed for netgroup [%s].\n", name); + ret = handle_error(status, dom, name); + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + ret = ENOMEM; + goto done; + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + do { + status = ctx->ops.getnetgrent_r(&result, buffer, BUFLEN, &ret); + if (status != NSS_STATUS_SUCCESS && + status != NSS_STATUS_RETURN && + status != NSS_STATUS_NOTFOUND) { + ret = handle_error(status, dom, name); + DEBUG(SSSDBG_OP_FAILURE, + "getnetgrent_r failed for netgroup [%s]: [%d][%s].\n", + name, ret, strerror(ret)); + goto done; + } + + if (status == NSS_STATUS_SUCCESS) { + ret = make_netgroup_attr(result, attrs); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "make_netgroup_attr failed.\n"); + goto done; + } + } + } while (status != NSS_STATUS_RETURN && status != NSS_STATUS_NOTFOUND); + + status = ctx->ops.endnetgrent(&result); + if (status != NSS_STATUS_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "endnetgrent failed.\n"); + ret = handle_error(status, dom, name); + goto done; + } + + ret = save_netgroup(dom, name, attrs, + !dom->case_sensitive, + dom->netgroup_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "save_netgroup failed.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/providers/proxy/proxy_services.c b/src/providers/proxy/proxy_services.c new file mode 100644 index 0000000..856da09 --- /dev/null +++ b/src/providers/proxy/proxy_services.c @@ -0,0 +1,372 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2012 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/proxy/proxy.h" +#include "util/util.h" +#include "util/strtonum.h" +#include "db/sysdb_services.h" + +#define BUFLEN 1024 + +errno_t +proxy_save_service(struct sss_domain_info *domain, + struct servent *svc, + bool lowercase, + uint64_t cache_timeout) +{ + errno_t ret; + char *cased_name; + const char **protocols; + const char **cased_aliases; + TALLOC_CTX *tmp_ctx; + char *lc_alias = NULL; + time_t now = time(NULL); + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + cased_name = sss_get_cased_name(tmp_ctx, svc->s_name, + domain->case_preserve); + if (!cased_name) { + ret = ENOMEM; + goto done; + } + + protocols = talloc_array(tmp_ctx, const char *, 2); + if (!protocols) { + ret = ENOMEM; + goto done; + } + + protocols[0] = sss_get_cased_name(protocols, svc->s_proto, + !lowercase); + if (!protocols[0]) { + ret = ENOMEM; + goto done; + } + protocols[1] = NULL; + + /* Count the aliases */ + ret = sss_get_cased_name_list(tmp_ctx, + (const char * const *) svc->s_aliases, + !lowercase, &cased_aliases); + if (ret != EOK) { + goto done; + } + + if (domain->case_preserve) { + /* Add lowercased alias to allow case-insensitive lookup */ + lc_alias = sss_tc_utf8_str_tolower(tmp_ctx, svc->s_name); + if (lc_alias == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot convert name to lowercase.\n"); + ret = ENOMEM; + goto done; + } + + ret = add_string_to_list(tmp_ctx, lc_alias, + discard_const_p(char **, &cased_aliases)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add lowercased name alias.\n"); + goto done; + } + } + + ret = sysdb_store_service(domain, + cased_name, + ntohs(svc->s_port), + cased_aliases, + protocols, + NULL, NULL, + cache_timeout, + now); +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +get_serv_byname(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *name, + const char *protocol) +{ + errno_t ret; + enum nss_status status; + struct servent *result; + TALLOC_CTX *tmp_ctx; + char buffer[BUFLEN]; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + result = talloc_zero(tmp_ctx, struct servent); + if (!result) { + ret = ENOMEM; + goto done; + } + + status = ctx->ops.getservbyname_r(name, protocol, result, + buffer, BUFLEN, &ret); + if (status != NSS_STATUS_SUCCESS && status != NSS_STATUS_NOTFOUND) { + DEBUG(SSSDBG_MINOR_FAILURE, + "getservbyname_r failed for service [%s].\n", name); + goto done; + } + + if (status == NSS_STATUS_NOTFOUND) { + /* Make sure we remove it from the cache */ + ret = sysdb_svc_delete(dom, name, 0, protocol); + } else { + + /* Results found. Save them into the cache */ + ret = proxy_save_service(dom, result, + !dom->case_sensitive, + dom->service_timeout); + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +get_serv_byport(struct proxy_id_ctx *ctx, + struct sss_domain_info *dom, + const char *be_filter, + const char *protocol) +{ + errno_t ret; + enum nss_status status; + struct servent *result; + TALLOC_CTX *tmp_ctx; + uint16_t port; + char buffer[BUFLEN]; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + result = talloc_zero(tmp_ctx, struct servent); + if (!result) { + ret = ENOMEM; + goto done; + } + + port = htons(strtouint16(be_filter, NULL, 0)); + if (errno) { + ret = errno; + goto done; + } + + status = ctx->ops.getservbyport_r(port, protocol, result, + buffer, BUFLEN, &ret); + if (status != NSS_STATUS_SUCCESS && status != NSS_STATUS_NOTFOUND) { + DEBUG(SSSDBG_MINOR_FAILURE, + "getservbyport_r failed for service [%s].\n", be_filter); + goto done; + } + + if (status == NSS_STATUS_NOTFOUND) { + /* Make sure we remove it from the cache */ + ret = sysdb_svc_delete(dom, NULL, port, protocol); + } else { + /* Results found. Save them into the cache */ + ret = proxy_save_service(dom, result, + !dom->case_sensitive, + dom->service_timeout); + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +enum_services(struct proxy_id_ctx *ctx, + struct sysdb_ctx *sysdb, + struct sss_domain_info *dom) +{ + TALLOC_CTX *tmpctx; + bool in_transaction = false; + struct servent *svc; + enum nss_status status; + size_t buflen; + char *buffer; + char *newbuf; + errno_t ret, sret; + time_t now = time(NULL); + const char **protocols; + const char **cased_aliases; + bool again; + + DEBUG(SSSDBG_TRACE_FUNC, "Enumerating services\n"); + + tmpctx = talloc_new(NULL); + if (!tmpctx) { + return ENOMEM; + } + + svc = talloc(tmpctx, struct servent); + if (!svc) { + ret = ENOMEM; + goto done; + } + + buflen = DEFAULT_BUFSIZE; + buffer = talloc_size(tmpctx, buflen); + if (!buffer) { + ret = ENOMEM; + goto done; + } + + protocols = talloc_zero_array(tmpctx, const char *, 2); + if (protocols == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_transaction_start(sysdb); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n"); + goto done; + } + in_transaction = true; + + status = ctx->ops.setservent(); + if (status != NSS_STATUS_SUCCESS) { + ret = EIO; + goto done; + } + + do { + again = false; + + /* always zero out the svc structure */ + memset(svc, 0, sizeof(struct servent)); + + /* get entry */ + status = ctx->ops.getservent_r(svc, buffer, buflen, &ret); + + switch (status) { + case NSS_STATUS_TRYAGAIN: + /* buffer too small? */ + if (buflen < MAX_BUF_SIZE) { + buflen *= 2; + } + if (buflen > MAX_BUF_SIZE) { + buflen = MAX_BUF_SIZE; + } + newbuf = talloc_realloc_size(tmpctx, buffer, buflen); + if (!newbuf) { + ret = ENOMEM; + goto done; + } + buffer = newbuf; + again = true; + break; + + case NSS_STATUS_NOTFOUND: + + /* we are done here */ + DEBUG(SSSDBG_TRACE_FUNC, "Enumeration completed.\n"); + + ret = sysdb_transaction_commit(sysdb); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n"); + goto done; + } + + in_transaction = false; + break; + + case NSS_STATUS_SUCCESS: + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Service found (%s, %d/%s)\n", + svc->s_name, svc->s_port, svc->s_proto); + + protocols[0] = sss_get_cased_name(protocols, svc->s_proto, + dom->case_sensitive); + if (!protocols[0]) { + ret = ENOMEM; + goto done; + } + protocols[1] = NULL; + + ret = sss_get_cased_name_list(tmpctx, + (const char * const *) svc->s_aliases, + dom->case_sensitive, &cased_aliases); + if (ret != EOK) { + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store service [%s]. Ignoring.\n", + strerror(ret)); + again = true; + break; + } + + ret = sysdb_store_service(dom, + svc->s_name, + svc->s_port, + cased_aliases, + protocols, + NULL, NULL, + dom->service_timeout, + now); + if (ret) { + /* Do not fail completely on errors. + * Just report the failure to save and go on */ + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store service [%s]. Ignoring.\n", + strerror(ret)); + } + again = true; + break; + + case NSS_STATUS_UNAVAIL: + /* "remote" backend unavailable. Enter offline mode */ + ret = ENXIO; + break; + + default: + ret = EIO; + DEBUG(SSSDBG_CRIT_FAILURE, + "proxy -> getservent_r failed (%d)[%s]\n", + ret, strerror(ret)); + break; + } + } while (again); + +done: + talloc_zfree(tmpctx); + if (in_transaction) { + sret = sysdb_transaction_cancel(sysdb); + if (sret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not cancel transaction! [%s]\n", + strerror(sret)); + } + } + ctx->ops.endservent(); + return ret; +} |