Updated ssh-keyscan patch for ssh2 support

Wayne Davison wayned at users.sourceforge.net
Sat Jul 28 04:18:13 EST 2001


In the past 2 months another change occurred in the CVS code that
broke my ssh-keyscan patch.  Here's an updated version that tweaks the
changed name (in the Kex struct) and also causes an attempt to grab an
ssh2 key from an older server (without ssh2 support) to fail earlier
and without an error message (Stuart Pearlman emailed me some code for
this).

This patch is based on the BSD CVS source, but should also apply to
the portable CVS codebase with just some offsets and a bit of fuzz.

If there are any issues remaining that keep this from going into CVS,
please let me know.

..wayne..
-------------- next part --------------
Index: ssh-keyscan.1
--- ssh-keyscan.1	2001/06/23 17:48:18	1.8
+++ ssh-keyscan.1	2001/07/27 17:56:05
@@ -14,14 +14,20 @@
 .Nd gather ssh public keys
 .Sh SYNOPSIS
 .Nm ssh-keyscan
-.Op Fl t Ar timeout
-.Op Ar -- | host | addrlist namelist
-.Op Fl f Ar files ...
+.Op Fl v46
+.Op Fl T Ar timeout
+.Op Fl t Ar type
+.Op Fl -
+.Op Ar host | addrlist namelist
+.Op Fl f Ar files
+.Op Ar ...
 .Sh DESCRIPTION
 .Nm
 is a utility for gathering the public ssh host keys of a number of
 hosts.  It was designed to aid in building and verifying
 .Pa ssh_known_hosts
+and
+.Pa ssh_known_hosts2
 files.
 .Nm
 provides a minimal interface suitable for use by shell and perl
@@ -46,14 +52,43 @@
 have begun after you created your ssh_known_hosts file.
 .Sh OPTIONS
 .Bl -tag -width Ds
-.It Fl t
+.It Fl v
+Verbose mode.
+Causes
+.Nm
+to print debugging messages about its progress.
+.It Fl 4
+Forces
+.Nm
+to use IPv4 addresses only.
+.It Fl 6
+Forces
+.Nm
+to use IPv6 addresses only.
+.It Fl T
 Set the timeout for connection attempts.  If
 .Pa timeout
 seconds have elapsed since a connection was initiated to a host or since the
 last time anything was read from that host, then the connection is
 closed and the host in question considered unavailable.  Default is 5
 seconds.
-.It Fl f
+.It Fl t Ar type
+Specifies the type of the key to fetch from the following hosts.
+The possible values are
+.Dq rsa1
+for protocol version 1 and
+.Dq rsa
+or
+.Dq dsa
+for protocol version 2.
+Multiple values may be specified by separating them with commas.
+The default is
+.Dq rsa1 .
+Specifying the
+.Pa -t
+option again later on the line will change the value for the hostnames that
+follow, allowing you to get different key-types from different hosts.
+.It Fl f Ar filename
 Read hosts or
 .Pa addrlist namelist
 pairs from this file, one per line.
@@ -64,33 +99,59 @@
 will read hosts or
 .Pa addrlist namelist
 pairs from the standard input.
+It is legal to specify multiple
+.Pa -f
+options and to intermingle them with literal hostnames in any order.
 .El
 .Sh EXAMPLES
-Print the host key for machine
-.Pa hostname :
+.Pp
+Print the
+.Pa rsa1
+host key for machine
+.Pa host1
+and the
+.Pa dsa
+host key for machine
+.Pa host2 :
 .Bd -literal
-ssh-keyscan hostname
+ssh-keyscan host1 -t dsa host2
 .Ed
 .Pp
 Find all hosts from the file
 .Pa ssh_hosts
 which have new or different keys from those in the sorted file
-.Pa ssh_known_hosts :
+.Pa ssh_known_hosts2 :
 .Bd -literal
-$ ssh-keyscan -f ssh_hosts | sort -u - ssh_known_hosts | \e\ 
-	diff ssh_known_hosts -
+$ ssh-keyscan -t rsa,dsa -f ssh_hosts | \e\
+	sort -u - ssh_known_hosts2 | diff ssh_known_hosts2 -
 .Ed
 .Sh FILES
 .Pa Input format:
