- djm@cvs.openbsd.org 2013/10/16 02:31:47
[readconf.c readconf.h roaming_client.c ssh.1 ssh.c ssh_config.5]
[sshconnect.c sshconnect.h]
Implement client-side hostname canonicalisation to allow an explicit
search path of domain suffixes to use to convert unqualified host names
to fully-qualified ones for host key matching.
This is particularly useful for host certificates, which would otherwise
need to list unqualified names alongside fully-qualified ones (and this
causes a number of problems).
"looks fine" markus@
diff --git a/ssh.c b/ssh.c
index ad6ae0f..230591b 100644
--- a/ssh.c
+++ b/ssh.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh.c,v 1.384 2013/10/14 23:31:01 djm Exp $ */
+/* $OpenBSD: ssh.c,v 1.385 2013/10/16 02:31:46 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -231,6 +231,134 @@
}
}
+static struct addrinfo *
+resolve_host(const char *name, u_int port, int logerr, char *cname, size_t clen)
+{
+ char strport[NI_MAXSERV];
+ struct addrinfo hints, *res;
+ int gaierr, loglevel = SYSLOG_LEVEL_DEBUG1;
+
+ snprintf(strport, sizeof strport, "%u", port);
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = options.address_family;
+ hints.ai_socktype = SOCK_STREAM;
+ if (cname != NULL)
+ hints.ai_flags = AI_CANONNAME;
+ if ((gaierr = getaddrinfo(name, strport, &hints, &res)) != 0) {
+ if (logerr || (gaierr != EAI_NONAME && gaierr != EAI_NODATA))
+ loglevel = SYSLOG_LEVEL_ERROR;
+ do_log2(loglevel, "%s: Could not resolve hostname %.100s: %s",
+ __progname, name, ssh_gai_strerror(gaierr));
+ return NULL;
+ }
+ if (cname != NULL && res->ai_canonname != NULL) {
+ if (strlcpy(cname, res->ai_canonname, clen) >= clen) {
+ error("%s: host \"%s\" cname \"%s\" too long (max %lu)",
+ __func__, name, res->ai_canonname, (u_long)clen);
+ if (clen > 0)
+ *cname = '\0';
+ }
+ }
+ return res;
+}
+
+/*
+ * Check whether the cname is a permitted replacement for the hostname
+ * and perform the replacement if it is.
+ */
+static int
+check_follow_cname(char **namep, const char *cname)
+{
+ int i;
+ struct allowed_cname *rule;
+
+ if (*cname == '\0' || options.num_permitted_cnames == 0 ||
+ strcmp(*namep, cname) == 0)
+ return 0;
+ if (options.canonicalise_hostname == SSH_CANONICALISE_NO)
+ return 0;
+ /*
+ * Don't attempt to canonicalise names that will be interpreted by
+ * a proxy unless the user specifically requests so.
+ */
+ if (options.proxy_command != NULL &&
+ options.canonicalise_hostname != SSH_CANONICALISE_ALWAYS)
+ return 0;
+ debug3("%s: check \"%s\" CNAME \"%s\"", __func__, *namep, cname);
+ for (i = 0; i < options.num_permitted_cnames; i++) {
+ rule = options.permitted_cnames + i;
+ if (match_pattern_list(*namep, rule->source_list,
+ strlen(rule->source_list), 1) != 1 ||
+ match_pattern_list(cname, rule->target_list,
+ strlen(rule->target_list), 1) != 1)
+ continue;
+ verbose("Canonicalised DNS aliased hostname "
+ "\"%s\" => \"%s\"", *namep, cname);
+ free(*namep);
+ *namep = xstrdup(cname);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Attempt to resolve the supplied hostname after applying the user's
+ * canonicalisation rules. Returns the address list for the host or NULL
+ * if no name was found after canonicalisation.
+ */
+static struct addrinfo *
+resolve_canonicalise(char **hostp, u_int port)
+{
+ int i, ndots;
+ char *cp, *fullhost, cname_target[NI_MAXHOST];
+ struct addrinfo *addrs;
+
+ if (options.canonicalise_hostname == SSH_CANONICALISE_NO)
+ return NULL;
+ /*
+ * Don't attempt to canonicalise names that will be interpreted by
+ * a proxy unless the user specifically requests so.
+ */
+ if (options.proxy_command != NULL &&
+ options.canonicalise_hostname != SSH_CANONICALISE_ALWAYS)
+ return NULL;
+ /* Don't apply canonicalisation to sufficiently-qualified hostnames */
+ ndots = 0;
+ for (cp = *hostp; *cp != '\0'; cp++) {
+ if (*cp == '.')
+ ndots++;
+ }
+ if (ndots > options.canonicalise_max_dots) {
+ debug3("%s: not canonicalising hostname \"%s\" (max dots %d)",
+ __func__, *hostp, options.canonicalise_max_dots);
+ return NULL;
+ }
+ /* Attempt each supplied suffix */
+ for (i = 0; i < options.num_canonical_domains; i++) {
+ *cname_target = '\0';
+ xasprintf(&fullhost, "%s.%s.", *hostp,
+ options.canonical_domains[i]);
+ if ((addrs = resolve_host(fullhost, options.port, 0,
+ cname_target, sizeof(cname_target))) == NULL) {
+ free(fullhost);
+ continue;
+ }
+ /* Remove trailing '.' */
+ fullhost[strlen(fullhost) - 1] = '\0';
+ /* Follow CNAME if requested */
+ if (!check_follow_cname(&fullhost, cname_target)) {
+ debug("Canonicalised hostname \"%s\" => \"%s\"",
+ *hostp, fullhost);
+ }
+ free(*hostp);
+ *hostp = fullhost;
+ return addrs;
+ }
+ if (!options.canonicalise_fallback_local)
+ fatal("%s: Could not resolve host \"%s\"", __progname, host);
+ return NULL;
+}
+
/*
* Main program for the ssh client.
*/
@@ -240,12 +368,14 @@
int i, r, opt, exit_status, use_syslog;
char *p, *cp, *line, *argv0, buf[MAXPATHLEN], *host_arg, *logfile;
char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV];
+ char cname[NI_MAXHOST];
struct stat st;
struct passwd *pw;
int timeout_ms;
extern int optind, optreset;
extern char *optarg;
Forward fwd;
+ struct addrinfo *addrs = NULL;
/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
sanitise_stdfd();
@@ -630,9 +760,9 @@
usage();
options.user = p;
*cp = '\0';
- host = ++cp;
+ host = xstrdup(++cp);
} else
- host = *av;
+ host = xstrdup(*av);
if (ac > 1) {
optind = optreset = 1;
goto again;
@@ -644,6 +774,9 @@
if (!host)
usage();
+ lowercase(host);
+ host_arg = xstrdup(host);
+
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
@@ -728,6 +861,10 @@
strcmp(options.proxy_command, "-") == 0 &&
options.proxy_use_fdpass)
fatal("ProxyCommand=- and ProxyUseFDPass are incompatible");
+#ifndef HAVE_CYGWIN
+ if (original_effective_uid != 0)
+ options.use_privileged_port = 0;
+#endif
/* reinit */
log_init(argv0, options.log_level, SYSLOG_FACILITY_USER, !use_syslog);
@@ -762,10 +899,26 @@
options.port = default_ssh_port();
/* preserve host name given on command line for %n expansion */
- host_arg = host;
if (options.hostname != NULL) {
- host = percent_expand(options.hostname,
+ cp = percent_expand(options.hostname,
"h", host, (char *)NULL);
+ free(host);
+ host = cp;
+ }
+
+ /* If canonicalisation requested then try to apply it */
+ if (options.canonicalise_hostname != SSH_CANONICALISE_NO)
+ addrs = resolve_canonicalise(&host, options.port);
+ /*
+ * If canonicalisation not requested, or if it failed then try to
+ * resolve the bare hostname name using the system resolver's usual
+ * search rules.
+ */
+ if (addrs == NULL) {
+ if ((addrs = resolve_host(host, options.port, 1,
+ cname, sizeof(cname))) == NULL)
+ cleanup_exit(255); /* resolve_host logs the error */
+ check_follow_cname(&host, cname);
}
if (gethostname(thishost, sizeof(thishost)) == -1)
@@ -803,16 +956,15 @@
timeout_ms = options.connection_timeout * 1000;
/* Open a connection to the remote host. */
- if (ssh_connect(host, &hostaddr, options.port,
- options.address_family, options.connection_attempts, &timeout_ms,
- options.tcp_keep_alive,
-#ifdef HAVE_CYGWIN
- options.use_privileged_port,
-#else
- original_effective_uid == 0 && options.use_privileged_port,
-#endif
- options.proxy_command) != 0)
- exit(255);
+ if (ssh_connect(host, addrs, &hostaddr, options.port,
+ options.address_family, options.connection_attempts,
+ &timeout_ms, options.tcp_keep_alive,
+ options.use_privileged_port) != 0)
+ exit(255);
+
+ freeaddrinfo(addrs);
+ packet_set_timeout(options.server_alive_interval,
+ options.server_alive_count_max);
if (timeout_ms > 0)
debug3("timeout: %d ms remain after connect", timeout_ms);
@@ -1621,4 +1773,3 @@
signal(sig, main_sigchld_handler);
errno = save_errno;
}
-