1511b41d2SMark Murray /* 2511b41d2SMark Murray * Author: Tatu Ylonen <ylo@cs.hut.fi> 3511b41d2SMark Murray * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4511b41d2SMark Murray * All rights reserved 5511b41d2SMark Murray * Code to connect to a remote host, and to perform the client side of the 6511b41d2SMark Murray * login (authentication) dialog. 742f71286SMark Murray * 8c2d3a559SKris Kennaway * As far as I am concerned, the code I have written for this software 9c2d3a559SKris Kennaway * can be used freely for any purpose. Any derived versions of this 10c2d3a559SKris Kennaway * software must be clearly marked as such, and if the derived work is 11c2d3a559SKris Kennaway * incompatible with the protocol description in the RFC file, it must be 12c2d3a559SKris Kennaway * called by a name other than "ssh" or "Secure Shell". 13511b41d2SMark Murray */ 14511b41d2SMark Murray 15511b41d2SMark Murray #include "includes.h" 16b74df5b2SDag-Erling Smørgrav RCSID("$OpenBSD: sshconnect.c,v 1.171 2005/12/06 22:38:27 reyk Exp $"); 17511b41d2SMark Murray 1818a71195SBrian Feldman #include <openssl/bn.h> 19e8aafc91SKris Kennaway 20ca3176e7SBrian Feldman #include "ssh.h" 21511b41d2SMark Murray #include "xmalloc.h" 22511b41d2SMark Murray #include "rsa.h" 23e8aafc91SKris Kennaway #include "buffer.h" 24511b41d2SMark Murray #include "packet.h" 25511b41d2SMark Murray #include "uidswap.h" 26511b41d2SMark Murray #include "compat.h" 273c6ae118SKris Kennaway #include "key.h" 28e8aafc91SKris Kennaway #include "sshconnect.h" 293c6ae118SKris Kennaway #include "hostfile.h" 30ca3176e7SBrian Feldman #include "log.h" 31ca3176e7SBrian Feldman #include "readconf.h" 32ca3176e7SBrian Feldman #include "atomicio.h" 33ca3176e7SBrian Feldman #include "misc.h" 34cf2b5f3bSDag-Erling Smørgrav #include "dns.h" 35cf2b5f3bSDag-Erling Smørgrav 36e8aafc91SKris Kennaway char *client_version_string = NULL; 37e8aafc91SKris Kennaway char *server_version_string = NULL; 38511b41d2SMark Murray 39b74df5b2SDag-Erling Smørgrav static int matching_host_key_dns = 0; 40cf2b5f3bSDag-Erling Smørgrav 4180628bacSDag-Erling Smørgrav /* import */ 42511b41d2SMark Murray extern Options options; 43511b41d2SMark Murray extern char *__progname; 4480628bacSDag-Erling Smørgrav extern uid_t original_real_uid; 4580628bacSDag-Erling Smørgrav extern uid_t original_effective_uid; 46f388f5efSDag-Erling Smørgrav extern pid_t proxy_command_pid; 47511b41d2SMark Murray 48989dd127SDag-Erling Smørgrav #ifndef INET6_ADDRSTRLEN /* for non IPv6 machines */ 49989dd127SDag-Erling Smørgrav #define INET6_ADDRSTRLEN 46 50989dd127SDag-Erling Smørgrav #endif 51989dd127SDag-Erling Smørgrav 52f388f5efSDag-Erling Smørgrav static int show_other_keys(const char *, Key *); 531ec0d754SDag-Erling Smørgrav static void warn_changed_key(Key *); 54ca3176e7SBrian Feldman 55511b41d2SMark Murray /* 56511b41d2SMark Murray * Connect to the given ssh server using a proxy command. 57511b41d2SMark Murray */ 58af12a3e7SDag-Erling Smørgrav static int 5980628bacSDag-Erling Smørgrav ssh_proxy_connect(const char *host, u_short port, const char *proxy_command) 60511b41d2SMark Murray { 61d4ecd108SDag-Erling Smørgrav char *command_string, *tmp; 62511b41d2SMark Murray int pin[2], pout[2]; 63e8aafc91SKris Kennaway pid_t pid; 64511b41d2SMark Murray char strport[NI_MAXSERV]; 65d4ecd108SDag-Erling Smørgrav size_t len; 66511b41d2SMark Murray 67511b41d2SMark Murray /* Convert the port number into a string. */ 68511b41d2SMark Murray snprintf(strport, sizeof strport, "%hu", port); 69511b41d2SMark Murray 70f388f5efSDag-Erling Smørgrav /* 71f388f5efSDag-Erling Smørgrav * Build the final command string in the buffer by making the 72f388f5efSDag-Erling Smørgrav * appropriate substitutions to the given proxy command. 73f388f5efSDag-Erling Smørgrav * 74f388f5efSDag-Erling Smørgrav * Use "exec" to avoid "sh -c" processes on some platforms 75f388f5efSDag-Erling Smørgrav * (e.g. Solaris) 76f388f5efSDag-Erling Smørgrav */ 77d4ecd108SDag-Erling Smørgrav len = strlen(proxy_command) + 6; 78d4ecd108SDag-Erling Smørgrav tmp = xmalloc(len); 79d4ecd108SDag-Erling Smørgrav strlcpy(tmp, "exec ", len); 80d4ecd108SDag-Erling Smørgrav strlcat(tmp, proxy_command, len); 81d4ecd108SDag-Erling Smørgrav command_string = percent_expand(tmp, "h", host, 82d4ecd108SDag-Erling Smørgrav "p", strport, (char *)NULL); 83d4ecd108SDag-Erling Smørgrav xfree(tmp); 84511b41d2SMark Murray 85511b41d2SMark Murray /* Create pipes for communicating with the proxy. */ 86511b41d2SMark Murray if (pipe(pin) < 0 || pipe(pout) < 0) 87511b41d2SMark Murray fatal("Could not create pipes to communicate with the proxy: %.100s", 88511b41d2SMark Murray strerror(errno)); 89511b41d2SMark Murray 90511b41d2SMark Murray debug("Executing proxy command: %.500s", command_string); 91511b41d2SMark Murray 92511b41d2SMark Murray /* Fork and execute the proxy command. */ 93511b41d2SMark Murray if ((pid = fork()) == 0) { 94511b41d2SMark Murray char *argv[10]; 95511b41d2SMark Murray 96511b41d2SMark Murray /* Child. Permanently give up superuser privileges. */ 9780628bacSDag-Erling Smørgrav seteuid(original_real_uid); 9880628bacSDag-Erling Smørgrav setuid(original_real_uid); 99511b41d2SMark Murray 100511b41d2SMark Murray /* Redirect stdin and stdout. */ 101511b41d2SMark Murray close(pin[1]); 102511b41d2SMark Murray if (pin[0] != 0) { 103511b41d2SMark Murray if (dup2(pin[0], 0) < 0) 104511b41d2SMark Murray perror("dup2 stdin"); 105511b41d2SMark Murray close(pin[0]); 106511b41d2SMark Murray } 107511b41d2SMark Murray close(pout[0]); 108511b41d2SMark Murray if (dup2(pout[1], 1) < 0) 109511b41d2SMark Murray perror("dup2 stdout"); 110511b41d2SMark Murray /* Cannot be 1 because pin allocated two descriptors. */ 111511b41d2SMark Murray close(pout[1]); 112511b41d2SMark Murray 113511b41d2SMark Murray /* Stderr is left as it is so that error messages get 114511b41d2SMark Murray printed on the user's terminal. */ 115ca3176e7SBrian Feldman argv[0] = _PATH_BSHELL; 116511b41d2SMark Murray argv[1] = "-c"; 117511b41d2SMark Murray argv[2] = command_string; 118511b41d2SMark Murray argv[3] = NULL; 119511b41d2SMark Murray 120511b41d2SMark Murray /* Execute the proxy command. Note that we gave up any 121511b41d2SMark Murray extra privileges above. */ 122ca3176e7SBrian Feldman execv(argv[0], argv); 123ca3176e7SBrian Feldman perror(argv[0]); 124511b41d2SMark Murray exit(1); 125511b41d2SMark Murray } 126511b41d2SMark Murray /* Parent. */ 127511b41d2SMark Murray if (pid < 0) 128511b41d2SMark Murray fatal("fork failed: %.100s", strerror(errno)); 129f388f5efSDag-Erling Smørgrav else 130f388f5efSDag-Erling Smørgrav proxy_command_pid = pid; /* save pid to clean up later */ 131511b41d2SMark Murray 132511b41d2SMark Murray /* Close child side of the descriptors. */ 133511b41d2SMark Murray close(pin[0]); 134511b41d2SMark Murray close(pout[1]); 135511b41d2SMark Murray 136511b41d2SMark Murray /* Free the command name. */ 137d4ecd108SDag-Erling Smørgrav xfree(command_string); 138511b41d2SMark Murray 139511b41d2SMark Murray /* Set the connection file descriptors. */ 140511b41d2SMark Murray packet_set_connection(pout[0], pin[1]); 141511b41d2SMark Murray 142af12a3e7SDag-Erling Smørgrav /* Indicate OK return */ 143af12a3e7SDag-Erling Smørgrav return 0; 144511b41d2SMark Murray } 145511b41d2SMark Murray 146511b41d2SMark Murray /* 147511b41d2SMark Murray * Creates a (possibly privileged) socket for use as the ssh connection. 148511b41d2SMark Murray */ 149af12a3e7SDag-Erling Smørgrav static int 150cf2b5f3bSDag-Erling Smørgrav ssh_create_socket(int privileged, struct addrinfo *ai) 151511b41d2SMark Murray { 152af12a3e7SDag-Erling Smørgrav int sock, gaierr; 153af12a3e7SDag-Erling Smørgrav struct addrinfo hints, *res; 154511b41d2SMark Murray 155511b41d2SMark Murray /* 156511b41d2SMark Murray * If we are running as root and want to connect to a privileged 157511b41d2SMark Murray * port, bind our own socket to a privileged port. 158511b41d2SMark Murray */ 159511b41d2SMark Murray if (privileged) { 160511b41d2SMark Murray int p = IPPORT_RESERVED - 1; 16180628bacSDag-Erling Smørgrav PRIV_START; 162cf2b5f3bSDag-Erling Smørgrav sock = rresvport_af(&p, ai->ai_family); 16380628bacSDag-Erling Smørgrav PRIV_END; 164511b41d2SMark Murray if (sock < 0) 165cf2b5f3bSDag-Erling Smørgrav error("rresvport: af=%d %.100s", ai->ai_family, 166cf2b5f3bSDag-Erling Smørgrav strerror(errno)); 167511b41d2SMark Murray else 168511b41d2SMark Murray debug("Allocated local port %d.", p); 169af12a3e7SDag-Erling Smørgrav return sock; 170af12a3e7SDag-Erling Smørgrav } 171cf2b5f3bSDag-Erling Smørgrav sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); 172511b41d2SMark Murray if (sock < 0) 173511b41d2SMark Murray error("socket: %.100s", strerror(errno)); 174af12a3e7SDag-Erling Smørgrav 175af12a3e7SDag-Erling Smørgrav /* Bind the socket to an alternative local IP address */ 176af12a3e7SDag-Erling Smørgrav if (options.bind_address == NULL) 177af12a3e7SDag-Erling Smørgrav return sock; 178af12a3e7SDag-Erling Smørgrav 179af12a3e7SDag-Erling Smørgrav memset(&hints, 0, sizeof(hints)); 180cf2b5f3bSDag-Erling Smørgrav hints.ai_family = ai->ai_family; 181cf2b5f3bSDag-Erling Smørgrav hints.ai_socktype = ai->ai_socktype; 182cf2b5f3bSDag-Erling Smørgrav hints.ai_protocol = ai->ai_protocol; 183af12a3e7SDag-Erling Smørgrav hints.ai_flags = AI_PASSIVE; 184af12a3e7SDag-Erling Smørgrav gaierr = getaddrinfo(options.bind_address, "0", &hints, &res); 185af12a3e7SDag-Erling Smørgrav if (gaierr) { 186af12a3e7SDag-Erling Smørgrav error("getaddrinfo: %s: %s", options.bind_address, 187af12a3e7SDag-Erling Smørgrav gai_strerror(gaierr)); 188af12a3e7SDag-Erling Smørgrav close(sock); 189af12a3e7SDag-Erling Smørgrav return -1; 190511b41d2SMark Murray } 191af12a3e7SDag-Erling Smørgrav if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { 192af12a3e7SDag-Erling Smørgrav error("bind: %s: %s", options.bind_address, strerror(errno)); 193af12a3e7SDag-Erling Smørgrav close(sock); 194af12a3e7SDag-Erling Smørgrav freeaddrinfo(res); 195af12a3e7SDag-Erling Smørgrav return -1; 196af12a3e7SDag-Erling Smørgrav } 197af12a3e7SDag-Erling Smørgrav freeaddrinfo(res); 198511b41d2SMark Murray return sock; 199511b41d2SMark Murray } 200511b41d2SMark Murray 201cf2b5f3bSDag-Erling Smørgrav static int 202cf2b5f3bSDag-Erling Smørgrav timeout_connect(int sockfd, const struct sockaddr *serv_addr, 203cf2b5f3bSDag-Erling Smørgrav socklen_t addrlen, int timeout) 204cf2b5f3bSDag-Erling Smørgrav { 205cf2b5f3bSDag-Erling Smørgrav fd_set *fdset; 206cf2b5f3bSDag-Erling Smørgrav struct timeval tv; 207cf2b5f3bSDag-Erling Smørgrav socklen_t optlen; 208cf2b5f3bSDag-Erling Smørgrav int fdsetsz, optval, rc, result = -1; 209cf2b5f3bSDag-Erling Smørgrav 210cf2b5f3bSDag-Erling Smørgrav if (timeout <= 0) 211cf2b5f3bSDag-Erling Smørgrav return (connect(sockfd, serv_addr, addrlen)); 212cf2b5f3bSDag-Erling Smørgrav 2131ec0d754SDag-Erling Smørgrav set_nonblock(sockfd); 214cf2b5f3bSDag-Erling Smørgrav rc = connect(sockfd, serv_addr, addrlen); 2151ec0d754SDag-Erling Smørgrav if (rc == 0) { 2161ec0d754SDag-Erling Smørgrav unset_nonblock(sockfd); 217cf2b5f3bSDag-Erling Smørgrav return (0); 2181ec0d754SDag-Erling Smørgrav } 219cf2b5f3bSDag-Erling Smørgrav if (errno != EINPROGRESS) 220cf2b5f3bSDag-Erling Smørgrav return (-1); 221cf2b5f3bSDag-Erling Smørgrav 222cf2b5f3bSDag-Erling Smørgrav fdsetsz = howmany(sockfd + 1, NFDBITS) * sizeof(fd_mask); 223cf2b5f3bSDag-Erling Smørgrav fdset = (fd_set *)xmalloc(fdsetsz); 224cf2b5f3bSDag-Erling Smørgrav 225cf2b5f3bSDag-Erling Smørgrav memset(fdset, 0, fdsetsz); 226cf2b5f3bSDag-Erling Smørgrav FD_SET(sockfd, fdset); 227cf2b5f3bSDag-Erling Smørgrav tv.tv_sec = timeout; 228cf2b5f3bSDag-Erling Smørgrav tv.tv_usec = 0; 229cf2b5f3bSDag-Erling Smørgrav 230cf2b5f3bSDag-Erling Smørgrav for (;;) { 231cf2b5f3bSDag-Erling Smørgrav rc = select(sockfd + 1, NULL, fdset, NULL, &tv); 232cf2b5f3bSDag-Erling Smørgrav if (rc != -1 || errno != EINTR) 233cf2b5f3bSDag-Erling Smørgrav break; 234cf2b5f3bSDag-Erling Smørgrav } 235cf2b5f3bSDag-Erling Smørgrav 236cf2b5f3bSDag-Erling Smørgrav switch (rc) { 237cf2b5f3bSDag-Erling Smørgrav case 0: 238cf2b5f3bSDag-Erling Smørgrav /* Timed out */ 239cf2b5f3bSDag-Erling Smørgrav errno = ETIMEDOUT; 240cf2b5f3bSDag-Erling Smørgrav break; 241cf2b5f3bSDag-Erling Smørgrav case -1: 242cf2b5f3bSDag-Erling Smørgrav /* Select error */ 243cf2b5f3bSDag-Erling Smørgrav debug("select: %s", strerror(errno)); 244cf2b5f3bSDag-Erling Smørgrav break; 245cf2b5f3bSDag-Erling Smørgrav case 1: 246cf2b5f3bSDag-Erling Smørgrav /* Completed or failed */ 247cf2b5f3bSDag-Erling Smørgrav optval = 0; 248cf2b5f3bSDag-Erling Smørgrav optlen = sizeof(optval); 249cf2b5f3bSDag-Erling Smørgrav if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, 250cf2b5f3bSDag-Erling Smørgrav &optlen) == -1) { 251cf2b5f3bSDag-Erling Smørgrav debug("getsockopt: %s", strerror(errno)); 252cf2b5f3bSDag-Erling Smørgrav break; 253cf2b5f3bSDag-Erling Smørgrav } 254cf2b5f3bSDag-Erling Smørgrav if (optval != 0) { 255cf2b5f3bSDag-Erling Smørgrav errno = optval; 256cf2b5f3bSDag-Erling Smørgrav break; 257cf2b5f3bSDag-Erling Smørgrav } 258cf2b5f3bSDag-Erling Smørgrav result = 0; 2591ec0d754SDag-Erling Smørgrav unset_nonblock(sockfd); 260cf2b5f3bSDag-Erling Smørgrav break; 261cf2b5f3bSDag-Erling Smørgrav default: 262cf2b5f3bSDag-Erling Smørgrav /* Should not occur */ 263cf2b5f3bSDag-Erling Smørgrav fatal("Bogus return (%d) from select()", rc); 264cf2b5f3bSDag-Erling Smørgrav } 265cf2b5f3bSDag-Erling Smørgrav 266cf2b5f3bSDag-Erling Smørgrav xfree(fdset); 267cf2b5f3bSDag-Erling Smørgrav return (result); 268cf2b5f3bSDag-Erling Smørgrav } 269cf2b5f3bSDag-Erling Smørgrav 270511b41d2SMark Murray /* 271511b41d2SMark Murray * Opens a TCP/IP connection to the remote server on the given host. 272511b41d2SMark Murray * The address of the remote host will be returned in hostaddr. 27380628bacSDag-Erling Smørgrav * If port is 0, the default port will be used. If needpriv is true, 274511b41d2SMark Murray * a privileged port will be allocated to make the connection. 27580628bacSDag-Erling Smørgrav * This requires super-user privileges if needpriv is true. 276511b41d2SMark Murray * Connection_attempts specifies the maximum number of tries (one per 277511b41d2SMark Murray * second). If proxy_command is non-NULL, it specifies the command (with %h 278511b41d2SMark Murray * and %p substituted for host and port, respectively) to use to contact 279511b41d2SMark Murray * the daemon. 280511b41d2SMark Murray */ 281511b41d2SMark Murray int 2821f5ce8f4SBrian Feldman ssh_connect(const char *host, struct sockaddr_storage * hostaddr, 283af12a3e7SDag-Erling Smørgrav u_short port, int family, int connection_attempts, 28480628bacSDag-Erling Smørgrav int needpriv, const char *proxy_command) 285511b41d2SMark Murray { 286511b41d2SMark Murray int gaierr; 287ca3176e7SBrian Feldman int on = 1; 288ca3176e7SBrian Feldman int sock = -1, attempt; 289ca3176e7SBrian Feldman char ntop[NI_MAXHOST], strport[NI_MAXSERV]; 290ca3176e7SBrian Feldman struct addrinfo hints, *ai, *aitop; 291511b41d2SMark Murray 292e73e9afaSDag-Erling Smørgrav debug2("ssh_connect: needpriv %d", needpriv); 293511b41d2SMark Murray 294511b41d2SMark Murray /* If a proxy command is given, connect using it. */ 295511b41d2SMark Murray if (proxy_command != NULL) 29680628bacSDag-Erling Smørgrav return ssh_proxy_connect(host, port, proxy_command); 297511b41d2SMark Murray 298511b41d2SMark Murray /* No proxy command. */ 299511b41d2SMark Murray 300511b41d2SMark Murray memset(&hints, 0, sizeof(hints)); 301af12a3e7SDag-Erling Smørgrav hints.ai_family = family; 302511b41d2SMark Murray hints.ai_socktype = SOCK_STREAM; 303a82e551fSDag-Erling Smørgrav snprintf(strport, sizeof strport, "%u", port); 3041f5ce8f4SBrian Feldman if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) 3051f5ce8f4SBrian Feldman fatal("%s: %.100s: %s", __progname, host, 306511b41d2SMark Murray gai_strerror(gaierr)); 307511b41d2SMark Murray 308511b41d2SMark Murray /* 309511b41d2SMark Murray * Try to connect several times. On some machines, the first time 310511b41d2SMark Murray * will sometimes fail. In general socket code appears to behave 311511b41d2SMark Murray * quite magically on many machines. 312511b41d2SMark Murray */ 313af12a3e7SDag-Erling Smørgrav for (attempt = 0; ;) { 314511b41d2SMark Murray if (attempt > 0) 315511b41d2SMark Murray debug("Trying again..."); 316511b41d2SMark Murray 317511b41d2SMark Murray /* Loop through addresses for this host, and try each one in 318511b41d2SMark Murray sequence until the connection succeeds. */ 319511b41d2SMark Murray for (ai = aitop; ai; ai = ai->ai_next) { 320511b41d2SMark Murray if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) 321511b41d2SMark Murray continue; 322511b41d2SMark Murray if (getnameinfo(ai->ai_addr, ai->ai_addrlen, 323511b41d2SMark Murray ntop, sizeof(ntop), strport, sizeof(strport), 324511b41d2SMark Murray NI_NUMERICHOST|NI_NUMERICSERV) != 0) { 325511b41d2SMark Murray error("ssh_connect: getnameinfo failed"); 326511b41d2SMark Murray continue; 327511b41d2SMark Murray } 328511b41d2SMark Murray debug("Connecting to %.200s [%.100s] port %s.", 3291f5ce8f4SBrian Feldman host, ntop, strport); 330511b41d2SMark Murray 331511b41d2SMark Murray /* Create a socket for connecting. */ 332cf2b5f3bSDag-Erling Smørgrav sock = ssh_create_socket(needpriv, ai); 333511b41d2SMark Murray if (sock < 0) 334af12a3e7SDag-Erling Smørgrav /* Any error is already output */ 335511b41d2SMark Murray continue; 336511b41d2SMark Murray 337cf2b5f3bSDag-Erling Smørgrav if (timeout_connect(sock, ai->ai_addr, ai->ai_addrlen, 338cf2b5f3bSDag-Erling Smørgrav options.connection_timeout) >= 0) { 339511b41d2SMark Murray /* Successful connection. */ 340c322fe35SKris Kennaway memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); 341511b41d2SMark Murray break; 342511b41d2SMark Murray } else { 343f388f5efSDag-Erling Smørgrav debug("connect to address %s port %s: %s", 344f388f5efSDag-Erling Smørgrav ntop, strport, strerror(errno)); 345511b41d2SMark Murray /* 346511b41d2SMark Murray * Close the failed socket; there appear to 347511b41d2SMark Murray * be some problems when reusing a socket for 348511b41d2SMark Murray * which connect() has already returned an 349511b41d2SMark Murray * error. 350511b41d2SMark Murray */ 351511b41d2SMark Murray close(sock); 352511b41d2SMark Murray } 353511b41d2SMark Murray } 3541f5ce8f4SBrian Feldman if (ai) 355511b41d2SMark Murray break; /* Successful connection. */ 356511b41d2SMark Murray 357af12a3e7SDag-Erling Smørgrav attempt++; 358af12a3e7SDag-Erling Smørgrav if (attempt >= connection_attempts) 359af12a3e7SDag-Erling Smørgrav break; 360511b41d2SMark Murray /* Sleep a moment before retrying. */ 361511b41d2SMark Murray sleep(1); 362511b41d2SMark Murray } 363511b41d2SMark Murray 364511b41d2SMark Murray freeaddrinfo(aitop); 365511b41d2SMark Murray 366511b41d2SMark Murray /* Return failure if we didn't get a successful connection. */ 367f388f5efSDag-Erling Smørgrav if (attempt >= connection_attempts) { 368aa49c926SDag-Erling Smørgrav error("ssh: connect to host %s port %s: %s", 369f388f5efSDag-Erling Smørgrav host, strport, strerror(errno)); 370aa49c926SDag-Erling Smørgrav return (-1); 371f388f5efSDag-Erling Smørgrav } 372511b41d2SMark Murray 373511b41d2SMark Murray debug("Connection established."); 374511b41d2SMark Murray 3751ec0d754SDag-Erling Smørgrav /* Set SO_KEEPALIVE if requested. */ 3761ec0d754SDag-Erling Smørgrav if (options.tcp_keep_alive && 377ca3176e7SBrian Feldman setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, 378ca3176e7SBrian Feldman sizeof(on)) < 0) 379ca3176e7SBrian Feldman error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); 380ca3176e7SBrian Feldman 381511b41d2SMark Murray /* Set the connection. */ 382511b41d2SMark Murray packet_set_connection(sock, sock); 383511b41d2SMark Murray 384af12a3e7SDag-Erling Smørgrav return 0; 385511b41d2SMark Murray } 386511b41d2SMark Murray 387511b41d2SMark Murray /* 388e8aafc91SKris Kennaway * Waits for the server identification string, and sends our own 389e8aafc91SKris Kennaway * identification string. 390511b41d2SMark Murray */ 391af12a3e7SDag-Erling Smørgrav static void 392ca3176e7SBrian Feldman ssh_exchange_identification(void) 393511b41d2SMark Murray { 394e8aafc91SKris Kennaway char buf[256], remote_version[256]; /* must be same size! */ 395d4ecd108SDag-Erling Smørgrav int remote_major, remote_minor, mismatch; 396e8aafc91SKris Kennaway int connection_in = packet_get_connection_in(); 397e8aafc91SKris Kennaway int connection_out = packet_get_connection_out(); 398ca3176e7SBrian Feldman int minor1 = PROTOCOL_MINOR_1; 399d4ecd108SDag-Erling Smørgrav u_int i; 400511b41d2SMark Murray 401d4ecd108SDag-Erling Smørgrav /* Read other side's version identification. */ 402c2d3a559SKris Kennaway for (;;) { 403e8aafc91SKris Kennaway for (i = 0; i < sizeof(buf) - 1; i++) { 404d4ecd108SDag-Erling Smørgrav size_t len = atomicio(read, connection_in, &buf[i], 1); 405d4ecd108SDag-Erling Smørgrav 406d4ecd108SDag-Erling Smørgrav if (len != 1 && errno == EPIPE) 407e8aafc91SKris Kennaway fatal("ssh_exchange_identification: Connection closed by remote host"); 408d4ecd108SDag-Erling Smørgrav else if (len != 1) 409d4ecd108SDag-Erling Smørgrav fatal("ssh_exchange_identification: read: %.100s", strerror(errno)); 410e8aafc91SKris Kennaway if (buf[i] == '\r') { 411e8aafc91SKris Kennaway buf[i] = '\n'; 412e8aafc91SKris Kennaway buf[i + 1] = 0; 413e8aafc91SKris Kennaway continue; /**XXX wait for \n */ 414511b41d2SMark Murray } 415e8aafc91SKris Kennaway if (buf[i] == '\n') { 416e8aafc91SKris Kennaway buf[i + 1] = 0; 417511b41d2SMark Murray break; 418e8aafc91SKris Kennaway } 419e8aafc91SKris Kennaway } 420e8aafc91SKris Kennaway buf[sizeof(buf) - 1] = 0; 421c2d3a559SKris Kennaway if (strncmp(buf, "SSH-", 4) == 0) 422c2d3a559SKris Kennaway break; 423c2d3a559SKris Kennaway debug("ssh_exchange_identification: %s", buf); 424c2d3a559SKris Kennaway } 425e8aafc91SKris Kennaway server_version_string = xstrdup(buf); 426511b41d2SMark Murray 427511b41d2SMark Murray /* 428e8aafc91SKris Kennaway * Check that the versions match. In future this might accept 429e8aafc91SKris Kennaway * several versions and set appropriate flags to handle them. 430511b41d2SMark Murray */ 431e8aafc91SKris Kennaway if (sscanf(server_version_string, "SSH-%d.%d-%[^\n]\n", 432e8aafc91SKris Kennaway &remote_major, &remote_minor, remote_version) != 3) 433e8aafc91SKris Kennaway fatal("Bad remote protocol version identification: '%.100s'", buf); 434e8aafc91SKris Kennaway debug("Remote protocol version %d.%d, remote software version %.100s", 435e8aafc91SKris Kennaway remote_major, remote_minor, remote_version); 436511b41d2SMark Murray 437e8aafc91SKris Kennaway compat_datafellows(remote_version); 438e8aafc91SKris Kennaway mismatch = 0; 439e8aafc91SKris Kennaway 440e8aafc91SKris Kennaway switch (remote_major) { 441e8aafc91SKris Kennaway case 1: 442e8aafc91SKris Kennaway if (remote_minor == 99 && 443e8aafc91SKris Kennaway (options.protocol & SSH_PROTO_2) && 444e8aafc91SKris Kennaway !(options.protocol & SSH_PROTO_1_PREFERRED)) { 445e8aafc91SKris Kennaway enable_compat20(); 446511b41d2SMark Murray break; 447e8aafc91SKris Kennaway } 448e8aafc91SKris Kennaway if (!(options.protocol & SSH_PROTO_1)) { 449e8aafc91SKris Kennaway mismatch = 1; 450e8aafc91SKris Kennaway break; 451e8aafc91SKris Kennaway } 452e8aafc91SKris Kennaway if (remote_minor < 3) { 453e8aafc91SKris Kennaway fatal("Remote machine has too old SSH software version."); 454ca3176e7SBrian Feldman } else if (remote_minor == 3 || remote_minor == 4) { 455e8aafc91SKris Kennaway /* We speak 1.3, too. */ 456e8aafc91SKris Kennaway enable_compat13(); 457ca3176e7SBrian Feldman minor1 = 3; 458e8aafc91SKris Kennaway if (options.forward_agent) { 459cf2b5f3bSDag-Erling Smørgrav logit("Agent forwarding disabled for protocol 1.3"); 460e8aafc91SKris Kennaway options.forward_agent = 0; 461e8aafc91SKris Kennaway } 462e8aafc91SKris Kennaway } 463e8aafc91SKris Kennaway break; 464e8aafc91SKris Kennaway case 2: 465e8aafc91SKris Kennaway if (options.protocol & SSH_PROTO_2) { 466e8aafc91SKris Kennaway enable_compat20(); 467e8aafc91SKris Kennaway break; 468e8aafc91SKris Kennaway } 469e8aafc91SKris Kennaway /* FALLTHROUGH */ 470511b41d2SMark Murray default: 471e8aafc91SKris Kennaway mismatch = 1; 472e8aafc91SKris Kennaway break; 473511b41d2SMark Murray } 474e8aafc91SKris Kennaway if (mismatch) 475e8aafc91SKris Kennaway fatal("Protocol major versions differ: %d vs. %d", 476e8aafc91SKris Kennaway (options.protocol & SSH_PROTO_2) ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, 477e8aafc91SKris Kennaway remote_major); 478e8aafc91SKris Kennaway /* Send our own protocol version identification. */ 479e8aafc91SKris Kennaway snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s\n", 480e8aafc91SKris Kennaway compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, 481ca3176e7SBrian Feldman compat20 ? PROTOCOL_MINOR_2 : minor1, 482e8aafc91SKris Kennaway SSH_VERSION); 483cf2b5f3bSDag-Erling Smørgrav if (atomicio(vwrite, connection_out, buf, strlen(buf)) != strlen(buf)) 484e8aafc91SKris Kennaway fatal("write: %.100s", strerror(errno)); 485e8aafc91SKris Kennaway client_version_string = xstrdup(buf); 486e8aafc91SKris Kennaway chop(client_version_string); 487e8aafc91SKris Kennaway chop(server_version_string); 488e8aafc91SKris Kennaway debug("Local version string %.100s", client_version_string); 489511b41d2SMark Murray } 490511b41d2SMark Murray 491ca3176e7SBrian Feldman /* defaults to 'no' */ 492af12a3e7SDag-Erling Smørgrav static int 493af12a3e7SDag-Erling Smørgrav confirm(const char *prompt) 494511b41d2SMark Murray { 495af12a3e7SDag-Erling Smørgrav const char *msg, *again = "Please type 'yes' or 'no': "; 496af12a3e7SDag-Erling Smørgrav char *p; 497af12a3e7SDag-Erling Smørgrav int ret = -1; 498511b41d2SMark Murray 499ca3176e7SBrian Feldman if (options.batch_mode) 500ca3176e7SBrian Feldman return 0; 501af12a3e7SDag-Erling Smørgrav for (msg = prompt;;msg = again) { 502af12a3e7SDag-Erling Smørgrav p = read_passphrase(msg, RP_ECHO); 503af12a3e7SDag-Erling Smørgrav if (p == NULL || 504af12a3e7SDag-Erling Smørgrav (p[0] == '\0') || (p[0] == '\n') || 505af12a3e7SDag-Erling Smørgrav strncasecmp(p, "no", 2) == 0) 506af12a3e7SDag-Erling Smørgrav ret = 0; 507f388f5efSDag-Erling Smørgrav if (p && strncasecmp(p, "yes", 3) == 0) 508af12a3e7SDag-Erling Smørgrav ret = 1; 509af12a3e7SDag-Erling Smørgrav if (p) 510af12a3e7SDag-Erling Smørgrav xfree(p); 511af12a3e7SDag-Erling Smørgrav if (ret != -1) 512af12a3e7SDag-Erling Smørgrav return ret; 513511b41d2SMark Murray } 514511b41d2SMark Murray } 515511b41d2SMark Murray 516e8aafc91SKris Kennaway /* 517af12a3e7SDag-Erling Smørgrav * check whether the supplied host key is valid, return -1 if the key 518af12a3e7SDag-Erling Smørgrav * is not valid. the user_hostfile will not be updated if 'readonly' is true. 519e8aafc91SKris Kennaway */ 520af12a3e7SDag-Erling Smørgrav static int 521e8aafc91SKris Kennaway check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, 522af12a3e7SDag-Erling Smørgrav int readonly, const char *user_hostfile, const char *system_hostfile) 523511b41d2SMark Murray { 524e8aafc91SKris Kennaway Key *file_key; 5251ec0d754SDag-Erling Smørgrav const char *type = key_type(host_key); 526e8aafc91SKris Kennaway char *ip = NULL; 527ca3176e7SBrian Feldman char hostline[1000], *hostp, *fp; 528e8aafc91SKris Kennaway HostStatus host_status; 529e8aafc91SKris Kennaway HostStatus ip_status; 530aa49c926SDag-Erling Smørgrav int r, local = 0, host_ip_differ = 0; 531989dd127SDag-Erling Smørgrav int salen; 532e8aafc91SKris Kennaway char ntop[NI_MAXHOST]; 533af12a3e7SDag-Erling Smørgrav char msg[1024]; 534cf2b5f3bSDag-Erling Smørgrav int len, host_line, ip_line; 535ca3176e7SBrian Feldman const char *host_file = NULL, *ip_file = NULL; 536511b41d2SMark Murray 537e8aafc91SKris Kennaway /* 538e8aafc91SKris Kennaway * Force accepting of the host key for loopback/localhost. The 539e8aafc91SKris Kennaway * problem is that if the home directory is NFS-mounted to multiple 540e8aafc91SKris Kennaway * machines, localhost will refer to a different machine in each of 541e8aafc91SKris Kennaway * them, and the user will get bogus HOST_CHANGED warnings. This 542e8aafc91SKris Kennaway * essentially disables host authentication for localhost; however, 543e8aafc91SKris Kennaway * this is probably not a real problem. 544e8aafc91SKris Kennaway */ 545e8aafc91SKris Kennaway /** hostaddr == 0! */ 546e8aafc91SKris Kennaway switch (hostaddr->sa_family) { 547e8aafc91SKris Kennaway case AF_INET: 548af12a3e7SDag-Erling Smørgrav local = (ntohl(((struct sockaddr_in *)hostaddr)-> 549af12a3e7SDag-Erling Smørgrav sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; 550989dd127SDag-Erling Smørgrav salen = sizeof(struct sockaddr_in); 551511b41d2SMark Murray break; 552e8aafc91SKris Kennaway case AF_INET6: 553af12a3e7SDag-Erling Smørgrav local = IN6_IS_ADDR_LOOPBACK( 554af12a3e7SDag-Erling Smørgrav &(((struct sockaddr_in6 *)hostaddr)->sin6_addr)); 555989dd127SDag-Erling Smørgrav salen = sizeof(struct sockaddr_in6); 556511b41d2SMark Murray break; 557e8aafc91SKris Kennaway default: 558e8aafc91SKris Kennaway local = 0; 559989dd127SDag-Erling Smørgrav salen = sizeof(struct sockaddr_storage); 560511b41d2SMark Murray break; 561511b41d2SMark Murray } 562af12a3e7SDag-Erling Smørgrav if (options.no_host_authentication_for_localhost == 1 && local && 563af12a3e7SDag-Erling Smørgrav options.host_key_alias == NULL) { 564ca3176e7SBrian Feldman debug("Forcing accepting of host key for " 565ca3176e7SBrian Feldman "loopback/localhost."); 566af12a3e7SDag-Erling Smørgrav return 0; 567511b41d2SMark Murray } 568511b41d2SMark Murray 569e8aafc91SKris Kennaway /* 570ca3176e7SBrian Feldman * We don't have the remote ip-address for connections 571ca3176e7SBrian Feldman * using a proxy command 572e8aafc91SKris Kennaway */ 573ca3176e7SBrian Feldman if (options.proxy_command == NULL) { 574989dd127SDag-Erling Smørgrav if (getnameinfo(hostaddr, salen, ntop, sizeof(ntop), 575e8aafc91SKris Kennaway NULL, 0, NI_NUMERICHOST) != 0) 576e8aafc91SKris Kennaway fatal("check_host_key: getnameinfo failed"); 577e8aafc91SKris Kennaway ip = xstrdup(ntop); 578ca3176e7SBrian Feldman } else { 579ca3176e7SBrian Feldman ip = xstrdup("<no hostip for proxy command>"); 580ca3176e7SBrian Feldman } 581ca3176e7SBrian Feldman /* 582ca3176e7SBrian Feldman * Turn off check_host_ip if the connection is to localhost, via proxy 583ca3176e7SBrian Feldman * command or if we don't have a hostname to compare with 584ca3176e7SBrian Feldman */ 585ca3176e7SBrian Feldman if (options.check_host_ip && 586ca3176e7SBrian Feldman (local || strcmp(host, ip) == 0 || options.proxy_command != NULL)) 587ca3176e7SBrian Feldman options.check_host_ip = 0; 588ca3176e7SBrian Feldman 589ca3176e7SBrian Feldman /* 590ca3176e7SBrian Feldman * Allow the user to record the key under a different name. This is 591ca3176e7SBrian Feldman * useful for ssh tunneling over forwarded connections or if you run 592ca3176e7SBrian Feldman * multiple sshd's on different ports on the same machine. 593ca3176e7SBrian Feldman */ 594ca3176e7SBrian Feldman if (options.host_key_alias != NULL) { 595ca3176e7SBrian Feldman host = options.host_key_alias; 596ca3176e7SBrian Feldman debug("using hostkeyalias: %s", host); 597e8aafc91SKris Kennaway } 598e8aafc91SKris Kennaway 599e8aafc91SKris Kennaway /* 600e8aafc91SKris Kennaway * Store the host key from the known host file in here so that we can 601e8aafc91SKris Kennaway * compare it with the key for the IP address. 602e8aafc91SKris Kennaway */ 603e8aafc91SKris Kennaway file_key = key_new(host_key->type); 604e8aafc91SKris Kennaway 605e8aafc91SKris Kennaway /* 606b74df5b2SDag-Erling Smørgrav * Check if the host key is present in the user's list of known 607e8aafc91SKris Kennaway * hosts or in the systemwide list. 608e8aafc91SKris Kennaway */ 609ca3176e7SBrian Feldman host_file = user_hostfile; 610af12a3e7SDag-Erling Smørgrav host_status = check_host_in_hostfile(host_file, host, host_key, 611af12a3e7SDag-Erling Smørgrav file_key, &host_line); 612ca3176e7SBrian Feldman if (host_status == HOST_NEW) { 613ca3176e7SBrian Feldman host_file = system_hostfile; 614af12a3e7SDag-Erling Smørgrav host_status = check_host_in_hostfile(host_file, host, host_key, 615af12a3e7SDag-Erling Smørgrav file_key, &host_line); 616ca3176e7SBrian Feldman } 617e8aafc91SKris Kennaway /* 618e8aafc91SKris Kennaway * Also perform check for the ip address, skip the check if we are 619e8aafc91SKris Kennaway * localhost or the hostname was an ip address to begin with 620e8aafc91SKris Kennaway */ 621ca3176e7SBrian Feldman if (options.check_host_ip) { 622e8aafc91SKris Kennaway Key *ip_key = key_new(host_key->type); 623e8aafc91SKris Kennaway 624ca3176e7SBrian Feldman ip_file = user_hostfile; 625af12a3e7SDag-Erling Smørgrav ip_status = check_host_in_hostfile(ip_file, ip, host_key, 626af12a3e7SDag-Erling Smørgrav ip_key, &ip_line); 627ca3176e7SBrian Feldman if (ip_status == HOST_NEW) { 628ca3176e7SBrian Feldman ip_file = system_hostfile; 629af12a3e7SDag-Erling Smørgrav ip_status = check_host_in_hostfile(ip_file, ip, 630af12a3e7SDag-Erling Smørgrav host_key, ip_key, &ip_line); 631ca3176e7SBrian Feldman } 632e8aafc91SKris Kennaway if (host_status == HOST_CHANGED && 633e8aafc91SKris Kennaway (ip_status != HOST_CHANGED || !key_equal(ip_key, file_key))) 634e8aafc91SKris Kennaway host_ip_differ = 1; 635e8aafc91SKris Kennaway 636e8aafc91SKris Kennaway key_free(ip_key); 637e8aafc91SKris Kennaway } else 638e8aafc91SKris Kennaway ip_status = host_status; 639e8aafc91SKris Kennaway 640e8aafc91SKris Kennaway key_free(file_key); 641e8aafc91SKris Kennaway 642e8aafc91SKris Kennaway switch (host_status) { 643e8aafc91SKris Kennaway case HOST_OK: 644e8aafc91SKris Kennaway /* The host is known and the key matches. */ 645e8aafc91SKris Kennaway debug("Host '%.200s' is known and matches the %s host key.", 646e8aafc91SKris Kennaway host, type); 647ca3176e7SBrian Feldman debug("Found key in %s:%d", host_file, host_line); 648ca3176e7SBrian Feldman if (options.check_host_ip && ip_status == HOST_NEW) { 649af12a3e7SDag-Erling Smørgrav if (readonly) 650cf2b5f3bSDag-Erling Smørgrav logit("%s host key for IP address " 651af12a3e7SDag-Erling Smørgrav "'%.128s' not in list of known hosts.", 652e8aafc91SKris Kennaway type, ip); 653af12a3e7SDag-Erling Smørgrav else if (!add_host_to_hostfile(user_hostfile, ip, 654aa49c926SDag-Erling Smørgrav host_key, options.hash_known_hosts)) 655cf2b5f3bSDag-Erling Smørgrav logit("Failed to add the %s host key for IP " 656af12a3e7SDag-Erling Smørgrav "address '%.128s' to the list of known " 657af12a3e7SDag-Erling Smørgrav "hosts (%.30s).", type, ip, user_hostfile); 658af12a3e7SDag-Erling Smørgrav else 659cf2b5f3bSDag-Erling Smørgrav logit("Warning: Permanently added the %s host " 660af12a3e7SDag-Erling Smørgrav "key for IP address '%.128s' to the list " 661af12a3e7SDag-Erling Smørgrav "of known hosts.", type, ip); 662e8aafc91SKris Kennaway } 663e8aafc91SKris Kennaway break; 664e8aafc91SKris Kennaway case HOST_NEW: 665af12a3e7SDag-Erling Smørgrav if (readonly) 666af12a3e7SDag-Erling Smørgrav goto fail; 667e8aafc91SKris Kennaway /* The host is new. */ 668e8aafc91SKris Kennaway if (options.strict_host_key_checking == 1) { 669af12a3e7SDag-Erling Smørgrav /* 670af12a3e7SDag-Erling Smørgrav * User has requested strict host key checking. We 671af12a3e7SDag-Erling Smørgrav * will not add the host key automatically. The only 672af12a3e7SDag-Erling Smørgrav * alternative left is to abort. 673af12a3e7SDag-Erling Smørgrav */ 674af12a3e7SDag-Erling Smørgrav error("No %s host key is known for %.200s and you " 675af12a3e7SDag-Erling Smørgrav "have requested strict checking.", type, host); 676af12a3e7SDag-Erling Smørgrav goto fail; 677e8aafc91SKris Kennaway } else if (options.strict_host_key_checking == 2) { 678cf2b5f3bSDag-Erling Smørgrav char msg1[1024], msg2[1024]; 679cf2b5f3bSDag-Erling Smørgrav 680cf2b5f3bSDag-Erling Smørgrav if (show_other_keys(host, host_key)) 681cf2b5f3bSDag-Erling Smørgrav snprintf(msg1, sizeof(msg1), 682cf2b5f3bSDag-Erling Smørgrav "\nbut keys of different type are already" 683cf2b5f3bSDag-Erling Smørgrav " known for this host."); 684cf2b5f3bSDag-Erling Smørgrav else 685cf2b5f3bSDag-Erling Smørgrav snprintf(msg1, sizeof(msg1), "."); 686e8aafc91SKris Kennaway /* The default */ 687ca3176e7SBrian Feldman fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); 688cf2b5f3bSDag-Erling Smørgrav msg2[0] = '\0'; 689cf2b5f3bSDag-Erling Smørgrav if (options.verify_host_key_dns) { 6901ec0d754SDag-Erling Smørgrav if (matching_host_key_dns) 691cf2b5f3bSDag-Erling Smørgrav snprintf(msg2, sizeof(msg2), 692cf2b5f3bSDag-Erling Smørgrav "Matching host key fingerprint" 693cf2b5f3bSDag-Erling Smørgrav " found in DNS.\n"); 694cf2b5f3bSDag-Erling Smørgrav else 695cf2b5f3bSDag-Erling Smørgrav snprintf(msg2, sizeof(msg2), 696cf2b5f3bSDag-Erling Smørgrav "No matching host key fingerprint" 697cf2b5f3bSDag-Erling Smørgrav " found in DNS.\n"); 698cf2b5f3bSDag-Erling Smørgrav } 699af12a3e7SDag-Erling Smørgrav snprintf(msg, sizeof(msg), 700af12a3e7SDag-Erling Smørgrav "The authenticity of host '%.200s (%s)' can't be " 701f388f5efSDag-Erling Smørgrav "established%s\n" 702cf2b5f3bSDag-Erling Smørgrav "%s key fingerprint is %s.\n%s" 703af12a3e7SDag-Erling Smørgrav "Are you sure you want to continue connecting " 704f388f5efSDag-Erling Smørgrav "(yes/no)? ", 705cf2b5f3bSDag-Erling Smørgrav host, ip, msg1, type, fp, msg2); 706ca3176e7SBrian Feldman xfree(fp); 707af12a3e7SDag-Erling Smørgrav if (!confirm(msg)) 708af12a3e7SDag-Erling Smørgrav goto fail; 709e8aafc91SKris Kennaway } 710af12a3e7SDag-Erling Smørgrav /* 711af12a3e7SDag-Erling Smørgrav * If not in strict mode, add the key automatically to the 712af12a3e7SDag-Erling Smørgrav * local known_hosts file. 713af12a3e7SDag-Erling Smørgrav */ 714aa49c926SDag-Erling Smørgrav if (options.check_host_ip && ip_status == HOST_NEW) { 715aa49c926SDag-Erling Smørgrav snprintf(hostline, sizeof(hostline), "%s,%s", 716aa49c926SDag-Erling Smørgrav host, ip); 717aa49c926SDag-Erling Smørgrav hostp = hostline; 718aa49c926SDag-Erling Smørgrav if (options.hash_known_hosts) { 719aa49c926SDag-Erling Smørgrav /* Add hash of host and IP separately */ 720aa49c926SDag-Erling Smørgrav r = add_host_to_hostfile(user_hostfile, host, 721aa49c926SDag-Erling Smørgrav host_key, options.hash_known_hosts) && 722aa49c926SDag-Erling Smørgrav add_host_to_hostfile(user_hostfile, ip, 723aa49c926SDag-Erling Smørgrav host_key, options.hash_known_hosts); 724aa49c926SDag-Erling Smørgrav } else { 725aa49c926SDag-Erling Smørgrav /* Add unhashed "host,ip" */ 726aa49c926SDag-Erling Smørgrav r = add_host_to_hostfile(user_hostfile, 727aa49c926SDag-Erling Smørgrav hostline, host_key, 728aa49c926SDag-Erling Smørgrav options.hash_known_hosts); 729aa49c926SDag-Erling Smørgrav } 730aa49c926SDag-Erling Smørgrav } else { 731aa49c926SDag-Erling Smørgrav r = add_host_to_hostfile(user_hostfile, host, host_key, 732aa49c926SDag-Erling Smørgrav options.hash_known_hosts); 733aa49c926SDag-Erling Smørgrav hostp = host; 734aa49c926SDag-Erling Smørgrav } 735aa49c926SDag-Erling Smørgrav 736aa49c926SDag-Erling Smørgrav if (!r) 737cf2b5f3bSDag-Erling Smørgrav logit("Failed to add the host to the list of known " 738af12a3e7SDag-Erling Smørgrav "hosts (%.500s).", user_hostfile); 739e8aafc91SKris Kennaway else 740cf2b5f3bSDag-Erling Smørgrav logit("Warning: Permanently added '%.200s' (%s) to the " 741af12a3e7SDag-Erling Smørgrav "list of known hosts.", hostp, type); 742e8aafc91SKris Kennaway break; 743e8aafc91SKris Kennaway case HOST_CHANGED: 744e8aafc91SKris Kennaway if (options.check_host_ip && host_ip_differ) { 74521e764dfSDag-Erling Smørgrav char *key_msg; 746e8aafc91SKris Kennaway if (ip_status == HOST_NEW) 74721e764dfSDag-Erling Smørgrav key_msg = "is unknown"; 748e8aafc91SKris Kennaway else if (ip_status == HOST_OK) 74921e764dfSDag-Erling Smørgrav key_msg = "is unchanged"; 750e8aafc91SKris Kennaway else 75121e764dfSDag-Erling Smørgrav key_msg = "has a different value"; 752e8aafc91SKris Kennaway error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); 753e8aafc91SKris Kennaway error("@ WARNING: POSSIBLE DNS SPOOFING DETECTED! @"); 754e8aafc91SKris Kennaway error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); 755e8aafc91SKris Kennaway error("The %s host key for %s has changed,", type, host); 756e8aafc91SKris Kennaway error("and the key for the according IP address %s", ip); 75721e764dfSDag-Erling Smørgrav error("%s. This could either mean that", key_msg); 758e8aafc91SKris Kennaway error("DNS SPOOFING is happening or the IP address for the host"); 759ca3176e7SBrian Feldman error("and its host key have changed at the same time."); 760ca3176e7SBrian Feldman if (ip_status != HOST_NEW) 761ca3176e7SBrian Feldman error("Offending key for IP in %s:%d", ip_file, ip_line); 762e8aafc91SKris Kennaway } 763e8aafc91SKris Kennaway /* The host key has changed. */ 7641ec0d754SDag-Erling Smørgrav warn_changed_key(host_key); 765e8aafc91SKris Kennaway error("Add correct host key in %.100s to get rid of this message.", 766e8aafc91SKris Kennaway user_hostfile); 767ca3176e7SBrian Feldman error("Offending key in %s:%d", host_file, host_line); 768e8aafc91SKris Kennaway 769e8aafc91SKris Kennaway /* 770e8aafc91SKris Kennaway * If strict host key checking is in use, the user will have 771e8aafc91SKris Kennaway * to edit the key manually and we can only abort. 772e8aafc91SKris Kennaway */ 773af12a3e7SDag-Erling Smørgrav if (options.strict_host_key_checking) { 774af12a3e7SDag-Erling Smørgrav error("%s host key for %.200s has changed and you have " 775af12a3e7SDag-Erling Smørgrav "requested strict checking.", type, host); 776af12a3e7SDag-Erling Smørgrav goto fail; 777af12a3e7SDag-Erling Smørgrav } 778e8aafc91SKris Kennaway 779e8aafc91SKris Kennaway /* 780e8aafc91SKris Kennaway * If strict host key checking has not been requested, allow 781cf2b5f3bSDag-Erling Smørgrav * the connection but without MITM-able authentication or 782e8aafc91SKris Kennaway * agent forwarding. 783e8aafc91SKris Kennaway */ 784e8aafc91SKris Kennaway if (options.password_authentication) { 785af12a3e7SDag-Erling Smørgrav error("Password authentication is disabled to avoid " 786af12a3e7SDag-Erling Smørgrav "man-in-the-middle attacks."); 787e8aafc91SKris Kennaway options.password_authentication = 0; 788e8aafc91SKris Kennaway } 789cf2b5f3bSDag-Erling Smørgrav if (options.kbd_interactive_authentication) { 790cf2b5f3bSDag-Erling Smørgrav error("Keyboard-interactive authentication is disabled" 791cf2b5f3bSDag-Erling Smørgrav " to avoid man-in-the-middle attacks."); 792cf2b5f3bSDag-Erling Smørgrav options.kbd_interactive_authentication = 0; 793cf2b5f3bSDag-Erling Smørgrav options.challenge_response_authentication = 0; 794cf2b5f3bSDag-Erling Smørgrav } 795cf2b5f3bSDag-Erling Smørgrav if (options.challenge_response_authentication) { 796cf2b5f3bSDag-Erling Smørgrav error("Challenge/response authentication is disabled" 797cf2b5f3bSDag-Erling Smørgrav " to avoid man-in-the-middle attacks."); 798cf2b5f3bSDag-Erling Smørgrav options.challenge_response_authentication = 0; 799cf2b5f3bSDag-Erling Smørgrav } 800e8aafc91SKris Kennaway if (options.forward_agent) { 801af12a3e7SDag-Erling Smørgrav error("Agent forwarding is disabled to avoid " 802af12a3e7SDag-Erling Smørgrav "man-in-the-middle attacks."); 803e8aafc91SKris Kennaway options.forward_agent = 0; 804e8aafc91SKris Kennaway } 805ca3176e7SBrian Feldman if (options.forward_x11) { 806af12a3e7SDag-Erling Smørgrav error("X11 forwarding is disabled to avoid " 807af12a3e7SDag-Erling Smørgrav "man-in-the-middle attacks."); 808ca3176e7SBrian Feldman options.forward_x11 = 0; 809ca3176e7SBrian Feldman } 810af12a3e7SDag-Erling Smørgrav if (options.num_local_forwards > 0 || 811af12a3e7SDag-Erling Smørgrav options.num_remote_forwards > 0) { 812af12a3e7SDag-Erling Smørgrav error("Port forwarding is disabled to avoid " 813af12a3e7SDag-Erling Smørgrav "man-in-the-middle attacks."); 814af12a3e7SDag-Erling Smørgrav options.num_local_forwards = 815af12a3e7SDag-Erling Smørgrav options.num_remote_forwards = 0; 816ca3176e7SBrian Feldman } 817e8aafc91SKris Kennaway /* 818e8aafc91SKris Kennaway * XXX Should permit the user to change to use the new id. 819e8aafc91SKris Kennaway * This could be done by converting the host key to an 820e8aafc91SKris Kennaway * identifying sentence, tell that the host identifies itself 821e8aafc91SKris Kennaway * by that sentence, and ask the user if he/she whishes to 822e8aafc91SKris Kennaway * accept the authentication. 823e8aafc91SKris Kennaway */ 824e8aafc91SKris Kennaway break; 825f388f5efSDag-Erling Smørgrav case HOST_FOUND: 826f388f5efSDag-Erling Smørgrav fatal("internal error"); 827f388f5efSDag-Erling Smørgrav break; 828e8aafc91SKris Kennaway } 829ca3176e7SBrian Feldman 830ca3176e7SBrian Feldman if (options.check_host_ip && host_status != HOST_CHANGED && 831ca3176e7SBrian Feldman ip_status == HOST_CHANGED) { 832af12a3e7SDag-Erling Smørgrav snprintf(msg, sizeof(msg), 833af12a3e7SDag-Erling Smørgrav "Warning: the %s host key for '%.200s' " 834af12a3e7SDag-Erling Smørgrav "differs from the key for the IP address '%.128s'" 835af12a3e7SDag-Erling Smørgrav "\nOffending key for IP in %s:%d", 836af12a3e7SDag-Erling Smørgrav type, host, ip, ip_file, ip_line); 837af12a3e7SDag-Erling Smørgrav if (host_status == HOST_OK) { 838af12a3e7SDag-Erling Smørgrav len = strlen(msg); 839af12a3e7SDag-Erling Smørgrav snprintf(msg + len, sizeof(msg) - len, 840af12a3e7SDag-Erling Smørgrav "\nMatching host key in %s:%d", 841af12a3e7SDag-Erling Smørgrav host_file, host_line); 842af12a3e7SDag-Erling Smørgrav } 843ca3176e7SBrian Feldman if (options.strict_host_key_checking == 1) { 844cf2b5f3bSDag-Erling Smørgrav logit("%s", msg); 845af12a3e7SDag-Erling Smørgrav error("Exiting, you have requested strict checking."); 846af12a3e7SDag-Erling Smørgrav goto fail; 847ca3176e7SBrian Feldman } else if (options.strict_host_key_checking == 2) { 848af12a3e7SDag-Erling Smørgrav strlcat(msg, "\nAre you sure you want " 849af12a3e7SDag-Erling Smørgrav "to continue connecting (yes/no)? ", sizeof(msg)); 850af12a3e7SDag-Erling Smørgrav if (!confirm(msg)) 851af12a3e7SDag-Erling Smørgrav goto fail; 852af12a3e7SDag-Erling Smørgrav } else { 853cf2b5f3bSDag-Erling Smørgrav logit("%s", msg); 854ca3176e7SBrian Feldman } 855ca3176e7SBrian Feldman } 856ca3176e7SBrian Feldman 857e8aafc91SKris Kennaway xfree(ip); 858af12a3e7SDag-Erling Smørgrav return 0; 859af12a3e7SDag-Erling Smørgrav 860af12a3e7SDag-Erling Smørgrav fail: 861af12a3e7SDag-Erling Smørgrav xfree(ip); 862af12a3e7SDag-Erling Smørgrav return -1; 863e8aafc91SKris Kennaway } 864511b41d2SMark Murray 865cf2b5f3bSDag-Erling Smørgrav /* returns 0 if key verifies or -1 if key does NOT verify */ 866fe5fd017SMark Murray int 867af12a3e7SDag-Erling Smørgrav verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key) 868fe5fd017SMark Murray { 869af12a3e7SDag-Erling Smørgrav struct stat st; 8701ec0d754SDag-Erling Smørgrav int flags = 0; 871fe5fd017SMark Murray 8721ec0d754SDag-Erling Smørgrav if (options.verify_host_key_dns && 8731ec0d754SDag-Erling Smørgrav verify_host_key_dns(host, hostaddr, host_key, &flags) == 0) { 8741ec0d754SDag-Erling Smørgrav 8751ec0d754SDag-Erling Smørgrav if (flags & DNS_VERIFY_FOUND) { 8761ec0d754SDag-Erling Smørgrav 8771ec0d754SDag-Erling Smørgrav if (options.verify_host_key_dns == 1 && 8781ec0d754SDag-Erling Smørgrav flags & DNS_VERIFY_MATCH && 8791ec0d754SDag-Erling Smørgrav flags & DNS_VERIFY_SECURE) 880cf2b5f3bSDag-Erling Smørgrav return 0; 8811ec0d754SDag-Erling Smørgrav 8821ec0d754SDag-Erling Smørgrav if (flags & DNS_VERIFY_MATCH) { 8831ec0d754SDag-Erling Smørgrav matching_host_key_dns = 1; 8841ec0d754SDag-Erling Smørgrav } else { 8851ec0d754SDag-Erling Smørgrav warn_changed_key(host_key); 8861ec0d754SDag-Erling Smørgrav error("Update the SSHFP RR in DNS with the new " 8871ec0d754SDag-Erling Smørgrav "host key to get rid of this message."); 888cf2b5f3bSDag-Erling Smørgrav } 889cf2b5f3bSDag-Erling Smørgrav } 8901ec0d754SDag-Erling Smørgrav } 891cf2b5f3bSDag-Erling Smørgrav 892af12a3e7SDag-Erling Smørgrav /* return ok if the key can be found in an old keyfile */ 893af12a3e7SDag-Erling Smørgrav if (stat(options.system_hostfile2, &st) == 0 || 894af12a3e7SDag-Erling Smørgrav stat(options.user_hostfile2, &st) == 0) { 895af12a3e7SDag-Erling Smørgrav if (check_host_key(host, hostaddr, host_key, /*readonly*/ 1, 896af12a3e7SDag-Erling Smørgrav options.user_hostfile2, options.system_hostfile2) == 0) 897af12a3e7SDag-Erling Smørgrav return 0; 898fe5fd017SMark Murray } 899af12a3e7SDag-Erling Smørgrav return check_host_key(host, hostaddr, host_key, /*readonly*/ 0, 900af12a3e7SDag-Erling Smørgrav options.user_hostfile, options.system_hostfile); 901fe5fd017SMark Murray } 902fe5fd017SMark Murray 903511b41d2SMark Murray /* 904511b41d2SMark Murray * Starts a dialog with the server, and authenticates the current user on the 905511b41d2SMark Murray * server. This does not need any extra privileges. The basic connection 906511b41d2SMark Murray * to the server must already have been established before this is called. 907511b41d2SMark Murray * If login fails, this function prints an error and never returns. 908511b41d2SMark Murray * This function does not require super-user privileges. 909511b41d2SMark Murray */ 910511b41d2SMark Murray void 91180628bacSDag-Erling Smørgrav ssh_login(Sensitive *sensitive, const char *orighost, 912ca3176e7SBrian Feldman struct sockaddr *hostaddr, struct passwd *pw) 913511b41d2SMark Murray { 914511b41d2SMark Murray char *host, *cp; 915e8aafc91SKris Kennaway char *server_user, *local_user; 916e8aafc91SKris Kennaway 917e8aafc91SKris Kennaway local_user = xstrdup(pw->pw_name); 918e8aafc91SKris Kennaway server_user = options.user ? options.user : local_user; 919511b41d2SMark Murray 920511b41d2SMark Murray /* Convert the user-supplied hostname into all lowercase. */ 921511b41d2SMark Murray host = xstrdup(orighost); 922511b41d2SMark Murray for (cp = host; *cp; cp++) 923511b41d2SMark Murray if (isupper(*cp)) 924511b41d2SMark Murray *cp = tolower(*cp); 925511b41d2SMark Murray 926511b41d2SMark Murray /* Exchange protocol version identification strings with the server. */ 927511b41d2SMark Murray ssh_exchange_identification(); 928511b41d2SMark Murray 929511b41d2SMark Murray /* Put the connection into non-blocking mode. */ 930511b41d2SMark Murray packet_set_nonblocking(); 931511b41d2SMark Murray 932511b41d2SMark Murray /* key exchange */ 933511b41d2SMark Murray /* authenticate user */ 934e8aafc91SKris Kennaway if (compat20) { 935e8aafc91SKris Kennaway ssh_kex2(host, hostaddr); 93680628bacSDag-Erling Smørgrav ssh_userauth2(local_user, server_user, host, sensitive); 937e8aafc91SKris Kennaway } else { 938e8aafc91SKris Kennaway ssh_kex(host, hostaddr); 93980628bacSDag-Erling Smørgrav ssh_userauth1(local_user, server_user, host, sensitive); 940e8aafc91SKris Kennaway } 941511b41d2SMark Murray } 942e0fbb1d2SBrian Feldman 943e0fbb1d2SBrian Feldman void 944e0fbb1d2SBrian Feldman ssh_put_password(char *password) 945e0fbb1d2SBrian Feldman { 946e0fbb1d2SBrian Feldman int size; 947e0fbb1d2SBrian Feldman char *padded; 948e0fbb1d2SBrian Feldman 949ca3176e7SBrian Feldman if (datafellows & SSH_BUG_PASSWORDPAD) { 950af12a3e7SDag-Erling Smørgrav packet_put_cstring(password); 951ca3176e7SBrian Feldman return; 952ca3176e7SBrian Feldman } 953e0fbb1d2SBrian Feldman size = roundup(strlen(password) + 1, 32); 954e0fbb1d2SBrian Feldman padded = xmalloc(size); 955e0fbb1d2SBrian Feldman memset(padded, 0, size); 956e0fbb1d2SBrian Feldman strlcpy(padded, password, size); 957e0fbb1d2SBrian Feldman packet_put_string(padded, size); 958e0fbb1d2SBrian Feldman memset(padded, 0, size); 959e0fbb1d2SBrian Feldman xfree(padded); 960e0fbb1d2SBrian Feldman } 961f388f5efSDag-Erling Smørgrav 962f388f5efSDag-Erling Smørgrav static int 963f388f5efSDag-Erling Smørgrav show_key_from_file(const char *file, const char *host, int keytype) 964f388f5efSDag-Erling Smørgrav { 965f388f5efSDag-Erling Smørgrav Key *found; 966f388f5efSDag-Erling Smørgrav char *fp; 967f388f5efSDag-Erling Smørgrav int line, ret; 968f388f5efSDag-Erling Smørgrav 969f388f5efSDag-Erling Smørgrav found = key_new(keytype); 970f388f5efSDag-Erling Smørgrav if ((ret = lookup_key_in_hostfile_by_type(file, host, 971f388f5efSDag-Erling Smørgrav keytype, found, &line))) { 972f388f5efSDag-Erling Smørgrav fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX); 973cf2b5f3bSDag-Erling Smørgrav logit("WARNING: %s key found for host %s\n" 974f388f5efSDag-Erling Smørgrav "in %s:%d\n" 975f388f5efSDag-Erling Smørgrav "%s key fingerprint %s.", 976f388f5efSDag-Erling Smørgrav key_type(found), host, file, line, 977f388f5efSDag-Erling Smørgrav key_type(found), fp); 978f388f5efSDag-Erling Smørgrav xfree(fp); 979f388f5efSDag-Erling Smørgrav } 980f388f5efSDag-Erling Smørgrav key_free(found); 981f388f5efSDag-Erling Smørgrav return (ret); 982f388f5efSDag-Erling Smørgrav } 983f388f5efSDag-Erling Smørgrav 984f388f5efSDag-Erling Smørgrav /* print all known host keys for a given host, but skip keys of given type */ 985f388f5efSDag-Erling Smørgrav static int 986f388f5efSDag-Erling Smørgrav show_other_keys(const char *host, Key *key) 987f388f5efSDag-Erling Smørgrav { 988f388f5efSDag-Erling Smørgrav int type[] = { KEY_RSA1, KEY_RSA, KEY_DSA, -1}; 989f388f5efSDag-Erling Smørgrav int i, found = 0; 990f388f5efSDag-Erling Smørgrav 991f388f5efSDag-Erling Smørgrav for (i = 0; type[i] != -1; i++) { 992f388f5efSDag-Erling Smørgrav if (type[i] == key->type) 993f388f5efSDag-Erling Smørgrav continue; 994f388f5efSDag-Erling Smørgrav if (type[i] != KEY_RSA1 && 995f388f5efSDag-Erling Smørgrav show_key_from_file(options.user_hostfile2, host, type[i])) { 996f388f5efSDag-Erling Smørgrav found = 1; 997f388f5efSDag-Erling Smørgrav continue; 998f388f5efSDag-Erling Smørgrav } 999f388f5efSDag-Erling Smørgrav if (type[i] != KEY_RSA1 && 1000f388f5efSDag-Erling Smørgrav show_key_from_file(options.system_hostfile2, host, type[i])) { 1001f388f5efSDag-Erling Smørgrav found = 1; 1002f388f5efSDag-Erling Smørgrav continue; 1003f388f5efSDag-Erling Smørgrav } 1004f388f5efSDag-Erling Smørgrav if (show_key_from_file(options.user_hostfile, host, type[i])) { 1005f388f5efSDag-Erling Smørgrav found = 1; 1006f388f5efSDag-Erling Smørgrav continue; 1007f388f5efSDag-Erling Smørgrav } 1008f388f5efSDag-Erling Smørgrav if (show_key_from_file(options.system_hostfile, host, type[i])) { 1009f388f5efSDag-Erling Smørgrav found = 1; 1010f388f5efSDag-Erling Smørgrav continue; 1011f388f5efSDag-Erling Smørgrav } 1012f388f5efSDag-Erling Smørgrav debug2("no key of type %d for host %s", type[i], host); 1013f388f5efSDag-Erling Smørgrav } 1014f388f5efSDag-Erling Smørgrav return (found); 1015f388f5efSDag-Erling Smørgrav } 10161ec0d754SDag-Erling Smørgrav 10171ec0d754SDag-Erling Smørgrav static void 10181ec0d754SDag-Erling Smørgrav warn_changed_key(Key *host_key) 10191ec0d754SDag-Erling Smørgrav { 10201ec0d754SDag-Erling Smørgrav char *fp; 10211ec0d754SDag-Erling Smørgrav const char *type = key_type(host_key); 10221ec0d754SDag-Erling Smørgrav 10231ec0d754SDag-Erling Smørgrav fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); 10241ec0d754SDag-Erling Smørgrav 10251ec0d754SDag-Erling Smørgrav error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); 10261ec0d754SDag-Erling Smørgrav error("@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @"); 10271ec0d754SDag-Erling Smørgrav error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); 10281ec0d754SDag-Erling Smørgrav error("IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!"); 10291ec0d754SDag-Erling Smørgrav error("Someone could be eavesdropping on you right now (man-in-the-middle attack)!"); 10301ec0d754SDag-Erling Smørgrav error("It is also possible that the %s host key has just been changed.", type); 10311ec0d754SDag-Erling Smørgrav error("The fingerprint for the %s key sent by the remote host is\n%s.", 10321ec0d754SDag-Erling Smørgrav type, fp); 10331ec0d754SDag-Erling Smørgrav error("Please contact your system administrator."); 10341ec0d754SDag-Erling Smørgrav 10351ec0d754SDag-Erling Smørgrav xfree(fp); 10361ec0d754SDag-Erling Smørgrav } 1037b74df5b2SDag-Erling Smørgrav 1038b74df5b2SDag-Erling Smørgrav /* 1039b74df5b2SDag-Erling Smørgrav * Execute a local command 1040b74df5b2SDag-Erling Smørgrav */ 1041b74df5b2SDag-Erling Smørgrav int 1042b74df5b2SDag-Erling Smørgrav ssh_local_cmd(const char *args) 1043b74df5b2SDag-Erling Smørgrav { 1044b74df5b2SDag-Erling Smørgrav char *shell; 1045b74df5b2SDag-Erling Smørgrav pid_t pid; 1046b74df5b2SDag-Erling Smørgrav int status; 1047b74df5b2SDag-Erling Smørgrav 1048b74df5b2SDag-Erling Smørgrav if (!options.permit_local_command || 1049b74df5b2SDag-Erling Smørgrav args == NULL || !*args) 1050b74df5b2SDag-Erling Smørgrav return (1); 1051b74df5b2SDag-Erling Smørgrav 1052b74df5b2SDag-Erling Smørgrav if ((shell = getenv("SHELL")) == NULL) 1053b74df5b2SDag-Erling Smørgrav shell = _PATH_BSHELL; 1054b74df5b2SDag-Erling Smørgrav 1055b74df5b2SDag-Erling Smørgrav pid = fork(); 1056b74df5b2SDag-Erling Smørgrav if (pid == 0) { 1057b74df5b2SDag-Erling Smørgrav debug3("Executing %s -c \"%s\"", shell, args); 1058b74df5b2SDag-Erling Smørgrav execl(shell, shell, "-c", args, (char *)NULL); 1059b74df5b2SDag-Erling Smørgrav error("Couldn't execute %s -c \"%s\": %s", 1060b74df5b2SDag-Erling Smørgrav shell, args, strerror(errno)); 1061b74df5b2SDag-Erling Smørgrav _exit(1); 1062b74df5b2SDag-Erling Smørgrav } else if (pid == -1) 1063b74df5b2SDag-Erling Smørgrav fatal("fork failed: %.100s", strerror(errno)); 1064b74df5b2SDag-Erling Smørgrav while (waitpid(pid, &status, 0) == -1) 1065b74df5b2SDag-Erling Smørgrav if (errno != EINTR) 1066b74df5b2SDag-Erling Smørgrav fatal("Couldn't wait for child: %s", strerror(errno)); 1067b74df5b2SDag-Erling Smørgrav 1068b74df5b2SDag-Erling Smørgrav if (!WIFEXITED(status)) 1069b74df5b2SDag-Erling Smørgrav return (1); 1070b74df5b2SDag-Erling Smørgrav 1071b74df5b2SDag-Erling Smørgrav return (WEXITSTATUS(status)); 1072b74df5b2SDag-Erling Smørgrav } 1073