+.Bd -literal
 1.2.3.4,1.2.4.4 name.my.domain,name,n.my.domain,n,1.2.3.4,1.2.4.4
+.Ed
 .Pp
-.Pa Output format:
+.Pa Output format for rsa1 keys:
+.Bd -literal
 host-or-namelist bits exponent modulus
+.Ed
+.Pp
+.Pa Output format for rsa and dsa keys:
+.Bd -literal
+host-or-namelist keytype base64-encoded-key
+.Ed
+.Pp
+Where
+.Pa keytype
+is either
+.Dq ssh-rsa
+or
+.Dq ssh-dsa .
 .Pp
 .Pa /etc/ssh_known_hosts
 .Sh BUGS
 It generates "Connection closed by remote host" messages on the consoles
-of all the machines it scans.
+of all the machines it scans if the server is older than version 2.9.
 This is because it opens a connection to the ssh port, reads the public
 key, and drops the connection as soon as it gets the key.
 .Sh SEE ALSO
@@ -98,3 +159,6 @@
 .Xr sshd 8
 .Sh AUTHORS
 David Mazieres <dm at lcs.mit.edu>
+wrote the initial version, and
+Wayne Davison <wayned at users.sourceforge.net>
+added support for ssh protocol 2.
Index: ssh-keyscan.c
--- ssh-keyscan.c	2001/06/23 15:12:20	1.24
+++ ssh-keyscan.c	2001/07/27 17:56:05
@@ -14,10 +14,16 @@
 
 #include <openssl/bn.h>
 
+#include <setjmp.h>
 #include "xmalloc.h"
 #include "ssh.h"
 #include "ssh1.h"
 #include "key.h"
+#include "kex.h"
+#include "compat.h"
+#include "myproposal.h"
+#include "packet.h"
+#include "dispatch.h"
 #include "buffer.h"
 #include "bufaux.h"
 #include "log.h"
@@ -25,8 +31,20 @@
 
 static int argno = 1;		/* Number of argument currently being parsed */
 
-int family = AF_UNSPEC;		/* IPv4, IPv6 or both */
+/* Flag indicating whether IPv4 or IPv6.  This can be set on the command line.
+   Default value is AF_UNSPEC means both IPv4 and IPv6. */
+#ifdef IPV4_DEFAULT
+int IPv4or6 = AF_INET;
+#else
+int IPv4or6 = AF_UNSPEC;
+#endif
+
+#define KT_RSA1	1
+#define KT_DSA	2
+#define KT_RSA	4
 
+int get_keytypes = KT_RSA1;	/* Get only RSA1 keys by default */
+
 #define MAXMAXFD 256
 
 /* The number of seconds after which to give up on a TCP connection */
@@ -39,6 +57,8 @@
 fd_set *read_wait;
 size_t read_wait_size;
 int ncon;
+int nonfatal_fatal = 0;
+jmp_buf kexjmp;
 
 /*
  * Keep a connection structure for each file descriptor.  The state
@@ -54,11 +74,13 @@
 	int c_plen;		/* Packet length field for ssh packet */
 	int c_len;		/* Total bytes which must be read. */
 	int c_off;		/* Length of data read so far. */
+	int c_keytype;		/* Only one of KT_RSA1, KT_DSA, or KT_RSA */
 	char *c_namebase;	/* Address to free for c_name and c_namelist */
 	char *c_name;		/* Hostname of connection for errors */
 	char *c_namelist;	/* Pointer to other possible addresses */
 	char *c_output_name;	/* Hostname of connection for output */
 	char *c_data;		/* Data read from this fd */
+	Kex *c_kex;		/* The key-exchange struct for ssh2 */
 	struct timeval c_tv;	/* Time at which connection gets aborted */
 	TAILQ_ENTRY(Connection) c_link;	/* List of connections in timeout order. */
 } con;
@@ -242,8 +264,8 @@
 	return (tok);
 }
 
-static void
-keyprint(char *host, char *output_name, char *kd, int len)
+static Key *
+keygrab_ssh1(con *c)
 {
 	static Key *rsa;
 	static Buffer msg;
@@ -252,12 +274,12 @@
 		buffer_init(&msg);
 		rsa = key_new(KEY_RSA1);
 	}
