version 1.135, 2002/09/19 01:58:18 |
version 1.135.2.2, 2003/09/16 21:20:28 |
|
|
#include "misc.h" |
#include "misc.h" |
#include "readpass.h" |
#include "readpass.h" |
|
|
|
#ifdef DNS |
|
#include "dns.h" |
|
#endif |
|
|
char *client_version_string = NULL; |
char *client_version_string = NULL; |
char *server_version_string = NULL; |
char *server_version_string = NULL; |
|
|
|
#ifdef DNS |
|
int verified_host_key_dns = 0; |
|
#endif |
|
|
/* import */ |
/* import */ |
extern Options options; |
extern Options options; |
extern char *__progname; |
extern char *__progname; |
|
|
* Creates a (possibly privileged) socket for use as the ssh connection. |
* Creates a (possibly privileged) socket for use as the ssh connection. |
*/ |
*/ |
static int |
static int |
ssh_create_socket(int privileged, int family) |
ssh_create_socket(int privileged, struct addrinfo *ai) |
{ |
{ |
int sock, gaierr; |
int sock, gaierr; |
struct addrinfo hints, *res; |
struct addrinfo hints, *res; |
|
|
if (privileged) { |
if (privileged) { |
int p = IPPORT_RESERVED - 1; |
int p = IPPORT_RESERVED - 1; |
PRIV_START; |
PRIV_START; |
sock = rresvport_af(&p, family); |
sock = rresvport_af(&p, ai->ai_family); |
PRIV_END; |
PRIV_END; |
if (sock < 0) |
if (sock < 0) |
error("rresvport: af=%d %.100s", family, strerror(errno)); |
error("rresvport: af=%d %.100s", ai->ai_family, |
|
strerror(errno)); |
else |
else |
debug("Allocated local port %d.", p); |
debug("Allocated local port %d.", p); |
return sock; |
return sock; |
} |
} |
sock = socket(family, SOCK_STREAM, 0); |
sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); |
if (sock < 0) |
if (sock < 0) |
error("socket: %.100s", strerror(errno)); |
error("socket: %.100s", strerror(errno)); |
|
|
|
|
return sock; |
return sock; |
|
|
memset(&hints, 0, sizeof(hints)); |
memset(&hints, 0, sizeof(hints)); |
hints.ai_family = family; |
hints.ai_family = ai->ai_family; |
hints.ai_socktype = SOCK_STREAM; |
hints.ai_socktype = ai->ai_socktype; |
|
hints.ai_protocol = ai->ai_protocol; |
hints.ai_flags = AI_PASSIVE; |
hints.ai_flags = AI_PASSIVE; |
gaierr = getaddrinfo(options.bind_address, "0", &hints, &res); |
gaierr = getaddrinfo(options.bind_address, "0", &hints, &res); |
if (gaierr) { |
if (gaierr) { |
|
|
return sock; |
return sock; |
} |
} |
|
|
|
static int |
|
timeout_connect(int sockfd, const struct sockaddr *serv_addr, |
|
socklen_t addrlen, int timeout) |
|
{ |
|
fd_set *fdset; |
|
struct timeval tv; |
|
socklen_t optlen; |
|
int fdsetsz, optval, rc, result = -1; |
|
|
|
if (timeout <= 0) |
|
return (connect(sockfd, serv_addr, addrlen)); |
|
|
|
if (fcntl(sockfd, F_SETFL, O_NONBLOCK) < 0) |
|
return (-1); |
|
|
|
rc = connect(sockfd, serv_addr, addrlen); |
|
if (rc == 0) |
|
return (0); |
|
if (errno != EINPROGRESS) |
|
return (-1); |
|
|
|
fdsetsz = howmany(sockfd + 1, NFDBITS) * sizeof(fd_mask); |
|
fdset = (fd_set *)xmalloc(fdsetsz); |
|
|
|
memset(fdset, 0, fdsetsz); |
|
FD_SET(sockfd, fdset); |
|
tv.tv_sec = timeout; |
|
tv.tv_usec = 0; |
|
|
|
for(;;) { |
|
rc = select(sockfd + 1, NULL, fdset, NULL, &tv); |
|
if (rc != -1 || errno != EINTR) |
|
break; |
|
} |
|
|
|
switch(rc) { |
|
case 0: |
|
/* Timed out */ |
|
errno = ETIMEDOUT; |
|
break; |
|
case -1: |
|
/* Select error */ |
|
debug("select: %s", strerror(errno)); |
|
break; |
|
case 1: |
|
/* Completed or failed */ |
|
optval = 0; |
|
optlen = sizeof(optval); |
|
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, |
|
&optlen) == -1) |
|
debug("getsockopt: %s", strerror(errno)); |
|
break; |
|
if (optval != 0) { |
|
errno = optval; |
|
break; |
|
} |
|
result = 0; |
|
break; |
|
default: |
|
/* Should not occur */ |
|
fatal("Bogus return (%d) from select()", rc); |
|
} |
|
|
|
xfree(fdset); |
|
return (result); |
|
} |
|
|
/* |
/* |
* Opens a TCP/IP connection to the remote server on the given host. |
* Opens a TCP/IP connection to the remote server on the given host. |
* The address of the remote host will be returned in hostaddr. |
* The address of the remote host will be returned in hostaddr. |
|
|
*/ |
*/ |
int full_failure = 1; |
int full_failure = 1; |
|
|
debug("ssh_connect: needpriv %d", needpriv); |
debug2("ssh_connect: needpriv %d", needpriv); |
|
|
/* Get default port if port has not been set. */ |
/* Get default port if port has not been set. */ |
if (port == 0) { |
if (port == 0) { |
|
|
host, ntop, strport); |
host, ntop, strport); |
|
|
/* Create a socket for connecting. */ |
/* Create a socket for connecting. */ |
sock = ssh_create_socket(needpriv, ai->ai_family); |
sock = ssh_create_socket(needpriv, ai); |
if (sock < 0) |
if (sock < 0) |
/* Any error is already output */ |
/* Any error is already output */ |
continue; |
continue; |
|
|
if (connect(sock, ai->ai_addr, ai->ai_addrlen) >= 0) { |
if (timeout_connect(sock, ai->ai_addr, ai->ai_addrlen, |
|
options.connection_timeout) >= 0) { |
/* Successful connection. */ |
/* Successful connection. */ |
memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); |
memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); |
break; |
break; |
|
|
|
|
/* Return failure if we didn't get a successful connection. */ |
/* Return failure if we didn't get a successful connection. */ |
if (attempt >= connection_attempts) { |
if (attempt >= connection_attempts) { |
log("ssh: connect to host %s port %s: %s", |
logit("ssh: connect to host %s port %s: %s", |
host, strport, strerror(errno)); |
host, strport, strerror(errno)); |
return full_failure ? ECONNABORTED : ECONNREFUSED; |
return full_failure ? ECONNABORTED : ECONNREFUSED; |
} |
} |
|
|
enable_compat13(); |
enable_compat13(); |
minor1 = 3; |
minor1 = 3; |
if (options.forward_agent) { |
if (options.forward_agent) { |
log("Agent forwarding disabled for protocol 1.3"); |
logit("Agent forwarding disabled for protocol 1.3"); |
options.forward_agent = 0; |
options.forward_agent = 0; |
} |
} |
} |
} |
|
|
compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, |
compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, |
compat20 ? PROTOCOL_MINOR_2 : minor1, |
compat20 ? PROTOCOL_MINOR_2 : minor1, |
SSH_VERSION); |
SSH_VERSION); |
if (atomicio(write, connection_out, buf, strlen(buf)) != strlen(buf)) |
if (atomicio(vwrite, connection_out, buf, strlen(buf)) != strlen(buf)) |
fatal("write: %.100s", strerror(errno)); |
fatal("write: %.100s", strerror(errno)); |
client_version_string = xstrdup(buf); |
client_version_string = xstrdup(buf); |
chop(client_version_string); |
chop(client_version_string); |
|
|
int local = 0, host_ip_differ = 0; |
int local = 0, host_ip_differ = 0; |
char ntop[NI_MAXHOST]; |
char ntop[NI_MAXHOST]; |
char msg[1024]; |
char msg[1024]; |
int len, host_line, ip_line, has_keys; |
int len, host_line, ip_line; |
const char *host_file = NULL, *ip_file = NULL; |
const char *host_file = NULL, *ip_file = NULL; |
|
|
/* |
/* |
|
|
debug("Found key in %s:%d", host_file, host_line); |
debug("Found key in %s:%d", host_file, host_line); |
if (options.check_host_ip && ip_status == HOST_NEW) { |
if (options.check_host_ip && ip_status == HOST_NEW) { |
if (readonly) |
if (readonly) |
log("%s host key for IP address " |
logit("%s host key for IP address " |
"'%.128s' not in list of known hosts.", |
"'%.128s' not in list of known hosts.", |
type, ip); |
type, ip); |
else if (!add_host_to_hostfile(user_hostfile, ip, |
else if (!add_host_to_hostfile(user_hostfile, ip, |
host_key)) |
host_key)) |
log("Failed to add the %s host key for IP " |
logit("Failed to add the %s host key for IP " |
"address '%.128s' to the list of known " |
"address '%.128s' to the list of known " |
"hosts (%.30s).", type, ip, user_hostfile); |
"hosts (%.30s).", type, ip, user_hostfile); |
else |
else |
log("Warning: Permanently added the %s host " |
logit("Warning: Permanently added the %s host " |
"key for IP address '%.128s' to the list " |
"key for IP address '%.128s' to the list " |
"of known hosts.", type, ip); |
"of known hosts.", type, ip); |
} |
} |
|
|
"have requested strict checking.", type, host); |
"have requested strict checking.", type, host); |
goto fail; |
goto fail; |
} else if (options.strict_host_key_checking == 2) { |
} else if (options.strict_host_key_checking == 2) { |
has_keys = show_other_keys(host, host_key); |
char msg1[1024], msg2[1024]; |
|
|
|
if (show_other_keys(host, host_key)) |
|
snprintf(msg1, sizeof(msg1), |
|
"\nbut keys of different type are already" |
|
" known for this host."); |
|
else |
|
snprintf(msg1, sizeof(msg1), "."); |
/* The default */ |
/* The default */ |
fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); |
fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); |
|
msg2[0] = '\0'; |
|
#ifdef DNS |
|
if (options.verify_host_key_dns) { |
|
if (verified_host_key_dns) |
|
snprintf(msg2, sizeof(msg2), |
|
"Matching host key fingerprint" |
|
" found in DNS.\n"); |
|
else |
|
snprintf(msg2, sizeof(msg2), |
|
"No matching host key fingerprint" |
|
" found in DNS.\n"); |
|
} |
|
#endif |
snprintf(msg, sizeof(msg), |
snprintf(msg, sizeof(msg), |
"The authenticity of host '%.200s (%s)' can't be " |
"The authenticity of host '%.200s (%s)' can't be " |
"established%s\n" |
"established%s\n" |
"%s key fingerprint is %s.\n" |
"%s key fingerprint is %s.\n%s" |
"Are you sure you want to continue connecting " |
"Are you sure you want to continue connecting " |
"(yes/no)? ", |
"(yes/no)? ", |
host, ip, |
host, ip, msg1, type, fp, msg2); |
has_keys ? ",\nbut keys of different type are already " |
|
"known for this host." : ".", |
|
type, fp); |
|
xfree(fp); |
xfree(fp); |
if (!confirm(msg)) |
if (!confirm(msg)) |
goto fail; |
goto fail; |
|
|
* local known_hosts file. |
* local known_hosts file. |
*/ |
*/ |
if (!add_host_to_hostfile(user_hostfile, hostp, host_key)) |
if (!add_host_to_hostfile(user_hostfile, hostp, host_key)) |
log("Failed to add the host to the list of known " |
logit("Failed to add the host to the list of known " |
"hosts (%.500s).", user_hostfile); |
"hosts (%.500s).", user_hostfile); |
else |
else |
log("Warning: Permanently added '%.200s' (%s) to the " |
logit("Warning: Permanently added '%.200s' (%s) to the " |
"list of known hosts.", hostp, type); |
"list of known hosts.", hostp, type); |
break; |
break; |
case HOST_CHANGED: |
case HOST_CHANGED: |
|
|
|
|
/* |
/* |
* If strict host key checking has not been requested, allow |
* If strict host key checking has not been requested, allow |
* the connection but without password authentication or |
* the connection but without MITM-able authentication or |
* agent forwarding. |
* agent forwarding. |
*/ |
*/ |
if (options.password_authentication) { |
if (options.password_authentication) { |
|
|
"man-in-the-middle attacks."); |
"man-in-the-middle attacks."); |
options.password_authentication = 0; |
options.password_authentication = 0; |
} |
} |
|
if (options.kbd_interactive_authentication) { |
|
error("Keyboard-interactive authentication is disabled" |
|
" to avoid man-in-the-middle attacks."); |
|
options.kbd_interactive_authentication = 0; |
|
options.challenge_response_authentication = 0; |
|
} |
|
if (options.challenge_response_authentication) { |
|
error("Challenge/response authentication is disabled" |
|
" to avoid man-in-the-middle attacks."); |
|
options.challenge_response_authentication = 0; |
|
} |
if (options.forward_agent) { |
if (options.forward_agent) { |
error("Agent forwarding is disabled to avoid " |
error("Agent forwarding is disabled to avoid " |
"man-in-the-middle attacks."); |
"man-in-the-middle attacks."); |
|
|
host_file, host_line); |
host_file, host_line); |
} |
} |
if (options.strict_host_key_checking == 1) { |
if (options.strict_host_key_checking == 1) { |
log(msg); |
logit("%s", msg); |
error("Exiting, you have requested strict checking."); |
error("Exiting, you have requested strict checking."); |
goto fail; |
goto fail; |
} else if (options.strict_host_key_checking == 2) { |
} else if (options.strict_host_key_checking == 2) { |
|
|
if (!confirm(msg)) |
if (!confirm(msg)) |
goto fail; |
goto fail; |
} else { |
} else { |
log(msg); |
logit("%s", msg); |
} |
} |
} |
} |
|
|
|
|
return -1; |
return -1; |
} |
} |
|
|
|
/* returns 0 if key verifies or -1 if key does NOT verify */ |
int |
int |
verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key) |
verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key) |
{ |
{ |
struct stat st; |
struct stat st; |
|
|
|
#ifdef DNS |
|
if (options.verify_host_key_dns) { |
|
switch(verify_host_key_dns(host, hostaddr, host_key)) { |
|
case DNS_VERIFY_OK: |
|
#ifdef DNSSEC |
|
return 0; |
|
#else |
|
verified_host_key_dns = 1; |
|
break; |
|
#endif |
|
case DNS_VERIFY_FAILED: |
|
return -1; |
|
case DNS_VERIFY_ERROR: |
|
break; |
|
default: |
|
debug3("bad return value from verify_host_key_dns"); |
|
break; |
|
} |
|
} |
|
#endif /* DNS */ |
|
|
/* return ok if the key can be found in an old keyfile */ |
/* return ok if the key can be found in an old keyfile */ |
if (stat(options.system_hostfile2, &st) == 0 || |
if (stat(options.system_hostfile2, &st) == 0 || |
stat(options.user_hostfile2, &st) == 0) { |
stat(options.user_hostfile2, &st) == 0) { |
|
|
if ((ret = lookup_key_in_hostfile_by_type(file, host, |
if ((ret = lookup_key_in_hostfile_by_type(file, host, |
keytype, found, &line))) { |
keytype, found, &line))) { |
fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX); |
fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX); |
log("WARNING: %s key found for host %s\n" |
logit("WARNING: %s key found for host %s\n" |
"in %s:%d\n" |
"in %s:%d\n" |
"%s key fingerprint %s.", |
"%s key fingerprint %s.", |
key_type(found), host, file, line, |
key_type(found), host, file, line, |