ssh-keyscan.c revision 106130
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"
10106130SdesRCSID("$OpenBSD: ssh-keyscan.c,v 1.40 2002/07/06 17:47:58 stevesk Exp $");
11106130SdesRCSID("$FreeBSD: head/crypto/openssh/ssh-keyscan.c 106130 2002-10-29 10:16:02Z des $");
1276259Sgreen
13106130Sdes#include "openbsd-compat/sys-queue.h"
1476259Sgreen
1576259Sgreen#include <openssl/bn.h>
1676259Sgreen
1792555Sdes#include <setjmp.h>
1876259Sgreen#include "xmalloc.h"
1976259Sgreen#include "ssh.h"
2076259Sgreen#include "ssh1.h"
2176259Sgreen#include "key.h"
2292555Sdes#include "kex.h"
2392555Sdes#include "compat.h"
2492555Sdes#include "myproposal.h"
2592555Sdes#include "packet.h"
2692555Sdes#include "dispatch.h"
2776259Sgreen#include "buffer.h"
2876259Sgreen#include "bufaux.h"
2976259Sgreen#include "log.h"
3076259Sgreen#include "atomicio.h"
3192555Sdes#include "misc.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. */
3598941Sdes#ifdef IPV4_DEFAULT
3698941Sdesint IPv4or6 = AF_INET;
3798941Sdes#else
3898941Sdesint IPv4or6 = AF_UNSPEC;
3998941Sdes#endif
4076259Sgreen
4192555Sdesint ssh_port = SSH_DEFAULT_PORT;
4276259Sgreen
4392555Sdes#define KT_RSA1	1
4492555Sdes#define KT_DSA	2
4592555Sdes#define KT_RSA	4
4692555Sdes
4792555Sdesint get_keytypes = KT_RSA1;	/* Get only RSA1 keys by default */
4892555Sdes
4976259Sgreen#define MAXMAXFD 256
5076259Sgreen
5176259Sgreen/* The number of seconds after which to give up on a TCP connection */
5276259Sgreenint timeout = 5;
5376259Sgreen
5476259Sgreenint maxfd;
5576259Sgreen#define MAXCON (maxfd - 10)
5676259Sgreen
5798941Sdes#ifdef HAVE___PROGNAME
5876259Sgreenextern char *__progname;
5998941Sdes#else
6098941Sdeschar *__progname;
6198941Sdes#endif
6276259Sgreenfd_set *read_wait;
6376259Sgreensize_t read_wait_size;
6476259Sgreenint ncon;
6592555Sdesint nonfatal_fatal = 0;
6692555Sdesjmp_buf kexjmp;
6792555SdesKey *kexjmp_key;
6876259Sgreen
6976259Sgreen/*
7076259Sgreen * Keep a connection structure for each file descriptor.  The state
7176259Sgreen * associated with file descriptor n is held in fdcon[n].
7276259Sgreen */
7376259Sgreentypedef struct Connection {
7476259Sgreen	u_char c_status;	/* State of connection on this file desc. */
7576259Sgreen#define CS_UNUSED 0		/* File descriptor unused */
7676259Sgreen#define CS_CON 1		/* Waiting to connect/read greeting */
7776259Sgreen#define CS_SIZE 2		/* Waiting to read initial packet size */
7876259Sgreen#define CS_KEYS 3		/* Waiting to read public key packet */
7976259Sgreen	int c_fd;		/* Quick lookup: c->c_fd == c - fdcon */
8076259Sgreen	int c_plen;		/* Packet length field for ssh packet */
8176259Sgreen	int c_len;		/* Total bytes which must be read. */
8276259Sgreen	int c_off;		/* Length of data read so far. */
8392555Sdes	int c_keytype;		/* Only one of KT_RSA1, KT_DSA, or KT_RSA */
8476259Sgreen	char *c_namebase;	/* Address to free for c_name and c_namelist */
8576259Sgreen	char *c_name;		/* Hostname of connection for errors */
8676259Sgreen	char *c_namelist;	/* Pointer to other possible addresses */
8776259Sgreen	char *c_output_name;	/* Hostname of connection for output */
8876259Sgreen	char *c_data;		/* Data read from this fd */
8992555Sdes	Kex *c_kex;		/* The key-exchange struct for ssh2 */
9076259Sgreen	struct timeval c_tv;	/* Time at which connection gets aborted */
9176259Sgreen	TAILQ_ENTRY(Connection) c_link;	/* List of connections in timeout order. */
9276259Sgreen} con;
9376259Sgreen
9476259SgreenTAILQ_HEAD(conlist, Connection) tq;	/* Timeout Queue */
9576259Sgreencon *fdcon;
9676259Sgreen
9776259Sgreen/*
9876259Sgreen *  This is just a wrapper around fgets() to make it usable.
9976259Sgreen */
10076259Sgreen
10176259Sgreen/* Stress-test.  Increase this later. */
10276259Sgreen#define LINEBUF_SIZE 16
10376259Sgreen
10476259Sgreentypedef struct {
10576259Sgreen	char *buf;
10676259Sgreen	u_int size;
10776259Sgreen	int lineno;
10876259Sgreen	const char *filename;
10976259Sgreen	FILE *stream;
11076259Sgreen	void (*errfun) (const char *,...);
11176259Sgreen} Linebuf;
11276259Sgreen
11392555Sdesstatic Linebuf *
11476259SgreenLinebuf_alloc(const char *filename, void (*errfun) (const char *,...))
11576259Sgreen{
11676259Sgreen	Linebuf *lb;
11776259Sgreen
11876259Sgreen	if (!(lb = malloc(sizeof(*lb)))) {
11976259Sgreen		if (errfun)
120106130Sdes			(*errfun) ("linebuf (%s): malloc failed\n",
121106130Sdes			    filename ? filename : "(stdin)");
12276259Sgreen		return (NULL);
12376259Sgreen	}
12476259Sgreen	if (filename) {
12576259Sgreen		lb->filename = filename;
12676259Sgreen		if (!(lb->stream = fopen(filename, "r"))) {
12776259Sgreen			xfree(lb);
12876259Sgreen			if (errfun)
12976259Sgreen				(*errfun) ("%s: %s\n", filename, strerror(errno));
13076259Sgreen			return (NULL);
13176259Sgreen		}
13276259Sgreen	} else {
13376259Sgreen		lb->filename = "(stdin)";
13476259Sgreen		lb->stream = stdin;
13576259Sgreen	}
13676259Sgreen
13776259Sgreen	if (!(lb->buf = malloc(lb->size = LINEBUF_SIZE))) {
13876259Sgreen		if (errfun)
13976259Sgreen			(*errfun) ("linebuf (%s): malloc failed\n", lb->filename);
14076259Sgreen		xfree(lb);
14176259Sgreen		return (NULL);
14276259Sgreen	}
14376259Sgreen	lb->errfun = errfun;
14476259Sgreen	lb->lineno = 0;
14576259Sgreen	return (lb);
14676259Sgreen}
14776259Sgreen
14892555Sdesstatic void
14976259SgreenLinebuf_free(Linebuf * lb)
15076259Sgreen{
15176259Sgreen	fclose(lb->stream);
15276259Sgreen	xfree(lb->buf);
15376259Sgreen	xfree(lb);
15476259Sgreen}
15576259Sgreen
15692555Sdes#if 0
15792555Sdesstatic void
15876259SgreenLinebuf_restart(Linebuf * lb)
15976259Sgreen{
16076259Sgreen	clearerr(lb->stream);
16176259Sgreen	rewind(lb->stream);
16276259Sgreen	lb->lineno = 0;
16376259Sgreen}
16476259Sgreen
16592555Sdesstatic int
16676259SgreenLinebuf_lineno(Linebuf * lb)
16776259Sgreen{
16876259Sgreen	return (lb->lineno);
16976259Sgreen}
17092555Sdes#endif
17176259Sgreen
17292555Sdesstatic char *
17376259SgreenLinebuf_getline(Linebuf * lb)
17476259Sgreen{
17576259Sgreen	int n = 0;
176106130Sdes	void *p;
17776259Sgreen
17876259Sgreen	lb->lineno++;
17976259Sgreen	for (;;) {
18076259Sgreen		/* Read a line */
18176259Sgreen		if (!fgets(&lb->buf[n], lb->size - n, lb->stream)) {
18276259Sgreen			if (ferror(lb->stream) && lb->errfun)
183106130Sdes				(*lb->errfun)("%s: %s\n", lb->filename,
18476259Sgreen				    strerror(errno));
18576259Sgreen			return (NULL);
18676259Sgreen		}
18776259Sgreen		n = strlen(lb->buf);
18876259Sgreen
18976259Sgreen		/* Return it or an error if it fits */
19076259Sgreen		if (n > 0 && lb->buf[n - 1] == '\n') {
19176259Sgreen			lb->buf[n - 1] = '\0';
19276259Sgreen			return (lb->buf);
19376259Sgreen		}
19476259Sgreen		if (n != lb->size - 1) {
19576259Sgreen			if (lb->errfun)
196106130Sdes				(*lb->errfun)("%s: skipping incomplete last line\n",
19776259Sgreen				    lb->filename);
19876259Sgreen			return (NULL);
19976259Sgreen		}
20076259Sgreen		/* Double the buffer if we need more space */
201106130Sdes		lb->size *= 2;
202106130Sdes		if ((p = realloc(lb->buf, lb->size)) == NULL) {
203106130Sdes			lb->size /= 2;
20476259Sgreen			if (lb->errfun)
205106130Sdes				(*lb->errfun)("linebuf (%s): realloc failed\n",
20676259Sgreen				    lb->filename);
20776259Sgreen			return (NULL);
20876259Sgreen		}
209106130Sdes		lb->buf = p;
21076259Sgreen	}
21176259Sgreen}
21276259Sgreen
21392555Sdesstatic int
21476259Sgreenfdlim_get(int hard)
21576259Sgreen{
21698941Sdes#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE)
21776259Sgreen	struct rlimit rlfd;
21876259Sgreen
21976259Sgreen	if (getrlimit(RLIMIT_NOFILE, &rlfd) < 0)
22076259Sgreen		return (-1);
22176259Sgreen	if ((hard ? rlfd.rlim_max : rlfd.rlim_cur) == RLIM_INFINITY)
22276259Sgreen		return 10000;
22376259Sgreen	else
22476259Sgreen		return hard ? rlfd.rlim_max : rlfd.rlim_cur;
22598941Sdes#elif defined (HAVE_SYSCONF)
22698941Sdes	return sysconf (_SC_OPEN_MAX);
22798941Sdes#else
22898941Sdes	return 10000;
22998941Sdes#endif
23076259Sgreen}
23176259Sgreen
23292555Sdesstatic int
23376259Sgreenfdlim_set(int lim)
23476259Sgreen{
23598941Sdes#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
23676259Sgreen	struct rlimit rlfd;
23798941Sdes#endif
238106130Sdes
23976259Sgreen	if (lim <= 0)
24076259Sgreen		return (-1);
24198941Sdes#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
24276259Sgreen	if (getrlimit(RLIMIT_NOFILE, &rlfd) < 0)
24376259Sgreen		return (-1);
24476259Sgreen	rlfd.rlim_cur = lim;
24576259Sgreen	if (setrlimit(RLIMIT_NOFILE, &rlfd) < 0)
24676259Sgreen		return (-1);
24798941Sdes#elif defined (HAVE_SETDTABLESIZE)
24898941Sdes	setdtablesize(lim);
24998941Sdes#endif
25076259Sgreen	return (0);
25176259Sgreen}
25276259Sgreen
25376259Sgreen/*
25476259Sgreen * This is an strsep function that returns a null field for adjacent
25576259Sgreen * separators.  This is the same as the 4.4BSD strsep, but different from the
25676259Sgreen * one in the GNU libc.
25776259Sgreen */
25892555Sdesstatic char *
25976259Sgreenxstrsep(char **str, const char *delim)
26076259Sgreen{
26176259Sgreen	char *s, *e;
26276259Sgreen
26376259Sgreen	if (!**str)
26476259Sgreen		return (NULL);
26576259Sgreen
26676259Sgreen	s = *str;
26776259Sgreen	e = s + strcspn(s, delim);
26876259Sgreen
26976259Sgreen	if (*e != '\0')
27076259Sgreen		*e++ = '\0';
27176259Sgreen	*str = e;
27276259Sgreen
27376259Sgreen	return (s);
27476259Sgreen}
27576259Sgreen
27676259Sgreen/*
27776259Sgreen * Get the next non-null token (like GNU strsep).  Strsep() will return a
27876259Sgreen * null token for two adjacent separators, so we may have to loop.
27976259Sgreen */
28092555Sdesstatic char *
28176259Sgreenstrnnsep(char **stringp, char *delim)
28276259Sgreen{
28376259Sgreen	char *tok;
28476259Sgreen
28576259Sgreen	do {
28676259Sgreen		tok = xstrsep(stringp, delim);
28776259Sgreen	} while (tok && *tok == '\0');
28876259Sgreen	return (tok);
28976259Sgreen}
29076259Sgreen
29192555Sdesstatic Key *
29292555Sdeskeygrab_ssh1(con *c)
29376259Sgreen{
29476259Sgreen	static Key *rsa;
29576259Sgreen	static Buffer msg;
29676259Sgreen
29776259Sgreen	if (rsa == NULL) {
29876259Sgreen		buffer_init(&msg);
29976259Sgreen		rsa = key_new(KEY_RSA1);
30076259Sgreen	}
30192555Sdes	buffer_append(&msg, c->c_data, c->c_plen);
30292555Sdes	buffer_consume(&msg, 8 - (c->c_plen & 7));	/* padding */
30376259Sgreen	if (buffer_get_char(&msg) != (int) SSH_SMSG_PUBLIC_KEY) {
30492555Sdes		error("%s: invalid packet type", c->c_name);
30576259Sgreen		buffer_clear(&msg);
30692555Sdes		return NULL;
30776259Sgreen	}
30876259Sgreen	buffer_consume(&msg, 8);		/* cookie */
30976259Sgreen
31076259Sgreen	/* server key */
31176259Sgreen	(void) buffer_get_int(&msg);
31276259Sgreen	buffer_get_bignum(&msg, rsa->rsa->e);
31376259Sgreen	buffer_get_bignum(&msg, rsa->rsa->n);
31476259Sgreen
31576259Sgreen	/* host key */
31676259Sgreen	(void) buffer_get_int(&msg);
31776259Sgreen	buffer_get_bignum(&msg, rsa->rsa->e);
31876259Sgreen	buffer_get_bignum(&msg, rsa->rsa->n);
31992555Sdes
32076259Sgreen	buffer_clear(&msg);
32176259Sgreen
32292555Sdes	return (rsa);
32392555Sdes}
32492555Sdes
32592555Sdesstatic int
32692555Sdeshostjump(Key *hostkey)
32792555Sdes{
32892555Sdes	kexjmp_key = hostkey;
32992555Sdes	longjmp(kexjmp, 1);
33092555Sdes}
33192555Sdes
33292555Sdesstatic int
33392555Sdesssh2_capable(int remote_major, int remote_minor)
33492555Sdes{
33592555Sdes	switch (remote_major) {
33692555Sdes	case 1:
33792555Sdes		if (remote_minor == 99)
33892555Sdes			return 1;
33992555Sdes		break;
34092555Sdes	case 2:
34192555Sdes		return 1;
34292555Sdes	default:
34392555Sdes		break;
34492555Sdes	}
34592555Sdes	return 0;
34692555Sdes}
34792555Sdes
34892555Sdesstatic Key *
34992555Sdeskeygrab_ssh2(con *c)
35092555Sdes{
35192555Sdes	int j;
35292555Sdes
35392555Sdes	packet_set_connection(c->c_fd, c->c_fd);
35492555Sdes	enable_compat20();
35592555Sdes	myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = c->c_keytype == KT_DSA?
35692555Sdes	    "ssh-dss": "ssh-rsa";
35792555Sdes	c->c_kex = kex_setup(myproposal);
35892555Sdes	c->c_kex->verify_host_key = hostjump;
35992555Sdes
36092555Sdes	if (!(j = setjmp(kexjmp))) {
36192555Sdes		nonfatal_fatal = 1;
36292555Sdes		dispatch_run(DISPATCH_BLOCK, &c->c_kex->done, c->c_kex);
36392555Sdes		fprintf(stderr, "Impossible! dispatch_run() returned!\n");
36492555Sdes		exit(1);
36592555Sdes	}
36692555Sdes	nonfatal_fatal = 0;
36792555Sdes	xfree(c->c_kex);
36892555Sdes	c->c_kex = NULL;
36992555Sdes	packet_close();
37092555Sdes
37192555Sdes	return j < 0? NULL : kexjmp_key;
37292555Sdes}
37392555Sdes
37492555Sdesstatic void
37592555Sdeskeyprint(con *c, Key *key)
37692555Sdes{
37792555Sdes	if (!key)
37892555Sdes		return;
37992555Sdes
38092555Sdes	fprintf(stdout, "%s ", c->c_output_name ? c->c_output_name : c->c_name);
38192555Sdes	key_write(key, stdout);
38276259Sgreen	fputs("\n", stdout);
38376259Sgreen}
38476259Sgreen
38592555Sdesstatic int
38676259Sgreentcpconnect(char *host)
38776259Sgreen{
38876259Sgreen	struct addrinfo hints, *ai, *aitop;
38976259Sgreen	char strport[NI_MAXSERV];
39076259Sgreen	int gaierr, s = -1;
39176259Sgreen
39292555Sdes	snprintf(strport, sizeof strport, "%d", ssh_port);
39376259Sgreen	memset(&hints, 0, sizeof(hints));
39492555Sdes	hints.ai_family = IPv4or6;
39576259Sgreen	hints.ai_socktype = SOCK_STREAM;
39676259Sgreen	if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0)
39776259Sgreen		fatal("getaddrinfo %s: %s", host, gai_strerror(gaierr));
39876259Sgreen	for (ai = aitop; ai; ai = ai->ai_next) {
39976259Sgreen		s = socket(ai->ai_family, SOCK_STREAM, 0);
40076259Sgreen		if (s < 0) {
40176259Sgreen			error("socket: %s", strerror(errno));
40276259Sgreen			continue;
40376259Sgreen		}
40476259Sgreen		if (fcntl(s, F_SETFL, O_NONBLOCK) < 0)
40576259Sgreen			fatal("F_SETFL: %s", strerror(errno));
40676259Sgreen		if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0 &&
40776259Sgreen		    errno != EINPROGRESS)
40876259Sgreen			error("connect (`%s'): %s", host, strerror(errno));
40976259Sgreen		else
41076259Sgreen			break;
41176259Sgreen		close(s);
41276259Sgreen		s = -1;
41376259Sgreen	}
41476259Sgreen	freeaddrinfo(aitop);
41576259Sgreen	return s;
41676259Sgreen}
41776259Sgreen
41892555Sdesstatic int
41992555Sdesconalloc(char *iname, char *oname, int keytype)
42076259Sgreen{
421106130Sdes	char *namebase, *name, *namelist;
42276259Sgreen	int s;
42376259Sgreen
42476259Sgreen	namebase = namelist = xstrdup(iname);
42576259Sgreen
42676259Sgreen	do {
42776259Sgreen		name = xstrsep(&namelist, ",");
42876259Sgreen		if (!name) {
42976259Sgreen			xfree(namebase);
43076259Sgreen			return (-1);
43176259Sgreen		}
43276259Sgreen	} while ((s = tcpconnect(name)) < 0);
43376259Sgreen
43476259Sgreen	if (s >= maxfd)
43576259Sgreen		fatal("conalloc: fdno %d too high", s);
43676259Sgreen	if (fdcon[s].c_status)
43776259Sgreen		fatal("conalloc: attempt to reuse fdno %d", s);
43876259Sgreen
43976259Sgreen	fdcon[s].c_fd = s;
44076259Sgreen	fdcon[s].c_status = CS_CON;
44176259Sgreen	fdcon[s].c_namebase = namebase;
44276259Sgreen	fdcon[s].c_name = name;
44376259Sgreen	fdcon[s].c_namelist = namelist;
44476259Sgreen	fdcon[s].c_output_name = xstrdup(oname);
44576259Sgreen	fdcon[s].c_data = (char *) &fdcon[s].c_plen;
44676259Sgreen	fdcon[s].c_len = 4;
44776259Sgreen	fdcon[s].c_off = 0;
44892555Sdes	fdcon[s].c_keytype = keytype;
44976259Sgreen	gettimeofday(&fdcon[s].c_tv, NULL);
45076259Sgreen	fdcon[s].c_tv.tv_sec += timeout;
45176259Sgreen	TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link);
45276259Sgreen	FD_SET(s, read_wait);
45376259Sgreen	ncon++;
45476259Sgreen	return (s);
45576259Sgreen}
45676259Sgreen
45792555Sdesstatic void
45876259Sgreenconfree(int s)
45976259Sgreen{
46076259Sgreen	if (s >= maxfd || fdcon[s].c_status == CS_UNUSED)
46176259Sgreen		fatal("confree: attempt to free bad fdno %d", s);
46276259Sgreen	close(s);
46376259Sgreen	xfree(fdcon[s].c_namebase);
46476259Sgreen	xfree(fdcon[s].c_output_name);
46576259Sgreen	if (fdcon[s].c_status == CS_KEYS)
46676259Sgreen		xfree(fdcon[s].c_data);
46776259Sgreen	fdcon[s].c_status = CS_UNUSED;
46892555Sdes	fdcon[s].c_keytype = 0;
46976259Sgreen	TAILQ_REMOVE(&tq, &fdcon[s], c_link);
47076259Sgreen	FD_CLR(s, read_wait);
47176259Sgreen	ncon--;
47276259Sgreen}
47376259Sgreen
47492555Sdesstatic void
47576259Sgreencontouch(int s)
47676259Sgreen{
47776259Sgreen	TAILQ_REMOVE(&tq, &fdcon[s], c_link);
47876259Sgreen	gettimeofday(&fdcon[s].c_tv, NULL);
47976259Sgreen	fdcon[s].c_tv.tv_sec += timeout;
48076259Sgreen	TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link);
48176259Sgreen}
48276259Sgreen
48392555Sdesstatic int
48476259Sgreenconrecycle(int s)
48576259Sgreen{
486106130Sdes	con *c = &fdcon[s];
48776259Sgreen	int ret;
48876259Sgreen
48992555Sdes	ret = conalloc(c->c_namelist, c->c_output_name, c->c_keytype);
49076259Sgreen	confree(s);
49176259Sgreen	return (ret);
49276259Sgreen}
49376259Sgreen
49492555Sdesstatic void
49576259Sgreencongreet(int s)
49676259Sgreen{
497106130Sdes	int remote_major, remote_minor, n = 0;
49892555Sdes	char buf[256], *cp;
49992555Sdes	char remote_version[sizeof buf];
50076259Sgreen	size_t bufsiz;
50176259Sgreen	con *c = &fdcon[s];
50276259Sgreen
50376259Sgreen	bufsiz = sizeof(buf);
50476259Sgreen	cp = buf;
50592555Sdes	while (bufsiz-- && (n = read(s, cp, 1)) == 1 && *cp != '\n') {
50692555Sdes		if (*cp == '\r')
50792555Sdes			*cp = '\n';
50876259Sgreen		cp++;
50992555Sdes	}
51076259Sgreen	if (n < 0) {
51176259Sgreen		if (errno != ECONNREFUSED)
51276259Sgreen			error("read (%s): %s", c->c_name, strerror(errno));
51376259Sgreen		conrecycle(s);
51476259Sgreen		return;
51576259Sgreen	}
51692555Sdes	if (n == 0) {
51792555Sdes		error("%s: Connection closed by remote host", c->c_name);
51892555Sdes		conrecycle(s);
51992555Sdes		return;
52092555Sdes	}
52176259Sgreen	if (*cp != '\n' && *cp != '\r') {
52276259Sgreen		error("%s: bad greeting", c->c_name);
52376259Sgreen		confree(s);
52476259Sgreen		return;
52576259Sgreen	}
52676259Sgreen	*cp = '\0';
52792555Sdes	if (sscanf(buf, "SSH-%d.%d-%[^\n]\n",
52892555Sdes	    &remote_major, &remote_minor, remote_version) == 3)
52992555Sdes		compat_datafellows(remote_version);
53092555Sdes	else
53192555Sdes		datafellows = 0;
53292555Sdes	if (c->c_keytype != KT_RSA1) {
53392555Sdes		if (!ssh2_capable(remote_major, remote_minor)) {
53492555Sdes			debug("%s doesn't support ssh2", c->c_name);
53592555Sdes			confree(s);
53692555Sdes			return;
53792555Sdes		}
53892555Sdes	} else if (remote_major != 1) {
53992555Sdes		debug("%s doesn't support ssh1", c->c_name);
54092555Sdes		confree(s);
54192555Sdes		return;
54292555Sdes	}
54392555Sdes	fprintf(stderr, "# %s %s\n", c->c_name, chop(buf));
54492555Sdes	n = snprintf(buf, sizeof buf, "SSH-%d.%d-OpenSSH-keyscan\r\n",
54592555Sdes	    c->c_keytype == KT_RSA1? PROTOCOL_MAJOR_1 : PROTOCOL_MAJOR_2,
54692555Sdes	    c->c_keytype == KT_RSA1? PROTOCOL_MINOR_1 : PROTOCOL_MINOR_2);
54776259Sgreen	if (atomicio(write, s, buf, n) != n) {
54876259Sgreen		error("write (%s): %s", c->c_name, strerror(errno));
54976259Sgreen		confree(s);
55076259Sgreen		return;
55176259Sgreen	}
55292555Sdes	if (c->c_keytype != KT_RSA1) {
55392555Sdes		keyprint(c, keygrab_ssh2(c));
55492555Sdes		confree(s);
55592555Sdes		return;
55692555Sdes	}
55776259Sgreen	c->c_status = CS_SIZE;
55876259Sgreen	contouch(s);
55976259Sgreen}
56076259Sgreen
56192555Sdesstatic void
56276259Sgreenconread(int s)
56376259Sgreen{
564106130Sdes	con *c = &fdcon[s];
56576259Sgreen	int n;
56676259Sgreen
56776259Sgreen	if (c->c_status == CS_CON) {
56876259Sgreen		congreet(s);
56976259Sgreen		return;
57076259Sgreen	}
57176259Sgreen	n = read(s, c->c_data + c->c_off, c->c_len - c->c_off);
57276259Sgreen	if (n < 0) {
57376259Sgreen		error("read (%s): %s", c->c_name, strerror(errno));
57476259Sgreen		confree(s);
57576259Sgreen		return;
57676259Sgreen	}
57776259Sgreen	c->c_off += n;
57876259Sgreen
57976259Sgreen	if (c->c_off == c->c_len)
58076259Sgreen		switch (c->c_status) {
58176259Sgreen		case CS_SIZE:
58276259Sgreen			c->c_plen = htonl(c->c_plen);
58376259Sgreen			c->c_len = c->c_plen + 8 - (c->c_plen & 7);
58476259Sgreen			c->c_off = 0;
58576259Sgreen			c->c_data = xmalloc(c->c_len);
58676259Sgreen			c->c_status = CS_KEYS;
58776259Sgreen			break;
58876259Sgreen		case CS_KEYS:
58992555Sdes			keyprint(c, keygrab_ssh1(c));
59076259Sgreen			confree(s);
59176259Sgreen			return;
59276259Sgreen			break;
59376259Sgreen		default:
59476259Sgreen			fatal("conread: invalid status %d", c->c_status);
59576259Sgreen			break;
59676259Sgreen		}
59776259Sgreen
59876259Sgreen	contouch(s);
59976259Sgreen}
60076259Sgreen
60192555Sdesstatic void
60276259Sgreenconloop(void)
60376259Sgreen{
604106130Sdes	struct timeval seltime, now;
60576259Sgreen	fd_set *r, *e;
606106130Sdes	con *c;
60776259Sgreen	int i;
60876259Sgreen
60976259Sgreen	gettimeofday(&now, NULL);
61098675Sdes	c = TAILQ_FIRST(&tq);
61176259Sgreen
61276259Sgreen	if (c && (c->c_tv.tv_sec > now.tv_sec ||
61376259Sgreen	    (c->c_tv.tv_sec == now.tv_sec && c->c_tv.tv_usec > now.tv_usec))) {
61476259Sgreen		seltime = c->c_tv;
61576259Sgreen		seltime.tv_sec -= now.tv_sec;
61676259Sgreen		seltime.tv_usec -= now.tv_usec;
61776259Sgreen		if (seltime.tv_usec < 0) {
61876259Sgreen			seltime.tv_usec += 1000000;
61976259Sgreen			seltime.tv_sec--;
62076259Sgreen		}
62176259Sgreen	} else
62276259Sgreen		seltime.tv_sec = seltime.tv_usec = 0;
62376259Sgreen
62476259Sgreen	r = xmalloc(read_wait_size);
62576259Sgreen	memcpy(r, read_wait, read_wait_size);
62676259Sgreen	e = xmalloc(read_wait_size);
62776259Sgreen	memcpy(e, read_wait, read_wait_size);
62876259Sgreen
62976259Sgreen	while (select(maxfd, r, NULL, e, &seltime) == -1 &&
63076259Sgreen	    (errno == EAGAIN || errno == EINTR))
63176259Sgreen		;
63276259Sgreen
63376259Sgreen	for (i = 0; i < maxfd; i++) {
63476259Sgreen		if (FD_ISSET(i, e)) {
63576259Sgreen			error("%s: exception!", fdcon[i].c_name);
63676259Sgreen			confree(i);
63776259Sgreen		} else if (FD_ISSET(i, r))
63876259Sgreen			conread(i);
63976259Sgreen	}
64076259Sgreen	xfree(r);
64176259Sgreen	xfree(e);
64276259Sgreen
64398675Sdes	c = TAILQ_FIRST(&tq);
64476259Sgreen	while (c && (c->c_tv.tv_sec < now.tv_sec ||
64576259Sgreen	    (c->c_tv.tv_sec == now.tv_sec && c->c_tv.tv_usec < now.tv_usec))) {
64676259Sgreen		int s = c->c_fd;
64776259Sgreen
64898675Sdes		c = TAILQ_NEXT(c, c_link);
64976259Sgreen		conrecycle(s);
65076259Sgreen	}
65176259Sgreen}
65276259Sgreen
65392555Sdesstatic void
65492555Sdesdo_host(char *host)
65576259Sgreen{
65692555Sdes	char *name = strnnsep(&host, " \t\n");
65792555Sdes	int j;
65876259Sgreen
65992555Sdes	if (name == NULL)
66092555Sdes		return;
66192555Sdes	for (j = KT_RSA1; j <= KT_RSA; j *= 2) {
66292555Sdes		if (get_keytypes & j) {
66392555Sdes			while (ncon >= MAXCON)
66492555Sdes				conloop();
66592555Sdes			conalloc(name, *host ? host : name, j);
66676259Sgreen		}
66776259Sgreen	}
66876259Sgreen}
66976259Sgreen
67076259Sgreenvoid
67192555Sdesfatal(const char *fmt,...)
67292555Sdes{
67392555Sdes	va_list args;
674106130Sdes
67592555Sdes	va_start(args, fmt);
67692555Sdes	do_log(SYSLOG_LEVEL_FATAL, fmt, args);
67792555Sdes	va_end(args);
67892555Sdes	if (nonfatal_fatal)
67992555Sdes		longjmp(kexjmp, -1);
68092555Sdes	else
68192555Sdes		fatal_cleanup();
68292555Sdes}
68392555Sdes
68492555Sdesstatic void
68576259Sgreenusage(void)
68676259Sgreen{
687106130Sdes	fprintf(stderr, "usage: %s [-v46] [-p port] [-T timeout] [-f file]\n"
688106130Sdes	    "\t\t   [host | addrlist namelist] [...]\n",
68992555Sdes	    __progname);
69092555Sdes	exit(1);
69176259Sgreen}
69276259Sgreen
69376259Sgreenint
69476259Sgreenmain(int argc, char **argv)
69576259Sgreen{
69692555Sdes	int debug_flag = 0, log_level = SYSLOG_LEVEL_INFO;
69792555Sdes	int opt, fopt_count = 0;
69892555Sdes	char *tname;
69976259Sgreen
70092555Sdes	extern int optind;
70192555Sdes	extern char *optarg;
70292555Sdes
70398941Sdes	__progname = get_progname(argv[0]);
70498941Sdes	init_rng();
70598941Sdes	seed_rng();
70676259Sgreen	TAILQ_INIT(&tq);
70776259Sgreen
70892555Sdes	if (argc <= 1)
70976259Sgreen		usage();
71076259Sgreen
71192555Sdes	while ((opt = getopt(argc, argv, "v46p:T:t:f:")) != -1) {
71292555Sdes		switch (opt) {
71392555Sdes		case 'p':
71492555Sdes			ssh_port = a2port(optarg);
71592555Sdes			if (ssh_port == 0) {
71692555Sdes				fprintf(stderr, "Bad port '%s'\n", optarg);
71792555Sdes				exit(1);
71892555Sdes			}
71992555Sdes			break;
72092555Sdes		case 'T':
721106130Sdes			timeout = convtime(optarg);
722106130Sdes			if (timeout == -1 || timeout == 0) {
723106130Sdes				fprintf(stderr, "Bad timeout '%s'\n", optarg);
72476259Sgreen				usage();
725106130Sdes			}
72692555Sdes			break;
72792555Sdes		case 'v':
72892555Sdes			if (!debug_flag) {
72992555Sdes				debug_flag = 1;
73092555Sdes				log_level = SYSLOG_LEVEL_DEBUG1;
73192555Sdes			}
73292555Sdes			else if (log_level < SYSLOG_LEVEL_DEBUG3)
73392555Sdes				log_level++;
73492555Sdes			else
73592555Sdes				fatal("Too high debugging level.");
73692555Sdes			break;
73792555Sdes		case 'f':
73892555Sdes			if (strcmp(optarg, "-") == 0)
73992555Sdes				optarg = NULL;
74092555Sdes			argv[fopt_count++] = optarg;
74192555Sdes			break;
74292555Sdes		case 't':
74392555Sdes			get_keytypes = 0;
74492555Sdes			tname = strtok(optarg, ",");
74592555Sdes			while (tname) {
74692555Sdes				int type = key_type_from_name(tname);
74792555Sdes				switch (type) {
74892555Sdes				case KEY_RSA1:
74992555Sdes					get_keytypes |= KT_RSA1;
75092555Sdes					break;
75192555Sdes				case KEY_DSA:
75292555Sdes					get_keytypes |= KT_DSA;
75392555Sdes					break;
75492555Sdes				case KEY_RSA:
75592555Sdes					get_keytypes |= KT_RSA;
75692555Sdes					break;
75792555Sdes				case KEY_UNSPEC:
75892555Sdes					fatal("unknown key type %s", tname);
75992555Sdes				}
76092555Sdes				tname = strtok(NULL, ",");
76192555Sdes			}
76292555Sdes			break;
76392555Sdes		case '4':
76492555Sdes			IPv4or6 = AF_INET;
76592555Sdes			break;
76692555Sdes		case '6':
76792555Sdes			IPv4or6 = AF_INET6;
76892555Sdes			break;
76992555Sdes		case '?':
77092555Sdes		default:
77192555Sdes			usage();
77276259Sgreen		}
77376259Sgreen	}
77492555Sdes	if (optind == argc && !fopt_count)
77576259Sgreen		usage();
77676259Sgreen
77792555Sdes	log_init("ssh-keyscan", log_level, SYSLOG_FACILITY_USER, 1);
77892555Sdes
77976259Sgreen	maxfd = fdlim_get(1);
78076259Sgreen	if (maxfd < 0)
78176259Sgreen		fatal("%s: fdlim_get: bad value", __progname);
78276259Sgreen	if (maxfd > MAXMAXFD)
78376259Sgreen		maxfd = MAXMAXFD;
78476259Sgreen	if (MAXCON <= 0)
78576259Sgreen		fatal("%s: not enough file descriptors", __progname);
78676259Sgreen	if (maxfd > fdlim_get(0))
78776259Sgreen		fdlim_set(maxfd);
78876259Sgreen	fdcon = xmalloc(maxfd * sizeof(con));
78976259Sgreen	memset(fdcon, 0, maxfd * sizeof(con));
79076259Sgreen
79176259Sgreen	read_wait_size = howmany(maxfd, NFDBITS) * sizeof(fd_mask);
79276259Sgreen	read_wait = xmalloc(read_wait_size);
79376259Sgreen	memset(read_wait, 0, read_wait_size);
79476259Sgreen
79592555Sdes	if (fopt_count) {
79692555Sdes		Linebuf *lb;
79792555Sdes		char *line;
79892555Sdes		int j;
79976259Sgreen
80092555Sdes		for (j = 0; j < fopt_count; j++) {
80192555Sdes			lb = Linebuf_alloc(argv[j], error);
80292555Sdes			if (!lb)
80392555Sdes				continue;
80492555Sdes			while ((line = Linebuf_getline(lb)) != NULL)
80592555Sdes				do_host(line);
80692555Sdes			Linebuf_free(lb);
80776259Sgreen		}
80892555Sdes	}
80992555Sdes
81092555Sdes	while (optind < argc)
81192555Sdes		do_host(argv[optind++]);
81292555Sdes
81376259Sgreen	while (ncon > 0)
81476259Sgreen		conloop();
81576259Sgreen
81676259Sgreen	return (0);
81776259Sgreen}
818