>From 5c9e073df33f3e9eadb3233b050806f8932634db Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jcholast@redhat.com>
Date: Fri, 3 Feb 2012 22:48:28 +0100
Subject: [PATCH 7/9] SSH: Responder

---
 Makefile.am                        |   15 +
 src/confdb/confdb.h                |    3 +
 src/monitor/monitor.c              |    2 +-
 src/providers/data_provider_be.c   |    5 +
 src/providers/dp_backend.h         |    1 +
 src/responder/ssh/sshsrv.c         |  193 +++++++++++
 src/responder/ssh/sshsrv_cmd.c     |  669 ++++++++++++++++++++++++++++++++++++
 src/responder/ssh/sshsrv_private.h |   49 +++
 8 files changed, 936 insertions(+), 1 deletions(-)
 create mode 100644 src/responder/ssh/sshsrv.c
 create mode 100644 src/responder/ssh/sshsrv_cmd.c
 create mode 100644 src/responder/ssh/sshsrv_private.h

diff --git a/Makefile.am b/Makefile.am
index f2569df..796a93b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -81,6 +81,9 @@ sssdlibexec_PROGRAMS = \
 if BUILD_SUDO
 sssdlibexec_PROGRAMS += sssd_sudo
 endif
+if BUILD_SSH
+sssdlibexec_PROGRAMS += sssd_ssh
+endif
 
 if HAVE_CHECK
     non_interactive_check_based_tests = \
@@ -217,6 +220,7 @@ AM_CPPFLAGS = \
     -DSSS_PAM_SOCKET_NAME=\"$(pipepath)/pam\" \
     -DSSS_PAM_PRIV_SOCKET_NAME=\"$(pipepath)/private/pam\" \
     -DSSS_SUDO_SOCKET_NAME=\"$(pipepath)/sudo\" \
+    -DSSS_SSH_SOCKET_NAME=\"$(pipepath)/ssh\" \
     -DLOCALEDIR=\"$(localedir)\"
 
 EXTRA_DIST = build/config.rpath
@@ -321,6 +325,7 @@ dist_noinst_HEADERS = \
     src/responder/nss/nsssrv_services.h \
     src/responder/common/negcache.h \
     src/responder/sudo/sudosrv_private.h \
+    src/responder/ssh/sshsrv_private.h \
     src/sbus/sbus_client.h \
     src/sbus/sssd_dbus.h \
     src/sbus/sssd_dbus_private.h \
@@ -479,6 +484,16 @@ sssd_sudo_LDADD = \
     libsss_util.la
 endif
 
+if BUILD_SSH
+sssd_ssh_SOURCES = \
+    src/responder/ssh/sshsrv.c \
+    src/responder/ssh/sshsrv_cmd.c \
+    $(SSSD_RESPONDER_OBJ)
+sssd_ssh_LDADD = \
+    $(SSSD_LIBS) \
+    libsss_util.la
+endif
+
 sssd_be_SOURCES = \
     src/providers/data_provider_be.c \
     src/providers/data_provider_fo.c \
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index 56b06b9..808f034 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -94,6 +94,9 @@
 /* SUDO */
 #define CONFDB_SUDO_CONF_ENTRY "config/sudo"
 
+/* SSH */
+#define CONFDB_SSH_CONF_ENTRY "config/ssh"
+
 /* Data Provider */
 #define CONFDB_DP_CONF_ENTRY "config/dp"
 
diff --git a/src/monitor/monitor.c b/src/monitor/monitor.c
index d2717fa..4690f9f 100644
--- a/src/monitor/monitor.c
+++ b/src/monitor/monitor.c
@@ -754,7 +754,7 @@ static int check_local_domain_unique(struct sss_domain_info *domains)
 
 static char *check_services(char **services)
 {
-    const char *known_services[] = { "nss", "pam", "sudo", NULL };
+    const char *known_services[] = { "nss", "pam", "sudo", "ssh", NULL };
     int i;
     int ii;
 
diff --git a/src/providers/data_provider_be.c b/src/providers/data_provider_be.c
index 35d705a..405b993 100644
--- a/src/providers/data_provider_be.c
+++ b/src/providers/data_provider_be.c
@@ -853,6 +853,9 @@ static int be_client_destructor(void *ctx)
         } else if (becli->bectx->sudo_cli == becli) {
             DEBUG(4, ("Removed SUDO client\n"));
             becli->bectx->sudo_cli = NULL;
+        } else if (becli->bectx->ssh_cli == becli) {
+            DEBUG(4, ("Removed SSH client\n"));
+            becli->bectx->ssh_cli = NULL;
         } else {
             DEBUG(2, ("Unknown client removed ...\n"));
         }
