don't attempt to force hostnames that are addresses to lowercase, but
authordjm <djm@openbsd.org>
Tue, 23 Jan 2018 05:06:25 +0000 (05:06 +0000)
committerdjm <djm@openbsd.org>
Tue, 23 Jan 2018 05:06:25 +0000 (05:06 +0000)
instead canonicalise them through getnameinfo/getaddrinfo to remove
ambiguities (e.g. ::0001 => ::1) before they are matched against
known_hosts; bz#2763, ok dtucker@

usr.bin/ssh/ssh.c

index 3eb9563..0c15c19 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh.c,v 1.469 2017/11/01 00:04:15 djm Exp $ */
+/* $OpenBSD: ssh.c,v 1.470 2018/01/23 05:06:25 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -255,6 +255,40 @@ resolve_host(const char *name, int port, int logerr, char *cname, size_t clen)
        return res;
 }
 
+/* Returns non-zero if name can only be an address and not a hostname */
+static int
+is_addr_fast(const char *name)
+{
+       return (strchr(name, '%') != NULL || strchr(name, ':') != NULL ||
+           strspn(name, "0123456789.") == strlen(name));
+}
+
+/* Returns non-zero if name represents a valid, single address */
+static int
+is_addr(const char *name)
+{
+       char strport[NI_MAXSERV];
+       struct addrinfo hints, *res;
+
+       if (is_addr_fast(name))
+               return 1;
+
+       snprintf(strport, sizeof strport, "%u", default_ssh_port());
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = options.address_family == -1 ?
+           AF_UNSPEC : options.address_family;
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV;
+       if (getaddrinfo(name, strport, &hints, &res) != 0)
+               return 0;
+       if (res == NULL || res->ai_next != NULL) {
+               freeaddrinfo(res);
+               return 0;
+       }
+       freeaddrinfo(res);
+       return 1;
+}
+
 /*
  * Attempt to resolve a numeric host address / port to a single address.
  * Returns a canonical address string.
@@ -360,20 +394,10 @@ resolve_canonicalize(char **hostp, int port)
        char *cp, *fullhost, newname[NI_MAXHOST];
        struct addrinfo *addrs;
 
-       if (options.canonicalize_hostname == SSH_CANONICALISE_NO)
-               return NULL;
-
        /*
-        * Don't attempt to canonicalize names that will be interpreted by
-        * a proxy unless the user specifically requests so.
+        * Attempt to canonicalise addresses, regardless of
+        * whether hostname canonicalisation was requested
         */
-       direct = option_clear_or_none(options.proxy_command) &&
-           options.jump_host == NULL;
-       if (!direct &&
-           options.canonicalize_hostname != SSH_CANONICALISE_ALWAYS)
-               return NULL;
-
-       /* Try numeric hostnames first */
        if ((addrs = resolve_addr(*hostp, port,
            newname, sizeof(newname))) != NULL) {
                debug2("%s: hostname %.100s is address", __func__, *hostp);
@@ -386,6 +410,30 @@ resolve_canonicalize(char **hostp, int port)
                return addrs;
        }
 
+       /*
+        * If this looks like an address but didn't parse as one, it might
+        * be an address with an invalid interface scope. Skip further
+        * attempts at canonicalisation.
+        */
+       if (is_addr_fast(*hostp)) {
+               debug("%s: hostname %.100s is an unrecognised address",
+                   __func__, *hostp);
+               return NULL;
+       }
+
+       if (options.canonicalize_hostname == SSH_CANONICALISE_NO)
+               return NULL;
+
+       /*
+        * Don't attempt to canonicalize names that will be interpreted by
+        * a proxy unless the user specifically requests so.
+        */
+       direct = option_clear_or_none(options.proxy_command) &&
+           options.jump_host == NULL;
+       if (!direct &&
+           options.canonicalize_hostname != SSH_CANONICALISE_ALWAYS)
+               return NULL;
+
        /* If domain name is anchored, then resolve it now */
        if ((*hostp)[strlen(*hostp) - 1] == '.') {
                debug3("%s: name is fully qualified", __func__);
@@ -498,7 +546,7 @@ main(int ac, char **av)
 {
        struct ssh *ssh = NULL;
        int i, r, opt, exit_status, use_syslog, direct, timeout_ms;
-       int config_test = 0, opt_terminated = 0;
+       int was_addr, config_test = 0, opt_terminated = 0;
        char *p, *cp, *line, *argv0, buf[PATH_MAX], *logfile;
        char cname[NI_MAXHOST];
        struct stat st;
@@ -1024,9 +1072,15 @@ main(int ac, char **av)
                options.hostname = xstrdup(host);
        }
 
-       /* If canonicalization requested then try to apply it */
-       lowercase(host);
-       if (options.canonicalize_hostname != SSH_CANONICALISE_NO)
+       /* Don't lowercase addresses, they will be explicitly canonicalised */
+       if ((was_addr = is_addr(host)) == 0)
+               lowercase(host);
+
+       /*
+        * Try to canonicalize if requested by configuration or the
+        * hostname is an address.
+        */
+       if (options.canonicalize_hostname != SSH_CANONICALISE_NO || was_addr)
                addrs = resolve_canonicalize(&host, options.port);
 
        /*