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