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