@@ -903,6 +906,8 @@ static int client_registration(DBusMessage *message,
         becli->bectx->pam_cli = becli;
     } else if (strcasecmp(cli_name, "SUDO") == 0) {
         becli->bectx->sudo_cli = becli;
+    } else if (strcasecmp(cli_name, "SSH") == 0) {
+        becli->bectx->ssh_cli = becli;
     } else {
         DEBUG(1, ("Unknown client! [%s]\n", cli_name));
     }
diff --git a/src/providers/dp_backend.h b/src/providers/dp_backend.h
index 1a36778..023d3d9 100644
--- a/src/providers/dp_backend.h
+++ b/src/providers/dp_backend.h
@@ -113,6 +113,7 @@ struct be_ctx {
     struct be_client *nss_cli;
     struct be_client *pam_cli;
     struct be_client *sudo_cli;
+    struct be_client *ssh_cli;
 
     struct loaded_be loaded_be[BET_MAX];
     struct bet_info bet_info[BET_MAX];
diff --git a/src/responder/ssh/sshsrv.c b/src/responder/ssh/sshsrv.c
new file mode 100644
index 0000000..e5bb5b0
--- /dev/null
+++ b/src/responder/ssh/sshsrv.c
@@ -0,0 +1,193 @@
+/*
+    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 "confdb/confdb.h"
+#include "monitor/monitor_interfaces.h"
+#include "responder/common/responder.h"
+#include "responder/ssh/sshsrv_private.h"
+#include "providers/data_provider.h"
+
+struct sbus_method monitor_ssh_methods[] = {
+    { MON_CLI_METHOD_PING, monitor_common_pong },
+    { MON_CLI_METHOD_RES_INIT, monitor_common_res_init },
+    { MON_CLI_METHOD_ROTATE, responder_logrotate },
+    { NULL, NULL }
+};
+
+struct sbus_interface monitor_ssh_interface = {
+    MONITOR_INTERFACE,
+    MONITOR_PATH,
+    SBUS_DEFAULT_VTABLE,
+    monitor_ssh_methods,
+    NULL
+};
+
+static struct sbus_method ssh_dp_methods[] = {
+    { NULL, NULL }
+};
+
+struct sbus_interface ssh_dp_interface = {
+    DP_INTERFACE,
+    DP_PATH,
+    SBUS_DEFAULT_VTABLE,
+    ssh_dp_methods,
+    NULL
+};
+
+static void ssh_dp_reconnect_init(struct sbus_connection *conn,
+                                  int status, void *pvt)
+{
+    struct be_conn *be_conn = talloc_get_type(pvt, struct be_conn);
+    int ret;
+
+    /* Did we reconnect successfully? */
+    if (status == SBUS_RECONNECT_SUCCESS) {
+        DEBUG(SSSDBG_TRACE_FUNC, ("Reconnected to the Data Provider.\n"));
+
+        /* Identify ourselves to the data provider */
+        ret = dp_common_send_id(be_conn->conn,
+                                DATA_PROVIDER_VERSION,
+                                "SSH");
+        /* all fine */
+        if (ret == EOK) {
+            handle_requests_after_reconnect();
+            return;
+        }
+    }
+
+    /* Failed to reconnect */
+    DEBUG(SSSDBG_FATAL_FAILURE, ("Could not reconnect to %s provider.\n",
+                                 be_conn->domain->name));
+}
+
+int ssh_process_init(TALLOC_CTX *mem_ctx,
+                     struct tevent_context *ev,
+                     struct confdb_ctx *cdb)
+{
+    struct sss_cmd_table *ssh_cmds;
+    struct ssh_ctx *ssh_ctx;
+    struct be_conn *iter;
+    int ret;
+    int max_retries;
+
+    ssh_ctx = talloc_zero(mem_ctx, struct ssh_ctx);
+    if (!ssh_ctx) {
+        DEBUG(SSSDBG_FATAL_FAILURE, ("fatal error initializing ssh_ctx\n"));
+        return ENOMEM;
+    }
+
+    ssh_cmds = get_ssh_cmds();
+    ret = sss_process_init(ssh_ctx, ev, cdb,
+                           ssh_cmds,
+                           SSS_SSH_SOCKET_NAME, NULL,
+                           CONFDB_SSH_CONF_ENTRY,
+                           SSS_SSH_SBUS_SERVICE_NAME,
+                           SSS_SSH_SBUS_SERVICE_VERSION,
+                           &monitor_ssh_interface,
+                           "SSH",
+                           &ssh_dp_interface,
+                           &ssh_ctx->rctx);
+    if (ret != EOK) {
+        return ret;
+    }
+    ssh_ctx->rctx->pvt_ctx = ssh_ctx;
+
+    /* Enable automatic reconnection to the Data Provider */
+    ret = confdb_get_int(ssh_ctx->rctx->cdb, ssh_ctx->rctx,
+                         CONFDB_SSH_CONF_ENTRY,
+                         CONFDB_SERVICE_RECON_RETRIES,
+                         3, &max_retries);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              ("Failed to set up automatic reconnection\n"));
+        return ret;
+    }
+
+    for (iter = ssh_ctx->rctx->be_conns; iter; iter = iter->next) {
+        sbus_reconnect_init(iter->conn, max_retries,
+                            ssh_dp_reconnect_init, iter);
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, ("SSH Initialization complete\n"));
+
+    return EOK;
+}
+
+int main(int argc, const char *argv[])
+{
+    int opt;
+    poptContext pc;
+    struct main_context *main_ctx;
+    int ret;
+
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        SSSD_MAIN_OPTS
+        POPT_TABLEEND
+    };
+
+    /* Set debug level to invalid value so we can deside if -d 0 was used. */
+    debug_level = SSSDBG_INVALID;
+
+    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);
+
+    CONVERT_AND_SET_DEBUG_LEVEL(debug_level);
+
+    /* set up things like debug, signals, daemonization, etc... */
+    debug_log_file = "sssd_ssh";
+
+    ret = server_setup("sssd[ssh]", 0, CONFDB_SSH_CONF_ENTRY, &main_ctx);
+    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;
+}
diff --git a/src/responder/ssh/sshsrv_cmd.c b/src/responder/ssh/sshsrv_cmd.c
new file mode 100644
index 0000000..386b1ae
--- /dev/null
+++ b/src/responder/ssh/sshsrv_cmd.c
@@ -0,0 +1,669 @@
+/*
+    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 <talloc.h>
+
+#include "util/util.h"
+#include "util/crypto/sss_crypto.h"
+#include "db/sysdb.h"
+#include "providers/ipa/ipa_hbac_private.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_cmd_done(struct ssh_cmd_ctx *cmd_ctx,
+             errno_t ret);
+
+static errno_t
+ssh_user_pubkeys_search(struct ssh_cmd_ctx *cmd_ctx);
+
+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->type = SSS_DP_USER;
+
+    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 (cmd_ctx->domname) {
+        cmd_ctx->domain = responder_get_domain(cctx->rctx->domains,
+                                               cmd_ctx->domname);
+        if (!cmd_ctx->domain) {
+            ret = ENOENT;
+            goto done;
+        }
+    } else {
+        cmd_ctx->domain = cctx->rctx->domains;
+        cmd_ctx->check_next = true;
+    }
+    cmd_ctx->check_provider = NEED_CHECK_PROVIDER(cmd_ctx->domain->provider);
+
+    ret = ssh_user_pubkeys_search(cmd_ctx);
+
+done:
+    return ssh_cmd_done(cmd_ctx, ret);
+}
+
+static errno_t
+ssh_host_pubkeys_search(struct ssh_cmd_ctx *cmd_ctx);
+
+static int
+sss_ssh_cmd_get_host_pubkeys(struct cli_ctx *cctx)
+{
+    struct ssh_cmd_ctx *cmd_ctx;
+    errno_t ret;
+
+    cmd_ctx = talloc_zero(cctx, struct ssh_cmd_ctx);
+    if (!cmd_ctx) {
+        return ENOMEM;
+    }
+    cmd_ctx->cctx = cctx;
+    cmd_ctx->type = SSS_DP_HOST;
+
+    ret = ssh_cmd_parse_request(cmd_ctx);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC,
+          ("Requesting SSH host public keys for [%s] from [%s]\n",
+           cmd_ctx->name, cmd_ctx->domname ? cmd_ctx->domname : "<ALL>"));
+
+    if (cmd_ctx->domname) {
+        cmd_ctx->domain = responder_get_domain(cctx->rctx->domains,
+                                               cmd_ctx->domname);
+        if (!cmd_ctx->domain) {
+            ret = ENOENT;
+            goto done;
+        }
+    } else {
+        cmd_ctx->domain = cctx->rctx->domains;
+        cmd_ctx->check_next = true;
+    }
+    cmd_ctx->check_provider = NEED_CHECK_PROVIDER(cmd_ctx->domain->provider);
+
+    ret = ssh_host_pubkeys_search(cmd_ctx);
+
+done:
+    return ssh_cmd_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_account_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 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)
+{
+    errno_t ret;
+    struct sss_domain_info *dom = cmd_ctx->domain;
+    struct cli_ctx *cctx = cmd_ctx->cctx;
+    struct sysdb_ctx *sysdb;
+    struct tevent_req *req;
+    struct dp_callback_ctx *cb_ctx;
+    const char *attrs[] = { SYSDB_NAME, SYSDB_SSH_PUBKEY, NULL };
+    struct ldb_result *res;
+
+    while (dom) {
+        /* if it is a domainless search, skip domains that require fully
+         * qualified names instead */
+        while (dom && cmd_ctx->check_next && dom->fqnames) {
+            dom = dom->next;
+        }
+
+        if (!dom) break;
+
+        if (dom != cmd_ctx->domain) {
+            /* make sure we reset the check_provider flag when we check
+             * a new domain */
+            cmd_ctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
+        }
+
+        /* make sure to update the dom_ctx if we changed domain */
+        cmd_ctx->domain = dom;
+
+        /* refresh the user's cache entry */
+        if (cmd_ctx->check_provider) {
+            cmd_ctx->check_provider = false;
+
+            req = sss_dp_get_account_send(cmd_ctx, cmd_ctx->cctx->rctx,
+                                          dom, 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;
+        }
+
+        DEBUG(SSSDBG_TRACE_FUNC,
+              ("Requesting SSH user public keys for [%s@%s]\n",
+               cmd_ctx->name, dom->name));
+
+        ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, dom, &sysdb);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_FATAL_FAILURE,
+                  ("Fatal: Sysdb CTX not found for this domain!\n"));
+            return EFAULT;
+        }
+
+        ret = sysdb_get_user_attr(cmd_ctx, sysdb,
+                                  cmd_ctx->name, attrs, &res);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  ("Failed to make request to our cache!\n"));
+            return EIO;
+        }
+
+        cmd_ctx->results = res->msgs;
+        cmd_ctx->results_len = res->count;
+
+        if (cmd_ctx->results_len > 1) {
+            DEBUG(SSSDBG_FATAL_FAILURE,
+                ("User search by name (%s) returned > 1 results!\n",
+                 cmd_ctx->name));
+            return ENOENT;
+        }
+
+        if (cmd_ctx->results_len == 0) {
+            /* if a multidomain search, try with next */
+            if (cmd_ctx->check_next) {
+                dom = dom->next;
+                continue;
+            }
+
+            DEBUG(SSSDBG_OP_FAILURE,
+                  ("No attributes for user [%s] found.\n", cmd_ctx->name));
+
+            return ENOENT;
+        }
+
+        /* one result found */
+        return EOK;
+    }
+
+    DEBUG(SSSDBG_OP_FAILURE,
+          ("No matching domain found for [%s], fail!\n", cmd_ctx->name));
+    return ENOENT;
+}
+
+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));
+
+        if (!cmd_ctx->check_next) {
+            ret = ENOENT;
+            goto done;
+        }
+
+        cmd_ctx->domain = cmd_ctx->domain->next;
+        if (cmd_ctx->domain) {
+            cmd_ctx->check_provider = NEED_CHECK_PROVIDER(
+                    cmd_ctx->domain->provider);
+        }
+    }
+
+    ret = ssh_user_pubkeys_search(cmd_ctx);
+
+done:
+    ssh_cmd_done(cmd_ctx, ret);
+}
+
+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)
+{
+    errno_t ret;
+    struct sss_domain_info *dom = cmd_ctx->domain;
+    struct cli_ctx *cctx = cmd_ctx->cctx;
+    struct sysdb_ctx *sysdb;
+    struct tevent_req *req;
+    struct dp_callback_ctx *cb_ctx;
+    const char *attrs[] = { SYSDB_NAME, SYSDB_SSH_PUBKEY, NULL };
+
+    while (dom) {
+        /* if it is a domainless search, skip domains that require fully
+         * qualified names instead */
+        while (dom && cmd_ctx->check_next && dom->fqnames) {
+            dom = dom->next;
+        }
+
+        if (!dom) break;
+
+        if (dom != cmd_ctx->domain) {
+            /* make sure we reset the check_provider flag when we check
+             * a new domain */
+            cmd_ctx->check_provider = NEED_CHECK_PROVIDER(dom->provider);
+        }
+
+        /* make sure to update the dom_ctx if we changed domain */
+        cmd_ctx->domain = dom;
+
+        /* refresh the host's cache entry */
+        if (cmd_ctx->check_provider) {
+            cmd_ctx->check_provider = false;
+
+            req = sss_dp_get_account_send(cmd_ctx, cmd_ctx->cctx->rctx,
+                                          dom, false, SSS_DP_HOST,
+                                          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_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;
+        }
+
+        DEBUG(SSSDBG_TRACE_FUNC,
+              ("Requesting SSH host public keys for [%s@%s]\n",
+               cmd_ctx->name, dom->name));
+
+        ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, dom, &sysdb);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_FATAL_FAILURE,
+                  ("Fatal: Sysdb CTX not found for this domain!\n"));
+            return EFAULT;
+        }
+
+        ret = sysdb_search_custom_by_name(cmd_ctx, sysdb, cmd_ctx->name,
+                                          HBAC_HOSTS_SUBDIR, attrs,
+                                          &cmd_ctx->results_len,
+                                          &cmd_ctx->results);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  ("Failed to make request to our cache!\n"));
+            return EIO;
+        }
+
+        if (cmd_ctx->results_len > 1) {
+            DEBUG(SSSDBG_FATAL_FAILURE,
+                ("Host search by name (%s) returned > 1 results!\n",
+                 cmd_ctx->name));
+            return ENOENT;
+        }
+
+        if (cmd_ctx->results_len == 0) {
+            /* if a multidomain search, try with next */
+            if (cmd_ctx->check_next) {
+                dom = dom->next;
+                continue;
+            }
+
+            DEBUG(SSSDBG_OP_FAILURE,
+                  ("No attributes for host [%s] found.\n", cmd_ctx->name));
+
+            return ENOENT;
+        }
+
+        /* one result found */
+        return EOK;
+    }
+
+    DEBUG(SSSDBG_OP_FAILURE,
+          ("No matching domain found for [%s], fail!\n", cmd_ctx->name));
+    return ENOENT;
+}
+
+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));
+
+        if (!cmd_ctx->check_next) {
+            ret = ENOENT;
+            goto done;
+        }
+
+        cmd_ctx->domain = cmd_ctx->domain->next;
+        if (cmd_ctx->domain) {
+            cmd_ctx->check_provider = NEED_CHECK_PROVIDER(
+                    cmd_ctx->domain->provider);
+        }
+    }
+
+    ret = ssh_host_pubkeys_search(cmd_ctx);
+
+done:
+    ssh_cmd_done(cmd_ctx, ret);
+}
+
+static errno_t
+ssh_cmd_parse_request(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;
+    uint32_t reserved;
+    uint32_t name_len;
+    char *name;
+
+    sss_packet_get_body(cctx->creq->in, &body, &body_len);
+
+    SAFEALIGN_COPY_UINT32_CHECK(&reserved, body+c, body_len, &c);
+    if (reserved != 0) {
+        return EINVAL;
+    }
+
+    SAFEALIGN_COPY_UINT32_CHECK(&name_len, body+c, body_len, &c);
+    if (name_len == 0) {
+        return EINVAL;
+    }
+
+    name = (char *)(body+c);
+    if (strnlen(name, name_len) != name_len-1) {
+        return EINVAL;
+    }
+    c += name_len;
+
+    ret = sss_parse_name(cmd_ctx, cctx->rctx->names, name,
+                         &cmd_ctx->domname, &cmd_ctx->name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, ("Invalid name received [%s]\n", name));
+        return ENOENT;
+    }
+
+    return EOK;
+}
+
+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;
+    size_t i;
+    unsigned int j;
+    struct ldb_message_element *el;
+    uint32_t count = 0;
+    const char *name;
+    char *fqname;
+    uint32_t fqname_len;
+    uint8_t *key;
+    size_t key_len;
+
+    /* count number of results */
+    for (i = 0; i < cmd_ctx->results_len; i++) {
+        el = ldb_msg_find_element(cmd_ctx->results[i], SYSDB_SSH_PUBKEY);
+        if (!el) {
+            continue;
+        }
+
+        count += el->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);
+
+    for (i = 0; i < cmd_ctx->results_len; i++) {
+        name = ldb_msg_find_attr_as_string(cmd_ctx->results[i],
+                                           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;
+
+        el = ldb_msg_find_element(cmd_ctx->results[i], SYSDB_SSH_PUBKEY);
+        if (!el) {
+            /* this object has no SSH public keys */
+            continue;
+        }
+
+        for (j = 0; j < el->num_values; j++) {
+            key = sss_base64_decode(cmd_ctx,
+                                    (const char *)el->values[j].data,
+                                    &key_len);
+            if (!key) {
+                return ENOMEM;
+            }
+
+            ret = sss_packet_grow(cctx->creq->out,
+                                  3*sizeof(uint32_t) + key_len + fqname_len);
+            if (ret != EOK) {
+                talloc_free(key);
+                return ret;
+            }
+            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);
+
+            talloc_free(key);
+            count++;
+        }
+    }
+
+    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 = sss_packet_new(cctx->creq, 0,
+                         sss_packet_get_cmd(cctx->creq->in),
+                         &cctx->creq->out);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    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;
+}
+
+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;
+}
diff --git a/src/responder/ssh/sshsrv_private.h b/src/responder/ssh/sshsrv_private.h
new file mode 100644
index 0000000..9246d78
--- /dev/null
+++ b/src/responder/ssh/sshsrv_private.h
@@ -0,0 +1,49 @@
+/*
+    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"
+
+#define SSS_SSH_SBUS_SERVICE_VERSION 0x0001
+#define SSS_SSH_SBUS_SERVICE_NAME    "ssh"
+
+struct ssh_ctx {
+    struct resp_ctx *rctx;
+};
+
+struct ssh_cmd_ctx {
+    struct cli_ctx *cctx;
+    enum sss_dp_acct_type type;
+    char *name;
+    char *domname;
+
+    struct sss_domain_info *domain;
+    bool check_next;
+    bool check_provider;
+
+    struct ldb_message **results;
+    size_t results_len;
+};
+
+struct sss_cmd_table *get_ssh_cmds(void);
+
+#endif /* _SSHSRV_PRIVATE_H_ */
-- 
1.7.6.5

