ssh-keyscan.c revision 76259
1/*
2 * Copyright 1995, 1996 by David Mazieres <dm@lcs.mit.edu>.
3 *
4 * Modification and redistribution in source and binary forms is
5 * permitted provided that due credit is given to the author and the
6 * OpenBSD project (for instance by leaving this copyright notice
7 * intact).
8 */
9
10#include "includes.h"
11RCSID("$OpenBSD: ssh-keyscan.c,v 1.22 2001/03/06 06:11:18 deraadt Exp $");
12
13#include <sys/queue.h>
14#include <errno.h>
15
16#include <openssl/bn.h>
17
18#include "xmalloc.h"
19#include "ssh.h"
20#include "ssh1.h"
21#include "key.h"
22#include "buffer.h"
23#include "bufaux.h"
24#include "log.h"
25#include "atomicio.h"
26
27static int argno = 1;		/* Number of argument currently being parsed */
28
29int family = AF_UNSPEC;		/* IPv4, IPv6 or both */
30
31#define MAXMAXFD 256
32
33/* The number of seconds after which to give up on a TCP connection */
34int timeout = 5;
35
36int maxfd;
37#define MAXCON (maxfd - 10)
38
39extern char *__progname;
40fd_set *read_wait;
41size_t read_wait_size;
42int ncon;
43
44/*
45 * Keep a connection structure for each file descriptor.  The state
46 * associated with file descriptor n is held in fdcon[n].
47 */
48typedef struct Connection {
49	u_char c_status;	/* State of connection on this file desc. */
50#define CS_UNUSED 0		/* File descriptor unused */
51#define CS_CON 1		/* Waiting to connect/read greeting */
52#define CS_SIZE 2		/* Waiting to read initial packet size */
53#define CS_KEYS 3		/* Waiting to read public key packet */
54	int c_fd;		/* Quick lookup: c->c_fd == c - fdcon */
55	int c_plen;		/* Packet length field for ssh packet */
56	int c_len;		/* Total bytes which must be read. */
57	int c_off;		/* Length of data read so far. */
58	char *c_namebase;	/* Address to free for c_name and c_namelist */
59	char *c_name;		/* Hostname of connection for errors */
60	char *c_namelist;	/* Pointer to other possible addresses */
61	char *c_output_name;	/* Hostname of connection for output */
62	char *c_data;		/* Data read from this fd */
63	struct timeval c_tv;	/* Time at which connection gets aborted */
64	TAILQ_ENTRY(Connection) c_link;	/* List of connections in timeout order. */
65} con;
66
67TAILQ_HEAD(conlist, Connection) tq;	/* Timeout Queue */
68con *fdcon;
69
70/*
71 *  This is just a wrapper around fgets() to make it usable.
72 */
73
74/* Stress-test.  Increase this later. */
75#define LINEBUF_SIZE 16
76
77typedef struct {
78	char *buf;
79	u_int size;
80	int lineno;
81	const char *filename;
82	FILE *stream;
83	void (*errfun) (const char *,...);
84} Linebuf;
85
86Linebuf *
87Linebuf_alloc(const char *filename, void (*errfun) (const char *,...))
88{
89	Linebuf *lb;
90
91	if (!(lb = malloc(sizeof(*lb)))) {
92		if (errfun)
93			(*errfun) ("linebuf (%s): malloc failed\n", lb->filename);
94		return (NULL);
95	}
96	if (filename) {
97		lb->filename = filename;
98		if (!(lb->stream = fopen(filename, "r"))) {
99			xfree(lb);
100			if (errfun)
101				(*errfun) ("%s: %s\n", filename, strerror(errno));
102			return (NULL);
103		}
104	} else {
105		lb->filename = "(stdin)";
106		lb->stream = stdin;
107	}
108
109	if (!(lb->buf = malloc(lb->size = LINEBUF_SIZE))) {
110		if (errfun)
111			(*errfun) ("linebuf (%s): malloc failed\n", lb->filename);
112		xfree(lb);
113		return (NULL);
114	}
115	lb->errfun = errfun;
116	lb->lineno = 0;
117	return (lb);
118}
119
120void
121Linebuf_free(Linebuf * lb)
122{
123	fclose(lb->stream);
124	xfree(lb->buf);
125	xfree(lb);
126}
127
128void
129Linebuf_restart(Linebuf * lb)
130{
131	clearerr(lb->stream);
132	rewind(lb->stream);
133	lb->lineno = 0;
134}
135
136int
137Linebuf_lineno(Linebuf * lb)
138{
139	return (lb->lineno);
140}
141
142char *
143Linebuf_getline(Linebuf * lb)
144{
145	int n = 0;
146
147	lb->lineno++;
148	for (;;) {
149		/* Read a line */
150		if (!fgets(&lb->buf[n], lb->size - n, lb->stream)) {
151			if (ferror(lb->stream) && lb->errfun)
152				(*lb->errfun) ("%s: %s\n", lb->filename,
153				    strerror(errno));
154			return (NULL);
155		}
156		n = strlen(lb->buf);
157
158		/* Return it or an error if it fits */
159		if (n > 0 && lb->buf[n - 1] == '\n') {
160			lb->buf[n - 1] = '\0';
161			return (lb->buf);
162		}
163		if (n != lb->size - 1) {
164			if (lb->errfun)
165				(*lb->errfun) ("%s: skipping incomplete last line\n",
166				    lb->filename);
167			return (NULL);
168		}
169		/* Double the buffer if we need more space */
170		if (!(lb->buf = realloc(lb->buf, (lb->size *= 2)))) {
171			if (lb->errfun)
172				(*lb->errfun) ("linebuf (%s): realloc failed\n",
173				    lb->filename);
174			return (NULL);
175		}
176	}
177}
178
179int
180fdlim_get(int hard)
181{
182	struct rlimit rlfd;
183
184	if (getrlimit(RLIMIT_NOFILE, &rlfd) < 0)
185		return (-1);
186	if ((hard ? rlfd.rlim_max : rlfd.rlim_cur) == RLIM_INFINITY)
187		return 10000;
188	else
189		return hard ? rlfd.rlim_max : rlfd.rlim_cur;
190}
191
192int
193fdlim_set(int lim)
194{
195	struct rlimit rlfd;
196	if (lim <= 0)
197		return (-1);
198	if (getrlimit(RLIMIT_NOFILE, &rlfd) < 0)
199		return (-1);
200	rlfd.rlim_cur = lim;
201	if (setrlimit(RLIMIT_NOFILE, &rlfd) < 0)
202		return (-1);
203	return (0);
204}
205
206/*
207 * This is an strsep function that returns a null field for adjacent
208 * separators.  This is the same as the 4.4BSD strsep, but different from the
209 * one in the GNU libc.
210 */
211char *
212xstrsep(char **str, const char *delim)
213{
214	char *s, *e;
215
216	if (!**str)
217		return (NULL);
218
219	s = *str;
220	e = s + strcspn(s, delim);
221
222	if (*e != '\0')
223		*e++ = '\0';
224	*str = e;
225
226	return (s);
227}
228
229/*
230 * Get the next non-null token (like GNU strsep).  Strsep() will return a
231 * null token for two adjacent separators, so we may have to loop.
232 */
233char *
234strnnsep(char **stringp, char *delim)
235{
236	char *tok;
237
238	do {
239		tok = xstrsep(stringp, delim);
240	} while (tok && *tok == '\0');
241	return (tok);
242}
243
244void
245keyprint(char *host, char *output_name, char *kd, int len)
246{
247	static Key *rsa;
248	static Buffer msg;
249
250	if (rsa == NULL) {
251		buffer_init(&msg);
252		rsa = key_new(KEY_RSA1);
253	}
254	buffer_append(&msg, kd, len);
255	buffer_consume(&msg, 8 - (len & 7));	/* padding */
256	if (buffer_get_char(&msg) != (int) SSH_SMSG_PUBLIC_KEY) {
257		error("%s: invalid packet type", host);
258		buffer_clear(&msg);
259		return;
260	}
261	buffer_consume(&msg, 8);		/* cookie */
262
263	/* server key */
264	(void) buffer_get_int(&msg);
265	buffer_get_bignum(&msg, rsa->rsa->e);
266	buffer_get_bignum(&msg, rsa->rsa->n);
267
268	/* host key */
269	(void) buffer_get_int(&msg);
270	buffer_get_bignum(&msg, rsa->rsa->e);
271	buffer_get_bignum(&msg, rsa->rsa->n);
272	buffer_clear(&msg);
273
274	fprintf(stdout, "%s ", output_name ? output_name : host);
275	key_write(rsa, stdout);
276	fputs("\n", stdout);
277}
278
279int
280tcpconnect(char *host)
281{
282	struct addrinfo hints, *ai, *aitop;
283	char strport[NI_MAXSERV];
284	int gaierr, s = -1;
285
286	snprintf(strport, sizeof strport, "%d", SSH_DEFAULT_PORT);
287	memset(&hints, 0, sizeof(hints));
288	hints.ai_family = family;
289	hints.ai_socktype = SOCK_STREAM;
290	if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0)
291		fatal("getaddrinfo %s: %s", host, gai_strerror(gaierr));
292	for (ai = aitop; ai; ai = ai->ai_next) {
293		s = socket(ai->ai_family, SOCK_STREAM, 0);
294		if (s < 0) {
295			error("socket: %s", strerror(errno));
296			continue;
297		}
298		if (fcntl(s, F_SETFL, O_NONBLOCK) < 0)
299			fatal("F_SETFL: %s", strerror(errno));
300		if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0 &&
301		    errno != EINPROGRESS)
302			error("connect (`%s'): %s", host, strerror(errno));
303		else
304			break;
305		close(s);
306		s = -1;
307	}
308	freeaddrinfo(aitop);
309	return s;
310}
311
312int
313conalloc(char *iname, char *oname)
314{
315	int s;
316	char *namebase, *name, *namelist;
317
318	namebase = namelist = xstrdup(iname);
319
320	do {
321		name = xstrsep(&namelist, ",");
322		if (!name) {
323			xfree(namebase);
324			return (-1);
325		}
326	} while ((s = tcpconnect(name)) < 0);
327
328	if (s >= maxfd)
329		fatal("conalloc: fdno %d too high", s);
330	if (fdcon[s].c_status)
331		fatal("conalloc: attempt to reuse fdno %d", s);
332
333	fdcon[s].c_fd = s;
334	fdcon[s].c_status = CS_CON;
335	fdcon[s].c_namebase = namebase;
336	fdcon[s].c_name = name;
337	fdcon[s].c_namelist = namelist;
338	fdcon[s].c_output_name = xstrdup(oname);
339	fdcon[s].c_data = (char *) &fdcon[s].c_plen;
340	fdcon[s].c_len = 4;
341	fdcon[s].c_off = 0;
342	gettimeofday(&fdcon[s].c_tv, NULL);
343	fdcon[s].c_tv.tv_sec += timeout;
344	TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link);
345	FD_SET(s, read_wait);
346	ncon++;
347	return (s);
348}
349
350void
351confree(int s)
352{
353	if (s >= maxfd || fdcon[s].c_status == CS_UNUSED)
354		fatal("confree: attempt to free bad fdno %d", s);
355	close(s);
356	xfree(fdcon[s].c_namebase);
357	xfree(fdcon[s].c_output_name);
358	if (fdcon[s].c_status == CS_KEYS)
359		xfree(fdcon[s].c_data);
360	fdcon[s].c_status = CS_UNUSED;
361	TAILQ_REMOVE(&tq, &fdcon[s], c_link);
362	FD_CLR(s, read_wait);
363	ncon--;
364}
365
366void
367contouch(int s)
368{
369	TAILQ_REMOVE(&tq, &fdcon[s], c_link);
370	gettimeofday(&fdcon[s].c_tv, NULL);
371	fdcon[s].c_tv.tv_sec += timeout;
372	TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link);
373}
374
375int
376conrecycle(int s)
377{
378	int ret;
379	con *c = &fdcon[s];
380	char *iname, *oname;
381
382	iname = xstrdup(c->c_namelist);
383	oname = xstrdup(c->c_output_name);
384	confree(s);
385	ret = conalloc(iname, oname);
386	xfree(iname);
387	xfree(oname);
388	return (ret);
389}
390
391void
392congreet(int s)
393{
394	char buf[80], *cp;
395	size_t bufsiz;
396	int n = 0;
397	con *c = &fdcon[s];
398
399	bufsiz = sizeof(buf);
400	cp = buf;
401	while (bufsiz-- && (n = read(s, cp, 1)) == 1 && *cp != '\n' && *cp != '\r')
402		cp++;
403	if (n < 0) {
404		if (errno != ECONNREFUSED)
405			error("read (%s): %s", c->c_name, strerror(errno));
406		conrecycle(s);
407		return;
408	}
409	if (*cp != '\n' && *cp != '\r') {
410		error("%s: bad greeting", c->c_name);
411		confree(s);
412		return;
413	}
414	*cp = '\0';
415	fprintf(stderr, "# %s %s\n", c->c_name, buf);
416	n = snprintf(buf, sizeof buf, "SSH-1.5-OpenSSH-keyscan\r\n");
417	if (atomicio(write, s, buf, n) != n) {
418		error("write (%s): %s", c->c_name, strerror(errno));
419		confree(s);
420		return;
421	}
422	c->c_status = CS_SIZE;
423	contouch(s);
424}
425
426void
427conread(int s)
428{
429	int n;
430	con *c = &fdcon[s];
431
432	if (c->c_status == CS_CON) {
433		congreet(s);
434		return;
435	}
436	n = read(s, c->c_data + c->c_off, c->c_len - c->c_off);
437	if (n < 0) {
438		error("read (%s): %s", c->c_name, strerror(errno));
439		confree(s);
440		return;
441	}
442	c->c_off += n;
443
444	if (c->c_off == c->c_len)
445		switch (c->c_status) {
446		case CS_SIZE:
447			c->c_plen = htonl(c->c_plen);
448			c->c_len = c->c_plen + 8 - (c->c_plen & 7);
449			c->c_off = 0;
450			c->c_data = xmalloc(c->c_len);
451			c->c_status = CS_KEYS;
452			break;
453		case CS_KEYS:
454			keyprint(c->c_name, c->c_output_name, c->c_data, c->c_plen);
455			confree(s);
456			return;
457			break;
458		default:
459			fatal("conread: invalid status %d", c->c_status);
460			break;
461		}
462
463	contouch(s);
464}
465
466void
467conloop(void)
468{
469	fd_set *r, *e;
470	struct timeval seltime, now;
471	int i;
472	con *c;
473
474	gettimeofday(&now, NULL);
475	c = tq.tqh_first;
476
477	if (c && (c->c_tv.tv_sec > now.tv_sec ||
478	    (c->c_tv.tv_sec == now.tv_sec && c->c_tv.tv_usec > now.tv_usec))) {
479		seltime = c->c_tv;
480		seltime.tv_sec -= now.tv_sec;
481		seltime.tv_usec -= now.tv_usec;
482		if (seltime.tv_usec < 0) {
483			seltime.tv_usec += 1000000;
484			seltime.tv_sec--;
485		}
486	} else
487		seltime.tv_sec = seltime.tv_usec = 0;
488
489	r = xmalloc(read_wait_size);
490	memcpy(r, read_wait, read_wait_size);
491	e = xmalloc(read_wait_size);
492	memcpy(e, read_wait, read_wait_size);
493
494	while (select(maxfd, r, NULL, e, &seltime) == -1 &&
495	    (errno == EAGAIN || errno == EINTR))
496		;
497
498	for (i = 0; i < maxfd; i++) {
499		if (FD_ISSET(i, e)) {
500			error("%s: exception!", fdcon[i].c_name);
501			confree(i);
502		} else if (FD_ISSET(i, r))
503			conread(i);
504	}
505	xfree(r);
506	xfree(e);
507
508	c = tq.tqh_first;
509	while (c && (c->c_tv.tv_sec < now.tv_sec ||
510	    (c->c_tv.tv_sec == now.tv_sec && c->c_tv.tv_usec < now.tv_usec))) {
511		int s = c->c_fd;
512
513		c = c->c_link.tqe_next;
514		conrecycle(s);
515	}
516}
517
518char *
519nexthost(int argc, char **argv)
520{
521	static Linebuf *lb;
522
523	for (;;) {
524		if (!lb) {
525			if (argno >= argc)
526				return (NULL);
527			if (argv[argno][0] != '-')
528				return (argv[argno++]);
529			if (!strcmp(argv[argno], "--")) {
530				if (++argno >= argc)
531					return (NULL);
532				return (argv[argno++]);
533			} else if (!strncmp(argv[argno], "-f", 2)) {
534				char *fname;
535
536				if (argv[argno][2])
537					fname = &argv[argno++][2];
538				else if (++argno >= argc) {
539					error("missing filename for `-f'");
540					return (NULL);
541				} else
542					fname = argv[argno++];
543				if (!strcmp(fname, "-"))
544					fname = NULL;
545				lb = Linebuf_alloc(fname, error);
546			} else
547				error("ignoring invalid/misplaced option `%s'",
548				    argv[argno++]);
549		} else {
550			char *line;
551
552			line = Linebuf_getline(lb);
553			if (line)
554				return (line);
555			Linebuf_free(lb);
556			lb = NULL;
557		}
558	}
559}
560
561void
562usage(void)
563{
564	fatal("usage: %s [-t timeout] { [--] host | -f file } ...", __progname);
565	return;
566}
567
568int
569main(int argc, char **argv)
570{
571	char *host = NULL;
572
573	TAILQ_INIT(&tq);
574
575	if (argc <= argno)
576		usage();
577
578	if (argv[1][0] == '-' && argv[1][1] == 't') {
579		argno++;
580		if (argv[1][2])
581			timeout = atoi(&argv[1][2]);
582		else {
583			if (argno >= argc)
584				usage();
585			timeout = atoi(argv[argno++]);
586		}
587		if (timeout <= 0)
588			usage();
589	}
590	if (argc <= argno)
591		usage();
592
593	maxfd = fdlim_get(1);
594	if (maxfd < 0)
595		fatal("%s: fdlim_get: bad value", __progname);
596	if (maxfd > MAXMAXFD)
597		maxfd = MAXMAXFD;
598	if (MAXCON <= 0)
599		fatal("%s: not enough file descriptors", __progname);
600	if (maxfd > fdlim_get(0))
601		fdlim_set(maxfd);
602	fdcon = xmalloc(maxfd * sizeof(con));
603	memset(fdcon, 0, maxfd * sizeof(con));
604
605	read_wait_size = howmany(maxfd, NFDBITS) * sizeof(fd_mask);
606	read_wait = xmalloc(read_wait_size);
607	memset(read_wait, 0, read_wait_size);
608
609	do {
610		while (ncon < MAXCON) {
611			char *name;
612
613			host = nexthost(argc, argv);
614			if (host == NULL)
615				break;
616			name = strnnsep(&host, " \t\n");
617			conalloc(name, *host ? host : name);
618		}
619		conloop();
620	} while (host);
621	while (ncon > 0)
622		conloop();
623
624	return (0);
625}
626