-	buffer_append(&msg, kd, len);
-	buffer_consume(&msg, 8 - (len & 7));	/* padding */
+	buffer_append(&msg, c->c_data, c->c_plen);
+	buffer_consume(&msg, 8 - (c->c_plen & 7));	/* padding */
 	if (buffer_get_char(&msg) != (int) SSH_SMSG_PUBLIC_KEY) {
-		error("%s: invalid packet type", host);
+		error("%s: invalid packet type", c->c_name);
 		buffer_clear(&msg);
-		return;
+		return NULL;
 	}
 	buffer_consume(&msg, 8);		/* cookie */
 
@@ -270,10 +292,70 @@
 	(void) buffer_get_int(&msg);
 	buffer_get_bignum(&msg, rsa->rsa->e);
 	buffer_get_bignum(&msg, rsa->rsa->n);
+
 	buffer_clear(&msg);
+
+	return (rsa);
+}
+
+static int
+hostjump(Key *hostkey)
+{
+	longjmp(kexjmp, (int)hostkey);
+}
+
+int
+ssh2_capable(int remote_major, int remote_minor)
+{
+	switch (remote_major) {
+	case 1:
+		if (remote_minor == 99)
+			return 1;
+		break;
+	case 2:
+		return 1;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static Key *
+keygrab_ssh2(con *c)
+{
+	int j;
+
+	packet_set_connection(c->c_fd, c->c_fd);
+	enable_compat20();
+	myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = c->c_keytype == KT_DSA?
+	    "ssh-dss": "ssh-rsa";
+	c->c_kex = kex_setup(myproposal);
+	c->c_kex->verify_host_key = hostjump;
+
+	if (!(j = setjmp(kexjmp))) {
+		nonfatal_fatal = 1;
+		dispatch_run(DISPATCH_BLOCK, &c->c_kex->done, c->c_kex);
+		fprintf(stderr, "Impossible! dispatch_run() returned!\n");
+		exit(1);
+	}
+	nonfatal_fatal = 0;
+	xfree(c->c_kex);
+	c->c_kex = NULL;
+	packet_close();
+	if (j < 0)
+		j = 0;
 
-	fprintf(stdout, "%s ", output_name ? output_name : host);
-	key_write(rsa, stdout);
+	return (Key*)(j);
+}
+
+static void
+keyprint(con *c, Key *key)
+{
+	if (!key)
+		return;
+
+	fprintf(stdout, "%s ", c->c_output_name ? c->c_output_name : c->c_name);
+	key_write(key, stdout);
 	fputs("\n", stdout);
 }
 
@@ -286,7 +368,7 @@
 
 	snprintf(strport, sizeof strport, "%d", SSH_DEFAULT_PORT);
 	memset(&hints, 0, sizeof(hints));
-	hints.ai_family = family;
+	hints.ai_family = IPv4or6;
 	hints.ai_socktype = SOCK_STREAM;
 	if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0)
 		fatal("getaddrinfo %s: %s", host, gai_strerror(gaierr));
@@ -311,7 +393,7 @@
 }
 
 static int
-conalloc(char *iname, char *oname)
+conalloc(char *iname, char *oname, int keytype)
 {
 	int s;
 	char *namebase, *name, *namelist;
@@ -340,6 +422,7 @@
 	fdcon[s].c_data = (char *) &fdcon[s].c_plen;
 	fdcon[s].c_len = 4;
 	fdcon[s].c_off = 0;
+	fdcon[s].c_keytype = keytype;
 	gettimeofday(&fdcon[s].c_tv, NULL);
 	fdcon[s].c_tv.tv_sec += timeout;
 	TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link);
@@ -359,6 +442,7 @@
 	if (fdcon[s].c_status == CS_KEYS)
 		xfree(fdcon[s].c_data);
 	fdcon[s].c_status = CS_UNUSED;
+	fdcon[s].c_keytype = 0;
 	TAILQ_REMOVE(&tq, &fdcon[s], c_link);
 	FD_CLR(s, read_wait);
 	ncon--;
