sshconnect.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved
* Code to connect to a remote host, and to perform the client side of the
* login (authentication) dialog.
*
* As far as I am concerned, the code I have written for this software
* can be used freely for any purpose. Any derived versions of this
* software must be clearly marked as such, and if the derived work is
* incompatible with the protocol description in the RFC file, it must be
* called by a name other than "ssh" or "Secure Shell".
*/
#include "includes.h"
RCSID("$OpenBSD: sshconnect.c,v 1.135 2002/09/19 01:58:18 djm Exp $");
#pragma ident "%Z%%M% %I% %E% SMI"
#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"
#include "readpass.h"
#include <langinfo.h>
char *client_version_string = NULL;
char *server_version_string = NULL;
/* import */
extern char *__progname;
extern uid_t original_real_uid;
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 *);
/*
* Connect to the given ssh server using a proxy command.
*/
static int
{
const char *cp;
char *command_string;
char strport[NI_MAXSERV];
/* Convert the port number into a string. */
/*
* Build the final command string in the buffer by making the
* appropriate substitutions to the given proxy command.
*
* Use "exec" to avoid "sh -c" processes on some platforms
* (e.g. Solaris)
*/
;
cp++;
continue;
}
cp++;
continue;
}
cp++;
continue;
}
}
/* Get the final command string. */
/* Create pipes for communicating with the proxy. */
fatal("Could not create pipes to communicate with the proxy: %.100s",
/* Fork and execute the proxy command. */
char *argv[10];
/* Child. Permanently give up superuser privileges. */
/* Redirect stdin and stdout. */
if (pin[0] != 0) {
perror("dup2 stdin");
}
perror("dup2 stdout");
/* Cannot be 1 because pin allocated two descriptors. */
/* Stderr is left as it is so that error messages get
printed on the user's terminal. */
argv[0] = _PATH_BSHELL;
/* Execute the proxy command. Note that we gave up any
extra privileges above. */
exit(1);
}
/* Parent. */
if (pid < 0)
else
/* Close child side of the descriptors. */
/* Free the command name. */
/* Set the connection file descriptors. */
/* Indicate OK return */
return 0;
}
/*
* Creates a (possibly privileged) socket for use as the ssh connection.
*/
static int
{
/*
* If we are running as root and want to connect to a privileged
* port, bind our own socket to a privileged port.
*/
if (privileged) {
int p = IPPORT_RESERVED - 1;
if (sock < 0)
else
debug("Allocated local port %d.", p);
return sock;
}
if (sock < 0)
/* Bind the socket to an alternative local IP address */
return sock;
if (gaierr) {
return -1;
}
return -1;
}
return sock;
}
/*
* The address of the remote host will be returned in hostaddr.
* If port is 0, the default port will be used. If needpriv is true,
* a privileged port will be allocated to make the connection.
* This requires super-user privileges if needpriv is true.
* Connection_attempts specifies the maximum number of tries (one per
* second). If proxy_command is non-NULL, it specifies the command (with %h
* and %p substituted for host and port, respectively) to use to contact
* the daemon.
* Return values:
* 0 for OK
* ECONNREFUSED if we got a "Connection Refused" by the peer on any address
* ECONNABORTED if we failed without a "Connection refused"
* Suitable error messages for the connection failure will already have been
* printed.
*/
int
int needpriv, const char *proxy_command)
{
int gaierr;
int on = 1;
/*
* Did we get only other errors than "Connection refused" (which
* should block fallback to rsh and similar), or did we get at least
* one "Connection refused"?
*/
int full_failure = 1;
/* Get default port if port has not been set. */
if (port == 0) {
if (sp)
else
}
/* If a proxy command is given, connect using it. */
if (proxy_command != NULL)
/* No proxy command. */
/*
* 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)
debug("Trying again...");
/* Loop through addresses for this host, and try each one in
sequence until the connection succeeds. */
continue;
NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
error("ssh_connect: getnameinfo failed");
continue;
}
debug("Connecting to %.200s [%.100s] port %s.",
/* Create a socket for connecting. */
if (sock < 0)
/* Any error is already output */
continue;
/* Successful connection. */
break;
} else {
if (errno == ECONNREFUSED)
full_failure = 0;
debug("connect to address %s port %s: %s",
/*
* Close the failed socket; there appear to
* be some problems when reusing a socket for
* which connect() has already returned an
* error.
*/
}
}
if (ai)
break; /* Successful connection. */
attempt++;
if (attempt >= connection_attempts)
break;
/* Sleep a moment before retrying. */
sleep(1);
}
/* Return failure if we didn't get a successful connection. */
if (attempt >= connection_attempts) {
log("ssh: connect to host %s port %s: %s",
}
debug("Connection established.");
/* Set keepalives if requested. */
if (options.keepalives &&
sizeof(on)) < 0)
/* Set the connection. */
return 0;
}
/*
* Waits for the server identification string, and sends our own
* identification string.
*/
static void
{
int connection_in = packet_get_connection_in();
int connection_out = packet_get_connection_out();
int minor1 = PROTOCOL_MINOR_1;
/* Read other side\'s version identification. */
for (;;) {
for (i = 0; i < sizeof(buf) - 1; i++) {
if (len < 0)
if (len != 1)
fatal("ssh_exchange_identification: Connection closed by remote host");
if (buf[i] == '\r') {
buf[i] = '\n';
buf[i + 1] = 0;
continue; /**XXX wait for \n */
}
if (buf[i] == '\n') {
buf[i + 1] = 0;
break;
}
}
break;
}
/*
* Check that the versions match. In future this might accept
* several versions and set appropriate flags to handle them.
*/
debug("Remote protocol version %d.%d, remote software version %.100s",
mismatch = 0;
switch (remote_major) {
case 1:
if (remote_minor == 99 &&
break;
}
mismatch = 1;
break;
}
if (remote_minor < 3) {
fatal("Remote machine has too old SSH software version.");
/* We speak 1.3, too. */
minor1 = 3;
if (options.forward_agent) {
log("Agent forwarding disabled for protocol 1.3");
options.forward_agent = 0;
}
}
break;
case 2:
break;
}
/* FALLTHROUGH */
default:
mismatch = 1;
break;
}
if (mismatch)
fatal("Protocol major versions differ: %d vs. %d",
/* Send our own protocol version identification. */
}
/* defaults to 'no' */
static int
{
const char *msg;
int n, ret = -1;
if (options.batch_mode)
return 0;
if (p == NULL ||
(p[0] == '\0') || (p[0] == '\n') ||
ret = 0;
ret = 1;
if (p)
xfree(p);
if (ret != -1)
return ret;
}
}
/*
* 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.
*/
static int
{
int local = 0, host_ip_differ = 0;
int salen;
char ntop[NI_MAXHOST];
char msg[1024];
/*
* problem is that if the home directory is NFS-mounted to multiple
* machines, localhost will refer to a different machine in each of
* them, and the user will get bogus HOST_CHANGED warnings. This
* essentially disables host authentication for localhost; however,
* this is probably not a real problem.
*/
/** hostaddr == 0! */
case AF_INET:
/* LINTED */
salen = sizeof(struct sockaddr_in);
break;
case AF_INET6:
/* LINTED */
salen = sizeof(struct sockaddr_in6);
break;
default:
local = 0;
salen = sizeof(struct sockaddr_storage);
break;
}
debug("Forcing accepting of host key for "
return 0;
}
/*
* We don't have the remote ip-address for connections
* using a proxy command
*/
NULL, 0, NI_NUMERICHOST) != 0)
fatal("check_host_key: getnameinfo failed");
} else {
}
/*
* 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 &&
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.
*/
}
/*
* Store the host key from the known host file in here so that we can
* compare it with the key for the IP address.
*/
/*
* Check if the host key is present in the user\'s list of known
* hosts or in the systemwide list.
*/
if (host_status == HOST_NEW) {
}
/*
* Also perform check for the ip address, skip the check if we are
* localhost or the hostname was an ip address to begin with
*/
if (options.check_host_ip) {
}
if (host_status == HOST_CHANGED &&
host_ip_differ = 1;
} else
switch (host_status) {
case HOST_OK:
/* The host is known and the key matches. */
if (validated)
debug("Host '%.200s' is known and matches the "
else
debug("Host '%.200s' is known and matches the %s host "
if (readonly)
log("%s host key for IP address "
"'%.128s' not in list of known hosts.",
host_key))
log("Failed to add the %s host key for IP "
"address '%.128s' to the list of known "
else
log("Warning: Permanently added the %s host "
"key for IP address '%.128s' to the list "
}
break;
case HOST_NEW:
if (readonly)
goto fail;
/* The host is new. */
/*
* User has requested strict host key checking. We
* will not add the host key automatically. The only
* alternative left is to abort.
*/
error("No %s host key is known for %.200s and you "
goto fail;
} else if (!validated &&
/* The default */
gettext("The authenticity of host '%.200s (%s)' "
"can't be established%s\n%s key fingerprint "
"is %s.\n"
"Are you sure you want to continue connecting "
"(%s/%s)? "),
"are already known for this host.") : ".",
goto fail;
}
} else
/*
* If not in strict mode, add the key automatically to the
* local known_hosts file.
*/
log("Failed to add the host to the list of known "
"hosts (%.500s).", user_hostfile);
else
log("Warning: Permanently added '%.200s' (%s) to the "
break;
case HOST_CHANGED:
if (validated) {
log("Warning: The host key for host %s has changed; "
"please update your known hosts file(s) "
log("Warning: The host key for host %s has "
"changed; please update your known "
}
break;
}
char *msg;
msg = "is unknown";
msg = "is unchanged";
else
msg = "has a different value";
error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
"@ WARNING: POSSIBLE DNS SPOOFING DETECTED! @\n"
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
"The %s host key for %s has changed,\n"
"and the key for the according IP address %s\n"
"%s. This could either mean that\n"
"DNS SPOOFING is happening or the IP address for the host\n"
"and its host key have changed at the same time.\n",
}
/* The host key has changed. */
error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
"@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @\n"
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
"IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n"
"Someone could be eavesdropping on you right now (man-in-the-middle attack)!\n"
"It is also possible that the %s host key has just been changed.\n"
"The fingerprint for the %s key sent by the remote host is\n%s.\n"
"Please contact your system administrator.\n"
"Add correct host key in %.100s to get rid of this message.\n"
"Offending key in %s:%d\n",
/*
* If strict host key checking is in use, the user will have
* to edit the key manually and we can only abort.
*/
if (options.strict_host_key_checking) {
error("%s host key for %.200s has changed and you have "
goto fail;
}
/*
* If strict host key checking has not been requested, allow
* the connection but without password authentication or
* agent forwarding.
*/
if (options.password_authentication) {
error("Password authentication is disabled to avoid "
"man-in-the-middle attacks.");
}
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.");
}
/*
* 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
* accept the authentication.
*/
break;
case HOST_FOUND:
fatal("internal error");
break;
}
ip_status == HOST_CHANGED) {
gettext("Warning: the %s host key for '%.200s' "
"differs from the key for the IP address '%.128s'"
"\nOffending key for IP in %s:%d"),
if (host_status == HOST_OK) {
"\nMatching host key in %s:%d",
}
error("Exiting, you have requested strict checking.");
goto fail;
} else if (!validated &&
gettext("\nAre you sure you want to continue "
"connecting (%s/%s)"),
goto fail;
} else {
}
}
return 0;
fail:
return -1;
}
int
{
/* return ok if the key can be found in an old keyfile */
return 0;
}
}
int
{
/* return ok if the key can be found in an old keyfile */
return 0;
}
}
/*
* Starts a dialog with the server, and authenticates the current user on the
* server. This does not need any extra privileges. The basic connection
* to the server must already have been established before this is called.
* If login fails, this function prints an error and never returns.
* This function does not require super-user privileges.
*/
void
{
char *server_user, *local_user;
/* Convert the user-supplied hostname into all lowercase. */
/* Exchange protocol version identification strings with the server. */
/* Put the connection into non-blocking mode. */
/* key exchange */
/* authenticate user */
if (compat20) {
} else {
}
}
void
ssh_put_password(char *password)
{
int size;
char *padded;
if (datafellows & SSH_BUG_PASSWORDPAD) {
return;
}
}
static int
{
char *fp;
log("WARNING: %s key found for host %s\n"
"in %s:%d\n"
"%s key fingerprint %s.",
}
return (ret);
}
/* print all known host keys for a given host, but skip keys of given type */
static int
{
int i, found = 0;
for (i = 0; type[i] != -1; i++) {
continue;
found = 1;
continue;
}
found = 1;
continue;
}
found = 1;
continue;
}
found = 1;
continue;
}
}
return (found);
}