ssh-keyscan.c revision 149753
176259Sgreen/*
276259Sgreen * Copyright 1995, 1996 by David Mazieres <dm@lcs.mit.edu>.
376259Sgreen *
476259Sgreen * Modification and redistribution in source and binary forms is
576259Sgreen * permitted provided that due credit is given to the author and the
692555Sdes * OpenBSD project by leaving this copyright notice intact.
776259Sgreen */
876259Sgreen
976259Sgreen#include "includes.h"
10149753SdesRCSID("$OpenBSD: ssh-keyscan.c,v 1.55 2005/06/17 02:44:33 djm Exp $");
1176259Sgreen
12106130Sdes#include "openbsd-compat/sys-queue.h"
1376259Sgreen
1476259Sgreen#include <openssl/bn.h>
1576259Sgreen
1692555Sdes#include <setjmp.h>
1776259Sgreen#include "xmalloc.h"
1876259Sgreen#include "ssh.h"
1976259Sgreen#include "ssh1.h"
2076259Sgreen#include "key.h"
2192555Sdes#include "kex.h"
2292555Sdes#include "compat.h"
2392555Sdes#include "myproposal.h"
2492555Sdes#include "packet.h"
2592555Sdes#include "dispatch.h"
2676259Sgreen#include "buffer.h"
2776259Sgreen#include "bufaux.h"
2876259Sgreen#include "log.h"
2976259Sgreen#include "atomicio.h"
3092555Sdes#include "misc.h"
31147005Sdes#include "hostfile.h"
3276259Sgreen
3398941Sdes/* Flag indicating whether IPv4 or IPv6.  This can be set on the command line.
3498941Sdes   Default value is AF_UNSPEC means both IPv4 and IPv6. */
3598941Sdesint IPv4or6 = AF_UNSPEC;
3676259Sgreen
3792555Sdesint ssh_port = SSH_DEFAULT_PORT;
3876259Sgreen
3992555Sdes#define KT_RSA1	1
4092555Sdes#define KT_DSA	2
4192555Sdes#define KT_RSA	4
4292555Sdes
4392555Sdesint get_keytypes = KT_RSA1;	/* Get only RSA1 keys by default */
4492555Sdes
45147005Sdesint hash_hosts = 0;		/* Hash hostname on output */
46147005Sdes
4776259Sgreen#define MAXMAXFD 256
4876259Sgreen
4976259Sgreen/* The number of seconds after which to give up on a TCP connection */
5076259Sgreenint timeout = 5;
5176259Sgreen
5276259Sgreenint maxfd;
5376259Sgreen#define MAXCON (maxfd - 10)
5476259Sgreen
5576259Sgreenextern char *__progname;
5676259Sgreenfd_set *read_wait;
5776259Sgreensize_t read_wait_size;
5876259Sgreenint ncon;
5992555Sdesint nonfatal_fatal = 0;
6092555Sdesjmp_buf kexjmp;
6192555SdesKey *kexjmp_key;
6276259Sgreen
6376259Sgreen/*
6476259Sgreen * Keep a connection structure for each file descriptor.  The state
6576259Sgreen * associated with file descriptor n is held in fdcon[n].
6676259Sgreen */
6776259Sgreentypedef struct Connection {
6876259Sgreen	u_char c_status;	/* State of connection on this file desc. */
6976259Sgreen#define CS_UNUSED 0		/* File descriptor unused */
7076259Sgreen#define CS_CON 1		/* Waiting to connect/read greeting */
7176259Sgreen#define CS_SIZE 2		/* Waiting to read initial packet size */
7276259Sgreen#define CS_KEYS 3		/* Waiting to read public key packet */
7376259Sgreen	int c_fd;		/* Quick lookup: c->c_fd == c - fdcon */
7476259Sgreen	int c_plen;		/* Packet length field for ssh packet */
7576259Sgreen	int c_len;		/* Total bytes which must be read. */
7676259Sgreen	int c_off;		/* Length of data read so far. */
7792555Sdes	int c_keytype;		/* Only one of KT_RSA1, KT_DSA, or KT_RSA */
7876259Sgreen	char *c_namebase;	/* Address to free for c_name and c_namelist */
7976259Sgreen	char *c_name;		/* Hostname of connection for errors */
8076259Sgreen	char *c_namelist;	/* Pointer to other possible addresses */
8176259Sgreen	char *c_output_name;	/* Hostname of connection for output */
8276259Sgreen	char *c_data;		/* Data read from this fd */
8392555Sdes	Kex *c_kex;		/* The key-exchange struct for ssh2 */
8476259Sgreen	struct timeval c_tv;	/* Time at which connection gets aborted */
8576259Sgreen	TAILQ_ENTRY(Connection) c_link;	/* List of connections in timeout order. */
8676259Sgreen} con;
8776259Sgreen
8876259SgreenTAILQ_HEAD(conlist, Connection) tq;	/* Timeout Queue */
8976259Sgreencon *fdcon;
9076259Sgreen
9176259Sgreen/*
9276259Sgreen *  This is just a wrapper around fgets() to make it usable.
9376259Sgreen */
9476259Sgreen
9576259Sgreen/* Stress-test.  Increase this later. */
9676259Sgreen#define LINEBUF_SIZE 16
9776259Sgreen
9876259Sgreentypedef struct {
9976259Sgreen	char *buf;
10076259Sgreen	u_int size;
10176259Sgreen	int lineno;
10276259Sgreen	const char *filename;
10376259Sgreen	FILE *stream;
10476259Sgreen	void (*errfun) (const char *,...);
10576259Sgreen} Linebuf;
10676259Sgreen
10792555Sdesstatic Linebuf *
10876259SgreenLinebuf_alloc(const char *filename, void (*errfun) (const char *,...))
10976259Sgreen{
11076259Sgreen	Linebuf *lb;
11176259Sgreen
11276259Sgreen	if (!(lb = malloc(sizeof(*lb)))) {
11376259Sgreen		if (errfun)
114106130Sdes			(*errfun) ("linebuf (%s): malloc failed\n",
115106130Sdes			    filename ? filename : "(stdin)");
11676259Sgreen		return (NULL);
11776259Sgreen	}
11876259Sgreen	if (filename) {
11976259Sgreen		lb->filename = filename;
12076259Sgreen		if (!(lb->stream = fopen(filename, "r"))) {
12176259Sgreen			xfree(lb);
12276259Sgreen			if (errfun)
12376259Sgreen				(*errfun) ("%s: %s\n", filename, strerror(errno));
12476259Sgreen			return (NULL);
12576259Sgreen		}
12676259Sgreen	} else {
12776259Sgreen		lb->filename = "(stdin)";
12876259Sgreen		lb->stream = stdin;
12976259Sgreen	}
13076259Sgreen
13176259Sgreen	if (!(lb->buf = malloc(lb->size = LINEBUF_SIZE))) {
13276259Sgreen		if (errfun)
13376259Sgreen			(*errfun) ("linebuf (%s): malloc failed\n", lb->filename);
13476259Sgreen		xfree(lb);
13576259Sgreen		return (NULL);
13676259Sgreen	}
13776259Sgreen	lb->errfun = errfun;
13876259Sgreen	lb->lineno = 0;
13976259Sgreen	return (lb);
14076259Sgreen}
14176259Sgreen
14292555Sdesstatic void
14376259SgreenLinebuf_free(Linebuf * lb)
14476259Sgreen{
14576259Sgreen	fclose(lb->stream);
14676259Sgreen	xfree(lb->buf);
14776259Sgreen	xfree(lb);
14876259Sgreen}
14976259Sgreen
15092555Sdes#if 0
15192555Sdesstatic void
15276259SgreenLinebuf_restart(Linebuf * lb)
15376259Sgreen{
15476259Sgreen	clearerr(lb->stream);
15576259Sgreen	rewind(lb->stream);
15676259Sgreen	lb->lineno = 0;
15776259Sgreen}
15876259Sgreen
15992555Sdesstatic int
16076259SgreenLinebuf_lineno(Linebuf * lb)
16176259Sgreen{
16276259Sgreen	return (lb->lineno);
16376259Sgreen}
16492555Sdes#endif
16576259Sgreen
16692555Sdesstatic char *
16776259SgreenLinebuf_getline(Linebuf * lb)
16876259Sgreen{
169149753Sdes	size_t n = 0;
170106130Sdes	void *p;
17176259Sgreen
17276259Sgreen	lb->lineno++;
17376259Sgreen	for (;;) {
17476259Sgreen		/* Read a line */
17576259Sgreen		if (!fgets(&lb->buf[n], lb->size - n, lb->stream)) {
17676259Sgreen			if (ferror(lb->stream) && lb->errfun)
177106130Sdes				(*lb->errfun)("%s: %s\n", lb->filename,
17876259Sgreen				    strerror(errno));
17976259Sgreen			return (NULL);
18076259Sgreen		}
18176259Sgreen		n = strlen(lb->buf);
18276259Sgreen
18376259Sgreen		/* Return it or an error if it fits */
18476259Sgreen		if (n > 0 && lb->buf[n - 1] == '\n') {
18576259Sgreen			lb->buf[n - 1] = '\0';
18676259Sgreen			return (lb->buf);
18776259Sgreen		}
18876259Sgreen		if (n != lb->size - 1) {
18976259Sgreen			if (lb->errfun)
190106130Sdes				(*lb->errfun)("%s: skipping incomplete last line\n",
19176259Sgreen				    lb->filename);
19276259Sgreen			return (NULL);
19376259Sgreen		}
19476259Sgreen		/* Double the buffer if we need more space */
195106130Sdes		lb->size *= 2;
196106130Sdes		if ((p = realloc(lb->buf, lb->size)) == NULL) {
197106130Sdes			lb->size /= 2;
19876259Sgreen			if (lb->errfun)
199106130Sdes				(*lb->errfun)("linebuf (%s): realloc failed\n",
20076259Sgreen				    lb->filename);
20176259Sgreen			return (NULL);
20276259Sgreen		}
203106130Sdes		lb->buf = p;
20476259Sgreen	}
20576259Sgreen}
20676259Sgreen
20792555Sdesstatic int
20876259Sgreenfdlim_get(int hard)
20976259Sgreen{
21098941Sdes#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE)
21176259Sgreen	struct rlimit rlfd;
21276259Sgreen
21376259Sgreen	if (getrlimit(RLIMIT_NOFILE, &rlfd) < 0)
21476259Sgreen		return (-1);
21576259Sgreen	if ((hard ? rlfd.rlim_max : rlfd.rlim_cur) == RLIM_INFINITY)
216126277Sdes		return SSH_SYSFDMAX;
21776259Sgreen	else
21876259Sgreen		return hard ? rlfd.rlim_max : rlfd.rlim_cur;
21998941Sdes#else
220126277Sdes	return SSH_SYSFDMAX;
22198941Sdes#endif
22276259Sgreen}
22376259Sgreen
22492555Sdesstatic int
22576259Sgreenfdlim_set(int lim)
22676259Sgreen{
22798941Sdes#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
22876259Sgreen	struct rlimit rlfd;
22998941Sdes#endif
230106130Sdes
23176259Sgreen	if (lim <= 0)
23276259Sgreen		return (-1);
23398941Sdes#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
23476259Sgreen	if (getrlimit(RLIMIT_NOFILE, &rlfd) < 0)
23576259Sgreen		return (-1);
23676259Sgreen	rlfd.rlim_cur = lim;
23776259Sgreen	if (setrlimit(RLIMIT_NOFILE, &rlfd) < 0)
23876259Sgreen		return (-1);
23998941Sdes#elif defined (HAVE_SETDTABLESIZE)
24098941Sdes	setdtablesize(lim);
24198941Sdes#endif
24276259Sgreen	return (0);
24376259Sgreen}
24476259Sgreen
24576259Sgreen/*
24676259Sgreen * This is an strsep function that returns a null field for adjacent
24776259Sgreen * separators.  This is the same as the 4.4BSD strsep, but different from the
24876259Sgreen * one in the GNU libc.
24976259Sgreen */
25092555Sdesstatic char *
25176259Sgreenxstrsep(char **str, const char *delim)
25276259Sgreen{
25376259Sgreen	char *s, *e;
25476259Sgreen
25576259Sgreen	if (!**str)
25676259Sgreen		return (NULL);
25776259Sgreen
25876259Sgreen	s = *str;
25976259Sgreen	e = s + strcspn(s, delim);
26076259Sgreen
26176259Sgreen	if (*e != '\0')
26276259Sgreen		*e++ = '\0';
26376259Sgreen	*str = e;
26476259Sgreen
26576259Sgreen	return (s);
26676259Sgreen}
26776259Sgreen
26876259Sgreen/*
26976259Sgreen * Get the next non-null token (like GNU strsep).  Strsep() will return a
27076259Sgreen * null token for two adjacent separators, so we may have to loop.
27176259Sgreen */
27292555Sdesstatic char *
27376259Sgreenstrnnsep(char **stringp, char *delim)
27476259Sgreen{
27576259Sgreen	char *tok;
27676259Sgreen
27776259Sgreen	do {
27876259Sgreen		tok = xstrsep(stringp, delim);
27976259Sgreen	} while (tok && *tok == '\0');
28076259Sgreen	return (tok);
28176259Sgreen}
28276259Sgreen
28392555Sdesstatic Key *
28492555Sdeskeygrab_ssh1(con *c)
28576259Sgreen{
28676259Sgreen	static Key *rsa;
28776259Sgreen	static Buffer msg;
28876259Sgreen
28976259Sgreen	if (rsa == NULL) {
29076259Sgreen		buffer_init(&msg);
29176259Sgreen		rsa = key_new(KEY_RSA1);
29276259Sgreen	}
29392555Sdes	buffer_append(&msg, c->c_data, c->c_plen);
29492555Sdes	buffer_consume(&msg, 8 - (c->c_plen & 7));	/* padding */
29576259Sgreen	if (buffer_get_char(&msg) != (int) SSH_SMSG_PUBLIC_KEY) {
29692555Sdes		error("%s: invalid packet type", c->c_name);
29776259Sgreen		buffer_clear(&msg);
29892555Sdes		return NULL;
29976259Sgreen	}
30076259Sgreen	buffer_consume(&msg, 8);		/* cookie */
30176259Sgreen
30276259Sgreen	/* server key */
30376259Sgreen	(void) buffer_get_int(&msg);
30476259Sgreen	buffer_get_bignum(&msg, rsa->rsa->e);
30576259Sgreen	buffer_get_bignum(&msg, rsa->rsa->n);
30676259Sgreen
30776259Sgreen	/* host key */
30876259Sgreen	(void) buffer_get_int(&msg);
30976259Sgreen	buffer_get_bignum(&msg, rsa->rsa->e);
31076259Sgreen	buffer_get_bignum(&msg, rsa->rsa->n);
31192555Sdes
31276259Sgreen	buffer_clear(&msg);
31376259Sgreen
31492555Sdes	return (rsa);
31592555Sdes}
31692555Sdes
31792555Sdesstatic int
31892555Sdeshostjump(Key *hostkey)
31992555Sdes{
32092555Sdes	kexjmp_key = hostkey;
32192555Sdes	longjmp(kexjmp, 1);
32292555Sdes}
32392555Sdes
32492555Sdesstatic int
32592555Sdesssh2_capable(int remote_major, int remote_minor)
32692555Sdes{
32792555Sdes	switch (remote_major) {
32892555Sdes	case 1:
32992555Sdes		if (remote_minor == 99)
33092555Sdes			return 1;
33192555Sdes		break;
33292555Sdes	case 2:
33392555Sdes		return 1;
33492555Sdes	default:
33592555Sdes		break;
33692555Sdes	}
33792555Sdes	return 0;
33892555Sdes}
33992555Sdes
34092555Sdesstatic Key *
34192555Sdeskeygrab_ssh2(con *c)
34292555Sdes{
34392555Sdes	int j;
34492555Sdes
34592555Sdes	packet_set_connection(c->c_fd, c->c_fd);
34692555Sdes	enable_compat20();
34792555Sdes	myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = c->c_keytype == KT_DSA?
34892555Sdes	    "ssh-dss": "ssh-rsa";
34992555Sdes	c->c_kex = kex_setup(myproposal);
350113911Sdes	c->c_kex->kex[KEX_DH_GRP1_SHA1] = kexdh_client;
351137019Sdes	c->c_kex->kex[KEX_DH_GRP14_SHA1] = kexdh_client;
352113911Sdes	c->c_kex->kex[KEX_DH_GEX_SHA1] = kexgex_client;
35392555Sdes	c->c_kex->verify_host_key = hostjump;
35492555Sdes
35592555Sdes	if (!(j = setjmp(kexjmp))) {
35692555Sdes		nonfatal_fatal = 1;
35792555Sdes		dispatch_run(DISPATCH_BLOCK, &c->c_kex->done, c->c_kex);
35892555Sdes		fprintf(stderr, "Impossible! dispatch_run() returned!\n");
35992555Sdes		exit(1);
36092555Sdes	}
36192555Sdes	nonfatal_fatal = 0;
36292555Sdes	xfree(c->c_kex);
36392555Sdes	c->c_kex = NULL;
36492555Sdes	packet_close();
36592555Sdes
36692555Sdes	return j < 0? NULL : kexjmp_key;
36792555Sdes}
36892555Sdes
36992555Sdesstatic void
37092555Sdeskeyprint(con *c, Key *key)
37192555Sdes{
372147005Sdes	char *host = c->c_output_name ? c->c_output_name : c->c_name;
373147005Sdes
37492555Sdes	if (!key)
37592555Sdes		return;
376147005Sdes	if (hash_hosts && (host = host_hash(host, NULL, 0)) == NULL)
377147005Sdes		fatal("host_hash failed");
37892555Sdes
379147005Sdes	fprintf(stdout, "%s ", host);
38092555Sdes	key_write(key, stdout);
38176259Sgreen	fputs("\n", stdout);
38276259Sgreen}
38376259Sgreen
38492555Sdesstatic int
38576259Sgreentcpconnect(char *host)
38676259Sgreen{
38776259Sgreen	struct addrinfo hints, *ai, *aitop;
38876259Sgreen	char strport[NI_MAXSERV];
38976259Sgreen	int gaierr, s = -1;
39076259Sgreen
39192555Sdes	snprintf(strport, sizeof strport, "%d", ssh_port);
39276259Sgreen	memset(&hints, 0, sizeof(hints));
39392555Sdes	hints.ai_family = IPv4or6;
39476259Sgreen	hints.ai_socktype = SOCK_STREAM;
39576259Sgreen	if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0)
39676259Sgreen		fatal("getaddrinfo %s: %s", host, gai_strerror(gaierr));
39776259Sgreen	for (ai = aitop; ai; ai = ai->ai_next) {
398124211Sdes		s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
39976259Sgreen		if (s < 0) {
40076259Sgreen			error("socket: %s", strerror(errno));
40176259Sgreen			continue;
40276259Sgreen		}
403137019Sdes		if (set_nonblock(s) == -1)
404137019Sdes			fatal("%s: set_nonblock(%d)", __func__, s);
40576259Sgreen		if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0 &&
40676259Sgreen		    errno != EINPROGRESS)
40776259Sgreen			error("connect (`%s'): %s", host, strerror(errno));
40876259Sgreen		else
40976259Sgreen			break;
41076259Sgreen		close(s);
41176259Sgreen		s = -1;
41276259Sgreen	}
41376259Sgreen	freeaddrinfo(aitop);
41476259Sgreen	return s;
41576259Sgreen}
41676259Sgreen
41792555Sdesstatic int
41892555Sdesconalloc(char *iname, char *oname, int keytype)
41976259Sgreen{
420106130Sdes	char *namebase, *name, *namelist;
42176259Sgreen	int s;
42276259Sgreen
42376259Sgreen	namebase = namelist = xstrdup(iname);
42476259Sgreen
42576259Sgreen	do {
42676259Sgreen		name = xstrsep(&namelist, ",");
42776259Sgreen		if (!name) {
42876259Sgreen			xfree(namebase);
42976259Sgreen			return (-1);
43076259Sgreen		}
43176259Sgreen	} while ((s = tcpconnect(name)) < 0);
43276259Sgreen
43376259Sgreen	if (s >= maxfd)
43476259Sgreen		fatal("conalloc: fdno %d too high", s);
43576259Sgreen	if (fdcon[s].c_status)
43676259Sgreen		fatal("conalloc: attempt to reuse fdno %d", s);
43776259Sgreen
43876259Sgreen	fdcon[s].c_fd = s;
43976259Sgreen	fdcon[s].c_status = CS_CON;
44076259Sgreen	fdcon[s].c_namebase = namebase;
44176259Sgreen	fdcon[s].c_name = name;
44276259Sgreen	fdcon[s].c_namelist = namelist;
44376259Sgreen	fdcon[s].c_output_name = xstrdup(oname);
44476259Sgreen	fdcon[s].c_data = (char *) &fdcon[s].c_plen;
44576259Sgreen	fdcon[s].c_len = 4;
44676259Sgreen	fdcon[s].c_off = 0;
44792555Sdes	fdcon[s].c_keytype = keytype;
44876259Sgreen	gettimeofday(&fdcon[s].c_tv, NULL);
44976259Sgreen	fdcon[s].c_tv.tv_sec += timeout;
45076259Sgreen	TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link);
45176259Sgreen	FD_SET(s, read_wait);
45276259Sgreen	ncon++;
45376259Sgreen	return (s);
45476259Sgreen}
45576259Sgreen
45692555Sdesstatic void
45776259Sgreenconfree(int s)
45876259Sgreen{
45976259Sgreen	if (s >= maxfd || fdcon[s].c_status == CS_UNUSED)
46076259Sgreen		fatal("confree: attempt to free bad fdno %d", s);
46176259Sgreen	close(s);
46276259Sgreen	xfree(fdcon[s].c_namebase);
46376259Sgreen	xfree(fdcon[s].c_output_name);
46476259Sgreen	if (fdcon[s].c_status == CS_KEYS)
46576259Sgreen		xfree(fdcon[s].c_data);
46676259Sgreen	fdcon[s].c_status = CS_UNUSED;
46792555Sdes	fdcon[s].c_keytype = 0;
46876259Sgreen	TAILQ_REMOVE(&tq, &fdcon[s], c_link);
46976259Sgreen	FD_CLR(s, read_wait);
47076259Sgreen	ncon--;
47176259Sgreen}
47276259Sgreen
47392555Sdesstatic void
47476259Sgreencontouch(int s)
47576259Sgreen{
47676259Sgreen	TAILQ_REMOVE(&tq, &fdcon[s], c_link);
47776259Sgreen	gettimeofday(&fdcon[s].c_tv, NULL);
47876259Sgreen	fdcon[s].c_tv.tv_sec += timeout;
47976259Sgreen	TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link);
48076259Sgreen}
48176259Sgreen
48292555Sdesstatic int
48376259Sgreenconrecycle(int s)
48476259Sgreen{
485106130Sdes	con *c = &fdcon[s];
48676259Sgreen	int ret;
48776259Sgreen
48892555Sdes	ret = conalloc(c->c_namelist, c->c_output_name, c->c_keytype);
48976259Sgreen	confree(s);
49076259Sgreen	return (ret);
49176259Sgreen}
49276259Sgreen
49392555Sdesstatic void
49476259Sgreencongreet(int s)
49576259Sgreen{
496149753Sdes	int n = 0, remote_major = 0, remote_minor = 0;
49792555Sdes	char buf[256], *cp;
49892555Sdes	char remote_version[sizeof buf];
49976259Sgreen	size_t bufsiz;
50076259Sgreen	con *c = &fdcon[s];
50176259Sgreen
50276259Sgreen	bufsiz = sizeof(buf);
50376259Sgreen	cp = buf;
504137019Sdes	while (bufsiz-- && (n = atomicio(read, s, cp, 1)) == 1 && *cp != '\n') {
50592555Sdes		if (*cp == '\r')
50692555Sdes			*cp = '\n';
50776259Sgreen		cp++;
50892555Sdes	}
509149753Sdes	if (n == 0) {
510149753Sdes		switch (errno) {
511149753Sdes		case EPIPE:
512149753Sdes			error("%s: Connection closed by remote host", c->c_name);
513149753Sdes			break;
514149753Sdes		case ECONNREFUSED:
515149753Sdes			break;
516149753Sdes		default:
51776259Sgreen			error("read (%s): %s", c->c_name, strerror(errno));
518149753Sdes			break;
519149753Sdes		}
52076259Sgreen		conrecycle(s);
52176259Sgreen		return;
52276259Sgreen	}
52376259Sgreen	if (*cp != '\n' && *cp != '\r') {
52476259Sgreen		error("%s: bad greeting", c->c_name);
52576259Sgreen		confree(s);
52676259Sgreen		return;
52776259Sgreen	}
52876259Sgreen	*cp = '\0';
52992555Sdes	if (sscanf(buf, "SSH-%d.%d-%[^\n]\n",
53092555Sdes	    &remote_major, &remote_minor, remote_version) == 3)
53192555Sdes		compat_datafellows(remote_version);
53292555Sdes	else
53392555Sdes		datafellows = 0;
53492555Sdes	if (c->c_keytype != KT_RSA1) {
53592555Sdes		if (!ssh2_capable(remote_major, remote_minor)) {
53692555Sdes			debug("%s doesn't support ssh2", c->c_name);
53792555Sdes			confree(s);
53892555Sdes			return;
53992555Sdes		}
54092555Sdes	} else if (remote_major != 1) {
54192555Sdes		debug("%s doesn't support ssh1", c->c_name);
54292555Sdes		confree(s);
54392555Sdes		return;
54492555Sdes	}
54592555Sdes	fprintf(stderr, "# %s %s\n", c->c_name, chop(buf));
54692555Sdes	n = snprintf(buf, sizeof buf, "SSH-%d.%d-OpenSSH-keyscan\r\n",
54792555Sdes	    c->c_keytype == KT_RSA1? PROTOCOL_MAJOR_1 : PROTOCOL_MAJOR_2,
54892555Sdes	    c->c_keytype == KT_RSA1? PROTOCOL_MINOR_1 : PROTOCOL_MINOR_2);
549149753Sdes	if (n < 0 || (size_t)n >= sizeof(buf)) {
550149753Sdes		error("snprintf: buffer too small");
551149753Sdes		confree(s);
552149753Sdes		return;
553149753Sdes	}
554149753Sdes	if (atomicio(vwrite, s, buf, n) != (size_t)n) {
55576259Sgreen		error("write (%s): %s", c->c_name, strerror(errno));
55676259Sgreen		confree(s);
55776259Sgreen		return;
55876259Sgreen	}
55992555Sdes	if (c->c_keytype != KT_RSA1) {
56092555Sdes		keyprint(c, keygrab_ssh2(c));
56192555Sdes		confree(s);
56292555Sdes		return;
56392555Sdes	}
56476259Sgreen	c->c_status = CS_SIZE;
56576259Sgreen	contouch(s);
56676259Sgreen}
56776259Sgreen
56892555Sdesstatic void
56976259Sgreenconread(int s)
57076259Sgreen{
571106130Sdes	con *c = &fdcon[s];
572149753Sdes	size_t n;
57376259Sgreen
57476259Sgreen	if (c->c_status == CS_CON) {
57576259Sgreen		congreet(s);
57676259Sgreen		return;
57776259Sgreen	}
578137019Sdes	n = atomicio(read, s, c->c_data + c->c_off, c->c_len - c->c_off);
579149753Sdes	if (n == 0) {
58076259Sgreen		error("read (%s): %s", c->c_name, strerror(errno));
58176259Sgreen		confree(s);
58276259Sgreen		return;
58376259Sgreen	}
58476259Sgreen	c->c_off += n;
58576259Sgreen
58676259Sgreen	if (c->c_off == c->c_len)
58776259Sgreen		switch (c->c_status) {
58876259Sgreen		case CS_SIZE:
58976259Sgreen			c->c_plen = htonl(c->c_plen);
59076259Sgreen			c->c_len = c->c_plen + 8 - (c->c_plen & 7);
59176259Sgreen			c->c_off = 0;
59276259Sgreen			c->c_data = xmalloc(c->c_len);
59376259Sgreen			c->c_status = CS_KEYS;
59476259Sgreen			break;
59576259Sgreen		case CS_KEYS:
59692555Sdes			keyprint(c, keygrab_ssh1(c));
59776259Sgreen			confree(s);
59876259Sgreen			return;
59976259Sgreen			break;
60076259Sgreen		default:
60176259Sgreen			fatal("conread: invalid status %d", c->c_status);
60276259Sgreen			break;
60376259Sgreen		}
60476259Sgreen
60576259Sgreen	contouch(s);
60676259Sgreen}
60776259Sgreen
60892555Sdesstatic void
60976259Sgreenconloop(void)
61076259Sgreen{
611106130Sdes	struct timeval seltime, now;
61276259Sgreen	fd_set *r, *e;
613106130Sdes	con *c;
61476259Sgreen	int i;
61576259Sgreen
61676259Sgreen	gettimeofday(&now, NULL);
61798675Sdes	c = TAILQ_FIRST(&tq);
61876259Sgreen
61976259Sgreen	if (c && (c->c_tv.tv_sec > now.tv_sec ||
62076259Sgreen	    (c->c_tv.tv_sec == now.tv_sec && c->c_tv.tv_usec > now.tv_usec))) {
62176259Sgreen		seltime = c->c_tv;
62276259Sgreen		seltime.tv_sec -= now.tv_sec;
62376259Sgreen		seltime.tv_usec -= now.tv_usec;
62476259Sgreen		if (seltime.tv_usec < 0) {
62576259Sgreen			seltime.tv_usec += 1000000;
62676259Sgreen			seltime.tv_sec--;
62776259Sgreen		}
62876259Sgreen	} else
62976259Sgreen		seltime.tv_sec = seltime.tv_usec = 0;
63076259Sgreen
63176259Sgreen	r = xmalloc(read_wait_size);
63276259Sgreen	memcpy(r, read_wait, read_wait_size);
63376259Sgreen	e = xmalloc(read_wait_size);
63476259Sgreen	memcpy(e, read_wait, read_wait_size);
63576259Sgreen
63676259Sgreen	while (select(maxfd, r, NULL, e, &seltime) == -1 &&
63776259Sgreen	    (errno == EAGAIN || errno == EINTR))
63876259Sgreen		;
63976259Sgreen
64076259Sgreen	for (i = 0; i < maxfd; i++) {
64176259Sgreen		if (FD_ISSET(i, e)) {
64276259Sgreen			error("%s: exception!", fdcon[i].c_name);
64376259Sgreen			confree(i);
64476259Sgreen		} else if (FD_ISSET(i, r))
64576259Sgreen			conread(i);
64676259Sgreen	}
64776259Sgreen	xfree(r);
64876259Sgreen	xfree(e);
64976259Sgreen
65098675Sdes	c = TAILQ_FIRST(&tq);
65176259Sgreen	while (c && (c->c_tv.tv_sec < now.tv_sec ||
65276259Sgreen	    (c->c_tv.tv_sec == now.tv_sec && c->c_tv.tv_usec < now.tv_usec))) {
65376259Sgreen		int s = c->c_fd;
65476259Sgreen
65598675Sdes		c = TAILQ_NEXT(c, c_link);
65676259Sgreen		conrecycle(s);
65776259Sgreen	}
65876259Sgreen}
65976259Sgreen
66092555Sdesstatic void
66192555Sdesdo_host(char *host)
66276259Sgreen{
66392555Sdes	char *name = strnnsep(&host, " \t\n");
66492555Sdes	int j;
66576259Sgreen
66692555Sdes	if (name == NULL)
66792555Sdes		return;
66892555Sdes	for (j = KT_RSA1; j <= KT_RSA; j *= 2) {
66992555Sdes		if (get_keytypes & j) {
67092555Sdes			while (ncon >= MAXCON)
67192555Sdes				conloop();
67292555Sdes			conalloc(name, *host ? host : name, j);
67376259Sgreen		}
67476259Sgreen	}
67576259Sgreen}
67676259Sgreen
67776259Sgreenvoid
67892555Sdesfatal(const char *fmt,...)
67992555Sdes{
68092555Sdes	va_list args;
681106130Sdes
68292555Sdes	va_start(args, fmt);
68392555Sdes	do_log(SYSLOG_LEVEL_FATAL, fmt, args);
68492555Sdes	va_end(args);
68592555Sdes	if (nonfatal_fatal)
68692555Sdes		longjmp(kexjmp, -1);
68792555Sdes	else
688126277Sdes		exit(255);
68992555Sdes}
69092555Sdes
69192555Sdesstatic void
69276259Sgreenusage(void)
69376259Sgreen{
694147005Sdes	fprintf(stderr, "usage: %s [-46Hv] [-f file] [-p port] [-T timeout] [-t type]\n"
695106130Sdes	    "\t\t   [host | addrlist namelist] [...]\n",
69692555Sdes	    __progname);
69792555Sdes	exit(1);
69876259Sgreen}
69976259Sgreen
70076259Sgreenint
70176259Sgreenmain(int argc, char **argv)
70276259Sgreen{
70392555Sdes	int debug_flag = 0, log_level = SYSLOG_LEVEL_INFO;
70492555Sdes	int opt, fopt_count = 0;
70592555Sdes	char *tname;
70676259Sgreen
70792555Sdes	extern int optind;
70892555Sdes	extern char *optarg;
70992555Sdes
710124211Sdes	__progname = ssh_get_progname(argv[0]);
71198941Sdes	init_rng();
71298941Sdes	seed_rng();
71376259Sgreen	TAILQ_INIT(&tq);
71476259Sgreen
71592555Sdes	if (argc <= 1)
71676259Sgreen		usage();
71776259Sgreen
718147005Sdes	while ((opt = getopt(argc, argv, "Hv46p:T:t:f:")) != -1) {
71992555Sdes		switch (opt) {
720147005Sdes		case 'H':
721147005Sdes			hash_hosts = 1;
722147005Sdes			break;
72392555Sdes		case 'p':
72492555Sdes			ssh_port = a2port(optarg);
72592555Sdes			if (ssh_port == 0) {
72692555Sdes				fprintf(stderr, "Bad port '%s'\n", optarg);
72792555Sdes				exit(1);
72892555Sdes			}
72992555Sdes			break;
73092555Sdes		case 'T':
731106130Sdes			timeout = convtime(optarg);
732106130Sdes			if (timeout == -1 || timeout == 0) {
733106130Sdes				fprintf(stderr, "Bad timeout '%s'\n", optarg);
73476259Sgreen				usage();
735106130Sdes			}
73692555Sdes			break;
73792555Sdes		case 'v':
73892555Sdes			if (!debug_flag) {
73992555Sdes				debug_flag = 1;
74092555Sdes				log_level = SYSLOG_LEVEL_DEBUG1;
74192555Sdes			}
74292555Sdes			else if (log_level < SYSLOG_LEVEL_DEBUG3)
74392555Sdes				log_level++;
74492555Sdes			else
74592555Sdes				fatal("Too high debugging level.");
74692555Sdes			break;
74792555Sdes		case 'f':
74892555Sdes			if (strcmp(optarg, "-") == 0)
74992555Sdes				optarg = NULL;
75092555Sdes			argv[fopt_count++] = optarg;
75192555Sdes			break;
75292555Sdes		case 't':
75392555Sdes			get_keytypes = 0;
75492555Sdes			tname = strtok(optarg, ",");
75592555Sdes			while (tname) {
75692555Sdes				int type = key_type_from_name(tname);
75792555Sdes				switch (type) {
75892555Sdes				case KEY_RSA1:
75992555Sdes					get_keytypes |= KT_RSA1;
76092555Sdes					break;
76192555Sdes				case KEY_DSA:
76292555Sdes					get_keytypes |= KT_DSA;
76392555Sdes					break;
76492555Sdes				case KEY_RSA:
76592555Sdes					get_keytypes |= KT_RSA;
76692555Sdes					break;
76792555Sdes				case KEY_UNSPEC:
76892555Sdes					fatal("unknown key type %s", tname);
76992555Sdes				}
77092555Sdes				tname = strtok(NULL, ",");
77192555Sdes			}
77292555Sdes			break;
77392555Sdes		case '4':
77492555Sdes			IPv4or6 = AF_INET;
77592555Sdes			break;
77692555Sdes		case '6':
77792555Sdes			IPv4or6 = AF_INET6;
77892555Sdes			break;
77992555Sdes		case '?':
78092555Sdes		default:
78192555Sdes			usage();
78276259Sgreen		}
78376259Sgreen	}
78492555Sdes	if (optind == argc && !fopt_count)
78576259Sgreen		usage();
78676259Sgreen
78792555Sdes	log_init("ssh-keyscan", log_level, SYSLOG_FACILITY_USER, 1);
78892555Sdes
78976259Sgreen	maxfd = fdlim_get(1);
79076259Sgreen	if (maxfd < 0)
79176259Sgreen		fatal("%s: fdlim_get: bad value", __progname);
79276259Sgreen	if (maxfd > MAXMAXFD)
79376259Sgreen		maxfd = MAXMAXFD;
79476259Sgreen	if (MAXCON <= 0)
79576259Sgreen		fatal("%s: not enough file descriptors", __progname);
79676259Sgreen	if (maxfd > fdlim_get(0))
79776259Sgreen		fdlim_set(maxfd);
79876259Sgreen	fdcon = xmalloc(maxfd * sizeof(con));
79976259Sgreen	memset(fdcon, 0, maxfd * sizeof(con));
80076259Sgreen
80176259Sgreen	read_wait_size = howmany(maxfd, NFDBITS) * sizeof(fd_mask);
80276259Sgreen	read_wait = xmalloc(read_wait_size);
80376259Sgreen	memset(read_wait, 0, read_wait_size);
80476259Sgreen
80592555Sdes	if (fopt_count) {
80692555Sdes		Linebuf *lb;
80792555Sdes		char *line;
80892555Sdes		int j;
80976259Sgreen
81092555Sdes		for (j = 0; j < fopt_count; j++) {
81192555Sdes			lb = Linebuf_alloc(argv[j], error);
81292555Sdes			if (!lb)
81392555Sdes				continue;
81492555Sdes			while ((line = Linebuf_getline(lb)) != NULL)
81592555Sdes				do_host(line);
81692555Sdes			Linebuf_free(lb);
81776259Sgreen		}
81892555Sdes	}
81992555Sdes
82092555Sdes	while (optind < argc)
82192555Sdes		do_host(argv[optind++]);
82292555Sdes
82376259Sgreen	while (ncon > 0)
82476259Sgreen		conloop();
82576259Sgreen
82676259Sgreen	return (0);
82776259Sgreen}
828