1*2e828220SEd Maste /* $OpenBSD: sshconnect.c,v 1.360 2022/11/03 21:59:20 djm Exp $ */ 2511b41d2SMark Murray /* 3511b41d2SMark Murray * Author: Tatu Ylonen <ylo@cs.hut.fi> 4511b41d2SMark Murray * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 5511b41d2SMark Murray * All rights reserved 6511b41d2SMark Murray * Code to connect to a remote host, and to perform the client side of the 7511b41d2SMark Murray * login (authentication) dialog. 842f71286SMark Murray * 9c2d3a559SKris Kennaway * As far as I am concerned, the code I have written for this software 10c2d3a559SKris Kennaway * can be used freely for any purpose. Any derived versions of this 11c2d3a559SKris Kennaway * software must be clearly marked as such, and if the derived work is 12c2d3a559SKris Kennaway * incompatible with the protocol description in the RFC file, it must be 13c2d3a559SKris Kennaway * called by a name other than "ssh" or "Secure Shell". 14511b41d2SMark Murray */ 15511b41d2SMark Murray 16511b41d2SMark Murray #include "includes.h" 17511b41d2SMark Murray 18333ee039SDag-Erling Smørgrav #include <sys/types.h> 19333ee039SDag-Erling Smørgrav #include <sys/wait.h> 20333ee039SDag-Erling Smørgrav #include <sys/stat.h> 21333ee039SDag-Erling Smørgrav #include <sys/socket.h> 22333ee039SDag-Erling Smørgrav #ifdef HAVE_SYS_TIME_H 23333ee039SDag-Erling Smørgrav # include <sys/time.h> 24333ee039SDag-Erling Smørgrav #endif 25e8aafc91SKris Kennaway 2647dd1d1bSDag-Erling Smørgrav #include <net/if.h> 27333ee039SDag-Erling Smørgrav #include <netinet/in.h> 28333ee039SDag-Erling Smørgrav #include <arpa/inet.h> 29333ee039SDag-Erling Smørgrav 30333ee039SDag-Erling Smørgrav #include <ctype.h> 31333ee039SDag-Erling Smørgrav #include <errno.h> 32b15c8340SDag-Erling Smørgrav #include <fcntl.h> 3319261079SEd Maste #include <limits.h> 34333ee039SDag-Erling Smørgrav #include <netdb.h> 35333ee039SDag-Erling Smørgrav #ifdef HAVE_PATHS_H 36333ee039SDag-Erling Smørgrav #include <paths.h> 37333ee039SDag-Erling Smørgrav #endif 38333ee039SDag-Erling Smørgrav #include <pwd.h> 394f52dfbbSDag-Erling Smørgrav #ifdef HAVE_POLL_H 404f52dfbbSDag-Erling Smørgrav #include <poll.h> 414f52dfbbSDag-Erling Smørgrav #endif 424a421b63SDag-Erling Smørgrav #include <signal.h> 43333ee039SDag-Erling Smørgrav #include <stdio.h> 44333ee039SDag-Erling Smørgrav #include <stdlib.h> 4519261079SEd Maste #include <stdarg.h> 46333ee039SDag-Erling Smørgrav #include <string.h> 47333ee039SDag-Erling Smørgrav #include <unistd.h> 4847dd1d1bSDag-Erling Smørgrav #ifdef HAVE_IFADDRS_H 4947dd1d1bSDag-Erling Smørgrav # include <ifaddrs.h> 5047dd1d1bSDag-Erling Smørgrav #endif 51333ee039SDag-Erling Smørgrav 52511b41d2SMark Murray #include "xmalloc.h" 53333ee039SDag-Erling Smørgrav #include "hostfile.h" 54333ee039SDag-Erling Smørgrav #include "ssh.h" 55190cef3dSDag-Erling Smørgrav #include "sshbuf.h" 56511b41d2SMark Murray #include "packet.h" 57511b41d2SMark Murray #include "compat.h" 58190cef3dSDag-Erling Smørgrav #include "sshkey.h" 59e8aafc91SKris Kennaway #include "sshconnect.h" 60ca3176e7SBrian Feldman #include "log.h" 61a0ee8cc6SDag-Erling Smørgrav #include "misc.h" 62ca3176e7SBrian Feldman #include "readconf.h" 63ca3176e7SBrian Feldman #include "atomicio.h" 64cf2b5f3bSDag-Erling Smørgrav #include "dns.h" 65f7167e0eSDag-Erling Smørgrav #include "monitor_fdpass.h" 66b15c8340SDag-Erling Smørgrav #include "ssh2.h" 67333ee039SDag-Erling Smørgrav #include "version.h" 68bc5531deSDag-Erling Smørgrav #include "authfile.h" 69bc5531deSDag-Erling Smørgrav #include "ssherr.h" 70acc1a9efSDag-Erling Smørgrav #include "authfd.h" 7119261079SEd Maste #include "kex.h" 72cf2b5f3bSDag-Erling Smørgrav 734f52dfbbSDag-Erling Smørgrav struct sshkey *previous_host_key = NULL; 74511b41d2SMark Murray 75b74df5b2SDag-Erling Smørgrav static int matching_host_key_dns = 0; 76cf2b5f3bSDag-Erling Smørgrav 774a421b63SDag-Erling Smørgrav static pid_t proxy_command_pid = 0; 784a421b63SDag-Erling Smørgrav 7980628bacSDag-Erling Smørgrav /* import */ 8019261079SEd Maste extern int debug_flag; 81511b41d2SMark Murray extern Options options; 82511b41d2SMark Murray extern char *__progname; 83511b41d2SMark Murray 844f52dfbbSDag-Erling Smørgrav static int show_other_keys(struct hostkeys *, struct sshkey *); 854f52dfbbSDag-Erling Smørgrav static void warn_changed_key(struct sshkey *); 86ca3176e7SBrian Feldman 87f7167e0eSDag-Erling Smørgrav /* Expand a proxy command */ 88f7167e0eSDag-Erling Smørgrav static char * 89f7167e0eSDag-Erling Smørgrav expand_proxy_command(const char *proxy_command, const char *user, 9019261079SEd Maste const char *host, const char *host_arg, int port) 91f7167e0eSDag-Erling Smørgrav { 92f7167e0eSDag-Erling Smørgrav char *tmp, *ret, strport[NI_MAXSERV]; 9319261079SEd Maste const char *keyalias = options.host_key_alias ? 9419261079SEd Maste options.host_key_alias : host_arg; 95f7167e0eSDag-Erling Smørgrav 96f7167e0eSDag-Erling Smørgrav snprintf(strport, sizeof strport, "%d", port); 97f7167e0eSDag-Erling Smørgrav xasprintf(&tmp, "exec %s", proxy_command); 9819261079SEd Maste ret = percent_expand(tmp, 9919261079SEd Maste "h", host, 10019261079SEd Maste "k", keyalias, 10119261079SEd Maste "n", host_arg, 10219261079SEd Maste "p", strport, 10319261079SEd Maste "r", options.user, 10419261079SEd Maste (char *)NULL); 105f7167e0eSDag-Erling Smørgrav free(tmp); 106f7167e0eSDag-Erling Smørgrav return ret; 107f7167e0eSDag-Erling Smørgrav } 108f7167e0eSDag-Erling Smørgrav 109f7167e0eSDag-Erling Smørgrav /* 110f7167e0eSDag-Erling Smørgrav * Connect to the given ssh server using a proxy command that passes a 111f7167e0eSDag-Erling Smørgrav * a connected fd back to us. 112f7167e0eSDag-Erling Smørgrav */ 113f7167e0eSDag-Erling Smørgrav static int 11419261079SEd Maste ssh_proxy_fdpass_connect(struct ssh *ssh, const char *host, 11519261079SEd Maste const char *host_arg, u_short port, const char *proxy_command) 116f7167e0eSDag-Erling Smørgrav { 117f7167e0eSDag-Erling Smørgrav char *command_string; 118f7167e0eSDag-Erling Smørgrav int sp[2], sock; 119f7167e0eSDag-Erling Smørgrav pid_t pid; 120f7167e0eSDag-Erling Smørgrav char *shell; 121f7167e0eSDag-Erling Smørgrav 122f7167e0eSDag-Erling Smørgrav if ((shell = getenv("SHELL")) == NULL) 123f7167e0eSDag-Erling Smørgrav shell = _PATH_BSHELL; 124f7167e0eSDag-Erling Smørgrav 12519261079SEd Maste if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) == -1) 126f7167e0eSDag-Erling Smørgrav fatal("Could not create socketpair to communicate with " 127f7167e0eSDag-Erling Smørgrav "proxy dialer: %.100s", strerror(errno)); 128f7167e0eSDag-Erling Smørgrav 129f7167e0eSDag-Erling Smørgrav command_string = expand_proxy_command(proxy_command, options.user, 13019261079SEd Maste host, host_arg, port); 131f7167e0eSDag-Erling Smørgrav debug("Executing proxy dialer command: %.500s", command_string); 132f7167e0eSDag-Erling Smørgrav 133f7167e0eSDag-Erling Smørgrav /* Fork and execute the proxy command. */ 134f7167e0eSDag-Erling Smørgrav if ((pid = fork()) == 0) { 135f7167e0eSDag-Erling Smørgrav char *argv[10]; 136f7167e0eSDag-Erling Smørgrav 137f7167e0eSDag-Erling Smørgrav close(sp[1]); 138f7167e0eSDag-Erling Smørgrav /* Redirect stdin and stdout. */ 139f7167e0eSDag-Erling Smørgrav if (sp[0] != 0) { 14019261079SEd Maste if (dup2(sp[0], 0) == -1) 141f7167e0eSDag-Erling Smørgrav perror("dup2 stdin"); 142f7167e0eSDag-Erling Smørgrav } 143f7167e0eSDag-Erling Smørgrav if (sp[0] != 1) { 14419261079SEd Maste if (dup2(sp[0], 1) == -1) 145f7167e0eSDag-Erling Smørgrav perror("dup2 stdout"); 146f7167e0eSDag-Erling Smørgrav } 147f7167e0eSDag-Erling Smørgrav if (sp[0] >= 2) 148f7167e0eSDag-Erling Smørgrav close(sp[0]); 149f7167e0eSDag-Erling Smørgrav 150f7167e0eSDag-Erling Smørgrav /* 15119261079SEd Maste * Stderr is left for non-ControlPersist connections is so 15219261079SEd Maste * error messages may be printed on the user's terminal. 153f7167e0eSDag-Erling Smørgrav */ 15419261079SEd Maste if (!debug_flag && options.control_path != NULL && 15519261079SEd Maste options.control_persist && stdfd_devnull(0, 0, 1) == -1) 15619261079SEd Maste error_f("stdfd_devnull failed"); 15719261079SEd Maste 158f7167e0eSDag-Erling Smørgrav argv[0] = shell; 159f7167e0eSDag-Erling Smørgrav argv[1] = "-c"; 160f7167e0eSDag-Erling Smørgrav argv[2] = command_string; 161f7167e0eSDag-Erling Smørgrav argv[3] = NULL; 162f7167e0eSDag-Erling Smørgrav 163f7167e0eSDag-Erling Smørgrav /* 164f7167e0eSDag-Erling Smørgrav * Execute the proxy command. 165f7167e0eSDag-Erling Smørgrav * Note that we gave up any extra privileges above. 166f7167e0eSDag-Erling Smørgrav */ 167f7167e0eSDag-Erling Smørgrav execv(argv[0], argv); 168f7167e0eSDag-Erling Smørgrav perror(argv[0]); 169f7167e0eSDag-Erling Smørgrav exit(1); 170f7167e0eSDag-Erling Smørgrav } 171f7167e0eSDag-Erling Smørgrav /* Parent. */ 17219261079SEd Maste if (pid == -1) 173f7167e0eSDag-Erling Smørgrav fatal("fork failed: %.100s", strerror(errno)); 174f7167e0eSDag-Erling Smørgrav close(sp[0]); 175f7167e0eSDag-Erling Smørgrav free(command_string); 176f7167e0eSDag-Erling Smørgrav 177f7167e0eSDag-Erling Smørgrav if ((sock = mm_receive_fd(sp[1])) == -1) 178f7167e0eSDag-Erling Smørgrav fatal("proxy dialer did not pass back a connection"); 179acc1a9efSDag-Erling Smørgrav close(sp[1]); 180f7167e0eSDag-Erling Smørgrav 181f7167e0eSDag-Erling Smørgrav while (waitpid(pid, NULL, 0) == -1) 182f7167e0eSDag-Erling Smørgrav if (errno != EINTR) 183f7167e0eSDag-Erling Smørgrav fatal("Couldn't wait for child: %s", strerror(errno)); 184f7167e0eSDag-Erling Smørgrav 185f7167e0eSDag-Erling Smørgrav /* Set the connection file descriptors. */ 1864f52dfbbSDag-Erling Smørgrav if (ssh_packet_set_connection(ssh, sock, sock) == NULL) 1874f52dfbbSDag-Erling Smørgrav return -1; /* ssh_packet_set_connection logs error */ 188f7167e0eSDag-Erling Smørgrav 189f7167e0eSDag-Erling Smørgrav return 0; 190f7167e0eSDag-Erling Smørgrav } 191f7167e0eSDag-Erling Smørgrav 192511b41d2SMark Murray /* 193511b41d2SMark Murray * Connect to the given ssh server using a proxy command. 194511b41d2SMark Murray */ 195af12a3e7SDag-Erling Smørgrav static int 19619261079SEd Maste ssh_proxy_connect(struct ssh *ssh, const char *host, const char *host_arg, 19719261079SEd Maste u_short port, const char *proxy_command) 198511b41d2SMark Murray { 199f7167e0eSDag-Erling Smørgrav char *command_string; 200511b41d2SMark Murray int pin[2], pout[2]; 201e8aafc91SKris Kennaway pid_t pid; 202f7167e0eSDag-Erling Smørgrav char *shell; 203420bce64SDag-Erling Smørgrav 2044a421b63SDag-Erling Smørgrav if ((shell = getenv("SHELL")) == NULL || *shell == '\0') 205d4af9e69SDag-Erling Smørgrav shell = _PATH_BSHELL; 206511b41d2SMark Murray 207511b41d2SMark Murray /* Create pipes for communicating with the proxy. */ 20819261079SEd Maste if (pipe(pin) == -1 || pipe(pout) == -1) 209511b41d2SMark Murray fatal("Could not create pipes to communicate with the proxy: %.100s", 210511b41d2SMark Murray strerror(errno)); 211511b41d2SMark Murray 212f7167e0eSDag-Erling Smørgrav command_string = expand_proxy_command(proxy_command, options.user, 21319261079SEd Maste host, host_arg, port); 214511b41d2SMark Murray debug("Executing proxy command: %.500s", command_string); 215511b41d2SMark Murray 216511b41d2SMark Murray /* Fork and execute the proxy command. */ 217511b41d2SMark Murray if ((pid = fork()) == 0) { 218511b41d2SMark Murray char *argv[10]; 219511b41d2SMark Murray 220511b41d2SMark Murray /* Redirect stdin and stdout. */ 221511b41d2SMark Murray close(pin[1]); 222511b41d2SMark Murray if (pin[0] != 0) { 22319261079SEd Maste if (dup2(pin[0], 0) == -1) 224511b41d2SMark Murray perror("dup2 stdin"); 225511b41d2SMark Murray close(pin[0]); 226511b41d2SMark Murray } 227511b41d2SMark Murray close(pout[0]); 22819261079SEd Maste if (dup2(pout[1], 1) == -1) 229511b41d2SMark Murray perror("dup2 stdout"); 230511b41d2SMark Murray /* Cannot be 1 because pin allocated two descriptors. */ 231511b41d2SMark Murray close(pout[1]); 232511b41d2SMark Murray 23319261079SEd Maste /* 23419261079SEd Maste * Stderr is left for non-ControlPersist connections is so 23519261079SEd Maste * error messages may be printed on the user's terminal. 23619261079SEd Maste */ 23719261079SEd Maste if (!debug_flag && options.control_path != NULL && 23819261079SEd Maste options.control_persist && stdfd_devnull(0, 0, 1) == -1) 23919261079SEd Maste error_f("stdfd_devnull failed"); 24019261079SEd Maste 241d4af9e69SDag-Erling Smørgrav argv[0] = shell; 242511b41d2SMark Murray argv[1] = "-c"; 243511b41d2SMark Murray argv[2] = command_string; 244511b41d2SMark Murray argv[3] = NULL; 245511b41d2SMark Murray 24619261079SEd Maste /* 24719261079SEd Maste * Execute the proxy command. Note that we gave up any 24819261079SEd Maste * extra privileges above. 24919261079SEd Maste */ 25019261079SEd Maste ssh_signal(SIGPIPE, SIG_DFL); 251ca3176e7SBrian Feldman execv(argv[0], argv); 252ca3176e7SBrian Feldman perror(argv[0]); 253511b41d2SMark Murray exit(1); 254511b41d2SMark Murray } 255511b41d2SMark Murray /* Parent. */ 25619261079SEd Maste if (pid == -1) 257511b41d2SMark Murray fatal("fork failed: %.100s", strerror(errno)); 258f388f5efSDag-Erling Smørgrav else 259f388f5efSDag-Erling Smørgrav proxy_command_pid = pid; /* save pid to clean up later */ 260511b41d2SMark Murray 261511b41d2SMark Murray /* Close child side of the descriptors. */ 262511b41d2SMark Murray close(pin[0]); 263511b41d2SMark Murray close(pout[1]); 264511b41d2SMark Murray 265511b41d2SMark Murray /* Free the command name. */ 266e4a9863fSDag-Erling Smørgrav free(command_string); 267511b41d2SMark Murray 268511b41d2SMark Murray /* Set the connection file descriptors. */ 2694f52dfbbSDag-Erling Smørgrav if (ssh_packet_set_connection(ssh, pout[0], pin[1]) == NULL) 2704f52dfbbSDag-Erling Smørgrav return -1; /* ssh_packet_set_connection logs error */ 271511b41d2SMark Murray 272af12a3e7SDag-Erling Smørgrav return 0; 273511b41d2SMark Murray } 274511b41d2SMark Murray 2754a421b63SDag-Erling Smørgrav void 2764a421b63SDag-Erling Smørgrav ssh_kill_proxy_command(void) 2774a421b63SDag-Erling Smørgrav { 2784a421b63SDag-Erling Smørgrav /* 2794a421b63SDag-Erling Smørgrav * Send SIGHUP to proxy command if used. We don't wait() in 2804a421b63SDag-Erling Smørgrav * case it hangs and instead rely on init to reap the child 2814a421b63SDag-Erling Smørgrav */ 2824a421b63SDag-Erling Smørgrav if (proxy_command_pid > 1) 2834a421b63SDag-Erling Smørgrav kill(proxy_command_pid, SIGHUP); 2844a421b63SDag-Erling Smørgrav } 2854a421b63SDag-Erling Smørgrav 28647dd1d1bSDag-Erling Smørgrav #ifdef HAVE_IFADDRS_H 28747dd1d1bSDag-Erling Smørgrav /* 28847dd1d1bSDag-Erling Smørgrav * Search a interface address list (returned from getifaddrs(3)) for an 289190cef3dSDag-Erling Smørgrav * address that matches the desired address family on the specified interface. 29047dd1d1bSDag-Erling Smørgrav * Returns 0 and fills in *resultp and *rlenp on success. Returns -1 on failure. 29147dd1d1bSDag-Erling Smørgrav */ 29247dd1d1bSDag-Erling Smørgrav static int 29347dd1d1bSDag-Erling Smørgrav check_ifaddrs(const char *ifname, int af, const struct ifaddrs *ifaddrs, 29447dd1d1bSDag-Erling Smørgrav struct sockaddr_storage *resultp, socklen_t *rlenp) 29547dd1d1bSDag-Erling Smørgrav { 29647dd1d1bSDag-Erling Smørgrav struct sockaddr_in6 *sa6; 29747dd1d1bSDag-Erling Smørgrav struct sockaddr_in *sa; 29847dd1d1bSDag-Erling Smørgrav struct in6_addr *v6addr; 29947dd1d1bSDag-Erling Smørgrav const struct ifaddrs *ifa; 30047dd1d1bSDag-Erling Smørgrav int allow_local; 30147dd1d1bSDag-Erling Smørgrav 30247dd1d1bSDag-Erling Smørgrav /* 30347dd1d1bSDag-Erling Smørgrav * Prefer addresses that are not loopback or linklocal, but use them 30447dd1d1bSDag-Erling Smørgrav * if nothing else matches. 30547dd1d1bSDag-Erling Smørgrav */ 30647dd1d1bSDag-Erling Smørgrav for (allow_local = 0; allow_local < 2; allow_local++) { 30747dd1d1bSDag-Erling Smørgrav for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { 30847dd1d1bSDag-Erling Smørgrav if (ifa->ifa_addr == NULL || ifa->ifa_name == NULL || 30947dd1d1bSDag-Erling Smørgrav (ifa->ifa_flags & IFF_UP) == 0 || 31047dd1d1bSDag-Erling Smørgrav ifa->ifa_addr->sa_family != af || 31147dd1d1bSDag-Erling Smørgrav strcmp(ifa->ifa_name, options.bind_interface) != 0) 31247dd1d1bSDag-Erling Smørgrav continue; 31347dd1d1bSDag-Erling Smørgrav switch (ifa->ifa_addr->sa_family) { 31447dd1d1bSDag-Erling Smørgrav case AF_INET: 31547dd1d1bSDag-Erling Smørgrav sa = (struct sockaddr_in *)ifa->ifa_addr; 31647dd1d1bSDag-Erling Smørgrav if (!allow_local && sa->sin_addr.s_addr == 31747dd1d1bSDag-Erling Smørgrav htonl(INADDR_LOOPBACK)) 31847dd1d1bSDag-Erling Smørgrav continue; 31947dd1d1bSDag-Erling Smørgrav if (*rlenp < sizeof(struct sockaddr_in)) { 32019261079SEd Maste error_f("v4 addr doesn't fit"); 32147dd1d1bSDag-Erling Smørgrav return -1; 32247dd1d1bSDag-Erling Smørgrav } 32347dd1d1bSDag-Erling Smørgrav *rlenp = sizeof(struct sockaddr_in); 32447dd1d1bSDag-Erling Smørgrav memcpy(resultp, sa, *rlenp); 32547dd1d1bSDag-Erling Smørgrav return 0; 32647dd1d1bSDag-Erling Smørgrav case AF_INET6: 32747dd1d1bSDag-Erling Smørgrav sa6 = (struct sockaddr_in6 *)ifa->ifa_addr; 32847dd1d1bSDag-Erling Smørgrav v6addr = &sa6->sin6_addr; 32947dd1d1bSDag-Erling Smørgrav if (!allow_local && 33047dd1d1bSDag-Erling Smørgrav (IN6_IS_ADDR_LINKLOCAL(v6addr) || 33147dd1d1bSDag-Erling Smørgrav IN6_IS_ADDR_LOOPBACK(v6addr))) 33247dd1d1bSDag-Erling Smørgrav continue; 33347dd1d1bSDag-Erling Smørgrav if (*rlenp < sizeof(struct sockaddr_in6)) { 33419261079SEd Maste error_f("v6 addr doesn't fit"); 33547dd1d1bSDag-Erling Smørgrav return -1; 33647dd1d1bSDag-Erling Smørgrav } 33747dd1d1bSDag-Erling Smørgrav *rlenp = sizeof(struct sockaddr_in6); 33847dd1d1bSDag-Erling Smørgrav memcpy(resultp, sa6, *rlenp); 33947dd1d1bSDag-Erling Smørgrav return 0; 34047dd1d1bSDag-Erling Smørgrav } 34147dd1d1bSDag-Erling Smørgrav } 34247dd1d1bSDag-Erling Smørgrav } 34347dd1d1bSDag-Erling Smørgrav return -1; 34447dd1d1bSDag-Erling Smørgrav } 34547dd1d1bSDag-Erling Smørgrav #endif 34647dd1d1bSDag-Erling Smørgrav 347511b41d2SMark Murray /* 348190cef3dSDag-Erling Smørgrav * Creates a socket for use as the ssh connection. 349511b41d2SMark Murray */ 350af12a3e7SDag-Erling Smørgrav static int 351190cef3dSDag-Erling Smørgrav ssh_create_socket(struct addrinfo *ai) 352511b41d2SMark Murray { 353190cef3dSDag-Erling Smørgrav int sock, r; 35447dd1d1bSDag-Erling Smørgrav struct sockaddr_storage bindaddr; 35547dd1d1bSDag-Erling Smørgrav socklen_t bindaddrlen = 0; 356b83788ffSDag-Erling Smørgrav struct addrinfo hints, *res = NULL; 35747dd1d1bSDag-Erling Smørgrav #ifdef HAVE_IFADDRS_H 35847dd1d1bSDag-Erling Smørgrav struct ifaddrs *ifaddrs = NULL; 35947dd1d1bSDag-Erling Smørgrav #endif 36047dd1d1bSDag-Erling Smørgrav char ntop[NI_MAXHOST]; 361511b41d2SMark Murray 362cf2b5f3bSDag-Erling Smørgrav sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); 36319261079SEd Maste if (sock == -1) { 364f7167e0eSDag-Erling Smørgrav error("socket: %s", strerror(errno)); 365b15c8340SDag-Erling Smørgrav return -1; 366b15c8340SDag-Erling Smørgrav } 367b15c8340SDag-Erling Smørgrav fcntl(sock, F_SETFD, FD_CLOEXEC); 368af12a3e7SDag-Erling Smørgrav 36919261079SEd Maste /* Use interactive QOS (if specified) until authentication completed */ 37019261079SEd Maste if (options.ip_qos_interactive != INT_MAX) 37119261079SEd Maste set_sock_tos(sock, options.ip_qos_interactive); 37219261079SEd Maste 373af12a3e7SDag-Erling Smørgrav /* Bind the socket to an alternative local IP address */ 374190cef3dSDag-Erling Smørgrav if (options.bind_address == NULL && options.bind_interface == NULL) 375af12a3e7SDag-Erling Smørgrav return sock; 376af12a3e7SDag-Erling Smørgrav 37747dd1d1bSDag-Erling Smørgrav if (options.bind_address != NULL) { 378af12a3e7SDag-Erling Smørgrav memset(&hints, 0, sizeof(hints)); 379cf2b5f3bSDag-Erling Smørgrav hints.ai_family = ai->ai_family; 380cf2b5f3bSDag-Erling Smørgrav hints.ai_socktype = ai->ai_socktype; 381cf2b5f3bSDag-Erling Smørgrav hints.ai_protocol = ai->ai_protocol; 382af12a3e7SDag-Erling Smørgrav hints.ai_flags = AI_PASSIVE; 38347dd1d1bSDag-Erling Smørgrav if ((r = getaddrinfo(options.bind_address, NULL, 38447dd1d1bSDag-Erling Smørgrav &hints, &res)) != 0) { 385af12a3e7SDag-Erling Smørgrav error("getaddrinfo: %s: %s", options.bind_address, 38647dd1d1bSDag-Erling Smørgrav ssh_gai_strerror(r)); 38747dd1d1bSDag-Erling Smørgrav goto fail; 388511b41d2SMark Murray } 38947dd1d1bSDag-Erling Smørgrav if (res == NULL) { 39047dd1d1bSDag-Erling Smørgrav error("getaddrinfo: no addrs"); 39147dd1d1bSDag-Erling Smørgrav goto fail; 39247dd1d1bSDag-Erling Smørgrav } 39347dd1d1bSDag-Erling Smørgrav memcpy(&bindaddr, res->ai_addr, res->ai_addrlen); 39447dd1d1bSDag-Erling Smørgrav bindaddrlen = res->ai_addrlen; 39547dd1d1bSDag-Erling Smørgrav } else if (options.bind_interface != NULL) { 39647dd1d1bSDag-Erling Smørgrav #ifdef HAVE_IFADDRS_H 39747dd1d1bSDag-Erling Smørgrav if ((r = getifaddrs(&ifaddrs)) != 0) { 39847dd1d1bSDag-Erling Smørgrav error("getifaddrs: %s: %s", options.bind_interface, 39947dd1d1bSDag-Erling Smørgrav strerror(errno)); 40047dd1d1bSDag-Erling Smørgrav goto fail; 40147dd1d1bSDag-Erling Smørgrav } 40247dd1d1bSDag-Erling Smørgrav bindaddrlen = sizeof(bindaddr); 40347dd1d1bSDag-Erling Smørgrav if (check_ifaddrs(options.bind_interface, ai->ai_family, 40447dd1d1bSDag-Erling Smørgrav ifaddrs, &bindaddr, &bindaddrlen) != 0) { 40547dd1d1bSDag-Erling Smørgrav logit("getifaddrs: %s: no suitable addresses", 40647dd1d1bSDag-Erling Smørgrav options.bind_interface); 40747dd1d1bSDag-Erling Smørgrav goto fail; 40847dd1d1bSDag-Erling Smørgrav } 40947dd1d1bSDag-Erling Smørgrav #else 41047dd1d1bSDag-Erling Smørgrav error("BindInterface not supported on this platform."); 41147dd1d1bSDag-Erling Smørgrav #endif 41247dd1d1bSDag-Erling Smørgrav } 41347dd1d1bSDag-Erling Smørgrav if ((r = getnameinfo((struct sockaddr *)&bindaddr, bindaddrlen, 41447dd1d1bSDag-Erling Smørgrav ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST)) != 0) { 41519261079SEd Maste error_f("getnameinfo failed: %s", ssh_gai_strerror(r)); 41647dd1d1bSDag-Erling Smørgrav goto fail; 417b83788ffSDag-Erling Smørgrav } 418190cef3dSDag-Erling Smørgrav if (bind(sock, (struct sockaddr *)&bindaddr, bindaddrlen) != 0) { 41947dd1d1bSDag-Erling Smørgrav error("bind %s: %s", ntop, strerror(errno)); 42047dd1d1bSDag-Erling Smørgrav goto fail; 42147dd1d1bSDag-Erling Smørgrav } 42219261079SEd Maste debug_f("bound to %s", ntop); 42347dd1d1bSDag-Erling Smørgrav /* success */ 42447dd1d1bSDag-Erling Smørgrav goto out; 425f7167e0eSDag-Erling Smørgrav fail: 426af12a3e7SDag-Erling Smørgrav close(sock); 42747dd1d1bSDag-Erling Smørgrav sock = -1; 42847dd1d1bSDag-Erling Smørgrav out: 429b83788ffSDag-Erling Smørgrav if (res != NULL) 430af12a3e7SDag-Erling Smørgrav freeaddrinfo(res); 43147dd1d1bSDag-Erling Smørgrav #ifdef HAVE_IFADDRS_H 43247dd1d1bSDag-Erling Smørgrav if (ifaddrs != NULL) 43347dd1d1bSDag-Erling Smørgrav freeifaddrs(ifaddrs); 43447dd1d1bSDag-Erling Smørgrav #endif 435511b41d2SMark Murray return sock; 436511b41d2SMark Murray } 437511b41d2SMark Murray 4384f52dfbbSDag-Erling Smørgrav /* 439511b41d2SMark Murray * Opens a TCP/IP connection to the remote server on the given host. 440511b41d2SMark Murray * The address of the remote host will be returned in hostaddr. 441190cef3dSDag-Erling Smørgrav * If port is 0, the default port will be used. 442511b41d2SMark Murray * Connection_attempts specifies the maximum number of tries (one per 443511b41d2SMark Murray * second). If proxy_command is non-NULL, it specifies the command (with %h 444511b41d2SMark Murray * and %p substituted for host and port, respectively) to use to contact 445511b41d2SMark Murray * the daemon. 446511b41d2SMark Murray */ 447f7167e0eSDag-Erling Smørgrav static int 4484f52dfbbSDag-Erling Smørgrav ssh_connect_direct(struct ssh *ssh, const char *host, struct addrinfo *aitop, 44919261079SEd Maste struct sockaddr_storage *hostaddr, u_short port, int connection_attempts, 45019261079SEd Maste int *timeout_ms, int want_keepalive) 451511b41d2SMark Murray { 45219261079SEd Maste int on = 1, saved_timeout_ms = *timeout_ms; 45347dd1d1bSDag-Erling Smørgrav int oerrno, sock = -1, attempt; 454ca3176e7SBrian Feldman char ntop[NI_MAXHOST], strport[NI_MAXSERV]; 455f7167e0eSDag-Erling Smørgrav struct addrinfo *ai; 456511b41d2SMark Murray 45719261079SEd Maste debug3_f("entering"); 458acc1a9efSDag-Erling Smørgrav memset(ntop, 0, sizeof(ntop)); 459acc1a9efSDag-Erling Smørgrav memset(strport, 0, sizeof(strport)); 460511b41d2SMark Murray 461333ee039SDag-Erling Smørgrav for (attempt = 0; attempt < connection_attempts; attempt++) { 46262efe23aSDag-Erling Smørgrav if (attempt > 0) { 46362efe23aSDag-Erling Smørgrav /* Sleep a moment before retrying. */ 46462efe23aSDag-Erling Smørgrav sleep(1); 465511b41d2SMark Murray debug("Trying again..."); 46662efe23aSDag-Erling Smørgrav } 467333ee039SDag-Erling Smørgrav /* 468333ee039SDag-Erling Smørgrav * Loop through addresses for this host, and try each one in 469333ee039SDag-Erling Smørgrav * sequence until the connection succeeds. 470333ee039SDag-Erling Smørgrav */ 471511b41d2SMark Murray for (ai = aitop; ai; ai = ai->ai_next) { 472f7167e0eSDag-Erling Smørgrav if (ai->ai_family != AF_INET && 47347dd1d1bSDag-Erling Smørgrav ai->ai_family != AF_INET6) { 47447dd1d1bSDag-Erling Smørgrav errno = EAFNOSUPPORT; 475511b41d2SMark Murray continue; 47647dd1d1bSDag-Erling Smørgrav } 477511b41d2SMark Murray if (getnameinfo(ai->ai_addr, ai->ai_addrlen, 478511b41d2SMark Murray ntop, sizeof(ntop), strport, sizeof(strport), 479511b41d2SMark Murray NI_NUMERICHOST|NI_NUMERICSERV) != 0) { 48047dd1d1bSDag-Erling Smørgrav oerrno = errno; 48119261079SEd Maste error_f("getnameinfo failed"); 48247dd1d1bSDag-Erling Smørgrav errno = oerrno; 483511b41d2SMark Murray continue; 484511b41d2SMark Murray } 485511b41d2SMark Murray debug("Connecting to %.200s [%.100s] port %s.", 4861f5ce8f4SBrian Feldman host, ntop, strport); 487511b41d2SMark Murray 488511b41d2SMark Murray /* Create a socket for connecting. */ 489190cef3dSDag-Erling Smørgrav sock = ssh_create_socket(ai); 49047dd1d1bSDag-Erling Smørgrav if (sock < 0) { 491af12a3e7SDag-Erling Smørgrav /* Any error is already output */ 49247dd1d1bSDag-Erling Smørgrav errno = 0; 493511b41d2SMark Murray continue; 49447dd1d1bSDag-Erling Smørgrav } 495511b41d2SMark Murray 49619261079SEd Maste *timeout_ms = saved_timeout_ms; 497cf2b5f3bSDag-Erling Smørgrav if (timeout_connect(sock, ai->ai_addr, ai->ai_addrlen, 498d4af9e69SDag-Erling Smørgrav timeout_ms) >= 0) { 499511b41d2SMark Murray /* Successful connection. */ 500c322fe35SKris Kennaway memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); 501511b41d2SMark Murray break; 502511b41d2SMark Murray } else { 50347dd1d1bSDag-Erling Smørgrav oerrno = errno; 504f388f5efSDag-Erling Smørgrav debug("connect to address %s port %s: %s", 505f388f5efSDag-Erling Smørgrav ntop, strport, strerror(errno)); 506511b41d2SMark Murray close(sock); 507333ee039SDag-Erling Smørgrav sock = -1; 50847dd1d1bSDag-Erling Smørgrav errno = oerrno; 509511b41d2SMark Murray } 510511b41d2SMark Murray } 511333ee039SDag-Erling Smørgrav if (sock != -1) 512511b41d2SMark Murray break; /* Successful connection. */ 513511b41d2SMark Murray } 514511b41d2SMark Murray 515511b41d2SMark Murray /* Return failure if we didn't get a successful connection. */ 516333ee039SDag-Erling Smørgrav if (sock == -1) { 517aa49c926SDag-Erling Smørgrav error("ssh: connect to host %s port %s: %s", 51847dd1d1bSDag-Erling Smørgrav host, strport, errno == 0 ? "failure" : strerror(errno)); 51947dd1d1bSDag-Erling Smørgrav return -1; 520f388f5efSDag-Erling Smørgrav } 521511b41d2SMark Murray 522511b41d2SMark Murray debug("Connection established."); 523511b41d2SMark Murray 5241ec0d754SDag-Erling Smørgrav /* Set SO_KEEPALIVE if requested. */ 525d4af9e69SDag-Erling Smørgrav if (want_keepalive && 526ca3176e7SBrian Feldman setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, 52719261079SEd Maste sizeof(on)) == -1) 528ca3176e7SBrian Feldman error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); 529ca3176e7SBrian Feldman 530511b41d2SMark Murray /* Set the connection. */ 5314f52dfbbSDag-Erling Smørgrav if (ssh_packet_set_connection(ssh, sock, sock) == NULL) 5324f52dfbbSDag-Erling Smørgrav return -1; /* ssh_packet_set_connection logs error */ 533511b41d2SMark Murray 534af12a3e7SDag-Erling Smørgrav return 0; 535511b41d2SMark Murray } 536511b41d2SMark Murray 537f7167e0eSDag-Erling Smørgrav int 53819261079SEd Maste ssh_connect(struct ssh *ssh, const char *host, const char *host_arg, 53919261079SEd Maste struct addrinfo *addrs, struct sockaddr_storage *hostaddr, u_short port, 540190cef3dSDag-Erling Smørgrav int connection_attempts, int *timeout_ms, int want_keepalive) 541f7167e0eSDag-Erling Smørgrav { 54219261079SEd Maste int in, out; 54319261079SEd Maste 544f7167e0eSDag-Erling Smørgrav if (options.proxy_command == NULL) { 5454f52dfbbSDag-Erling Smørgrav return ssh_connect_direct(ssh, host, addrs, hostaddr, port, 54619261079SEd Maste connection_attempts, timeout_ms, want_keepalive); 547f7167e0eSDag-Erling Smørgrav } else if (strcmp(options.proxy_command, "-") == 0) { 54819261079SEd Maste if ((in = dup(STDIN_FILENO)) == -1 || 54919261079SEd Maste (out = dup(STDOUT_FILENO)) == -1) { 55019261079SEd Maste if (in >= 0) 55119261079SEd Maste close(in); 55219261079SEd Maste error_f("dup() in/out failed"); 55319261079SEd Maste return -1; /* ssh_packet_set_connection logs error */ 55419261079SEd Maste } 55519261079SEd Maste if ((ssh_packet_set_connection(ssh, in, out)) == NULL) 5564f52dfbbSDag-Erling Smørgrav return -1; /* ssh_packet_set_connection logs error */ 5574f52dfbbSDag-Erling Smørgrav return 0; 558f7167e0eSDag-Erling Smørgrav } else if (options.proxy_use_fdpass) { 55919261079SEd Maste return ssh_proxy_fdpass_connect(ssh, host, host_arg, port, 560f7167e0eSDag-Erling Smørgrav options.proxy_command); 561f7167e0eSDag-Erling Smørgrav } 56219261079SEd Maste return ssh_proxy_connect(ssh, host, host_arg, port, 56319261079SEd Maste options.proxy_command); 564511b41d2SMark Murray } 565511b41d2SMark Murray 566ca3176e7SBrian Feldman /* defaults to 'no' */ 567af12a3e7SDag-Erling Smørgrav static int 56819261079SEd Maste confirm(const char *prompt, const char *fingerprint) 569511b41d2SMark Murray { 570af12a3e7SDag-Erling Smørgrav const char *msg, *again = "Please type 'yes' or 'no': "; 57119261079SEd Maste const char *again_fp = "Please type 'yes', 'no' or the fingerprint: "; 57219261079SEd Maste char *p, *cp; 573af12a3e7SDag-Erling Smørgrav int ret = -1; 574511b41d2SMark Murray 575ca3176e7SBrian Feldman if (options.batch_mode) 576ca3176e7SBrian Feldman return 0; 57719261079SEd Maste for (msg = prompt;;msg = fingerprint ? again_fp : again) { 57819261079SEd Maste cp = p = read_passphrase(msg, RP_ECHO); 57947dd1d1bSDag-Erling Smørgrav if (p == NULL) 58047dd1d1bSDag-Erling Smørgrav return 0; 58119261079SEd Maste p += strspn(p, " \t"); /* skip leading whitespace */ 58219261079SEd Maste p[strcspn(p, " \t\n")] = '\0'; /* remove trailing whitespace */ 58347dd1d1bSDag-Erling Smørgrav if (p[0] == '\0' || strcasecmp(p, "no") == 0) 584af12a3e7SDag-Erling Smørgrav ret = 0; 58519261079SEd Maste else if (strcasecmp(p, "yes") == 0 || (fingerprint != NULL && 58619261079SEd Maste strcmp(p, fingerprint) == 0)) 587af12a3e7SDag-Erling Smørgrav ret = 1; 58819261079SEd Maste free(cp); 589af12a3e7SDag-Erling Smørgrav if (ret != -1) 590af12a3e7SDag-Erling Smørgrav return ret; 591511b41d2SMark Murray } 592511b41d2SMark Murray } 593511b41d2SMark Murray 594b15c8340SDag-Erling Smørgrav static int 5954a421b63SDag-Erling Smørgrav sockaddr_is_local(struct sockaddr *hostaddr) 5964a421b63SDag-Erling Smørgrav { 5974a421b63SDag-Erling Smørgrav switch (hostaddr->sa_family) { 5984a421b63SDag-Erling Smørgrav case AF_INET: 5994a421b63SDag-Erling Smørgrav return (ntohl(((struct sockaddr_in *)hostaddr)-> 6004a421b63SDag-Erling Smørgrav sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; 6014a421b63SDag-Erling Smørgrav case AF_INET6: 6024a421b63SDag-Erling Smørgrav return IN6_IS_ADDR_LOOPBACK( 6034a421b63SDag-Erling Smørgrav &(((struct sockaddr_in6 *)hostaddr)->sin6_addr)); 6044a421b63SDag-Erling Smørgrav default: 6054a421b63SDag-Erling Smørgrav return 0; 6064a421b63SDag-Erling Smørgrav } 6074a421b63SDag-Erling Smørgrav } 6084a421b63SDag-Erling Smørgrav 6094a421b63SDag-Erling Smørgrav /* 6104a421b63SDag-Erling Smørgrav * Prepare the hostname and ip address strings that are used to lookup 6114a421b63SDag-Erling Smørgrav * host keys in known_hosts files. These may have a port number appended. 6124a421b63SDag-Erling Smørgrav */ 6134a421b63SDag-Erling Smørgrav void 6144a421b63SDag-Erling Smørgrav get_hostfile_hostname_ipaddr(char *hostname, struct sockaddr *hostaddr, 6154a421b63SDag-Erling Smørgrav u_short port, char **hostfile_hostname, char **hostfile_ipaddr) 6164a421b63SDag-Erling Smørgrav { 6174a421b63SDag-Erling Smørgrav char ntop[NI_MAXHOST]; 6184a421b63SDag-Erling Smørgrav socklen_t addrlen; 6194a421b63SDag-Erling Smørgrav 6204a421b63SDag-Erling Smørgrav switch (hostaddr == NULL ? -1 : hostaddr->sa_family) { 6214a421b63SDag-Erling Smørgrav case -1: 6224a421b63SDag-Erling Smørgrav addrlen = 0; 6234a421b63SDag-Erling Smørgrav break; 6244a421b63SDag-Erling Smørgrav case AF_INET: 6254a421b63SDag-Erling Smørgrav addrlen = sizeof(struct sockaddr_in); 6264a421b63SDag-Erling Smørgrav break; 6274a421b63SDag-Erling Smørgrav case AF_INET6: 6284a421b63SDag-Erling Smørgrav addrlen = sizeof(struct sockaddr_in6); 6294a421b63SDag-Erling Smørgrav break; 6304a421b63SDag-Erling Smørgrav default: 6314a421b63SDag-Erling Smørgrav addrlen = sizeof(struct sockaddr); 6324a421b63SDag-Erling Smørgrav break; 6334a421b63SDag-Erling Smørgrav } 6344a421b63SDag-Erling Smørgrav 6354a421b63SDag-Erling Smørgrav /* 6364a421b63SDag-Erling Smørgrav * We don't have the remote ip-address for connections 6374a421b63SDag-Erling Smørgrav * using a proxy command 6384a421b63SDag-Erling Smørgrav */ 6394a421b63SDag-Erling Smørgrav if (hostfile_ipaddr != NULL) { 6404a421b63SDag-Erling Smørgrav if (options.proxy_command == NULL) { 6414a421b63SDag-Erling Smørgrav if (getnameinfo(hostaddr, addrlen, 6424a421b63SDag-Erling Smørgrav ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST) != 0) 64319261079SEd Maste fatal_f("getnameinfo failed"); 6444a421b63SDag-Erling Smørgrav *hostfile_ipaddr = put_host_port(ntop, port); 6454a421b63SDag-Erling Smørgrav } else { 6464a421b63SDag-Erling Smørgrav *hostfile_ipaddr = xstrdup("<no hostip for proxy " 6474a421b63SDag-Erling Smørgrav "command>"); 6484a421b63SDag-Erling Smørgrav } 6494a421b63SDag-Erling Smørgrav } 6504a421b63SDag-Erling Smørgrav 6514a421b63SDag-Erling Smørgrav /* 6524a421b63SDag-Erling Smørgrav * Allow the user to record the key under a different name or 6534a421b63SDag-Erling Smørgrav * differentiate a non-standard port. This is useful for ssh 6544a421b63SDag-Erling Smørgrav * tunneling over forwarded connections or if you run multiple 6554a421b63SDag-Erling Smørgrav * sshd's on different ports on the same machine. 6564a421b63SDag-Erling Smørgrav */ 6574a421b63SDag-Erling Smørgrav if (hostfile_hostname != NULL) { 6584a421b63SDag-Erling Smørgrav if (options.host_key_alias != NULL) { 6594a421b63SDag-Erling Smørgrav *hostfile_hostname = xstrdup(options.host_key_alias); 6604a421b63SDag-Erling Smørgrav debug("using hostkeyalias: %s", *hostfile_hostname); 6614a421b63SDag-Erling Smørgrav } else { 6624a421b63SDag-Erling Smørgrav *hostfile_hostname = put_host_port(hostname, port); 6634a421b63SDag-Erling Smørgrav } 6644a421b63SDag-Erling Smørgrav } 6654a421b63SDag-Erling Smørgrav } 6664a421b63SDag-Erling Smørgrav 66719261079SEd Maste /* returns non-zero if path appears in hostfiles, or 0 if not. */ 66819261079SEd Maste static int 66919261079SEd Maste path_in_hostfiles(const char *path, char **hostfiles, u_int num_hostfiles) 67019261079SEd Maste { 67119261079SEd Maste u_int i; 67219261079SEd Maste 67319261079SEd Maste for (i = 0; i < num_hostfiles; i++) { 67419261079SEd Maste if (strcmp(path, hostfiles[i]) == 0) 67519261079SEd Maste return 1; 67619261079SEd Maste } 67719261079SEd Maste return 0; 67819261079SEd Maste } 67919261079SEd Maste 68019261079SEd Maste struct find_by_key_ctx { 68119261079SEd Maste const char *host, *ip; 68219261079SEd Maste const struct sshkey *key; 68319261079SEd Maste char **names; 68419261079SEd Maste u_int nnames; 68519261079SEd Maste }; 68619261079SEd Maste 68719261079SEd Maste /* Try to replace home directory prefix (per $HOME) with a ~/ sequence */ 68819261079SEd Maste static char * 68919261079SEd Maste try_tilde_unexpand(const char *path) 69019261079SEd Maste { 69119261079SEd Maste char *home, *ret = NULL; 69219261079SEd Maste size_t l; 69319261079SEd Maste 69419261079SEd Maste if (*path != '/') 69519261079SEd Maste return xstrdup(path); 69619261079SEd Maste if ((home = getenv("HOME")) == NULL || (l = strlen(home)) == 0) 69719261079SEd Maste return xstrdup(path); 69819261079SEd Maste if (strncmp(path, home, l) != 0) 69919261079SEd Maste return xstrdup(path); 70019261079SEd Maste /* 70119261079SEd Maste * ensure we have matched on a path boundary: either the $HOME that 70219261079SEd Maste * we just compared ends with a '/' or the next character of the path 70319261079SEd Maste * must be a '/'. 70419261079SEd Maste */ 70519261079SEd Maste if (home[l - 1] != '/' && path[l] != '/') 70619261079SEd Maste return xstrdup(path); 70719261079SEd Maste if (path[l] == '/') 70819261079SEd Maste l++; 70919261079SEd Maste xasprintf(&ret, "~/%s", path + l); 71019261079SEd Maste return ret; 71119261079SEd Maste } 71219261079SEd Maste 71319261079SEd Maste static int 71419261079SEd Maste hostkeys_find_by_key_cb(struct hostkey_foreach_line *l, void *_ctx) 71519261079SEd Maste { 71619261079SEd Maste struct find_by_key_ctx *ctx = (struct find_by_key_ctx *)_ctx; 71719261079SEd Maste char *path; 71819261079SEd Maste 71919261079SEd Maste /* we are looking for keys with names that *do not* match */ 72019261079SEd Maste if ((l->match & HKF_MATCH_HOST) != 0) 72119261079SEd Maste return 0; 72219261079SEd Maste /* not interested in marker lines */ 72319261079SEd Maste if (l->marker != MRK_NONE) 72419261079SEd Maste return 0; 72519261079SEd Maste /* we are only interested in exact key matches */ 72619261079SEd Maste if (l->key == NULL || !sshkey_equal(ctx->key, l->key)) 72719261079SEd Maste return 0; 72819261079SEd Maste path = try_tilde_unexpand(l->path); 72919261079SEd Maste debug_f("found matching key in %s:%lu", path, l->linenum); 73019261079SEd Maste ctx->names = xrecallocarray(ctx->names, 73119261079SEd Maste ctx->nnames, ctx->nnames + 1, sizeof(*ctx->names)); 73219261079SEd Maste xasprintf(&ctx->names[ctx->nnames], "%s:%lu: %s", path, l->linenum, 73319261079SEd Maste strncmp(l->hosts, HASH_MAGIC, strlen(HASH_MAGIC)) == 0 ? 73419261079SEd Maste "[hashed name]" : l->hosts); 73519261079SEd Maste ctx->nnames++; 73619261079SEd Maste free(path); 73719261079SEd Maste return 0; 73819261079SEd Maste } 73919261079SEd Maste 74019261079SEd Maste static int 74119261079SEd Maste hostkeys_find_by_key_hostfile(const char *file, const char *which, 74219261079SEd Maste struct find_by_key_ctx *ctx) 74319261079SEd Maste { 74419261079SEd Maste int r; 74519261079SEd Maste 74619261079SEd Maste debug3_f("trying %s hostfile \"%s\"", which, file); 74719261079SEd Maste if ((r = hostkeys_foreach(file, hostkeys_find_by_key_cb, ctx, 74819261079SEd Maste ctx->host, ctx->ip, HKF_WANT_PARSE_KEY, 0)) != 0) { 74919261079SEd Maste if (r == SSH_ERR_SYSTEM_ERROR && errno == ENOENT) { 75019261079SEd Maste debug_f("hostkeys file %s does not exist", file); 75119261079SEd Maste return 0; 75219261079SEd Maste } 75319261079SEd Maste error_fr(r, "hostkeys_foreach failed for %s", file); 75419261079SEd Maste return r; 75519261079SEd Maste } 75619261079SEd Maste return 0; 75719261079SEd Maste } 75819261079SEd Maste 75919261079SEd Maste /* 76019261079SEd Maste * Find 'key' in known hosts file(s) that do not match host/ip. 76119261079SEd Maste * Used to display also-known-as information for previously-unseen hostkeys. 76219261079SEd Maste */ 76319261079SEd Maste static void 76419261079SEd Maste hostkeys_find_by_key(const char *host, const char *ip, const struct sshkey *key, 76519261079SEd Maste char **user_hostfiles, u_int num_user_hostfiles, 76619261079SEd Maste char **system_hostfiles, u_int num_system_hostfiles, 76719261079SEd Maste char ***names, u_int *nnames) 76819261079SEd Maste { 76919261079SEd Maste struct find_by_key_ctx ctx = {0, 0, 0, 0, 0}; 77019261079SEd Maste u_int i; 77119261079SEd Maste 77219261079SEd Maste *names = NULL; 77319261079SEd Maste *nnames = 0; 77419261079SEd Maste 77519261079SEd Maste if (key == NULL || sshkey_is_cert(key)) 77619261079SEd Maste return; 77719261079SEd Maste 77819261079SEd Maste ctx.host = host; 77919261079SEd Maste ctx.ip = ip; 78019261079SEd Maste ctx.key = key; 78119261079SEd Maste 78219261079SEd Maste for (i = 0; i < num_user_hostfiles; i++) { 78319261079SEd Maste if (hostkeys_find_by_key_hostfile(user_hostfiles[i], 78419261079SEd Maste "user", &ctx) != 0) 78519261079SEd Maste goto fail; 78619261079SEd Maste } 78719261079SEd Maste for (i = 0; i < num_system_hostfiles; i++) { 78819261079SEd Maste if (hostkeys_find_by_key_hostfile(system_hostfiles[i], 78919261079SEd Maste "system", &ctx) != 0) 79019261079SEd Maste goto fail; 79119261079SEd Maste } 79219261079SEd Maste /* success */ 79319261079SEd Maste *names = ctx.names; 79419261079SEd Maste *nnames = ctx.nnames; 79519261079SEd Maste ctx.names = NULL; 79619261079SEd Maste ctx.nnames = 0; 79719261079SEd Maste return; 79819261079SEd Maste fail: 79919261079SEd Maste for (i = 0; i < ctx.nnames; i++) 80019261079SEd Maste free(ctx.names[i]); 80119261079SEd Maste free(ctx.names); 80219261079SEd Maste } 80319261079SEd Maste 80419261079SEd Maste #define MAX_OTHER_NAMES 8 /* Maximum number of names to list */ 80519261079SEd Maste static char * 80619261079SEd Maste other_hostkeys_message(const char *host, const char *ip, 80719261079SEd Maste const struct sshkey *key, 80819261079SEd Maste char **user_hostfiles, u_int num_user_hostfiles, 80919261079SEd Maste char **system_hostfiles, u_int num_system_hostfiles) 81019261079SEd Maste { 81119261079SEd Maste char *ret = NULL, **othernames = NULL; 81219261079SEd Maste u_int i, n, num_othernames = 0; 81319261079SEd Maste 81419261079SEd Maste hostkeys_find_by_key(host, ip, key, 81519261079SEd Maste user_hostfiles, num_user_hostfiles, 81619261079SEd Maste system_hostfiles, num_system_hostfiles, 81719261079SEd Maste &othernames, &num_othernames); 81819261079SEd Maste if (num_othernames == 0) 81938a52bd3SEd Maste return xstrdup("This key is not known by any other names."); 82019261079SEd Maste 82119261079SEd Maste xasprintf(&ret, "This host key is known by the following other " 82219261079SEd Maste "names/addresses:"); 82319261079SEd Maste 82419261079SEd Maste n = num_othernames; 82519261079SEd Maste if (n > MAX_OTHER_NAMES) 82619261079SEd Maste n = MAX_OTHER_NAMES; 82719261079SEd Maste for (i = 0; i < n; i++) { 82819261079SEd Maste xextendf(&ret, "\n", " %s", othernames[i]); 82919261079SEd Maste } 83019261079SEd Maste if (n < num_othernames) { 83119261079SEd Maste xextendf(&ret, "\n", " (%d additional names omitted)", 83219261079SEd Maste num_othernames - n); 83319261079SEd Maste } 83419261079SEd Maste for (i = 0; i < num_othernames; i++) 83519261079SEd Maste free(othernames[i]); 83619261079SEd Maste free(othernames); 83719261079SEd Maste return ret; 83819261079SEd Maste } 83919261079SEd Maste 84019261079SEd Maste void 84119261079SEd Maste load_hostkeys_command(struct hostkeys *hostkeys, const char *command_template, 84219261079SEd Maste const char *invocation, const struct ssh_conn_info *cinfo, 84319261079SEd Maste const struct sshkey *host_key, const char *hostfile_hostname) 84419261079SEd Maste { 84519261079SEd Maste int r, i, ac = 0; 84619261079SEd Maste char *key_fp = NULL, *keytext = NULL, *tmp; 84719261079SEd Maste char *command = NULL, *tag = NULL, **av = NULL; 84819261079SEd Maste FILE *f = NULL; 84919261079SEd Maste pid_t pid; 85019261079SEd Maste void (*osigchld)(int); 85119261079SEd Maste 85219261079SEd Maste xasprintf(&tag, "KnownHostsCommand-%s", invocation); 85319261079SEd Maste 85419261079SEd Maste if (host_key != NULL) { 85519261079SEd Maste if ((key_fp = sshkey_fingerprint(host_key, 85619261079SEd Maste options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) 85719261079SEd Maste fatal_f("sshkey_fingerprint failed"); 85819261079SEd Maste if ((r = sshkey_to_base64(host_key, &keytext)) != 0) 85919261079SEd Maste fatal_fr(r, "sshkey_to_base64 failed"); 86019261079SEd Maste } 86119261079SEd Maste /* 86219261079SEd Maste * NB. all returns later this function should go via "out" to 86319261079SEd Maste * ensure the original SIGCHLD handler is restored properly. 86419261079SEd Maste */ 86519261079SEd Maste osigchld = ssh_signal(SIGCHLD, SIG_DFL); 86619261079SEd Maste 86719261079SEd Maste /* Turn the command into an argument vector */ 86819261079SEd Maste if (argv_split(command_template, &ac, &av, 0) != 0) { 86919261079SEd Maste error("%s \"%s\" contains invalid quotes", tag, 87019261079SEd Maste command_template); 87119261079SEd Maste goto out; 87219261079SEd Maste } 87319261079SEd Maste if (ac == 0) { 87419261079SEd Maste error("%s \"%s\" yielded no arguments", tag, 87519261079SEd Maste command_template); 87619261079SEd Maste goto out; 87719261079SEd Maste } 87819261079SEd Maste for (i = 1; i < ac; i++) { 87919261079SEd Maste tmp = percent_dollar_expand(av[i], 88019261079SEd Maste DEFAULT_CLIENT_PERCENT_EXPAND_ARGS(cinfo), 88119261079SEd Maste "H", hostfile_hostname, 88219261079SEd Maste "I", invocation, 88319261079SEd Maste "t", host_key == NULL ? "NONE" : sshkey_ssh_name(host_key), 88419261079SEd Maste "f", key_fp == NULL ? "NONE" : key_fp, 88519261079SEd Maste "K", keytext == NULL ? "NONE" : keytext, 88619261079SEd Maste (char *)NULL); 88719261079SEd Maste if (tmp == NULL) 88819261079SEd Maste fatal_f("percent_expand failed"); 88919261079SEd Maste free(av[i]); 89019261079SEd Maste av[i] = tmp; 89119261079SEd Maste } 89219261079SEd Maste /* Prepare a printable command for logs, etc. */ 89319261079SEd Maste command = argv_assemble(ac, av); 89419261079SEd Maste 89519261079SEd Maste if ((pid = subprocess(tag, command, ac, av, &f, 89619261079SEd Maste SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_UNSAFE_PATH| 89719261079SEd Maste SSH_SUBPROCESS_PRESERVE_ENV, NULL, NULL, NULL)) == 0) 89819261079SEd Maste goto out; 89919261079SEd Maste 90019261079SEd Maste load_hostkeys_file(hostkeys, hostfile_hostname, tag, f, 1); 90119261079SEd Maste 90219261079SEd Maste if (exited_cleanly(pid, tag, command, 0) != 0) 90319261079SEd Maste fatal("KnownHostsCommand failed"); 90419261079SEd Maste 90519261079SEd Maste out: 90619261079SEd Maste if (f != NULL) 90719261079SEd Maste fclose(f); 90819261079SEd Maste ssh_signal(SIGCHLD, osigchld); 90919261079SEd Maste for (i = 0; i < ac; i++) 91019261079SEd Maste free(av[i]); 91119261079SEd Maste free(av); 91219261079SEd Maste free(tag); 91319261079SEd Maste free(command); 91419261079SEd Maste free(key_fp); 91519261079SEd Maste free(keytext); 91619261079SEd Maste } 91719261079SEd Maste 918e8aafc91SKris Kennaway /* 919af12a3e7SDag-Erling Smørgrav * check whether the supplied host key is valid, return -1 if the key 920e146993eSDag-Erling Smørgrav * is not valid. user_hostfile[0] will not be updated if 'readonly' is true. 921e8aafc91SKris Kennaway */ 922333ee039SDag-Erling Smørgrav #define RDRW 0 923333ee039SDag-Erling Smørgrav #define RDONLY 1 924333ee039SDag-Erling Smørgrav #define ROQUIET 2 925af12a3e7SDag-Erling Smørgrav static int 92619261079SEd Maste check_host_key(char *hostname, const struct ssh_conn_info *cinfo, 92719261079SEd Maste struct sockaddr *hostaddr, u_short port, 92819261079SEd Maste struct sshkey *host_key, int readonly, int clobber_port, 929e146993eSDag-Erling Smørgrav char **user_hostfiles, u_int num_user_hostfiles, 93019261079SEd Maste char **system_hostfiles, u_int num_system_hostfiles, 93119261079SEd Maste const char *hostfile_command) 932511b41d2SMark Murray { 93319261079SEd Maste HostStatus host_status = -1, ip_status = -1; 9344f52dfbbSDag-Erling Smørgrav struct sshkey *raw_key = NULL; 935e146993eSDag-Erling Smørgrav char *ip = NULL, *host = NULL; 936e146993eSDag-Erling Smørgrav char hostline[1000], *hostp, *fp, *ra; 937af12a3e7SDag-Erling Smørgrav char msg[1024]; 938*2e828220SEd Maste const char *type, *fail_reason = NULL; 93919261079SEd Maste const struct hostkey_entry *host_found = NULL, *ip_found = NULL; 94019261079SEd Maste int len, cancelled_forwarding = 0, confirmed; 941e146993eSDag-Erling Smørgrav int local = sockaddr_is_local(hostaddr); 9424f52dfbbSDag-Erling Smørgrav int r, want_cert = sshkey_is_cert(host_key), host_ip_differ = 0; 943bc5531deSDag-Erling Smørgrav int hostkey_trusted = 0; /* Known or explicitly accepted by user */ 944e146993eSDag-Erling Smørgrav struct hostkeys *host_hostkeys, *ip_hostkeys; 945e146993eSDag-Erling Smørgrav u_int i; 946511b41d2SMark Murray 947e8aafc91SKris Kennaway /* 948e8aafc91SKris Kennaway * Force accepting of the host key for loopback/localhost. The 949e8aafc91SKris Kennaway * problem is that if the home directory is NFS-mounted to multiple 950e8aafc91SKris Kennaway * machines, localhost will refer to a different machine in each of 951e8aafc91SKris Kennaway * them, and the user will get bogus HOST_CHANGED warnings. This 952e8aafc91SKris Kennaway * essentially disables host authentication for localhost; however, 953e8aafc91SKris Kennaway * this is probably not a real problem. 954e8aafc91SKris Kennaway */ 955af12a3e7SDag-Erling Smørgrav if (options.no_host_authentication_for_localhost == 1 && local && 956af12a3e7SDag-Erling Smørgrav options.host_key_alias == NULL) { 957ca3176e7SBrian Feldman debug("Forcing accepting of host key for " 958ca3176e7SBrian Feldman "loopback/localhost."); 95919261079SEd Maste options.update_hostkeys = 0; 960af12a3e7SDag-Erling Smørgrav return 0; 961511b41d2SMark Murray } 962511b41d2SMark Murray 963e8aafc91SKris Kennaway /* 964*2e828220SEd Maste * Don't ever try to write an invalid name to a known hosts file. 965*2e828220SEd Maste * Note: do this before get_hostfile_hostname_ipaddr() to catch 966*2e828220SEd Maste * '[' or ']' in the name before they are added. 967*2e828220SEd Maste */ 968*2e828220SEd Maste if (strcspn(hostname, "@?*#[]|'\'\"\\") != strlen(hostname)) { 969*2e828220SEd Maste debug_f("invalid hostname \"%s\"; will not record: %s", 970*2e828220SEd Maste hostname, fail_reason); 971*2e828220SEd Maste readonly = RDONLY; 972*2e828220SEd Maste } 973*2e828220SEd Maste 974*2e828220SEd Maste /* 9754a421b63SDag-Erling Smørgrav * Prepare the hostname and address strings used for hostkey lookup. 9764a421b63SDag-Erling Smørgrav * In some cases, these will have a port number appended. 977e8aafc91SKris Kennaway */ 97819261079SEd Maste get_hostfile_hostname_ipaddr(hostname, hostaddr, 97919261079SEd Maste clobber_port ? 0 : port, &host, &ip); 980d4af9e69SDag-Erling Smørgrav 981ca3176e7SBrian Feldman /* 982ca3176e7SBrian Feldman * Turn off check_host_ip if the connection is to localhost, via proxy 983ca3176e7SBrian Feldman * command or if we don't have a hostname to compare with 984ca3176e7SBrian Feldman */ 985333ee039SDag-Erling Smørgrav if (options.check_host_ip && (local || 986333ee039SDag-Erling Smørgrav strcmp(hostname, ip) == 0 || options.proxy_command != NULL)) 987ca3176e7SBrian Feldman options.check_host_ip = 0; 988ca3176e7SBrian Feldman 9894a421b63SDag-Erling Smørgrav host_hostkeys = init_hostkeys(); 990e146993eSDag-Erling Smørgrav for (i = 0; i < num_user_hostfiles; i++) 99119261079SEd Maste load_hostkeys(host_hostkeys, host, user_hostfiles[i], 0); 992e146993eSDag-Erling Smørgrav for (i = 0; i < num_system_hostfiles; i++) 99319261079SEd Maste load_hostkeys(host_hostkeys, host, system_hostfiles[i], 0); 99419261079SEd Maste if (hostfile_command != NULL && !clobber_port) { 99519261079SEd Maste load_hostkeys_command(host_hostkeys, hostfile_command, 99619261079SEd Maste "HOSTNAME", cinfo, host_key, host); 99719261079SEd Maste } 9984a421b63SDag-Erling Smørgrav 9994a421b63SDag-Erling Smørgrav ip_hostkeys = NULL; 10004a421b63SDag-Erling Smørgrav if (!want_cert && options.check_host_ip) { 10014a421b63SDag-Erling Smørgrav ip_hostkeys = init_hostkeys(); 1002e146993eSDag-Erling Smørgrav for (i = 0; i < num_user_hostfiles; i++) 100319261079SEd Maste load_hostkeys(ip_hostkeys, ip, user_hostfiles[i], 0); 1004e146993eSDag-Erling Smørgrav for (i = 0; i < num_system_hostfiles; i++) 100519261079SEd Maste load_hostkeys(ip_hostkeys, ip, system_hostfiles[i], 0); 100619261079SEd Maste if (hostfile_command != NULL && !clobber_port) { 100719261079SEd Maste load_hostkeys_command(ip_hostkeys, hostfile_command, 100819261079SEd Maste "ADDRESS", cinfo, host_key, ip); 100919261079SEd Maste } 1010e8aafc91SKris Kennaway } 1011e8aafc91SKris Kennaway 1012b15c8340SDag-Erling Smørgrav retry: 10134a421b63SDag-Erling Smørgrav /* Reload these as they may have changed on cert->key downgrade */ 10144f52dfbbSDag-Erling Smørgrav want_cert = sshkey_is_cert(host_key); 10154f52dfbbSDag-Erling Smørgrav type = sshkey_type(host_key); 1016b15c8340SDag-Erling Smørgrav 1017e8aafc91SKris Kennaway /* 1018b74df5b2SDag-Erling Smørgrav * Check if the host key is present in the user's list of known 1019e8aafc91SKris Kennaway * hosts or in the systemwide list. 1020e8aafc91SKris Kennaway */ 10214a421b63SDag-Erling Smørgrav host_status = check_key_in_hostkeys(host_hostkeys, host_key, 10224a421b63SDag-Erling Smørgrav &host_found); 10234a421b63SDag-Erling Smørgrav 1024e8aafc91SKris Kennaway /* 102519261079SEd Maste * If there are no hostfiles, or if the hostkey was found via 102619261079SEd Maste * KnownHostsCommand, then don't try to touch the disk. 102719261079SEd Maste */ 102819261079SEd Maste if (!readonly && (num_user_hostfiles == 0 || 102919261079SEd Maste (host_found != NULL && host_found->note != 0))) 103019261079SEd Maste readonly = RDONLY; 103119261079SEd Maste 103219261079SEd Maste /* 1033e8aafc91SKris Kennaway * Also perform check for the ip address, skip the check if we are 1034b15c8340SDag-Erling Smørgrav * localhost, looking for a certificate, or the hostname was an ip 1035b15c8340SDag-Erling Smørgrav * address to begin with. 1036e8aafc91SKris Kennaway */ 10374a421b63SDag-Erling Smørgrav if (!want_cert && ip_hostkeys != NULL) { 10384a421b63SDag-Erling Smørgrav ip_status = check_key_in_hostkeys(ip_hostkeys, host_key, 10394a421b63SDag-Erling Smørgrav &ip_found); 1040e8aafc91SKris Kennaway if (host_status == HOST_CHANGED && 10414a421b63SDag-Erling Smørgrav (ip_status != HOST_CHANGED || 10424a421b63SDag-Erling Smørgrav (ip_found != NULL && 10434f52dfbbSDag-Erling Smørgrav !sshkey_equal(ip_found->key, host_found->key)))) 1044e8aafc91SKris Kennaway host_ip_differ = 1; 1045e8aafc91SKris Kennaway } else 1046e8aafc91SKris Kennaway ip_status = host_status; 1047e8aafc91SKris Kennaway 1048e8aafc91SKris Kennaway switch (host_status) { 1049e8aafc91SKris Kennaway case HOST_OK: 1050e8aafc91SKris Kennaway /* The host is known and the key matches. */ 1051b15c8340SDag-Erling Smørgrav debug("Host '%.200s' is known and matches the %s host %s.", 1052b15c8340SDag-Erling Smørgrav host, type, want_cert ? "certificate" : "key"); 10534a421b63SDag-Erling Smørgrav debug("Found %s in %s:%lu", want_cert ? "CA key" : "key", 10544a421b63SDag-Erling Smørgrav host_found->file, host_found->line); 105519261079SEd Maste if (want_cert) { 105619261079SEd Maste if (sshkey_cert_check_host(host_key, 105719261079SEd Maste options.host_key_alias == NULL ? 105819261079SEd Maste hostname : options.host_key_alias, 0, 105919261079SEd Maste options.ca_sign_algorithms, &fail_reason) != 0) { 106019261079SEd Maste error("%s", fail_reason); 1061b15c8340SDag-Erling Smørgrav goto fail; 106219261079SEd Maste } 106319261079SEd Maste /* 106419261079SEd Maste * Do not attempt hostkey update if a certificate was 106519261079SEd Maste * successfully matched. 106619261079SEd Maste */ 106719261079SEd Maste if (options.update_hostkeys != 0) { 106819261079SEd Maste options.update_hostkeys = 0; 106919261079SEd Maste debug3_f("certificate host key in use; " 107019261079SEd Maste "disabling UpdateHostkeys"); 107119261079SEd Maste } 107219261079SEd Maste } 107319261079SEd Maste /* Turn off UpdateHostkeys if key was in system known_hosts */ 107419261079SEd Maste if (options.update_hostkeys != 0 && 107519261079SEd Maste (path_in_hostfiles(host_found->file, 107619261079SEd Maste system_hostfiles, num_system_hostfiles) || 107719261079SEd Maste (ip_status == HOST_OK && ip_found != NULL && 107819261079SEd Maste path_in_hostfiles(ip_found->file, 107919261079SEd Maste system_hostfiles, num_system_hostfiles)))) { 108019261079SEd Maste options.update_hostkeys = 0; 108119261079SEd Maste debug3_f("host key found in GlobalKnownHostsFile; " 108219261079SEd Maste "disabling UpdateHostkeys"); 108319261079SEd Maste } 108419261079SEd Maste if (options.update_hostkeys != 0 && host_found->note) { 108519261079SEd Maste options.update_hostkeys = 0; 108619261079SEd Maste debug3_f("host key found via KnownHostsCommand; " 108719261079SEd Maste "disabling UpdateHostkeys"); 108819261079SEd Maste } 1089ca3176e7SBrian Feldman if (options.check_host_ip && ip_status == HOST_NEW) { 1090b15c8340SDag-Erling Smørgrav if (readonly || want_cert) 1091cf2b5f3bSDag-Erling Smørgrav logit("%s host key for IP address " 1092af12a3e7SDag-Erling Smørgrav "'%.128s' not in list of known hosts.", 1093e8aafc91SKris Kennaway type, ip); 1094e146993eSDag-Erling Smørgrav else if (!add_host_to_hostfile(user_hostfiles[0], ip, 1095aa49c926SDag-Erling Smørgrav host_key, options.hash_known_hosts)) 1096cf2b5f3bSDag-Erling Smørgrav logit("Failed to add the %s host key for IP " 1097af12a3e7SDag-Erling Smørgrav "address '%.128s' to the list of known " 1098557f75e5SDag-Erling Smørgrav "hosts (%.500s).", type, ip, 1099e146993eSDag-Erling Smørgrav user_hostfiles[0]); 1100af12a3e7SDag-Erling Smørgrav else 1101cf2b5f3bSDag-Erling Smørgrav logit("Warning: Permanently added the %s host " 1102af12a3e7SDag-Erling Smørgrav "key for IP address '%.128s' to the list " 1103af12a3e7SDag-Erling Smørgrav "of known hosts.", type, ip); 1104d4af9e69SDag-Erling Smørgrav } else if (options.visual_host_key) { 1105bc5531deSDag-Erling Smørgrav fp = sshkey_fingerprint(host_key, 1106bc5531deSDag-Erling Smørgrav options.fingerprint_hash, SSH_FP_DEFAULT); 1107bc5531deSDag-Erling Smørgrav ra = sshkey_fingerprint(host_key, 1108bc5531deSDag-Erling Smørgrav options.fingerprint_hash, SSH_FP_RANDOMART); 1109bc5531deSDag-Erling Smørgrav if (fp == NULL || ra == NULL) 111019261079SEd Maste fatal_f("sshkey_fingerprint failed"); 1111acc1a9efSDag-Erling Smørgrav logit("Host key fingerprint is %s\n%s", fp, ra); 1112e4a9863fSDag-Erling Smørgrav free(ra); 1113e4a9863fSDag-Erling Smørgrav free(fp); 1114e8aafc91SKris Kennaway } 1115bc5531deSDag-Erling Smørgrav hostkey_trusted = 1; 1116e8aafc91SKris Kennaway break; 1117e8aafc91SKris Kennaway case HOST_NEW: 1118333ee039SDag-Erling Smørgrav if (options.host_key_alias == NULL && port != 0 && 111919261079SEd Maste port != SSH_DEFAULT_PORT && !clobber_port) { 1120333ee039SDag-Erling Smørgrav debug("checking without port identifier"); 112119261079SEd Maste if (check_host_key(hostname, cinfo, hostaddr, 0, 112219261079SEd Maste host_key, ROQUIET, 1, 112319261079SEd Maste user_hostfiles, num_user_hostfiles, 112419261079SEd Maste system_hostfiles, num_system_hostfiles, 112519261079SEd Maste hostfile_command) == 0) { 1126333ee039SDag-Erling Smørgrav debug("found matching key w/out port"); 1127333ee039SDag-Erling Smørgrav break; 1128333ee039SDag-Erling Smørgrav } 1129333ee039SDag-Erling Smørgrav } 1130b15c8340SDag-Erling Smørgrav if (readonly || want_cert) 1131af12a3e7SDag-Erling Smørgrav goto fail; 1132e8aafc91SKris Kennaway /* The host is new. */ 11334f52dfbbSDag-Erling Smørgrav if (options.strict_host_key_checking == 11344f52dfbbSDag-Erling Smørgrav SSH_STRICT_HOSTKEY_YES) { 1135af12a3e7SDag-Erling Smørgrav /* 1136af12a3e7SDag-Erling Smørgrav * User has requested strict host key checking. We 1137af12a3e7SDag-Erling Smørgrav * will not add the host key automatically. The only 1138af12a3e7SDag-Erling Smørgrav * alternative left is to abort. 1139af12a3e7SDag-Erling Smørgrav */ 1140af12a3e7SDag-Erling Smørgrav error("No %s host key is known for %.200s and you " 1141af12a3e7SDag-Erling Smørgrav "have requested strict checking.", type, host); 1142af12a3e7SDag-Erling Smørgrav goto fail; 11434f52dfbbSDag-Erling Smørgrav } else if (options.strict_host_key_checking == 11444f52dfbbSDag-Erling Smørgrav SSH_STRICT_HOSTKEY_ASK) { 114519261079SEd Maste char *msg1 = NULL, *msg2 = NULL; 1146cf2b5f3bSDag-Erling Smørgrav 114719261079SEd Maste xasprintf(&msg1, "The authenticity of host " 114819261079SEd Maste "'%.200s (%s)' can't be established", host, ip); 114919261079SEd Maste 115019261079SEd Maste if (show_other_keys(host_hostkeys, host_key)) { 115119261079SEd Maste xextendf(&msg1, "\n", "but keys of different " 115219261079SEd Maste "type are already known for this host."); 115319261079SEd Maste } else 115419261079SEd Maste xextendf(&msg1, "", "."); 115519261079SEd Maste 1156bc5531deSDag-Erling Smørgrav fp = sshkey_fingerprint(host_key, 1157bc5531deSDag-Erling Smørgrav options.fingerprint_hash, SSH_FP_DEFAULT); 1158bc5531deSDag-Erling Smørgrav ra = sshkey_fingerprint(host_key, 1159bc5531deSDag-Erling Smørgrav options.fingerprint_hash, SSH_FP_RANDOMART); 1160bc5531deSDag-Erling Smørgrav if (fp == NULL || ra == NULL) 116119261079SEd Maste fatal_f("sshkey_fingerprint failed"); 116219261079SEd Maste xextendf(&msg1, "\n", "%s key fingerprint is %s.", 116319261079SEd Maste type, fp); 116419261079SEd Maste if (options.visual_host_key) 116519261079SEd Maste xextendf(&msg1, "\n", "%s", ra); 1166cf2b5f3bSDag-Erling Smørgrav if (options.verify_host_key_dns) { 116719261079SEd Maste xextendf(&msg1, "\n", 116819261079SEd Maste "%s host key fingerprint found in DNS.", 116919261079SEd Maste matching_host_key_dns ? 117019261079SEd Maste "Matching" : "No matching"); 1171cf2b5f3bSDag-Erling Smørgrav } 117219261079SEd Maste /* msg2 informs for other names matching this key */ 117319261079SEd Maste if ((msg2 = other_hostkeys_message(host, ip, host_key, 117419261079SEd Maste user_hostfiles, num_user_hostfiles, 117519261079SEd Maste system_hostfiles, num_system_hostfiles)) != NULL) 117619261079SEd Maste xextendf(&msg1, "\n", "%s", msg2); 117719261079SEd Maste 117819261079SEd Maste xextendf(&msg1, "\n", 1179af12a3e7SDag-Erling Smørgrav "Are you sure you want to continue connecting " 118019261079SEd Maste "(yes/no/[fingerprint])? "); 118119261079SEd Maste 118219261079SEd Maste confirmed = confirm(msg1, fp); 1183e4a9863fSDag-Erling Smørgrav free(ra); 1184e4a9863fSDag-Erling Smørgrav free(fp); 118519261079SEd Maste free(msg1); 118619261079SEd Maste free(msg2); 118719261079SEd Maste if (!confirmed) 1188af12a3e7SDag-Erling Smørgrav goto fail; 1189bc5531deSDag-Erling Smørgrav hostkey_trusted = 1; /* user explicitly confirmed */ 1190e8aafc91SKris Kennaway } 1191af12a3e7SDag-Erling Smørgrav /* 11924f52dfbbSDag-Erling Smørgrav * If in "new" or "off" strict mode, add the key automatically 11934f52dfbbSDag-Erling Smørgrav * to the local known_hosts file. 1194af12a3e7SDag-Erling Smørgrav */ 1195aa49c926SDag-Erling Smørgrav if (options.check_host_ip && ip_status == HOST_NEW) { 11964a421b63SDag-Erling Smørgrav snprintf(hostline, sizeof(hostline), "%s,%s", host, ip); 1197aa49c926SDag-Erling Smørgrav hostp = hostline; 1198aa49c926SDag-Erling Smørgrav if (options.hash_known_hosts) { 1199aa49c926SDag-Erling Smørgrav /* Add hash of host and IP separately */ 1200e146993eSDag-Erling Smørgrav r = add_host_to_hostfile(user_hostfiles[0], 1201e146993eSDag-Erling Smørgrav host, host_key, options.hash_known_hosts) && 1202e146993eSDag-Erling Smørgrav add_host_to_hostfile(user_hostfiles[0], ip, 1203aa49c926SDag-Erling Smørgrav host_key, options.hash_known_hosts); 1204aa49c926SDag-Erling Smørgrav } else { 1205aa49c926SDag-Erling Smørgrav /* Add unhashed "host,ip" */ 1206e146993eSDag-Erling Smørgrav r = add_host_to_hostfile(user_hostfiles[0], 1207aa49c926SDag-Erling Smørgrav hostline, host_key, 1208aa49c926SDag-Erling Smørgrav options.hash_known_hosts); 1209aa49c926SDag-Erling Smørgrav } 1210aa49c926SDag-Erling Smørgrav } else { 1211e146993eSDag-Erling Smørgrav r = add_host_to_hostfile(user_hostfiles[0], host, 1212e146993eSDag-Erling Smørgrav host_key, options.hash_known_hosts); 1213aa49c926SDag-Erling Smørgrav hostp = host; 1214aa49c926SDag-Erling Smørgrav } 1215aa49c926SDag-Erling Smørgrav 1216aa49c926SDag-Erling Smørgrav if (!r) 1217cf2b5f3bSDag-Erling Smørgrav logit("Failed to add the host to the list of known " 1218e146993eSDag-Erling Smørgrav "hosts (%.500s).", user_hostfiles[0]); 1219e8aafc91SKris Kennaway else 1220cf2b5f3bSDag-Erling Smørgrav logit("Warning: Permanently added '%.200s' (%s) to the " 1221af12a3e7SDag-Erling Smørgrav "list of known hosts.", hostp, type); 1222e8aafc91SKris Kennaway break; 1223b15c8340SDag-Erling Smørgrav case HOST_REVOKED: 1224b15c8340SDag-Erling Smørgrav error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); 1225b15c8340SDag-Erling Smørgrav error("@ WARNING: REVOKED HOST KEY DETECTED! @"); 1226b15c8340SDag-Erling Smørgrav error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); 1227b15c8340SDag-Erling Smørgrav error("The %s host key for %s is marked as revoked.", type, host); 1228b15c8340SDag-Erling Smørgrav error("This could mean that a stolen key is being used to"); 1229b15c8340SDag-Erling Smørgrav error("impersonate this host."); 1230b15c8340SDag-Erling Smørgrav 1231b15c8340SDag-Erling Smørgrav /* 1232b15c8340SDag-Erling Smørgrav * If strict host key checking is in use, the user will have 1233b15c8340SDag-Erling Smørgrav * to edit the key manually and we can only abort. 1234b15c8340SDag-Erling Smørgrav */ 12354f52dfbbSDag-Erling Smørgrav if (options.strict_host_key_checking != 12364f52dfbbSDag-Erling Smørgrav SSH_STRICT_HOSTKEY_OFF) { 1237b15c8340SDag-Erling Smørgrav error("%s host key for %.200s was revoked and you have " 1238b15c8340SDag-Erling Smørgrav "requested strict checking.", type, host); 1239b15c8340SDag-Erling Smørgrav goto fail; 1240b15c8340SDag-Erling Smørgrav } 1241b15c8340SDag-Erling Smørgrav goto continue_unsafe; 1242b15c8340SDag-Erling Smørgrav 1243e8aafc91SKris Kennaway case HOST_CHANGED: 1244b15c8340SDag-Erling Smørgrav if (want_cert) { 1245b15c8340SDag-Erling Smørgrav /* 1246b15c8340SDag-Erling Smørgrav * This is only a debug() since it is valid to have 1247b15c8340SDag-Erling Smørgrav * CAs with wildcard DNS matches that don't match 1248b15c8340SDag-Erling Smørgrav * all hosts that one might visit. 1249b15c8340SDag-Erling Smørgrav */ 1250b15c8340SDag-Erling Smørgrav debug("Host certificate authority does not " 12514a421b63SDag-Erling Smørgrav "match %s in %s:%lu", CA_MARKER, 12524a421b63SDag-Erling Smørgrav host_found->file, host_found->line); 1253b15c8340SDag-Erling Smørgrav goto fail; 1254b15c8340SDag-Erling Smørgrav } 1255333ee039SDag-Erling Smørgrav if (readonly == ROQUIET) 1256333ee039SDag-Erling Smørgrav goto fail; 1257e8aafc91SKris Kennaway if (options.check_host_ip && host_ip_differ) { 125821e764dfSDag-Erling Smørgrav char *key_msg; 1259e8aafc91SKris Kennaway if (ip_status == HOST_NEW) 126021e764dfSDag-Erling Smørgrav key_msg = "is unknown"; 1261e8aafc91SKris Kennaway else if (ip_status == HOST_OK) 126221e764dfSDag-Erling Smørgrav key_msg = "is unchanged"; 1263e8aafc91SKris Kennaway else 126421e764dfSDag-Erling Smørgrav key_msg = "has a different value"; 1265e8aafc91SKris Kennaway error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); 1266e8aafc91SKris Kennaway error("@ WARNING: POSSIBLE DNS SPOOFING DETECTED! @"); 1267e8aafc91SKris Kennaway error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); 1268e8aafc91SKris Kennaway error("The %s host key for %s has changed,", type, host); 1269d4af9e69SDag-Erling Smørgrav error("and the key for the corresponding IP address %s", ip); 127021e764dfSDag-Erling Smørgrav error("%s. This could either mean that", key_msg); 1271e8aafc91SKris Kennaway error("DNS SPOOFING is happening or the IP address for the host"); 1272ca3176e7SBrian Feldman error("and its host key have changed at the same time."); 1273ca3176e7SBrian Feldman if (ip_status != HOST_NEW) 12744a421b63SDag-Erling Smørgrav error("Offending key for IP in %s:%lu", 12754a421b63SDag-Erling Smørgrav ip_found->file, ip_found->line); 1276e8aafc91SKris Kennaway } 1277e8aafc91SKris Kennaway /* The host key has changed. */ 12781ec0d754SDag-Erling Smørgrav warn_changed_key(host_key); 1279e8aafc91SKris Kennaway error("Add correct host key in %.100s to get rid of this message.", 1280e146993eSDag-Erling Smørgrav user_hostfiles[0]); 12814f52dfbbSDag-Erling Smørgrav error("Offending %s key in %s:%lu", 12824f52dfbbSDag-Erling Smørgrav sshkey_type(host_found->key), 12834a421b63SDag-Erling Smørgrav host_found->file, host_found->line); 1284e8aafc91SKris Kennaway 1285e8aafc91SKris Kennaway /* 1286e8aafc91SKris Kennaway * If strict host key checking is in use, the user will have 1287e8aafc91SKris Kennaway * to edit the key manually and we can only abort. 1288e8aafc91SKris Kennaway */ 12894f52dfbbSDag-Erling Smørgrav if (options.strict_host_key_checking != 12904f52dfbbSDag-Erling Smørgrav SSH_STRICT_HOSTKEY_OFF) { 129119261079SEd Maste error("Host key for %.200s has changed and you have " 129219261079SEd Maste "requested strict checking.", host); 1293af12a3e7SDag-Erling Smørgrav goto fail; 1294af12a3e7SDag-Erling Smørgrav } 1295e8aafc91SKris Kennaway 1296b15c8340SDag-Erling Smørgrav continue_unsafe: 1297e8aafc91SKris Kennaway /* 1298e8aafc91SKris Kennaway * If strict host key checking has not been requested, allow 1299cf2b5f3bSDag-Erling Smørgrav * the connection but without MITM-able authentication or 1300333ee039SDag-Erling Smørgrav * forwarding. 1301e8aafc91SKris Kennaway */ 1302e8aafc91SKris Kennaway if (options.password_authentication) { 1303af12a3e7SDag-Erling Smørgrav error("Password authentication is disabled to avoid " 1304af12a3e7SDag-Erling Smørgrav "man-in-the-middle attacks."); 1305e8aafc91SKris Kennaway options.password_authentication = 0; 1306d4af9e69SDag-Erling Smørgrav cancelled_forwarding = 1; 1307e8aafc91SKris Kennaway } 1308cf2b5f3bSDag-Erling Smørgrav if (options.kbd_interactive_authentication) { 1309cf2b5f3bSDag-Erling Smørgrav error("Keyboard-interactive authentication is disabled" 1310cf2b5f3bSDag-Erling Smørgrav " to avoid man-in-the-middle attacks."); 1311cf2b5f3bSDag-Erling Smørgrav options.kbd_interactive_authentication = 0; 1312d4af9e69SDag-Erling Smørgrav cancelled_forwarding = 1; 1313cf2b5f3bSDag-Erling Smørgrav } 1314e8aafc91SKris Kennaway if (options.forward_agent) { 1315af12a3e7SDag-Erling Smørgrav error("Agent forwarding is disabled to avoid " 1316af12a3e7SDag-Erling Smørgrav "man-in-the-middle attacks."); 1317e8aafc91SKris Kennaway options.forward_agent = 0; 1318d4af9e69SDag-Erling Smørgrav cancelled_forwarding = 1; 1319e8aafc91SKris Kennaway } 1320ca3176e7SBrian Feldman if (options.forward_x11) { 1321af12a3e7SDag-Erling Smørgrav error("X11 forwarding is disabled to avoid " 1322af12a3e7SDag-Erling Smørgrav "man-in-the-middle attacks."); 1323ca3176e7SBrian Feldman options.forward_x11 = 0; 1324d4af9e69SDag-Erling Smørgrav cancelled_forwarding = 1; 1325ca3176e7SBrian Feldman } 1326af12a3e7SDag-Erling Smørgrav if (options.num_local_forwards > 0 || 1327af12a3e7SDag-Erling Smørgrav options.num_remote_forwards > 0) { 1328af12a3e7SDag-Erling Smørgrav error("Port forwarding is disabled to avoid " 1329af12a3e7SDag-Erling Smørgrav "man-in-the-middle attacks."); 1330af12a3e7SDag-Erling Smørgrav options.num_local_forwards = 1331af12a3e7SDag-Erling Smørgrav options.num_remote_forwards = 0; 1332d4af9e69SDag-Erling Smørgrav cancelled_forwarding = 1; 1333ca3176e7SBrian Feldman } 1334333ee039SDag-Erling Smørgrav if (options.tun_open != SSH_TUNMODE_NO) { 1335333ee039SDag-Erling Smørgrav error("Tunnel forwarding is disabled to avoid " 1336333ee039SDag-Erling Smørgrav "man-in-the-middle attacks."); 1337333ee039SDag-Erling Smørgrav options.tun_open = SSH_TUNMODE_NO; 1338d4af9e69SDag-Erling Smørgrav cancelled_forwarding = 1; 1339333ee039SDag-Erling Smørgrav } 134019261079SEd Maste if (options.update_hostkeys != 0) { 134119261079SEd Maste error("UpdateHostkeys is disabled because the host " 134219261079SEd Maste "key is not trusted."); 134319261079SEd Maste options.update_hostkeys = 0; 134419261079SEd Maste } 1345d4af9e69SDag-Erling Smørgrav if (options.exit_on_forward_failure && cancelled_forwarding) 1346d4af9e69SDag-Erling Smørgrav fatal("Error: forwarding disabled due to host key " 1347d4af9e69SDag-Erling Smørgrav "check failure"); 1348d4af9e69SDag-Erling Smørgrav 1349e8aafc91SKris Kennaway /* 1350e8aafc91SKris Kennaway * XXX Should permit the user to change to use the new id. 1351e8aafc91SKris Kennaway * This could be done by converting the host key to an 1352e8aafc91SKris Kennaway * identifying sentence, tell that the host identifies itself 135319261079SEd Maste * by that sentence, and ask the user if they wish to 1354e8aafc91SKris Kennaway * accept the authentication. 1355e8aafc91SKris Kennaway */ 1356e8aafc91SKris Kennaway break; 1357f388f5efSDag-Erling Smørgrav case HOST_FOUND: 1358f388f5efSDag-Erling Smørgrav fatal("internal error"); 1359f388f5efSDag-Erling Smørgrav break; 1360e8aafc91SKris Kennaway } 1361ca3176e7SBrian Feldman 1362ca3176e7SBrian Feldman if (options.check_host_ip && host_status != HOST_CHANGED && 1363ca3176e7SBrian Feldman ip_status == HOST_CHANGED) { 1364af12a3e7SDag-Erling Smørgrav snprintf(msg, sizeof(msg), 1365af12a3e7SDag-Erling Smørgrav "Warning: the %s host key for '%.200s' " 1366af12a3e7SDag-Erling Smørgrav "differs from the key for the IP address '%.128s'" 13674a421b63SDag-Erling Smørgrav "\nOffending key for IP in %s:%lu", 13684a421b63SDag-Erling Smørgrav type, host, ip, ip_found->file, ip_found->line); 1369af12a3e7SDag-Erling Smørgrav if (host_status == HOST_OK) { 1370af12a3e7SDag-Erling Smørgrav len = strlen(msg); 1371af12a3e7SDag-Erling Smørgrav snprintf(msg + len, sizeof(msg) - len, 13724a421b63SDag-Erling Smørgrav "\nMatching host key in %s:%lu", 13734a421b63SDag-Erling Smørgrav host_found->file, host_found->line); 1374af12a3e7SDag-Erling Smørgrav } 13754f52dfbbSDag-Erling Smørgrav if (options.strict_host_key_checking == 13764f52dfbbSDag-Erling Smørgrav SSH_STRICT_HOSTKEY_ASK) { 1377af12a3e7SDag-Erling Smørgrav strlcat(msg, "\nAre you sure you want " 1378af12a3e7SDag-Erling Smørgrav "to continue connecting (yes/no)? ", sizeof(msg)); 137919261079SEd Maste if (!confirm(msg, NULL)) 1380af12a3e7SDag-Erling Smørgrav goto fail; 13814f52dfbbSDag-Erling Smørgrav } else if (options.strict_host_key_checking != 13824f52dfbbSDag-Erling Smørgrav SSH_STRICT_HOSTKEY_OFF) { 13834f52dfbbSDag-Erling Smørgrav logit("%s", msg); 13844f52dfbbSDag-Erling Smørgrav error("Exiting, you have requested strict checking."); 13854f52dfbbSDag-Erling Smørgrav goto fail; 1386af12a3e7SDag-Erling Smørgrav } else { 1387cf2b5f3bSDag-Erling Smørgrav logit("%s", msg); 1388ca3176e7SBrian Feldman } 1389ca3176e7SBrian Feldman } 1390ca3176e7SBrian Feldman 1391bc5531deSDag-Erling Smørgrav if (!hostkey_trusted && options.update_hostkeys) { 139219261079SEd Maste debug_f("hostkey not known or explicitly trusted: " 139319261079SEd Maste "disabling UpdateHostkeys"); 1394bc5531deSDag-Erling Smørgrav options.update_hostkeys = 0; 1395bc5531deSDag-Erling Smørgrav } 1396bc5531deSDag-Erling Smørgrav 1397e4a9863fSDag-Erling Smørgrav free(ip); 1398e4a9863fSDag-Erling Smørgrav free(host); 13994a421b63SDag-Erling Smørgrav if (host_hostkeys != NULL) 14004a421b63SDag-Erling Smørgrav free_hostkeys(host_hostkeys); 14014a421b63SDag-Erling Smørgrav if (ip_hostkeys != NULL) 14024a421b63SDag-Erling Smørgrav free_hostkeys(ip_hostkeys); 1403af12a3e7SDag-Erling Smørgrav return 0; 1404af12a3e7SDag-Erling Smørgrav 1405af12a3e7SDag-Erling Smørgrav fail: 1406b15c8340SDag-Erling Smørgrav if (want_cert && host_status != HOST_REVOKED) { 1407b15c8340SDag-Erling Smørgrav /* 1408b15c8340SDag-Erling Smørgrav * No matching certificate. Downgrade cert to raw key and 1409b15c8340SDag-Erling Smørgrav * search normally. 1410b15c8340SDag-Erling Smørgrav */ 1411b15c8340SDag-Erling Smørgrav debug("No matching CA found. Retry with plain key"); 14124f52dfbbSDag-Erling Smørgrav if ((r = sshkey_from_private(host_key, &raw_key)) != 0) 141319261079SEd Maste fatal_fr(r, "decode key"); 14144f52dfbbSDag-Erling Smørgrav if ((r = sshkey_drop_cert(raw_key)) != 0) 141519261079SEd Maste fatal_r(r, "Couldn't drop certificate"); 1416b15c8340SDag-Erling Smørgrav host_key = raw_key; 1417b15c8340SDag-Erling Smørgrav goto retry; 1418b15c8340SDag-Erling Smørgrav } 14194f52dfbbSDag-Erling Smørgrav sshkey_free(raw_key); 1420e4a9863fSDag-Erling Smørgrav free(ip); 1421e4a9863fSDag-Erling Smørgrav free(host); 14224a421b63SDag-Erling Smørgrav if (host_hostkeys != NULL) 14234a421b63SDag-Erling Smørgrav free_hostkeys(host_hostkeys); 14244a421b63SDag-Erling Smørgrav if (ip_hostkeys != NULL) 14254a421b63SDag-Erling Smørgrav free_hostkeys(ip_hostkeys); 1426af12a3e7SDag-Erling Smørgrav return -1; 1427e8aafc91SKris Kennaway } 1428511b41d2SMark Murray 1429cf2b5f3bSDag-Erling Smørgrav /* returns 0 if key verifies or -1 if key does NOT verify */ 1430fe5fd017SMark Murray int 143119261079SEd Maste verify_host_key(char *host, struct sockaddr *hostaddr, struct sshkey *host_key, 143219261079SEd Maste const struct ssh_conn_info *cinfo) 1433fe5fd017SMark Murray { 1434acc1a9efSDag-Erling Smørgrav u_int i; 1435a0ee8cc6SDag-Erling Smørgrav int r = -1, flags = 0; 1436acc1a9efSDag-Erling Smørgrav char valid[64], *fp = NULL, *cafp = NULL; 1437bc5531deSDag-Erling Smørgrav struct sshkey *plain = NULL; 14384a421b63SDag-Erling Smørgrav 1439bc5531deSDag-Erling Smørgrav if ((fp = sshkey_fingerprint(host_key, 1440bc5531deSDag-Erling Smørgrav options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) { 144119261079SEd Maste error_fr(r, "fingerprint host key"); 1442bc5531deSDag-Erling Smørgrav r = -1; 1443bc5531deSDag-Erling Smørgrav goto out; 1444bc5531deSDag-Erling Smørgrav } 1445fe5fd017SMark Murray 1446acc1a9efSDag-Erling Smørgrav if (sshkey_is_cert(host_key)) { 1447acc1a9efSDag-Erling Smørgrav if ((cafp = sshkey_fingerprint(host_key->cert->signature_key, 1448acc1a9efSDag-Erling Smørgrav options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) { 144919261079SEd Maste error_fr(r, "fingerprint CA key"); 1450acc1a9efSDag-Erling Smørgrav r = -1; 1451acc1a9efSDag-Erling Smørgrav goto out; 1452acc1a9efSDag-Erling Smørgrav } 1453acc1a9efSDag-Erling Smørgrav sshkey_format_cert_validity(host_key->cert, 1454acc1a9efSDag-Erling Smørgrav valid, sizeof(valid)); 1455acc1a9efSDag-Erling Smørgrav debug("Server host certificate: %s %s, serial %llu " 1456acc1a9efSDag-Erling Smørgrav "ID \"%s\" CA %s %s valid %s", 1457acc1a9efSDag-Erling Smørgrav sshkey_ssh_name(host_key), fp, 1458acc1a9efSDag-Erling Smørgrav (unsigned long long)host_key->cert->serial, 1459acc1a9efSDag-Erling Smørgrav host_key->cert->key_id, 1460acc1a9efSDag-Erling Smørgrav sshkey_ssh_name(host_key->cert->signature_key), cafp, 1461acc1a9efSDag-Erling Smørgrav valid); 1462acc1a9efSDag-Erling Smørgrav for (i = 0; i < host_key->cert->nprincipals; i++) { 1463acc1a9efSDag-Erling Smørgrav debug2("Server host certificate hostname: %s", 1464acc1a9efSDag-Erling Smørgrav host_key->cert->principals[i]); 1465acc1a9efSDag-Erling Smørgrav } 1466acc1a9efSDag-Erling Smørgrav } else { 14674f52dfbbSDag-Erling Smørgrav debug("Server host key: %s %s", sshkey_ssh_name(host_key), fp); 1468acc1a9efSDag-Erling Smørgrav } 1469bc5531deSDag-Erling Smørgrav 1470bc5531deSDag-Erling Smørgrav if (sshkey_equal(previous_host_key, host_key)) { 147119261079SEd Maste debug2_f("server host key %s %s matches cached key", 147219261079SEd Maste sshkey_type(host_key), fp); 1473bc5531deSDag-Erling Smørgrav r = 0; 1474bc5531deSDag-Erling Smørgrav goto out; 1475bc5531deSDag-Erling Smørgrav } 1476bc5531deSDag-Erling Smørgrav 1477bc5531deSDag-Erling Smørgrav /* Check in RevokedHostKeys file if specified */ 1478bc5531deSDag-Erling Smørgrav if (options.revoked_host_keys != NULL) { 1479bc5531deSDag-Erling Smørgrav r = sshkey_check_revoked(host_key, options.revoked_host_keys); 1480bc5531deSDag-Erling Smørgrav switch (r) { 1481bc5531deSDag-Erling Smørgrav case 0: 1482bc5531deSDag-Erling Smørgrav break; /* not revoked */ 1483bc5531deSDag-Erling Smørgrav case SSH_ERR_KEY_REVOKED: 1484bc5531deSDag-Erling Smørgrav error("Host key %s %s revoked by file %s", 1485bc5531deSDag-Erling Smørgrav sshkey_type(host_key), fp, 1486bc5531deSDag-Erling Smørgrav options.revoked_host_keys); 1487bc5531deSDag-Erling Smørgrav r = -1; 1488bc5531deSDag-Erling Smørgrav goto out; 1489bc5531deSDag-Erling Smørgrav default: 149019261079SEd Maste error_r(r, "Error checking host key %s %s in " 149119261079SEd Maste "revoked keys file %s", sshkey_type(host_key), 149219261079SEd Maste fp, options.revoked_host_keys); 1493bc5531deSDag-Erling Smørgrav r = -1; 1494bc5531deSDag-Erling Smørgrav goto out; 1495bc5531deSDag-Erling Smørgrav } 1496a0ee8cc6SDag-Erling Smørgrav } 1497a0ee8cc6SDag-Erling Smørgrav 14983a0b9b77SXin LI if (options.verify_host_key_dns) { 14993a0b9b77SXin LI /* 15003a0b9b77SXin LI * XXX certs are not yet supported for DNS, so downgrade 15013a0b9b77SXin LI * them and try the plain key. 15023a0b9b77SXin LI */ 1503bc5531deSDag-Erling Smørgrav if ((r = sshkey_from_private(host_key, &plain)) != 0) 1504bc5531deSDag-Erling Smørgrav goto out; 1505bc5531deSDag-Erling Smørgrav if (sshkey_is_cert(plain)) 1506bc5531deSDag-Erling Smørgrav sshkey_drop_cert(plain); 15073a0b9b77SXin LI if (verify_host_key_dns(host, hostaddr, plain, &flags) == 0) { 15081ec0d754SDag-Erling Smørgrav if (flags & DNS_VERIFY_FOUND) { 15091ec0d754SDag-Erling Smørgrav if (options.verify_host_key_dns == 1 && 15101ec0d754SDag-Erling Smørgrav flags & DNS_VERIFY_MATCH && 15113a0b9b77SXin LI flags & DNS_VERIFY_SECURE) { 1512a0ee8cc6SDag-Erling Smørgrav r = 0; 1513bc5531deSDag-Erling Smørgrav goto out; 15143a0b9b77SXin LI } 15151ec0d754SDag-Erling Smørgrav if (flags & DNS_VERIFY_MATCH) { 15161ec0d754SDag-Erling Smørgrav matching_host_key_dns = 1; 15171ec0d754SDag-Erling Smørgrav } else { 15183a0b9b77SXin LI warn_changed_key(plain); 15193a0b9b77SXin LI error("Update the SSHFP RR in DNS " 15203a0b9b77SXin LI "with the new host key to get rid " 15213a0b9b77SXin LI "of this message."); 1522cf2b5f3bSDag-Erling Smørgrav } 1523cf2b5f3bSDag-Erling Smørgrav } 15241ec0d754SDag-Erling Smørgrav } 15253a0b9b77SXin LI } 152619261079SEd Maste r = check_host_key(host, cinfo, hostaddr, options.port, host_key, 152719261079SEd Maste RDRW, 0, options.user_hostfiles, options.num_user_hostfiles, 152819261079SEd Maste options.system_hostfiles, options.num_system_hostfiles, 152919261079SEd Maste options.known_hosts_command); 1530a0ee8cc6SDag-Erling Smørgrav 1531bc5531deSDag-Erling Smørgrav out: 1532bc5531deSDag-Erling Smørgrav sshkey_free(plain); 1533bc5531deSDag-Erling Smørgrav free(fp); 1534acc1a9efSDag-Erling Smørgrav free(cafp); 1535a0ee8cc6SDag-Erling Smørgrav if (r == 0 && host_key != NULL) { 15364f52dfbbSDag-Erling Smørgrav sshkey_free(previous_host_key); 15374f52dfbbSDag-Erling Smørgrav r = sshkey_from_private(host_key, &previous_host_key); 1538a0ee8cc6SDag-Erling Smørgrav } 1539a0ee8cc6SDag-Erling Smørgrav 1540a0ee8cc6SDag-Erling Smørgrav return r; 1541fe5fd017SMark Murray } 1542fe5fd017SMark Murray 1543511b41d2SMark Murray /* 1544511b41d2SMark Murray * Starts a dialog with the server, and authenticates the current user on the 1545511b41d2SMark Murray * server. This does not need any extra privileges. The basic connection 1546511b41d2SMark Murray * to the server must already have been established before this is called. 1547511b41d2SMark Murray * If login fails, this function prints an error and never returns. 1548511b41d2SMark Murray * This function does not require super-user privileges. 1549511b41d2SMark Murray */ 1550511b41d2SMark Murray void 155119261079SEd Maste ssh_login(struct ssh *ssh, Sensitive *sensitive, const char *orighost, 155219261079SEd Maste struct sockaddr *hostaddr, u_short port, struct passwd *pw, int timeout_ms, 155319261079SEd Maste const struct ssh_conn_info *cinfo) 1554511b41d2SMark Murray { 1555f7167e0eSDag-Erling Smørgrav char *host; 1556e8aafc91SKris Kennaway char *server_user, *local_user; 155719261079SEd Maste int r; 1558e8aafc91SKris Kennaway 1559e8aafc91SKris Kennaway local_user = xstrdup(pw->pw_name); 1560e8aafc91SKris Kennaway server_user = options.user ? options.user : local_user; 1561511b41d2SMark Murray 1562511b41d2SMark Murray /* Convert the user-supplied hostname into all lowercase. */ 1563511b41d2SMark Murray host = xstrdup(orighost); 1564f7167e0eSDag-Erling Smørgrav lowercase(host); 1565511b41d2SMark Murray 1566511b41d2SMark Murray /* Exchange protocol version identification strings with the server. */ 1567bffe60eaSEd Maste if ((r = kex_exchange_identification(ssh, timeout_ms, NULL)) != 0) 156819261079SEd Maste sshpkt_fatal(ssh, r, "banner exchange"); 1569511b41d2SMark Murray 1570511b41d2SMark Murray /* Put the connection into non-blocking mode. */ 157119261079SEd Maste ssh_packet_set_nonblocking(ssh); 1572511b41d2SMark Murray 1573511b41d2SMark Murray /* key exchange */ 1574511b41d2SMark Murray /* authenticate user */ 1575557f75e5SDag-Erling Smørgrav debug("Authenticating to %s:%d as '%s'", host, port, server_user); 157619261079SEd Maste ssh_kex2(ssh, host, hostaddr, port, cinfo); 157719261079SEd Maste ssh_userauth2(ssh, local_user, server_user, host, sensitive); 1578e4a9863fSDag-Erling Smørgrav free(local_user); 157919261079SEd Maste free(host); 1580e0fbb1d2SBrian Feldman } 1581f388f5efSDag-Erling Smørgrav 1582f388f5efSDag-Erling Smørgrav /* print all known host keys for a given host, but skip keys of given type */ 1583f388f5efSDag-Erling Smørgrav static int 15844f52dfbbSDag-Erling Smørgrav show_other_keys(struct hostkeys *hostkeys, struct sshkey *key) 1585f388f5efSDag-Erling Smørgrav { 1586f7167e0eSDag-Erling Smørgrav int type[] = { 1587f7167e0eSDag-Erling Smørgrav KEY_RSA, 1588f7167e0eSDag-Erling Smørgrav KEY_DSA, 1589f7167e0eSDag-Erling Smørgrav KEY_ECDSA, 1590f7167e0eSDag-Erling Smørgrav KEY_ED25519, 159147dd1d1bSDag-Erling Smørgrav KEY_XMSS, 1592f7167e0eSDag-Erling Smørgrav -1 1593f7167e0eSDag-Erling Smørgrav }; 15944a421b63SDag-Erling Smørgrav int i, ret = 0; 15954a421b63SDag-Erling Smørgrav char *fp, *ra; 15964a421b63SDag-Erling Smørgrav const struct hostkey_entry *found; 1597f388f5efSDag-Erling Smørgrav 1598f388f5efSDag-Erling Smørgrav for (i = 0; type[i] != -1; i++) { 1599f388f5efSDag-Erling Smørgrav if (type[i] == key->type) 1600f388f5efSDag-Erling Smørgrav continue; 160119261079SEd Maste if (!lookup_key_in_hostkeys_by_type(hostkeys, type[i], 160219261079SEd Maste -1, &found)) 1603f388f5efSDag-Erling Smørgrav continue; 1604bc5531deSDag-Erling Smørgrav fp = sshkey_fingerprint(found->key, 1605bc5531deSDag-Erling Smørgrav options.fingerprint_hash, SSH_FP_DEFAULT); 1606bc5531deSDag-Erling Smørgrav ra = sshkey_fingerprint(found->key, 1607bc5531deSDag-Erling Smørgrav options.fingerprint_hash, SSH_FP_RANDOMART); 1608bc5531deSDag-Erling Smørgrav if (fp == NULL || ra == NULL) 160919261079SEd Maste fatal_f("sshkey_fingerprint fail"); 16104a421b63SDag-Erling Smørgrav logit("WARNING: %s key found for host %s\n" 16114a421b63SDag-Erling Smørgrav "in %s:%lu\n" 16124a421b63SDag-Erling Smørgrav "%s key fingerprint %s.", 1613190cef3dSDag-Erling Smørgrav sshkey_type(found->key), 16144a421b63SDag-Erling Smørgrav found->host, found->file, found->line, 1615190cef3dSDag-Erling Smørgrav sshkey_type(found->key), fp); 16164a421b63SDag-Erling Smørgrav if (options.visual_host_key) 16174a421b63SDag-Erling Smørgrav logit("%s", ra); 1618e4a9863fSDag-Erling Smørgrav free(ra); 1619e4a9863fSDag-Erling Smørgrav free(fp); 16204a421b63SDag-Erling Smørgrav ret = 1; 1621f388f5efSDag-Erling Smørgrav } 16224a421b63SDag-Erling Smørgrav return ret; 1623f388f5efSDag-Erling Smørgrav } 16241ec0d754SDag-Erling Smørgrav 16251ec0d754SDag-Erling Smørgrav static void 16264f52dfbbSDag-Erling Smørgrav warn_changed_key(struct sshkey *host_key) 16271ec0d754SDag-Erling Smørgrav { 16281ec0d754SDag-Erling Smørgrav char *fp; 16291ec0d754SDag-Erling Smørgrav 1630bc5531deSDag-Erling Smørgrav fp = sshkey_fingerprint(host_key, options.fingerprint_hash, 1631bc5531deSDag-Erling Smørgrav SSH_FP_DEFAULT); 1632bc5531deSDag-Erling Smørgrav if (fp == NULL) 163319261079SEd Maste fatal_f("sshkey_fingerprint fail"); 16341ec0d754SDag-Erling Smørgrav 16351ec0d754SDag-Erling Smørgrav error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); 16361ec0d754SDag-Erling Smørgrav error("@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @"); 16371ec0d754SDag-Erling Smørgrav error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); 16381ec0d754SDag-Erling Smørgrav error("IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!"); 16391ec0d754SDag-Erling Smørgrav error("Someone could be eavesdropping on you right now (man-in-the-middle attack)!"); 16404a421b63SDag-Erling Smørgrav error("It is also possible that a host key has just been changed."); 16411ec0d754SDag-Erling Smørgrav error("The fingerprint for the %s key sent by the remote host is\n%s.", 1642190cef3dSDag-Erling Smørgrav sshkey_type(host_key), fp); 16431ec0d754SDag-Erling Smørgrav error("Please contact your system administrator."); 16441ec0d754SDag-Erling Smørgrav 1645e4a9863fSDag-Erling Smørgrav free(fp); 16461ec0d754SDag-Erling Smørgrav } 1647b74df5b2SDag-Erling Smørgrav 1648b74df5b2SDag-Erling Smørgrav /* 1649b74df5b2SDag-Erling Smørgrav * Execute a local command 1650b74df5b2SDag-Erling Smørgrav */ 1651b74df5b2SDag-Erling Smørgrav int 1652b74df5b2SDag-Erling Smørgrav ssh_local_cmd(const char *args) 1653b74df5b2SDag-Erling Smørgrav { 1654b74df5b2SDag-Erling Smørgrav char *shell; 1655b74df5b2SDag-Erling Smørgrav pid_t pid; 1656b74df5b2SDag-Erling Smørgrav int status; 16574a421b63SDag-Erling Smørgrav void (*osighand)(int); 1658b74df5b2SDag-Erling Smørgrav 1659b74df5b2SDag-Erling Smørgrav if (!options.permit_local_command || 1660b74df5b2SDag-Erling Smørgrav args == NULL || !*args) 1661b74df5b2SDag-Erling Smørgrav return (1); 1662b74df5b2SDag-Erling Smørgrav 16634a421b63SDag-Erling Smørgrav if ((shell = getenv("SHELL")) == NULL || *shell == '\0') 1664b74df5b2SDag-Erling Smørgrav shell = _PATH_BSHELL; 1665b74df5b2SDag-Erling Smørgrav 166619261079SEd Maste osighand = ssh_signal(SIGCHLD, SIG_DFL); 1667b74df5b2SDag-Erling Smørgrav pid = fork(); 1668b74df5b2SDag-Erling Smørgrav if (pid == 0) { 166919261079SEd Maste ssh_signal(SIGPIPE, SIG_DFL); 1670b74df5b2SDag-Erling Smørgrav debug3("Executing %s -c \"%s\"", shell, args); 1671b74df5b2SDag-Erling Smørgrav execl(shell, shell, "-c", args, (char *)NULL); 1672b74df5b2SDag-Erling Smørgrav error("Couldn't execute %s -c \"%s\": %s", 1673b74df5b2SDag-Erling Smørgrav shell, args, strerror(errno)); 1674b74df5b2SDag-Erling Smørgrav _exit(1); 1675b74df5b2SDag-Erling Smørgrav } else if (pid == -1) 1676b74df5b2SDag-Erling Smørgrav fatal("fork failed: %.100s", strerror(errno)); 1677b74df5b2SDag-Erling Smørgrav while (waitpid(pid, &status, 0) == -1) 1678b74df5b2SDag-Erling Smørgrav if (errno != EINTR) 1679b74df5b2SDag-Erling Smørgrav fatal("Couldn't wait for child: %s", strerror(errno)); 168019261079SEd Maste ssh_signal(SIGCHLD, osighand); 1681b74df5b2SDag-Erling Smørgrav 1682b74df5b2SDag-Erling Smørgrav if (!WIFEXITED(status)) 1683b74df5b2SDag-Erling Smørgrav return (1); 1684b74df5b2SDag-Erling Smørgrav 1685b74df5b2SDag-Erling Smørgrav return (WEXITSTATUS(status)); 1686b74df5b2SDag-Erling Smørgrav } 1687acc1a9efSDag-Erling Smørgrav 1688acc1a9efSDag-Erling Smørgrav void 168919261079SEd Maste maybe_add_key_to_agent(const char *authfile, struct sshkey *private, 169019261079SEd Maste const char *comment, const char *passphrase) 1691acc1a9efSDag-Erling Smørgrav { 1692acc1a9efSDag-Erling Smørgrav int auth_sock = -1, r; 169319261079SEd Maste const char *skprovider = NULL; 1694acc1a9efSDag-Erling Smørgrav 1695acc1a9efSDag-Erling Smørgrav if (options.add_keys_to_agent == 0) 1696acc1a9efSDag-Erling Smørgrav return; 1697acc1a9efSDag-Erling Smørgrav 1698acc1a9efSDag-Erling Smørgrav if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { 1699acc1a9efSDag-Erling Smørgrav debug3("no authentication agent, not adding key"); 1700acc1a9efSDag-Erling Smørgrav return; 1701acc1a9efSDag-Erling Smørgrav } 1702acc1a9efSDag-Erling Smørgrav 1703acc1a9efSDag-Erling Smørgrav if (options.add_keys_to_agent == 2 && 1704acc1a9efSDag-Erling Smørgrav !ask_permission("Add key %s (%s) to agent?", authfile, comment)) { 1705acc1a9efSDag-Erling Smørgrav debug3("user denied adding this key"); 1706d93a896eSDag-Erling Smørgrav close(auth_sock); 1707acc1a9efSDag-Erling Smørgrav return; 1708acc1a9efSDag-Erling Smørgrav } 170919261079SEd Maste if (sshkey_is_sk(private)) 171019261079SEd Maste skprovider = options.sk_provider; 171119261079SEd Maste if ((r = ssh_add_identity_constrained(auth_sock, private, 171219261079SEd Maste comment == NULL ? authfile : comment, 171319261079SEd Maste options.add_keys_to_agent_lifespan, 17141323ec57SEd Maste (options.add_keys_to_agent == 3), 0, skprovider, NULL, 0)) == 0) 1715acc1a9efSDag-Erling Smørgrav debug("identity added to agent: %s", authfile); 1716acc1a9efSDag-Erling Smørgrav else 1717acc1a9efSDag-Erling Smørgrav debug("could not add identity to agent: %s (%d)", authfile, r); 1718d93a896eSDag-Erling Smørgrav close(auth_sock); 1719acc1a9efSDag-Erling Smørgrav } 1720