diff options
-rw-r--r-- | ChangeLog | 10 | ||||
-rw-r--r-- | Makefile.in | 12 | ||||
-rw-r--r-- | ssh-keyscan.1 | 94 | ||||
-rw-r--r-- | ssh-keyscan.c | 605 |
4 files changed, 717 insertions, 4 deletions
@@ -1,6 +1,14 @@ | |||
1 | 20001205 | ||
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 | |||
1 | 20001204 | 9 | 20001204 |
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 | ||
35 | INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@ | 35 | INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@ |
36 | 36 | ||
37 | TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT) | 37 | TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT) |
38 | 38 | ||
39 | LIBSSH_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 | 39 | LIBSSH_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 | ||
45 | SSHDOBJS= 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 | 45 | SSHDOBJS= 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 | ||
47 | TROFFMAN = scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh.1 sshd.8 sftp-server.8 | 47 | TROFFMAN = scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8 |
48 | CATMAN = scp.0 ssh-add.0 ssh-agent.0 ssh-keygen.0 ssh.0 sshd.0 sftp-server.0 | 48 | CATMAN = scp.0 ssh-add.0 ssh-agent.0 ssh-keygen.0 ssh-keyscan.0 ssh.0 sshd.0 sftp-server.0 |
49 | MANPAGES = @MANTYPE@ | 49 | MANPAGES = @MANTYPE@ |
50 | 50 | ||
51 | CONFIGFILES=sshd_config ssh_config primes | 51 | CONFIGFILES=sshd_config ssh_config primes |
@@ -100,6 +100,9 @@ ssh-agent$(EXEEXT): libopenbsd-compat.a libssh.a ssh-agent.o log-client.o | |||
100 | ssh-keygen$(EXEEXT): libopenbsd-compat.a libssh.a ssh-keygen.o log-client.o | 100 | ssh-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 | ||
103 | ssh-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 | |||
103 | sftp-server$(EXEEXT): libopenbsd-compat.a libssh.a sftp-server.o log-server.o | 106 | sftp-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 | ||
14 | is a utility for gathering the public ssh host keys of a number of | ||
15 | hosts. It was designed to aid in building and verifying | ||
16 | .Pa ssh_known_hosts | ||
17 | files. | ||
18 | .Nm | ||
19 | provides a minimal interface suitable for use by shell and perl | ||
20 | scripts. | ||
21 | .Pp | ||
22 | .Nm | ||
23 | uses non-blocking socket I/O to contact as many hosts as possible in | ||
24 | parallel, so it is very efficient. The keys from a domain of 1,000 | ||
25 | hosts can be collected in tens of seconds, even when some of those | ||
26 | hosts are down or do not run ssh. You do not need login access to the | ||
27 | machines you are scanning, nor does does the scanning process involve | ||
28 | any encryption. | ||
29 | .Sh SECURITY | ||
30 | If you make an ssh_known_hosts file using | ||
31 | .Nm | ||
32 | without verifying the keys, you will be vulnerable to | ||
33 | .I man in the middle | ||
34 | attacks. | ||
35 | On the other hand, if your security model allows such a risk, | ||
36 | .Nm | ||
37 | can help you detect tampered keyfiles or man in the middle attacks which | ||
38 | have begun after you created your ssh_known_hosts file. | ||
39 | .Sh OPTIONS | ||
40 | .Bl -tag -width Ds | ||
41 | .It Fl t | ||
42 | Set the timeout for connection attempts. If | ||
43 | .Pa timeout | ||
44 | seconds have elapsed since a connection was initiated to a host or since the | ||
45 | last time anything was read from that host, then the connection is | ||
46 | closed and the host in question considered unavailable. Default is 5 | ||
47 | seconds. | ||
48 | .It Fl f | ||
49 | Read hosts or | ||
50 | .Pa addrlist namelist | ||
51 | pairs from this file, one per line. | ||
52 | If | ||
53 | .Pa - | ||
54 | is supplied instead of a filename, | ||
55 | .Nm | ||
56 | will read hosts or | ||
57 | .Pa addrlist namelist | ||
58 | pairs from the standard input. | ||
59 | .Sh EXAMPLES | ||
60 | .Pp | ||
61 | Print the host key for machine | ||
62 | .Pa hostname : | ||
63 | .Bd -literal | ||
64 | ssh-keyscan hostname | ||
65 | .Ed | ||
66 | .Pp | ||
67 | Find all hosts from the file | ||
68 | .Pa ssh_hosts | ||
69 | which have new or different keys from those in the sorted file | ||
70 | .Pa ssh_known_hosts : | ||
71 | .Bd -literal | ||
72 | ssh-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: | ||
79 | 1.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: | ||
82 | host-or-namelist bits exponent modulus | ||
83 | .Pp | ||
84 | .Pa /etc/ssh_known_hosts | ||
85 | .Sh BUGS | ||
86 | It generates "Connection closed by remote host" messages on the consoles | ||
87 | of all the machines it scans. | ||
88 | This is because it opens a connection to the ssh port, reads the public | ||
89 | key, 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 | ||
94 | David 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" | ||
11 | RCSID("$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 | |||
27 | static int argno = 1; /* Number of argument currently being parsed */ | ||
28 | |||
29 | int 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 */ | ||
35 | int timeout = 5; | ||
36 | |||
37 | int maxfd; | ||
38 | #define maxcon (maxfd - 10) | ||
39 | |||
40 | char *prog; | ||
41 | fd_set read_wait; | ||
42 | int 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 | */ | ||
48 | typedef 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 | |||
67 | TAILQ_HEAD(conlist, Connection) tq; /* Timeout Queue */ | ||
68 | con *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 | |||
77 | typedef 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 | |||
86 | static inline Linebuf * | ||
87 | Linebuf_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 | |||
120 | static inline void | ||
121 | Linebuf_free(Linebuf * lb) | ||
122 | { | ||
123 | fclose(lb->stream); | ||
124 | free(lb->buf); | ||
125 | free(lb); | ||
126 | } | ||
127 | |||
128 | static inline void | ||
129 | Linebuf_restart(Linebuf * lb) | ||
130 | { | ||
131 | clearerr(lb->stream); | ||
132 | rewind(lb->stream); | ||
133 | lb->lineno = 0; | ||
134 | } | ||
135 | |||
136 | static inline int | ||
137 | Linebuf_lineno(Linebuf * lb) | ||
138 | { | ||
139 | return (lb->lineno); | ||
140 | } | ||
141 | |||
142 | static inline char * | ||
143 | 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, 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 | |||
176 | static int | ||
177 | fdlim_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 | |||
188 | static int | ||
189 | fdlim_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 | */ | ||
207 | inline char * | ||
208 | xstrsep(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 | */ | ||
229 | char * | ||
230 | strnnsep(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 | |||
240 | void | ||
241 | keyprint(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 | |||
275 | int | ||
276 | tcpconnect(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 | |||
308 | int | ||
309 | conalloc(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 | |||
346 | void | ||
347 | confree(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 | |||
362 | void | ||
363 | contouch(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 | |||
371 | int | ||
372 | conrecycle(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 | |||
387 | void | ||
388 | congreet(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 | |||
418 | void | ||
419 | conread(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 | |||
458 | void | ||
459 | conloop(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 | |||
501 | char * | ||
502 | nexthost(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 | |||
541 | static void | ||
542 | usage(void) | ||
543 | { | ||
544 | fatal("usage: %s [-t timeout] { [--] host | -f file } ...\n", prog); | ||
545 | return; | ||
546 | } | ||
547 | |||
548 | int | ||
549 | main(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 | } | ||