+/* $OpenBSD: sshconnect.c,v 1.218 2010/01/13 00:19:04 dtucker Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
*/
#include "includes.h"
-RCSID("$OpenBSD: sshconnect.c,v 1.173 2006/02/08 14:16:59 stevesk Exp $");
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#ifdef HAVE_PATHS_H
#include <paths.h>
+#endif
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
-#include "ssh.h"
#include "xmalloc.h"
+#include "key.h"
+#include "hostfile.h"
+#include "ssh.h"
#include "rsa.h"
#include "buffer.h"
#include "packet.h"
#include "atomicio.h"
#include "misc.h"
#include "dns.h"
+#include "roaming.h"
+#include "version.h"
char *client_version_string = NULL;
char *server_version_string = NULL;
extern uid_t original_effective_uid;
extern pid_t proxy_command_pid;
-#ifndef INET6_ADDRSTRLEN /* for non IPv6 machines */
-#define INET6_ADDRSTRLEN 46
-#endif
-
static int show_other_keys(const char *, Key *);
static void warn_changed_key(Key *);
char *command_string, *tmp;
int pin[2], pout[2];
pid_t pid;
- char strport[NI_MAXSERV];
- size_t len;
+ char *shell, strport[NI_MAXSERV];
+
+ if ((shell = getenv("SHELL")) == NULL)
+ shell = _PATH_BSHELL;
/* Convert the port number into a string. */
snprintf(strport, sizeof strport, "%hu", port);
* Use "exec" to avoid "sh -c" processes on some platforms
* (e.g. Solaris)
*/
- len = strlen(proxy_command) + 6;
- tmp = xmalloc(len);
- strlcpy(tmp, "exec ", len);
- strlcat(tmp, proxy_command, len);
+ xasprintf(&tmp, "exec %s", proxy_command);
command_string = percent_expand(tmp, "h", host,
"p", strport, (char *)NULL);
xfree(tmp);
char *argv[10];
/* Child. Permanently give up superuser privileges. */
- seteuid(original_real_uid);
- setuid(original_real_uid);
+ permanently_drop_suid(original_real_uid);
/* Redirect stdin and stdout. */
close(pin[1]);
/* Stderr is left as it is so that error messages get
printed on the user's terminal. */
- argv[0] = _PATH_BSHELL;
+ argv[0] = shell;
argv[1] = "-c";
argv[2] = command_string;
argv[3] = NULL;
/* Set the connection file descriptors. */
packet_set_connection(pout[0], pin[1]);
+ packet_set_timeout(options.server_alive_interval,
+ options.server_alive_count_max);
/* Indicate OK return */
return 0;
return sock;
}
sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
- if (sock < 0)
+ if (sock < 0) {
error("socket: %.100s", strerror(errno));
+ return -1;
+ }
+ fcntl(sock, F_SETFD, FD_CLOEXEC);
/* Bind the socket to an alternative local IP address */
if (options.bind_address == NULL)
hints.ai_socktype = ai->ai_socktype;
hints.ai_protocol = ai->ai_protocol;
hints.ai_flags = AI_PASSIVE;
- gaierr = getaddrinfo(options.bind_address, "0", &hints, &res);
+ gaierr = getaddrinfo(options.bind_address, NULL, &hints, &res);
if (gaierr) {
error("getaddrinfo: %s: %s", options.bind_address,
- gai_strerror(gaierr));
+ ssh_gai_strerror(gaierr));
close(sock);
return -1;
}
static int
timeout_connect(int sockfd, const struct sockaddr *serv_addr,
- socklen_t addrlen, int timeout)
+ socklen_t addrlen, int *timeoutp)
{
fd_set *fdset;
- struct timeval tv;
+ struct timeval tv, t_start;
socklen_t optlen;
- int fdsetsz, optval, rc, result = -1;
+ int optval, rc, result = -1;
- if (timeout <= 0)
- return (connect(sockfd, serv_addr, addrlen));
+ gettimeofday(&t_start, NULL);
+
+ if (*timeoutp <= 0) {
+ result = connect(sockfd, serv_addr, addrlen);
+ goto done;
+ }
set_nonblock(sockfd);
rc = connect(sockfd, serv_addr, addrlen);
if (rc == 0) {
unset_nonblock(sockfd);
- return (0);
+ result = 0;
+ goto done;
+ }
+ if (errno != EINPROGRESS) {
+ result = -1;
+ goto done;
}
- if (errno != EINPROGRESS)
- return (-1);
-
- fdsetsz = howmany(sockfd + 1, NFDBITS) * sizeof(fd_mask);
- fdset = (fd_set *)xmalloc(fdsetsz);
- memset(fdset, 0, fdsetsz);
+ fdset = (fd_set *)xcalloc(howmany(sockfd + 1, NFDBITS),
+ sizeof(fd_mask));
FD_SET(sockfd, fdset);
- tv.tv_sec = timeout;
- tv.tv_usec = 0;
+ ms_to_timeval(&tv, *timeoutp);
for (;;) {
rc = select(sockfd + 1, NULL, fdset, NULL, &tv);
}
xfree(fdset);
+
+ done:
+ if (result == 0 && *timeoutp > 0) {
+ ms_subtract_diff(&t_start, timeoutp);
+ if (*timeoutp <= 0) {
+ errno = ETIMEDOUT;
+ result = -1;
+ }
+ }
+
return (result);
}
*/
int
ssh_connect(const char *host, struct sockaddr_storage * hostaddr,
- u_short port, int family, int connection_attempts,
- int needpriv, const char *proxy_command)
+ u_short port, int family, int connection_attempts, int *timeout_ms,
+ int want_keepalive, int needpriv, const char *proxy_command)
{
int gaierr;
int on = 1;
hints.ai_socktype = SOCK_STREAM;
snprintf(strport, sizeof strport, "%u", port);
if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0)
- fatal("%s: %.100s: %s", __progname, host,
- gai_strerror(gaierr));
+ fatal("%s: Could not resolve hostname %.100s: %s", __progname,
+ host, ssh_gai_strerror(gaierr));
- /*
- * Try to connect several times. On some machines, the first time
- * will sometimes fail. In general socket code appears to behave
- * quite magically on many machines.
- */
- for (attempt = 0; ;) {
- if (attempt > 0)
+ for (attempt = 0; attempt < connection_attempts; attempt++) {
+ if (attempt > 0) {
+ /* Sleep a moment before retrying. */
+ sleep(1);
debug("Trying again...");
-
- /* Loop through addresses for this host, and try each one in
- sequence until the connection succeeds. */
+ }
+ /*
+ * Loop through addresses for this host, and try each one in
+ * sequence until the connection succeeds.
+ */
for (ai = aitop; ai; ai = ai->ai_next) {
if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
continue;
continue;
if (timeout_connect(sock, ai->ai_addr, ai->ai_addrlen,
- options.connection_timeout) >= 0) {
+ timeout_ms) >= 0) {
/* Successful connection. */
memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen);
break;
} else {
debug("connect to address %s port %s: %s",
ntop, strport, strerror(errno));
- /*
- * Close the failed socket; there appear to
- * be some problems when reusing a socket for
- * which connect() has already returned an
- * error.
- */
close(sock);
+ sock = -1;
}
}
- if (ai)
+ if (sock != -1)
break; /* Successful connection. */
-
- attempt++;
- if (attempt >= connection_attempts)
- break;
- /* Sleep a moment before retrying. */
- sleep(1);
}
freeaddrinfo(aitop);
/* Return failure if we didn't get a successful connection. */
- if (attempt >= connection_attempts) {
+ if (sock == -1) {
error("ssh: connect to host %s port %s: %s",
host, strport, strerror(errno));
return (-1);
debug("Connection established.");
/* Set SO_KEEPALIVE if requested. */
- if (options.tcp_keep_alive &&
+ if (want_keepalive &&
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on,
sizeof(on)) < 0)
error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno));
/* Set the connection. */
packet_set_connection(sock, sock);
+ packet_set_timeout(options.server_alive_interval,
+ options.server_alive_count_max);
return 0;
}
* Waits for the server identification string, and sends our own
* identification string.
*/
-static void
-ssh_exchange_identification(void)
+void
+ssh_exchange_identification(int timeout_ms)
{
char buf[256], remote_version[256]; /* must be same size! */
int remote_major, remote_minor, mismatch;
int connection_in = packet_get_connection_in();
int connection_out = packet_get_connection_out();
int minor1 = PROTOCOL_MINOR_1;
- u_int i;
+ u_int i, n;
+ size_t len;
+ int fdsetsz, remaining, rc;
+ struct timeval t_start, t_remaining;
+ fd_set *fdset;
+
+ fdsetsz = howmany(connection_in + 1, NFDBITS) * sizeof(fd_mask);
+ fdset = xcalloc(1, fdsetsz);
/* Read other side's version identification. */
- for (;;) {
+ remaining = timeout_ms;
+ for (n = 0;;) {
for (i = 0; i < sizeof(buf) - 1; i++) {
- size_t len = atomicio(read, connection_in, &buf[i], 1);
+ if (timeout_ms > 0) {
+ gettimeofday(&t_start, NULL);
+ ms_to_timeval(&t_remaining, remaining);
+ FD_SET(connection_in, fdset);
+ rc = select(connection_in + 1, fdset, NULL,
+ fdset, &t_remaining);
+ ms_subtract_diff(&t_start, &remaining);
+ if (rc == 0 || remaining <= 0)
+ fatal("Connection timed out during "
+ "banner exchange");
+ if (rc == -1) {
+ if (errno == EINTR)
+ continue;
+ fatal("ssh_exchange_identification: "
+ "select: %s", strerror(errno));
+ }
+ }
+
+ len = roaming_atomicio(read, connection_in, &buf[i], 1);
if (len != 1 && errno == EPIPE)
- fatal("ssh_exchange_identification: Connection closed by remote host");
+ fatal("ssh_exchange_identification: "
+ "Connection closed by remote host");
else if (len != 1)
- fatal("ssh_exchange_identification: read: %.100s", strerror(errno));
+ fatal("ssh_exchange_identification: "
+ "read: %.100s", strerror(errno));
if (buf[i] == '\r') {
buf[i] = '\n';
buf[i + 1] = 0;
buf[i + 1] = 0;
break;
}
+ if (++n > 65536)
+ fatal("ssh_exchange_identification: "
+ "No banner received");
}
buf[sizeof(buf) - 1] = 0;
if (strncmp(buf, "SSH-", 4) == 0)
debug("ssh_exchange_identification: %s", buf);
}
server_version_string = xstrdup(buf);
+ xfree(fdset);
/*
* Check that the versions match. In future this might accept
(options.protocol & SSH_PROTO_2) ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1,
remote_major);
/* Send our own protocol version identification. */
- snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s\n",
+ snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s%s",
compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1,
compat20 ? PROTOCOL_MINOR_2 : minor1,
- SSH_VERSION);
- if (atomicio(vwrite, connection_out, buf, strlen(buf)) != strlen(buf))
+ SSH_VERSION, compat20 ? "\r\n" : "\n");
+ if (roaming_atomicio(vwrite, connection_out, buf, strlen(buf))
+ != strlen(buf))
fatal("write: %.100s", strerror(errno));
client_version_string = xstrdup(buf);
chop(client_version_string);
* check whether the supplied host key is valid, return -1 if the key
* is not valid. the user_hostfile will not be updated if 'readonly' is true.
*/
+#define RDRW 0
+#define RDONLY 1
+#define ROQUIET 2
static int
-check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key,
- int readonly, const char *user_hostfile, const char *system_hostfile)
+check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
+ Key *host_key, int readonly, const char *user_hostfile,
+ const char *system_hostfile)
{
Key *file_key;
const char *type = key_type(host_key);
- char *ip = NULL;
- char hostline[1000], *hostp, *fp;
+ char *ip = NULL, *host = NULL;
+ char hostline[1000], *hostp, *fp, *ra;
HostStatus host_status;
HostStatus ip_status;
int r, local = 0, host_ip_differ = 0;
int salen;
char ntop[NI_MAXHOST];
char msg[1024];
- int len, host_line, ip_line;
+ int len, host_line, ip_line, cancelled_forwarding = 0;
const char *host_file = NULL, *ip_file = NULL;
/*
if (getnameinfo(hostaddr, salen, ntop, sizeof(ntop),
NULL, 0, NI_NUMERICHOST) != 0)
fatal("check_host_key: getnameinfo failed");
- ip = xstrdup(ntop);
+ ip = put_host_port(ntop, port);
} else {
ip = xstrdup("<no hostip for proxy command>");
}
+
/*
* Turn off check_host_ip if the connection is to localhost, via proxy
* command or if we don't have a hostname to compare with
*/
- if (options.check_host_ip &&
- (local || strcmp(host, ip) == 0 || options.proxy_command != NULL))
+ if (options.check_host_ip && (local ||
+ strcmp(hostname, ip) == 0 || options.proxy_command != NULL))
options.check_host_ip = 0;
/*
- * Allow the user to record the key under a different name. This is
- * useful for ssh tunneling over forwarded connections or if you run
- * multiple sshd's on different ports on the same machine.
+ * Allow the user to record the key under a different name or
+ * differentiate a non-standard port. This is useful for ssh
+ * tunneling over forwarded connections or if you run multiple
+ * sshd's on different ports on the same machine.
*/
if (options.host_key_alias != NULL) {
- host = options.host_key_alias;
+ host = xstrdup(options.host_key_alias);
debug("using hostkeyalias: %s", host);
+ } else {
+ host = put_host_port(hostname, port);
}
/*
logit("Warning: Permanently added the %s host "
"key for IP address '%.128s' to the list "
"of known hosts.", type, ip);
+ } else if (options.visual_host_key) {
+ fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX);
+ ra = key_fingerprint(host_key, SSH_FP_MD5,
+ SSH_FP_RANDOMART);
+ logit("Host key fingerprint is %s\n%s\n", fp, ra);
+ xfree(ra);
+ xfree(fp);
}
break;
case HOST_NEW:
+ if (options.host_key_alias == NULL && port != 0 &&
+ port != SSH_DEFAULT_PORT) {
+ debug("checking without port identifier");
+ if (check_host_key(hostname, hostaddr, 0, host_key,
+ ROQUIET, user_hostfile, system_hostfile) == 0) {
+ debug("found matching key w/out port");
+ break;
+ }
+ }
if (readonly)
goto fail;
/* The host is new. */
snprintf(msg1, sizeof(msg1), ".");
/* The default */
fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX);
+ ra = key_fingerprint(host_key, SSH_FP_MD5,
+ SSH_FP_RANDOMART);
msg2[0] = '\0';
if (options.verify_host_key_dns) {
if (matching_host_key_dns)
snprintf(msg, sizeof(msg),
"The authenticity of host '%.200s (%s)' can't be "
"established%s\n"
- "%s key fingerprint is %s.\n%s"
+ "%s key fingerprint is %s.%s%s\n%s"
"Are you sure you want to continue connecting "
"(yes/no)? ",
- host, ip, msg1, type, fp, msg2);
+ host, ip, msg1, type, fp,
+ options.visual_host_key ? "\n" : "",
+ options.visual_host_key ? ra : "",
+ msg2);
+ xfree(ra);
xfree(fp);
if (!confirm(msg))
goto fail;
"list of known hosts.", hostp, type);
break;
case HOST_CHANGED:
+ if (readonly == ROQUIET)
+ goto fail;
if (options.check_host_ip && host_ip_differ) {
char *key_msg;
if (ip_status == HOST_NEW)
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("and the key for the corresponding IP address %s", ip);
error("%s. This could either mean that", key_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 strict host key checking has not been requested, allow
* the connection but without MITM-able authentication or
- * agent forwarding.
+ * forwarding.
*/
if (options.password_authentication) {
error("Password authentication is disabled to avoid "
"man-in-the-middle attacks.");
options.password_authentication = 0;
+ cancelled_forwarding = 1;
}
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;
+ cancelled_forwarding = 1;
}
if (options.challenge_response_authentication) {
error("Challenge/response authentication is disabled"
" to avoid man-in-the-middle attacks.");
options.challenge_response_authentication = 0;
+ cancelled_forwarding = 1;
}
if (options.forward_agent) {
error("Agent forwarding is disabled to avoid "
"man-in-the-middle attacks.");
options.forward_agent = 0;
+ cancelled_forwarding = 1;
}
if (options.forward_x11) {
error("X11 forwarding is disabled to avoid "
"man-in-the-middle attacks.");
options.forward_x11 = 0;
+ cancelled_forwarding = 1;
}
if (options.num_local_forwards > 0 ||
options.num_remote_forwards > 0) {
"man-in-the-middle attacks.");
options.num_local_forwards =
options.num_remote_forwards = 0;
+ cancelled_forwarding = 1;
+ }
+ if (options.tun_open != SSH_TUNMODE_NO) {
+ error("Tunnel forwarding is disabled to avoid "
+ "man-in-the-middle attacks.");
+ options.tun_open = SSH_TUNMODE_NO;
+ cancelled_forwarding = 1;
}
+ if (options.exit_on_forward_failure && cancelled_forwarding)
+ fatal("Error: forwarding disabled due to host key "
+ "check failure");
+
/*
* XXX Should permit the user to change to use the new id.
* This could be done by converting the host key to an
* identifying sentence, tell that the host identifies itself
- * by that sentence, and ask the user if he/she whishes to
+ * by that sentence, and ask the user if he/she wishes to
* accept the authentication.
*/
break;
}
xfree(ip);
+ xfree(host);
return 0;
fail:
xfree(ip);
+ xfree(host);
return -1;
}
/* return ok if the key can be found in an old keyfile */
if (stat(options.system_hostfile2, &st) == 0 ||
stat(options.user_hostfile2, &st) == 0) {
- if (check_host_key(host, hostaddr, host_key, /*readonly*/ 1,
- options.user_hostfile2, options.system_hostfile2) == 0)
+ if (check_host_key(host, hostaddr, options.port, host_key,
+ RDONLY, options.user_hostfile2,
+ options.system_hostfile2) == 0)
return 0;
}
- return check_host_key(host, hostaddr, host_key, /*readonly*/ 0,
- options.user_hostfile, options.system_hostfile);
+ return check_host_key(host, hostaddr, options.port, host_key,
+ RDRW, options.user_hostfile, options.system_hostfile);
}
/*
*/
void
ssh_login(Sensitive *sensitive, const char *orighost,
- struct sockaddr *hostaddr, struct passwd *pw)
+ struct sockaddr *hostaddr, struct passwd *pw, int timeout_ms)
{
char *host, *cp;
char *server_user, *local_user;
host = xstrdup(orighost);
for (cp = host; *cp; cp++)
if (isupper(*cp))
- *cp = tolower(*cp);
+ *cp = (char)tolower(*cp);
/* Exchange protocol version identification strings with the server. */
- ssh_exchange_identification();
+ ssh_exchange_identification(timeout_ms);
/* Put the connection into non-blocking mode. */
packet_set_nonblocking();
ssh_kex(host, hostaddr);
ssh_userauth1(local_user, server_user, host, sensitive);
}
+ xfree(local_user);
}
void
return;
}
size = roundup(strlen(password) + 1, 32);
- padded = xmalloc(size);
- memset(padded, 0, size);
+ padded = xcalloc(1, size);
strlcpy(padded, password, size);
packet_put_string(padded, size);
memset(padded, 0, size);
show_key_from_file(const char *file, const char *host, int keytype)
{
Key *found;
- char *fp;
+ char *fp, *ra;
int line, ret;
found = key_new(keytype);
if ((ret = lookup_key_in_hostfile_by_type(file, host,
keytype, found, &line))) {
fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX);
+ ra = key_fingerprint(found, SSH_FP_MD5, SSH_FP_RANDOMART);
logit("WARNING: %s key found for host %s\n"
"in %s:%d\n"
- "%s key fingerprint %s.",
+ "%s key fingerprint %s.\n%s\n",
key_type(found), host, file, line,
- key_type(found), fp);
+ key_type(found), fp, ra);
+ xfree(ra);
xfree(fp);
}
key_free(found);