diff options
Diffstat (limited to 'src/responder/ssh')
-rw-r--r-- | src/responder/ssh/ssh_cert_to_ssh_key.c | 342 | ||||
-rw-r--r-- | src/responder/ssh/ssh_cmd.c | 409 | ||||
-rw-r--r-- | src/responder/ssh/ssh_known_hosts.c | 333 | ||||
-rw-r--r-- | src/responder/ssh/ssh_private.h | 100 | ||||
-rw-r--r-- | src/responder/ssh/ssh_protocol.c | 252 | ||||
-rw-r--r-- | src/responder/ssh/ssh_reply.c | 429 | ||||
-rw-r--r-- | src/responder/ssh/sshsrv.c | 235 |
7 files changed, 2100 insertions, 0 deletions
diff --git a/src/responder/ssh/ssh_cert_to_ssh_key.c b/src/responder/ssh/ssh_cert_to_ssh_key.c new file mode 100644 index 0000000..b8bc8b7 --- /dev/null +++ b/src/responder/ssh/ssh_cert_to_ssh_key.c @@ -0,0 +1,342 @@ +/* + SSSD - certificate handling utils + + Copyright (C) Sumit Bose <sbose@redhat.com> 2018 + + 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 "util/cert.h" +#include "util/crypto/sss_crypto.h" +#include "util/child_common.h" +#include "lib/certmap/sss_certmap.h" + +struct cert_to_ssh_key_state { + struct tevent_context *ev; + const char *logfile; + time_t timeout; + const char **extra_args; + const char **certs; + struct ldb_val *keys; + size_t cert_count; + size_t iter; + size_t valid_keys; + + struct sss_child_ctx_old *child_ctx; + struct tevent_timer *timeout_handler; + struct child_io_fds *io; +}; + +static errno_t cert_to_ssh_key_step(struct tevent_req *req); +static void cert_to_ssh_key_done(int child_status, + struct tevent_signal *sige, + void *pvt); + +struct tevent_req *cert_to_ssh_key_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *logfile, time_t timeout, + const char *ca_db, + struct sss_certmap_ctx *sss_certmap_ctx, + size_t cert_count, + struct ldb_val *bin_certs, + const char *verify_opts) +{ + struct tevent_req *req; + struct cert_to_ssh_key_state *state; + size_t arg_c; + size_t c; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct cert_to_ssh_key_state); + if (req == NULL) { + return NULL; + } + + if (ca_db == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing CA DB path.\n"); + ret = EINVAL; + goto done; + } + + state->ev = ev; + state->logfile = logfile; + state->timeout = timeout; + state->io = talloc(state, struct child_io_fds); + if (state->io == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto done; + } + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + state->keys = talloc_zero_array(state, struct ldb_val, cert_count); + if (state->keys == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + state->valid_keys = 0; + + state->extra_args = talloc_zero_array(state, const char *, 8); + if (state->extra_args == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + /* extra_args are added in revers order, base64 encoded certificate is + * added at 0 */ + arg_c = 1; + state->extra_args[arg_c++] = "--certificate"; + state->extra_args[arg_c++] = ca_db; + state->extra_args[arg_c++] = "--ca_db"; + if (verify_opts != NULL) { + state->extra_args[arg_c++] = verify_opts; + state->extra_args[arg_c++] = "--verify"; + } + state->extra_args[arg_c++] = "--verification"; + + state->certs = talloc_zero_array(state, const char *, cert_count); + if (state->certs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + state->cert_count = 0; + for (c = 0; c < cert_count; c++) { + + if (sss_certmap_ctx != NULL) { + ret = sss_certmap_match_cert(sss_certmap_ctx, bin_certs[c].data, + bin_certs[c].length); + if (ret != 0) { + DEBUG(SSSDBG_TRACE_ALL, "Certificate does not match matching " + "rules and is ignored.\n"); + continue; + } + } + state->certs[state->cert_count] = sss_base64_encode(state->certs, + bin_certs[c].data, + bin_certs[c].length); + if (state->certs[state->cert_count] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_encode failed.\n"); + ret = EINVAL; + goto done; + } + + state->cert_count++; + } + + state->iter = 0; + + ret = cert_to_ssh_key_step(req); + +done: + if (ret != EAGAIN) { + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + } + + return req; +} + +static void p11_child_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 cert_to_ssh_key_state *state = + tevent_req_data(req, struct cert_to_ssh_key_state); + + DEBUG(SSSDBG_MINOR_FAILURE, "Timeout reached for p11_child.\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + tevent_req_error(req, ERR_P11_CHILD_TIMEOUT); +} + +static errno_t cert_to_ssh_key_step(struct tevent_req *req) +{ + struct cert_to_ssh_key_state *state = tevent_req_data(req, + struct cert_to_ssh_key_state); + int ret; + int pipefd_from_child[2] = PIPE_INIT; + int pipefd_to_child[2] = PIPE_INIT; + pid_t child_pid; + struct timeval tv; + + if (state->iter >= state->cert_count) { + return EOK; + } + + state->extra_args[0] = state->certs[state->iter]; + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + child_pid = fork(); + if (child_pid == 0) { /* child */ + exec_child_ex(state, pipefd_to_child, pipefd_from_child, P11_CHILD_PATH, + state->logfile, state->extra_args, false, + STDIN_FILENO, STDOUT_FILENO); + /* We should never get here */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec p11 child\n"); + } else if (child_pid > 0) { /* parent */ + + state->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + sss_fd_nonblocking(state->io->read_from_child_fd); + + state->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->write_to_child_fd); + + /* Set up SIGCHLD handler */ + ret = child_handler_setup(state->ev, child_pid, cert_to_ssh_key_done, + req, &state->child_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_P11_CHILD; + goto done; + } + + /* Set up timeout handler */ + tv = sss_tevent_timeval_current_ofs_time_t(state->timeout); + state->timeout_handler = tevent_add_timer(state->ev, req, tv, + p11_child_timeout, + req); + if (state->timeout_handler == NULL) { + ret = ERR_P11_CHILD; + goto done; + } + /* Now either wait for the timeout to fire or the child to finish */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + return EAGAIN; + +done: + if (ret != EOK) { + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + } + + return ret; +} + +static void cert_to_ssh_key_done(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct cert_to_ssh_key_state *state = tevent_req_data(req, + struct cert_to_ssh_key_state); + int ret; + bool valid = false; + + PIPE_FD_CLOSE(state->io->read_from_child_fd); + PIPE_FD_CLOSE(state->io->write_to_child_fd); + + if (WIFEXITED(child_status)) { + if (WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + P11_CHILD_PATH " failed with status [%d]\n", child_status); + } else { + valid = true; + } + } + + if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + P11_CHILD_PATH " was terminated by signal [%d]\n", + WTERMSIG(child_status)); + } + + if (valid) { + DEBUG(SSSDBG_TRACE_LIBS, "Certificate [%s] is valid.\n", + state->certs[state->iter]); + ret = get_ssh_key_from_derb64(state->keys, + state->certs[state->iter], + &state->keys[state->iter].data, + &state->keys[state->iter].length); + if (ret == EOK) { + state->valid_keys++; + } else { + DEBUG(SSSDBG_OP_FAILURE, "get_ssh_key_from_cert failed, " + "skipping certificate [%s].\n", + state->certs[state->iter]); + state->keys[state->iter].data = NULL; + state->keys[state->iter].length = 0; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Certificate [%s] is not valid.\n", + state->certs[state->iter]); + state->keys[state->iter].data = NULL; + state->keys[state->iter].length = 0; + } + + state->iter++; + ret = cert_to_ssh_key_step(req); + + if (ret != EAGAIN) { + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + } + + return; +} + +errno_t cert_to_ssh_key_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct ldb_val **keys, size_t *valid_keys) +{ + struct cert_to_ssh_key_state *state = tevent_req_data(req, + struct cert_to_ssh_key_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (keys != NULL) { + *keys = talloc_steal(mem_ctx, state->keys); + } + + if (valid_keys != NULL) { + *valid_keys = state->valid_keys; + } + + return EOK; +} diff --git a/src/responder/ssh/ssh_cmd.c b/src/responder/ssh/ssh_cmd.c new file mode 100644 index 0000000..45ab57b --- /dev/null +++ b/src/responder/ssh/ssh_cmd.c @@ -0,0 +1,409 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <talloc.h> +#include <string.h> +#include <pwd.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/ssh/ssh_private.h" +#include "responder/pam/pam_helpers.h" +#include "lib/certmap/sss_certmap.h" + +struct ssh_cmd_ctx { + struct cli_ctx *cli_ctx; + const char *name; + const char *alias; + const char *domain; +}; + +static errno_t +ssh_check_non_sssd_user(const char *username) +{ + struct passwd *pwd; + + pwd = getpwnam(username); + if (pwd != NULL) { + DEBUG(SSSDBG_TRACE_ALL, "%s is a non-SSSD user\n", username); + return ERR_NON_SSSD_USER; + } + + return ENOENT; +} + + +static struct sss_domain_info * +ssh_get_result_domain(struct resp_ctx *rctx, + struct cache_req_result *result, + const char *name) +{ + if (result != NULL) { + return result->domain; + } + + return find_domain_by_name(rctx->domains, name, true); +} + +static void ssh_cmd_get_user_pubkeys_done(struct tevent_req *subreq); + +static errno_t ssh_cmd_get_user_pubkeys(struct cli_ctx *cli_ctx) +{ + struct ssh_cmd_ctx *cmd_ctx; + struct tevent_req *subreq; + errno_t ret; + + static const char *attrs[] = { SYSDB_NAME, SYSDB_SSH_PUBKEY, + SYSDB_USER_CERT, NULL }; + + cmd_ctx = talloc_zero(cli_ctx, struct ssh_cmd_ctx); + if (cmd_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cmd_ctx->cli_ctx = cli_ctx; + + ret = ssh_protocol_parse_user(cli_ctx, cli_ctx->rctx->default_domain, + &cmd_ctx->name, &cmd_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Requesting SSH user public keys for [%s] from [%s]\n", + cmd_ctx->name, cmd_ctx->domain ? cmd_ctx->domain : "<ALL>"); + + if (strcmp(cmd_ctx->name, "root") == 0) { + ret = ERR_NON_SSSD_USER; + goto done; + } + + subreq = cache_req_user_by_name_attrs_send(cmd_ctx, cli_ctx->ev, + cli_ctx->rctx, + cli_ctx->rctx->ncache, 0, + cmd_ctx->domain, + cmd_ctx->name, attrs); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ssh_cmd_get_user_pubkeys_done, cmd_ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmd_ctx); + return ssh_protocol_done(cli_ctx, ret); + } + + return ret; +} + +struct priv_sss_debug { + int level; +}; + +static void ssh_ext_debug(void *private, const char *file, long line, + const char *function, const char *format, ...) +{ + va_list ap; + struct priv_sss_debug *data = private; + int level = SSSDBG_OP_FAILURE; + + if (data != NULL) { + level = data->level; + } + + va_start(ap, format); + sss_vdebug_fn(file, line, function, level, APPEND_LINE_FEED, + format, ap); + va_end(ap); +} + +static errno_t ssh_cmd_refresh_certmap_ctx(struct ssh_ctx *ssh_ctx, + struct sss_domain_info *domains) +{ + + struct sss_certmap_ctx *sss_certmap_ctx = NULL; + struct sss_domain_info *dom; + struct certmap_info **certmap_list; + size_t c; + int ret; + bool rule_added; + bool all_rules = false; + bool no_rules = false; + bool rules_present = false; + + ssh_ctx->cert_rules_error = false; + + if (ssh_ctx->cert_rules == NULL || ssh_ctx->cert_rules[0] == NULL) { + all_rules = true; + } else if (ssh_ctx->cert_rules[0] != NULL + && ssh_ctx->cert_rules[1] == NULL) { + if (strcmp(ssh_ctx->cert_rules[0], "all_rules") == 0) { + all_rules = true; + } else if (strcmp(ssh_ctx->cert_rules[0], "no_rules") == 0) { + no_rules = true; + } + } + + if (!ssh_ctx->use_cert_keys + || ssh_ctx->certmap_last_read + >= ssh_ctx->rctx->get_domains_last_call.tv_sec + || no_rules) { + DEBUG(SSSDBG_TRACE_ALL, "No certmap update needed.\n"); + return EOK; + } + + ret = sss_certmap_init(ssh_ctx, ssh_ext_debug, NULL, &sss_certmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_init failed.\n"); + goto done; + } + + rule_added = false; + DLIST_FOR_EACH(dom, domains) { + certmap_list = dom->certmaps; + if (certmap_list == NULL || *certmap_list == NULL) { + continue; + } + + for (c = 0; certmap_list[c] != NULL; c++) { + rules_present = true; + + if (!all_rules && !string_in_list(certmap_list[c]->name, + ssh_ctx->cert_rules, true)) { + DEBUG(SSSDBG_TRACE_ALL, "Skipping matching rule [%s], it is " + "not listed in the ssh_use_certificate_matching_rules " + "option.\n", certmap_list[c]->name); + continue; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Trying to add rule [%s][%d][%s][%s].\n", + certmap_list[c]->name, certmap_list[c]->priority, + certmap_list[c]->match_rule, certmap_list[c]->map_rule); + + ret = sss_certmap_add_rule(sss_certmap_ctx, + certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule, + certmap_list[c]->domains); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_certmap_add_rule failed for rule [%s] " + "with error [%d][%s], skipping. " + "Please check for typos and if rule syntax is supported.\n", + certmap_list[c]->name, ret, sss_strerror(ret)); + continue; + } + rule_added = true; + } + } + + if (!rule_added) { + if (!rules_present) { + DEBUG(SSSDBG_TRACE_FUNC, + "No rules available, trying to add default matching rule.\n"); + ret = sss_certmap_add_rule(sss_certmap_ctx, SSS_CERTMAP_MIN_PRIO, + CERT_AUTH_DEFAULT_MATCHING_RULE, + NULL, NULL); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add default matching rule [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + } else { + DEBUG(SSSDBG_CONF_SETTINGS, + "No matching rule added, please check " + "ssh_use_certificate_matching_rules option values for " + "typos.\n"); + + ret = EINVAL; + goto done; + } + } + + ret = EOK; + +done: + if (ret == EOK) { + sss_certmap_free_ctx(ssh_ctx->sss_certmap_ctx); + ssh_ctx->sss_certmap_ctx = sss_certmap_ctx; + ssh_ctx->certmap_last_read = ssh_ctx->rctx->get_domains_last_call.tv_sec; + } else { + sss_certmap_free_ctx(sss_certmap_ctx); + ssh_ctx->cert_rules_error = true; + } + + return ret; +} + +static void ssh_cmd_get_user_pubkeys_done(struct tevent_req *subreq) +{ + struct cache_req_result *result; + struct ssh_cmd_ctx *cmd_ctx; + errno_t ret; + struct ssh_ctx *ssh_ctx; + + cmd_ctx = tevent_req_callback_data(subreq, struct ssh_cmd_ctx); + + ret = cache_req_user_by_name_attrs_recv(cmd_ctx, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + if (ret == ENOENT) { + /* Check if it is a non SSSD user. */ + ret = ssh_check_non_sssd_user(cmd_ctx->name); + } + + ssh_protocol_done(cmd_ctx->cli_ctx, ret); + goto done; + } + + ssh_ctx = talloc_get_type(cmd_ctx->cli_ctx->rctx->pvt_ctx, struct ssh_ctx); + ret = ssh_cmd_refresh_certmap_ctx(ssh_ctx, cmd_ctx->cli_ctx->rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ssh_cmd_refresh_certmap_ctx failed, " + "certificate matching might not work as expected.\n"); + } + + ssh_protocol_reply(cmd_ctx->cli_ctx, result); + +done: + talloc_free(cmd_ctx); +} + +static void ssh_cmd_get_host_pubkeys_done(struct tevent_req *subreq); + +static errno_t ssh_cmd_get_host_pubkeys(struct cli_ctx *cli_ctx) +{ + struct ssh_cmd_ctx *cmd_ctx; + struct tevent_req *subreq; + errno_t ret; + + static const char *attrs[] = { SYSDB_NAME, SYSDB_SSH_PUBKEY, NULL }; + + cmd_ctx = talloc_zero(cli_ctx, struct ssh_cmd_ctx); + if (cmd_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cmd_ctx->cli_ctx = cli_ctx; + + ret = ssh_protocol_parse_host(cli_ctx, &cmd_ctx->name, &cmd_ctx->alias, + &cmd_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Requesting SSH host public keys for [%s] from [%s]\n", + cmd_ctx->name, cmd_ctx->domain ? cmd_ctx->domain : "<ALL>"); + + subreq = cache_req_ssh_host_id_by_name_send(cmd_ctx, cli_ctx->ev, + cli_ctx->rctx, + cli_ctx->rctx->ncache, 0, + cmd_ctx->domain, + cmd_ctx->name, + cmd_ctx->alias, attrs); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ssh_cmd_get_host_pubkeys_done, cmd_ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmd_ctx); + return ssh_protocol_done(cli_ctx, ret); + } + + return ret; +} + +static void ssh_cmd_get_host_pubkeys_done(struct tevent_req *subreq) +{ + struct cache_req_result *result = NULL; + struct sss_domain_info *domain; + struct ssh_cmd_ctx *cmd_ctx; + struct ssh_ctx *ssh_ctx; + errno_t ret; + + cmd_ctx = tevent_req_callback_data(subreq, struct ssh_cmd_ctx); + ssh_ctx = talloc_get_type(cmd_ctx->cli_ctx->rctx->pvt_ctx, struct ssh_ctx); + + ret = cache_req_ssh_host_id_by_name_recv(cmd_ctx, subreq, &result); + talloc_zfree(subreq); + + if (ret == EOK || ret == ENOENT) { + domain = ssh_get_result_domain(ssh_ctx->rctx, result, cmd_ctx->domain); + + ssh_update_known_hosts_file(ssh_ctx->rctx->domains, domain, + cmd_ctx->name, ssh_ctx->hash_known_hosts, + ssh_ctx->known_hosts_timeout); + } + + if (ret != EOK) { + ssh_protocol_done(cmd_ctx->cli_ctx, ret); + goto done; + } + + ssh_protocol_reply(cmd_ctx->cli_ctx, result); + +done: + talloc_free(cmd_ctx); +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version ssh_cli_protocol_version[] = { + {0, NULL, NULL} + }; + + return ssh_cli_protocol_version; +} + +struct sss_cmd_table *get_ssh_cmds(void) { + static struct sss_cmd_table ssh_cmds[] = { + {SSS_GET_VERSION, sss_cmd_get_version}, + {SSS_SSH_GET_USER_PUBKEYS, ssh_cmd_get_user_pubkeys}, + {SSS_SSH_GET_HOST_PUBKEYS, ssh_cmd_get_host_pubkeys}, + {SSS_CLI_NULL, NULL} + }; + + return ssh_cmds; +} diff --git a/src/responder/ssh/ssh_known_hosts.c b/src/responder/ssh/ssh_known_hosts.c new file mode 100644 index 0000000..2be240b --- /dev/null +++ b/src/responder/ssh/ssh_known_hosts.c @@ -0,0 +1,333 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <talloc.h> + +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "util/sss_ssh.h" +#include "db/sysdb.h" +#include "db/sysdb_ssh.h" +#include "responder/ssh/ssh_private.h" + +static char * +ssh_host_pubkeys_format_known_host_plain(TALLOC_CTX *mem_ctx, + struct sss_ssh_ent *ent) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + char *name, *pubkey; + char *result = NULL; + size_t i; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return NULL; + } + + name = talloc_strdup(tmp_ctx, ent->name); + if (!name) { + goto done; + } + + for (i = 0; i < ent->num_aliases; i++) { + name = talloc_asprintf_append(name, ",%s", ent->aliases[i]); + if (!name) { + goto done; + } + } + + result = talloc_strdup(tmp_ctx, ""); + if (!result) { + goto done; + } + + for (i = 0; i < ent->num_pubkeys; i++) { + ret = sss_ssh_format_pubkey(tmp_ctx, &ent->pubkeys[i], &pubkey); + if (ret != EOK) { + result = NULL; + goto done; + } + + result = talloc_asprintf_append(result, "%s %s\n", name, pubkey); + if (!result) { + goto done; + } + + talloc_free(pubkey); + } + + talloc_steal(mem_ctx, result); + +done: + talloc_free(tmp_ctx); + + return result; +} + +static char * +ssh_host_pubkeys_format_known_host_hashed(TALLOC_CTX *mem_ctx, + struct sss_ssh_ent *ent) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + char *name, *pubkey, *saltstr, *hashstr, *result; + unsigned char salt[SSS_SHA1_LENGTH], hash[SSS_SHA1_LENGTH]; + size_t i, j; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return NULL; + } + + result = talloc_strdup(tmp_ctx, ""); + if (!result) { + goto done; + } + + for (i = 0; i < ent->num_pubkeys; i++) { + ret = sss_ssh_format_pubkey(tmp_ctx, &ent->pubkeys[i], &pubkey); + if (ret != EOK) { + result = NULL; + goto done; + } + + for (j = 0; j <= ent->num_aliases; j++) { + name = (j == 0 ? ent->name : ent->aliases[j-1]); + + ret = sss_generate_csprng_buffer((uint8_t *)salt, SSS_SHA1_LENGTH); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_generate_csprng_buffer() failed (%d)\n", ret); + result = NULL; + goto done; + } + + ret = sss_hmac_sha1(salt, SSS_SHA1_LENGTH, + (unsigned char *)name, strlen(name), + hash); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_hmac_sha1() failed (%d): %s\n", + ret, strerror(ret)); + result = NULL; + goto done; + } + + saltstr = sss_base64_encode(tmp_ctx, salt, SSS_SHA1_LENGTH); + if (!saltstr) { + result = NULL; + goto done; + } + + hashstr = sss_base64_encode(tmp_ctx, hash, SSS_SHA1_LENGTH); + if (!hashstr) { + result = NULL; + goto done; + } + + result = talloc_asprintf_append(result, "|1|%s|%s %s\n", + saltstr, hashstr, pubkey); + if (!result) { + goto done; + } + + talloc_free(saltstr); + talloc_free(hashstr); + } + + talloc_free(pubkey); + } + + talloc_steal(mem_ctx, result); + +done: + talloc_free(tmp_ctx); + + return result; +} + +static errno_t +ssh_write_known_hosts(struct sss_domain_info *domains, + bool hash_known_hosts, + time_t now, + int fd) +{ + TALLOC_CTX *tmp_ctx; + struct sss_domain_info *dom; + struct ldb_message **hosts; + struct sysdb_ctx *sysdb; + struct sss_ssh_ent *ent; + char *entstr; + size_t num_hosts; + size_t i; + ssize_t wret; + errno_t ret; + + static const char *attrs[] = { + SYSDB_NAME, + SYSDB_NAME_ALIAS, + SYSDB_SSH_PUBKEY, + NULL + }; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + for (dom = domains; dom != NULL; dom = get_next_domain(dom, false)) { + sysdb = dom->sysdb; + if (sysdb == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Fatal: Sysdb CTX not found for this domain!\n"); + ret = EFAULT; + goto done; + } + + ret = sysdb_get_ssh_known_hosts(tmp_ctx, dom, now, attrs, + &hosts, &num_hosts); + if (ret == ENOENT) { + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Host search failed for domain " + "%s [%d]: %s\n", dom->name, ret, sss_strerror(ret)); + continue; + } + + for (i = 0; i < num_hosts; i++) { + ret = sss_ssh_make_ent(tmp_ctx, hosts[i], &ent); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get SSH host public keys\n"); + continue; + } + + if (hash_known_hosts) { + entstr = ssh_host_pubkeys_format_known_host_hashed(ent, ent); + } else { + entstr = ssh_host_pubkeys_format_known_host_plain(ent, ent); + } + + if (entstr == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to format known_hosts data " + "for [%s]\n", ent->name); + continue; + } + + wret = sss_atomic_write_s(fd, entstr, strlen(entstr)); + if (wret == -1) { + ret = errno; + goto done; + } + + talloc_free(ent); + } + + talloc_free(hosts); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +ssh_update_known_hosts_file(struct sss_domain_info *domains, + struct sss_domain_info *domain, + const char *name, + bool hash_known_hosts, + int known_hosts_timeout) +{ + TALLOC_CTX *tmp_ctx; + char *filename; + errno_t ret; + time_t now; + int fd = -1; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + now = time(NULL); + + /* Update host's expiration time. */ + if (domain != NULL) { + ret = sysdb_update_ssh_known_host_expire(domain, name, now, + known_hosts_timeout); + if (ret != EOK && ret != ENOENT) { + goto done; + } + } + + /* Create temporary known hosts file. */ + filename = talloc_strdup(tmp_ctx, SSS_SSH_KNOWN_HOSTS_TEMP_TMPL); + if (filename == NULL) { + ret = ENOMEM; + goto done; + } + + fd = sss_unique_file_ex(tmp_ctx, filename, 0133, &ret); + if (fd == -1) { + filename = NULL; + goto done; + } + + /* Write contents. */ + ret = ssh_write_known_hosts(domains, hash_known_hosts, now, fd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to write known hosts file " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + + /* Rename to SSH known hosts file. */ + ret = fchmod(fd, 0644); + if (ret == -1) { + ret = errno; + goto done; + } + + ret = rename(filename, SSS_SSH_KNOWN_HOSTS_PATH); + if (ret == -1) { + ret = errno; + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + if (fd != -1) { + close(fd); + } + + return ret; +} diff --git a/src/responder/ssh/ssh_private.h b/src/responder/ssh/ssh_private.h new file mode 100644 index 0000000..0e4ed10 --- /dev/null +++ b/src/responder/ssh/ssh_private.h @@ -0,0 +1,100 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SSHSRV_PRIVATE_H_ +#define _SSHSRV_PRIVATE_H_ + +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" + +#define SSS_SSH_KNOWN_HOSTS_PATH PUBCONF_PATH"/known_hosts" +#define SSS_SSH_KNOWN_HOSTS_TEMP_TMPL PUBCONF_PATH"/.known_hosts.XXXXXX" + +struct ssh_ctx { + struct resp_ctx *rctx; + struct sss_names_ctx *snctx; + + bool hash_known_hosts; + int known_hosts_timeout; + char *ca_db; + bool use_cert_keys; + + time_t certmap_last_read; + struct sss_certmap_ctx *sss_certmap_ctx; + char **cert_rules; + bool cert_rules_error; +}; + +struct sss_cmd_table *get_ssh_cmds(void); + +errno_t +ssh_protocol_parse_user(struct cli_ctx *cli_ctx, + const char *default_domain, + const char **_name, + const char **_domain); + +errno_t +ssh_protocol_parse_host(struct cli_ctx *cli_ctx, + const char **_name, + const char **_alias, + const char **_domain); + +void ssh_protocol_reply(struct cli_ctx *cli_ctx, + struct cache_req_result *result); + +errno_t +ssh_protocol_done(struct cli_ctx *cli_ctx, errno_t error); + +struct tevent_req * ssh_get_output_keys_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_ctx *cli_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg); + +errno_t ssh_get_output_keys_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct sized_string *name, + struct ldb_message_element ***elements, + uint32_t *num_keys); + +errno_t +ssh_protocol_build_reply(struct sss_packet *packet, + struct sized_string name, + struct ldb_message_element **elements, + uint32_t num_keys); + +errno_t +ssh_update_known_hosts_file(struct sss_domain_info *domains, + struct sss_domain_info *domain, + const char *name, + bool hash_known_hosts, + int known_hosts_timeout); + +struct tevent_req *cert_to_ssh_key_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *logfile, time_t timeout, + const char *ca_db, + struct sss_certmap_ctx *sss_certmap_ctx, + size_t cert_count, + struct ldb_val *bin_certs, + const char *verify_opts); + +errno_t cert_to_ssh_key_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct ldb_val **keys, size_t *valid_keys); +#endif /* _SSHSRV_PRIVATE_H_ */ diff --git a/src/responder/ssh/ssh_protocol.c b/src/responder/ssh/ssh_protocol.c new file mode 100644 index 0000000..5a9081b --- /dev/null +++ b/src/responder/ssh/ssh_protocol.c @@ -0,0 +1,252 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2017 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <talloc.h> + +#include "util/util.h" +#include "util/sss_ssh.h" +#include "responder/common/responder.h" +#include "responder/common/responder_packet.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/ssh/ssh_private.h" + +errno_t +ssh_protocol_done(struct cli_ctx *cli_ctx, errno_t error) +{ + struct cli_protocol *pctx; + errno_t ret; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + switch (error) { + case EOK: + /* Create empty packet if none was provided. */ + if (pctx->creq->out == NULL) { + ret = sss_packet_new(pctx->creq, 0, + sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + goto done; + } + + sss_packet_set_error(pctx->creq->out, EOK); + } + + DEBUG(SSSDBG_TRACE_ALL, "Sending reply: success\n"); + ret = EOK; + goto done; + default: + DEBUG(SSSDBG_TRACE_ALL, "Sending reply: error [%d]: %s\n", + error, sss_strerror(error)); + ret = sss_cmd_send_error(cli_ctx, error); + goto done; + } + +done: + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send reply [%d]: %s!\n", + ret, sss_strerror(ret)); + return ret; + } + + sss_cmd_done(cli_ctx, NULL); + return EOK; +} + +static void got_ssh_keys(struct tevent_req *req); +void ssh_protocol_reply(struct cli_ctx *cli_ctx, + struct cache_req_result *result) +{ + errno_t ret; + struct tevent_req *req; + + /* Make sure we have the results around until the end of the request. To + * avoid copying and memory allocation the keys and certificates from the + * result will be referenced during the next requests, so they should not + * be freed too early. */ + result = talloc_steal(cli_ctx, result); + + req = ssh_get_output_keys_send(cli_ctx, cli_ctx->ev, cli_ctx, + result->domain, result->msgs[0]); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_get_output_keys_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(req, got_ssh_keys, cli_ctx); + + return; + +done: + ssh_protocol_done(cli_ctx, ret); +} + +static void got_ssh_keys(struct tevent_req *req) +{ + errno_t ret; + struct cli_ctx *cli_ctx = tevent_req_callback_data(req, struct cli_ctx); + struct cli_protocol *pctx; + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + struct ldb_message_element **elements; + uint32_t num_keys; + struct sized_string name; + + ret = ssh_get_output_keys_recv(req, cli_ctx, &name, &elements, &num_keys); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_get_output_keys_revc failed"); + goto done; + } + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + goto done; + } + + ret = ssh_protocol_build_reply(pctx->creq->out, name, elements, num_keys); + if (ret != EOK) { + goto done; + } + + sss_packet_set_error(pctx->creq->out, EOK); + +done: + ssh_protocol_done(cli_ctx, ret); +} + +static errno_t +ssh_protocol_parse_request(struct cli_ctx *cli_ctx, + const char *default_domain, + const char **_name, + const char **_alias, + const char **_domain) +{ + struct cli_protocol *pctx; + const char *name = NULL; + const char *alias = NULL; + const char *domain = NULL; + uint32_t flags; + uint32_t name_len; + uint32_t alias_len; + uint32_t domain_len; + size_t body_len; + uint8_t *body; + size_t c = 0; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(pctx->creq->in, &body, &body_len); + + SAFEALIGN_COPY_UINT32_CHECK(&flags, body + c, body_len, &c); + if (flags & ~(uint32_t)SSS_SSH_REQ_MASK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid flags received [0x%x]\n", flags); + return EINVAL; + } + + SAFEALIGN_COPY_UINT32_CHECK(&name_len, body + c, body_len, &c); + if (name_len == 0 || name_len > body_len - c) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid name length\n"); + return EINVAL; + } + + name = (const char *)(body + c); + if (!sss_utf8_check((const uint8_t *)name, name_len-1) || + name[name_len - 1] != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Name is not valid UTF-8 string\n"); + return EINVAL; + } + c += name_len; + + if (flags & SSS_SSH_REQ_ALIAS) { + SAFEALIGN_COPY_UINT32_CHECK(&alias_len, body + c, body_len, &c); + if (alias_len == 0 || alias_len > body_len - c) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid alias length\n"); + return EINVAL; + } + + alias = (const char *)(body+c); + if (!sss_utf8_check((const uint8_t *)alias, alias_len - 1) || + alias[alias_len - 1] != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Alias is not valid UTF-8 string\n"); + return EINVAL; + } + c += alias_len; + } + + if (flags & SSS_SSH_REQ_DOMAIN) { + SAFEALIGN_COPY_UINT32_CHECK(&domain_len, body + c, body_len, &c); + if (domain_len > 0) { + if (domain_len > body_len - c) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid domain length\n"); + return EINVAL; + } + + domain = (const char *)(body + c); + if (!sss_utf8_check((const uint8_t *)domain, domain_len - 1) || + domain[domain_len - 1] != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Domain is not valid UTF-8 string\n"); + return EINVAL; + } + c += domain_len; + } else { + domain = default_domain; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Requested domain [%s]\n", domain ? domain : "<ALL>"); + } + + if (_name != NULL) { + *_name = name; + } + + if (_alias != NULL) { + *_alias = alias; + } + + if (_domain != NULL) { + *_domain = domain; + } + + return EOK; +} + +errno_t +ssh_protocol_parse_user(struct cli_ctx *cli_ctx, + const char *default_domain, + const char **_name, + const char **_domain) +{ + return ssh_protocol_parse_request(cli_ctx, default_domain, + _name, NULL, _domain); +} + +errno_t +ssh_protocol_parse_host(struct cli_ctx *cli_ctx, + const char **_name, + const char **_alias, + const char **_domain) +{ + return ssh_protocol_parse_request(cli_ctx, NULL, _name, _alias, _domain); +} diff --git a/src/responder/ssh/ssh_reply.c b/src/responder/ssh/ssh_reply.c new file mode 100644 index 0000000..edeb287 --- /dev/null +++ b/src/responder/ssh/ssh_reply.c @@ -0,0 +1,429 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "util/sss_ssh.h" +#include "util/cert.h" +#include "responder/common/responder.h" +#include "responder/common/responder_packet.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/ssh/ssh_private.h" + +/* Locally used flag for libldb's ldb_message_element structure to indicate + * binary data. Since the related data is only used in memory it is safe. If + * should be used with care if libldb's I/O operations are involved. */ +#define SSS_EL_FLAG_BIN_DATA (1<<4) + +static errno_t decode_and_add_base64_data(struct sss_packet *packet, + struct ldb_message_element *el, + bool skip_base64_decode, + size_t fqname_len, + const char *fqname, + size_t *c) +{ + uint8_t *key; + size_t key_len; + uint8_t *body; + size_t body_len; + int ret; + size_t d; + TALLOC_CTX *tmp_ctx; + + if (el == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Mssing element, nothing to do.\n"); + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + for (d = 0; d < el->num_values; d++) { + if (el->values[d].length == 0 && el->values[d].data == NULL) { + /* skip empty keys, e.g. due to invalid certificate */ + continue; + } + if (skip_base64_decode || (el->flags & SSS_EL_FLAG_BIN_DATA)) { + key = el->values[d].data; + key_len = el->values[d].length; + } else { + key = sss_base64_decode(tmp_ctx, (const char *) el->values[d].data, + &key_len); + if (key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); + ret = ENOMEM; + goto done; + } + } + + ret = sss_packet_grow(packet, + 3*sizeof(uint32_t) + key_len + fqname_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n"); + goto done; + } + sss_packet_get_body(packet, &body, &body_len); + + SAFEALIGN_SET_UINT32(body+(*c), 0, c); + SAFEALIGN_SET_UINT32(body+(*c), fqname_len, c); + safealign_memcpy(body+(*c), fqname, fqname_len, c); + SAFEALIGN_SET_UINT32(body+(*c), key_len, c); + safealign_memcpy(body+(*c), key, key_len, c); + + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +struct ssh_get_output_keys_state { + struct tevent_context *ev; + struct cli_ctx *cli_ctx; + struct ldb_message *msg; + char *cert_verification_opts; + int p11_child_timeout; + struct ssh_ctx *ssh_ctx; + struct ldb_message_element *user_cert; + struct ldb_message_element *user_cert_override; + struct ldb_message_element *current_cert; + + const char *name; + struct ldb_message_element **elements; + uint32_t num_keys; + size_t iter; +}; + +void ssh_get_output_keys_done(struct tevent_req *subreq); + +struct tevent_req *ssh_get_output_keys_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_ctx *cli_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg) +{ + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + struct ssh_get_output_keys_state *state; + + req = tevent_req_create(mem_ctx, &state, struct ssh_get_output_keys_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->cli_ctx = cli_ctx; + state->msg = msg; + state->num_keys = 0; + state->iter = 0; + state->ssh_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct ssh_ctx); + if (state->ssh_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing ssh responder context.\n"); + ret = EINVAL; + goto done; + } + + state->name = ldb_msg_find_attr_as_string(state->msg, SYSDB_NAME, NULL); + if (state->name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing name.\n"); + ret = EINVAL; + goto done; + } + + state->elements = talloc_zero_array(state, struct ldb_message_element *, 6); + if (state->elements == NULL) { + ret = ENOMEM; + goto done; + } + + state->elements[state->iter] = ldb_msg_find_element(state->msg, + SYSDB_SSH_PUBKEY); + if (state->elements[state->iter] != NULL) { + state->num_keys += state->elements[state->iter]->num_values; + state->iter++; + } + + state->elements[state->iter] = ldb_msg_find_element(state->msg, + ORIGINALAD_PREFIX SYSDB_SSH_PUBKEY); + if (state->elements[state->iter] != NULL) { + state->num_keys += state->elements[state->iter]->num_values; + state->iter++; + } + + if (DOM_HAS_VIEWS(domain)) { + state->elements[state->iter] = ldb_msg_find_element(state->msg, + OVERRIDE_PREFIX SYSDB_SSH_PUBKEY); + if (state->elements[state->iter] != NULL) { + state->num_keys += state->elements[state->iter]->num_values; + state->iter++; + } + } + + if (!state->ssh_ctx->use_cert_keys) { + DEBUG(SSSDBG_TRACE_ALL, "Skipping keys from certificates.\n"); + ret = EOK; + goto done; + } + + if (state->ssh_ctx->cert_rules_error) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Skipping keys from certificates because there was an error " + "while processing matching rules.\n"); + ret = EOK; + goto done; + } + + ret = confdb_get_string(cli_ctx->rctx->cdb, state, + CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_CERT_VERIFICATION, NULL, + &state->cert_verification_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read verification options from confdb: [%d] %s\n", + ret, sss_strerror(ret)); + goto done; + } + + state->p11_child_timeout = -1; + ret = confdb_get_int(cli_ctx->rctx->cdb, CONFDB_SSH_CONF_ENTRY, + CONFDB_PAM_P11_CHILD_TIMEOUT, -1, + &state->p11_child_timeout); + if (ret != EOK || state->p11_child_timeout == -1) { + /* check pam configuration as well or use default */ + ret = confdb_get_int(cli_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_CHILD_TIMEOUT, + P11_CHILD_TIMEOUT_DEFAULT, + &state->p11_child_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read p11_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + state->user_cert = ldb_msg_find_element(state->msg, SYSDB_USER_CERT); + if (DOM_HAS_VIEWS(domain)) { + state->user_cert_override = ldb_msg_find_element(state->msg, + OVERRIDE_PREFIX SYSDB_USER_CERT); + } + + if (state->user_cert == NULL && state->user_cert_override == NULL) { + /* no certificates to convert, we are done */ + ret = EOK; + goto done; + } + + state->current_cert = state->user_cert != NULL ? state->user_cert + : state->user_cert_override; + + subreq = cert_to_ssh_key_send(state, state->ev, + P11_CHILD_LOG_FILE, + state->p11_child_timeout, + state->ssh_ctx->ca_db, + state->ssh_ctx->sss_certmap_ctx, + state->current_cert->num_values, + state->current_cert->values, + state->cert_verification_opts); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "cert_to_ssh_key_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ssh_get_output_keys_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + } + + return req; +} + +void ssh_get_output_keys_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ssh_get_output_keys_state *state = tevent_req_data(req, + struct ssh_get_output_keys_state); + int ret; + struct ldb_val *keys; + size_t valid_keys; + + ret = cert_to_ssh_key_recv(subreq, state, &keys, &valid_keys); + talloc_zfree(subreq); + if (ret != EOK) { + if (ret == ERR_P11_CHILD_TIMEOUT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "cert_to_ssh_key request timeout, " + "consider increasing p11_child_timeout.\n"); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "cert_to_ssh_key request failed, ssh keys derived " + "from certificates will be skipped.\n"); + } + /* Ignore ssh keys from certificates and return what we already have */ + tevent_req_done(req); + return; + } + + state->elements[state->iter] = talloc_zero(state->elements, + struct ldb_message_element); + if (state->elements[state->iter] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + state->elements[state->iter]->values = talloc_steal( + state->elements[state->iter], + keys); + state->elements[state->iter]->num_values = state->current_cert->num_values; + state->elements[state->iter]->flags |= SSS_EL_FLAG_BIN_DATA; + state->num_keys += valid_keys; + + if (state->current_cert == state->user_cert) { + state->current_cert = state->user_cert_override; + } else if (state->current_cert == state->user_cert_override) { + state->current_cert = NULL; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected certificate pointer.\n"); + tevent_req_error(req, EINVAL); + return; + } + + if (state->current_cert == NULL) { + /* done */ + ret = EOK; + goto done; + } + + subreq = cert_to_ssh_key_send(state, state->ev, NULL, + state->p11_child_timeout, + state->ssh_ctx->ca_db, + state->ssh_ctx->sss_certmap_ctx, + state->current_cert->num_values, + state->current_cert->values, + state->cert_verification_opts); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "cert_to_ssh_key_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ssh_get_output_keys_done, req); + return; +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + return; +} + +errno_t ssh_get_output_keys_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct sized_string *name, + struct ldb_message_element ***elements, + uint32_t *num_keys) +{ + struct ssh_get_output_keys_state *state = tevent_req_data(req, + struct ssh_get_output_keys_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (name != NULL) { + name->str = talloc_strdup(mem_ctx, state->name); + name->len = strlen(name->str) + 1; + } + + if (elements != NULL) { + *elements = talloc_steal(mem_ctx, state->elements); + } + + if (num_keys != NULL) { + *num_keys = state->num_keys; + } + + return EOK; +} + +errno_t +ssh_protocol_build_reply(struct sss_packet *packet, + struct sized_string name, + struct ldb_message_element **elements, + uint32_t num_keys) +{ + size_t body_len; + uint8_t *body; + size_t c = 0; + errno_t ret; + int i; + + ret = sss_packet_grow(packet, 2 * sizeof(uint32_t)); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + + SAFEALIGN_SET_UINT32(&body[c], num_keys, &c); + SAFEALIGN_SET_UINT32(&body[c], 0, &c); + + if (num_keys == 0) { + ret = EOK; + goto done; + } + + for (i = 0; elements[i] != NULL; i++) { + ret = decode_and_add_base64_data(packet, elements[i], false, + name.len, name.str, &c); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "decode_and_add_base64_data failed.\n"); + goto done; + } + } + + ret = EOK; + +done: + + return ret; +} diff --git a/src/responder/ssh/sshsrv.c b/src/responder/ssh/sshsrv.c new file mode 100644 index 0000000..91fb77b --- /dev/null +++ b/src/responder/ssh/sshsrv.c @@ -0,0 +1,235 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + + Copyright (C) 2012 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <popt.h> + +#include "util/util.h" +#include "util/child_common.h" +#include "confdb/confdb.h" +#include "responder/common/responder.h" +#include "responder/ssh/ssh_private.h" +#include "providers/data_provider.h" +#include "sss_iface/sss_iface_async.h" + +int ssh_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb) +{ + struct resp_ctx *rctx; + struct sss_cmd_table *ssh_cmds; + struct ssh_ctx *ssh_ctx; + int ret; + + ssh_cmds = get_ssh_cmds(); + ret = sss_process_init(mem_ctx, ev, cdb, + ssh_cmds, + SSS_SSH_SOCKET_NAME, -1, NULL, -1, + CONFDB_SSH_CONF_ENTRY, + SSS_BUS_SSH, SSS_SSH_SBUS_SERVICE_NAME, + sss_connection_setup, + &rctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "sss_process_init() failed\n"); + return ret; + } + + ssh_ctx = talloc_zero(rctx, struct ssh_ctx); + if (!ssh_ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing ssh_ctx\n"); + ret = ENOMEM; + goto fail; + } + + ssh_ctx->rctx = rctx; + ssh_ctx->rctx->pvt_ctx = ssh_ctx; + + ret = sss_names_init_from_args(ssh_ctx, + SSS_DEFAULT_RE, + "%1$s@%2$s", &ssh_ctx->snctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing regex data\n"); + goto fail; + } + + /* Get responder options */ + + /* Get ssh_hash_known_hosts option */ + ret = confdb_get_bool(ssh_ctx->rctx->cdb, + CONFDB_SSH_CONF_ENTRY, CONFDB_SSH_HASH_KNOWN_HOSTS, + CONFDB_DEFAULT_SSH_HASH_KNOWN_HOSTS, + &ssh_ctx->hash_known_hosts); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + + /* Get ssh_known_hosts_timeout option */ + ret = confdb_get_int(ssh_ctx->rctx->cdb, + CONFDB_SSH_CONF_ENTRY, CONFDB_SSH_KNOWN_HOSTS_TIMEOUT, + CONFDB_DEFAULT_SSH_KNOWN_HOSTS_TIMEOUT, + &ssh_ctx->known_hosts_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + + ret = confdb_get_string(ssh_ctx->rctx->cdb, ssh_ctx, + CONFDB_SSH_CONF_ENTRY, CONFDB_SSH_CA_DB, + CONFDB_DEFAULT_SSH_CA_DB, &ssh_ctx->ca_db); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading CA DB from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + + ret = confdb_get_bool(ssh_ctx->rctx->cdb, CONFDB_SSH_CONF_ENTRY, + CONFDB_SSH_USE_CERT_KEYS, + CONFDB_DEFAULT_SSH_USE_CERT_KEYS, + &ssh_ctx->use_cert_keys); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE,"Error reading option " + CONFDB_SSH_USE_CERT_KEYS + "from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + + ret = confdb_get_string_as_list(ssh_ctx->rctx->cdb, ssh_ctx, + CONFDB_SSH_CONF_ENTRY, + CONFDB_SSH_USE_CERT_RULES, + &ssh_ctx->cert_rules); + if (ret == ENOENT) { + ssh_ctx->cert_rules = NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading " CONFDB_SSH_USE_CERT_RULES + " from confdb (%d) [%s].\n", ret, + sss_strerror(ret)); + goto fail; + } + + ret = schedule_get_domains_task(rctx, rctx->ev, rctx, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "schedule_get_domains_tasks failed.\n"); + goto fail; + } + + /* The responder is initialized. Now tell it to the monitor. */ + ret = sss_monitor_service_init(rctx, rctx->ev, SSS_BUS_SSH, + SSS_SSH_SBUS_SERVICE_NAME, + SSS_SSH_SBUS_SERVICE_VERSION, + MT_SVC_SERVICE, + &rctx->last_request_time, &rctx->mon_conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up message bus\n"); + goto fail; + } + + ret = sss_resp_register_service_iface(rctx); + if (ret != EOK) { + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "SSH Initialization complete\n"); + + return EOK; + +fail: + talloc_free(rctx); + return ret; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + struct main_context *main_ctx; + int ret; + uid_t uid = 0; + gid_t gid = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + SSSD_RESPONDER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + umask(DFL_RSP_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = "sssd_ssh"; + DEBUG_INIT(debug_level, opt_logger); + + /* server_setup() might switch to an unprivileged user, so the permissions + * for p11_child.log have to be fixed first. We might call p11_child to + * validate certificates. */ + ret = chown_debug_file("p11_child", uid, gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot chown the p11_child debug file, " + "debugging might not work!\n"); + } + + ret = server_setup("ssh", true, 0, uid, gid, + CONFDB_SSH_CONF_ENTRY, &main_ctx, true); + if (ret != EOK) { + return 2; + } + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, "Could not set up to exit " + "when parent process does\n"); + } + + ret = ssh_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx); + if (ret != EOK) { + return 3; + } + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} |