diff options
author | Stephen Gallagher <sgallagh@redhat.com> | 2010-02-18 07:49:04 -0500 |
---|---|---|
committer | Stephen Gallagher <sgallagh@redhat.com> | 2010-02-18 13:48:45 -0500 |
commit | 1c48b5a62f73234ed26bb20f0ab345ab61cda0ab (patch) | |
tree | 0b6cddd567a862e1a7b5df23764869782a62ca78 /src/providers | |
parent | 8c56df3176f528fe0260974b3bf934173c4651ea (diff) | |
download | sssd-1c48b5a62f73234ed26bb20f0ab345ab61cda0ab.tar.gz sssd-1c48b5a62f73234ed26bb20f0ab345ab61cda0ab.tar.xz sssd-1c48b5a62f73234ed26bb20f0ab345ab61cda0ab.zip |
Rename server/ directory to src/
Also update BUILD.txt
Diffstat (limited to 'src/providers')
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, ×tamp); + 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, ×tamp); + 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, ×tamp); + 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, ×tamp); + 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: + *; +}; |