Annotation of src/usr.bin/ssh/ssh-keyscan.c, Revision 1.1
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
! 6: * OpenBSD project (for instance by leaving this copyright notice
! 7: * intact).
! 8: */
! 9:
! 10: #include "includes.h"
! 11: RCSID("$OpenBSD:$");
! 12:
! 13: #include <sys/queue.h>
! 14: #include <err.h>
! 15: #include <errno.h>
! 16:
! 17: #include <ssl/bn.h>
! 18: #include <ssl/rsa.h>
! 19: #include <ssl/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: }