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