diff options
author | Markus Friedl <markus@cvs.openbsd.org> | 2000-12-04 19:24:03 +0000 |
---|---|---|
committer | Markus Friedl <markus@cvs.openbsd.org> | 2000-12-04 19:24:03 +0000 |
commit | 4b24556b4a12441d9101af9bc9bcd8505c1e1911 (patch) | |
tree | a23ed9e7de392c351a54f8085b0bec3d544be00a /usr.bin | |
parent | 4545a38d48e985f11cb8c849be2e0247da3ad63f (diff) |
David Maziere's ssh-keyscan, ok niels@
Diffstat (limited to 'usr.bin')
-rw-r--r-- | usr.bin/ssh/Makefile | 5 | ||||
-rw-r--r-- | usr.bin/ssh/ssh-keyscan.1 | 94 | ||||
-rw-r--r-- | usr.bin/ssh/ssh-keyscan.c | 605 | ||||
-rw-r--r-- | usr.bin/ssh/ssh-keyscan/Makefile | 16 |
4 files changed, 718 insertions, 2 deletions
diff --git a/usr.bin/ssh/Makefile b/usr.bin/ssh/Makefile index 299d3497cf2..59eed7d4873 100644 --- a/usr.bin/ssh/Makefile +++ b/usr.bin/ssh/Makefile @@ -1,8 +1,9 @@ -# $OpenBSD: Makefile,v 1.6 2000/08/31 21:52:23 markus Exp $ +# $OpenBSD: Makefile,v 1.7 2000/12/04 19:24:01 markus Exp $ .include <bsd.own.mk> -SUBDIR= lib ssh sshd ssh-add ssh-keygen ssh-agent scp sftp-server +SUBDIR= lib ssh sshd ssh-add ssh-keygen ssh-agent scp sftp-server \ + ssh-keyscan distribution: install -C -o root -g wheel -m 0644 ${.CURDIR}/ssh_config \ diff --git a/usr.bin/ssh/ssh-keyscan.1 b/usr.bin/ssh/ssh-keyscan.1 new file mode 100644 index 00000000000..efd6e744af3 --- /dev/null +++ b/usr.bin/ssh/ssh-keyscan.1 @@ -0,0 +1,94 @@ +.Dd January 1, 1996 +.Dt ssh-keyscan 1 +.Os +.Sh NAME +.Nm ssh-keyscan +.Nd gather ssh public keys +.Sh SYNOPSIS +.Nm ssh-keyscan +.Op Fl t Ar timeout +.Op Ar -- | host | addrlist namelist +.Op Fl f Ar files ... +.Sh DESCRIPTION +.Nm +is a utility for gathering the public ssh host keys of a number of +hosts. It was designed to aid in building and verifying +.Pa ssh_known_hosts +files. +.Nm +provides a minimal interface suitable for use by shell and perl +scripts. +.Pp +.Nm +uses non-blocking socket I/O to contact as many hosts as possible in +parallel, so it is very efficient. The keys from a domain of 1,000 +hosts can be collected in tens of seconds, even when some of those +hosts are down or do not run ssh. You do not need login access to the +machines you are scanning, nor does does the scanning process involve +any encryption. +.Sh SECURITY +If you make an ssh_known_hosts file using +.Nm +without verifying the keys, you will be vulnerable to +.I man in the middle +attacks. +On the other hand, if your security model allows such a risk, +.Nm +can help you detect tampered keyfiles or man in the middle attacks which +have begun after you created your ssh_known_hosts file. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl t +Set the timeout for connection attempts. If +.Pa timeout +seconds have elapsed since a connection was initiated to a host or since the +last time anything was read from that host, then the connection is +closed and the host in question considered unavailable. Default is 5 +seconds. +.It Fl f +Read hosts or +.Pa addrlist namelist +pairs from this file, one per line. +If +.Pa - +is supplied instead of a filename, +.Nm +will read hosts or +.Pa addrlist namelist +pairs from the standard input. +.Sh EXAMPLES +.Pp +Print the host key for machine +.Pa hostname : +.Bd -literal +ssh-keyscan hostname +.Ed +.Pp +Find all hosts from the file +.Pa ssh_hosts +which have new or different keys from those in the sorted file +.Pa ssh_known_hosts : +.Bd -literal +ssh-keyscan -f ssh_hosts | sort -u - ssh_known_hosts | \e\ + diff ssh_known_hosts - +.Ed +.Pp +.Sh FILES +.Pp +.Pa Input format: +1.2.3.4,1.2.4.4 name.my.domain,name,n.my.domain,n,1.2.3.4,1.2.4.4 +.Pp +.Pa Output format: +host-or-namelist bits exponent modulus +.Pp +.Pa /etc/ssh_known_hosts +.Sh BUGS +It generates "Connection closed by remote host" messages on the consoles +of all the machines it scans. +This is because it opens a connection to the ssh port, reads the public +key, and drops the connection as soon as it gets the key. +.Sh SEE ALSO +.Xr ssh 1 +.Xr sshd 8 +.Sh AUTHOR +David Mazieres <dm@lcs.mit.edu> diff --git a/usr.bin/ssh/ssh-keyscan.c b/usr.bin/ssh/ssh-keyscan.c new file mode 100644 index 00000000000..8823484bc38 --- /dev/null +++ b/usr.bin/ssh/ssh-keyscan.c @@ -0,0 +1,605 @@ +/* + * Copyright 1995, 1996 by David Mazieres <dm@lcs.mit.edu>. + * + * Modification and redistribution in source and binary forms is + * permitted provided that due credit is given to the author and the + * OpenBSD project (for instance by leaving this copyright notice + * intact). + */ + +#include "includes.h" +RCSID("$OpenBSD: ssh-keyscan.c,v 1.1 2000/12/04 19:24:02 markus Exp $"); + +#include <sys/queue.h> +#include <err.h> +#include <errno.h> + +#include <ssl/bn.h> +#include <ssl/rsa.h> +#include <ssl/dsa.h> + +#include "xmalloc.h" +#include "ssh.h" +#include "key.h" +#include "buffer.h" +#include "bufaux.h" + +static int argno = 1; /* Number of argument currently being parsed */ + +int family = AF_UNSPEC; /* IPv4, IPv6 or both */ + +#define PORT 22 +#define MAXMAXFD 256 + +/* The number of seconds after which to give up on a TCP connection */ +int timeout = 5; + +int maxfd; +#define maxcon (maxfd - 10) + +char *prog; +fd_set read_wait; +int ncon; + +/* + * Keep a connection structure for each file descriptor. The state + * associated with file descriptor n is held in fdcon[n]. + */ +typedef struct Connection { + unsigned char c_status; /* State of connection on this file desc. */ +#define CS_UNUSED 0 /* File descriptor unused */ +#define CS_CON 1 /* Waiting to connect/read greeting */ +#define CS_SIZE 2 /* Waiting to read initial packet size */ +#define CS_KEYS 3 /* Waiting to read public key packet */ + int c_fd; /* Quick lookup: c->c_fd == c - fdcon */ + int c_plen; /* Packet length field for ssh packet */ + int c_len; /* Total bytes which must be read. */ + int c_off; /* Length of data read so far. */ + char *c_namebase; /* Address to free for c_name and c_namelist */ + char *c_name; /* Hostname of connection for errors */ + char *c_namelist; /* Pointer to other possible addresses */ + char *c_output_name; /* Hostname of connection for output */ + char *c_data; /* Data read from this fd */ + struct timeval c_tv; /* Time at which connection gets aborted */ + TAILQ_ENTRY(Connection) c_link; /* List of connections in timeout order. */ +} con; + +TAILQ_HEAD(conlist, Connection) tq; /* Timeout Queue */ +con *fdcon; + +/* + * This is just a wrapper around fgets() to make it usable. + */ + +/* Stress-test. Increase this later. */ +#define LINEBUF_SIZE 16 + +typedef struct { + char *buf; + unsigned int size; + int lineno; + const char *filename; + FILE *stream; + void (*errfun) (const char *,...); +} Linebuf; + +static inline Linebuf * +Linebuf_alloc(const char *filename, void (*errfun) (const char *,...)) +{ + Linebuf *lb; + + if (!(lb = malloc(sizeof(*lb)))) { + if (errfun) + (*errfun) ("linebuf (%s): malloc failed\n", lb->filename); + return (NULL); + } + if (filename) { + lb->filename = filename; + if (!(lb->stream = fopen(filename, "r"))) { + free(lb); + if (errfun) + (*errfun) ("%s: %s\n", filename, strerror(errno)); + return (NULL); + } + } else { + lb->filename = "(stdin)"; + lb->stream = stdin; + } + + if (!(lb->buf = malloc(lb->size = LINEBUF_SIZE))) { + if (errfun) + (*errfun) ("linebuf (%s): malloc failed\n", lb->filename); + free(lb); + return (NULL); + } + lb->errfun = errfun; + lb->lineno = 0; + return (lb); +} + +static inline void +Linebuf_free(Linebuf * lb) +{ + fclose(lb->stream); + free(lb->buf); + free(lb); +} + +static inline void +Linebuf_restart(Linebuf * lb) +{ + clearerr(lb->stream); + rewind(lb->stream); + lb->lineno = 0; +} + +static inline int +Linebuf_lineno(Linebuf * lb) +{ + return (lb->lineno); +} + +static inline char * +getline(Linebuf * lb) +{ + int n = 0; + + lb->lineno++; + for (;;) { + /* Read a line */ + if (!fgets(&lb->buf[n], lb->size - n, lb->stream)) { + if (ferror(lb->stream) && lb->errfun) + (*lb->errfun) ("%s: %s\n", lb->filename, strerror(errno)); + return (NULL); + } + n = strlen(lb->buf); + + /* Return it or an error if it fits */ + if (n > 0 && lb->buf[n - 1] == '\n') { + lb->buf[n - 1] = '\0'; + return (lb->buf); + } + if (n != lb->size - 1) { + if (lb->errfun) + (*lb->errfun) ("%s: skipping incomplete last line\n", lb->filename); + return (NULL); + } + /* Double the buffer if we need more space */ + if (!(lb->buf = realloc(lb->buf, (lb->size *= 2)))) { + if (lb->errfun) + (*lb->errfun) ("linebuf (%s): realloc failed\n", lb->filename); + return (NULL); + } + } +} + +static int +fdlim_get(int hard) +{ + struct rlimit rlfd; + if (getrlimit(RLIMIT_NOFILE, &rlfd) < 0) + return (-1); + if ((hard ? rlfd.rlim_max : rlfd.rlim_cur) == RLIM_INFINITY) + return 10000; + else + return hard ? rlfd.rlim_max : rlfd.rlim_cur; +} + +static int +fdlim_set(int lim) +{ + struct rlimit rlfd; + if (lim <= 0) + return (-1); + if (getrlimit(RLIMIT_NOFILE, &rlfd) < 0) + return (-1); + rlfd.rlim_cur = lim; + if (setrlimit(RLIMIT_NOFILE, &rlfd) < 0) + return (-1); + return (0); +} + +/* + * This is an strsep function that returns a null field for adjacent + * separators. This is the same as the 4.4BSD strsep, but different from the + * one in the GNU libc. + */ +inline char * +xstrsep(char **str, const char *delim) +{ + char *s, *e; + + if (!**str) + return (NULL); + + s = *str; + e = s + strcspn(s, delim); + + if (*e != '\0') + *e++ = '\0'; + *str = e; + + return (s); +} + +/* + * Get the next non-null token (like GNU strsep). Strsep() will return a + * null token for two adjacent separators, so we may have to loop. + */ +char * +strnnsep(char **stringp, char *delim) +{ + char *tok; + + do { + tok = xstrsep(stringp, delim); + } while (tok && *tok == '\0'); + return (tok); +} + +void +keyprint(char *host, char *output_name, char *kd, int len) +{ + static Key *rsa; + static Buffer msg; + + if (rsa == NULL) { + buffer_init(&msg); + rsa = key_new(KEY_RSA1); + } + buffer_append(&msg, kd, len); + buffer_consume(&msg, 8 - (len & 7)); /* padding */ + if (buffer_get_char(&msg) != (int) SSH_SMSG_PUBLIC_KEY) { + error("%s: invalid packet type", host); + buffer_clear(&msg); + return; + } + buffer_consume(&msg, 8); /* cookie */ + + /* server key */ + (void) buffer_get_int(&msg); + buffer_get_bignum(&msg, rsa->rsa->e); + buffer_get_bignum(&msg, rsa->rsa->n); + + /* host key */ + (void) buffer_get_int(&msg); + buffer_get_bignum(&msg, rsa->rsa->e); + buffer_get_bignum(&msg, rsa->rsa->n); + buffer_clear(&msg); + + fprintf(stdout, "%s ", output_name ? output_name : host); + key_write(rsa, stdout); + fputs("\n", stdout); +} + +int +tcpconnect(char *host) +{ + struct addrinfo hints, *ai, *aitop; + char strport[NI_MAXSERV]; + int gaierr, s = -1; + + snprintf(strport, sizeof strport, "%d", PORT); + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) + fatal("getaddrinfo %s: %s", host, gai_strerror(gaierr)); + for (ai = aitop; ai; ai = ai->ai_next) { + s = socket(ai->ai_family, SOCK_STREAM, 0); + if (s < 0) { + error("socket: %s", strerror(errno)); + continue; + } + if (fcntl(s, F_SETFL, O_NDELAY) < 0) + fatal("F_SETFL: %s", strerror(errno)); + if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0 && + errno != EINPROGRESS) + error("connect (`%s'): %s", host, strerror(errno)); + else + break; + close(s); + s = -1; + } + freeaddrinfo(aitop); + return s; +} + +int +conalloc(char *iname, char *oname) +{ + int s; + char *namebase, *name, *namelist; + + namebase = namelist = xstrdup(iname); + + do { + name = xstrsep(&namelist, ","); + if (!name) { + free(namebase); + return (-1); + } + } while ((s = tcpconnect(name)) < 0); + + if (s >= maxfd) + fatal("conalloc: fdno %d too high\n", s); + if (fdcon[s].c_status) + fatal("conalloc: attempt to reuse fdno %d\n", s); + + fdcon[s].c_fd = s; + fdcon[s].c_status = CS_CON; + fdcon[s].c_namebase = namebase; + fdcon[s].c_name = name; + fdcon[s].c_namelist = namelist; + fdcon[s].c_output_name = xstrdup(oname); + fdcon[s].c_data = (char *) &fdcon[s].c_plen; + fdcon[s].c_len = 4; + fdcon[s].c_off = 0; + gettimeofday(&fdcon[s].c_tv, NULL); + fdcon[s].c_tv.tv_sec += timeout; + TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link); + FD_SET(s, &read_wait); + ncon++; + return (s); +} + +void +confree(int s) +{ + close(s); + if (s >= maxfd || fdcon[s].c_status == CS_UNUSED) + fatal("confree: attempt to free bad fdno %d\n", s); + free(fdcon[s].c_namebase); + free(fdcon[s].c_output_name); + if (fdcon[s].c_status == CS_KEYS) + free(fdcon[s].c_data); + fdcon[s].c_status = CS_UNUSED; + TAILQ_REMOVE(&tq, &fdcon[s], c_link); + FD_CLR(s, &read_wait); + ncon--; +} + +void +contouch(int s) +{ + TAILQ_REMOVE(&tq, &fdcon[s], c_link); + gettimeofday(&fdcon[s].c_tv, NULL); + fdcon[s].c_tv.tv_sec += timeout; + TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link); +} + +int +conrecycle(int s) +{ + int ret; + con *c = &fdcon[s]; + char *iname, *oname; + + iname = xstrdup(c->c_namelist); + oname = c->c_output_name; + c->c_output_name = NULL;/* prevent it from being freed */ + confree(s); + ret = conalloc(iname, oname); + free(iname); + return (ret); +} + +void +congreet(int s) +{ + char buf[80]; + int n; + con *c = &fdcon[s]; + + n = read(s, buf, sizeof(buf)); + if (n < 0) { + if (errno != ECONNREFUSED) + error("read (%s): %s", c->c_name, strerror(errno)); + conrecycle(s); + return; + } + if (buf[n - 1] != '\n') { + error("%s: bad greeting", c->c_name); + confree(s); + return; + } + buf[n - 1] = '\0'; + fprintf(stderr, "# %s %s\n", c->c_name, buf); + n = snprintf(buf, sizeof buf, "SSH-1.5-OpenSSH-keyscan\r\n"); + if (write(s, buf, n) != n) { + error("write (%s): %s", c->c_name, strerror(errno)); + confree(s); + return; + } + c->c_status = CS_SIZE; + contouch(s); +} + +void +conread(int s) +{ + int n; + con *c = &fdcon[s]; + + if (c->c_status == CS_CON) { + congreet(s); + return; + } + n = read(s, c->c_data + c->c_off, c->c_len - c->c_off); + if (n < 0) { + error("read (%s): %s", c->c_name, strerror(errno)); + confree(s); + return; + } + c->c_off += n; + + if (c->c_off == c->c_len) + switch (c->c_status) { + case CS_SIZE: + c->c_plen = htonl(c->c_plen); + c->c_len = c->c_plen + 8 - (c->c_plen & 7); + c->c_off = 0; + c->c_data = xmalloc(c->c_len); + c->c_status = CS_KEYS; + break; + case CS_KEYS: + keyprint(c->c_name, c->c_output_name, c->c_data, c->c_plen); + confree(s); + return; + break; + default: + fatal("conread: invalid status %d\n", c->c_status); + break; + } + + contouch(s); +} + +void +conloop(void) +{ + fd_set r, e; + struct timeval seltime, now; + int i; + con *c; + + gettimeofday(&now, NULL); + c = tq.tqh_first; + + if (c && + (c->c_tv.tv_sec > now.tv_sec || + (c->c_tv.tv_sec == now.tv_sec && c->c_tv.tv_usec > now.tv_usec))) { + seltime = c->c_tv; + seltime.tv_sec -= now.tv_sec; + seltime.tv_usec -= now.tv_usec; + if ((int) seltime.tv_usec < 0) { + seltime.tv_usec += 1000000; + seltime.tv_sec--; + } + } else + seltime.tv_sec = seltime.tv_usec = 0; + + r = e = read_wait; + select(maxfd, &r, NULL, &e, &seltime); + for (i = 0; i < maxfd; i++) + if (FD_ISSET(i, &e)) { + error("%s: exception!", fdcon[i].c_name); + confree(i); + } else if (FD_ISSET(i, &r)) + conread(i); + + c = tq.tqh_first; + while (c && + (c->c_tv.tv_sec < now.tv_sec || + (c->c_tv.tv_sec == now.tv_sec && c->c_tv.tv_usec < now.tv_usec))) { + int s = c->c_fd; + c = c->c_link.tqe_next; + conrecycle(s); + } +} + +char * +nexthost(int argc, char **argv) +{ + static Linebuf *lb; + + for (;;) { + if (!lb) { + if (argno >= argc) + return (NULL); + if (argv[argno][0] != '-') + return (argv[argno++]); + if (!strcmp(argv[argno], "--")) { + if (++argno >= argc) + return (NULL); + return (argv[argno++]); + } else if (!strncmp(argv[argno], "-f", 2)) { + char *fname; + if (argv[argno][2]) + fname = &argv[argno++][2]; + else if (++argno >= argc) { + error("missing filename for `-f'"); + return (NULL); + } else + fname = argv[argno++]; + if (!strcmp(fname, "-")) + fname = NULL; + lb = Linebuf_alloc(fname, warn); + } else + error("ignoring invalid/misplaced option `%s'", argv[argno++]); + } else { + char *line; + line = getline(lb); + if (line) + return (line); + Linebuf_free(lb); + lb = NULL; + } + } +} + +static void +usage(void) +{ + fatal("usage: %s [-t timeout] { [--] host | -f file } ...\n", prog); + return; +} + +int +main(int argc, char **argv) +{ + char *host = NULL; + + TAILQ_INIT(&tq); + + if ((prog = strrchr(argv[0], '/'))) + prog++; + else + prog = argv[0]; + + if (argc <= argno) + usage(); + + if (argv[1][0] == '-' && argv[1][1] == 't') { + argno++; + if (argv[1][2]) + timeout = atoi(&argv[1][2]); + else { + if (argno >= argc) + usage(); + timeout = atoi(argv[argno++]); + } + if (timeout <= 0) + usage(); + } + if (argc <= argno) + usage(); + + maxfd = fdlim_get(1); + if (maxfd < 0) + fatal("%s: fdlim_get: bad value\n", prog); + if (maxfd > MAXMAXFD) + maxfd = MAXMAXFD; + if (maxcon <= 0) + fatal("%s: not enough file descriptors\n", prog); + if (maxfd > fdlim_get(0)) + fdlim_set(maxfd); + fdcon = xmalloc(maxfd * sizeof(con)); + + do { + while (ncon < maxcon) { + char *name; + + host = nexthost(argc, argv); + if (host == NULL) + break; + name = strnnsep(&host, " \t\n"); + conalloc(name, *host ? host : name); + } + conloop(); + } while (host); + while (ncon > 0) + conloop(); + + return (0); +} diff --git a/usr.bin/ssh/ssh-keyscan/Makefile b/usr.bin/ssh/ssh-keyscan/Makefile new file mode 100644 index 00000000000..d40940d4c6e --- /dev/null +++ b/usr.bin/ssh/ssh-keyscan/Makefile @@ -0,0 +1,16 @@ +.PATH: ${.CURDIR}/.. + +PROG= ssh-keyscan +BINOWN= root + +BINMODE?=555 + +BINDIR= /usr/bin +MAN= ssh-keyscan.1 + +SRCS= ssh-keyscan.c log-client.c + +.include <bsd.prog.mk> + +LDADD+= -lcrypto +DPADD+= ${LIBCRYPTO} |