version 1.22, 2001/03/06 06:11:18 |
version 1.22.4.2, 2001/11/15 00:15:19 |
|
|
* |
* |
* Modification and redistribution in source and binary forms is |
* Modification and redistribution in source and binary forms is |
* permitted provided that due credit is given to the author and the |
* permitted provided that due credit is given to the author and the |
* OpenBSD project (for instance by leaving this copyright notice |
* OpenBSD project by leaving this copyright notice intact. |
* intact). |
|
*/ |
*/ |
|
|
#include "includes.h" |
#include "includes.h" |
|
|
|
|
#include <openssl/bn.h> |
#include <openssl/bn.h> |
|
|
|
#include <setjmp.h> |
#include "xmalloc.h" |
#include "xmalloc.h" |
#include "ssh.h" |
#include "ssh.h" |
#include "ssh1.h" |
#include "ssh1.h" |
#include "key.h" |
#include "key.h" |
|
#include "kex.h" |
|
#include "compat.h" |
|
#include "myproposal.h" |
|
#include "packet.h" |
|
#include "dispatch.h" |
#include "buffer.h" |
#include "buffer.h" |
#include "bufaux.h" |
#include "bufaux.h" |
#include "log.h" |
#include "log.h" |
#include "atomicio.h" |
#include "atomicio.h" |
|
#include "misc.h" |
|
|
static int argno = 1; /* Number of argument currently being parsed */ |
/* Flag indicating whether IPv4 or IPv6. This can be set on the command line. |
|
Default value is AF_UNSPEC means both IPv4 and IPv6. */ |
|
int IPv4or6 = AF_UNSPEC; |
|
|
int family = AF_UNSPEC; /* IPv4, IPv6 or both */ |
int ssh_port = SSH_DEFAULT_PORT; |
|
|
|
#define KT_RSA1 1 |
|
#define KT_DSA 2 |
|
#define KT_RSA 4 |
|
|
|
int get_keytypes = KT_RSA1; /* Get only RSA1 keys by default */ |
|
|
#define MAXMAXFD 256 |
#define MAXMAXFD 256 |
|
|
/* The number of seconds after which to give up on a TCP connection */ |
/* The number of seconds after which to give up on a TCP connection */ |
|
|
fd_set *read_wait; |
fd_set *read_wait; |
size_t read_wait_size; |
size_t read_wait_size; |
int ncon; |
int ncon; |
|
int nonfatal_fatal = 0; |
|
jmp_buf kexjmp; |
|
Key *kexjmp_key; |
|
|
/* |
/* |
* Keep a connection structure for each file descriptor. The state |
* Keep a connection structure for each file descriptor. The state |
|
|
int c_plen; /* Packet length field for ssh packet */ |
int c_plen; /* Packet length field for ssh packet */ |
int c_len; /* Total bytes which must be read. */ |
int c_len; /* Total bytes which must be read. */ |
int c_off; /* Length of data read so far. */ |
int c_off; /* Length of data read so far. */ |
|
int c_keytype; /* Only one of KT_RSA1, KT_DSA, or KT_RSA */ |
char *c_namebase; /* Address to free for c_name and c_namelist */ |
char *c_namebase; /* Address to free for c_name and c_namelist */ |
char *c_name; /* Hostname of connection for errors */ |
char *c_name; /* Hostname of connection for errors */ |
char *c_namelist; /* Pointer to other possible addresses */ |
char *c_namelist; /* Pointer to other possible addresses */ |
char *c_output_name; /* Hostname of connection for output */ |
char *c_output_name; /* Hostname of connection for output */ |
char *c_data; /* Data read from this fd */ |
char *c_data; /* Data read from this fd */ |
|
Kex *c_kex; /* The key-exchange struct for ssh2 */ |
struct timeval c_tv; /* Time at which connection gets aborted */ |
struct timeval c_tv; /* Time at which connection gets aborted */ |
TAILQ_ENTRY(Connection) c_link; /* List of connections in timeout order. */ |
TAILQ_ENTRY(Connection) c_link; /* List of connections in timeout order. */ |
} con; |
} con; |
|
|
void (*errfun) (const char *,...); |
void (*errfun) (const char *,...); |
} Linebuf; |
} Linebuf; |
|
|
Linebuf * |
static Linebuf * |
Linebuf_alloc(const char *filename, void (*errfun) (const char *,...)) |
Linebuf_alloc(const char *filename, void (*errfun) (const char *,...)) |
{ |
{ |
Linebuf *lb; |
Linebuf *lb; |
|
|
return (lb); |
return (lb); |
} |
} |
|
|
void |
static void |
Linebuf_free(Linebuf * lb) |
Linebuf_free(Linebuf * lb) |
{ |
{ |
fclose(lb->stream); |
fclose(lb->stream); |
|
|
xfree(lb); |
xfree(lb); |
} |
} |
|
|
void |
#if 0 |
|
static void |
Linebuf_restart(Linebuf * lb) |
Linebuf_restart(Linebuf * lb) |
{ |
{ |
clearerr(lb->stream); |
clearerr(lb->stream); |
|
|
lb->lineno = 0; |
lb->lineno = 0; |
} |
} |
|
|
int |
static int |
Linebuf_lineno(Linebuf * lb) |
Linebuf_lineno(Linebuf * lb) |
{ |
{ |
return (lb->lineno); |
return (lb->lineno); |
} |
} |
|
#endif |
|
|
char * |
static char * |
Linebuf_getline(Linebuf * lb) |
Linebuf_getline(Linebuf * lb) |
{ |
{ |
int n = 0; |
int n = 0; |
|
|
} |
} |
} |
} |
|
|
int |
static int |
fdlim_get(int hard) |
fdlim_get(int hard) |
{ |
{ |
struct rlimit rlfd; |
struct rlimit rlfd; |
|
|
return hard ? rlfd.rlim_max : rlfd.rlim_cur; |
return hard ? rlfd.rlim_max : rlfd.rlim_cur; |
} |
} |
|
|
int |
static int |
fdlim_set(int lim) |
fdlim_set(int lim) |
{ |
{ |
struct rlimit rlfd; |
struct rlimit rlfd; |
|
|
* separators. This is the same as the 4.4BSD strsep, but different from the |
* separators. This is the same as the 4.4BSD strsep, but different from the |
* one in the GNU libc. |
* one in the GNU libc. |
*/ |
*/ |
char * |
static char * |
xstrsep(char **str, const char *delim) |
xstrsep(char **str, const char *delim) |
{ |
{ |
char *s, *e; |
char *s, *e; |
|
|
* Get the next non-null token (like GNU strsep). Strsep() will return a |
* Get the next non-null token (like GNU strsep). Strsep() will return a |
* null token for two adjacent separators, so we may have to loop. |
* null token for two adjacent separators, so we may have to loop. |
*/ |
*/ |
char * |
static char * |
strnnsep(char **stringp, char *delim) |
strnnsep(char **stringp, char *delim) |
{ |
{ |
char *tok; |
char *tok; |
|
|
return (tok); |
return (tok); |
} |
} |
|
|
void |
static Key * |
keyprint(char *host, char *output_name, char *kd, int len) |
keygrab_ssh1(con *c) |
{ |
{ |
static Key *rsa; |
static Key *rsa; |
static Buffer msg; |
static Buffer msg; |
|
|
buffer_init(&msg); |
buffer_init(&msg); |
rsa = key_new(KEY_RSA1); |
rsa = key_new(KEY_RSA1); |
} |
} |
buffer_append(&msg, kd, len); |
buffer_append(&msg, c->c_data, c->c_plen); |
buffer_consume(&msg, 8 - (len & 7)); /* padding */ |
buffer_consume(&msg, 8 - (c->c_plen & 7)); /* padding */ |
if (buffer_get_char(&msg) != (int) SSH_SMSG_PUBLIC_KEY) { |
if (buffer_get_char(&msg) != (int) SSH_SMSG_PUBLIC_KEY) { |
error("%s: invalid packet type", host); |
error("%s: invalid packet type", c->c_name); |
buffer_clear(&msg); |
buffer_clear(&msg); |
return; |
return NULL; |
} |
} |
buffer_consume(&msg, 8); /* cookie */ |
buffer_consume(&msg, 8); /* cookie */ |
|
|
|
|
(void) buffer_get_int(&msg); |
(void) buffer_get_int(&msg); |
buffer_get_bignum(&msg, rsa->rsa->e); |
buffer_get_bignum(&msg, rsa->rsa->e); |
buffer_get_bignum(&msg, rsa->rsa->n); |
buffer_get_bignum(&msg, rsa->rsa->n); |
|
|
buffer_clear(&msg); |
buffer_clear(&msg); |
|
|
fprintf(stdout, "%s ", output_name ? output_name : host); |
return (rsa); |
key_write(rsa, stdout); |
} |
|
|
|
static int |
|
hostjump(Key *hostkey) |
|
{ |
|
kexjmp_key = hostkey; |
|
longjmp(kexjmp, 1); |
|
} |
|
|
|
static int |
|
ssh2_capable(int remote_major, int remote_minor) |
|
{ |
|
switch (remote_major) { |
|
case 1: |
|
if (remote_minor == 99) |
|
return 1; |
|
break; |
|
case 2: |
|
return 1; |
|
default: |
|
break; |
|
} |
|
return 0; |
|
} |
|
|
|
static Key * |
|
keygrab_ssh2(con *c) |
|
{ |
|
int j; |
|
|
|
packet_set_connection(c->c_fd, c->c_fd); |
|
enable_compat20(); |
|
myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = c->c_keytype == KT_DSA? |
|
"ssh-dss": "ssh-rsa"; |
|
c->c_kex = kex_setup(myproposal); |
|
c->c_kex->verify_host_key = hostjump; |
|
|
|
if (!(j = setjmp(kexjmp))) { |
|
nonfatal_fatal = 1; |
|
dispatch_run(DISPATCH_BLOCK, &c->c_kex->done, c->c_kex); |
|
fprintf(stderr, "Impossible! dispatch_run() returned!\n"); |
|
exit(1); |
|
} |
|
nonfatal_fatal = 0; |
|
xfree(c->c_kex); |
|
c->c_kex = NULL; |
|
packet_close(); |
|
|
|
return j < 0? NULL : kexjmp_key; |
|
} |
|
|
|
static void |
|
keyprint(con *c, Key *key) |
|
{ |
|
if (!key) |
|
return; |
|
|
|
fprintf(stdout, "%s ", c->c_output_name ? c->c_output_name : c->c_name); |
|
key_write(key, stdout); |
fputs("\n", stdout); |
fputs("\n", stdout); |
} |
} |
|
|
int |
static int |
tcpconnect(char *host) |
tcpconnect(char *host) |
{ |
{ |
struct addrinfo hints, *ai, *aitop; |
struct addrinfo hints, *ai, *aitop; |
char strport[NI_MAXSERV]; |
char strport[NI_MAXSERV]; |
int gaierr, s = -1; |
int gaierr, s = -1; |
|
|
snprintf(strport, sizeof strport, "%d", SSH_DEFAULT_PORT); |
snprintf(strport, sizeof strport, "%d", ssh_port); |
memset(&hints, 0, sizeof(hints)); |
memset(&hints, 0, sizeof(hints)); |
hints.ai_family = family; |
hints.ai_family = IPv4or6; |
hints.ai_socktype = SOCK_STREAM; |
hints.ai_socktype = SOCK_STREAM; |
if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) |
if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) |
fatal("getaddrinfo %s: %s", host, gai_strerror(gaierr)); |
fatal("getaddrinfo %s: %s", host, gai_strerror(gaierr)); |
|
|
return s; |
return s; |
} |
} |
|
|
int |
static int |
conalloc(char *iname, char *oname) |
conalloc(char *iname, char *oname, int keytype) |
{ |
{ |
int s; |
int s; |
char *namebase, *name, *namelist; |
char *namebase, *name, *namelist; |
|
|
fdcon[s].c_data = (char *) &fdcon[s].c_plen; |
fdcon[s].c_data = (char *) &fdcon[s].c_plen; |
fdcon[s].c_len = 4; |
fdcon[s].c_len = 4; |
fdcon[s].c_off = 0; |
fdcon[s].c_off = 0; |
|
fdcon[s].c_keytype = keytype; |
gettimeofday(&fdcon[s].c_tv, NULL); |
gettimeofday(&fdcon[s].c_tv, NULL); |
fdcon[s].c_tv.tv_sec += timeout; |
fdcon[s].c_tv.tv_sec += timeout; |
TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link); |
TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link); |
|
|
return (s); |
return (s); |
} |
} |
|
|
void |
static void |
confree(int s) |
confree(int s) |
{ |
{ |
if (s >= maxfd || fdcon[s].c_status == CS_UNUSED) |
if (s >= maxfd || fdcon[s].c_status == CS_UNUSED) |
|
|
if (fdcon[s].c_status == CS_KEYS) |
if (fdcon[s].c_status == CS_KEYS) |
xfree(fdcon[s].c_data); |
xfree(fdcon[s].c_data); |
fdcon[s].c_status = CS_UNUSED; |
fdcon[s].c_status = CS_UNUSED; |
|
fdcon[s].c_keytype = 0; |
TAILQ_REMOVE(&tq, &fdcon[s], c_link); |
TAILQ_REMOVE(&tq, &fdcon[s], c_link); |
FD_CLR(s, read_wait); |
FD_CLR(s, read_wait); |
ncon--; |
ncon--; |
} |
} |
|
|
void |
static void |
contouch(int s) |
contouch(int s) |
{ |
{ |
TAILQ_REMOVE(&tq, &fdcon[s], c_link); |
TAILQ_REMOVE(&tq, &fdcon[s], c_link); |
|
|
TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link); |
TAILQ_INSERT_TAIL(&tq, &fdcon[s], c_link); |
} |
} |
|
|
int |
static int |
conrecycle(int s) |
conrecycle(int s) |
{ |
{ |
int ret; |
int ret; |
con *c = &fdcon[s]; |
con *c = &fdcon[s]; |
char *iname, *oname; |
|
|
|
iname = xstrdup(c->c_namelist); |
ret = conalloc(c->c_namelist, c->c_output_name, c->c_keytype); |
oname = xstrdup(c->c_output_name); |
|
confree(s); |
confree(s); |
ret = conalloc(iname, oname); |
|
xfree(iname); |
|
xfree(oname); |
|
return (ret); |
return (ret); |
} |
} |
|
|
void |
static void |
congreet(int s) |
congreet(int s) |
{ |
{ |
char buf[80], *cp; |
char buf[256], *cp; |
size_t bufsiz; |
size_t bufsiz; |
int n = 0; |
int n = 0; |
con *c = &fdcon[s]; |
con *c = &fdcon[s]; |
|
|
bufsiz = sizeof(buf); |
bufsiz = sizeof(buf); |
cp = buf; |
cp = buf; |
while (bufsiz-- && (n = read(s, cp, 1)) == 1 && *cp != '\n' && *cp != '\r') |
while (bufsiz-- && (n = read(s, cp, 1)) == 1 && *cp != '\n') { |
|
if (*cp == '\r') |
|
*cp = '\n'; |
cp++; |
cp++; |
|
} |
if (n < 0) { |
if (n < 0) { |
if (errno != ECONNREFUSED) |
if (errno != ECONNREFUSED) |
error("read (%s): %s", c->c_name, strerror(errno)); |
error("read (%s): %s", c->c_name, strerror(errno)); |
|
|
return; |
return; |
} |
} |
*cp = '\0'; |
*cp = '\0'; |
fprintf(stderr, "# %s %s\n", c->c_name, buf); |
if (c->c_keytype != KT_RSA1) { |
n = snprintf(buf, sizeof buf, "SSH-1.5-OpenSSH-keyscan\r\n"); |
int remote_major, remote_minor; |
|
char remote_version[sizeof buf]; |
|
|
|
if (sscanf(buf, "SSH-%d.%d-%[^\n]\n", |
|
&remote_major, &remote_minor, remote_version) == 3) |
|
compat_datafellows(remote_version); |
|
else |
|
datafellows = 0; |
|
if (!ssh2_capable(remote_major, remote_minor)) { |
|
debug("%s doesn't support ssh2", c->c_name); |
|
confree(s); |
|
return; |
|
} |
|
} |
|
fprintf(stderr, "# %s %s\n", c->c_name, chop(buf)); |
|
n = snprintf(buf, sizeof buf, "SSH-%d.%d-OpenSSH-keyscan\r\n", |
|
c->c_keytype == KT_RSA1? PROTOCOL_MAJOR_1 : PROTOCOL_MAJOR_2, |
|
c->c_keytype == KT_RSA1? PROTOCOL_MINOR_1 : PROTOCOL_MINOR_2); |
if (atomicio(write, s, buf, n) != n) { |
if (atomicio(write, s, buf, n) != n) { |
error("write (%s): %s", c->c_name, strerror(errno)); |
error("write (%s): %s", c->c_name, strerror(errno)); |
confree(s); |
confree(s); |
return; |
return; |
} |
} |
|
if (c->c_keytype != KT_RSA1) { |
|
keyprint(c, keygrab_ssh2(c)); |
|
confree(s); |
|
return; |
|
} |
c->c_status = CS_SIZE; |
c->c_status = CS_SIZE; |
contouch(s); |
contouch(s); |
} |
} |
|
|
void |
static void |
conread(int s) |
conread(int s) |
{ |
{ |
int n; |
int n; |
|
|
c->c_status = CS_KEYS; |
c->c_status = CS_KEYS; |
break; |
break; |
case CS_KEYS: |
case CS_KEYS: |
keyprint(c->c_name, c->c_output_name, c->c_data, c->c_plen); |
keyprint(c, keygrab_ssh1(c)); |
confree(s); |
confree(s); |
return; |
return; |
break; |
break; |
|
|
contouch(s); |
contouch(s); |
} |
} |
|
|
void |
static void |
conloop(void) |
conloop(void) |
{ |
{ |
fd_set *r, *e; |
fd_set *r, *e; |
|
|
} |
} |
} |
} |
|
|
char * |
static void |
nexthost(int argc, char **argv) |
do_host(char *host) |
{ |
{ |
static Linebuf *lb; |
char *name = strnnsep(&host, " \t\n"); |
|
int j; |
|
|
for (;;) { |
for (j = KT_RSA1; j <= KT_RSA; j *= 2) { |
if (!lb) { |
if (get_keytypes & j) { |
if (argno >= argc) |
while (ncon >= MAXCON) |
return (NULL); |
conloop(); |
if (argv[argno][0] != '-') |
conalloc(name, *host ? host : name, j); |
return (argv[argno++]); |
|
if (!strcmp(argv[argno], "--")) { |
|
if (++argno >= argc) |
|
return (NULL); |
|
return (argv[argno++]); |
|
} else if (!strncmp(argv[argno], "-f", 2)) { |
|
char *fname; |
|
|
|
if (argv[argno][2]) |
|
fname = &argv[argno++][2]; |
|
else if (++argno >= argc) { |
|
error("missing filename for `-f'"); |
|
return (NULL); |
|
} else |
|
fname = argv[argno++]; |
|
if (!strcmp(fname, "-")) |
|
fname = NULL; |
|
lb = Linebuf_alloc(fname, error); |
|
} else |
|
error("ignoring invalid/misplaced option `%s'", |
|
argv[argno++]); |
|
} else { |
|
char *line; |
|
|
|
line = Linebuf_getline(lb); |
|
if (line) |
|
return (line); |
|
Linebuf_free(lb); |
|
lb = NULL; |
|
} |
} |
} |
} |
} |
} |
|
|
void |
static void |
|
fatal_callback(void *arg) |
|
{ |
|
if (nonfatal_fatal) |
|
longjmp(kexjmp, -1); |
|
} |
|
|
|
static void |
usage(void) |
usage(void) |
{ |
{ |
fatal("usage: %s [-t timeout] { [--] host | -f file } ...", __progname); |
fprintf(stderr, "Usage: %s [options] host ...\n", |
return; |
__progname); |
|
fprintf(stderr, "Options:\n"); |
|
fprintf(stderr, " -f file Read hosts or addresses from file.\n"); |
|
fprintf(stderr, " -p port Connect to the specified port.\n"); |
|
fprintf(stderr, " -t keytype Specify the host key type.\n"); |
|
fprintf(stderr, " -T timeout Set connection timeout.\n"); |
|
fprintf(stderr, " -v Verbose; display verbose debugging messages.\n"); |
|
fprintf(stderr, " -4 Use IPv4 only.\n"); |
|
fprintf(stderr, " -6 Use IPv6 only.\n"); |
|
exit(1); |
} |
} |
|
|
int |
int |
main(int argc, char **argv) |
main(int argc, char **argv) |
{ |
{ |
char *host = NULL; |
int debug_flag = 0, log_level = SYSLOG_LEVEL_INFO; |
|
int opt, fopt_count = 0; |
|
char *tname; |
|
|
|
extern int optind; |
|
extern char *optarg; |
|
|
TAILQ_INIT(&tq); |
TAILQ_INIT(&tq); |
|
|
if (argc <= argno) |
if (argc <= 1) |
usage(); |
usage(); |
|
|
if (argv[1][0] == '-' && argv[1][1] == 't') { |
while ((opt = getopt(argc, argv, "v46p:T:t:f:")) != -1) { |
argno++; |
switch (opt) { |
if (argv[1][2]) |
case 'p': |
timeout = atoi(&argv[1][2]); |
ssh_port = a2port(optarg); |
else { |
if (ssh_port == 0) { |
if (argno >= argc) |
fprintf(stderr, "Bad port '%s'\n", optarg); |
|
exit(1); |
|
} |
|
break; |
|
case 'T': |
|
timeout = atoi(optarg); |
|
if (timeout <= 0) |
usage(); |
usage(); |
timeout = atoi(argv[argno++]); |
break; |
} |
case 'v': |
if (timeout <= 0) |
if (!debug_flag) { |
|
debug_flag = 1; |
|
log_level = SYSLOG_LEVEL_DEBUG1; |
|
} |
|
else if (log_level < SYSLOG_LEVEL_DEBUG3) |
|
log_level++; |
|
else |
|
fatal("Too high debugging level."); |
|
break; |
|
case 'f': |
|
if (strcmp(optarg, "-") == 0) |
|
optarg = NULL; |
|
argv[fopt_count++] = optarg; |
|
break; |
|
case 't': |
|
get_keytypes = 0; |
|
tname = strtok(optarg, ","); |
|
while (tname) { |
|
int type = key_type_from_name(tname); |
|
switch (type) { |
|
case KEY_RSA1: |
|
get_keytypes |= KT_RSA1; |
|
break; |
|
case KEY_DSA: |
|
get_keytypes |= KT_DSA; |
|
break; |
|
case KEY_RSA: |
|
get_keytypes |= KT_RSA; |
|
break; |
|
case KEY_UNSPEC: |
|
fatal("unknown key type %s\n", tname); |
|
} |
|
tname = strtok(NULL, ","); |
|
} |
|
break; |
|
case '4': |
|
IPv4or6 = AF_INET; |
|
break; |
|
case '6': |
|
IPv4or6 = AF_INET6; |
|
break; |
|
case '?': |
|
default: |
usage(); |
usage(); |
|
} |
} |
} |
if (argc <= argno) |
if (optind == argc && !fopt_count) |
usage(); |
usage(); |
|
|
|
log_init("ssh-keyscan", log_level, SYSLOG_FACILITY_USER, 1); |
|
fatal_add_cleanup(fatal_callback, NULL); |
|
|
maxfd = fdlim_get(1); |
maxfd = fdlim_get(1); |
if (maxfd < 0) |
if (maxfd < 0) |
fatal("%s: fdlim_get: bad value", __progname); |
fatal("%s: fdlim_get: bad value", __progname); |
|
|
read_wait = xmalloc(read_wait_size); |
read_wait = xmalloc(read_wait_size); |
memset(read_wait, 0, read_wait_size); |
memset(read_wait, 0, read_wait_size); |
|
|
do { |
if (fopt_count) { |
while (ncon < MAXCON) { |
Linebuf *lb; |
char *name; |
char *line; |
|
int j; |
|
|
host = nexthost(argc, argv); |
for (j = 0; j < fopt_count; j++) { |
if (host == NULL) |
lb = Linebuf_alloc(argv[j], error); |
break; |
if (!lb) |
name = strnnsep(&host, " \t\n"); |
continue; |
conalloc(name, *host ? host : name); |
while ((line = Linebuf_getline(lb)) != NULL) |
|
do_host(line); |
|
Linebuf_free(lb); |
} |
} |
conloop(); |
} |
} while (host); |
|
|
while (optind < argc) |
|
do_host(argv[optind++]); |
|
|
while (ncon > 0) |
while (ncon > 0) |
conloop(); |
conloop(); |
|
|