]> andersk Git - openssh.git/blame - ssh-keyscan.c
20001205
[openssh.git] / ssh-keyscan.c
CommitLineData
f6fdbddf 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"
11RCSID("$OpenBSD: ssh-keyscan.c,v 1.1 2000/12/04 19:24:02 markus Exp $");
12
13#include <sys/queue.h>
14#include <err.h>
15#include <errno.h>
16
17#include <openssl/bn.h>
18#include <openssl/rsa.h>
19#include <openssl/dsa.h>
20
21#include "xmalloc.h"
22#include "ssh.h"
23#include "key.h"
24#include "buffer.h"
25#include "bufaux.h"
26
27static int argno = 1; /* Number of argument currently being parsed */
28
29int 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 */
35int timeout = 5;
36
37int maxfd;
38#define maxcon (maxfd - 10)
39
40char *prog;
41fd_set read_wait;
42int 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 */
48typedef 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
67TAILQ_HEAD(conlist, Connection) tq; /* Timeout Queue */
68con *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
77typedef 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
86static inline Linebuf *
87Linebuf_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
120static inline void
121Linebuf_free(Linebuf * lb)
122{
123 fclose(lb->stream);
124 free(lb->buf);
125 free(lb);
126}
127
128static inline void
129Linebuf_restart(Linebuf * lb)
130{
131 clearerr(lb->stream);
132 rewind(lb->stream);
133 lb->lineno = 0;
134}
135
136static inline int
137Linebuf_lineno(Linebuf * lb)
138{
139 return (lb->lineno);
140}
141
142static inline char *
143getline(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
176static int
177fdlim_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
188static int
189fdlim_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 */
207inline char *
208xstrsep(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 */
229char *
230strnnsep(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
240void
241keyprint(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
275int
276tcpconnect(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
308int
309conalloc(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
346void
347confree(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
362void
363contouch(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
371int
372conrecycle(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
387void
388congreet(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
418void
419conread(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
458void
459conloop(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
501char *
502nexthost(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
541static void
542usage(void)
543{
544 fatal("usage: %s [-t timeout] { [--] host | -f file } ...\n", prog);
545 return;
546}
547
548int
549main(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}
This page took 0.152135 seconds and 5 git commands to generate.