]>
Commit | Line | Data |
---|---|---|
4e838120 | 1 | /* $OpenBSD: ssh-keyscan.c,v 1.155 2024/01/11 01:45:36 djm Exp $ */ |
b6434ae0 BL |
2 | /* |
3 | * Copyright 1995, 1996 by David Mazieres <dm@lcs.mit.edu>. | |
4 | * | |
5 | * Modification and redistribution in source and binary forms is | |
6 | * permitted provided that due credit is given to the author and the | |
a238f6e8 | 7 | * OpenBSD project by leaving this copyright notice intact. |
b6434ae0 BL |
8 | */ |
9 | ||
10 | #include "includes.h" | |
cd4223c2 | 11 | |
48b68ce1 | 12 | #include <sys/types.h> |
9b481510 | 13 | #include "openbsd-compat/sys-queue.h" |
cd4223c2 | 14 | #include <sys/resource.h> |
9aec9194 DM |
15 | #ifdef HAVE_SYS_TIME_H |
16 | # include <sys/time.h> | |
17 | #endif | |
e3476ed0 | 18 | |
46aa3e0c DT |
19 | #include <netinet/in.h> |
20 | #include <arpa/inet.h> | |
21 | ||
670104b9 | 22 | #ifdef WITH_OPENSSL |
e3476ed0 | 23 | #include <openssl/bn.h> |
670104b9 | 24 | #endif |
e3476ed0 | 25 | |
a69062f1 | 26 | #include <limits.h> |
b8fe89c4 | 27 | #include <netdb.h> |
deecec98 | 28 | #include <errno.h> |
b544ce1a | 29 | #ifdef HAVE_POLL_H |
7eec7679 | 30 | #include <poll.h> |
b544ce1a | 31 | #endif |
e7a1e5cf | 32 | #include <stdarg.h> |
a7a73ee3 | 33 | #include <stdio.h> |
e7a1e5cf | 34 | #include <stdlib.h> |
d7834353 | 35 | #include <signal.h> |
e3476ed0 | 36 | #include <string.h> |
e6b3b610 | 37 | #include <unistd.h> |
b6434ae0 | 38 | |
b6434ae0 BL |
39 | #include "xmalloc.h" |
40 | #include "ssh.h" | |
3f797653 | 41 | #include "sshbuf.h" |
42 | #include "sshkey.h" | |
d7834353 | 43 | #include "cipher.h" |
d651f5c9 | 44 | #include "digest.h" |
325e70c9 BL |
45 | #include "kex.h" |
46 | #include "compat.h" | |
47 | #include "myproposal.h" | |
48 | #include "packet.h" | |
49 | #include "dispatch.h" | |
226cfa03 | 50 | #include "log.h" |
d20b855b | 51 | #include "atomicio.h" |
325e70c9 | 52 | #include "misc.h" |
db7b8171 | 53 | #include "hostfile.h" |
3f797653 | 54 | #include "ssherr.h" |
55 | #include "ssh_api.h" | |
1a348359 | 56 | #include "dns.h" |
11925885 | 57 | #include "addr.h" |
b6434ae0 | 58 | |
325e70c9 BL |
59 | /* Flag indicating whether IPv4 or IPv6. This can be set on the command line. |
60 | Default value is AF_UNSPEC means both IPv4 and IPv6. */ | |
325e70c9 | 61 | int IPv4or6 = AF_UNSPEC; |
b6434ae0 | 62 | |
325e70c9 | 63 | int ssh_port = SSH_DEFAULT_PORT; |
76e7d9b6 | 64 | |
873d3e7d | 65 | #define KT_DSA (1) |
66 | #define KT_RSA (1<<1) | |
67 | #define KT_ECDSA (1<<2) | |
68 | #define KT_ED25519 (1<<3) | |
1b11ea7c | 69 | #define KT_XMSS (1<<4) |
9b6e30b9 | 70 | #define KT_ECDSA_SK (1<<5) |
71 | #define KT_ED25519_SK (1<<6) | |
873d3e7d | 72 | |
73 | #define KT_MIN KT_DSA | |
9b6e30b9 | 74 | #define KT_MAX KT_ED25519_SK |
76e7d9b6 | 75 | |
3a424cdd | 76 | int get_cert = 0; |
9b6e30b9 | 77 | int get_keytypes = KT_RSA|KT_ECDSA|KT_ED25519|KT_ECDSA_SK|KT_ED25519_SK; |
b6434ae0 | 78 | |
db7b8171 DM |
79 | int hash_hosts = 0; /* Hash hostname on output */ |
80 | ||
1a348359 | 81 | int print_sshfp = 0; /* Print SSHFP records instead of known_hosts */ |
82 | ||
c2c18a39 | 83 | int found_one = 0; /* Successfully found a key */ |
84 | ||
d651f5c9 | 85 | int hashalg = -1; /* Hash for SSHFP records or -1 for all */ |
86 | ||
b6434ae0 BL |
87 | #define MAXMAXFD 256 |
88 | ||
89 | /* The number of seconds after which to give up on a TCP connection */ | |
90 | int timeout = 5; | |
91 | ||
92 | int maxfd; | |
d20b855b | 93 | #define MAXCON (maxfd - 10) |
b6434ae0 | 94 | |
ec84dc12 | 95 | extern char *__progname; |
7eec7679 | 96 | struct pollfd *read_wait; |
b6434ae0 BL |
97 | int ncon; |
98 | ||
99 | /* | |
100 | * Keep a connection structure for each file descriptor. The state | |
101 | * associated with file descriptor n is held in fdcon[n]. | |
102 | */ | |
103 | typedef struct Connection { | |
46c16220 | 104 | u_char c_status; /* State of connection on this file desc. */ |
b6434ae0 BL |
105 | #define CS_UNUSED 0 /* File descriptor unused */ |
106 | #define CS_CON 1 /* Waiting to connect/read greeting */ | |
107 | #define CS_SIZE 2 /* Waiting to read initial packet size */ | |
108 | #define CS_KEYS 3 /* Waiting to read public key packet */ | |
109 | int c_fd; /* Quick lookup: c->c_fd == c - fdcon */ | |
110 | int c_plen; /* Packet length field for ssh packet */ | |
111 | int c_len; /* Total bytes which must be read. */ | |
112 | int c_off; /* Length of data read so far. */ | |
873d3e7d | 113 | int c_keytype; /* Only one of KT_* */ |
c265e2e6 | 114 | sig_atomic_t c_done; /* SSH2 done */ |
b6434ae0 BL |
115 | char *c_namebase; /* Address to free for c_name and c_namelist */ |
116 | char *c_name; /* Hostname of connection for errors */ | |
117 | char *c_namelist; /* Pointer to other possible addresses */ | |
118 | char *c_output_name; /* Hostname of connection for output */ | |
119 | char *c_data; /* Data read from this fd */ | |
3f797653 | 120 | struct ssh *c_ssh; /* SSH-connection */ |
7eec7679 | 121 | struct timespec c_ts; /* Time at which connection gets aborted */ |
b6434ae0 BL |
122 | TAILQ_ENTRY(Connection) c_link; /* List of connections in timeout order. */ |
123 | } con; | |
124 | ||
125 | TAILQ_HEAD(conlist, Connection) tq; /* Timeout Queue */ | |
126 | con *fdcon; | |
127 | ||
3f797653 | 128 | static void keyprint(con *c, struct sshkey *key); |
129 | ||
bba81213 | 130 | static int |
b6434ae0 BL |
131 | fdlim_get(int hard) |
132 | { | |
5adbad22 | 133 | #if defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE) |
b6434ae0 | 134 | struct rlimit rlfd; |
cfca6f17 | 135 | rlim_t lim; |
b0a4cd8f | 136 | |
cfca6f17 DM |
137 | if (getrlimit(RLIMIT_NOFILE, &rlfd) == -1) |
138 | return -1; | |
139 | lim = hard ? rlfd.rlim_max : rlfd.rlim_cur; | |
140 | if (lim <= 0) | |
a69062f1 | 141 | return -1; |
cfca6f17 DM |
142 | if (lim == RLIM_INFINITY) |
143 | lim = SSH_SYSFDMAX; | |
144 | if (lim >= INT_MAX) | |
145 | lim = INT_MAX; | |
146 | return lim; | |
2c467a20 | 147 | #else |
cfca6f17 DM |
148 | return (SSH_SYSFDMAX <= 0) ? -1 : |
149 | ((SSH_SYSFDMAX >= INT_MAX) ? INT_MAX : SSH_SYSFDMAX); | |
2c467a20 | 150 | #endif |
b6434ae0 BL |
151 | } |
152 | ||
bba81213 | 153 | static int |
b6434ae0 BL |
154 | fdlim_set(int lim) |
155 | { | |
5adbad22 | 156 | #if defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE) |
b6434ae0 | 157 | struct rlimit rlfd; |
2c467a20 | 158 | #endif |
5c98db50 | 159 | |
b6434ae0 BL |
160 | if (lim <= 0) |
161 | return (-1); | |
5adbad22 | 162 | #if defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE) |
4d28fa78 | 163 | if (getrlimit(RLIMIT_NOFILE, &rlfd) == -1) |
b6434ae0 BL |
164 | return (-1); |
165 | rlfd.rlim_cur = lim; | |
4d28fa78 | 166 | if (setrlimit(RLIMIT_NOFILE, &rlfd) == -1) |
b6434ae0 | 167 | return (-1); |
2c467a20 | 168 | #elif defined (HAVE_SETDTABLESIZE) |
28a7f26d | 169 | setdtablesize(lim); |
2c467a20 | 170 | #endif |
b6434ae0 BL |
171 | return (0); |
172 | } | |
173 | ||
174 | /* | |
175 | * This is an strsep function that returns a null field for adjacent | |
176 | * separators. This is the same as the 4.4BSD strsep, but different from the | |
177 | * one in the GNU libc. | |
178 | */ | |
bba81213 | 179 | static char * |
b6434ae0 BL |
180 | xstrsep(char **str, const char *delim) |
181 | { | |
182 | char *s, *e; | |
183 | ||
184 | if (!**str) | |
185 | return (NULL); | |
186 | ||
187 | s = *str; | |
188 | e = s + strcspn(s, delim); | |
189 | ||
190 | if (*e != '\0') | |
191 | *e++ = '\0'; | |
192 | *str = e; | |
193 | ||
194 | return (s); | |
195 | } | |
196 | ||
197 | /* | |
198 | * Get the next non-null token (like GNU strsep). Strsep() will return a | |
199 | * null token for two adjacent separators, so we may have to loop. | |
200 | */ | |
bba81213 | 201 | static char * |
b6434ae0 BL |
202 | strnnsep(char **stringp, char *delim) |
203 | { | |
204 | char *tok; | |
205 | ||
206 | do { | |
207 | tok = xstrsep(stringp, delim); | |
208 | } while (tok && *tok == '\0'); | |
209 | return (tok); | |
210 | } | |
211 | ||
325e70c9 BL |
212 | |
213 | static int | |
3f797653 | 214 | key_print_wrapper(struct sshkey *hostkey, struct ssh *ssh) |
325e70c9 | 215 | { |
3f797653 | 216 | con *c; |
217 | ||
218 | if ((c = ssh_get_app_data(ssh)) != NULL) | |
219 | keyprint(c, hostkey); | |
220 | /* always abort key exchange */ | |
221 | return -1; | |
325e70c9 BL |
222 | } |
223 | ||
224 | static int | |
225 | ssh2_capable(int remote_major, int remote_minor) | |
226 | { | |
227 | switch (remote_major) { | |
228 | case 1: | |
229 | if (remote_minor == 99) | |
230 | return 1; | |
231 | break; | |
232 | case 2: | |
233 | return 1; | |
234 | default: | |
235 | break; | |
236 | } | |
237 | return 0; | |
238 | } | |
239 | ||
3f797653 | 240 | static void |
325e70c9 BL |
241 | keygrab_ssh2(con *c) |
242 | { | |
9235a030 | 243 | char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT }; |
3f797653 | 244 | int r; |
325e70c9 | 245 | |
3a424cdd | 246 | switch (c->c_keytype) { |
247 | case KT_DSA: | |
248 | myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = get_cert ? | |
249 | "ssh-dss-cert-v01@openssh.com" : "ssh-dss"; | |
250 | break; | |
251 | case KT_RSA: | |
252 | myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = get_cert ? | |
7250879c | 253 | "rsa-sha2-512-cert-v01@openssh.com," |
254 | "rsa-sha2-256-cert-v01@openssh.com," | |
255 | "ssh-rsa-cert-v01@openssh.com" : | |
256 | "rsa-sha2-512," | |
257 | "rsa-sha2-256," | |
258 | "ssh-rsa"; | |
3a424cdd | 259 | break; |
260 | case KT_ED25519: | |
261 | myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = get_cert ? | |
262 | "ssh-ed25519-cert-v01@openssh.com" : "ssh-ed25519"; | |
263 | break; | |
1b11ea7c | 264 | case KT_XMSS: |
265 | myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = get_cert ? | |
266 | "ssh-xmss-cert-v01@openssh.com" : "ssh-xmss@openssh.com"; | |
267 | break; | |
3a424cdd | 268 | case KT_ECDSA: |
269 | myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = get_cert ? | |
270 | "ecdsa-sha2-nistp256-cert-v01@openssh.com," | |
271 | "ecdsa-sha2-nistp384-cert-v01@openssh.com," | |
272 | "ecdsa-sha2-nistp521-cert-v01@openssh.com" : | |
273 | "ecdsa-sha2-nistp256," | |
274 | "ecdsa-sha2-nistp384," | |
275 | "ecdsa-sha2-nistp521"; | |
276 | break; | |
9b6e30b9 | 277 | case KT_ECDSA_SK: |
278 | myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = get_cert ? | |
279 | "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com" : | |
280 | "sk-ecdsa-sha2-nistp256@openssh.com"; | |
281 | break; | |
282 | case KT_ED25519_SK: | |
283 | myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = get_cert ? | |
284 | "sk-ssh-ed25519-cert-v01@openssh.com" : | |
285 | "sk-ssh-ed25519@openssh.com"; | |
286 | break; | |
3a424cdd | 287 | default: |
288 | fatal("unknown key type %d", c->c_keytype); | |
289 | break; | |
290 | } | |
3f797653 | 291 | if ((r = kex_setup(c->c_ssh, myproposal)) != 0) { |
292 | free(c->c_ssh); | |
293 | fprintf(stderr, "kex_setup: %s\n", ssh_err(r)); | |
325e70c9 BL |
294 | exit(1); |
295 | } | |
3f797653 | 296 | #ifdef WITH_OPENSSL |
aaca72d6 | 297 | c->c_ssh->kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_client; |
298 | c->c_ssh->kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_client; | |
299 | c->c_ssh->kex->kex[KEX_DH_GRP14_SHA256] = kex_gen_client; | |
300 | c->c_ssh->kex->kex[KEX_DH_GRP16_SHA512] = kex_gen_client; | |
301 | c->c_ssh->kex->kex[KEX_DH_GRP18_SHA512] = kex_gen_client; | |
3f797653 | 302 | c->c_ssh->kex->kex[KEX_DH_GEX_SHA1] = kexgex_client; |
303 | c->c_ssh->kex->kex[KEX_DH_GEX_SHA256] = kexgex_client; | |
f2004cd1 | 304 | # ifdef OPENSSL_HAS_ECC |
aaca72d6 | 305 | c->c_ssh->kex->kex[KEX_ECDH_SHA2] = kex_gen_client; |
f2004cd1 | 306 | # endif |
3f797653 | 307 | #endif |
aaca72d6 | 308 | c->c_ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client; |
2c71cec0 | 309 | c->c_ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client; |
3f797653 | 310 | ssh_set_verify_host_key_callback(c->c_ssh, key_print_wrapper); |
311 | /* | |
312 | * do the key-exchange until an error occurs or until | |
313 | * the key_print_wrapper() callback sets c_done. | |
314 | */ | |
92e9fe63 | 315 | ssh_dispatch_run(c->c_ssh, DISPATCH_BLOCK, &c->c_done); |
325e70c9 BL |
316 | } |
317 | ||
318 | static void | |
8a283445 | 319 | keyprint_one(const char *host, struct sshkey *key) |
325e70c9 | 320 | { |
57680a2a | 321 | char *hostport = NULL, *hashed = NULL; |
322 | const char *known_host; | |
1883841f | 323 | int r = 0; |
325e70c9 | 324 | |
c2c18a39 | 325 | found_one = 1; |
326 | ||
1a348359 | 327 | if (print_sshfp) { |
d651f5c9 | 328 | export_dns_rr(host, key, stdout, 0, hashalg); |
1a348359 | 329 | return; |
330 | } | |
331 | ||
2c2cfe1a | 332 | hostport = put_host_port(host, ssh_port); |
db259720 | 333 | lowercase(hostport); |
e9c71498 | 334 | if (hash_hosts && (hashed = host_hash(hostport, NULL, 0)) == NULL) |
8a283445 | 335 | fatal("host_hash failed"); |
336 | known_host = hash_hosts ? hashed : hostport; | |
3a424cdd | 337 | if (!get_cert) |
1883841f | 338 | r = fprintf(stdout, "%s ", known_host); |
339 | if (r >= 0 && sshkey_write(key, stdout) == 0) | |
340 | (void)fputs("\n", stdout); | |
57680a2a | 341 | free(hashed); |
2c2cfe1a | 342 | free(hostport); |
b6434ae0 BL |
343 | } |
344 | ||
9ada37d3 | 345 | static void |
346 | keyprint(con *c, struct sshkey *key) | |
347 | { | |
348 | char *hosts = c->c_output_name ? c->c_output_name : c->c_name; | |
349 | char *host, *ohosts; | |
350 | ||
351 | if (key == NULL) | |
352 | return; | |
3a424cdd | 353 | if (get_cert || (!hash_hosts && ssh_port == SSH_DEFAULT_PORT)) { |
9ada37d3 | 354 | keyprint_one(hosts, key); |
355 | return; | |
356 | } | |
357 | ohosts = hosts = xstrdup(hosts); | |
358 | while ((host = strsep(&hosts, ",")) != NULL) | |
359 | keyprint_one(host, key); | |
360 | free(ohosts); | |
361 | } | |
362 | ||
bba81213 | 363 | static int |
b6434ae0 BL |
364 | tcpconnect(char *host) |
365 | { | |
366 | struct addrinfo hints, *ai, *aitop; | |
367 | char strport[NI_MAXSERV]; | |
368 | int gaierr, s = -1; | |
369 | ||
325e70c9 | 370 | snprintf(strport, sizeof strport, "%d", ssh_port); |
b6434ae0 | 371 | memset(&hints, 0, sizeof(hints)); |
325e70c9 | 372 | hints.ai_family = IPv4or6; |
b6434ae0 | 373 | hints.ai_socktype = SOCK_STREAM; |
fae7bbe5 | 374 | if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) { |
375 | error("getaddrinfo %s: %s", host, ssh_gai_strerror(gaierr)); | |
376 | return -1; | |
377 | } | |
b6434ae0 | 378 | for (ai = aitop; ai; ai = ai->ai_next) { |
7bd98e7f | 379 | s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); |
4d28fa78 | 380 | if (s == -1) { |
b6434ae0 BL |
381 | error("socket: %s", strerror(errno)); |
382 | continue; | |
383 | } | |
232711f6 | 384 | if (set_nonblock(s) == -1) |
816036f1 | 385 | fatal_f("set_nonblock(%d)", s); |
4d28fa78 | 386 | if (connect(s, ai->ai_addr, ai->ai_addrlen) == -1 && |
b6434ae0 BL |
387 | errno != EINPROGRESS) |
388 | error("connect (`%s'): %s", host, strerror(errno)); | |
389 | else | |
390 | break; | |
391 | close(s); | |
392 | s = -1; | |
393 | } | |
394 | freeaddrinfo(aitop); | |
395 | return s; | |
396 | } | |
397 | ||
bba81213 | 398 | static int |
11925885 | 399 | conalloc(const char *iname, const char *oname, int keytype) |
b6434ae0 | 400 | { |
b6434ae0 | 401 | char *namebase, *name, *namelist; |
965710f6 | 402 | int s; |
b6434ae0 BL |
403 | |
404 | namebase = namelist = xstrdup(iname); | |
405 | ||
406 | do { | |
407 | name = xstrsep(&namelist, ","); | |
408 | if (!name) { | |
a627d42e | 409 | free(namebase); |
b6434ae0 BL |
410 | return (-1); |
411 | } | |
412 | } while ((s = tcpconnect(name)) < 0); | |
413 | ||
414 | if (s >= maxfd) | |
fa72ddac | 415 | fatal("conalloc: fdno %d too high", s); |
b6434ae0 | 416 | if (fdcon[s].c_status) |
fa72ddac | 417 | fatal("conalloc: attempt to reuse fdno %d", s); |
b6434ae0 | 418 | |
816036f1 | 419 | debug3_f("oname %s kt %d", oname, keytype); |
b6434ae0 BL |
420 | fdcon[s].c_fd = s; |
421 | fdcon[s].c_status = CS_CON; | |
422 | fdcon[s].c_namebase = namebase; | |
423 | fdcon[s].c_name = name; | |
424 | fdcon[s].c_namelist = namelist; | |
425 | fdcon[s].c_output_name = xstrdup(oname); | |
426 | fdcon[s].c_data = (char *) &fdcon[s].c_plen; | |
427 | fdcon[s].c_len = 4; | |
428 | fdcon[s].c_off = 0; | |
325e70c9 | 429 | fdcon[s].c_keytype = keytype; |
7eec7679 | 430 | monotime_ts(&fdcon[s].c_ts); |
431 | fdcon[s].c_ts.tv_sec += timeout; | |
b6434ae0 | 432 | TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link); |
7eec7679 | 433 | read_wait[s].fd = s; |
434 | read_wait[s].events = POLLIN; | |
b6434ae0 BL |
435 | ncon++; |
436 | return (s); | |
437 | } | |
438 | ||
bba81213 | 439 | static void |
b6434ae0 BL |
440 | confree(int s) |
441 | { | |
b6434ae0 | 442 | if (s >= maxfd || fdcon[s].c_status == CS_UNUSED) |
fa72ddac | 443 | fatal("confree: attempt to free bad fdno %d", s); |
a627d42e DT |
444 | free(fdcon[s].c_namebase); |
445 | free(fdcon[s].c_output_name); | |
b6434ae0 | 446 | if (fdcon[s].c_status == CS_KEYS) |
a627d42e | 447 | free(fdcon[s].c_data); |
b6434ae0 | 448 | fdcon[s].c_status = CS_UNUSED; |
325e70c9 | 449 | fdcon[s].c_keytype = 0; |
3f797653 | 450 | if (fdcon[s].c_ssh) { |
451 | ssh_packet_close(fdcon[s].c_ssh); | |
452 | free(fdcon[s].c_ssh); | |
453 | fdcon[s].c_ssh = NULL; | |
d79bceb9 | 454 | } else |
455 | close(s); | |
b6434ae0 | 456 | TAILQ_REMOVE(&tq, &fdcon[s], c_link); |
7eec7679 | 457 | read_wait[s].fd = -1; |
458 | read_wait[s].events = 0; | |
b6434ae0 BL |
459 | ncon--; |
460 | } | |
461 | ||
bba81213 | 462 | static void |
b6434ae0 BL |
463 | contouch(int s) |
464 | { | |
465 | TAILQ_REMOVE(&tq, &fdcon[s], c_link); | |
7eec7679 | 466 | monotime_ts(&fdcon[s].c_ts); |
467 | fdcon[s].c_ts.tv_sec += timeout; | |
b6434ae0 BL |
468 | TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link); |
469 | } | |
470 | ||
bba81213 | 471 | static int |
b6434ae0 BL |
472 | conrecycle(int s) |
473 | { | |
b6434ae0 | 474 | con *c = &fdcon[s]; |
965710f6 | 475 | int ret; |
b6434ae0 | 476 | |
325e70c9 | 477 | ret = conalloc(c->c_namelist, c->c_output_name, c->c_keytype); |
b6434ae0 | 478 | confree(s); |
b6434ae0 BL |
479 | return (ret); |
480 | } | |
481 | ||
bba81213 | 482 | static void |
b6434ae0 BL |
483 | congreet(int s) |
484 | { | |
eccb9de7 | 485 | int n = 0, remote_major = 0, remote_minor = 0; |
325e70c9 | 486 | char buf[256], *cp; |
83c02ef6 | 487 | char remote_version[sizeof buf]; |
eccb9de7 | 488 | size_t bufsiz; |
b6434ae0 BL |
489 | con *c = &fdcon[s]; |
490 | ||
873d3e7d | 491 | /* send client banner */ |
492 | n = snprintf(buf, sizeof buf, "SSH-%d.%d-OpenSSH-keyscan\r\n", | |
493 | PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2); | |
494 | if (n < 0 || (size_t)n >= sizeof(buf)) { | |
495 | error("snprintf: buffer too small"); | |
496 | confree(s); | |
497 | return; | |
498 | } | |
499 | if (atomicio(vwrite, s, buf, n) != (size_t)n) { | |
500 | error("write (%s): %s", c->c_name, strerror(errno)); | |
501 | confree(s); | |
502 | return; | |
503 | } | |
504 | ||
ff89b1be | 505 | /* |
506 | * Read the server banner as per RFC4253 section 4.2. The "SSH-" | |
64ddf776 | 507 | * protocol identification string may be preceded by an arbitrarily |
ff89b1be | 508 | * large banner which we must read and ignore. Loop while reading |
509 | * newline-terminated lines until we have one starting with "SSH-". | |
510 | * The ID string cannot be longer than 255 characters although the | |
64ddf776 | 511 | * preceding banner lines may (in which case they'll be discarded |
ff89b1be | 512 | * in multiple iterations of the outer loop). |
513 | */ | |
4bbacb70 DM |
514 | for (;;) { |
515 | memset(buf, '\0', sizeof(buf)); | |
516 | bufsiz = sizeof(buf); | |
517 | cp = buf; | |
518 | while (bufsiz-- && | |
519 | (n = atomicio(read, s, cp, 1)) == 1 && *cp != '\n') { | |
520 | if (*cp == '\r') | |
521 | *cp = '\n'; | |
522 | cp++; | |
523 | } | |
524 | if (n != 1 || strncmp(buf, "SSH-", 4) == 0) | |
525 | break; | |
de8fc6fa | 526 | } |
6b28c35a | 527 | if (n == 0) { |
b253cc42 DM |
528 | switch (errno) { |
529 | case EPIPE: | |
530 | error("%s: Connection closed by remote host", c->c_name); | |
531 | break; | |
532 | case ECONNREFUSED: | |
533 | break; | |
534 | default: | |
535 | error("read (%s): %s", c->c_name, strerror(errno)); | |
536 | break; | |
537 | } | |
6b28c35a BL |
538 | conrecycle(s); |
539 | return; | |
540 | } | |
ff89b1be | 541 | if (cp >= buf + sizeof(buf)) { |
542 | error("%s: greeting exceeds allowable length", c->c_name); | |
543 | confree(s); | |
544 | return; | |
545 | } | |
884a4aca | 546 | if (*cp != '\n' && *cp != '\r') { |
b6434ae0 BL |
547 | error("%s: bad greeting", c->c_name); |
548 | confree(s); | |
549 | return; | |
550 | } | |
884a4aca | 551 | *cp = '\0'; |
4509b5d4 | 552 | if ((c->c_ssh = ssh_packet_set_connection(NULL, s, s)) == NULL) |
553 | fatal("ssh_packet_set_connection failed"); | |
802660cb | 554 | ssh_packet_set_timeout(c->c_ssh, timeout, 1); |
3f797653 | 555 | ssh_set_app_data(c->c_ssh, c); /* back link */ |
4ca6a1fa | 556 | c->c_ssh->compat = 0; |
83c02ef6 DM |
557 | if (sscanf(buf, "SSH-%d.%d-%[^\n]\n", |
558 | &remote_major, &remote_minor, remote_version) == 3) | |
4ca6a1fa | 559 | compat_banner(c->c_ssh, remote_version); |
873d3e7d | 560 | if (!ssh2_capable(remote_major, remote_minor)) { |
561 | debug("%s doesn't support ssh2", c->c_name); | |
83c02ef6 DM |
562 | confree(s); |
563 | return; | |
325e70c9 | 564 | } |
1a348359 | 565 | fprintf(stderr, "%c %s:%d %s\n", print_sshfp ? ';' : '#', |
566 | c->c_name, ssh_port, chop(buf)); | |
873d3e7d | 567 | keygrab_ssh2(c); |
568 | confree(s); | |
b6434ae0 BL |
569 | } |
570 | ||
bba81213 | 571 | static void |
b6434ae0 BL |
572 | conread(int s) |
573 | { | |
b6434ae0 | 574 | con *c = &fdcon[s]; |
b253cc42 | 575 | size_t n; |
b6434ae0 BL |
576 | |
577 | if (c->c_status == CS_CON) { | |
578 | congreet(s); | |
579 | return; | |
580 | } | |
fe6649da | 581 | n = atomicio(read, s, c->c_data + c->c_off, c->c_len - c->c_off); |
b253cc42 | 582 | if (n == 0) { |
b6434ae0 BL |
583 | error("read (%s): %s", c->c_name, strerror(errno)); |
584 | confree(s); | |
585 | return; | |
586 | } | |
587 | c->c_off += n; | |
588 | ||
589 | if (c->c_off == c->c_len) | |
590 | switch (c->c_status) { | |
591 | case CS_SIZE: | |
592 | c->c_plen = htonl(c->c_plen); | |
593 | c->c_len = c->c_plen + 8 - (c->c_plen & 7); | |
594 | c->c_off = 0; | |
595 | c->c_data = xmalloc(c->c_len); | |
596 | c->c_status = CS_KEYS; | |
597 | break; | |
b6434ae0 | 598 | default: |
fa72ddac | 599 | fatal("conread: invalid status %d", c->c_status); |
b6434ae0 BL |
600 | break; |
601 | } | |
602 | ||
603 | contouch(s); | |
604 | } | |
605 | ||
bba81213 | 606 | static void |
b6434ae0 BL |
607 | conloop(void) |
608 | { | |
7eec7679 | 609 | struct timespec seltime, now; |
b6434ae0 | 610 | con *c; |
965710f6 | 611 | int i; |
b6434ae0 | 612 | |
7eec7679 | 613 | monotime_ts(&now); |
61c183be | 614 | c = TAILQ_FIRST(&tq); |
b6434ae0 | 615 | |
7eec7679 | 616 | if (c && timespeccmp(&c->c_ts, &now, >)) |
617 | timespecsub(&c->c_ts, &now, &seltime); | |
1196d7f4 | 618 | else |
7eec7679 | 619 | timespecclear(&seltime); |
b6434ae0 | 620 | |
d069b020 | 621 | while (ppoll(read_wait, maxfd, &seltime, NULL) == -1) { |
622 | if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) | |
623 | continue; | |
624 | error("poll error"); | |
625 | } | |
f9452513 | 626 | |
d20b855b | 627 | for (i = 0; i < maxfd; i++) { |
7eec7679 | 628 | if (read_wait[i].revents & (POLLHUP|POLLERR|POLLNVAL)) |
b6434ae0 | 629 | confree(i); |
87540827 | 630 | else if (read_wait[i].revents & (POLLIN|POLLHUP)) |
b6434ae0 | 631 | conread(i); |
d20b855b | 632 | } |
b6434ae0 | 633 | |
61c183be | 634 | c = TAILQ_FIRST(&tq); |
7eec7679 | 635 | while (c && timespeccmp(&c->c_ts, &now, <)) { |
b6434ae0 | 636 | int s = c->c_fd; |
d20b855b | 637 | |
61c183be | 638 | c = TAILQ_NEXT(c, c_link); |
b6434ae0 BL |
639 | conrecycle(s); |
640 | } | |
641 | } | |
642 | ||
325e70c9 | 643 | static void |
11925885 | 644 | do_one_host(char *host) |
b6434ae0 | 645 | { |
325e70c9 BL |
646 | char *name = strnnsep(&host, " \t\n"); |
647 | int j; | |
648 | ||
eaffb9d6 BL |
649 | if (name == NULL) |
650 | return; | |
873d3e7d | 651 | for (j = KT_MIN; j <= KT_MAX; j *= 2) { |
325e70c9 BL |
652 | if (get_keytypes & j) { |
653 | while (ncon >= MAXCON) | |
654 | conloop(); | |
655 | conalloc(name, *host ? host : name, j); | |
b6434ae0 BL |
656 | } |
657 | } | |
658 | } | |
659 | ||
11925885 | 660 | static void |
661 | do_host(char *host) | |
662 | { | |
663 | char daddr[128]; | |
664 | struct xaddr addr, end_addr; | |
665 | u_int masklen; | |
666 | ||
667 | if (host == NULL) | |
668 | return; | |
669 | if (addr_pton_cidr(host, &addr, &masklen) != 0) { | |
670 | /* Assume argument is a hostname */ | |
671 | do_one_host(host); | |
672 | } else { | |
673 | /* Argument is a CIDR range */ | |
674 | debug("CIDR range %s", host); | |
675 | end_addr = addr; | |
676 | if (addr_host_to_all1s(&end_addr, masklen) != 0) | |
677 | goto badaddr; | |
678 | /* | |
679 | * Note: we deliberately include the all-zero/ones addresses. | |
680 | */ | |
681 | for (;;) { | |
682 | if (addr_ntop(&addr, daddr, sizeof(daddr)) != 0) { | |
683 | badaddr: | |
684 | error("Invalid address %s", host); | |
685 | return; | |
686 | } | |
687 | debug("CIDR expand: address %s", daddr); | |
688 | do_one_host(daddr); | |
689 | if (addr_cmp(&addr, &end_addr) == 0) | |
690 | break; | |
691 | addr_increment(&addr); | |
692 | }; | |
693 | } | |
694 | } | |
695 | ||
9c8edc96 | 696 | void |
3554b4af | 697 | sshfatal(const char *file, const char *func, int line, int showfunc, |
9e2c4f64 | 698 | LogLevel level, const char *suffix, const char *fmt, ...) |
325e70c9 | 699 | { |
9c8edc96 | 700 | va_list args; |
965710f6 | 701 | |
9c8edc96 | 702 | va_start(args, fmt); |
f7bd11e4 | 703 | sshlogv(file, func, line, showfunc, level, suffix, fmt, args); |
9c8edc96 | 704 | va_end(args); |
752250ca | 705 | cleanup_exit(255); |
325e70c9 BL |
706 | } |
707 | ||
bba81213 | 708 | static void |
b6434ae0 BL |
709 | usage(void) |
710 | { | |
c1719f7f | 711 | fprintf(stderr, |
285cf6cd | 712 | "usage: ssh-keyscan [-46cDHv] [-f file] [-O option] [-p port] [-T timeout]\n" |
713 | " [-t type] [host | addrlist namelist]\n"); | |
ddfb1e3a | 714 | exit(1); |
b6434ae0 BL |
715 | } |
716 | ||
717 | int | |
718 | main(int argc, char **argv) | |
719 | { | |
325e70c9 | 720 | int debug_flag = 0, log_level = SYSLOG_LEVEL_INFO; |
0e76c5e5 | 721 | int opt, fopt_count = 0, j; |
7f906352 | 722 | char *tname, *cp, *line = NULL; |
723 | size_t linesize = 0; | |
0e76c5e5 | 724 | FILE *fp; |
76e7d9b6 | 725 | |
325e70c9 BL |
726 | extern int optind; |
727 | extern char *optarg; | |
b6434ae0 | 728 | |
59d3d5b8 | 729 | __progname = ssh_get_progname(argv[0]); |
4e088e4d | 730 | seed_rng(); |
b6434ae0 BL |
731 | TAILQ_INIT(&tq); |
732 | ||
ce321d8a DT |
733 | /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ |
734 | sanitise_stdfd(); | |
735 | ||
325e70c9 | 736 | if (argc <= 1) |
b6434ae0 BL |
737 | usage(); |
738 | ||
d651f5c9 | 739 | while ((opt = getopt(argc, argv, "cDHv46O:p:T:t:f:")) != -1) { |
325e70c9 | 740 | switch (opt) { |
db7b8171 DM |
741 | case 'H': |
742 | hash_hosts = 1; | |
743 | break; | |
3a424cdd | 744 | case 'c': |
745 | get_cert = 1; | |
746 | break; | |
1a348359 | 747 | case 'D': |
748 | print_sshfp = 1; | |
749 | break; | |
325e70c9 BL |
750 | case 'p': |
751 | ssh_port = a2port(optarg); | |
3dc71ad8 | 752 | if (ssh_port <= 0) { |
325e70c9 BL |
753 | fprintf(stderr, "Bad port '%s'\n", optarg); |
754 | exit(1); | |
755 | } | |
756 | break; | |
757 | case 'T': | |
edd098b1 BL |
758 | timeout = convtime(optarg); |
759 | if (timeout == -1 || timeout == 0) { | |
760 | fprintf(stderr, "Bad timeout '%s'\n", optarg); | |
b6434ae0 | 761 | usage(); |
edd098b1 | 762 | } |
325e70c9 BL |
763 | break; |
764 | case 'v': | |
765 | if (!debug_flag) { | |
766 | debug_flag = 1; | |
767 | log_level = SYSLOG_LEVEL_DEBUG1; | |
768 | } | |
769 | else if (log_level < SYSLOG_LEVEL_DEBUG3) | |
770 | log_level++; | |
771 | else | |
772 | fatal("Too high debugging level."); | |
773 | break; | |
76e7d9b6 | 774 | case 'f': |
325e70c9 BL |
775 | if (strcmp(optarg, "-") == 0) |
776 | optarg = NULL; | |
777 | argv[fopt_count++] = optarg; | |
778 | break; | |
d651f5c9 | 779 | case 'O': |
780 | /* Maybe other misc options in the future too */ | |
781 | if (strncmp(optarg, "hashalg=", 8) != 0) | |
782 | fatal("Unsupported -O option"); | |
783 | if ((hashalg = ssh_digest_alg_by_name( | |
784 | optarg + 8)) == -1) | |
785 | fatal("Unsupported hash algorithm"); | |
786 | break; | |
325e70c9 BL |
787 | case 't': |
788 | get_keytypes = 0; | |
789 | tname = strtok(optarg, ","); | |
790 | while (tname) { | |
3f797653 | 791 | int type = sshkey_type_from_name(tname); |
f89b9285 | 792 | |
325e70c9 | 793 | switch (type) { |
4e838120 | 794 | #ifdef WITH_DSA |
325e70c9 BL |
795 | case KEY_DSA: |
796 | get_keytypes |= KT_DSA; | |
797 | break; | |
4e838120 | 798 | #endif |
eb8b60e3 DM |
799 | case KEY_ECDSA: |
800 | get_keytypes |= KT_ECDSA; | |
801 | break; | |
325e70c9 BL |
802 | case KEY_RSA: |
803 | get_keytypes |= KT_RSA; | |
804 | break; | |
5be9d9e3 DM |
805 | case KEY_ED25519: |
806 | get_keytypes |= KT_ED25519; | |
807 | break; | |
1b11ea7c | 808 | case KEY_XMSS: |
809 | get_keytypes |= KT_XMSS; | |
810 | break; | |
9b6e30b9 | 811 | case KEY_ED25519_SK: |
812 | get_keytypes |= KT_ED25519_SK; | |
813 | break; | |
814 | case KEY_ECDSA_SK: | |
815 | get_keytypes |= KT_ECDSA_SK; | |
816 | break; | |
325e70c9 | 817 | case KEY_UNSPEC: |
f89b9285 | 818 | default: |
819 | fatal("Unknown key type \"%s\"", tname); | |
325e70c9 BL |
820 | } |
821 | tname = strtok(NULL, ","); | |
822 | } | |
823 | break; | |
824 | case '4': | |
825 | IPv4or6 = AF_INET; | |
826 | break; | |
827 | case '6': | |
828 | IPv4or6 = AF_INET6; | |
829 | break; | |
325e70c9 | 830 | default: |
b6434ae0 | 831 | usage(); |
325e70c9 | 832 | } |
b6434ae0 | 833 | } |
325e70c9 | 834 | if (optind == argc && !fopt_count) |
b6434ae0 BL |
835 | usage(); |
836 | ||
325e70c9 | 837 | log_init("ssh-keyscan", log_level, SYSLOG_FACILITY_USER, 1); |
325e70c9 | 838 | |
b6434ae0 BL |
839 | maxfd = fdlim_get(1); |
840 | if (maxfd < 0) | |
fa72ddac | 841 | fatal("%s: fdlim_get: bad value", __progname); |
b6434ae0 BL |
842 | if (maxfd > MAXMAXFD) |
843 | maxfd = MAXMAXFD; | |
d20b855b | 844 | if (MAXCON <= 0) |
fa72ddac | 845 | fatal("%s: not enough file descriptors", __progname); |
b6434ae0 BL |
846 | if (maxfd > fdlim_get(0)) |
847 | fdlim_set(maxfd); | |
07d86bec | 848 | fdcon = xcalloc(maxfd, sizeof(con)); |
7eec7679 | 849 | read_wait = xcalloc(maxfd, sizeof(struct pollfd)); |
06acb04c | 850 | for (j = 0; j < maxfd; j++) |
851 | read_wait[j].fd = -1; | |
c1e0421c | 852 | |
0e76c5e5 DM |
853 | for (j = 0; j < fopt_count; j++) { |
854 | if (argv[j] == NULL) | |
855 | fp = stdin; | |
856 | else if ((fp = fopen(argv[j], "r")) == NULL) | |
816036f1 | 857 | fatal("%s: %s: %s", __progname, argv[j], strerror(errno)); |
0e76c5e5 | 858 | |
7f906352 | 859 | while (getline(&line, &linesize, fp) != -1) { |
0e76c5e5 DM |
860 | /* Chomp off trailing whitespace and comments */ |
861 | if ((cp = strchr(line, '#')) == NULL) | |
862 | cp = line + strlen(line) - 1; | |
863 | while (cp >= line) { | |
864 | if (*cp == ' ' || *cp == '\t' || | |
865 | *cp == '\n' || *cp == '#') | |
866 | *cp-- = '\0'; | |
867 | else | |
868 | break; | |
869 | } | |
325e70c9 | 870 | |
0e76c5e5 DM |
871 | /* Skip empty lines */ |
872 | if (*line == '\0') | |
78bbd9eb | 873 | continue; |
0e76c5e5 DM |
874 | |
875 | do_host(line); | |
b6434ae0 | 876 | } |
0e76c5e5 DM |
877 | |
878 | if (ferror(fp)) | |
816036f1 | 879 | fatal("%s: %s: %s", __progname, argv[j], strerror(errno)); |
0e76c5e5 DM |
880 | |
881 | fclose(fp); | |
325e70c9 | 882 | } |
7f906352 | 883 | free(line); |
325e70c9 BL |
884 | |
885 | while (optind < argc) | |
886 | do_host(argv[optind++]); | |
887 | ||
b6434ae0 BL |
888 | while (ncon > 0) |
889 | conloop(); | |
890 | ||
c2c18a39 | 891 | return found_one ? 0 : 1; |
b6434ae0 | 892 | } |