Annotation of src/usr.bin/ssh/ssh-keyscan.c, Revision 1.25
1.1 markus 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
1.23 pvalchev 6: * OpenBSD project by leaving this copyright notice intact.
1.1 markus 7: */
8:
9: #include "includes.h"
1.25 ! jakob 10: RCSID("$OpenBSD: ssh-keyscan.c,v 1.24 2001/06/23 15:12:20 itojun Exp $");
1.1 markus 11:
12: #include <sys/queue.h>
13: #include <errno.h>
14:
1.5 markus 15: #include <openssl/bn.h>
1.1 markus 16:
17: #include "xmalloc.h"
18: #include "ssh.h"
1.10 markus 19: #include "ssh1.h"
1.1 markus 20: #include "key.h"
21: #include "buffer.h"
22: #include "bufaux.h"
1.11 markus 23: #include "log.h"
1.18 deraadt 24: #include "atomicio.h"
1.1 markus 25:
26: static int argno = 1; /* Number of argument currently being parsed */
27:
28: int family = AF_UNSPEC; /* IPv4, IPv6 or both */
29:
30: #define MAXMAXFD 256
31:
32: /* The number of seconds after which to give up on a TCP connection */
33: int timeout = 5;
34:
35: int maxfd;
1.18 deraadt 36: #define MAXCON (maxfd - 10)
1.1 markus 37:
1.3 markus 38: extern char *__progname;
1.19 millert 39: fd_set *read_wait;
40: size_t read_wait_size;
1.1 markus 41: int ncon;
42:
43: /*
44: * Keep a connection structure for each file descriptor. The state
45: * associated with file descriptor n is held in fdcon[n].
46: */
47: typedef struct Connection {
1.6 markus 48: u_char c_status; /* State of connection on this file desc. */
1.1 markus 49: #define CS_UNUSED 0 /* File descriptor unused */
50: #define CS_CON 1 /* Waiting to connect/read greeting */
51: #define CS_SIZE 2 /* Waiting to read initial packet size */
52: #define CS_KEYS 3 /* Waiting to read public key packet */
53: int c_fd; /* Quick lookup: c->c_fd == c - fdcon */
54: int c_plen; /* Packet length field for ssh packet */
55: int c_len; /* Total bytes which must be read. */
56: int c_off; /* Length of data read so far. */
57: char *c_namebase; /* Address to free for c_name and c_namelist */
58: char *c_name; /* Hostname of connection for errors */
59: char *c_namelist; /* Pointer to other possible addresses */
60: char *c_output_name; /* Hostname of connection for output */
61: char *c_data; /* Data read from this fd */
62: struct timeval c_tv; /* Time at which connection gets aborted */
63: TAILQ_ENTRY(Connection) c_link; /* List of connections in timeout order. */
64: } con;
65:
66: TAILQ_HEAD(conlist, Connection) tq; /* Timeout Queue */
67: con *fdcon;
68:
69: /*
70: * This is just a wrapper around fgets() to make it usable.
71: */
72:
73: /* Stress-test. Increase this later. */
74: #define LINEBUF_SIZE 16
75:
76: typedef struct {
77: char *buf;
1.6 markus 78: u_int size;
1.1 markus 79: int lineno;
80: const char *filename;
81: FILE *stream;
82: void (*errfun) (const char *,...);
83: } Linebuf;
84:
1.24 itojun 85: static Linebuf *
1.1 markus 86: Linebuf_alloc(const char *filename, void (*errfun) (const char *,...))
87: {
88: Linebuf *lb;
89:
90: if (!(lb = malloc(sizeof(*lb)))) {
91: if (errfun)
92: (*errfun) ("linebuf (%s): malloc failed\n", lb->filename);
93: return (NULL);
94: }
95: if (filename) {
96: lb->filename = filename;
97: if (!(lb->stream = fopen(filename, "r"))) {
1.9 markus 98: xfree(lb);
1.1 markus 99: if (errfun)
100: (*errfun) ("%s: %s\n", filename, strerror(errno));
101: return (NULL);
102: }
103: } else {
104: lb->filename = "(stdin)";
105: lb->stream = stdin;
106: }
107:
108: if (!(lb->buf = malloc(lb->size = LINEBUF_SIZE))) {
109: if (errfun)
110: (*errfun) ("linebuf (%s): malloc failed\n", lb->filename);
1.9 markus 111: xfree(lb);
1.1 markus 112: return (NULL);
113: }
114: lb->errfun = errfun;
115: lb->lineno = 0;
116: return (lb);
117: }
118:
1.24 itojun 119: static void
1.1 markus 120: Linebuf_free(Linebuf * lb)
121: {
122: fclose(lb->stream);
1.9 markus 123: xfree(lb->buf);
124: xfree(lb);
1.1 markus 125: }
126:
1.24 itojun 127: #if 0
128: static void
1.1 markus 129: Linebuf_restart(Linebuf * lb)
130: {
131: clearerr(lb->stream);
132: rewind(lb->stream);
133: lb->lineno = 0;
134: }
135:
1.24 itojun 136: static int
1.1 markus 137: Linebuf_lineno(Linebuf * lb)
138: {
139: return (lb->lineno);
140: }
1.24 itojun 141: #endif
1.1 markus 142:
1.24 itojun 143: static char *
1.14 markus 144: Linebuf_getline(Linebuf * lb)
1.1 markus 145: {
146: int n = 0;
147:
148: lb->lineno++;
149: for (;;) {
150: /* Read a line */
151: if (!fgets(&lb->buf[n], lb->size - n, lb->stream)) {
152: if (ferror(lb->stream) && lb->errfun)
1.17 deraadt 153: (*lb->errfun) ("%s: %s\n", lb->filename,
154: strerror(errno));
1.1 markus 155: return (NULL);
156: }
157: n = strlen(lb->buf);
158:
159: /* Return it or an error if it fits */
160: if (n > 0 && lb->buf[n - 1] == '\n') {
161: lb->buf[n - 1] = '\0';
162: return (lb->buf);
163: }
164: if (n != lb->size - 1) {
165: if (lb->errfun)
1.17 deraadt 166: (*lb->errfun) ("%s: skipping incomplete last line\n",
167: lb->filename);
1.1 markus 168: return (NULL);
169: }
170: /* Double the buffer if we need more space */
171: if (!(lb->buf = realloc(lb->buf, (lb->size *= 2)))) {
172: if (lb->errfun)
1.17 deraadt 173: (*lb->errfun) ("linebuf (%s): realloc failed\n",
174: lb->filename);
1.1 markus 175: return (NULL);
176: }
177: }
178: }
179:
1.24 itojun 180: static int
1.1 markus 181: fdlim_get(int hard)
182: {
183: struct rlimit rlfd;
1.17 deraadt 184:
1.1 markus 185: if (getrlimit(RLIMIT_NOFILE, &rlfd) < 0)
186: return (-1);
187: if ((hard ? rlfd.rlim_max : rlfd.rlim_cur) == RLIM_INFINITY)
188: return 10000;
189: else
190: return hard ? rlfd.rlim_max : rlfd.rlim_cur;
191: }
192:
1.24 itojun 193: static int
1.1 markus 194: fdlim_set(int lim)
195: {
196: struct rlimit rlfd;
197: if (lim <= 0)
198: return (-1);
199: if (getrlimit(RLIMIT_NOFILE, &rlfd) < 0)
200: return (-1);
201: rlfd.rlim_cur = lim;
202: if (setrlimit(RLIMIT_NOFILE, &rlfd) < 0)
203: return (-1);
204: return (0);
205: }
206:
207: /*
208: * This is an strsep function that returns a null field for adjacent
209: * separators. This is the same as the 4.4BSD strsep, but different from the
210: * one in the GNU libc.
211: */
1.24 itojun 212: static char *
1.1 markus 213: xstrsep(char **str, const char *delim)
214: {
215: char *s, *e;
216:
217: if (!**str)
218: return (NULL);
219:
220: s = *str;
221: e = s + strcspn(s, delim);
222:
223: if (*e != '\0')
224: *e++ = '\0';
225: *str = e;
226:
227: return (s);
228: }
229:
230: /*
231: * Get the next non-null token (like GNU strsep). Strsep() will return a
232: * null token for two adjacent separators, so we may have to loop.
233: */
1.24 itojun 234: static char *
1.1 markus 235: strnnsep(char **stringp, char *delim)
236: {
237: char *tok;
238:
239: do {
240: tok = xstrsep(stringp, delim);
241: } while (tok && *tok == '\0');
242: return (tok);
243: }
244:
1.24 itojun 245: static void
1.1 markus 246: keyprint(char *host, char *output_name, char *kd, int len)
247: {
248: static Key *rsa;
249: static Buffer msg;
250:
251: if (rsa == NULL) {
252: buffer_init(&msg);
253: rsa = key_new(KEY_RSA1);
254: }
255: buffer_append(&msg, kd, len);
256: buffer_consume(&msg, 8 - (len & 7)); /* padding */
257: if (buffer_get_char(&msg) != (int) SSH_SMSG_PUBLIC_KEY) {
258: error("%s: invalid packet type", host);
259: buffer_clear(&msg);
260: return;
261: }
262: buffer_consume(&msg, 8); /* cookie */
263:
264: /* server key */
265: (void) buffer_get_int(&msg);
266: buffer_get_bignum(&msg, rsa->rsa->e);
267: buffer_get_bignum(&msg, rsa->rsa->n);
268:
269: /* host key */
270: (void) buffer_get_int(&msg);
271: buffer_get_bignum(&msg, rsa->rsa->e);
272: buffer_get_bignum(&msg, rsa->rsa->n);
273: buffer_clear(&msg);
274:
275: fprintf(stdout, "%s ", output_name ? output_name : host);
276: key_write(rsa, stdout);
277: fputs("\n", stdout);
278: }
279:
1.24 itojun 280: static int
1.1 markus 281: tcpconnect(char *host)
282: {
283: struct addrinfo hints, *ai, *aitop;
284: char strport[NI_MAXSERV];
285: int gaierr, s = -1;
286:
1.8 markus 287: snprintf(strport, sizeof strport, "%d", SSH_DEFAULT_PORT);
1.1 markus 288: memset(&hints, 0, sizeof(hints));
289: hints.ai_family = family;
290: hints.ai_socktype = SOCK_STREAM;
291: if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0)
292: fatal("getaddrinfo %s: %s", host, gai_strerror(gaierr));
293: for (ai = aitop; ai; ai = ai->ai_next) {
294: s = socket(ai->ai_family, SOCK_STREAM, 0);
295: if (s < 0) {
296: error("socket: %s", strerror(errno));
297: continue;
298: }
1.7 markus 299: if (fcntl(s, F_SETFL, O_NONBLOCK) < 0)
1.1 markus 300: fatal("F_SETFL: %s", strerror(errno));
301: if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0 &&
302: errno != EINPROGRESS)
303: error("connect (`%s'): %s", host, strerror(errno));
304: else
305: break;
306: close(s);
307: s = -1;
308: }
309: freeaddrinfo(aitop);
310: return s;
311: }
312:
1.24 itojun 313: static int
1.1 markus 314: conalloc(char *iname, char *oname)
315: {
316: int s;
317: char *namebase, *name, *namelist;
318:
319: namebase = namelist = xstrdup(iname);
320:
321: do {
322: name = xstrsep(&namelist, ",");
323: if (!name) {
1.9 markus 324: xfree(namebase);
1.1 markus 325: return (-1);
326: }
327: } while ((s = tcpconnect(name)) < 0);
328:
329: if (s >= maxfd)
1.4 markus 330: fatal("conalloc: fdno %d too high", s);
1.1 markus 331: if (fdcon[s].c_status)
1.4 markus 332: fatal("conalloc: attempt to reuse fdno %d", s);
1.1 markus 333:
334: fdcon[s].c_fd = s;
335: fdcon[s].c_status = CS_CON;
336: fdcon[s].c_namebase = namebase;
337: fdcon[s].c_name = name;
338: fdcon[s].c_namelist = namelist;
339: fdcon[s].c_output_name = xstrdup(oname);
340: fdcon[s].c_data = (char *) &fdcon[s].c_plen;
341: fdcon[s].c_len = 4;
342: fdcon[s].c_off = 0;
343: gettimeofday(&fdcon[s].c_tv, NULL);
344: fdcon[s].c_tv.tv_sec += timeout;
345: TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link);
1.19 millert 346: FD_SET(s, read_wait);
1.1 markus 347: ncon++;
348: return (s);
349: }
350:
1.24 itojun 351: static void
1.1 markus 352: confree(int s)
353: {
354: if (s >= maxfd || fdcon[s].c_status == CS_UNUSED)
1.4 markus 355: fatal("confree: attempt to free bad fdno %d", s);
1.18 deraadt 356: close(s);
1.9 markus 357: xfree(fdcon[s].c_namebase);
358: xfree(fdcon[s].c_output_name);
1.1 markus 359: if (fdcon[s].c_status == CS_KEYS)
1.9 markus 360: xfree(fdcon[s].c_data);
1.1 markus 361: fdcon[s].c_status = CS_UNUSED;
362: TAILQ_REMOVE(&tq, &fdcon[s], c_link);
1.19 millert 363: FD_CLR(s, read_wait);
1.1 markus 364: ncon--;
365: }
366:
1.24 itojun 367: static void
1.1 markus 368: contouch(int s)
369: {
370: TAILQ_REMOVE(&tq, &fdcon[s], c_link);
371: gettimeofday(&fdcon[s].c_tv, NULL);
372: fdcon[s].c_tv.tv_sec += timeout;
373: TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link);
374: }
375:
1.24 itojun 376: static int
1.1 markus 377: conrecycle(int s)
378: {
379: int ret;
380: con *c = &fdcon[s];
381: char *iname, *oname;
382:
383: iname = xstrdup(c->c_namelist);
1.9 markus 384: oname = xstrdup(c->c_output_name);
1.1 markus 385: confree(s);
386: ret = conalloc(iname, oname);
1.9 markus 387: xfree(iname);
388: xfree(oname);
1.1 markus 389: return (ret);
390: }
391:
1.24 itojun 392: static void
1.1 markus 393: congreet(int s)
394: {
1.21 millert 395: char buf[80], *cp;
396: size_t bufsiz;
1.22 deraadt 397: int n = 0;
1.1 markus 398: con *c = &fdcon[s];
399:
1.21 millert 400: bufsiz = sizeof(buf);
401: cp = buf;
402: while (bufsiz-- && (n = read(s, cp, 1)) == 1 && *cp != '\n' && *cp != '\r')
403: cp++;
1.1 markus 404: if (n < 0) {
405: if (errno != ECONNREFUSED)
406: error("read (%s): %s", c->c_name, strerror(errno));
407: conrecycle(s);
408: return;
409: }
1.21 millert 410: if (*cp != '\n' && *cp != '\r') {
1.1 markus 411: error("%s: bad greeting", c->c_name);
412: confree(s);
413: return;
414: }
1.21 millert 415: *cp = '\0';
1.1 markus 416: fprintf(stderr, "# %s %s\n", c->c_name, buf);
417: n = snprintf(buf, sizeof buf, "SSH-1.5-OpenSSH-keyscan\r\n");
1.18 deraadt 418: if (atomicio(write, s, buf, n) != n) {
1.1 markus 419: error("write (%s): %s", c->c_name, strerror(errno));
420: confree(s);
421: return;
422: }
423: c->c_status = CS_SIZE;
424: contouch(s);
425: }
426:
1.24 itojun 427: static void
1.1 markus 428: conread(int s)
429: {
430: int n;
431: con *c = &fdcon[s];
432:
433: if (c->c_status == CS_CON) {
434: congreet(s);
435: return;
436: }
437: n = read(s, c->c_data + c->c_off, c->c_len - c->c_off);
438: if (n < 0) {
439: error("read (%s): %s", c->c_name, strerror(errno));
440: confree(s);
441: return;
442: }
443: c->c_off += n;
444:
445: if (c->c_off == c->c_len)
446: switch (c->c_status) {
447: case CS_SIZE:
448: c->c_plen = htonl(c->c_plen);
449: c->c_len = c->c_plen + 8 - (c->c_plen & 7);
450: c->c_off = 0;
451: c->c_data = xmalloc(c->c_len);
452: c->c_status = CS_KEYS;
453: break;
454: case CS_KEYS:
455: keyprint(c->c_name, c->c_output_name, c->c_data, c->c_plen);
456: confree(s);
457: return;
458: break;
459: default:
1.4 markus 460: fatal("conread: invalid status %d", c->c_status);
1.1 markus 461: break;
462: }
463:
464: contouch(s);
465: }
466:
1.24 itojun 467: static void
1.1 markus 468: conloop(void)
469: {
1.19 millert 470: fd_set *r, *e;
1.1 markus 471: struct timeval seltime, now;
472: int i;
473: con *c;
474:
475: gettimeofday(&now, NULL);
476: c = tq.tqh_first;
477:
1.18 deraadt 478: if (c && (c->c_tv.tv_sec > now.tv_sec ||
479: (c->c_tv.tv_sec == now.tv_sec && c->c_tv.tv_usec > now.tv_usec))) {
1.1 markus 480: seltime = c->c_tv;
481: seltime.tv_sec -= now.tv_sec;
482: seltime.tv_usec -= now.tv_usec;
1.13 itojun 483: if (seltime.tv_usec < 0) {
1.1 markus 484: seltime.tv_usec += 1000000;
485: seltime.tv_sec--;
486: }
487: } else
488: seltime.tv_sec = seltime.tv_usec = 0;
489:
1.19 millert 490: r = xmalloc(read_wait_size);
491: memcpy(r, read_wait, read_wait_size);
492: e = xmalloc(read_wait_size);
493: memcpy(e, read_wait, read_wait_size);
494:
495: while (select(maxfd, r, NULL, e, &seltime) == -1 &&
1.16 deraadt 496: (errno == EAGAIN || errno == EINTR))
497: ;
498:
1.18 deraadt 499: for (i = 0; i < maxfd; i++) {
1.19 millert 500: if (FD_ISSET(i, e)) {
1.1 markus 501: error("%s: exception!", fdcon[i].c_name);
502: confree(i);
1.19 millert 503: } else if (FD_ISSET(i, r))
1.1 markus 504: conread(i);
1.18 deraadt 505: }
1.19 millert 506: xfree(r);
507: xfree(e);
1.1 markus 508:
509: c = tq.tqh_first;
1.18 deraadt 510: while (c && (c->c_tv.tv_sec < now.tv_sec ||
511: (c->c_tv.tv_sec == now.tv_sec && c->c_tv.tv_usec < now.tv_usec))) {
1.1 markus 512: int s = c->c_fd;
1.18 deraadt 513:
1.1 markus 514: c = c->c_link.tqe_next;
515: conrecycle(s);
516: }
517: }
518:
1.24 itojun 519: static char *
1.1 markus 520: nexthost(int argc, char **argv)
521: {
522: static Linebuf *lb;
523:
524: for (;;) {
525: if (!lb) {
526: if (argno >= argc)
527: return (NULL);
528: if (argv[argno][0] != '-')
529: return (argv[argno++]);
530: if (!strcmp(argv[argno], "--")) {
531: if (++argno >= argc)
532: return (NULL);
533: return (argv[argno++]);
534: } else if (!strncmp(argv[argno], "-f", 2)) {
535: char *fname;
1.18 deraadt 536:
1.1 markus 537: if (argv[argno][2])
538: fname = &argv[argno++][2];
539: else if (++argno >= argc) {
540: error("missing filename for `-f'");
541: return (NULL);
542: } else
543: fname = argv[argno++];
544: if (!strcmp(fname, "-"))
545: fname = NULL;
1.2 markus 546: lb = Linebuf_alloc(fname, error);
1.1 markus 547: } else
1.18 deraadt 548: error("ignoring invalid/misplaced option `%s'",
549: argv[argno++]);
1.1 markus 550: } else {
551: char *line;
1.18 deraadt 552:
1.14 markus 553: line = Linebuf_getline(lb);
1.1 markus 554: if (line)
555: return (line);
556: Linebuf_free(lb);
557: lb = NULL;
558: }
559: }
560: }
561:
1.24 itojun 562: static void
1.1 markus 563: usage(void)
564: {
1.25 ! jakob 565: fprintf(stderr, "Usage: %s [options] [ host | addrlist namelist ]\n",
! 566: __progname);
! 567: fprintf(stderr, "Options:\n");
! 568: fprintf(stderr, " -t timeout Set connection timeout.\n");
! 569: fprintf(stderr, " -f file Read hosts or addresses from file.\n");
! 570: exit(1);
1.1 markus 571: }
572:
573: int
574: main(int argc, char **argv)
575: {
576: char *host = NULL;
577:
578: TAILQ_INIT(&tq);
579:
580: if (argc <= argno)
581: usage();
582:
583: if (argv[1][0] == '-' && argv[1][1] == 't') {
584: argno++;
585: if (argv[1][2])
586: timeout = atoi(&argv[1][2]);
587: else {
588: if (argno >= argc)
589: usage();
590: timeout = atoi(argv[argno++]);
591: }
592: if (timeout <= 0)
593: usage();
594: }
595: if (argc <= argno)
596: usage();
597:
598: maxfd = fdlim_get(1);
599: if (maxfd < 0)
1.4 markus 600: fatal("%s: fdlim_get: bad value", __progname);
1.1 markus 601: if (maxfd > MAXMAXFD)
602: maxfd = MAXMAXFD;
1.18 deraadt 603: if (MAXCON <= 0)
1.4 markus 604: fatal("%s: not enough file descriptors", __progname);
1.1 markus 605: if (maxfd > fdlim_get(0))
606: fdlim_set(maxfd);
607: fdcon = xmalloc(maxfd * sizeof(con));
1.15 itojun 608: memset(fdcon, 0, maxfd * sizeof(con));
1.19 millert 609:
610: read_wait_size = howmany(maxfd, NFDBITS) * sizeof(fd_mask);
611: read_wait = xmalloc(read_wait_size);
612: memset(read_wait, 0, read_wait_size);
1.1 markus 613:
614: do {
1.18 deraadt 615: while (ncon < MAXCON) {
1.1 markus 616: char *name;
617:
618: host = nexthost(argc, argv);
619: if (host == NULL)
620: break;
621: name = strnnsep(&host, " \t\n");
622: conalloc(name, *host ? host : name);
623: }
624: conloop();
625: } while (host);
626: while (ncon > 0)
627: conloop();
628:
629: return (0);
630: }