#include "includes.h"
RCSID("$OpenBSD: sshconnect.c,v 1.115 2001/10/08 19:05:05 markus Exp $");
#include <openssl/bn.h>
#include "ssh.h"
#include "xmalloc.h"
#include "rsa.h"
#include "buffer.h"
#include "packet.h"
#include "uidswap.h"
#include "compat.h"
#include "key.h"
#include "sshconnect.h"
#include "hostfile.h"
#include "log.h"
#include "readconf.h"
#include "atomicio.h"
#include "misc.h"
char *client_version_string = NULL;
char *server_version_string = NULL;
extern Options options;
extern char *__progname;
#ifndef INET6_ADDRSTRLEN
#define INET6_ADDRSTRLEN 46
#endif
static const char *
sockaddr_ntop(struct sockaddr *sa)
{
void *addr;
static char addrbuf[INET6_ADDRSTRLEN];
switch (sa->sa_family) {
case AF_INET:
addr = &((struct sockaddr_in *)sa)->sin_addr;
break;
case AF_INET6:
addr = &((struct sockaddr_in6 *)sa)->sin6_addr;
break;
default:
abort();
}
inet_ntop(sa->sa_family, addr, addrbuf, sizeof(addrbuf));
return addrbuf;
}
static int
ssh_proxy_connect(const char *host, u_short port, struct passwd *pw,
const char *proxy_command)
{
Buffer command;
const char *cp;
char *command_string;
int pin[2], pout[2];
pid_t pid;
char strport[NI_MAXSERV];
snprintf(strport, sizeof strport, "%hu", port);
buffer_init(&command);
for (cp = proxy_command; *cp; cp++) {
if (cp[0] == '%' && cp[1] == '%') {
buffer_append(&command, "%", 1);
cp++;
continue;
}
if (cp[0] == '%' && cp[1] == 'h') {
buffer_append(&command, host, strlen(host));
cp++;
continue;
}
if (cp[0] == '%' && cp[1] == 'p') {
buffer_append(&command, strport, strlen(strport));
cp++;
continue;
}
buffer_append(&command, cp, 1);
}
buffer_append(&command, "\0", 1);
command_string = buffer_ptr(&command);
if (pipe(pin) < 0 || pipe(pout) < 0)
fatal("Could not create pipes to communicate with the proxy: %.100s",
strerror(errno));
debug("Executing proxy command: %.500s", command_string);
if ((pid = fork()) == 0) {
char *argv[10];
permanently_set_uid(pw);
close(pin[1]);
if (pin[0] != 0) {
if (dup2(pin[0], 0) < 0)
perror("dup2 stdin");
close(pin[0]);
}
close(pout[0]);
if (dup2(pout[1], 1) < 0)
perror("dup2 stdout");
close(pout[1]);
argv[0] = _PATH_BSHELL;
argv[1] = "-c";
argv[2] = command_string;
argv[3] = NULL;
execv(argv[0], argv);
perror(argv[0]);
exit(1);
}
if (pid < 0)
fatal("fork failed: %.100s", strerror(errno));
close(pin[0]);
close(pout[1]);
buffer_free(&command);
packet_set_connection(pout[0], pin[1]);
return 0;
}
static int
ssh_create_socket(struct passwd *pw, int privileged, int family)
{
int sock, gaierr;
struct addrinfo hints, *res;
if (privileged) {
int p = IPPORT_RESERVED - 1;
sock = rresvport_af(&p, family);
if (sock < 0)
error("rresvport: af=%d %.100s", family, strerror(errno));
else
debug("Allocated local port %d.", p);
return sock;
}
temporarily_use_uid(pw);
sock = socket(family, SOCK_STREAM, 0);
if (sock < 0)
error("socket: %.100s", strerror(errno));
restore_uid();
if (options.bind_address == NULL)
return sock;
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
gaierr = getaddrinfo(options.bind_address, "0", &hints, &res);
if (gaierr) {
error("getaddrinfo: %s: %s", options.bind_address,
gai_strerror(gaierr));
close(sock);
return -1;
}
if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) {
error("bind: %s: %s", options.bind_address, strerror(errno));
close(sock);
freeaddrinfo(res);
return -1;
}
freeaddrinfo(res);
return sock;
}
int
ssh_connect(const char *host, struct sockaddr_storage * hostaddr,
u_short port, int family, int connection_attempts,
int anonymous, struct passwd *pw, const char *proxy_command)
{
int gaierr;
int on = 1;
int sock = -1, attempt;
char ntop[NI_MAXHOST], strport[NI_MAXSERV];
struct addrinfo hints, *ai, *aitop;
struct linger linger;
struct servent *sp;
int full_failure = 1;
debug("ssh_connect: getuid %u geteuid %u anon %d",
(u_int) getuid(), (u_int) geteuid(), anonymous);
if (port == 0) {
sp = getservbyname(SSH_SERVICE_NAME, "tcp");
if (sp)
port = ntohs(sp->s_port);
else
port = SSH_DEFAULT_PORT;
}
if (proxy_command != NULL)
return ssh_proxy_connect(host, port, pw, proxy_command);
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM;
snprintf(strport, sizeof strport, "%d", port);
if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0)
fatal("%s: %.100s: %s", __progname, host,
gai_strerror(gaierr));
for (attempt = 0; ;) {
if (attempt > 0)
debug("Trying again...");
for (ai = aitop; ai; ai = ai->ai_next) {
if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
continue;
if (getnameinfo(ai->ai_addr, ai->ai_addrlen,
ntop, sizeof(ntop), strport, sizeof(strport),
NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
error("ssh_connect: getnameinfo failed");
continue;
}
debug("Connecting to %.200s [%.100s] port %s.",
host, ntop, strport);
sock = ssh_create_socket(pw,
#ifdef HAVE_CYGWIN
!anonymous,
#else
!anonymous && geteuid() == 0,
#endif
ai->ai_family);
if (sock < 0)
continue;
temporarily_use_uid(pw);
if (connect(sock, ai->ai_addr, ai->ai_addrlen) >= 0) {
memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen);
restore_uid();
break;
} else {
if (errno == ECONNREFUSED)
full_failure = 0;
log("ssh: connect to address %s port %s: %s",
sockaddr_ntop(ai->ai_addr), strport,
strerror(errno));
restore_uid();
shutdown(sock, SHUT_RDWR);
close(sock);
}
}
if (ai)
break;
attempt++;
if (attempt >= connection_attempts)
break;
sleep(1);
}
freeaddrinfo(aitop);
if (attempt >= connection_attempts)
return full_failure ? ECONNABORTED : ECONNREFUSED;
debug("Connection established.");
linger.l_onoff = 1;
linger.l_linger = 5;
setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *)&linger, sizeof(linger));
if (options.keepalives &&
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on,
sizeof(on)) < 0)
error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno));
packet_set_connection(sock, sock);
return 0;
}
static void
ssh_exchange_identification(void)
{
char buf[256], remote_version[256];
int remote_major, remote_minor, i, mismatch;
int connection_in = packet_get_connection_in();
int connection_out = packet_get_connection_out();
int minor1 = PROTOCOL_MINOR_1;
for (;;) {
for (i = 0; i < sizeof(buf) - 1; i++) {
int len = atomicio(read, connection_in, &buf[i], 1);
if (len < 0)
fatal("ssh_exchange_identification: read: %.100s", strerror(errno));
if (len != 1)
fatal("ssh_exchange_identification: Connection closed by remote host");
if (buf[i] == '\r') {
buf[i] = '\n';
buf[i + 1] = 0;
continue;
}
if (buf[i] == '\n') {
buf[i + 1] = 0;
break;
}
}
buf[sizeof(buf) - 1] = 0;
if (strncmp(buf, "SSH-", 4) == 0)
break;
debug("ssh_exchange_identification: %s", buf);
}
server_version_string = xstrdup(buf);
if (sscanf(server_version_string, "SSH-%d.%d-%[^\n]\n",
&remote_major, &remote_minor, remote_version) != 3)
fatal("Bad remote protocol version identification: '%.100s'", buf);
debug("Remote protocol version %d.%d, remote software version %.100s",
remote_major, remote_minor, remote_version);
compat_datafellows(remote_version);
mismatch = 0;
switch(remote_major) {
case 1:
if (remote_minor == 99 &&
(options.protocol & SSH_PROTO_2) &&
!(options.protocol & SSH_PROTO_1_PREFERRED)) {
enable_compat20();
break;
}
if (!(options.protocol & SSH_PROTO_1)) {
mismatch = 1;
break;
}
if (remote_minor < 3) {
fatal("Remote machine has too old SSH software version.");
} else if (remote_minor == 3 || remote_minor == 4) {
enable_compat13();
minor1 = 3;
if (options.forward_agent) {
log("Agent forwarding disabled for protocol 1.3");
options.forward_agent = 0;
}
}
break;
case 2:
if (options.protocol & SSH_PROTO_2) {
enable_compat20();
break;
}
default:
mismatch = 1;
break;
}
if (mismatch)
fatal("Protocol major versions differ: %d vs. %d",
(options.protocol & SSH_PROTO_2) ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1,
remote_major);
snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s\n",
compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1,
compat20 ? PROTOCOL_MINOR_2 : minor1,
SSH_VERSION);
if (atomicio(write, connection_out, buf, strlen(buf)) != strlen(buf))
fatal("write: %.100s", strerror(errno));
client_version_string = xstrdup(buf);
chop(client_version_string);
chop(server_version_string);
debug("Local version string %.100s", client_version_string);
}
static int
confirm(const char *prompt)
{
char buf[1024];
FILE *f;
int retval = -1;
if (options.batch_mode)
return 0;
if (isatty(STDIN_FILENO))
f = stdin;
else
f = fopen(_PATH_TTY, "rw");
if (f == NULL)
return 0;
fflush(stdout);
fprintf(stderr, "%s", prompt);
while (1) {
if (fgets(buf, sizeof(buf), f) == NULL) {
fprintf(stderr, "\n");
strlcpy(buf, "no", sizeof buf);
}
if (strchr(buf, '\n'))
*strchr(buf, '\n') = 0;
if (strcmp(buf, "yes") == 0)
retval = 1;
else if (strcmp(buf, "no") == 0)
retval = 0;
else
fprintf(stderr, "Please type 'yes' or 'no': ");
if (retval != -1) {
if (f != stdin)
fclose(f);
return retval;
}
}
}
static int
check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key,
int readonly, const char *user_hostfile, const char *system_hostfile)
{
Key *file_key;
char *type = key_type(host_key);
char *ip = NULL;
char hostline[1000], *hostp, *fp;
HostStatus host_status;
HostStatus ip_status;
int local = 0, host_ip_differ = 0;
int salen;
char ntop[NI_MAXHOST];
int host_line, ip_line;
const char *host_file = NULL, *ip_file = NULL;
switch (hostaddr->sa_family) {
case AF_INET:
local = (ntohl(((struct sockaddr_in *)hostaddr)->
sin_addr.s_addr) >> 24) == IN_LOOPBACKNET;
salen = sizeof(struct sockaddr_in);
break;
case AF_INET6:
local = IN6_IS_ADDR_LOOPBACK(
&(((struct sockaddr_in6 *)hostaddr)->sin6_addr));
salen = sizeof(struct sockaddr_in6);
break;
default:
local = 0;
salen = sizeof(struct sockaddr_storage);
break;
}
if (options.no_host_authentication_for_localhost == 1 && local &&
options.host_key_alias == NULL) {
debug("Forcing accepting of host key for "
"loopback/localhost.");
return 0;
}
if (options.proxy_command == NULL) {
if (getnameinfo(hostaddr, salen, ntop, sizeof(ntop),
NULL, 0, NI_NUMERICHOST) != 0)
fatal("check_host_key: getnameinfo failed");
ip = xstrdup(ntop);
} else {
ip = xstrdup("<no hostip for proxy command>");
}
if (options.check_host_ip &&
(local || strcmp(host, ip) == 0 || options.proxy_command != NULL))
options.check_host_ip = 0;
if (options.host_key_alias != NULL) {
host = options.host_key_alias;
debug("using hostkeyalias: %s", host);
}
file_key = key_new(host_key->type);
host_file = user_hostfile;
host_status = check_host_in_hostfile(host_file, host, host_key,
file_key, &host_line);
if (host_status == HOST_NEW) {
host_file = system_hostfile;
host_status = check_host_in_hostfile(host_file, host, host_key,
file_key, &host_line);
}
if (options.check_host_ip) {
Key *ip_key = key_new(host_key->type);
ip_file = user_hostfile;
ip_status = check_host_in_hostfile(ip_file, ip, host_key,
ip_key, &ip_line);
if (ip_status == HOST_NEW) {
ip_file = system_hostfile;
ip_status = check_host_in_hostfile(ip_file, ip,
host_key, ip_key, &ip_line);
}
if (host_status == HOST_CHANGED &&
(ip_status != HOST_CHANGED || !key_equal(ip_key, file_key)))
host_ip_differ = 1;
key_free(ip_key);
} else
ip_status = host_status;
key_free(file_key);
switch (host_status) {
case HOST_OK:
debug("Host '%.200s' is known and matches the %s host key.",
host, type);
debug("Found key in %s:%d", host_file, host_line);
if (options.check_host_ip && ip_status == HOST_NEW) {
if (readonly)
log("%s host key for IP address "
"'%.128s' not in list of known hosts.",
type, ip);
else if (!add_host_to_hostfile(user_hostfile, ip,
host_key))
log("Failed to add the %s host key for IP "
"address '%.128s' to the list of known "
"hosts (%.30s).", type, ip, user_hostfile);
else
log("Warning: Permanently added the %s host "
"key for IP address '%.128s' to the list "
"of known hosts.", type, ip);
}
break;
case HOST_NEW:
if (readonly)
goto fail;
if (options.strict_host_key_checking == 1) {
error("No %s host key is known for %.200s and you "
"have requested strict checking.", type, host);
goto fail;
} else if (options.strict_host_key_checking == 2) {
char prompt[1024];
fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX);
snprintf(prompt, sizeof(prompt),
"The authenticity of host '%.200s (%s)' can't be "
"established.\n"
"%s key fingerprint is %s.\n"
"Are you sure you want to continue connecting "
"(yes/no)? ", host, ip, type, fp);
xfree(fp);
if (!confirm(prompt)) {
goto fail;
}
}
if (options.check_host_ip && ip_status == HOST_NEW) {
snprintf(hostline, sizeof(hostline), "%s,%s", host, ip);
hostp = hostline;
} else
hostp = host;
if (!add_host_to_hostfile(user_hostfile, hostp, host_key))
log("Failed to add the host to the list of known "
"hosts (%.500s).", user_hostfile);
else
log("Warning: Permanently added '%.200s' (%s) to the "
"list of known hosts.", hostp, type);
break;
case HOST_CHANGED:
if (options.check_host_ip && host_ip_differ) {
char *msg;
if (ip_status == HOST_NEW)
msg = "is unknown";
else if (ip_status == HOST_OK)
msg = "is unchanged";
else
msg = "has a different value";
error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
error("@ WARNING: POSSIBLE DNS SPOOFING DETECTED! @");
error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
error("The %s host key for %s has changed,", type, host);
error("and the key for the according IP address %s", ip);
error("%s. This could either mean that", msg);
error("DNS SPOOFING is happening or the IP address for the host");
error("and its host key have changed at the same time.");
if (ip_status != HOST_NEW)
error("Offending key for IP in %s:%d", ip_file, ip_line);
}
fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX);
error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
error("@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @");
error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
error("IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!");
error("Someone could be eavesdropping on you right now (man-in-the-middle attack)!");
error("It is also possible that the %s host key has just been changed.", type);
error("The fingerprint for the %s key sent by the remote host is\n%s.",
type, fp);
error("Please contact your system administrator.");
error("Add correct host key in %.100s to get rid of this message.",
user_hostfile);
error("Offending key in %s:%d", host_file, host_line);
xfree(fp);
if (options.strict_host_key_checking) {
error("%s host key for %.200s has changed and you have "
"requested strict checking.", type, host);
goto fail;
}
if (options.password_authentication) {
error("Password authentication is disabled to avoid "
"man-in-the-middle attacks.");
options.password_authentication = 0;
}
if (options.forward_agent) {
error("Agent forwarding is disabled to avoid "
"man-in-the-middle attacks.");
options.forward_agent = 0;
}
if (options.forward_x11) {
error("X11 forwarding is disabled to avoid "
"man-in-the-middle attacks.");
options.forward_x11 = 0;
}
if (options.num_local_forwards > 0 ||
options.num_remote_forwards > 0) {
error("Port forwarding is disabled to avoid "
"man-in-the-middle attacks.");
options.num_local_forwards =
options.num_remote_forwards = 0;
}
break;
}
if (options.check_host_ip && host_status != HOST_CHANGED &&
ip_status == HOST_CHANGED) {
log("Warning: the %s host key for '%.200s' "
"differs from the key for the IP address '%.128s'",
type, host, ip);
if (host_status == HOST_OK)
log("Matching host key in %s:%d", host_file, host_line);
log("Offending key for IP in %s:%d", ip_file, ip_line);
if (options.strict_host_key_checking == 1) {
error("Exiting, you have requested strict checking.");
goto fail;
} else if (options.strict_host_key_checking == 2) {
if (!confirm("Are you sure you want "
"to continue connecting (yes/no)? ")) {
goto fail;
}
}
}
xfree(ip);
return 0;
fail:
xfree(ip);
return -1;
}
int
verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key)
{
struct stat st;
if (stat(options.system_hostfile2, &st) == 0 ||
stat(options.user_hostfile2, &st) == 0) {
if (check_host_key(host, hostaddr, host_key, 1,
options.user_hostfile2, options.system_hostfile2) == 0)
return 0;
}
return check_host_key(host, hostaddr, host_key, 0,
options.user_hostfile, options.system_hostfile);
}
void
ssh_login(Key **keys, int nkeys, const char *orighost,
struct sockaddr *hostaddr, struct passwd *pw)
{
char *host, *cp;
char *server_user, *local_user;
local_user = xstrdup(pw->pw_name);
server_user = options.user ? options.user : local_user;
host = xstrdup(orighost);
for (cp = host; *cp; cp++)
if (isupper(*cp))
*cp = tolower(*cp);
ssh_exchange_identification();
packet_set_nonblocking();
if (compat20) {
ssh_kex2(host, hostaddr);
ssh_userauth2(local_user, server_user, host, keys, nkeys);
} else {
ssh_kex(host, hostaddr);
ssh_userauth1(local_user, server_user, host, keys, nkeys);
}
}
void
ssh_put_password(char *password)
{
int size;
char *padded;
if (datafellows & SSH_BUG_PASSWORDPAD) {
packet_put_cstring(password);
return;
}
size = roundup(strlen(password) + 1, 32);
padded = xmalloc(size);
memset(padded, 0, size);
strlcpy(padded, password, size);
packet_put_string(padded, size);
memset(padded, 0, size);
xfree(padded);
}