diff options
-rw-r--r-- | Makefile.am | 11 | ||||
-rw-r--r-- | contrib/sssd.spec.in | 2 | ||||
-rw-r--r-- | src/man/Makefile.am | 2 | ||||
-rw-r--r-- | src/man/sss_ssh_knownhostsproxy.1.xml | 120 | ||||
-rw-r--r-- | src/sss_client/ssh/sss_ssh_knownhostsproxy.c | 401 |
5 files changed, 534 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am index 908c4acc..6b6aea9c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -63,7 +63,8 @@ ACLOCAL_AMFLAGS = -I m4 -I . if BUILD_SSH bin_PROGRAMS = \ - sss_ssh_authorizedkeys + sss_ssh_authorizedkeys \ + sss_ssh_knownhostsproxy endif sbin_PROGRAMS = \ @@ -652,6 +653,14 @@ sss_ssh_authorizedkeys_SOURCES = \ sss_ssh_authorizedkeys_CFLAGS = $(AM_CFLAGS) sss_ssh_authorizedkeys_LDADD = \ libsss_util.la + +sss_ssh_knownhostsproxy_SOURCES = \ + src/sss_client/common.c \ + src/sss_client/ssh/sss_ssh.c \ + src/sss_client/ssh/sss_ssh_knownhostsproxy.c +sss_ssh_knownhostsproxy_CFLAGS = $(AM_CFLAGS) +sss_ssh_knownhostsproxy_LDADD = \ + libsss_util.la endif ################# diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in index 86aaef37..3b85b12d 100644 --- a/contrib/sssd.spec.in +++ b/contrib/sssd.spec.in @@ -316,8 +316,10 @@ rm -rf $RPM_BUILD_ROOT %{_libdir}/krb5/plugins/libkrb5/sssd_krb5_locator_plugin.so %if (0%{?enable_experimental} == 1) %{_bindir}/sss_ssh_authorizedkeys +%{_bindir}/sss_ssh_knownhostsproxy %endif %{_mandir}/man1/sss_ssh_authorizedkeys.1* +%{_mandir}/man1/sss_ssh_knownhostsproxy.1* %{_mandir}/man8/pam_sss.8* %{_mandir}/man8/sssd_krb5_locator_plugin.8* diff --git a/src/man/Makefile.am b/src/man/Makefile.am index f6307715..c0c2281e 100644 --- a/src/man/Makefile.am +++ b/src/man/Makefile.am @@ -39,7 +39,7 @@ man_MANS = \ sssd-krb5.5 sssd-ipa.5 sssd-simple.5 \ sssd_krb5_locator_plugin.8 sss_groupshow.8 \ pam_sss.8 sss_obfuscate.8 sss_cache.8 sss_debuglevel.8 \ - sss_ssh_authorizedkeys.1 + sss_ssh_authorizedkeys.1 sss_ssh_knownhostsproxy.1 EXTRA_DIST = $(man_MANS:%=%.xml) $(wildcard $(srcdir)/include/*.xml) SUFFIXES = .1.xml .1 .3.xml .3 .5.xml .5 .8.xml .8 diff --git a/src/man/sss_ssh_knownhostsproxy.1.xml b/src/man/sss_ssh_knownhostsproxy.1.xml new file mode 100644 index 00000000..1cb16049 --- /dev/null +++ b/src/man/sss_ssh_knownhostsproxy.1.xml @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.4//EN" +"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> +<reference> +<title>SSSD Manual pages</title> +<refentry> + <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/upstream.xml" /> + + <refmeta> + <refentrytitle>sss_ssh_knownhostsproxy</refentrytitle> + <manvolnum>1</manvolnum> + </refmeta> + + <refnamediv id='name'> + <refname>sss_ssh_knownhostsproxy</refname> + <refpurpose>get OpenSSH host keys</refpurpose> + </refnamediv> + + <refsynopsisdiv id='synopsis'> + <cmdsynopsis> + <command>sss_ssh_knownhostsproxy</command> + <arg choice='opt'> + <replaceable>options</replaceable> + </arg> + <arg choice='plain'><replaceable>HOST</replaceable></arg> + <arg choice='opt'><replaceable>PROXY_COMMAND</replaceable></arg> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1 id='description'> + <title>DESCRIPTION</title> + <para> + <command>sss_ssh_knownhostsproxy</command> acquires SSH host + public keys for host <replaceable>HOST</replaceable>, stores + them in a custom OpenSSH known_hosts file (see the + <quote>SSH_KNOWN_HOSTS FILE FORMAT</quote> section of + <citerefentry><refentrytitle>sshd</refentrytitle> + <manvolnum>8</manvolnum></citerefentry> for more + information) and estabilishes connection to the host. + </para> + <para> + If <replaceable>PROXY_COMMAND</replaceable> is specified, + it is used to create the connection to the host instead of + opening a socket. + </para> + <para> + <citerefentry><refentrytitle>ssh</refentrytitle> + <manvolnum>1</manvolnum></citerefentry> can be configured to + use <command>sss_ssh_knownhostsproxy</command> for host key + authentication by using the following directives for + <citerefentry><refentrytitle>ssh</refentrytitle> + <manvolnum>1</manvolnum></citerefentry> configuration: +<programlisting> +ProxyCommand /usr/bin/sss_ssh_knownhostsproxy -p %p %h +UserKnownHostsFile2 .ssh/sss_known_hosts +</programlisting> + </para> + <para> + <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/experimental.xml" /> + </para> + </refsect1> + + <refsect1 id='options'> + <title>OPTIONS</title> + <variablelist remap='IP'> + <varlistentry> + <term> + <option>-f</option>,<option>--file</option> + <replaceable>FILE</replaceable> + </term> + <listitem> + <para> + Store host public keys in file <replaceable>FILE</replaceable>. + By default, <filename>.ssh/sss_known_hosts</filename> is used. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-p</option>,<option>--port</option> + <replaceable>PORT</replaceable> + </term> + <listitem> + <para> + Use port <replaceable>PORT</replaceable> to connect to the host. + By default, port 22 is used. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-d</option>,<option>--domain</option> + <replaceable>DOMAIN</replaceable> + </term> + <listitem> + <para> + Search for host public keys in SSSD domain <replaceable>DOMAIN</replaceable>. + </para> + </listitem> + </varlistentry> + <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/param_help.xml" /> + </variablelist> + </refsect1> + + <refsect1 id='see_also'> + <title>SEE ALSO</title> + <para> + <citerefentry> + <refentrytitle>ssh</refentrytitle><manvolnum>8</manvolnum> + </citerefentry>, + <citerefentry> + <refentrytitle>ssh_config</refentrytitle><manvolnum>5</manvolnum> + </citerefentry>, + <citerefentry> + <refentrytitle>sss_ssh_authorizedkeys</refentrytitle><manvolnum>1</manvolnum> + </citerefentry>. + </para> + </refsect1> +</refentry> +</reference> diff --git a/src/sss_client/ssh/sss_ssh_knownhostsproxy.c b/src/sss_client/ssh/sss_ssh_knownhostsproxy.c new file mode 100644 index 00000000..b95dbe8e --- /dev/null +++ b/src/sss_client/ssh/sss_ssh_knownhostsproxy.c @@ -0,0 +1,401 @@ +/* + 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 <stdio.h> +#include <talloc.h> +#include <unistd.h> +#include <pwd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <netdb.h> +#include <popt.h> + +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "tools/tools_util.h" +#include "sss_client/sss_cli.h" +#include "sss_client/ssh/sss_ssh.h" + +#define DEFAULT_FILE ".ssh/sss_known_hosts" + +#define BUFFER_SIZE 8192 + +/* run proxy command */ +static int run_proxy(char **args) +{ + int ret; + + execv(args[0], (char * const *)args); + + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, ("execv() failed (%d): %s\n", + ret, strerror(ret))); + ERROR("Failed to execute proxy command\n"); + + return EXIT_FAILURE; +} + +/* connect to server */ +static int run_connect(int af, struct sockaddr *addr, size_t addr_len) +{ + int flags; + int sock; + fd_set fds; + char buffer[BUFFER_SIZE]; + ssize_t rd_len, wr_len, wr_offs; + int ret; + + /* set O_NONBLOCK on standard input */ + flags = fcntl(0, F_GETFL); + if (flags == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, ("fcntl() failed (%d): %s\n", + ret, strerror(ret))); + return EXIT_FAILURE; + } + + ret = fcntl(0, F_SETFL, flags | O_NONBLOCK); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, ("fcntl() failed (%d): %s\n", + ret, strerror(ret))); + return EXIT_FAILURE; + } + + /* create socket */ + sock = socket(af, SOCK_STREAM, IPPROTO_TCP); + if (sock == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, ("socket() failed (%d): %s\n", + ret, strerror(ret))); + ERROR("Failed to open a socket\n"); + return EXIT_FAILURE; + } + + /* connect to the server */ + ret = connect(sock, addr, addr_len); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, ("connect() failed (%d): %s\n", + ret, strerror(ret))); + ERROR("Failed to connect to the server\n"); + close(sock); + return EXIT_FAILURE; + } + + /* set O_NONBLOCK on the socket */ + flags = fcntl(sock, F_GETFL); + if (flags == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, ("fcntl() failed (%d): %s\n", + ret, strerror(ret))); + close(sock); + return EXIT_FAILURE; + } + + ret = fcntl(sock, F_SETFL, flags | O_NONBLOCK); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, ("fcntl() failed (%d): %s\n", + ret, strerror(ret))); + close(sock); + return EXIT_FAILURE; + } + + while (1) { + FD_SET(0, &fds); + FD_SET(sock, &fds); + + ret = select(sock+1, &fds, NULL, NULL, NULL); + if (ret == -1) { + if (errno == EINTR || errno == EAGAIN) { + continue; + } + + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, ("select() failed (%d): %s\n", + ret, strerror(ret))); + close(sock); + return EXIT_FAILURE; + } + + /* read from standard input & write to socket */ + if (FD_ISSET(0, &fds)) { + rd_len = read(0, buffer, BUFFER_SIZE); + if (rd_len == -1) { + if (errno == EAGAIN) { + continue; + } + + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, ("read() failed (%d): %s\n", + ret, strerror(ret))); + close(sock); + return EXIT_FAILURE; + } + + wr_offs = 0; + do { + wr_len = send(sock, buffer+wr_offs, rd_len-wr_offs, 0); + if (wr_len == -1) { + if (errno == EAGAIN) { + continue; + } + + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, ("send() failed (%d): %s\n", + ret, strerror(ret))); + close(sock); + return EXIT_FAILURE; + } + + if (wr_len == 0) { + close(sock); + return EXIT_SUCCESS; + } + + wr_offs += wr_len; + } while(wr_offs < rd_len); + } + + /* read from socket & write to standard output */ + if (FD_ISSET(sock, &fds)) { + rd_len = recv(sock, buffer, BUFFER_SIZE, 0); + if (rd_len == -1) { + if (errno == EAGAIN) { + continue; + } + + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, ("recv() failed (%d): %s\n", + ret, strerror(ret))); + close(sock); + return EXIT_FAILURE; + } + + if (rd_len == 0) { + close(sock); + return EXIT_SUCCESS; + } + + wr_offs = 0; + do { + wr_len = write(1, buffer+wr_offs, rd_len-wr_offs); + if (wr_len == -1) { + if (errno == EAGAIN) { + continue; + } + + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, ("write() failed (%d): %s\n", + ret, strerror(ret))); + close(sock); + return EXIT_FAILURE; + } + + wr_offs += wr_len; + } while(wr_offs < rd_len); + } + } +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + int pc_debug = SSSDBG_DEFAULT; + const char *pc_file = DEFAULT_FILE; + const char *pc_port = "22"; + const char *pc_domain = NULL; + const char *pc_host = NULL; + const char **pc_args = NULL; + struct poptOption long_options[] = { + POPT_AUTOHELP + { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug, 0, + _("The debug level to run with"), NULL }, + { "file", 'f', POPT_ARG_STRING, &pc_file, 0, + _("The known_hosts file to use"), NULL }, + { "port", 'p', POPT_ARG_STRING, &pc_port, 0, + _("The port to use to connect to the host"), NULL }, + { "domain", 'd', POPT_ARG_STRING, &pc_domain, 0, + _("The SSSD domain to use"), NULL }, + POPT_TABLEEND + }; + poptContext pc = NULL; + const char *file; + struct passwd *pwd; + const char *host; + FILE *f; + struct addrinfo ai_hint, *ai = NULL; + struct sss_ssh_pubkey *pubkeys; + size_t num_pubkeys, i; + char *repr; + int ret; + + debug_prg_name = argv[0]; + + ret = set_locale(); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + ("set_locale() failed (%d): %s\n", ret, strerror(ret))); + ERROR("Error setting the locale\n"); + ret = EXIT_FAILURE; + goto fini; + } + + mem_ctx = talloc_new(NULL); + if (!mem_ctx) { + ERROR("Not enough memory\n"); + ret = EXIT_FAILURE; + goto fini; + } + + /* parse parameters */ + pc = poptGetContext(NULL, argc, argv, long_options, 0); + poptSetOtherOptionHelp(pc, "HOST [PROXY_COMMAND]"); + while ((ret = poptGetNextOpt(pc)) > 0) + ; + + debug_level = debug_convert_old_level(pc_debug); + + if (ret != -1) { + BAD_POPT_PARAMS(pc, poptStrerror(ret), ret, fini); + } + + pc_host = poptGetArg(pc); + if (pc_host == NULL) { + BAD_POPT_PARAMS(pc, _("Host not specified\n"), ret, fini); + } + + pc_args = poptGetArgs(pc); + if (pc_args && pc_args[0] && pc_args[0][0] != '/') { + BAD_POPT_PARAMS(pc, + _("The path to the proxy command must be absolute\n"), + ret, fini); + } + + /* get absolute filename of the known_hosts file */ + if (pc_file && pc_file[0] != '/') { + pwd = getpwuid(getuid()); + if (!pwd) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, ("getpwuid() failed (%d): %s\n", + ret, strerror(ret))); + ERROR("Failed to get user's home directory\n"); + ret = EXIT_FAILURE; + goto fini; + } + + file = talloc_asprintf(mem_ctx, "%s/%s", pwd->pw_dir, pc_file); + if (!file) { + ERROR("Not enough memory\n"); + ret = EXIT_FAILURE; + goto fini; + } + } else { + file = pc_file; + } + + /* get canonic hostname and IP addresses of the host */ + memset(&ai_hint, 0, sizeof(struct addrinfo)); + ai_hint.ai_family = AF_UNSPEC; + ai_hint.ai_socktype = SOCK_STREAM; + ai_hint.ai_protocol = IPPROTO_TCP; + ai_hint.ai_flags = AI_CANONNAME | AI_ADDRCONFIG | AI_NUMERICSERV; + + ret = getaddrinfo(pc_host, pc_port, &ai_hint, &ai); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + ("getaddrinfo() failed (%d): %s\n", ret, gai_strerror(ret))); + ERROR("Error looking up host\n"); + ret = EXIT_FAILURE; + goto fini; + } + + /* append domain to hostname if domain is specified */ + if (pc_domain) { + host = talloc_asprintf(mem_ctx, "%s@%s", ai[0].ai_canonname, pc_domain); + if (!host) { + ERROR("Not enough memory\n"); + ret = EXIT_FAILURE; + goto fini; + } + } else { + host = ai[0].ai_canonname; + } + + /* look up public keys */ + ret = sss_ssh_get_pubkeys(mem_ctx, SSS_SSH_GET_HOST_PUBKEYS, host, + &pubkeys, &num_pubkeys); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + ("sss_ssh_get_pubkeys failed() (%d): %s\n", ret, strerror(ret))); + ERROR("Error looking up public keys\n"); + ret = EXIT_FAILURE; + goto fini; + } + + /* write known_hosts file */ + /* FIXME: Do not overwrite the file, handle concurrent access */ + f = fopen(file, "w"); + if (!f) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, ("fopen() failed (%d): %s\n", + ret, strerror(ret))); + ERROR("Can't open known hosts file\n"); + ret = EXIT_FAILURE; + goto fini; + } + + fprintf(f, + "# Generated by sss_ssh_knownhostsproxy. Please do not modify.\n"); + + for (i = 0; i < num_pubkeys; i++) { + ret = sss_ssh_format_pubkey(mem_ctx, &pubkeys[i], + SSS_SSH_FORMAT_OPENSSH, &repr); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + ("sss_ssh_format_pubkey() failed (%d): %s\n", + ret, strerror(ret))); + continue; + } + + fprintf(f, "%s %s\n", pc_host, repr); + } + + fclose(f); + + /* connect to server */ + if (pc_args) { + ret = run_proxy(discard_const(pc_args)); + } else { + ret = run_connect(ai->ai_family, ai->ai_addr, ai->ai_addrlen); + } + +fini: + poptFreeContext(pc); + talloc_free(mem_ctx); + if (ai) freeaddrinfo(ai); + + return ret; +} |