summaryrefslogtreecommitdiffstats
path: root/src/providers/proxy
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
commit74aa0bc6779af38018a03fd2cf4419fe85917904 (patch)
tree9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/providers/proxy
parentInitial commit. (diff)
downloadsssd-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.h187
-rw-r--r--src/providers/proxy/proxy_auth.c877
-rw-r--r--src/providers/proxy/proxy_certmap.c191
-rw-r--r--src/providers/proxy/proxy_child.c606
-rw-r--r--src/providers/proxy/proxy_client.c134
-rw-r--r--src/providers/proxy/proxy_hosts.c768
-rw-r--r--src/providers/proxy/proxy_id.c1962
-rw-r--r--src/providers/proxy/proxy_init.c519
-rw-r--r--src/providers/proxy/proxy_ipnetworks.c628
-rw-r--r--src/providers/proxy/proxy_netgroup.c206
-rw-r--r--src/providers/proxy/proxy_services.c372
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;
+}