diff --git a/ssh.c b/ssh.c index 862625432..ac85b2bb5 100644 --- a/ssh.c +++ b/ssh.c @@ -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 * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -271,6 +271,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. @@ -376,6 +410,33 @@ resolve_canonicalize(char **hostp, int port) char *cp, *fullhost, newname[NI_MAXHOST]; struct addrinfo *addrs; + /* + * Attempt to canonicalise addresses, regardless of + * whether hostname canonicalisation was requested + */ + if ((addrs = resolve_addr(*hostp, port, + newname, sizeof(newname))) != NULL) { + debug2("%s: hostname %.100s is address", __func__, *hostp); + if (strcasecmp(*hostp, newname) != 0) { + debug2("%s: canonicalised address \"%s\" => \"%s\"", + __func__, *hostp, newname); + free(*hostp); + *hostp = xstrdup(newname); + } + 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; @@ -389,19 +450,6 @@ resolve_canonicalize(char **hostp, int port) 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); - if (strcasecmp(*hostp, newname) != 0) { - debug2("%s: canonicalised address \"%s\" => \"%s\"", - __func__, *hostp, newname); - free(*hostp); - *hostp = xstrdup(newname); - } - return addrs; - } - /* If domain name is anchored, then resolve it now */ if ((*hostp)[strlen(*hostp) - 1] == '.') { debug3("%s: name is fully qualified", __func__); @@ -514,7 +562,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; @@ -1055,9 +1103,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); /*