summaryrefslogtreecommitdiffstats
path: root/src/providers
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers')
-rw-r--r--src/providers/child_common.c416
-rw-r--r--src/providers/child_common.h73
-rw-r--r--src/providers/data_provider.h219
-rw-r--r--src/providers/data_provider_be.c1235
-rw-r--r--src/providers/data_provider_fo.c356
-rw-r--r--src/providers/data_provider_opts.c384
-rw-r--r--src/providers/dp_auth_util.c414
-rw-r--r--src/providers/dp_backend.h142
-rw-r--r--src/providers/dp_sbus.c45
-rw-r--r--src/providers/fail_over.c651
-rw-r--r--src/providers/fail_over.h108
-rw-r--r--src/providers/ipa/ipa_access.c1823
-rw-r--r--src/providers/ipa/ipa_access.h66
-rw-r--r--src/providers/ipa/ipa_auth.c313
-rw-r--r--src/providers/ipa/ipa_auth.h32
-rw-r--r--src/providers/ipa/ipa_common.c597
-rw-r--r--src/providers/ipa/ipa_common.h83
-rw-r--r--src/providers/ipa/ipa_init.c293
-rw-r--r--src/providers/ipa/ipa_timerules.c1186
-rw-r--r--src/providers/ipa/ipa_timerules.h56
-rw-r--r--src/providers/krb5/krb5_auth.c1193
-rw-r--r--src/providers/krb5/krb5_auth.h91
-rw-r--r--src/providers/krb5/krb5_become_user.c61
-rw-r--r--src/providers/krb5/krb5_child.c1030
-rw-r--r--src/providers/krb5/krb5_common.c356
-rw-r--r--src/providers/krb5/krb5_common.h72
-rw-r--r--src/providers/krb5/krb5_init.c152
-rw-r--r--src/providers/krb5/krb5_utils.c145
-rw-r--r--src/providers/krb5/krb5_utils.h39
-rw-r--r--src/providers/ldap/ldap_auth.c1055
-rw-r--r--src/providers/ldap/ldap_child.c429
-rw-r--r--src/providers/ldap/ldap_common.c589
-rw-r--r--src/providers/ldap/ldap_common.h115
-rw-r--r--src/providers/ldap/ldap_id.c795
-rw-r--r--src/providers/ldap/ldap_id_cleanup.c555
-rw-r--r--src/providers/ldap/ldap_id_enum.c608
-rw-r--r--src/providers/ldap/ldap_init.c179
-rw-r--r--src/providers/ldap/sdap.c388
-rw-r--r--src/providers/ldap/sdap.h258
-rw-r--r--src/providers/ldap/sdap_async.c1018
-rw-r--r--src/providers/ldap/sdap_async.h126
-rw-r--r--src/providers/ldap/sdap_async_accounts.c2065
-rw-r--r--src/providers/ldap/sdap_async_connection.c1141
-rw-r--r--src/providers/ldap/sdap_async_private.h68
-rw-r--r--src/providers/ldap/sdap_child_helpers.c462
-rw-r--r--src/providers/providers.h24
-rw-r--r--src/providers/proxy.c2521
-rw-r--r--src/providers/sssd_be.exports4
48 files changed, 24031 insertions, 0 deletions
diff --git a/src/providers/child_common.c b/src/providers/child_common.c
new file mode 100644
index 000000000..2ad0f04e3
--- /dev/null
+++ b/src/providers/child_common.c
@@ -0,0 +1,416 @@
+/*
+ SSSD
+
+ Common helper functions to be used in child processes
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 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 <sys/types.h>
+#include <fcntl.h>
+#include <tevent.h>
+#include <sys/wait.h>
+#include <errno.h>
+
+#include "util/util.h"
+#include "util/find_uid.h"
+#include "db/sysdb.h"
+#include "providers/child_common.h"
+
+/* Async communication with the child process via a pipe */
+
+struct write_pipe_state {
+ int fd;
+ uint8_t *buf;
+ size_t len;
+ size_t written;
+};
+
+static void write_pipe_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *pvt);
+
+struct tevent_req *write_pipe_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint8_t *buf, size_t len, int fd)
+{
+ struct tevent_req *req;
+ struct write_pipe_state *state;
+ struct tevent_fd *fde;
+
+ req = tevent_req_create(mem_ctx, &state, struct write_pipe_state);
+ if (req == NULL) return NULL;
+
+ state->fd = fd;
+ state->buf = buf;
+ state->len = len;
+ state->written = 0;
+
+ fde = tevent_add_fd(ev, state, fd, TEVENT_FD_WRITE,
+ write_pipe_handler, req);
+ if (fde == NULL) {
+ DEBUG(1, ("tevent_add_fd failed.\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ talloc_zfree(req);
+ return NULL;
+}
+
+static void write_pipe_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct write_pipe_state *state = tevent_req_data(req,
+ struct write_pipe_state);
+ ssize_t size;
+
+ if (flags & TEVENT_FD_READ) {
+ DEBUG(1, ("write_pipe_done called with TEVENT_FD_READ,"
+ " this should not happen.\n"));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ size = write(state->fd,
+ state->buf + state->written,
+ state->len - state->written);
+ if (size == -1) {
+ if (errno == EAGAIN || errno == EINTR) return;
+ DEBUG(1, ("write failed [%d][%s].\n", errno, strerror(errno)));
+ tevent_req_error(req, errno);
+ return;
+
+ } else if (size >= 0) {
+ state->written += size;
+ if (state->written > state->len) {
+ DEBUG(1, ("write to much, this should never happen.\n"));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+ } else {
+ DEBUG(1, ("unexpected return value of write [%d].\n", size));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ if (state->len == state->written) {
+ DEBUG(6, ("All data has been sent!\n"));
+ tevent_req_done(req);
+ return;
+ }
+}
+
+int write_pipe_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+struct read_pipe_state {
+ int fd;
+ uint8_t *buf;
+ size_t len;
+};
+
+static void read_pipe_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *pvt);
+
+struct tevent_req *read_pipe_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, int fd)
+{
+ struct tevent_req *req;
+ struct read_pipe_state *state;
+ struct tevent_fd *fde;
+
+ req = tevent_req_create(mem_ctx, &state, struct read_pipe_state);
+ if (req == NULL) return NULL;
+
+ state->fd = fd;
+ state->buf = talloc_array(state, uint8_t, MAX_CHILD_MSG_SIZE);
+ state->len = 0;
+ if (state->buf == NULL) goto fail;
+
+ fde = tevent_add_fd(ev, state, fd, TEVENT_FD_READ,
+ read_pipe_handler, req);
+ if (fde == NULL) {
+ DEBUG(1, ("tevent_add_fd failed.\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ talloc_zfree(req);
+ return NULL;
+}
+
+static void read_pipe_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct read_pipe_state *state = tevent_req_data(req,
+ struct read_pipe_state);
+ ssize_t size;
+ errno_t err;
+
+ if (flags & TEVENT_FD_WRITE) {
+ DEBUG(1, ("read_pipe_done called with TEVENT_FD_WRITE,"
+ " this should not happen.\n"));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ size = read(state->fd,
+ state->buf + state->len,
+ MAX_CHILD_MSG_SIZE - state->len);
+ if (size == -1) {
+ err = errno;
+ if (err == EAGAIN || err == EINTR) {
+ return;
+ }
+
+ DEBUG(1, ("read failed [%d][%s].\n", err, strerror(err)));
+ tevent_req_error(req, err);
+ return;
+
+ } else if (size > 0) {
+ state->len += size;
+ if (state->len > MAX_CHILD_MSG_SIZE) {
+ DEBUG(1, ("read to much, this should never happen.\n"));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ } else if (size == 0) {
+ DEBUG(6, ("EOF received, client finished\n"));
+ tevent_req_done(req);
+ return;
+
+ } else {
+ DEBUG(1, ("unexpected return value of read [%d].\n", size));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+}
+
+int read_pipe_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **buf, ssize_t *len)
+{
+ struct read_pipe_state *state;
+ state = tevent_req_data(req, struct read_pipe_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *buf = talloc_steal(mem_ctx, state->buf);
+ *len = state->len;
+
+ return EOK;
+}
+
+/* The pipes to communicate with the child must be nonblocking */
+void fd_nonblocking(int fd)
+{
+ int flags;
+ int ret;
+
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags == -1) {
+ ret = errno;
+ DEBUG(1, ("F_GETFL failed [%d][%s].\n", ret, strerror(ret)));
+ return;
+ }
+
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ ret = errno;
+ DEBUG(1, ("F_SETFL failed [%d][%s].\n", ret, strerror(ret)));
+ }
+
+ return;
+}
+
+void child_sig_handler(struct tevent_context *ev,
+ struct tevent_signal *sige, int signum,
+ int count, void *__siginfo, void *pvt)
+{
+ int ret;
+ int child_status;
+
+ DEBUG(7, ("Waiting for [%d] childeren.\n", count));
+ do {
+ errno = 0;
+ ret = waitpid(-1, &child_status, WNOHANG);
+
+ if (ret == -1) {
+ DEBUG(1, ("waitpid failed [%d][%s].\n", errno, strerror(errno)));
+ } else if (ret == 0) {
+ DEBUG(1, ("waitpid did not found a child with changed status.\n"));
+ } else {
+ if (WEXITSTATUS(child_status) != 0) {
+ DEBUG(1, ("child [%d] failed with status [%d].\n", ret,
+ child_status));
+ } else {
+ DEBUG(4, ("child [%d] finished successful.\n", ret));
+ }
+ }
+
+ --count;
+ } while (count < 0);
+
+ return;
+}
+
+static errno_t prepare_child_argv(TALLOC_CTX *mem_ctx,
+ int child_debug_fd,
+ const char *binary,
+ char ***_argv)
+{
+ uint_t argc = 3; /* program name, debug_level and NULL */
+ char ** argv;
+ errno_t ret = EINVAL;
+
+ /* Save the current state in case an interrupt changes it */
+ bool child_debug_to_file = debug_to_file;
+ bool child_debug_timestamps = debug_timestamps;
+
+ if (child_debug_to_file) argc++;
+ if (!child_debug_timestamps) argc++;
+
+ /* program name, debug_level,
+ * debug_to_file, debug_timestamps
+ * and NULL */
+ argv = talloc_array(mem_ctx, char *, argc);
+ if (argv == NULL) {
+ DEBUG(1, ("talloc_array failed.\n"));
+ return ENOMEM;
+ }
+
+ argv[--argc] = NULL;
+
+ argv[--argc] = talloc_asprintf(argv, "--debug-level=%d",
+ debug_level);
+ if (argv[argc] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ if (child_debug_to_file) {
+ argv[--argc] = talloc_asprintf(argv, "--debug-fd=%d",
+ child_debug_fd);
+ if (argv[argc] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (!child_debug_timestamps) {
+ argv[--argc] = talloc_strdup(argv, "--debug-timestamps=0");
+ if (argv[argc] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ argv[--argc] = talloc_strdup(argv, binary);
+ if (argv[argc] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ if (argc != 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+
+ *_argv = argv;
+
+ return EOK;
+
+fail:
+ talloc_free(argv);
+ return ret;
+}
+
+errno_t exec_child(TALLOC_CTX *mem_ctx,
+ int *pipefd_to_child, int *pipefd_from_child,
+ const char *binary, int debug_fd)
+{
+ int ret;
+ errno_t err;
+ char **argv;
+
+ close(pipefd_to_child[1]);
+ ret = dup2(pipefd_to_child[0], STDIN_FILENO);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("dup2 failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+
+ close(pipefd_from_child[0]);
+ ret = dup2(pipefd_from_child[1], STDOUT_FILENO);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("dup2 failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+
+ ret = prepare_child_argv(mem_ctx, debug_fd,
+ binary, &argv);
+ if (ret != EOK) {
+ DEBUG(1, ("prepare_child_argv.\n"));
+ return ret;
+ }
+
+ ret = execv(binary, argv);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("execv failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+
+ return EOK;
+}
+
+void child_cleanup(int readfd, int writefd)
+{
+ int ret;
+
+ if (readfd != -1) {
+ ret = close(readfd);
+ if (ret != EOK) {
+ ret = errno;
+ DEBUG(1, ("close failed [%d][%s].\n", errno, strerror(errno)));
+ }
+ }
+ if (writefd != -1) {
+ ret = close(writefd);
+ if (ret != EOK) {
+ ret = errno;
+ DEBUG(1, ("close failed [%d][%s].\n", errno, strerror(errno)));
+ }
+ }
+}
diff --git a/src/providers/child_common.h b/src/providers/child_common.h
new file mode 100644
index 000000000..a441df3c5
--- /dev/null
+++ b/src/providers/child_common.h
@@ -0,0 +1,73 @@
+/*
+ SSSD
+
+ Common helper functions to be used in child processes
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 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 __CHILD_COMMON_H__
+#define __CHILD_COMMON_H__
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <tevent.h>
+
+#include "util/util.h"
+
+#define IN_BUF_SIZE 512
+#define MAX_CHILD_MSG_SIZE 255
+
+struct response {
+ size_t max_size;
+ size_t size;
+ uint8_t *buf;
+};
+
+struct io_buffer {
+ uint8_t *data;
+ size_t size;
+};
+
+/* Async communication with the child process via a pipe */
+struct tevent_req *write_pipe_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint8_t *buf, size_t len, int fd);
+int write_pipe_recv(struct tevent_req *req);
+
+struct tevent_req *read_pipe_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, int fd);
+int read_pipe_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **buf, ssize_t *len);
+
+/* The pipes to communicate with the child must be nonblocking */
+void fd_nonblocking(int fd);
+
+void child_sig_handler(struct tevent_context *ev,
+ struct tevent_signal *sige, int signum,
+ int count, void *__siginfo, void *pvt);
+
+errno_t exec_child(TALLOC_CTX *mem_ctx,
+ int *pipefd_to_child, int *pipefd_from_child,
+ const char *binary, int debug_fd);
+
+void child_cleanup(int readfd, int writefd);
+
+#endif /* __CHILD_COMMON_H__ */
diff --git a/src/providers/data_provider.h b/src/providers/data_provider.h
new file mode 100644
index 000000000..76ba4cffb
--- /dev/null
+++ b/src/providers/data_provider.h
@@ -0,0 +1,219 @@
+/*
+ SSSD
+
+ Data Provider, private header file
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ 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 __DATA_PROVIDER_H__
+#define __DATA_PROVIDER_H__
+
+#include <stdint.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <stdbool.h>
+#include "talloc.h"
+#include "tevent.h"
+#include "ldb.h"
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "dbus/dbus.h"
+#include "sbus/sssd_dbus.h"
+#include "sbus/sbus_client.h"
+#include "sss_client/sss_cli.h"
+
+#define DATA_PROVIDER_VERSION 0x0001
+#define DATA_PROVIDER_SERVICE_NAME "dp"
+#define DATA_PROVIDER_PIPE "private/sbus-dp"
+
+#define DATA_PROVIDER_DB_FILE "sssd.ldb"
+#define DATA_PROVIDER_DB_CONF_SEC "config/services/nss"
+
+#define MOD_OFFLINE 0x0000
+#define MOD_ONLINE 0x0001
+
+#define DP_INTERFACE "org.freedesktop.sssd.dataprovider"
+#define DP_PATH "/org/freedesktop/sssd/dataprovider"
+
+#define BE_PROVIDE_ACC_INFO (1<<8)
+#define BE_PROVIDE_PAM (1<<9)
+#define BE_PROVIDE_POLICY (1<<10)
+
+#define DP_METHOD_REGISTER "RegisterService"
+#define DP_METHOD_ONLINE "getOnline"
+#define DP_METHOD_GETACCTINFO "getAccountInfo"
+#define DP_METHOD_PAMHANDLER "pamHandler"
+
+#define DP_ERR_OK 0
+#define DP_ERR_OFFLINE 1
+#define DP_ERR_TIMEOUT 2
+#define DP_ERR_FATAL 3
+
+#define BE_ATTR_CORE 1
+#define BE_ATTR_MEM 2
+#define BE_ATTR_ALL 3
+
+#define BE_FILTER_NAME 1
+#define BE_FILTER_IDNUM 2
+
+#define BE_REQ_USER 0x0001
+#define BE_REQ_GROUP 0x0002
+#define BE_REQ_INITGROUPS 0x0003
+#define BE_REQ_FAST 0x1000
+
+/* AUTH related common data and functions */
+
+#define DEBUG_PAM_DATA(level, pd) do { \
+ if (level <= debug_level) pam_print_data(level, pd); \
+} while(0);
+
+
+struct response_data {
+ int32_t type;
+ int32_t len;
+ uint8_t *data;
+ struct response_data *next;
+};
+
+struct pam_data {
+ int cmd;
+ uint32_t authtok_type;
+ uint32_t authtok_size;
+ uint32_t newauthtok_type;
+ uint32_t newauthtok_size;
+ char *domain;
+ char *user;
+ char *service;
+ char *tty;
+ char *ruser;
+ char *rhost;
+ uint8_t *authtok;
+ uint8_t *newauthtok;
+ uint32_t cli_pid;
+
+ int pam_status;
+ int response_delay;
+ struct response_data *resp_list;
+
+ bool offline_auth;
+ bool last_auth_saved;
+ int priv;
+ uid_t pw_uid;
+ gid_t gr_gid;
+
+ const char *upn;
+};
+
+/* from dp_auth_util.c */
+void pam_print_data(int l, struct pam_data *pd);
+int pam_add_response(struct pam_data *pd,
+ enum response_type type,
+ int len, const uint8_t *data);
+
+bool dp_pack_pam_request(DBusMessage *msg, struct pam_data *pd);
+bool dp_unpack_pam_request(DBusMessage *msg, struct pam_data *pd,
+ DBusError *dbus_error);
+
+bool dp_pack_pam_response(DBusMessage *msg, struct pam_data *pd);
+bool dp_unpack_pam_response(DBusMessage *msg, struct pam_data *pd,
+ DBusError *dbus_error);
+
+int dp_common_send_id(struct sbus_connection *conn, uint16_t version,
+ const char *name, const char *domain);
+
+/* from dp_sbus.c */
+int dp_get_sbus_address(TALLOC_CTX *mem_ctx,
+ char **address, const char *domain_name);
+
+
+/* Helpers */
+
+#define NULL_STRING { .string = NULL }
+#define NULL_BLOB { .blob = { NULL, 0 } }
+#define NULL_NUMBER { .number = 0 }
+#define BOOL_FALSE { .boolean = false }
+#define BOOL_TRUE { .boolean = true }
+
+enum dp_opt_type {
+ DP_OPT_STRING,
+ DP_OPT_BLOB,
+ DP_OPT_NUMBER,
+ DP_OPT_BOOL
+};
+
+struct dp_opt_blob {
+ uint8_t *data;
+ size_t length;
+};
+
+union dp_opt_value {
+ const char *cstring;
+ char *string;
+ struct dp_opt_blob blob;
+ int number;
+ bool boolean;
+};
+
+struct dp_option {
+ const char *opt_name;
+ enum dp_opt_type type;
+ union dp_opt_value def_val;
+ union dp_opt_value val;
+};
+
+int dp_get_options(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct dp_option *def_opts,
+ int num_opts,
+ struct dp_option **_opts);
+
+int dp_copy_options(TALLOC_CTX *memctx,
+ struct dp_option *src_opts,
+ int num_opts,
+ struct dp_option **_opts);
+
+const char *_dp_opt_get_cstring(struct dp_option *opts,
+ int id, const char *location);
+char *_dp_opt_get_string(struct dp_option *opts,
+ int id, const char *location);
+struct dp_opt_blob _dp_opt_get_blob(struct dp_option *opts,
+ int id, const char *location);
+int _dp_opt_get_int(struct dp_option *opts,
+ int id, const char *location);
+bool _dp_opt_get_bool(struct dp_option *opts,
+ int id, const char *location);
+#define dp_opt_get_cstring(o, i) _dp_opt_get_cstring(o, i, __FUNCTION__)
+#define dp_opt_get_string(o, i) _dp_opt_get_string(o, i, __FUNCTION__)
+#define dp_opt_get_blob(o, i) _dp_opt_get_blob(o, i, __FUNCTION__)
+#define dp_opt_get_int(o, i) _dp_opt_get_int(o, i, __FUNCTION__)
+#define dp_opt_get_bool(o, i) _dp_opt_get_bool(o, i, __FUNCTION__)
+
+int _dp_opt_set_string(struct dp_option *opts, int id,
+ const char *s, const char *location);
+int _dp_opt_set_blob(struct dp_option *opts, int id,
+ struct dp_opt_blob b, const char *location);
+int _dp_opt_set_int(struct dp_option *opts, int id,
+ int i, const char *location);
+int _dp_opt_set_bool(struct dp_option *opts, int id,
+ bool b, const char *location);
+#define dp_opt_set_string(o, i, v) _dp_opt_set_string(o, i, v, __FUNCTION__)
+#define dp_opt_set_blob(o, i, v) _dp_opt_set_blob(o, i, v, __FUNCTION__)
+#define dp_opt_set_int(o, i, v) _dp_opt_set_int(o, i, v, __FUNCTION__)
+#define dp_opt_set_bool(o, i, v) _dp_opt_set_bool(o, i, v, __FUNCTION__)
+
+#endif /* __DATA_PROVIDER_ */
diff --git a/src/providers/data_provider_be.c b/src/providers/data_provider_be.c
new file mode 100644
index 000000000..93cae0702
--- /dev/null
+++ b/src/providers/data_provider_be.c
@@ -0,0 +1,1235 @@
+/*
+ SSSD
+
+ Data Provider Process
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ 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 <security/pam_appl.h>
+#include <security/pam_modules.h>
+
+#include "popt.h"
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "db/sysdb.h"
+#include "dbus/dbus.h"
+#include "sbus/sssd_dbus.h"
+#include "providers/dp_backend.h"
+#include "providers/fail_over.h"
+#include "resolv/async_resolv.h"
+#include "monitor/monitor_interfaces.h"
+
+#define MSG_TARGET_NO_CONFIGURED "sssd_be: The requested target is not configured"
+
+#define ACCESS_PERMIT "permit"
+#define ACCESS_DENY "deny"
+#define NO_PROVIDER "none"
+
+static int data_provider_res_init(DBusMessage *message,
+ struct sbus_connection *conn);
+static int data_provider_go_offline(DBusMessage *message,
+ struct sbus_connection *conn);
+
+struct sbus_method monitor_be_methods[] = {
+ { MON_CLI_METHOD_PING, monitor_common_pong },
+ { MON_CLI_METHOD_RES_INIT, data_provider_res_init },
+ { MON_CLI_METHOD_OFFLINE, data_provider_go_offline },
+ { NULL, NULL }
+};
+
+struct sbus_interface monitor_be_interface = {
+ MONITOR_INTERFACE,
+ MONITOR_PATH,
+ SBUS_DEFAULT_VTABLE,
+ monitor_be_methods,
+ NULL
+};
+
+static int client_registration(DBusMessage *message, struct sbus_connection *conn);
+static int be_check_online(DBusMessage *message, struct sbus_connection *conn);
+static int be_get_account_info(DBusMessage *message, struct sbus_connection *conn);
+static int be_pam_handler(DBusMessage *message, struct sbus_connection *conn);
+
+struct sbus_method be_methods[] = {
+ { DP_METHOD_REGISTER, client_registration },
+ { DP_METHOD_ONLINE, be_check_online },
+ { DP_METHOD_GETACCTINFO, be_get_account_info },
+ { DP_METHOD_PAMHANDLER, be_pam_handler },
+ { NULL, NULL }
+};
+
+struct sbus_interface be_interface = {
+ DP_INTERFACE,
+ DP_PATH,
+ SBUS_DEFAULT_VTABLE,
+ be_methods,
+ NULL
+};
+
+static struct bet_data bet_data[] = {
+ {BET_NULL, NULL, NULL},
+ {BET_ID, CONFDB_DOMAIN_ID_PROVIDER, "sssm_%s_init"},
+ {BET_AUTH, CONFDB_DOMAIN_AUTH_PROVIDER, "sssm_%s_auth_init"},
+ {BET_ACCESS, CONFDB_DOMAIN_ACCESS_PROVIDER, "sssm_%s_access_init"},
+ {BET_CHPASS, CONFDB_DOMAIN_CHPASS_PROVIDER, "sssm_%s_chpass_init"},
+ {BET_MAX, NULL, NULL}
+};
+
+struct be_async_req {
+ be_req_fn_t fn;
+ struct be_req *req;
+};
+
+static void be_async_req_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct be_async_req *async_req;
+
+ async_req = talloc_get_type(pvt, struct be_async_req);
+
+ async_req->fn(async_req->req);
+}
+
+static int be_file_request(struct be_ctx *ctx,
+ be_req_fn_t fn,
+ struct be_req *req)
+{
+ struct be_async_req *areq;
+ struct tevent_timer *te;
+ struct timeval tv;
+
+ if (!fn || !req) return EINVAL;
+
+ areq = talloc(req, struct be_async_req);
+ if (!areq) {
+ return ENOMEM;
+ }
+ areq->fn = fn;
+ areq->req = req;
+
+ /* fire immediately */
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ te = tevent_add_timer(ctx->ev, req, tv, be_async_req_handler, areq);
+ if (te == NULL) {
+ return EIO;
+ }
+
+ return EOK;
+}
+
+bool be_is_offline(struct be_ctx *ctx)
+{
+ time_t now = time(NULL);
+
+ /* check if we are past the offline blackout timeout */
+ /* FIXME: get offline_timeout from configuration */
+ if (ctx->offstat.went_offline + 60 < now) {
+ ctx->offstat.offline = false;
+ }
+
+ return ctx->offstat.offline;
+}
+
+void be_mark_offline(struct be_ctx *ctx)
+{
+ DEBUG(8, ("Going offline!\n"));
+
+ ctx->offstat.went_offline = time(NULL);
+ ctx->offstat.offline = true;
+}
+
+static int be_check_online(DBusMessage *message, struct sbus_connection *conn)
+{
+ struct be_client *becli;
+ DBusMessage *reply;
+ DBusConnection *dbus_conn;
+ dbus_bool_t dbret;
+ void *user_data;
+ dbus_uint16_t online;
+ dbus_uint16_t err_maj = 0;
+ dbus_uint32_t err_min = 0;
+ static const char *err_msg = "Success";
+
+ user_data = sbus_conn_get_private_data(conn);
+ if (!user_data) return EINVAL;
+ becli = talloc_get_type(user_data, struct be_client);
+ if (!becli) return EINVAL;
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply) return ENOMEM;
+
+ if (be_is_offline(becli->bectx)) {
+ online = MOD_OFFLINE;
+ } else {
+ online = MOD_ONLINE;
+ }
+
+ dbret = dbus_message_append_args(reply,
+ DBUS_TYPE_UINT16, &online,
+ DBUS_TYPE_UINT16, &err_maj,
+ DBUS_TYPE_UINT32, &err_min,
+ DBUS_TYPE_STRING, &err_msg,
+ DBUS_TYPE_INVALID);
+ if (!dbret) {
+ DEBUG(1, ("Failed to generate dbus reply\n"));
+ return EIO;
+ }
+
+ dbus_conn = sbus_get_connection(becli->conn);
+ dbus_connection_send(dbus_conn, reply, NULL);
+ dbus_message_unref(reply);
+
+ DEBUG(4, ("Request processed. Returned %d,%d,%s\n",
+ err_maj, err_min, err_msg));
+
+ return EOK;
+}
+
+static char *dp_err_to_string(TALLOC_CTX *memctx, int dp_err_type, int errnum)
+{
+ switch (dp_err_type) {
+ case DP_ERR_OK:
+ return talloc_strdup(memctx, "Success");
+ break;
+
+ case DP_ERR_OFFLINE:
+ return talloc_asprintf(memctx,
+ "Provider is Offline (%s)",
+ strerror(errnum));
+ break;
+
+ case DP_ERR_TIMEOUT:
+ return talloc_asprintf(memctx,
+ "Request timed out (%s)",
+ strerror(errnum));
+ break;
+
+ case DP_ERR_FATAL:
+ default:
+ return talloc_asprintf(memctx,
+ "Internal Error (%s)",
+ strerror(errnum));
+ break;
+ }
+
+ return NULL;
+}
+
+
+static void acctinfo_callback(struct be_req *req,
+ int dp_err_type,
+ int errnum,
+ const char *errstr)
+{
+ DBusMessage *reply;
+ DBusConnection *dbus_conn;
+ dbus_bool_t dbret;
+ dbus_uint16_t err_maj = 0;
+ dbus_uint32_t err_min = 0;
+ const char *err_msg = NULL;
+
+ reply = (DBusMessage *)req->pvt;
+
+ if (reply) {
+ /* Return a reply if one was requested
+ * There may not be one if this request began
+ * while we were offline
+ */
+
+ err_maj = dp_err_type;
+ err_min = errnum;
+ if (errstr) {
+ err_msg = errstr;
+ } else {
+ err_msg = dp_err_to_string(req, dp_err_type, errnum);
+ }
+ if (!err_msg) {
+ DEBUG(1, ("Failed to set err_msg, Out of memory?\n"));
+ err_msg = "OOM";
+ }
+
+ dbret = dbus_message_append_args(reply,
+ DBUS_TYPE_UINT16, &err_maj,
+ DBUS_TYPE_UINT32, &err_min,
+ DBUS_TYPE_STRING, &err_msg,
+ DBUS_TYPE_INVALID);
+ if (!dbret) {
+ DEBUG(1, ("Failed to generate dbus reply\n"));
+ return;
+ }
+
+ dbus_conn = sbus_get_connection(req->becli->conn);
+ dbus_connection_send(dbus_conn, reply, NULL);
+ dbus_message_unref(reply);
+
+ DEBUG(4, ("Request processed. Returned %d,%d,%s\n",
+ err_maj, err_min, err_msg));
+ }
+
+ /* finally free the request */
+ talloc_free(req);
+}
+
+static int be_get_account_info(DBusMessage *message, struct sbus_connection *conn)
+{
+ struct be_acct_req *req;
+ struct be_req *be_req;
+ struct be_client *becli;
+ DBusMessage *reply;
+ DBusError dbus_error;
+ dbus_bool_t dbret;
+ void *user_data;
+ uint32_t type;
+ char *filter;
+ int filter_type;
+ uint32_t attr_type;
+ char *filter_val;
+ int ret;
+ dbus_uint16_t err_maj;
+ dbus_uint32_t err_min;
+ const char *err_msg;
+
+ be_req = NULL;
+
+ user_data = sbus_conn_get_private_data(conn);
+ if (!user_data) return EINVAL;
+ becli = talloc_get_type(user_data, struct be_client);
+ if (!becli) return EINVAL;
+
+ dbus_error_init(&dbus_error);
+
+ ret = dbus_message_get_args(message, &dbus_error,
+ DBUS_TYPE_UINT32, &type,
+ DBUS_TYPE_UINT32, &attr_type,
+ DBUS_TYPE_STRING, &filter,
+ DBUS_TYPE_INVALID);
+ if (!ret) {
+ DEBUG(1,("Failed, to parse message!\n"));
+ if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error);
+ return EIO;
+ }
+
+ DEBUG(4, ("Got request for [%u][%d][%s]\n", type, attr_type, filter));
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply) return ENOMEM;
+
+ /* If we are offline and fast reply was requested
+ * return offline immediately
+ */
+ if ((type & BE_REQ_FAST) && becli->bectx->offstat.offline) {
+ /* Send back an immediate reply */
+ err_maj = DP_ERR_OFFLINE;
+ err_min = EAGAIN;
+ err_msg = "Fast reply - offline";
+
+ dbret = dbus_message_append_args(reply,
+ DBUS_TYPE_UINT16, &err_maj,
+ DBUS_TYPE_UINT32, &err_min,
+ DBUS_TYPE_STRING, &err_msg,
+ DBUS_TYPE_INVALID);
+ if (!dbret) return EIO;
+
+ DEBUG(4, ("Request processed. Returned %d,%d,%s\n",
+ err_maj, err_min, err_msg));
+
+ sbus_conn_send_reply(conn, reply);
+ dbus_message_unref(reply);
+ reply = NULL;
+ /* This reply will be queued and sent
+ * when we reenter the mainloop.
+ *
+ * Continue processing in case we are
+ * going back online.
+ */
+ }
+
+ if ((attr_type != BE_ATTR_CORE) &&
+ (attr_type != BE_ATTR_MEM) &&
+ (attr_type != BE_ATTR_ALL)) {
+ /* Unrecognized attr type */
+ err_maj = DP_ERR_FATAL;
+ err_min = EINVAL;
+ err_msg = "Invalid Attrs Parameter";
+ goto done;
+ }
+
+ if (filter) {
+ if (strncmp(filter, "name=", 5) == 0) {
+ filter_type = BE_FILTER_NAME;
+ filter_val = &filter[5];
+ } else if (strncmp(filter, "idnumber=", 9) == 0) {
+ filter_type = BE_FILTER_IDNUM;
+ filter_val = &filter[9];
+ } else {
+ err_maj = DP_ERR_FATAL;
+ err_min = EINVAL;
+ err_msg = "Invalid Filter";
+ goto done;
+ }
+ } else {
+ err_maj = DP_ERR_FATAL;
+ err_min = EINVAL;
+ err_msg = "Missing Filter Parameter";
+ goto done;
+ }
+
+ /* process request */
+ be_req = talloc(becli, struct be_req);
+ if (!be_req) {
+ err_maj = DP_ERR_FATAL;
+ err_min = ENOMEM;
+ err_msg = "Out of memory";
+ goto done;
+ }
+ be_req->becli = becli;
+ be_req->be_ctx = becli->bectx;
+ be_req->fn = acctinfo_callback;
+ be_req->pvt = reply;
+
+ req = talloc(be_req, struct be_acct_req);
+ if (!req) {
+ err_maj = DP_ERR_FATAL;
+ err_min = ENOMEM;
+ err_msg = "Out of memory";
+ goto done;
+ }
+ req->entry_type = type;
+ req->attr_type = (int)attr_type;
+ req->filter_type = filter_type;
+ req->filter_value = talloc_strdup(req, filter_val);
+
+ be_req->req_data = req;
+
+ ret = be_file_request(becli->bectx,
+ becli->bectx->bet_info[BET_ID].bet_ops->handler,
+ be_req);
+ if (ret != EOK) {
+ err_maj = DP_ERR_FATAL;
+ err_min = ret;
+ err_msg = "Failed to file request";
+ goto done;
+ }
+
+ return EOK;
+
+done:
+ if (be_req) {
+ talloc_free(be_req);
+ }
+
+ if (reply) {
+ dbret = dbus_message_append_args(reply,
+ DBUS_TYPE_UINT16, &err_maj,
+ DBUS_TYPE_UINT32, &err_min,
+ DBUS_TYPE_STRING, &err_msg,
+ DBUS_TYPE_INVALID);
+ if (!dbret) return EIO;
+
+ DEBUG(4, ("Request processed. Returned %d,%d,%s\n",
+ err_maj, err_min, err_msg));
+
+ /* send reply back */
+ sbus_conn_send_reply(conn, reply);
+ dbus_message_unref(reply);
+ }
+
+ return EOK;
+}
+
+static void be_pam_handler_callback(struct be_req *req,
+ int dp_err_type,
+ int errnum,
+ const char *errstr)
+{
+ struct pam_data *pd;
+ DBusMessage *reply;
+ DBusConnection *dbus_conn;
+ dbus_bool_t dbret;
+
+ DEBUG(4, ("Backend returned: (%d, %d, %s) [%s]\n",
+ dp_err_type, errnum, errstr?errstr:"<NULL>",
+ dp_err_to_string(req, dp_err_type, 0)));
+
+ pd = talloc_get_type(req->req_data, struct pam_data);
+
+ DEBUG(4, ("Sending result [%d][%s]\n", pd->pam_status, pd->domain));
+ reply = (DBusMessage *)req->pvt;
+ dbret = dp_pack_pam_response(reply, pd);
+ if (!dbret) {
+ DEBUG(1, ("Failed to generate dbus reply\n"));
+ dbus_message_unref(reply);
+ return;
+ }
+
+ dbus_conn = sbus_get_connection(req->becli->conn);
+ dbus_connection_send(dbus_conn, reply, NULL);
+ dbus_message_unref(reply);
+
+ DEBUG(4, ("Sent result [%d][%s]\n", pd->pam_status, pd->domain));
+
+ talloc_free(req);
+}
+
+static int be_pam_handler(DBusMessage *message, struct sbus_connection *conn)
+{
+ DBusError dbus_error;
+ DBusMessage *reply;
+ struct be_client *becli;
+ dbus_bool_t ret;
+ void *user_data;
+ struct pam_data *pd = NULL;
+ struct be_req *be_req = NULL;
+ enum bet_type target = BET_NULL;
+
+ user_data = sbus_conn_get_private_data(conn);
+ if (!user_data) return EINVAL;
+ becli = talloc_get_type(user_data, struct be_client);
+ if (!becli) return EINVAL;
+
+ reply = dbus_message_new_method_return(message);
+ if (!reply) {
+ DEBUG(1, ("dbus_message_new_method_return failed, cannot send reply.\n"));
+ return ENOMEM;
+ }
+
+ be_req = talloc_zero(becli, struct be_req);
+ if (!be_req) {
+ DEBUG(7, ("talloc_zero failed.\n"));
+ goto done;
+ }
+
+ be_req->becli = becli;
+ be_req->be_ctx = becli->bectx;
+ be_req->fn = be_pam_handler_callback;
+ be_req->pvt = reply;
+
+ pd = talloc_zero(be_req, struct pam_data);
+ if (!pd) {
+ talloc_free(be_req);
+ return ENOMEM;
+ }
+
+ pd->pam_status = PAM_SYSTEM_ERR;
+ pd->domain = talloc_strdup(pd, becli->bectx->domain->name);
+ if (pd->domain == NULL) {
+ talloc_free(be_req);
+ return ENOMEM;
+ }
+
+ dbus_error_init(&dbus_error);
+
+ ret = dp_unpack_pam_request(message, pd, &dbus_error);
+ if (!ret) {
+ DEBUG(1,("Failed, to parse message!\n"));
+ talloc_free(be_req);
+ return EIO;
+ }
+
+ DEBUG(4, ("Got request with the following data\n"));
+ DEBUG_PAM_DATA(4, pd);
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ target = BET_AUTH;
+ break;
+ case SSS_PAM_ACCT_MGMT:
+ target = BET_ACCESS;
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ target = BET_CHPASS;
+ break;
+ case SSS_PAM_SETCRED:
+ case SSS_PAM_OPEN_SESSION:
+ case SSS_PAM_CLOSE_SESSION:
+ pd->pam_status = PAM_SUCCESS;
+ goto done;
+ break;
+ default:
+ DEBUG(7, ("Unsupported PAM command [%d].\n", pd->cmd));
+ pd->pam_status = PAM_MODULE_UNKNOWN;
+ goto done;
+ }
+
+ /* return an error if corresponding backend target is not configured */
+ if (!becli->bectx->bet_info[target].bet_ops) {
+ DEBUG(7, ("Undefined backend target.\n"));
+ pd->pam_status = PAM_MODULE_UNKNOWN;
+ ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO,
+ sizeof(MSG_TARGET_NO_CONFIGURED),
+ (const uint8_t *) MSG_TARGET_NO_CONFIGURED);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ }
+ goto done;
+ }
+
+ be_req->req_data = pd;
+
+ ret = be_file_request(becli->bectx,
+ becli->bectx->bet_info[target].bet_ops->handler,
+ be_req);
+ if (ret != EOK) {
+ DEBUG(7, ("be_file_request failed.\n"));
+ goto done;
+ }
+
+ return EOK;
+
+done:
+
+ DEBUG(4, ("Sending result [%d][%s]\n",
+ pd->pam_status, pd->domain));
+
+ ret = dp_pack_pam_response(reply, pd);
+ if (!ret) {
+ DEBUG(1, ("Failed to generate dbus reply\n"));
+ talloc_free(be_req);
+ dbus_message_unref(reply);
+ return EIO;
+ }
+
+ /* send reply back immediately */
+ sbus_conn_send_reply(conn, reply);
+ dbus_message_unref(reply);
+
+ talloc_free(be_req);
+
+ return EOK;
+}
+
+static int be_client_destructor(void *ctx)
+{
+ struct be_client *becli = talloc_get_type(ctx, struct be_client);
+ if (becli->bectx) {
+ if (becli->bectx->nss_cli == becli) {
+ DEBUG(4, ("Removed NSS client\n"));
+ becli->bectx->nss_cli = NULL;
+ } else if (becli->bectx->pam_cli == becli) {
+ DEBUG(4, ("Removed PAM client\n"));
+ becli->bectx->pam_cli = NULL;
+ } else {
+ DEBUG(2, ("Unknown client removed ...\n"));
+ }
+ }
+ return 0;
+}
+
+static int client_registration(DBusMessage *message,
+ struct sbus_connection *conn)
+{
+ dbus_uint16_t version = DATA_PROVIDER_VERSION;
+ struct be_client *becli;
+ DBusMessage *reply;
+ DBusError dbus_error;
+ dbus_uint16_t cli_ver;
+ char *cli_name;
+ char *cli_domain;
+ dbus_bool_t dbret;
+ void *data;
+
+ data = sbus_conn_get_private_data(conn);
+ becli = talloc_get_type(data, struct be_client);
+ if (!becli) {
+ DEBUG(0, ("Connection holds no valid init data\n"));
+ return EINVAL;
+ }
+
+ /* First thing, cancel the timeout */
+ DEBUG(4, ("Cancel DP ID timeout [%p]\n", becli->timeout));
+ talloc_zfree(becli->timeout);
+
+ dbus_error_init(&dbus_error);
+
+ dbret = dbus_message_get_args(message, &dbus_error,
+ DBUS_TYPE_UINT16, &cli_ver,
+ DBUS_TYPE_STRING, &cli_name,
+ DBUS_TYPE_STRING, &cli_domain,
+ DBUS_TYPE_INVALID);
+ if (!dbret) {
+ DEBUG(1, ("Failed to parse message, killing connection\n"));
+ if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error);
+ sbus_disconnect(conn);
+ /* FIXME: should we just talloc_zfree(conn) ? */
+ return EIO;
+ }
+
+ if (strcasecmp(cli_name, "NSS") == 0) {
+ becli->bectx->nss_cli = becli;
+ } else if (strcasecmp(cli_name, "PAM") == 0) {
+ becli->bectx->pam_cli = becli;
+ } else {
+ DEBUG(1, ("Unknown client! [%s]\n", cli_name));
+ }
+ talloc_set_destructor((TALLOC_CTX *)becli, be_client_destructor);
+
+ DEBUG(4, ("Added Frontend client [%s]\n", cli_name));
+
+ /* reply that all is ok */
+ reply = dbus_message_new_method_return(message);
+ if (!reply) {
+ DEBUG(0, ("Dbus Out of memory!\n"));
+ return ENOMEM;
+ }
+
+ dbret = dbus_message_append_args(reply,
+ DBUS_TYPE_UINT16, &version,
+ DBUS_TYPE_INVALID);
+ if (!dbret) {
+ DEBUG(0, ("Failed to build dbus reply\n"));
+ dbus_message_unref(reply);
+ sbus_disconnect(conn);
+ return EIO;
+ }
+
+ /* send reply back */
+ sbus_conn_send_reply(conn, reply);
+ dbus_message_unref(reply);
+
+ becli->initialized = true;
+ return EOK;
+}
+
+static void init_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct be_client *becli;
+
+ DEBUG(2, ("Client timed out before Identification [%p]!\n", te));
+
+ becli = talloc_get_type(ptr, struct be_client);
+
+ sbus_disconnect(becli->conn);
+ talloc_zfree(becli);
+}
+
+static int be_client_init(struct sbus_connection *conn, void *data)
+{
+ struct be_ctx *bectx;
+ struct be_client *becli;
+ struct timeval tv;
+
+ bectx = talloc_get_type(data, struct be_ctx);
+
+ /* hang off this memory to the connection so that when the connection
+ * is freed we can potentially call a destructor */
+
+ becli = talloc(conn, struct be_client);
+ if (!becli) {
+ DEBUG(0,("Out of memory?!\n"));
+ talloc_zfree(conn);
+ return ENOMEM;
+ }
+ becli->bectx = bectx;
+ becli->conn = conn;
+ becli->initialized = false;
+
+ /* 5 seconds should be plenty */
+ tv = tevent_timeval_current_ofs(5, 0);
+
+ becli->timeout = tevent_add_timer(bectx->ev, becli,
+ tv, init_timeout, becli);
+ if (!becli->timeout) {
+ DEBUG(0,("Out of memory?!\n"));
+ talloc_zfree(conn);
+ return ENOMEM;
+ }
+ DEBUG(4, ("Set-up Backend ID timeout [%p]\n", becli->timeout));
+
+ /* Attach the client context to the connection context, so that it is
+ * always available when we need to manage the connection. */
+ sbus_conn_set_private_data(conn, becli);
+
+ return EOK;
+}
+
+/* be_srv_init
+ * set up per-domain sbus channel */
+static int be_srv_init(struct be_ctx *ctx)
+{
+ char *sbus_address;
+ int ret;
+
+ /* Set up SBUS connection to the monitor */
+ ret = dp_get_sbus_address(ctx, &sbus_address, ctx->domain->name);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not get sbus backend address.\n"));
+ return ret;
+ }
+
+ ret = sbus_new_server(ctx, ctx->ev, sbus_address,
+ &be_interface, &ctx->sbus_srv,
+ be_client_init, ctx);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not set up sbus server.\n"));
+ return ret;
+ }
+
+ return EOK;
+}
+
+/* mon_cli_init
+ * sbus channel to the monitor daemon */
+static int mon_cli_init(struct be_ctx *ctx)
+{
+ char *sbus_address;
+ int ret;
+
+ /* Set up SBUS connection to the monitor */
+ ret = monitor_get_sbus_address(ctx, &sbus_address);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not locate monitor address.\n"));
+ return ret;
+ }
+
+ ret = sbus_client_init(ctx, ctx->ev, sbus_address,
+ &monitor_be_interface, &ctx->mon_conn,
+ NULL, ctx);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to connect to monitor services.\n"));
+ return ret;
+ }
+
+ /* Identify ourselves to the monitor */
+ ret = monitor_common_send_id(ctx->mon_conn,
+ ctx->identity,
+ DATA_PROVIDER_VERSION);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to identify to the monitor!\n"));
+ return ret;
+ }
+
+ return EOK;
+}
+
+static void be_target_access_permit(struct be_req *be_req)
+{
+ struct pam_data *pd = talloc_get_type(be_req->req_data, struct pam_data);
+ DEBUG(9, ("be_target_access_permit called, returning PAM_SUCCESS.\n"));
+
+ pd->pam_status = PAM_SUCCESS;
+ be_req->fn(be_req, DP_ERR_OK, PAM_SUCCESS, NULL);
+}
+
+static struct bet_ops be_target_access_permit_ops = {
+ .check_online = NULL,
+ .handler = be_target_access_permit,
+ .finalize = NULL
+};
+
+static void be_target_access_deny(struct be_req *be_req)
+{
+ struct pam_data *pd = talloc_get_type(be_req->req_data, struct pam_data);
+ DEBUG(9, ("be_target_access_deny called, returning PAM_PERM_DENIED.\n"));
+
+ pd->pam_status = PAM_PERM_DENIED;
+ be_req->fn(be_req, DP_ERR_OK, PAM_PERM_DENIED, NULL);
+}
+
+static struct bet_ops be_target_access_deny_ops = {
+ .check_online = NULL,
+ .handler = be_target_access_deny,
+ .finalize = NULL
+};
+
+static int load_backend_module(struct be_ctx *ctx,
+ enum bet_type bet_type,
+ struct bet_info *bet_info,
+ const char *default_mod_name)
+{
+ TALLOC_CTX *tmp_ctx;
+ int ret = EINVAL;
+ bool already_loaded = false;
+ int lb=0;
+ char *mod_name = NULL;
+ char *path = NULL;
+ void *handle;
+ char *mod_init_fn_name = NULL;
+ bet_init_fn_t mod_init_fn = NULL;
+
+ (*bet_info).mod_name = NULL;
+ (*bet_info).bet_ops = NULL;
+ (*bet_info).pvt_bet_data = NULL;
+
+ if (bet_type <= BET_NULL || bet_type >= BET_MAX ||
+ bet_type != bet_data[bet_type].bet_type) {
+ DEBUG(2, ("invalid bet_type or bet_data corrupted.\n"));
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(ctx);
+ if (!tmp_ctx) {
+ DEBUG(7, ("talloc_new failed.\n"));
+ return ENOMEM;
+ }
+
+ ret = confdb_get_string(ctx->cdb, tmp_ctx, ctx->conf_path,
+ bet_data[bet_type].option_name, NULL,
+ &mod_name);
+ if (ret != EOK) {
+ ret = EFAULT;
+ goto done;
+ }
+ if (!mod_name) {
+ if (default_mod_name != NULL) {
+ DEBUG(5, ("no module name found in confdb, using [%s].\n",
+ default_mod_name));
+ mod_name = talloc_strdup(ctx, default_mod_name);
+ } else {
+ ret = ENOENT;
+ goto done;
+ }
+ }
+
+ if (strcasecmp(mod_name, NO_PROVIDER) == 0) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ if (bet_type == BET_ACCESS) {
+ if (strcmp(mod_name, ACCESS_PERMIT) == 0) {
+ (*bet_info).bet_ops = &be_target_access_permit_ops;
+ (*bet_info).pvt_bet_data = NULL;
+ (*bet_info).mod_name = talloc_strdup(ctx, ACCESS_PERMIT);
+
+ ret = EOK;
+ goto done;
+ }
+ if (strcmp(mod_name, ACCESS_DENY) == 0) {
+ (*bet_info).bet_ops = &be_target_access_deny_ops;
+ (*bet_info).pvt_bet_data = NULL;
+ (*bet_info).mod_name = talloc_strdup(ctx, ACCESS_DENY);
+
+ ret = EOK;
+ goto done;
+ }
+ }
+
+ mod_init_fn_name = talloc_asprintf(tmp_ctx,
+ bet_data[bet_type].mod_init_fn_name_fmt,
+ mod_name);
+ if (mod_init_fn_name == NULL) {
+ DEBUG(7, ("talloc_asprintf failed\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+
+
+ lb = 0;
+ while(ctx->loaded_be[lb].be_name != NULL) {
+ if (strncmp(ctx->loaded_be[lb].be_name, mod_name,
+ strlen(mod_name)) == 0) {
+ DEBUG(7, ("Backend [%s] already loaded.\n", mod_name));
+ already_loaded = true;
+ break;
+ }
+
+ ++lb;
+ if (lb >= BET_MAX) {
+ DEBUG(2, ("Backend context corrupted.\n"));
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ if (!already_loaded) {
+ path = talloc_asprintf(tmp_ctx, "%s/libsss_%s.so",
+ DATA_PROVIDER_PLUGINS_PATH, mod_name);
+ if (!path) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(7, ("Loading backend [%s] with path [%s].\n", mod_name, path));
+ handle = dlopen(path, RTLD_NOW);
+ if (!handle) {
+ DEBUG(0, ("Unable to load %s module with path (%s), error: %s\n",
+ mod_name, path, dlerror()));
+ ret = ELIBACC;
+ goto done;
+ }
+
+ ctx->loaded_be[lb].be_name = talloc_strdup(ctx, mod_name);
+ ctx->loaded_be[lb].handle = handle;
+ }
+
+ mod_init_fn = (bet_init_fn_t)dlsym(ctx->loaded_be[lb].handle,
+ mod_init_fn_name);
+ if (mod_init_fn == NULL) {
+ if (default_mod_name != NULL &&
+ strcmp(default_mod_name, mod_name) == 0 ) {
+ /* If the default is used and fails we indicate this to the caller
+ * by returning ENOENT. Ths way the caller can decide how to
+ * handle the different types of error conditions. */
+ ret = ENOENT;
+ } else {
+ DEBUG(0, ("Unable to load init fn %s from module %s, error: %s\n",
+ mod_init_fn_name, mod_name, dlerror()));
+ ret = ELIBBAD;
+ }
+ goto done;
+ }
+
+ ret = mod_init_fn(ctx, &(*bet_info).bet_ops, &(*bet_info).pvt_bet_data);
+ if (ret != EOK) {
+ DEBUG(0, ("Error (%d) in module (%s) initialization (%s)!\n",
+ ret, mod_name, mod_init_fn_name));
+ goto done;
+ }
+
+ (*bet_info).mod_name = talloc_strdup(ctx, mod_name);
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static void signal_be_offline(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ struct be_ctx *ctx = talloc_get_type(private_data, struct be_ctx);
+ be_mark_offline(ctx);
+}
+
+int be_process_init(TALLOC_CTX *mem_ctx,
+ const char *be_domain,
+ struct tevent_context *ev,
+ struct confdb_ctx *cdb)
+{
+ struct be_ctx *ctx;
+ struct tevent_signal *tes;
+ int ret;
+
+ ctx = talloc_zero(mem_ctx, struct be_ctx);
+ if (!ctx) {
+ DEBUG(0, ("fatal error initializing be_ctx\n"));
+ return ENOMEM;
+ }
+ ctx->ev = ev;
+ ctx->cdb = cdb;
+ ctx->identity = talloc_asprintf(ctx, "%%BE_%s", be_domain);
+ ctx->conf_path = talloc_asprintf(ctx, CONFDB_DOMAIN_PATH_TMPL, be_domain);
+ if (!ctx->identity || !ctx->conf_path) {
+ DEBUG(0, ("Out of memory!?\n"));
+ return ENOMEM;
+ }
+
+ ret = be_init_failover(ctx);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error initializing failover context\n"));
+ return ret;
+ }
+
+ ret = confdb_get_domain(cdb, be_domain, &ctx->domain);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error retrieving domain configuration\n"));
+ return ret;
+ }
+
+ ret = sysdb_domain_init(ctx, ev, ctx->domain, DB_PATH, &ctx->sysdb);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error opening cache database\n"));
+ return ret;
+ }
+
+ ret = mon_cli_init(ctx);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error setting up monitor bus\n"));
+ return ret;
+ }
+
+ ret = be_srv_init(ctx);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error setting up server bus\n"));
+ return ret;
+ }
+
+ ret = load_backend_module(ctx, BET_ID,
+ &ctx->bet_info[BET_ID], NULL);
+ if (ret != EOK) {
+ DEBUG(0, ("fatal error initializing data providers\n"));
+ return ret;
+ }
+ DEBUG(9, ("ID backend target successfully loaded from provider [%s].\n",
+ ctx->bet_info[BET_ID].mod_name));
+
+ ret = load_backend_module(ctx, BET_AUTH,
+ &ctx->bet_info[BET_AUTH],
+ ctx->bet_info[BET_ID].mod_name);
+ if (ret != EOK) {
+ if (ret != ENOENT) {
+ DEBUG(0, ("fatal error initializing data providers\n"));
+ return ret;
+ }
+ DEBUG(1, ("No authentication module provided for [%s] !!\n",
+ be_domain));
+ } else {
+ DEBUG(9, ("AUTH backend target successfully loaded "
+ "from provider [%s].\n", ctx->bet_info[BET_AUTH].mod_name));
+ }
+
+ ret = load_backend_module(ctx, BET_ACCESS, &ctx->bet_info[BET_ACCESS],
+ ACCESS_PERMIT);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to setup ACCESS backend.\n"));
+ return ret;
+ }
+ DEBUG(9, ("ACCESS backend target successfully loaded "
+ "from provider [%s].\n", ctx->bet_info[BET_ACCESS].mod_name));
+
+ ret = load_backend_module(ctx, BET_CHPASS,
+ &ctx->bet_info[BET_CHPASS],
+ ctx->bet_info[BET_AUTH].mod_name);
+ if (ret != EOK) {
+ if (ret != ENOENT) {
+ DEBUG(0, ("fatal error initializing data providers\n"));
+ return ret;
+ }
+ DEBUG(1, ("No change password module provided for [%s] !!\n",
+ be_domain));
+ } else {
+ DEBUG(9, ("CHPASS backend target successfully loaded "
+ "from provider [%s].\n", ctx->bet_info[BET_CHPASS].mod_name));
+ }
+
+ /* Handle SIGUSR1 to force offline behavior */
+ BlockSignals(false, SIGUSR1);
+ tes = tevent_add_signal(ctx->ev, ctx, SIGUSR1, 0,
+ signal_be_offline, ctx);
+ if (tes == NULL) {
+ return EIO;
+ }
+
+ return EOK;
+}
+
+int main(int argc, const char *argv[])
+{
+ int opt;
+ poptContext pc;
+ char *be_domain = NULL;
+ char *srv_name = NULL;
+ char *conf_entry = NULL;
+ struct main_context *main_ctx;
+ int ret;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_MAIN_OPTS
+ {"domain", 0, POPT_ARG_STRING, &be_domain, 0,
+ _("Domain of the information provider (mandatory)"), NULL },
+ POPT_TABLEEND
+ };
+
+ 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 (be_domain == NULL) {
+ fprintf(stderr, "\nMissing option, --domain 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, "sssd_%s", be_domain);
+ if (!debug_log_file) return 2;
+
+ srv_name = talloc_asprintf(NULL, "sssd[be[%s]]", be_domain);
+ if (!srv_name) return 2;
+
+ conf_entry = talloc_asprintf(NULL, CONFDB_DOMAIN_PATH_TMPL, be_domain);
+ if (!conf_entry) return 2;
+
+ ret = server_setup(srv_name, 0, conf_entry, &main_ctx);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not set up mainloop [%d]\n", ret));
+ return 2;
+ }
+
+ ret = die_if_parent_died();
+ if (ret != EOK) {
+ /* This is not fatal, don't return */
+ DEBUG(2, ("Could not set up to exit when parent process does\n"));
+ }
+
+ ret = be_process_init(main_ctx,
+ be_domain,
+ main_ctx->event_ctx,
+ main_ctx->confdb_ctx);
+ if (ret != EOK) {
+ DEBUG(0, ("Could not initialize backend [%d]\n", ret));
+ return 3;
+ }
+
+ DEBUG(1, ("Backend provider (%s) started!\n", be_domain));
+
+ /* loop on main */
+ server_loop(main_ctx);
+
+ return 0;
+}
+
+static int data_provider_res_init(DBusMessage *message,
+ struct sbus_connection *conn)
+{
+ resolv_reread_configuration();
+
+ return monitor_common_res_init(message, conn);
+}
+
+static int data_provider_go_offline(DBusMessage *message,
+ struct sbus_connection *conn)
+{
+ struct be_ctx *be_ctx;
+ be_ctx = talloc_get_type(sbus_conn_get_private_data(conn), struct be_ctx);
+ be_mark_offline(be_ctx);
+ return monitor_common_pong(message, conn);
+}
diff --git a/src/providers/data_provider_fo.c b/src/providers/data_provider_fo.c
new file mode 100644
index 000000000..7d0240489
--- /dev/null
+++ b/src/providers/data_provider_fo.c
@@ -0,0 +1,356 @@
+/*
+ SSSD
+
+ Data Provider Helpers
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+
+ 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 <netdb.h>
+#include <arpa/inet.h>
+#include "providers/dp_backend.h"
+#include "resolv/async_resolv.h"
+
+struct be_svc_callback {
+ struct be_svc_callback *prev;
+ struct be_svc_callback *next;
+
+ struct be_svc_data *svc;
+
+ be_svc_callback_fn_t *fn;
+ void *private_data;
+};
+
+struct be_svc_data {
+ struct be_svc_data *prev;
+ struct be_svc_data *next;
+
+ const char *name;
+ struct fo_service *fo_service;
+
+ struct fo_server *last_good_srv;
+
+ struct be_svc_callback *callbacks;
+};
+
+struct be_failover_ctx {
+ struct fo_ctx *fo_ctx;
+ struct resolv_ctx *resolv;
+
+ struct be_svc_data *svcs;
+};
+
+int be_init_failover(struct be_ctx *ctx)
+{
+ int ret;
+
+ if (ctx->be_fo != NULL) {
+ return EOK;
+ }
+
+ ctx->be_fo = talloc_zero(ctx, struct be_failover_ctx);
+ if (!ctx->be_fo) {
+ return ENOMEM;
+ }
+
+ ret = resolv_init(ctx, ctx->ev, 5, &ctx->be_fo->resolv);
+ if (ret != EOK) {
+ talloc_zfree(ctx->be_fo);
+ return ret;
+ }
+
+ /* todo get timeout from configuration */
+ ctx->be_fo->fo_ctx = fo_context_init(ctx->be_fo, 30);
+ if (!ctx->be_fo->fo_ctx) {
+ talloc_zfree(ctx->be_fo);
+ return ENOMEM;
+ }
+
+ return EOK;
+}
+
+static int be_svc_data_destroy(void *memptr)
+{
+ struct be_svc_data *svc;
+
+ svc = talloc_get_type(memptr, struct be_svc_data);
+
+ while (svc->callbacks) {
+ /* callbacks removes themselves from the list,
+ * so this while will freem them all and then terminate */
+ talloc_free(svc->callbacks);
+ }
+
+ return 0;
+}
+
+int be_fo_add_service(struct be_ctx *ctx, const char *service_name)
+{
+ struct fo_service *service;
+ struct be_svc_data *svc;
+ int ret;
+
+ DLIST_FOR_EACH(svc, ctx->be_fo->svcs) {
+ if (strcmp(svc->name, service_name) == 0) {
+ DEBUG(6, ("Failover service already initialized!\n"));
+ /* we already have a service up and configured,
+ * can happen when using both id and auth provider
+ */
+ return EOK;
+ }
+ }
+
+ /* if not in the be service list, try to create new one */
+
+ ret = fo_new_service(ctx->be_fo->fo_ctx, service_name, &service);
+ if (ret != EOK && ret != EEXIST) {
+ DEBUG(1, ("Failed to create failover service!\n"));
+ return ret;
+ }
+
+ svc = talloc_zero(ctx->be_fo, struct be_svc_data);
+ if (!svc) {
+ return ENOMEM;
+ }
+ talloc_set_destructor((TALLOC_CTX *)svc, be_svc_data_destroy);
+
+ svc->name = talloc_strdup(svc, service_name);
+ if (!svc->name) {
+ talloc_zfree(svc);
+ return ENOMEM;
+ }
+ svc->fo_service = service;
+
+ DLIST_ADD(ctx->be_fo->svcs, svc);
+
+ return EOK;
+}
+
+static int be_svc_callback_destroy(void *memptr)
+{
+ struct be_svc_callback *callback;
+
+ callback = talloc_get_type(memptr, struct be_svc_callback);
+
+ if (callback->svc) {
+ DLIST_REMOVE(callback->svc->callbacks, callback);
+ }
+
+ return 0;
+}
+
+int be_fo_service_add_callback(TALLOC_CTX *memctx,
+ struct be_ctx *ctx, const char *service_name,
+ be_svc_callback_fn_t *fn, void *private_data)
+{
+ struct be_svc_callback *callback;
+ struct be_svc_data *svc;
+
+ DLIST_FOR_EACH(svc, ctx->be_fo->svcs) {
+ if (strcmp(svc->name, service_name) == 0) {
+ break;
+ }
+ }
+ if (NULL == svc) {
+ return ENOENT;
+ }
+
+ callback = talloc_zero(memctx, struct be_svc_callback);
+ if (!callback) {
+ return ENOMEM;
+ }
+ talloc_set_destructor((TALLOC_CTX *)callback, be_svc_callback_destroy);
+
+ callback->svc = svc;
+ callback->fn = fn;
+ callback->private_data = private_data;
+
+ DLIST_ADD(svc->callbacks, callback);
+
+ return EOK;
+}
+
+int be_fo_add_server(struct be_ctx *ctx, const char *service_name,
+ const char *server, int port, void *user_data)
+{
+ struct be_svc_data *svc;
+ int ret;
+
+ DLIST_FOR_EACH(svc, ctx->be_fo->svcs) {
+ if (strcmp(svc->name, service_name) == 0) {
+ break;
+ }
+ }
+ if (NULL == svc) {
+ return ENOENT;
+ }
+
+ ret = fo_add_server(svc->fo_service, server, port, user_data);
+ if (ret && ret != EEXIST) {
+ DEBUG(1, ("Failed to add server to failover service\n"));
+ return ret;
+ }
+
+ return EOK;
+}
+
+struct be_resolve_server_state {
+ struct tevent_context *ev;
+ struct be_ctx *ctx;
+
+ struct be_svc_data *svc;
+ int attempts;
+
+ struct fo_server *srv;
+};
+
+static void be_resolve_server_done(struct tevent_req *subreq);
+
+struct tevent_req *be_resolve_server_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct be_ctx *ctx,
+ const char *service_name)
+{
+ struct tevent_req *req, *subreq;
+ struct be_resolve_server_state *state;
+ struct be_svc_data *svc;
+
+ req = tevent_req_create(memctx, &state, struct be_resolve_server_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+
+ DLIST_FOR_EACH(svc, ctx->be_fo->svcs) {
+ if (strcmp(svc->name, service_name) == 0) {
+ state->svc = svc;
+ break;
+ }
+ }
+
+ if (NULL == svc) {
+ tevent_req_error(req, EINVAL);
+ tevent_req_post(req, ev);
+ return req;
+ }
+
+ state->attempts = 0;
+
+ subreq = fo_resolve_service_send(state, ev,
+ ctx->be_fo->resolv, svc->fo_service);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, be_resolve_server_done, req);
+
+ return req;
+}
+
+static void be_resolve_server_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct be_resolve_server_state *state = tevent_req_data(req,
+ struct be_resolve_server_state);
+ struct be_svc_callback *callback;
+ int ret;
+
+ ret = fo_resolve_service_recv(subreq, &state->srv);
+ talloc_zfree(subreq);
+ switch (ret) {
+ case EOK:
+ if (!state->srv) {
+ tevent_req_error(req, EFAULT);
+ return;
+ }
+ break;
+
+ case ENOENT:
+ /* all servers have been tried and none
+ * was found good, go offline */
+ tevent_req_error(req, EIO);
+ return;
+
+ default:
+ /* mark server as bad and retry */
+ if (!state->srv) {
+ tevent_req_error(req, EFAULT);
+ return;
+ }
+ DEBUG(6, ("Couldn't resolve server (%s), resolver returned (%d)\n",
+ fo_get_server_name(state->srv), ret));
+
+ state->attempts++;
+ if (state->attempts >= 10) {
+ DEBUG(2, ("Failed to find a server after 10 attempts\n"));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ /* now try next one */
+ DEBUG(6, ("Trying with the next one!\n"));
+ subreq = fo_resolve_service_send(state, state->ev,
+ state->ctx->be_fo->resolv,
+ state->svc->fo_service);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, be_resolve_server_done, req);
+
+ return;
+ }
+
+ /* all fine we got the server */
+
+ if (debug_level >= 4) {
+ struct hostent *srvaddr;
+ char ipaddr[128];
+ srvaddr = fo_get_server_hostent(state->srv);
+ inet_ntop(srvaddr->h_addrtype, srvaddr->h_addr_list[0],
+ ipaddr, 128);
+
+ DEBUG(4, ("Found address for server %s: [%s]\n",
+ fo_get_server_name(state->srv), ipaddr));
+ }
+
+ /* now call all svc callbacks if server changed */
+ if (state->srv != state->svc->last_good_srv) {
+ state->svc->last_good_srv = state->srv;
+
+ DLIST_FOR_EACH(callback, state->svc->callbacks) {
+ callback->fn(callback->private_data, state->srv);
+ }
+ }
+
+ tevent_req_done(req);
+}
+
+int be_resolve_server_recv(struct tevent_req *req, struct fo_server **srv)
+{
+ struct be_resolve_server_state *state = tevent_req_data(req,
+ struct be_resolve_server_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (srv) {
+ *srv = state->srv;
+ }
+
+ return EOK;
+}
+
diff --git a/src/providers/data_provider_opts.c b/src/providers/data_provider_opts.c
new file mode 100644
index 000000000..98283e430
--- /dev/null
+++ b/src/providers/data_provider_opts.c
@@ -0,0 +1,384 @@
+/*
+ SSSD
+
+ Data Provider Helpers
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+
+ 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 "data_provider.h"
+
+/* =Retrieve-Options====================================================== */
+
+int dp_get_options(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct dp_option *def_opts,
+ int num_opts,
+ struct dp_option **_opts)
+{
+ struct dp_option *opts;
+ int i, ret;
+
+ opts = talloc_zero_array(memctx, struct dp_option, num_opts);
+ if (!opts) return ENOMEM;
+
+ for (i = 0; i < num_opts; i++) {
+ char *tmp;
+
+ opts[i].opt_name = def_opts[i].opt_name;
+ opts[i].type = def_opts[i].type;
+ opts[i].def_val = def_opts[i].def_val;
+ ret = EOK;
+
+ switch (def_opts[i].type) {
+ case DP_OPT_STRING:
+ ret = confdb_get_string(cdb, opts, conf_path,
+ opts[i].opt_name,
+ opts[i].def_val.cstring,
+ &opts[i].val.string);
+ if (ret != EOK ||
+ ((opts[i].def_val.string != NULL) &&
+ (opts[i].val.string == NULL))) {
+ DEBUG(0, ("Failed to retrieve value for option (%s)\n",
+ opts[i].opt_name));
+ if (ret == EOK) ret = EINVAL;
+ goto done;
+ }
+ DEBUG(6, ("Option %s has value %s\n",
+ opts[i].opt_name, opts[i].val.cstring));
+ break;
+
+ case DP_OPT_BLOB:
+ ret = confdb_get_string(cdb, opts, conf_path,
+ opts[i].opt_name,
+ NULL, &tmp);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to retrieve value for option (%s)\n",
+ opts[i].opt_name));
+ goto done;
+ }
+
+ if (tmp) {
+ opts[i].val.blob.data = (uint8_t *)tmp;
+ opts[i].val.blob.length = strlen(tmp);
+ } else {
+ opts[i].val.blob.data = NULL;
+ opts[i].val.blob.length = 0;
+ }
+
+ DEBUG(6, ("Option %s has %s binary value.\n",
+ opts[i].opt_name,
+ opts[i].val.blob.length?"a":"no"));
+ break;
+
+ case DP_OPT_NUMBER:
+ ret = confdb_get_int(cdb, opts, conf_path,
+ opts[i].opt_name,
+ opts[i].def_val.number,
+ &opts[i].val.number);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to retrieve value for option (%s)\n",
+ opts[i].opt_name));
+ goto done;
+ }
+ DEBUG(6, ("Option %s has value %d\n",
+ opts[i].opt_name, opts[i].val.number));
+ break;
+
+ case DP_OPT_BOOL:
+ ret = confdb_get_bool(cdb, opts, conf_path,
+ opts[i].opt_name,
+ opts[i].def_val.boolean,
+ &opts[i].val.boolean);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to retrieve value for option (%s)\n",
+ opts[i].opt_name));
+ goto done;
+ }
+ DEBUG(6, ("Option %s is %s\n",
+ opts[i].opt_name,
+ opts[i].val.boolean?"TRUE":"FALSE"));
+ break;
+ }
+ }
+
+ ret = EOK;
+ *_opts = opts;
+
+done:
+ if (ret != EOK) talloc_zfree(opts);
+ return ret;
+}
+
+/* =Basic-Option-Helpers================================================== */
+
+int dp_copy_options(TALLOC_CTX *memctx,
+ struct dp_option *src_opts,
+ int num_opts,
+ struct dp_option **_opts)
+{
+ struct dp_option *opts;
+ int i, ret;
+
+ opts = talloc_zero_array(memctx, struct dp_option, num_opts);
+ if (!opts) return ENOMEM;
+
+ for (i = 0; i < num_opts; i++) {
+ opts[i].opt_name = src_opts[i].opt_name;
+ opts[i].type = src_opts[i].type;
+ opts[i].def_val = src_opts[i].def_val;
+ ret = EOK;
+
+ switch (src_opts[i].type) {
+ case DP_OPT_STRING:
+ if (src_opts[i].val.string) {
+ ret = dp_opt_set_string(opts, i, src_opts[i].val.string);
+ } else if (src_opts[i].def_val.string) {
+ ret = dp_opt_set_string(opts, i, src_opts[i].def_val.string);
+ }
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to copy value for option (%s)\n",
+ opts[i].opt_name));
+ goto done;
+ }
+ DEBUG(6, ("Option %s has value %s\n",
+ opts[i].opt_name, opts[i].val.cstring));
+ break;
+
+ case DP_OPT_BLOB:
+ if (src_opts[i].val.blob.data) {
+ ret = dp_opt_set_blob(opts, i, src_opts[i].val.blob);
+ } else if (src_opts[i].def_val.blob.data) {
+ ret = dp_opt_set_blob(opts, i, src_opts[i].def_val.blob);
+ }
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to retrieve value for option (%s)\n",
+ opts[i].opt_name));
+ goto done;
+ }
+ DEBUG(6, ("Option %s has %s binary value.\n",
+ opts[i].opt_name,
+ opts[i].val.blob.length?"a":"no"));
+ break;
+
+ case DP_OPT_NUMBER:
+ if (src_opts[i].val.number) {
+ ret = dp_opt_set_int(opts, i, src_opts[i].val.number);
+ } else if (src_opts[i].def_val.number) {
+ ret = dp_opt_set_int(opts, i, src_opts[i].def_val.number);
+ }
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to retrieve value for option (%s)\n",
+ opts[i].opt_name));
+ goto done;
+ }
+ DEBUG(6, ("Option %s has value %d\n",
+ opts[i].opt_name, opts[i].val.number));
+ break;
+
+ case DP_OPT_BOOL:
+ if (src_opts[i].val.boolean) {
+ ret = dp_opt_set_bool(opts, i, src_opts[i].val.boolean);
+ } else if (src_opts[i].def_val.boolean) {
+ ret = dp_opt_set_int(opts, i, src_opts[i].def_val.boolean);
+ }
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to retrieve value for option (%s)\n",
+ opts[i].opt_name));
+ goto done;
+ }
+ DEBUG(6, ("Option %s is %s\n",
+ opts[i].opt_name,
+ opts[i].val.boolean?"TRUE":"FALSE"));
+ break;
+ }
+ }
+
+ *_opts = opts;
+
+done:
+ if (ret != EOK) talloc_zfree(opts);
+ return ret;
+}
+
+static const char *dp_opt_type_to_string(enum dp_opt_type type)
+{
+ switch (type) {
+ case DP_OPT_STRING:
+ return "String";
+ case DP_OPT_BLOB:
+ return "Blob";
+ case DP_OPT_NUMBER:
+ return "Number";
+ case DP_OPT_BOOL:
+ return "Boolean";
+ }
+ return NULL;
+}
+
+/* Getters */
+const char *_dp_opt_get_cstring(struct dp_option *opts,
+ int id, const char *location)
+{
+ if (opts[id].type != DP_OPT_STRING) {
+ DEBUG(0, ("[%s] Requested type 'String' for option '%s'"
+ " but value is of type '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return NULL;
+ }
+ return opts[id].val.cstring;
+}
+
+char *_dp_opt_get_string(struct dp_option *opts,
+ int id, const char *location)
+{
+ if (opts[id].type != DP_OPT_STRING) {
+ DEBUG(0, ("[%s] Requested type 'String' for option '%s'"
+ " but value is of type '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return NULL;
+ }
+ return opts[id].val.string;
+}
+
+struct dp_opt_blob _dp_opt_get_blob(struct dp_option *opts,
+ int id, const char *location)
+{
+ struct dp_opt_blob null_blob = { NULL, 0 };
+ if (opts[id].type != DP_OPT_BLOB) {
+ DEBUG(0, ("[%s] Requested type 'Blob' for option '%s'"
+ " but value is of type '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return null_blob;
+ }
+ return opts[id].val.blob;
+}
+
+int _dp_opt_get_int(struct dp_option *opts,
+ int id, const char *location)
+{
+ if (opts[id].type != DP_OPT_NUMBER) {
+ DEBUG(0, ("[%s] Requested type 'Number' for option '%s'"
+ " but value is of type '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return 0;
+ }
+ return opts[id].val.number;
+}
+
+bool _dp_opt_get_bool(struct dp_option *opts,
+ int id, const char *location)
+{
+ if (opts[id].type != DP_OPT_BOOL) {
+ DEBUG(0, ("[%s] Requested type 'Boolean' for option '%s'"
+ " but value is of type '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return false;
+ }
+ return opts[id].val.boolean;
+}
+
+/* Setters */
+int _dp_opt_set_string(struct dp_option *opts, int id,
+ const char *s, const char *location)
+{
+ if (opts[id].type != DP_OPT_STRING) {
+ DEBUG(0, ("[%s] Requested type 'String' for option '%s'"
+ " but type is '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return EINVAL;
+ }
+
+ if (opts[id].val.string) {
+ talloc_zfree(opts[id].val.string);
+ }
+ if (s) {
+ opts[id].val.string = talloc_strdup(opts, s);
+ if (!opts[id].val.string) {
+ DEBUG(0, ("talloc_strdup() failed!\n"));
+ return ENOMEM;
+ }
+ }
+
+ return EOK;
+}
+
+int _dp_opt_set_blob(struct dp_option *opts, int id,
+ struct dp_opt_blob b, const char *location)
+{
+ if (opts[id].type != DP_OPT_BLOB) {
+ DEBUG(0, ("[%s] Requested type 'Blob' for option '%s'"
+ " but type is '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return EINVAL;
+ }
+
+ if (opts[id].val.blob.data) {
+ talloc_zfree(opts[id].val.blob.data);
+ opts[id].val.blob.length = 0;
+ }
+ if (b.data) {
+ opts[id].val.blob.data = talloc_memdup(opts, b.data, b.length);
+ if (!opts[id].val.blob.data) {
+ DEBUG(0, ("talloc_memdup() failed!\n"));
+ return ENOMEM;
+ }
+ }
+ opts[id].val.blob.length = b.length;
+
+ return EOK;
+}
+
+int _dp_opt_set_int(struct dp_option *opts, int id,
+ int i, const char *location)
+{
+ if (opts[id].type != DP_OPT_NUMBER) {
+ DEBUG(0, ("[%s] Requested type 'Number' for option '%s'"
+ " but type is '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return EINVAL;
+ }
+
+ opts[id].val.number = i;
+
+ return EOK;
+}
+
+int _dp_opt_set_bool(struct dp_option *opts, int id,
+ bool b, const char *location)
+{
+ if (opts[id].type != DP_OPT_BOOL) {
+ DEBUG(0, ("[%s] Requested type 'Boolean' for option '%s'"
+ " but type is '%s'!\n",
+ location, opts[id].opt_name,
+ dp_opt_type_to_string(opts[id].type)));
+ return EINVAL;
+ }
+
+ opts[id].val.boolean = b;
+
+ return EOK;
+}
+
diff --git a/src/providers/dp_auth_util.c b/src/providers/dp_auth_util.c
new file mode 100644
index 000000000..39cc0f60f
--- /dev/null
+++ b/src/providers/dp_auth_util.c
@@ -0,0 +1,414 @@
+/*
+ SSSD
+
+ Data Provider, auth utils
+
+ Copyright (C) Sumit Bose <sbose@redhat.com> 2009
+
+ 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 "data_provider.h"
+
+void pam_print_data(int l, struct pam_data *pd)
+{
+ DEBUG(l, ("command: %d\n", pd->cmd));
+ DEBUG(l, ("domain: %s\n", pd->domain));
+ DEBUG(l, ("user: %s\n", pd->user));
+ DEBUG(l, ("service: %s\n", pd->service));
+ DEBUG(l, ("tty: %s\n", pd->tty));
+ DEBUG(l, ("ruser: %s\n", pd->ruser));
+ DEBUG(l, ("rhost: %s\n", pd->rhost));
+ DEBUG(l, ("authtok type: %d\n", pd->authtok_type));
+ DEBUG(l, ("authtok size: %d\n", pd->authtok_size));
+ DEBUG(l, ("newauthtok type: %d\n", pd->newauthtok_type));
+ DEBUG(l, ("newauthtok size: %d\n", pd->newauthtok_size));
+ DEBUG(l, ("priv: %d\n", pd->priv));
+ DEBUG(l, ("pw_uid: %d\n", pd->pw_uid));
+ DEBUG(l, ("gr_gid: %d\n", pd->gr_gid));
+ DEBUG(l, ("cli_pid: %d\n", pd->cli_pid));
+}
+
+int pam_add_response(struct pam_data *pd, enum response_type type,
+ int len, const uint8_t *data)
+{
+ struct response_data *new;
+
+ new = talloc(pd, struct response_data);
+ if (new == NULL) return ENOMEM;
+
+ new->type = type;
+ new->len = len;
+ new->data = talloc_memdup(pd, data, len);
+ if (new->data == NULL) return ENOMEM;
+ new->next = pd->resp_list;
+ pd->resp_list = new;
+
+ return EOK;
+}
+
+bool dp_pack_pam_request(DBusMessage *msg, struct pam_data *pd)
+{
+ int ret;
+
+ if (pd->user == NULL || pd->domain == NULL) return false;
+ if (pd->service == NULL) pd->service = talloc_strdup(pd, "");
+ if (pd->tty == NULL) pd->tty = talloc_strdup(pd, "");
+ if (pd->ruser == NULL) pd->ruser = talloc_strdup(pd, "");
+ if (pd->rhost == NULL) pd->rhost = talloc_strdup(pd, "");
+
+
+ ret = dbus_message_append_args(msg,
+ DBUS_TYPE_INT32, &(pd->cmd),
+ DBUS_TYPE_STRING, &(pd->domain),
+ DBUS_TYPE_STRING, &(pd->user),
+ DBUS_TYPE_STRING, &(pd->service),
+ DBUS_TYPE_STRING, &(pd->tty),
+ DBUS_TYPE_STRING, &(pd->ruser),
+ DBUS_TYPE_STRING, &(pd->rhost),
+ DBUS_TYPE_UINT32, &(pd->authtok_type),
+ DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+ &(pd->authtok),
+ (pd->authtok_size),
+ DBUS_TYPE_UINT32, &(pd->newauthtok_type),
+ DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+ &(pd->newauthtok),
+ pd->newauthtok_size,
+ DBUS_TYPE_INT32, &(pd->priv),
+ DBUS_TYPE_INT32, &(pd->pw_uid),
+ DBUS_TYPE_INT32, &(pd->gr_gid),
+ DBUS_TYPE_UINT32, &(pd->cli_pid),
+ DBUS_TYPE_INVALID);
+
+ return ret;
+}
+
+bool dp_unpack_pam_request(DBusMessage *msg, struct pam_data *pd, DBusError *dbus_error)
+{
+ int ret;
+
+ ret = dbus_message_get_args(msg, dbus_error,
+ DBUS_TYPE_INT32, &(pd->cmd),
+ DBUS_TYPE_STRING, &(pd->domain),
+ DBUS_TYPE_STRING, &(pd->user),
+ DBUS_TYPE_STRING, &(pd->service),
+ DBUS_TYPE_STRING, &(pd->tty),
+ DBUS_TYPE_STRING, &(pd->ruser),
+ DBUS_TYPE_STRING, &(pd->rhost),
+ DBUS_TYPE_UINT32, &(pd->authtok_type),
+ DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+ &(pd->authtok),
+ &(pd->authtok_size),
+ DBUS_TYPE_UINT32, &(pd->newauthtok_type),
+ DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+ &(pd->newauthtok),
+ &(pd->newauthtok_size),
+ DBUS_TYPE_INT32, &(pd->priv),
+ DBUS_TYPE_INT32, &(pd->pw_uid),
+ DBUS_TYPE_INT32, &(pd->gr_gid),
+ DBUS_TYPE_UINT32, &(pd->cli_pid),
+ DBUS_TYPE_INVALID);
+
+ return ret;
+}
+
+bool dp_pack_pam_response(DBusMessage *msg, struct pam_data *pd)
+{
+ dbus_bool_t dbret;
+ struct response_data *resp;
+ DBusMessageIter iter;
+ DBusMessageIter array_iter;
+ DBusMessageIter struct_iter;
+ DBusMessageIter data_iter;
+
+ dbus_message_iter_init_append(msg, &iter);
+
+ /* Append the PAM status */
+ dbret = dbus_message_iter_append_basic(&iter,
+ DBUS_TYPE_UINT32, &(pd->pam_status));
+ if (!dbret) {
+ return false;
+ }
+
+ /* Append the domain */
+ dbret = dbus_message_iter_append_basic(&iter,
+ DBUS_TYPE_STRING, &(pd->domain));
+ if (!dbret) {
+ return false;
+ }
+
+ /* Create an array of response structures */
+ dbret = dbus_message_iter_open_container(&iter,
+ DBUS_TYPE_ARRAY, "(uay)",
+ &array_iter);
+ if (!dbret) {
+ return false;
+ }
+
+ resp = pd->resp_list;
+ while (resp != NULL) {
+ /* Create a DBUS struct */
+ dbret = dbus_message_iter_open_container(&array_iter,
+ DBUS_TYPE_STRUCT, NULL,
+ &struct_iter);
+ if (!dbret) {
+ return false;
+ }
+
+ /* Add the response type */
+ dbret = dbus_message_iter_append_basic(&struct_iter,
+ DBUS_TYPE_UINT32,
+ &(resp->type));
+ if (!dbret) {
+ return false;
+ }
+
+ /* Add the response message */
+ dbret = dbus_message_iter_open_container(&struct_iter,
+ DBUS_TYPE_ARRAY, "y",
+ &data_iter);
+ if (!dbret) {
+ return false;
+ }
+ dbret = dbus_message_iter_append_fixed_array(&data_iter,
+ DBUS_TYPE_BYTE, &(resp->data), resp->len);
+ if (!dbret) {
+ return false;
+ }
+ dbret = dbus_message_iter_close_container(&struct_iter, &data_iter);
+ if (!dbret) {
+ return false;
+ }
+
+ resp = resp->next;
+ dbret = dbus_message_iter_close_container(&array_iter, &struct_iter);
+ if (!dbret) {
+ return false;
+ }
+ }
+
+ /* Close the struct array */
+ dbret = dbus_message_iter_close_container(&iter, &array_iter);
+ if (!dbret) {
+ return false;
+ }
+
+ return true;
+}
+
+bool dp_unpack_pam_response(DBusMessage *msg, struct pam_data *pd, DBusError *dbus_error)
+{
+ DBusMessageIter iter;
+ DBusMessageIter array_iter;
+ DBusMessageIter struct_iter;
+ DBusMessageIter sub_iter;
+ int type;
+ int len;
+ const uint8_t *data;
+
+ if (!dbus_message_iter_init(msg, &iter)) {
+ DEBUG(1, ("pam response has no arguments.\n"));
+ return false;
+ }
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) {
+ DEBUG(1, ("pam response format error.\n"));
+ return false;
+ }
+ dbus_message_iter_get_basic(&iter, &(pd->pam_status));
+
+ if (!dbus_message_iter_next(&iter)) {
+ DEBUG(1, ("pam response has too few arguments.\n"));
+ return false;
+ }
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+ DEBUG(1, ("pam response format error.\n"));
+ return false;
+ }
+ dbus_message_iter_get_basic(&iter, &(pd->domain));
+
+ if (!dbus_message_iter_next(&iter)) {
+ DEBUG(1, ("pam response has too few arguments.\n"));
+ return false;
+ }
+
+ /* After this point will be an array of pam data */
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ DEBUG(1, ("pam response format error.\n"));
+ DEBUG(1, ("Type was %c\n", (char)dbus_message_iter_get_arg_type(&iter)));
+ return false;
+ }
+
+ if (dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
+ DEBUG(1, ("pam response format error.\n"));
+ return false;
+ }
+
+ dbus_message_iter_recurse(&iter, &array_iter);
+ while (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID) {
+ /* Read in a pam data struct */
+ if (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_STRUCT) {
+ DEBUG(1, ("pam response format error.\n"));
+ return false;
+ }
+
+ dbus_message_iter_recurse(&array_iter, &struct_iter);
+
+ /* PAM data struct contains a type and a byte-array of data */
+
+ /* Get the pam data type */
+ if (dbus_message_iter_get_arg_type(&struct_iter) != DBUS_TYPE_UINT32) {
+ DEBUG(1, ("pam response format error.\n"));
+ return false;
+ }
+ dbus_message_iter_get_basic(&struct_iter, &type);
+
+ if (!dbus_message_iter_next(&struct_iter)) {
+ DEBUG(1, ("pam response format error.\n"));
+ return false;
+ }
+
+ /* Get the byte array */
+ if (dbus_message_iter_get_arg_type(&struct_iter) != DBUS_TYPE_ARRAY ||
+ dbus_message_iter_get_element_type(&struct_iter) != DBUS_TYPE_BYTE) {
+ DEBUG(1, ("pam response format error.\n"));
+ return false;
+ }
+
+ dbus_message_iter_recurse(&struct_iter, &sub_iter);
+ dbus_message_iter_get_fixed_array(&sub_iter, &data, &len);
+
+ pam_add_response(pd, type, len, data);
+ dbus_message_iter_next(&array_iter);
+ }
+
+ return true;
+}
+
+static void id_callback(DBusPendingCall *pending, void *ptr)
+{
+ DBusMessage *reply;
+ DBusError dbus_error;
+ dbus_bool_t ret;
+ dbus_uint16_t dp_ver;
+ int type;
+
+ dbus_error_init(&dbus_error);
+
+ reply = dbus_pending_call_steal_reply(pending);
+ if (!reply) {
+ /* reply should never be null. This function shouldn't be called
+ * until reply is valid or timeout has occurred. If reply is NULL
+ * here, something is seriously wrong and we should bail out.
+ */
+ DEBUG(0, ("Severe error. A reply callback was called but no"
+ " reply was received and no timeout occurred\n"));
+
+ /* FIXME: Destroy this connection ? */
+ goto done;
+ }
+
+ type = dbus_message_get_type(reply);
+ switch (type) {
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ ret = dbus_message_get_args(reply, &dbus_error,
+ DBUS_TYPE_UINT16, &dp_ver,
+ DBUS_TYPE_INVALID);
+ if (!ret) {
+ DEBUG(1, ("Failed to parse message\n"));
+ if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error);
+ /* FIXME: Destroy this connection ? */
+ goto done;
+ }
+
+ DEBUG(4, ("Got id ack and version (%d) from DP\n", dp_ver));
+
+ break;
+
+ case DBUS_MESSAGE_TYPE_ERROR:
+ DEBUG(0,("The Monitor returned an error [%s]\n",
+ dbus_message_get_error_name(reply)));
+ /* Falling through to default intentionally*/
+ default:
+ /*
+ * Timeout or other error occurred or something
+ * unexpected happened.
+ * It doesn't matter which, because either way we
+ * know that this connection isn't trustworthy.
+ * We'll destroy it now.
+ */
+
+ /* FIXME: Destroy this connection ? */
+ break;
+ }
+
+done:
+ dbus_pending_call_unref(pending);
+ dbus_message_unref(reply);
+}
+
+int dp_common_send_id(struct sbus_connection *conn, uint16_t version,
+ const char *name, const char *domain)
+{
+ DBusPendingCall *pending_reply;
+ DBusConnection *dbus_conn;
+ DBusMessage *msg;
+ dbus_bool_t ret;
+
+ dbus_conn = sbus_get_connection(conn);
+
+ /* create the message */
+ msg = dbus_message_new_method_call(NULL,
+ DP_PATH,
+ DP_INTERFACE,
+ DP_METHOD_REGISTER);
+ if (msg == NULL) {
+ DEBUG(0, ("Out of memory?!\n"));
+ return ENOMEM;
+ }
+
+ DEBUG(4, ("Sending ID to DP: (%d,%s,%s)\n",
+ version, name, domain));
+
+ ret = dbus_message_append_args(msg,
+ DBUS_TYPE_UINT16, &version,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &domain,
+ DBUS_TYPE_INVALID);
+ if (!ret) {
+ DEBUG(1, ("Failed to build message\n"));
+ return EIO;
+ }
+
+ ret = dbus_connection_send_with_reply(dbus_conn, msg, &pending_reply,
+ 30000 /* TODO: set timeout */);
+ if (!ret || !pending_reply) {
+ /*
+ * Critical Failure
+ * We can't communicate on this connection
+ * We'll drop it using the default destructor.
+ */
+ DEBUG(0, ("D-BUS send failed.\n"));
+ dbus_message_unref(msg);
+ return EIO;
+ }
+
+ /* Set up the reply handler */
+ dbus_pending_call_set_notify(pending_reply, id_callback, NULL, NULL);
+ dbus_message_unref(msg);
+
+ return EOK;
+}
+
diff --git a/src/providers/dp_backend.h b/src/providers/dp_backend.h
new file mode 100644
index 000000000..f1069d0db
--- /dev/null
+++ b/src/providers/dp_backend.h
@@ -0,0 +1,142 @@
+/*
+ SSSD
+
+ Data Provider, private header file
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __DP_BACKEND_H__
+#define __DP_BACKEND_H__
+
+#include "providers/data_provider.h"
+#include "providers/fail_over.h"
+#include "db/sysdb.h"
+
+struct be_ctx;
+struct bet_ops;
+struct be_req;
+
+typedef int (*bet_init_fn_t)(TALLOC_CTX *, struct bet_ops **, void **);
+typedef void (*be_shutdown_fn)(void *);
+typedef void (*be_req_fn_t)(struct be_req *);
+typedef void (*be_async_callback_t)(struct be_req *, int, int, const char *);
+
+enum bet_type {
+ BET_NULL = 0,
+ BET_ID,
+ BET_AUTH,
+ BET_ACCESS,
+ BET_CHPASS,
+ BET_MAX
+};
+
+struct bet_data {
+ enum bet_type bet_type;
+ const char *option_name;
+ const char *mod_init_fn_name_fmt;
+};
+
+struct loaded_be {
+ char *be_name;
+ void *handle;
+};
+
+struct bet_info {
+ enum bet_type bet_type;
+ struct bet_ops *bet_ops;
+ void *pvt_bet_data;
+ char *mod_name;
+};
+
+struct be_offline_status {
+ time_t went_offline;
+ bool offline;
+};
+
+struct be_client {
+ struct be_ctx *bectx;
+ struct sbus_connection *conn;
+ struct tevent_timer *timeout;
+ bool initialized;
+};
+
+struct be_failover_ctx;
+
+struct be_ctx {
+ struct tevent_context *ev;
+ struct confdb_ctx *cdb;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+ const char *identity;
+ const char *conf_path;
+ struct be_failover_ctx *be_fo;
+
+ struct be_offline_status offstat;
+
+ struct sbus_connection *mon_conn;
+ struct sbus_connection *sbus_srv;
+
+ struct be_client *nss_cli;
+ struct be_client *pam_cli;
+
+ struct loaded_be loaded_be[BET_MAX];
+ struct bet_info bet_info[BET_MAX];
+};
+
+struct bet_ops {
+ be_req_fn_t check_online;
+ be_req_fn_t handler;
+ be_req_fn_t finalize;
+};
+
+struct be_req {
+ struct be_client *becli;
+ struct be_ctx *be_ctx;
+ void *req_data;
+
+ be_async_callback_t fn;
+ void *pvt;
+};
+
+struct be_acct_req {
+ int entry_type;
+ int attr_type;
+ int filter_type;
+ char *filter_value;
+};
+
+bool be_is_offline(struct be_ctx *ctx);
+void be_mark_offline(struct be_ctx *ctx);
+
+/* from data_provider_fo.c */
+typedef void (be_svc_callback_fn_t)(void *, struct fo_server *);
+
+int be_init_failover(struct be_ctx *ctx);
+int be_fo_add_service(struct be_ctx *ctx, const char *service_name);
+int be_fo_service_add_callback(TALLOC_CTX *memctx,
+ struct be_ctx *ctx, const char *service_name,
+ be_svc_callback_fn_t *fn, void *private_data);
+int be_fo_add_server(struct be_ctx *ctx, const char *service_name,
+ const char *server, int port, void *user_data);
+
+struct tevent_req *be_resolve_server_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct be_ctx *ctx,
+ const char *service_name);
+int be_resolve_server_recv(struct tevent_req *req, struct fo_server **srv);
+
+#endif /* __DP_BACKEND_H___ */
diff --git a/src/providers/dp_sbus.c b/src/providers/dp_sbus.c
new file mode 100644
index 000000000..f9dd28216
--- /dev/null
+++ b/src/providers/dp_sbus.c
@@ -0,0 +1,45 @@
+/*
+ SSSD
+
+ Data Provider Helpers
+
+ Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 2009
+
+ 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 "talloc.h"
+#include "tevent.h"
+#include "confdb/confdb.h"
+#include "sbus/sssd_dbus.h"
+#include "providers/data_provider.h"
+
+int dp_get_sbus_address(TALLOC_CTX *mem_ctx,
+ char **address, const char *domain_name)
+{
+ char *default_address;
+
+ *address = NULL;
+ default_address = talloc_asprintf(mem_ctx, "unix:path=%s/%s_%s",
+ PIPE_PATH, DATA_PROVIDER_PIPE,
+ domain_name);
+ if (default_address == NULL) {
+ return ENOMEM;
+ }
+
+ *address = default_address;
+ return EOK;
+}
+
diff --git a/src/providers/fail_over.c b/src/providers/fail_over.c
new file mode 100644
index 000000000..7560b89ee
--- /dev/null
+++ b/src/providers/fail_over.c
@@ -0,0 +1,651 @@
+/*
+ SSSD
+
+ Fail over helper functions.
+
+ Authors:
+ Martin Nagy <mnagy@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ 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 <sys/time.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <strings.h>
+#include <talloc.h>
+
+#include "util/dlinklist.h"
+#include "util/refcount.h"
+#include "util/util.h"
+#include "providers/fail_over.h"
+#include "resolv/async_resolv.h"
+
+#define STATUS_DIFF(p, now) ((now).tv_sec - (p)->last_status_change.tv_sec)
+#define SERVER_NAME(s) ((s)->common ? (s)->common->name : "(no name)")
+
+#define DEFAULT_PORT_STATUS PORT_NEUTRAL
+#define DEFAULT_SERVER_STATUS SERVER_NAME_NOT_RESOLVED
+
+struct fo_ctx {
+ struct fo_service *service_list;
+ struct server_common *server_common_list;
+
+ /* Settings. */
+ time_t retry_timeout;
+};
+
+struct fo_service {
+ struct fo_service *prev;
+ struct fo_service *next;
+
+ struct fo_ctx *ctx;
+ char *name;
+ struct fo_server *active_server;
+ struct fo_server *last_tried_server;
+ struct fo_server *server_list;
+};
+
+struct fo_server {
+ struct fo_server *prev;
+ struct fo_server *next;
+
+ void *user_data;
+ int port;
+ int port_status;
+ struct fo_service *service;
+ struct timeval last_status_change;
+ struct server_common *common;
+};
+
+struct server_common {
+ REFCOUNT_COMMON;
+
+ struct server_common *prev;
+ struct server_common *next;
+
+ char *name;
+ struct hostent *hostent;
+ struct resolve_service_request *request_list;
+ int server_status;
+ struct timeval last_status_change;
+};
+
+struct resolve_service_request {
+ struct resolve_service_request *prev;
+ struct resolve_service_request *next;
+
+ struct server_common *server_common;
+ struct tevent_req *req;
+};
+
+struct status {
+ int value;
+ struct timeval last_change;
+};
+
+struct fo_ctx *
+fo_context_init(TALLOC_CTX *mem_ctx, time_t retry_timeout)
+{
+ struct fo_ctx *ctx;
+
+ ctx = talloc_zero(mem_ctx, struct fo_ctx);
+ if (ctx == NULL) {
+ DEBUG(1, ("No memory\n"));
+ return NULL;
+ }
+
+ ctx->retry_timeout = retry_timeout;
+
+ DEBUG(3, ("Created new fail over context, retry timeout is %d\n",
+ retry_timeout));
+ return ctx;
+}
+
+static const char *
+str_port_status(enum port_status status)
+{
+ switch (status) {
+ case PORT_NEUTRAL:
+ return "neutral";
+ case PORT_WORKING:
+ return "working";
+ case PORT_NOT_WORKING:
+ return "not working";
+ }
+
+ return "unknown port status";
+}
+
+static const char *
+str_server_status(enum server_status status)
+{
+ switch (status) {
+ case SERVER_NAME_NOT_RESOLVED:
+ return "name not resolved";
+ case SERVER_RESOLVING_NAME:
+ return "resolving name";
+ case SERVER_NAME_RESOLVED:
+ return "name resolved";
+ case SERVER_WORKING:
+ return "working";
+ case SERVER_NOT_WORKING:
+ return "not working";
+ }
+
+ return "unknown server status";
+}
+
+/*
+ * This function will return the status of the server. If the status was
+ * last updated a long time ago, we will first reset the status.
+ */
+static enum server_status
+get_server_status(struct fo_server *server)
+{
+ struct timeval tv;
+ time_t timeout;
+
+ if (server->common == NULL)
+ return SERVER_NAME_RESOLVED;
+
+ DEBUG(7, ("Status of server '%s' is '%s'\n", SERVER_NAME(server),
+ str_server_status(server->common->server_status)));
+
+ timeout = server->service->ctx->retry_timeout;
+ if (timeout != 0 && server->common->server_status == SERVER_NOT_WORKING) {
+ gettimeofday(&tv, NULL);
+ if (STATUS_DIFF(server->common, tv) > timeout) {
+ DEBUG(4, ("Reseting the server status of '%s'\n",
+ SERVER_NAME(server)));
+ server->common->server_status = SERVER_NAME_NOT_RESOLVED;
+ server->last_status_change.tv_sec = 0;
+ }
+ }
+
+ return server->common->server_status;
+}
+
+/*
+ * This function will return the status of the service. If the status was
+ * last updated a long time ago, we will first reset the status.
+ */
+static enum port_status
+get_port_status(struct fo_server *server)
+{
+ struct timeval tv;
+ time_t timeout;
+
+ DEBUG(7, ("Port status of port %d for server '%s' is '%s'\n", server->port,
+ SERVER_NAME(server), str_port_status(server->port_status)));
+
+ timeout = server->service->ctx->retry_timeout;
+ if (timeout != 0 && server->port_status == PORT_NOT_WORKING) {
+ gettimeofday(&tv, NULL);
+ if (STATUS_DIFF(server, tv) > timeout) {
+ DEBUG(4, ("Reseting the status of port %d for server '%s'\n",
+ server->port, SERVER_NAME(server)));
+ server->port_status = PORT_NEUTRAL;
+ server->last_status_change.tv_sec = tv.tv_sec;
+ }
+ }
+
+ return server->port_status;
+}
+
+static int
+server_works(struct fo_server *server)
+{
+ if (get_server_status(server) == SERVER_NOT_WORKING)
+ return 0;
+
+ return 1;
+}
+
+static int
+service_works(struct fo_server *server)
+{
+ if (!server_works(server))
+ return 0;
+ if (get_port_status(server) == PORT_NOT_WORKING)
+ return 0;
+
+ return 1;
+}
+
+static int
+service_destructor(struct fo_service *service)
+{
+ DLIST_REMOVE(service->ctx->service_list, service);
+ return 0;
+}
+
+int
+fo_new_service(struct fo_ctx *ctx, const char *name,
+ struct fo_service **_service)
+{
+ struct fo_service *service;
+ int ret;
+
+ DEBUG(3, ("Creating new service '%s'\n", name));
+ ret = fo_get_service(ctx, name, &service);
+ if (ret == EOK) {
+ DEBUG(5, ("Service '%s' already exists\n", name));
+ if (_service) {
+ *_service = service;
+ }
+ return EEXIST;
+ } else if (ret != ENOENT) {
+ return ret;
+ }
+
+ service = talloc_zero(ctx, struct fo_service);
+ if (service == NULL)
+ return ENOMEM;
+
+ service->name = talloc_strdup(service, name);
+ if (service->name == NULL) {
+ talloc_free(service);
+ return ENOMEM;
+ }
+
+ service->ctx = ctx;
+ DLIST_ADD(ctx->service_list, service);
+
+ talloc_set_destructor(service, service_destructor);
+ if (_service) {
+ *_service = service;
+ }
+
+ return EOK;
+}
+
+int
+fo_get_service(struct fo_ctx *ctx, const char *name,
+ struct fo_service **_service)
+{
+ struct fo_service *service;
+
+ DLIST_FOR_EACH(service, ctx->service_list) {
+ if (!strcmp(name, service->name)) {
+ *_service = service;
+ return EOK;
+ }
+ }
+
+ return ENOENT;
+}
+
+static int
+get_server_common(TALLOC_CTX *mem_ctx, struct fo_ctx *ctx, const char *name,
+ struct server_common **_common)
+{
+ struct server_common *common;
+
+ DLIST_FOR_EACH(common, ctx->server_common_list) {
+ if (!strcmp(name, common->name)) {
+ *_common = rc_reference(mem_ctx, struct server_common, common);
+ if (_common == NULL)
+ return ENOMEM;
+ return EOK;
+ }
+ }
+
+ return ENOENT;
+}
+
+static struct server_common *
+create_server_common(TALLOC_CTX *mem_ctx, struct fo_ctx *ctx, const char *name)
+{
+ struct server_common *common;
+
+ common = rc_alloc(mem_ctx, struct server_common);
+ if (common == NULL)
+ return NULL;
+
+ common->name = talloc_strdup(common, name);
+ if (common->name == NULL) {
+ talloc_free(common);
+ return NULL;
+ }
+
+ common->prev = NULL;
+ common->next = NULL;
+ common->hostent = NULL;
+ common->request_list = NULL;
+ common->server_status = DEFAULT_SERVER_STATUS;
+ common->last_status_change.tv_sec = 0;
+ common->last_status_change.tv_usec = 0;
+
+ DLIST_ADD_END(ctx->server_common_list, common, struct server_common *);
+ return common;
+}
+
+int
+fo_add_server(struct fo_service *service, const char *name, int port,
+ void *user_data)
+{
+ struct fo_server *server;
+ int ret;
+
+ DEBUG(3, ("Adding new server '%s', to service '%s'\n",
+ name ? name : "(no name)", service->name));
+ DLIST_FOR_EACH(server, service->server_list) {
+ if (server->port != port || server->user_data != user_data)
+ continue;
+ if (name == NULL && server->common == NULL) {
+ return EEXIST;
+ } else if (name != NULL && server->common != NULL) {
+ if (!strcmp(name, server->common->name))
+ return EEXIST;
+ }
+ }
+
+ server = talloc_zero(service, struct fo_server);
+ if (server == NULL)
+ return ENOMEM;
+
+ server->port = port;
+ server->user_data = user_data;
+ server->service = service;
+ server->port_status = DEFAULT_PORT_STATUS;
+
+ if (name != NULL) {
+ ret = get_server_common(server, service->ctx, name, &server->common);
+ if (ret == ENOENT) {
+ server->common = create_server_common(server, service->ctx, name);
+ if (server->common == NULL) {
+ talloc_free(server);
+ return ENOMEM;
+ }
+ } else if (ret != EOK) {
+ talloc_free(server);
+ return ret;
+ }
+ }
+
+ DLIST_ADD_END(service->server_list, server, struct fo_server *);
+
+ return EOK;
+}
+
+static int
+get_first_server_entity(struct fo_service *service, struct fo_server **_server)
+{
+ struct fo_server *server;
+
+ /* If we already have a working server, use that one. */
+ server = service->active_server;
+ if (server != NULL) {
+ if (service_works(server)) {
+ goto done;
+ }
+ service->active_server = NULL;
+ }
+
+ /*
+ * Otherwise iterate through the server list.
+ */
+
+ /* First, try servers after the last one we tried. */
+ if (service->last_tried_server != NULL) {
+ DLIST_FOR_EACH(server, service->last_tried_server->next) {
+ if (service_works(server)) {
+ goto done;
+ }
+ }
+ }
+
+ /* If none were found, try at the start. */
+ DLIST_FOR_EACH(server, service->server_list) {
+ if (service_works(server)) {
+ goto done;
+ }
+ if (server == service->last_tried_server) {
+ break;
+ }
+ }
+
+ service->last_tried_server = NULL;
+ return ENOENT;
+
+done:
+ service->last_tried_server = server;
+ *_server = server;
+ return EOK;
+}
+
+static int
+resolve_service_request_destructor(struct resolve_service_request *request)
+{
+ DLIST_REMOVE(request->server_common->request_list, request);
+ return 0;
+}
+
+static int
+set_lookup_hook(struct fo_server *server, struct tevent_req *req)
+{
+ struct resolve_service_request *request;
+
+ request = talloc(req, struct resolve_service_request);
+ if (request == NULL) {
+ DEBUG(1, ("No memory\n"));
+ talloc_free(request);
+ return ENOMEM;
+ }
+ request->server_common = server->common;
+ request->req = req;
+ DLIST_ADD(server->common->request_list, request);
+ talloc_set_destructor(request, resolve_service_request_destructor);
+
+ return EOK;
+}
+
+/*******************************************************************
+ * Get server to connect to. *
+ *******************************************************************/
+
+struct resolve_service_state {
+ struct fo_server *server;
+};
+
+static void fo_resolve_service_done(struct tevent_req *subreq);
+
+struct tevent_req *
+fo_resolve_service_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct resolv_ctx *resolv, struct fo_service *service)
+{
+ int ret;
+ struct fo_server *server;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct resolve_service_state *state;
+
+ DEBUG(4, ("Trying to resolve service '%s'\n", service->name));
+ req = tevent_req_create(mem_ctx, &state, struct resolve_service_state);
+ if (req == NULL)
+ return NULL;
+
+ ret = get_first_server_entity(service, &server);
+ if (ret != EOK) {
+ DEBUG(1, ("No available servers for service '%s'\n", service->name));
+ goto done;
+ }
+
+ state->server = server;
+
+ if (server->common == NULL) {
+ /* This server doesn't have a name, we don't do name resolution. */
+ tevent_req_done(req);
+ tevent_req_post(req, ev);
+ return req;
+ }
+
+ switch (get_server_status(server)) {
+ case SERVER_NAME_NOT_RESOLVED: /* Request name resolution. */
+ subreq = resolv_gethostbyname_send(server->common, ev, resolv,
+ server->common->name);
+ if (subreq == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(subreq, fo_resolve_service_done, server->common);
+ fo_set_server_status(server, SERVER_RESOLVING_NAME);
+ /* FALLTHROUGH */
+ case SERVER_RESOLVING_NAME:
+ /* Name resolution is already under way. Just add ourselves into the
+ * waiting queue so we get notified after the operation is finished. */
+ ret = set_lookup_hook(server, req);
+ if (ret != EOK)
+ goto done;
+ break;
+ default: /* The name is already resolved. Return immediately. */
+ tevent_req_done(req);
+ tevent_req_post(req, ev);
+ break;
+ }
+
+done:
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void set_server_common_status(struct server_common *common,
+ enum server_status status);
+
+static void
+fo_resolve_service_done(struct tevent_req *subreq)
+{
+ int resolv_status;
+ struct resolve_service_request *request;
+ struct server_common *common;
+ int ret;
+
+ common = tevent_req_callback_data(subreq, struct server_common);
+
+ if (common->hostent != NULL) {
+ talloc_zfree(common->hostent);
+ }
+
+ ret = resolv_gethostbyname_recv(subreq, common,
+ &resolv_status, NULL, &common->hostent);
+ talloc_free(subreq);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to resolve server '%s': %s\n", common->name,
+ resolv_strerror(resolv_status)));
+ set_server_common_status(common, SERVER_NOT_WORKING);
+ } else {
+ set_server_common_status(common, SERVER_NAME_RESOLVED);
+ }
+
+ /* Take care of all requests for this server. */
+ while ((request = common->request_list) != NULL) {
+ DLIST_REMOVE(common->request_list, request);
+ if (resolv_status) {
+ /* FIXME FIXME: resolv_status is an ARES error.
+ * but any caller will expect classic error codes.
+ * also the send() function may return ENOENT, so this mix
+ * IS explosive (ENOENT = 2 = ARES_EFORMER) */
+ tevent_req_error(request->req, resolv_status);
+ } else {
+ tevent_req_done(request->req);
+ }
+ }
+}
+
+int
+fo_resolve_service_recv(struct tevent_req *req, struct fo_server **server)
+{
+ struct resolve_service_state *state;
+
+ state = tevent_req_data(req, struct resolve_service_state);
+
+ /* always return the server if asked for, otherwise the caller
+ * cannot mark it as faulty in case we return an error */
+ if (server)
+ *server = state->server;
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+static void
+set_server_common_status(struct server_common *common,
+ enum server_status status)
+{
+ DEBUG(4, ("Marking server '%s' as '%s'\n", common->name,
+ str_server_status(status)));
+
+ common->server_status = status;
+ gettimeofday(&common->last_status_change, NULL);
+}
+
+void
+fo_set_server_status(struct fo_server *server, enum server_status status)
+{
+ if (server->common == NULL) {
+ DEBUG(1, ("Bug: Trying to set server status of a name-less server\n"));
+ return;
+ }
+
+ set_server_common_status(server->common, status);
+}
+
+void
+fo_set_port_status(struct fo_server *server, enum port_status status)
+{
+ DEBUG(4, ("Marking port %d of server '%s' as '%s'\n", server->port,
+ SERVER_NAME(server), str_port_status(status)));
+
+ server->port_status = status;
+ gettimeofday(&server->last_status_change, NULL);
+ if (status == PORT_WORKING) {
+ fo_set_server_status(server, SERVER_WORKING);
+ server->service->active_server = server;
+ }
+}
+
+void *
+fo_get_server_user_data(struct fo_server *server)
+{
+ return server->user_data;
+}
+
+int
+fo_get_server_port(struct fo_server *server)
+{
+ return server->port;
+}
+
+const char *fo_get_server_name(struct fo_server *server)
+{
+ return server->common->name;
+}
+
+struct hostent *
+fo_get_server_hostent(struct fo_server *server)
+{
+ if (server->common == NULL) {
+ DEBUG(1, ("Bug: Trying to get hostent from a name-less server\n"));
+ return NULL;
+ }
+ return server->common->hostent;
+}
diff --git a/src/providers/fail_over.h b/src/providers/fail_over.h
new file mode 100644
index 000000000..e581fbaf5
--- /dev/null
+++ b/src/providers/fail_over.h
@@ -0,0 +1,108 @@
+#ifndef __FAIL_OVER_H__
+#define __FAIL_OVER_H__
+
+#include <stdbool.h>
+#include <talloc.h>
+
+/* Some forward declarations that don't have to do anything with fail over. */
+struct hostent;
+struct resolv_ctx;
+struct tevent_context;
+struct tevent_req;
+
+enum port_status {
+ PORT_NEUTRAL, /* We didn't try this port yet. */
+ PORT_WORKING, /* This port was reported to work. */
+ PORT_NOT_WORKING /* This port was reported to not work. */
+};
+
+enum server_status {
+ SERVER_NAME_NOT_RESOLVED, /* We didn't yet resolved the host name. */
+ SERVER_RESOLVING_NAME, /* Name resolving is in progress. */
+ SERVER_NAME_RESOLVED, /* We resolved the host name but didn't try to connect. */
+ SERVER_WORKING, /* We successfully connected to the server. */
+ SERVER_NOT_WORKING /* We tried and failed to connect to the server. */
+};
+
+struct fo_ctx;
+struct fo_service;
+struct fo_server;
+
+/*
+ * Create a new fail over context. The 'retry_timeout' argument specifies the
+ * duration in seconds of how long a server or port will be considered
+ * non-working after being marked as such.
+ */
+struct fo_ctx *fo_context_init(TALLOC_CTX *mem_ctx,
+ time_t retry_timeout);
+
+/*
+ * Create a new service structure for 'ctx', saving it to the location pointed
+ * to by '_service'. The needed memory will be allocated from 'ctx'.
+ * Service name will be set to 'name'.
+ */
+int fo_new_service(struct fo_ctx *ctx,
+ const char *name,
+ struct fo_service **_service);
+
+/*
+ * Look up service named 'name' from the 'ctx' service list. Target of
+ * '_service' will be set to the service if it was found.
+ */
+int fo_get_service(struct fo_ctx *ctx,
+ const char *name,
+ struct fo_service **_service);
+
+/*
+ * Adds a server 'name' to the 'service'. Port 'port' will be used for
+ * connection. If 'name' is NULL, no server resolution will be done.
+ */
+int fo_add_server(struct fo_service *service,
+ const char *name,
+ int port,
+ void *user_data);
+
+/*
+ * Request the first server from the service's list of servers. It is only
+ * considered if it is not marked as not working (or the retry interval already
+ * passed). If the server address wasn't resolved yet, it will be done.
+ */
+struct tevent_req *fo_resolve_service_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *resolv,
+ struct fo_service *service);
+
+int fo_resolve_service_recv(struct tevent_req *req,
+ struct fo_server **server);
+
+/*
+ * Set feedback about 'server'. Caller should use this to indicate a problem
+ * with the server itself, not only with the service on that server. This
+ * should be used, for example, when the IP address of the server can't be
+ * reached. This setting can affect other services as well, since they can
+ * share the same server.
+ */
+void fo_set_server_status(struct fo_server *server,
+ enum server_status status);
+
+/*
+ * Set feedback about the port status. This function should be used when
+ * the server itself is working but the service is not. When status is set
+ * to PORT_WORKING, 'server' is also marked as an "active server" for its
+ * service. When the next fo_resolve_service_send() function is called, this
+ * server will be preferred. This will hold as long as it is not marked as
+ * not-working.
+ */
+void fo_set_port_status(struct fo_server *server,
+ enum port_status status);
+
+
+void *fo_get_server_user_data(struct fo_server *server);
+
+int fo_get_server_port(struct fo_server *server);
+
+const char *fo_get_server_name(struct fo_server *server);
+
+struct hostent *fo_get_server_hostent(struct fo_server *server);
+
+#endif /* !__FAIL_OVER_H__ */
diff --git a/src/providers/ipa/ipa_access.c b/src/providers/ipa/ipa_access.c
new file mode 100644
index 000000000..7dfe1fd90
--- /dev/null
+++ b/src/providers/ipa/ipa_access.c
@@ -0,0 +1,1823 @@
+/*
+ SSSD
+
+ IPA Backend Module -- Access control
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 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 <sys/param.h>
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "providers/ldap/sdap_async.h"
+#include "providers/ipa/ipa_common.h"
+#include "providers/ipa/ipa_access.h"
+#include "providers/ipa/ipa_timerules.h"
+
+#define IPA_HOST_MEMBEROF "memberOf"
+#define IPA_HOST_SERVERHOSTNAME "serverHostName"
+#define IPA_HOST_FQDN "fqdn"
+#define IPA_ACCESS_RULE_TYPE "accessRuleType"
+#define IPA_MEMBER_USER "memberUser"
+#define IPA_USER_CATEGORY "userCategory"
+#define IPA_SERVICE_NAME "serviceName"
+#define IPA_SOURCE_HOST "sourceHost"
+#define IPA_SOURCE_HOST_CATEGORY "sourceHostCategory"
+#define IPA_EXTERNAL_HOST "externalHost"
+#define IPA_ACCESS_TIME "accessTime"
+#define IPA_UNIQUE_ID "ipauniqueid"
+#define IPA_ENABLED_FLAG "ipaenabledflag"
+#define IPA_MEMBER_HOST "memberHost"
+#define IPA_HOST_CATEGORY "hostCategory"
+#define IPA_CN "cn"
+
+#define IPA_HOST_BASE_TMPL "cn=computers,cn=accounts,dc=%s"
+#define IPA_HBAC_BASE_TMPL "cn=hbac,dc=%s"
+
+#define SYSDB_HBAC_BASE_TMPL "cn=hbac,"SYSDB_TMPL_CUSTOM_BASE
+
+#define HBAC_RULES_SUBDIR "hbac_rules"
+#define HBAC_HOSTS_SUBDIR "hbac_hosts"
+
+static errno_t msgs2attrs_array(TALLOC_CTX *mem_ctx, size_t count,
+ struct ldb_message **msgs,
+ struct sysdb_attrs ***attrs)
+{
+ int i;
+ struct sysdb_attrs **a;
+
+ a = talloc_array(mem_ctx, struct sysdb_attrs *, count);
+ if (a == NULL) {
+ DEBUG(1, ("talloc_array failed.\n"));
+ return ENOMEM;
+ }
+
+ for (i = 0; i < count; i++) {
+ a[i] = talloc(a, struct sysdb_attrs);
+ if (a[i] == NULL) {
+ DEBUG(1, ("talloc_array failed.\n"));
+ talloc_free(a);
+ return ENOMEM;
+ }
+ a[i]->num = msgs[i]->num_elements;
+ a[i]->a = talloc_steal(a[i], msgs[i]->elements);
+ }
+
+ *attrs = a;
+
+ return EOK;
+}
+
+static void ipa_access_reply(struct be_req *be_req, int pam_status)
+{
+ struct pam_data *pd;
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+ pd->pam_status = pam_status;
+
+ if (pam_status == PAM_SUCCESS) {
+ be_req->fn(be_req, DP_ERR_OK, pam_status, NULL);
+ } else {
+ be_req->fn(be_req, DP_ERR_FATAL, pam_status, NULL);
+ }
+}
+
+struct hbac_get_user_info_state {
+ struct tevent_context *ev;
+ struct be_ctx *be_ctx;;
+ struct sysdb_handle *handle;
+
+ const char *user;
+ const char *user_orig_dn;
+ struct ldb_dn *user_dn;
+ size_t groups_count;
+ const char **groups;
+};
+
+static void search_user_done(struct tevent_req *subreq);
+static void search_groups_done(struct tevent_req *subreq);
+
+struct tevent_req *hbac_get_user_info_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct be_ctx *be_ctx,
+ const char *user)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct hbac_get_user_info_state *state;
+ int ret;
+ const char **attrs;
+
+ req = tevent_req_create(memctx, &state, struct hbac_get_user_info_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->be_ctx = be_ctx;
+ state->handle = NULL;
+ state->user = user;
+ state->user_orig_dn = NULL;
+ state->user_dn = NULL;
+ state->groups_count = 0;
+ state->groups = NULL;
+
+ attrs = talloc_array(state, const char *, 2);
+ if (attrs == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ attrs[0] = SYSDB_ORIG_DN;
+ attrs[1] = NULL;
+
+ subreq = sysdb_search_user_by_name_send(state, ev, be_ctx->sysdb, NULL,
+ be_ctx->domain, user, attrs);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_search_user_by_name_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, search_user_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void search_user_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_user_info_state *state = tevent_req_data(req,
+ struct hbac_get_user_info_state);
+ int ret;
+ const char **attrs;
+ const char *dummy;
+ struct ldb_message *user_msg;
+
+
+ ret = sysdb_search_user_recv(subreq, state, &user_msg);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(9, ("Found user info for user [%s].\n", state->user));
+ state->user_dn = talloc_steal(state, user_msg->dn);
+ dummy = ldb_msg_find_attr_as_string(user_msg, SYSDB_ORIG_DN, NULL);
+ if (dummy == NULL) {
+ DEBUG(1, ("Original DN of user [%s] not available.\n", state->user));
+ ret = EINVAL;
+ goto failed;
+ }
+ state->user_orig_dn = talloc_strdup(state, dummy);
+ if (state->user_dn == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ ret = ENOMEM;
+ goto failed;
+ }
+ DEBUG(9, ("Found original DN [%s] for user [%s].\n", state->user_orig_dn,
+ state->user));
+
+ attrs = talloc_array(state, const char *, 2);
+ if (attrs == NULL) {
+ DEBUG(1, ("talloc_array failed.\n"));
+ ret = ENOMEM;
+ goto failed;
+ }
+ attrs[0] = SYSDB_ORIG_DN;
+ attrs[1] = NULL;
+
+ subreq = sysdb_asq_search_send(state, state->ev, state->be_ctx->sysdb, NULL,
+ state->be_ctx->domain, state->user_dn, NULL,
+ SYSDB_MEMBEROF, attrs);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_asq_search_send failed.\n"));
+ ret = ENOMEM;
+ goto failed;
+ }
+
+ tevent_req_set_callback(subreq, search_groups_done, req);
+ return;
+
+failed:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void search_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_user_info_state *state = tevent_req_data(req,
+ struct hbac_get_user_info_state);
+ int ret;
+ int i;
+ struct ldb_message **msg;
+
+ ret = sysdb_asq_search_recv(subreq, state, &state->groups_count, &msg);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->groups_count == 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ state->groups = talloc_array(state, const char *, state->groups_count);
+ if (state->groups == NULL) {
+ DEBUG(1, ("talloc_groups failed.\n"));
+ ret = ENOMEM;
+ goto failed;
+ }
+
+ for(i = 0; i < state->groups_count; i++) {
+ if (msg[i]->num_elements != 1) {
+ DEBUG(1, ("Unexpected number of elements.\n"));
+ ret = EINVAL;
+ goto failed;
+ }
+
+ if (msg[i]->elements[0].num_values != 1) {
+ DEBUG(1, ("Unexpected number of values.\n"));
+ ret = EINVAL;
+ goto failed;
+ }
+
+ state->groups[i] = talloc_strndup(state->groups,
+ (const char *) msg[i]->elements[0].values[0].data,
+ msg[i]->elements[0].values[0].length);
+ if (state->groups[i] == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ ret = ENOMEM;
+ goto failed;
+ }
+
+ DEBUG(9, ("Found group [%s].\n", state->groups[i]));
+ }
+
+ tevent_req_done(req);
+ return;
+
+failed:
+ talloc_free(state->groups);
+ tevent_req_error(req, ret);
+ return;
+}
+
+static int hbac_get_user_info_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+ const char **user_dn, size_t *groups_count,
+ const char ***groups)
+{
+ struct hbac_get_user_info_state *state = tevent_req_data(req,
+ struct hbac_get_user_info_state);
+ int i;
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *user_dn = talloc_steal(memctx, state->user_orig_dn);
+ *groups_count = state->groups_count;
+ for (i = 0; i < state->groups_count; i++) {
+ talloc_steal(memctx, state->groups[i]);
+ }
+ *groups = talloc_steal(memctx, state->groups);
+
+ return EOK;
+}
+
+
+struct hbac_get_host_info_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *sdap_ctx;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+ bool offline;
+
+ char *host_filter;
+ char *host_search_base;
+ const char **host_attrs;
+
+ struct sysdb_attrs **host_reply_list;
+ size_t host_reply_count;
+ size_t current_item;
+ struct hbac_host_info **hbac_host_info;
+};
+
+static void hbac_get_host_info_connect_done(struct tevent_req *subreq);
+static void hbac_get_host_memberof_done(struct tevent_req *subreq);
+static void hbac_get_host_info_sysdb_transaction_started(struct tevent_req *subreq);
+static void hbac_get_host_info_store_prepare(struct tevent_req *req);
+static void hbac_get_host_info_store_done(struct tevent_req *subreq);
+
+static struct tevent_req *hbac_get_host_info_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ bool offline,
+ struct sdap_id_ctx *sdap_ctx,
+ struct sysdb_ctx *sysdb,
+ const char *ipa_domain,
+ const char **hostnames)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct hbac_get_host_info_state *state;
+ int ret;
+ int i;
+
+ if (hostnames == NULL || ipa_domain == NULL) {
+ DEBUG(1, ("Missing hostnames or domain.\n"));
+ return NULL;
+ }
+
+ req = tevent_req_create(memctx, &state, struct hbac_get_host_info_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->sdap_ctx = sdap_ctx;
+ state->sysdb = sysdb;
+ state->handle = NULL;
+ state->offline = offline;
+
+ state->host_reply_list = NULL;
+ state->host_reply_count = 0;
+ state->current_item = 0;
+ state->hbac_host_info = NULL;
+
+ state->host_filter = talloc_asprintf(state, "(|");
+ if (state->host_filter == NULL) {
+ DEBUG(1, ("Failed to create filter.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ for (i = 0; hostnames[i] != NULL; i++) {
+ state->host_filter = talloc_asprintf_append(state->host_filter,
+ "(&(objectclass=ipaHost)"
+ "(|(fqdn=%s)(serverhostname=%s)))",
+ hostnames[i], hostnames[i]);
+ if (state->host_filter == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+ state->host_filter = talloc_asprintf_append(state->host_filter, ")");
+ if (state->host_filter == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ state->host_search_base = talloc_asprintf(state, IPA_HOST_BASE_TMPL,
+ ipa_domain);
+ if (state->host_search_base == NULL) {
+ DEBUG(1, ("Failed to create host search base.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ state->host_attrs = talloc_array(state, const char *, 7);
+ if (state->host_attrs == NULL) {
+ DEBUG(1, ("Failed to allocate host attribute list.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ state->host_attrs[0] = IPA_HOST_MEMBEROF;
+ state->host_attrs[1] = IPA_HOST_SERVERHOSTNAME;
+ state->host_attrs[2] = IPA_HOST_FQDN;
+ state->host_attrs[3] = "objectClass";
+ state->host_attrs[4] = SYSDB_ORIG_DN;
+ state->host_attrs[5] = SYSDB_ORIG_MEMBEROF;
+ state->host_attrs[6] = NULL;
+
+ if (offline) {
+ subreq = sysdb_search_custom_send(state, state->ev, state->sysdb, NULL,
+ state->sdap_ctx->be->domain,
+ state->host_filter, HBAC_HOSTS_SUBDIR,
+ state->host_attrs);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_search_custom_send.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_get_host_memberof_done, req);
+
+ return req;
+ }
+
+ if (sdap_ctx->gsh == NULL || ! sdap_ctx->gsh->connected) {
+ if (sdap_ctx->gsh != NULL) {
+ talloc_zfree(sdap_ctx->gsh);
+ }
+
+ subreq = sdap_cli_connect_send(state, ev, sdap_ctx->opts,
+ sdap_ctx->be, sdap_ctx->service, NULL);
+ if (!subreq) {
+ DEBUG(1, ("sdap_cli_connect_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_get_host_info_connect_done, req);
+
+ return req;
+ }
+
+ subreq = sdap_get_generic_send(state, state->ev,
+ state->sdap_ctx->opts,
+ state->sdap_ctx->gsh,
+ state->host_search_base,
+ LDAP_SCOPE_SUB,
+ state->host_filter,
+ state->host_attrs,
+ NULL, 0);
+
+ if (subreq == NULL) {
+ DEBUG(1, ("sdap_get_generic_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_get_host_memberof_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void hbac_get_host_info_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_host_info_state *state = tevent_req_data(req,
+ struct hbac_get_host_info_state);
+ int ret;
+
+ ret = sdap_cli_connect_recv(subreq, state->sdap_ctx, &state->sdap_ctx->gsh,
+ NULL);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_get_generic_send(state, state->ev,
+ state->sdap_ctx->opts,
+ state->sdap_ctx->gsh,
+ state->host_search_base,
+ LDAP_SCOPE_SUB,
+ state->host_filter,
+ state->host_attrs,
+ NULL, 0);
+
+ if (subreq == NULL) {
+ DEBUG(1, ("sdap_get_generic_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_get_host_memberof_done, req);
+
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void hbac_get_host_memberof_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_host_info_state *state = tevent_req_data(req,
+ struct hbac_get_host_info_state);
+ int ret;
+ int i;
+ int v;
+ struct ldb_message_element *el;
+ struct hbac_host_info **hhi;
+ struct ldb_message **msgs;
+
+ if (state->offline) {
+ ret = sysdb_search_custom_recv(subreq, state, &state->host_reply_count,
+ &msgs);
+ } else {
+ ret = sdap_get_generic_recv(subreq, state, &state->host_reply_count,
+ &state->host_reply_list);
+ }
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->host_reply_count == 0) {
+ DEBUG(1, ("No hosts not found in IPA server.\n"));
+ ret = ENOENT;
+ goto fail;
+ }
+
+ if (state->offline) {
+ ret = msgs2attrs_array(state, state->host_reply_count, msgs,
+ &state->host_reply_list);
+ talloc_zfree(msgs);
+ if (ret != EOK) {
+ DEBUG(1, ("msgs2attrs_array failed.\n"));
+ goto fail;
+ }
+ }
+
+ hhi = talloc_array(state, struct hbac_host_info *, state->host_reply_count + 1);
+ if (hhi == NULL) {
+ DEBUG(1, ("talloc_array failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ memset(hhi, 0,
+ sizeof(struct hbac_host_info *) * (state->host_reply_count + 1));
+
+ for (i = 0; i < state->host_reply_count; i++) {
+ hhi[i] = talloc_zero(hhi, struct hbac_host_info);
+ if (hhi[i] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(state->host_reply_list[i], SYSDB_ORIG_DN, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+ DEBUG(9, ("OriginalDN: [%.*s].\n", el->values[0].length,
+ (char *)el->values[0].data));
+ hhi[i]->dn = talloc_strndup(hhi, (char *)el->values[0].data,
+ el->values[0].length);
+ if (hhi[i]->dn == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(state->host_reply_list[i],
+ IPA_HOST_SERVERHOSTNAME, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+ DEBUG(9, ("ServerHostName: [%.*s].\n", el->values[0].length,
+ (char *)el->values[0].data));
+ hhi[i]->serverhostname = talloc_strndup(hhi, (char *)el->values[0].data,
+ el->values[0].length);
+ if (hhi[i]->serverhostname == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(state->host_reply_list[i],
+ IPA_HOST_FQDN, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+ DEBUG(9, ("FQDN: [%.*s].\n", el->values[0].length,
+ (char *)el->values[0].data));
+ hhi[i]->fqdn = talloc_strndup(hhi, (char *)el->values[0].data,
+ el->values[0].length);
+ if (hhi[i]->fqdn == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(state->host_reply_list[i],
+ state->offline ? SYSDB_ORIG_MEMBEROF :
+ IPA_HOST_MEMBEROF,
+ &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ goto fail;
+ }
+
+ hhi[i]->memberof = talloc_array(hhi, const char *, el->num_values + 1);
+ if (hhi[i]->memberof == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ memset(hhi[i]->memberof, 0,
+ sizeof(const char *) * (el->num_values + 1));
+
+ for(v = 0; v < el->num_values; v++) {
+ DEBUG(9, ("%s: [%.*s].\n", IPA_HOST_MEMBEROF, el->values[v].length,
+ (const char *)el->values[v].data));
+ hhi[i]->memberof[v] = talloc_strndup(hhi,
+ (const char *)el->values[v].data,
+ el->values[v].length);
+ if (hhi[i]->memberof[v] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+ }
+
+ state->hbac_host_info = hhi;
+
+ if (state->offline) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_transaction_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, hbac_get_host_info_sysdb_transaction_started, req);
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void hbac_get_host_info_sysdb_transaction_started(
+ struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_host_info_state *state = tevent_req_data(req,
+ struct hbac_get_host_info_state);
+ int ret;
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->current_item = 0;
+ hbac_get_host_info_store_prepare(req);
+ return;
+}
+
+static void hbac_get_host_info_store_prepare(struct tevent_req *req)
+{
+ struct hbac_get_host_info_state *state = tevent_req_data(req,
+ struct hbac_get_host_info_state);
+ int ret;
+ char *object_name;
+ struct ldb_message_element *el;
+ struct tevent_req *subreq;
+
+ if (state->current_item < state->host_reply_count) {
+ ret = sysdb_attrs_get_el(state->host_reply_list[state->current_item],
+ IPA_HOST_FQDN, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+ object_name = talloc_strndup(state, (const char *)el->values[0].data,
+ el->values[0].length);
+ if (object_name == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ DEBUG(9, ("Fqdn [%s].\n", object_name));
+
+
+ ret = sysdb_attrs_replace_name(
+ state->host_reply_list[state->current_item],
+ IPA_HOST_MEMBEROF, SYSDB_ORIG_MEMBEROF);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_replace_name failed.\n"));
+ goto fail;
+ }
+
+ subreq = sysdb_store_custom_send(state, state->ev,
+ state->handle,
+ state->sdap_ctx->be->domain,
+ object_name,
+ HBAC_HOSTS_SUBDIR,
+ state->host_reply_list[state->current_item]);
+
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_store_custom_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_get_host_info_store_done, req);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_transaction_commit_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void hbac_get_host_info_store_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_host_info_state *state = tevent_req_data(req,
+ struct hbac_get_host_info_state);
+ int ret;
+
+ ret = sysdb_store_custom_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->current_item++;
+ hbac_get_host_info_store_prepare(req);
+}
+
+static int hbac_get_host_info_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+ struct hbac_host_info ***hhi)
+{
+ struct hbac_get_host_info_state *state = tevent_req_data(req,
+ struct hbac_get_host_info_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *hhi = talloc_steal(memctx, state->hbac_host_info);
+ return EOK;
+}
+
+
+struct hbac_get_rules_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *sdap_ctx;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+ bool offline;
+
+ const char *host_dn;
+ const char **memberof;
+ char *hbac_filter;
+ char *hbac_search_base;
+ const char **hbac_attrs;
+
+ struct ldb_message *old_rules;
+ struct sysdb_attrs **hbac_reply_list;
+ size_t hbac_reply_count;
+ int current_item;
+};
+
+static void hbac_get_rules_connect_done(struct tevent_req *subreq);
+static void hbac_rule_get_done(struct tevent_req *subreq);
+static void hbac_rule_sysdb_transaction_started(struct tevent_req *subreq);
+static void hbac_rule_sysdb_delete_done(struct tevent_req *subreq);
+static void hbac_rule_store_prepare(struct tevent_req *req);
+static void hbac_rule_store_done(struct tevent_req *subreq);
+
+static struct tevent_req *hbac_get_rules_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ bool offline,
+ struct sdap_id_ctx *sdap_ctx,
+ struct sysdb_ctx *sysdb,
+ const char *ipa_domain,
+ const char *host_dn,
+ const char **memberof)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct hbac_get_rules_state *state;
+ int ret;
+ int i;
+
+ if (host_dn == NULL || ipa_domain == NULL) {
+ DEBUG(1, ("Missing host_dn or domain.\n"));
+ return NULL;
+ }
+
+ req = tevent_req_create(memctx, &state, struct hbac_get_rules_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->offline = offline;
+ state->sdap_ctx = sdap_ctx;
+ state->sysdb = sysdb;
+ state->handle = NULL;
+ state->host_dn = host_dn;
+ state->memberof = memberof;
+
+ state->old_rules = NULL;
+ state->hbac_reply_list = NULL;
+ state->hbac_reply_count = 0;
+ state->current_item = 0;
+
+ state->hbac_search_base = talloc_asprintf(state, IPA_HBAC_BASE_TMPL,
+ ipa_domain);
+ if (state->hbac_search_base == NULL) {
+ DEBUG(1, ("Failed to create HBAC search base.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ state->hbac_attrs = talloc_array(state, const char *, 16);
+ if (state->hbac_attrs == NULL) {
+ DEBUG(1, ("Failed to allocate HBAC attribute list.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ state->hbac_attrs[0] = IPA_ACCESS_RULE_TYPE;
+ state->hbac_attrs[1] = IPA_MEMBER_USER;
+ state->hbac_attrs[2] = IPA_USER_CATEGORY;
+ state->hbac_attrs[3] = IPA_SERVICE_NAME;
+ state->hbac_attrs[4] = IPA_SOURCE_HOST;
+ state->hbac_attrs[5] = IPA_SOURCE_HOST_CATEGORY;
+ state->hbac_attrs[6] = IPA_EXTERNAL_HOST;
+ state->hbac_attrs[7] = IPA_ACCESS_TIME;
+ state->hbac_attrs[8] = IPA_UNIQUE_ID;
+ state->hbac_attrs[9] = IPA_ENABLED_FLAG;
+ state->hbac_attrs[10] = IPA_CN;
+ state->hbac_attrs[11] = "objectclass";
+ state->hbac_attrs[12] = IPA_MEMBER_HOST;
+ state->hbac_attrs[13] = IPA_HOST_CATEGORY;
+ state->hbac_attrs[14] = SYSDB_ORIG_DN;
+ state->hbac_attrs[15] = NULL;
+
+ state->hbac_filter = talloc_asprintf(state,
+ "(&(objectclass=ipaHBACRule)"
+ "(|(%s=%s)(%s=%s)",
+ IPA_HOST_CATEGORY, "all",
+ IPA_MEMBER_HOST, host_dn);
+ if (state->hbac_filter == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ for (i = 0; memberof[i] != NULL; i++) {
+ state->hbac_filter = talloc_asprintf_append(state->hbac_filter,
+ "(%s=%s)",
+ IPA_MEMBER_HOST,
+ memberof[i]);
+ if (state->hbac_filter == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+ state->hbac_filter = talloc_asprintf_append(state->hbac_filter, "))");
+ if (state->hbac_filter == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ DEBUG(9, ("HBAC rule filter: [%s].\n", state->hbac_filter));
+
+ if (offline) {
+ subreq = sysdb_search_custom_send(state, state->ev, state->sysdb, NULL,
+ state->sdap_ctx->be->domain,
+ state->hbac_filter, HBAC_RULES_SUBDIR,
+ state->hbac_attrs);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_search_custom_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_rule_get_done, req);
+
+ return req;
+ }
+
+ if (sdap_ctx->gsh == NULL || ! sdap_ctx->gsh->connected) {
+ if (sdap_ctx->gsh != NULL) {
+ talloc_zfree(sdap_ctx->gsh);
+ }
+
+ subreq = sdap_cli_connect_send(state, ev, sdap_ctx->opts,
+ sdap_ctx->be, sdap_ctx->service, NULL);
+ if (!subreq) {
+ DEBUG(1, ("sdap_cli_connect_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_get_rules_connect_done, req);
+
+ return req;
+ }
+
+ subreq = sdap_get_generic_send(state, state->ev,
+ state->sdap_ctx->opts,
+ state->sdap_ctx->gsh,
+ state->hbac_search_base,
+ LDAP_SCOPE_SUB,
+ state->hbac_filter,
+ state->hbac_attrs,
+ NULL, 0);
+
+ if (subreq == NULL) {
+ DEBUG(1, ("sdap_get_generic_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_rule_get_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void hbac_get_rules_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_rules_state *state = tevent_req_data(req,
+ struct hbac_get_rules_state);
+ int ret;
+
+ ret = sdap_cli_connect_recv(subreq, state->sdap_ctx, &state->sdap_ctx->gsh,
+ NULL);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_get_generic_send(state, state->ev,
+ state->sdap_ctx->opts,
+ state->sdap_ctx->gsh,
+ state->hbac_search_base,
+ LDAP_SCOPE_SUB,
+ state->hbac_filter,
+ state->hbac_attrs,
+ NULL, 0);
+
+ if (subreq == NULL) {
+ DEBUG(1, ("sdap_get_generic_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_rule_get_done, req);
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void hbac_rule_get_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_rules_state *state = tevent_req_data(req,
+ struct hbac_get_rules_state);
+ int ret;
+ int i;
+ struct ldb_message_element *el;
+ struct ldb_message **msgs;
+
+ if (state->offline) {
+ ret = sysdb_search_custom_recv(subreq, state, &state->hbac_reply_count,
+ &msgs);
+ } else {
+ ret = sdap_get_generic_recv(subreq, state, &state->hbac_reply_count,
+ &state->hbac_reply_list);
+ }
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->offline) {
+ ret = msgs2attrs_array(state, state->hbac_reply_count, msgs,
+ &state->hbac_reply_list);
+ talloc_zfree(msgs);
+ if (ret != EOK) {
+ DEBUG(1, ("msgs2attrs_array failed.\n"));
+ goto fail;
+ }
+ }
+
+ for (i = 0; i < state->hbac_reply_count; i++) {
+ ret = sysdb_attrs_get_el(state->hbac_reply_list[i], SYSDB_ORIG_DN, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(1, ("Missing original DN.\n"));
+ ret = EINVAL;
+ goto fail;
+ }
+ DEBUG(9, ("OriginalDN: [%s].\n", (const char *)el->values[0].data));
+ }
+
+ if (state->hbac_reply_count == 0 || state->offline) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_transaction_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, hbac_rule_sysdb_transaction_started, req);
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void hbac_rule_sysdb_transaction_started(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_rules_state *state = tevent_req_data(req,
+ struct hbac_get_rules_state);
+ int ret;
+ struct ldb_dn *hbac_base_dn;
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ hbac_base_dn = sysdb_custom_subtree_dn(state->sysdb, state,
+ state->sdap_ctx->be->domain->name,
+ HBAC_RULES_SUBDIR);
+ if (hbac_base_dn == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ subreq = sysdb_delete_recursive_send(state, state->ev, state->handle,
+ hbac_base_dn, true);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_delete_recursive_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, hbac_rule_sysdb_delete_done, req);
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void hbac_rule_sysdb_delete_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_rules_state *state = tevent_req_data(req,
+ struct hbac_get_rules_state);
+ int ret;
+
+ ret = sysdb_delete_recursive_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->current_item = 0;
+ hbac_rule_store_prepare(req);
+}
+
+static void hbac_rule_store_prepare(struct tevent_req *req)
+{
+ struct hbac_get_rules_state *state = tevent_req_data(req,
+ struct hbac_get_rules_state);
+ int ret;
+ struct ldb_message_element *el;
+ struct tevent_req *subreq;
+ char *object_name;
+
+ if (state->current_item < state->hbac_reply_count) {
+
+ ret = sysdb_attrs_get_el(state->hbac_reply_list[state->current_item],
+ IPA_UNIQUE_ID, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+ object_name = talloc_strndup(state, (const char *)el->values[0].data,
+ el->values[0].length);
+ if (object_name == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ DEBUG(9, ("IPAUniqueId: [%s].\n", object_name));
+
+ subreq = sysdb_store_custom_send(state, state->ev,
+ state->handle,
+ state->sdap_ctx->be->domain,
+ object_name,
+ HBAC_RULES_SUBDIR,
+ state->hbac_reply_list[state->current_item]);
+
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_store_custom_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, hbac_rule_store_done, req);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (subreq == NULL) {
+ DEBUG(1, ("sysdb_transaction_commit_send failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+
+ return;
+
+fail:
+ tevent_req_error(req, ret);
+ return;
+}
+
+static void hbac_rule_store_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct hbac_get_rules_state *state = tevent_req_data(req,
+ struct hbac_get_rules_state);
+ int ret;
+
+ ret = sysdb_store_custom_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->current_item++;
+ hbac_rule_store_prepare(req);
+}
+
+static int hbac_get_rules_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+ size_t *hbac_rule_count,
+ struct sysdb_attrs ***hbac_rule_list)
+{
+ struct hbac_get_rules_state *state = tevent_req_data(req,
+ struct hbac_get_rules_state);
+ int i;
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *hbac_rule_count = state->hbac_reply_count;
+ *hbac_rule_list = talloc_steal(memctx, state->hbac_reply_list);
+ for (i = 0; i < state->hbac_reply_count; i++) {
+ talloc_steal(memctx, state->hbac_reply_list[i]);
+ }
+ return EOK;
+}
+
+enum hbac_result {
+ HBAC_ALLOW = 1,
+ HBAC_DENY,
+ HBAC_NOT_APPLICABLE
+};
+
+enum check_result {
+ RULE_APPLICABLE = 0,
+ RULE_NOT_APPLICABLE,
+ RULE_ERROR
+};
+
+enum check_result check_service(struct pam_data *pd,
+ struct sysdb_attrs *rule_attrs)
+{
+ int ret;
+ int i;
+ struct ldb_message_element *el;
+
+ if (pd->service == NULL) {
+ DEBUG(1, ("No service in pam data, assuming error.\n"));
+ return RULE_ERROR;
+ }
+
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_SERVICE_NAME, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return RULE_ERROR;
+ }
+ if (el->num_values == 0) {
+ DEBUG(9, ("No services in rule specified, assuming rule applies.\n"));
+ return RULE_APPLICABLE;
+ } else {
+ for (i = 0; i < el->num_values; i++) {
+ if (strncasecmp(pd->service, (const char *) el->values[i].data,
+ el->values[i].length) == 0) {
+ DEBUG(9, ("Service [%s] found, rule applies.\n",
+ pd->service));
+ return RULE_APPLICABLE;
+ }
+ }
+ DEBUG(9, ("No matching service found, rule does not apply.\n"));
+ return RULE_NOT_APPLICABLE;
+ }
+
+ return RULE_ERROR;
+}
+
+enum check_result check_access_time(struct time_rules_ctx *tr_ctx,
+ struct sysdb_attrs *rule_attrs)
+{
+ int ret;
+ int i;
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct ldb_message_element *el;
+ char *rule;
+ time_t now;
+ bool result;
+
+ now = time(NULL);
+ if (now == (time_t) -1) {
+ DEBUG(1, ("time failed [%d][%s].\n", errno, strerror(errno)));
+ return RULE_ERROR;
+ }
+
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_ACCESS_TIME, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return RULE_ERROR;
+ }
+ if (el->num_values == 0) {
+ DEBUG(9, ("No access time specified, assuming rule applies.\n"));
+ return RULE_APPLICABLE;
+ } else {
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(1, ("talloc_new failed.\n"));
+ return RULE_ERROR;
+ }
+
+ for (i = 0; i < el->num_values; i++) {
+ rule = talloc_strndup(tmp_ctx, (const char *) el->values[i].data,
+ el->values[i].length);
+ ret = check_time_rule(tmp_ctx, tr_ctx, rule, now, &result);
+ if (ret != EOK) {
+ DEBUG(1, ("check_time_rule failed.\n"));
+ ret = RULE_ERROR;
+ goto done;
+ }
+
+ if (result) {
+ DEBUG(9, ("Current time [%d] matches rule [%s].\n", now, rule));
+ ret = RULE_APPLICABLE;
+ goto done;
+ }
+ }
+ }
+
+ ret = RULE_NOT_APPLICABLE;
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+enum check_result check_user(struct hbac_ctx *hbac_ctx,
+ struct sysdb_attrs *rule_attrs)
+{
+ int ret;
+ int i;
+ int g;
+ struct ldb_message_element *el;
+
+ if (hbac_ctx->user_dn == NULL) {
+ DEBUG(1, ("No user DN available, this should never happen.\n"));
+ return RULE_ERROR;
+ }
+
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_USER_CATEGORY, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return RULE_ERROR;
+ }
+ if (el->num_values == 0) {
+ DEBUG(9, ("USer category is not set.\n"));
+ } else {
+ for (i = 0; i < el->num_values; i++) {
+ if (strncasecmp("all", (const char *) el->values[i].data,
+ el->values[i].length) == 0) {
+ DEBUG(9, ("User category is set to 'all', rule applies.\n"));
+ return RULE_APPLICABLE;
+ }
+ DEBUG(9, ("Unsupported user category [%.*s].\n",
+ el->values[i].length,
+ (char *) el->values[i].data));
+ }
+ }
+
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_MEMBER_USER, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return RULE_ERROR;
+ }
+ if (el->num_values == 0) {
+ DEBUG(9, ("No user specified, rule does not apply.\n"));
+ return RULE_APPLICABLE;
+ } else {
+ for (i = 0; i < el->num_values; i++) {
+ DEBUG(9, ("Searching matches for [%.*s].\n", el->values[i].length,
+ (const char *) el->values[i].data));
+ DEBUG(9, ("Checking user [%s].\n", hbac_ctx->user_dn));
+ if (strncmp(hbac_ctx->user_dn, (const char *) el->values[i].data,
+ el->values[i].length) == 0) {
+ DEBUG(9, ("User [%s] found, rule applies.\n",
+ hbac_ctx->user_dn));
+ return RULE_APPLICABLE;
+ }
+
+ for (g = 0; g < hbac_ctx->groups_count; g++) {
+ DEBUG(9, ("Checking group [%s].\n", hbac_ctx->groups[g]));
+ if (strncmp(hbac_ctx->groups[g],
+ (const char *) el->values[i].data,
+ el->values[i].length) == 0) {
+ DEBUG(9, ("Group [%s] found, rule applies.\n",
+ hbac_ctx->groups[g]));
+ return RULE_APPLICABLE;
+ }
+ }
+ }
+ DEBUG(9, ("No matching user found, rule does not apply.\n"));
+ return RULE_NOT_APPLICABLE;
+ }
+
+ return RULE_ERROR;
+}
+
+enum check_result check_remote_hosts(const char *rhost,
+ struct hbac_host_info *hhi,
+ struct sysdb_attrs *rule_attrs)
+{
+ int ret;
+ int i;
+ int m;
+ struct ldb_message_element *cat_el;
+ struct ldb_message_element *src_el;
+ struct ldb_message_element *ext_el;
+
+ if (hhi == NULL && (rhost == NULL || *rhost == '\0')) {
+ DEBUG(1, ("No remote host information specified, assuming error.\n"));
+ return RULE_ERROR;
+ }
+
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_SOURCE_HOST_CATEGORY, &cat_el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return RULE_ERROR;
+ }
+ if (cat_el->num_values == 0) {
+ DEBUG(9, ("Source host category not set.\n"));
+ } else {
+ for(i = 0; i < cat_el->num_values; i++) {
+ if (strncasecmp("all", (const char *) cat_el->values[i].data,
+ cat_el->values[i].length) == 0) {
+ DEBUG(9, ("Source host category is set to 'all', "
+ "rule applies.\n"));
+ return RULE_APPLICABLE;
+ }
+ DEBUG(9, ("Unsupported source hosts category [%.*s].\n",
+ cat_el->values[i].length,
+ (char *) cat_el->values[i].data));
+ }
+ }
+
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_SOURCE_HOST, &src_el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return RULE_ERROR;
+ }
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_EXTERNAL_HOST, &ext_el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return RULE_ERROR;
+ }
+
+ if (src_el->num_values == 0 && ext_el->num_values == 0) {
+ DEBUG(9, ("No remote host specified in rule, rule does not apply.\n"));
+ return RULE_NOT_APPLICABLE;
+ } else {
+ if (hhi != NULL) {
+ for (i = 0; i < src_el->num_values; i++) {
+ if (strncasecmp(hhi->dn, (const char *) src_el->values[i].data,
+ src_el->values[i].length) == 0) {
+ DEBUG(9, ("Source host [%s] found, rule applies.\n",
+ hhi->dn));
+ return RULE_APPLICABLE;
+ }
+ for (m = 0; hhi->memberof[m] != NULL; m++) {
+ if (strncasecmp(hhi->memberof[m],
+ (const char *) src_el->values[i].data,
+ src_el->values[i].length) == 0) {
+ DEBUG(9, ("Source host group [%s] found, rule applies.\n",
+ hhi->memberof[m]));
+ return RULE_APPLICABLE;
+ }
+ }
+ }
+ }
+
+ if (rhost != NULL && *rhost != '\0') {
+ for (i = 0; i < ext_el->num_values; i++) {
+ if (strncasecmp(rhost, (const char *) ext_el->values[i].data,
+ ext_el->values[i].length) == 0) {
+ DEBUG(9, ("External host [%s] found, rule applies.\n",
+ rhost));
+ return RULE_APPLICABLE;
+ }
+ }
+ }
+ DEBUG(9, ("No matching remote host found.\n"));
+ return RULE_NOT_APPLICABLE;
+ }
+
+ return RULE_ERROR;
+}
+
+static errno_t check_if_rule_applies(enum hbac_result *result,
+ struct hbac_ctx *hbac_ctx,
+ struct sysdb_attrs *rule_attrs) {
+ int ret;
+ struct ldb_message_element *el;
+ enum hbac_result rule_type;
+ char *rule_name;
+ struct pam_data *pd = hbac_ctx->pd;
+
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_CN, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return ret;
+ }
+ if (el->num_values == 0) {
+ DEBUG(4, ("rule has no name, assuming '(none)'.\n"));
+ rule_name = talloc_strdup(rule_attrs, "(none)");
+ } else {
+ rule_name = talloc_strndup(rule_attrs, (const char*) el->values[0].data,
+ el->values[0].length);
+ }
+ if (rule_name == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ return ENOMEM;
+ }
+ DEBUG(9, ("Processsing rule [%s].\n", rule_name));
+
+ /* rule type */
+ ret = sysdb_attrs_get_el(rule_attrs, IPA_ACCESS_RULE_TYPE, &el);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_get_el failed.\n"));
+ return ret;
+ }
+ if (el->num_values == 0) {
+ DEBUG(4, ("rule has no type, assuming 'deny'.\n"));
+ rule_type = HBAC_DENY;
+ } else if (el->num_values == 1) {
+ if (strncasecmp((const char *) el->values[0].data, "allow",
+ el->values[0].length) == 0) {
+ rule_type = HBAC_ALLOW;
+ } else {
+ rule_type = HBAC_DENY;
+ }
+ } else {
+ DEBUG(1, ("rule has an unsupported number of values [%d].\n",
+ el->num_values));
+ return EINVAL;
+ }
+
+ ret = check_service(pd, rule_attrs);
+ if (ret != RULE_APPLICABLE) {
+ goto not_applicable;
+ }
+
+ ret = check_user(hbac_ctx, rule_attrs);
+ if (ret != RULE_APPLICABLE) {
+ goto not_applicable;
+ }
+
+ ret = check_access_time(hbac_ctx->tr_ctx, rule_attrs);
+ if (ret != RULE_APPLICABLE) {
+ goto not_applicable;
+ }
+
+ ret = check_remote_hosts(pd->rhost, hbac_ctx->remote_hhi, rule_attrs);
+ if (ret != RULE_APPLICABLE) {
+ goto not_applicable;
+ }
+
+ *result = rule_type;
+
+ return EOK;
+
+not_applicable:
+ if (ret == RULE_NOT_APPLICABLE) {
+ *result = HBAC_NOT_APPLICABLE;
+ } else {
+ *result = HBAC_DENY;
+ }
+ return EOK;
+}
+
+static int evaluate_ipa_hbac_rules(struct hbac_ctx *hbac_ctx,
+ bool *access_allowed)
+{
+ bool allow_matched = false;
+ enum hbac_result result;
+ int ret;
+ int i;
+
+ *access_allowed = false;
+
+ for (i = 0; i < hbac_ctx->hbac_rule_count ; i++) {
+
+ ret = check_if_rule_applies(&result, hbac_ctx,
+ hbac_ctx->hbac_rule_list[i]);
+ if (ret != EOK) {
+ DEBUG(1, ("check_if_rule_applies failed.\n"));
+ return ret;
+ }
+
+ switch (result) {
+ case HBAC_DENY:
+ DEBUG(3, ("Access denied by single rule.\n"));
+ return EOK;
+ break;
+ case HBAC_ALLOW:
+ allow_matched = true;
+ DEBUG(9, ("Current rule allows access.\n"));
+ break;
+ default:
+ DEBUG(9, ("Current rule does not apply.\n"));
+ }
+
+ }
+
+ *access_allowed = allow_matched;
+
+ return EOK;
+}
+
+static void hbac_get_host_info_done(struct tevent_req *req);
+static void hbac_get_rules_done(struct tevent_req *req);
+static void hbac_get_user_info_done(struct tevent_req *req);
+
+void ipa_access_handler(struct be_req *be_req)
+{
+ struct tevent_req *req;
+ struct pam_data *pd;
+ struct hbac_ctx *hbac_ctx;
+ int pam_status = PAM_SYSTEM_ERR;
+ struct ipa_access_ctx *ipa_access_ctx;
+ const char *hostlist[3];
+
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+
+ hbac_ctx = talloc_zero(be_req, struct hbac_ctx);
+ if (hbac_ctx == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ goto fail;
+ }
+ hbac_ctx->be_req = be_req;
+ hbac_ctx->pd = pd;
+ ipa_access_ctx = talloc_get_type(
+ be_req->be_ctx->bet_info[BET_ACCESS].pvt_bet_data,
+ struct ipa_access_ctx);
+ hbac_ctx->sdap_ctx = ipa_access_ctx->sdap_ctx;
+ hbac_ctx->ipa_options = ipa_access_ctx->ipa_options;
+ hbac_ctx->tr_ctx = ipa_access_ctx->tr_ctx;
+ hbac_ctx->offline = be_is_offline(be_req->be_ctx);
+
+ DEBUG(9, ("Connection status is [%s].\n", hbac_ctx->offline ? "offline" :
+ "online"));
+
+
+ hostlist[0] = dp_opt_get_cstring(hbac_ctx->ipa_options, IPA_HOSTNAME);
+ if (hostlist[0] == NULL) {
+ DEBUG(1, ("ipa_hostname not available.\n"));
+ goto fail;
+ }
+ if (pd->rhost != NULL && *pd->rhost != '\0') {
+ hostlist[1] = pd->rhost;
+ } else {
+ hostlist[1] = NULL;
+ pd->rhost = dp_opt_get_string(hbac_ctx->ipa_options, IPA_HOSTNAME);
+ if (pd->rhost == NULL) {
+ DEBUG(1, ("ipa_hostname not available.\n"));
+ goto fail;
+ }
+ }
+ hostlist[2] = NULL;
+
+ req = hbac_get_host_info_send(hbac_ctx, be_req->be_ctx->ev,
+ hbac_ctx->offline,
+ hbac_ctx->sdap_ctx, be_req->be_ctx->sysdb,
+ dp_opt_get_string(hbac_ctx->ipa_options,
+ IPA_DOMAIN),
+ hostlist);
+ if (req == NULL) {
+ DEBUG(1, ("hbac_get_host_info_send failed.\n"));
+ goto fail;
+ }
+
+ tevent_req_set_callback(req, hbac_get_host_info_done, hbac_ctx);
+ return;
+
+fail:
+ ipa_access_reply(be_req, pam_status);
+}
+
+static void hbac_get_host_info_done(struct tevent_req *req)
+{
+ struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx);
+ struct be_req *be_req = hbac_ctx->be_req;
+ int ret;
+ int pam_status = PAM_SYSTEM_ERR;
+ const char *ipa_hostname;
+ struct hbac_host_info *local_hhi = NULL;
+ int i;
+
+ ret = hbac_get_host_info_recv(req, hbac_ctx, &hbac_ctx->hbac_host_info);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ ipa_hostname = dp_opt_get_cstring(hbac_ctx->ipa_options, IPA_HOSTNAME);
+ if (ipa_hostname == NULL) {
+ DEBUG(1, ("Missing ipa_hostname, this should never happen.\n"));
+ ret = EINVAL;
+ goto fail;
+ }
+
+ for (i = 0; hbac_ctx->hbac_host_info[i] != NULL; i++) {
+ if (strcmp(hbac_ctx->hbac_host_info[i]->fqdn, ipa_hostname) == 0 ||
+ strcmp(hbac_ctx->hbac_host_info[i]->serverhostname,
+ ipa_hostname) == 0) {
+ local_hhi = hbac_ctx->hbac_host_info[i];
+ }
+ if (hbac_ctx->pd->rhost != NULL && *hbac_ctx->pd->rhost != '\0') {
+ if (strcmp(hbac_ctx->hbac_host_info[i]->fqdn,
+ hbac_ctx->pd->rhost) == 0 ||
+ strcmp(hbac_ctx->hbac_host_info[i]->serverhostname,
+ hbac_ctx->pd->rhost) == 0) {
+ hbac_ctx->remote_hhi = hbac_ctx->hbac_host_info[i];
+ }
+ }
+ }
+ if (local_hhi == NULL) {
+ DEBUG(1, ("Missing host info for [%s].\n", ipa_hostname));
+ ret = EINVAL;
+ goto fail;
+ }
+ req = hbac_get_rules_send(hbac_ctx, be_req->be_ctx->ev, hbac_ctx->offline,
+ hbac_ctx->sdap_ctx, be_req->be_ctx->sysdb,
+ dp_opt_get_string(hbac_ctx->ipa_options,
+ IPA_DOMAIN),
+ local_hhi->dn, local_hhi->memberof);
+ if (req == NULL) {
+ DEBUG(1, ("hbac_get_rules_send failed.\n"));
+ goto fail;
+ }
+
+ tevent_req_set_callback(req, hbac_get_rules_done, hbac_ctx);
+ return;
+
+fail:
+ ipa_access_reply(be_req, pam_status);
+}
+
+static void hbac_get_rules_done(struct tevent_req *req)
+{
+ struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx);
+ struct pam_data *pd = hbac_ctx->pd;
+ struct be_req *be_req = hbac_ctx->be_req;
+ int ret;
+ int pam_status = PAM_SYSTEM_ERR;
+
+ ret = hbac_get_rules_recv(req, hbac_ctx, &hbac_ctx->hbac_rule_count,
+ &hbac_ctx->hbac_rule_list);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ req = hbac_get_user_info_send(hbac_ctx, be_req->be_ctx->ev, be_req->be_ctx,
+ pd->user);
+ if (req == NULL) {
+ DEBUG(1, ("hbac_get_user_info_send failed.\n"));
+ goto fail;
+ }
+
+ tevent_req_set_callback(req, hbac_get_user_info_done, hbac_ctx);
+ return;
+
+fail:
+ ipa_access_reply(be_req, pam_status);
+}
+
+static void hbac_get_user_info_done(struct tevent_req *req)
+{
+ struct hbac_ctx *hbac_ctx = tevent_req_callback_data(req, struct hbac_ctx);
+ struct be_req *be_req = hbac_ctx->be_req;
+ int ret;
+ int pam_status = PAM_SYSTEM_ERR;
+ bool access_allowed = false;
+
+ ret = hbac_get_user_info_recv(req, hbac_ctx, &hbac_ctx->user_dn,
+ &hbac_ctx->groups_count,
+ &hbac_ctx->groups);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ goto failed;
+ }
+
+ ret = evaluate_ipa_hbac_rules(hbac_ctx, &access_allowed);
+ if (ret != EOK) {
+ DEBUG(1, ("evaluate_ipa_hbac_rules failed.\n"));
+ goto failed;
+ }
+
+ if (access_allowed) {
+ pam_status = PAM_SUCCESS;
+ DEBUG(5, ("Access allowed.\n"));
+ } else {
+ pam_status = PAM_PERM_DENIED;
+ DEBUG(3, ("Access denied.\n"));
+ }
+
+failed:
+ ipa_access_reply(be_req, pam_status);
+}
diff --git a/src/providers/ipa/ipa_access.h b/src/providers/ipa/ipa_access.h
new file mode 100644
index 000000000..bd221c574
--- /dev/null
+++ b/src/providers/ipa/ipa_access.h
@@ -0,0 +1,66 @@
+/*
+ SSSD
+
+ IPA Backend Module -- Access control
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 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 _IPA_ACCESS_H_
+#define _IPA_ACCESS_H_
+
+#include "providers/ldap/ldap_common.h"
+
+enum ipa_access_mode {
+ IPA_ACCESS_DENY = 0,
+ IPA_ACCESS_ALLOW
+};
+
+struct hbac_host_info {
+ const char *fqdn;
+ const char *serverhostname;
+ const char *dn;
+ const char **memberof;
+};
+
+struct ipa_access_ctx {
+ struct sdap_id_ctx *sdap_ctx;
+ struct dp_option *ipa_options;
+ struct time_rules_ctx *tr_ctx;
+};
+
+struct hbac_ctx {
+ struct sdap_id_ctx *sdap_ctx;
+ struct dp_option *ipa_options;
+ struct time_rules_ctx *tr_ctx;
+ struct be_req *be_req;
+ struct pam_data *pd;
+ struct hbac_host_info **hbac_host_info;
+ struct hbac_host_info *remote_hhi;
+ struct sysdb_attrs **hbac_rule_list;
+ size_t hbac_rule_count;
+ const char *user_dn;
+ size_t groups_count;
+ const char **groups;
+ bool offline;
+};
+
+void ipa_access_handler(struct be_req *be_req);
+
+#endif /* _IPA_ACCESS_H_ */
diff --git a/src/providers/ipa/ipa_auth.c b/src/providers/ipa/ipa_auth.c
new file mode 100644
index 000000000..86b72e495
--- /dev/null
+++ b/src/providers/ipa/ipa_auth.c
@@ -0,0 +1,313 @@
+/*
+ SSSD
+
+ IPA Backend Module -- Authentication
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 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 <sys/param.h>
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/ipa/ipa_common.h"
+
+struct ipa_auth_ctx {
+ struct sdap_auth_ctx *sdap_auth_ctx;
+ struct krb5_ctx *krb5_ctx;
+ struct be_req *be_req;
+ be_async_callback_t callback;
+ void *pvt;
+ bool password_migration;
+
+ int dp_err_type;
+ int errnum;
+ char *errstr;
+};
+
+static void ipa_auth_reply(struct ipa_auth_ctx *ipa_auth_ctx)
+{
+ struct pam_data *pd;
+ struct be_req *be_req = ipa_auth_ctx->be_req;
+ be_req->fn = ipa_auth_ctx->callback;
+ be_req->pvt = ipa_auth_ctx->pvt;
+ be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data = ipa_auth_ctx->krb5_ctx;
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+ int dp_err_type = ipa_auth_ctx->dp_err_type;
+ char *errstr = ipa_auth_ctx->errstr;
+
+ talloc_zfree(ipa_auth_ctx);
+ DEBUG(9, ("sending [%d] [%d] [%s].\n", dp_err_type, pd->pam_status,
+ errstr));
+
+ be_req->fn(be_req, dp_err_type, pd->pam_status, errstr);
+}
+
+struct ipa_auth_handler_state {
+ struct tevent_context *ev;
+
+ int dp_err_type;
+ int errnum;
+ char *errstr;
+};
+
+static void ipa_auth_handler_callback(struct be_req *be_req,
+ int dp_err_type,
+ int errnum,
+ const char *errstr);
+
+static struct tevent_req *ipa_auth_handler_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct be_req *be_req,
+ be_req_fn_t auth_handler)
+{
+ struct ipa_auth_handler_state *state;
+ struct tevent_req *req;
+
+ req = tevent_req_create(memctx, &state, struct ipa_auth_handler_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+
+ be_req->fn = ipa_auth_handler_callback;
+ be_req->pvt = req;
+
+ auth_handler(be_req);
+
+ return req;
+}
+
+static void ipa_auth_handler_callback(struct be_req *be_req,
+ int dp_err_type,
+ int errnum,
+ const char *errstr)
+{
+ struct tevent_req *req = talloc_get_type(be_req->pvt, struct tevent_req);
+ struct ipa_auth_handler_state *state = tevent_req_data(req,
+ struct ipa_auth_handler_state);
+
+ DEBUG(9, ("received from handler [%d] [%d] [%s].\n", dp_err_type, errnum,
+ errstr));
+ state->dp_err_type = dp_err_type;
+ state->errnum = errnum;
+ state->errstr = talloc_strdup(state, errstr);
+
+ tevent_req_post(req, state->ev);
+ tevent_req_done(req);
+ return;
+}
+
+static int ipa_auth_handler_recv(struct tevent_req *req, TALLOC_CTX *memctx,
+ int *dp_err_type, int *errnum,
+ char **errstr)
+{
+ struct ipa_auth_handler_state *state = tevent_req_data(req,
+ struct ipa_auth_handler_state);
+ enum tevent_req_state tstate;
+ uint64_t err;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ if (err) return err;
+ return EIO;
+ }
+
+ *dp_err_type = state->dp_err_type;
+ *errnum = state->errnum;
+ *errstr = talloc_steal(memctx, state->errstr);
+
+ return EOK;
+}
+
+
+static void ipa_auth_handler_done(struct tevent_req *req);
+static void ipa_auth_ldap_done(struct tevent_req *req);
+static void ipa_auth_handler_retry_done(struct tevent_req *req);
+
+void ipa_auth(struct be_req *be_req)
+{
+ struct tevent_req *req;
+ struct ipa_auth_ctx *ipa_auth_ctx;
+ struct sdap_id_ctx *sdap_id_ctx;
+
+ ipa_auth_ctx = talloc_zero(be_req, struct ipa_auth_ctx);
+ if (ipa_auth_ctx == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ be_req->fn(be_req, DP_ERR_FATAL, PAM_SYSTEM_ERR, NULL);
+ }
+
+ ipa_auth_ctx->callback = be_req->fn;
+ ipa_auth_ctx->pvt = be_req->pvt;
+
+ ipa_auth_ctx->be_req = be_req;
+
+ ipa_auth_ctx->sdap_auth_ctx = talloc_zero(ipa_auth_ctx,
+ struct sdap_auth_ctx);
+ if (ipa_auth_ctx->sdap_auth_ctx == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ goto fail;
+ }
+
+ sdap_id_ctx = talloc_get_type(
+ be_req->be_ctx->bet_info[BET_ID].pvt_bet_data,
+ struct sdap_id_ctx);
+ ipa_auth_ctx->sdap_auth_ctx->be = sdap_id_ctx->be;
+ ipa_auth_ctx->sdap_auth_ctx->opts = sdap_id_ctx->opts;
+
+ ipa_auth_ctx->krb5_ctx = talloc_get_type(
+ be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data,
+ struct krb5_ctx);
+
+/* TODO: test and activate when server side support is available */
+ ipa_auth_ctx->password_migration = false;
+
+ ipa_auth_ctx->dp_err_type = DP_ERR_FATAL;
+ ipa_auth_ctx->errnum = EIO;
+ ipa_auth_ctx->errstr = NULL;
+
+ req = ipa_auth_handler_send(ipa_auth_ctx, be_req->be_ctx->ev, be_req,
+ krb5_pam_handler);
+ if (req == NULL) {
+ DEBUG(1, ("ipa_auth_handler_send failed.\n"));
+ goto fail;
+ }
+
+ tevent_req_set_callback(req, ipa_auth_handler_done, ipa_auth_ctx);
+ return;
+
+fail:
+ ipa_auth_reply(ipa_auth_ctx);
+}
+
+static void ipa_auth_handler_done(struct tevent_req *req)
+{
+ struct ipa_auth_ctx *ipa_auth_ctx = tevent_req_callback_data(req,
+ struct ipa_auth_ctx);
+ struct pam_data *pd;
+ struct be_req *be_req;
+ int ret;
+
+ be_req = ipa_auth_ctx->be_req;
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+
+ ret = ipa_auth_handler_recv(req, ipa_auth_ctx, &ipa_auth_ctx->dp_err_type,
+ &ipa_auth_ctx->errnum, &ipa_auth_ctx->errstr);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(1, ("ipa_auth_handler request failed.\n"));
+ pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ if (ipa_auth_ctx->dp_err_type != DP_ERR_OK) {
+ pd->pam_status = ipa_auth_ctx->errnum;
+ goto done;
+ }
+
+ if (ipa_auth_ctx->password_migration && pd->pam_status == PAM_CRED_ERR) {
+ DEBUG(1, ("Assuming Kerberos password is missing, "
+ "starting password migration.\n"));
+ be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data =
+ ipa_auth_ctx->sdap_auth_ctx;
+ req = ipa_auth_handler_send(ipa_auth_ctx, be_req->be_ctx->ev, be_req,
+ sdap_pam_auth_handler);
+ if (req == NULL) {
+ DEBUG(1, ("ipa_auth_ldap_send failed.\n"));
+ goto done;
+ }
+
+ tevent_req_set_callback(req, ipa_auth_ldap_done, ipa_auth_ctx);
+ return;
+ }
+
+done:
+ ipa_auth_reply(ipa_auth_ctx);
+}
+
+static void ipa_auth_ldap_done(struct tevent_req *req)
+{
+ struct ipa_auth_ctx *ipa_auth_ctx = tevent_req_callback_data(req,
+ struct ipa_auth_ctx);
+ struct pam_data *pd;
+ struct be_req *be_req;
+ int ret;
+
+ be_req = ipa_auth_ctx->be_req;
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+
+ ret = ipa_auth_handler_recv(req, ipa_auth_ctx, &ipa_auth_ctx->dp_err_type,
+ &ipa_auth_ctx->errnum, &ipa_auth_ctx->errstr);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(1, ("ipa_auth_handler request failed.\n"));
+ pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ if (ipa_auth_ctx->dp_err_type != DP_ERR_OK) {
+ pd->pam_status = ipa_auth_ctx->errnum;
+ goto done;
+ }
+
+ if (pd->pam_status == PAM_SUCCESS) {
+ DEBUG(1, ("LDAP authentication succeded, "
+ "trying Kerberos authentication again.\n"));
+ be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data = ipa_auth_ctx->krb5_ctx;
+ req = ipa_auth_handler_send(ipa_auth_ctx, be_req->be_ctx->ev, be_req,
+ krb5_pam_handler);
+ if (req == NULL) {
+ DEBUG(1, ("ipa_auth_ldap_send failed.\n"));
+ goto done;
+ }
+
+ tevent_req_set_callback(req, ipa_auth_handler_retry_done, ipa_auth_ctx);
+ return;
+ }
+
+done:
+ ipa_auth_reply(ipa_auth_ctx);
+}
+
+static void ipa_auth_handler_retry_done(struct tevent_req *req)
+{
+ struct ipa_auth_ctx *ipa_auth_ctx = tevent_req_callback_data(req,
+ struct ipa_auth_ctx);
+ struct pam_data *pd;
+ struct be_req *be_req;
+ int ret;
+
+ be_req = ipa_auth_ctx->be_req;
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+
+ ret = ipa_auth_handler_recv(req, ipa_auth_ctx, &ipa_auth_ctx->dp_err_type,
+ &ipa_auth_ctx->errnum, &ipa_auth_ctx->errstr);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(1, ("ipa_auth_handler request failed.\n"));
+ pd->pam_status = PAM_SYSTEM_ERR;
+ }
+ if (ipa_auth_ctx->dp_err_type != DP_ERR_OK) {
+ pd->pam_status = ipa_auth_ctx->errnum;
+ }
+
+ ipa_auth_reply(ipa_auth_ctx);
+}
diff --git a/src/providers/ipa/ipa_auth.h b/src/providers/ipa/ipa_auth.h
new file mode 100644
index 000000000..3079bbd1b
--- /dev/null
+++ b/src/providers/ipa/ipa_auth.h
@@ -0,0 +1,32 @@
+/*
+ SSSD
+
+ IPA Backend Module -- Authentication
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 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 _IPA_AUTH_H_
+#define _IPA_AUTH_H_
+
+#include "providers/dp_backend.h"
+
+void ipa_auth(struct be_req *be_req);
+
+#endif /* _IPA_AUTH_H_ */
diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c
new file mode 100644
index 000000000..7686227a5
--- /dev/null
+++ b/src/providers/ipa/ipa_common.c
@@ -0,0 +1,597 @@
+/*
+ SSSD
+
+ IPA Provider Common Functions
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2009 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 <netdb.h>
+#include <ctype.h>
+#include "providers/ipa/ipa_common.h"
+
+struct dp_option ipa_basic_opts[] = {
+ { "ipa_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ipa_server", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ipa_hostname", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+};
+
+struct dp_option ipa_def_ldap_opts[] = {
+ { "ldap_uri", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_default_bind_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_default_authtok_type", DP_OPT_STRING, NULL_STRING, NULL_STRING},
+ { "ldap_default_authtok", DP_OPT_BLOB, NULL_BLOB, NULL_BLOB },
+ { "ldap_search_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER },
+ { "ldap_network_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER },
+ { "ldap_opt_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER },
+ { "ldap_tls_reqcert", DP_OPT_STRING, { "hard" }, NULL_STRING },
+ { "ldap_user_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_user_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
+ { "ldap_user_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_group_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
+ { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_schema", DP_OPT_STRING, { "ipa_v1" }, NULL_STRING },
+ { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER },
+ { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+ { "ldap_enumeration_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER },
+ { "ldap_purge_cache_timeout", DP_OPT_NUMBER, { .number = 3600 }, NULL_NUMBER },
+ { "entry_cache_timeout", DP_OPT_NUMBER, { .number = 1800 }, NULL_NUMBER },
+ { "ldap_tls_cacert", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_tls_cacertdir", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_id_use_start_tls", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "ldap_sasl_mech", DP_OPT_STRING, { "GSSAPI" } , NULL_STRING },
+ { "ldap_sasl_authid", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_krb5_init_creds", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+ /* use the same parm name as the krb5 module so we set it only once */
+ { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_pwd_policy", DP_OPT_STRING, { "none" } , NULL_STRING },
+ { "ldap_referrals", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }
+};
+
+struct sdap_attr_map ipa_attr_map[] = {
+ { "ldap_entry_usn", "entryUSN", SYSDB_USN, NULL },
+ { "ldap_rootdse_last_usn", "lastUSN", SYSDB_HIGH_USN, NULL }
+};
+
+struct sdap_attr_map ipa_user_map[] = {
+ { "ldap_user_object_class", "posixAccount", SYSDB_USER_CLASS, NULL },
+ { "ldap_user_name", "uid", SYSDB_NAME, NULL },
+ { "ldap_user_pwd", "userPassword", SYSDB_PWD, NULL },
+ { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL },
+ { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL },
+ { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL },
+ { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL },
+ { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL },
+ { "ldap_user_principal", "krbPrincipalName", SYSDB_UPN, NULL },
+ { "ldap_user_fullname", "cn", SYSDB_FULLNAME, NULL },
+ { "ldap_user_member_of", "memberOf", SYSDB_MEMBEROF, NULL },
+ { "ldap_user_uuid", "nsUniqueId", SYSDB_UUID, NULL },
+ { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL },
+ { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL },
+ { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL },
+ { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL },
+ { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL },
+ { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL },
+ { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL },
+ { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL },
+ { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL },
+ { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL },
+ { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL }
+};
+
+struct sdap_attr_map ipa_group_map[] = {
+ { "ldap_group_object_class", "posixGroup", SYSDB_GROUP_CLASS, NULL },
+ { "ldap_group_name", "cn", SYSDB_NAME, NULL },
+ { "ldap_group_pwd", "userPassword", SYSDB_PWD, NULL },
+ { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL },
+ { "ldap_group_member", "member", SYSDB_MEMBER, NULL },
+ { "ldap_group_uuid", "nsUniqueId", SYSDB_UUID, NULL },
+ { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }
+};
+
+struct dp_option ipa_def_krb5_opts[] = {
+ { "krb5_kdcip", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_ccachedir", DP_OPT_STRING, { "/tmp" }, NULL_STRING },
+ { "krb5_ccname_template", DP_OPT_STRING, { "FILE:%d/krb5cc_%U_XXXXXX" }, NULL_STRING},
+ { "krb5_changepw_principal", DP_OPT_STRING, { "kadmin/changepw" }, NULL_STRING },
+ { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 15 }, NULL_NUMBER },
+ { "krb5_keytab", DP_OPT_STRING, { "/etc/krb5.keytab" }, NULL_STRING },
+ { "krb5_validate", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }
+};
+
+int domain_to_basedn(TALLOC_CTX *memctx, const char *domain, char **basedn)
+{
+ const char *s;
+ char *dn;
+ char *p;
+ int l;
+
+ s = domain;
+ dn = talloc_strdup(memctx, "dc=");
+
+ while ((p = strchr(s, '.'))) {
+ l = p - s;
+ dn = talloc_asprintf_append_buffer(dn, "%.*s,dc=", l, s);
+ if (!dn) {
+ return ENOMEM;
+ }
+ s = p + 1;
+ }
+ dn = talloc_strdup_append_buffer(dn, s);
+ if (!dn) {
+ return ENOMEM;
+ }
+
+ *basedn = dn;
+ return EOK;
+}
+
+int ipa_get_options(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sss_domain_info *dom,
+ struct ipa_options **_opts)
+{
+ struct ipa_options *opts;
+ char *domain;
+ char *server;
+ char *ipa_hostname;
+ int ret;
+ char hostname[HOST_NAME_MAX + 1];
+
+ opts = talloc_zero(memctx, struct ipa_options);
+ if (!opts) return ENOMEM;
+
+ ret = dp_get_options(opts, cdb, conf_path,
+ ipa_basic_opts,
+ IPA_OPTS_BASIC,
+ &opts->basic);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ domain = dp_opt_get_string(opts->basic, IPA_DOMAIN);
+ if (!domain) {
+ ret = dp_opt_set_string(opts->basic, IPA_DOMAIN, dom->name);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+
+ /* FIXME: Make non-fatal once we have discovery */
+ server = dp_opt_get_string(opts->basic, IPA_SERVER);
+ if (!server) {
+ DEBUG(0, ("Can't find ipa server, missing option!\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ipa_hostname = dp_opt_get_string(opts->basic, IPA_HOSTNAME);
+ if (ipa_hostname == NULL) {
+ ret = gethostname(hostname, HOST_NAME_MAX);
+ if (ret != EOK) {
+ DEBUG(1, ("gethostname failed [%d][%s].\n", errno,
+ strerror(errno)));
+ ret = errno;
+ goto done;
+ }
+ hostname[HOST_NAME_MAX] = '\0';
+ DEBUG(9, ("Setting ipa_hostname to [%s].\n", hostname));
+ ret = dp_opt_set_string(opts->basic, IPA_HOSTNAME, hostname);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+
+
+ ret = EOK;
+ *_opts = opts;
+
+done:
+ if (ret != EOK) {
+ talloc_zfree(opts);
+ }
+ return ret;
+}
+
+int ipa_get_id_options(struct ipa_options *ipa_opts,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sdap_options **_opts)
+{
+ TALLOC_CTX *tmpctx;
+ char *hostname;
+ char *basedn;
+ char *realm;
+ char *value;
+ int ret;
+ int i;
+
+ /* self check test, this should never fail, unless someone forgot
+ * to properly update the code after new ldap options have been added */
+ if (SDAP_OPTS_BASIC != IPA_OPTS_BASIC_TEST) {
+ DEBUG(0, ("Option numbers do not match (%d != %d)\n",
+ SDAP_OPTS_BASIC, IPA_OPTS_BASIC_TEST));
+ abort();
+ }
+
+ tmpctx = talloc_new(ipa_opts);
+ if (!tmpctx) {
+ return ENOMEM;
+ }
+
+ ipa_opts->id = talloc_zero(ipa_opts, struct sdap_options);
+ if (!ipa_opts->id) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* get sdap options */
+ ret = dp_get_options(ipa_opts->id, cdb, conf_path,
+ ipa_def_ldap_opts,
+ SDAP_OPTS_BASIC,
+ &ipa_opts->id->basic);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ if (NULL == dp_opt_get_string(ipa_opts->id->basic, SDAP_SEARCH_BASE)) {
+ ret = domain_to_basedn(tmpctx,
+ dp_opt_get_string(ipa_opts->basic, IPA_DOMAIN),
+ &basedn);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ /* FIXME: get values by querying IPA */
+ /* set search base */
+ value = talloc_asprintf(tmpctx, "cn=accounts,%s", basedn);
+ if (!value) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = dp_opt_set_string(ipa_opts->id->basic,
+ SDAP_SEARCH_BASE, value);
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ ipa_opts->id->basic[SDAP_SEARCH_BASE].opt_name,
+ dp_opt_get_string(ipa_opts->id->basic, SDAP_SEARCH_BASE)));
+ }
+
+ /* set the ldap_sasl_authid if the ipa_hostname override was specified */
+ if (NULL == dp_opt_get_string(ipa_opts->id->basic, SDAP_SASL_AUTHID)) {
+ hostname = dp_opt_get_string(ipa_opts->basic, IPA_HOSTNAME);
+ if (hostname) {
+ value = talloc_asprintf(tmpctx, "host/%s", hostname);
+ if (!value) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = dp_opt_set_string(ipa_opts->id->basic,
+ SDAP_SASL_AUTHID, value);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ ipa_opts->id->basic[SDAP_SASL_AUTHID].opt_name,
+ dp_opt_get_string(ipa_opts->id->basic, SDAP_SASL_AUTHID)));
+ }
+
+ /* set krb realm */
+ if (NULL == dp_opt_get_string(ipa_opts->id->basic, SDAP_KRB5_REALM)) {
+ realm = dp_opt_get_string(ipa_opts->basic, IPA_DOMAIN);
+ for (i = 0; realm[i]; i++) {
+ realm[i] = toupper(realm[i]);
+ }
+ ret = dp_opt_set_string(ipa_opts->id->basic,
+ SDAP_KRB5_REALM, realm);
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ ipa_opts->id->basic[SDAP_KRB5_REALM].opt_name,
+ dp_opt_get_string(ipa_opts->id->basic, SDAP_KRB5_REALM)));
+ }
+
+ /* fix schema to IPAv1 for now */
+ ipa_opts->id->schema_type = SDAP_SCHEMA_IPA_V1;
+
+ /* set user/group search bases if they are not specified */
+ if (NULL == dp_opt_get_string(ipa_opts->id->basic,
+ SDAP_USER_SEARCH_BASE)) {
+ ret = dp_opt_set_string(ipa_opts->id->basic, SDAP_USER_SEARCH_BASE,
+ dp_opt_get_string(ipa_opts->id->basic,
+ SDAP_SEARCH_BASE));
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ ipa_opts->id->basic[SDAP_USER_SEARCH_BASE].opt_name,
+ dp_opt_get_string(ipa_opts->id->basic,
+ SDAP_USER_SEARCH_BASE)));
+ }
+
+ if (NULL == dp_opt_get_string(ipa_opts->id->basic,
+ SDAP_GROUP_SEARCH_BASE)) {
+ ret = dp_opt_set_string(ipa_opts->id->basic, SDAP_GROUP_SEARCH_BASE,
+ dp_opt_get_string(ipa_opts->id->basic,
+ SDAP_SEARCH_BASE));
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ ipa_opts->id->basic[SDAP_GROUP_SEARCH_BASE].opt_name,
+ dp_opt_get_string(ipa_opts->id->basic,
+ SDAP_GROUP_SEARCH_BASE)));
+ }
+
+ ret = sdap_get_map(ipa_opts->id, cdb, conf_path,
+ ipa_attr_map,
+ SDAP_AT_GENERAL,
+ &ipa_opts->id->gen_map);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sdap_get_map(ipa_opts->id,
+ cdb, conf_path,
+ ipa_user_map,
+ SDAP_OPTS_USER,
+ &ipa_opts->id->user_map);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sdap_get_map(ipa_opts->id,
+ cdb, conf_path,
+ ipa_group_map,
+ SDAP_OPTS_GROUP,
+ &ipa_opts->id->group_map);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = EOK;
+ *_opts = ipa_opts->id;
+
+done:
+ talloc_zfree(tmpctx);
+ if (ret != EOK) {
+ talloc_zfree(ipa_opts->id);
+ }
+ return ret;
+}
+
+/* the following define is used to keep track of * the options in the krb5
+ * module, so that if they change and ipa is not updated correspondingly
+ * this will trigger a runtime abort error */
+#define IPA_KRB5_OPTS_TEST 8
+
+int ipa_get_auth_options(struct ipa_options *ipa_opts,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct dp_option **_opts)
+{
+ char *value;
+ int ret;
+ int i;
+
+ /* self check test, this should never fail, unless someone forgot
+ * to properly update the code after new ldap options have been added */
+ if (KRB5_OPTS != IPA_KRB5_OPTS_TEST) {
+ DEBUG(0, ("Option numbers do not match (%d != %d)\n",
+ KRB5_OPTS, IPA_KRB5_OPTS_TEST));
+ abort();
+ }
+
+ ipa_opts->auth = talloc_zero(ipa_opts, struct dp_option);
+ if (ipa_opts->auth == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* get krb5 options */
+ ret = dp_get_options(ipa_opts, cdb, conf_path,
+ ipa_def_krb5_opts,
+ KRB5_OPTS, &ipa_opts->auth);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ /* set krb realm */
+ if (NULL == dp_opt_get_string(ipa_opts->auth, KRB5_REALM)) {
+ value = dp_opt_get_string(ipa_opts->basic, IPA_DOMAIN);
+ if (!value) {
+ ret = ENOMEM;
+ goto done;
+ }
+ for (i = 0; value[i]; i++) {
+ value[i] = toupper(value[i]);
+ }
+ ret = dp_opt_set_string(ipa_opts->auth, KRB5_REALM, value);
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ ipa_opts->auth[KRB5_REALM].opt_name,
+ dp_opt_get_string(ipa_opts->auth, KRB5_REALM)));
+ }
+
+ *_opts = ipa_opts->auth;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_zfree(ipa_opts->auth);
+ }
+ return ret;
+}
+
+static void ipa_resolve_callback(void *private_data, struct fo_server *server)
+{
+ struct ipa_service *service;
+ struct hostent *srvaddr;
+ char *address;
+ char *new_uri;
+ int ret;
+
+ service = talloc_get_type(private_data, struct ipa_service);
+ if (!service) {
+ DEBUG(1, ("FATAL: Bad private_data\n"));
+ return;
+ }
+
+ srvaddr = fo_get_server_hostent(server);
+ if (!srvaddr) {
+ DEBUG(1, ("FATAL: No hostent available for server (%s)\n",
+ fo_get_server_name(server)));
+ return;
+ }
+
+ address = talloc_asprintf(service, "%s", srvaddr->h_name);
+ if (!address) {
+ DEBUG(1, ("Failed to copy address ...\n"));
+ return;
+ }
+
+ new_uri = talloc_asprintf(service, "ldap://%s", address);
+ if (!new_uri) {
+ DEBUG(2, ("Failed to copy URI ...\n"));
+ talloc_free(address);
+ return;
+ }
+
+ /* free old one and replace with new one */
+ talloc_zfree(service->sdap->uri);
+ service->sdap->uri = new_uri;
+ talloc_zfree(service->krb5_service->address);
+ service->krb5_service->address = address;
+
+ ret = write_kdcinfo_file(service->krb5_service->realm, address);
+ if (ret != EOK) {
+ DEBUG(2, ("write_kdcinfo_file failed, authentication might fail.\n"));
+ }
+
+}
+
+int ipa_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
+ const char *servers, const char *domain,
+ struct ipa_service **_service)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ipa_service *service;
+ char **list = NULL;
+ char *realm;
+ int ret;
+ int i;
+
+ tmp_ctx = talloc_new(memctx);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ service = talloc_zero(tmp_ctx, struct ipa_service);
+ if (!service) {
+ ret = ENOMEM;
+ goto done;
+ }
+ service->sdap = talloc_zero(service, struct sdap_service);
+ if (!service->sdap) {
+ ret = ENOMEM;
+ goto done;
+ }
+ service->krb5_service = talloc_zero(service, struct krb5_service);
+ if (!service->krb5_service) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = be_fo_add_service(ctx, "IPA");
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to create failover service!\n"));
+ goto done;
+ }
+
+ service->sdap->name = talloc_strdup(service, "IPA");
+ if (!service->sdap->name) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ service->krb5_service->name = talloc_strdup(service, "IPA");
+ if (!service->krb5_service->name) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ realm = talloc_strdup(service, domain);
+ if (!realm) {
+ ret = ENOMEM;
+ goto done;
+ }
+ for (i = 0; realm[i]; i++) {
+ realm[i] = toupper(realm[i]);
+ }
+ service->krb5_service->realm = realm;
+
+ /* split server parm into a list */
+ ret = split_on_separator(tmp_ctx, servers, ',', true, &list, NULL);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to parse server list!\n"));
+ goto done;
+ }
+
+ /* now for each one add a new server to the failover service */
+ for (i = 0; list[i]; i++) {
+
+ talloc_steal(service, list[i]);
+
+ ret = be_fo_add_server(ctx, "IPA", list[i], 0, NULL);
+ if (ret && ret != EEXIST) {
+ DEBUG(0, ("Failed to add server\n"));
+ goto done;
+ }
+
+ DEBUG(6, ("Added Server %s\n", list[i]));
+ }
+
+ ret = be_fo_service_add_callback(memctx, ctx, "IPA",
+ ipa_resolve_callback, service);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to add failover callback!\n"));
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret == EOK) {
+ *_service = talloc_steal(memctx, service);
+ }
+ talloc_zfree(tmp_ctx);
+ return ret;
+}
+
diff --git a/src/providers/ipa/ipa_common.h b/src/providers/ipa/ipa_common.h
new file mode 100644
index 000000000..60c7313f0
--- /dev/null
+++ b/src/providers/ipa/ipa_common.h
@@ -0,0 +1,83 @@
+/*
+ SSSD
+
+ IPA Common utility code
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+
+ 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 _IPA_COMMON_H_
+#define _IPA_COMMON_H_
+
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/krb5/krb5_common.h"
+
+struct ipa_service {
+ struct sdap_service *sdap;
+ struct krb5_service *krb5_service;
+};
+
+/* the following define is used to keep track of the options in the ldap
+ * module, so that if they change and ipa is not updated correspondingly
+ * this will trigger a runtime abort error */
+#define IPA_OPTS_BASIC_TEST 31
+
+enum ipa_basic_opt {
+ IPA_DOMAIN = 0,
+ IPA_SERVER,
+ IPA_HOSTNAME,
+
+ IPA_OPTS_BASIC /* opts counter */
+};
+
+struct ipa_options {
+ struct dp_option *basic;
+
+ struct ipa_service *service;
+
+ /* id provider */
+ struct sdap_options *id;
+ struct sdap_id_ctx *id_ctx;
+
+ /* auth and chpass provider */
+ struct dp_option *auth;
+ struct krb5_ctx *auth_ctx;
+};
+
+/* options parsers */
+int ipa_get_options(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sss_domain_info *dom,
+ struct ipa_options **_opts);
+
+int ipa_get_id_options(struct ipa_options *ipa_opts,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sdap_options **_opts);
+
+int ipa_get_auth_options(struct ipa_options *ipa_opts,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct dp_option **_opts);
+
+int ipa_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
+ const char *servers, const char *domain,
+ struct ipa_service **_service);
+
+#endif /* _IPA_COMMON_H_ */
diff --git a/src/providers/ipa/ipa_init.c b/src/providers/ipa/ipa_init.c
new file mode 100644
index 000000000..10b9257a2
--- /dev/null
+++ b/src/providers/ipa/ipa_init.c
@@ -0,0 +1,293 @@
+/*
+ SSSD
+
+ IPA Provider Initialization functions
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2009 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 <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "providers/child_common.h"
+#include "providers/ipa/ipa_common.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/ipa/ipa_auth.h"
+#include "providers/ipa/ipa_access.h"
+#include "providers/ipa/ipa_timerules.h"
+
+struct ipa_options *ipa_options = NULL;
+
+/* Id Handler */
+struct bet_ops ipa_id_ops = {
+ .handler = sdap_account_info_handler,
+ .finalize = NULL
+};
+
+struct bet_ops ipa_auth_ops = {
+ .handler = ipa_auth,
+ .finalize = NULL,
+};
+
+struct bet_ops ipa_chpass_ops = {
+ .handler = krb5_pam_handler,
+ .finalize = NULL,
+};
+
+struct bet_ops ipa_access_ops = {
+ .handler = ipa_access_handler,
+ .finalize = NULL
+};
+
+int common_ipa_init(struct be_ctx *bectx)
+{
+ const char *ipa_servers;
+ const char *ipa_domain;
+ int ret;
+
+ ret = ipa_get_options(bectx, bectx->cdb,
+ bectx->conf_path,
+ bectx->domain, &ipa_options);
+ if (ret != EOK) {
+ return ret;
+ }
+
+ ipa_servers = dp_opt_get_string(ipa_options->basic, IPA_SERVER);
+ if (!ipa_servers) {
+ DEBUG(0, ("Missing ipa_server option!\n"));
+ return EINVAL;
+ }
+
+ ipa_domain = dp_opt_get_string(ipa_options->basic, IPA_DOMAIN);
+ if (!ipa_domain) {
+ DEBUG(0, ("Missing ipa_domain option!\n"));
+ return EINVAL;
+ }
+
+ ret = ipa_service_init(ipa_options, bectx, ipa_servers, ipa_domain,
+ &ipa_options->service);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to init IPA failover service!\n"));
+ return ret;
+ }
+
+ return EOK;
+}
+
+int sssm_ipa_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_data)
+{
+ struct sdap_id_ctx *ctx;
+ int ret;
+
+ if (!ipa_options) {
+ ret = common_ipa_init(bectx);
+ if (ret != EOK) {
+ return ret;
+ }
+ }
+
+ if (ipa_options->id_ctx) {
+ /* already initialized */
+ *ops = &ipa_id_ops;
+ *pvt_data = ipa_options->id_ctx;
+ return EOK;
+ }
+
+ ctx = talloc_zero(ipa_options, struct sdap_id_ctx);
+ if (!ctx) {
+ return ENOMEM;
+ }
+ ctx->be = bectx;
+ ctx->service = ipa_options->service->sdap;
+ ipa_options->id_ctx = ctx;
+
+ ret = ipa_get_id_options(ipa_options, bectx->cdb,
+ bectx->conf_path,
+ &ctx->opts);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = setup_tls_config(ctx->opts->basic);
+ if (ret != EOK) {
+ DEBUG(1, ("setup_tls_config failed [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ ret = sdap_id_setup_tasks(ctx);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = setup_child(ctx);
+ if (ret != EOK) {
+ DEBUG(1, ("setup_child failed [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ *ops = &ipa_id_ops;
+ *pvt_data = ctx;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_zfree(ipa_options->id_ctx);
+ }
+ return ret;
+}
+
+int sssm_ipa_auth_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_data)
+{
+ struct krb5_ctx *ctx;
+ struct tevent_signal *sige;
+ FILE *debug_filep;
+ unsigned v;
+ int ret;
+
+ if (!ipa_options) {
+ ret = common_ipa_init(bectx);
+ if (ret != EOK) {
+ return ret;
+ }
+ }
+
+ if (ipa_options->auth_ctx) {
+ /* already initialized */
+ *ops = &ipa_auth_ops;
+ *pvt_data = ipa_options->auth_ctx;
+ return EOK;
+ }
+
+ ctx = talloc_zero(bectx, struct krb5_ctx);
+ if (!ctx) {
+ return ENOMEM;
+ }
+ ctx->service = ipa_options->service->krb5_service;
+ ipa_options->auth_ctx = ctx;
+
+ ret = ipa_get_auth_options(ipa_options, bectx->cdb,
+ bectx->conf_path,
+ &ctx->opts);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = check_and_export_options(ctx->opts, bectx->domain);
+ if (ret != EOK) {
+ DEBUG(1, ("check_and_export_opts failed.\n"));
+ goto done;
+ }
+
+ sige = tevent_add_signal(bectx->ev, ctx, SIGCHLD, SA_SIGINFO,
+ child_sig_handler, NULL);
+ if (sige == NULL) {
+ DEBUG(1, ("tevent_add_signal failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (debug_to_file != 0) {
+ ret = open_debug_file_ex("krb5_child", &debug_filep);
+ if (ret != EOK) {
+ DEBUG(0, ("Error setting up logging (%d) [%s]\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ ctx->child_debug_fd = fileno(debug_filep);
+ if (ctx->child_debug_fd == -1) {
+ DEBUG(0, ("fileno failed [%d][%s]\n", errno, strerror(errno)));
+ ret = errno;
+ goto done;
+ }
+
+ v = fcntl(ctx->child_debug_fd, F_GETFD, 0);
+ fcntl(ctx->child_debug_fd, F_SETFD, v & ~FD_CLOEXEC);
+ }
+
+ *ops = &ipa_auth_ops;
+ *pvt_data = ctx;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_zfree(ipa_options->auth_ctx);
+ }
+ return ret;
+}
+
+int sssm_ipa_chpass_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_data)
+{
+ int ret;
+ ret = sssm_ipa_auth_init(bectx, ops, pvt_data);
+ *ops = &ipa_chpass_ops;
+ return ret;
+}
+
+int sssm_ipa_access_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_data)
+{
+ int ret;
+ struct ipa_access_ctx *ipa_access_ctx;
+
+ ipa_access_ctx = talloc_zero(bectx, struct ipa_access_ctx);
+ if (ipa_access_ctx == NULL) {
+ DEBUG(1, ("talloc_zero failed.\n"));
+ return ENOMEM;
+ }
+
+ ret = sssm_ipa_init(bectx, ops, (void **) &ipa_access_ctx->sdap_ctx);
+ if (ret != EOK) {
+ DEBUG(1, ("sssm_ipa_init failed.\n"));
+ goto done;
+ }
+
+ ret = dp_copy_options(ipa_access_ctx, ipa_options->basic,
+ IPA_OPTS_BASIC, &ipa_access_ctx->ipa_options);
+ if (ret != EOK) {
+ DEBUG(1, ("dp_copy_options failed.\n"));
+ goto done;
+ }
+
+ ret = init_time_rules_parser(ipa_access_ctx, &ipa_access_ctx->tr_ctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_time_rules_parser failed.\n"));
+ goto done;
+ }
+
+ *ops = &ipa_access_ops;
+ *pvt_data = ipa_access_ctx;
+
+done:
+ if (ret != EOK) {
+ talloc_free(ipa_access_ctx);
+ }
+ return ret;
+}
diff --git a/src/providers/ipa/ipa_timerules.c b/src/providers/ipa/ipa_timerules.c
new file mode 100644
index 000000000..1a52eef12
--- /dev/null
+++ b/src/providers/ipa/ipa_timerules.c
@@ -0,0 +1,1186 @@
+/*
+ SSSD
+
+ IPA Provider Time Rules Parsing
+
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ 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/>.
+*/
+
+#define _XOPEN_SOURCE /* strptime() needs this */
+
+#include <pcre.h>
+#include <talloc.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <stdbool.h>
+#include <limits.h>
+
+#include "providers/ipa/ipa_timerules.h"
+#include "util/util.h"
+
+#define JMP_NEOK(variable) do { \
+ if (variable != EOK) goto done; \
+} while (0)
+
+#define JMP_NEOK_LABEL(variable, label) do { \
+ if (variable != EOK) goto label; \
+} while (0)
+
+#define CHECK_PTR(ptr) do { \
+ if (ptr == NULL) { \
+ return ENOMEM; \
+ } \
+} while (0)
+
+#define CHECK_PTR_JMP(ptr) do { \
+ if (ptr == NULL) { \
+ ret = ENOMEM; \
+ goto done; \
+ } \
+} while (0)
+
+#define BUFFER_OR_JUMP(ctx, ptr, count) do { \
+ ptr = talloc_array(ctx, unsigned char, count); \
+ if (ptr == NULL) { \
+ return ENOMEM; \
+ } \
+ memset(ptr, 0, sizeof(unsigned char)*count); \
+} while (0)
+
+#define TEST_BIT_RANGE(bitfield, index, resptr) do { \
+ if (bitfield) { \
+ if (test_bit(&bitfield, index) == 0) { \
+ *resptr = false; \
+ return EOK; \
+ } \
+ } \
+} while (0)
+
+#define TEST_BIT_RANGE_PTR(bitfield, index, resptr) do { \
+ if (bitfield) { \
+ if (test_bit(bitfield, index) == 0) { \
+ *resptr = false; \
+ return EOK; \
+ } \
+ } \
+} while (0)
+
+/* number of match offsets when matching pcre regexes */
+#define OVEC_SIZE 30
+
+/* regular expressions describing syntax of our HBAC grammar */
+#define RGX_WEEKLY "day (?P<day_of_week>(0|1|2|3|4|5|6|7|Mon|Tue|Wed|Thu|Fri|Sat|Sun|,|-)+)"
+
+#define RGX_MDAY "(?P<mperspec_day>day) (?P<interval_day>[0-9,-]+) "
+#define RGX_MWEEK "(?P<mperspec_week>week) (?P<interval_week>[0-9,-]+) "RGX_WEEKLY
+#define RGX_MONTHLY RGX_MDAY"|"RGX_MWEEK
+
+#define RGX_YDAY "(?P<yperspec_day>day) (?P<day_of_year>[0-9,-]+) "
+#define RGX_YWEEK "(?P<yperspec_week>week) (?P<week_of_year>[0-9,-]+) "RGX_WEEKLY
+#define RGX_YMONTH "(?P<yperspec_month>month) (?P<month_number>[0-9,-]+) (?P<m_period>.*?)$"
+#define RGX_YEARLY RGX_YMONTH"|"RGX_YWEEK"|"RGX_YDAY
+
+#define RGX_TIMESPEC "(?P<timeFrom>[0-9]{4}) ~ (?P<timeTo>[0-9]{4})"
+
+#define RGX_GENERALIZED "(?P<year>[0-9]{4})(?P<month>[0-9]{2})(?P<day>[0-9]{2})(?P<hour>[0-9]{2})?(?P<minute>[0-9]{2})?(?P<second>[0-9]{2})?"
+
+#define RGX_PERIODIC "^periodic (?P<perspec>daily|weekly|monthly|yearly) (?P<period>.*?)"RGX_TIMESPEC"$"
+#define RGX_ABSOLUTE "^absolute (?P<from>\\S+) ~ (?P<to>\\S+)$"
+
+/* limits on various parameters */
+#define DAY_OF_WEEK_MAX 7
+#define DAY_OF_MONTH_MAX 31
+#define WEEK_OF_MONTH_MAX 5
+#define WEEK_OF_YEAR_MAX 54
+#define DAY_OF_YEAR_MAX 366
+#define MONTH_MAX 12
+#define HOUR_MAX 23
+#define MINUTE_MAX 59
+
+/* limits on sizes of buffers for bit arrays */
+#define DAY_OF_MONTH_BUFSIZE 8
+#define DAY_OF_YEAR_BUFSIZE 44
+#define WEEK_OF_YEAR_BUFSIZE 13
+#define MONTH_BUFSIZE 2
+#define HOUR_BUFSIZE 4
+#define MINUTE_BUFSIZE 8
+
+/* Lookup tables for translating names of days and months */
+static const char *names_day_of_week[] =
+ { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", NULL };
+static const char *names_months[] =
+ { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Nov", "Dec", NULL };
+
+/*
+ * Timelib knows two types of ranges - periodic and absolute
+ */
+enum rangetypes {
+ TYPE_ABSOLUTE,
+ TYPE_PERIODIC
+};
+
+struct absolute_range {
+ time_t time_from;
+ time_t time_to;
+};
+
+struct periodic_range {
+ unsigned char day_of_week;
+ unsigned char *day_of_month;
+ unsigned char *day_of_year;
+ unsigned char week_of_month;
+ unsigned char *week_of_year;
+ unsigned char *month;
+ unsigned char *hour;
+ unsigned char *minute;
+};
+
+/*
+ * Context of one time rule being analyzed
+ */
+struct range_ctx {
+ /* main context with precompiled patterns */
+ struct time_rules_ctx *trctx;
+ /* enum rangetypes */
+ enum rangetypes type;
+
+ struct absolute_range *abs;
+ struct periodic_range *per;
+};
+
+
+/*
+ * The context of one regular expression
+ */
+struct parse_ctx {
+ /* the regular expression used for one parsing */
+ pcre *re;
+ /* number of matches */
+ int matches;
+ /* vector of matches */
+ int *ovec;
+};
+
+/* indexes to the array of precompiled regexes */
+enum timelib_rgx {
+ LP_RGX_GENERALIZED,
+ LP_RGX_MDAY,
+ LP_RGX_MWEEK,
+ LP_RGX_YEARLY,
+ LP_RGX_WEEKLY,
+ LP_RGX_ABSOLUTE,
+ LP_RGX_PERIODIC,
+ LP_RGX_MAX,
+};
+
+/* matches the indexes */
+static const char *lookup_table[] = {
+ RGX_GENERALIZED,
+ RGX_MDAY,
+ RGX_MWEEK,
+ RGX_YEARLY,
+ RGX_WEEKLY,
+ RGX_ABSOLUTE,
+ RGX_PERIODIC,
+ NULL,
+};
+
+/*
+ * Main struct passed outside
+ * holds precompiled regular expressions
+ */
+struct time_rules_ctx {
+ pcre *re[LP_RGX_MAX];
+};
+
+/*******************************************************************
+ * helper function - bit arrays *
+ *******************************************************************/
+
+/* set a single bit in a bitmap */
+static void set_bit(unsigned char *bitmap, unsigned int bit)
+{
+ bitmap[bit/CHAR_BIT] |= 1 << (bit%CHAR_BIT);
+}
+
+/*
+ * This function is based on bit_nset macro written originally by Paul Vixie,
+ * copyrighted by The Regents of the University of California, as found
+ * in tarball of fcron, file bitstring.h
+ */
+static void set_bit_range(unsigned char *bitmap, unsigned int start,
+ unsigned int stop)
+{
+ int startbyte = start/CHAR_BIT;
+ int stopbyte = stop/CHAR_BIT;
+
+ if (startbyte == stopbyte) {
+ bitmap[startbyte] |= ((0xff << (start & 0x7)) &
+ (0xff >> (CHAR_BIT- 1 - (stop & 0x7))));
+ } else {
+ bitmap[startbyte] |= 0xff << (start & 0x7);
+ while (++startbyte < stopbyte) {
+ bitmap[startbyte] |= 0xff;
+ }
+ bitmap[stopbyte] |= 0xff >> (CHAR_BIT- 1 - (stop & 0x7));
+ }
+}
+
+static int test_bit(unsigned char *bitmap, unsigned int bit)
+{
+ return (int)(bitmap[bit/CHAR_BIT] >> (bit%CHAR_BIT)) & 1;
+}
+
+/*******************************************************************
+ * parsing intervals *
+ *******************************************************************/
+
+/*
+ * Some ranges allow symbolic names, like Mon..Sun for names of day.
+ * This routine takes a list of symbolic names as NAME_ARRAY and the
+ * one we're looking for as KEY and returns its index or -1 when not
+ * found. The last member of NAME_ARRAY must be NULL.
+ */
+static int name_index(const char **name_array, const char *key, int min)
+{
+ int index = 0;
+ const char *one;
+
+ if (name_array == NULL) {
+ return -1;
+ }
+
+ while ((one = name_array[index]) != NULL) {
+ if (strcmp(key,one) == 0) {
+ return index+min;
+ }
+ index++;
+ }
+
+ return -1;
+}
+
+/*
+ * Sets appropriate bits given by an interval in STR (in form of 1,5-7,10) to
+ * a bitfield given in OUT. Does no boundary checking. STR can also contain
+ * symbolic names, these would be given in TRANSLATE.
+ */
+static int interval2bitfield(TALLOC_CTX *mem_ctx,
+ unsigned char *out,
+ const char *str,
+ int min, int max,
+ const char **translate)
+{
+ char *copy;
+ char *next, *token;
+ int tokval, tokmax;
+ char *end_ptr;
+ int ret;
+ char *dash;
+
+ DEBUG(9, ("Converting '%s' to interval\n", str));
+
+ copy = talloc_strdup(mem_ctx, str);
+ CHECK_PTR(copy);
+
+ next = copy;
+ while (next) {
+ token = next;
+ next = strchr(next, ',');
+ if (next) {
+ *next = '\0';
+ next++;
+ }
+
+ errno = 0;
+ tokval = strtol(token, &end_ptr, 10);
+ if (*end_ptr == '\0' && errno == 0) {
+ if (tokval <= max && tokval >= 0) {
+ set_bit(out, tokval);
+ continue;
+ } else {
+ ret = ERANGE;
+ goto done;
+ }
+ } else if ((dash = strchr(token, '-')) != NULL){
+ *dash = '\0';
+ ++dash;
+
+ errno = 0;
+ tokval = strtol(token, &end_ptr, 10);
+ if (*end_ptr != '\0' || errno != 0) {
+ tokval = name_index(translate, token, min);
+ if (tokval == -1) {
+ ret = ERANGE;
+ goto done;
+ }
+ }
+ errno = 0;
+ tokmax = strtol(dash, &end_ptr, 10);
+ if (*end_ptr != '\0' || errno != 0) {
+ tokmax = name_index(translate, dash, min);
+ if (tokmax == -1) {
+ ret = ERANGE;
+ goto done;
+ }
+ }
+
+ if (tokval <= max && tokmax <= max &&
+ tokval >= min && tokmax >= min) {
+ if (tokmax > tokval) {
+ DEBUG(7, ("Setting interval %d-%d\n", tokval, tokmax));
+ DEBUG(9, ("interval: %p\n", out));
+ set_bit_range(out, tokval, tokmax);
+ } else {
+ /* Interval wraps around - i.e. from 18.00 to 06.00 */
+ DEBUG(7, ("Setting inverted interval %d-%d\n", tokval, tokmax));
+ DEBUG(9, ("interval: %p\n", out));
+ set_bit_range(out, min, tokmax);
+ set_bit_range(out, tokval, max);
+ }
+ continue;
+ } else {
+ /* tokval or tokmax are not between <min, max> */
+ ret = ERANGE;
+ goto done;
+ }
+ } else if ((tokval = name_index(translate, token, min)) != -1) {
+ /* Try to translate one token by name */
+ if (tokval <= max) {
+ set_bit(out, tokval);
+ continue;
+ } else {
+ ret = ERANGE;
+ goto done;
+ }
+ } else {
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ ret = EOK;
+done:
+ talloc_free(copy);
+ return ret;
+}
+
+/*******************************************************************
+ * wrappers around regexp handling *
+ *******************************************************************/
+
+/*
+ * Copies a named substring SUBSTR_NAME from string STR using the parsing
+ * information from PCTX. The context PCTX is also used as a talloc context.
+ *
+ * The resulting string is stored in OUT.
+ * Return value is EOK on no error or ENOENT on error capturing the substring
+ */
+static int copy_substring(struct parse_ctx *pctx,
+ const char *str,
+ const char *substr_name,
+ char **out)
+{
+ const char *result = NULL;
+ int ret;
+ char *o = NULL;
+
+ result = NULL;
+
+ ret = pcre_get_named_substring(pctx->re, str, pctx->ovec,
+ pctx->matches, substr_name, &result);
+ if (ret < 0 || result == NULL) {
+ DEBUG(5, ("named substring '%s' does not exist in '%s'\n",
+ substr_name, str));
+ return ENOENT;
+ }
+
+ o = talloc_strdup(pctx, result);
+ pcre_free_substring(result);
+ if (o == NULL) {
+ return ENOMEM;
+ }
+
+ DEBUG(9, ("Copied substring named '%s' value '%s'\n", substr_name, o));
+
+ *out = o;
+ return EOK;
+}
+
+/*
+ * Copies a named substring SUBSTR_NAME from string STR using the parsing
+ * information from PCTX and converts it to an integer.
+ * The context PCTX is also used as a talloc context.
+ *
+ * The resulting string is stored in OUT.
+ * Return value is EOK on no error or ENOENT on error capturing the substring
+ */
+static int substring_strtol(struct parse_ctx *pctx,
+ const char *str,
+ const char *substr_name,
+ int *out)
+{
+ char *substr = NULL;
+ int ret;
+ int val;
+ char *err_ptr;
+
+ ret = copy_substring(pctx, str, substr_name, &substr);
+ if (ret != EOK) {
+ DEBUG(5, ("substring '%s' does not exist\n", substr_name));
+ return ret;
+ }
+
+ errno = 0;
+ val = strtol(substr, &err_ptr, 10);
+ if (substr == '\0' || *err_ptr != '\0' || errno != 0) {
+ DEBUG(5, ("substring '%s' does not contain an integerexist\n",
+ substr));
+ talloc_free(substr);
+ return EINVAL;
+ }
+
+ *out = val;
+ talloc_free(substr);
+ return EOK;
+}
+
+/*
+ * Compiles a regular expression REGEXP and tries to match it against the
+ * string STR. Fills in structure _PCTX with info about matching.
+ *
+ * Returns EOK on no error, EFAULT on bad regexp, EINVAL when it cannot
+ * match the regexp.
+ */
+static int matches_regexp(TALLOC_CTX *ctx,
+ struct time_rules_ctx *trctx,
+ const char *str,
+ enum timelib_rgx regex,
+ struct parse_ctx **_pctx)
+{
+ int ret;
+ struct parse_ctx *pctx = NULL;
+
+ pctx = talloc_zero(ctx, struct parse_ctx);
+ CHECK_PTR(pctx);
+ pctx->ovec = talloc_array(pctx, int, OVEC_SIZE);
+ CHECK_PTR_JMP(pctx->ovec);
+ pctx->re = trctx->re[regex];
+
+ ret = pcre_exec(pctx->re, NULL, str, strlen(str), 0, PCRE_NOTEMPTY, pctx->ovec, OVEC_SIZE);
+ if (ret <= 0) {
+ DEBUG(8, ("string '%s' did *NOT* match regexp '%s'\n", str, lookup_table[regex]));
+ ret = EINVAL;
+ goto done;
+ }
+ DEBUG(8, ("string '%s' matched regexp '%s'\n", str, lookup_table[regex]));
+
+ pctx->matches = ret;
+ *_pctx = pctx;
+ return EOK;
+
+done:
+ talloc_free(pctx);
+ return ret;
+}
+
+/*******************************************************************
+ * date/time helper functions *
+ *******************************************************************/
+
+/*
+ * Returns week number as an integer
+ * This may seem ugly, but I think it's actually less error prone
+ * than writing my own routine
+ */
+static int weeknum(const struct tm *t)
+{
+ char buf[3];
+
+ if (!strftime(buf, 3, "%U", t)) {
+ return -1;
+ }
+
+ /* %U returns 0-53, we want 1-54 */
+ return atoi(buf)+1;
+}
+
+/*
+ * Return the week of the month
+ * Range is 1 to 5
+ */
+static int get_week_of_month(const struct tm *t)
+{
+ int fs; /* first sunday */
+
+ fs = (t->tm_mday % 7) - t->tm_wday;
+ if (fs <= 0) {
+ fs += 7;
+ }
+
+ return (t->tm_mday <= fs) ? 1 : (2 + (t->tm_mday - fs - 1) / 7);
+}
+
+/*
+ * Normalize differencies between our HBAC definition and semantics of
+ * struct tm
+ */
+static void abs2tm(struct tm *t)
+{
+ /* tm defines tm_year as num of yrs since 1900, we have absolute number */
+ t->tm_year %= 1900;
+ /* struct tm defines tm_mon as number of month since January */
+ t->tm_mon--;
+}
+
+/*
+ * Normalize differencies between our HBAC definition and semantics of
+ * struct tm
+ */
+static void tm2abs(struct tm *t)
+{
+ /* tm defines tm_year as num of yrs since 1900, we have absolute number */
+ t->tm_year += 1900;
+ /* struct tm defines tm_mon as number of month since January */
+ t->tm_mon++;
+}
+
+/*******************************************************************
+ * parsing of HBAC rules themselves *
+ *******************************************************************/
+
+/*
+ * Parses generalized time string given in STR and fills the
+ * information into OUT.
+ */
+static int parse_generalized_time(struct parse_ctx *pctx,
+ struct time_rules_ctx *trctx,
+ const char *str,
+ time_t *out)
+{
+ int ret;
+ struct parse_ctx *gctx = NULL;
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_isdst = -1;
+
+ ret = matches_regexp(pctx, trctx, str, LP_RGX_GENERALIZED, &gctx);
+ JMP_NEOK(ret);
+
+ /* compulsory */
+ ret = substring_strtol(gctx, str, "year", &tm.tm_year);
+ JMP_NEOK(ret);
+ ret = substring_strtol(gctx, str, "month", &tm.tm_mon);
+ JMP_NEOK(ret);
+ ret = substring_strtol(gctx, str, "day", &tm.tm_mday);
+ JMP_NEOK(ret);
+ /* optional */
+ ret = substring_strtol(gctx, str, "hour", &tm.tm_hour);
+ JMP_NEOK_LABEL(ret, enoent);
+ ret = substring_strtol(gctx, str, "minute", &tm.tm_min);
+ JMP_NEOK_LABEL(ret, enoent);
+ ret = substring_strtol(gctx, str, "second", &tm.tm_sec);
+ JMP_NEOK_LABEL(ret, enoent);
+
+enoent:
+ if (ret == ENOENT) {
+ ret = EOK;
+ }
+
+ abs2tm(&tm);
+
+ *out = mktime(&tm);
+ DEBUG(3, ("converted to time: '%s'\n", ctime(out)));
+ if (*out == -1) {
+ ret = EINVAL;
+ }
+done:
+ talloc_free(gctx);
+ return ret;
+}
+
+/*
+ * Parses absolute timerange string given in STR and fills the
+ * information into ABS.
+ */
+static int parse_absolute(struct absolute_range *absr,
+ struct time_rules_ctx *trctx,
+ struct parse_ctx *pctx,
+ const char *str)
+{
+ char *from = NULL, *to = NULL;
+ int ret;
+
+ ret = copy_substring(pctx, str, "from", &from);
+ if (ret != EOK) {
+ DEBUG(1, ("Missing required part 'from' in absolute timespec\n"));
+ ret = EINVAL;
+ goto done;
+ }
+ ret = copy_substring(pctx, str, "to", &to);
+ if (ret != EOK) {
+ DEBUG(1, ("Missing required part 'to' in absolute timespec\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = parse_generalized_time(pctx, trctx, from, &absr->time_from);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse generalized time - first part\n"));
+ goto done;
+ }
+
+ ret = parse_generalized_time(pctx, trctx, to, &absr->time_to);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse generalized time - second part\n"));
+ goto done;
+ }
+
+ if (difftime(absr->time_to, absr->time_from) < 0) {
+ DEBUG(1, ("Not a valid interval\n"));
+ ret = EINVAL;
+ }
+
+ ret = EOK;
+done:
+ talloc_free(from);
+ talloc_free(to);
+ return ret;
+}
+
+static int parse_hhmm(const char *str, int *hour, int *min)
+{
+ struct tm t;
+ char *err;
+
+ err = strptime(str, "%H%M", &t);
+ if (*err != '\0') {
+ return EINVAL;
+ }
+
+ *hour = t.tm_hour;
+ *min = t.tm_min;
+
+ return EOK;
+}
+
+/*
+ * Parses monthly periodic timerange given in STR.
+ * Fills the information into PER.
+ */
+static int parse_periodic_monthly(TALLOC_CTX *ctx,
+ struct time_rules_ctx *trctx,
+ struct periodic_range *per,
+ const char *str)
+{
+ int ret;
+ struct parse_ctx *mpctx = NULL;
+ char *match = NULL;
+ char *mperspec = NULL;
+
+ /* This code would be much less ugly if RHEL5 PCRE knew about PCRE_DUPNAMES */
+ ret = matches_regexp(ctx, trctx, str, LP_RGX_MDAY, &mpctx);
+ if (ret == EOK) {
+ ret = copy_substring(mpctx, str, "mperspec_day", &mperspec);
+ JMP_NEOK(ret);
+ ret = copy_substring(mpctx, str, "interval_day", &match);
+ JMP_NEOK(ret);
+ BUFFER_OR_JUMP(ctx, per->day_of_month, DAY_OF_MONTH_BUFSIZE);
+ ret = interval2bitfield(mpctx, per->day_of_month, match,
+ 1, DAY_OF_MONTH_MAX, NULL);
+ JMP_NEOK(ret);
+ } else {
+ ret = matches_regexp(ctx, trctx, str, LP_RGX_MWEEK, &mpctx);
+ JMP_NEOK(ret);
+ ret = copy_substring(mpctx, str, "mperspec_week", &mperspec);
+ JMP_NEOK(ret);
+
+ ret = copy_substring(mpctx, str, "interval_week", &match);
+ JMP_NEOK(ret);
+ ret = interval2bitfield(mpctx, &per->week_of_month, match,
+ 1, WEEK_OF_MONTH_MAX, NULL);
+ JMP_NEOK(ret);
+
+ ret = copy_substring(mpctx, str, "day_of_week", &match);
+ JMP_NEOK(ret);
+ ret = interval2bitfield(mpctx, &per->day_of_week, match,
+ 1, DAY_OF_WEEK_MAX, names_day_of_week);
+ JMP_NEOK(ret);
+ }
+
+done:
+ talloc_free(mpctx);
+ return ret;
+}
+
+/*
+ * Parses yearly periodic timerange given in STR.
+ * Fills the information into PER.
+ */
+static int parse_periodic_yearly(TALLOC_CTX *ctx,
+ struct time_rules_ctx *trctx,
+ struct periodic_range *per,
+ const char *str)
+{
+ int ret;
+ struct parse_ctx *ypctx = NULL;
+ char *match = NULL;
+ char *yperspec = NULL;
+
+ ret = matches_regexp(ctx, trctx, str, LP_RGX_YEARLY, &ypctx);
+ JMP_NEOK(ret);
+ ret = copy_substring(ypctx, str, "yperspec_day", &yperspec);
+ if (ret == EOK) {
+ ret = copy_substring(ypctx, str, "day_of_year", &match);
+ JMP_NEOK(ret);
+ BUFFER_OR_JUMP(ypctx, per->day_of_year, DAY_OF_YEAR_BUFSIZE);
+ ret = interval2bitfield(ypctx, per->day_of_year, match,
+ 1, DAY_OF_YEAR_MAX, NULL);
+ JMP_NEOK(ret);
+ }
+
+ if (ret != ENOENT) goto done;
+
+ ret = copy_substring(ypctx, str, "yperspec_week", &yperspec);
+ if (ret == EOK) {
+ ret = copy_substring(ypctx, str, "week_of_year", &match);
+ JMP_NEOK(ret);
+ BUFFER_OR_JUMP(ypctx, per->week_of_year, WEEK_OF_YEAR_BUFSIZE);
+ ret = interval2bitfield(ypctx, per->week_of_year, match,
+ 1, WEEK_OF_YEAR_MAX, NULL);
+ JMP_NEOK(ret);
+
+ talloc_free(match);
+ ret = copy_substring(ypctx, str, "day_of_week", &match);
+ JMP_NEOK(ret);
+ ret = interval2bitfield(ypctx, &per->day_of_week, match,
+ 1, DAY_OF_WEEK_MAX, names_day_of_week);
+ JMP_NEOK(ret);
+ }
+
+ if (ret != ENOENT) goto done;
+
+ ret = copy_substring(ypctx, str, "yperspec_month", &yperspec);
+ JMP_NEOK(ret);
+
+ talloc_free(match);
+ ret = copy_substring(ypctx, str, "month_number", &match);
+ JMP_NEOK(ret);
+ BUFFER_OR_JUMP(ypctx, per->month, MONTH_BUFSIZE);
+ ret = interval2bitfield(ypctx, per->month, match,
+ 1, MONTH_MAX, names_months);
+ JMP_NEOK(ret);
+
+ talloc_free(match);
+ ret = copy_substring(ypctx, str, "m_period", &match);
+ JMP_NEOK(ret);
+ DEBUG(7, ("Monthly year period - calling parse_periodic_monthly()\n"));
+ ret = parse_periodic_monthly(ypctx, trctx, per, match);
+ JMP_NEOK(ret);
+
+done:
+ talloc_free(ypctx);
+ return ret;
+}
+
+/*
+ * Parses weekly periodic timerange given in STR.
+ * Fills the information into PER.
+ */
+static int parse_periodic_weekly(TALLOC_CTX *ctx,
+ struct time_rules_ctx *trctx,
+ struct periodic_range *per,
+ const char *str)
+{
+ int ret;
+ struct parse_ctx *wpctx = NULL;
+ char *dow = NULL;
+
+ ret = matches_regexp(ctx, trctx, str, LP_RGX_WEEKLY, &wpctx);
+ JMP_NEOK(ret);
+
+ ret = copy_substring(wpctx, str, "day_of_week", &dow);
+ JMP_NEOK(ret);
+ DEBUG(8, ("day_of_week = '%s'\n", dow));
+
+ ret = interval2bitfield(wpctx, &per->day_of_week, dow,
+ 1, DAY_OF_WEEK_MAX, names_day_of_week);
+
+done:
+ talloc_free(wpctx);
+ return ret;
+}
+
+static int parse_periodic_time(struct periodic_range *per,
+ struct parse_ctx *pctx,
+ const char *str)
+{
+ char *substr = NULL;
+ int ret;
+
+ int hour_from;
+ int hour_to;
+ int min_from;
+ int min_to;
+
+ /* parse out the time */
+ ret = copy_substring(pctx, str, "timeFrom", &substr);
+ JMP_NEOK(ret);
+ parse_hhmm(substr, &hour_from, &min_from);
+ DEBUG(7, ("Parsed timeFrom: %d:%d\n", hour_from, min_from));
+ JMP_NEOK(ret);
+
+ talloc_free(substr);
+ ret = copy_substring(pctx, str, "timeTo", &substr);
+ JMP_NEOK(ret);
+ parse_hhmm(substr, &hour_to, &min_to);
+ DEBUG(7, ("Parsed timeTo: %d:%d\n", hour_to, min_to));
+ JMP_NEOK(ret);
+
+ /* set the interval */
+ if (hour_from > hour_to ) {
+ set_bit_range(per->hour, 0, hour_to);
+ set_bit_range(per->hour, hour_from, HOUR_MAX);
+ } else {
+ set_bit_range(per->hour, hour_from, hour_to);
+ }
+
+ if (min_from > min_to) {
+ set_bit_range(per->minute, 0, min_to);
+ set_bit_range(per->minute, min_from, MINUTE_MAX);
+ } else {
+ set_bit_range(per->minute, min_from, min_to);
+ }
+
+
+ ret = EOK;
+done:
+ talloc_free(substr);
+ return ret;
+}
+
+/*
+ * Parses periodic timerange given in STR.
+ * Fills the information into PER.
+ */
+static int parse_periodic(struct periodic_range *per,
+ struct time_rules_ctx *trctx,
+ struct parse_ctx *pctx,
+ const char *str)
+{
+ char *substr = NULL;
+ char *period = NULL;
+ int ret;
+
+ /* These are mandatory */
+ BUFFER_OR_JUMP(per, per->hour, HOUR_BUFSIZE);
+ BUFFER_OR_JUMP(per, per->minute, MINUTE_BUFSIZE);
+
+ ret = copy_substring(pctx, str, "perspec", &substr);
+ JMP_NEOK(ret);
+ ret = copy_substring(pctx, str, "period", &period);
+ JMP_NEOK(ret);
+
+ if (strcmp(substr, "yearly") == 0) {
+ DEBUG(5, ("periodic yearly\n"));
+ ret = parse_periodic_yearly(pctx, trctx, per, period);
+ JMP_NEOK(ret);
+ } else if (strcmp(substr, "monthly") == 0) {
+ DEBUG(5, ("periodic monthly\n"));
+ ret = parse_periodic_monthly(pctx, trctx, per, period);
+ JMP_NEOK(ret);
+ } else if (strcmp(substr, "weekly") == 0) {
+ DEBUG(5, ("periodic weekly\n"));
+ ret = parse_periodic_weekly(pctx, trctx, per, period);
+ JMP_NEOK(ret);
+ } else if (strcmp(substr, "daily") == 0) {
+ DEBUG(5, ("periodic daily\n"));
+ } else {
+ DEBUG(1, ("Cannot determine periodic rule type"
+ "(perspec = '%s', period = '%s')\n", substr, period));
+ ret = EINVAL;
+ goto done;
+ }
+
+ talloc_free(period);
+
+ ret = parse_periodic_time(per, pctx, str);
+ JMP_NEOK(ret);
+
+ ret = EOK;
+done:
+ talloc_free(substr);
+ return ret;
+}
+
+/*
+ * Parses time specification given in string RULE into range_ctx
+ * context CTX.
+ */
+static int parse_timespec(struct range_ctx *ctx, const char *rule)
+{
+ int ret;
+ struct parse_ctx *pctx = NULL;
+
+ if (matches_regexp(ctx, ctx->trctx, rule, LP_RGX_ABSOLUTE, &pctx) == EOK) {
+ DEBUG(5, ("Matched absolute range\n"));
+ ctx->type = TYPE_ABSOLUTE;
+ ctx->abs = talloc_zero(ctx, struct absolute_range);
+ CHECK_PTR_JMP(ctx->abs);
+
+ ret = parse_absolute(ctx->abs, ctx->trctx, pctx, rule);
+ JMP_NEOK(ret);
+ } else if (matches_regexp(ctx, ctx->trctx, rule, LP_RGX_PERIODIC, &pctx) == EOK) {
+ DEBUG(5, ("Matched periodic range\n"));
+ ctx->type = TYPE_PERIODIC;
+ ctx->per = talloc_zero(ctx, struct periodic_range);
+ CHECK_PTR_JMP(ctx->per);
+
+ ret = parse_periodic(ctx->per, ctx->trctx, pctx, rule);
+ JMP_NEOK(ret);
+ } else {
+ DEBUG(1, ("Cannot determine rule type\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = EOK;
+done:
+ talloc_free(pctx);
+ return ret;
+}
+
+/*******************************************************************
+ * validation of rules against time_t *
+ *******************************************************************/
+
+static int absolute_timerange_valid(struct absolute_range *absr,
+ const time_t now,
+ bool *result)
+{
+ if (difftime(absr->time_from, now) > 0) {
+ DEBUG(3, ("Absolute timerange invalid (before interval)\n"));
+ *result = false;
+ return EOK;
+ }
+
+ if (difftime(absr->time_to, now) < 0) {
+ DEBUG(3, ("Absolute timerange invalid (after interval)\n"));
+ *result = false;
+ return EOK;
+ }
+
+ DEBUG(3, ("Absolute timerange valid\n"));
+ *result = true;
+ return EOK;
+}
+
+static int periodic_timerange_valid(struct periodic_range *per,
+ const time_t now,
+ bool *result)
+{
+ struct tm tm_now;
+ int wnum;
+ int wom;
+
+ memset(&tm_now, 0, sizeof(struct tm));
+ if (localtime_r(&now, &tm_now) == NULL) {
+ DEBUG(0, ("Cannot convert time_t to struct tm\n"));
+ return EFAULT;
+ }
+ DEBUG(9, ("Got struct tm value %s", asctime(&tm_now)));
+ tm2abs(&tm_now);
+
+ wnum = weeknum(&tm_now);
+ if (wnum == -1) {
+ DEBUG(7, ("Cannot get week number"));
+ return EINVAL;
+ }
+ DEBUG(9, ("Week number is %d\n", wnum));
+
+ wom = get_week_of_month(&tm_now);
+ if (wnum == -1) {
+ DEBUG(7, ("Cannot get week of number"));
+ return EINVAL;
+ }
+ DEBUG(9, ("Week of month number is %d\n", wom));
+
+ /* The validation itself */
+ TEST_BIT_RANGE(per->day_of_week, tm_now.tm_wday, result);
+ DEBUG(9, ("day of week OK\n"));
+ TEST_BIT_RANGE_PTR(per->day_of_month, tm_now.tm_mday, result);
+ DEBUG(9, ("day of month OK\n"));
+ TEST_BIT_RANGE(per->week_of_month, wom, result);
+ DEBUG(9, ("week of month OK\n"));
+ TEST_BIT_RANGE_PTR(per->week_of_year, wnum, result);
+ DEBUG(9, ("week of year OK\n"));
+ TEST_BIT_RANGE_PTR(per->month, tm_now.tm_mon, result);
+ DEBUG(9, ("month OK\n"));
+ TEST_BIT_RANGE_PTR(per->day_of_year, tm_now.tm_yday, result);
+ DEBUG(9, ("day of year OK\n"));
+ TEST_BIT_RANGE_PTR(per->hour, tm_now.tm_hour, result);
+ DEBUG(9, ("hour OK\n"));
+ TEST_BIT_RANGE_PTR(per->minute, tm_now.tm_min, result);
+ DEBUG(9, ("minute OK\n"));
+
+ DEBUG(3, ("Periodic timerange valid\n"));
+ *result = true;
+ return EOK;
+}
+
+/*
+ * Returns EOK if the timerange in range_ctx context is valid compared against a
+ * given time_t value in NOW, returns ERANGE if the time value is outside the
+ * specified range.
+ */
+static int timerange_valid(struct range_ctx *ctx,
+ const time_t now,
+ bool *result)
+{
+ int ret;
+
+ switch(ctx->type) {
+ case TYPE_ABSOLUTE:
+ DEBUG(7, ("Checking absolute range\n"));
+ ret = absolute_timerange_valid(ctx->abs, now, result);
+ break;
+
+ case TYPE_PERIODIC:
+ DEBUG(7, ("Checking periodic range\n"));
+ ret = periodic_timerange_valid(ctx->per, now, result);
+ break;
+
+ default:
+ DEBUG(1, ("Unknown range type (%d)\n", ctx->type));
+ ret = EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+/*******************************************************************
+ * public interface *
+ *******************************************************************/
+
+/*
+ * This is actually the meat of the library. The function takes a string
+ * representation of a time rule in STR and time to check against (usually that
+ * would be current time) in NOW.
+ *
+ * It returns EOK if the rule is valid in the current time, ERANGE if not and
+ * EINVAL if the rule cannot be parsed
+ */
+int check_time_rule(TALLOC_CTX *mem_ctx,
+ struct time_rules_ctx *trctx,
+ const char *str,
+ const time_t now,
+ bool *result)
+{
+ int ret;
+ struct range_ctx *ctx;
+
+ ctx = talloc_zero(mem_ctx, struct range_ctx);
+ CHECK_PTR_JMP(ctx);
+ ctx->trctx = trctx;
+
+ DEBUG(9, ("Got time_t value %s", ctime(&now)));
+
+ ret = parse_timespec(ctx, str);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse the time specification (%d)\n", ret));
+ goto done;
+ }
+
+ ret = timerange_valid(ctx, now, result);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot check the time range (%d)\n", ret));
+ goto done;
+ }
+
+ ret = EOK;
+done:
+ talloc_free(ctx);
+ return EOK;
+}
+
+/*
+ * Frees the resources taken by the precompiled rules
+ */
+static int time_rules_parser_destructor(struct time_rules_ctx *ctx)
+{
+ int i;
+
+ for (i = 0; i< LP_RGX_MAX; ++i) {
+ pcre_free(ctx->re[i]);
+ ctx->re[i] = NULL;
+ }
+
+ return 0;
+}
+
+/*
+ * Initializes the parser by precompiling the regular expressions
+ * for later use
+ */
+int init_time_rules_parser(TALLOC_CTX *mem_ctx,
+ struct time_rules_ctx **_out)
+{
+ const char *errstr;
+ int errval;
+ int errpos;
+ int ret;
+ int i;
+ struct time_rules_ctx *ctx = NULL;
+
+ ctx = talloc_zero(mem_ctx, struct time_rules_ctx);
+ CHECK_PTR(ctx);
+ talloc_set_destructor(ctx, time_rules_parser_destructor);
+
+ /* Precompile regular expressions */
+ for (i = LP_RGX_GENERALIZED; i< LP_RGX_MAX; ++i) {
+ ctx->re[i] = pcre_compile2(lookup_table[i],
+ 0,
+ &errval,
+ &errstr,
+ &errpos,
+ NULL);
+
+ if (ctx->re[i] == NULL) {
+ DEBUG(0, ("Invalid Regular Expression pattern '%s' at position %d"
+ " (Error: %d [%s])\n", lookup_table[i],
+ errpos, errval, errstr));
+ ret = EFAULT;
+ goto done;
+ }
+
+ }
+
+ *_out = ctx;
+ return EOK;
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
diff --git a/src/providers/ipa/ipa_timerules.h b/src/providers/ipa/ipa_timerules.h
new file mode 100644
index 000000000..e1beaa22e
--- /dev/null
+++ b/src/providers/ipa/ipa_timerules.h
@@ -0,0 +1,56 @@
+/*
+ SSSD
+
+ IPA Provider Time Rules Parsing
+
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ 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 __IPA_TIMERULES_H_
+#define __IPA_TIMERULES_H_
+
+#include <stdbool.h>
+#include <talloc.h>
+
+/* Opaque structure given after init */
+struct time_rules_ctx;
+
+/*
+ * Init the parser. Destroy the allocated resources by simply
+ * talloc_free()-ing the time_rules_ctx
+ */
+int init_time_rules_parser(TALLOC_CTX *mem_ctx,
+ struct time_rules_ctx **_out);
+
+/*
+ * This is actually the meat of the library. The function takes a string
+ * representation of a time rule in STR and time to check against (usually that
+ * would be current time) in NOW.
+ *
+ * It returns EOK if the rule can be parsed, error code if not. If the time
+ * given in the NOW parameter would be accepted by the rule, it stores true in
+ * RESULT, false otherwise.
+ */
+int check_time_rule(TALLOC_CTX *mem_ctx,
+ struct time_rules_ctx *trctx,
+ const char *str,
+ const time_t now,
+ bool *result);
+
+#endif /* __IPA_TIMERULES_H_ */
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
new file mode 100644
index 000000000..a2dadc808
--- /dev/null
+++ b/src/providers/krb5/krb5_auth.c
@@ -0,0 +1,1193 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 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 <errno.h>
+#include <sys/time.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <pwd.h>
+#include <sys/stat.h>
+
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "util/find_uid.h"
+#include "db/sysdb.h"
+#include "providers/child_common.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/krb5/krb5_utils.h"
+
+#ifndef SSSD_LIBEXEC_PATH
+#error "SSSD_LIBEXEC_PATH not defined"
+#else
+#define KRB5_CHILD SSSD_LIBEXEC_PATH"/krb5_child"
+#endif
+
+static errno_t add_krb5_env(struct dp_option *opts, const char *ccname,
+ struct pam_data *pd)
+{
+ int ret;
+ const char *dummy;
+ char *env;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(1, ("talloc_new failed.\n"));
+ return ENOMEM;
+ }
+
+ if (ccname != NULL) {
+ env = talloc_asprintf(tmp_ctx, "%s=%s",CCACHE_ENV_NAME, ccname);
+ if (env == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1,
+ (uint8_t *) env);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ goto done;
+ }
+ }
+
+ dummy = dp_opt_get_cstring(opts, KRB5_REALM);
+ if (dummy != NULL) {
+ env = talloc_asprintf(tmp_ctx, "%s=%s", SSSD_KRB5_REALM, dummy);
+ if (env == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1,
+ (uint8_t *) env);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ goto done;
+ }
+ }
+
+ dummy = dp_opt_get_cstring(opts, KRB5_KDC);
+ if (dummy != NULL) {
+ env = talloc_asprintf(tmp_ctx, "%s=%s", SSSD_KRB5_KDC, dummy);
+ if (env == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+ ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1,
+ (uint8_t *) env);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ goto done;
+ }
+ }
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static errno_t check_if_ccache_file_is_used(uid_t uid, const char *ccname,
+ bool *result)
+{
+ int ret;
+ size_t offset = 0;
+ struct stat stat_buf;
+ const char *filename;
+ bool active;
+
+ *result = false;
+
+ if (ccname == NULL || *ccname == '\0') {
+ return EINVAL;
+ }
+
+ if (strncmp(ccname, "FILE:", 5) == 0) {
+ offset = 5;
+ }
+
+ filename = ccname + offset;
+
+ if (filename[0] != '/') {
+ DEBUG(1, ("Only absolute path names are allowed"));
+ return EINVAL;
+ }
+
+ ret = lstat(filename, &stat_buf);
+
+ if (ret == -1 && errno != ENOENT) {
+ DEBUG(1, ("stat failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ } else if (ret == EOK) {
+ if (stat_buf.st_uid != uid) {
+ DEBUG(1, ("Cache file [%s] exists, but is owned by [%d] instead of "
+ "[%d].\n", filename, stat_buf.st_uid, uid));
+ return EINVAL;
+ }
+
+ if (!S_ISREG(stat_buf.st_mode)) {
+ DEBUG(1, ("Cache file [%s] exists, but is not a regular file.\n",
+ filename));
+ return EINVAL;
+ }
+ }
+
+ ret = check_if_uid_is_active(uid, &active);
+ if (ret != EOK) {
+ DEBUG(1, ("check_if_uid_is_active failed.\n"));
+ return ret;
+ }
+
+ if (!active) {
+ DEBUG(5, ("User [%d] is not active\n", uid));
+ } else {
+ DEBUG(9, ("User [%d] is still active, reusing ccache file [%s].\n",
+ uid, filename));
+ *result = true;
+ }
+ return EOK;
+}
+
+struct krb5_save_ccname_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+ struct sss_domain_info *domain;
+ const char *name;
+ struct sysdb_attrs *attrs;
+};
+
+static void krb5_save_ccname_trans(struct tevent_req *subreq);
+static void krb5_set_user_attr_done(struct tevent_req *subreq);
+
+static struct tevent_req *krb5_save_ccname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *name,
+ const char *ccname)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct krb5_save_ccname_state *state;
+ int ret;
+
+ if (name == NULL || ccname == NULL) {
+ DEBUG(1, ("Missing user or ccache name.\n"));
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct krb5_save_ccname_state);
+ if (req == NULL) {
+ DEBUG(1, ("tevent_req_create failed.\n"));
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = NULL;
+ state->domain = domain;
+ state->name = name;
+
+ state->attrs = sysdb_new_attrs(state);
+ ret = sysdb_attrs_add_string(state->attrs, SYSDB_CCACHE_FILE, ccname);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_add_string failed.\n"));
+ goto failed;
+ }
+
+ subreq = sysdb_transaction_send(state, ev, sysdb);
+ if (subreq == NULL) {
+ goto failed;
+ }
+ tevent_req_set_callback(subreq, krb5_save_ccname_trans, req);
+
+ return req;
+
+failed:
+ talloc_free(req);
+ return NULL;
+}
+
+static void krb5_save_ccname_trans(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct krb5_save_ccname_state *state = tevent_req_data(req,
+ struct krb5_save_ccname_state);
+ int ret;
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_set_user_attr_send(state, state->ev, state->handle,
+ state->domain, state->name,
+ state->attrs, SYSDB_MOD_REP);
+ if (subreq == NULL) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, krb5_set_user_attr_done, req);
+}
+
+static void krb5_set_user_attr_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct krb5_save_ccname_state *state = tevent_req_data(req,
+ struct krb5_save_ccname_state);
+ int ret;
+
+ ret = sysdb_set_user_attr_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (subreq == NULL) {
+ DEBUG(6, ("Error: Out of memory\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+ return;
+}
+
+int krb5_save_ccname_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+errno_t create_send_buffer(struct krb5child_req *kr, struct io_buffer **io_buf)
+{
+ struct io_buffer *buf;
+ size_t rp;
+ const char *keytab;
+ uint32_t validate;
+
+ keytab = dp_opt_get_cstring(kr->krb5_ctx->opts, KRB5_KEYTAB);
+ if (keytab == NULL) {
+ DEBUG(1, ("Missing keytab option.\n"));
+ return EINVAL;
+ }
+
+ validate = dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE) ? 1 : 0;
+
+ buf = talloc(kr, struct io_buffer);
+ if (buf == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ return ENOMEM;
+ }
+
+ buf->size = 9*sizeof(uint32_t) + strlen(kr->pd->upn) + strlen(kr->ccname) +
+ strlen(keytab) +
+ kr->pd->authtok_size;
+ if (kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
+ buf->size += sizeof(uint32_t) + kr->pd->newauthtok_size;
+ }
+
+ buf->data = talloc_size(kr, buf->size);
+ if (buf->data == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ talloc_free(buf);
+ return ENOMEM;
+ }
+
+ rp = 0;
+ COPY_UINT32(&buf->data[rp], &kr->pd->cmd, rp);
+ COPY_UINT32(&buf->data[rp], &kr->pd->pw_uid, rp);
+ COPY_UINT32(&buf->data[rp], &kr->pd->gr_gid, rp);
+ COPY_UINT32(&buf->data[rp], &validate, rp);
+ COPY_UINT32(&buf->data[rp], &kr->is_offline, rp);
+
+ COPY_UINT32_VALUE(&buf->data[rp], strlen(kr->pd->upn), rp);
+ COPY_MEM(&buf->data[rp], kr->pd->upn, rp, strlen(kr->pd->upn));
+
+ COPY_UINT32_VALUE(&buf->data[rp], strlen(kr->ccname), rp);
+ COPY_MEM(&buf->data[rp], kr->ccname, rp, strlen(kr->ccname));
+
+ COPY_UINT32_VALUE(&buf->data[rp], strlen(keytab), rp);
+ COPY_MEM(&buf->data[rp], keytab, rp, strlen(keytab));
+
+ COPY_UINT32(&buf->data[rp], &kr->pd->authtok_size, rp);
+ COPY_MEM(&buf->data[rp], kr->pd->authtok, rp, kr->pd->authtok_size);
+
+ if (kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
+ COPY_UINT32(&buf->data[rp], &kr->pd->newauthtok_size, rp);
+ COPY_MEM(&buf->data[rp], kr->pd->newauthtok,
+ rp, kr->pd->newauthtok_size);
+ }
+
+ *io_buf = buf;
+
+ return EOK;
+}
+
+static struct krb5_ctx *get_krb5_ctx(struct be_req *be_req)
+{
+ struct pam_data *pd;
+
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ return talloc_get_type(be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data,
+ struct krb5_ctx);
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ return talloc_get_type(be_req->be_ctx->bet_info[BET_CHPASS].pvt_bet_data,
+ struct krb5_ctx);
+ break;
+ default:
+ DEBUG(1, ("Unsupported PAM task.\n"));
+ return NULL;
+ }
+}
+
+static void krb_reply(struct be_req *req, int dp_err, int result);
+
+static void krb5_child_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct krb5child_req *kr = talloc_get_type(pvt, struct krb5child_req);
+ struct be_req *be_req = kr->req;
+ struct pam_data *pd = kr->pd;
+ int ret;
+
+ if (kr->timeout_handler == NULL) {
+ return;
+ }
+
+ DEBUG(9, ("timeout for child [%d] reached.\n", kr->child_pid));
+
+ ret = kill(kr->child_pid, SIGKILL);
+ if (ret == -1) {
+ DEBUG(1, ("kill failed [%d][%s].\n", errno, strerror(errno)));
+ }
+
+ talloc_zfree(kr);
+
+ pd->pam_status = PAM_AUTHINFO_UNAVAIL;
+ be_mark_offline(be_req->be_ctx);
+
+ krb_reply(be_req, DP_ERR_OFFLINE, pd->pam_status);
+}
+
+static errno_t activate_child_timeout_handler(struct krb5child_req *kr)
+{
+ struct timeval tv;
+
+ tv = tevent_timeval_current();
+ tv = tevent_timeval_add(&tv,
+ dp_opt_get_int(kr->krb5_ctx->opts,
+ KRB5_AUTH_TIMEOUT),
+ 0);
+ kr->timeout_handler = tevent_add_timer(kr->req->be_ctx->ev, kr, tv,
+ krb5_child_timeout, kr);
+ if (kr->timeout_handler == NULL) {
+ DEBUG(1, ("tevent_add_timer failed.\n"));
+ return ENOMEM;
+ }
+
+ return EOK;
+}
+
+static int krb5_cleanup(void *ptr)
+{
+ struct krb5child_req *kr = talloc_get_type(ptr, struct krb5child_req);
+
+ if (kr == NULL) return EOK;
+
+ child_cleanup(kr->read_from_child_fd, kr->write_to_child_fd);
+ memset(kr, 0, sizeof(struct krb5child_req));
+
+ return EOK;
+}
+
+static errno_t krb5_setup(struct be_req *req, struct krb5child_req **krb5_req)
+{
+ struct krb5child_req *kr = NULL;
+ struct krb5_ctx *krb5_ctx;
+ struct pam_data *pd;
+ errno_t err;
+
+ pd = talloc_get_type(req->req_data, struct pam_data);
+
+ krb5_ctx = get_krb5_ctx(req);
+ if (krb5_ctx == NULL) {
+ DEBUG(1, ("Kerberos context not available.\n"));
+ err = EINVAL;
+ goto failed;
+ }
+
+ kr = talloc_zero(req, struct krb5child_req);
+ if (kr == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ err = ENOMEM;
+ goto failed;
+ }
+ kr->read_from_child_fd = -1;
+ kr->write_to_child_fd = -1;
+ kr->is_offline = false;
+ kr->active_ccache_present = true;
+ talloc_set_destructor((TALLOC_CTX *) kr, krb5_cleanup);
+
+ kr->pd = pd;
+ kr->req = req;
+ kr->krb5_ctx = krb5_ctx;
+
+ *krb5_req = kr;
+
+ return EOK;
+
+failed:
+ talloc_zfree(kr);
+
+ return err;
+}
+
+static errno_t fork_child(struct krb5child_req *kr)
+{
+ int pipefd_to_child[2];
+ int pipefd_from_child[2];
+ pid_t pid;
+ int ret;
+ errno_t err;
+
+ ret = pipe(pipefd_from_child);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("pipe failed [%d][%s].\n", errno, strerror(errno)));
+ return err;
+ }
+ ret = pipe(pipefd_to_child);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("pipe failed [%d][%s].\n", errno, strerror(errno)));
+ return err;
+ }
+
+ pid = fork();
+
+ if (pid == 0) { /* child */
+ /* We need to keep the root privileges to read the keytab file if
+ * validation is enabled, otherwise we can drop them here and run
+ * krb5_child with user privileges.
+ * If authtok_size is zero we are offline and want to create an empty
+ * ccache file. In this case we can drop the privileges, too. */
+ if (!dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE) ||
+ kr->pd->authtok_size == 0) {
+ ret = become_user(kr->pd->pw_uid, kr->pd->gr_gid);
+ if (ret != EOK) {
+ DEBUG(1, ("become_user failed.\n"));
+ return ret;
+ }
+ }
+
+ err = exec_child(kr,
+ pipefd_to_child, pipefd_from_child,
+ KRB5_CHILD, kr->krb5_ctx->child_debug_fd);
+ if (err != EOK) {
+ DEBUG(1, ("Could not exec LDAP child: [%d][%s].\n",
+ err, strerror(err)));
+ return err;
+ }
+ } else if (pid > 0) { /* parent */
+ kr->child_pid = pid;
+ kr->read_from_child_fd = pipefd_from_child[0];
+ close(pipefd_from_child[1]);
+ kr->write_to_child_fd = pipefd_to_child[1];
+ close(pipefd_to_child[0]);
+ fd_nonblocking(kr->read_from_child_fd);
+ fd_nonblocking(kr->write_to_child_fd);
+
+ err = activate_child_timeout_handler(kr);
+ if (err != EOK) {
+ DEBUG(1, ("activate_child_timeout_handler failed.\n"));
+ }
+
+ } else { /* error */
+ err = errno;
+ DEBUG(1, ("fork failed [%d][%s].\n", errno, strerror(errno)));
+ return err;
+ }
+
+ return EOK;
+}
+
+struct handle_child_state {
+ struct tevent_context *ev;
+ struct krb5child_req *kr;
+ uint8_t *buf;
+ ssize_t len;
+};
+
+static void handle_child_step(struct tevent_req *subreq);
+static void handle_child_done(struct tevent_req *subreq);
+
+static struct tevent_req *handle_child_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct krb5child_req *kr)
+{
+ struct tevent_req *req, *subreq;
+ struct handle_child_state *state;
+ struct io_buffer *buf;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct handle_child_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->kr = kr;
+ state->buf = NULL;
+ state->len = 0;
+
+ ret = create_send_buffer(kr, &buf);
+ if (ret != EOK) {
+ DEBUG(1, ("create_send_buffer failed.\n"));
+ goto fail;
+ }
+
+ ret = fork_child(kr);
+ if (ret != EOK) {
+ DEBUG(1, ("fork_child failed.\n"));
+ goto fail;
+ }
+
+ subreq = write_pipe_send(state, ev, buf->data, buf->size,
+ kr->write_to_child_fd);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, handle_child_step, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void handle_child_step(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct handle_child_state *state = tevent_req_data(req,
+ struct handle_child_state);
+ int ret;
+
+ ret = write_pipe_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ close(state->kr->write_to_child_fd);
+ state->kr->write_to_child_fd = -1;
+
+ subreq = read_pipe_send(state, state->ev, state->kr->read_from_child_fd);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, handle_child_done, req);
+}
+
+static void handle_child_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct handle_child_state *state = tevent_req_data(req,
+ struct handle_child_state);
+ int ret;
+
+ ret = read_pipe_recv(subreq, state, &state->buf, &state->len);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ close(state->kr->read_from_child_fd);
+ state->kr->read_from_child_fd = -1;
+
+ tevent_req_done(req);
+ return;
+}
+
+static int handle_child_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **buf, ssize_t *len)
+{
+ struct handle_child_state *state = tevent_req_data(req,
+ struct handle_child_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *buf = talloc_move(mem_ctx, &state->buf);
+ *len = state->len;
+
+ return EOK;
+}
+
+static void get_user_attr_done(void *pvt, int err, struct ldb_result *res);
+static void krb5_resolve_done(struct tevent_req *req);
+static void krb5_save_ccname_done(struct tevent_req *req);
+static void krb5_child_done(struct tevent_req *req);
+static void krb5_pam_handler_cache_done(struct tevent_req *treq);
+
+void krb5_pam_handler(struct be_req *be_req)
+{
+ struct pam_data *pd;
+ const char **attrs;
+ int pam_status = PAM_SYSTEM_ERR;
+ int dp_err = DP_ERR_FATAL;
+ int ret;
+
+ pd = talloc_get_type(be_req->req_data, struct pam_data);
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ case SSS_PAM_CHAUTHTOK:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ break;
+ case SSS_PAM_ACCT_MGMT:
+ case SSS_PAM_SETCRED:
+ case SSS_PAM_OPEN_SESSION:
+ case SSS_PAM_CLOSE_SESSION:
+ pam_status = PAM_SUCCESS;
+ dp_err = DP_ERR_OK;
+ goto done;
+ break;
+ default:
+ DEBUG(4, ("krb5 does not handles pam task %d.\n", pd->cmd));
+ pam_status = PAM_MODULE_UNKNOWN;
+ dp_err = DP_ERR_OK;
+ goto done;
+ }
+
+ if (be_is_offline(be_req->be_ctx) &&
+ (pd->cmd == SSS_PAM_CHAUTHTOK || pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM)) {
+ DEBUG(9, ("Password changes are not possible while offline.\n"));
+ pam_status = PAM_AUTHINFO_UNAVAIL;
+ dp_err = DP_ERR_OFFLINE;
+ goto done;
+ }
+
+ attrs = talloc_array(be_req, const char *, 4);
+ if (attrs == NULL) {
+ goto done;
+ }
+
+ attrs[0] = SYSDB_UPN;
+ attrs[1] = SYSDB_HOMEDIR;
+ attrs[2] = SYSDB_CCACHE_FILE;
+ attrs[3] = NULL;
+
+ ret = sysdb_get_user_attr(be_req, be_req->be_ctx->sysdb,
+ be_req->be_ctx->domain, pd->user, attrs,
+ get_user_attr_done, be_req);
+
+ if (ret) {
+ goto done;
+ }
+
+ return;
+
+done:
+ pd->pam_status = pam_status;
+
+ krb_reply(be_req, dp_err, pd->pam_status);
+}
+
+static void get_user_attr_done(void *pvt, int err, struct ldb_result *res)
+{
+ struct be_req *be_req = talloc_get_type(pvt, struct be_req);
+ struct krb5_ctx *krb5_ctx;
+ struct krb5child_req *kr = NULL;
+ struct tevent_req *req;
+ krb5_error_code kerr;
+ int ret;
+ struct pam_data *pd = talloc_get_type(be_req->req_data, struct pam_data);
+ int pam_status=PAM_SYSTEM_ERR;
+ int dp_err = DP_ERR_FATAL;
+ const char *ccache_file = NULL;
+ const char *realm;
+
+ ret = krb5_setup(be_req, &kr);
+ if (ret != EOK) {
+ DEBUG(1, ("krb5_setup failed.\n"));
+ goto failed;
+ }
+
+ krb5_ctx = kr->krb5_ctx;
+
+ if (err != LDB_SUCCESS) {
+ DEBUG(5, ("sysdb search for upn of user [%s] failed.\n", pd->user));
+ goto failed;
+ }
+
+ realm = dp_opt_get_cstring(krb5_ctx->opts, KRB5_REALM);
+ if (realm == NULL) {
+ DEBUG(1, ("Missing Kerberos realm.\n"));
+ goto failed;
+ }
+
+ switch (res->count) {
+ case 0:
+ DEBUG(5, ("No attributes for user [%s] found.\n", pd->user));
+ goto failed;
+ break;
+
+ case 1:
+ pd->upn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_UPN, NULL);
+ if (pd->upn == NULL) {
+ /* NOTE: this is a hack, works only in some environments */
+ pd->upn = talloc_asprintf(be_req, "%s@%s", pd->user, realm);
+ if (pd->upn == NULL) {
+ DEBUG(1, ("failed to build simple upn.\n"));
+ goto failed;
+ }
+ DEBUG(9, ("Using simple UPN [%s].\n", pd->upn));
+ }
+
+ kr->homedir = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_HOMEDIR,
+ NULL);
+ if (kr->homedir == NULL) {
+ DEBUG(4, ("Home directory for user [%s] not known.\n", pd->user));
+ }
+
+ ccache_file = ldb_msg_find_attr_as_string(res->msgs[0],
+ SYSDB_CCACHE_FILE,
+ NULL);
+ if (ccache_file != NULL) {
+ ret = check_if_ccache_file_is_used(pd->pw_uid, ccache_file,
+ &kr->active_ccache_present);
+ if (ret != EOK) {
+ DEBUG(1, ("check_if_ccache_file_is_used failed.\n"));
+ goto failed;
+ }
+
+ kerr = check_for_valid_tgt(ccache_file, realm, pd->upn,
+ &kr->valid_tgt_present);
+ if (kerr != 0) {
+ DEBUG(1, ("check_for_valid_tgt failed.\n"));
+ goto failed;
+ }
+ } else {
+ kr->active_ccache_present = false;
+ kr->valid_tgt_present = false;
+ DEBUG(4, ("No ccache file for user [%s] found.\n", pd->user));
+ }
+ DEBUG(9, ("Ccache_file is [%s] and is %s active and TGT is %s valid.\n",
+ ccache_file ? ccache_file : "not set",
+ kr->active_ccache_present ? "" : "not",
+ kr->valid_tgt_present ? "" : "not"));
+ kr->ccname = ccache_file;
+ break;
+
+ default:
+ DEBUG(1, ("A user search by name (%s) returned > 1 results!\n",
+ pd->user));
+ goto failed;
+ break;
+ }
+
+ req = be_resolve_server_send(kr, be_req->be_ctx->ev, be_req->be_ctx,
+ krb5_ctx->service->name);
+ if (req == NULL) {
+ DEBUG(1, ("handle_child_send failed.\n"));
+ goto failed;
+ }
+
+ tevent_req_set_callback(req, krb5_resolve_done, kr);
+
+ return;
+
+failed:
+ talloc_free(kr);
+
+ pd->pam_status = pam_status;
+ krb_reply(be_req, dp_err, pd->pam_status);
+}
+
+static void krb5_resolve_done(struct tevent_req *req)
+{
+ struct krb5child_req *kr = tevent_req_callback_data(req,
+ struct krb5child_req);
+ int ret;
+ int pam_status = PAM_SYSTEM_ERR;
+ int dp_err = DP_ERR_FATAL;
+ struct pam_data *pd = kr->pd;
+ struct be_req *be_req = kr->req;
+ char *msg;
+ size_t offset = 0;
+
+ ret = be_resolve_server_recv(req, &kr->srv);
+ talloc_zfree(req);
+ if (ret) {
+ /* all servers have been tried and none
+ * was found good, setting offline,
+ * but we still have to call the child to setup
+ * the ccache file. */
+ be_mark_offline(be_req->be_ctx);
+ kr->is_offline = true;
+ }
+
+ if (kr->ccname == NULL ||
+ (be_is_offline(be_req->be_ctx) && !kr->active_ccache_present &&
+ !kr->valid_tgt_present) ||
+ (!be_is_offline(be_req->be_ctx) && !kr->active_ccache_present)) {
+ DEBUG(9, ("Recreating ccache file.\n"));
+ if (kr->ccname != NULL) {
+ if (strncmp(kr->ccname, "FILE:", 5) == 0) {
+ offset = 5;
+ }
+ if (kr->ccname[offset] != '/') {
+ DEBUG(1, ("Ccache file name [%s] is not an absolute path.\n",
+ kr->ccname + offset));
+ goto done;
+ }
+ ret = unlink(kr->ccname + offset);
+ if (ret == -1 && errno != ENOENT) {
+ DEBUG(1, ("unlink [%s] failed [%d][%s].\n", kr->ccname,
+ errno, strerror(errno)));
+ goto done;
+ }
+ }
+ kr->ccname = expand_ccname_template(kr, kr,
+ dp_opt_get_cstring(kr->krb5_ctx->opts,
+ KRB5_CCNAME_TMPL)
+ );
+ if (kr->ccname == NULL) {
+ DEBUG(1, ("expand_ccname_template failed.\n"));
+ goto done;
+ }
+ }
+
+ if (be_is_offline(be_req->be_ctx)) {
+ DEBUG(9, ("Preparing for offline operation.\n"));
+ kr->is_offline = true;
+
+ if (kr->valid_tgt_present) {
+ DEBUG(9, ("Valid TGT available, nothing to do.\n"));
+ msg = talloc_asprintf(pd, "%s=%s", CCACHE_ENV_NAME, kr->ccname);
+ if (msg == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ goto done;
+ }
+
+ ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(msg) + 1,
+ (uint8_t *) msg);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ }
+
+ pam_status = PAM_AUTHINFO_UNAVAIL;
+ dp_err = DP_ERR_OFFLINE;
+ goto done;
+ }
+ memset(pd->authtok, 0, pd->authtok_size);
+ pd->authtok_size = 0;
+
+ if (kr->active_ccache_present) {
+ req = krb5_save_ccname_send(kr, be_req->be_ctx->ev,
+ be_req->be_ctx->sysdb,
+ be_req->be_ctx->domain, pd->user,
+ kr->ccname);
+ if (req == NULL) {
+ DEBUG(1, ("krb5_save_ccname_send failed.\n"));
+ goto done;
+ }
+
+ tevent_req_set_callback(req, krb5_save_ccname_done, kr);
+ return;
+ }
+ }
+
+ req = handle_child_send(kr, be_req->be_ctx->ev, kr);
+ if (req == NULL) {
+ DEBUG(1, ("handle_child_send failed.\n"));
+ goto done;
+ }
+
+ tevent_req_set_callback(req, krb5_child_done, kr);
+ return;
+
+done:
+ talloc_free(kr);
+ pd->pam_status = pam_status;
+ krb_reply(be_req, dp_err, pd->pam_status);
+}
+
+static void krb5_child_done(struct tevent_req *req)
+{
+ struct krb5child_req *kr = tevent_req_callback_data(req,
+ struct krb5child_req);
+ struct pam_data *pd = kr->pd;
+ struct be_req *be_req = kr->req;
+ int ret;
+ uint8_t *buf = NULL;
+ ssize_t len = -1;
+ ssize_t pref_len;
+ int p;
+ int32_t *msg_status;
+ int32_t *msg_type;
+ int32_t *msg_len;
+ int pam_status = PAM_SYSTEM_ERR;
+ int dp_err = DP_ERR_FATAL;
+
+ ret = handle_child_recv(req, pd, &buf, &len);
+ talloc_zfree(kr->timeout_handler);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(1, ("child failed (%d [%s])\n", ret, strerror(ret)));
+ goto done;
+ }
+
+ if ((size_t) len < 3*sizeof(int32_t)) {
+ DEBUG(1, ("message too short.\n"));
+ goto done;
+ }
+
+ p=0;
+ msg_status = ((int32_t *)(buf+p));
+ p += sizeof(int32_t);
+
+ msg_type = ((int32_t *)(buf+p));
+ p += sizeof(int32_t);
+
+ msg_len = ((int32_t *)(buf+p));
+ p += sizeof(int32_t);
+
+ DEBUG(4, ("child response [%d][%d][%d].\n", *msg_status, *msg_type,
+ *msg_len));
+
+ if ((p + *msg_len) != len) {
+ DEBUG(1, ("message format error.\n"));
+ goto done;
+ }
+
+ if (*msg_status != PAM_SUCCESS && *msg_status != PAM_AUTHINFO_UNAVAIL) {
+ pam_status = *msg_status;
+ dp_err = DP_ERR_OK;
+
+ ret = pam_add_response(pd, *msg_type, *msg_len, &buf[p]);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ }
+
+ goto done;
+ } else {
+ pd->pam_status = *msg_status;
+ }
+
+ if (*msg_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+ pam_status = PAM_SUCCESS;
+ dp_err = DP_ERR_OK;
+ goto done;
+ }
+
+ pref_len = strlen(CCACHE_ENV_NAME)+1;
+ if (*msg_len > pref_len &&
+ strncmp((const char *) &buf[p], CCACHE_ENV_NAME"=", pref_len) == 0) {
+ kr->ccname = talloc_strndup(kr, (char *) &buf[p+pref_len],
+ *msg_len-pref_len);
+ if (kr->ccname == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ goto done;
+ }
+ } else {
+ DEBUG(1, ("Missing ccache name in child response [%.*s].\n", *msg_len,
+ &buf[p]));
+ goto done;
+ }
+
+ if (*msg_status == PAM_AUTHINFO_UNAVAIL) {
+ if (kr->srv != NULL) {
+ fo_set_port_status(kr->srv, PORT_NOT_WORKING);
+ }
+ be_mark_offline(be_req->be_ctx);
+ kr->is_offline = true;
+ } else if (kr->srv != NULL) {
+ fo_set_port_status(kr->srv, PORT_WORKING);
+ }
+
+ struct sysdb_attrs *attrs;
+ attrs = sysdb_new_attrs(kr);
+ ret = sysdb_attrs_add_string(attrs, SYSDB_CCACHE_FILE, kr->ccname);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_attrs_add_string failed.\n"));
+ goto done;
+ }
+
+ req = krb5_save_ccname_send(kr, be_req->be_ctx->ev, be_req->be_ctx->sysdb,
+ be_req->be_ctx->domain, pd->user, kr->ccname);
+ if (req == NULL) {
+ DEBUG(1, ("krb5_save_ccname_send failed.\n"));
+ goto done;
+ }
+
+ tevent_req_set_callback(req, krb5_save_ccname_done, kr);
+ return;
+done:
+ talloc_free(kr);
+ pd->pam_status = pam_status;
+ krb_reply(be_req, dp_err, pd->pam_status);
+}
+
+static void krb5_save_ccname_done(struct tevent_req *req)
+{
+ struct krb5child_req *kr = tevent_req_callback_data(req,
+ struct krb5child_req);
+ struct pam_data *pd = kr->pd;
+ struct be_req *be_req = kr->req;
+ struct krb5_ctx *krb5_ctx = kr->krb5_ctx;
+ int pam_status = PAM_SYSTEM_ERR;
+ int dp_err = DP_ERR_FATAL;
+ int ret;
+ char *password = NULL;
+
+ if (pd->cmd == SSS_PAM_AUTHENTICATE) {
+ ret = add_krb5_env(krb5_ctx->opts, kr->ccname, pd);
+ if (ret != EOK) {
+ DEBUG(1, ("add_krb5_env failed.\n"));
+ goto failed;
+ }
+ }
+
+ ret = sysdb_set_user_attr_recv(req);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ DEBUG(1, ("Saving ccache name failed.\n"));
+ goto failed;
+ }
+
+ if (kr->is_offline) {
+ DEBUG(4, ("Backend is marked offline, retry later!\n"));
+ pam_status = PAM_AUTHINFO_UNAVAIL;
+ dp_err = DP_ERR_OFFLINE;
+ goto failed;
+ }
+
+ if (be_req->be_ctx->domain->cache_credentials == TRUE) {
+
+ /* password caching failures are not fatal errors */
+ pd->pam_status = PAM_SUCCESS;
+
+ switch(pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ password = talloc_size(be_req, pd->authtok_size + 1);
+ if (password != NULL) {
+ memcpy(password, pd->authtok, pd->authtok_size);
+ password[pd->authtok_size] = '\0';
+ }
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ password = talloc_size(be_req, pd->newauthtok_size + 1);
+ if (password != NULL) {
+ memcpy(password, pd->newauthtok, pd->newauthtok_size);
+ password[pd->newauthtok_size] = '\0';
+ }
+ break;
+ default:
+ DEBUG(0, ("unsupported PAM command [%d].\n", pd->cmd));
+ }
+
+ if (password == NULL) {
+ DEBUG(0, ("password not available, offline auth may not work.\n"));
+ goto failed;
+ }
+
+ talloc_set_destructor((TALLOC_CTX *)password, password_destructor);
+
+ req = sysdb_cache_password_send(be_req, be_req->be_ctx->ev,
+ be_req->be_ctx->sysdb, NULL,
+ be_req->be_ctx->domain, pd->user,
+ password);
+ if (req == NULL) {
+ DEBUG(2, ("cache_password_send failed, offline auth may not work.\n"));
+ goto failed;
+ }
+ tevent_req_set_callback(req, krb5_pam_handler_cache_done, be_req);
+ return;
+ }
+
+ pam_status = PAM_SUCCESS;
+ dp_err = DP_ERR_OK;
+
+failed:
+ talloc_free(kr);
+
+ pd->pam_status = pam_status;
+ krb_reply(be_req, dp_err, pd->pam_status);
+}
+
+static void krb5_pam_handler_cache_done(struct tevent_req *subreq)
+{
+ struct be_req *be_req = tevent_req_callback_data(subreq, struct be_req);
+ int ret;
+
+ /* password caching failures are not fatal errors */
+ ret = sysdb_cache_password_recv(subreq);
+ talloc_zfree(subreq);
+
+ /* so we just log it any return */
+ if (ret) {
+ DEBUG(2, ("Failed to cache password (%d)[%s]!?\n",
+ ret, strerror(ret)));
+ }
+
+ krb_reply(be_req, DP_ERR_OK, PAM_SUCCESS);
+}
+
+static void krb_reply(struct be_req *req, int dp_err, int result)
+{
+ req->fn(req, dp_err, result, NULL);
+}
+
diff --git a/src/providers/krb5/krb5_auth.h b/src/providers/krb5/krb5_auth.h
new file mode 100644
index 000000000..a011af890
--- /dev/null
+++ b/src/providers/krb5/krb5_auth.h
@@ -0,0 +1,91 @@
+/*
+ SSSD
+
+ Kerberos Backend, private header file
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 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 __KRB5_AUTH_H__
+#define __KRB5_AUTH_H__
+
+#include "util/sss_krb5.h"
+#include "providers/dp_backend.h"
+#include "providers/krb5/krb5_common.h"
+
+#define CCACHE_ENV_NAME "KRB5CCNAME"
+#define SSSD_KRB5_CHANGEPW_PRINCIPLE "SSSD_KRB5_CHANGEPW_PRINCIPLE"
+
+typedef enum { INIT_PW, INIT_KT, RENEW, VALIDATE } action_type;
+
+struct krb5child_req {
+ pid_t child_pid;
+ int read_from_child_fd;
+ int write_to_child_fd;
+
+ struct be_req *req;
+ struct pam_data *pd;
+ struct krb5_ctx *krb5_ctx;
+
+ struct tevent_timer *timeout_handler;
+
+ const char *ccname;
+ const char *homedir;
+ bool is_offline;
+ struct fo_server *srv;
+ bool active_ccache_present;
+ bool valid_tgt_present;
+};
+
+struct fo_service;
+
+struct krb5_ctx {
+ /* opts taken from kinit */
+ /* in seconds */
+ krb5_deltat starttime;
+ krb5_deltat lifetime;
+ krb5_deltat rlife;
+
+ int forwardable;
+ int proxiable;
+ int addresses;
+
+ int not_forwardable;
+ int not_proxiable;
+ int no_addresses;
+
+ int verbose;
+
+ char* principal_name;
+ char* service_name;
+ char* keytab_name;
+ char* k5_cache_name;
+ char* k4_cache_name;
+
+ action_type action;
+
+ struct dp_option *opts;
+ struct krb5_service *service;
+ int child_debug_fd;
+};
+
+void krb5_pam_handler(struct be_req *be_req);
+
+#endif /* __KRB5_AUTH_H__ */
diff --git a/src/providers/krb5/krb5_become_user.c b/src/providers/krb5/krb5_become_user.c
new file mode 100644
index 000000000..351f539a3
--- /dev/null
+++ b/src/providers/krb5/krb5_become_user.c
@@ -0,0 +1,61 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- Utilities
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 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 <sys/types.h>
+#include <unistd.h>
+
+#include "util/util.h"
+
+errno_t become_user(uid_t uid, gid_t gid)
+{
+ int ret;
+
+ DEBUG(9, ("Trying to become user [%d][%d].\n", uid, gid));
+ ret = setgid(gid);
+ if (ret == -1) {
+ DEBUG(1, ("setgid failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ ret = setuid(uid);
+ if (ret == -1) {
+ DEBUG(1, ("setuid failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ ret = setegid(gid);
+ if (ret == -1) {
+ DEBUG(1, ("setegid failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ ret = seteuid(uid);
+ if (ret == -1) {
+ DEBUG(1, ("seteuid failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ return EOK;
+}
+
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
new file mode 100644
index 000000000..5e185940e
--- /dev/null
+++ b/src/providers/krb5/krb5_child.c
@@ -0,0 +1,1030 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- tgt_req and changepw child
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 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 <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <popt.h>
+
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "util/user_info_msg.h"
+#include "providers/child_common.h"
+#include "providers/dp_backend.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/krb5/krb5_utils.h"
+
+struct krb5_child_ctx {
+ /* opts taken from kinit */
+ /* in seconds */
+ krb5_deltat starttime;
+ krb5_deltat lifetime;
+ krb5_deltat rlife;
+
+ int forwardable;
+ int proxiable;
+ int addresses;
+
+ int not_forwardable;
+ int not_proxiable;
+ int no_addresses;
+
+ int verbose;
+
+ char* principal_name;
+ char* service_name;
+ char* keytab_name;
+ char* k5_cache_name;
+ char* k4_cache_name;
+
+ action_type action;
+
+ char *kdcip;
+ char *realm;
+ char *changepw_principle;
+ char *ccache_dir;
+ char *ccname_template;
+ int auth_timeout;
+
+ int child_debug_fd;
+};
+
+struct krb5_req {
+ krb5_context ctx;
+ krb5_principal princ;
+ char* name;
+ krb5_creds *creds;
+ krb5_get_init_creds_opt *options;
+ pid_t child_pid;
+ int read_from_child_fd;
+ int write_to_child_fd;
+
+ struct be_req *req;
+ struct pam_data *pd;
+ struct krb5_child_ctx *krb5_ctx;
+ errno_t (*child_req)(int fd, struct krb5_req *kr);
+
+ char *ccname;
+ char *keytab;
+ bool validate;
+};
+
+static krb5_context krb5_error_ctx;
+static const char *__krb5_error_msg;
+#define KRB5_DEBUG(level, krb5_error) do { \
+ __krb5_error_msg = sss_krb5_get_error_message(krb5_error_ctx, krb5_error); \
+ DEBUG(level, ("%d: [%d][%s]\n", __LINE__, krb5_error, __krb5_error_msg)); \
+ sss_krb5_free_error_message(krb5_error_ctx, __krb5_error_msg); \
+} while(0);
+
+static krb5_error_code create_empty_cred(struct krb5_req *kr, krb5_creds **_cred)
+{
+ krb5_error_code kerr;
+ krb5_creds *cred = NULL;
+ krb5_data *krb5_realm;
+
+ cred = calloc(sizeof(krb5_creds), 1);
+ if (cred == NULL) {
+ DEBUG(1, ("calloc failed.\n"));
+ return ENOMEM;
+ }
+
+ kerr = krb5_copy_principal(kr->ctx, kr->princ, &cred->client);
+ if (kerr != 0) {
+ DEBUG(1, ("krb5_copy_principal failed.\n"));
+ goto done;
+ }
+
+ krb5_realm = krb5_princ_realm(kr->ctx, kr->princ);
+
+ kerr = krb5_build_principal_ext(kr->ctx, &cred->server,
+ krb5_realm->length, krb5_realm->data,
+ KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME,
+ krb5_realm->length, krb5_realm->data, 0);
+ if (kerr != 0) {
+ DEBUG(1, ("krb5_build_principal_ext failed.\n"));
+ goto done;
+ }
+
+done:
+ if (kerr != 0) {
+ if (cred != NULL && cred->client != NULL) {
+ krb5_free_principal(kr->ctx, cred->client);
+ }
+
+ free(cred);
+ } else {
+ *_cred = cred;
+ }
+
+ return kerr;
+}
+
+static krb5_error_code create_ccache_file(struct krb5_req *kr, krb5_creds *creds)
+{
+ krb5_error_code kerr;
+ krb5_ccache tmp_cc = NULL;
+ char *cc_file_name;
+ int fd = -1;
+ size_t ccname_len;
+ char *dummy;
+ char *tmp_ccname;
+ krb5_creds *l_cred;
+
+ if (strncmp(kr->ccname, "FILE:", 5) == 0) {
+ cc_file_name = kr->ccname + 5;
+ } else {
+ cc_file_name = kr->ccname;
+ }
+
+ if (cc_file_name[0] != '/') {
+ DEBUG(1, ("Ccache filename is not an absolute path.\n"));
+ return EINVAL;
+ }
+
+ dummy = strrchr(cc_file_name, '/');
+ tmp_ccname = talloc_strndup(kr, cc_file_name, (size_t) (dummy-cc_file_name));
+ if (tmp_ccname == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ return ENOMEM;
+ }
+ tmp_ccname = talloc_asprintf_append(tmp_ccname, "/.krb5cc_dummy_XXXXXX");
+
+ fd = mkstemp(tmp_ccname);
+ if (fd == -1) {
+ DEBUG(1, ("mkstemp failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ kerr = krb5_cc_resolve(kr->ctx, tmp_ccname, &tmp_cc);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto done;
+ }
+
+ kerr = krb5_cc_initialize(kr->ctx, tmp_cc, kr->princ);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto done;
+ }
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+
+ if (creds == NULL) {
+ kerr = create_empty_cred(kr, &l_cred);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto done;
+ }
+ } else {
+ l_cred = creds;
+ }
+
+ kerr = krb5_cc_store_cred(kr->ctx, tmp_cc, l_cred);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto done;
+ }
+
+ kerr = krb5_cc_close(kr->ctx, tmp_cc);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto done;
+ }
+ tmp_cc = NULL;
+
+ ccname_len = strlen(cc_file_name);
+ if (ccname_len >= 6 && strcmp(cc_file_name + (ccname_len-6), "XXXXXX")==0 ) {
+ fd = mkstemp(cc_file_name);
+ if (fd == -1) {
+ DEBUG(1, ("mkstemp failed [%d][%s].\n", errno, strerror(errno)));
+ kerr = errno;
+ goto done;
+ }
+ }
+
+ kerr = rename(tmp_ccname, cc_file_name);
+ if (kerr == -1) {
+ DEBUG(1, ("rename failed [%d][%s].\n", errno, strerror(errno)));
+ }
+
+done:
+ if (fd != -1) {
+ close(fd);
+ fd = -1;
+ }
+ if (kerr != 0 && tmp_cc != NULL) {
+ krb5_cc_destroy(kr->ctx, tmp_cc);
+ }
+ return kerr;
+}
+
+static struct response *init_response(TALLOC_CTX *mem_ctx) {
+ struct response *r;
+ r = talloc(mem_ctx, struct response);
+ r->buf = talloc_size(mem_ctx, MAX_CHILD_MSG_SIZE);
+ if (r->buf == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ return NULL;
+ }
+ r->max_size = MAX_CHILD_MSG_SIZE;
+ r->size = 0;
+
+ return r;
+}
+
+static errno_t pack_response_packet(struct response *resp, int status, int type,
+ size_t len, const uint8_t *data)
+{
+ int p=0;
+
+ if ((3*sizeof(int32_t) + len +1) > resp->max_size) {
+ DEBUG(1, ("response message too big.\n"));
+ return ENOMEM;
+ }
+
+ COPY_INT32_VALUE(&resp->buf[p], status, p);
+ COPY_INT32_VALUE(&resp->buf[p], type, p);
+ COPY_INT32_VALUE(&resp->buf[p], len, p);
+ COPY_MEM(&resp->buf[p], data, p, len);
+
+ resp->size = p;
+
+ return EOK;
+}
+
+static struct response *prepare_response_message(struct krb5_req *kr,
+ krb5_error_code kerr,
+ char *user_error_message,
+ int pam_status)
+{
+ char *msg = NULL;
+ const char *krb5_msg = NULL;
+ int ret;
+ struct response *resp;
+ size_t user_resp_len;
+ uint8_t *user_resp;
+
+ resp = init_response(kr);
+ if (resp == NULL) {
+ DEBUG(1, ("init_response failed.\n"));
+ return NULL;
+ }
+
+ if (kerr == 0) {
+ if(kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+ ret = pack_response_packet(resp, PAM_SUCCESS, SSS_PAM_SYSTEM_INFO,
+ strlen("success") + 1,
+ (const uint8_t *) "success");
+ } else {
+ if (kr->ccname == NULL) {
+ DEBUG(1, ("Error obtaining ccname.\n"));
+ return NULL;
+ }
+
+ msg = talloc_asprintf(kr, "%s=%s",CCACHE_ENV_NAME, kr->ccname);
+ if (msg == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ return NULL;
+ }
+
+ ret = pack_response_packet(resp, PAM_SUCCESS, SSS_PAM_ENV_ITEM,
+ strlen(msg) + 1, (uint8_t *) msg);
+ talloc_zfree(msg);
+ }
+ } else {
+
+ if (user_error_message != NULL) {
+ ret = pack_user_info_chpass_error(kr, user_error_message,
+ &user_resp_len, &user_resp);
+ if (ret != EOK) {
+ DEBUG(1, ("pack_user_info_chpass_error failed.\n"));
+ talloc_zfree(user_error_message);
+ } else {
+ ret = pack_response_packet(resp, pam_status, SSS_PAM_USER_INFO,
+ user_resp_len, user_resp);
+ if (ret != EOK) {
+ DEBUG(1, ("pack_response_packet failed.\n"));
+ talloc_zfree(user_error_message);
+ }
+ }
+ }
+
+ if (user_error_message == NULL) {
+ krb5_msg = sss_krb5_get_error_message(krb5_error_ctx, kerr);
+ if (krb5_msg == NULL) {
+ DEBUG(1, ("sss_krb5_get_error_message failed.\n"));
+ return NULL;
+ }
+
+ ret = pack_response_packet(resp, pam_status, SSS_PAM_SYSTEM_INFO,
+ strlen(krb5_msg) + 1,
+ (const uint8_t *) krb5_msg);
+ sss_krb5_free_error_message(krb5_error_ctx, krb5_msg);
+ } else {
+
+ }
+
+ }
+
+ if (ret != EOK) {
+ DEBUG(1, ("pack_response_packet failed.\n"));
+ return NULL;
+ }
+
+ return resp;
+}
+
+static errno_t sendresponse(int fd, krb5_error_code kerr,
+ char *user_error_message, int pam_status,
+ struct krb5_req *kr)
+{
+ struct response *resp;
+ size_t written;
+ int ret;
+
+ resp = prepare_response_message(kr, kerr, user_error_message, pam_status);
+ if (resp == NULL) {
+ DEBUG(1, ("prepare_response_message failed.\n"));
+ return ENOMEM;
+ }
+
+ written = 0;
+ while (written < resp->size) {
+ ret = write(fd, resp->buf + written, resp->size - written);
+ if (ret == -1) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ }
+ ret = errno;
+ DEBUG(1, ("write failed [%d][%s].\n", ret, strerror(ret)));
+ return ret;
+ }
+ written += ret;
+ }
+
+ return EOK;
+}
+
+static krb5_error_code validate_tgt(struct krb5_req *kr)
+{
+ krb5_error_code kerr;
+ krb5_error_code kt_err;
+ char *principal;
+ krb5_keytab keytab;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry entry;
+ krb5_verify_init_creds_opt opt;
+
+ memset(&keytab, 0, sizeof(keytab));
+ kerr = krb5_kt_resolve(kr->ctx, kr->keytab, &keytab);
+ if (kerr != 0) {
+ DEBUG(1, ("error resolving keytab [%s], not verifying TGT.\n",
+ kr->keytab));
+ return kerr;
+ }
+
+ memset(&cursor, 0, sizeof(cursor));
+ kerr = krb5_kt_start_seq_get(kr->ctx, keytab, &cursor);
+ if (kerr != 0) {
+ DEBUG(1, ("error reading keytab [%s], not verifying TGT.\n",
+ kr->keytab));
+ return kerr;
+ }
+
+ /* We look for the first entry from our realm or take the last one */
+ memset(&entry, 0, sizeof(entry));
+ while ((kt_err = krb5_kt_next_entry(kr->ctx, keytab, &entry, &cursor)) == 0) {
+ if (krb5_realm_compare(kr->ctx, entry.principal, kr->princ)) {
+ DEBUG(9, ("Found keytab entry with the realm of the credential.\n"));
+ break;
+ }
+
+ kerr = krb5_free_keytab_entry_contents(kr->ctx, &entry);
+ if (kerr != 0) {
+ DEBUG(1, ("Failed to free keytab entry.\n"));
+ }
+ memset(&entry, 0, sizeof(entry));
+ }
+
+ /* Close the keytab here. Even though we're using cursors, the file
+ * handle is stored in the krb5_keytab structure, and it gets
+ * overwritten when the verify_init_creds() call below creates its own
+ * cursor, creating a leak. */
+ kerr = krb5_kt_end_seq_get(kr->ctx, keytab, &cursor);
+ if (kerr != 0) {
+ DEBUG(1, ("krb5_kt_end_seq_get failed, not verifying TGT.\n"));
+ goto done;
+ }
+
+ /* check if we got any errors from krb5_kt_next_entry */
+ if (kt_err != 0 && kt_err != KRB5_KT_END) {
+ DEBUG(1, ("error reading keytab [%s], not verifying TGT.\n",
+ kr->keytab));
+ goto done;
+ }
+
+ /* Get the principal to which the key belongs, for logging purposes. */
+ principal = NULL;
+ kerr = krb5_unparse_name(kr->ctx, entry.principal, &principal);
+ if (kerr != 0) {
+ DEBUG(1, ("internal error parsing principal name, "
+ "not verifying TGT.\n"));
+ goto done;
+ }
+
+
+ krb5_verify_init_creds_opt_init(&opt);
+ kerr = krb5_verify_init_creds(kr->ctx, kr->creds, entry.principal, keytab,
+ NULL, &opt);
+
+ if (kerr == 0) {
+ DEBUG(5, ("TGT verified using key for [%s].\n", principal));
+ } else {
+ DEBUG(1 ,("TGT failed verification using key for [%s].\n", principal));
+ }
+
+done:
+ if (krb5_kt_close(kr->ctx, keytab) != 0) {
+ DEBUG(1, ("krb5_kt_close failed"));
+ }
+ if (krb5_free_keytab_entry_contents(kr->ctx, &entry) != 0) {
+ DEBUG(1, ("Failed to free keytab entry.\n"));
+ }
+ if (principal != NULL) {
+ sss_krb5_free_unparsed_name(kr->ctx, principal);
+ }
+
+ return kerr;
+
+}
+
+static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
+ char *password)
+{
+ krb5_error_code kerr = 0;
+ int ret;
+
+ kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
+ password, NULL, NULL, 0, NULL,
+ kr->options);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ return kerr;
+ }
+
+ if (kr->validate) {
+ kerr = validate_tgt(kr);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ return kerr;
+ }
+
+ /* We drop root privileges which were needed to read the keytab file
+ * for the validation validation of the credentials here to run the
+ * ccache I/O operations with user privileges. */
+ ret = become_user(kr->pd->pw_uid, kr->pd->gr_gid);
+ if (ret != EOK) {
+ DEBUG(1, ("become_user failed.\n"));
+ return ret;
+ }
+ } else {
+ DEBUG(9, ("TGT validation is disabled.\n"));
+ }
+
+ kerr = create_ccache_file(kr, kr->creds);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto done;
+ }
+
+ kerr = 0;
+
+done:
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+
+ return kerr;
+
+}
+
+static errno_t changepw_child(int fd, struct krb5_req *kr)
+{
+ int ret;
+ krb5_error_code kerr = 0;
+ char *pass_str = NULL;
+ char *newpass_str = NULL;
+ int pam_status = PAM_SYSTEM_ERR;
+ int result_code = -1;
+ krb5_data result_code_string;
+ krb5_data result_string;
+ char *user_error_message = NULL;
+
+ pass_str = talloc_strndup(kr, (const char *) kr->pd->authtok,
+ kr->pd->authtok_size);
+ if (pass_str == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ kerr = KRB5KRB_ERR_GENERIC;
+ goto sendresponse;
+ }
+
+ kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
+ pass_str, NULL, NULL, 0,
+ kr->krb5_ctx->changepw_principle,
+ kr->options);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ if (kerr == KRB5_KDC_UNREACH) {
+ pam_status = PAM_AUTHINFO_UNAVAIL;
+ }
+ goto sendresponse;
+ }
+
+ memset(pass_str, 0, kr->pd->authtok_size);
+ talloc_zfree(pass_str);
+ memset(kr->pd->authtok, 0, kr->pd->authtok_size);
+
+ if (kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+ DEBUG(9, ("Initial authentication for change password operation "
+ "successfull.\n"));
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+ pam_status = PAM_SUCCESS;
+ goto sendresponse;
+ }
+
+ newpass_str = talloc_strndup(kr, (const char *) kr->pd->newauthtok,
+ kr->pd->newauthtok_size);
+ if (newpass_str == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ kerr = KRB5KRB_ERR_GENERIC;
+ goto sendresponse;
+ }
+
+ kerr = krb5_change_password(kr->ctx, kr->creds, newpass_str, &result_code,
+ &result_code_string, &result_string);
+
+ if (kerr != 0 || result_code != 0) {
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ } else {
+ kerr = KRB5KRB_ERR_GENERIC;
+ }
+
+ if (result_code_string.length > 0) {
+ DEBUG(1, ("krb5_change_password failed [%d][%.*s].\n", result_code,
+ result_code_string.length, result_code_string.data));
+ user_error_message = talloc_strndup(kr->pd, result_code_string.data,
+ result_code_string.length);
+ if (user_error_message == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ }
+ }
+
+ if (result_string.length > 0) {
+ DEBUG(1, ("krb5_change_password failed [%d][%.*s].\n", result_code,
+ result_string.length, result_string.data));
+ talloc_free(user_error_message);
+ user_error_message = talloc_strndup(kr->pd, result_string.data,
+ result_string.length);
+ if (user_error_message == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ }
+ }
+
+ pam_status = PAM_AUTHTOK_ERR;
+ goto sendresponse;
+ }
+
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+
+ kerr = get_and_save_tgt(kr, newpass_str);
+ memset(newpass_str, 0, kr->pd->newauthtok_size);
+ talloc_zfree(newpass_str);
+ memset(kr->pd->newauthtok, 0, kr->pd->newauthtok_size);
+
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ if (kerr == KRB5_KDC_UNREACH) {
+ pam_status = PAM_AUTHINFO_UNAVAIL;
+ }
+ }
+
+sendresponse:
+ ret = sendresponse(fd, kerr, user_error_message, pam_status, kr);
+ if (ret != EOK) {
+ DEBUG(1, ("sendresponse failed.\n"));
+ }
+
+ return ret;
+}
+
+static errno_t tgt_req_child(int fd, struct krb5_req *kr)
+{
+ int ret;
+ krb5_error_code kerr = 0;
+ char *pass_str = NULL;
+ int pam_status = PAM_SYSTEM_ERR;
+
+ pass_str = talloc_strndup(kr, (const char *) kr->pd->authtok,
+ kr->pd->authtok_size);
+ if (pass_str == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ kerr = KRB5KRB_ERR_GENERIC;
+ goto sendresponse;
+ }
+
+ kerr = get_and_save_tgt(kr, pass_str);
+
+ /* If the password is expired the KDC will always return
+ KRB5KDC_ERR_KEY_EXP regardless if the supplied password is correct or
+ not. In general the password can still be used to get a changepw ticket.
+ So we validate the password by trying to get a changepw ticket. */
+ if (kerr == KRB5KDC_ERR_KEY_EXP) {
+ kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
+ pass_str, NULL, NULL, 0,
+ kr->krb5_ctx->changepw_principle,
+ kr->options);
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+ if (kerr == 0) {
+ kerr = KRB5KDC_ERR_KEY_EXP;
+ }
+ }
+
+ memset(pass_str, 0, kr->pd->authtok_size);
+ talloc_zfree(pass_str);
+ memset(kr->pd->authtok, 0, kr->pd->authtok_size);
+
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ switch (kerr) {
+ case KRB5_KDC_UNREACH:
+ pam_status = PAM_AUTHINFO_UNAVAIL;
+ break;
+ case KRB5KDC_ERR_KEY_EXP:
+ pam_status = PAM_AUTHTOK_EXPIRED;
+ break;
+ case KRB5KDC_ERR_PREAUTH_FAILED:
+ pam_status = PAM_CRED_ERR;
+ break;
+ default:
+ pam_status = PAM_SYSTEM_ERR;
+ }
+ }
+
+sendresponse:
+ ret = sendresponse(fd, kerr, NULL, pam_status, kr);
+ if (ret != EOK) {
+ DEBUG(1, ("sendresponse failed.\n"));
+ }
+
+ return ret;
+}
+
+static errno_t create_empty_ccache(int fd, struct krb5_req *kr)
+{
+ int ret;
+ int pam_status = PAM_SUCCESS;
+
+ ret = create_ccache_file(kr, NULL);
+ if (ret != 0) {
+ KRB5_DEBUG(1, ret);
+ pam_status = PAM_SYSTEM_ERR;
+ }
+
+ ret = sendresponse(fd, ret, NULL, pam_status, kr);
+ if (ret != EOK) {
+ DEBUG(1, ("sendresponse failed.\n"));
+ }
+
+ return ret;
+}
+
+static errno_t unpack_buffer(uint8_t *buf, size_t size, struct pam_data *pd,
+ char **ccname, char **keytab, uint32_t *validate,
+ uint32_t *offline)
+{
+ size_t p = 0;
+ uint32_t len;
+
+ COPY_UINT32_CHECK(&pd->cmd, buf + p, p, size);
+ COPY_UINT32_CHECK(&pd->pw_uid, buf + p, p, size);
+ COPY_UINT32_CHECK(&pd->gr_gid, buf + p, p, size);
+ COPY_UINT32_CHECK(validate, buf + p, p, size);
+ COPY_UINT32_CHECK(offline, buf + p, p, size);
+
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+ if ((p + len ) > size) return EINVAL;
+ pd->upn = talloc_strndup(pd, (char *)(buf + p), len);
+ if (pd->upn == NULL) return ENOMEM;
+ p += len;
+
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+ if ((p + len ) > size) return EINVAL;
+ *ccname = talloc_strndup(pd, (char *)(buf + p), len);
+ if (*ccname == NULL) return ENOMEM;
+ p += len;
+
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+ if ((p + len ) > size) return EINVAL;
+ *keytab = talloc_strndup(pd, (char *)(buf + p), len);
+ if (*keytab == NULL) return ENOMEM;
+ p += len;
+
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+ if ((p + len) > size) return EINVAL;
+ pd->authtok = (uint8_t *)talloc_strndup(pd, (char *)(buf + p), len);
+ if (pd->authtok == NULL) return ENOMEM;
+ pd->authtok_size = len + 1;
+ p += len;
+
+ if (pd->cmd == SSS_PAM_CHAUTHTOK) {
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+
+ if ((p + len) > size) return EINVAL;
+ pd->newauthtok = (uint8_t *)talloc_strndup(pd, (char *)(buf + p), len);
+ if (pd->newauthtok == NULL) return ENOMEM;
+ pd->newauthtok_size = len + 1;
+ p += len;
+ } else {
+ pd->newauthtok = NULL;
+ pd->newauthtok_size = 0;
+ }
+
+ return EOK;
+}
+
+static int krb5_cleanup(void *ptr)
+{
+ struct krb5_req *kr = talloc_get_type(ptr, struct krb5_req);
+ if (kr == NULL) return EOK;
+
+ if (kr->options != NULL) {
+ sss_krb5_get_init_creds_opt_free(kr->ctx, kr->options);
+ }
+
+ if (kr->creds != NULL) {
+ krb5_free_cred_contents(kr->ctx, kr->creds);
+ krb5_free_creds(kr->ctx, kr->creds);
+ }
+ if (kr->name != NULL)
+ sss_krb5_free_unparsed_name(kr->ctx, kr->name);
+ if (kr->princ != NULL)
+ krb5_free_principal(kr->ctx, kr->princ);
+ if (kr->ctx != NULL)
+ krb5_free_context(kr->ctx);
+
+ if (kr->krb5_ctx != NULL) {
+ memset(kr->krb5_ctx, 0, sizeof(struct krb5_child_ctx));
+ }
+ memset(kr, 0, sizeof(struct krb5_req));
+
+ return EOK;
+}
+
+static int krb5_setup(struct pam_data *pd, const char *user_princ_str,
+ uint32_t offline, struct krb5_req **krb5_req)
+{
+ struct krb5_req *kr = NULL;
+ krb5_error_code kerr = 0;
+
+ kr = talloc_zero(pd, struct krb5_req);
+ if (kr == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ kerr = ENOMEM;
+ goto failed;
+ }
+ talloc_set_destructor((TALLOC_CTX *) kr, krb5_cleanup);
+
+ kr->krb5_ctx = talloc_zero(kr, struct krb5_child_ctx);
+ if (kr->krb5_ctx == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ kerr = ENOMEM;
+ goto failed;
+ }
+
+ kr->krb5_ctx->changepw_principle = getenv(SSSD_KRB5_CHANGEPW_PRINCIPLE);
+ if (kr->krb5_ctx->changepw_principle == NULL) {
+ DEBUG(1, ("Cannot read [%s] from environment.\n",
+ SSSD_KRB5_CHANGEPW_PRINCIPLE));
+ if (pd->cmd == SSS_PAM_CHAUTHTOK) {
+ goto failed;
+ }
+ }
+
+ kr->krb5_ctx->realm = getenv(SSSD_KRB5_REALM);
+ if (kr->krb5_ctx->realm == NULL) {
+ DEBUG(2, ("Cannot read [%s] from environment.\n", SSSD_KRB5_REALM));
+ }
+
+ kr->pd = pd;
+
+ switch(pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ /* If we are offline, we need to create an empty ccache file */
+ if (offline) {
+ kr->child_req = create_empty_ccache;
+ } else {
+ kr->child_req = tgt_req_child;
+ }
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ kr->child_req = changepw_child;
+ break;
+ default:
+ DEBUG(1, ("PAM command [%d] not supported.\n", pd->cmd));
+ kerr = EINVAL;
+ goto failed;
+ }
+
+ kerr = krb5_init_context(&kr->ctx);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto failed;
+ }
+
+ kerr = krb5_parse_name(kr->ctx, user_princ_str, &kr->princ);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto failed;
+ }
+
+ kerr = krb5_unparse_name(kr->ctx, kr->princ, &kr->name);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto failed;
+ }
+
+ kr->creds = calloc(1, sizeof(krb5_creds));
+ if (kr->creds == NULL) {
+ DEBUG(1, ("talloc_zero failed.\n"));
+ kerr = ENOMEM;
+ goto failed;
+ }
+
+ kerr = sss_krb5_get_init_creds_opt_alloc(kr->ctx, &kr->options);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto failed;
+ }
+
+/* TODO: set options, e.g.
+ * krb5_get_init_creds_opt_set_tkt_life
+ * krb5_get_init_creds_opt_set_renew_life
+ * krb5_get_init_creds_opt_set_forwardable
+ * krb5_get_init_creds_opt_set_proxiable
+ * krb5_get_init_creds_opt_set_etype_list
+ * krb5_get_init_creds_opt_set_address_list
+ * krb5_get_init_creds_opt_set_preauth_list
+ * krb5_get_init_creds_opt_set_salt
+ * krb5_get_init_creds_opt_set_change_password_prompt
+ * krb5_get_init_creds_opt_set_pa
+ */
+
+ *krb5_req = kr;
+ return EOK;
+
+failed:
+ talloc_free(kr);
+
+ return kerr;
+}
+
+int main(int argc, const char *argv[])
+{
+ uint8_t *buf = NULL;
+ int ret;
+ ssize_t len = 0;
+ struct pam_data *pd = NULL;
+ struct krb5_req *kr = NULL;
+ char *ccname;
+ char *keytab;
+ uint32_t validate;
+ uint32_t offline;
+ int opt;
+ poptContext pc;
+ int debug_fd = -1;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {"debug-level", 'd', POPT_ARG_INT, &debug_level, 0,
+ _("Debug level"), NULL},
+ {"debug-timestamps", 0, POPT_ARG_INT, &debug_timestamps, 0,
+ _("Add debug timestamps"), NULL},
+ {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0,
+ _("An open file descriptor for the debug logs"), NULL},
+ POPT_TABLEEND
+ };
+
+
+ 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);
+ _exit(-1);
+ }
+ }
+
+ poptFreeContext(pc);
+
+ DEBUG(7, ("krb5_child started.\n"));
+
+ pd = talloc(NULL, struct pam_data);
+ if (pd == NULL) {
+ DEBUG(1, ("malloc failed.\n"));
+ _exit(-1);
+ }
+
+ debug_prg_name = talloc_asprintf(pd, "[sssd[krb5_child[%d]]]", getpid());
+
+ if (debug_fd != -1) {
+ ret = set_debug_file_from_fd(debug_fd);
+ if (ret != EOK) {
+ DEBUG(1, ("set_debug_file_from_fd failed.\n"));
+ }
+ }
+
+ buf = talloc_size(pd, sizeof(uint8_t)*IN_BUF_SIZE);
+ if (buf == NULL) {
+ DEBUG(1, ("malloc failed.\n"));
+ _exit(-1);
+ }
+
+ while ((ret = read(STDIN_FILENO, buf + len, IN_BUF_SIZE - len)) != 0) {
+ if (ret == -1) {
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ }
+ DEBUG(1, ("read failed [%d][%s].\n", errno, strerror(errno)));
+ goto fail;
+ } else if (ret > 0) {
+ len += ret;
+ if (len > IN_BUF_SIZE) {
+ DEBUG(1, ("read too much, this should never happen.\n"));
+ goto fail;
+ }
+ continue;
+ } else {
+ DEBUG(1, ("unexpected return code of read [%d].\n", ret));
+ goto fail;
+ }
+ }
+ close(STDIN_FILENO);
+
+ ret = unpack_buffer(buf, len, pd, &ccname, &keytab, &validate, &offline);
+ if (ret != EOK) {
+ DEBUG(1, ("unpack_buffer failed.\n"));
+ goto fail;
+ }
+
+ ret = krb5_setup(pd, pd->upn, offline, &kr);
+ if (ret != EOK) {
+ DEBUG(1, ("krb5_setup failed.\n"));
+ goto fail;
+ }
+ kr->ccname = ccname;
+ kr->keytab = keytab;
+ kr->validate = (validate == 0) ? false : true;
+
+ ret = kr->child_req(STDOUT_FILENO, kr);
+ if (ret != EOK) {
+ DEBUG(1, ("Child request failed.\n"));
+ goto fail;
+ }
+
+ close(STDOUT_FILENO);
+ talloc_free(pd);
+
+ return 0;
+
+fail:
+ close(STDOUT_FILENO);
+ talloc_free(pd);
+ exit(-1);
+}
diff --git a/src/providers/krb5/krb5_common.c b/src/providers/krb5/krb5_common.c
new file mode 100644
index 000000000..86676f44c
--- /dev/null
+++ b/src/providers/krb5/krb5_common.c
@@ -0,0 +1,356 @@
+/*
+ SSSD
+
+ Kerberos Provider Common Functions
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2008-2009 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 <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <netdb.h>
+
+#include "providers/dp_backend.h"
+#include "providers/krb5/krb5_common.h"
+
+struct dp_option default_krb5_opts[] = {
+ { "krb5_kdcip", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "krb5_ccachedir", DP_OPT_STRING, { "/tmp" }, NULL_STRING },
+ { "krb5_ccname_template", DP_OPT_STRING, { "FILE:%d/krb5cc_%U_XXXXXX" }, NULL_STRING},
+ { "krb5_changepw_principal", DP_OPT_STRING, { "kadmin/changepw" }, NULL_STRING },
+ { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 15 }, NULL_NUMBER },
+ { "krb5_keytab", DP_OPT_STRING, { "/etc/krb5.keytab" }, NULL_STRING },
+ { "krb5_validate", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }
+};
+
+errno_t check_and_export_options(struct dp_option *opts,
+ struct sss_domain_info *dom)
+{
+ int ret;
+ char *value;
+ const char *realm;
+ const char *dummy;
+ struct stat stat_buf;
+ char **list;
+
+ realm = dp_opt_get_cstring(opts, KRB5_REALM);
+ if (realm == NULL) {
+ ret = dp_opt_set_string(opts, KRB5_REALM, dom->name);
+ if (ret != EOK) {
+ DEBUG(1, ("dp_opt_set_string failed.\n"));
+ return ret;
+ }
+ realm = dom->name;
+ }
+
+ ret = setenv(SSSD_KRB5_REALM, realm, 1);
+ if (ret != EOK) {
+ DEBUG(2, ("setenv %s failed, authentication might fail.\n",
+ SSSD_KRB5_REALM));
+ }
+
+ dummy = dp_opt_get_cstring(opts, KRB5_KDC);
+ if (dummy == NULL) {
+ DEBUG(1, ("No KDC expicitly configured, using defaults"));
+ } else {
+ ret = split_on_separator(opts, dummy, ',', true, &list, NULL);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to parse server list!\n"));
+ return ret;
+ }
+ ret = write_kdcinfo_file(realm, list[0]);
+ if (ret != EOK) {
+ DEBUG(1, ("write_kdcinfo_file failed, "
+ "using kerberos defaults from /etc/krb5.conf"));
+ }
+ talloc_free(list);
+ }
+
+ dummy = dp_opt_get_cstring(opts, KRB5_CCACHEDIR);
+ ret = lstat(dummy, &stat_buf);
+ if (ret != EOK) {
+ DEBUG(1, ("lstat for [%s] failed: [%d][%s].\n", dummy, errno,
+ strerror(errno)));
+ return ret;
+ }
+ if ( !S_ISDIR(stat_buf.st_mode) ) {
+ DEBUG(1, ("Value of krb5ccache_dir [%s] is not a directory.\n", dummy));
+ return EINVAL;
+ }
+
+ dummy = dp_opt_get_cstring(opts, KRB5_CCNAME_TMPL);
+ if (dummy == NULL) {
+ DEBUG(1, ("Missing credential cache name template.\n"));
+ return EINVAL;
+ }
+ if (dummy[0] != '/' && strncmp(dummy, "FILE:", 5) != 0) {
+ DEBUG(1, ("Currently only file based credential caches are supported "
+ "and krb5ccname_template must start with '/' or 'FILE:'\n"));
+ return EINVAL;
+ }
+
+ dummy = dp_opt_get_cstring(opts, KRB5_CHANGEPW_PRINC);
+ if (dummy == NULL) {
+ DEBUG(1, ("Missing change password principle.\n"));
+ return EINVAL;
+ }
+ if (strchr(dummy, '@') == NULL) {
+ value = talloc_asprintf(opts, "%s@%s", dummy, realm);
+ if (value == NULL) {
+ DEBUG(7, ("talloc_asprintf failed.\n"));
+ return ENOMEM;
+ }
+ ret = dp_opt_set_string(opts, KRB5_CHANGEPW_PRINC, value);
+ if (ret != EOK) {
+ DEBUG(1, ("dp_opt_set_string failed.\n"));
+ return ret;
+ }
+ dummy = value;
+ }
+
+ ret = setenv(SSSD_KRB5_CHANGEPW_PRINCIPLE, dummy, 1);
+ if (ret != EOK) {
+ DEBUG(2, ("setenv %s failed, password change might fail.\n",
+ SSSD_KRB5_CHANGEPW_PRINCIPLE));
+ }
+
+ return EOK;
+}
+
+errno_t krb5_get_options(TALLOC_CTX *memctx, struct confdb_ctx *cdb,
+ const char *conf_path, struct dp_option **_opts)
+{
+ int ret;
+ struct dp_option *opts;
+
+ opts = talloc_zero(memctx, struct dp_option);
+ if (opts == NULL) {
+ DEBUG(1, ("talloc_zero failed.\n"));
+ return ENOMEM;
+ }
+
+ ret = dp_get_options(opts, cdb, conf_path, default_krb5_opts,
+ KRB5_OPTS, &opts);
+ if (ret != EOK) {
+ DEBUG(1, ("dp_get_options failed.\n"));
+ goto done;
+ }
+
+ *_opts = opts;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_zfree(opts);
+ }
+
+ return ret;
+}
+
+errno_t write_kdcinfo_file(const char *realm, const char *kdc)
+{
+ int ret;
+ int fd = -1;
+ char *tmp_name = NULL;
+ char *kdcinfo_name = NULL;
+ TALLOC_CTX *tmp_ctx = NULL;
+ int kdc_len;
+
+ if (realm == NULL || *realm == '\0' || kdc == NULL || *kdc == '\0') {
+ DEBUG(1, ("Missing or empty realm or kdc.\n"));
+ return EINVAL;
+ }
+
+ kdc_len = strlen(kdc);
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(1, ("talloc_new failed.\n"));
+ return ENOMEM;
+ }
+
+ tmp_name = talloc_asprintf(tmp_ctx, PUBCONF_PATH"/.kdcinfo_dummy_XXXXXX");
+ if (tmp_name == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+
+ kdcinfo_name = talloc_asprintf(tmp_ctx, KDCINFO_TMPL, realm);
+ if (kdcinfo_name == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+
+ fd = mkstemp(tmp_name);
+ if (fd == -1) {
+ DEBUG(1, ("mkstemp failed [%d][%s].\n", errno, strerror(errno)));
+ ret = errno;
+ goto done;
+ }
+
+ ret = write(fd, kdc, kdc_len);
+ if (ret == -1) {
+ DEBUG(1, ("write failed [%d][%s].\n", errno, strerror(errno)));
+ goto done;
+ }
+ if (ret != kdc_len) {
+ DEBUG(1, ("Partial write occured, this should never happen.\n"));
+ ret = EINTR;
+ goto done;
+ }
+
+ ret = fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (ret == -1) {
+ DEBUG(1, ("fchmod failed [%d][%s].\n", errno, strerror(errno)));
+ goto done;
+ }
+
+ ret = close(fd);
+ if (ret == -1) {
+ DEBUG(1, ("close failed [%d][%s].\n", errno, strerror(errno)));
+ goto done;
+ }
+
+ ret = rename(tmp_name, kdcinfo_name);
+ if (ret == -1) {
+ DEBUG(1, ("rename failed [%d][%s].\n", errno, strerror(errno)));
+ goto done;
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static void krb5_resolve_callback(void *private_data, struct fo_server *server)
+{
+ struct krb5_service *krb5_service;
+ struct hostent *srvaddr;
+ char *address;
+ int ret;
+
+ krb5_service = talloc_get_type(private_data, struct krb5_service);
+ if (!krb5_service) {
+ DEBUG(1, ("FATAL: Bad private_data\n"));
+ return;
+ }
+
+ srvaddr = fo_get_server_hostent(server);
+ if (!srvaddr) {
+ DEBUG(1, ("FATAL: No hostent available for server (%s)\n",
+ fo_get_server_name(server)));
+ return;
+ }
+
+ address = talloc_asprintf(krb5_service, "%s", srvaddr->h_name);
+ if (!address) {
+ DEBUG(1, ("Failed to copy address ...\n"));
+ return;
+ }
+
+ talloc_zfree(krb5_service->address);
+ krb5_service->address = address;
+
+ ret = write_kdcinfo_file(krb5_service->realm, address);
+ if (ret != EOK) {
+ DEBUG(2, ("write_kdcinfo_file failed, authentication might fail.\n"));
+ }
+
+ return;
+}
+
+
+int krb5_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
+ const char *service_name, const char *servers,
+ const char *realm, struct krb5_service **_service)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct krb5_service *service;
+ char **list = NULL;
+ int ret;
+ int i;
+
+ tmp_ctx = talloc_new(memctx);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ service = talloc_zero(tmp_ctx, struct krb5_service);
+ if (!service) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = be_fo_add_service(ctx, service_name);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to create failover service!\n"));
+ goto done;
+ }
+
+ service->name = talloc_strdup(service, service_name);
+ if (!service->name) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ service->realm = talloc_strdup(service, realm);
+ if (!service->realm) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = split_on_separator(tmp_ctx, servers, ',', true, &list, NULL);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to parse server list!\n"));
+ goto done;
+ }
+
+ for (i = 0; list[i]; i++) {
+
+ talloc_steal(service, list[i]);
+
+ ret = be_fo_add_server(ctx, service_name, list[i], 0, NULL);
+ if (ret && ret != EEXIST) {
+ DEBUG(0, ("Failed to add server\n"));
+ goto done;
+ }
+
+ DEBUG(6, ("Added Server %s\n", list[i]));
+ }
+
+ ret = be_fo_service_add_callback(memctx, ctx, service_name,
+ krb5_resolve_callback, service);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to add failover callback!\n"));
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret == EOK) {
+ *_service = talloc_steal(memctx, service);
+ }
+ talloc_zfree(tmp_ctx);
+ return ret;
+}
+
diff --git a/src/providers/krb5/krb5_common.h b/src/providers/krb5/krb5_common.h
new file mode 100644
index 000000000..832ffcdd5
--- /dev/null
+++ b/src/providers/krb5/krb5_common.h
@@ -0,0 +1,72 @@
+/*
+ SSSD
+
+ Kerberos Backend, common header file
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 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 __KRB5_COMMON_H__
+#define __KRB5_COMMON_H__
+
+#include "config.h"
+#include <stdbool.h>
+
+#include "providers/dp_backend.h"
+#include "util/util.h"
+#include "util/sss_krb5.h"
+
+#define SSSD_KRB5_KDC "SSSD_KRB5_KDC"
+#define SSSD_KRB5_REALM "SSSD_KRB5_REALM"
+#define SSSD_KRB5_CHANGEPW_PRINCIPLE "SSSD_KRB5_CHANGEPW_PRINCIPLE"
+
+#define KDCINFO_TMPL PUBCONF_PATH"/kdcinfo.%s"
+
+enum krb5_opts {
+ KRB5_KDC = 0,
+ KRB5_REALM,
+ KRB5_CCACHEDIR,
+ KRB5_CCNAME_TMPL,
+ KRB5_CHANGEPW_PRINC,
+ KRB5_AUTH_TIMEOUT,
+ KRB5_KEYTAB,
+ KRB5_VALIDATE,
+
+ KRB5_OPTS
+};
+
+struct krb5_service {
+ char *name;
+ char *address;
+ char *realm;
+};
+
+errno_t check_and_export_options(struct dp_option *opts,
+ struct sss_domain_info *dom);
+
+errno_t krb5_get_options(TALLOC_CTX *memctx, struct confdb_ctx *cdb,
+ const char *conf_path, struct dp_option **_opts);
+
+errno_t write_kdcinfo_file(const char *realm, const char *kdc);
+
+int krb5_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
+ const char *service_name, const char *servers,
+ const char *realm, struct krb5_service **_service);
+#endif /* __KRB5_COMMON_H__ */
diff --git a/src/providers/krb5/krb5_init.c b/src/providers/krb5/krb5_init.c
new file mode 100644
index 000000000..43cbc1bc7
--- /dev/null
+++ b/src/providers/krb5/krb5_init.c
@@ -0,0 +1,152 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 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 <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include "providers/child_common.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/krb5/krb5_common.h"
+
+struct krb5_options {
+ struct dp_option *opts;
+ struct krb5_ctx *auth_ctx;
+};
+
+struct krb5_options *krb5_options = NULL;
+
+struct bet_ops krb5_auth_ops = {
+ .handler = krb5_pam_handler,
+ .finalize = NULL,
+};
+
+int sssm_krb5_auth_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_auth_data)
+{
+ struct krb5_ctx *ctx = NULL;
+ int ret;
+ struct tevent_signal *sige;
+ unsigned v;
+ FILE *debug_filep;
+ const char *krb5_servers;
+ const char *krb5_realm;
+
+ if (krb5_options == NULL) {
+ krb5_options = talloc_zero(bectx, struct krb5_options);
+ if (krb5_options == NULL) {
+ DEBUG(1, ("talloc_zero failed.\n"));
+ return ENOMEM;
+ }
+ ret = krb5_get_options(krb5_options, bectx->cdb, bectx->conf_path,
+ &krb5_options->opts);
+ if (ret != EOK) {
+ DEBUG(1, ("krb5_get_options failed.\n"));
+ return ret;
+ }
+ }
+
+ if (krb5_options->auth_ctx != NULL) {
+ *ops = &krb5_auth_ops;
+ *pvt_auth_data = krb5_options->auth_ctx;
+ return EOK;
+ }
+
+ ctx = talloc_zero(bectx, struct krb5_ctx);
+ if (!ctx) {
+ DEBUG(1, ("talloc failed.\n"));
+ return ENOMEM;
+ }
+
+ ctx->action = INIT_PW;
+ ctx->opts = krb5_options->opts;
+
+ krb5_servers = dp_opt_get_string(ctx->opts, KRB5_KDC);
+ if (krb5_servers == NULL) {
+ DEBUG(0, ("Missing krb5_kdcip option!\n"));
+ return EINVAL;
+ }
+
+ krb5_realm = dp_opt_get_string(ctx->opts, KRB5_REALM);
+ if (krb5_realm == NULL) {
+ DEBUG(0, ("Missing krb5_realm option!\n"));
+ return EINVAL;
+ }
+
+ ret = krb5_service_init(ctx, bectx, "KRB5", krb5_servers, krb5_realm,
+ &ctx->service);
+ if (ret != EOK) {
+ DEBUG(0, ("Failed to init IPA failover service!\n"));
+ return ret;
+ }
+
+ ret = check_and_export_options(ctx->opts, bectx->domain);
+ if (ret != EOK) {
+ DEBUG(1, ("check_and_export_options failed.\n"));
+ goto fail;
+ }
+
+ sige = tevent_add_signal(bectx->ev, ctx, SIGCHLD, SA_SIGINFO,
+ child_sig_handler, NULL);
+ if (sige == NULL) {
+ DEBUG(1, ("tevent_add_signal failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ if (debug_to_file != 0) {
+ ret = open_debug_file_ex("krb5_child", &debug_filep);
+ if (ret != EOK) {
+ DEBUG(0, ("Error setting up logging (%d) [%s]\n",
+ ret, strerror(ret)));
+ goto fail;
+ }
+
+ ctx->child_debug_fd = fileno(debug_filep);
+ if (ctx->child_debug_fd == -1) {
+ DEBUG(0, ("fileno failed [%d][%s]\n", errno, strerror(errno)));
+ ret = errno;
+ goto fail;
+ }
+
+ v = fcntl(ctx->child_debug_fd, F_GETFD, 0);
+ fcntl(ctx->child_debug_fd, F_SETFD, v & ~FD_CLOEXEC);
+ }
+
+ *ops = &krb5_auth_ops;
+ *pvt_auth_data = ctx;
+ return EOK;
+
+fail:
+ talloc_free(ctx);
+ return ret;
+}
+
+int sssm_krb5_chpass_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_auth_data)
+{
+ return sssm_krb5_auth_init(bectx, ops, pvt_auth_data);
+}
diff --git a/src/providers/krb5/krb5_utils.c b/src/providers/krb5/krb5_utils.c
new file mode 100644
index 000000000..489030af8
--- /dev/null
+++ b/src/providers/krb5/krb5_utils.c
@@ -0,0 +1,145 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- Utilities
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 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 <string.h>
+#include <stdlib.h>
+
+#include "providers/krb5/krb5_utils.h"
+#include "providers/krb5/krb5_auth.h"
+#include "util/util.h"
+
+char *expand_ccname_template(TALLOC_CTX *mem_ctx, struct krb5child_req *kr,
+ const char *template)
+{
+ char *copy;
+ char *p;
+ char *n;
+ char *result = NULL;
+ const char *dummy;
+
+ if (template == NULL) {
+ DEBUG(1, ("Missing template.\n"));
+ return NULL;
+ }
+
+ copy = talloc_strdup(mem_ctx, template);
+ if (copy == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ return NULL;
+ }
+
+ result = talloc_strdup(mem_ctx, "");
+ if (result == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ return NULL;
+ }
+
+ p = copy;
+ while ( (n = strchr(p, '%')) != NULL) {
+ *n = '\0';
+ n++;
+ if ( *n == '\0' ) {
+ DEBUG(1, ("format error, single %% at the end of the template.\n"));
+ return NULL;
+ }
+
+ switch( *n ) {
+ case 'u':
+ if (kr->pd->user == NULL) {
+ DEBUG(1, ("Cannot expand user name template "
+ "because user name is empty.\n"));
+ return NULL;
+ }
+ result = talloc_asprintf_append(result, "%s%s", p,
+ kr->pd->user);
+ break;
+ case 'U':
+ if (kr->pd->pw_uid <= 0) {
+ DEBUG(1, ("Cannot expand uid template "
+ "because uid is invalid.\n"));
+ return NULL;
+ }
+ result = talloc_asprintf_append(result, "%s%d", p,
+ kr->pd->pw_uid);
+ break;
+ case 'p':
+ if (kr->pd->upn == NULL) {
+ DEBUG(1, ("Cannot expand user principle name template "
+ "because upn is empty.\n"));
+ return NULL;
+ }
+ result = talloc_asprintf_append(result, "%s%s", p, kr->pd->upn);
+ break;
+ case '%':
+ result = talloc_asprintf_append(result, "%s%%", p);
+ break;
+ case 'r':
+ dummy = dp_opt_get_string(kr->krb5_ctx->opts, KRB5_REALM);
+ if (dummy == NULL) {
+ DEBUG(1, ("Missing kerberos realm.\n"));
+ return NULL;
+ }
+ result = talloc_asprintf_append(result, "%s%s", p, dummy);
+ break;
+ case 'h':
+ if (kr->homedir == NULL) {
+ DEBUG(1, ("Cannot expand home directory template "
+ "because the path is not available.\n"));
+ return NULL;
+ }
+ result = talloc_asprintf_append(result, "%s%s", p, kr->homedir);
+ break;
+ case 'd':
+ dummy = dp_opt_get_string(kr->krb5_ctx->opts, KRB5_CCACHEDIR);
+ if (dummy == NULL) {
+ DEBUG(1, ("Missing credential cache directory.\n"));
+ return NULL;
+ }
+ result = talloc_asprintf_append(result, "%s%s", p, dummy);
+ break;
+ case 'P':
+ if (kr->pd->cli_pid == 0) {
+ DEBUG(1, ("Cannot expand PID template "
+ "because PID is not available.\n"));
+ return NULL;
+ }
+ result = talloc_asprintf_append(result, "%s%d", p,
+ kr->pd->cli_pid);
+ break;
+ default:
+ DEBUG(1, ("format error, unknown template [%%%c].\n", *n));
+ return NULL;
+ }
+
+ if (result == NULL) {
+ DEBUG(1, ("talloc_asprintf_append failed.\n"));
+ return NULL;
+ }
+
+ p = n + 1;
+ }
+
+ result = talloc_asprintf_append(result, "%s", p);
+
+ return result;
+}
diff --git a/src/providers/krb5/krb5_utils.h b/src/providers/krb5/krb5_utils.h
new file mode 100644
index 000000000..7637041a6
--- /dev/null
+++ b/src/providers/krb5/krb5_utils.h
@@ -0,0 +1,39 @@
+/*
+ SSSD
+
+ Kerberos Backend, header file for utilities
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 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 __KRB5_UTILS_H__
+#define __KRB5_UTILS_H__
+
+#include <talloc.h>
+
+#include "providers/krb5/krb5_auth.h"
+#include "providers/data_provider.h"
+
+char *expand_ccname_template(TALLOC_CTX *mem_ctx, struct krb5child_req *kr,
+ const char *template);
+
+errno_t become_user(uid_t uid, gid_t gid);
+
+#endif /* __KRB5_UTILS_H__ */
diff --git a/src/providers/ldap/ldap_auth.c b/src/providers/ldap/ldap_auth.c
new file mode 100644
index 000000000..cfe8adb97
--- /dev/null
+++ b/src/providers/ldap/ldap_auth.c
@@ -0,0 +1,1055 @@
+/*
+ SSSD
+
+ LDAP Backend Module
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2008 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/>.
+*/
+
+#ifdef WITH_MOZLDAP
+#define LDAP_OPT_SUCCESS LDAP_SUCCESS
+#define LDAP_TAG_EXOP_MODIFY_PASSWD_ID ((ber_tag_t) 0x80U)
+#define LDAP_TAG_EXOP_MODIFY_PASSWD_OLD ((ber_tag_t) 0x81U)
+#define LDAP_TAG_EXOP_MODIFY_PASSWD_NEW ((ber_tag_t) 0x82U)
+#endif
+
+#define _XOPEN_SOURCE 500 /* for strptime() */
+#include <time.h>
+#undef _XOPEN_SOURCE
+#include <errno.h>
+#include <sys/time.h>
+#include <strings.h>
+
+#include <shadow.h>
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "util/user_info_msg.h"
+#include "db/sysdb.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async.h"
+
+enum pwexpire {
+ PWEXPIRE_NONE = 0,
+ PWEXPIRE_LDAP_PASSWORD_POLICY,
+ PWEXPIRE_KERBEROS,
+ PWEXPIRE_SHADOW
+};
+
+static errno_t check_pwexpire_kerberos(const char *expire_date, time_t now,
+ enum sdap_result *result)
+{
+ char *end;
+ struct tm tm = {0, 0, 0, 0, 0, 0, 0, 0, 0};
+ time_t expire_time;
+
+ *result = SDAP_AUTH_FAILED;
+
+ end = strptime(expire_date, "%Y%m%d%H%M%SZ", &tm);
+ if (end == NULL) {
+ DEBUG(1, ("Kerberos expire date [%s] invalid.\n", expire_date));
+ return EINVAL;
+ }
+ if (*end != '\0') {
+ DEBUG(1, ("Kerberos expire date [%s] contains extra characters.\n",
+ expire_date));
+ return EINVAL;
+ }
+
+ expire_time = mktime(&tm);
+ if (expire_time == -1) {
+ DEBUG(1, ("mktime failed to convert [%s].\n", expire_date));
+ return EINVAL;
+ }
+
+ tzset();
+ expire_time -= timezone;
+ DEBUG(9, ("Time info: tzname[0] [%s] tzname[1] [%s] timezone [%d] "
+ "daylight [%d] now [%d] expire_time [%d].\n", tzname[0],
+ tzname[1], timezone, daylight, now, expire_time));
+
+ if (difftime(now, expire_time) > 0.0) {
+ DEBUG(4, ("Kerberos password expired.\n"));
+ *result = SDAP_AUTH_PW_EXPIRED;
+ } else {
+ *result = SDAP_AUTH_SUCCESS;
+ }
+
+ return EOK;
+}
+
+static errno_t check_pwexpire_shadow(struct spwd *spwd, time_t now,
+ enum sdap_result *result)
+{
+ long today;
+ long password_age;
+
+ if (spwd->sp_lstchg <= 0) {
+ DEBUG(4, ("Last change day is not set, new password needed.\n"));
+ *result = SDAP_AUTH_PW_EXPIRED;
+ return EOK;
+ }
+
+ today = (long) (now / (60 * 60 *24));
+ password_age = today - spwd->sp_lstchg;
+ if (password_age < 0) {
+ DEBUG(2, ("The last password change time is in the future!.\n"));
+ *result = SDAP_AUTH_SUCCESS;
+ return EOK;
+ }
+
+ if ((spwd->sp_expire != -1 && today > spwd->sp_expire) ||
+ (spwd->sp_max != -1 && spwd->sp_inact != -1 &&
+ password_age > spwd->sp_max + spwd->sp_inact))
+ {
+ DEBUG(4, ("Account expired.\n"));
+ *result = SDAP_ACCT_EXPIRED;
+ return EOK;
+ }
+
+ if (spwd->sp_max != -1 && password_age > spwd->sp_max) {
+ DEBUG(4, ("Password expired.\n"));
+ *result = SDAP_AUTH_PW_EXPIRED;
+ return EOK;
+ }
+
+/* TODO: evaluate spwd->min and spwd->warn */
+
+ *result = SDAP_AUTH_SUCCESS;
+ return EOK;
+}
+
+static errno_t string_to_shadowpw_days(const char *s, long *d)
+{
+ long l;
+ char *endptr;
+
+ if (s == NULL || *s == '\0') {
+ *d = -1;
+ return EOK;
+ }
+
+ errno = 0;
+ l = strtol(s, &endptr, 10);
+ if (errno != 0) {
+ DEBUG(1, ("strtol failed [%d][%s].\n", errno, strerror(errno)));
+ return errno;
+ }
+
+ if (*endptr != '\0') {
+ DEBUG(1, ("Input string [%s] is invalid.\n", s));
+ return EINVAL;
+ }
+
+ if (*d < -1) {
+ DEBUG(1, ("Input string contains not allowed negative value [%d].\n",
+ *d));
+ return EINVAL;
+ }
+
+ *d = l;
+
+ return EOK;
+}
+
+static errno_t find_password_expiration_attributes(TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ struct dp_option *opts,
+ enum pwexpire *type, void **data)
+{
+ const char *mark;
+ const char *val;
+ struct spwd *spwd;
+ const char *pwd_policy;
+ int ret;
+
+ *type = PWEXPIRE_NONE;
+ *data = NULL;
+
+ pwd_policy = dp_opt_get_string(opts, SDAP_PWD_POLICY);
+ if (pwd_policy == NULL) {
+ DEBUG(1, ("Missing password policy.\n"));
+ return EINVAL;
+ }
+
+ mark = ldb_msg_find_attr_as_string(msg, SYSDB_PWD_ATTRIBUTE, NULL);
+ if (mark != NULL) {
+ DEBUG(9, ("Found pwdAttribute, "
+ "assuming LDAP password policies are active.\n"));
+
+ *type = PWEXPIRE_LDAP_PASSWORD_POLICY;
+ return EOK;
+ }
+
+ if (strcasecmp(pwd_policy, PWD_POL_OPT_NONE) == 0) {
+ DEBUG(9, ("No password policy requested.\n"));
+ return EOK;
+ } else if (strcasecmp(pwd_policy, PWD_POL_OPT_MIT) == 0) {
+ mark = ldb_msg_find_attr_as_string(msg, SYSDB_KRBPW_LASTCHANGE, NULL);
+ if (mark != NULL) {
+ DEBUG(9, ("Found Kerberos password expiration attributes.\n"))
+ val = ldb_msg_find_attr_as_string(msg, SYSDB_KRBPW_EXPIRATION,
+ NULL);
+ if (val != NULL) {
+ *data = talloc_strdup(mem_ctx, val);
+ if (*data == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ return ENOMEM;
+ }
+ *type = PWEXPIRE_KERBEROS;
+
+ return EOK;
+ }
+ } else {
+ DEBUG(1, ("No Kerberos password expiration attributes found, "
+ "but MIT Kerberos password policy was requested.\n"));
+ return EINVAL;
+ }
+ } else if (strcasecmp(pwd_policy, PWD_POL_OPT_SHADOW) == 0) {
+ mark = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_LASTCHANGE, NULL);
+ if (mark != NULL) {
+ DEBUG(9, ("Found shadow password expiration attributes.\n"))
+ spwd = talloc_zero(mem_ctx, struct spwd);
+ if (spwd == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ return ENOMEM;
+ }
+
+ val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_LASTCHANGE, NULL);
+ ret = string_to_shadowpw_days(val, &spwd->sp_lstchg);
+ if (ret != EOK) goto shadow_fail;
+
+ val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_MIN, NULL);
+ ret = string_to_shadowpw_days(val, &spwd->sp_min);
+ if (ret != EOK) goto shadow_fail;
+
+ val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_MAX, NULL);
+ ret = string_to_shadowpw_days(val, &spwd->sp_max);
+ if (ret != EOK) goto shadow_fail;
+
+ val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_WARNING, NULL);
+ ret = string_to_shadowpw_days(val, &spwd->sp_warn);
+ if (ret != EOK) goto shadow_fail;
+
+ val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_INACTIVE, NULL);
+ ret = string_to_shadowpw_days(val, &spwd->sp_inact);
+ if (ret != EOK) goto shadow_fail;
+
+ val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_EXPIRE, NULL);
+ ret = string_to_shadowpw_days(val, &spwd->sp_expire);
+ if (ret != EOK) goto shadow_fail;
+
+ *data = spwd;
+ *type = PWEXPIRE_SHADOW;
+
+ return EOK;
+ } else {
+ DEBUG(1, ("No shadow password attributes found, "
+ "but shadow password policy was requested.\n"));
+ return EINVAL;
+ }
+ }
+
+ DEBUG(9, ("No password expiration attributes found.\n"));
+ return EOK;
+
+shadow_fail:
+ talloc_free(spwd);
+ return ret;
+}
+
+/* ==Get-User-DN========================================================== */
+
+struct get_user_dn_state {
+ struct tevent_context *ev;
+ struct sdap_auth_ctx *ctx;
+ struct sdap_handle *sh;
+
+ const char **attrs;
+ const char *name;
+
+ char *dn;
+ enum pwexpire pw_expire_type;
+ void *pw_expire_data;
+};
+
+static void get_user_dn_done(void *pvt, int err, struct ldb_result *res);
+
+struct tevent_req *get_user_dn_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_auth_ctx *ctx,
+ struct sdap_handle *sh,
+ const char *username)
+{
+ struct tevent_req *req;
+ struct get_user_dn_state *state;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct get_user_dn_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sh = sh;
+ state->name = username;
+
+ state->attrs = talloc_array(state, const char *, 11);
+ if (!state->attrs) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ state->attrs[0] = SYSDB_ORIG_DN;
+ state->attrs[1] = SYSDB_SHADOWPW_LASTCHANGE;
+ state->attrs[2] = SYSDB_SHADOWPW_MIN;
+ state->attrs[3] = SYSDB_SHADOWPW_MAX;
+ state->attrs[4] = SYSDB_SHADOWPW_WARNING;
+ state->attrs[5] = SYSDB_SHADOWPW_INACTIVE;
+ state->attrs[6] = SYSDB_SHADOWPW_EXPIRE;
+ state->attrs[7] = SYSDB_KRBPW_LASTCHANGE;
+ state->attrs[8] = SYSDB_KRBPW_EXPIRATION;
+ state->attrs[9] = SYSDB_PWD_ATTRIBUTE;
+ state->attrs[10] = NULL;
+
+ /* this sysdb call uses a sysdn operation, which means it will be
+ * schedule only after we return, no timer hack needed */
+ ret = sysdb_get_user_attr(state, state->ctx->be->sysdb,
+ state->ctx->be->domain, state->name,
+ state->attrs, get_user_dn_done, req);
+ if (ret) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void get_user_dn_done(void *pvt, int err, struct ldb_result *res)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct get_user_dn_state *state = tevent_req_data(req,
+ struct get_user_dn_state);
+ const char *dn;
+ int ret;
+
+ if (err != LDB_SUCCESS) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ switch (res->count) {
+ case 0:
+ /* FIXME: not in cache, needs a true search */
+ tevent_req_error(req, ENOENT);
+ break;
+
+ case 1:
+ dn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_ORIG_DN, NULL);
+ if (!dn) {
+ /* TODO: try to search ldap server ? */
+
+ /* FIXME: remove once we store originalDN on every call
+ * NOTE: this is wrong, works only with some DITs */
+ dn = talloc_asprintf(state, "%s=%s,%s",
+ state->ctx->opts->user_map[SDAP_AT_USER_NAME].name,
+ state->name,
+ dp_opt_get_string(state->ctx->opts->basic,
+ SDAP_USER_SEARCH_BASE));
+ if (!dn) {
+ tevent_req_error(req, ENOMEM);
+ break;
+ }
+ }
+
+ state->dn = talloc_strdup(state, dn);
+ if (!state->dn) {
+ tevent_req_error(req, ENOMEM);
+ break;
+ }
+
+ ret = find_password_expiration_attributes(state, res->msgs[0],
+ state->ctx->opts->basic,
+ &state->pw_expire_type,
+ &state->pw_expire_data);
+ if (ret != EOK) {
+ DEBUG(1, ("find_password_expiration_attributes failed.\n"));
+ tevent_req_error(req, ENOMEM);
+ break;
+ }
+
+ tevent_req_done(req);
+ break;
+
+ default:
+ DEBUG(1, ("A user search by name (%s) returned > 1 results!\n",
+ state->name));
+ tevent_req_error(req, EFAULT);
+ break;
+ }
+}
+
+static int get_user_dn_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx, char **dn,
+ enum pwexpire *pw_expire_type,
+ void **pw_expire_data)
+{
+ struct get_user_dn_state *state = tevent_req_data(req,
+ struct get_user_dn_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *dn = talloc_steal(memctx, state->dn);
+ if (!*dn) return ENOMEM;
+
+ /* state->pw_expire_data may be NULL */
+ *pw_expire_data = talloc_steal(memctx, state->pw_expire_data);
+
+ *pw_expire_type = state->pw_expire_type;
+
+ return EOK;
+}
+
+/* ==Authenticate-User==================================================== */
+
+struct auth_state {
+ struct tevent_context *ev;
+ struct sdap_auth_ctx *ctx;
+ const char *username;
+ struct dp_opt_blob password;
+
+ struct sdap_handle *sh;
+
+ enum sdap_result result;
+ char *dn;
+ enum pwexpire pw_expire_type;
+ void *pw_expire_data;
+
+ struct fo_server *srv;
+};
+
+static void auth_resolve_done(struct tevent_req *subreq);
+static void auth_connect_done(struct tevent_req *subreq);
+static void auth_get_user_dn_done(struct tevent_req *subreq);
+static void auth_bind_user_done(struct tevent_req *subreq);
+
+static struct tevent_req *auth_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_auth_ctx *ctx,
+ const char *username,
+ struct dp_opt_blob password)
+{
+ struct tevent_req *req, *subreq;
+ struct auth_state *state;
+
+ req = tevent_req_create(memctx, &state, struct auth_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->username = username;
+ state->password = password;
+ state->srv = NULL;
+
+ subreq = be_resolve_server_send(state, ev, ctx->be, ctx->service->name);
+ if (!subreq) goto fail;
+
+ tevent_req_set_callback(subreq, auth_resolve_done, req);
+
+ return req;
+
+fail:
+ talloc_zfree(req);
+ return NULL;
+}
+
+static void auth_resolve_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct auth_state *state = tevent_req_data(req,
+ struct auth_state);
+ int ret;
+
+ ret = be_resolve_server_recv(subreq, &state->srv);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_connect_send(state, state->ev, state->ctx->opts,
+ state->ctx->service->uri, true);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ tevent_req_set_callback(subreq, auth_connect_done, req);
+}
+
+static void auth_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct auth_state *state = tevent_req_data(req,
+ struct auth_state);
+ int ret;
+
+ ret = sdap_connect_recv(subreq, state, &state->sh);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (state->srv) {
+ /* mark this server as bad if connection failed */
+ fo_set_port_status(state->srv, PORT_NOT_WORKING);
+ }
+
+ tevent_req_error(req, ret);
+ return;
+ } else if (state->srv) {
+ fo_set_port_status(state->srv, PORT_WORKING);
+ }
+
+ subreq = get_user_dn_send(state, state->ev,
+ state->ctx, state->sh,
+ state->username);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ tevent_req_set_callback(subreq, auth_get_user_dn_done, req);
+}
+
+static void auth_get_user_dn_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct auth_state *state = tevent_req_data(req,
+ struct auth_state);
+ int ret;
+
+ ret = get_user_dn_recv(subreq, state, &state->dn, &state->pw_expire_type,
+ &state->pw_expire_data);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_auth_send(state, state->ev, state->sh,
+ NULL, NULL, state->dn,
+ "password", state->password);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ tevent_req_set_callback(subreq, auth_bind_user_done, req);
+}
+
+static void auth_bind_user_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct auth_state *state = tevent_req_data(req,
+ struct auth_state);
+ int ret;
+
+ ret = sdap_auth_recv(subreq, &state->result);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int auth_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx,
+ struct sdap_handle **sh,
+ enum sdap_result *result, char **dn,
+ enum pwexpire *pw_expire_type, void **pw_expire_data)
+{
+ struct auth_state *state = tevent_req_data(req, struct auth_state);
+ enum tevent_req_state tstate;
+ uint64_t err;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ switch (tstate) {
+ case TEVENT_REQ_USER_ERROR:
+ if (err == ETIMEDOUT) *result = SDAP_UNAVAIL;
+ else *result = SDAP_ERROR;
+ return err;
+ default:
+ *result = SDAP_ERROR;
+ return EIO;
+ }
+ }
+
+ if (sh != NULL) {
+ *sh = talloc_steal(memctx, state->sh);
+ if (*sh == NULL) return ENOMEM;
+ }
+
+ if (dn != NULL) {
+ *dn = talloc_steal(memctx, state->dn);
+ if (*dn == NULL) return ENOMEM;
+ }
+
+ if (pw_expire_data != NULL) {
+ *pw_expire_data = talloc_steal(memctx, state->pw_expire_data);
+ }
+
+ *pw_expire_type = state->pw_expire_type;
+
+ *result = state->result;
+ return EOK;
+}
+
+/* ==Perform-Password-Change===================== */
+
+struct sdap_pam_chpass_state {
+ struct be_req *breq;
+ struct pam_data *pd;
+ const char *username;
+ char *dn;
+ char *password;
+ char *new_password;
+ struct sdap_handle *sh;
+};
+
+static void sdap_auth4chpass_done(struct tevent_req *req);
+static void sdap_pam_chpass_done(struct tevent_req *req);
+static void sdap_pam_auth_reply(struct be_req *breq, int dp_err, int result);
+
+void sdap_pam_chpass_handler(struct be_req *breq)
+{
+ struct sdap_pam_chpass_state *state;
+ struct sdap_auth_ctx *ctx;
+ struct tevent_req *subreq;
+ struct pam_data *pd;
+ struct dp_opt_blob authtok;
+ int dp_err = DP_ERR_FATAL;
+
+ ctx = talloc_get_type(breq->be_ctx->bet_info[BET_CHPASS].pvt_bet_data,
+ struct sdap_auth_ctx);
+ pd = talloc_get_type(breq->req_data, struct pam_data);
+
+ if (be_is_offline(ctx->be)) {
+ DEBUG(4, ("Backend is marked offline, retry later!\n"));
+ pd->pam_status = PAM_AUTHINFO_UNAVAIL;
+ dp_err = DP_ERR_OFFLINE;
+ goto done;
+ }
+
+ DEBUG(2, ("starting password change request for user [%s].\n", pd->user));
+
+ pd->pam_status = PAM_SYSTEM_ERR;
+
+ if (pd->cmd != SSS_PAM_CHAUTHTOK && pd->cmd != SSS_PAM_CHAUTHTOK_PRELIM) {
+ DEBUG(2, ("chpass target was called by wrong pam command.\n"));
+ goto done;
+ }
+
+ state = talloc_zero(breq, struct sdap_pam_chpass_state);
+ if (!state) goto done;
+
+ state->breq = breq;
+ state->pd = pd;
+ state->username = pd->user;
+ state->password = talloc_strndup(state,
+ (char *)pd->authtok, pd->authtok_size);
+ if (!state->password) goto done;
+ talloc_set_destructor((TALLOC_CTX *)state->password,
+ password_destructor);
+
+ if (pd->cmd == SSS_PAM_CHAUTHTOK) {
+ state->new_password = talloc_strndup(state,
+ (char *)pd->newauthtok,
+ pd->newauthtok_size);
+ if (!state->new_password) goto done;
+ talloc_set_destructor((TALLOC_CTX *)state->new_password,
+ password_destructor);
+ }
+
+ authtok.data = (uint8_t *)state->password;
+ authtok.length = strlen(state->password);
+ subreq = auth_send(breq, breq->be_ctx->ev,
+ ctx, state->username, authtok);
+ if (!subreq) goto done;
+
+ tevent_req_set_callback(subreq, sdap_auth4chpass_done, state);
+ return;
+
+done:
+ sdap_pam_auth_reply(breq, dp_err, pd->pam_status);
+}
+
+static void sdap_auth4chpass_done(struct tevent_req *req)
+{
+ struct sdap_pam_chpass_state *state =
+ tevent_req_callback_data(req, struct sdap_pam_chpass_state);
+ struct tevent_req *subreq;
+ enum sdap_result result;
+ enum pwexpire pw_expire_type;
+ void *pw_expire_data;
+ int dp_err = DP_ERR_FATAL;
+ int ret;
+
+ ret = auth_recv(req, state, &state->sh,
+ &result, &state->dn,
+ &pw_expire_type, &pw_expire_data);
+ talloc_zfree(req);
+ if (ret) {
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+
+ if (result == SDAP_AUTH_SUCCESS &&
+ state->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+ DEBUG(9, ("Initial authentication for change password operation "
+ "successful.\n"));
+ state->pd->pam_status = PAM_SUCCESS;
+ goto done;
+ }
+
+ if (result == SDAP_AUTH_SUCCESS) {
+ switch (pw_expire_type) {
+ case PWEXPIRE_SHADOW:
+ ret = check_pwexpire_shadow(pw_expire_data, time(NULL),
+ &result);
+ if (ret != EOK) {
+ DEBUG(1, ("check_pwexpire_shadow failed.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ break;
+ case PWEXPIRE_KERBEROS:
+ ret = check_pwexpire_kerberos(pw_expire_data, time(NULL),
+ &result);
+ if (ret != EOK) {
+ DEBUG(1, ("check_pwexpire_kerberos failed.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+
+ if (result == SDAP_AUTH_PW_EXPIRED) {
+ DEBUG(1, ("LDAP provider cannot change kerberos "
+ "passwords.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ break;
+ case PWEXPIRE_LDAP_PASSWORD_POLICY:
+ case PWEXPIRE_NONE:
+ break;
+ default:
+ DEBUG(1, ("Unknow pasword expiration type.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ }
+
+ switch (result) {
+ case SDAP_AUTH_SUCCESS:
+ case SDAP_AUTH_PW_EXPIRED:
+ DEBUG(7, ("user [%s] successfully authenticated.\n", state->dn));
+ if (pw_expire_type == PWEXPIRE_SHADOW) {
+/* TODO: implement async ldap modify request */
+ DEBUG(1, ("Changing shadow password attributes not implemented.\n"));
+ state->pd->pam_status = PAM_MODULE_UNKNOWN;
+ goto done;
+ } else {
+ subreq = sdap_exop_modify_passwd_send(state,
+ state->breq->be_ctx->ev,
+ state->sh,
+ state->dn,
+ state->password,
+ state->new_password);
+
+ if (!subreq) {
+ DEBUG(2, ("Failed to change password for %s\n", state->username));
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, sdap_pam_chpass_done, state);
+ return;
+ }
+ break;
+ case SDAP_AUTH_FAILED:
+ state->pd->pam_status = PAM_AUTH_ERR;
+ break;
+ default:
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ }
+
+done:
+ sdap_pam_auth_reply(state->breq, dp_err, state->pd->pam_status);
+}
+
+static void sdap_pam_chpass_done(struct tevent_req *req)
+{
+ struct sdap_pam_chpass_state *state =
+ tevent_req_callback_data(req, struct sdap_pam_chpass_state);
+ enum sdap_result result;
+ int dp_err = DP_ERR_FATAL;
+ int ret;
+ char *user_error_message = NULL;
+ size_t msg_len;
+ uint8_t *msg;
+
+ ret = sdap_exop_modify_passwd_recv(req, state, &result, &user_error_message);
+ talloc_zfree(req);
+ if (ret) {
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+
+ switch (result) {
+ case SDAP_SUCCESS:
+ state->pd->pam_status = PAM_SUCCESS;
+ dp_err = DP_ERR_OK;
+ break;
+ default:
+ state->pd->pam_status = PAM_AUTHTOK_ERR;
+ if (user_error_message != NULL) {
+ ret = pack_user_info_chpass_error(state->pd, user_error_message,
+ &msg_len, &msg);
+ if (ret != EOK) {
+ DEBUG(1, ("pack_user_info_chpass_error failed.\n"));
+ } else {
+ ret = pam_add_response(state->pd, SSS_PAM_USER_INFO, msg_len,
+ msg);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ }
+ }
+ }
+ }
+
+done:
+ sdap_pam_auth_reply(state->breq, dp_err, state->pd->pam_status);
+}
+/* ==Perform-User-Authentication-and-Password-Caching===================== */
+
+struct sdap_pam_auth_state {
+ struct be_req *breq;
+ struct pam_data *pd;
+ const char *username;
+ struct dp_opt_blob password;
+};
+
+static void sdap_pam_auth_done(struct tevent_req *req);
+static void sdap_password_cache_done(struct tevent_req *req);
+
+void sdap_pam_auth_handler(struct be_req *breq)
+{
+ struct sdap_pam_auth_state *state;
+ struct sdap_auth_ctx *ctx;
+ struct tevent_req *subreq;
+ struct pam_data *pd;
+ int dp_err = DP_ERR_FATAL;
+
+ ctx = talloc_get_type(breq->be_ctx->bet_info[BET_AUTH].pvt_bet_data,
+ struct sdap_auth_ctx);
+ pd = talloc_get_type(breq->req_data, struct pam_data);
+
+ if (be_is_offline(ctx->be)) {
+ DEBUG(4, ("Backend is marked offline, retry later!\n"));
+ pd->pam_status = PAM_AUTHINFO_UNAVAIL;
+ dp_err = DP_ERR_OFFLINE;
+ goto done;
+ }
+
+ pd->pam_status = PAM_SYSTEM_ERR;
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+
+ state = talloc_zero(breq, struct sdap_pam_auth_state);
+ if (!state) goto done;
+
+ state->breq = breq;
+ state->pd = pd;
+ state->username = pd->user;
+ state->password.data = pd->authtok;
+ state->password.length = pd->authtok_size;
+
+ subreq = auth_send(breq, breq->be_ctx->ev, ctx,
+ state->username, state->password);
+ if (!subreq) goto done;
+
+ tevent_req_set_callback(subreq, sdap_pam_auth_done, state);
+ return;
+
+ case SSS_PAM_CHAUTHTOK:
+ break;
+
+ case SSS_PAM_ACCT_MGMT:
+ case SSS_PAM_SETCRED:
+ case SSS_PAM_OPEN_SESSION:
+ case SSS_PAM_CLOSE_SESSION:
+ pd->pam_status = PAM_SUCCESS;
+ dp_err = DP_ERR_OK;
+ break;
+ default:
+ pd->pam_status = PAM_MODULE_UNKNOWN;
+ dp_err = DP_ERR_OK;
+ }
+
+done:
+ sdap_pam_auth_reply(breq, dp_err, pd->pam_status);
+}
+
+static void sdap_pam_auth_done(struct tevent_req *req)
+{
+ struct sdap_pam_auth_state *state =
+ tevent_req_callback_data(req, struct sdap_pam_auth_state);
+ struct tevent_req *subreq;
+ enum sdap_result result;
+ enum pwexpire pw_expire_type;
+ void *pw_expire_data;
+ int dp_err = DP_ERR_OK;
+ int ret;
+
+ ret = auth_recv(req, state, NULL,
+ &result, NULL,
+ &pw_expire_type, &pw_expire_data);
+ talloc_zfree(req);
+ if (ret) {
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ dp_err = DP_ERR_FATAL;
+ goto done;
+ }
+
+ if (result == SDAP_AUTH_SUCCESS) {
+ switch (pw_expire_type) {
+ case PWEXPIRE_SHADOW:
+ ret = check_pwexpire_shadow(pw_expire_data, time(NULL),
+ &result);
+ if (ret != EOK) {
+ DEBUG(1, ("check_pwexpire_shadow failed.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ break;
+ case PWEXPIRE_KERBEROS:
+ ret = check_pwexpire_kerberos(pw_expire_data, time(NULL),
+ &result);
+ if (ret != EOK) {
+ DEBUG(1, ("check_pwexpire_kerberos failed.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ break;
+ case PWEXPIRE_LDAP_PASSWORD_POLICY:
+ case PWEXPIRE_NONE:
+ break;
+ default:
+ DEBUG(1, ("Unknow pasword expiration type.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ goto done;
+ }
+ }
+
+ switch (result) {
+ case SDAP_AUTH_SUCCESS:
+ state->pd->pam_status = PAM_SUCCESS;
+ break;
+ case SDAP_AUTH_FAILED:
+ state->pd->pam_status = PAM_PERM_DENIED;
+ break;
+ case SDAP_UNAVAIL:
+ state->pd->pam_status = PAM_AUTHINFO_UNAVAIL;
+ break;
+ case SDAP_ACCT_EXPIRED:
+ state->pd->pam_status = PAM_ACCT_EXPIRED;
+ break;
+ case SDAP_AUTH_PW_EXPIRED:
+ state->pd->pam_status = PAM_AUTHTOK_EXPIRED;
+ break;
+ default:
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ dp_err = DP_ERR_FATAL;
+ }
+
+ if (result == SDAP_UNAVAIL) {
+ be_mark_offline(state->breq->be_ctx);
+ dp_err = DP_ERR_OFFLINE;
+ goto done;
+ }
+
+ if (result == SDAP_AUTH_SUCCESS &&
+ state->breq->be_ctx->domain->cache_credentials) {
+
+ char *password = talloc_strndup(state, (char *)
+ state->password.data,
+ state->password.length);
+ /* password caching failures are not fatal errors */
+ if (!password) {
+ DEBUG(2, ("Failed to cache password for %s\n", state->username));
+ goto done;
+ }
+ talloc_set_destructor((TALLOC_CTX *)password, password_destructor);
+
+ subreq = sysdb_cache_password_send(state,
+ state->breq->be_ctx->ev,
+ state->breq->be_ctx->sysdb,
+ NULL,
+ state->breq->be_ctx->domain,
+ state->username, password);
+
+ /* password caching failures are not fatal errors */
+ if (!subreq) {
+ DEBUG(2, ("Failed to cache password for %s\n", state->username));
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, sdap_password_cache_done, state);
+ return;
+ }
+
+done:
+ sdap_pam_auth_reply(state->breq, dp_err, state->pd->pam_status);
+}
+
+static void sdap_password_cache_done(struct tevent_req *subreq)
+{
+ struct sdap_pam_auth_state *state = tevent_req_callback_data(subreq,
+ struct sdap_pam_auth_state);
+ int ret;
+
+ ret = sysdb_cache_password_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ /* password caching failures are not fatal errors */
+ DEBUG(2, ("Failed to cache password for %s\n", state->username));
+ } else {
+ DEBUG(4, ("Password successfully cached for %s\n", state->username));
+ }
+
+ sdap_pam_auth_reply(state->breq, DP_ERR_OK, state->pd->pam_status);
+}
+
+static void sdap_pam_auth_reply(struct be_req *req, int dp_err, int result)
+{
+ req->fn(req, dp_err, result, NULL);
+}
+
diff --git a/src/providers/ldap/ldap_child.c b/src/providers/ldap/ldap_child.c
new file mode 100644
index 000000000..0d34be2ca
--- /dev/null
+++ b/src/providers/ldap/ldap_child.c
@@ -0,0 +1,429 @@
+/*
+ SSSD
+
+ LDAP Backend Module -- prime ccache with TGT in a child process
+
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2009 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 <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <popt.h>
+
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "util/sss_krb5.h"
+#include "providers/child_common.h"
+#include "providers/dp_backend.h"
+
+static krb5_context krb5_error_ctx;
+
+struct input_buffer {
+ const char *realm_str;
+ const char *princ_str;
+ const char *keytab_name;
+};
+
+static errno_t unpack_buffer(uint8_t *buf, size_t size,
+ struct input_buffer *ibuf)
+{
+ size_t p = 0;
+ uint32_t len;
+
+ DEBUG(7, ("total buffer size: %d\n", size));
+
+ /* realm_str size and length */
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+
+ DEBUG(7, ("realm_str size: %d\n", len));
+ if (len) {
+ if ((p + len ) > size) return EINVAL;
+ ibuf->realm_str = talloc_strndup(ibuf, (char *)(buf + p), len);
+ DEBUG(7, ("got realm_str: %s\n", ibuf->realm_str));
+ if (ibuf->realm_str == NULL) return ENOMEM;
+ p += len;
+ }
+
+ /* princ_str size and length */
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+
+ DEBUG(7, ("princ_str size: %d\n", len));
+ if (len) {
+ if ((p + len ) > size) return EINVAL;
+ ibuf->princ_str = talloc_strndup(ibuf, (char *)(buf + p), len);
+ DEBUG(7, ("got princ_str: %s\n", ibuf->princ_str));
+ if (ibuf->princ_str == NULL) return ENOMEM;
+ p += len;
+ }
+
+ /* keytab_name size and length */
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+
+ DEBUG(7, ("keytab_name size: %d\n", len));
+ if (len) {
+ if ((p + len ) > size) return EINVAL;
+ ibuf->keytab_name = talloc_strndup(ibuf, (char *)(buf + p), len);
+ DEBUG(7, ("got keytab_name: %s\n", ibuf->keytab_name));
+ if (ibuf->keytab_name == NULL) return ENOMEM;
+ p += len;
+ }
+
+ return EOK;
+}
+
+static int pack_buffer(struct response *r, int result, const char *msg)
+{
+ int len;
+ int p = 0;
+
+ len = strlen(msg);
+ r->size = 2 * sizeof(uint32_t) + len;
+
+ /* result */
+ COPY_UINT32_VALUE(&r->buf[p], result, p);
+
+ /* message size */
+ COPY_UINT32_VALUE(&r->buf[p], len, p);
+
+ /* message itself */
+ COPY_MEM(&r->buf[p], msg, p, len);
+
+ return EOK;
+}
+
+static int ldap_child_get_tgt_sync(TALLOC_CTX *memctx,
+ const char *realm_str,
+ const char *princ_str,
+ const char *keytab_name,
+ const char **ccname_out)
+{
+ char *ccname;
+ char *realm_name = NULL;
+ char *full_princ = NULL;
+ krb5_context context = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_principal kprinc;
+ krb5_creds my_creds;
+ krb5_get_init_creds_opt options;
+ krb5_error_code krberr;
+ int ret;
+
+ krberr = krb5_init_context(&context);
+ if (krberr) {
+ DEBUG(2, ("Failed to init kerberos context\n"));
+ return EFAULT;
+ }
+
+ if (!realm_str) {
+ krberr = krb5_get_default_realm(context, &realm_name);
+ if (krberr) {
+ DEBUG(2, ("Failed to get default realm name: %s\n",
+ sss_krb5_get_error_message(context, krberr)));
+ ret = EFAULT;
+ goto done;
+ }
+ } else {
+ realm_name = talloc_strdup(memctx, realm_str);
+ if (!realm_name) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ if (princ_str) {
+ if (!strchr(princ_str, '@')) {
+ full_princ = talloc_asprintf(memctx, "%s@%s",
+ princ_str, realm_name);
+ } else {
+ full_princ = talloc_strdup(memctx, princ_str);
+ }
+ } else {
+ char hostname[512];
+
+ ret = gethostname(hostname, 511);
+ if (ret == -1) {
+ ret = errno;
+ goto done;
+ }
+ hostname[511] = '\0';
+
+ full_princ = talloc_asprintf(memctx, "host/%s@%s",
+ hostname, realm_name);
+ }
+ if (!full_princ) {
+ ret = ENOMEM;
+ goto done;
+ }
+ DEBUG(4, ("Principal name is: [%s]\n", full_princ));
+
+ krberr = krb5_parse_name(context, full_princ, &kprinc);
+ if (krberr) {
+ DEBUG(2, ("Unable to build principal: %s\n",
+ sss_krb5_get_error_message(context, krberr)));
+ ret = EFAULT;
+ goto done;
+ }
+
+ if (keytab_name) {
+ krberr = krb5_kt_resolve(context, keytab_name, &keytab);
+ } else {
+ krberr = krb5_kt_default(context, &keytab);
+ }
+ if (krberr) {
+ DEBUG(2, ("Failed to read keytab file: %s\n",
+ sss_krb5_get_error_message(context, krberr)));
+ ret = EFAULT;
+ goto done;
+ }
+
+ ccname = talloc_asprintf(memctx, "FILE:%s/ccache_%s", DB_PATH, realm_name);
+ if (!ccname) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ krberr = krb5_cc_resolve(context, ccname, &ccache);
+ if (krberr) {
+ DEBUG(2, ("Failed to set cache name: %s\n",
+ sss_krb5_get_error_message(context, krberr)));
+ ret = EFAULT;
+ goto done;
+ }
+
+ memset(&my_creds, 0, sizeof(my_creds));
+ memset(&options, 0, sizeof(options));
+
+ krb5_get_init_creds_opt_set_address_list(&options, NULL);
+ krb5_get_init_creds_opt_set_forwardable(&options, 0);
+ krb5_get_init_creds_opt_set_proxiable(&options, 0);
+ /* set a very short lifetime, we don't keep the ticket around */
+ krb5_get_init_creds_opt_set_tkt_life(&options, 300);
+
+ krberr = krb5_get_init_creds_keytab(context, &my_creds, kprinc,
+ keytab, 0, NULL, &options);
+
+ if (krberr) {
+ DEBUG(2, ("Failed to init credentials: %s\n",
+ sss_krb5_get_error_message(context, krberr)));
+ ret = EFAULT;
+ goto done;
+ }
+
+ krberr = krb5_cc_initialize(context, ccache, kprinc);
+ if (krberr) {
+ DEBUG(2, ("Failed to init ccache: %s\n",
+ sss_krb5_get_error_message(context, krberr)));
+ ret = EFAULT;
+ goto done;
+ }
+
+ krberr = krb5_cc_store_cred(context, ccache, &my_creds);
+ if (krberr) {
+ DEBUG(2, ("Failed to store creds: %s\n",
+ sss_krb5_get_error_message(context, krberr)));
+ ret = EFAULT;
+ goto done;
+ }
+
+ ret = EOK;
+ *ccname_out = ccname;
+
+done:
+ if (keytab) krb5_kt_close(context, keytab);
+ if (context) krb5_free_context(context);
+ return ret;
+}
+
+static int prepare_response(TALLOC_CTX *mem_ctx,
+ const char *ccname,
+ krb5_error_code kerr,
+ struct response **rsp)
+{
+ int ret;
+ struct response *r = NULL;
+ const char *krb5_msg = NULL;
+
+ r = talloc_zero(mem_ctx, struct response);
+ if (!r) return ENOMEM;
+
+ r->buf = talloc_size(mem_ctx, MAX_CHILD_MSG_SIZE);
+ if (r->buf == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ return ENOMEM;
+ }
+ r->max_size = MAX_CHILD_MSG_SIZE;
+ r->size = 0;
+
+ if (kerr == 0) {
+ ret = pack_buffer(r, EOK, ccname);
+ } else {
+ krb5_msg = sss_krb5_get_error_message(krb5_error_ctx, kerr);
+ if (krb5_msg == NULL) {
+ DEBUG(1, ("sss_krb5_get_error_message failed.\n"));
+ return ENOMEM;
+ }
+
+ ret = pack_buffer(r, EFAULT, krb5_msg);
+ sss_krb5_free_error_message(krb5_error_ctx, krb5_msg);
+ }
+
+ if (ret != EOK) {
+ DEBUG(1, ("pack_buffer failed\n"));
+ return ret;
+ }
+
+ *rsp = r;
+ return EOK;
+}
+
+int main(int argc, const char *argv[])
+{
+ int ret;
+ int kerr;
+ int opt;
+ int debug_fd = -1;
+ poptContext pc;
+ TALLOC_CTX *main_ctx;
+ uint8_t *buf = NULL;
+ ssize_t len = 0;
+ const char *ccname = NULL;
+ struct input_buffer *ibuf = NULL;
+ struct response *resp = NULL;
+ size_t written;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {"debug-level", 'd', POPT_ARG_INT, &debug_level, 0,
+ _("Debug level"), NULL},
+ {"debug-timestamps", 0, POPT_ARG_INT, &debug_timestamps, 0,
+ _("Add debug timestamps"), NULL},
+ {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0,
+ _("An open file descriptor for the debug logs"), NULL},
+ POPT_TABLEEND
+ };
+
+ 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);
+ _exit(-1);
+ }
+ }
+
+ poptFreeContext(pc);
+
+ DEBUG(7, ("ldap_child started.\n"));
+
+ main_ctx = talloc_new(NULL);
+ if (main_ctx == NULL) {
+ DEBUG(1, ("talloc_new failed.\n"));
+ _exit(-1);
+ }
+
+ debug_prg_name = talloc_asprintf(main_ctx, "[sssd[ldap_child[%d]]]", getpid());
+
+ if (debug_fd != -1) {
+ ret = set_debug_file_from_fd(debug_fd);
+ if (ret != EOK) {
+ DEBUG(1, ("set_debug_file_from_fd failed.\n"));
+ }
+ }
+
+ buf = talloc_size(main_ctx, sizeof(uint8_t)*IN_BUF_SIZE);
+ if (buf == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ goto fail;
+ }
+
+ ibuf = talloc_zero(main_ctx, struct input_buffer);
+ if (ibuf == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ goto fail;
+ }
+
+ while ((ret = read(STDIN_FILENO, buf + len, IN_BUF_SIZE - len)) != 0) {
+ if (ret == -1) {
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ }
+ DEBUG(1, ("read failed [%d][%s].\n", errno, strerror(errno)));
+ goto fail;
+ } else if (ret > 0) {
+ len += ret;
+ if (len > IN_BUF_SIZE) {
+ DEBUG(1, ("read too much, this should never happen.\n"));
+ goto fail;
+ }
+ continue;
+ } else {
+ DEBUG(1, ("unexpected return code of read [%d].\n", ret));
+ goto fail;
+ }
+ }
+ close(STDIN_FILENO);
+
+ ret = unpack_buffer(buf, len, ibuf);
+ if (ret != EOK) {
+ DEBUG(1, ("unpack_buffer failed.[%d][%s].\n", ret, strerror(ret)));
+ goto fail;
+ }
+
+ kerr = ldap_child_get_tgt_sync(main_ctx,
+ ibuf->realm_str, ibuf->princ_str,
+ ibuf->keytab_name, &ccname);
+ if (kerr != EOK) {
+ DEBUG(1, ("ldap_child_get_tgt_sync failed.\n"));
+ /* Do not return, must report failure */
+ }
+
+ ret = prepare_response(main_ctx, ccname, kerr, &resp);
+ if (ret != EOK) {
+ DEBUG(1, ("prepare_response failed. [%d][%s].\n", ret, strerror(ret)));
+ return ENOMEM;
+ }
+
+ written = 0;
+ while (written < resp->size) {
+ ret = write(STDOUT_FILENO, resp->buf + written, resp->size - written);
+ if (ret == -1) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ }
+ ret = errno;
+ DEBUG(1, ("write failed [%d][%s].\n", ret, strerror(ret)));
+ return ret;
+ }
+ written += ret;
+ }
+
+ close(STDOUT_FILENO);
+ talloc_free(main_ctx);
+ _exit(0);
+
+fail:
+ close(STDOUT_FILENO);
+ talloc_free(main_ctx);
+ _exit(-1);
+}
diff --git a/src/providers/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c
new file mode 100644
index 000000000..bd4294f83
--- /dev/null
+++ b/src/providers/ldap/ldap_common.c
@@ -0,0 +1,589 @@
+/*
+ SSSD
+
+ LDAP Provider Common Functions
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2008-2009 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/ldap/ldap_common.h"
+#include "providers/fail_over.h"
+
+#include "util/sss_krb5.h"
+
+/* a fd the child process would log into */
+int ldap_child_debug_fd = -1;
+
+struct dp_option default_basic_opts[] = {
+ { "ldap_uri", DP_OPT_STRING, { "ldap://localhost" }, NULL_STRING },
+ { "ldap_search_base", DP_OPT_STRING, { "dc=example,dc=com" }, NULL_STRING },
+ { "ldap_default_bind_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_default_authtok_type", DP_OPT_STRING, NULL_STRING, NULL_STRING},
+ { "ldap_default_authtok", DP_OPT_BLOB, NULL_BLOB, NULL_BLOB },
+ { "ldap_search_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER },
+ { "ldap_network_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER },
+ { "ldap_opt_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER },
+ { "ldap_tls_reqcert", DP_OPT_STRING, { "hard" }, NULL_STRING },
+ { "ldap_user_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_user_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
+ { "ldap_user_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_group_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
+ { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_schema", DP_OPT_STRING, { "rfc2307" }, NULL_STRING },
+ { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER },
+ { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "ldap_enumeration_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER },
+ { "ldap_purge_cache_timeout", DP_OPT_NUMBER, { .number = 3600 }, NULL_NUMBER },
+ { "entry_cache_timoeut", DP_OPT_NUMBER, { .number = 1800 }, NULL_NUMBER },
+ { "ldap_tls_cacert", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_tls_cacertdir", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_id_use_start_tls", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+ { "ldap_sasl_mech", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_sasl_authid", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_krb5_keytab", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_krb5_init_creds", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+ /* use the same parm name as the krb5 module so we set it only once */
+ { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+ { "ldap_pwd_policy", DP_OPT_STRING, { "none" } , NULL_STRING },
+ { "ldap_referrals", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE }
+};
+
+struct sdap_attr_map generic_attr_map[] = {
+ { "ldap_entry_usn", NULL, SYSDB_USN, NULL },
+ { "ldap_rootdse_last_usn", NULL, SYSDB_USN, NULL }
+};
+
+struct sdap_attr_map gen_ipa_attr_map[] = {
+ { "ldap_entry_usn", "entryUSN", SYSDB_USN, NULL },
+ { "ldap_rootdse_last_usn", "lastUSN", SYSDB_HIGH_USN, NULL }
+};
+
+struct sdap_attr_map gen_ad_attr_map[] = {
+ { "ldap_entry_usn", "uSNChanged", SYSDB_USN, NULL },
+ { "ldap_rootdse_last_usn", "highestCommittedUSN", SYSDB_HIGH_USN, NULL }
+};
+
+struct sdap_attr_map rfc2307_user_map[] = {
+ { "ldap_user_object_class", "posixAccount", SYSDB_USER_CLASS, NULL },
+ { "ldap_user_name", "uid", SYSDB_NAME, NULL },
+ { "ldap_user_pwd", "userPassword", SYSDB_PWD, NULL },
+ { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL },
+ { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL },
+ { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL },
+ { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL },
+ { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL },
+ { "ldap_user_principal", "krbPrincipalName", SYSDB_UPN, NULL },
+ { "ldap_user_fullname", "cn", SYSDB_FULLNAME, NULL },
+ { "ldap_user_member_of", NULL, SYSDB_MEMBEROF, NULL },
+ { "ldap_user_uuid", NULL, SYSDB_UUID, NULL },
+ { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL },
+ { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL },
+ { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL },
+ { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL },
+ { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL },
+ { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL },
+ { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL },
+ { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL },
+ { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL },
+ { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL },
+ { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL }
+};
+
+struct sdap_attr_map rfc2307_group_map[] = {
+ { "ldap_group_object_class", "posixGroup", SYSDB_GROUP_CLASS, NULL },
+ { "ldap_group_name", "cn", SYSDB_NAME, NULL },
+ { "ldap_group_pwd", "userPassword", SYSDB_PWD, NULL },
+ { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL },
+ { "ldap_group_member", "memberuid", SYSDB_MEMBER, NULL },
+ { "ldap_group_uuid", NULL, SYSDB_UUID, NULL },
+ { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }
+};
+
+struct sdap_attr_map rfc2307bis_user_map[] = {
+ { "ldap_user_object_class", "posixAccount", SYSDB_USER_CLASS, NULL },
+ { "ldap_user_name", "uid", SYSDB_NAME, NULL },
+ { "ldap_user_pwd", "userPassword", SYSDB_PWD, NULL },
+ { "ldap_user_uid_number", "uidNumber", SYSDB_UIDNUM, NULL },
+ { "ldap_user_gid_number", "gidNumber", SYSDB_GIDNUM, NULL },
+ { "ldap_user_gecos", "gecos", SYSDB_GECOS, NULL },
+ { "ldap_user_home_directory", "homeDirectory", SYSDB_HOMEDIR, NULL },
+ { "ldap_user_shell", "loginShell", SYSDB_SHELL, NULL },
+ { "ldap_user_principal", "krbPrincipalName", SYSDB_UPN, NULL },
+ { "ldap_user_fullname", "cn", SYSDB_FULLNAME, NULL },
+ { "ldap_user_member_of", "memberOf", SYSDB_MEMBEROF, NULL },
+ /* FIXME: this is 389ds specific */
+ { "ldap_user_uuid", "nsUniqueId", SYSDB_UUID, NULL },
+ { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL },
+ { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL },
+ { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL },
+ { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL },
+ { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL },
+ { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL },
+ { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL },
+ { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL },
+ { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL },
+ { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL },
+ { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL }
+};
+
+struct sdap_attr_map rfc2307bis_group_map[] = {
+ { "ldap_group_object_class", "posixGroup", SYSDB_GROUP_CLASS, NULL },
+ { "ldap_group_name", "cn", SYSDB_NAME, NULL },
+ { "ldap_group_pwd", "userPassword", SYSDB_PWD, NULL },
+ { "ldap_group_gid_number", "gidNumber", SYSDB_GIDNUM, NULL },
+ { "ldap_group_member", "member", SYSDB_MEMBER, NULL },
+ /* FIXME: this is 389ds specific */
+ { "ldap_group_uuid", "nsUniqueId", SYSDB_UUID, NULL },
+ { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }
+};
+
+int ldap_get_options(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sdap_options **_opts)
+{
+ struct sdap_attr_map *default_attr_map;
+ struct sdap_attr_map *default_user_map;
+ struct sdap_attr_map *default_group_map;
+ struct sdap_options *opts;
+ char *schema;
+ const char *pwd_policy;
+ int ret;
+
+ opts = talloc_zero(memctx, struct sdap_options);
+ if (!opts) return ENOMEM;
+
+ ret = dp_get_options(opts, cdb, conf_path,
+ default_basic_opts,
+ SDAP_OPTS_BASIC,
+ &opts->basic);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ /* set user/group search bases if they are not */
+ if (NULL == dp_opt_get_string(opts->basic, SDAP_USER_SEARCH_BASE)) {
+ ret = dp_opt_set_string(opts->basic, SDAP_USER_SEARCH_BASE,
+ dp_opt_get_string(opts->basic,
+ SDAP_SEARCH_BASE));
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ opts->basic[SDAP_USER_SEARCH_BASE].opt_name,
+ dp_opt_get_string(opts->basic, SDAP_USER_SEARCH_BASE)));
+ }
+
+ if (NULL == dp_opt_get_string(opts->basic, SDAP_GROUP_SEARCH_BASE)) {
+ ret = dp_opt_set_string(opts->basic, SDAP_GROUP_SEARCH_BASE,
+ dp_opt_get_string(opts->basic,
+ SDAP_SEARCH_BASE));
+ if (ret != EOK) {
+ goto done;
+ }
+ DEBUG(6, ("Option %s set to %s\n",
+ opts->basic[SDAP_GROUP_SEARCH_BASE].opt_name,
+ dp_opt_get_string(opts->basic, SDAP_GROUP_SEARCH_BASE)));
+ }
+
+ pwd_policy = dp_opt_get_string(opts->basic, SDAP_PWD_POLICY);
+ if (pwd_policy == NULL) {
+ DEBUG(1, ("Missing password policy, this may not happen.\n"));
+ ret = EINVAL;
+ goto done;
+ }
+ if (strcasecmp(pwd_policy, PWD_POL_OPT_NONE) != 0 &&
+ strcasecmp(pwd_policy, PWD_POL_OPT_SHADOW) != 0 &&
+ strcasecmp(pwd_policy, PWD_POL_OPT_MIT) != 0) {
+ DEBUG(1, ("Unsupported password policy [%s].\n", pwd_policy));
+ ret = EINVAL;
+ goto done;
+ }
+
+
+#ifndef HAVE_LDAP_CONNCB
+ bool ldap_referrals;
+
+ ldap_referrals = dp_opt_get_bool(opts->basic, SDAP_REFERRALS);
+ if (ldap_referrals) {
+ DEBUG(1, ("LDAP referrals are not supported, because the LDAP library "
+ "is too old, see sssd-ldap(5) for details.\n"));
+ ret = dp_opt_set_bool(opts->basic, SDAP_REFERRALS, false);
+ }
+#endif
+
+ /* schema type */
+ schema = dp_opt_get_string(opts->basic, SDAP_SCHEMA);
+ if (strcasecmp(schema, "rfc2307") == 0) {
+ opts->schema_type = SDAP_SCHEMA_RFC2307;
+ default_attr_map = generic_attr_map;
+ default_user_map = rfc2307_user_map;
+ default_group_map = rfc2307_group_map;
+ } else
+ if (strcasecmp(schema, "rfc2307bis") == 0) {
+ opts->schema_type = SDAP_SCHEMA_RFC2307BIS;
+ default_attr_map = generic_attr_map;
+ default_user_map = rfc2307bis_user_map;
+ default_group_map = rfc2307bis_group_map;
+ } else
+ if (strcasecmp(schema, "IPA") == 0) {
+ opts->schema_type = SDAP_SCHEMA_IPA_V1;
+ default_attr_map = gen_ipa_attr_map;
+ default_user_map = rfc2307bis_user_map;
+ default_group_map = rfc2307bis_group_map;
+ } else
+ if (strcasecmp(schema, "AD") == 0) {
+ opts->schema_type = SDAP_SCHEMA_AD;
+ default_attr_map = gen_ad_attr_map;
+ default_user_map = rfc2307bis_user_map;
+ default_group_map = rfc2307bis_group_map;
+ } else {
+ DEBUG(0, ("Unrecognized schema type: %s\n", schema));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = sdap_get_map(opts, cdb, conf_path,
+ default_attr_map,
+ SDAP_AT_GENERAL,
+ &opts->gen_map);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sdap_get_map(opts, cdb, conf_path,
+ default_user_map,
+ SDAP_OPTS_USER,
+ &opts->user_map);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = sdap_get_map(opts, cdb, conf_path,
+ default_group_map,
+ SDAP_OPTS_GROUP,
+ &opts->group_map);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = EOK;
+ *_opts = opts;
+
+done:
+ if (ret != EOK) {
+ talloc_zfree(opts);
+ }
+ return ret;
+}
+
+void sdap_handler_done(struct be_req *req, int dp_err,
+ int error, const char *errstr)
+{
+ return req->fn(req, dp_err, error, errstr);
+}
+
+bool sdap_connected(struct sdap_id_ctx *ctx)
+{
+ if (ctx->gsh) {
+ return ctx->gsh->connected;
+ }
+
+ return false;
+}
+
+void sdap_mark_offline(struct sdap_id_ctx *ctx)
+{
+ if (ctx->gsh) {
+ /* make sure we mark the connection as gone when we go offline so that
+ * we do not try to reuse a bad connection by mistale later */
+ talloc_zfree(ctx->gsh);
+ }
+
+ be_mark_offline(ctx->be);
+}
+
+bool sdap_check_gssapi_reconnect(struct sdap_id_ctx *ctx)
+{
+ int ret;
+ bool result = false;
+ const char *mech;
+ const char *realm;
+ char *ccname = NULL;
+ krb5_context context = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_error_code krberr;
+ TALLOC_CTX *tmp_ctx = NULL;
+ krb5_creds mcred;
+ krb5_creds cred;
+ char *server_name = NULL;
+ char *client_princ_str = NULL;
+ char *full_princ = NULL;
+ krb5_principal client_principal = NULL;
+ krb5_principal server_principal = NULL;
+ char hostname[512];
+ int l_errno;
+
+
+ mech = dp_opt_get_string(ctx->opts->basic, SDAP_SASL_MECH);
+ if (mech == NULL || strcasecmp(mech, "GSSAPI") != 0) {
+ return false;
+ }
+
+ realm = dp_opt_get_string(ctx->opts->basic, SDAP_KRB5_REALM);
+ if (realm == NULL) {
+ DEBUG(3, ("Kerberos realm not available.\n"));
+ return false;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(1, ("talloc_new failed.\n"));
+ return false;
+ }
+
+ ccname = talloc_asprintf(tmp_ctx, "FILE:%s/ccache_%s", DB_PATH, realm);
+ if (ccname == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ goto done;
+ }
+
+ krberr = krb5_init_context(&context);
+ if (krberr) {
+ DEBUG(1, ("Failed to init kerberos context\n"));
+ goto done;
+ }
+
+ krberr = krb5_cc_resolve(context, ccname, &ccache);
+ if (krberr != 0) {
+ DEBUG(1, ("krb5_cc_resolve failed.\n"));
+ goto done;
+ }
+
+ server_name = talloc_asprintf(tmp_ctx, "krbtgt/%s@%s", realm, realm);
+ if (server_name == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ goto done;
+ }
+
+ krberr = krb5_parse_name(context, server_name, &server_principal);
+ if (krberr != 0) {
+ DEBUG(1, ("krb5_parse_name failed.\n"));
+ goto done;
+ }
+
+ client_princ_str = dp_opt_get_string(ctx->opts->basic, SDAP_SASL_AUTHID);
+ if (client_princ_str) {
+ if (!strchr(client_princ_str, '@')) {
+ full_princ = talloc_asprintf(tmp_ctx, "%s@%s", client_princ_str,
+ realm);
+ } else {
+ full_princ = talloc_strdup(tmp_ctx, client_princ_str);
+ }
+ } else {
+ ret = gethostname(hostname, sizeof(hostname)-1);
+ if (ret == -1) {
+ l_errno = errno;
+ DEBUG(1, ("gethostname failed [%d][%s].\n", l_errno,
+ strerror(l_errno)));
+ goto done;
+ }
+ hostname[sizeof(hostname)-1] = '\0';
+
+ full_princ = talloc_asprintf(tmp_ctx, "host/%s@%s", hostname, realm);
+ }
+ if (!full_princ) {
+ DEBUG(1, ("Client principal not available.\n"));
+ goto done;
+ }
+ DEBUG(7, ("Client principal name is: [%s]\n", full_princ));
+ krberr = krb5_parse_name(context, full_princ, &client_principal);
+ if (krberr != 0) {
+ DEBUG(1, ("krb5_parse_name failed.\n"));
+ goto done;
+ }
+
+ memset(&mcred, 0, sizeof(mcred));
+ memset(&cred, 0, sizeof(mcred));
+ mcred.client = client_principal;
+ mcred.server = server_principal;
+
+ krberr = krb5_cc_retrieve_cred(context, ccache, 0, &mcred, &cred);
+ if (krberr != 0) {
+ DEBUG(1, ("krb5_cc_retrieve_cred failed.\n"));
+ goto done;
+ }
+
+ DEBUG(7, ("TGT end time [%d].\n", cred.times.endtime));
+
+ if (cred.times.endtime <= time(NULL)) {
+ DEBUG(3, ("TGT is expired.\n"));
+ result = true;
+ }
+ krb5_free_cred_contents(context, &cred);
+
+done:
+ if (client_principal != NULL) {
+ krb5_free_principal(context, client_principal);
+ }
+ if (server_principal != NULL) {
+ krb5_free_principal(context, server_principal);
+ }
+ if (ccache != NULL) {
+ if (result) {
+ krb5_cc_destroy(context, ccache);
+ } else {
+ krb5_cc_close(context, ccache);
+ }
+ }
+ if (context != NULL) krb5_free_context(context);
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+int sdap_id_setup_tasks(struct sdap_id_ctx *ctx)
+{
+ struct timeval tv;
+ int ret = EOK;
+
+ /* set up enumeration task */
+ if (ctx->be->domain->enumerate) {
+ /* run the first one in a couple of seconds so that we have time to
+ * finish initializations first*/
+ tv = tevent_timeval_current_ofs(10, 0);
+ ret = ldap_id_enumerate_set_timer(ctx, tv);
+ } else {
+ /* the enumeration task, runs the cleanup process by itself,
+ * but if enumeration is not runnig we need to schedule it */
+
+ /* run the first one in a couple of seconds so that we have time to
+ * finish initializations first*/
+ tv = tevent_timeval_current_ofs(10, 0);
+ ret = ldap_id_cleanup_set_timer(ctx, tv);
+ }
+
+ return ret;
+}
+
+static void sdap_uri_callback(void *private_data, struct fo_server *server)
+{
+ struct sdap_service *service;
+ const char *tmp;
+ char *new_uri;
+
+ service = talloc_get_type(private_data, struct sdap_service);
+ if (!service) return;
+
+ tmp = (const char *)fo_get_server_user_data(server);
+ if (tmp && ldap_is_ldap_url(tmp)) {
+ new_uri = talloc_strdup(service, tmp);
+ } else {
+ new_uri = talloc_asprintf(service, "ldap://%s",
+ fo_get_server_name(server));
+ }
+ if (!new_uri) {
+ DEBUG(2, ("Failed to copy URI ...\n"));
+ return;
+ }
+
+ /* free old one and replace with new one */
+ talloc_zfree(service->uri);
+ service->uri = new_uri;
+}
+
+int sdap_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx,
+ const char *service_name, const char *urls,
+ struct sdap_service **_service)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct sdap_service *service;
+ LDAPURLDesc *lud;
+ char **list = NULL;
+ int ret;
+ int i;
+
+ tmp_ctx = talloc_new(memctx);
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ service = talloc_zero(tmp_ctx, struct sdap_service);
+ if (!service) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = be_fo_add_service(ctx, service_name);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to create failover service!\n"));
+ goto done;
+ }
+
+ service->name = talloc_strdup(service, service_name);
+ if (!service->name) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* split server parm into a list */
+ ret = split_on_separator(tmp_ctx, urls, ',', true, &list, NULL);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to parse server list!\n"));
+ goto done;
+ }
+
+ /* now for each URI add a new server to the failover service */
+ for (i = 0; list[i]; i++) {
+ ret = ldap_url_parse(list[i], &lud);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(0, ("Failed to parse ldap URI (%s)!\n", list[i]));
+ ret = EINVAL;
+ goto done;
+ }
+
+ DEBUG(6, ("Added URI %s\n", list[i]));
+
+ talloc_steal(service, list[i]);
+
+ ret = be_fo_add_server(ctx, service->name,
+ lud->lud_host, lud->lud_port, list[i]);
+ if (ret) {
+ goto done;
+ }
+ ldap_free_urldesc(lud);
+ }
+
+ ret = be_fo_service_add_callback(memctx, ctx, service->name,
+ sdap_uri_callback, service);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to add failover callback!\n"));
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ if (ret == EOK) {
+ *_service = talloc_steal(memctx, service);
+ }
+ talloc_zfree(tmp_ctx);
+ return ret;
+}
+
diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h
new file mode 100644
index 000000000..ff1ffb725
--- /dev/null
+++ b/src/providers/ldap/ldap_common.h
@@ -0,0 +1,115 @@
+/*
+ SSSD
+
+ LDAP Common utility code
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+
+ 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 _LDAP_COMMON_H_
+#define _LDAP_COMMON_H_
+
+#include "providers/dp_backend.h"
+#include "providers/ldap/sdap.h"
+#include "providers/fail_over.h"
+
+#define PWD_POL_OPT_NONE "none"
+#define PWD_POL_OPT_SHADOW "shadow"
+#define PWD_POL_OPT_MIT "mit_kerberos"
+
+/* a fd the child process would log into */
+extern int ldap_child_debug_fd;
+
+struct sdap_id_ctx {
+ struct be_ctx *be;
+ struct sdap_options *opts;
+ struct fo_service *fo_service;
+ struct sdap_service *service;
+
+ /* what rootDSE returns */
+ struct sysdb_attrs *rootDSE;
+
+ /* global sdap handler */
+ struct sdap_handle *gsh;
+
+ /* enumeration loop timer */
+ struct timeval last_enum;
+ /* cleanup loop timer */
+ struct timeval last_purge;
+
+ char *max_user_timestamp;
+ char *max_group_timestamp;
+};
+
+struct sdap_auth_ctx {
+ struct be_ctx *be;
+ struct sdap_options *opts;
+ struct fo_service *fo_service;
+ struct sdap_service *service;
+};
+
+/* id */
+void sdap_account_info_handler(struct be_req *breq);
+int sdap_id_setup_tasks(struct sdap_id_ctx *ctx);
+
+/* auth */
+void sdap_pam_auth_handler(struct be_req *breq);
+
+/* chpass */
+void sdap_pam_chpass_handler(struct be_req *breq);
+
+
+
+void sdap_handler_done(struct be_req *req, int dp_err,
+ int error, const char *errstr);
+
+int sdap_service_init(TALLOC_CTX *mmectx, struct be_ctx *ctx,
+ const char *service_name, const char *urls,
+ struct sdap_service **service);
+
+/* options parser */
+int ldap_get_options(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sdap_options **_opts);
+
+int ldap_id_enumerate_set_timer(struct sdap_id_ctx *ctx, struct timeval tv);
+int ldap_id_cleanup_set_timer(struct sdap_id_ctx *ctx, struct timeval tv);
+
+bool sdap_connected(struct sdap_id_ctx *ctx);
+void sdap_mark_offline(struct sdap_id_ctx *ctx);
+bool sdap_check_gssapi_reconnect(struct sdap_id_ctx *ctx);
+
+struct tevent_req *users_get_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ const char *name,
+ int filter_type,
+ int attrs_type);
+int users_get_recv(struct tevent_req *req);
+
+struct tevent_req *groups_get_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ const char *name,
+ int filter_type,
+ int attrs_type);
+int groups_get_recv(struct tevent_req *req);
+
+/* setup child logging */
+int setup_child(struct sdap_id_ctx *ctx);
+
+#endif /* _LDAP_COMMON_H_ */
diff --git a/src/providers/ldap/ldap_id.c b/src/providers/ldap/ldap_id.c
new file mode 100644
index 000000000..4bbc07a68
--- /dev/null
+++ b/src/providers/ldap/ldap_id.c
@@ -0,0 +1,795 @@
+/*
+ SSSD
+
+ LDAP Identity Backend Module
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2008 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 <errno.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async.h"
+
+/* =Users-Related-Functions-(by-name,by-uid)============================== */
+
+struct users_get_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+
+ const char *name;
+ int filter_type;
+
+ char *filter;
+ const char **attrs;
+};
+
+static void users_get_connect_done(struct tevent_req *subreq);
+static void users_get_done(struct tevent_req *subreq);
+static void users_get_delete(struct tevent_req *subreq);
+
+struct tevent_req *users_get_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ const char *name,
+ int filter_type,
+ int attrs_type)
+{
+ struct tevent_req *req, *subreq;
+ struct users_get_state *state;
+ const char *attr_name;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct users_get_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = ctx->be->sysdb;
+ state->domain = state->ctx->be->domain;
+ state->name = name;
+ state->filter_type = filter_type;
+
+ switch (filter_type) {
+ case BE_FILTER_NAME:
+ attr_name = ctx->opts->user_map[SDAP_AT_USER_NAME].name;
+ break;
+ case BE_FILTER_IDNUM:
+ attr_name = ctx->opts->user_map[SDAP_AT_USER_UID].name;
+ break;
+ default:
+ ret = EINVAL;
+ goto fail;
+ }
+
+ state->filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))",
+ attr_name, name,
+ ctx->opts->user_map[SDAP_OC_USER].name);
+ if (!state->filter) {
+ DEBUG(2, ("Failed to build filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ /* TODO: handle attrs_type */
+ ret = build_attrs_from_map(state, ctx->opts->user_map,
+ SDAP_OPTS_USER, &state->attrs);
+ if (ret != EOK) goto fail;
+
+ if (!sdap_connected(ctx)) {
+
+ if (ctx->gsh) talloc_zfree(ctx->gsh);
+
+ /* FIXME: add option to decide if tls should be used
+ * or SASL/GSSAPI, etc ... */
+ subreq = sdap_cli_connect_send(state, ev, ctx->opts,
+ ctx->be, ctx->service,
+ &ctx->rootDSE);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, users_get_connect_done, req);
+
+ return req;
+ }
+
+ subreq = sdap_get_users_send(state, state->ev,
+ state->domain, state->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, users_get_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void users_get_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct users_get_state *state = tevent_req_data(req,
+ struct users_get_state);
+ int ret;
+
+ ret = sdap_cli_connect_recv(subreq, state->ctx,
+ &state->ctx->gsh, &state->ctx->rootDSE);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENOTSUP) {
+ DEBUG(0, ("Authentication mechanism not Supported by server"));
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_get_users_send(state, state->ev,
+ state->domain, state->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, users_get_done, req);
+}
+
+static void users_get_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct users_get_state *state = tevent_req_data(req,
+ struct users_get_state);
+ char *endptr;
+ uid_t uid;
+ int ret;
+
+ ret = sdap_get_users_recv(subreq, NULL, NULL);
+ talloc_zfree(subreq);
+ if (ret && ret != ENOENT) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (ret == ENOENT) {
+ if (strchr(state->name, '*')) {
+ /* it was an enumeration */
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ switch (state->filter_type) {
+ case BE_FILTER_NAME:
+ subreq = sysdb_delete_user_send(state, state->ev,
+ state->sysdb, NULL,
+ state->domain, state->name, 0);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, users_get_delete, req);
+ return;
+
+ case BE_FILTER_IDNUM:
+ errno = 0;
+ uid = (uid_t)strtol(state->name, &endptr, 0);
+ if (errno || *endptr || (state->name == endptr)) {
+ tevent_req_error(req, errno);
+ return;
+ }
+
+ subreq = sysdb_delete_user_send(state, state->ev,
+ state->sysdb, NULL,
+ state->domain, NULL, uid);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, users_get_delete, req);
+ return;
+
+ default:
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+ }
+
+ tevent_req_done(req);
+}
+
+static void users_get_delete(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct users_get_state *state = tevent_req_data(req,
+ struct users_get_state);
+ int ret;
+
+ ret = sysdb_delete_user_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("User (%s) delete returned %d (%s)\n",
+ state->name, ret, strerror(ret)));
+ }
+
+ tevent_req_done(req);
+}
+
+int users_get_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* =Groups-Related-Functions-(by-name,by-uid)============================= */
+
+struct groups_get_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+
+ const char *name;
+ int filter_type;
+
+ char *filter;
+ const char **attrs;
+};
+
+static void groups_get_connect_done(struct tevent_req *subreq);
+static void groups_get_done(struct tevent_req *subreq);
+static void groups_get_delete(struct tevent_req *subreq);
+
+struct tevent_req *groups_get_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ const char *name,
+ int filter_type,
+ int attrs_type)
+{
+ struct tevent_req *req, *subreq;
+ struct groups_get_state *state;
+ const char *attr_name;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct groups_get_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = ctx->be->sysdb;
+ state->domain = state->ctx->be->domain;
+ state->name = name;
+ state->filter_type = filter_type;
+
+ switch(filter_type) {
+ case BE_FILTER_NAME:
+ attr_name = ctx->opts->group_map[SDAP_AT_GROUP_NAME].name;
+ break;
+ case BE_FILTER_IDNUM:
+ attr_name = ctx->opts->group_map[SDAP_AT_GROUP_GID].name;
+ break;
+ default:
+ ret = EINVAL;
+ goto fail;
+ }
+
+ state->filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))",
+ attr_name, name,
+ ctx->opts->group_map[SDAP_OC_GROUP].name);
+ if (!state->filter) {
+ DEBUG(2, ("Failed to build filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ /* TODO: handle attrs_type */
+ ret = build_attrs_from_map(state, ctx->opts->group_map,
+ SDAP_OPTS_GROUP, &state->attrs);
+ if (ret != EOK) goto fail;
+
+ if (!sdap_connected(ctx)) {
+
+ if (ctx->gsh) talloc_zfree(ctx->gsh);
+
+ /* FIXME: add option to decide if tls should be used
+ * or SASL/GSSAPI, etc ... */
+ subreq = sdap_cli_connect_send(state, ev, ctx->opts,
+ ctx->be, ctx->service,
+ &ctx->rootDSE);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, groups_get_connect_done, req);
+
+ return req;
+ }
+
+ subreq = sdap_get_groups_send(state, state->ev,
+ state->domain, state->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, groups_get_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void groups_get_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct groups_get_state *state = tevent_req_data(req,
+ struct groups_get_state);
+ int ret;
+
+ ret = sdap_cli_connect_recv(subreq, state->ctx,
+ &state->ctx->gsh, &state->ctx->rootDSE);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENOTSUP) {
+ DEBUG(0, ("Authentication mechanism not Supported by server"));
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_get_groups_send(state, state->ev,
+ state->domain, state->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, groups_get_done, req);
+}
+
+static void groups_get_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct groups_get_state *state = tevent_req_data(req,
+ struct groups_get_state);
+ char *endptr;
+ gid_t gid;
+ int ret;
+
+ ret = sdap_get_groups_recv(subreq, NULL, NULL);
+ talloc_zfree(subreq);
+ if (ret && ret != ENOENT) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (ret == ENOENT) {
+ if (strchr(state->name, '*')) {
+ /* it was an enumeration */
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ switch (state->filter_type) {
+ case BE_FILTER_NAME:
+ subreq = sysdb_delete_group_send(state, state->ev,
+ state->sysdb, NULL,
+ state->domain, state->name, 0);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, groups_get_delete, req);
+ return;
+
+ case BE_FILTER_IDNUM:
+ errno = 0;
+ gid = (gid_t)strtol(state->name, &endptr, 0);
+ if (errno || *endptr || (state->name == endptr)) {
+ tevent_req_error(req, errno);
+ return;
+ }
+
+ subreq = sysdb_delete_group_send(state, state->ev,
+ state->sysdb, NULL,
+ state->domain, NULL, gid);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, groups_get_delete, req);
+ return;
+
+ default:
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+ }
+
+ tevent_req_done(req);
+}
+
+static void groups_get_delete(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct groups_get_state *state = tevent_req_data(req,
+ struct groups_get_state);
+ int ret;
+
+ ret = sysdb_delete_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Group (%s) delete returned %d (%s)\n",
+ state->name, ret, strerror(ret)));
+ }
+
+ tevent_req_done(req);
+}
+
+int groups_get_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* =Get-Groups-for-User================================================== */
+
+struct groups_by_user_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+ const char *name;
+ const char **attrs;
+};
+
+static void groups_by_user_connect_done(struct tevent_req *subreq);
+static void groups_by_user_done(struct tevent_req *subreq);
+
+static struct tevent_req *groups_by_user_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ const char *name)
+{
+ struct tevent_req *req, *subreq;
+ struct groups_by_user_state *state;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct groups_by_user_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->name = name;
+
+ ret = build_attrs_from_map(state, ctx->opts->group_map,
+ SDAP_OPTS_GROUP, &state->attrs);
+ if (ret != EOK) goto fail;
+
+ if (!sdap_connected(ctx)) {
+
+ if (ctx->gsh) talloc_zfree(ctx->gsh);
+
+ /* FIXME: add option to decide if tls should be used
+ * or SASL/GSSAPI, etc ... */
+ subreq = sdap_cli_connect_send(state, ev, ctx->opts,
+ ctx->be, ctx->service,
+ &ctx->rootDSE);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, groups_by_user_connect_done, req);
+
+ return req;
+ }
+
+ subreq = sdap_get_initgr_send(state, state->ev,
+ state->ctx->be->domain,
+ state->ctx->be->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->name, state->attrs);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, groups_by_user_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void groups_by_user_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct groups_by_user_state *state = tevent_req_data(req,
+ struct groups_by_user_state);
+ int ret;
+
+ ret = sdap_cli_connect_recv(subreq, state->ctx,
+ &state->ctx->gsh, &state->ctx->rootDSE);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENOTSUP) {
+ DEBUG(0, ("Authentication mechanism not Supported by server"));
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_get_initgr_send(state, state->ev,
+ state->ctx->be->domain,
+ state->ctx->be->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->name, state->attrs);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, groups_by_user_done, req);
+}
+
+static void groups_by_user_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sdap_get_initgr_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int groups_by_user_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+
+/* =Get-Account-Info-Call================================================= */
+
+/* FIXME: embed this function in sssd_be and only call out
+ * specific functions from modules ? */
+
+static void sdap_account_info_users_done(struct tevent_req *req);
+static void sdap_account_info_groups_done(struct tevent_req *req);
+static void sdap_account_info_initgr_done(struct tevent_req *req);
+
+void sdap_account_info_handler(struct be_req *breq)
+{
+ struct sdap_id_ctx *ctx;
+ struct be_acct_req *ar;
+ struct tevent_req *req;
+ const char *err = "Unknown Error";
+ int ret = EOK;
+
+ ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, struct sdap_id_ctx);
+
+ if (be_is_offline(ctx->be)) {
+ return sdap_handler_done(breq, DP_ERR_OFFLINE, EAGAIN, "Offline");
+ }
+
+ ar = talloc_get_type(breq->req_data, struct be_acct_req);
+
+ switch (ar->entry_type & 0xFFF) {
+ case BE_REQ_USER: /* user */
+
+ /* skip enumerations on demand */
+ if (strcmp(ar->filter_value, "*") == 0) {
+ return sdap_handler_done(breq, DP_ERR_OK, EOK, "Success");
+ }
+
+ req = users_get_send(breq, breq->be_ctx->ev, ctx,
+ ar->filter_value,
+ ar->filter_type,
+ ar->attr_type);
+ if (!req) {
+ return sdap_handler_done(breq, DP_ERR_FATAL, ENOMEM, "Out of memory");
+ }
+
+ tevent_req_set_callback(req, sdap_account_info_users_done, breq);
+
+ break;
+
+ case BE_REQ_GROUP: /* group */
+
+ if (strcmp(ar->filter_value, "*") == 0) {
+ return sdap_handler_done(breq, DP_ERR_OK, EOK, "Success");
+ }
+
+ /* skip enumerations on demand */
+ req = groups_get_send(breq, breq->be_ctx->ev, ctx,
+ ar->filter_value,
+ ar->filter_type,
+ ar->attr_type);
+ if (!req) {
+ return sdap_handler_done(breq, DP_ERR_FATAL, ENOMEM, "Out of memory");
+ }
+
+ tevent_req_set_callback(req, sdap_account_info_groups_done, breq);
+
+ break;
+
+ case BE_REQ_INITGROUPS: /* init groups for user */
+ if (ar->filter_type != BE_FILTER_NAME) {
+ ret = EINVAL;
+ err = "Invalid filter type";
+ break;
+ }
+ if (ar->attr_type != BE_ATTR_CORE) {
+ ret = EINVAL;
+ err = "Invalid attr type";
+ break;
+ }
+ if (strchr(ar->filter_value, '*')) {
+ ret = EINVAL;
+ err = "Invalid filter value";
+ break;
+ }
+ req = groups_by_user_send(breq, breq->be_ctx->ev, ctx,
+ ar->filter_value);
+ if (!req) ret = ENOMEM;
+ /* tevent_req_set_callback(req, groups_by_user_done, breq); */
+
+ tevent_req_set_callback(req, sdap_account_info_initgr_done, breq);
+
+ break;
+
+ default: /*fail*/
+ ret = EINVAL;
+ err = "Invalid request type";
+ }
+
+ if (ret != EOK) return sdap_handler_done(breq, DP_ERR_FATAL, ret, err);
+}
+
+static void sdap_account_info_users_done(struct tevent_req *req)
+{
+ struct be_req *breq = tevent_req_callback_data(req, struct be_req);
+ struct sdap_id_ctx *ctx;
+ int dp_err = DP_ERR_OK;
+ const char *error = NULL;
+ int ret;
+
+ ret = users_get_recv(req);
+ talloc_zfree(req);
+
+ if (ret) {
+ dp_err = DP_ERR_FATAL;
+ error = "Enum Users Failed";
+
+ if (ret == ETIMEDOUT || ret == EFAULT || ret == EIO) {
+ dp_err = DP_ERR_OFFLINE;
+ ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data,
+ struct sdap_id_ctx);
+ if (sdap_check_gssapi_reconnect(ctx)) {
+ talloc_zfree(ctx->gsh);
+ sdap_account_info_handler(breq);
+ return;
+ }
+ sdap_mark_offline(ctx);
+ }
+ }
+
+ sdap_handler_done(breq, dp_err, ret, error);
+}
+
+static void sdap_account_info_groups_done(struct tevent_req *req)
+{
+ struct be_req *breq = tevent_req_callback_data(req, struct be_req);
+ struct sdap_id_ctx *ctx;
+ int dp_err = DP_ERR_OK;
+ const char *error = NULL;
+ int ret;
+
+ ret = groups_get_recv(req);
+ talloc_zfree(req);
+
+ if (ret) {
+ dp_err = DP_ERR_FATAL;
+ error = "Enum Groups Failed";
+
+ if (ret == ETIMEDOUT || ret == EFAULT || ret == EIO) {
+ dp_err = DP_ERR_OFFLINE;
+ ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data,
+ struct sdap_id_ctx);
+ if (sdap_check_gssapi_reconnect(ctx)) {
+ talloc_zfree(ctx->gsh);
+ sdap_account_info_handler(breq);
+ return;
+ }
+ sdap_mark_offline(ctx);
+ }
+ }
+
+ return sdap_handler_done(breq, dp_err, ret, error);
+}
+
+static void sdap_account_info_initgr_done(struct tevent_req *req)
+{
+ struct be_req *breq = tevent_req_callback_data(req, struct be_req);
+ struct sdap_id_ctx *ctx;
+ int dp_err = DP_ERR_OK;
+ const char *error = NULL;
+ int ret;
+
+ ret = groups_by_user_recv(req);
+ talloc_zfree(req);
+
+ if (ret) {
+ dp_err = DP_ERR_FATAL;
+ error = "Init Groups Failed";
+
+ if (ret == ETIMEDOUT || ret == EFAULT || ret == EIO) {
+ dp_err = DP_ERR_OFFLINE;
+ ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data,
+ struct sdap_id_ctx);
+ if (sdap_check_gssapi_reconnect(ctx)) {
+ talloc_zfree(ctx->gsh);
+ sdap_account_info_handler(breq);
+ return;
+ }
+ sdap_mark_offline(ctx);
+ }
+ }
+
+ return sdap_handler_done(breq, dp_err, ret, error);
+}
+
diff --git a/src/providers/ldap/ldap_id_cleanup.c b/src/providers/ldap/ldap_id_cleanup.c
new file mode 100644
index 000000000..f3fb4443c
--- /dev/null
+++ b/src/providers/ldap/ldap_id_cleanup.c
@@ -0,0 +1,555 @@
+/*
+ SSSD
+
+ LDAP Identity Cleanup Functions
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2009 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 <errno.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async.h"
+
+/* ==Cleanup-Task========================================================= */
+
+struct tevent_req *ldap_id_cleanup_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx);
+static void ldap_id_cleanup_reschedule(struct tevent_req *req);
+
+static void ldap_id_cleanup_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt);
+
+static void ldap_id_cleanup_timer(struct tevent_context *ev,
+ struct tevent_timer *tt,
+ struct timeval tv, void *pvt)
+{
+ struct sdap_id_ctx *ctx = talloc_get_type(pvt, struct sdap_id_ctx);
+ struct tevent_timer *timeout;
+ struct tevent_req *req;
+ int delay;
+
+ if (be_is_offline(ctx->be)) {
+ DEBUG(4, ("Backend is marked offline, retry later!\n"));
+ /* schedule starting from now, not the last run */
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT);
+ tv = tevent_timeval_current_ofs(delay, 0);
+ ldap_id_cleanup_set_timer(ctx, tv);
+ return;
+ }
+
+ req = ldap_id_cleanup_send(ctx, ev, ctx);
+ if (!req) {
+ DEBUG(1, ("Failed to schedule cleanup, retrying later!\n"));
+ /* schedule starting from now, not the last run */
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT);
+ tv = tevent_timeval_current_ofs(delay, 0);
+ ldap_id_cleanup_set_timer(ctx, tv);
+ return;
+ }
+ tevent_req_set_callback(req, ldap_id_cleanup_reschedule, ctx);
+
+ /* if cleanup takes so long, either we try to cleanup too
+ * frequently, or something went seriously wrong */
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT);
+ tv = tevent_timeval_current_ofs(delay, 0);
+ timeout = tevent_add_timer(ctx->be->ev, req, tv,
+ ldap_id_cleanup_timeout, req);
+ return;
+}
+
+static void ldap_id_cleanup_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct sdap_id_ctx *ctx = tevent_req_callback_data(req,
+ struct sdap_id_ctx);
+ int delay;
+
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT);
+ DEBUG(1, ("Cleanup timed out! Timeout too small? (%ds)!\n", delay));
+
+ tv = tevent_timeval_current_ofs(delay, 0);
+ ldap_id_enumerate_set_timer(ctx, tv);
+
+ talloc_zfree(req);
+}
+
+static void ldap_id_cleanup_reschedule(struct tevent_req *req)
+{
+ struct sdap_id_ctx *ctx = tevent_req_callback_data(req,
+ struct sdap_id_ctx);
+ enum tevent_req_state tstate;
+ uint64_t err;
+ struct timeval tv;
+ int delay;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ /* On error schedule starting from now, not the last run */
+ tv = tevent_timeval_current();
+ } else {
+ tv = ctx->last_purge;
+ }
+ talloc_zfree(req);
+
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT);
+ tv = tevent_timeval_add(&tv, delay, 0);
+ ldap_id_enumerate_set_timer(ctx, tv);
+}
+
+
+
+int ldap_id_cleanup_set_timer(struct sdap_id_ctx *ctx, struct timeval tv)
+{
+ struct tevent_timer *cleanup_task;
+
+ DEBUG(6, ("Scheduling next cleanup at %ld.%ld\n",
+ (long)tv.tv_sec, (long)tv.tv_usec));
+
+ cleanup_task = tevent_add_timer(ctx->be->ev, ctx,
+ tv, ldap_id_cleanup_timer, ctx);
+ if (!cleanup_task) {
+ DEBUG(0, ("FATAL: failed to setup cleanup task!\n"));
+ return EFAULT;
+ }
+
+ return EOK;
+}
+
+
+
+struct global_cleanup_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+};
+
+static struct tevent_req *cleanup_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx);
+static void ldap_id_cleanup_users_done(struct tevent_req *subreq);
+static struct tevent_req *cleanup_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx);
+static void ldap_id_cleanup_groups_done(struct tevent_req *subreq);
+
+struct tevent_req *ldap_id_cleanup_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx)
+{
+ struct global_cleanup_state *state;
+ struct tevent_req *req, *subreq;
+
+ req = tevent_req_create(memctx, &state, struct global_cleanup_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+
+ subreq = cleanup_users_send(state, ev, ctx);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, ldap_id_cleanup_users_done, req);
+
+ ctx->last_purge = tevent_timeval_current();
+
+ return req;
+}
+
+static void ldap_id_cleanup_users_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct global_cleanup_state *state = tevent_req_data(req,
+ struct global_cleanup_state);
+ enum tevent_req_state tstate;
+ uint64_t err = 0;
+
+ if (tevent_req_is_error(subreq, &tstate, &err)) {
+ if (tstate != TEVENT_REQ_USER_ERROR) {
+ err = EIO;
+ }
+ if (err != ENOENT) {
+ goto fail;
+ }
+ }
+ talloc_zfree(subreq);
+
+ subreq = cleanup_groups_send(state, state->ev, state->ctx);
+ if (!subreq) {
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, ldap_id_cleanup_groups_done, req);
+
+ return;
+
+fail:
+ if (err) {
+ DEBUG(9, ("User cleanup failed with: (%d)[%s]\n",
+ (int)err, strerror(err)));
+
+ if (sdap_check_gssapi_reconnect(state->ctx)) {
+ talloc_zfree(state->ctx->gsh);
+ subreq = cleanup_users_send(state, state->ev, state->ctx);
+ if (subreq != NULL) {
+ tevent_req_set_callback(subreq, ldap_id_cleanup_users_done, req);
+ return;
+ }
+ }
+ sdap_mark_offline(state->ctx);
+ }
+
+ DEBUG(1, ("Failed to cleanup users, retrying later!\n"));
+ tevent_req_done(req);
+}
+
+static void ldap_id_cleanup_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct global_cleanup_state *state = tevent_req_data(req,
+ struct global_cleanup_state);
+ enum tevent_req_state tstate;
+ uint64_t err;
+
+ if (tevent_req_is_error(subreq, &tstate, &err)) {
+ if (tstate != TEVENT_REQ_USER_ERROR) {
+ err = EIO;
+ }
+ if (err != ENOENT) {
+ goto fail;
+ }
+ }
+ talloc_zfree(subreq);
+
+ tevent_req_done(req);
+ return;
+
+fail:
+ /* check if credentials are expired otherwise go offline on failures */
+ if (sdap_check_gssapi_reconnect(state->ctx)) {
+ talloc_zfree(state->ctx->gsh);
+ subreq = cleanup_groups_send(state, state->ev, state->ctx);
+ if (subreq != NULL) {
+ tevent_req_set_callback(subreq, ldap_id_cleanup_groups_done, req);
+ return;
+ }
+ }
+ sdap_mark_offline(state->ctx);
+ DEBUG(1, ("Failed to cleanup groups (%d [%s]), retrying later!\n",
+ (int)err, strerror(err)));
+ tevent_req_done(req);
+}
+
+
+/* ==User-Cleanup-Process================================================= */
+
+struct cleanup_users_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+
+ struct sysdb_handle *handle;
+
+ struct ldb_message **msgs;
+ size_t count;
+ int cur;
+};
+
+static void cleanup_users_process(struct tevent_req *subreq);
+static void cleanup_users_update(struct tevent_req *req);
+static void cleanup_users_up_done(struct tevent_req *subreq);
+
+static struct tevent_req *cleanup_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx)
+{
+ struct tevent_req *req, *subreq;
+ struct cleanup_users_state *state;
+ static const char *attrs[] = { SYSDB_NAME, NULL };
+ time_t now = time(NULL);
+ char *subfilter;
+
+ req = tevent_req_create(memctx, &state, struct cleanup_users_state);
+ if (!req) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = ctx->be->sysdb;
+ state->domain = ctx->be->domain;
+ state->msgs = NULL;
+ state->count = 0;
+ state->cur = 0;
+
+ subfilter = talloc_asprintf(state, "(&(!(%s=0))(%s<=%ld))",
+ SYSDB_CACHE_EXPIRE,
+ SYSDB_CACHE_EXPIRE, (long)now);
+ if (!subfilter) {
+ DEBUG(2, ("Failed to build filter\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ subreq = sysdb_search_users_send(state, state->ev,
+ state->sysdb, NULL,
+ state->domain, subfilter, attrs);
+ if (!subreq) {
+ DEBUG(2, ("Failed to send entry search\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, cleanup_users_process, req);
+
+ return req;
+}
+
+static void cleanup_users_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cleanup_users_state *state = tevent_req_data(req,
+ struct cleanup_users_state);
+ int ret;
+
+ ret = sysdb_search_users_recv(subreq, state, &state->count, &state->msgs);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENOENT) {
+ tevent_req_done(req);
+ return;
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(4, ("Found %d expired user entries!\n", state->count));
+
+ if (state->count == 0) {
+ tevent_req_done(req);
+ }
+
+ cleanup_users_update(req);
+}
+
+static void cleanup_users_update(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct cleanup_users_state *state = tevent_req_data(req,
+ struct cleanup_users_state);
+ const char *str;
+
+ str = ldb_msg_find_attr_as_string(state->msgs[state->cur],
+ SYSDB_NAME, NULL);
+ if (!str) {
+ DEBUG(2, ("Entry %s has no Name Attribute ?!?\n",
+ ldb_dn_get_linearized(state->msgs[state->cur]->dn)));
+ tevent_req_error(req, EFAULT);
+ return;
+ }
+
+ subreq = users_get_send(state, state->ev, state->ctx,
+ str, BE_FILTER_NAME, BE_ATTR_CORE);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, cleanup_users_up_done, req);
+}
+
+static void cleanup_users_up_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cleanup_users_state *state = tevent_req_data(req,
+ struct cleanup_users_state);
+ int ret;
+
+ ret = users_get_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("User check returned: %d(%s)\n",
+ ret, strerror(ret)));
+ }
+
+ /* if the entry doesn't need to be purged, remove it from the list */
+ if (ret != ENOENT) {
+ talloc_zfree(state->msgs[state->cur]);
+ }
+
+ state->cur++;
+ if (state->cur < state->count) {
+ cleanup_users_update(req);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+/* ==Group-Cleanup-Process================================================ */
+
+struct cleanup_groups_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+
+ struct sysdb_handle *handle;
+
+ struct ldb_message **msgs;
+ size_t count;
+ int cur;
+};
+
+static void cleanup_groups_process(struct tevent_req *subreq);
+static void cleanup_groups_update(struct tevent_req *req);
+static void cleanup_groups_up_done(struct tevent_req *subreq);
+
+static struct tevent_req *cleanup_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx)
+{
+ struct tevent_req *req, *subreq;
+ struct cleanup_groups_state *state;
+ static const char *attrs[] = { SYSDB_NAME, NULL };
+ time_t now = time(NULL);
+ char *subfilter;
+
+ req = tevent_req_create(memctx, &state, struct cleanup_groups_state);
+ if (!req) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = ctx->be->sysdb;
+ state->domain = ctx->be->domain;
+ state->msgs = NULL;
+ state->count = 0;
+ state->cur = 0;
+
+ subfilter = talloc_asprintf(state, "(&(!(%s=0))(%s<=%ld))",
+ SYSDB_CACHE_EXPIRE,
+ SYSDB_CACHE_EXPIRE, (long)now);
+ if (!subfilter) {
+ DEBUG(2, ("Failed to build filter\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ subreq = sysdb_search_groups_send(state, state->ev,
+ state->sysdb, NULL,
+ state->domain, subfilter, attrs);
+ if (!subreq) {
+ DEBUG(2, ("Failed to send entry search\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, cleanup_groups_process, req);
+
+ return req;
+}
+
+static void cleanup_groups_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cleanup_groups_state *state = tevent_req_data(req,
+ struct cleanup_groups_state);
+ int ret;
+
+ ret = sysdb_search_groups_recv(subreq, state, &state->count, &state->msgs);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENOENT) {
+ tevent_req_done(req);
+ return;
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(4, ("Found %d expired group entries!\n", state->count));
+
+ if (state->count == 0) {
+ tevent_req_done(req);
+ }
+
+ cleanup_groups_update(req);
+}
+
+static void cleanup_groups_update(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct cleanup_groups_state *state = tevent_req_data(req,
+ struct cleanup_groups_state);
+ const char *str;
+
+ str = ldb_msg_find_attr_as_string(state->msgs[state->cur],
+ SYSDB_NAME, NULL);
+ if (!str) {
+ DEBUG(2, ("Entry %s has no Name Attribute ?!?\n",
+ ldb_dn_get_linearized(state->msgs[state->cur]->dn)));
+ tevent_req_error(req, EFAULT);
+ return;
+ }
+
+ subreq = groups_get_send(state, state->ev, state->ctx,
+ str, BE_FILTER_NAME, BE_ATTR_CORE);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, cleanup_groups_up_done, req);
+}
+
+static void cleanup_groups_up_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cleanup_groups_state *state = tevent_req_data(req,
+ struct cleanup_groups_state);
+ int ret;
+
+ ret = groups_get_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("User check returned: %d(%s)\n",
+ ret, strerror(ret)));
+ }
+
+ state->cur++;
+ if (state->cur < state->count) {
+ cleanup_groups_update(req);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
diff --git a/src/providers/ldap/ldap_id_enum.c b/src/providers/ldap/ldap_id_enum.c
new file mode 100644
index 000000000..bc06e8bdc
--- /dev/null
+++ b/src/providers/ldap/ldap_id_enum.c
@@ -0,0 +1,608 @@
+/*
+ SSSD
+
+ LDAP Identity Enumeration
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2009 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 <errno.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async.h"
+
+extern struct tevent_req *ldap_id_cleanup_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx);
+
+/* ==Enumeration-Task===================================================== */
+
+static struct tevent_req *ldap_id_enumerate_send(struct tevent_context *ev,
+ struct sdap_id_ctx *ctx);
+
+static void ldap_id_enumerate_reschedule(struct tevent_req *req);
+
+static void ldap_id_enumerate_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt);
+
+static void ldap_id_enumerate_timer(struct tevent_context *ev,
+ struct tevent_timer *tt,
+ struct timeval tv, void *pvt)
+{
+ struct sdap_id_ctx *ctx = talloc_get_type(pvt, struct sdap_id_ctx);
+ struct tevent_timer *timeout;
+ struct tevent_req *req;
+ int delay;
+
+ if (be_is_offline(ctx->be)) {
+ DEBUG(4, ("Backend is marked offline, retry later!\n"));
+ /* schedule starting from now, not the last run */
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT);
+ tv = tevent_timeval_current_ofs(delay, 0);
+ ldap_id_enumerate_set_timer(ctx, tv);
+ return;
+ }
+
+ req = ldap_id_enumerate_send(ev, ctx);
+ if (!req) {
+ DEBUG(1, ("Failed to schedule enumeration, retrying later!\n"));
+ /* schedule starting from now, not the last run */
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT);
+ tv = tevent_timeval_current_ofs(delay, 0);
+ ldap_id_enumerate_set_timer(ctx, tv);
+ return;
+ }
+ tevent_req_set_callback(req, ldap_id_enumerate_reschedule, ctx);
+
+ /* if enumeration takes so long, either we try to enumerate too
+ * frequently, or something went seriously wrong */
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT);
+ tv = tevent_timeval_current_ofs(delay, 0);
+ timeout = tevent_add_timer(ctx->be->ev, req, tv,
+ ldap_id_enumerate_timeout, req);
+ return;
+}
+
+static void ldap_id_enumerate_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct sdap_id_ctx *ctx = tevent_req_callback_data(req,
+ struct sdap_id_ctx);
+ int delay;
+
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT);
+ DEBUG(1, ("Enumeration timed out! Timeout too small? (%ds)!\n", delay));
+
+ tv = tevent_timeval_current_ofs(delay, 0);
+ ldap_id_enumerate_set_timer(ctx, tv);
+
+ talloc_zfree(req);
+}
+
+static void ldap_id_enumerate_reschedule(struct tevent_req *req)
+{
+ struct sdap_id_ctx *ctx = tevent_req_callback_data(req,
+ struct sdap_id_ctx);
+ enum tevent_req_state tstate;
+ uint64_t err;
+ struct timeval tv;
+ int delay;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ /* On error schedule starting from now, not the last run */
+ tv = tevent_timeval_current();
+ } else {
+ tv = ctx->last_enum;
+ }
+ talloc_zfree(req);
+
+ delay = dp_opt_get_int(ctx->opts->basic, SDAP_ENUM_REFRESH_TIMEOUT);
+ tv = tevent_timeval_add(&tv, delay, 0);
+ ldap_id_enumerate_set_timer(ctx, tv);
+}
+
+
+
+int ldap_id_enumerate_set_timer(struct sdap_id_ctx *ctx, struct timeval tv)
+{
+ struct tevent_timer *enum_task;
+
+ DEBUG(6, ("Scheduling next enumeration at %ld.%ld\n",
+ (long)tv.tv_sec, (long)tv.tv_usec));
+
+ enum_task = tevent_add_timer(ctx->be->ev, ctx,
+ tv, ldap_id_enumerate_timer, ctx);
+ if (!enum_task) {
+ DEBUG(0, ("FATAL: failed to setup enumeration task!\n"));
+ return EFAULT;
+ }
+
+ return EOK;
+}
+
+
+struct global_enum_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+
+ bool purge;
+};
+
+static struct tevent_req *enum_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ bool purge);
+static void ldap_id_enum_users_done(struct tevent_req *subreq);
+static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ bool purge);
+static void ldap_id_enum_groups_done(struct tevent_req *subreq);
+static void ldap_id_enum_cleanup_done(struct tevent_req *subreq);
+
+static struct tevent_req *ldap_id_enumerate_send(struct tevent_context *ev,
+ struct sdap_id_ctx *ctx)
+{
+ struct global_enum_state *state;
+ struct tevent_req *req, *subreq;
+ int t;
+
+ req = tevent_req_create(ctx, &state, struct global_enum_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+
+ ctx->last_enum = tevent_timeval_current();
+
+ t = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT);
+ if ((ctx->last_purge.tv_sec + t) < ctx->last_enum.tv_sec) {
+ state->purge = true;
+ } else {
+ state->purge = false;
+ }
+
+ subreq = enum_users_send(state, ev, ctx, state->purge);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, ldap_id_enum_users_done, req);
+
+ return req;
+}
+
+static void ldap_id_enum_users_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct global_enum_state *state = tevent_req_data(req,
+ struct global_enum_state);
+ enum tevent_req_state tstate;
+ uint64_t err = 0;
+
+ if (tevent_req_is_error(subreq, &tstate, &err)) {
+ if (tstate != TEVENT_REQ_USER_ERROR) {
+ err = EIO;
+ }
+ if (err != ENOENT) {
+ goto fail;
+ }
+ }
+ talloc_zfree(subreq);
+
+ subreq = enum_groups_send(state, state->ev, state->ctx, state->purge);
+ if (!subreq) {
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, ldap_id_enum_groups_done, req);
+
+ return;
+
+fail:
+ if (err) {
+ DEBUG(9, ("User enumeration failed with: (%d)[%s]\n",
+ (int)err, strerror(err)));
+
+ if (sdap_check_gssapi_reconnect(state->ctx)) {
+ talloc_zfree(state->ctx->gsh);
+ subreq = enum_users_send(state, state->ev, state->ctx, state->purge);
+ if (subreq != NULL) {
+ tevent_req_set_callback(subreq, ldap_id_enum_users_done, req);
+ return;
+ }
+ }
+ sdap_mark_offline(state->ctx);
+ }
+
+ DEBUG(1, ("Failed to enumerate users, retrying later!\n"));
+ tevent_req_done(req);
+}
+
+static void ldap_id_enum_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct global_enum_state *state = tevent_req_data(req,
+ struct global_enum_state);
+ enum tevent_req_state tstate;
+ uint64_t err = 0;
+
+ if (tevent_req_is_error(subreq, &tstate, &err)) {
+ if (tstate != TEVENT_REQ_USER_ERROR) {
+ err = EIO;
+ }
+ if (err != ENOENT) {
+ goto fail;
+ }
+ }
+ talloc_zfree(subreq);
+
+ if (state->purge) {
+
+ subreq = ldap_id_cleanup_send(state, state->ev, state->ctx);
+ if (!subreq) {
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, ldap_id_enum_cleanup_done, req);
+
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+
+fail:
+ /* check if credentials are expired otherwise go offline on failures */
+ if (sdap_check_gssapi_reconnect(state->ctx)) {
+ talloc_zfree(state->ctx->gsh);
+ subreq = enum_groups_send(state, state->ev, state->ctx, state->purge);
+ if (subreq != NULL) {
+ tevent_req_set_callback(subreq, ldap_id_enum_groups_done, req);
+ return;
+ }
+ }
+ sdap_mark_offline(state->ctx);
+ DEBUG(1, ("Failed to enumerate groups (%d [%s]), retrying later!\n",
+ (int)err, strerror(err)));
+ tevent_req_done(req);
+}
+
+static void ldap_id_enum_cleanup_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ talloc_zfree(subreq);
+ tevent_req_done(req);
+}
+
+
+/* ==User-Enumeration===================================================== */
+
+struct enum_users_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+
+ char *filter;
+ const char **attrs;
+};
+
+static void enum_users_connect_done(struct tevent_req *subreq);
+static void enum_users_op_done(struct tevent_req *subreq);
+
+static struct tevent_req *enum_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ bool purge)
+{
+ struct tevent_req *req, *subreq;
+ struct enum_users_state *state;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct enum_users_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+
+ if (ctx->max_user_timestamp && !purge) {
+
+ state->filter = talloc_asprintf(state,
+ "(&(%s=*)(objectclass=%s)(%s>=%s)(!(%s=%s)))",
+ ctx->opts->user_map[SDAP_AT_USER_NAME].name,
+ ctx->opts->user_map[SDAP_OC_USER].name,
+ ctx->opts->user_map[SDAP_AT_USER_MODSTAMP].name,
+ ctx->max_user_timestamp,
+ ctx->opts->user_map[SDAP_AT_USER_MODSTAMP].name,
+ ctx->max_user_timestamp);
+ } else {
+ state->filter = talloc_asprintf(state,
+ "(&(%s=*)(objectclass=%s))",
+ ctx->opts->user_map[SDAP_AT_USER_NAME].name,
+ ctx->opts->user_map[SDAP_OC_USER].name);
+ }
+ if (!state->filter) {
+ DEBUG(2, ("Failed to build filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ /* TODO: handle attrs_type */
+ ret = build_attrs_from_map(state, ctx->opts->user_map,
+ SDAP_OPTS_USER, &state->attrs);
+ if (ret != EOK) goto fail;
+
+ if (!sdap_connected(ctx)) {
+
+ if (ctx->gsh) talloc_zfree(ctx->gsh);
+
+ /* FIXME: add option to decide if tls should be used
+ * or SASL/GSSAPI, etc ... */
+ subreq = sdap_cli_connect_send(state, ev, ctx->opts,
+ ctx->be, ctx->service,
+ &ctx->rootDSE);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, enum_users_connect_done, req);
+
+ return req;
+ }
+
+ subreq = sdap_get_users_send(state, state->ev,
+ state->ctx->be->domain,
+ state->ctx->be->sysdb,
+ state->ctx->opts,
+ state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, enum_users_op_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void enum_users_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct enum_users_state *state = tevent_req_data(req,
+ struct enum_users_state);
+ int ret;
+
+ ret = sdap_cli_connect_recv(subreq, state->ctx,
+ &state->ctx->gsh, &state->ctx->rootDSE);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENOTSUP) {
+ DEBUG(0, ("Authentication mechanism not Supported by server"));
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_get_users_send(state, state->ev,
+ state->ctx->be->domain,
+ state->ctx->be->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, enum_users_op_done, req);
+}
+
+static void enum_users_op_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct enum_users_state *state = tevent_req_data(req,
+ struct enum_users_state);
+ char *timestamp;
+ int ret;
+
+ ret = sdap_get_users_recv(subreq, state, &timestamp);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (timestamp) {
+ talloc_zfree(state->ctx->max_user_timestamp);
+ state->ctx->max_user_timestamp = talloc_steal(state->ctx, timestamp);
+ }
+
+ DEBUG(4, ("Users higher timestamp: [%s]\n",
+ state->ctx->max_user_timestamp));
+
+ tevent_req_done(req);
+}
+
+/* =Group-Enumeration===================================================== */
+
+struct enum_groups_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+
+ char *filter;
+ const char **attrs;
+};
+
+static void enum_groups_connect_done(struct tevent_req *subreq);
+static void enum_groups_op_done(struct tevent_req *subreq);
+
+static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ bool purge)
+{
+ struct tevent_req *req, *subreq;
+ struct enum_groups_state *state;
+ const char *attr_name;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct enum_groups_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+
+ attr_name = ctx->opts->group_map[SDAP_AT_GROUP_NAME].name;
+
+ if (ctx->max_group_timestamp && !purge) {
+
+ state->filter = talloc_asprintf(state,
+ "(&(%s=*)(objectclass=%s)(%s>=%s)(!(%s=%s)))",
+ ctx->opts->group_map[SDAP_AT_GROUP_NAME].name,
+ ctx->opts->group_map[SDAP_OC_GROUP].name,
+ ctx->opts->group_map[SDAP_AT_GROUP_MODSTAMP].name,
+ ctx->max_group_timestamp,
+ ctx->opts->group_map[SDAP_AT_GROUP_MODSTAMP].name,
+ ctx->max_group_timestamp);
+ } else {
+ state->filter = talloc_asprintf(state,
+ "(&(%s=*)(objectclass=%s))",
+ ctx->opts->group_map[SDAP_AT_GROUP_NAME].name,
+ ctx->opts->group_map[SDAP_OC_GROUP].name);
+ }
+ if (!state->filter) {
+ DEBUG(2, ("Failed to build filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ /* TODO: handle attrs_type */
+ ret = build_attrs_from_map(state, ctx->opts->group_map,
+ SDAP_OPTS_GROUP, &state->attrs);
+ if (ret != EOK) goto fail;
+
+ if (!sdap_connected(ctx)) {
+
+ if (ctx->gsh) talloc_zfree(ctx->gsh);
+
+ /* FIXME: add option to decide if tls should be used
+ * or SASL/GSSAPI, etc ... */
+ subreq = sdap_cli_connect_send(state, ev, ctx->opts,
+ ctx->be, ctx->service,
+ &ctx->rootDSE);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, enum_groups_connect_done, req);
+
+ return req;
+ }
+
+ subreq = sdap_get_groups_send(state, state->ev,
+ state->ctx->be->domain,
+ state->ctx->be->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, enum_groups_op_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void enum_groups_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct enum_groups_state *state = tevent_req_data(req,
+ struct enum_groups_state);
+ int ret;
+
+ ret = sdap_cli_connect_recv(subreq, state->ctx,
+ &state->ctx->gsh, &state->ctx->rootDSE);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENOTSUP) {
+ DEBUG(0, ("Authentication mechanism not Supported by server"));
+ }
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_get_groups_send(state, state->ev,
+ state->ctx->be->domain,
+ state->ctx->be->sysdb,
+ state->ctx->opts, state->ctx->gsh,
+ state->attrs, state->filter);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, enum_groups_op_done, req);
+}
+
+static void enum_groups_op_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct enum_groups_state *state = tevent_req_data(req,
+ struct enum_groups_state);
+ char *timestamp;
+ int ret;
+
+ ret = sdap_get_groups_recv(subreq, state, &timestamp);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (timestamp) {
+ talloc_zfree(state->ctx->max_group_timestamp);
+ state->ctx->max_group_timestamp = talloc_steal(state->ctx, timestamp);
+ }
+
+ DEBUG(4, ("Groups higher timestamp: [%s]\n",
+ state->ctx->max_group_timestamp));
+
+ tevent_req_done(req);
+}
+
diff --git a/src/providers/ldap/ldap_init.c b/src/providers/ldap/ldap_init.c
new file mode 100644
index 000000000..b1f053fbc
--- /dev/null
+++ b/src/providers/ldap/ldap_init.c
@@ -0,0 +1,179 @@
+/*
+ SSSD
+
+ LDAP Provider Initialization functions
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+
+ Copyright (C) 2009 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/child_common.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async_private.h"
+
+static void sdap_shutdown(struct be_req *req);
+
+/* Id Handler */
+struct bet_ops sdap_id_ops = {
+ .handler = sdap_account_info_handler,
+ .finalize = sdap_shutdown
+};
+
+/* Auth Handler */
+struct bet_ops sdap_auth_ops = {
+ .handler = sdap_pam_auth_handler,
+ .finalize = sdap_shutdown
+};
+
+/* Chpass Handler */
+struct bet_ops sdap_chpass_ops = {
+ .handler = sdap_pam_chpass_handler,
+ .finalize = sdap_shutdown
+};
+
+int sssm_ldap_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_data)
+{
+ struct sdap_id_ctx *ctx;
+ const char *urls;
+ int ret;
+
+ ctx = talloc_zero(bectx, struct sdap_id_ctx);
+ if (!ctx) return ENOMEM;
+
+ ctx->be = bectx;
+
+ ret = ldap_get_options(ctx, bectx->cdb,
+ bectx->conf_path, &ctx->opts);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ urls = dp_opt_get_string(ctx->opts->basic, SDAP_URI);
+ if (!urls) {
+ DEBUG(0, ("Missing ldap_uri\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = sdap_service_init(ctx, ctx->be, "LDAP", urls, &ctx->service);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to initialize failover service!\n"));
+ goto done;
+ }
+
+ ret = setup_tls_config(ctx->opts->basic);
+ if (ret != EOK) {
+ DEBUG(1, ("setup_tls_config failed [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ ret = sdap_id_setup_tasks(ctx);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ ret = setup_child(ctx);
+ if (ret != EOK) {
+ DEBUG(1, ("setup_child failed [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ *ops = &sdap_id_ops;
+ *pvt_data = ctx;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(ctx);
+ }
+ return ret;
+}
+
+int sssm_ldap_auth_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_data)
+{
+ struct sdap_auth_ctx *ctx;
+ const char *urls;
+ int ret;
+
+ ctx = talloc(bectx, struct sdap_auth_ctx);
+ if (!ctx) return ENOMEM;
+
+ ctx->be = bectx;
+
+ ret = ldap_get_options(ctx, bectx->cdb,
+ bectx->conf_path, &ctx->opts);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ urls = dp_opt_get_string(ctx->opts->basic, SDAP_URI);
+ if (!urls) {
+ DEBUG(0, ("Missing ldap_uri\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = sdap_service_init(ctx, ctx->be, "LDAP", urls, &ctx->service);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to initialize failover service!\n"));
+ goto done;
+ }
+
+ ret = setup_tls_config(ctx->opts->basic);
+ if (ret != EOK) {
+ DEBUG(1, ("setup_tls_config failed [%d][%s].\n",
+ ret, strerror(ret)));
+ goto done;
+ }
+
+ *ops = &sdap_auth_ops;
+ *pvt_data = ctx;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(ctx);
+ }
+ return ret;
+}
+
+int sssm_ldap_chpass_init(struct be_ctx *bectx,
+ struct bet_ops **ops,
+ void **pvt_data)
+{
+ int ret;
+
+ ret = sssm_ldap_auth_init(bectx, ops, pvt_data);
+
+ *ops = &sdap_chpass_ops;
+
+ return ret;
+}
+
+static void sdap_shutdown(struct be_req *req)
+{
+ /* TODO: Clean up any internal data */
+ sdap_handler_done(req, DP_ERR_OK, EOK, NULL);
+}
+
diff --git a/src/providers/ldap/sdap.c b/src/providers/ldap/sdap.c
new file mode 100644
index 000000000..39c67cc92
--- /dev/null
+++ b/src/providers/ldap/sdap.c
@@ -0,0 +1,388 @@
+/*
+ SSSD
+
+ LDAP Helper routines
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com>
+
+ 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/>.
+*/
+
+#define LDAP_DEPRECATED 1
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "providers/ldap/sdap.h"
+
+/* =Retrieve-Options====================================================== */
+
+int sdap_get_map(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sdap_attr_map *def_map,
+ int num_entries,
+ struct sdap_attr_map **_map)
+{
+ struct sdap_attr_map *map;
+ int i, ret;
+
+ map = talloc_array(memctx, struct sdap_attr_map, num_entries);
+ if (!map) {
+ return ENOMEM;
+ }
+
+ for (i = 0; i < num_entries; i++) {
+
+ map[i].opt_name = def_map[i].opt_name;
+ map[i].def_name = def_map[i].def_name;
+ map[i].sys_name = def_map[i].sys_name;
+
+ ret = confdb_get_string(cdb, map, conf_path,
+ map[i].opt_name,
+ map[i].def_name,
+ &map[i].name);
+ if ((ret != EOK) || (map[i].def_name && !map[i].name)) {
+ DEBUG(0, ("Failed to retrieve value for %s\n", map[i].opt_name));
+ if (ret != EOK) {
+ talloc_zfree(map);
+ return EINVAL;
+ }
+ }
+
+ DEBUG(5, ("Option %s has value %s\n", map[i].opt_name, map[i].name));
+ }
+
+ *_map = map;
+ return EOK;
+}
+
+/* =Parse-msg============================================================= */
+
+int sdap_parse_entry(TALLOC_CTX *memctx,
+ struct sdap_handle *sh, struct sdap_msg *sm,
+ struct sdap_attr_map *map, int attrs_num,
+ struct sysdb_attrs **_attrs, char **_dn)
+{
+ struct sysdb_attrs *attrs;
+ BerElement *ber = NULL;
+ struct berval **vals;
+ struct ldb_val v;
+ char *str;
+ int lerrno;
+ int a, i, ret;
+ const char *name;
+ bool store;
+
+ lerrno = 0;
+ ldap_set_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno);
+
+ attrs = sysdb_new_attrs(memctx);
+ if (!attrs) return ENOMEM;
+
+ str = ldap_get_dn(sh->ldap, sm->msg);
+ if (!str) {
+ ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno);
+ DEBUG(1, ("ldap_get_dn failed: %d(%s)\n",
+ lerrno, ldap_err2string(lerrno)));
+ ret = EIO;
+ goto fail;
+ }
+
+ DEBUG(9, ("OriginalDN: [%s].\n", str));
+ ret = sysdb_attrs_add_string(attrs, SYSDB_ORIG_DN, str);
+ if (ret) goto fail;
+ if (_dn) {
+ *_dn = talloc_strdup(memctx, str);
+ if (!*_dn) {
+ ret = ENOMEM;
+ ldap_memfree(str);
+ goto fail;
+ }
+ }
+ ldap_memfree(str);
+
+ if (map) {
+ vals = ldap_get_values_len(sh->ldap, sm->msg, "objectClass");
+ if (!vals) {
+ DEBUG(1, ("Unknown entry type, no objectClasses found!\n"));
+ ret = EINVAL;
+ goto fail;
+ }
+
+ for (i = 0; vals[i]; i++) {
+ /* the objectclass is always the first name in the map */
+ if (strncasecmp(map[0].name,
+ vals[i]->bv_val, vals[i]->bv_len) == 0) {
+ /* ok it's an entry of the right type */
+ break;
+ }
+ }
+ if (!vals[i]) {
+ DEBUG(1, ("objectClass not matching: %s\n",
+ map[0].name));
+ ldap_value_free_len(vals);
+ ret = EINVAL;
+ goto fail;
+ }
+ ldap_value_free_len(vals);
+ }
+
+ str = ldap_first_attribute(sh->ldap, sm->msg, &ber);
+ if (!str) {
+ ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno);
+ DEBUG(1, ("Entry has no attributes [%d(%s)]!?\n",
+ lerrno, ldap_err2string(lerrno)));
+ if (map) {
+ ret = EINVAL;
+ goto fail;
+ }
+ }
+ while (str) {
+ if (map) {
+ for (a = 1; a < attrs_num; a++) {
+ /* check if this attr is valid with the chosen schema */
+ if (!map[a].name) continue;
+ /* check if it is an attr we are interested in */
+ if (strcasecmp(str, map[a].name) == 0) break;
+ }
+ /* interesting attr */
+ if (a < attrs_num) {
+ store = true;
+ name = map[a].sys_name;
+ } else {
+ store = false;
+ }
+ } else {
+ name = str;
+ store = true;
+ }
+
+ if (store) {
+ vals = ldap_get_values_len(sh->ldap, sm->msg, str);
+ if (!vals) {
+ ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno);
+ DEBUG(1, ("LDAP Library error: %d(%s)",
+ lerrno, ldap_err2string(lerrno)));
+ ret = EIO;
+ goto fail;
+ }
+ if (!vals[0]) {
+ DEBUG(1, ("Missing value after ldap_get_values() ??\n"));
+ ret = EINVAL;
+ goto fail;
+ }
+ for (i = 0; vals[i]; i++) {
+ v.data = (uint8_t *)vals[i]->bv_val;
+ v.length = vals[i]->bv_len;
+
+ ret = sysdb_attrs_add_val(attrs, name, &v);
+ if (ret) goto fail;
+ }
+ ldap_value_free_len(vals);
+ }
+
+ ldap_memfree(str);
+ str = ldap_next_attribute(sh->ldap, sm->msg, ber);
+ }
+ ber_free(ber, 0);
+
+ ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno);
+ if (lerrno) {
+ DEBUG(1, ("LDAP Library error: %d(%s)",
+ lerrno, ldap_err2string(lerrno)));
+ ret = EIO;
+ goto fail;
+ }
+
+ *_attrs = attrs;
+ return EOK;
+
+fail:
+ if (ber) ber_free(ber, 0);
+ talloc_free(attrs);
+ return ret;
+}
+
+/* This function converts an ldap message into a sysdb_attrs structure.
+ * It converts only known user attributes, the rest are ignored.
+ * If the entry is not that of an user an error is returned.
+ * The original DN is stored as an attribute named originalDN */
+
+int sdap_parse_user(TALLOC_CTX *memctx, struct sdap_options *opts,
+ struct sdap_handle *sh, struct sdap_msg *sm,
+ struct sysdb_attrs **_attrs, char **_dn)
+{
+
+ return sdap_parse_entry(memctx, sh, sm, opts->user_map,
+ SDAP_OPTS_USER, _attrs, _dn);
+}
+
+/* This function converts an ldap message into a sysdb_attrs structure.
+ * It converts only known group attributes, the rest are ignored.
+ * If the entry is not that of an user an error is returned.
+ * The original DN is stored as an attribute named originalDN */
+
+int sdap_parse_group(TALLOC_CTX *memctx, struct sdap_options *opts,
+ struct sdap_handle *sh, struct sdap_msg *sm,
+ struct sysdb_attrs **_attrs, char **_dn)
+{
+
+ return sdap_parse_entry(memctx, sh, sm, opts->group_map,
+ SDAP_OPTS_GROUP, _attrs, _dn);
+}
+
+/* =Get-DN-from-message=================================================== */
+
+int sdap_get_msg_dn(TALLOC_CTX *memctx, struct sdap_handle *sh,
+ struct sdap_msg *sm, char **_dn)
+{
+ char *str;
+ int lerrno;
+
+ lerrno = 0;
+ ldap_set_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno);
+
+ str = ldap_get_dn(sh->ldap, sm->msg);
+ if (!str) {
+ ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno);
+ DEBUG(1, ("ldap_get_dn failed: %d(%s)\n",
+ lerrno, ldap_err2string(lerrno)));
+ return EIO;
+ }
+
+ *_dn = talloc_strdup(memctx, str);
+ ldap_memfree(str);
+ if (!*_dn) return ENOMEM;
+
+ return EOK;
+}
+
+errno_t setup_tls_config(struct dp_option *basic_opts)
+{
+ int ret;
+ int ldap_opt_x_tls_require_cert;
+ const char *tls_opt;
+ tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_REQCERT);
+ if (tls_opt) {
+ if (strcasecmp(tls_opt, "never") == 0) {
+ ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_NEVER;
+ }
+ else if (strcasecmp(tls_opt, "allow") == 0) {
+ ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_ALLOW;
+ }
+ else if (strcasecmp(tls_opt, "try") == 0) {
+ ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_TRY;
+ }
+ else if (strcasecmp(tls_opt, "demand") == 0) {
+ ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_DEMAND;
+ }
+ else if (strcasecmp(tls_opt, "hard") == 0) {
+ ldap_opt_x_tls_require_cert = LDAP_OPT_X_TLS_HARD;
+ }
+ else {
+ DEBUG(1, ("Unknown value for tls_reqcert.\n"));
+ return EINVAL;
+ }
+ /* LDAP_OPT_X_TLS_REQUIRE_CERT has to be set as a global option,
+ * because the SSL/TLS context is initialized from this value. */
+ ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT,
+ &ldap_opt_x_tls_require_cert);
+ if (ret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("ldap_set_option failed: %s\n", ldap_err2string(ret)));
+ return EIO;
+ }
+ }
+
+ tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_CACERT);
+ if (tls_opt) {
+ ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, tls_opt);
+ if (ret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("ldap_set_option failed: %s\n", ldap_err2string(ret)));
+ return EIO;
+ }
+ }
+
+ tls_opt = dp_opt_get_string(basic_opts, SDAP_TLS_CACERTDIR);
+ if (tls_opt) {
+ ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTDIR, tls_opt);
+ if (ret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("ldap_set_option failed: %s\n", ldap_err2string(ret)));
+ return EIO;
+ }
+ }
+
+ return EOK;
+}
+
+
+bool sdap_rootdse_sasl_mech_is_supported(struct sysdb_attrs *rootdse,
+ const char *sasl_mech)
+{
+ struct ldb_message_element *el = NULL;
+ struct ldb_val *val;
+ int i;
+
+ if (!sasl_mech) return false;
+
+ for (i = 0; i < rootdse->num; i++) {
+ if (strcasecmp(rootdse->a[i].name, "supportedSASLMechanisms")) {
+ continue;
+ }
+ el = &rootdse->a[i];
+ break;
+ }
+
+ if (!el) {
+ /* no supported SASL Mechanism at all ? */
+ return false;
+ }
+
+ for (i = 0; i < el->num_values; i++) {
+ val = &el->values[i];
+ if (strncasecmp(sasl_mech, (const char *)val->data, val->length)) {
+ continue;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+int build_attrs_from_map(TALLOC_CTX *memctx,
+ struct sdap_attr_map *map,
+ size_t size, const char ***_attrs)
+{
+ char **attrs;
+ int i, j;
+
+ attrs = talloc_array(memctx, char *, size + 1);
+ if (!attrs) return ENOMEM;
+
+ /* first attribute is "objectclass" not the specifc one */
+ attrs[0] = talloc_strdup(memctx, "objectClass");
+ if (!attrs[0]) return ENOMEM;
+
+ /* add the others */
+ for (i = j = 1; i < size; i++) {
+ if (map[i].name) {
+ attrs[j] = map[i].name;
+ j++;
+ }
+ }
+ attrs[j] = NULL;
+
+ *_attrs = (const char **)attrs;
+
+ return EOK;
+}
+
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
new file mode 100644
index 000000000..16dbb7843
--- /dev/null
+++ b/src/providers/ldap/sdap.h
@@ -0,0 +1,258 @@
+/*
+ SSSD
+
+ LDAP Helper routines
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com>
+
+ 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 _SDAP_H_
+#define _SDAP_H_
+
+#include "providers/dp_backend.h"
+#include <ldap.h>
+#include "util/sss_ldap.h"
+
+struct sdap_msg {
+ struct sdap_msg *next;
+ LDAPMessage *msg;
+};
+
+struct sdap_op;
+
+typedef void (sdap_op_callback_t)(struct sdap_op *op,
+ struct sdap_msg *, int, void *);
+
+struct sdap_handle;
+
+struct sdap_op {
+ struct sdap_op *prev, *next;
+ struct sdap_handle *sh;
+
+ int msgid;
+ bool done;
+
+ sdap_op_callback_t *callback;
+ void *data;
+
+ struct tevent_context *ev;
+ struct sdap_msg *list;
+ struct sdap_msg *last;
+};
+
+struct fd_event_item {
+ struct fd_event_item *prev;
+ struct fd_event_item *next;
+
+ int fd;
+ struct tevent_fd *fde;
+};
+
+struct ldap_cb_data {
+ struct sdap_handle *sh;
+ struct tevent_context *ev;
+ struct fd_event_item *fd_list;
+};
+
+struct sdap_handle {
+ LDAP *ldap;
+ bool connected;
+
+#ifdef HAVE_LDAP_CONNCB
+ struct ldap_conncb *conncb;
+#else
+ struct tevent_fd *fde;
+#endif
+
+ struct sdap_op *ops;
+};
+
+struct sdap_service {
+ char *name;
+ char *uri;
+};
+
+#define SYSDB_SHADOWPW_LASTCHANGE "shadowLastChange"
+#define SYSDB_SHADOWPW_MIN "shadowMin"
+#define SYSDB_SHADOWPW_MAX "shadowMax"
+#define SYSDB_SHADOWPW_WARNING "shadowWarning"
+#define SYSDB_SHADOWPW_INACTIVE "shadowInactive"
+#define SYSDB_SHADOWPW_EXPIRE "shadowExpire"
+#define SYSDB_SHADOWPW_FLAG "shadowFlag"
+
+#define SYSDB_KRBPW_LASTCHANGE "krbLastPwdChange"
+#define SYSDB_KRBPW_EXPIRATION "krbPasswordExpiration"
+
+#define SYSDB_PWD_ATTRIBUTE "pwdAttribute"
+
+enum sdap_result {
+ SDAP_SUCCESS,
+ SDAP_NOT_FOUND,
+ SDAP_UNAVAIL,
+ SDAP_RETRY,
+ SDAP_ERROR,
+ SDAP_AUTH_SUCCESS,
+ SDAP_AUTH_FAILED,
+ SDAP_AUTH_PW_EXPIRED,
+ SDAP_ACCT_EXPIRED
+};
+
+enum sdap_basic_opt {
+ SDAP_URI = 0,
+ SDAP_SEARCH_BASE,
+ SDAP_DEFAULT_BIND_DN,
+ SDAP_DEFAULT_AUTHTOK_TYPE,
+ SDAP_DEFAULT_AUTHTOK,
+ SDAP_SEARCH_TIMEOUT,
+ SDAP_NETWORK_TIMEOUT,
+ SDAP_OPT_TIMEOUT,
+ SDAP_TLS_REQCERT,
+ SDAP_USER_SEARCH_BASE,
+ SDAP_USER_SEARCH_SCOPE,
+ SDAP_USER_SEARCH_FILTER,
+ SDAP_GROUP_SEARCH_BASE,
+ SDAP_GROUP_SEARCH_SCOPE,
+ SDAP_GROUP_SEARCH_FILTER,
+ SDAP_SCHEMA,
+ SDAP_OFFLINE_TIMEOUT,
+ SDAP_FORCE_UPPER_CASE_REALM,
+ SDAP_ENUM_REFRESH_TIMEOUT,
+ SDAP_CACHE_PURGE_TIMEOUT,
+ SDAP_ENTRY_CACHE_TIMEOUT,
+ SDAP_TLS_CACERT,
+ SDAP_TLS_CACERTDIR,
+ SDAP_ID_TLS,
+ SDAP_SASL_MECH,
+ SDAP_SASL_AUTHID,
+ SDAP_KRB5_KEYTAB,
+ SDAP_KRB5_KINIT,
+ SDAP_KRB5_REALM,
+ SDAP_PWD_POLICY,
+ SDAP_REFERRALS,
+
+ SDAP_OPTS_BASIC /* opts counter */
+};
+
+enum sdap_gen_attrs {
+ SDAP_AT_ENTRY_USN = 0,
+ SDAP_AT_LAST_USN,
+
+ SDAP_AT_GENERAL /* attrs counter */
+};
+
+/* the objectclass must be the first attribute.
+ * Functions depend on this */
+enum sdap_user_attrs {
+ SDAP_OC_USER = 0,
+ SDAP_AT_USER_NAME,
+ SDAP_AT_USER_PWD,
+ SDAP_AT_USER_UID,
+ SDAP_AT_USER_GID,
+ SDAP_AT_USER_GECOS,
+ SDAP_AT_USER_HOME,
+ SDAP_AT_USER_SHELL,
+ SDAP_AT_USER_PRINC,
+ SDAP_AT_USER_FULLNAME,
+ SDAP_AT_USER_MEMBEROF,
+ SDAP_AT_USER_UUID,
+ SDAP_AT_USER_MODSTAMP,
+ SDAP_AT_SP_LSTCHG,
+ SDAP_AT_SP_MIN,
+ SDAP_AT_SP_MAX,
+ SDAP_AT_SP_WARN,
+ SDAP_AT_SP_INACT,
+ SDAP_AT_SP_EXPIRE,
+ SDAP_AT_SP_FLAG,
+ SDAP_AT_KP_LASTCHANGE,
+ SDAP_AT_KP_EXPIRATION,
+ SDAP_AT_PWD_ATTRIBUTE,
+
+ SDAP_OPTS_USER /* attrs counter */
+};
+
+#define SDAP_FIRST_EXTRA_USER_AT SDAP_AT_SP_LSTCHG
+
+/* the objectclass must be the first attribute.
+ * Functions depend on this */
+enum sdap_group_attrs {
+ SDAP_OC_GROUP = 0,
+ SDAP_AT_GROUP_NAME,
+ SDAP_AT_GROUP_PWD,
+ SDAP_AT_GROUP_GID,
+ SDAP_AT_GROUP_MEMBER,
+ SDAP_AT_GROUP_UUID,
+ SDAP_AT_GROUP_MODSTAMP,
+
+ SDAP_OPTS_GROUP /* attrs counter */
+};
+
+struct sdap_attr_map {
+ const char *opt_name;
+ const char *def_name;
+ const char *sys_name;
+ char *name;
+};
+
+struct sdap_options {
+ struct dp_option *basic;
+ struct sdap_attr_map *gen_map;
+ struct sdap_attr_map *user_map;
+ struct sdap_attr_map *group_map;
+
+ /* supported schema types */
+ enum schema_type {
+ SDAP_SCHEMA_RFC2307 = 1, /* memberUid = uid */
+ SDAP_SCHEMA_RFC2307BIS = 2, /* member = dn */
+ SDAP_SCHEMA_IPA_V1 = 3, /* member/memberof */
+ SDAP_SCHEMA_AD = 4 /* AD's member/memberof */
+ } schema_type;
+
+ struct ldb_dn *users_base;
+ struct ldb_dn *groups_base;
+};
+
+int sdap_get_map(TALLOC_CTX *memctx,
+ struct confdb_ctx *cdb,
+ const char *conf_path,
+ struct sdap_attr_map *def_map,
+ int num_entries,
+ struct sdap_attr_map **_map);
+
+int sdap_parse_entry(TALLOC_CTX *memctx,
+ struct sdap_handle *sh, struct sdap_msg *sm,
+ struct sdap_attr_map *map, int attrs_num,
+ struct sysdb_attrs **_attrs, char **_dn);
+
+int sdap_parse_user(TALLOC_CTX *memctx, struct sdap_options *opts,
+ struct sdap_handle *sh, struct sdap_msg *sm,
+ struct sysdb_attrs **_attrs, char **_dn);
+
+int sdap_parse_group(TALLOC_CTX *memctx, struct sdap_options *opts,
+ struct sdap_handle *sh, struct sdap_msg *sm,
+ struct sysdb_attrs **_attrs, char **_dn);
+
+int sdap_get_msg_dn(TALLOC_CTX *memctx, struct sdap_handle *sh,
+ struct sdap_msg *sm, char **_dn);
+
+errno_t setup_tls_config(struct dp_option *basic_opts);
+
+bool sdap_rootdse_sasl_mech_is_supported(struct sysdb_attrs *rootdse,
+ const char *sasl_mech);
+
+int build_attrs_from_map(TALLOC_CTX *memctx,
+ struct sdap_attr_map *map,
+ size_t size, const char ***_attrs);
+#endif /* _SDAP_H_ */
diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c
new file mode 100644
index 000000000..959c08a65
--- /dev/null
+++ b/src/providers/ldap/sdap_async.c
@@ -0,0 +1,1018 @@
+/*
+ SSSD
+
+ Async LDAP Helper routines
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009
+
+ 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 <ctype.h>
+#include "util/util.h"
+#include "providers/ldap/sdap_async_private.h"
+
+#define REALM_SEPARATOR '@'
+#define REPLY_REALLOC_INCREMENT 10
+
+void make_realm_upper_case(const char *upn)
+{
+ char *c;
+
+ c = strchr(upn, REALM_SEPARATOR);
+ if (c == NULL) {
+ DEBUG(9, ("No realm delimiter found in upn [%s].\n", upn));
+ return;
+ }
+
+ while(*(++c) != '\0') {
+ c[0] = toupper(*c);
+ }
+
+ return;
+}
+
+/* ==LDAP-Memory-Handling================================================= */
+
+static int lmsg_destructor(void *mem)
+{
+ ldap_msgfree((LDAPMessage *)mem);
+ return 0;
+}
+
+static int sdap_msg_attach(TALLOC_CTX *memctx, LDAPMessage *msg)
+{
+ void *h;
+
+ if (!msg) return EINVAL;
+
+ h = sss_mem_attach(memctx, msg, lmsg_destructor);
+ if (!h) return ENOMEM;
+
+ return EOK;
+}
+
+/* ==sdap-hanlde-utility-functions======================================== */
+
+static inline void sdap_handle_release(struct sdap_handle *sh);
+static int sdap_handle_destructor(void *mem);
+
+struct sdap_handle *sdap_handle_create(TALLOC_CTX *memctx)
+{
+ struct sdap_handle *sh;
+
+ sh = talloc_zero(memctx, struct sdap_handle);
+ if (!sh) return NULL;
+
+ talloc_set_destructor((TALLOC_CTX *)sh, sdap_handle_destructor);
+
+ return sh;
+}
+
+static int sdap_handle_destructor(void *mem)
+{
+ struct sdap_handle *sh = talloc_get_type(mem, struct sdap_handle);
+
+ sdap_handle_release(sh);
+
+ return 0;
+}
+
+static void sdap_handle_release(struct sdap_handle *sh)
+{
+ DEBUG(8, ("Trace: sh[%p], connected[%d], ops[%p], ldap[%p]\n",
+ sh, (int)sh->connected, sh->ops, sh->ldap));
+
+ if (sh->connected) {
+ struct sdap_op *op;
+
+#ifdef HAVE_LDAP_CONNCB
+ /* remove all related fd events from the event loop */
+ talloc_zfree(sh->conncb->lc_arg);
+#else
+ talloc_zfree(sh->fde);
+#endif
+
+ while (sh->ops) {
+ op = sh->ops;
+ op->callback(op, NULL, EIO, op->data);
+ /* calling the callback may result in freeing the op */
+ /* check if it is still the same or avoid freeing */
+ if (op == sh->ops) talloc_free(op);
+ }
+
+ if (sh->ldap) {
+ ldap_unbind_ext(sh->ldap, NULL, NULL);
+ }
+#ifdef HAVE_LDAP_CONNCB
+ talloc_zfree(sh->conncb);
+#endif
+ sh->connected = false;
+ sh->ldap = NULL;
+ sh->ops = NULL;
+ }
+}
+
+/* ==Parse-Results-And-Handle-Disconnections============================== */
+static void sdap_process_message(struct tevent_context *ev,
+ struct sdap_handle *sh, LDAPMessage *msg);
+static void sdap_process_result(struct tevent_context *ev, void *pvt);
+static void sdap_process_next_reply(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt);
+
+static void sdap_ldap_result(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *pvt)
+{
+ sdap_process_result(ev, pvt);
+}
+
+static void sdap_ldap_next_result(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ sdap_process_result(ev, pvt);
+}
+
+static void sdap_process_result(struct tevent_context *ev, void *pvt)
+{
+ struct sdap_handle *sh = talloc_get_type(pvt, struct sdap_handle);
+ struct timeval no_timeout = {0, 0};
+ struct tevent_timer *te;
+ LDAPMessage *msg;
+ int ret;
+
+ DEBUG(8, ("Trace: sh[%p], connected[%d], ops[%p], ldap[%p]\n",
+ sh, (int)sh->connected, sh->ops, sh->ldap));
+
+ if (!sh->connected || !sh->ldap) {
+ DEBUG(2, ("ERROR: LDAP connection is not connected!\n"));
+ return;
+ }
+
+ ret = ldap_result(sh->ldap, LDAP_RES_ANY, 0, &no_timeout, &msg);
+ if (ret == 0) {
+ /* this almost always means we have reached the end of
+ * the list of received messages */
+ DEBUG(8, ("Trace: ldap_result found nothing!\n"));
+ return;
+ }
+
+ if (ret == -1) {
+ DEBUG(4, ("ldap_result gave -1, something bad happend!\n"));
+ sdap_handle_release(sh);
+ return;
+ }
+
+ /* We don't know if this will be the last result.
+ *
+ * important: we must do this before actually processing the message
+ * because the message processing might even free the sdap_handler
+ * so it must be the last operation.
+ * FIXME: use tevent_immediate/tevent_queues, when avilable */
+ memset(&no_timeout, 0, sizeof(struct timeval));
+
+ te = tevent_add_timer(ev, sh, no_timeout, sdap_ldap_next_result, sh);
+ if (!te) {
+ DEBUG(1, ("Failed to add critical timer to fetch next result!\n"));
+ }
+
+ /* now process this message */
+ sdap_process_message(ev, sh, msg);
+}
+
+/* process a messgae calling the right operation callback.
+ * msg is completely taken care of (including freeeing it)
+ * NOTE: this function may even end up freeing the sdap_handle
+ * so sdap_hanbdle must not be used after this function is called
+ */
+static void sdap_process_message(struct tevent_context *ev,
+ struct sdap_handle *sh, LDAPMessage *msg)
+{
+ struct sdap_msg *reply;
+ struct sdap_op *op;
+ int msgid;
+ int msgtype;
+ int ret;
+
+ msgid = ldap_msgid(msg);
+ if (msgid == -1) {
+ DEBUG(2, ("can't fire callback, message id invalid!\n"));
+ ldap_msgfree(msg);
+ return;
+ }
+
+ msgtype = ldap_msgtype(msg);
+
+ for (op = sh->ops; op; op = op->next) {
+ if (op->msgid == msgid) break;
+ }
+
+ if (op == NULL) {
+ DEBUG(2, ("Unmatched msgid, discarding message (type: %0x)\n",
+ msgtype));
+ ldap_msgfree(msg);
+ return;
+ }
+
+ /* shouldn't happen */
+ if (op->done) {
+ DEBUG(2, ("Operation [%p] already handled (type: %0x)\n", op, msgtype));
+ ldap_msgfree(msg);
+ return;
+ }
+
+ switch (msgtype) {
+ case LDAP_RES_SEARCH_ENTRY:
+ /* go and process entry */
+ break;
+
+ case LDAP_RES_SEARCH_REFERENCE:
+ /* more ops to come with this msgid */
+ /* just ignore */
+ ldap_msgfree(msg);
+ return;
+
+ case LDAP_RES_BIND:
+ case LDAP_RES_SEARCH_RESULT:
+ case LDAP_RES_MODIFY:
+ case LDAP_RES_ADD:
+ case LDAP_RES_DELETE:
+ case LDAP_RES_MODDN:
+ case LDAP_RES_COMPARE:
+ case LDAP_RES_EXTENDED:
+ case LDAP_RES_INTERMEDIATE:
+ /* no more results expected with this msgid */
+ op->done = true;
+ break;
+
+ default:
+ /* unkwon msg type ?? */
+ DEBUG(1, ("Couldn't figure out the msg type! [%0x]\n", msgtype));
+ ldap_msgfree(msg);
+ return;
+ }
+
+ reply = talloc_zero(op, struct sdap_msg);
+ if (!reply) {
+ ldap_msgfree(msg);
+ ret = ENOMEM;
+ } else {
+ reply->msg = msg;
+ ret = sdap_msg_attach(reply, msg);
+ if (ret != EOK) {
+ ldap_msgfree(msg);
+ talloc_zfree(reply);
+ }
+ }
+
+ if (op->list) {
+ /* list exist, queue it */
+
+ op->last->next = reply;
+ op->last = reply;
+
+ } else {
+ /* create list, then call callback */
+ op->list = op->last = reply;
+
+ /* must be the last operation as it may end up freeing all memory
+ * including all ops handlers */
+ op->callback(op, reply, ret, op->data);
+ }
+}
+
+static void sdap_unlock_next_reply(struct sdap_op *op)
+{
+ struct timeval tv;
+ struct tevent_timer *te;
+ struct sdap_msg *next_reply;
+
+ if (op->list) {
+ next_reply = op->list->next;
+ /* get rid of the previous reply, it has been processed already */
+ talloc_zfree(op->list);
+ op->list = next_reply;
+ }
+
+ /* if there are still replies to parse, queue a new operation */
+ if (op->list) {
+ /* use a very small timeout, so that fd operations have a chance to be
+ * served while processing a long reply */
+ tv = tevent_timeval_current();
+
+ /* wait 5 microsecond */
+ tv.tv_usec += 5;
+ tv.tv_sec += tv.tv_usec / 1000000;
+ tv.tv_usec = tv.tv_usec % 1000000;
+
+ te = tevent_add_timer(op->ev, op, tv,
+ sdap_process_next_reply, op);
+ if (!te) {
+ DEBUG(1, ("Failed to add critical timer for next reply!\n"));
+ op->callback(op, NULL, EFAULT, op->data);
+ }
+ }
+}
+
+static void sdap_process_next_reply(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct sdap_op *op = talloc_get_type(pvt, struct sdap_op);
+
+ op->callback(op, op->list, EOK, op->data);
+}
+
+#ifdef HAVE_LDAP_CONNCB
+int sdap_ldap_connect_callback_add(LDAP *ld, Sockbuf *sb, LDAPURLDesc *srv,
+ struct sockaddr *addr, struct ldap_conncb *ctx)
+{
+ int ret;
+ ber_socket_t ber_fd;
+ struct fd_event_item *fd_event_item;
+ struct ldap_cb_data *cb_data = talloc_get_type(ctx->lc_arg,
+ struct ldap_cb_data);
+
+ ret = ber_sockbuf_ctrl(sb, LBER_SB_OPT_GET_FD, &ber_fd);
+ if (ret == -1) {
+ DEBUG(1, ("ber_sockbuf_ctrl failed.\n"));
+ return EINVAL;
+ }
+ DEBUG(9, ("New LDAP connection to [%s] with fd [%d].\n",
+ ldap_url_desc2str(srv), ber_fd));
+
+ fd_event_item = talloc_zero(cb_data, struct fd_event_item);
+ if (fd_event_item == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ return ENOMEM;
+ }
+
+ fd_event_item->fde = tevent_add_fd(cb_data->ev, fd_event_item, ber_fd,
+ TEVENT_FD_READ, sdap_ldap_result,
+ cb_data->sh);
+ if (fd_event_item->fde == NULL) {
+ DEBUG(1, ("tevent_add_fd failed.\n"));
+ talloc_free(fd_event_item);
+ return ENOMEM;
+ }
+ fd_event_item->fd = ber_fd;
+
+ DLIST_ADD(cb_data->fd_list, fd_event_item);
+
+ return LDAP_SUCCESS;
+}
+
+void sdap_ldap_connect_callback_del(LDAP *ld, Sockbuf *sb,
+ struct ldap_conncb *ctx)
+{
+ int ret;
+ ber_socket_t ber_fd;
+ struct fd_event_item *fd_event_item;
+ struct ldap_cb_data *cb_data = talloc_get_type(ctx->lc_arg,
+ struct ldap_cb_data);
+
+ if (sb == NULL || cb_data == NULL) {
+ return;
+ }
+
+ ret = ber_sockbuf_ctrl(sb, LBER_SB_OPT_GET_FD, &ber_fd);
+ if (ret == -1) {
+ DEBUG(1, ("ber_sockbuf_ctrl failed.\n"));
+ return;
+ }
+ DEBUG(9, ("Closing LDAP connection with fd [%d].\n", ber_fd));
+
+ DLIST_FOR_EACH(fd_event_item, cb_data->fd_list) {
+ if (fd_event_item->fd == ber_fd) {
+ break;
+ }
+ }
+ if (fd_event_item == NULL) {
+ DEBUG(1, ("No event for fd [%d] found.\n", ber_fd));
+ return;
+ }
+
+ DLIST_REMOVE(cb_data->fd_list, fd_event_item);
+ talloc_zfree(fd_event_item);
+
+ return;
+}
+
+#else
+
+static int get_fd_from_ldap(LDAP *ldap, int *fd)
+{
+ int ret;
+
+ ret = ldap_get_option(ldap, LDAP_OPT_DESC, fd);
+ if (ret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("Failed to get fd from ldap!!\n"));
+ *fd = -1;
+ return EIO;
+ }
+
+ return EOK;
+}
+
+int sdap_install_ldap_callbacks(struct sdap_handle *sh,
+ struct tevent_context *ev)
+{
+ int fd;
+ int ret;
+
+ ret = get_fd_from_ldap(sh->ldap, &fd);
+ if (ret) return ret;
+
+ sh->fde = tevent_add_fd(ev, sh, fd, TEVENT_FD_READ, sdap_ldap_result, sh);
+ if (!sh->fde) return ENOMEM;
+
+ DEBUG(8, ("Trace: sh[%p], connected[%d], ops[%p], fde[%p], ldap[%p]\n",
+ sh, (int)sh->connected, sh->ops, sh->fde, sh->ldap));
+
+ return EOK;
+}
+
+#endif
+
+
+/* ==LDAP-Operations-Helpers============================================== */
+
+static int sdap_op_destructor(void *mem)
+{
+ struct sdap_op *op = (struct sdap_op *)mem;
+
+ DLIST_REMOVE(op->sh->ops, op);
+
+ if (op->done) return 0;
+
+ /* we don't check the result here, if a message was really abandoned,
+ * hopefully the server will get an abandon.
+ * If the operation was already fully completed, this is going to be
+ * just a noop */
+ ldap_abandon_ext(op->sh->ldap, op->msgid, NULL, NULL);
+
+ return 0;
+}
+
+static void sdap_op_timeout(struct tevent_req *req)
+{
+ struct sdap_op *op = tevent_req_callback_data(req, struct sdap_op);
+
+ /* should never happen, but just in case */
+ if (op->done) {
+ DEBUG(2, ("Timeout happened after op was finished !?\n"));
+ return;
+ }
+
+ /* signal the caller that we have a timeout */
+ op->callback(op, NULL, ETIMEDOUT, op->data);
+}
+
+int sdap_op_add(TALLOC_CTX *memctx, struct tevent_context *ev,
+ struct sdap_handle *sh, int msgid,
+ sdap_op_callback_t *callback, void *data,
+ int timeout, struct sdap_op **_op)
+{
+ struct sdap_op *op;
+
+ op = talloc_zero(memctx, struct sdap_op);
+ if (!op) return ENOMEM;
+
+ op->sh = sh;
+ op->msgid = msgid;
+ op->callback = callback;
+ op->data = data;
+ op->ev = ev;
+
+ /* check if we need to set a timeout */
+ if (timeout) {
+ struct tevent_req *req;
+ struct timeval tv;
+
+ tv = tevent_timeval_current();
+ tv = tevent_timeval_add(&tv, timeout, 0);
+
+ /* allocate on op, so when it get freed the timeout is removed */
+ req = tevent_wakeup_send(op, ev, tv);
+ if (!req) {
+ talloc_zfree(op);
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req, sdap_op_timeout, op);
+ }
+
+ DLIST_ADD(sh->ops, op);
+
+ talloc_set_destructor((TALLOC_CTX *)op, sdap_op_destructor);
+
+ *_op = op;
+ return EOK;
+}
+
+/* ==Modify-Password====================================================== */
+
+struct sdap_exop_modify_passwd_state {
+ struct sdap_handle *sh;
+
+ struct sdap_op *op;
+
+ int result;
+ char *user_error_message;
+};
+
+static void sdap_exop_modify_passwd_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt);
+
+struct tevent_req *sdap_exop_modify_passwd_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ char *user_dn,
+ char *password,
+ char *new_password)
+{
+ struct tevent_req *req = NULL;
+ struct sdap_exop_modify_passwd_state *state;
+ int ret;
+ BerElement *ber = NULL;
+ struct berval *bv = NULL;
+ int msgid;
+ LDAPControl *request_controls[2];
+
+ req = tevent_req_create(memctx, &state,
+ struct sdap_exop_modify_passwd_state);
+ if (!req) return NULL;
+
+ state->sh = sh;
+ state->user_error_message = NULL;
+
+ ber = ber_alloc_t( LBER_USE_DER );
+ if (ber == NULL) {
+ DEBUG(7, ("ber_alloc_t failed.\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ ret = ber_printf( ber, "{tststs}", LDAP_TAG_EXOP_MODIFY_PASSWD_ID,
+ user_dn,
+ LDAP_TAG_EXOP_MODIFY_PASSWD_OLD, password,
+ LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, new_password);
+ if (ret == -1) {
+ DEBUG(1, ("ber_printf failed.\n"));
+ ber_free(ber, 1);
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ ret = ber_flatten(ber, &bv);
+ ber_free(ber, 1);
+ if (ret == -1) {
+ DEBUG(1, ("ber_flatten failed.\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST,
+ 0, NULL, 0, &request_controls[0]);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(1, ("sss_ldap_control_create failed.\n"));
+ goto fail;
+ }
+ request_controls[1] = NULL;
+
+ DEBUG(4, ("Executing extended operation\n"));
+
+ ret = ldap_extended_operation(state->sh->ldap, LDAP_EXOP_MODIFY_PASSWD,
+ bv, request_controls, NULL, &msgid);
+ ber_bvfree(bv);
+ ldap_control_free(request_controls[0]);
+ if (ret == -1 || msgid == -1) {
+ DEBUG(1, ("ldap_extended_operation failed\n"));
+ goto fail;
+ }
+ DEBUG(8, ("ldap_extended_operation sent, msgid = %d\n", msgid));
+
+ /* FIXME: get timeouts from configuration, for now 5 secs. */
+ ret = sdap_op_add(state, ev, state->sh, msgid,
+ sdap_exop_modify_passwd_done, req, 5, &state->op);
+ if (ret) {
+ DEBUG(1, ("Failed to set up operation!\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ tevent_req_error(req, EIO);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sdap_exop_modify_passwd_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct sdap_exop_modify_passwd_state *state = tevent_req_data(req,
+ struct sdap_exop_modify_passwd_state);
+ char *errmsg = NULL;
+ int ret;
+ LDAPControl **response_controls = NULL;
+ int c;
+ ber_int_t pp_grace;
+ ber_int_t pp_expire;
+ LDAPPasswordPolicyError pp_error;
+
+ if (error) {
+ tevent_req_error(req, error);
+ return;
+ }
+
+ ret = ldap_parse_result(state->sh->ldap, reply->msg,
+ &state->result, NULL, &errmsg, NULL,
+ &response_controls, 0);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(2, ("ldap_parse_result failed (%d)\n", state->op->msgid));
+ ret = EIO;
+ goto done;
+ }
+
+ if (response_controls == NULL) {
+ DEBUG(5, ("Server returned no controls.\n"));
+ } else {
+ for (c = 0; response_controls[c] != NULL; c++) {
+ DEBUG(9, ("Server returned control [%s].\n",
+ response_controls[c]->ldctl_oid));
+ if (strcmp(response_controls[c]->ldctl_oid,
+ LDAP_CONTROL_PASSWORDPOLICYRESPONSE) == 0) {
+ ret = ldap_parse_passwordpolicy_control(state->sh->ldap,
+ response_controls[c],
+ &pp_expire, &pp_grace,
+ &pp_error);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(1, ("ldap_parse_passwordpolicy_control failed.\n"));
+ ret = EIO;
+ goto done;
+ }
+
+ DEBUG(7, ("Password Policy Response: expire [%d] grace [%d] "
+ "error [%s].\n", pp_expire, pp_grace,
+ ldap_passwordpolicy_err2txt(pp_error)));
+ }
+ }
+ }
+
+ if (state->result != LDAP_SUCCESS) {
+ state->user_error_message = talloc_strdup(state, errmsg);
+ if (state->user_error_message == NULL) {
+ DEBUG(1, ("talloc_strdup failed.\n"));
+ }
+ }
+
+ DEBUG(3, ("ldap_extended_operation result: %s(%d), %s\n",
+ ldap_err2string(state->result), state->result, errmsg));
+
+ ret = LDAP_SUCCESS;
+done:
+ ldap_controls_free(response_controls);
+ ldap_memfree(errmsg);
+
+ if (ret == LDAP_SUCCESS) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, ret);
+ }
+}
+
+int sdap_exop_modify_passwd_recv(struct tevent_req *req,
+ TALLOC_CTX * mem_ctx,
+ enum sdap_result *result,
+ char **user_error_message)
+{
+ struct sdap_exop_modify_passwd_state *state = tevent_req_data(req,
+ struct sdap_exop_modify_passwd_state);
+
+ *result = SDAP_ERROR;
+ *user_error_message = talloc_steal(mem_ctx, state->user_error_message);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (state->result == LDAP_SUCCESS) {
+ *result = SDAP_SUCCESS;
+ }
+
+ return EOK;
+}
+
+/* ==Fetch-RootDSE============================================= */
+
+struct sdap_get_rootdse_state {
+ struct tevent_context *ev;
+ struct sdap_options *opts;
+ struct sdap_handle *sh;
+
+ struct sysdb_attrs *rootdse;
+};
+
+static void sdap_get_rootdse_done(struct tevent_req *subreq);
+
+struct tevent_req *sdap_get_rootdse_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct sdap_handle *sh)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_get_rootdse_state *state;
+
+ DEBUG(9, ("Getting rootdse\n"));
+
+ req = tevent_req_create(memctx, &state, struct sdap_get_rootdse_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sh = sh;
+ state->rootdse = NULL;
+
+ subreq = sdap_get_generic_send(state, ev, opts, sh,
+ "", LDAP_SCOPE_BASE,
+ "(objectclass=*)", NULL, NULL, 0);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_get_rootdse_done, req);
+
+ return req;
+}
+
+static void sdap_get_rootdse_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_rootdse_state *state = tevent_req_data(req,
+ struct sdap_get_rootdse_state);
+ struct sysdb_attrs **results;
+ size_t num_results;
+ int ret;
+
+ ret = sdap_get_generic_recv(subreq, state, &num_results, &results);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (num_results == 0 || !results) {
+ DEBUG(2, ("No RootDSE for server ?!\n"));
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+
+ if (num_results > 1) {
+ DEBUG(2, ("Multiple replies when searching for RootDSE ??\n"));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ state->rootdse = talloc_steal(state, results[0]);
+ talloc_zfree(results);
+
+ DEBUG(9, ("Got rootdse\n"));
+
+ tevent_req_done(req);
+}
+
+int sdap_get_rootdse_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx,
+ struct sysdb_attrs **rootdse)
+{
+ struct sdap_get_rootdse_state *state = tevent_req_data(req,
+ struct sdap_get_rootdse_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *rootdse = talloc_steal(memctx, state->rootdse);
+
+ return EOK;
+}
+
+/* ==Generic Search============================================ */
+
+struct sdap_get_generic_state {
+ struct tevent_context *ev;
+ struct sdap_options *opts;
+ struct sdap_handle *sh;
+ const char *search_base;
+ int scope;
+ const char *filter;
+ const char **attrs;
+ struct sdap_attr_map *map;
+ int map_num_attrs;
+
+ struct sdap_op *op;
+
+ size_t reply_max;
+ size_t reply_count;
+ struct sysdb_attrs **reply;
+};
+
+static errno_t add_to_reply(struct sdap_get_generic_state *state,
+ struct sysdb_attrs *msg);
+
+static void sdap_get_generic_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt);
+
+struct tevent_req *sdap_get_generic_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char *search_base,
+ int scope,
+ const char *filter,
+ const char **attrs,
+ struct sdap_attr_map *map,
+ int map_num_attrs)
+{
+ struct tevent_req *req = NULL;
+ struct sdap_get_generic_state *state = NULL;
+ int lret;
+ int ret;
+ int msgid;
+
+ req = tevent_req_create(memctx, &state, struct sdap_get_generic_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sh = sh;
+ state->search_base = search_base;
+ state->scope = scope;
+ state->filter = filter;
+ state->attrs = attrs;
+ state->map = map;
+ state->map_num_attrs = map_num_attrs;
+ state->op = NULL;
+ state->reply_max = 0;
+ state->reply_count = 0;
+ state->reply = NULL;
+
+ DEBUG(6, ("calling ldap_search_ext with [%s][%s].\n", state->filter,
+ state->search_base));
+ if (debug_level >= 7) {
+ int i;
+
+ if (state->attrs) {
+ for (i = 0; state->attrs[i]; i++) {
+ DEBUG(7, ("Requesting attrs: [%s]\n", state->attrs[i]));
+ }
+ }
+ }
+
+ lret = ldap_search_ext(state->sh->ldap, state->search_base,
+ state->scope, state->filter,
+ discard_const(state->attrs),
+ false, NULL, NULL, NULL, 0, &msgid);
+ if (lret != LDAP_SUCCESS) {
+ DEBUG(3, ("ldap_search_ext failed: %s\n", ldap_err2string(lret)));
+ ret = EIO;
+ goto fail;
+ }
+ DEBUG(8, ("ldap_search_ext called, msgid = %d\n", msgid));
+
+ ret = sdap_op_add(state, state->ev, state->sh, msgid,
+ sdap_get_generic_done, req,
+ dp_opt_get_int(state->opts->basic,
+ SDAP_SEARCH_TIMEOUT),
+ &state->op);
+ if (ret != EOK) {
+ DEBUG(1, ("Failed to set up operation!\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+
+static void sdap_get_generic_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct sdap_get_generic_state *state = tevent_req_data(req,
+ struct sdap_get_generic_state);
+ struct sysdb_attrs *attrs;
+ char *errmsg;
+ int result;
+ int ret;
+
+ if (error) {
+ tevent_req_error(req, error);
+ return;
+ }
+
+ switch (ldap_msgtype(reply->msg)) {
+ case LDAP_RES_SEARCH_REFERENCE:
+ /* ignore references for now */
+ talloc_free(reply);
+
+ /* unlock the operation so that we can proceed with the next result */
+ sdap_unlock_next_reply(state->op);
+ break;
+
+ case LDAP_RES_SEARCH_ENTRY:
+ ret = sdap_parse_entry(state, state->sh, reply,
+ state->map, state->map_num_attrs,
+ &attrs, NULL);
+ if (ret != EOK) {
+ DEBUG(1, ("sdap_parse_generic_entry failed.\n"));
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ ret = add_to_reply(state, attrs);
+ if (ret != EOK) {
+ DEBUG(1, ("add_to_reply failed.\n"));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ sdap_unlock_next_reply(state->op);
+ break;
+
+ case LDAP_RES_SEARCH_RESULT:
+ ret = ldap_parse_result(state->sh->ldap, reply->msg,
+ &result, NULL, &errmsg, NULL, NULL, 0);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(2, ("ldap_parse_result failed (%d)\n", state->op->msgid));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ DEBUG(6, ("Search result: %s(%d), %s\n",
+ ldap_err2string(result), result, errmsg));
+
+ tevent_req_done(req);
+ return;
+
+ default:
+ /* what is going on here !? */
+ tevent_req_error(req, EIO);
+ return;
+ }
+}
+
+static errno_t add_to_reply(struct sdap_get_generic_state *state,
+ struct sysdb_attrs *msg)
+{
+ if (state->reply == NULL || state->reply_max == state->reply_count) {
+ state->reply_max += REPLY_REALLOC_INCREMENT;
+ state->reply = talloc_realloc(state, state->reply,
+ struct sysdb_attrs *,
+ state->reply_max);
+ if (state->reply == NULL) {
+ DEBUG(1, ("talloc_realloc failed.\n"));
+ return ENOMEM;
+ }
+ }
+
+ state->reply[state->reply_count++] = talloc_steal(state->reply, msg);
+
+ return EOK;
+}
+
+int sdap_get_generic_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ size_t *reply_count,
+ struct sysdb_attrs ***reply)
+{
+ struct sdap_get_generic_state *state = tevent_req_data(req,
+ struct sdap_get_generic_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *reply_count = state->reply_count;
+ *reply = talloc_steal(mem_ctx, state->reply);
+
+ return EOK;
+}
+
diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h
new file mode 100644
index 000000000..3c52d236b
--- /dev/null
+++ b/src/providers/ldap/sdap_async.h
@@ -0,0 +1,126 @@
+/*
+ SSSD
+
+ Async LDAP Helper routines
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com>
+
+ 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 _SDAP_ASYNC_H_
+#define _SDAP_ASYNC_H_
+
+#include <talloc.h>
+#include <tevent.h>
+#include "providers/dp_backend.h"
+#include "providers/ldap/sdap.h"
+#include "providers/fail_over.h"
+
+struct tevent_req *sdap_connect_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ const char *uri,
+ bool use_start_tls);
+int sdap_connect_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx,
+ struct sdap_handle **sh);
+
+struct tevent_req *sdap_get_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char **attrs,
+ const char *wildcard);
+int sdap_get_users_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp);
+
+struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char **attrs,
+ const char *wildcard);
+int sdap_get_groups_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp);
+
+struct tevent_req *sdap_kinit_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ int timeout,
+ const char *keytab,
+ const char *principal,
+ const char *realm);
+int sdap_kinit_recv(struct tevent_req *req, enum sdap_result *result);
+
+struct tevent_req *sdap_auth_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ const char *sasl_mech,
+ const char *sasl_user,
+ const char *user_dn,
+ const char *authtok_type,
+ struct dp_opt_blob authtok);
+int sdap_auth_recv(struct tevent_req *req, enum sdap_result *result);
+
+struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char *name,
+ const char **grp_attrs);
+int sdap_get_initgr_recv(struct tevent_req *req);
+
+struct tevent_req *sdap_exop_modify_passwd_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ char *user_dn,
+ char *password,
+ char *new_password);
+int sdap_exop_modify_passwd_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ enum sdap_result *result,
+ char **user_error_msg);
+
+struct tevent_req *sdap_cli_connect_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct be_ctx *be,
+ struct sdap_service *service,
+ struct sysdb_attrs **rootdse);
+int sdap_cli_connect_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx,
+ struct sdap_handle **gsh,
+ struct sysdb_attrs **rootdse);
+
+struct tevent_req *sdap_get_generic_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char *search_base,
+ int scope,
+ const char *filter,
+ const char **attrs,
+ struct sdap_attr_map *map,
+ int map_num_attrs);
+int sdap_get_generic_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, size_t *reply_count,
+ struct sysdb_attrs ***reply_list);
+
+#endif /* _SDAP_ASYNC_H_ */
diff --git a/src/providers/ldap/sdap_async_accounts.c b/src/providers/ldap/sdap_async_accounts.c
new file mode 100644
index 000000000..abebe2883
--- /dev/null
+++ b/src/providers/ldap/sdap_async_accounts.c
@@ -0,0 +1,2065 @@
+/*
+ SSSD
+
+ Async LDAP Helper routines
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009
+
+ 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 "db/sysdb.h"
+#include "providers/ldap/sdap_async_private.h"
+
+/* ==Save-User-Entry====================================================== */
+
+struct sdap_save_user_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sdap_options *opts;
+
+ struct sss_domain_info *dom;
+
+ const char *name;
+ struct sysdb_attrs *attrs;
+ char *timestamp;
+};
+
+static void sdap_save_user_done(struct tevent_req *subreq);
+
+ /* FIXME: support storing additional attributes */
+
+static struct tevent_req *sdap_save_user_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sdap_options *opts,
+ struct sss_domain_info *dom,
+ struct sysdb_attrs *attrs,
+ bool is_initgr)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_save_user_state *state;
+ struct ldb_message_element *el;
+ int ret;
+ const char *pwd;
+ const char *gecos;
+ const char *homedir;
+ const char *shell;
+ long int l;
+ uid_t uid;
+ gid_t gid;
+ struct sysdb_attrs *user_attrs;
+ char *upn = NULL;
+ int i;
+ char *val = NULL;
+ int cache_timeout;
+
+ DEBUG(9, ("Save user\n"));
+
+ req = tevent_req_create(memctx, &state, struct sdap_save_user_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->dom = dom;
+ state->opts = opts;
+ state->attrs = attrs;
+ state->timestamp = NULL;
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_NAME].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+ state->name = (const char *)el->values[0].data;
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_PWD].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) pwd = NULL;
+ else pwd = (const char *)el->values[0].data;
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_GECOS].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) gecos = NULL;
+ else gecos = (const char *)el->values[0].data;
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_HOME].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) homedir = NULL;
+ else homedir = (const char *)el->values[0].data;
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_SHELL].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) shell = NULL;
+ else shell = (const char *)el->values[0].data;
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_UID].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) {
+ DEBUG(1, ("no uid provided for [%s] in domain [%s].\n",
+ state->name, dom->name));
+ ret = EINVAL;
+ goto fail;
+ }
+ errno = 0;
+ l = strtol((const char *)el->values[0].data, NULL, 0);
+ if (errno) {
+ ret = EINVAL;
+ goto fail;
+ }
+ uid = l;
+
+ /* check that the uid is valid for this domain */
+ if (OUT_OF_ID_RANGE(uid, dom->id_min, dom->id_max)) {
+ DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
+ state->name));
+ ret = EINVAL;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_GID].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) {
+ DEBUG(1, ("no gid provided for [%s] in domain [%s].\n",
+ state->name, dom->name));
+ ret = EINVAL;
+ goto fail;
+ }
+ errno = 0;
+ l = strtol((const char *)el->values[0].data, NULL, 0);
+ if (errno) {
+ ret = EINVAL;
+ goto fail;
+ }
+ gid = l;
+
+ /* check that the gid is valid for this domain */
+ if (OUT_OF_ID_RANGE(gid, dom->id_min, dom->id_max)) {
+ DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
+ state->name));
+ ret = EINVAL;
+ goto fail;
+ }
+
+ user_attrs = sysdb_new_attrs(state);
+ if (user_attrs == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(state->attrs, SYSDB_ORIG_DN, &el);
+ if (ret) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("Original DN is not available for [%s].\n", state->name));
+ } else {
+ DEBUG(7, ("Adding original DN [%s] to attributes of [%s].\n",
+ el->values[0].data, state->name));
+ ret = sysdb_attrs_add_string(user_attrs, SYSDB_ORIG_DN,
+ (const char *) el->values[0].data);
+ if (ret) {
+ goto fail;
+ }
+ }
+
+ ret = sysdb_attrs_get_el(state->attrs, SYSDB_MEMBEROF, &el);
+ if (ret) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("Original memberOf is not available for [%s].\n",
+ state->name));
+ } else {
+ DEBUG(7, ("Adding original memberOf attributes to [%s].\n",
+ state->name));
+ for (i = 0; i < el->num_values; i++) {
+ ret = sysdb_attrs_add_string(user_attrs, SYSDB_ORIG_MEMBEROF,
+ (const char *) el->values[i].data);
+ if (ret) {
+ goto fail;
+ }
+ }
+ }
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_MODSTAMP].sys_name, &el);
+ if (ret) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("Original mod-Timestamp is not available for [%s].\n",
+ state->name));
+ } else {
+ ret = sysdb_attrs_add_string(user_attrs,
+ opts->user_map[SDAP_AT_USER_MODSTAMP].sys_name,
+ (const char*)el->values[0].data);
+ if (ret) {
+ goto fail;
+ }
+ state->timestamp = talloc_strdup(state,
+ (const char*)el->values[0].data);
+ if (!state->timestamp) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ ret = sysdb_attrs_get_el(state->attrs,
+ opts->user_map[SDAP_AT_USER_PRINC].sys_name, &el);
+ if (ret) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("User principle is not available for [%s].\n", state->name));
+ } else {
+ upn = talloc_strdup(user_attrs, (const char*) el->values[0].data);
+ if (!upn) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ if (dp_opt_get_bool(opts->basic, SDAP_FORCE_UPPER_CASE_REALM)) {
+ make_realm_upper_case(upn);
+ }
+ DEBUG(7, ("Adding user principle [%s] to attributes of [%s].\n",
+ upn, state->name));
+ ret = sysdb_attrs_add_string(user_attrs, SYSDB_UPN, upn);
+ if (ret) {
+ goto fail;
+ }
+ }
+
+ for (i = SDAP_FIRST_EXTRA_USER_AT; i < SDAP_OPTS_USER; i++) {
+ ret = sysdb_attrs_get_el(state->attrs, opts->user_map[i].sys_name, &el);
+ if (ret) {
+ goto fail;
+ }
+ if (el->num_values > 0) {
+ DEBUG(9, ("Adding [%s]=[%s] to user attributes.\n",
+ opts->user_map[i].sys_name,
+ (const char*) el->values[0].data));
+ val = talloc_strdup(user_attrs, (const char*) el->values[0].data);
+ if (val == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ ret = sysdb_attrs_add_string(user_attrs,
+ opts->user_map[i].sys_name, val);
+ if (ret) {
+ goto fail;
+ }
+ }
+ }
+
+ cache_timeout = dp_opt_get_int(opts->basic, SDAP_ENTRY_CACHE_TIMEOUT);
+
+ if (is_initgr) {
+ ret = sysdb_attrs_add_time_t(user_attrs, SYSDB_INITGR_EXPIRE,
+ (cache_timeout ?
+ (time(NULL) + cache_timeout) : 0));
+ if (ret) {
+ goto fail;
+ }
+ }
+
+ DEBUG(6, ("Storing info for user %s\n", state->name));
+
+ subreq = sysdb_store_user_send(state, state->ev, state->handle,
+ state->dom, state->name, pwd,
+ uid, gid, gecos, homedir, shell,
+ user_attrs, cache_timeout);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sdap_save_user_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sdap_save_user_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_save_user_state *state = tevent_req_data(req,
+ struct sdap_save_user_state);
+ int ret;
+
+ ret = sysdb_store_user_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Failed to save user %s\n", state->name));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int sdap_save_user_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp)
+{
+ struct sdap_save_user_state *state = tevent_req_data(req,
+ struct sdap_save_user_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (timestamp) {
+ *timestamp = talloc_steal(mem_ctx, state->timestamp);
+ }
+
+ return EOK;
+}
+
+
+/* ==Generic-Function-to-save-multiple-users============================= */
+
+struct sdap_save_users_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sdap_options *opts;
+ struct sss_domain_info *dom;
+
+ struct sysdb_attrs **users;
+ int count;
+ int cur;
+
+ struct sysdb_handle *handle;
+
+ char *higher_timestamp;
+};
+
+static void sdap_save_users_trans(struct tevent_req *subreq);
+static void sdap_save_users_store(struct tevent_req *req);
+static void sdap_save_users_process(struct tevent_req *subreq);
+struct tevent_req *sdap_save_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sysdb_attrs **users,
+ int num_users)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_save_users_state *state;
+
+ req = tevent_req_create(memctx, &state, struct sdap_save_users_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sysdb = sysdb;
+ state->dom = dom;
+ state->users = users;
+ state->count = num_users;
+ state->cur = 0;
+ state->handle = NULL;
+ state->higher_timestamp = NULL;
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ tevent_req_post(req, ev);
+ return req;
+ }
+ tevent_req_set_callback(subreq, sdap_save_users_trans, req);
+
+ return req;
+}
+
+static void sdap_save_users_trans(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_save_users_state *state;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_save_users_state);
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ sdap_save_users_store(req);
+}
+
+static void sdap_save_users_store(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct sdap_save_users_state *state;
+
+ state = tevent_req_data(req, struct sdap_save_users_state);
+
+ subreq = sdap_save_user_send(state, state->ev, state->handle,
+ state->opts, state->dom,
+ state->users[state->cur], false);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_save_users_process, req);
+}
+
+static void sdap_save_users_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_save_users_state *state;
+ char *timestamp = NULL;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_save_users_state);
+
+ ret = sdap_save_user_recv(subreq, state, &timestamp);
+ talloc_zfree(subreq);
+
+ /* Do not fail completely on errors.
+ * Just report the failure to save and go on */
+ if (ret) {
+ DEBUG(2, ("Failed to store user %d. Ignoring.\n", state->cur));
+ } else {
+ DEBUG(9, ("User %d processed!\n", state->cur));
+ }
+
+ if (timestamp) {
+ if (state->higher_timestamp) {
+ if (strcmp(timestamp, state->higher_timestamp) > 0) {
+ talloc_zfree(state->higher_timestamp);
+ state->higher_timestamp = timestamp;
+ } else {
+ talloc_zfree(timestamp);
+ }
+ } else {
+ state->higher_timestamp = timestamp;
+ }
+ }
+
+ state->cur++;
+ if (state->cur < state->count) {
+ sdap_save_users_store(req);
+ } else {
+ subreq = sysdb_transaction_commit_send(state, state->ev,
+ state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ /* sysdb_transaction_complete will call tevent_req_done(req) */
+ tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+ }
+}
+
+static int sdap_save_users_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp)
+{
+ struct sdap_save_users_state *state = tevent_req_data(req,
+ struct sdap_save_users_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (timestamp) {
+ *timestamp = talloc_steal(mem_ctx, state->higher_timestamp);
+ }
+
+ return EOK;
+}
+
+
+/* ==Search-Users-with-filter============================================= */
+
+struct sdap_get_users_state {
+ struct tevent_context *ev;
+ struct sdap_options *opts;
+ struct sdap_handle *sh;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ const char **attrs;
+ const char *filter;
+
+ char *higher_timestamp;
+ struct sysdb_attrs **users;
+ size_t count;
+};
+
+static void sdap_get_users_process(struct tevent_req *subreq);
+static void sdap_get_users_done(struct tevent_req *subreq);
+
+struct tevent_req *sdap_get_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char **attrs,
+ const char *filter)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_get_users_state *state;
+
+ req = tevent_req_create(memctx, &state, struct sdap_get_users_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->dom = dom;
+ state->sh = sh;
+ state->sysdb = sysdb;
+ state->filter = filter;
+ state->attrs = attrs;
+ state->higher_timestamp = NULL;
+ state->users = NULL;
+ state->count = 0;
+
+ subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh,
+ dp_opt_get_string(state->opts->basic,
+ SDAP_USER_SEARCH_BASE),
+ LDAP_SCOPE_SUBTREE,
+ state->filter, state->attrs,
+ state->opts->user_map, SDAP_OPTS_USER);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_get_users_process, req);
+
+ return req;
+}
+
+static void sdap_get_users_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_users_state *state = tevent_req_data(req,
+ struct sdap_get_users_state);
+ int ret;
+
+ ret = sdap_get_generic_recv(subreq, state,
+ &state->count, &state->users);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(6, ("Search for users, returned %d results.\n", state->count));
+
+ if (state->count == 0) {
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+
+ subreq = sdap_save_users_send(state, state->ev, state->dom,
+ state->sysdb, state->opts,
+ state->users, state->count);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_users_done, req);
+}
+
+static void sdap_get_users_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_users_state *state = tevent_req_data(req,
+ struct sdap_get_users_state);
+ int ret;
+
+ DEBUG(9, ("Saving %d Users - Done\n", state->count));
+
+ ret = sdap_save_users_recv(subreq, state, &state->higher_timestamp);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Failed to store users.\n"));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sdap_get_users_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp)
+{
+ struct sdap_get_users_state *state = tevent_req_data(req,
+ struct sdap_get_users_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (timestamp) {
+ *timestamp = talloc_steal(mem_ctx, state->higher_timestamp);
+ }
+
+ return EOK;
+}
+
+/* ==Group-Parsing Routines=============================================== */
+
+struct sdap_orig_entry_state {
+ int done;
+};
+
+static void sdap_find_entry_by_origDN_done(struct tevent_req *req)
+{
+ struct sdap_orig_entry_state *state = tevent_req_callback_data(req,
+ struct sdap_orig_entry_state);
+ state->done = 1;
+}
+
+/* WARNING: this is a sync routine for now */
+static int sdap_find_entry_by_origDN(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *orig_dn,
+ char **localdn)
+{
+ struct tevent_req *req;
+ struct sdap_orig_entry_state *state;
+ static const char *attrs[] = { NULL };
+ struct ldb_dn *base_dn;
+ char *filter;
+ struct ldb_message **msgs;
+ size_t num_msgs;
+ int ret;
+
+ state = talloc_zero(memctx, struct sdap_orig_entry_state);
+ if (!state) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ filter = talloc_asprintf(state, "%s=%s", SYSDB_ORIG_DN, orig_dn);
+ if (!filter) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ base_dn = sysdb_domain_dn(sysdb_handle_get_ctx(handle),
+ state, domain->name);
+ if (!base_dn) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ req = sysdb_search_entry_send(state, ev, handle, base_dn,
+ LDB_SCOPE_SUBTREE, filter, attrs);
+ if (!req) {
+ ret = ENOMEM;
+ goto done;
+ }
+ tevent_req_set_callback(req, sdap_find_entry_by_origDN_done, state);
+
+ /* WARNING: SYNC LOOP HERE */
+ tevent_loop_allow_nesting(ev);
+ while (state->done == 0) {
+ tevent_loop_once(ev);
+ }
+
+ ret = sysdb_search_entry_recv(req, state, &num_msgs, &msgs);
+ if (ret) {
+ goto done;
+ }
+ if (num_msgs != 1) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ *localdn = talloc_strdup(memctx, ldb_dn_get_linearized(msgs[0]->dn));
+ if (!*localdn) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ talloc_zfree(state);
+ return ret;
+}
+
+static int sdap_fill_memberships(struct sysdb_attrs *group_attrs,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sdap_options *opts,
+ struct sss_domain_info *domain,
+ struct ldb_val *values,
+ int num_values)
+{
+ struct ldb_message_element *el;
+ int i, j;
+ int ret;
+
+ switch (opts->schema_type) {
+ case SDAP_SCHEMA_RFC2307:
+ DEBUG(9, ("[RFC2307 Schema]\n"));
+
+ ret = sysdb_attrs_users_from_ldb_vals(group_attrs, SYSDB_MEMBER,
+ domain->name,
+ values, num_values);
+ if (ret) {
+ goto done;
+ }
+
+ break;
+
+ case SDAP_SCHEMA_RFC2307BIS:
+ case SDAP_SCHEMA_IPA_V1:
+ case SDAP_SCHEMA_AD:
+ DEBUG(9, ("[IPA or AD Schema]\n"));
+
+ ret = sysdb_attrs_get_el(group_attrs, SYSDB_MEMBER, &el);
+ if (ret) {
+ goto done;
+ }
+
+ /* Just allocate both big enough to contain all members for now */
+ el->values = talloc_realloc(el, el->values, struct ldb_val,
+ el->num_values + num_values);
+ if (!el->values) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ for (i = 0, j = el->num_values; i < num_values; i++) {
+
+ /* sync search entry with this as origDN */
+ ret = sdap_find_entry_by_origDN(el->values, ev,
+ handle, domain,
+ (char *)values[i].data,
+ (char **)&el->values[j].data);
+ if (ret != EOK) {
+ if (ret != ENOENT) {
+ goto done;
+ }
+
+ DEBUG(7, (" member #%d (%s): not found!\n",
+ i, (char *)values[i].data));
+ } else {
+ DEBUG(7, (" member #%d (%s): [%s]\n",
+ i, (char *)values[i].data,
+ (char *)el->values[j].data));
+
+ el->values[j].length = strlen((char *)el->values[j].data);
+ j++;
+ }
+ }
+ el->num_values = j;
+
+ break;
+
+ default:
+ DEBUG(0, ("FATAL ERROR: Unhandled schema type! (%d)\n",
+ opts->schema_type));
+ ret = EFAULT;
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ return ret;
+}
+
+/* ==Save-Group-Entry===================================================== */
+
+struct sdap_save_group_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sdap_options *opts;
+
+ struct sss_domain_info *dom;
+
+ const char *name;
+ char *timestamp;
+};
+
+static void sdap_save_group_done(struct tevent_req *subreq);
+
+ /* FIXME: support non legacy */
+ /* FIXME: support storing additional attributes */
+
+static struct tevent_req *sdap_save_group_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sdap_options *opts,
+ struct sss_domain_info *dom,
+ struct sysdb_attrs *attrs,
+ bool store_members)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_save_group_state *state;
+ struct ldb_message_element *el;
+ struct sysdb_attrs *group_attrs;
+ long int l;
+ gid_t gid;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct sdap_save_group_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->dom = dom;
+ state->opts = opts;
+ state->timestamp = NULL;
+
+ ret = sysdb_attrs_get_el(attrs,
+ opts->group_map[SDAP_AT_GROUP_NAME].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+ state->name = (const char *)el->values[0].data;
+
+ ret = sysdb_attrs_get_el(attrs,
+ opts->group_map[SDAP_AT_GROUP_GID].sys_name, &el);
+ if (ret) goto fail;
+ if (el->num_values == 0) {
+ DEBUG(1, ("no gid provided for [%s] in domain [%s].\n",
+ state->name, dom->name));
+ ret = EINVAL;
+ goto fail;
+ }
+ errno = 0;
+ l = strtol((const char *)el->values[0].data, NULL, 0);
+ if (errno) {
+ ret = EINVAL;
+ goto fail;
+ }
+ gid = l;
+
+ /* check that the gid is valid for this domain */
+ if (OUT_OF_ID_RANGE(gid, dom->id_min, dom->id_max)) {
+ DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
+ state->name));
+ ret = EINVAL;
+ goto fail;
+ }
+
+ group_attrs = sysdb_new_attrs(state);
+ if (!group_attrs) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(attrs, SYSDB_ORIG_DN, &el);
+ if (ret) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("Original DN is not available for [%s].\n", state->name));
+ } else {
+ DEBUG(7, ("Adding original DN [%s] to attributes of [%s].\n",
+ el->values[0].data, state->name));
+ ret = sysdb_attrs_add_string(group_attrs, SYSDB_ORIG_DN,
+ (const char *)el->values[0].data);
+ if (ret) {
+ goto fail;
+ }
+ }
+
+ ret = sysdb_attrs_get_el(attrs,
+ opts->group_map[SDAP_AT_GROUP_MODSTAMP].sys_name, &el);
+ if (ret) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("Original mod-Timestamp is not available for [%s].\n",
+ state->name));
+ } else {
+ ret = sysdb_attrs_add_string(group_attrs,
+ opts->group_map[SDAP_AT_GROUP_MODSTAMP].sys_name,
+ (const char*)el->values[0].data);
+ if (ret) {
+ goto fail;
+ }
+ state->timestamp = talloc_strdup(state,
+ (const char*)el->values[0].data);
+ if (!state->timestamp) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (store_members) {
+ ret = sysdb_attrs_get_el(attrs,
+ opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, &el);
+ if (ret != EOK) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("No members for group [%s]\n", state->name));
+
+ } else {
+ DEBUG(7, ("Adding member users to group [%s]\n", state->name));
+
+ ret = sdap_fill_memberships(group_attrs, ev, handle, opts, dom,
+ el->values, el->num_values);
+ if (ret) {
+ goto fail;
+ }
+ }
+ }
+
+ DEBUG(6, ("Storing info for group %s\n", state->name));
+
+ subreq = sysdb_store_group_send(state, state->ev,
+ state->handle, state->dom,
+ state->name, gid,
+ group_attrs,
+ dp_opt_get_int(opts->basic,
+ SDAP_ENTRY_CACHE_TIMEOUT));
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sdap_save_group_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sdap_save_group_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_save_group_state *state = tevent_req_data(req,
+ struct sdap_save_group_state);
+ int ret;
+
+ ret = sysdb_store_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Failed to save group %s [%d]\n", state->name, ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int sdap_save_group_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp)
+{
+ struct sdap_save_group_state *state = tevent_req_data(req,
+ struct sdap_save_group_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if ( timestamp ) {
+ *timestamp = talloc_steal(mem_ctx, state->timestamp);
+ }
+
+ return EOK;
+}
+
+
+/* ==Save-Group-Memebrs=================================================== */
+
+struct sdap_save_grpmem_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct sdap_options *opts;
+
+ struct sss_domain_info *dom;
+
+ const char *name;
+};
+
+static void sdap_save_grpmem_done(struct tevent_req *subreq);
+
+ /* FIXME: support non legacy */
+ /* FIXME: support storing additional attributes */
+
+static struct tevent_req *sdap_save_grpmem_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct sdap_options *opts,
+ struct sss_domain_info *dom,
+ struct sysdb_attrs *attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_save_grpmem_state *state;
+ struct ldb_message_element *el;
+ struct sysdb_attrs *group_attrs = NULL;
+ int ret;
+
+ req = tevent_req_create(memctx, &state, struct sdap_save_grpmem_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->dom = dom;
+ state->opts = opts;
+
+ ret = sysdb_attrs_get_string(attrs,
+ opts->group_map[SDAP_AT_GROUP_NAME].sys_name,
+ &state->name);
+ if (ret != EOK) {
+ goto fail;
+ }
+
+ ret = sysdb_attrs_get_el(attrs,
+ opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, &el);
+ if (ret != EOK) {
+ goto fail;
+ }
+ if (el->num_values == 0) {
+ DEBUG(7, ("No members for group [%s]\n", state->name));
+
+ } else {
+ DEBUG(7, ("Adding member users to group [%s]\n", state->name));
+
+ group_attrs = sysdb_new_attrs(state);
+ if (!group_attrs) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ ret = sdap_fill_memberships(group_attrs, ev, handle, opts, dom,
+ el->values, el->num_values);
+ if (ret) {
+ goto fail;
+ }
+ }
+
+ DEBUG(6, ("Storing members for group %s\n", state->name));
+
+ subreq = sysdb_store_group_send(state, state->ev,
+ state->handle, state->dom,
+ state->name, 0,
+ group_attrs,
+ dp_opt_get_int(opts->basic,
+ SDAP_ENTRY_CACHE_TIMEOUT));
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sdap_save_grpmem_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sdap_save_grpmem_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_save_grpmem_state *state = tevent_req_data(req,
+ struct sdap_save_grpmem_state);
+ int ret;
+
+ ret = sysdb_store_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Failed to save group members for %s [%d]\n",
+ state->name, ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int sdap_save_grpmem_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* ==Generic-Function-to-save-multiple-groups============================= */
+
+struct sdap_save_groups_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sdap_options *opts;
+ struct sss_domain_info *dom;
+
+ struct sysdb_attrs **groups;
+ int count;
+ int cur;
+ bool twopass;
+
+ struct sysdb_handle *handle;
+
+ char *higher_timestamp;
+};
+
+static void sdap_save_groups_trans(struct tevent_req *subreq);
+static void sdap_save_groups_save(struct tevent_req *req);
+static void sdap_save_groups_loop(struct tevent_req *subreq);
+static void sdap_save_groups_mem_save(struct tevent_req *req);
+static void sdap_save_groups_mem_loop(struct tevent_req *subreq);
+struct tevent_req *sdap_save_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sysdb_attrs **groups,
+ int num_groups)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_save_groups_state *state;
+
+ req = tevent_req_create(memctx, &state, struct sdap_save_groups_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sysdb = sysdb;
+ state->dom = dom;
+ state->groups = groups;
+ state->count = num_groups;
+ state->cur = 0;
+ state->handle = NULL;
+ state->higher_timestamp = NULL;
+
+ switch (opts->schema_type) {
+ case SDAP_SCHEMA_RFC2307:
+ state->twopass = false;
+ break;
+
+ case SDAP_SCHEMA_RFC2307BIS:
+ case SDAP_SCHEMA_IPA_V1:
+ case SDAP_SCHEMA_AD:
+ state->twopass = true;
+ break;
+
+ default:
+ tevent_req_error(req, EINVAL);
+ tevent_req_post(req, ev);
+ return req;
+ }
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ tevent_req_post(req, ev);
+ return req;
+ }
+ tevent_req_set_callback(subreq, sdap_save_groups_trans, req);
+
+ return req;
+}
+
+static void sdap_save_groups_trans(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_save_groups_state *state;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_save_groups_state);
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ sdap_save_groups_save(req);
+}
+
+static void sdap_save_groups_save(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct sdap_save_groups_state *state;
+
+ state = tevent_req_data(req, struct sdap_save_groups_state);
+
+ /* if 2 pass savemembers = false */
+ subreq = sdap_save_group_send(state, state->ev, state->handle,
+ state->opts, state->dom,
+ state->groups[state->cur],
+ (!state->twopass));
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_save_groups_loop, req);
+}
+
+static void sdap_save_groups_loop(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_save_groups_state *state;
+ char *timestamp = NULL;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_save_groups_state);
+
+ ret = sdap_save_group_recv(subreq, state, &timestamp);
+ talloc_zfree(subreq);
+
+ /* Do not fail completely on errors.
+ * Just report the failure to save and go on */
+ if (ret) {
+ DEBUG(2, ("Failed to store group %d. Ignoring.\n", state->cur));
+ } else {
+ DEBUG(9, ("Group %d processed!\n", state->cur));
+ }
+
+ if (timestamp) {
+ if (state->higher_timestamp) {
+ if (strcmp(timestamp, state->higher_timestamp) > 0) {
+ talloc_zfree(state->higher_timestamp);
+ state->higher_timestamp = timestamp;
+ } else {
+ talloc_zfree(timestamp);
+ }
+ } else {
+ state->higher_timestamp = timestamp;
+ }
+ }
+
+ state->cur++;
+ if (state->cur < state->count) {
+
+ sdap_save_groups_save(req);
+
+ } else if (state->twopass) {
+
+ state->cur = 0;
+ sdap_save_groups_mem_save(req);
+
+ } else {
+
+ subreq = sysdb_transaction_commit_send(state, state->ev,
+ state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ /* sysdb_transaction_complete will call tevent_req_done(req) */
+ tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+ }
+}
+
+static void sdap_save_groups_mem_save(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct sdap_save_groups_state *state;
+
+ state = tevent_req_data(req, struct sdap_save_groups_state);
+
+ subreq = sdap_save_grpmem_send(state, state->ev, state->handle,
+ state->opts, state->dom,
+ state->groups[state->cur]);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_save_groups_mem_loop, req);
+}
+
+static void sdap_save_groups_mem_loop(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_save_groups_state *state;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_save_groups_state);
+
+ ret = sdap_save_grpmem_recv(subreq);
+ talloc_zfree(subreq);
+
+ /* Do not fail completely on errors.
+ * Just report the failure to save and go on */
+ if (ret) {
+ DEBUG(2, ("Failed to store group %d. Ignoring.\n", state->cur));
+ }
+
+ state->cur++;
+ if (state->cur < state->count) {
+
+ sdap_save_groups_mem_save(req);
+
+ } else {
+
+ subreq = sysdb_transaction_commit_send(state, state->ev,
+ state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ /* sysdb_transaction_complete will call tevent_req_done(req) */
+ tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+ }
+}
+
+static int sdap_save_groups_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp)
+{
+ struct sdap_save_groups_state *state = tevent_req_data(req,
+ struct sdap_save_groups_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (timestamp) {
+ *timestamp = talloc_steal(mem_ctx, state->higher_timestamp);
+ }
+
+ return EOK;
+}
+
+
+/* ==Search-Groups-with-filter============================================ */
+
+struct sdap_get_groups_state {
+ struct tevent_context *ev;
+ struct sdap_options *opts;
+ struct sdap_handle *sh;
+ struct sss_domain_info *dom;
+ struct sysdb_ctx *sysdb;
+ const char **attrs;
+ const char *filter;
+
+ char *higher_timestamp;
+ struct sysdb_attrs **groups;
+ size_t count;
+};
+
+static void sdap_get_groups_process(struct tevent_req *subreq);
+static void sdap_get_groups_done(struct tevent_req *subreq);
+
+struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char **attrs,
+ const char *filter)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_get_groups_state *state;
+
+ req = tevent_req_create(memctx, &state, struct sdap_get_groups_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->dom = dom;
+ state->sh = sh;
+ state->sysdb = sysdb;
+ state->filter = filter;
+ state->attrs = attrs;
+ state->higher_timestamp = NULL;
+ state->groups = NULL;
+ state->count = 0;
+
+ subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh,
+ dp_opt_get_string(state->opts->basic,
+ SDAP_GROUP_SEARCH_BASE),
+ LDAP_SCOPE_SUBTREE,
+ state->filter, state->attrs,
+ state->opts->group_map, SDAP_OPTS_GROUP);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_get_groups_process, req);
+
+ return req;
+}
+
+static void sdap_get_groups_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_groups_state *state = tevent_req_data(req,
+ struct sdap_get_groups_state);
+ int ret;
+
+ ret = sdap_get_generic_recv(subreq, state,
+ &state->count, &state->groups);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(6, ("Search for groups, returned %d results.\n", state->count));
+
+ if (state->count == 0) {
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+
+ subreq = sdap_save_groups_send(state, state->ev, state->dom,
+ state->sysdb, state->opts,
+ state->groups, state->count);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_groups_done, req);
+}
+
+static void sdap_get_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_groups_state *state = tevent_req_data(req,
+ struct sdap_get_groups_state);
+ int ret;
+
+ DEBUG(9, ("Saving %d Groups - Done\n", state->count));
+
+ ret = sdap_save_groups_recv(subreq, state, &state->higher_timestamp);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Failed to store groups.\n"));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sdap_get_groups_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, char **timestamp)
+{
+ struct sdap_get_groups_state *state = tevent_req_data(req,
+ struct sdap_get_groups_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (timestamp) {
+ *timestamp = talloc_steal(mem_ctx, state->higher_timestamp);
+ }
+
+ return EOK;
+}
+
+
+/* ==Initgr-call-(groups-a-user-is-member-of)-RFC2307-Classic/BIS========= */
+
+struct sdap_initgr_rfc2307_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sdap_options *opts;
+ struct sss_domain_info *dom;
+ struct sdap_handle *sh;
+
+ struct sdap_op *op;
+};
+
+static void sdap_initgr_rfc2307_process(struct tevent_req *subreq);
+static void sdap_initgr_rfc2307_done(struct tevent_req *subreq);
+struct tevent_req *sdap_initgr_rfc2307_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *dom,
+ struct sdap_handle *sh,
+ const char *base_dn,
+ const char *name,
+ const char **grp_attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_initgr_rfc2307_state *state;
+ const char *filter;
+
+ req = tevent_req_create(memctx, &state, struct sdap_initgr_rfc2307_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sysdb = sysdb;
+ state->dom = dom;
+ state->sh = sh;
+ state->op = NULL;
+
+ filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))",
+ opts->group_map[SDAP_AT_GROUP_MEMBER].name,
+ name, opts->group_map[SDAP_OC_GROUP].name);
+ if (!filter) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ subreq = sdap_get_generic_send(state, state->ev, state->opts,
+ state->sh, base_dn, LDAP_SCOPE_SUBTREE,
+ filter, grp_attrs,
+ state->opts->group_map, SDAP_OPTS_GROUP);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_initgr_rfc2307_process, req);
+
+ return req;
+}
+
+static void sdap_initgr_rfc2307_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_initgr_rfc2307_state *state;
+ struct sysdb_attrs **groups;
+ size_t count;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_initgr_rfc2307_state);
+
+ ret = sdap_get_generic_recv(subreq, state, &count, &groups);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (count == 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = sdap_save_groups_send(state, state->ev, state->dom,
+ state->sysdb, state->opts,
+ groups, count);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_initgr_rfc2307_done, req);
+}
+
+static void sdap_initgr_rfc2307_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+
+ ret = sdap_save_groups_recv(subreq, NULL, NULL);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int sdap_initgr_rfc2307_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* ==Initgr-call-(groups-a-user-is-member-of)-nested-groups=============== */
+
+struct sdap_initgr_nested_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sdap_options *opts;
+ struct sss_domain_info *dom;
+ struct sdap_handle *sh;
+
+ const char **grp_attrs;
+
+ char *filter;
+ char **group_dns;
+ int count;
+ int cur;
+
+ struct sdap_op *op;
+
+ struct sysdb_attrs **groups;
+ int groups_cur;
+};
+
+static void sdap_initgr_nested_search(struct tevent_req *subreq);
+static void sdap_initgr_nested_store(struct tevent_req *req);
+static void sdap_initgr_nested_done(struct tevent_req *subreq);
+static struct tevent_req *sdap_initgr_nested_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *dom,
+ struct sdap_handle *sh,
+ struct sysdb_attrs *user,
+ const char **grp_attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_initgr_nested_state *state;
+ struct ldb_message_element *el;
+ int i, ret;
+
+ req = tevent_req_create(memctx, &state, struct sdap_initgr_nested_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sysdb = sysdb;
+ state->dom = dom;
+ state->sh = sh;
+ state->grp_attrs = grp_attrs;
+ state->op = NULL;
+
+ state->filter = talloc_asprintf(state, "(objectclass=%s)",
+ opts->group_map[SDAP_OC_GROUP].name);
+ if (!state->filter) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ /* TODO: test rootDSE for deref support and use it if available */
+ /* TODO: or test rootDSE for ASQ support and use it if available */
+
+ ret = sysdb_attrs_get_el(user, SYSDB_MEMBEROF, &el);
+ if (ret || !el || el->num_values == 0) {
+ DEBUG(4, ("User entry lacks original memberof ?\n"));
+ /* user with no groups ? */
+ tevent_req_error(req, ENOENT);
+ tevent_req_post(req, ev);
+ }
+ state->count = el->num_values;
+
+ state->groups = talloc_zero_array(state, struct sysdb_attrs *,
+ state->count + 1);;
+ if (!state->groups) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ state->groups_cur = 0;
+
+ state->group_dns = talloc_array(state, char *, state->count + 1);
+ if (!state->group_dns) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ for (i = 0; i < state->count; i++) {
+ state->group_dns[i] = talloc_strdup(state->group_dns,
+ (char *)el->values[i].data);
+ if (!state->group_dns[i]) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ }
+ state->group_dns[i] = NULL; /* terminate */
+ state->cur = 0;
+
+ subreq = sdap_get_generic_send(state, state->ev, state->opts, state->sh,
+ state->group_dns[state->cur],
+ LDAP_SCOPE_BASE,
+ state->filter, state->grp_attrs,
+ state->opts->group_map, SDAP_OPTS_GROUP);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_initgr_nested_search, req);
+
+ return req;
+}
+
+static void sdap_initgr_nested_search(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ struct sdap_initgr_nested_state *state;
+ struct sysdb_attrs **groups;
+ size_t count;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct sdap_initgr_nested_state);
+
+ ret = sdap_get_generic_recv(subreq, state, &count, &groups);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (count == 1) {
+ state->groups[state->groups_cur] = groups[0];
+ state->groups_cur++;
+ } else {
+ DEBUG(2, ("Search for group %s, returned %d results. Skipping\n",
+ state->group_dns[state->cur], count));
+ }
+
+ state->cur++;
+ if (state->cur < state->count) {
+ subreq = sdap_get_generic_send(state, state->ev,
+ state->opts, state->sh,
+ state->group_dns[state->cur],
+ LDAP_SCOPE_BASE,
+ state->filter, state->grp_attrs,
+ state->opts->group_map,
+ SDAP_OPTS_GROUP);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_initgr_nested_search, req);
+ } else {
+ sdap_initgr_nested_store(req);
+ }
+}
+
+static void sdap_initgr_nested_store(struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ struct sdap_initgr_nested_state *state;
+
+ state = tevent_req_data(req, struct sdap_initgr_nested_state);
+
+ subreq = sdap_save_groups_send(state, state->ev, state->dom,
+ state->sysdb, state->opts,
+ state->groups, state->groups_cur);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_initgr_nested_done, req);
+}
+
+static void sdap_initgr_nested_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req;
+ int ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+
+ ret = sdap_save_groups_recv(subreq, NULL, NULL);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int sdap_initgr_nested_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* ==Initgr-call-(groups-a-user-is-member-of)============================= */
+
+struct sdap_get_initgr_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sdap_options *opts;
+ struct sss_domain_info *dom;
+ struct sdap_handle *sh;
+ const char *name;
+ const char **grp_attrs;
+
+ struct sysdb_attrs *orig_user;
+
+ struct sysdb_handle *handle;
+};
+
+static void sdap_get_initgr_user(struct tevent_req *subreq);
+static void sdap_get_initgr_store(struct tevent_req *subreq);
+static void sdap_get_initgr_commit(struct tevent_req *subreq);
+static void sdap_get_initgr_process(struct tevent_req *subreq);
+static void sdap_get_initgr_done(struct tevent_req *subreq);
+
+struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sss_domain_info *dom,
+ struct sysdb_ctx *sysdb,
+ struct sdap_options *opts,
+ struct sdap_handle *sh,
+ const char *name,
+ const char **grp_attrs)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_get_initgr_state *state;
+ const char *base_dn;
+ char *filter;
+ const char **attrs;
+ int ret;
+
+ DEBUG(9, ("Retrieving info for initgroups call\n"));
+
+ req = tevent_req_create(memctx, &state, struct sdap_get_initgr_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sysdb = sysdb;
+ state->dom = dom;
+ state->sh = sh;
+ state->name = name;
+ state->grp_attrs = grp_attrs;
+ state->orig_user = NULL;
+
+ filter = talloc_asprintf(state, "(&(%s=%s)(objectclass=%s))",
+ state->opts->user_map[SDAP_AT_USER_NAME].name,
+ state->name,
+ state->opts->user_map[SDAP_OC_USER].name);
+ if (!filter) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ base_dn = dp_opt_get_string(state->opts->basic,
+ SDAP_USER_SEARCH_BASE);
+ if (!base_dn) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ ret = build_attrs_from_map(state, state->opts->user_map,
+ SDAP_OPTS_USER, &attrs);
+ if (ret) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ subreq = sdap_get_generic_send(state, state->ev,
+ state->opts, state->sh,
+ base_dn, LDAP_SCOPE_SUBTREE,
+ filter, attrs,
+ state->opts->user_map, SDAP_OPTS_USER);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_get_initgr_user, req);
+
+ return req;
+}
+
+static void sdap_get_initgr_user(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_initgr_state *state = tevent_req_data(req,
+ struct sdap_get_initgr_state);
+ struct sysdb_attrs **usr_attrs;
+ size_t count;
+ int ret;
+
+ DEBUG(9, ("Receiving info for the user\n"));
+
+ ret = sdap_get_generic_recv(subreq, state, &count, &usr_attrs);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (count != 1) {
+ DEBUG(2, ("Expected one user entry and got %d\n", count));
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+
+ state->orig_user = usr_attrs[0];
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_initgr_store, req);
+}
+
+static void sdap_get_initgr_store(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_initgr_state *state = tevent_req_data(req,
+ struct sdap_get_initgr_state);
+ int ret;
+
+ DEBUG(9, ("Storing the user\n"));
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sdap_save_user_send(state, state->ev, state->handle,
+ state->opts, state->dom,
+ state->orig_user, true);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_initgr_commit, req);
+}
+
+static void sdap_get_initgr_commit(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_initgr_state *state = tevent_req_data(req,
+ struct sdap_get_initgr_state);
+ int ret;
+
+ DEBUG(9, ("Commit change\n"));
+
+ ret = sdap_save_user_recv(subreq, NULL, NULL);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_initgr_process, req);
+}
+
+static void sdap_get_initgr_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_initgr_state *state = tevent_req_data(req,
+ struct sdap_get_initgr_state);
+ int ret;
+
+ DEBUG(9, ("Process user's groups\n"));
+
+ ret = sysdb_transaction_commit_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ switch (state->opts->schema_type) {
+ case SDAP_SCHEMA_RFC2307:
+ subreq = sdap_initgr_rfc2307_send(state, state->ev, state->opts,
+ state->sysdb, state->dom, state->sh,
+ dp_opt_get_string(state->opts->basic,
+ SDAP_GROUP_SEARCH_BASE),
+ state->name, state->grp_attrs);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_initgr_done, req);
+ break;
+
+ case SDAP_SCHEMA_RFC2307BIS:
+ case SDAP_SCHEMA_IPA_V1:
+ case SDAP_SCHEMA_AD:
+ /* TODO: AD uses a different member/memberof schema
+ * We need an AD specific call that is able to unroll
+ * nested groups by doing extensive recursive searches */
+
+ subreq = sdap_initgr_nested_send(state, state->ev, state->opts,
+ state->sysdb, state->dom, state->sh,
+ state->orig_user, state->grp_attrs);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_initgr_done, req);
+ return;
+
+ default:
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+}
+
+static void sdap_get_initgr_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_initgr_state *state = tevent_req_data(req,
+ struct sdap_get_initgr_state);
+ int ret;
+
+ DEBUG(9, ("Initgroups done\n"));
+
+ switch (state->opts->schema_type) {
+ case SDAP_SCHEMA_RFC2307:
+
+ ret = sdap_initgr_rfc2307_recv(subreq);
+ break;
+
+ case SDAP_SCHEMA_RFC2307BIS:
+ case SDAP_SCHEMA_IPA_V1:
+ case SDAP_SCHEMA_AD:
+
+ ret = sdap_initgr_nested_recv(subreq);
+ break;
+
+ default:
+
+ ret = EINVAL;
+ break;
+ }
+
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sdap_get_initgr_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
diff --git a/src/providers/ldap/sdap_async_connection.c b/src/providers/ldap/sdap_async_connection.c
new file mode 100644
index 000000000..18e47d3b7
--- /dev/null
+++ b/src/providers/ldap/sdap_async_connection.c
@@ -0,0 +1,1141 @@
+/*
+ SSSD
+
+ Async LDAP Helper routines
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009
+
+ 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 <sasl/sasl.h>
+#include "util/util.h"
+#include "util/sss_krb5.h"
+#include "providers/ldap/sdap_async_private.h"
+
+#define LDAP_X_SSSD_PASSWORD_EXPIRED 0x555D
+
+/* ==Connect-to-LDAP-Server=============================================== */
+
+struct sdap_connect_state {
+ struct tevent_context *ev;
+ struct sdap_options *opts;
+ struct sdap_handle *sh;
+
+ struct sdap_op *op;
+
+ struct sdap_msg *reply;
+ int result;
+};
+
+static void sdap_connect_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt);
+
+struct tevent_req *sdap_connect_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ const char *uri,
+ bool use_start_tls)
+{
+ struct tevent_req *req;
+ struct sdap_connect_state *state;
+ struct timeval tv;
+ int ver;
+ int lret;
+ int ret = EOK;
+ int msgid;
+ bool ldap_referrals;
+
+ req = tevent_req_create(memctx, &state, struct sdap_connect_state);
+ if (!req) return NULL;
+
+ state->reply = talloc(state, struct sdap_msg);
+ if (!state->reply) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->opts = opts;
+ state->sh = sdap_handle_create(state);
+ if (!state->sh) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ /* Initialize LDAP handler */
+ lret = ldap_initialize(&state->sh->ldap, uri);
+ if (lret != LDAP_SUCCESS) {
+ DEBUG(1, ("ldap_initialize failed: %s\n", ldap_err2string(lret)));
+ goto fail;
+ }
+
+ /* Force ldap version to 3 */
+ ver = LDAP_VERSION3;
+ lret = ldap_set_option(state->sh->ldap, LDAP_OPT_PROTOCOL_VERSION, &ver);
+ if (lret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("Failed to set ldap version to 3\n"));
+ goto fail;
+ }
+
+ /* Set Network Timeout */
+ tv.tv_sec = dp_opt_get_int(opts->basic, SDAP_NETWORK_TIMEOUT);
+ tv.tv_usec = 0;
+ lret = ldap_set_option(state->sh->ldap, LDAP_OPT_NETWORK_TIMEOUT, &tv);
+ if (lret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("Failed to set network timeout to %d\n",
+ dp_opt_get_int(opts->basic, SDAP_NETWORK_TIMEOUT)));
+ goto fail;
+ }
+
+ /* Set Default Timeout */
+ tv.tv_sec = dp_opt_get_int(opts->basic, SDAP_OPT_TIMEOUT);
+ tv.tv_usec = 0;
+ lret = ldap_set_option(state->sh->ldap, LDAP_OPT_TIMEOUT, &tv);
+ if (lret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("Failed to set default timeout to %d\n",
+ dp_opt_get_int(opts->basic, SDAP_OPT_TIMEOUT)));
+ goto fail;
+ }
+
+ /* Set Referral chasing */
+ ldap_referrals = dp_opt_get_bool(opts->basic, SDAP_REFERRALS);
+ lret = ldap_set_option(state->sh->ldap, LDAP_OPT_REFERRALS,
+ (ldap_referrals ? LDAP_OPT_ON : LDAP_OPT_OFF));
+ if (lret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("Failed to set referral chasing to %s\n",
+ (ldap_referrals ? "LDAP_OPT_ON" : "LDAP_OPT_OFF")));
+ goto fail;
+ }
+
+#ifdef HAVE_LDAP_CONNCB
+ struct ldap_cb_data *cb_data;
+
+ /* add connection callback */
+ state->sh->conncb = talloc_zero(state->sh, struct ldap_conncb);
+ if (state->sh->conncb == NULL) {
+ DEBUG(1, ("talloc_zero failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ cb_data = talloc_zero(state->sh->conncb, struct ldap_cb_data);
+ if (cb_data == NULL) {
+ DEBUG(1, ("talloc_zero failed.\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ cb_data->sh = state->sh;
+ cb_data->ev = state->ev;
+
+ state->sh->conncb->lc_add = sdap_ldap_connect_callback_add;
+ state->sh->conncb->lc_del = sdap_ldap_connect_callback_del;
+ state->sh->conncb->lc_arg = cb_data;
+
+ lret = ldap_set_option(state->sh->ldap, LDAP_OPT_CONNECT_CB,
+ state->sh->conncb);
+ if (lret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("Failed to set connection callback\n"));
+ goto fail;
+ }
+#endif
+
+ /* if we do not use start_tls the connection is not really connected yet
+ * just fake an async procedure and leave connection to the bind call */
+ if (!use_start_tls) {
+ tevent_req_post(req, ev);
+ return req;
+ }
+
+ DEBUG(4, ("Executing START TLS\n"));
+
+ lret = ldap_start_tls(state->sh->ldap, NULL, NULL, &msgid);
+ if (lret != LDAP_SUCCESS) {
+ DEBUG(3, ("ldap_start_tls failed: [%s]", ldap_err2string(ret)));
+ goto fail;
+ }
+
+ state->sh->connected = true;
+#ifndef HAVE_LDAP_CONNCB
+ ret = sdap_install_ldap_callbacks(state->sh, state->ev);
+ if (ret) goto fail;
+#endif
+
+ /* FIXME: get timeouts from configuration, for now 5 secs. */
+ ret = sdap_op_add(state, ev, state->sh, msgid,
+ sdap_connect_done, req, 5, &state->op);
+ if (ret) {
+ DEBUG(1, ("Failed to set up operation!\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ if (ret) {
+ tevent_req_error(req, ret);
+ } else {
+ if (lret == LDAP_SERVER_DOWN) {
+ tevent_req_error(req, ETIMEDOUT);
+ } else {
+ tevent_req_error(req, EIO);
+ }
+ }
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sdap_connect_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct sdap_connect_state *state = tevent_req_data(req,
+ struct sdap_connect_state);
+ char *errmsg;
+ int ret;
+
+ if (error) {
+ tevent_req_error(req, error);
+ return;
+ }
+
+ state->reply = talloc_steal(state, reply);
+
+ ret = ldap_parse_result(state->sh->ldap, state->reply->msg,
+ &state->result, NULL, &errmsg, NULL, NULL, 0);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(2, ("ldap_parse_result failed (%d)\n", state->op->msgid));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ DEBUG(3, ("START TLS result: %s(%d), %s\n",
+ ldap_err2string(state->result), state->result, errmsg));
+
+ if (ldap_tls_inplace(state->sh->ldap)) {
+ DEBUG(9, ("SSL/TLS handler already in place.\n"));
+ tevent_req_done(req);
+ return;
+ }
+
+/* FIXME: take care that ldap_install_tls might block */
+ ret = ldap_install_tls(state->sh->ldap);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(1, ("ldap_install_tls failed: [%d][%s]\n", ret,
+ ldap_err2string(ret)));
+ state->result = ret;
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sdap_connect_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx,
+ struct sdap_handle **sh)
+{
+ struct sdap_connect_state *state = tevent_req_data(req,
+ struct sdap_connect_state);
+ enum tevent_req_state tstate;
+ uint64_t err = EIO;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ /* if tstate shows in progress, it is because
+ * we did not ask to perform tls, just pretend all is fine */
+ if (tstate != TEVENT_REQ_IN_PROGRESS) {
+ return err;
+ }
+ }
+
+ *sh = talloc_steal(memctx, state->sh);
+ if (!*sh) {
+ return ENOMEM;
+ }
+ return EOK;
+}
+
+/* ==Simple-Bind========================================================== */
+
+struct simple_bind_state {
+ struct tevent_context *ev;
+ struct sdap_handle *sh;
+ const char *user_dn;
+ struct berval *pw;
+
+ struct sdap_op *op;
+
+ struct sdap_msg *reply;
+ int result;
+};
+
+static void simple_bind_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt);
+
+static struct tevent_req *simple_bind_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ const char *user_dn,
+ struct berval *pw)
+{
+ struct tevent_req *req;
+ struct simple_bind_state *state;
+ int ret = EOK;
+ int msgid;
+ int ldap_err;
+ LDAPControl *request_controls[2];
+
+ req = tevent_req_create(memctx, &state, struct simple_bind_state);
+ if (!req) return NULL;
+
+ state->reply = talloc(state, struct sdap_msg);
+ if (!state->reply) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->sh = sh;
+ state->user_dn = user_dn;
+ state->pw = pw;
+
+ ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST,
+ 0, NULL, 0, &request_controls[0]);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(1, ("sss_ldap_control_create failed.\n"));
+ goto fail;
+ }
+ request_controls[1] = NULL;
+
+ DEBUG(4, ("Executing simple bind as: %s\n", state->user_dn));
+
+ ret = ldap_sasl_bind(state->sh->ldap, state->user_dn, LDAP_SASL_SIMPLE,
+ state->pw, request_controls, NULL, &msgid);
+ ldap_control_free(request_controls[0]);
+ if (ret == -1 || msgid == -1) {
+ ret = ldap_get_option(state->sh->ldap,
+ LDAP_OPT_RESULT_CODE, &ldap_err);
+ if (ret != LDAP_OPT_SUCCESS) {
+ DEBUG(1, ("ldap_bind failed (couldn't get ldap error)\n"));
+ ret = LDAP_LOCAL_ERROR;
+ } else {
+ DEBUG(1, ("ldap_bind failed (%d)[%s]\n",
+ ldap_err, ldap_err2string(ldap_err)));
+ ret = ldap_err;
+ }
+ goto fail;
+ }
+ DEBUG(8, ("ldap simple bind sent, msgid = %d\n", msgid));
+
+ if (!sh->connected) {
+ sh->connected = true;
+#ifndef HAVE_LDAP_CONNCB
+ ret = sdap_install_ldap_callbacks(sh, ev);
+ if (ret) goto fail;
+#endif
+ }
+
+ /* FIXME: get timeouts from configuration, for now 5 secs. */
+ ret = sdap_op_add(state, ev, sh, msgid,
+ simple_bind_done, req, 5, &state->op);
+ if (ret) {
+ DEBUG(1, ("Failed to set up operation!\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ if (ret == LDAP_SERVER_DOWN) {
+ tevent_req_error(req, ETIMEDOUT);
+ } else {
+ tevent_req_error(req, EIO);
+ }
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void simple_bind_done(struct sdap_op *op,
+ struct sdap_msg *reply,
+ int error, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct simple_bind_state *state = tevent_req_data(req,
+ struct simple_bind_state);
+ char *errmsg;
+ int ret;
+ LDAPControl **response_controls;
+ int c;
+ ber_int_t pp_grace;
+ ber_int_t pp_expire;
+ LDAPPasswordPolicyError pp_error;
+
+ if (error) {
+ tevent_req_error(req, error);
+ return;
+ }
+
+ state->reply = talloc_steal(state, reply);
+
+ ret = ldap_parse_result(state->sh->ldap, state->reply->msg,
+ &state->result, NULL, &errmsg, NULL,
+ &response_controls, 0);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(2, ("ldap_parse_result failed (%d)\n", state->op->msgid));
+ ret = EIO;
+ goto done;
+ }
+
+ if (response_controls == NULL) {
+ DEBUG(5, ("Server returned no controls.\n"));
+ } else {
+ for (c = 0; response_controls[c] != NULL; c++) {
+ DEBUG(9, ("Server returned control [%s].\n",
+ response_controls[c]->ldctl_oid));
+ if (strcmp(response_controls[c]->ldctl_oid,
+ LDAP_CONTROL_PASSWORDPOLICYRESPONSE) == 0) {
+ ret = ldap_parse_passwordpolicy_control(state->sh->ldap,
+ response_controls[c],
+ &pp_expire, &pp_grace,
+ &pp_error);
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(1, ("ldap_parse_passwordpolicy_control failed.\n"));
+ ret = EIO;
+ goto done;
+ }
+
+ DEBUG(7, ("Password Policy Response: expire [%d] grace [%d] "
+ "error [%s].\n", pp_expire, pp_grace,
+ ldap_passwordpolicy_err2txt(pp_error)));
+
+ if (state->result == LDAP_SUCCESS &&
+ (pp_error == PP_changeAfterReset || pp_grace > 0)) {
+ DEBUG(4, ("User must set a new password.\n"));
+ state->result = LDAP_X_SSSD_PASSWORD_EXPIRED;
+ }
+ }
+ }
+ }
+
+ DEBUG(3, ("Bind result: %s(%d), %s\n",
+ ldap_err2string(state->result), state->result, errmsg));
+
+ ret = LDAP_SUCCESS;
+done:
+ ldap_controls_free(response_controls);
+
+ if (ret == LDAP_SUCCESS) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, ret);
+ }
+}
+
+static int simple_bind_recv(struct tevent_req *req, int *ldaperr)
+{
+ struct simple_bind_state *state = tevent_req_data(req,
+ struct simple_bind_state);
+
+ *ldaperr = LDAP_OTHER;
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *ldaperr = state->result;
+ return EOK;
+}
+
+/* ==SASL-Bind============================================================ */
+
+struct sasl_bind_state {
+ struct tevent_context *ev;
+ struct sdap_handle *sh;
+
+ const char *sasl_mech;
+ const char *sasl_user;
+ struct berval *sasl_cred;
+
+ int result;
+};
+
+static int sdap_sasl_interact(LDAP *ld, unsigned flags,
+ void *defaults, void *interact);
+
+static struct tevent_req *sasl_bind_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ const char *sasl_mech,
+ const char *sasl_user,
+ struct berval *sasl_cred)
+{
+ struct tevent_req *req;
+ struct sasl_bind_state *state;
+ int ret = EOK;
+
+ req = tevent_req_create(memctx, &state, struct sasl_bind_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->sh = sh;
+ state->sasl_mech = sasl_mech;
+ state->sasl_user = sasl_user;
+ state->sasl_cred = sasl_cred;
+
+ DEBUG(4, ("Executing sasl bind mech: %s, user: %s\n",
+ sasl_mech, sasl_user));
+
+ /* FIXME: Warning, this is a sync call!
+ * No async variant exist in openldap libraries yet */
+
+ ret = ldap_sasl_interactive_bind_s(state->sh->ldap, NULL,
+ sasl_mech, NULL, NULL,
+ LDAP_SASL_QUIET,
+ (*sdap_sasl_interact), state);
+ state->result = ret;
+ if (ret != LDAP_SUCCESS) {
+ DEBUG(1, ("ldap_sasl_bind failed (%d)[%s]\n",
+ ret, ldap_err2string(ret)));
+ goto fail;
+ }
+
+ if (!sh->connected) {
+ sh->connected = true;
+#ifndef HAVE_LDAP_CONNCB
+ ret = sdap_install_ldap_callbacks(sh, ev);
+ if (ret) goto fail;
+#endif
+ }
+
+ tevent_req_post(req, ev);
+ return req;
+
+fail:
+ if (ret == LDAP_SERVER_DOWN) {
+ tevent_req_error(req, ETIMEDOUT);
+ } else {
+ tevent_req_error(req, EIO);
+ }
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static int sdap_sasl_interact(LDAP *ld, unsigned flags,
+ void *defaults, void *interact)
+{
+ struct sasl_bind_state *state = talloc_get_type(defaults,
+ struct sasl_bind_state);
+ sasl_interact_t *in = (sasl_interact_t *)interact;
+
+ if (!ld) return LDAP_PARAM_ERROR;
+
+ while (in->id != SASL_CB_LIST_END) {
+
+ switch (in->id) {
+ case SASL_CB_GETREALM:
+ case SASL_CB_AUTHNAME:
+ case SASL_CB_PASS:
+ if (in->defresult) {
+ in->result = in->defresult;
+ } else {
+ in->result = "";
+ }
+ in->len = strlen(in->result);
+ break;
+ case SASL_CB_USER:
+ if (state->sasl_user) {
+ in->result = state->sasl_user;
+ } else if (in->defresult) {
+ in->result = in->defresult;
+ } else {
+ in->result = "";
+ }
+ in->len = strlen(in->result);
+ break;
+ case SASL_CB_NOECHOPROMPT:
+ case SASL_CB_ECHOPROMPT:
+ goto fail;
+ }
+
+ in++;
+ }
+
+ return LDAP_SUCCESS;
+
+fail:
+ return LDAP_UNAVAILABLE;
+}
+
+static int sasl_bind_recv(struct tevent_req *req, int *ldaperr)
+{
+ struct sasl_bind_state *state = tevent_req_data(req,
+ struct sasl_bind_state);
+ enum tevent_req_state tstate;
+ uint64_t err = EIO;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ if (tstate != TEVENT_REQ_IN_PROGRESS) {
+ *ldaperr = LDAP_OTHER;
+ return err;
+ }
+ }
+
+ *ldaperr = state->result;
+ return EOK;
+}
+
+/* ==Perform-Kinit-given-keytab-and-principal============================= */
+
+struct sdap_kinit_state {
+ int result;
+};
+
+static void sdap_kinit_done(struct tevent_req *subreq);
+
+struct tevent_req *sdap_kinit_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ int timeout,
+ const char *keytab,
+ const char *principal,
+ const char *realm)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct sdap_kinit_state *state;
+ int ret;
+
+ DEBUG(6, ("Attempting kinit (%s, %s, %s)\n", keytab, principal, realm));
+
+ req = tevent_req_create(memctx, &state, struct sdap_kinit_state);
+ if (!req) return NULL;
+
+ state->result = SDAP_AUTH_FAILED;
+
+ if (keytab) {
+ ret = setenv("KRB5_KTNAME", keytab, 1);
+ if (ret == -1) {
+ DEBUG(2, ("Failed to set KRB5_KTNAME to %s\n", keytab));
+ return NULL;
+ }
+ }
+
+ subreq = sdap_get_tgt_send(state, ev, realm, principal, keytab, timeout);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_kinit_done, req);
+
+ return req;
+}
+
+static void sdap_kinit_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_kinit_state *state = tevent_req_data(req,
+ struct sdap_kinit_state);
+
+ int ret;
+ int result;
+ char *ccname = NULL;
+
+ ret = sdap_get_tgt_recv(subreq, state, &result, &ccname);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ state->result = SDAP_AUTH_FAILED;
+ DEBUG(1, ("child failed (%d [%s])\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (result == EOK) {
+ ret = setenv("KRB5CCNAME", ccname, 1);
+ if (ret == -1) {
+ DEBUG(2, ("Unable to set env. variable KRB5CCNAME!\n"));
+ state->result = SDAP_AUTH_FAILED;
+ tevent_req_error(req, EFAULT);
+ }
+
+ state->result = SDAP_AUTH_SUCCESS;
+ tevent_req_done(req);
+ return;
+ }
+
+ DEBUG(4, ("Could not get TGT: %d [%s]\n", result, strerror(result)));
+ state->result = SDAP_AUTH_FAILED;
+ tevent_req_error(req, EIO);
+}
+
+int sdap_kinit_recv(struct tevent_req *req, enum sdap_result *result)
+{
+ struct sdap_kinit_state *state = tevent_req_data(req,
+ struct sdap_kinit_state);
+ enum tevent_req_state tstate;
+ uint64_t err = EIO;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ if (tstate != TEVENT_REQ_IN_PROGRESS) {
+ *result = SDAP_ERROR;
+ return err;
+ }
+ }
+
+ *result = state->result;
+ return EOK;
+}
+
+
+/* ==Authenticaticate-User-by-DN========================================== */
+
+struct sdap_auth_state {
+ const char *user_dn;
+ struct berval pw;
+
+ int result;
+ bool is_sasl;
+};
+
+static void sdap_auth_done(struct tevent_req *subreq);
+
+/* TODO: handle sasl_cred */
+struct tevent_req *sdap_auth_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_handle *sh,
+ const char *sasl_mech,
+ const char *sasl_user,
+ const char *user_dn,
+ const char *authtok_type,
+ struct dp_opt_blob authtok)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_auth_state *state;
+
+ if (authtok_type != NULL && strcasecmp(authtok_type,"password") != 0) {
+ DEBUG(1,("Authentication token type [%s] is not supported"));
+ return NULL;
+ }
+
+ req = tevent_req_create(memctx, &state, struct sdap_auth_state);
+ if (!req) return NULL;
+
+ state->user_dn = user_dn;
+ state->pw.bv_val = (char *)authtok.data;
+ state->pw.bv_len = authtok.length;
+
+ if (sasl_mech) {
+ state->is_sasl = true;
+ subreq = sasl_bind_send(state, ev, sh, sasl_mech, sasl_user, NULL);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ state->is_sasl = false;
+ subreq = simple_bind_send(state, ev, sh, user_dn, &state->pw);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ tevent_req_set_callback(subreq, sdap_auth_done, req);
+ return req;
+}
+
+static void sdap_auth_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_auth_state *state = tevent_req_data(req,
+ struct sdap_auth_state);
+ int ret;
+
+ if (state->is_sasl) {
+ ret = sasl_bind_recv(subreq, &state->result);
+ } else {
+ ret = simple_bind_recv(subreq, &state->result);
+ }
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sdap_auth_recv(struct tevent_req *req, enum sdap_result *result)
+{
+ struct sdap_auth_state *state = tevent_req_data(req,
+ struct sdap_auth_state);
+
+ *result = SDAP_ERROR;
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ switch (state->result) {
+ case LDAP_SUCCESS:
+ *result = SDAP_AUTH_SUCCESS;
+ break;
+ case LDAP_INVALID_CREDENTIALS:
+ *result = SDAP_AUTH_FAILED;
+ break;
+ case LDAP_X_SSSD_PASSWORD_EXPIRED:
+ *result = SDAP_AUTH_PW_EXPIRED;
+ break;
+ default:
+ break;
+ }
+
+ return EOK;
+}
+
+/* ==Client connect============================================ */
+
+struct sdap_cli_connect_state {
+ struct tevent_context *ev;
+ struct sdap_options *opts;
+ struct sdap_service *service;
+
+ bool use_rootdse;
+ struct sysdb_attrs *rootdse;
+
+ struct sdap_handle *sh;
+
+ struct fo_server *srv;
+};
+
+static void sdap_cli_resolve_done(struct tevent_req *subreq);
+static void sdap_cli_connect_done(struct tevent_req *subreq);
+static void sdap_cli_rootdse_step(struct tevent_req *req);
+static void sdap_cli_rootdse_done(struct tevent_req *subreq);
+static void sdap_cli_kinit_step(struct tevent_req *req);
+static void sdap_cli_kinit_done(struct tevent_req *subreq);
+static void sdap_cli_auth_step(struct tevent_req *req);
+static void sdap_cli_auth_done(struct tevent_req *subreq);
+
+struct tevent_req *sdap_cli_connect_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct be_ctx *be,
+ struct sdap_service *service,
+ struct sysdb_attrs **rootdse)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_cli_connect_state *state;
+
+ req = tevent_req_create(memctx, &state, struct sdap_cli_connect_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->opts = opts;
+ state->service = service;
+ state->srv = NULL;
+
+ if (rootdse) {
+ state->use_rootdse = true;
+ state->rootdse = *rootdse;
+ } else {
+ state->use_rootdse = false;
+ state->rootdse = NULL;
+ }
+
+ /* NOTE: this call may cause service->uri to be refreshed
+ * with a new valid server. Do not use service->uri before */
+ subreq = be_resolve_server_send(state, ev, be, service->name);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, sdap_cli_resolve_done, req);
+
+ return req;
+}
+
+static void sdap_cli_resolve_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_cli_connect_state *state = tevent_req_data(req,
+ struct sdap_cli_connect_state);
+ int ret;
+
+ ret = be_resolve_server_recv(subreq, &state->srv);
+ talloc_zfree(subreq);
+ if (ret) {
+ /* all servers have been tried and none
+ * was found good, go offline */
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ subreq = sdap_connect_send(state, state->ev, state->opts,
+ state->service->uri,
+ dp_opt_get_bool(state->opts->basic,
+ SDAP_ID_TLS));
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_cli_connect_done, req);
+}
+
+static void sdap_cli_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_cli_connect_state *state = tevent_req_data(req,
+ struct sdap_cli_connect_state);
+ const char *sasl_mech;
+ int ret;
+
+ ret = sdap_connect_recv(subreq, state, &state->sh);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->use_rootdse && !state->rootdse) {
+ /* fetch the rootDSE this time */
+ sdap_cli_rootdse_step(req);
+ return;
+ }
+
+ sasl_mech = dp_opt_get_string(state->opts->basic, SDAP_SASL_MECH);
+
+ if (sasl_mech && state->use_rootdse) {
+ /* check if server claims to support GSSAPI */
+ if (!sdap_rootdse_sasl_mech_is_supported(state->rootdse,
+ sasl_mech)) {
+ tevent_req_error(req, ENOTSUP);
+ return;
+ }
+ }
+
+ if (sasl_mech && (strcasecmp(sasl_mech, "GSSAPI") == 0)) {
+ if (dp_opt_get_bool(state->opts->basic, SDAP_KRB5_KINIT)) {
+ sdap_cli_kinit_step(req);
+ return;
+ }
+ }
+
+ sdap_cli_auth_step(req);
+}
+
+static void sdap_cli_rootdse_step(struct tevent_req *req)
+{
+ struct sdap_cli_connect_state *state = tevent_req_data(req,
+ struct sdap_cli_connect_state);
+ struct tevent_req *subreq;
+
+ subreq = sdap_get_rootdse_send(state, state->ev, state->opts, state->sh);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_cli_rootdse_done, req);
+
+ if (!state->sh->connected) {
+ /* this rootdse search is performed before we actually do a bind,
+ * so we need to set up the callbacks or we will never get notified
+ * of a reply */
+ state->sh->connected = true;
+#ifndef HAVE_LDAP_CONNCB
+ int ret;
+
+ ret = sdap_install_ldap_callbacks(state->sh, state->ev);
+ if (ret) {
+ tevent_req_error(req, ret);
+ }
+#endif
+ }
+}
+
+static void sdap_cli_rootdse_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_cli_connect_state *state = tevent_req_data(req,
+ struct sdap_cli_connect_state);
+ const char *sasl_mech;
+ int ret;
+
+ ret = sdap_get_rootdse_recv(subreq, state, &state->rootdse);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ sasl_mech = dp_opt_get_string(state->opts->basic, SDAP_SASL_MECH);
+
+ if (sasl_mech && state->use_rootdse) {
+ /* check if server claims to support GSSAPI */
+ if (!sdap_rootdse_sasl_mech_is_supported(state->rootdse,
+ sasl_mech)) {
+ tevent_req_error(req, ENOTSUP);
+ return;
+ }
+ }
+
+ if (sasl_mech && (strcasecmp(sasl_mech, "GSSAPI") == 0)) {
+ if (dp_opt_get_bool(state->opts->basic, SDAP_KRB5_KINIT)) {
+ sdap_cli_kinit_step(req);
+ return;
+ }
+ }
+
+ sdap_cli_auth_step(req);
+}
+
+static void sdap_cli_kinit_step(struct tevent_req *req)
+{
+ struct sdap_cli_connect_state *state = tevent_req_data(req,
+ struct sdap_cli_connect_state);
+ struct tevent_req *subreq;
+
+ subreq = sdap_kinit_send(state, state->ev,
+ state->sh,
+ dp_opt_get_int(state->opts->basic,
+ SDAP_OPT_TIMEOUT),
+ dp_opt_get_string(state->opts->basic,
+ SDAP_KRB5_KEYTAB),
+ dp_opt_get_string(state->opts->basic,
+ SDAP_SASL_AUTHID),
+ dp_opt_get_string(state->opts->basic,
+ SDAP_KRB5_REALM));
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_cli_kinit_done, req);
+}
+
+static void sdap_cli_kinit_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ enum sdap_result result;
+ int ret;
+
+ ret = sdap_kinit_recv(subreq, &result);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ if (result != SDAP_AUTH_SUCCESS) {
+ tevent_req_error(req, EACCES);
+ return;
+ }
+
+ sdap_cli_auth_step(req);
+}
+
+static void sdap_cli_auth_step(struct tevent_req *req)
+{
+ struct sdap_cli_connect_state *state = tevent_req_data(req,
+ struct sdap_cli_connect_state);
+ struct tevent_req *subreq;
+
+ subreq = sdap_auth_send(state,
+ state->ev,
+ state->sh,
+ dp_opt_get_string(state->opts->basic,
+ SDAP_SASL_MECH),
+ dp_opt_get_string(state->opts->basic,
+ SDAP_SASL_AUTHID),
+ dp_opt_get_string(state->opts->basic,
+ SDAP_DEFAULT_BIND_DN),
+ dp_opt_get_string(state->opts->basic,
+ SDAP_DEFAULT_AUTHTOK_TYPE),
+ dp_opt_get_blob(state->opts->basic,
+ SDAP_DEFAULT_AUTHTOK));
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_cli_auth_done, req);
+}
+
+static void sdap_cli_auth_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ enum sdap_result result;
+ int ret;
+
+ ret = sdap_auth_recv(subreq, &result);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ if (result != SDAP_AUTH_SUCCESS) {
+ tevent_req_error(req, EACCES);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int sdap_cli_connect_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx,
+ struct sdap_handle **gsh,
+ struct sysdb_attrs **rootdse)
+{
+ struct sdap_cli_connect_state *state = tevent_req_data(req,
+ struct sdap_cli_connect_state);
+ enum tevent_req_state tstate;
+ uint64_t err;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ /* mark the server as bad if connection failed */
+ if (state->srv) {
+ fo_set_port_status(state->srv, PORT_NOT_WORKING);
+ }
+
+ if (tstate == TEVENT_REQ_USER_ERROR) {
+ return err;
+ }
+ return EIO;
+ } else if (state->srv) {
+ fo_set_port_status(state->srv, PORT_WORKING);
+ }
+
+ if (gsh) {
+ *gsh = talloc_steal(memctx, state->sh);
+ if (!*gsh) {
+ return ENOMEM;
+ }
+ } else {
+ talloc_zfree(state->sh);
+ }
+
+ if (rootdse) {
+ if (state->use_rootdse) {
+ *rootdse = talloc_steal(memctx, state->rootdse);
+ if (!*rootdse) {
+ return ENOMEM;
+ }
+ } else {
+ *rootdse = NULL;
+ }
+ } else {
+ talloc_zfree(rootdse);
+ }
+
+ return EOK;
+}
+
diff --git a/src/providers/ldap/sdap_async_private.h b/src/providers/ldap/sdap_async_private.h
new file mode 100644
index 000000000..55f76ed70
--- /dev/null
+++ b/src/providers/ldap/sdap_async_private.h
@@ -0,0 +1,68 @@
+/*
+ SSSD
+
+ Async LDAP Helper routines
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com>
+
+ 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 _SDAP_ASYNC_PRIVATE_H_
+#define _SDAP_ASYNC_PRIVATE_H_
+
+#include "config.h"
+#include "providers/ldap/sdap_async.h"
+
+void make_realm_upper_case(const char *upn);
+struct sdap_handle *sdap_handle_create(TALLOC_CTX *memctx);
+
+#ifdef HAVE_LDAP_CONNCB
+int sdap_ldap_connect_callback_add(LDAP *ld, Sockbuf *sb, LDAPURLDesc *srv,
+ struct sockaddr *addr, struct ldap_conncb *ctx);
+void sdap_ldap_connect_callback_del(LDAP *ld, Sockbuf *sb,
+ struct ldap_conncb *ctx);
+#else
+int sdap_install_ldap_callbacks(struct sdap_handle *sh,
+ struct tevent_context *ev);
+#endif
+
+int sdap_op_add(TALLOC_CTX *memctx, struct tevent_context *ev,
+ struct sdap_handle *sh, int msgid,
+ sdap_op_callback_t *callback, void *data,
+ int timeout, struct sdap_op **_op);
+
+struct tevent_req *sdap_get_rootdse_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_options *opts,
+ struct sdap_handle *sh);
+int sdap_get_rootdse_recv(struct tevent_req *req,
+ TALLOC_CTX *memctx,
+ struct sysdb_attrs **rootdse);
+
+/* from sdap_child_helpers.c */
+
+struct tevent_req *sdap_get_tgt_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *realm_str,
+ const char *princ_str,
+ const char *keytab_name,
+ int timeout);
+
+int sdap_get_tgt_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ int *result,
+ char **ccname);
+
+#endif /* _SDAP_ASYNC_PRIVATE_H_ */
diff --git a/src/providers/ldap/sdap_child_helpers.c b/src/providers/ldap/sdap_child_helpers.c
new file mode 100644
index 000000000..0a95c8a0d
--- /dev/null
+++ b/src/providers/ldap/sdap_child_helpers.c
@@ -0,0 +1,462 @@
+/*
+ SSSD
+
+ LDAP Backend Module -- child helpers
+
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2009 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 <sys/types.h>
+#include <sys/wait.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "util/util.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/child_common.h"
+
+#ifndef SSSD_LIBEXEC_PATH
+#error "SSSD_LIBEXEC_PATH not defined"
+#else
+#define LDAP_CHILD SSSD_LIBEXEC_PATH"/ldap_child"
+#endif
+
+#ifndef LDAP_CHILD_USER
+#define LDAP_CHILD_USER "nobody"
+#endif
+
+struct sdap_child {
+ /* child info */
+ pid_t pid;
+ int read_from_child_fd;
+ int write_to_child_fd;
+};
+
+static void sdap_close_fd(int *fd)
+{
+ int ret;
+
+ if (*fd == -1) {
+ DEBUG(6, ("fd already closed\n"));
+ return;
+ }
+
+ ret = close(*fd);
+ if (ret) {
+ ret = errno;
+ DEBUG(2, ("Closing fd %d, return error %d (%s)\n",
+ *fd, ret, strerror(ret)));
+ }
+
+ *fd = -1;
+}
+
+static int sdap_child_destructor(void *ptr)
+{
+ struct sdap_child *child = talloc_get_type(ptr, struct sdap_child);
+
+ child_cleanup(child->read_from_child_fd, child->write_to_child_fd);
+
+ return 0;
+}
+
+static errno_t sdap_fork_child(struct sdap_child *child)
+{
+ int pipefd_to_child[2];
+ int pipefd_from_child[2];
+ pid_t pid;
+ int ret;
+ errno_t err;
+
+ ret = pipe(pipefd_from_child);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("pipe failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+ ret = pipe(pipefd_to_child);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("pipe failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+
+ pid = fork();
+
+ if (pid == 0) { /* child */
+ err = exec_child(child,
+ pipefd_to_child, pipefd_from_child,
+ LDAP_CHILD, ldap_child_debug_fd);
+ if (err != EOK) {
+ DEBUG(1, ("Could not exec LDAP child: [%d][%s].\n",
+ err, strerror(err)));
+ return err;
+ }
+ } else if (pid > 0) { /* parent */
+ child->pid = pid;
+ child->read_from_child_fd = pipefd_from_child[0];
+ close(pipefd_from_child[1]);
+ child->write_to_child_fd = pipefd_to_child[1];
+ close(pipefd_to_child[0]);
+ fd_nonblocking(child->read_from_child_fd);
+ fd_nonblocking(child->write_to_child_fd);
+
+ } else { /* error */
+ err = errno;
+ DEBUG(1, ("fork failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+
+ return EOK;
+}
+
+static errno_t create_tgt_req_send_buffer(TALLOC_CTX *mem_ctx,
+ const char *realm_str,
+ const char *princ_str,
+ const char *keytab_name,
+ struct io_buffer **io_buf)
+{
+ struct io_buffer *buf;
+ size_t rp;
+
+ buf = talloc(mem_ctx, struct io_buffer);
+ if (buf == NULL) {
+ DEBUG(1, ("talloc failed.\n"));
+ return ENOMEM;
+ }
+
+ buf->size = 3 * sizeof(uint32_t);
+ if (realm_str) {
+ buf->size += strlen(realm_str);
+ }
+ if (princ_str) {
+ buf->size += strlen(princ_str);
+ }
+ if (keytab_name) {
+ buf->size += strlen(keytab_name);
+ }
+
+ DEBUG(7, ("buffer size: %d\n", buf->size));
+
+ buf->data = talloc_size(buf, buf->size);
+ if (buf->data == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ talloc_free(buf);
+ return ENOMEM;
+ }
+
+ rp = 0;
+
+ /* realm */
+ if (realm_str) {
+ COPY_UINT32_VALUE(&buf->data[rp], strlen(realm_str), rp);
+ COPY_MEM(&buf->data[rp], realm_str, rp, strlen(realm_str));
+ } else {
+ COPY_UINT32_VALUE(&buf->data[rp], 0, rp);
+ }
+
+ /* principal */
+ if (princ_str) {
+ COPY_UINT32_VALUE(&buf->data[rp], strlen(princ_str), rp);
+ COPY_MEM(&buf->data[rp], princ_str, rp, strlen(princ_str));
+ } else {
+ COPY_UINT32_VALUE(&buf->data[rp], 0, rp);
+ }
+
+ /* keytab */
+ if (keytab_name) {
+ COPY_UINT32_VALUE(&buf->data[rp], strlen(keytab_name), rp);
+ COPY_MEM(&buf->data[rp], keytab_name, rp, strlen(realm_str));
+ } else {
+ COPY_UINT32_VALUE(&buf->data[rp], 0, rp);
+ }
+
+ *io_buf = buf;
+ return EOK;
+}
+
+static int parse_child_response(TALLOC_CTX *mem_ctx,
+ uint8_t *buf, ssize_t size,
+ int *result, char **ccache)
+{
+ size_t p = 0;
+ uint32_t len;
+ uint32_t res;
+ char *ccn;
+
+ /* operation result code */
+ COPY_UINT32_CHECK(&res, buf + p, p, size);
+
+ /* ccache name size */
+ COPY_UINT32_CHECK(&len, buf + p, p, size);
+
+ if ((p + len ) > size) return EINVAL;
+
+ ccn = talloc_size(mem_ctx, sizeof(char) * (len + 1));
+ if (ccn == NULL) {
+ DEBUG(1, ("talloc_size failed.\n"));
+ return ENOMEM;
+ }
+ memcpy(ccn, buf+p, sizeof(char) * (len + 1));
+ ccn[len] = '\0';
+
+ *result = res;
+ *ccache = ccn;
+ return EOK;
+}
+
+/* ==The-public-async-interface============================================*/
+
+struct sdap_get_tgt_state {
+ struct tevent_context *ev;
+ struct sdap_child *child;
+ ssize_t len;
+ uint8_t *buf;
+};
+
+static errno_t set_tgt_child_timeout(struct tevent_req *req,
+ struct tevent_context *ev,
+ int timeout);
+static void sdap_get_tgt_step(struct tevent_req *subreq);
+static void sdap_get_tgt_done(struct tevent_req *subreq);
+
+struct tevent_req *sdap_get_tgt_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *realm_str,
+ const char *princ_str,
+ const char *keytab_name,
+ int timeout)
+{
+ struct tevent_req *req, *subreq;
+ struct sdap_get_tgt_state *state;
+ struct io_buffer *buf;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct sdap_get_tgt_state);
+ if (!req) {
+ return NULL;
+ }
+
+ state->ev = ev;
+
+ state->child = talloc_zero(state, struct sdap_child);
+ if (!state->child) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ state->child->read_from_child_fd = -1;
+ state->child->write_to_child_fd = -1;
+ talloc_set_destructor((TALLOC_CTX *)state->child, sdap_child_destructor);
+
+ /* prepare the data to pass to child */
+ ret = create_tgt_req_send_buffer(state,
+ realm_str, princ_str, keytab_name,
+ &buf);
+ if (ret != EOK) {
+ DEBUG(1, ("create_tgt_req_send_buffer failed.\n"));
+ goto fail;
+ }
+
+ ret = sdap_fork_child(state->child);
+ if (ret != EOK) {
+ DEBUG(1, ("sdap_fork_child failed.\n"));
+ goto fail;
+ }
+
+ ret = set_tgt_child_timeout(req, ev, timeout);
+ if (ret != EOK) {
+ DEBUG(1, ("activate_child_timeout_handler failed.\n"));
+ goto fail;
+ }
+
+ subreq = write_pipe_send(state, ev, buf->data, buf->size,
+ state->child->write_to_child_fd);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, sdap_get_tgt_step, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void sdap_get_tgt_step(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_tgt_state *state = tevent_req_data(req,
+ struct sdap_get_tgt_state);
+ int ret;
+
+ ret = write_pipe_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ sdap_close_fd(&state->child->write_to_child_fd);
+
+ subreq = read_pipe_send(state, state->ev,
+ state->child->read_from_child_fd);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_get_tgt_done, req);
+}
+
+static void sdap_get_tgt_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_get_tgt_state *state = tevent_req_data(req,
+ struct sdap_get_tgt_state);
+ int ret;
+
+ ret = read_pipe_recv(subreq, state, &state->buf, &state->len);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ sdap_close_fd(&state->child->read_from_child_fd);
+
+ tevent_req_done(req);
+}
+
+int sdap_get_tgt_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ int *result,
+ char **ccname)
+{
+ struct sdap_get_tgt_state *state = tevent_req_data(req,
+ struct sdap_get_tgt_state);
+ char *ccn;
+ int res;
+ int ret;
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ ret = parse_child_response(mem_ctx, state->buf, state->len, &res, &ccn);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse child response: [%d][%s]\n", ret, strerror(ret)));
+ return ret;
+ }
+
+ DEBUG(6, ("Child responded: %d [%s]\n", res, ccn));
+ *result = res;
+ *ccname = ccn;
+ return EOK;
+}
+
+
+
+static void get_tgt_timeout_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct sdap_get_tgt_state *state = tevent_req_data(req,
+ struct sdap_get_tgt_state);
+ int ret;
+
+ DEBUG(9, ("timeout for tgt child [%d] reached.\n", state->child->pid));
+
+ ret = kill(state->child->pid, SIGKILL);
+ if (ret == -1) {
+ DEBUG(1, ("kill failed [%d][%s].\n", errno, strerror(errno)));
+ }
+
+ tevent_req_error(req, ETIMEDOUT);
+}
+
+static errno_t set_tgt_child_timeout(struct tevent_req *req,
+ struct tevent_context *ev,
+ int timeout)
+{
+ struct tevent_timer *te;
+ struct timeval tv;
+
+ DEBUG(6, ("Setting %d seconds timeout for tgt child\n", timeout));
+
+ tv = tevent_timeval_current_ofs(timeout, 0);
+
+ te = tevent_add_timer(ev, req, tv, get_tgt_timeout_handler, req);
+ if (te == NULL) {
+ DEBUG(1, ("tevent_add_timer failed.\n"));
+ return ENOMEM;
+ }
+
+ return EOK;
+}
+
+
+
+/* Setup child logging */
+int setup_child(struct sdap_id_ctx *ctx)
+{
+ int ret;
+ const char *mech;
+ struct tevent_signal *sige;
+ unsigned v;
+ FILE *debug_filep;
+
+ mech = dp_opt_get_string(ctx->opts->basic,
+ SDAP_SASL_MECH);
+ if (!mech) {
+ return EOK;
+ }
+
+ sige = tevent_add_signal(ctx->be->ev, ctx, SIGCHLD, SA_SIGINFO,
+ child_sig_handler, NULL);
+ if (sige == NULL) {
+ DEBUG(1, ("tevent_add_signal failed.\n"));
+ return ENOMEM;
+ }
+
+ if (debug_to_file != 0 && ldap_child_debug_fd == -1) {
+ ret = open_debug_file_ex("ldap_child", &debug_filep);
+ if (ret != EOK) {
+ DEBUG(0, ("Error setting up logging (%d) [%s]\n",
+ ret, strerror(ret)));
+ return ret;
+ }
+
+ ldap_child_debug_fd = fileno(debug_filep);
+ if (ldap_child_debug_fd == -1) {
+ DEBUG(0, ("fileno failed [%d][%s]\n", errno, strerror(errno)));
+ ret = errno;
+ return ret;
+ }
+
+ v = fcntl(ldap_child_debug_fd, F_GETFD, 0);
+ fcntl(ldap_child_debug_fd, F_SETFD, v & ~FD_CLOEXEC);
+ }
+
+ return EOK;
+}
diff --git a/src/providers/providers.h b/src/providers/providers.h
new file mode 100644
index 000000000..44e7028ab
--- /dev/null
+++ b/src/providers/providers.h
@@ -0,0 +1,24 @@
+/*
+ SSSD
+
+ Data Provider, public header file
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+
+ 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/>.
+*/
+
+int dp_process_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct confdb_ctx *cdb);
diff --git a/src/providers/proxy.c b/src/providers/proxy.c
new file mode 100644
index 000000000..12bb25ec7
--- /dev/null
+++ b/src/providers/proxy.c
@@ -0,0 +1,2521 @@
+/*
+ SSSD
+
+ Proxy Module
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2008-2009
+
+ 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 <nss.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dlfcn.h>
+
+#include <security/pam_appl.h>
+#include <security/pam_modules.h>
+
+#include "util/util.h"
+#include "providers/dp_backend.h"
+#include "db/sysdb.h"
+
+struct proxy_nss_ops {
+ enum nss_status (*getpwnam_r)(const char *name, struct passwd *result,
+ char *buffer, size_t buflen, int *errnop);
+ enum nss_status (*getpwuid_r)(uid_t uid, struct passwd *result,
+ char *buffer, size_t buflen, int *errnop);
+ enum nss_status (*setpwent)(void);
+ enum nss_status (*getpwent_r)(struct passwd *result,
+ char *buffer, size_t buflen, int *errnop);
+ enum nss_status (*endpwent)(void);
+
+ enum nss_status (*getgrnam_r)(const char *name, struct group *result,
+ char *buffer, size_t buflen, int *errnop);
+ enum nss_status (*getgrgid_r)(gid_t gid, struct group *result,
+ char *buffer, size_t buflen, int *errnop);
+ enum nss_status (*setgrent)(void);
+ enum nss_status (*getgrent_r)(struct group *result,
+ char *buffer, size_t buflen, int *errnop);
+ enum nss_status (*endgrent)(void);
+ enum nss_status (*initgroups_dyn)(const char *user, gid_t group,
+ long int *start, long int *size,
+ gid_t **groups, long int limit,
+ int *errnop);
+};
+
+struct proxy_ctx {
+ struct be_ctx *be;
+ int entry_cache_timeout;
+ struct proxy_nss_ops ops;
+};
+
+struct proxy_auth_ctx {
+ struct be_ctx *be;
+ char *pam_target;
+};
+
+struct authtok_conv {
+ uint32_t authtok_size;
+ uint8_t *authtok;
+};
+
+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;
+
+ 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(4, ("Conversation message: [%s]\n", msgm[i]->msg));
+ reply[i].resp_retcode = 0;
+ reply[i].resp = calloc(auth_data->authtok_size + 1,
+ sizeof(char));
+ if (reply[i].resp == NULL) goto failed;
+ memcpy(reply[i].resp, auth_data->authtok, auth_data->authtok_size);
+
+ break;
+ default:
+ DEBUG(1, ("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 void proxy_pam_handler_cache_done(struct tevent_req *treq);
+static void proxy_reply(struct be_req *req, int dp_err,
+ int error, const char *errstr);
+
+static void proxy_pam_handler(struct be_req *req) {
+ int ret;
+ int pam_status;
+ pam_handle_t *pamh=NULL;
+ struct authtok_conv *auth_data;
+ struct pam_conv conv;
+ struct pam_data *pd;
+ struct proxy_auth_ctx *ctx;;
+ bool cache_auth_data = false;
+
+ pd = talloc_get_type(req->req_data, struct pam_data);
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ ctx = talloc_get_type(req->be_ctx->bet_info[BET_AUTH].pvt_bet_data,
+ struct proxy_auth_ctx);
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ ctx = talloc_get_type(req->be_ctx->bet_info[BET_CHPASS].pvt_bet_data,
+ struct proxy_auth_ctx);
+ break;
+ case SSS_PAM_ACCT_MGMT:
+ ctx = talloc_get_type(req->be_ctx->bet_info[BET_ACCESS].pvt_bet_data,
+ struct proxy_auth_ctx);
+ break;
+ case SSS_PAM_SETCRED:
+ case SSS_PAM_OPEN_SESSION:
+ case SSS_PAM_CLOSE_SESSION:
+ pd->pam_status = PAM_SUCCESS;
+ proxy_reply(req, DP_ERR_OK, EOK, NULL);
+ return;
+ default:
+ DEBUG(1, ("Unsupported PAM task.\n"));
+ pd->pam_status = PAM_MODULE_UNKNOWN;
+ proxy_reply(req, DP_ERR_OK, EINVAL, "Unsupported PAM task");
+ return;
+ }
+
+ conv.conv=proxy_internal_conv;
+ auth_data = talloc_zero(req, struct authtok_conv);
+ conv.appdata_ptr=auth_data;
+
+ ret = pam_start(ctx->pam_target, pd->user, &conv, &pamh);
+ if (ret == PAM_SUCCESS) {
+ DEBUG(1, ("Pam transaction started.\n"));
+ ret = pam_set_item(pamh, PAM_TTY, pd->tty);
+ if (ret != PAM_SUCCESS) {
+ DEBUG(1, ("Setting PAM_TTY failed: %s.\n", pam_strerror(pamh, ret)));
+ }
+ ret = pam_set_item(pamh, PAM_RUSER, pd->ruser);
+ if (ret != PAM_SUCCESS) {
+ DEBUG(1, ("Setting PAM_RUSER failed: %s.\n", pam_strerror(pamh, ret)));
+ }
+ ret = pam_set_item(pamh, PAM_RHOST, pd->rhost);
+ if (ret != PAM_SUCCESS) {
+ DEBUG(1, ("Setting PAM_RHOST failed: %s.\n", pam_strerror(pamh, ret)));
+ }
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ auth_data->authtok_size = pd->authtok_size;
+ auth_data->authtok = pd->authtok;
+ pam_status = pam_authenticate(pamh, 0);
+ if ((pam_status == PAM_SUCCESS) &&
+ (req->be_ctx->domain->cache_credentials)) {
+ cache_auth_data = true;
+ }
+ 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:
+ if (pd->priv != 1) {
+ auth_data->authtok_size = pd->authtok_size;
+ auth_data->authtok = pd->authtok;
+ pam_status = pam_authenticate(pamh, 0);
+ if (pam_status != PAM_SUCCESS) break;
+ }
+ auth_data->authtok_size = pd->newauthtok_size;
+ auth_data->authtok = pd->newauthtok;
+ pam_status = pam_chauthtok(pamh, 0);
+ if ((pam_status == PAM_SUCCESS) &&
+ (req->be_ctx->domain->cache_credentials)) {
+ cache_auth_data = true;
+ }
+ break;
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ if (pd->priv != 1) {
+ auth_data->authtok_size = pd->authtok_size;
+ auth_data->authtok = pd->authtok;
+ pam_status = pam_authenticate(pamh, 0);
+ } else {
+ pam_status = PAM_SUCCESS;
+ }
+ break;
+ default:
+ DEBUG(1, ("unknown PAM call"));
+ pam_status=PAM_ABORT;
+ }
+
+ DEBUG(4, ("Pam result: [%d][%s]\n", pam_status,
+ pam_strerror(pamh, pam_status)));
+
+ if (pam_status == PAM_AUTHINFO_UNAVAIL) {
+ be_mark_offline(req->be_ctx);
+ }
+
+ ret = pam_end(pamh, pam_status);
+ if (ret != PAM_SUCCESS) {
+ pamh=NULL;
+ DEBUG(1, ("Cannot terminate pam transaction.\n"));
+ }
+
+ } else {
+ DEBUG(1, ("Failed to initialize pam transaction.\n"));
+ pam_status = PAM_SYSTEM_ERR;
+ }
+
+ pd->pam_status = pam_status;
+
+ if (cache_auth_data) {
+ struct tevent_req *subreq;
+ char *password;
+
+ password = talloc_size(req, auth_data->authtok_size + 1);
+ if (!password) {
+ /* password caching failures are not fatal errors */
+ return proxy_reply(req, DP_ERR_OK, EOK, NULL);
+ }
+ memcpy(password, auth_data->authtok, auth_data->authtok_size);
+ password[auth_data->authtok_size] = '\0';
+ talloc_set_destructor((TALLOC_CTX *)password, password_destructor);
+
+ subreq = sysdb_cache_password_send(req, req->be_ctx->ev,
+ req->be_ctx->sysdb, NULL,
+ req->be_ctx->domain,
+ pd->user, password);
+ if (!subreq) {
+ /* password caching failures are not fatal errors */
+ return proxy_reply(req, DP_ERR_OK, EOK, NULL);
+ }
+ tevent_req_set_callback(subreq, proxy_pam_handler_cache_done, req);
+ }
+
+ proxy_reply(req, DP_ERR_OK, EOK, NULL);
+}
+
+static void proxy_pam_handler_cache_done(struct tevent_req *subreq)
+{
+ struct be_req *req = tevent_req_callback_data(subreq, struct be_req);
+ int ret;
+
+ /* password caching failures are not fatal errors */
+ ret = sysdb_cache_password_recv(subreq);
+ talloc_zfree(subreq);
+
+ /* so we just log it any return */
+ if (ret) {
+ DEBUG(2, ("Failed to cache password (%d)[%s]!?\n",
+ ret, strerror(ret)));
+ }
+
+ return proxy_reply(req, DP_ERR_OK, EOK, NULL);
+}
+
+static void proxy_reply(struct be_req *req, int dp_err,
+ int error, const char *errstr)
+{
+ return req->fn(req, dp_err, error, errstr);
+}
+
+/* =Common-proxy-tevent_req-utils=========================================*/
+
+#define DEFAULT_BUFSIZE 4096
+#define MAX_BUF_SIZE 1024*1024 /* max 1MiB */
+
+struct proxy_state {
+ struct tevent_context *ev;
+ struct proxy_ctx *ctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+ const char *name;
+
+ struct sysdb_handle *handle;
+ struct passwd *pwd;
+ struct group *grp;
+ uid_t uid;
+ gid_t gid;
+};
+
+static void proxy_default_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_transaction_commit_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int proxy_default_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* =Getpwnam-wrapper======================================================*/
+
+static void get_pw_name_process(struct tevent_req *subreq);
+static void get_pw_name_remove_done(struct tevent_req *subreq);
+static void get_pw_name_add_done(struct tevent_req *subreq);
+
+static struct tevent_req *get_pw_name_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct proxy_ctx *ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *name)
+{
+ struct tevent_req *req, *subreq;
+ struct proxy_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct proxy_state);
+ if (!req) return NULL;
+
+ memset(state, 0, sizeof(struct proxy_state));
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = sysdb;
+ state->domain = domain;
+ state->name = name;
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, get_pw_name_process, req);
+
+ return req;
+}
+
+static void get_pw_name_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ struct proxy_ctx *ctx = state->ctx;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ char *buffer;
+ size_t buflen;
+ bool delete_user = false;
+ int ret;
+
+ DEBUG(7, ("Searching user by name (%s)\n", state->name));
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ state->pwd = talloc(state, struct passwd);
+ if (!state->pwd) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ buflen = DEFAULT_BUFSIZE;
+ buffer = talloc_size(state, buflen);
+ if (!buffer) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ /* FIXME: should we move this call outside the transaction to keep the
+ * transaction as short as possible ? */
+ status = ctx->ops.getpwnam_r(state->name, state->pwd,
+ buffer, buflen, &ret);
+
+ switch (status) {
+ case NSS_STATUS_NOTFOUND:
+
+ DEBUG(7, ("User %s not found.\n", state->name));
+ delete_user = true;
+ break;
+
+ case NSS_STATUS_SUCCESS:
+
+ DEBUG(7, ("User %s found: (%s, %d, %d)\n",
+ state->name, state->pwd->pw_name,
+ state->pwd->pw_uid, state->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(state->pwd->pw_uid, dom->id_min, dom->id_max) ||
+ OUT_OF_ID_RANGE(state->pwd->pw_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
+ state->name));
+ delete_user = true;
+ break;
+ }
+
+ subreq = sysdb_store_user_send(state, state->ev, state->handle,
+ state->domain,
+ state->pwd->pw_name,
+ state->pwd->pw_passwd,
+ state->pwd->pw_uid,
+ state->pwd->pw_gid,
+ state->pwd->pw_gecos,
+ state->pwd->pw_dir,
+ state->pwd->pw_shell,
+ NULL, ctx->entry_cache_timeout);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_pw_name_add_done, req);
+ return;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ tevent_req_error(req, ENXIO);
+ return;
+
+ default:
+ DEBUG(2, ("proxy -> getpwnam_r failed for '%s' <%d>\n",
+ state->name, status));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ if (delete_user) {
+ struct ldb_dn *dn;
+
+ DEBUG(7, ("User %s does not exist (or is invalid) on remote server,"
+ " deleting!\n", state->name));
+
+ dn = sysdb_user_dn(state->sysdb, state,
+ state->domain->name, state->name);
+ if (!dn) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ subreq = sysdb_delete_entry_send(state, state->ev, state->handle, dn, true);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_pw_name_remove_done, req);
+ }
+}
+
+static void get_pw_name_add_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ int ret;
+
+ ret = sysdb_store_user_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+}
+
+static void get_pw_name_remove_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+}
+
+/* =Getpwuid-wrapper======================================================*/
+
+static void get_pw_uid_process(struct tevent_req *subreq);
+static void get_pw_uid_remove_done(struct tevent_req *subreq);
+
+static struct tevent_req *get_pw_uid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct proxy_ctx *ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ uid_t uid)
+{
+ struct tevent_req *req, *subreq;
+ struct proxy_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct proxy_state);
+ if (!req) return NULL;
+
+ memset(state, 0, sizeof(struct proxy_state));
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = sysdb;
+ state->domain = domain;
+ state->uid = uid;
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, get_pw_uid_process, req);
+
+ return req;
+}
+
+static void get_pw_uid_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ struct proxy_ctx *ctx = state->ctx;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ char *buffer;
+ size_t buflen;
+ bool delete_user = false;
+ int ret;
+
+ DEBUG(7, ("Searching user by uid (%d)\n", state->uid));
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ state->pwd = talloc(state, struct passwd);
+ if (!state->pwd) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ buflen = DEFAULT_BUFSIZE;
+ buffer = talloc_size(state, buflen);
+ if (!buffer) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ /* always zero out the pwd structure */
+ memset(state->pwd, 0, sizeof(struct passwd));
+
+ status = ctx->ops.getpwuid_r(state->uid, state->pwd,
+ buffer, buflen, &ret);
+
+ switch (status) {
+ case NSS_STATUS_NOTFOUND:
+
+ DEBUG(7, ("User %d not found.\n", state->uid));
+ delete_user = true;
+ break;
+
+ case NSS_STATUS_SUCCESS:
+
+ DEBUG(7, ("User %d found (%s, %d, %d)\n",
+ state->uid, state->pwd->pw_name,
+ state->pwd->pw_uid, state->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(state->pwd->pw_uid, dom->id_min, dom->id_max) ||
+ OUT_OF_ID_RANGE(state->pwd->pw_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
+ state->name));
+ delete_user = true;
+ break;
+ }
+
+ subreq = sysdb_store_user_send(state, state->ev, state->handle,
+ state->domain,
+ state->pwd->pw_name,
+ state->pwd->pw_passwd,
+ state->pwd->pw_uid,
+ state->pwd->pw_gid,
+ state->pwd->pw_gecos,
+ state->pwd->pw_dir,
+ state->pwd->pw_shell,
+ NULL, ctx->entry_cache_timeout);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_pw_name_add_done, req);
+ return;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ tevent_req_error(req, ENXIO);
+ return;
+
+ default:
+ DEBUG(2, ("proxy -> getpwnam_r failed for '%s' <%d>\n",
+ state->name, status));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ if (delete_user) {
+ DEBUG(7, ("User %d does not exist (or is invalid) on remote server,"
+ " deleting!\n", state->uid));
+
+ subreq = sysdb_delete_user_send(state, state->ev,
+ NULL, state->handle,
+ state->domain,
+ NULL, state->uid);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_pw_uid_remove_done, req);
+ }
+}
+
+static void get_pw_uid_remove_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ int ret;
+
+ ret = sysdb_delete_user_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret && ret != ENOENT) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+}
+
+/* =Getpwent-wrapper======================================================*/
+
+struct enum_users_state {
+ struct tevent_context *ev;
+ struct proxy_ctx *ctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+ struct sysdb_handle *handle;
+
+ struct passwd *pwd;
+
+ size_t buflen;
+ char *buffer;
+
+ bool in_transaction;
+};
+
+static void enum_users_process(struct tevent_req *subreq);
+
+static struct tevent_req *enum_users_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct proxy_ctx *ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain)
+{
+ struct tevent_req *req, *subreq;
+ struct enum_users_state *state;
+ enum nss_status status;
+
+ DEBUG(7, ("Enumerating users\n"));
+
+ req = tevent_req_create(mem_ctx, &state, struct enum_users_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = sysdb;
+ state->domain = domain;
+ state->handle = NULL;
+
+ state->pwd = talloc(state, struct passwd);
+ if (!state->pwd) {
+ tevent_req_error(req, ENOMEM);
+ goto fail;
+ }
+
+ state->buflen = DEFAULT_BUFSIZE;
+ state->buffer = talloc_size(state, state->buflen);
+ if (!state->buffer) {
+ tevent_req_error(req, ENOMEM);
+ goto fail;
+ }
+
+ state->in_transaction = false;
+
+ status = ctx->ops.setpwent();
+ if (status != NSS_STATUS_SUCCESS) {
+ tevent_req_error(req, EIO);
+ goto fail;
+ }
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, enum_users_process, req);
+
+ return req;
+
+fail:
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void enum_users_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct enum_users_state *state = tevent_req_data(req,
+ struct enum_users_state);
+ struct proxy_ctx *ctx = state->ctx;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ char *newbuf;
+ int ret;
+
+ if (!state->in_transaction) {
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ if (ret) {
+ goto fail;
+ }
+ talloc_zfree(subreq);
+
+ state->in_transaction = true;
+ } else {
+ ret = sysdb_store_user_recv(subreq);
+ if (ret) {
+ /* Do not fail completely on errors.
+ * Just report the failure to save and go on */
+ DEBUG(2, ("Failed to store user. Ignoring.\n"));
+ }
+ talloc_zfree(subreq);
+ }
+
+again:
+ /* always zero out the pwd structure */
+ memset(state->pwd, 0, sizeof(struct passwd));
+
+ /* get entry */
+ status = ctx->ops.getpwent_r(state->pwd,
+ state->buffer, state->buflen, &ret);
+
+ switch (status) {
+ case NSS_STATUS_TRYAGAIN:
+ /* buffer too small ? */
+ if (state->buflen < MAX_BUF_SIZE) {
+ state->buflen *= 2;
+ }
+ if (state->buflen > MAX_BUF_SIZE) {
+ state->buflen = MAX_BUF_SIZE;
+ }
+ newbuf = talloc_realloc_size(state, state->buffer, state->buflen);
+ if (!newbuf) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ state->buffer = newbuf;
+ goto again;
+
+ case NSS_STATUS_NOTFOUND:
+
+ /* we are done here */
+ DEBUG(7, ("Enumeration completed.\n"));
+
+ ctx->ops.endpwent();
+ subreq = sysdb_transaction_commit_send(state, state->ev,
+ state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+ return;
+
+ case NSS_STATUS_SUCCESS:
+
+ DEBUG(7, ("User found (%s, %d, %d)\n", state->pwd->pw_name,
+ state->pwd->pw_uid, state->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(state->pwd->pw_uid, dom->id_min, dom->id_max) ||
+ OUT_OF_ID_RANGE(state->pwd->pw_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
+ state->pwd->pw_name));
+
+ goto again; /* skip */
+ }
+
+ subreq = sysdb_store_user_send(state, state->ev, state->handle,
+ state->domain,
+ state->pwd->pw_name,
+ state->pwd->pw_passwd,
+ state->pwd->pw_uid,
+ state->pwd->pw_gid,
+ state->pwd->pw_gecos,
+ state->pwd->pw_dir,
+ state->pwd->pw_shell,
+ NULL, ctx->entry_cache_timeout);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, enum_users_process, req);
+ return;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ ret = ENXIO;
+ goto fail;
+
+ default:
+ DEBUG(2, ("proxy -> getpwent_r failed (%d)[%s]\n",
+ ret, strerror(ret)));
+ goto fail;
+ }
+
+fail:
+ ctx->ops.endpwent();
+ tevent_req_error(req, ret);
+}
+
+/* =Getgrnam-wrapper======================================================*/
+
+#define DEBUG_GR_MEM(level, state) \
+ do { \
+ if (debug_level >= level) { \
+ if (!state->grp->gr_mem || !state->grp->gr_mem[0]) { \
+ DEBUG(level, ("Group %s has no members!\n", \
+ state->grp->gr_name)); \
+ } else { \
+ int i = 0; \
+ while (state->grp->gr_mem[i]) { \
+ /* count */ \
+ i++; \
+ } \
+ DEBUG(level, ("Group %s has %d members!\n", \
+ state->grp->gr_name, i)); \
+ } \
+ } \
+ } while(0)
+
+static void get_gr_name_process(struct tevent_req *subreq);
+static void get_gr_name_remove_done(struct tevent_req *subreq);
+static void get_gr_name_add_done(struct tevent_req *subreq);
+
+static struct tevent_req *get_gr_name_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct proxy_ctx *ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *name)
+{
+ struct tevent_req *req, *subreq;
+ struct proxy_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct proxy_state);
+ if (!req) return NULL;
+
+ memset(state, 0, sizeof(struct proxy_state));
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = sysdb;
+ state->domain = domain;
+ state->name = name;
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, get_gr_name_process, req);
+
+ return req;
+}
+
+static void get_gr_name_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ struct proxy_ctx *ctx = state->ctx;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ char *buffer;
+ char *newbuf;
+ size_t buflen;
+ bool delete_group = false;
+ struct sysdb_attrs *members;
+ int ret;
+
+ DEBUG(7, ("Searching group by name (%s)\n", state->name));
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ state->grp = talloc(state, struct group);
+ if (!state->grp) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ buflen = DEFAULT_BUFSIZE;
+ buffer = talloc_size(state, buflen);
+ if (!buffer) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ /* FIXME: should we move this call outside the transaction to keep the
+ * transaction as short as possible ? */
+again:
+ /* always zero out the grp structure */
+ memset(state->grp, 0, sizeof(struct group));
+
+ status = ctx->ops.getgrnam_r(state->name, state->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(state, buffer, buflen);
+ if (!newbuf) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ buffer = newbuf;
+ goto again;
+
+ case NSS_STATUS_NOTFOUND:
+
+ DEBUG(7, ("Group %s not found.\n", state->name));
+ delete_group = true;
+ break;
+
+ case NSS_STATUS_SUCCESS:
+
+ DEBUG(7, ("Group %s found: (%s, %d)\n", state->name,
+ state->grp->gr_name, state->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(state->grp->gr_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
+ state->name));
+ delete_group = true;
+ break;
+ }
+
+ DEBUG_GR_MEM(7, state);
+
+ if (state->grp->gr_mem && state->grp->gr_mem[0]) {
+ members = sysdb_new_attrs(state);
+ if (!members) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER,
+ state->domain->name,
+ (const char **)state->grp->gr_mem);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ } else {
+ members = NULL;
+ }
+
+ subreq = sysdb_store_group_send(state, state->ev, state->handle,
+ state->domain,
+ state->grp->gr_name,
+ state->grp->gr_gid,
+ members,
+ ctx->entry_cache_timeout);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_gr_name_add_done, req);
+ return;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ tevent_req_error(req, ENXIO);
+ return;
+
+ default:
+ DEBUG(2, ("proxy -> getgrnam_r failed for '%s' <%d>\n",
+ state->name, status));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ if (delete_group) {
+ struct ldb_dn *dn;
+
+ DEBUG(7, ("Group %s does not exist (or is invalid) on remote server,"
+ " deleting!\n", state->name));
+
+ dn = sysdb_group_dn(state->sysdb, state,
+ state->domain->name, state->name);
+ if (!dn) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ subreq = sysdb_delete_entry_send(state, state->ev, state->handle, dn, true);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_gr_name_remove_done, req);
+ }
+}
+
+static void get_gr_name_add_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ int ret;
+
+ ret = sysdb_store_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+}
+
+static void get_gr_name_remove_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+}
+
+/* =Getgrgid-wrapper======================================================*/
+
+static void get_gr_gid_process(struct tevent_req *subreq);
+static void get_gr_gid_remove_done(struct tevent_req *subreq);
+
+static struct tevent_req *get_gr_gid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct proxy_ctx *ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ gid_t gid)
+{
+ struct tevent_req *req, *subreq;
+ struct proxy_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct proxy_state);
+ if (!req) return NULL;
+
+ memset(state, 0, sizeof(struct proxy_state));
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = sysdb;
+ state->domain = domain;
+ state->gid = gid;
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, get_gr_gid_process, req);
+
+ return req;
+}
+
+static void get_gr_gid_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ struct proxy_ctx *ctx = state->ctx;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ char *buffer;
+ char *newbuf;
+ size_t buflen;
+ bool delete_group = false;
+ struct sysdb_attrs *members;
+ int ret;
+
+ DEBUG(7, ("Searching group by gid (%d)\n", state->gid));
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ state->grp = talloc(state, struct group);
+ if (!state->grp) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ buflen = DEFAULT_BUFSIZE;
+ buffer = talloc_size(state, buflen);
+ if (!buffer) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+again:
+ /* always zero out the group structure */
+ memset(state->grp, 0, sizeof(struct group));
+
+ status = ctx->ops.getgrgid_r(state->gid, state->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(state, buffer, buflen);
+ if (!newbuf) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ buffer = newbuf;
+ goto again;
+
+ case NSS_STATUS_NOTFOUND:
+
+ DEBUG(7, ("Group %d not found.\n", state->gid));
+ delete_group = true;
+ break;
+
+ case NSS_STATUS_SUCCESS:
+
+ DEBUG(7, ("Group %d found (%s, %d)\n", state->gid,
+ state->grp->gr_name, state->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(state->grp->gr_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
+ state->grp->gr_name));
+ delete_group = true;
+ break;
+ }
+
+ DEBUG_GR_MEM(7, state);
+
+ if (state->grp->gr_mem && state->grp->gr_mem[0]) {
+ members = sysdb_new_attrs(state);
+ if (!members) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER,
+ state->domain->name,
+ (const char **)state->grp->gr_mem);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ } else {
+ members = NULL;
+ }
+
+ subreq = sysdb_store_group_send(state, state->ev, state->handle,
+ state->domain,
+ state->grp->gr_name,
+ state->grp->gr_gid,
+ members,
+ ctx->entry_cache_timeout);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_gr_name_add_done, req);
+ return;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ tevent_req_error(req, ENXIO);
+ return;
+
+ default:
+ DEBUG(2, ("proxy -> getgrgid_r failed for '%d' <%d>\n",
+ state->gid, status));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ if (delete_group) {
+
+ DEBUG(7, ("Group %d does not exist (or is invalid) on remote server,"
+ " deleting!\n", state->gid));
+
+ subreq = sysdb_delete_group_send(state, state->ev,
+ NULL, state->handle,
+ state->domain,
+ NULL, state->gid);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_gr_gid_remove_done, req);
+ }
+}
+
+static void get_gr_gid_remove_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ int ret;
+
+ ret = sysdb_delete_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret && ret != ENOENT) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+}
+
+/* =Getgrent-wrapper======================================================*/
+
+struct enum_groups_state {
+ struct tevent_context *ev;
+ struct proxy_ctx *ctx;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+ struct sysdb_handle *handle;
+
+ struct group *grp;
+
+ size_t buflen;
+ char *buffer;
+
+ bool in_transaction;
+};
+
+static void enum_groups_process(struct tevent_req *subreq);
+
+static struct tevent_req *enum_groups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct proxy_ctx *ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain)
+{
+ struct tevent_req *req, *subreq;
+ struct enum_groups_state *state;
+ enum nss_status status;
+
+ DEBUG(7, ("Enumerating groups\n"));
+
+ req = tevent_req_create(mem_ctx, &state, struct enum_groups_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = sysdb;
+ state->domain = domain;
+ state->handle = NULL;
+
+ state->grp = talloc(state, struct group);
+ if (!state->grp) {
+ tevent_req_error(req, ENOMEM);
+ goto fail;
+ }
+
+ state->buflen = DEFAULT_BUFSIZE;
+ state->buffer = talloc_size(state, state->buflen);
+ if (!state->buffer) {
+ tevent_req_error(req, ENOMEM);
+ goto fail;
+ }
+
+ state->in_transaction = false;
+
+ status = ctx->ops.setgrent();
+ if (status != NSS_STATUS_SUCCESS) {
+ tevent_req_error(req, EIO);
+ goto fail;
+ }
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, enum_groups_process, req);
+
+ return req;
+
+fail:
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void enum_groups_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct enum_groups_state *state = tevent_req_data(req,
+ struct enum_groups_state);
+ struct proxy_ctx *ctx = state->ctx;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ struct sysdb_attrs *members;
+ char *newbuf;
+ int ret;
+
+ if (!state->in_transaction) {
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ state->in_transaction = true;
+ } else {
+ ret = sysdb_store_group_recv(subreq);
+ if (ret) {
+ /* Do not fail completely on errors.
+ * Just report the failure to save and go on */
+ DEBUG(2, ("Failed to store group. Ignoring.\n"));
+ }
+ talloc_zfree(subreq);
+ }
+
+again:
+ /* always zero out the grp structure */
+ memset(state->grp, 0, sizeof(struct group));
+
+ /* get entry */
+ status = ctx->ops.getgrent_r(state->grp,
+ state->buffer, state->buflen, &ret);
+
+ switch (status) {
+ case NSS_STATUS_TRYAGAIN:
+ /* buffer too small ? */
+ if (state->buflen < MAX_BUF_SIZE) {
+ state->buflen *= 2;
+ }
+ if (state->buflen > MAX_BUF_SIZE) {
+ state->buflen = MAX_BUF_SIZE;
+ }
+ newbuf = talloc_realloc_size(state, state->buffer, state->buflen);
+ if (!newbuf) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ state->buffer = newbuf;
+ goto again;
+
+ case NSS_STATUS_NOTFOUND:
+
+ /* we are done here */
+ DEBUG(7, ("Enumeration completed.\n"));
+
+ ctx->ops.endgrent();
+ subreq = sysdb_transaction_commit_send(state, state->ev,
+ state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+ return;
+
+ case NSS_STATUS_SUCCESS:
+
+ DEBUG(7, ("Group found (%s, %d)\n",
+ state->grp->gr_name, state->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(state->grp->gr_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
+ state->grp->gr_name));
+
+ goto again; /* skip */
+ }
+
+ DEBUG_GR_MEM(7, state);
+
+ if (state->grp->gr_mem && state->grp->gr_mem[0]) {
+ members = sysdb_new_attrs(state);
+ if (!members) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER,
+ state->domain->name,
+ (const char **)state->grp->gr_mem);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ } else {
+ members = NULL;
+ }
+
+ subreq = sysdb_store_group_send(state, state->ev, state->handle,
+ state->domain,
+ state->grp->gr_name,
+ state->grp->gr_gid,
+ members,
+ ctx->entry_cache_timeout);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, enum_groups_process, req);
+ return;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ ret = ENXIO;
+ goto fail;
+
+ default:
+ DEBUG(2, ("proxy -> getgrent_r failed (%d)[%s]\n",
+ ret, strerror(ret)));
+ goto fail;
+ }
+
+fail:
+ ctx->ops.endgrent();
+ tevent_req_error(req, ret);
+}
+
+
+/* =Initgroups-wrapper====================================================*/
+
+static void get_initgr_process(struct tevent_req *subreq);
+static void get_initgr_groups_process(struct tevent_req *subreq);
+static void get_initgr_groups_done(struct tevent_req *subreq);
+static struct tevent_req *get_groups_by_gid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct proxy_ctx *ctx,
+ struct sss_domain_info *domain,
+ gid_t *gids, int num_gids);
+static int get_groups_by_gid_recv(struct tevent_req *req);
+static void get_groups_by_gid_process(struct tevent_req *subreq);
+static struct tevent_req *get_group_from_gid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct proxy_ctx *ctx,
+ struct sss_domain_info *domain,
+ gid_t gid);
+static int get_group_from_gid_recv(struct tevent_req *req);
+static void get_group_from_gid_send_del_done(struct tevent_req *subreq);
+static void get_group_from_gid_send_add_done(struct tevent_req *subreq);
+
+
+static struct tevent_req *get_initgr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct proxy_ctx *ctx,
+ struct sysdb_ctx *sysdb,
+ struct sss_domain_info *domain,
+ const char *name)
+{
+ struct tevent_req *req, *subreq;
+ struct proxy_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct proxy_state);
+ if (!req) return NULL;
+
+ memset(state, 0, sizeof(struct proxy_state));
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sysdb = sysdb;
+ state->domain = domain;
+ state->name = name;
+
+ subreq = sysdb_transaction_send(state, state->ev, state->sysdb);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, get_initgr_process, req);
+
+ return req;
+}
+
+static void get_initgr_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ struct proxy_ctx *ctx = state->ctx;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ char *buffer;
+ size_t buflen;
+ bool delete_user = false;
+ int ret;
+
+ ret = sysdb_transaction_recv(subreq, state, &state->handle);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ state->pwd = talloc(state, struct passwd);
+ if (!state->pwd) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ buflen = DEFAULT_BUFSIZE;
+ buffer = talloc_size(state, buflen);
+ if (!buffer) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ /* FIXME: should we move this call outside the transaction to keep the
+ * transaction as short as possible ? */
+ status = ctx->ops.getpwnam_r(state->name, state->pwd,
+ buffer, buflen, &ret);
+
+ switch (status) {
+ case NSS_STATUS_NOTFOUND:
+
+ delete_user = true;
+ break;
+
+ case NSS_STATUS_SUCCESS:
+
+ /* 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(state->pwd->pw_uid, dom->id_min, dom->id_max) ||
+ OUT_OF_ID_RANGE(state->pwd->pw_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("User [%s] filtered out! (id out of range)\n",
+ state->name));
+ delete_user = true;
+ break;
+ }
+
+ subreq = sysdb_store_user_send(state, state->ev, state->handle,
+ state->domain,
+ state->pwd->pw_name,
+ state->pwd->pw_passwd,
+ state->pwd->pw_uid,
+ state->pwd->pw_gid,
+ state->pwd->pw_gecos,
+ state->pwd->pw_dir,
+ state->pwd->pw_shell,
+ NULL, ctx->entry_cache_timeout);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_initgr_groups_process, req);
+ return;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ tevent_req_error(req, ENXIO);
+ return;
+
+ default:
+ DEBUG(2, ("proxy -> getpwnam_r failed for '%s' <%d>\n",
+ state->name, status));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ if (delete_user) {
+ struct ldb_dn *dn;
+
+ dn = sysdb_user_dn(state->sysdb, state,
+ state->domain->name, state->name);
+ if (!dn) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ subreq = sysdb_delete_entry_send(state, state->ev, state->handle, dn, true);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_pw_name_remove_done, req);
+ }
+}
+
+static void get_initgr_groups_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ struct proxy_ctx *ctx = state->ctx;
+ enum nss_status status;
+ long int limit;
+ long int size;
+ long int num;
+ long int num_gids;
+ gid_t *gids;
+ int ret;
+
+ ret = sysdb_store_user_recv(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ num_gids = 0;
+ limit = 4096;
+ num = 4096;
+ size = num*sizeof(gid_t);
+ gids = talloc_size(state, size);
+ if (!gids) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ state->gid = state->pwd->pw_gid;
+
+again:
+ /* FIXME: should we move this call outside the transaction to keep the
+ * transaction as short as possible ? */
+ status = ctx->ops.initgroups_dyn(state->name, state->gid, &num_gids,
+ &num, &gids, limit, &ret);
+ switch (status) {
+ case 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(state, gids, size);
+ if (!gids) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ goto again; /* retry with more memory */
+
+ case NSS_STATUS_SUCCESS:
+ DEBUG(4, ("User [%s] appears to be member of %lu groups\n",
+ state->name, num_gids));
+
+ subreq = get_groups_by_gid_send(state, state->ev, state->handle,
+ state->ctx, state->domain,
+ gids, num_gids);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_initgr_groups_done, req);
+ break;
+
+ default:
+ DEBUG(2, ("proxy -> initgroups_dyn failed (%d)[%s]\n",
+ ret, strerror(ret)));
+ tevent_req_error(req, EIO);
+ return;
+ }
+}
+
+static void get_initgr_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct proxy_state *state = tevent_req_data(req,
+ struct proxy_state);
+ int ret;
+
+ ret = get_groups_by_gid_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, proxy_default_done, req);
+}
+
+struct get_groups_state {
+ struct tevent_context *ev;
+ struct sysdb_handle *handle;
+ struct proxy_ctx *ctx;
+ struct sss_domain_info *domain;
+
+ gid_t *gids;
+ int num_gids;
+ int cur_gid;
+};
+
+static struct tevent_req *get_groups_by_gid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct proxy_ctx *ctx,
+ struct sss_domain_info *domain,
+ gid_t *gids, int num_gids)
+{
+ struct tevent_req *req, *subreq;
+ struct get_groups_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct get_groups_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ctx = ctx;
+ state->domain = domain;
+ state->gids = gids;
+ state->num_gids = num_gids;
+ state->cur_gid = 0;
+
+ subreq = get_group_from_gid_send(state, ev, handle, ctx, domain, gids[0]);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, get_groups_by_gid_process, req);
+
+ return req;
+}
+
+static void get_groups_by_gid_process(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct get_groups_state *state = tevent_req_data(req,
+ struct get_groups_state);
+ int ret;
+
+ ret = get_group_from_gid_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->cur_gid++;
+ if (state->cur_gid >= state->num_gids) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = get_group_from_gid_send(state,
+ state->ev, state->handle,
+ state->ctx, state->domain,
+ state->gids[state->cur_gid]);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, get_groups_by_gid_process, req);
+}
+
+static int get_groups_by_gid_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+static struct tevent_req *get_group_from_gid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_handle *handle,
+ struct proxy_ctx *ctx,
+ struct sss_domain_info *domain,
+ gid_t gid)
+{
+ struct tevent_req *req, *subreq;
+ struct proxy_state *state;
+ struct sss_domain_info *dom = ctx->be->domain;
+ enum nss_status status;
+ char *buffer;
+ char *newbuf;
+ size_t buflen;
+ bool delete_group = false;
+ struct sysdb_attrs *members;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct proxy_state);
+ if (!req) return NULL;
+
+ memset(state, 0, sizeof(struct proxy_state));
+
+ state->ev = ev;
+ state->handle = handle;
+ state->ctx = ctx;
+ state->domain = domain;
+ state->gid = gid;
+
+ state->grp = talloc(state, struct group);
+ if (!state->grp) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ buflen = DEFAULT_BUFSIZE;
+ buffer = talloc_size(state, buflen);
+ if (!buffer) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+again:
+ /* always zero out the grp structure */
+ memset(state->grp, 0, sizeof(struct group));
+
+ status = ctx->ops.getgrgid_r(state->gid, state->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(state, buffer, buflen);
+ if (!newbuf) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ buffer = newbuf;
+ goto again;
+
+ case NSS_STATUS_NOTFOUND:
+
+ delete_group = true;
+ break;
+
+ case NSS_STATUS_SUCCESS:
+
+ /* gid=0 is an invalid value */
+ /* also check that the id is in the valid range for this domain */
+ if (OUT_OF_ID_RANGE(state->grp->gr_gid, dom->id_min, dom->id_max)) {
+
+ DEBUG(2, ("Group [%s] filtered out! (id out of range)\n",
+ state->grp->gr_name));
+ delete_group = true;
+ break;
+ }
+
+ if (state->grp->gr_mem && state->grp->gr_mem[0]) {
+ members = sysdb_new_attrs(state);
+ if (!members) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ ret = sysdb_attrs_users_from_str_list(members, SYSDB_MEMBER,
+ state->domain->name,
+ (const char **)state->grp->gr_mem);
+ if (ret) {
+ goto fail;
+ }
+ } else {
+ members = NULL;
+ }
+
+ subreq = sysdb_store_group_send(state, state->ev, state->handle,
+ state->domain,
+ state->grp->gr_name,
+ state->grp->gr_gid,
+ members,
+ ctx->entry_cache_timeout);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, get_group_from_gid_send_add_done, req);
+ break;
+
+ case NSS_STATUS_UNAVAIL:
+ /* "remote" backend unavailable. Enter offline mode */
+ ret = ENXIO;
+ goto fail;
+
+ default:
+ DEBUG(2, ("proxy -> getgrgid_r failed for '%d' <%d>\n",
+ state->gid, status));
+ ret = EIO;
+ goto fail;
+ }
+
+ if (delete_group) {
+ subreq = sysdb_delete_group_send(state, state->ev,
+ NULL, state->handle,
+ state->domain,
+ NULL, state->gid);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, get_group_from_gid_send_del_done, req);
+ }
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void get_group_from_gid_send_add_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_store_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static void get_group_from_gid_send_del_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret && ret != ENOENT) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int get_group_from_gid_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+
+/* =Proxy_Id-Functions====================================================*/
+
+static void proxy_get_account_info_done(struct tevent_req *subreq);
+
+/* TODO: See if we can use async_req code */
+static void proxy_get_account_info(struct be_req *breq)
+{
+ struct tevent_req *subreq;
+ struct be_acct_req *ar;
+ struct proxy_ctx *ctx;
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sss_domain_info *domain;
+ uid_t uid;
+ gid_t gid;
+
+ ar = talloc_get_type(breq->req_data, struct be_acct_req);
+ ctx = talloc_get_type(breq->be_ctx->bet_info[BET_ID].pvt_bet_data, struct proxy_ctx);
+ ev = breq->be_ctx->ev;
+ sysdb = breq->be_ctx->sysdb;
+ domain = breq->be_ctx->domain;
+
+ if (be_is_offline(breq->be_ctx)) {
+ return proxy_reply(breq, DP_ERR_OFFLINE, EAGAIN, "Offline");
+ }
+
+ /* for now we support only core attrs */
+ if (ar->attr_type != BE_ATTR_CORE) {
+ return proxy_reply(breq, DP_ERR_FATAL, EINVAL, "Invalid attr type");
+ }
+
+ switch (ar->entry_type & 0xFFF) {
+ case BE_REQ_USER: /* user */
+ switch (ar->filter_type) {
+ case BE_FILTER_NAME:
+ if (strchr(ar->filter_value, '*')) {
+ subreq = enum_users_send(breq, ev, ctx,
+ sysdb, domain);
+ if (!subreq) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENOMEM, "Out of memory");
+ }
+ tevent_req_set_callback(subreq,
+ proxy_get_account_info_done, breq);
+ return;
+ } else {
+ subreq = get_pw_name_send(breq, ev, ctx,
+ sysdb, domain,
+ ar->filter_value);
+ if (!subreq) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENOMEM, "Out of memory");
+ }
+ tevent_req_set_callback(subreq,
+ proxy_get_account_info_done, breq);
+ return;
+ }
+ break;
+
+ case BE_FILTER_IDNUM:
+ if (strchr(ar->filter_value, '*')) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid attr type");
+ } else {
+ char *endptr;
+ errno = 0;
+ uid = (uid_t)strtol(ar->filter_value, &endptr, 0);
+ if (errno || *endptr || (ar->filter_value == endptr)) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid attr type");
+ }
+ subreq = get_pw_uid_send(breq, ev, ctx,
+ sysdb, domain, uid);
+ if (!subreq) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENOMEM, "Out of memory");
+ }
+ tevent_req_set_callback(subreq,
+ proxy_get_account_info_done, breq);
+ return;
+ }
+ break;
+ default:
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid filter type");
+ }
+ break;
+
+ case BE_REQ_GROUP: /* group */
+ switch (ar->filter_type) {
+ case BE_FILTER_NAME:
+ if (strchr(ar->filter_value, '*')) {
+ subreq = enum_groups_send(breq, ev, ctx,
+ sysdb, domain);
+ if (!subreq) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENOMEM, "Out of memory");
+ }
+ tevent_req_set_callback(subreq,
+ proxy_get_account_info_done, breq);
+ return;
+ } else {
+ subreq = get_gr_name_send(breq, ev, ctx,
+ sysdb, domain,
+ ar->filter_value);
+ if (!subreq) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENOMEM, "Out of memory");
+ }
+ tevent_req_set_callback(subreq,
+ proxy_get_account_info_done, breq);
+ return;
+ }
+ break;
+ case BE_FILTER_IDNUM:
+ if (strchr(ar->filter_value, '*')) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid attr type");
+ } else {
+ char *endptr;
+ errno = 0;
+ gid = (gid_t)strtol(ar->filter_value, &endptr, 0);
+ if (errno || *endptr || (ar->filter_value == endptr)) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid attr type");
+ }
+ subreq = get_gr_gid_send(breq, ev, ctx,
+ sysdb, domain, gid);
+ if (!subreq) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENOMEM, "Out of memory");
+ }
+ tevent_req_set_callback(subreq,
+ proxy_get_account_info_done, breq);
+ return;
+ }
+ break;
+ default:
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid filter type");
+ }
+ break;
+
+ case BE_REQ_INITGROUPS: /* init groups for user */
+ if (ar->filter_type != BE_FILTER_NAME) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid filter type");
+ }
+ if (strchr(ar->filter_value, '*')) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid filter value");
+ }
+ if (ctx->ops.initgroups_dyn == NULL) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENODEV, "Initgroups call not supported");
+ }
+ subreq = get_initgr_send(breq, ev, ctx, sysdb,
+ domain, ar->filter_value);
+ if (!subreq) {
+ return proxy_reply(breq, DP_ERR_FATAL,
+ ENOMEM, "Out of memory");
+ }
+ tevent_req_set_callback(subreq,
+ proxy_get_account_info_done, breq);
+ return;
+
+ default: /*fail*/
+ break;
+ }
+
+ return proxy_reply(breq, DP_ERR_FATAL,
+ EINVAL, "Invalid request type");
+}
+
+static void proxy_get_account_info_done(struct tevent_req *subreq)
+{
+ struct be_req *breq = tevent_req_callback_data(subreq,
+ struct be_req);
+ int ret;
+ ret = proxy_default_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ if (ret == ENXIO) {
+ DEBUG(2, ("proxy returned UNAVAIL error, going offline!\n"));
+ be_mark_offline(breq->be_ctx);
+ }
+ proxy_reply(breq, DP_ERR_FATAL, ret, NULL);
+ return;
+ }
+ proxy_reply(breq, DP_ERR_OK, EOK, NULL);
+}
+
+static void proxy_shutdown(struct be_req *req)
+{
+ /* TODO: Clean up any internal data */
+ req->fn(req, DP_ERR_OK, EOK, NULL);
+}
+
+static void proxy_auth_shutdown(struct be_req *req)
+{
+ talloc_free(req->be_ctx->bet_info[BET_AUTH].pvt_bet_data);
+ req->fn(req, DP_ERR_OK, EOK, NULL);
+}
+
+struct bet_ops proxy_id_ops = {
+ .handler = proxy_get_account_info,
+ .finalize = proxy_shutdown
+};
+
+struct bet_ops proxy_auth_ops = {
+ .handler = proxy_pam_handler,
+ .finalize = proxy_auth_shutdown
+};
+
+struct bet_ops proxy_access_ops = {
+ .handler = proxy_pam_handler,
+ .finalize = proxy_auth_shutdown
+};
+
+struct bet_ops proxy_chpass_ops = {
+ .handler = proxy_pam_handler,
+ .finalize = proxy_auth_shutdown
+};
+
+static void *proxy_dlsym(void *handle, const char *functemp, char *libname)
+{
+ char *funcname;
+ void *funcptr;
+
+ funcname = talloc_asprintf(NULL, functemp, libname);
+ if (funcname == NULL) return NULL;
+
+ funcptr = dlsym(handle, funcname);
+ talloc_free(funcname);
+
+ return funcptr;
+}
+
+int sssm_proxy_init(struct be_ctx *bectx,
+ struct bet_ops **ops, void **pvt_data)
+{
+ struct proxy_ctx *ctx;
+ char *libname;
+ char *libpath;
+ void *handle;
+ int ret;
+
+ ctx = talloc_zero(bectx, struct proxy_ctx);
+ if (!ctx) {
+ return ENOMEM;
+ }
+ ctx->be = bectx;
+
+ ret = confdb_get_int(bectx->cdb, ctx, bectx->conf_path,
+ CONFDB_DOMAIN_ENTRY_CACHE_TIMEOUT, 600,
+ &ctx->entry_cache_timeout);
+ if (ret != EOK) goto done;
+
+ ret = confdb_get_string(bectx->cdb, ctx, bectx->conf_path,
+ CONFDB_PROXY_LIBNAME, NULL, &libname);
+ if (ret != EOK) goto done;
+ if (libname == NULL) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ libpath = talloc_asprintf(ctx, "libnss_%s.so.2", libname);
+ if (!libpath) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ handle = dlopen(libpath, RTLD_NOW);
+ if (!handle) {
+ DEBUG(0, ("Unable to load %s module with path, error: %s\n",
+ libpath, dlerror()));
+ ret = ELIBACC;
+ goto done;
+ }
+
+ ctx->ops.getpwnam_r = proxy_dlsym(handle, "_nss_%s_getpwnam_r", libname);
+ if (!ctx->ops.getpwnam_r) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.getpwuid_r = proxy_dlsym(handle, "_nss_%s_getpwuid_r", libname);
+ if (!ctx->ops.getpwuid_r) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.setpwent = proxy_dlsym(handle, "_nss_%s_setpwent", libname);
+ if (!ctx->ops.setpwent) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.getpwent_r = proxy_dlsym(handle, "_nss_%s_getpwent_r", libname);
+ if (!ctx->ops.getpwent_r) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.endpwent = proxy_dlsym(handle, "_nss_%s_endpwent", libname);
+ if (!ctx->ops.endpwent) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.getgrnam_r = proxy_dlsym(handle, "_nss_%s_getgrnam_r", libname);
+ if (!ctx->ops.getgrnam_r) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.getgrgid_r = proxy_dlsym(handle, "_nss_%s_getgrgid_r", libname);
+ if (!ctx->ops.getgrgid_r) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.setgrent = proxy_dlsym(handle, "_nss_%s_setgrent", libname);
+ if (!ctx->ops.setgrent) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.getgrent_r = proxy_dlsym(handle, "_nss_%s_getgrent_r", libname);
+ if (!ctx->ops.getgrent_r) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.endgrent = proxy_dlsym(handle, "_nss_%s_endgrent", libname);
+ if (!ctx->ops.endgrent) {
+ DEBUG(0, ("Failed to load NSS fns, error: %s\n", dlerror()));
+ ret = ELIBBAD;
+ goto done;
+ }
+
+ ctx->ops.initgroups_dyn = proxy_dlsym(handle, "_nss_%s_initgroups_dyn",
+ libname);
+ if (!ctx->ops.initgroups_dyn) {
+ DEBUG(1, ("The '%s' library does not provides the "
+ "_nss_XXX_initgroups_dyn function!\n"
+ "initgroups will be slow as it will require "
+ "full groups enumeration!\n", libname));
+ }
+
+ *ops = &proxy_id_ops;
+ *pvt_data = ctx;
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(ctx);
+ }
+ return ret;
+}
+
+int sssm_proxy_auth_init(struct be_ctx *bectx,
+ struct bet_ops **ops, void **pvt_data)
+{
+ struct proxy_auth_ctx *ctx;
+ int ret;
+
+ ctx = talloc(bectx, struct proxy_auth_ctx);
+ if (!ctx) {
+ return ENOMEM;
+ }
+ ctx->be = bectx;
+
+ ret = confdb_get_string(bectx->cdb, ctx, bectx->conf_path,
+ CONFDB_PROXY_PAM_TARGET, NULL,
+ &ctx->pam_target);
+ if (ret != EOK) goto done;
+ if (!ctx->pam_target) {
+ DEBUG(1, ("Missing option proxy_pam_target.\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ *ops = &proxy_auth_ops;
+ *pvt_data = ctx;
+
+done:
+ if (ret != EOK) {
+ talloc_free(ctx);
+ }
+ return ret;
+}
+
+int sssm_proxy_access_init(struct be_ctx *bectx,
+ struct bet_ops **ops, void **pvt_data)
+{
+ int ret;
+ ret = sssm_proxy_auth_init(bectx, ops, pvt_data);
+ *ops = &proxy_access_ops;
+ return ret;
+}
+
+int sssm_proxy_chpass_init(struct be_ctx *bectx,
+ struct bet_ops **ops, void **pvt_data)
+{
+ int ret;
+ ret = sssm_proxy_auth_init(bectx, ops, pvt_data);
+ *ops = &proxy_chpass_ops;
+ return ret;
+}
diff --git a/src/providers/sssd_be.exports b/src/providers/sssd_be.exports
new file mode 100644
index 000000000..9afa106be
--- /dev/null
+++ b/src/providers/sssd_be.exports
@@ -0,0 +1,4 @@
+{
+ global:
+ *;
+};