@@ -378,21 +462,16 @@
 {
 	int ret;
 	con *c = &fdcon[s];
-	char *iname, *oname;
 
-	iname = xstrdup(c->c_namelist);
-	oname = xstrdup(c->c_output_name);
+	ret = conalloc(c->c_namelist, c->c_output_name, c->c_keytype);
 	confree(s);
-	ret = conalloc(iname, oname);
-	xfree(iname);
-	xfree(oname);
 	return (ret);
 }
 
 static void
 congreet(int s)
 {
-	char buf[80], *cp;
+	char buf[256], *cp;
 	size_t bufsiz;
 	int n = 0;
 	con *c = &fdcon[s];
@@ -414,12 +493,34 @@
 	}
 	*cp = '\0';
 	fprintf(stderr, "# %s %s\n", c->c_name, buf);
-	n = snprintf(buf, sizeof buf, "SSH-1.5-OpenSSH-keyscan\r\n");
+	if (c->c_keytype != KT_RSA1) {
+		int remote_major, remote_minor;
+		char remote_version[sizeof buf];
+
+		if (sscanf(buf, "SSH-%d.%d-%[^\n]\n",
+		    &remote_major, &remote_minor, remote_version) == 3)
+			compat_datafellows(remote_version);
+		else
+			datafellows = 0;
+		if (!ssh2_capable(remote_major, remote_minor)) {
+			debug("%s doesn't support ssh2", c->c_name);
+			confree(s);
+			return;
+		}
+	}
+	n = snprintf(buf, sizeof buf, "SSH-%d.%d-OpenSSH-keyscan\r\n",
+	    c->c_keytype == KT_RSA1? PROTOCOL_MAJOR_1 : PROTOCOL_MAJOR_2,
+	    c->c_keytype == KT_RSA1? PROTOCOL_MINOR_1 : PROTOCOL_MINOR_2);
 	if (atomicio(write, s, buf, n) != n) {
 		error("write (%s): %s", c->c_name, strerror(errno));
 		confree(s);
 		return;
 	}
+	if (c->c_keytype != KT_RSA1) {
+		keyprint(c, keygrab_ssh2(c));
+		confree(s);
+		return;
+	}
 	c->c_status = CS_SIZE;
 	contouch(s);
 }
@@ -452,7 +553,7 @@
 			c->c_status = CS_KEYS;
 			break;
 		case CS_KEYS:
-			keyprint(c->c_name, c->c_output_name, c->c_data, c->c_plen);
+			keyprint(c, keygrab_ssh1(c));
 			confree(s);
 			return;
 			break;
@@ -520,49 +621,99 @@
 nexthost(int argc, char **argv)
 {
 	static Linebuf *lb;
+	char *fname, *tname;
 
 	for (;;) {
-		if (!lb) {
-			if (argno >= argc)
-				return (NULL);
-			if (argv[argno][0] != '-')
-				return (argv[argno++]);
-			if (!strcmp(argv[argno], "--")) {
+		if (lb) {
+			char *line;
+
+			line = Linebuf_getline(lb);
+			if (line)
+				return (line);
+			Linebuf_free(lb);
+			lb = NULL;
+		}
+		if (argno >= argc)
+			return (NULL);
+		if (argv[argno][0] != '-')
+			return (argv[argno++]);
+		while (*++(argv[argno])) {
+			switch (argv[argno][0]) {
+			case '-':
 				if (++argno >= argc)
 					return (NULL);
 				return (argv[argno++]);
-			} else if (!strncmp(argv[argno], "-f", 2)) {
-				char *fname;
-
-				if (argv[argno][2])
-					fname = &argv[argno++][2];
+			case 'f':
+				if (argv[argno][1])
+					fname = &argv[argno][1];
 				else if (++argno >= argc) {
 					error("missing filename for `-f'");
 					return (NULL);
 				} else
-					fname = argv[argno++];
+					fname = argv[argno];
 				if (!strcmp(fname, "-"))
 					fname = NULL;
 				lb = Linebuf_alloc(fname, error);
-			} else
+				goto double_break;
+			case 't':
+				get_keytypes = 0;
+				tname = &argv[argno][1];
+				if (!*tname) {
+					if (++argno >= argc) {
+						error("missing types for `-t'");
+						return (NULL);
+					}
+					tname = argv[argno];
+				}
+				tname = strtok(tname, ",");
+				while (tname) {
+					int type = key_type_from_name(tname);
+					switch (type) {
+					case KEY_RSA1:
+						get_keytypes |= KT_RSA1;
+						break;
+					case KEY_DSA:
+						get_keytypes |= KT_DSA;
+						break;
+					case KEY_RSA:
+						get_keytypes |= KT_RSA;
+						break;
+					case KEY_UNSPEC:
+						fatal("unknown key type %s\n",
+						    tname);
+					}
+					tname = strtok(NULL, ",");
+				}
+				goto double_break;
+			case '4':
+				IPv4or6 = AF_INET;
+				break;
+			case '6':
+				IPv4or6 = AF_INET6;
+				break;
+			default:
 				error("ignoring invalid/misplaced option `%s'",
-				    argv[argno++]);
-		} else {
-			char *line;
-
-			line = Linebuf_getline(lb);
-			if (line)
-				return (line);
-			Linebuf_free(lb);
-			lb = NULL;
+				    argv[argno]);
+				goto double_break;
+			}
 		}
+double_break:
+		argno++;
 	}
 }
 
 static void
