summaryrefslogtreecommitdiffstats
path: root/usr.bin/ssh
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/ssh')
-rw-r--r--usr.bin/ssh/readconf.c12
-rw-r--r--usr.bin/ssh/readconf.h4
-rw-r--r--usr.bin/ssh/ssh.15
-rw-r--r--usr.bin/ssh/ssh_config.558
-rw-r--r--usr.bin/ssh/sshconnect.c110
-rw-r--r--usr.bin/ssh/sshconnect.h6
-rw-r--r--usr.bin/ssh/sshconnect2.c9
7 files changed, 188 insertions, 16 deletions
diff --git a/usr.bin/ssh/readconf.c b/usr.bin/ssh/readconf.c
index d9c21ed24ef..f3b3fd44c93 100644
--- a/usr.bin/ssh/readconf.c
+++ b/usr.bin/ssh/readconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.345 2020/12/21 09:19:53 djm Exp $ */
+/* $OpenBSD: readconf.c,v 1.346 2020/12/22 00:15:22 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -158,7 +158,7 @@ typedef enum {
oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys,
oFingerprintHash, oUpdateHostkeys, oHostbasedKeyTypes,
oPubkeyAcceptedKeyTypes, oCASignatureAlgorithms, oProxyJump,
- oSecurityKeyProvider,
+ oSecurityKeyProvider, oKnownHostsCommand,
oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported
} OpCodes;
@@ -297,6 +297,7 @@ static struct {
{ "ignoreunknown", oIgnoreUnknown },
{ "proxyjump", oProxyJump },
{ "securitykeyprovider", oSecurityKeyProvider },
+ { "knownhostscommand", oKnownHostsCommand },
{ NULL, oBadOption }
};
@@ -1240,6 +1241,10 @@ parse_char_array:
charptr = &options->sk_provider;
goto parse_string;
+ case oKnownHostsCommand:
+ charptr = &options->known_hosts_command;
+ goto parse_command;
+
case oProxyCommand:
charptr = &options->proxy_command;
/* Ignore ProxyCommand if ProxyJump already specified */
@@ -2203,6 +2208,7 @@ initialize_options(Options * options)
options->update_hostkeys = -1;
options->hostbased_key_types = NULL;
options->pubkey_key_types = NULL;
+ options->known_hosts_command = NULL;
}
/*
@@ -2431,6 +2437,7 @@ fill_default_options(Options * options)
CLEAR_ON_NONE(options->revoked_host_keys);
CLEAR_ON_NONE(options->pkcs11_provider);
CLEAR_ON_NONE(options->sk_provider);
+ CLEAR_ON_NONE(options->known_hosts_command);
if (options->jump_host != NULL &&
strcmp(options->jump_host, "none") == 0 &&
options->jump_port == 0 && options->jump_user == NULL) {
@@ -3079,6 +3086,7 @@ dump_client_config(Options *o, const char *host)
dump_cfg_string(oPubkeyAcceptedKeyTypes, o->pubkey_key_types);
dump_cfg_string(oRevokedHostKeys, o->revoked_host_keys);
dump_cfg_string(oXAuthLocation, o->xauth_location);
+ dump_cfg_string(oKnownHostsCommand, o->known_hosts_command);
/* Forwards */
dump_cfg_forwards(oDynamicForward, o->num_local_forwards, o->local_forwards);
diff --git a/usr.bin/ssh/readconf.h b/usr.bin/ssh/readconf.h
index 268dbf179a9..85ea2e112a4 100644
--- a/usr.bin/ssh/readconf.h
+++ b/usr.bin/ssh/readconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.h,v 1.136 2020/12/17 23:10:27 djm Exp $ */
+/* $OpenBSD: readconf.h,v 1.137 2020/12/22 00:15:23 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -169,6 +169,8 @@ typedef struct {
int jump_port;
char *jump_extra;
+ char *known_hosts_command;
+
char *ignored_unknown; /* Pattern list of unknown tokens to ignore */
} Options;
diff --git a/usr.bin/ssh/ssh.1 b/usr.bin/ssh/ssh.1
index 555317887e1..81e147c7504 100644
--- a/usr.bin/ssh/ssh.1
+++ b/usr.bin/ssh/ssh.1
@@ -33,8 +33,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
-.\" $OpenBSD: ssh.1,v 1.414 2020/07/15 05:40:05 jmc Exp $
-.Dd $Mdocdate: July 15 2020 $
+.\" $OpenBSD: ssh.1,v 1.415 2020/12/22 00:15:23 djm Exp $
+.Dd $Mdocdate: December 22 2020 $
.Dt SSH 1
.Os
.Sh NAME
@@ -521,6 +521,7 @@ For full details of the options listed below, and their possible values, see
.It KbdInteractiveAuthentication
.It KbdInteractiveDevices
.It KexAlgorithms
+.It KnownHostsCommand
.It LocalCommand
.It LocalForward
.It LogLevel
diff --git a/usr.bin/ssh/ssh_config.5 b/usr.bin/ssh/ssh_config.5
index 723b43ebc80..37670d93b87 100644
--- a/usr.bin/ssh/ssh_config.5
+++ b/usr.bin/ssh/ssh_config.5
@@ -33,8 +33,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
-.\" $OpenBSD: ssh_config.5,v 1.338 2020/10/16 14:34:33 jmc Exp $
-.Dd $Mdocdate: October 16 2020 $
+.\" $OpenBSD: ssh_config.5,v 1.339 2020/12/22 00:15:23 djm Exp $
+.Dd $Mdocdate: December 22 2020 $
.Dt SSH_CONFIG 5
.Os
.Sh NAME
@@ -1121,6 +1121,31 @@ diffie-hellman-group14-sha256
.Pp
The list of available key exchange algorithms may also be obtained using
.Qq ssh -Q kex .
+.It Cm KnownHostsCommand
+Specifies a command to use to obtain a list of host keys, additional to
+those listed in
+.Cm UserKnownHostsFile
+and
+.Cm GlobalKnownHostsFile .
+This command is executed after the files have been read.
+It may write host keys lines to standard output in identical format to the
+usual files (described in the
+.Sx VERIFYING HOST KEYS
+section in
+.Xr ssh 1 ) .
+Arguments to
+.Cm KnownHostsCommand
+accept the tokens described in the
+.Sx TOKENS
+section.
+The command may be invoked multiple times per connection: when preparing
+the preference list of host key algorithms to use, again to obtain the
+host key for the requested host name and, if
+.Cm CheckHostIP
+is enabled, one more time to obtain the host key matching the server's
+address.
+If the command exits abnormally or returns a non-zero exit status then the
+connection is terminated.
.It Cm LocalCommand
Specifies a command to execute on the local machine after successfully
connecting to the server.
@@ -1884,10 +1909,31 @@ A literal
Hash of %l%h%p%r.
.It %d
Local user's home directory.
+.It %f
+The fingerprint of the server's host key.
+.It %H
+The
+.Pa known_hosts
+hostname or address that is being searched for.
.It %h
The remote hostname.
+.It %I
+A string describing the reason for a
+.Cm KnownHostsCommand
+execution; either
+.Cm "ADDRESS"
+when looking up a host by address (only when
+.Cm CheckHostIP
+is enabled),
+.Cm "HOSTNAME"
+when searching by hostname or
+.Cm "ORDER"
+when preparing the host key algorithm preference list to use for the
+destination host.
.It %i
The local user ID.
+.It %K
+The base64 encoded host key.
.It %k
The host key alias if specified, otherwise the orignal remote hostname given
on the command line.
@@ -1910,6 +1956,9 @@ network interface assigned if
tunnel forwarding was requested, or
.Qq NONE
otherwise.
+.It %t
+The type of the server host key, e.g.
+.Cm ssh-ed25519
.It %u
The local username.
.El
@@ -1918,6 +1967,7 @@ The local username.
.Cm ControlPath ,
.Cm IdentityAgent ,
.Cm IdentityFile ,
+.Cm KnownHostsCommand ,
.Cm LocalForward ,
.Cm Match exec ,
.Cm RemoteCommand ,
@@ -1926,6 +1976,9 @@ and
.Cm UserKnownHostsFile
accept the tokens %%, %C, %d, %h, %i, %L, %l, %n, %p, %r, and %u.
.Pp
+.Cm KnownHostsCommand
+additionally accepts the tokens %f, %H, %I, %K and %t.
+.Pp
.Cm Hostname
accepts the tokens %% and %h.
.Pp
@@ -1949,6 +2002,7 @@ The keywords
.Cm ControlPath ,
.Cm IdentityAgent ,
.Cm IdentityFile
+.Cm KnownHostsCommand ,
and
.Cm UserKnownHostsFile
support environment variables.
diff --git a/usr.bin/ssh/sshconnect.c b/usr.bin/ssh/sshconnect.c
index 934fff2c5a9..65f2773870b 100644
--- a/usr.bin/ssh/sshconnect.c
+++ b/usr.bin/ssh/sshconnect.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshconnect.c,v 1.348 2020/12/20 23:40:19 djm Exp $ */
+/* $OpenBSD: sshconnect.c,v 1.349 2020/12/22 00:15:23 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -827,6 +827,84 @@ other_hostkeys_message(const char *host, const char *ip,
return ret;
}
+void
+load_hostkeys_command(struct hostkeys *hostkeys, const char *command_template,
+ const char *invocation, const struct ssh_conn_info *cinfo,
+ const struct sshkey *host_key, const char *hostfile_hostname)
+{
+ int r, i, ac = 0;
+ char *key_fp = NULL, *keytext = NULL, *tmp;
+ char *command = NULL, *tag = NULL, **av = NULL;
+ FILE *f = NULL;
+ pid_t pid;
+ void (*osigchld)(int);
+
+ xasprintf(&tag, "KnownHostsCommand-%s", invocation);
+
+ if (host_key != NULL) {
+ if ((key_fp = sshkey_fingerprint(host_key,
+ options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
+ fatal_f("sshkey_fingerprint failed");
+ if ((r = sshkey_to_base64(host_key, &keytext)) != 0)
+ fatal_fr(r, "sshkey_to_base64 failed");
+ }
+ /*
+ * NB. all returns later this function should go via "out" to
+ * ensure the original SIGCHLD handler is restored properly.
+ */
+ osigchld = ssh_signal(SIGCHLD, SIG_DFL);
+
+ /* Turn the command into an argument vector */
+ if (argv_split(command_template, &ac, &av) != 0) {
+ error("%s \"%s\" contains invalid quotes", tag,
+ command_template);
+ goto out;
+ }
+ if (ac == 0) {
+ error("%s \"%s\" yielded no arguments", tag,
+ command_template);
+ goto out;
+ }
+ for (i = 1; i < ac; i++) {
+ tmp = percent_dollar_expand(av[i],
+ DEFAULT_CLIENT_PERCENT_EXPAND_ARGS(cinfo),
+ "H", hostfile_hostname,
+ "I", invocation,
+ "t", host_key == NULL ? "NONE" : sshkey_ssh_name(host_key),
+ "f", key_fp == NULL ? "NONE" : key_fp,
+ "K", keytext == NULL ? "NONE" : keytext,
+ (char *)NULL);
+ if (tmp == NULL)
+ fatal_f("percent_expand failed");
+ free(av[i]);
+ av[i] = tmp;
+ }
+ /* Prepare a printable command for logs, etc. */
+ command = argv_assemble(ac, av);
+
+ if ((pid = subprocess(tag, command, ac, av, &f,
+ SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_UNSAFE_PATH|
+ SSH_SUBPROCESS_PRESERVE_ENV, NULL, NULL, NULL)) == 0)
+ goto out;
+
+ load_hostkeys_file(hostkeys, hostfile_hostname, tag, f, 1);
+
+ if (exited_cleanly(pid, tag, command, 0) != 0)
+ fatal("KnownHostsCommand failed");
+
+ out:
+ if (f != NULL)
+ fclose(f);
+ ssh_signal(SIGCHLD, osigchld);
+ for (i = 0; i < ac; i++)
+ free(av[i]);
+ free(av);
+ free(tag);
+ free(command);
+ free(key_fp);
+ free(keytext);
+}
+
/*
* check whether the supplied host key is valid, return -1 if the key
* is not valid. user_hostfile[0] will not be updated if 'readonly' is true.
@@ -839,7 +917,8 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
struct sockaddr *hostaddr, u_short port,
struct sshkey *host_key, int readonly, int clobber_port,
char **user_hostfiles, u_int num_user_hostfiles,
- char **system_hostfiles, u_int num_system_hostfiles)
+ char **system_hostfiles, u_int num_system_hostfiles,
+ const char *hostfile_command)
{
HostStatus host_status = -1, ip_status = -1;
struct sshkey *raw_key = NULL;
@@ -891,6 +970,10 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
load_hostkeys(host_hostkeys, host, user_hostfiles[i], 0);
for (i = 0; i < num_system_hostfiles; i++)
load_hostkeys(host_hostkeys, host, system_hostfiles[i], 0);
+ if (hostfile_command != NULL && !clobber_port) {
+ load_hostkeys_command(host_hostkeys, hostfile_command,
+ "HOSTNAME", cinfo, host_key, host);
+ }
ip_hostkeys = NULL;
if (!want_cert && options.check_host_ip) {
@@ -899,6 +982,10 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
load_hostkeys(ip_hostkeys, ip, user_hostfiles[i], 0);
for (i = 0; i < num_system_hostfiles; i++)
load_hostkeys(ip_hostkeys, ip, system_hostfiles[i], 0);
+ if (hostfile_command != NULL && !clobber_port) {
+ load_hostkeys_command(ip_hostkeys, hostfile_command,
+ "ADDRESS", cinfo, host_key, ip);
+ }
}
retry:
@@ -913,8 +1000,12 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
host_status = check_key_in_hostkeys(host_hostkeys, host_key,
&host_found);
- /* If no host files were specified, then don't try to touch them */
- if (!readonly && num_user_hostfiles == 0)
+ /*
+ * If there are no hostfiles, or if the hostkey was found via
+ * KnownHostsCommand, then don't try to touch the disk.
+ */
+ if (!readonly && (num_user_hostfiles == 0 ||
+ (host_found != NULL && host_found->note != 0)))
readonly = RDONLY;
/*
@@ -955,6 +1046,11 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
debug3_f("host key found in GlobalKnownHostsFile; "
"disabling UpdateHostkeys");
}
+ if (options.update_hostkeys != 0 && host_found->note) {
+ options.update_hostkeys = 0;
+ debug3_f("host key found via KnownHostsCommand; "
+ "disabling UpdateHostkeys");
+ }
if (options.check_host_ip && ip_status == HOST_NEW) {
if (readonly || want_cert)
logit("%s host key for IP address "
@@ -990,7 +1086,8 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
if (check_host_key(hostname, cinfo, hostaddr, 0,
host_key, ROQUIET, 1,
user_hostfiles, num_user_hostfiles,
- system_hostfiles, num_system_hostfiles) == 0) {
+ system_hostfiles, num_system_hostfiles,
+ hostfile_command) == 0) {
debug("found matching key w/out port");
break;
}
@@ -1400,7 +1497,8 @@ verify_host_key(char *host, struct sockaddr *hostaddr, struct sshkey *host_key,
}
r = check_host_key(host, cinfo, hostaddr, options.port, host_key,
RDRW, 0, options.user_hostfiles, options.num_user_hostfiles,
- options.system_hostfiles, options.num_system_hostfiles);
+ options.system_hostfiles, options.num_system_hostfiles,
+ options.known_hosts_command);
out:
sshkey_free(plain);
diff --git a/usr.bin/ssh/sshconnect.h b/usr.bin/ssh/sshconnect.h
index 161056b4d9e..f518a9a1302 100644
--- a/usr.bin/ssh/sshconnect.h
+++ b/usr.bin/ssh/sshconnect.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshconnect.h,v 1.45 2020/12/20 23:40:19 djm Exp $ */
+/* $OpenBSD: sshconnect.h,v 1.46 2020/12/22 00:15:23 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
@@ -88,3 +88,7 @@ int ssh_local_cmd(const char *);
void maybe_add_key_to_agent(const char *, struct sshkey *,
const char *, const char *);
+
+void load_hostkeys_command(struct hostkeys *, const char *,
+ const char *, const struct ssh_conn_info *,
+ const struct sshkey *, const char *);
diff --git a/usr.bin/ssh/sshconnect2.c b/usr.bin/ssh/sshconnect2.c
index 27adacc6d4d..2435558e45e 100644
--- a/usr.bin/ssh/sshconnect2.c
+++ b/usr.bin/ssh/sshconnect2.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshconnect2.c,v 1.338 2020/12/20 23:40:19 djm Exp $ */
+/* $OpenBSD: sshconnect2.c,v 1.339 2020/12/22 00:15:23 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
* Copyright (c) 2008 Damien Miller. All rights reserved.
@@ -132,6 +132,10 @@ order_hostkeyalgs(char *host, struct sockaddr *hostaddr, u_short port,
load_hostkeys(hostkeys, hostname,
options.system_hostfiles[i], 0);
}
+ if (options.known_hosts_command != NULL) {
+ load_hostkeys_command(hostkeys, options.known_hosts_command,
+ "ORDER", cinfo, NULL, host);
+ }
/*
* If a plain public key exists that matches the type of the best
* preference HostkeyAlgorithms, then use the whole list as is.
@@ -193,7 +197,8 @@ order_hostkeyalgs(char *host, struct sockaddr *hostaddr, u_short port,
(*first == '\0' || *last == '\0') ? "" : ",", last);
if (*first != '\0')
debug3_f("prefer hostkeyalgs: %s", first);
-
+ else
+ debug3_f("no algorithms matched; accept original");
out:
free(best);
free(first);