sshsrv_cmd.c revision 4de84af23db74e13e867985c9093f394c9fa8d51
/*
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 <string.h>
#include <netdb.h>
#include "util/util.h"
#include "util/crypto/sss_crypto.h"
#include "util/sss_ssh.h"
#include "util/cert.h"
#include "db/sysdb.h"
#include "db/sysdb_ssh.h"
#include "providers/data_provider.h"
#include "responder/common/responder.h"
#include "responder/common/responder_packet.h"
#include "responder/ssh/sshsrv_private.h"
static errno_t
ssh_cmd_parse_request(struct ssh_cmd_ctx *cmd_ctx);
static errno_t
ssh_user_pubkeys_search(struct ssh_cmd_ctx *cmd_ctx);
static errno_t
ssh_cmd_get_user_pubkeys_done(struct ssh_cmd_ctx *cmd_ctx,
errno_t ret);
int
sss_ssh_cmd_get_user_pubkeys(struct cli_ctx *cctx)
{
errno_t ret;
struct ssh_cmd_ctx *cmd_ctx;
cmd_ctx = talloc_zero(cctx, struct ssh_cmd_ctx);
if (!cmd_ctx) {
return ENOMEM;
}
cmd_ctx->cctx = cctx;
cmd_ctx->is_user = true;
ret = ssh_cmd_parse_request(cmd_ctx);
if (ret != EOK) {
goto done;
}
DEBUG(SSSDBG_TRACE_FUNC,
"Requesting SSH user public keys for [%s] from [%s]\n",
cmd_ctx->name, cmd_ctx->domname ? cmd_ctx->domname : "<ALL>");
if (strcmp(cmd_ctx->name, "root") == 0) {
ret = ENOENT;
goto done;
}
if (cmd_ctx->domname) {
cmd_ctx->domain = responder_get_domain(cctx->rctx, cmd_ctx->domname);
if (!cmd_ctx->domain) {
ret = ENOENT;
goto done;
}
} else {
cmd_ctx->domain = cctx->rctx->domains;
cmd_ctx->check_next = true;
}
ret = ssh_user_pubkeys_search(cmd_ctx);
done:
return ssh_cmd_get_user_pubkeys_done(cmd_ctx, ret);
}
static errno_t
ssh_host_pubkeys_search(struct ssh_cmd_ctx *cmd_ctx);
static errno_t
ssh_cmd_get_host_pubkeys_done(struct ssh_cmd_ctx *cmd_ctx,
errno_t ret);
static int
sss_ssh_cmd_get_host_pubkeys(struct cli_ctx *cctx)
{
errno_t ret;
struct ssh_cmd_ctx *cmd_ctx;
cmd_ctx = talloc_zero(cctx, struct ssh_cmd_ctx);
if (!cmd_ctx) {
return ENOMEM;
}
cmd_ctx->cctx = cctx;
cmd_ctx->is_user = false;
ret = ssh_cmd_parse_request(cmd_ctx);
if (ret != EOK) {
goto done;
}
DEBUG(SSSDBG_TRACE_FUNC,
"Requesting SSH host public keys for [%s][%s] from [%s]\n",
cmd_ctx->name, cmd_ctx->alias ? cmd_ctx->alias : "",
cmd_ctx->domname ? cmd_ctx->domname : "<ALL>");
if (cmd_ctx->domname) {
cmd_ctx->domain = responder_get_domain(cctx->rctx, cmd_ctx->domname);
if (!cmd_ctx->domain) {
ret = ENOENT;
goto done;
}
} else {
cmd_ctx->domain = cctx->rctx->domains;
cmd_ctx->check_next = true;
}
ret = ssh_host_pubkeys_search(cmd_ctx);
done:
return ssh_cmd_get_host_pubkeys_done(cmd_ctx, ret);
}
static void
ssh_dp_send_req_done(struct tevent_req *req)
{
struct dp_callback_ctx *cb_ctx =
tevent_req_callback_data(req, struct dp_callback_ctx);
errno_t ret;
dbus_uint16_t err_maj;
dbus_uint32_t err_min;
char *err_msg;
ret = sss_dp_get_ssh_host_recv(cb_ctx->mem_ctx, req,
&err_maj, &err_min,
&err_msg);
talloc_zfree(req);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Fatal error, killing connection!\n");
talloc_free(cb_ctx->cctx);
return;
}
cb_ctx->callback(err_maj, err_min, err_msg, cb_ctx->ptr);
}
static errno_t
ssh_user_pubkeys_search_next(struct ssh_cmd_ctx *cmd_ctx);
static void
ssh_user_pubkeys_search_dp_callback(uint16_t err_maj,
uint32_t err_min,
const char *err_msg,
void *ptr);
static errno_t
ssh_user_pubkeys_search(struct ssh_cmd_ctx *cmd_ctx)
{
struct tevent_req *req;
struct dp_callback_ctx *cb_ctx;
/* if it is a domainless search, skip domains that require fully
* qualified names instead */
while (cmd_ctx->domain && cmd_ctx->check_next && cmd_ctx->domain->fqnames) {
cmd_ctx->domain = get_next_domain(cmd_ctx->domain, false);
}
if (!cmd_ctx->domain) {
DEBUG(SSSDBG_OP_FAILURE,
"No matching domain found for [%s], fail!\n", cmd_ctx->name);
return ENOENT;
}
/* refresh the user's cache entry */
if (NEED_CHECK_PROVIDER(cmd_ctx->domain->provider)) {
req = sss_dp_get_account_send(cmd_ctx, cmd_ctx->cctx->rctx,
cmd_ctx->domain, false, SSS_DP_USER,
cmd_ctx->name, 0, NULL);
if (!req) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Out of memory sending data provider request\n");
return ENOMEM;
}
cb_ctx = talloc_zero(cmd_ctx, struct dp_callback_ctx);
if (!cb_ctx) {
talloc_zfree(req);
return ENOMEM;
}
cb_ctx->callback = ssh_user_pubkeys_search_dp_callback;
cb_ctx->ptr = cmd_ctx;
cb_ctx->cctx = cmd_ctx->cctx;
cb_ctx->mem_ctx = cmd_ctx;
tevent_req_set_callback(req, ssh_dp_send_req_done, cb_ctx);
/* tell caller we are in an async call */
return EAGAIN;
}
return ssh_user_pubkeys_search_next(cmd_ctx);
}
static errno_t
ssh_user_pubkeys_search_next(struct ssh_cmd_ctx *cmd_ctx)
{
errno_t ret;
const char *attrs[] = { SYSDB_NAME, SYSDB_SSH_PUBKEY, SYSDB_USER_CERT,
NULL };
struct ldb_result *res;
DEBUG(SSSDBG_TRACE_FUNC,
"Requesting SSH user public keys for [%s@%s]\n",
cmd_ctx->name, cmd_ctx->domain->name);
if (cmd_ctx->domain->sysdb == NULL) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Fatal: Sysdb CTX not found for this domain!\n");
return EFAULT;
}
ret = sysdb_get_user_attr_with_views(cmd_ctx, cmd_ctx->domain,
cmd_ctx->name, attrs, &res);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to make request to our cache!\n");
return EIO;
}
if (res->count > 1) {
DEBUG(SSSDBG_FATAL_FAILURE,
"User search by name (%s) returned > 1 results!\n",
cmd_ctx->name);
return EINVAL;
}
if (!res->count) {
/* if a multidomain search, try with next */
if (cmd_ctx->check_next) {
cmd_ctx->domain = get_next_domain(cmd_ctx->domain, false);
return ssh_user_pubkeys_search(cmd_ctx);
}
DEBUG(SSSDBG_OP_FAILURE,
"No attributes for user [%s] found.\n", cmd_ctx->name);
return ENOENT;
}
cmd_ctx->result = res->msgs[0];
/* one result found */
return EOK;
}
static void
ssh_user_pubkeys_search_dp_callback(uint16_t err_maj,
uint32_t err_min,
const char *err_msg,
void *ptr)
{
struct ssh_cmd_ctx *cmd_ctx = talloc_get_type(ptr, struct ssh_cmd_ctx);
errno_t ret;
if (err_maj) {
DEBUG(SSSDBG_OP_FAILURE,
"Unable to get information from Data Provider\n"
"Error: %u, %u, %s\n",
(unsigned int)err_maj, (unsigned int)err_min, err_msg);
}
ret = ssh_user_pubkeys_search_next(cmd_ctx);
ssh_cmd_get_user_pubkeys_done(cmd_ctx, ret);
}
static errno_t
ssh_host_pubkeys_search_next(struct ssh_cmd_ctx *cmd_ctx);
static void
ssh_host_pubkeys_search_dp_callback(uint16_t err_maj,
uint32_t err_min,
const char *err_msg,
void *ptr);
static errno_t
ssh_host_pubkeys_search(struct ssh_cmd_ctx *cmd_ctx)
{
struct tevent_req *req;
struct dp_callback_ctx *cb_ctx;
if (!cmd_ctx->domain) {
DEBUG(SSSDBG_OP_FAILURE,
"No matching domain found for [%s], fail!\n", cmd_ctx->name);
return ENOENT;
}
/* refresh the host's cache entry */
if (NEED_CHECK_PROVIDER(cmd_ctx->domain->provider)) {
req = sss_dp_get_ssh_host_send(cmd_ctx, cmd_ctx->cctx->rctx,
cmd_ctx->domain, false,
cmd_ctx->name, cmd_ctx->alias);
if (!req) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Out of memory sending data provider request\n");
return ENOMEM;
}
cb_ctx = talloc_zero(cmd_ctx, struct dp_callback_ctx);
if (!cb_ctx) {
talloc_zfree(req);
return ENOMEM;
}
cb_ctx->callback = ssh_host_pubkeys_search_dp_callback;
cb_ctx->ptr = cmd_ctx;
cb_ctx->cctx = cmd_ctx->cctx;
cb_ctx->mem_ctx = cmd_ctx;
tevent_req_set_callback(req, ssh_dp_send_req_done, cb_ctx);
/* tell caller we are in an async call */
return EAGAIN;
}
return ssh_host_pubkeys_search_next(cmd_ctx);
}
static errno_t
ssh_host_pubkeys_search_next(struct ssh_cmd_ctx *cmd_ctx)
{
errno_t ret;
struct sysdb_ctx *sysdb;
const char *attrs[] = { SYSDB_NAME, SYSDB_SSH_PUBKEY, NULL };
DEBUG(SSSDBG_TRACE_FUNC,
"Requesting SSH host public keys for [%s@%s]\n",
cmd_ctx->name, cmd_ctx->domain->name);
sysdb = cmd_ctx->domain->sysdb;
if (sysdb == NULL) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Fatal: Sysdb CTX not found for this domain!\n");
return EFAULT;
}
ret = sysdb_get_ssh_host(cmd_ctx, cmd_ctx->domain,
cmd_ctx->name, attrs, &cmd_ctx->result);
if (ret != EOK && ret != ENOENT) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Failed to make request to our cache!\n");
return EIO;
}
if (ret == ENOENT) {
/* if a multidomain search, try with next */
if (cmd_ctx->check_next) {
cmd_ctx->domain = get_next_domain(cmd_ctx->domain, false);
return ssh_host_pubkeys_search(cmd_ctx);
}
DEBUG(SSSDBG_OP_FAILURE,
"No attributes for host [%s] found.\n", cmd_ctx->name);
return ENOENT;
}
return EOK;
}
static void
ssh_host_pubkeys_search_dp_callback(uint16_t err_maj,
uint32_t err_min,
const char *err_msg,
void *ptr)
{
struct ssh_cmd_ctx *cmd_ctx = talloc_get_type(ptr, struct ssh_cmd_ctx);
errno_t ret;
if (err_maj) {
DEBUG(SSSDBG_OP_FAILURE,
"Unable to get information from Data Provider\n"
"Error: %u, %u, %s\n",
(unsigned int)err_maj, (unsigned int)err_min, err_msg);
}
ret = ssh_host_pubkeys_search_next(cmd_ctx);
ssh_cmd_get_host_pubkeys_done(cmd_ctx, ret);
}
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, k;
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]);
for (k = 0; k < SSS_SHA1_LENGTH; k++) {
salt[k] = rand();
}
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_host_pubkeys_update_known_hosts(struct ssh_cmd_ctx *cmd_ctx)
{
TALLOC_CTX *tmp_ctx;
errno_t ret, tmp_ret;
const char *attrs[] = {
SYSDB_NAME,
SYSDB_NAME_ALIAS,
SYSDB_SSH_PUBKEY,
NULL
};
struct cli_ctx *cctx = cmd_ctx->cctx;
struct sss_domain_info *dom = cctx->rctx->domains;
struct ssh_ctx *ssh_ctx = (struct ssh_ctx *)cctx->rctx->pvt_ctx;
struct sysdb_ctx *sysdb;
time_t now = time(NULL);
struct ldb_message **hosts;
size_t num_hosts, i;
struct sss_ssh_ent *ent;
int fd = -1;
char *filename = NULL;
char *entstr;
ssize_t wret;
mode_t old_mask;
tmp_ctx = talloc_new(NULL);
if (!tmp_ctx) {
return ENOMEM;
}
if (cmd_ctx->domain) {
ret = sysdb_update_ssh_known_host_expire(cmd_ctx->domain,
cmd_ctx->name, now,
ssh_ctx->known_hosts_timeout);
if (ret != EOK && ret != ENOENT) {
goto done;
}
}
/* write known_hosts file */
filename = talloc_strdup(tmp_ctx, SSS_SSH_KNOWN_HOSTS_TEMP_TMPL);
if (!filename) {
ret = ENOMEM;
goto done;
}
old_mask = umask(0133);
fd = mkstemp(filename);
umask(old_mask);
if (fd == -1) {
filename = NULL;
ret = errno;
goto done;
}
for (; dom; 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 != EOK) {
if (ret != ENOENT) {
DEBUG(SSSDBG_OP_FAILURE,
"Host search failed for domain [%s]\n", dom->name);
}
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 (ssh_ctx->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) {
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 = 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:
if (fd != -1) close(fd);
if (ret != EOK && filename) {
tmp_ret = unlink(filename);
/* non-fatal failure */
if (tmp_ret != EOK) {
tmp_ret = errno;
DEBUG(SSSDBG_MINOR_FAILURE,
"Failed to remove file: %s - %d : [%s]!\n",
filename, tmp_ret, sss_strerror(tmp_ret));
}
}
talloc_free(tmp_ctx);
return ret;
}
static errno_t
ssh_cmd_parse_request(struct ssh_cmd_ctx *cmd_ctx)
{
struct cli_ctx *cctx = cmd_ctx->cctx;
struct ssh_ctx *ssh_ctx = talloc_get_type(cctx->rctx->pvt_ctx,
struct ssh_ctx);
errno_t ret;
uint8_t *body;
size_t body_len;
size_t c = 0;
uint32_t flags;
uint32_t name_len;
char *name;
uint32_t alias_len;
char *alias = NULL;
uint32_t domain_len;
char *domain = NULL;
sss_packet_get_body(cctx->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 = (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 = (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 = (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;
}
DEBUG(SSSDBG_TRACE_FUNC,
"Requested domain [%s]\n", domain ? domain : "<ALL>");
} else {
DEBUG(SSSDBG_TRACE_FUNC, "Splitting domain from name [%s]\n", name);
ret = sss_parse_name(cmd_ctx, ssh_ctx->snctx, name,
&cmd_ctx->domname, &cmd_ctx->name);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Invalid name received [%s]\n", name);
return ENOENT;
}
name = cmd_ctx->name;
}
if (cmd_ctx->is_user && cmd_ctx->domname == NULL) {
DEBUG(SSSDBG_TRACE_FUNC,
"Parsing name [%s][%s]\n", name, domain ? domain : "<ALL>");
ret = sss_parse_name_for_domains(cmd_ctx, cctx->rctx->domains,
domain, name,
&cmd_ctx->domname,
&cmd_ctx->name);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
"Invalid name received [%s]\n", name);
return ENOENT;
}
} else {
if (cmd_ctx->name == NULL) {
cmd_ctx->name = talloc_strdup(cmd_ctx, name);
if (!cmd_ctx->name) return ENOMEM;
}
if (cmd_ctx->domname == NULL && domain != NULL) {
cmd_ctx->domname = talloc_strdup(cmd_ctx, domain);
if (!cmd_ctx->domname) return ENOMEM;
}
}
if (alias != NULL && strcmp(cmd_ctx->name, alias) != 0) {
cmd_ctx->alias = talloc_strdup(cmd_ctx, alias);
if (!cmd_ctx->alias) return ENOMEM;
}
return EOK;
}
static errno_t decode_and_add_base64_data(struct ssh_cmd_ctx *cmd_ctx,
struct ldb_message_element *el,
bool cert_data,
struct ssh_ctx *ssh_ctx,
size_t fqname_len,
const char *fqname,
size_t *c)
{
struct cli_ctx *cctx = cmd_ctx->cctx;
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 (cert_data) {
ret = cert_to_ssh_key(tmp_ctx, ssh_ctx->ca_db,
el->values[d].data, el->values[d].length,
&key, &key_len);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "cert_to_ssh_key failed.\n");
return ret;
}
} 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(cctx->creq->out,
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(cctx->creq->out, &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;
}
static errno_t
ssh_cmd_build_reply(struct ssh_cmd_ctx *cmd_ctx)
{
struct cli_ctx *cctx = cmd_ctx->cctx;
errno_t ret;
uint8_t *body;
size_t body_len;
size_t c = 0;
struct ldb_message_element *el = NULL;
struct ldb_message_element *el_override = NULL;
struct ldb_message_element *el_orig = NULL;
struct ldb_message_element *el_user_cert = NULL;
uint32_t count = 0;
const char *name;
char *fqname;
uint32_t fqname_len;
struct ssh_ctx *ssh_ctx = talloc_get_type(cctx->rctx->pvt_ctx,
struct ssh_ctx);
ret = sss_packet_new(cctx->creq, 0,
sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
if (ret != EOK) {
return ret;
}
el = ldb_msg_find_element(cmd_ctx->result, SYSDB_SSH_PUBKEY);
if (el) {
count = el->num_values;
}
el_orig = ldb_msg_find_element(cmd_ctx->result,
ORIGINALAD_PREFIX SYSDB_SSH_PUBKEY);
if (el_orig) {
count = el_orig->num_values;
}
if (DOM_HAS_VIEWS(cmd_ctx->domain)) {
el_override = ldb_msg_find_element(cmd_ctx->result,
OVERRIDE_PREFIX SYSDB_SSH_PUBKEY);
if (el_override) {
count += el_override->num_values;
}
}
el_user_cert = ldb_msg_find_element(cmd_ctx->result, SYSDB_USER_CERT);
if (el_user_cert) {
/* TODO check if cert is valid */
count += el_user_cert->num_values;
}
ret = sss_packet_grow(cctx->creq->out, 2*sizeof(uint32_t));
if (ret != EOK) {
return ret;
}
sss_packet_get_body(cctx->creq->out, &body, &body_len);
SAFEALIGN_SET_UINT32(body+c, count, &c);
SAFEALIGN_SET_UINT32(body+c, 0, &c);
if (count == 0) {
return EOK;
}
name = ldb_msg_find_attr_as_string(cmd_ctx->result, SYSDB_NAME, NULL);
if (!name) {
DEBUG(SSSDBG_OP_FAILURE,
"Got unnamed result for [%s@%s]\n",
cmd_ctx->name, cmd_ctx->domain->name);
return ENOENT;
}
fqname = talloc_asprintf(cmd_ctx, "%s@%s",
name, cmd_ctx->domain->name);
if (!fqname) {
return ENOMEM;
}
fqname_len = strlen(fqname)+1;
ret = decode_and_add_base64_data(cmd_ctx, el, false, ssh_ctx,
fqname_len, fqname, &c);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "decode_and_add_base64_data failed.\n");
return ret;
}
ret = decode_and_add_base64_data(cmd_ctx, el_orig, false, ssh_ctx,
fqname_len, fqname, &c);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "decode_and_add_base64_data failed.\n");
return ret;
}
ret = decode_and_add_base64_data(cmd_ctx, el_override, false, ssh_ctx,
fqname_len, fqname, &c);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "decode_and_add_base64_data failed.\n");
return ret;
}
ret = decode_and_add_base64_data(cmd_ctx, el_user_cert, true, ssh_ctx,
fqname_len, fqname, &c);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "decode_and_add_base64_data failed.\n");
return ret;
}
return EOK;
}
static errno_t
ssh_cmd_send_error(struct ssh_cmd_ctx *cmd_ctx,
errno_t error)
{
struct cli_ctx *cctx = cmd_ctx->cctx;
errno_t ret;
ret = sss_cmd_send_error(cctx, error);
if (ret != EOK) {
return ret;
}
sss_cmd_done(cctx, cmd_ctx);
return EOK;
}
static errno_t
ssh_cmd_send_reply(struct ssh_cmd_ctx *cmd_ctx)
{
struct cli_ctx *cctx = cmd_ctx->cctx;
errno_t ret;
/* create response packet */
ret = ssh_cmd_build_reply(cmd_ctx);
if (ret != EOK) {
return ret;
}
sss_packet_set_error(cctx->creq->out, EOK);
sss_cmd_done(cctx, cmd_ctx);
return EOK;
}
static errno_t
ssh_cmd_done(struct ssh_cmd_ctx *cmd_ctx,
errno_t ret)
{
switch (ret) {
case EOK:
ret = ssh_cmd_send_reply(cmd_ctx);
break;
case EAGAIN:
return EOK;
case EFAULT:
break;
default:
ret = ssh_cmd_send_error(cmd_ctx, ret);
break;
}
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "Fatal error, killing connection!\n");
talloc_free(cmd_ctx->cctx);
return EFAULT;
}
return EOK;
}
static errno_t
ssh_cmd_get_user_pubkeys_done(struct ssh_cmd_ctx *cmd_ctx,
errno_t ret)
{
return ssh_cmd_done(cmd_ctx, ret);
}
static errno_t
ssh_cmd_get_host_pubkeys_done(struct ssh_cmd_ctx *cmd_ctx,
errno_t ret)
{
if (ret == EOK || ret == ENOENT) {
ssh_host_pubkeys_update_known_hosts(cmd_ctx);
}
return ssh_cmd_done(cmd_ctx, ret);
}
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, sss_ssh_cmd_get_user_pubkeys},
{SSS_SSH_GET_HOST_PUBKEYS, sss_ssh_cmd_get_host_pubkeys},
{SSS_CLI_NULL, NULL}
};
return ssh_cmds;
}