+fatal_callback(void *arg)
+{
+	if (nonfatal_fatal)
+		longjmp(kexjmp, -1);
+}
+
+static void
 usage(void)
 {
-	fatal("usage: %s [-t timeout] { [--] host | -f file } ...", __progname);
+	fatal("usage: %s [-v46] [-T timeout] { [-t type] [--] host | -f file } ...",
+	    __progname);
 	return;
 }
 
@@ -570,27 +721,53 @@
 main(int argc, char **argv)
 {
 	char *host = NULL;
+	int debug_flag = 0, log_level = SYSLOG_LEVEL_INFO;
 
 	TAILQ_INIT(&tq);
 
 	if (argc <= argno)
 		usage();
 
-	if (argv[1][0] == '-' && argv[1][1] == 't') {
-		argno++;
-		if (argv[1][2])
-			timeout = atoi(&argv[1][2]);
-		else {
-			if (argno >= argc)
-				usage();
-			timeout = atoi(argv[argno++]);
+	while (argv[argno][0] == '-') {
+		while (*++(argv[argno])) {
+			switch (argv[argno][0]) {
+			case 'T':
+				if (argv[argno][1])
+					timeout = atoi(&argv[argno][1]);
+				else {
+					if (++argno >= argc)
+						usage();
+					timeout = atoi(argv[argno]);
+				}
+				if (timeout <= 0)
+					usage();
+				goto double_break;
+			case 'v':
+				if (!debug_flag) {
+					debug_flag = 1;
+					log_level = SYSLOG_LEVEL_DEBUG1;
+				}
+				else if (log_level < SYSLOG_LEVEL_DEBUG3)
+					log_level++;
+				else
+					fatal("Too high debugging level.");
+				break;
+			default:
+				if (*--(argv[argno]) != '-')
+					fatal("Please separate options T and v from any other options.");
+				goto triple_break;
+			}
 		}
-		if (timeout <= 0)
-			usage();
+double_break:
+		argno++;
 	}
+triple_break:
 	if (argc <= argno)
 		usage();
 
+	log_init("ssh-keyscan", log_level, SYSLOG_FACILITY_USER, 1);
+	fatal_add_cleanup(fatal_callback, NULL);
+
 	maxfd = fdlim_get(1);
 	if (maxfd < 0)
 		fatal("%s: fdlim_get: bad value", __progname);
@@ -603,6 +780,9 @@
 	fdcon = xmalloc(maxfd * sizeof(con));
 	memset(fdcon, 0, maxfd * sizeof(con));
 
+	init_rng();
+	seed_rng();
+
 	read_wait_size = howmany(maxfd, NFDBITS) * sizeof(fd_mask);
 	read_wait = xmalloc(read_wait_size);
 	memset(read_wait, 0, read_wait_size);
@@ -610,12 +790,16 @@
 	do {
 		while (ncon < MAXCON) {
 			char *name;
+			int j;
 
 			host = nexthost(argc, argv);
 			if (host == NULL)
 				break;
 			name = strnnsep(&host, " \t\n");
-			conalloc(name, *host ? host : name);
+			for (j = KT_RSA1; j <= KT_RSA; j *= 2) {
+				if (get_keytypes & j)
+					conalloc(name, *host ? host : name, j);
+			}
 		}
 		conloop();
 	} while (host);


More information about the openssh-unix-dev mailing list