diff options
Diffstat (limited to 'src/responder/common/responder_common.c')
-rw-r--r-- | src/responder/common/responder_common.c | 2001 |
1 files changed, 2001 insertions, 0 deletions
diff --git a/src/responder/common/responder_common.c b/src/responder/common/responder_common.c new file mode 100644 index 0000000..ac0e727 --- /dev/null +++ b/src/responder/common/responder_common.c @@ -0,0 +1,2001 @@ +/* + SSSD + + Common Responder methods + + 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 "config.h" + +#include <stdio.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 <fcntl.h> +#include <popt.h> +#include <dbus/dbus.h> + +#include "util/util.h" +#include "util/strtonum.h" +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "responder/common/responder.h" +#include "responder/common/responder_packet.h" +#include "providers/data_provider.h" +#include "util/util_creds.h" +#include "sss_iface/sss_iface_async.h" +#include "util/sss_chain_id_tevent.h" +#include "util/sss_chain_id.h" + +#ifdef HAVE_SYSTEMD +#include <systemd/sd-daemon.h> +#endif + +#define SHELL_REALLOC_INCREMENT 5 +#define SHELL_REALLOC_MAX 50 + +static errno_t set_close_on_exec(int fd) +{ + int v; + int ferr; + errno_t error; + + /* Get the current flags for this file descriptor */ + v = fcntl(fd, F_GETFD, 0); + + errno = 0; + /* Set the close-on-exec flags on this fd */ + ferr = fcntl(fd, F_SETFD, v | FD_CLOEXEC); + if (ferr < 0) { + error = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to set fd close-on-exec: [%d][%s]\n", + error, strerror(error)); + return error; + } + return EOK; +} + +static void client_close_fn(struct tevent_context *ev, + struct tevent_fd *fde, int fd, + void *ptr) +{ + errno_t ret; + struct cli_ctx *ctx = talloc_get_type(ptr, struct cli_ctx); + + if ((ctx->cfd > 0) && close(ctx->cfd) < 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to close fd [%d]: [%s]\n", + ctx->cfd, strerror(ret)); + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Terminated client [%p][%d]\n", + ctx, ctx->cfd); + + ctx->cfd = -1; +} + +static errno_t get_client_cred(struct cli_ctx *cctx) +{ + SEC_CTX secctx; + int ret; + + cctx->creds = talloc_zero(cctx, struct cli_creds); + if (!cctx->creds) return ENOMEM; + +#ifdef HAVE_UCRED + socklen_t client_cred_len = sizeof(struct ucred); + char proc_path[32]; + char cmd_line[255] = { 0 }; + int proc_fd; + + cctx->creds->ucred.uid = -1; + cctx->creds->ucred.gid = -1; + cctx->creds->ucred.pid = -1; + + ret = getsockopt(cctx->cfd, SOL_SOCKET, SO_PEERCRED, &cctx->creds->ucred, + &client_cred_len); + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "getsockopt failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + if (client_cred_len != sizeof(struct ucred)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "getsockopt returned unexpected message size.\n"); + return ENOMSG; + } + + if (cctx->creds->ucred.pid > -1) { + snprintf(proc_path, sizeof(proc_path), "/proc/%d/cmdline", + (int)cctx->creds->ucred.pid); + proc_fd = open(proc_path, O_RDONLY); + if (proc_fd != -1) { + if (sss_fd_nonblocking(proc_fd) == EOK) { + ret = read(proc_fd, cmd_line, sizeof(cmd_line)-1); + if (ret > 0) { + cmd_line[ret] = 0; + cctx->cmd_line = talloc_strdup(cctx, cmd_line); + } + } + close(proc_fd); + } + } + + DEBUG(SSSDBG_TRACE_ALL, + "Client [%p][%d] creds: euid[%d] egid[%d] pid[%d] cmd_line['%s'].\n", + cctx, cctx->cfd, + cctx->creds->ucred.uid, cctx->creds->ucred.gid, + cctx->creds->ucred.pid, cmd_line); +#endif + + ret = SELINUX_getpeercon(cctx->cfd, &secctx); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "The following failure is expected to happen in case SELinux is disabled:\n" + "SELINUX_getpeercon failed [%d][%s].\n" + "Please, consider enabling SELinux in your system.\n", ret, strerror(ret)); + /* This is not fatal, as SELinux may simply be disabled */ + ret = EOK; + } else { + cctx->creds->selinux_ctx = SELINUX_context_new(secctx); + SELINUX_freecon(secctx); + } + + return ret; +} + +uid_t client_euid(struct cli_creds *creds) +{ + if (!creds) return -1; + return cli_creds_get_uid(creds); +} + +errno_t check_allowed_uids(uid_t uid, size_t allowed_uids_count, + const uid_t *allowed_uids) +{ + size_t c; + + if (allowed_uids == NULL) { + return EINVAL; + } + + for (c = 0; c < allowed_uids_count; c++) { + if (uid == allowed_uids[c]) { + return EOK; + } + } + + return EACCES; +} + +errno_t csv_string_to_uid_array(TALLOC_CTX *mem_ctx, const char *csv_string, + size_t *_uid_count, uid_t **_uids) +{ + int ret; + size_t c; + char **list = NULL; + int list_size; + uid_t *uids = NULL; + char *endptr; + const char *envvar; + bool loops_were_allowed; + + envvar = getenv("_SSS_LOOPS"); + loops_were_allowed = (envvar == NULL || strcmp(envvar, "NO") != 0); + + if (!loops_were_allowed) { + ret = unsetenv("_SSS_LOOPS"); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to unset _SSS_LOOPS.\n"); + goto done; + } + } + + ret = split_on_separator(mem_ctx, csv_string, ',', true, false, + &list, &list_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + uids = talloc_array(mem_ctx, uint32_t, list_size); + if (uids == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + ret = ENOMEM; + goto done; + } + + for (c = 0; c < list_size; c++) { + if (*list[c] == '\0') { + DEBUG(SSSDBG_OP_FAILURE, "Empty list item.\n"); + ret = EINVAL; + goto done; + } + + uids[c] = strtouint32(list[c], &endptr, 10); + if ((errno != 0) || (*endptr != '\0') || (list[c] == endptr)) { + ret = errno; + if (ret == ERANGE) { + DEBUG(SSSDBG_OP_FAILURE, "List item [%s] is out of range.\n", + list[c]); + goto done; + } + + ret = sss_user_by_name_or_uid(list[c], &uids[c], NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "List item [%s] is neither a valid " + "UID nor a user name which could be " + "resolved by getpwnam().\n", list[c]); + sss_log(SSS_LOG_WARNING, "List item [%s] is neither a valid " + "UID nor a user name which could be " + "resolved by getpwnam().\n", list[c]); + goto done; + } + } + } + + *_uid_count = list_size; + *_uids = uids; + + ret = EOK; + +done: + if (!loops_were_allowed) { + if (setenv("_SSS_LOOPS", "NO" , 0) != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to restore _SSS_LOOPS.\n"); + } + } + talloc_free(list); + if (ret != EOK) { + talloc_free(uids); + } + + return ret; +} + +static void client_send(struct cli_ctx *cctx) +{ + struct cli_protocol *pctx; + int ret; + + pctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + ret = sss_packet_send(pctx->creq->out, cctx->cfd); + if (ret == EAGAIN) { + /* not all data was sent, loop again */ + return; + } + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to send data, aborting client!\n"); + talloc_free(cctx); + return; + } + + /* ok all sent */ + TEVENT_FD_NOT_WRITEABLE(cctx->cfde); + TEVENT_FD_READABLE(cctx->cfde); + talloc_zfree(pctx->creq); + return; +} + +static int client_cmd_execute(struct cli_ctx *cctx, struct sss_cmd_table *sss_cmds) +{ + struct cli_protocol *pctx; + enum sss_cli_command cmd; + + pctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + cmd = sss_packet_get_cmd(pctx->creq->in); + return sss_cmd_execute(cctx, cmd, sss_cmds); +} + +static void client_recv(struct cli_ctx *cctx) +{ + struct cli_protocol *pctx; + int ret; + + pctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + if (!pctx->creq) { + pctx->creq = talloc_zero(cctx, struct cli_request); + if (!pctx->creq) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to alloc request, aborting client!\n"); + talloc_free(cctx); + return; + } + } + + if (!pctx->creq->in) { + ret = sss_packet_new(pctx->creq, SSS_PACKET_MAX_RECV_SIZE, + 0, &pctx->creq->in); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to alloc request, aborting client!\n"); + talloc_free(cctx); + return; + } + } + + ret = sss_packet_recv(pctx->creq->in, cctx->cfd); + switch (ret) { + case EOK: + /* do not read anymore */ + TEVENT_FD_NOT_READABLE(cctx->cfde); + /* execute command */ + ret = client_cmd_execute(cctx, cctx->rctx->sss_cmds); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to execute request, aborting client!\n"); + talloc_free(cctx); + } + /* past this point cctx can be freed at any time by callbacks + * in case of error, do not use it */ + return; + + case EAGAIN: + /* need to read still some data, loop again */ + break; + + case EINVAL: + DEBUG(SSSDBG_TRACE_FUNC, + "Invalid data from client, closing connection!\n"); + talloc_free(cctx); + break; + + case ENODATA: + DEBUG(SSSDBG_FUNC_DATA, "Client disconnected!\n"); + talloc_free(cctx); + break; + + default: + DEBUG(SSSDBG_TRACE_FUNC, "Failed to read request, aborting client!\n"); + talloc_free(cctx); + } + + return; +} + +static errno_t schedule_responder_idle_timer(struct resp_ctx *rctx); + +static void responder_idle_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *data) +{ + struct resp_ctx *rctx; + time_t now; + + rctx = talloc_get_type(data, struct resp_ctx); + + now = time(NULL); + if (rctx->last_request_time > now) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Time shift detected, re-scheduling the responder timeout\n"); + goto end; + } + + if ((now - rctx->last_request_time) >= rctx->idle_timeout) { + /* This responder is idle. Terminate it */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Terminating idle responder [%p]\n", rctx); + + talloc_free(rctx); + + orderly_shutdown(0); + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Re-scheduling the idle timeout [%s] for the responder [%p]\n", + CONFDB_RESPONDER_IDLE_TIMEOUT, rctx); + +end: + schedule_responder_idle_timer(rctx); +} + +static errno_t schedule_responder_idle_timer(struct resp_ctx *rctx) +{ + struct timeval tv; + + tv = tevent_timeval_current_ofs(rctx->idle_timeout / 2, 0); + + talloc_zfree(rctx->idle); + rctx->idle = tevent_add_timer(rctx->ev, + rctx, + tv, + responder_idle_handler, + rctx); + if (rctx->idle == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate time event: responder [%p] shutdown timeout\n", + rctx); + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Re-scheduling the idle timeout [%s] for the responder [%p]\n", + CONFDB_RESPONDER_IDLE_TIMEOUT, rctx); + + return EOK; +} + +static errno_t setup_responder_idle_timer(struct resp_ctx *rctx) +{ + errno_t ret; + + rctx->last_request_time = time(NULL); + + ret = schedule_responder_idle_timer(rctx); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Error scheduling the idle timeout [%s] for the responder [%p]: " + "%d [%s]\n", + CONFDB_RESPONDER_IDLE_TIMEOUT, rctx, ret, sss_strerror(ret)); + return ret; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Setting up the idle timeout [%s] for the responder [%p]\n", + CONFDB_RESPONDER_IDLE_TIMEOUT, rctx); + + return EOK; +} + +static void client_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *ptr) +{ + sss_client_fd_handler(ptr, client_recv, client_send, flags); +} + +static errno_t setup_client_idle_timer(struct cli_ctx *cctx); + +static int cli_ctx_destructor(struct cli_ctx *cctx) +{ + if (cctx->creds == NULL) { + return 0; + } + + if (cctx->creds->selinux_ctx == NULL) { + return 0; + } + + SELINUX_context_free(cctx->creds->selinux_ctx); + cctx->creds->selinux_ctx = NULL; + + return 0; +} + +struct accept_fd_ctx { + struct resp_ctx *rctx; + bool is_private; + connection_setup_t connection_setup; +}; + +/* + * Use this function only before the client context is established + */ +static void accept_and_terminate_cli(int fd) +{ + struct sockaddr_un addr; + int client_fd; + socklen_t len; + + /* accept and close to signal the client we have a problem */ + memset(&addr, 0, sizeof(addr)); + len = sizeof(addr); + client_fd = accept(fd, (struct sockaddr *)&addr, &len); + if (client_fd == -1) { + return; + } + close(client_fd); + return; +} + +static void accept_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *ptr) +{ + static uid_t last_violator_uid = (uid_t)-1; + /* accept and attach new event handler */ + struct accept_fd_ctx *accept_ctx = + talloc_get_type(ptr, struct accept_fd_ctx); + struct resp_ctx *rctx = accept_ctx->rctx; + struct cli_ctx *cctx; + socklen_t len; + struct stat stat_buf; + int ret; + int fd = accept_ctx->is_private ? rctx->priv_lfd : rctx->lfd; + + rctx->client_id_num++; + if (accept_ctx->is_private) { + ret = stat(rctx->priv_sock_name, &stat_buf); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "stat on privileged pipe failed: [%d][%s].\n", + errno, strerror(errno)); + accept_and_terminate_cli(fd); + return; + } + + if ( ! (stat_buf.st_uid == 0 && stat_buf.st_gid == 0 && + (stat_buf.st_mode&(S_IFSOCK|S_IRUSR|S_IWUSR)) == stat_buf.st_mode)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "privileged pipe has an illegal status.\n"); + accept_and_terminate_cli(fd); + return; + } + } + + cctx = talloc_zero(rctx, struct cli_ctx); + if (!cctx) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Out of memory trying to setup client context%s!\n", + accept_ctx->is_private ? " on privileged pipe": ""); + accept_and_terminate_cli(fd); + return; + } + + talloc_set_destructor(cctx, cli_ctx_destructor); + + cctx->client_id_num = rctx->client_id_num; + + len = sizeof(cctx->addr); + cctx->cfd = accept(fd, (struct sockaddr *)&cctx->addr, &len); + if (cctx->cfd == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "Accept failed [%s]\n", strerror(errno)); + talloc_free(cctx); + return; + } + + cctx->priv = accept_ctx->is_private; + + ret = get_client_cred(cctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_client_cred failed, " + "client cred may not be available.\n"); + } + + if (rctx->allowed_uids_count != 0) { + if (client_euid(cctx->creds) == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "allowed_uids configured, " \ + "but platform does not support " \ + "reading peer credential from the " \ + "socket. Access denied.\n"); + close(cctx->cfd); + talloc_free(cctx); + return; + } + + ret = check_allowed_uids(client_euid(cctx->creds), rctx->allowed_uids_count, + rctx->allowed_uids); + if (ret != EOK) { + if (ret == EACCES) { + if (client_euid(cctx->creds) != last_violator_uid) { + last_violator_uid = client_euid(cctx->creds); + DEBUG(SSSDBG_IMPORTANT_INFO, + "Access denied for uid [%"SPRIuid"].\n", + last_violator_uid); + } + } else { + DEBUG(SSSDBG_OP_FAILURE, "check_allowed_uids failed.\n"); + } + close(cctx->cfd); + talloc_free(cctx); + return; + } + } + + ret = accept_ctx->connection_setup(cctx); + if (ret != EOK) { + close(cctx->cfd); + talloc_free(cctx); + DEBUG(SSSDBG_OP_FAILURE, + "Failed to setup client handler%s\n", + accept_ctx->is_private ? " on privileged pipe" : ""); + return; + } + + cctx->cfde = tevent_add_fd(ev, cctx, cctx->cfd, + TEVENT_FD_READ, cctx->cfd_handler, + cctx); + if (!cctx->cfde) { + close(cctx->cfd); + talloc_free(cctx); + DEBUG(SSSDBG_OP_FAILURE, + "Failed to queue client handler%s\n", + accept_ctx->is_private ? " on privileged pipe" : ""); + return; + } + tevent_fd_set_close_fn(cctx->cfde, client_close_fn); + + cctx->ev = ev; + cctx->rctx = rctx; + + /* Record the new time and set up the idle timer */ + ret = reset_client_idle_timer(cctx); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not create idle timer for client. " + "This connection may not auto-terminate\n"); + /* Non-fatal, continue */ + } + + ret = setup_client_idle_timer(cctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not create idle timer for client. " + "This connection may not auto-terminate\n"); + /* Non-fatal, continue */ + } + + DEBUG(SSSDBG_TRACE_FUNC, + "[CID#%u] Client [cmd %s][uid %u][%p][%d] connected%s!\n", + cctx->client_id_num, cctx->cmd_line, client_euid(cctx->creds), + cctx, cctx->cfd, accept_ctx->is_private ? " to privileged pipe" : ""); + + return; +} + +static void client_idle_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *data) +{ + time_t now = time(NULL); + struct cli_ctx *cctx = talloc_get_type(data, struct cli_ctx); + + if (cctx->last_request_time > now) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Time shift detected, re-scheduling the client timeout [%s].\n", + CONFDB_RESPONDER_CLI_IDLE_TIMEOUT); + goto done; + } + + if ((now - cctx->last_request_time) > cctx->rctx->client_idle_timeout) { + /* This connection is idle. Terminate it */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Terminating idle client [%p][%d]\n", + cctx, cctx->cfd); + + /* The cli_ctx destructor will handle the rest */ + talloc_free(cctx); + return; + } + +done: + setup_client_idle_timer(cctx); +} + +errno_t reset_client_idle_timer(struct cli_ctx *cctx) +{ + cctx->last_request_time = time(NULL); + + return EOK; +} + +static errno_t setup_client_idle_timer(struct cli_ctx *cctx) +{ + struct timeval tv = + tevent_timeval_current_ofs(cctx->rctx->client_idle_timeout/2, 0); + + talloc_zfree(cctx->idle); + + cctx->idle = tevent_add_timer(cctx->ev, cctx, tv, client_idle_handler, cctx); + if (!cctx->idle) return ENOMEM; + + DEBUG(SSSDBG_TRACE_ALL, + "Idle timer re-set for client [%p][%d]\n", + cctx, cctx->cfd); + + return EOK; +} + +static void +sss_dp_on_reconnect(struct sbus_connection *conn, + enum sbus_reconnect_status status, + struct be_conn *be_conn); + +static void +sss_dp_init_done(struct tevent_req *req); + +static errno_t +sss_dp_init(struct resp_ctx *rctx, + const char *conn_name, + const char *cli_name, + struct sss_domain_info *domain) +{ + struct tevent_req *req; + struct be_conn *be_conn; + int max_retries; + errno_t ret; + + ret = confdb_get_int(rctx->cdb, rctx->confdb_service_path, + CONFDB_SERVICE_RECON_RETRIES, 3, &max_retries); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to read confdb [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + be_conn = talloc_zero(rctx, struct be_conn); + if (!be_conn) return ENOMEM; + + be_conn->cli_name = cli_name; + be_conn->domain = domain; + be_conn->rctx = rctx; + + be_conn->sbus_address = sss_iface_domain_address(be_conn, domain); + if (be_conn->sbus_address == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not locate DP address.\n"); + ret = ENOMEM; + goto done; + } + + be_conn->bus_name = sss_iface_domain_bus(be_conn, domain); + if (be_conn->bus_name == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not locate DP address.\n"); + ret = ENOMEM; + goto done; + } + + ret = sss_iface_connect_address(be_conn, rctx->ev, conn_name, + be_conn->sbus_address, NULL, + &be_conn->conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to connect to backend server.\n"); + goto done; + } + + ret = sss_resp_register_sbus_iface(be_conn->conn, rctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Cannot register generic responder " + "interface [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + sbus_reconnect_enable(be_conn->conn, max_retries, sss_dp_on_reconnect, + be_conn); + + DLIST_ADD_END(rctx->be_conns, be_conn, struct be_conn *); + + /* Identify ourselves to the DP */ + req = sbus_call_dp_client_Register_send(be_conn, be_conn->conn, + be_conn->bus_name, + SSS_BUS_PATH, cli_name); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, sss_dp_init_done, be_conn); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(be_conn); + } + + return ret; +} + +static void +sss_dp_on_reconnect(struct sbus_connection *conn, + enum sbus_reconnect_status status, + struct be_conn *be_conn) +{ + struct tevent_req *req; + + if (status != SBUS_RECONNECT_SUCCESS) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not reconnect to %s provider.\n", + be_conn->domain->name); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Reconnected to the Data Provider.\n"); + + /* Identify ourselves to the DP */ + req = sbus_call_dp_client_Register_send(be_conn, be_conn->conn, + be_conn->bus_name, + SSS_BUS_PATH, + be_conn->cli_name); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sbus_call_dp_client_Register_send() failed\n"); + return; + } + + tevent_req_set_callback(req, sss_dp_init_done, be_conn); +} + +static void +sss_dp_init_done(struct tevent_req *req) +{ + errno_t ret; + + ret = sbus_call_dp_client_Register_recv(req); + talloc_zfree(req); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to register client with DP\n"); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Client is registered with DP\n"); +} + +int create_pipe_fd(const char *sock_name, int *_fd, mode_t umaskval) +{ + struct sockaddr_un addr; + mode_t orig_umaskval; + errno_t ret; + int fd; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + return EIO; + } + + orig_umaskval = umask(umaskval); + + ret = sss_fd_nonblocking(fd); + if (ret != EOK) { + goto done; + } + + ret = set_close_on_exec(fd); + if (ret != EOK) { + goto done; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, sock_name, sizeof(addr.sun_path) - 1); + addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; + + /* make sure we have no old sockets around */ + ret = unlink(sock_name); + if (ret != 0 && errno != ENOENT) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot remove old socket (errno=%d [%s]), bind might fail!\n", + ret, sss_strerror(ret)); + } + + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to bind on socket '%s' [%d]: %s\n", + sock_name, ret, sss_strerror(ret)); + goto done; + } + + if (listen(fd, 128) == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to listen on socket '%s' [%d]: %s\n", + sock_name, ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + /* restore previous umask value */ + umask(orig_umaskval); + if (ret == EOK) { + *_fd = fd; + } else { + close(fd); + } + return ret; +} + +/* create a unix socket and listen to it */ +static int set_unix_socket(struct resp_ctx *rctx, + connection_setup_t conn_setup) +{ + errno_t ret; + struct accept_fd_ctx *accept_ctx = NULL; + +/* for future use */ +#if 0 + char *default_pipe; + int ret; + + default_pipe = talloc_asprintf(rctx, "%s/%s", PIPE_PATH, + rctx->sss_pipe_name); + if (!default_pipe) { + return ENOMEM; + } + + ret = confdb_get_string(rctx->cdb, rctx, + rctx->confdb_socket_path, "unixSocket", + default_pipe, &rctx->sock_name); + if (ret != EOK) { + talloc_free(default_pipe); + return ret; + } + talloc_free(default_pipe); + + default_pipe = talloc_asprintf(rctx, "%s/private/%s", PIPE_PATH, + rctx->sss_pipe_name); + if (!default_pipe) { + return ENOMEM; + } + + ret = confdb_get_string(rctx->cdb, rctx, + rctx->confdb_socket_path, "privUnixSocket", + default_pipe, &rctx->priv_sock_name); + if (ret != EOK) { + talloc_free(default_pipe); + return ret; + } + talloc_free(default_pipe); +#endif + + if (rctx->sock_name != NULL ) { + /* Set the umask so that permissions are set right on the socket. + * It must be readable and writable by anybody on the system. */ + if (rctx->lfd == -1) { + ret = create_pipe_fd(rctx->sock_name, &rctx->lfd, SCKT_RSP_UMASK); + if (ret != EOK) { + return ret; + } + } + + accept_ctx = talloc_zero(rctx, struct accept_fd_ctx); + if(!accept_ctx) goto failed; + accept_ctx->rctx = rctx; + accept_ctx->is_private = false; + accept_ctx->connection_setup = conn_setup; + + rctx->lfde = tevent_add_fd(rctx->ev, rctx, rctx->lfd, + TEVENT_FD_READ, accept_fd_handler, + accept_ctx); + if (!rctx->lfde) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to queue handler on pipe\n"); + goto failed; + } + } + + if (rctx->priv_sock_name != NULL ) { + /* create privileged pipe */ + if (rctx->priv_lfd == -1) { + ret = create_pipe_fd(rctx->priv_sock_name, &rctx->priv_lfd, + DFL_RSP_UMASK); + if (ret != EOK) { + goto failed; + } + } + + accept_ctx = talloc_zero(rctx, struct accept_fd_ctx); + if(!accept_ctx) goto failed; + accept_ctx->rctx = rctx; + accept_ctx->is_private = true; + accept_ctx->connection_setup = conn_setup; + + rctx->priv_lfde = tevent_add_fd(rctx->ev, rctx, rctx->priv_lfd, + TEVENT_FD_READ, accept_fd_handler, + accept_ctx); + if (!rctx->priv_lfde) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to queue handler on privileged pipe\n"); + goto failed; + } + } + + return EOK; + +failed: + if (rctx->lfd >= 0) close(rctx->lfd); + if (rctx->priv_lfd >= 0) close(rctx->priv_lfd); + return EIO; +} + +int activate_unix_sockets(struct resp_ctx *rctx, + connection_setup_t conn_setup) +{ + int ret; + +#ifdef HAVE_SYSTEMD + struct sockaddr_un sockaddr; + socklen_t sockaddr_len = sizeof(sockaddr); + + if (rctx->lfd == -1 && rctx->priv_lfd == -1) { + int numfds = (rctx->sock_name ? 1 : 0) + + (rctx->priv_sock_name ? 1 : 0); + /* but if systemd support is available, check if the sockets + * have been opened for us, via socket activation */ + ret = sd_listen_fds(1); + if (ret < 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unexpected error probing for active sockets. " + "Will proceed with no sockets. [Error %d (%s)]\n", + -ret, sss_strerror(-ret)); + } else if (ret > numfds) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Too many activated sockets have been found, " + "expected %d, found %d\n", numfds, ret); + ret = E2BIG; + goto done; + } + + if (ret == numfds) { + rctx->lfd = SD_LISTEN_FDS_START; + ret = sd_is_socket_unix(rctx->lfd, SOCK_STREAM, 1, NULL, 0); + if (ret < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Activated socket is not a UNIX listening socket\n"); + ret = EIO; + goto done; + } + + ret = getsockname(rctx->lfd, (struct sockaddr *) &sockaddr, &sockaddr_len); + if (ret == EOK) { + if (rctx->sock_name && + memcmp(rctx->sock_name, sockaddr.sun_path, strlen(rctx->sock_name)) != 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Warning: socket path defined in systemd unit (%s) and sssd.conf (%s) don't match\n", + sockaddr.sun_path, rctx->sock_name); + } + } + + ret = sss_fd_nonblocking(rctx->lfd); + if (ret != EOK) goto done; + if (numfds == 2) { + rctx->priv_lfd = SD_LISTEN_FDS_START + 1; + ret = sd_is_socket_unix(rctx->priv_lfd, SOCK_STREAM, 1, NULL, 0); + if (ret < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Activated priv socket is not a UNIX listening socket\n"); + ret = EIO; + goto done; + } + + ret = sss_fd_nonblocking(rctx->priv_lfd); + if (ret != EOK) goto done; + } + } + } +#endif + + ret = set_unix_socket(rctx, conn_setup); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Fatal error initializing sockets\n"); + goto done; + } + +done: + return ret; +} + +void sss_client_fd_handler(void *ptr, + void (*recv_fn) (struct cli_ctx *cctx), + void (*send_fn) (struct cli_ctx *cctx), + uint16_t flags) +{ + errno_t ret; + uint64_t old_chain_id; + struct cli_ctx *cctx = talloc_get_type(ptr, struct cli_ctx); + + /* Always reset the responder idle timer on any activity */ + cctx->rctx->last_request_time = time(NULL); + + /* Always reset the client idle timer on any activity */ + ret = reset_client_idle_timer(cctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not create idle timer for the client. " + "This connection may not auto-terminate.\n"); + /* Non-fatal, continue */ + } + + /* Set the chain id */ + old_chain_id = sss_chain_id_set(cctx->client_id_num); + + if (flags & TEVENT_FD_READ) { + recv_fn(cctx); + return; + } + + if (flags & TEVENT_FD_WRITE) { + send_fn(cctx); + return; + } + /* Restore the original chain id */ + sss_chain_id_set(old_chain_id); +} + +int sss_connection_setup(struct cli_ctx *cctx) +{ + cctx->protocol_ctx = talloc_zero(cctx, struct cli_protocol); + if (!cctx->protocol_ctx) { + return ENOMEM; + } + + cctx->cfd_handler = client_fd_handler; + + return EOK; +} + +static int sss_responder_ctx_destructor(void *ptr) +{ + struct resp_ctx *rctx = talloc_get_type(ptr, struct resp_ctx); + + /* mark that we are shutting down the responder, so it is propagated + * into underlying contexts that are freed right before rctx */ + DEBUG(SSSDBG_TRACE_FUNC, "Responder is being shut down\n"); + rctx->shutting_down = true; + + return 0; +} + +static errno_t responder_init_ncache(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + struct sss_nc_ctx **ncache) +{ + uint32_t neg_timeout; + uint32_t locals_timeout; + int tmp_value; + int ret; + + /* neg_timeout */ + ret = confdb_get_int(cdb, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_ENTRY_NEG_TIMEOUT, + 15, &tmp_value); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Fatal failure of setup negative cache timeout [%s].\n", + CONFDB_NSS_ENTRY_NEG_TIMEOUT); + ret = ENOENT; + goto done; + } + + if (tmp_value < 0) { + ret = EINVAL; + goto done; + } + + neg_timeout = tmp_value; + + /* local_timeout */ + ret = confdb_get_int(cdb, CONFDB_NSS_CONF_ENTRY, + CONFDB_RESPONDER_LOCAL_NEG_TIMEOUT, + CONFDB_RESPONDER_LOCAL_NEG_TIMEOUT_DEFAULT, + &tmp_value); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Fatal failure of setup negative cache timeout [%s].\n", + CONFDB_RESPONDER_LOCAL_NEG_TIMEOUT); + ret = ENOENT; + goto done; + } + + if (tmp_value < 0) { + ret = EINVAL; + goto done; + } + + locals_timeout = tmp_value; + + /* negative cache init */ + ret = sss_ncache_init(mem_ctx, neg_timeout, locals_timeout, ncache); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Fatal failure of initializing negative cache.\n"); + goto done; + } + + ret = EOK; + +done: + return ret; +} + +static errno_t sss_get_etc_shells(TALLOC_CTX *mem_ctx, char ***_shells) +{ + int i = 0; + char *sh; + char **shells = NULL; + TALLOC_CTX *tmp_ctx; + errno_t ret; + int size; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + shells = talloc_array(tmp_ctx, char *, SHELL_REALLOC_INCREMENT); + if (!shells) { + ret = ENOMEM; + goto done; + } + size = SHELL_REALLOC_INCREMENT; + + setusershell(); + while ((sh = getusershell())) { + shells[i] = talloc_strdup(shells, sh); + if (!shells[i]) { + endusershell(); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Found shell %s in /etc/shells\n", shells[i]); + i++; + + if (i == size) { + size += SHELL_REALLOC_INCREMENT; + if (size > SHELL_REALLOC_MAX) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Reached maximum number of shells [%d]. " + "Users may be denied access. " + "Please check /etc/shells for sanity\n", + SHELL_REALLOC_MAX); + break; + } + shells = talloc_realloc(NULL, shells, char *, + size); + if (!shells) { + ret = ENOMEM; + goto done; + } + } + } + endusershell(); + + if (i + 1 < size) { + shells = talloc_realloc(NULL, shells, char *, i + 1); + if (!shells) { + ret = ENOMEM; + goto done; + } + } + shells[i] = NULL; + + *_shells = talloc_move(mem_ctx, &shells); + ret = EOK; +done: + talloc_zfree(tmp_ctx); + return ret; +} + +int sss_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + struct sss_cmd_table sss_cmds[], + const char *sss_pipe_name, + int pipe_fd, + const char *sss_priv_pipe_name, + int priv_pipe_fd, + const char *confdb_service_path, + const char *conn_name, + const char *svc_name, + connection_setup_t conn_setup, + struct resp_ctx **responder_ctx) +{ + struct resp_ctx *rctx; + struct sss_domain_info *dom; + int ret; + char *tmp = NULL; + + rctx = talloc_zero(mem_ctx, struct resp_ctx); + if (!rctx) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing resp_ctx\n"); + return ENOMEM; + } + rctx->ev = ev; + rctx->cdb = cdb; + rctx->sss_cmds = sss_cmds; + rctx->sock_name = sss_pipe_name; + rctx->priv_sock_name = sss_priv_pipe_name; + rctx->lfd = pipe_fd; + rctx->priv_lfd = priv_pipe_fd; + rctx->confdb_service_path = confdb_service_path; + rctx->shutting_down = false; + rctx->socket_activated = is_socket_activated(); + rctx->dbus_activated = is_dbus_activated(); + + talloc_set_destructor((TALLOC_CTX*)rctx, sss_responder_ctx_destructor); + + ret = confdb_get_int(rctx->cdb, rctx->confdb_service_path, + CONFDB_RESPONDER_CLI_IDLE_TIMEOUT, + CONFDB_RESPONDER_CLI_IDLE_DEFAULT_TIMEOUT, + &rctx->client_idle_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the client idle timeout [%s] [%d]: %s\n", + CONFDB_RESPONDER_CLI_IDLE_TIMEOUT, ret, strerror(ret)); + goto fail; + } + + /* Ensure that the client timeout is at least ten seconds */ + if (rctx->client_idle_timeout < 10) { + rctx->client_idle_timeout = 10; + } + + if (rctx->socket_activated || rctx->dbus_activated) { + ret = responder_setup_idle_timeout_config(rctx); + if (ret != EOK) { + goto fail; + } + } + + ret = confdb_get_bool(rctx->cdb, rctx->confdb_service_path, + CONFDB_RESPONDER_CACHE_FIRST, + CONFDB_RESPONDER_CACHE_FIRST_DEFAILT, + &rctx->cache_first); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get \"cache_first_option\".\n" + "Querying the caches first before querying the " + "Data Providers will not be enforced [%d]: %s.\n", + ret, sss_strerror(ret)); + } + + ret = confdb_get_int(rctx->cdb, rctx->confdb_service_path, + CONFDB_RESPONDER_GET_DOMAINS_TIMEOUT, + GET_DOMAINS_DEFAULT_TIMEOUT, &rctx->domains_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the default domain timeout [%s] [%d]: %s\n", + CONFDB_RESPONDER_GET_DOMAINS_TIMEOUT, ret, strerror(ret)); + goto fail; + } + + if (rctx->domains_timeout < 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "timeout [%s] can't be set to negative value, " + "setting default [%d] seconds.\n", + CONFDB_RESPONDER_GET_DOMAINS_TIMEOUT, + GET_DOMAINS_DEFAULT_TIMEOUT); + rctx->domains_timeout = GET_DOMAINS_DEFAULT_TIMEOUT; + } + + ret = confdb_get_domains(rctx->cdb, &rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up domain map\n"); + goto fail; + } + + ret = confdb_get_string(rctx->cdb, rctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_DEFAULT_DOMAIN, NULL, + &rctx->default_domain); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the default domain [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + ret = confdb_get_string(rctx->cdb, rctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_OVERRIDE_SPACE, NULL, + &tmp); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the space substitution character [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + if (tmp != NULL) { + if (strlen(tmp) > 1) { + DEBUG(SSSDBG_MINOR_FAILURE, "Option %s is longer than 1 character " + "only the first character %c will be used\n", + CONFDB_MONITOR_OVERRIDE_SPACE, tmp[0]); + } + + rctx->override_space = tmp[0]; + } + + ret = confdb_get_string(rctx->cdb, rctx, + CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_DOMAIN_RESOLUTION_ORDER, NULL, + &tmp); + if (ret == EOK) { + rctx->domain_resolution_order = sss_replace_char(rctx, tmp, ',', ':'); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot get the \"domain_resolution_order\" option.\n" + "The set up lookup_order won't be followed [%d]: %s.\n", + ret, sss_strerror(ret)); + } + + /* Read shell settings */ + ret = confdb_get_string(cdb, rctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_OVERRIDE_SHELL, NULL, + &rctx->override_shell); + if (ret != EOK && ret != ENOENT) goto fail; + + ret = confdb_get_string_as_list(cdb, rctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_ALLOWED_SHELL, + &rctx->allowed_shells); + if (ret != EOK && ret != ENOENT) goto fail; + + ret = confdb_get_string_as_list(cdb, rctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_VETOED_SHELL, + &rctx->vetoed_shells); + if (ret != EOK && ret != ENOENT) goto fail; + + ret = sss_get_etc_shells(rctx, &rctx->etc_shells); + if (ret != EOK) goto fail; + + ret = confdb_get_string(cdb, rctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_SHELL_FALLBACK, + CONFDB_DEFAULT_SHELL_FALLBACK, + &rctx->shell_fallback); + if (ret != EOK) goto fail; + + ret = confdb_get_string(cdb, rctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_DEFAULT_SHELL, + NULL, + &rctx->default_shell); + if (ret != EOK) goto fail; + + /* Read session_recording section */ + ret = session_recording_conf_load(rctx, rctx->cdb, &rctx->sr_conf); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed loading session recording configuration: %s\n", + strerror(ret)); + goto fail; + } + + for (dom = rctx->domains; dom; dom = get_next_domain(dom, 0)) { + ret = sss_names_init(rctx->cdb, rctx->cdb, dom->name, &dom->names); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "fatal error initializing regex data for domain: %s\n", + dom->name); + goto fail; + } + + ret = sss_dp_init(rctx, conn_name, svc_name, dom); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "fatal error setting up backend connector\n"); + goto fail; + } + } + + ret = sysdb_init(rctx, rctx->domains); + if (ret != EOK) { + SYSDB_VERSION_ERROR_DAEMON(ret); + DEBUG(SSSDBG_FATAL_FAILURE, + "fatal error initializing sysdb connection\n"); + goto fail; + } + + /* after all initializations we are ready to listen on our socket */ + ret = activate_unix_sockets(rctx, conn_setup); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing socket\n"); + goto fail; + } + + ret = responder_init_ncache(rctx, rctx->cdb, &rctx->ncache); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "fatal error initializing negcache\n"); + goto fail; + } + + ret = sss_ad_default_names_ctx(rctx, &rctx->global_names); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_ad_default_names_ctx failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Responder initialization complete (%s)\n", + rctx->socket_activated ? "socket-activated" : + rctx->dbus_activated ? "dbus-activated" : + "explicitly configured"); + + *responder_ctx = rctx; + return EOK; + +fail: + talloc_free(rctx); + return ret; +} + +int sss_dp_get_domain_conn(struct resp_ctx *rctx, const char *domain, + struct be_conn **_conn) +{ + struct be_conn *iter; + + if (!rctx->be_conns) return ENOENT; + + for (iter = rctx->be_conns; iter; iter = iter->next) { + if (strcasecmp(domain, iter->domain->name) == 0) break; + } + + if (!iter) return ENOENT; + + *_conn = iter; + + return EOK; +} + +struct sss_domain_info * +responder_get_domain(struct resp_ctx *rctx, const char *name) +{ + struct sss_domain_info *dom; + struct sss_domain_info *ret_dom = NULL; + + for (dom = rctx->domains; dom; + dom = get_next_domain(dom, SSS_GND_DESCEND)) { + if (sss_domain_get_state(dom) == DOM_DISABLED) { + continue; + } + + if (strcasecmp(dom->name, name) == 0 || + (dom->flat_name != NULL && + strcasecmp(dom->flat_name, name) == 0)) { + ret_dom = dom; + break; + } + } + + if (!ret_dom) { + DEBUG(SSSDBG_OP_FAILURE, "Unknown domain [%s]\n", name); + } + + return ret_dom; +} + +errno_t responder_get_domain_by_id(struct resp_ctx *rctx, const char *id, + struct sss_domain_info **_ret_dom) +{ + struct sss_domain_info *dom; + struct sss_domain_info *ret_dom = NULL; + size_t id_len; + size_t dom_id_len; + int ret; + + if (id == NULL || _ret_dom == NULL) { + return EINVAL; + } + + id_len = strlen(id); + + for (dom = rctx->domains; dom; + dom = get_next_domain(dom, SSS_GND_DESCEND)) { + if (sss_domain_get_state(dom) == DOM_DISABLED || + dom->domain_id == NULL) { + continue; + } + + dom_id_len = strlen(dom->domain_id); + if ((id_len >= dom_id_len) && + strncasecmp(dom->domain_id, id, dom_id_len) == 0) { + if (IS_SUBDOMAIN(dom) && + ((time(NULL) - dom->parent->subdomains_last_checked.tv_sec) > + rctx->domains_timeout)) { + DEBUG(SSSDBG_TRACE_FUNC, "Domain entry with id [%s] " \ + "is expired.\n", id); + ret = EAGAIN; + goto done; + } + ret_dom = dom; + break; + } + } + + if (ret_dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unknown domain id [%s], checking for " + "possible subdomains!\n", id); + ret = ENOENT; + } else { + *_ret_dom = ret_dom; + ret = EOK; + } + +done: + return ret; +} + +errno_t +responder_logrotate(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct resp_ctx *rctx) +{ + return server_common_rotate_logs(rctx->cdb, rctx->confdb_service_path); + + return EOK; +} + +void responder_set_fd_limit(rlim_t fd_limit) +{ + struct rlimit current_limit, new_limit; + int limret; + + /* First, let's see if we have permission to just set + * the value as-is. + */ + new_limit.rlim_cur = fd_limit; + new_limit.rlim_max = fd_limit; + limret = setrlimit(RLIMIT_NOFILE, &new_limit); + if (limret == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Maximum file descriptors set to [%"SPRIrlim"]\n", + new_limit.rlim_cur); + return; + } + + /* We couldn't set the soft and hard limits to this + * value. Let's see how high we CAN set it. + */ + + /* Determine the maximum hard limit */ + limret = getrlimit(RLIMIT_NOFILE, ¤t_limit); + if (limret == 0) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Current fd limit: [%"SPRIrlim"]\n", + current_limit.rlim_cur); + /* Choose the lesser of the requested and the hard limit */ + if (current_limit.rlim_max < fd_limit) { + new_limit.rlim_cur = current_limit.rlim_max; + } else { + new_limit.rlim_cur = fd_limit; + } + new_limit.rlim_max = current_limit.rlim_max; + + limret = setrlimit(RLIMIT_NOFILE, &new_limit); + if (limret == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Maximum file descriptors set to [%"SPRIrlim"]\n", + new_limit.rlim_cur); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not set new fd limits. Proceeding with " + "[%"SPRIrlim"]\n", current_limit.rlim_cur); + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not determine fd limits. " + "Proceeding with system values\n"); + } +} + +errno_t responder_setup_idle_timeout_config(struct resp_ctx *rctx) +{ + errno_t ret; + + ret = confdb_get_int(rctx->cdb, rctx->confdb_service_path, + CONFDB_RESPONDER_IDLE_TIMEOUT, + CONFDB_RESPONDER_IDLE_DEFAULT_TIMEOUT, + &rctx->idle_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the responder idle timeout [%s] [%d]: %s\n", + CONFDB_RESPONDER_IDLE_TIMEOUT, ret, sss_strerror(ret)); + goto fail; + } + + /* Idle timeout set to 0 means that no timeout will be set up to + * the responder */ + if (rctx->idle_timeout == 0) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Responder idle timeout won't be set up as the " + "responder_idle_timeout is set to 0\n"); + } else { + /* Ensure that the responder timeout is at least sixty seconds */ + if (rctx->idle_timeout < 60) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "responder_idle_timeout is set to a value lower than " + "the minimum allowed (60s). " + "The minimum allowed value will be used.\n"); + + rctx->idle_timeout = 60; + } + + ret = setup_responder_idle_timer(rctx); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "An error occurred when setting up the responder's idle " + "timeout [%s] for the responder [%p]: %s [%d].\n" + "The responder won't be automatically shutdown after %d " + "seconds inactive.\n", + CONFDB_RESPONDER_IDLE_TIMEOUT, + rctx, sss_strerror(ret), ret, + rctx->idle_timeout); + } + } + + ret = EOK; + +fail: + return ret; + +} + +/* ====== Helper functions for the domain resolution order ======= */ +static errno_t +sss_resp_new_cr_domains_from_ipa_id_view(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domains, + struct sysdb_ctx *sysdb, + struct cache_req_domain **_cr_domains) +{ + TALLOC_CTX *tmp_ctx; + struct cache_req_domain *cr_domains = NULL; + const char *domain_resolution_order = NULL; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_get_view_domain_resolution_order(tmp_ctx, sysdb, + &domain_resolution_order); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sysdb_get_view_cache_req_domain() failed [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + if (ret == ENOENT) { + goto done; + } + + ret = cache_req_domain_new_list_from_domain_resolution_order( + mem_ctx, domains, domain_resolution_order, &cr_domains); + if (ret != EOK) { + DEBUG(SSSDBG_DEFAULT, + "cache_req_domain_new_list_from_domain_resolution_order() " + "failed [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + *_cr_domains = cr_domains; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sss_resp_new_cr_domains_from_ipa_config(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domains, + struct sysdb_ctx *sysdb, + const char *domain, + struct cache_req_domain **_cr_domains) +{ + TALLOC_CTX *tmp_ctx; + const char *domain_resolution_order = NULL; + errno_t ret; + + *_cr_domains = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_domain_get_domain_resolution_order(tmp_ctx, sysdb, domain, + &domain_resolution_order); + + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sysdb_domain_get_cache_req_domain() failed [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + if (ret == ENOENT) { + goto done; + } + + ret = cache_req_domain_new_list_from_domain_resolution_order( + mem_ctx, domains, domain_resolution_order, _cr_domains); + if (ret != EOK) { + DEBUG(SSSDBG_DEFAULT, + "cache_req_domain_new_list_from_domain_resolution_order() " + "failed [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sss_resp_populate_cr_domains(struct resp_ctx *rctx) +{ + struct cache_req_domain *cr_domains = NULL; + struct sss_domain_info *dom; + errno_t ret; + + if (rctx->domain_resolution_order != NULL) { + ret = cache_req_domain_new_list_from_domain_resolution_order( + rctx, rctx->domains, + rctx->domain_resolution_order, &cr_domains); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Using domain_resolution_order from sssd.conf\n"); + goto done; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to use domain_resolution_order set in the config file.\n" + "Trying to fallback to use ipaDomainOrderResolution setup by " + "IPA.\n"); + } + } + + for (dom = rctx->domains; dom != NULL; dom = dom->next) { + if (dom->provider != NULL && strcmp(dom->provider, "ipa") == 0) { + break; + } + } + + if (dom == NULL) { + ret = cache_req_domain_new_list_from_domain_resolution_order( + rctx, rctx->domains, NULL, &cr_domains); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to flatten the list of domains.\n"); + } + goto done; + } + + if (dom->has_views) { + ret = sss_resp_new_cr_domains_from_ipa_id_view(rctx, rctx->domains, + dom->sysdb, + &cr_domains); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Using domain_resolution_order from IPA ID View\n"); + goto done; + } + + if (ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to use ipaDomainResolutionOrder set for the " + "view \"%s\".\n" + "Trying to fallback to use ipaDomainOrderResolution " + "set in ipaConfig for the domain: %s.\n", + dom->view_name, dom->name); + } + } + + ret = sss_resp_new_cr_domains_from_ipa_config(rctx, rctx->domains, + dom->sysdb, dom->name, + &cr_domains); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Using domain_resolution_order from IPA Config\n"); + goto done; + } + + if (ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to use ipaDomainResolutionOrder set in ipaConfig " + "for the domain: \"%s\".\n" + "No ipaDomainResolutionOrder will be followed.\n", + dom->name); + } + + ret = cache_req_domain_new_list_from_domain_resolution_order( + rctx, rctx->domains, NULL, &cr_domains); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to flatten the list of domains.\n"); + goto done; + } + + ret = EOK; + +done: + cache_req_domain_list_zfree(&rctx->cr_domains); + rctx->cr_domains = cr_domains; + + return ret; +} + +/** + * Helper functions to format output names + */ +int sized_output_name(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + const char *orig_name, + struct sss_domain_info *name_dom, + struct sized_string **_name) +{ + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + char *name_str; + struct sized_string *name; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + name = talloc_zero(tmp_ctx, struct sized_string); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_output_fqname(name, name_dom, orig_name, + rctx->override_space, &name_str); + if (ret != EOK) { + goto done; + } + + to_sized_string(name, name_str); + *_name = talloc_steal(mem_ctx, name); + ret = EOK; +done: + talloc_zfree(tmp_ctx); + return ret; +} + +int sized_domain_name(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + const char *member_name, + struct sized_string **_name) +{ + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + char *domname; + struct sss_domain_info *member_dom; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmp_ctx, member_name, NULL, &domname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_parse_internal_fqname failed\n"); + goto done; + } + + if (domname == NULL) { + ret = ERR_WRONG_NAME_FORMAT; + goto done; + } + + member_dom = find_domain_by_name(get_domains_head(rctx->domains), + domname, true); + if (member_dom == NULL) { + ret = ERR_DOMAIN_NOT_FOUND; + goto done; + } + + ret = sized_output_name(mem_ctx, rctx, member_name, + member_dom, _name); +done: + talloc_free(tmp_ctx); + return ret; +} |