summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog10
-rw-r--r--Makefile.in12
-rw-r--r--ssh-keyscan.194
-rw-r--r--ssh-keyscan.c605
4 files changed, 717 insertions, 4 deletions
diff --git a/ChangeLog b/ChangeLog
index f5d13bc5f..5f350f82f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,14 @@
120001205
2 - (bal) OpenSSH CVS updates:
3 - markus@cvs.openbsd.org 2000/12/04 19:24:02
4 [ssh-keyscan.c ssh-keyscan.1]
5 David Maziere's ssh-keyscan, ok niels@
6 - (bal) Updated Makefile.in to include ssh-keyscan that was just added
7 to the recent OpenBSD source tree.
8
120001204 920001204
2 - (bal) More C functions defined in NeXT that are unaccessable without 10 - (bal) More C functions defined in NeXT that are unaccessable without
3 defining -POSIX. 11 defining -POSIX.
4 - (bal) OpenBSD CVS updates: 12 - (bal) OpenBSD CVS updates:
5 - markus@cvs.openbsd.org 2000/12/03 11:29:04 13 - markus@cvs.openbsd.org 2000/12/03 11:29:04
6 [compat.c] 14 [compat.c]
diff --git a/Makefile.in b/Makefile.in
index 1e5d4105a..b351985c1 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -34,7 +34,7 @@ SSH_MODE= @SSHMODE@
34 34
35INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@ 35INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@
36 36
37TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT) 37TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT)
38 38
39LIBSSH_OBJS=atomicio.o authfd.o authfile.o bufaux.o buffer.o canohost.o channels.o cipher.o cli.o compat.o compress.o crc32.o cygwin_util.o deattack.o dispatch.o hmac.o hostfile.o key.o kex.o log.o match.o mpaux.o nchan.o packet.o radix.o rijndael.o entropy.o readpass.o rsa.o ssh-dss.o ssh-rsa.o tildexpand.o ttymodes.o uidswap.o util.o uuencode.o xmalloc.o 39LIBSSH_OBJS=atomicio.o authfd.o authfile.o bufaux.o buffer.o canohost.o channels.o cipher.o cli.o compat.o compress.o crc32.o cygwin_util.o deattack.o dispatch.o hmac.o hostfile.o key.o kex.o log.o match.o mpaux.o nchan.o packet.o radix.o rijndael.o entropy.o readpass.o rsa.o ssh-dss.o ssh-rsa.o tildexpand.o ttymodes.o uidswap.o util.o uuencode.o xmalloc.o
40 40
@@ -44,8 +44,8 @@ SSHOBJS= ssh.o sshconnect.o sshconnect1.o sshconnect2.o log-client.o readconf.o
44 44
45SSHDOBJS= sshd.o auth.o auth1.o auth2.o auth-skey.o auth2-skey.o auth-rhosts.o auth-options.o auth-krb4.o auth-pam.o auth2-pam.o auth-passwd.o auth-rsa.o auth-rh-rsa.o dh.o pty.o log-server.o login.o loginrec.o servconf.o serverloop.o md5crypt.o session.o 45SSHDOBJS= sshd.o auth.o auth1.o auth2.o auth-skey.o auth2-skey.o auth-rhosts.o auth-options.o auth-krb4.o auth-pam.o auth2-pam.o auth-passwd.o auth-rsa.o auth-rh-rsa.o dh.o pty.o log-server.o login.o loginrec.o servconf.o serverloop.o md5crypt.o session.o
46 46
47TROFFMAN = scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh.1 sshd.8 sftp-server.8 47TROFFMAN = scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8
48CATMAN = scp.0 ssh-add.0 ssh-agent.0 ssh-keygen.0 ssh.0 sshd.0 sftp-server.0 48CATMAN = scp.0 ssh-add.0 ssh-agent.0 ssh-keygen.0 ssh-keyscan.0 ssh.0 sshd.0 sftp-server.0
49MANPAGES = @MANTYPE@ 49MANPAGES = @MANTYPE@
50 50
51CONFIGFILES=sshd_config ssh_config primes 51CONFIGFILES=sshd_config ssh_config primes
@@ -100,6 +100,9 @@ ssh-agent$(EXEEXT): libopenbsd-compat.a libssh.a ssh-agent.o log-client.o
100ssh-keygen$(EXEEXT): libopenbsd-compat.a libssh.a ssh-keygen.o log-client.o 100ssh-keygen$(EXEEXT): libopenbsd-compat.a libssh.a ssh-keygen.o log-client.o
101 $(LD) -o $@ ssh-keygen.o log-client.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) 101 $(LD) -o $@ ssh-keygen.o log-client.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
102 102
103ssh-keyscan$(EXEEXT): libopenbsd-compat.a libssh.a log-client.o ssh-keyscan.o
104 $(LD) -o $@ ssh-keyscan.o log-client.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
105
103sftp-server$(EXEEXT): libopenbsd-compat.a libssh.a sftp-server.o log-server.o 106sftp-server$(EXEEXT): libopenbsd-compat.a libssh.a sftp-server.o log-server.o
104 $(LD) -o $@ sftp-server.o log-server.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) 107 $(LD) -o $@ sftp-server.o log-server.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
105 108
@@ -146,6 +149,7 @@ install-files:
146 $(INSTALL) -m 0755 -s ssh-add $(DESTDIR)$(bindir)/ssh-add 149 $(INSTALL) -m 0755 -s ssh-add $(DESTDIR)$(bindir)/ssh-add
147 $(INSTALL) -m 0755 -s ssh-agent $(DESTDIR)$(bindir)/ssh-agent 150 $(INSTALL) -m 0755 -s ssh-agent $(DESTDIR)$(bindir)/ssh-agent
148 $(INSTALL) -m 0755 -s ssh-keygen $(DESTDIR)$(bindir)/ssh-keygen 151 $(INSTALL) -m 0755 -s ssh-keygen $(DESTDIR)$(bindir)/ssh-keygen
152 $(INSTALL) -m 0775 -s ssh-keyscan $(DESTDIR)$(bindir)/ssh-keyscan
149 $(INSTALL) -m 0755 -s sshd $(DESTDIR)$(sbindir)/sshd 153 $(INSTALL) -m 0755 -s sshd $(DESTDIR)$(sbindir)/sshd
150 $(INSTALL) -m 0755 -s sftp-server $(DESTDIR)$(libexecdir)/sftp-server 154 $(INSTALL) -m 0755 -s sftp-server $(DESTDIR)$(libexecdir)/sftp-server
151 $(INSTALL) -m 644 ssh.[01].out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1 155 $(INSTALL) -m 644 ssh.[01].out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1
@@ -229,12 +233,14 @@ uninstall:
229 -rm -f $(DESTDIR)$(bindir)/ssh-add$(EXEEXT) 233 -rm -f $(DESTDIR)$(bindir)/ssh-add$(EXEEXT)
230 -rm -f $(DESTDIR)$(bindir)/ssh-agent$(EXEEXT) 234 -rm -f $(DESTDIR)$(bindir)/ssh-agent$(EXEEXT)
231 -rm -f $(DESTDIR)$(bindir)/ssh-keygen$(EXEEXT) 235 -rm -f $(DESTDIR)$(bindir)/ssh-keygen$(EXEEXT)
236 -rm -f $(DESTDIR)$(bindir)/ssh-keyscan$(EXEEXT)
232 -rm -f $(DESTDIR)$(sbindir)/sshd$(EXEEXT) 237 -rm -f $(DESTDIR)$(sbindir)/sshd$(EXEEXT)
233 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1 238 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1
234 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1 239 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1
235 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1 240 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1
236 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-agent.1 241 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-agent.1
237 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keygen.1 242 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keygen.1
243 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keyscan.1
238 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8 244 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8
239 -rm -f $(DESTDIR)$(bindir)/slogin 245 -rm -f $(DESTDIR)$(bindir)/slogin
240 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/slogin.1 246 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/slogin.1
diff --git a/ssh-keyscan.1 b/ssh-keyscan.1
new file mode 100644
index 000000000..efd6e744a
--- /dev/null
+++ b/ssh-keyscan.1
@@ -0,0 +1,94 @@
1.Dd January 1, 1996
2.Dt ssh-keyscan 1
3.Os
4.Sh NAME
5.Nm ssh-keyscan
6.Nd gather ssh public keys
7.Sh SYNOPSIS
8.Nm ssh-keyscan
9.Op Fl t Ar timeout
10.Op Ar -- | host | addrlist namelist
11.Op Fl f Ar files ...
12.Sh DESCRIPTION
13.Nm
14is a utility for gathering the public ssh host keys of a number of
15hosts. It was designed to aid in building and verifying
16.Pa ssh_known_hosts
17files.
18.Nm
19provides a minimal interface suitable for use by shell and perl
20scripts.
21.Pp
22.Nm
23uses non-blocking socket I/O to contact as many hosts as possible in
24parallel, so it is very efficient. The keys from a domain of 1,000
25hosts can be collected in tens of seconds, even when some of those
26hosts are down or do not run ssh. You do not need login access to the
27machines you are scanning, nor does does the scanning process involve
28any encryption.
29.Sh SECURITY
30If you make an ssh_known_hosts file using
31.Nm
32without verifying the keys, you will be vulnerable to
33.I man in the middle
34attacks.
35On the other hand, if your security model allows such a risk,
36.Nm
37can help you detect tampered keyfiles or man in the middle attacks which
38have begun after you created your ssh_known_hosts file.
39.Sh OPTIONS
40.Bl -tag -width Ds
41.It Fl t
42Set the timeout for connection attempts. If
43.Pa timeout
44seconds have elapsed since a connection was initiated to a host or since the
45last time anything was read from that host, then the connection is
46closed and the host in question considered unavailable. Default is 5
47seconds.
48.It Fl f
49Read hosts or
50.Pa addrlist namelist
51pairs from this file, one per line.
52If
53.Pa -
54is supplied instead of a filename,
55.Nm
56will read hosts or
57.Pa addrlist namelist
58pairs from the standard input.
59.Sh EXAMPLES
60.Pp
61Print the host key for machine
62.Pa hostname :
63.Bd -literal
64ssh-keyscan hostname
65.Ed
66.Pp
67Find all hosts from the file
68.Pa ssh_hosts
69which have new or different keys from those in the sorted file
70.Pa ssh_known_hosts :
71.Bd -literal
72ssh-keyscan -f ssh_hosts | sort -u - ssh_known_hosts | \e\
73 diff ssh_known_hosts -
74.Ed
75.Pp
76.Sh FILES
77.Pp
78.Pa Input format:
791.2.3.4,1.2.4.4 name.my.domain,name,n.my.domain,n,1.2.3.4,1.2.4.4
80.Pp
81.Pa Output format:
82host-or-namelist bits exponent modulus
83.Pp
84.Pa /etc/ssh_known_hosts
85.Sh BUGS
86It generates "Connection closed by remote host" messages on the consoles
87of all the machines it scans.
88This is because it opens a connection to the ssh port, reads the public
89key, and drops the connection as soon as it gets the key.
90.Sh SEE ALSO
91.Xr ssh 1
92.Xr sshd 8
93.Sh AUTHOR
94David Mazieres <dm@lcs.mit.edu>
diff --git a/ssh-keyscan.c b/ssh-keyscan.c
new file mode 100644
index 000000000..0b2ed40a9
--- /dev/null
+++ b/ssh-keyscan.c
@@ -0,0 +1,605 @@
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.1 2000/12/04 19:24:02 markus Exp $");
12
13#include <sys/queue.h>
14#include <err.h>
15#include <errno.h>
16
17#include <openssl/bn.h>
18#include <openssl/rsa.h>
19#include <openssl/dsa.h>
20
21#include "xmalloc.h"
22#include "ssh.h"
23#include "key.h"
24#include "buffer.h"
25#include "bufaux.h"
26
27static int argno = 1; /* Number of argument currently being parsed */
28
29int family = AF_UNSPEC; /* IPv4, IPv6 or both */
30
31#define PORT 22
32#define MAXMAXFD 256
33
34/* The number of seconds after which to give up on a TCP connection */
35int timeout = 5;
36
37int maxfd;
38#define maxcon (maxfd - 10)
39
40char *prog;
41fd_set read_wait;
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 unsigned 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 unsigned int size;
80 int lineno;
81 const char *filename;
82 FILE *stream;
83 void (*errfun) (const char *,...);
84} Linebuf;
85
86static inline Linebuf *
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 free(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 free(lb);
113 return (NULL);
114 }
115 lb->errfun = errfun;
116 lb->lineno = 0;
117 return (lb);
118}
119
120static inline void
121Linebuf_free(Linebuf * lb)
122{
123 fclose(lb->stream);
124 free(lb->buf);
125 free(lb);
126}
127
128static inline void
129Linebuf_restart(Linebuf * lb)
130{
131 clearerr(lb->stream);
132 rewind(lb->stream);
133 lb->lineno = 0;
134}
135
136static inline int
137Linebuf_lineno(Linebuf * lb)
138{
139 return (lb->lineno);
140}
141
142static inline char *
143getline(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, strerror(errno));
153 return (NULL);
154 }
155 n = strlen(lb->buf);
156
157 /* Return it or an error if it fits */
158 if (n > 0 && lb->buf[n - 1] == '\n') {
159 lb->buf[n - 1] = '\0';
160 return (lb->buf);
161 }
162 if (n != lb->size - 1) {
163 if (lb->errfun)
164 (*lb->errfun) ("%s: skipping incomplete last line\n", lb->filename);
165 return (NULL);
166 }
167 /* Double the buffer if we need more space */
168 if (!(lb->buf = realloc(lb->buf, (lb->size *= 2)))) {
169 if (lb->errfun)
170 (*lb->errfun) ("linebuf (%s): realloc failed\n", lb->filename);
171 return (NULL);
172 }
173 }
174}
175
176static int
177fdlim_get(int hard)
178{
179 struct rlimit rlfd;
180 if (getrlimit(RLIMIT_NOFILE, &rlfd) < 0)
181 return (-1);
182 if ((hard ? rlfd.rlim_max : rlfd.rlim_cur) == RLIM_INFINITY)
183 return 10000;
184 else
185 return hard ? rlfd.rlim_max : rlfd.rlim_cur;
186}
187
188static int
189fdlim_set(int lim)
190{
191 struct rlimit rlfd;
192 if (lim <= 0)
193 return (-1);
194 if (getrlimit(RLIMIT_NOFILE, &rlfd) < 0)
195 return (-1);
196 rlfd.rlim_cur = lim;
197 if (setrlimit(RLIMIT_NOFILE, &rlfd) < 0)
198 return (-1);
199 return (0);
200}
201
202/*
203 * This is an strsep function that returns a null field for adjacent
204 * separators. This is the same as the 4.4BSD strsep, but different from the
205 * one in the GNU libc.
206 */
207inline char *
208xstrsep(char **str, const char *delim)
209{
210 char *s, *e;
211
212 if (!**str)
213 return (NULL);
214
215 s = *str;
216 e = s + strcspn(s, delim);
217
218 if (*e != '\0')
219 *e++ = '\0';
220 *str = e;
221
222 return (s);
223}
224
225/*
226 * Get the next non-null token (like GNU strsep). Strsep() will return a
227 * null token for two adjacent separators, so we may have to loop.
228 */
229char *
230strnnsep(char **stringp, char *delim)
231{
232 char *tok;
233
234 do {
235 tok = xstrsep(stringp, delim);
236 } while (tok && *tok == '\0');
237 return (tok);
238}
239
240void
241keyprint(char *host, char *output_name, char *kd, int len)
242{
243 static Key *rsa;
244 static Buffer msg;
245
246 if (rsa == NULL) {
247 buffer_init(&msg);
248 rsa = key_new(KEY_RSA1);
249 }
250 buffer_append(&msg, kd, len);
251 buffer_consume(&msg, 8 - (len & 7)); /* padding */
252 if (buffer_get_char(&msg) != (int) SSH_SMSG_PUBLIC_KEY) {
253 error("%s: invalid packet type", host);
254 buffer_clear(&msg);
255 return;
256 }
257 buffer_consume(&msg, 8); /* cookie */
258
259 /* server key */
260 (void) buffer_get_int(&msg);
261 buffer_get_bignum(&msg, rsa->rsa->e);
262 buffer_get_bignum(&msg, rsa->rsa->n);
263
264 /* host key */
265 (void) buffer_get_int(&msg);
266 buffer_get_bignum(&msg, rsa->rsa->e);
267 buffer_get_bignum(&msg, rsa->rsa->n);
268 buffer_clear(&msg);
269
270 fprintf(stdout, "%s ", output_name ? output_name : host);
271 key_write(rsa, stdout);
272 fputs("\n", stdout);
273}
274
275int
276tcpconnect(char *host)
277{
278 struct addrinfo hints, *ai, *aitop;
279 char strport[NI_MAXSERV];
280 int gaierr, s = -1;
281
282 snprintf(strport, sizeof strport, "%d", PORT);
283 memset(&hints, 0, sizeof(hints));
284 hints.ai_family = family;
285 hints.ai_socktype = SOCK_STREAM;
286 if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0)
287 fatal("getaddrinfo %s: %s", host, gai_strerror(gaierr));
288 for (ai = aitop; ai; ai = ai->ai_next) {
289 s = socket(ai->ai_family, SOCK_STREAM, 0);
290 if (s < 0) {
291 error("socket: %s", strerror(errno));
292 continue;
293 }
294 if (fcntl(s, F_SETFL, O_NDELAY) < 0)
295 fatal("F_SETFL: %s", strerror(errno));
296 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0 &&
297 errno != EINPROGRESS)
298 error("connect (`%s'): %s", host, strerror(errno));
299 else
300 break;
301 close(s);
302 s = -1;
303 }
304 freeaddrinfo(aitop);
305 return s;
306}
307
308int
309conalloc(char *iname, char *oname)
310{
311 int s;
312 char *namebase, *name, *namelist;
313
314 namebase = namelist = xstrdup(iname);
315
316 do {
317 name = xstrsep(&namelist, ",");
318 if (!name) {
319 free(namebase);
320 return (-1);
321 }
322 } while ((s = tcpconnect(name)) < 0);
323
324 if (s >= maxfd)
325 fatal("conalloc: fdno %d too high\n", s);
326 if (fdcon[s].c_status)
327 fatal("conalloc: attempt to reuse fdno %d\n", s);
328
329 fdcon[s].c_fd = s;
330 fdcon[s].c_status = CS_CON;
331 fdcon[s].c_namebase = namebase;
332 fdcon[s].c_name = name;
333 fdcon[s].c_namelist = namelist;
334 fdcon[s].c_output_name = xstrdup(oname);
335 fdcon[s].c_data = (char *) &fdcon[s].c_plen;
336 fdcon[s].c_len = 4;
337 fdcon[s].c_off = 0;
338 gettimeofday(&fdcon[s].c_tv, NULL);
339 fdcon[s].c_tv.tv_sec += timeout;
340 TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link);
341 FD_SET(s, &read_wait);
342 ncon++;
343 return (s);
344}
345
346void
347confree(int s)
348{
349 close(s);
350 if (s >= maxfd || fdcon[s].c_status == CS_UNUSED)
351 fatal("confree: attempt to free bad fdno %d\n", s);
352 free(fdcon[s].c_namebase);
353 free(fdcon[s].c_output_name);
354 if (fdcon[s].c_status == CS_KEYS)
355 free(fdcon[s].c_data);
356 fdcon[s].c_status = CS_UNUSED;
357 TAILQ_REMOVE(&tq, &fdcon[s], c_link);
358 FD_CLR(s, &read_wait);
359 ncon--;
360}
361
362void
363contouch(int s)
364{
365 TAILQ_REMOVE(&tq, &fdcon[s], c_link);
366 gettimeofday(&fdcon[s].c_tv, NULL);
367 fdcon[s].c_tv.tv_sec += timeout;
368 TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link);
369}
370
371int
372conrecycle(int s)
373{
374 int ret;
375 con *c = &fdcon[s];
376 char *iname, *oname;
377
378 iname = xstrdup(c->c_namelist);
379 oname = c->c_output_name;
380 c->c_output_name = NULL;/* prevent it from being freed */
381 confree(s);
382 ret = conalloc(iname, oname);
383 free(iname);
384 return (ret);
385}
386
387void
388congreet(int s)
389{
390 char buf[80];
391 int n;
392 con *c = &fdcon[s];
393
394 n = read(s, buf, sizeof(buf));
395 if (n < 0) {
396 if (errno != ECONNREFUSED)
397 error("read (%s): %s", c->c_name, strerror(errno));
398 conrecycle(s);
399 return;
400 }
401 if (buf[n - 1] != '\n') {
402 error("%s: bad greeting", c->c_name);
403 confree(s);
404 return;
405 }
406 buf[n - 1] = '\0';
407 fprintf(stderr, "# %s %s\n", c->c_name, buf);
408 n = snprintf(buf, sizeof buf, "SSH-1.5-OpenSSH-keyscan\r\n");
409 if (write(s, buf, n) != n) {
410 error("write (%s): %s", c->c_name, strerror(errno));
411 confree(s);
412 return;
413 }
414 c->c_status = CS_SIZE;
415 contouch(s);
416}
417
418void
419conread(int s)
420{
421 int n;
422 con *c = &fdcon[s];
423
424 if (c->c_status == CS_CON) {
425 congreet(s);
426 return;
427 }
428 n = read(s, c->c_data + c->c_off, c->c_len - c->c_off);
429 if (n < 0) {
430 error("read (%s): %s", c->c_name, strerror(errno));
431 confree(s);
432 return;
433 }
434 c->c_off += n;
435
436 if (c->c_off == c->c_len)
437 switch (c->c_status) {
438 case CS_SIZE:
439 c->c_plen = htonl(c->c_plen);
440 c->c_len = c->c_plen + 8 - (c->c_plen & 7);
441 c->c_off = 0;
442 c->c_data = xmalloc(c->c_len);
443 c->c_status = CS_KEYS;
444 break;
445 case CS_KEYS:
446 keyprint(c->c_name, c->c_output_name, c->c_data, c->c_plen);
447 confree(s);
448 return;
449 break;
450 default:
451 fatal("conread: invalid status %d\n", c->c_status);
452 break;
453 }
454
455 contouch(s);
456}
457
458void
459conloop(void)
460{
461 fd_set r, e;
462 struct timeval seltime, now;
463 int i;
464 con *c;
465
466 gettimeofday(&now, NULL);
467 c = tq.tqh_first;
468
469 if (c &&
470 (c->c_tv.tv_sec > now.tv_sec ||
471 (c->c_tv.tv_sec == now.tv_sec && c->c_tv.tv_usec > now.tv_usec))) {
472 seltime = c->c_tv;
473 seltime.tv_sec -= now.tv_sec;
474 seltime.tv_usec -= now.tv_usec;
475 if ((int) seltime.tv_usec < 0) {
476 seltime.tv_usec += 1000000;
477 seltime.tv_sec--;
478 }
479 } else
480 seltime.tv_sec = seltime.tv_usec = 0;
481
482 r = e = read_wait;
483 select(maxfd, &r, NULL, &e, &seltime);
484 for (i = 0; i < maxfd; i++)
485 if (FD_ISSET(i, &e)) {
486 error("%s: exception!", fdcon[i].c_name);
487 confree(i);
488 } else if (FD_ISSET(i, &r))
489 conread(i);
490
491 c = tq.tqh_first;
492 while (c &&
493 (c->c_tv.tv_sec < now.tv_sec ||
494 (c->c_tv.tv_sec == now.tv_sec && c->c_tv.tv_usec < now.tv_usec))) {
495 int s = c->c_fd;
496 c = c->c_link.tqe_next;
497 conrecycle(s);
498 }
499}
500
501char *
502nexthost(int argc, char **argv)
503{
504 static Linebuf *lb;
505
506 for (;;) {
507 if (!lb) {
508 if (argno >= argc)
509 return (NULL);
510 if (argv[argno][0] != '-')
511 return (argv[argno++]);
512 if (!strcmp(argv[argno], "--")) {
513 if (++argno >= argc)
514 return (NULL);
515 return (argv[argno++]);
516 } else if (!strncmp(argv[argno], "-f", 2)) {
517 char *fname;
518 if (argv[argno][2])
519 fname = &argv[argno++][2];
520 else if (++argno >= argc) {
521 error("missing filename for `-f'");
522 return (NULL);
523 } else
524 fname = argv[argno++];
525 if (!strcmp(fname, "-"))
526 fname = NULL;
527 lb = Linebuf_alloc(fname, warn);
528 } else
529 error("ignoring invalid/misplaced option `%s'", argv[argno++]);
530 } else {
531 char *line;
532 line = getline(lb);
533 if (line)
534 return (line);
535 Linebuf_free(lb);
536 lb = NULL;
537 }
538 }
539}
540
541static void
542usage(void)
543{
544 fatal("usage: %s [-t timeout] { [--] host | -f file } ...\n", prog);
545 return;
546}
547
548int
549main(int argc, char **argv)
550{
551 char *host = NULL;
552
553 TAILQ_INIT(&tq);
554
555 if ((prog = strrchr(argv[0], '/')))
556 prog++;
557 else
558 prog = argv[0];
559
560 if (argc <= argno)
561 usage();
562
563 if (argv[1][0] == '-' && argv[1][1] == 't') {
564 argno++;
565 if (argv[1][2])
566 timeout = atoi(&argv[1][2]);
567 else {
568 if (argno >= argc)
569 usage();
570 timeout = atoi(argv[argno++]);
571 }
572 if (timeout <= 0)
573 usage();
574 }
575 if (argc <= argno)
576 usage();
577
578 maxfd = fdlim_get(1);
579 if (maxfd < 0)
580 fatal("%s: fdlim_get: bad value\n", prog);
581 if (maxfd > MAXMAXFD)
582 maxfd = MAXMAXFD;
583 if (maxcon <= 0)
584 fatal("%s: not enough file descriptors\n", prog);
585 if (maxfd > fdlim_get(0))
586 fdlim_set(maxfd);
587 fdcon = xmalloc(maxfd * sizeof(con));
588
589 do {
590 while (ncon < maxcon) {
591 char *name;
592
593 host = nexthost(argc, argv);
594 if (host == NULL)
595 break;
596 name = strnnsep(&host, " \t\n");
597 conalloc(name, *host ? host : name);
598 }
599 conloop();
600 } while (host);
601 while (ncon > 0)
602 conloop();
603
604 return (0);
605}