sshconnect.c revision 92559
1112758Ssam/* 2112758Ssam * Author: Tatu Ylonen <ylo@cs.hut.fi> 3112758Ssam * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4112758Ssam * All rights reserved 5112758Ssam * Code to connect to a remote host, and to perform the client side of the 6112758Ssam * login (authentication) dialog. 7112758Ssam * 8112758Ssam * As far as I am concerned, the code I have written for this software 9112758Ssam * can be used freely for any purpose. Any derived versions of this 10112758Ssam * software must be clearly marked as such, and if the derived work is 11112758Ssam * incompatible with the protocol description in the RFC file, it must be 12112758Ssam * called by a name other than "ssh" or "Secure Shell". 13112758Ssam */ 14112758Ssam 15112758Ssam#include "includes.h" 16112758SsamRCSID("$OpenBSD: sshconnect.c,v 1.119 2002/01/21 15:13:51 markus Exp $"); 17112758SsamRCSID("$FreeBSD: head/crypto/openssh/sshconnect.c 92559 2002-03-18 10:09:43Z des $"); 18112758Ssam 19112758Ssam#include <openssl/bn.h> 20112758Ssam 21112758Ssam#include "ssh.h" 22112758Ssam#include "xmalloc.h" 23112758Ssam#include "rsa.h" 24112758Ssam#include "buffer.h" 25112758Ssam#include "packet.h" 26112758Ssam#include "uidswap.h" 27112758Ssam#include "compat.h" 28105197Ssam#include "key.h" 29105197Ssam#include "sshconnect.h" 30105197Ssam#include "hostfile.h" 31105197Ssam#include "log.h" 32105197Ssam#include "readconf.h" 33105197Ssam#include "atomicio.h" 34105197Ssam#include "misc.h" 35105197Ssam#include "readpass.h" 36105197Ssam 37105197Ssamchar *client_version_string = NULL; 38105197Ssamchar *server_version_string = NULL; 39105197Ssam 40105197Ssamextern Options options; 41105197Ssamextern char *__progname; 42105197Ssam 43105197Ssamstatic const char * 44105197Ssamsockaddr_ntop(struct sockaddr *sa) 45105197Ssam{ 46105197Ssam void *addr; 47105197Ssam static char addrbuf[INET6_ADDRSTRLEN]; 48105197Ssam 49105197Ssam switch (sa->sa_family) { 50105197Ssam case AF_INET: 51105197Ssam addr = &((struct sockaddr_in *)sa)->sin_addr; 52105197Ssam break; 53105197Ssam case AF_INET6: 54105197Ssam addr = &((struct sockaddr_in6 *)sa)->sin6_addr; 55105197Ssam break; 56105197Ssam default: 57105197Ssam /* This case should be protected against elsewhere */ 58105197Ssam abort(); /* XXX abort is bad -- do something else */ 59105197Ssam } 60105197Ssam inet_ntop(sa->sa_family, addr, addrbuf, sizeof(addrbuf)); 61105197Ssam return addrbuf; 62105197Ssam} 63105197Ssam 64105197Ssam/* 65105197Ssam * Connect to the given ssh server using a proxy command. 66105197Ssam */ 67105197Ssamstatic int 68105197Ssamssh_proxy_connect(const char *host, u_short port, struct passwd *pw, 69105197Ssam const char *proxy_command) 70105197Ssam{ 71105197Ssam Buffer command; 72105197Ssam const char *cp; 73105197Ssam char *command_string; 74105197Ssam int pin[2], pout[2]; 75105197Ssam pid_t pid; 76105197Ssam char strport[NI_MAXSERV]; 77105197Ssam 78105197Ssam /* Convert the port number into a string. */ 79105197Ssam snprintf(strport, sizeof strport, "%hu", port); 80105197Ssam 81105197Ssam /* Build the final command string in the buffer by making the 82105197Ssam appropriate substitutions to the given proxy command. */ 83105197Ssam buffer_init(&command); 84105197Ssam for (cp = proxy_command; *cp; cp++) { 85105197Ssam if (cp[0] == '%' && cp[1] == '%') { 86105197Ssam buffer_append(&command, "%", 1); 87105197Ssam cp++; 88105197Ssam continue; 89105197Ssam } 90105197Ssam if (cp[0] == '%' && cp[1] == 'h') { 91105197Ssam buffer_append(&command, host, strlen(host)); 92105197Ssam cp++; 93105197Ssam continue; 94105197Ssam } 95105197Ssam if (cp[0] == '%' && cp[1] == 'p') { 96105197Ssam buffer_append(&command, strport, strlen(strport)); 97105197Ssam cp++; 98105197Ssam continue; 99105197Ssam } 100105197Ssam buffer_append(&command, cp, 1); 101105197Ssam } 102105197Ssam buffer_append(&command, "\0", 1); 103105197Ssam 104105197Ssam /* Get the final command string. */ 105105197Ssam command_string = buffer_ptr(&command); 106105197Ssam 107105197Ssam /* Create pipes for communicating with the proxy. */ 108105197Ssam if (pipe(pin) < 0 || pipe(pout) < 0) 109105197Ssam fatal("Could not create pipes to communicate with the proxy: %.100s", 110105197Ssam strerror(errno)); 111105197Ssam 112105197Ssam debug("Executing proxy command: %.500s", command_string); 113105197Ssam 114105197Ssam /* Fork and execute the proxy command. */ 115105197Ssam if ((pid = fork()) == 0) { 116105197Ssam char *argv[10]; 117105197Ssam 118105197Ssam /* Child. Permanently give up superuser privileges. */ 119105197Ssam permanently_set_uid(pw); 120105197Ssam 121105197Ssam /* Redirect stdin and stdout. */ 122105197Ssam close(pin[1]); 123105197Ssam if (pin[0] != 0) { 124105197Ssam if (dup2(pin[0], 0) < 0) 125105197Ssam perror("dup2 stdin"); 126105197Ssam close(pin[0]); 127105197Ssam } 128105197Ssam close(pout[0]); 129105197Ssam if (dup2(pout[1], 1) < 0) 130105197Ssam perror("dup2 stdout"); 131105197Ssam /* Cannot be 1 because pin allocated two descriptors. */ 132105197Ssam close(pout[1]); 133105197Ssam 134105197Ssam /* Stderr is left as it is so that error messages get 135105197Ssam printed on the user's terminal. */ 136105197Ssam argv[0] = _PATH_BSHELL; 137105197Ssam argv[1] = "-c"; 138105197Ssam argv[2] = command_string; 139105197Ssam argv[3] = NULL; 140105197Ssam 141105197Ssam /* Execute the proxy command. Note that we gave up any 142105197Ssam extra privileges above. */ 143105197Ssam execv(argv[0], argv); 144105197Ssam perror(argv[0]); 145105197Ssam exit(1); 146105197Ssam } 147105197Ssam /* Parent. */ 148105197Ssam if (pid < 0) 149105197Ssam fatal("fork failed: %.100s", strerror(errno)); 150105197Ssam 151105197Ssam /* Close child side of the descriptors. */ 152105197Ssam close(pin[0]); 153105197Ssam close(pout[1]); 154105197Ssam 155105197Ssam /* Free the command name. */ 156105197Ssam buffer_free(&command); 157105197Ssam 158105197Ssam /* Set the connection file descriptors. */ 159105197Ssam packet_set_connection(pout[0], pin[1]); 160105197Ssam 161105197Ssam /* Indicate OK return */ 162105197Ssam return 0; 163105197Ssam} 164105197Ssam 165105197Ssam/* 166105197Ssam * Creates a (possibly privileged) socket for use as the ssh connection. 167105197Ssam */ 168105197Ssamstatic int 169105197Ssamssh_create_socket(struct passwd *pw, int privileged, int family) 170105197Ssam{ 171105197Ssam int sock, gaierr; 172105197Ssam struct addrinfo hints, *res; 173105197Ssam 174105197Ssam /* 175105197Ssam * If we are running as root and want to connect to a privileged 176105197Ssam * port, bind our own socket to a privileged port. 177105197Ssam */ 178105197Ssam if (privileged) { 179105197Ssam int p = IPPORT_RESERVED - 1; 180105197Ssam sock = rresvport_af(&p, family); 181105197Ssam if (sock < 0) 182105197Ssam error("rresvport: af=%d %.100s", family, strerror(errno)); 183105197Ssam else 184105197Ssam debug("Allocated local port %d.", p); 185105197Ssam return sock; 186105197Ssam } 187105197Ssam /* 188105197Ssam * Just create an ordinary socket on arbitrary port. We use 189105197Ssam * the user's uid to create the socket. 190105197Ssam */ 191105197Ssam temporarily_use_uid(pw); 192105197Ssam sock = socket(family, SOCK_STREAM, 0); 193105197Ssam if (sock < 0) 194105197Ssam error("socket: %.100s", strerror(errno)); 195105197Ssam restore_uid(); 196105197Ssam 197105197Ssam /* Bind the socket to an alternative local IP address */ 198105197Ssam if (options.bind_address == NULL) 199105197Ssam return sock; 200105197Ssam 201105197Ssam memset(&hints, 0, sizeof(hints)); 202105197Ssam hints.ai_family = family; 203105197Ssam hints.ai_socktype = SOCK_STREAM; 204105197Ssam hints.ai_flags = AI_PASSIVE; 205105197Ssam gaierr = getaddrinfo(options.bind_address, "0", &hints, &res); 206105197Ssam if (gaierr) { 207105197Ssam error("getaddrinfo: %s: %s", options.bind_address, 208105197Ssam gai_strerror(gaierr)); 209105197Ssam close(sock); 210105197Ssam return -1; 211105197Ssam } 212105197Ssam if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { 213105197Ssam error("bind: %s: %s", options.bind_address, strerror(errno)); 214105197Ssam close(sock); 215105197Ssam freeaddrinfo(res); 216105197Ssam return -1; 217105197Ssam } 218105197Ssam freeaddrinfo(res); 219105197Ssam return sock; 220105197Ssam} 221105197Ssam 222105197Ssam/* 223105197Ssam * Opens a TCP/IP connection to the remote server on the given host. 224105197Ssam * The address of the remote host will be returned in hostaddr. 225105197Ssam * If port is 0, the default port will be used. If anonymous is zero, 226105197Ssam * a privileged port will be allocated to make the connection. 227105197Ssam * This requires super-user privileges if anonymous is false. 228105197Ssam * Connection_attempts specifies the maximum number of tries (one per 229105197Ssam * second). If proxy_command is non-NULL, it specifies the command (with %h 230105197Ssam * and %p substituted for host and port, respectively) to use to contact 231105197Ssam * the daemon. 232105197Ssam * Return values: 233105197Ssam * 0 for OK 234105197Ssam * ECONNREFUSED if we got a "Connection Refused" by the peer on any address 235105197Ssam * ECONNABORTED if we failed without a "Connection refused" 236105197Ssam * Suitable error messages for the connection failure will already have been 237105197Ssam * printed. 238105197Ssam */ 239105197Ssamint 240105197Ssamssh_connect(const char *host, struct sockaddr_storage * hostaddr, 241105197Ssam u_short port, int family, int connection_attempts, 242105197Ssam int anonymous, struct passwd *pw, const char *proxy_command) 243105197Ssam{ 244105197Ssam int gaierr; 245105197Ssam int on = 1; 246105197Ssam int sock = -1, attempt; 247105197Ssam char ntop[NI_MAXHOST], strport[NI_MAXSERV]; 248105197Ssam struct addrinfo hints, *ai, *aitop; 249105197Ssam struct linger linger; 250105197Ssam struct servent *sp; 251105197Ssam /* 252105197Ssam * Did we get only other errors than "Connection refused" (which 253105197Ssam * should block fallback to rsh and similar), or did we get at least 254105197Ssam * one "Connection refused"? 255105197Ssam */ 256105197Ssam int full_failure = 1; 257105197Ssam 258105197Ssam debug("ssh_connect: getuid %u geteuid %u anon %d", 259105197Ssam (u_int) getuid(), (u_int) geteuid(), anonymous); 260105197Ssam 261105197Ssam /* Get default port if port has not been set. */ 262105197Ssam if (port == 0) { 263105197Ssam sp = getservbyname(SSH_SERVICE_NAME, "tcp"); 264105197Ssam if (sp) 265105197Ssam port = ntohs(sp->s_port); 266105197Ssam else 267105197Ssam port = SSH_DEFAULT_PORT; 268105197Ssam } 269105197Ssam /* If a proxy command is given, connect using it. */ 270105197Ssam if (proxy_command != NULL) 271105197Ssam return ssh_proxy_connect(host, port, pw, proxy_command); 272105197Ssam 273105197Ssam /* No proxy command. */ 274105197Ssam 275105197Ssam memset(&hints, 0, sizeof(hints)); 276105197Ssam hints.ai_family = family; 277105197Ssam hints.ai_socktype = SOCK_STREAM; 278105197Ssam snprintf(strport, sizeof strport, "%d", port); 279105197Ssam if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) 280105197Ssam fatal("%s: %.100s: %s", __progname, host, 281105197Ssam gai_strerror(gaierr)); 282105197Ssam 283105197Ssam /* 284105197Ssam * Try to connect several times. On some machines, the first time 285105197Ssam * will sometimes fail. In general socket code appears to behave 286105197Ssam * quite magically on many machines. 287105197Ssam */ 288105197Ssam for (attempt = 0; ;) { 289105197Ssam if (attempt > 0) 290105197Ssam debug("Trying again..."); 291105197Ssam 292105197Ssam /* Loop through addresses for this host, and try each one in 293105197Ssam sequence until the connection succeeds. */ 294105197Ssam for (ai = aitop; ai; ai = ai->ai_next) { 295105197Ssam if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) 296105197Ssam continue; 297105197Ssam if (getnameinfo(ai->ai_addr, ai->ai_addrlen, 298105197Ssam ntop, sizeof(ntop), strport, sizeof(strport), 299105197Ssam NI_NUMERICHOST|NI_NUMERICSERV) != 0) { 300105197Ssam error("ssh_connect: getnameinfo failed"); 301105197Ssam continue; 302105197Ssam } 303105197Ssam debug("Connecting to %.200s [%.100s] port %s.", 304105197Ssam host, ntop, strport); 305105197Ssam 306105197Ssam /* Create a socket for connecting. */ 307105197Ssam sock = ssh_create_socket(pw, 308105197Ssam !anonymous && geteuid() == 0, 309105197Ssam ai->ai_family); 310105197Ssam if (sock < 0) 311105197Ssam /* Any error is already output */ 312105197Ssam continue; 313105197Ssam 314105197Ssam /* Connect to the host. We use the user's uid in the 315105197Ssam * hope that it will help with tcp_wrappers showing 316105197Ssam * the remote uid as root. 317105197Ssam */ 318105197Ssam temporarily_use_uid(pw); 319105197Ssam if (connect(sock, ai->ai_addr, ai->ai_addrlen) >= 0) { 320105197Ssam /* Successful connection. */ 321105197Ssam memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); 322105197Ssam restore_uid(); 323105197Ssam break; 324105197Ssam } else { 325105197Ssam if (errno == ECONNREFUSED) 326105197Ssam full_failure = 0; 327105197Ssam log("ssh: connect to address %s port %s: %s", 328105197Ssam sockaddr_ntop(ai->ai_addr), strport, 329105197Ssam strerror(errno)); 330105197Ssam restore_uid(); 331105197Ssam /* 332105197Ssam * Close the failed socket; there appear to 333105197Ssam * be some problems when reusing a socket for 334105197Ssam * which connect() has already returned an 335105197Ssam * error. 336105197Ssam */ 337105197Ssam close(sock); 338105197Ssam } 339105197Ssam } 340105197Ssam if (ai) 341105197Ssam break; /* Successful connection. */ 342105197Ssam 343105197Ssam attempt++; 344105197Ssam if (attempt >= connection_attempts) 345105197Ssam break; 346105197Ssam /* Sleep a moment before retrying. */ 347105197Ssam sleep(1); 348105197Ssam } 349105197Ssam 350105197Ssam freeaddrinfo(aitop); 351105197Ssam 352105197Ssam /* Return failure if we didn't get a successful connection. */ 353105197Ssam if (attempt >= connection_attempts) 354105197Ssam return full_failure ? ECONNABORTED : ECONNREFUSED; 355105197Ssam 356105197Ssam debug("Connection established."); 357105197Ssam 358105197Ssam /* 359105197Ssam * Set socket options. We would like the socket to disappear as soon 360105197Ssam * as it has been closed for whatever reason. 361105197Ssam */ 362105197Ssam /* setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)); */ 363105197Ssam linger.l_onoff = 1; 364105197Ssam linger.l_linger = 5; 365105197Ssam setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *)&linger, sizeof(linger)); 366105197Ssam 367105197Ssam /* Set keepalives if requested. */ 368105197Ssam if (options.keepalives && 369105197Ssam setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, 370105197Ssam sizeof(on)) < 0) 371105197Ssam error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); 372105197Ssam 373105197Ssam /* Set the connection. */ 374105197Ssam packet_set_connection(sock, sock); 375105197Ssam 376105197Ssam return 0; 377105197Ssam} 378105197Ssam 379105197Ssam/* 380105197Ssam * Waits for the server identification string, and sends our own 381105197Ssam * identification string. 382105197Ssam */ 383105197Ssamstatic void 384105197Ssamssh_exchange_identification(void) 385105197Ssam{ 386105197Ssam char buf[256], remote_version[256]; /* must be same size! */ 387105197Ssam int remote_major, remote_minor, i, mismatch; 388105197Ssam int connection_in = packet_get_connection_in(); 389105197Ssam int connection_out = packet_get_connection_out(); 390105197Ssam int minor1 = PROTOCOL_MINOR_1; 391105197Ssam 392105197Ssam /* Read other side\'s version identification. */ 393105197Ssam for (;;) { 394105197Ssam for (i = 0; i < sizeof(buf) - 1; i++) { 395105197Ssam int len = atomicio(read, connection_in, &buf[i], 1); 396105197Ssam if (len < 0) 397105197Ssam fatal("ssh_exchange_identification: read: %.100s", strerror(errno)); 398105197Ssam if (len != 1) 399105197Ssam fatal("ssh_exchange_identification: Connection closed by remote host"); 400105197Ssam if (buf[i] == '\r') { 401105197Ssam buf[i] = '\n'; 402105197Ssam buf[i + 1] = 0; 403105197Ssam continue; /**XXX wait for \n */ 404105197Ssam } 405105197Ssam if (buf[i] == '\n') { 406105197Ssam buf[i + 1] = 0; 407105197Ssam break; 408105197Ssam } 409105197Ssam } 410105197Ssam buf[sizeof(buf) - 1] = 0; 411105197Ssam if (strncmp(buf, "SSH-", 4) == 0) 412105197Ssam break; 413105197Ssam debug("ssh_exchange_identification: %s", buf); 414105197Ssam } 415105197Ssam server_version_string = xstrdup(buf); 416105197Ssam 417105197Ssam /* 418105197Ssam * Check that the versions match. In future this might accept 419105197Ssam * several versions and set appropriate flags to handle them. 420105197Ssam */ 421105197Ssam if (sscanf(server_version_string, "SSH-%d.%d-%[^\n]\n", 422105197Ssam &remote_major, &remote_minor, remote_version) != 3) 423105197Ssam fatal("Bad remote protocol version identification: '%.100s'", buf); 424105197Ssam debug("Remote protocol version %d.%d, remote software version %.100s", 425105197Ssam remote_major, remote_minor, remote_version); 426105197Ssam 427105197Ssam compat_datafellows(remote_version); 428105197Ssam mismatch = 0; 429105197Ssam 430105197Ssam switch (remote_major) { 431105197Ssam case 1: 432105197Ssam if (remote_minor == 99 && 433105197Ssam (options.protocol & SSH_PROTO_2) && 434105197Ssam !(options.protocol & SSH_PROTO_1_PREFERRED)) { 435105197Ssam enable_compat20(); 436105197Ssam break; 437105197Ssam } 438105197Ssam if (!(options.protocol & SSH_PROTO_1)) { 439105197Ssam mismatch = 1; 440105197Ssam break; 441105197Ssam } 442105197Ssam if (remote_minor < 3) { 443105197Ssam fatal("Remote machine has too old SSH software version."); 444105197Ssam } else if (remote_minor == 3 || remote_minor == 4) { 445105197Ssam /* We speak 1.3, too. */ 446105197Ssam enable_compat13(); 447105197Ssam minor1 = 3; 448105197Ssam if (options.forward_agent) { 449105197Ssam log("Agent forwarding disabled for protocol 1.3"); 450105197Ssam options.forward_agent = 0; 451105197Ssam } 452105197Ssam } 453105197Ssam break; 454105197Ssam case 2: 455105197Ssam if (options.protocol & SSH_PROTO_2) { 456105197Ssam enable_compat20(); 457105197Ssam break; 458105197Ssam } 459105197Ssam /* FALLTHROUGH */ 460105197Ssam default: 461105197Ssam mismatch = 1; 462105197Ssam break; 463105197Ssam } 464105197Ssam if (mismatch) 465105197Ssam fatal("Protocol major versions differ: %d vs. %d", 466105197Ssam (options.protocol & SSH_PROTO_2) ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, 467105197Ssam remote_major); 468105197Ssam /* Send our own protocol version identification. */ 469105197Ssam snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s\n", 470105197Ssam compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, 471105197Ssam compat20 ? PROTOCOL_MINOR_2 : minor1, 472105197Ssam SSH_VERSION); 473105197Ssam if (atomicio(write, connection_out, buf, strlen(buf)) != strlen(buf)) 474105197Ssam fatal("write: %.100s", strerror(errno)); 475105197Ssam client_version_string = xstrdup(buf); 476105197Ssam chop(client_version_string); 477105197Ssam chop(server_version_string); 478105197Ssam debug("Local version string %.100s", client_version_string); 479105197Ssam} 480105197Ssam 481105197Ssam/* defaults to 'no' */ 482105197Ssamstatic int 483105197Ssamconfirm(const char *prompt) 484105197Ssam{ 485105197Ssam const char *msg, *again = "Please type 'yes' or 'no': "; 486105197Ssam char *p; 487105197Ssam int ret = -1; 488105197Ssam 489105197Ssam if (options.batch_mode) 490105197Ssam return 0; 491105197Ssam for (msg = prompt;;msg = again) { 492105197Ssam p = read_passphrase(msg, RP_ECHO); 493105197Ssam if (p == NULL || 494105197Ssam (p[0] == '\0') || (p[0] == '\n') || 495105197Ssam strncasecmp(p, "no", 2) == 0) 496105197Ssam ret = 0; 497111119Simp if (strncasecmp(p, "yes", 3) == 0) 498105197Ssam ret = 1; 499105197Ssam if (p) 500105197Ssam xfree(p); 501105197Ssam if (ret != -1) 502108466Ssam return ret; 503105197Ssam } 504105197Ssam} 505105197Ssam 506105197Ssam/* 507105197Ssam * check whether the supplied host key is valid, return -1 if the key 508105197Ssam * is not valid. the user_hostfile will not be updated if 'readonly' is true. 509105197Ssam */ 510105197Ssam 511105197Ssamstatic int 512105197Ssamcheck_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, 513105197Ssam int readonly, const char *user_hostfile, const char *system_hostfile) 514105197Ssam{ 515105197Ssam Key *file_key; 516105197Ssam char *type = key_type(host_key); 517105197Ssam char *ip = NULL; 518105197Ssam char hostline[1000], *hostp, *fp; 519105197Ssam HostStatus host_status; 520105197Ssam HostStatus ip_status; 521105197Ssam int local = 0, host_ip_differ = 0; 522105197Ssam char ntop[NI_MAXHOST]; 523105197Ssam char msg[1024]; 524105197Ssam int len, host_line, ip_line; 525105197Ssam const char *host_file = NULL, *ip_file = NULL; 526105197Ssam 527105197Ssam /* 528105197Ssam * Force accepting of the host key for loopback/localhost. The 529105197Ssam * problem is that if the home directory is NFS-mounted to multiple 530105197Ssam * machines, localhost will refer to a different machine in each of 531105197Ssam * them, and the user will get bogus HOST_CHANGED warnings. This 532105197Ssam * essentially disables host authentication for localhost; however, 533105197Ssam * this is probably not a real problem. 534105197Ssam */ 535105197Ssam /** hostaddr == 0! */ 536105197Ssam switch (hostaddr->sa_family) { 537105197Ssam case AF_INET: 538105197Ssam local = (ntohl(((struct sockaddr_in *)hostaddr)-> 539105197Ssam sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; 540105197Ssam break; 541105197Ssam case AF_INET6: 542105197Ssam local = IN6_IS_ADDR_LOOPBACK( 543105197Ssam &(((struct sockaddr_in6 *)hostaddr)->sin6_addr)); 544105197Ssam break; 545105197Ssam default: 546105197Ssam local = 0; 547105197Ssam break; 548105197Ssam } 549105197Ssam if (options.no_host_authentication_for_localhost == 1 && local && 550105197Ssam options.host_key_alias == NULL) { 551105197Ssam debug("Forcing accepting of host key for " 552105197Ssam "loopback/localhost."); 553105197Ssam return 0; 554105197Ssam } 555105197Ssam 556105197Ssam /* 557105197Ssam * We don't have the remote ip-address for connections 558105197Ssam * using a proxy command 559105197Ssam */ 560105197Ssam if (options.proxy_command == NULL) { 561105197Ssam if (getnameinfo(hostaddr, hostaddr->sa_len, ntop, sizeof(ntop), 562105197Ssam NULL, 0, NI_NUMERICHOST) != 0) 563105197Ssam fatal("check_host_key: getnameinfo failed"); 564105197Ssam ip = xstrdup(ntop); 565105197Ssam } else { 566105197Ssam ip = xstrdup("<no hostip for proxy command>"); 567105197Ssam } 568105197Ssam /* 569105197Ssam * Turn off check_host_ip if the connection is to localhost, via proxy 570105197Ssam * command or if we don't have a hostname to compare with 571105197Ssam */ 572105197Ssam if (options.check_host_ip && 573105197Ssam (local || strcmp(host, ip) == 0 || options.proxy_command != NULL)) 574105197Ssam options.check_host_ip = 0; 575105197Ssam 576105197Ssam /* 577105197Ssam * Allow the user to record the key under a different name. This is 578105197Ssam * useful for ssh tunneling over forwarded connections or if you run 579105197Ssam * multiple sshd's on different ports on the same machine. 580105197Ssam */ 581105197Ssam if (options.host_key_alias != NULL) { 582105197Ssam host = options.host_key_alias; 583105197Ssam debug("using hostkeyalias: %s", host); 584105197Ssam } 585105197Ssam 586105197Ssam /* 587105197Ssam * Store the host key from the known host file in here so that we can 588105197Ssam * compare it with the key for the IP address. 589105197Ssam */ 590105197Ssam file_key = key_new(host_key->type); 591105197Ssam 592105197Ssam /* 593105197Ssam * Check if the host key is present in the user\'s list of known 594105197Ssam * hosts or in the systemwide list. 595105197Ssam */ 596105197Ssam host_file = user_hostfile; 597105197Ssam host_status = check_host_in_hostfile(host_file, host, host_key, 598105197Ssam file_key, &host_line); 599105197Ssam if (host_status == HOST_NEW) { 600105197Ssam host_file = system_hostfile; 601105197Ssam host_status = check_host_in_hostfile(host_file, host, host_key, 602105197Ssam file_key, &host_line); 603105197Ssam } 604105197Ssam /* 605105197Ssam * Also perform check for the ip address, skip the check if we are 606105197Ssam * localhost or the hostname was an ip address to begin with 607105197Ssam */ 608111119Simp if (options.check_host_ip) { 609105197Ssam Key *ip_key = key_new(host_key->type); 610105197Ssam 611105197Ssam ip_file = user_hostfile; 612105197Ssam ip_status = check_host_in_hostfile(ip_file, ip, host_key, 613105197Ssam ip_key, &ip_line); 614105197Ssam if (ip_status == HOST_NEW) { 615105197Ssam ip_file = system_hostfile; 616105197Ssam ip_status = check_host_in_hostfile(ip_file, ip, 617105197Ssam host_key, ip_key, &ip_line); 618105197Ssam } 619105197Ssam if (host_status == HOST_CHANGED && 620105197Ssam (ip_status != HOST_CHANGED || !key_equal(ip_key, file_key))) 621105197Ssam host_ip_differ = 1; 622105197Ssam 623105197Ssam key_free(ip_key); 624105197Ssam } else 625105197Ssam ip_status = host_status; 626105197Ssam 627105197Ssam key_free(file_key); 628105197Ssam 629105197Ssam switch (host_status) { 630105197Ssam case HOST_OK: 631105197Ssam /* The host is known and the key matches. */ 632105197Ssam debug("Host '%.200s' is known and matches the %s host key.", 633105197Ssam host, type); 634105197Ssam debug("Found key in %s:%d", host_file, host_line); 635105197Ssam if (options.check_host_ip && ip_status == HOST_NEW) { 636105197Ssam if (readonly) 637105197Ssam log("%s host key for IP address " 638105197Ssam "'%.128s' not in list of known hosts.", 639105197Ssam type, ip); 640105197Ssam else if (!add_host_to_hostfile(user_hostfile, ip, 641105197Ssam host_key)) 642105197Ssam log("Failed to add the %s host key for IP " 643105197Ssam "address '%.128s' to the list of known " 644105197Ssam "hosts (%.30s).", type, ip, user_hostfile); 645105197Ssam else 646105197Ssam log("Warning: Permanently added the %s host " 647105197Ssam "key for IP address '%.128s' to the list " 648105197Ssam "of known hosts.", type, ip); 649105197Ssam } 650105197Ssam break; 651105197Ssam case HOST_NEW: 652105197Ssam if (readonly) 653105197Ssam goto fail; 654105197Ssam /* The host is new. */ 655105197Ssam if (options.strict_host_key_checking == 1) { 656105197Ssam /* 657105197Ssam * User has requested strict host key checking. We 658105197Ssam * will not add the host key automatically. The only 659105197Ssam * alternative left is to abort. 660105197Ssam */ 661105197Ssam error("No %s host key is known for %.200s and you " 662105197Ssam "have requested strict checking.", type, host); 663105197Ssam goto fail; 664105197Ssam } else if (options.strict_host_key_checking == 2) { 665105197Ssam /* The default */ 666105197Ssam fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); 667105197Ssam snprintf(msg, sizeof(msg), 668105197Ssam "The authenticity of host '%.200s (%s)' can't be " 669105197Ssam "established.\n" 670105197Ssam "%s key fingerprint is %s.\n" 671105197Ssam "Are you sure you want to continue connecting " 672105197Ssam "(yes/no)? ", host, ip, type, fp); 673105197Ssam xfree(fp); 674105197Ssam if (!confirm(msg)) 675105197Ssam goto fail; 676105197Ssam } 677105197Ssam if (options.check_host_ip && ip_status == HOST_NEW) { 678105197Ssam snprintf(hostline, sizeof(hostline), "%s,%s", host, ip); 679105197Ssam hostp = hostline; 680105197Ssam } else 681105197Ssam hostp = host; 682105197Ssam 683105197Ssam /* 684105197Ssam * If not in strict mode, add the key automatically to the 685105197Ssam * local known_hosts file. 686105197Ssam */ 687105197Ssam if (!add_host_to_hostfile(user_hostfile, hostp, host_key)) 688105197Ssam log("Failed to add the host to the list of known " 689105197Ssam "hosts (%.500s).", user_hostfile); 690105197Ssam else 691105197Ssam log("Warning: Permanently added '%.200s' (%s) to the " 692105197Ssam "list of known hosts.", hostp, type); 693105197Ssam break; 694105197Ssam case HOST_CHANGED: 695105197Ssam if (options.check_host_ip && host_ip_differ) { 696105197Ssam char *msg; 697105197Ssam if (ip_status == HOST_NEW) 698105197Ssam msg = "is unknown"; 699105197Ssam else if (ip_status == HOST_OK) 700105197Ssam msg = "is unchanged"; 701105197Ssam else 702105197Ssam msg = "has a different value"; 703105197Ssam error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); 704105197Ssam error("@ WARNING: POSSIBLE DNS SPOOFING DETECTED! @"); 705105197Ssam error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); 706105197Ssam error("The %s host key for %s has changed,", type, host); 707105197Ssam error("and the key for the according IP address %s", ip); 708105197Ssam error("%s. This could either mean that", msg); 709105197Ssam error("DNS SPOOFING is happening or the IP address for the host"); 710105197Ssam error("and its host key have changed at the same time."); 711105197Ssam if (ip_status != HOST_NEW) 712105197Ssam error("Offending key for IP in %s:%d", ip_file, ip_line); 713105197Ssam } 714105197Ssam /* The host key has changed. */ 715105197Ssam fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); 716105197Ssam error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); 717105197Ssam error("@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @"); 718105197Ssam error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); 719105197Ssam error("IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!"); 720105197Ssam error("Someone could be eavesdropping on you right now (man-in-the-middle attack)!"); 721105197Ssam error("It is also possible that the %s host key has just been changed.", type); 722105197Ssam error("The fingerprint for the %s key sent by the remote host is\n%s.", 723105197Ssam type, fp); 724105197Ssam error("Please contact your system administrator."); 725105197Ssam error("Add correct host key in %.100s to get rid of this message.", 726105197Ssam user_hostfile); 727105197Ssam error("Offending key in %s:%d", host_file, host_line); 728105197Ssam xfree(fp); 729105197Ssam 730105197Ssam /* 731105197Ssam * If strict host key checking is in use, the user will have 732105197Ssam * to edit the key manually and we can only abort. 733105197Ssam */ 734105197Ssam if (options.strict_host_key_checking) { 735105197Ssam error("%s host key for %.200s has changed and you have " 736105197Ssam "requested strict checking.", type, host); 737105197Ssam goto fail; 738105197Ssam } 739105197Ssam 740105197Ssam /* 741105197Ssam * If strict host key checking has not been requested, allow 742105197Ssam * the connection but without password authentication or 743105197Ssam * agent forwarding. 744105197Ssam */ 745105197Ssam if (options.password_authentication) { 746105197Ssam error("Password authentication is disabled to avoid " 747105197Ssam "man-in-the-middle attacks."); 748105197Ssam options.password_authentication = 0; 749105197Ssam } 750105197Ssam if (options.forward_agent) { 751105197Ssam error("Agent forwarding is disabled to avoid " 752105197Ssam "man-in-the-middle attacks."); 753105197Ssam options.forward_agent = 0; 754105197Ssam } 755105197Ssam if (options.forward_x11) { 756105197Ssam error("X11 forwarding is disabled to avoid " 757105197Ssam "man-in-the-middle attacks."); 758105197Ssam options.forward_x11 = 0; 759105197Ssam } 760105197Ssam if (options.num_local_forwards > 0 || 761105197Ssam options.num_remote_forwards > 0) { 762105197Ssam error("Port forwarding is disabled to avoid " 763 "man-in-the-middle attacks."); 764 options.num_local_forwards = 765 options.num_remote_forwards = 0; 766 } 767 /* 768 * XXX Should permit the user to change to use the new id. 769 * This could be done by converting the host key to an 770 * identifying sentence, tell that the host identifies itself 771 * by that sentence, and ask the user if he/she whishes to 772 * accept the authentication. 773 */ 774 break; 775 } 776 777 if (options.check_host_ip && host_status != HOST_CHANGED && 778 ip_status == HOST_CHANGED) { 779 snprintf(msg, sizeof(msg), 780 "Warning: the %s host key for '%.200s' " 781 "differs from the key for the IP address '%.128s'" 782 "\nOffending key for IP in %s:%d", 783 type, host, ip, ip_file, ip_line); 784 if (host_status == HOST_OK) { 785 len = strlen(msg); 786 snprintf(msg + len, sizeof(msg) - len, 787 "\nMatching host key in %s:%d", 788 host_file, host_line); 789 } 790 if (options.strict_host_key_checking == 1) { 791 log(msg); 792 error("Exiting, you have requested strict checking."); 793 goto fail; 794 } else if (options.strict_host_key_checking == 2) { 795 strlcat(msg, "\nAre you sure you want " 796 "to continue connecting (yes/no)? ", sizeof(msg)); 797 if (!confirm(msg)) 798 goto fail; 799 } else { 800 log(msg); 801 } 802 } 803 804 xfree(ip); 805 return 0; 806 807fail: 808 xfree(ip); 809 return -1; 810} 811 812int 813verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key) 814{ 815 struct stat st; 816 817 /* return ok if the key can be found in an old keyfile */ 818 if (stat(options.system_hostfile2, &st) == 0 || 819 stat(options.user_hostfile2, &st) == 0) { 820 if (check_host_key(host, hostaddr, host_key, /*readonly*/ 1, 821 options.user_hostfile2, options.system_hostfile2) == 0) 822 return 0; 823 } 824 return check_host_key(host, hostaddr, host_key, /*readonly*/ 0, 825 options.user_hostfile, options.system_hostfile); 826} 827 828/* 829 * Starts a dialog with the server, and authenticates the current user on the 830 * server. This does not need any extra privileges. The basic connection 831 * to the server must already have been established before this is called. 832 * If login fails, this function prints an error and never returns. 833 * This function does not require super-user privileges. 834 */ 835void 836ssh_login(Key **keys, int nkeys, const char *orighost, 837 struct sockaddr *hostaddr, struct passwd *pw) 838{ 839 char *host, *cp; 840 char *server_user, *local_user; 841 842 local_user = xstrdup(pw->pw_name); 843 server_user = options.user ? options.user : local_user; 844 845 /* Convert the user-supplied hostname into all lowercase. */ 846 host = xstrdup(orighost); 847 for (cp = host; *cp; cp++) 848 if (isupper(*cp)) 849 *cp = tolower(*cp); 850 851 /* Exchange protocol version identification strings with the server. */ 852 ssh_exchange_identification(); 853 854 /* Put the connection into non-blocking mode. */ 855 packet_set_nonblocking(); 856 857 /* key exchange */ 858 /* authenticate user */ 859 if (compat20) { 860 ssh_kex2(host, hostaddr); 861 ssh_userauth2(local_user, server_user, host, keys, nkeys); 862 } else { 863 ssh_kex(host, hostaddr); 864 ssh_userauth1(local_user, server_user, host, keys, nkeys); 865 } 866} 867 868void 869ssh_put_password(char *password) 870{ 871 int size; 872 char *padded; 873 874 if (datafellows & SSH_BUG_PASSWORDPAD) { 875 packet_put_cstring(password); 876 return; 877 } 878 size = roundup(strlen(password) + 1, 32); 879 padded = xmalloc(size); 880 memset(padded, 0, size); 881 strlcpy(padded, password, size); 882 packet_put_string(padded, size); 883 memset(padded, 0, size); 884 xfree(padded); 885} 886