responder_common.c revision 41e9e8b60e3bed0159914e755aa05df9a2448470
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen/*
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen SSSD
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen Common Responder methods
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen This program is free software; you can redistribute it and/or modify
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen it under the terms of the GNU General Public License as published by
a22c03355b9c4c8e1655250e216a4ce66d49de7cTimo Sirainen the Free Software Foundation; either version 3 of the License, or
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen (at your option) any later version.
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen This program is distributed in the hope that it will be useful,
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen but WITHOUT ANY WARRANTY; without even the implied warranty of
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen GNU General Public License for more details.
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen You should have received a copy of the GNU General Public License
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen along with this program. If not, see <http://www.gnu.org/licenses/>.
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen*/
02a54da28f376dd66d7939d8546a196a0045b486Timo Sirainen
#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 <popt.h>
#include <dbus/dbus.h>
#include "util/util.h"
#include "util/strtonum.h"
#include "db/sysdb.h"
#include "confdb/confdb.h"
#include "sbus/sssd_dbus.h"
#include "responder/common/responder.h"
#include "responder/common/responder_packet.h"
#include "providers/data_provider.h"
#include "monitor/monitor_interfaces.h"
#include "sbus/sbus_client.h"
#include "util/util_creds.h"
#ifdef HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif
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(cctx, struct cli_creds);
if (!cctx->creds) return ENOMEM;
#ifdef HAVE_UCRED
socklen_t client_cred_len = sizeof(struct ucred);
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,
"getsock 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;
}
DEBUG(SSSDBG_TRACE_ALL,
"Client creds: euid[%d] egid[%d] pid[%d].\n",
cctx->creds->ucred.uid, cctx->creds->ucred.gid,
cctx->creds->ucred.pid);
#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,
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,
bool allow_sss_loop,
size_t *_uid_count, uid_t **_uids)
{
int ret;
size_t c;
char **list = NULL;
int list_size;
uid_t *uids = NULL;
char *endptr;
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;
}
if (allow_sss_loop) {
ret = unsetenv("_SSS_LOOPS");
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Failed to unset _SSS_LOOPS, getpwnam "
"might not find sssd users.\n");
}
}
for (c = 0; c < list_size; c++) {
errno = 0;
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') {
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(setenv("_SSS_LOOPS", "NO", 0) != 0) {
DEBUG(SSSDBG_OP_FAILURE, "Failed to set _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 void client_fd_handler(struct tevent_context *ev,
struct tevent_fd *fde,
uint16_t flags, void *ptr)
{
errno_t ret;
struct cli_ctx *cctx = talloc_get_type(ptr, struct cli_ctx);
/* Always reset the idle timer on any activity */
ret = reset_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 */
}
if (flags & TEVENT_FD_READ) {
client_recv(cctx);
return;
}
if (flags & TEVENT_FD_WRITE) {
client_send(cctx);
return;
}
}
struct accept_fd_ctx {
struct resp_ctx *rctx;
bool is_private;
connection_setup_t connection_setup;
};
static void accept_fd_handler(struct tevent_context *ev,
struct tevent_fd *fde,
uint16_t flags, void *ptr)
{
/* 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;
int client_fd;
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));
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");
/* TODO: what is the best response to this condition? Terminate? */
return;
}
}
cctx = talloc_zero(rctx, struct cli_ctx);
if (!cctx) {
struct sockaddr_un addr;
DEBUG(SSSDBG_FATAL_FAILURE,
"Out of memory trying to setup client context%s!\n",
accept_ctx->is_private ? " on privileged pipe": "");
/* 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;
}
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) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Access denied for uid [%"SPRIuid"].\n",
client_euid(cctx->creds));
} 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;
/* Set up the idle timer */
ret = reset_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,
"Client connected%s!\n",
accept_ctx->is_private ? " to privileged pipe" : "");
return;
}
errno_t reset_idle_timer(struct cli_ctx *cctx)
{
struct timeval tv =
tevent_timeval_current_ofs(cctx->rctx->client_idle_timeout, 0);
talloc_zfree(cctx->idle);
cctx->idle = tevent_add_timer(cctx->ev, cctx, tv, 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;
}
void idle_handler(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *data)
{
/* This connection is idle. Terminate it */
struct cli_ctx *cctx =
talloc_get_type(data, struct cli_ctx);
DEBUG(SSSDBG_TRACE_INTERNAL,
"Terminating idle client [%p][%d]\n",
cctx, cctx->cfd);
/* The cli_ctx destructor will handle the rest */
talloc_free(cctx);
}
static int sss_dp_init(struct resp_ctx *rctx,
struct sbus_iface_map *sbus_iface,
const char *cli_name,
struct sss_domain_info *domain)
{
struct be_conn *be_conn;
int 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;
/* Set up SBUS connection to the monitor */
ret = dp_get_sbus_address(be_conn, &be_conn->sbus_address, domain->name);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE, "Could not locate DP address.\n");
return ret;
}
ret = sbus_client_init(rctx, rctx->ev,
be_conn->sbus_address,
&be_conn->conn);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE, "Failed to connect to monitor services.\n");
return ret;
}
if (sbus_iface != NULL) {
ret = sbus_conn_register_iface_map(be_conn->conn, sbus_iface, rctx);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE, "Failed to register D-Bus interface.\n");
return ret;
}
}
DLIST_ADD_END(rctx->be_conns, be_conn, struct be_conn *);
/* Identify ourselves to the DP */
ret = rdp_register_client(be_conn, cli_name);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE, "Failed to identify to the DP!\n");
return ret;
}
return EOK;
}
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) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Unable to bind on socket '%s'\n", sock_name);
ret = EIO;
goto done;
}
if (listen(fd, 10) == -1) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Unable to listen on socket '%s'\n", sock_name);
ret = EIO;
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;
/* by default we want to open sockets ourselves */
rctx->lfd = -1;
rctx->priv_lfd = -1;
#ifdef HAVE_SYSTEMD
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 = 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;
}
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.\n");
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,
0, &tmp_value);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Fatal failure of setup negative cache timeout.\n");
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;
}
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 *svc_name,
uint16_t svc_version,
struct mon_cli_iface *monitor_intf,
const char *cli_name,
struct sbus_iface_map *sbus_iface,
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();
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 [%d]: %s\n",
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;
}
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,
"Cannnot get the default domain timeout [%d]: %s\n",
ret, strerror(ret));
goto fail;
}
if (rctx->domains_timeout < 0) {
DEBUG(SSSDBG_CONF_SETTINGS, "timeout can't be set to negative value, setting default\n");
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,
"Cannnot 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,
"Cannnot 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 = sss_monitor_init(rctx, rctx->ev, monitor_intf,
svc_name, svc_version, MT_SVC_SERVICE,
rctx, &rctx->mon_conn);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up message bus\n");
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;
}
/* skip local domain, it doesn't have a backend */
if (strcasecmp(dom->provider, "local") == 0) {
continue;
}
ret = sss_dp_init(rctx, sbus_iface, cli_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 = set_unix_socket(rctx, conn_setup);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing socket\n");
goto fail;
}
/* Create DP request table */
ret = sss_hash_create(rctx, 30, &rctx->dp_request_table);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Could not create hash table for the request queue\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" :
"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;
}
int responder_logrotate(struct sbus_request *dbus_req, void *data)
{
errno_t ret;
struct resp_ctx *rctx = talloc_get_type(data, struct resp_ctx);
ret = server_common_rotate_logs(rctx->cdb, rctx->confdb_service_path);
if (ret != EOK) return ret;
return sbus_request_return_and_finish(dbus_req, DBUS_TYPE_INVALID);
}
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, &current_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");
}
}