diff options
author | Damien Miller <djm@mindrot.org> | 2014-02-24 15:57:55 +1100 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2014-02-24 15:57:55 +1100 |
commit | 13f97b2286142fd0b8eab94e4ce84fe124eeb752 (patch) | |
tree | b332b16a032b4670b60db1261b6dac48e059d916 | |
parent | bee3a234f3d1ad4244952bcff1b4b7c525330dc2 (diff) |
- djm@cvs.openbsd.org 2014/02/23 20:11:36
[readconf.c readconf.h ssh.c ssh_config.5]
reparse ssh_config and ~/.ssh/config if hostname canonicalisation changes
the hostname. This allows users to write configurations that always
refer to canonical hostnames, e.g.
CanonicalizeHostname yes
CanonicalDomains int.example.org example.org
CanonicalizeFallbackLocal no
Host *.int.example.org
Compression off
Host *.example.org
User djm
ok markus@
-rw-r--r-- | ChangeLog | 16 | ||||
-rw-r--r-- | readconf.c | 27 | ||||
-rw-r--r-- | readconf.h | 4 | ||||
-rw-r--r-- | ssh.c | 150 | ||||
-rw-r--r-- | ssh_config.5 | 10 |
5 files changed, 145 insertions, 62 deletions
@@ -16,6 +16,22 @@ [ssh-ed25519.c] check for unsigned overflow; not reachable in OpenSSH but others might copy our code... + - djm@cvs.openbsd.org 2014/02/23 20:11:36 + [readconf.c readconf.h ssh.c ssh_config.5] + reparse ssh_config and ~/.ssh/config if hostname canonicalisation changes + the hostname. This allows users to write configurations that always + refer to canonical hostnames, e.g. + + CanonicalizeHostname yes + CanonicalDomains int.example.org example.org + CanonicalizeFallbackLocal no + + Host *.int.example.org + Compression off + Host *.example.org + User djm + + ok markus@ 20140213 - (dtucker) [configure.ac openbsd-compat/openssl-compat.{c,h}] Add compat @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.217 2014/02/22 01:32:19 djm Exp $ */ +/* $OpenBSD: readconf.c,v 1.218 2014/02/23 20:11:36 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -1467,6 +1467,13 @@ read_config_file(const char *filename, struct passwd *pw, const char *host, return 1; } +/* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */ +int +option_clear_or_none(const char *o) +{ + return o == NULL || strcasecmp(o, "none") == 0; +} + /* * Initializes options to special values that indicate that they have not yet * been set. Read_config_file will only set options with this value. Options @@ -1564,10 +1571,24 @@ initialize_options(Options * options) } /* + * A petite version of fill_default_options() that just fills the options + * needed for hostname canonicalization to proceed. + */ +void +fill_default_options_for_canonicalization(Options *options) +{ + if (options->canonicalize_max_dots == -1) + options->canonicalize_max_dots = 1; + if (options->canonicalize_fallback_local == -1) + options->canonicalize_fallback_local = 1; + if (options->canonicalize_hostname == -1) + options->canonicalize_hostname = SSH_CANONICALISE_NO; +} + +/* * Called after processing other sources of option data, this fills those * options for which no value has been specified with their default values. */ - void fill_default_options(Options * options) { @@ -1722,7 +1743,7 @@ fill_default_options(Options * options) options->canonicalize_hostname = SSH_CANONICALISE_NO; #define CLEAR_ON_NONE(v) \ do { \ - if (v != NULL && strcasecmp(v, "none") == 0) { \ + if (option_clear_or_none(v)) { \ free(v); \ v = NULL; \ } \ @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.100 2014/01/29 06:18:35 djm Exp $ */ +/* $OpenBSD: readconf.h,v 1.101 2014/02/23 20:11:36 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> @@ -176,12 +176,14 @@ typedef struct { void initialize_options(Options *); void fill_default_options(Options *); +void fill_default_options_for_canonicalization(Options *); int process_config_line(Options *, struct passwd *, const char *, char *, const char *, int, int *, int); int read_config_file(const char *, struct passwd *, const char *, Options *, int); int parse_forward(Forward *, const char *, int, int); int default_ssh_port(void); +int option_clear_or_none(const char *); void add_local_forward(Options *, const Forward *); void add_remote_forward(Options *, const Forward *); @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.399 2014/02/04 00:24:29 djm Exp $ */ +/* $OpenBSD: ssh.c,v 1.400 2014/02/23 20:11:36 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -231,16 +231,26 @@ tilde_expand_paths(char **paths, u_int num_paths) } } +/* + * Attempt to resolve a host name / port to a set of addresses and + * optionally return any CNAMEs encountered along the way. + * Returns NULL on failure. + * NB. this function must operate with a options having undefined members. + */ static struct addrinfo * -resolve_host(const char *name, u_int port, int logerr, char *cname, size_t clen) +resolve_host(const char *name, int port, int logerr, char *cname, size_t clen) { char strport[NI_MAXSERV]; struct addrinfo hints, *res; int gaierr, loglevel = SYSLOG_LEVEL_DEBUG1; + if (port <= 0) + port = default_ssh_port(); + snprintf(strport, sizeof strport, "%u", port); memset(&hints, 0, sizeof(hints)); - hints.ai_family = options.address_family; + hints.ai_family = options.address_family == -1 ? + AF_UNSPEC : options.address_family; hints.ai_socktype = SOCK_STREAM; if (cname != NULL) hints.ai_flags = AI_CANONNAME; @@ -265,6 +275,7 @@ resolve_host(const char *name, u_int port, int logerr, char *cname, size_t clen) /* * Check whether the cname is a permitted replacement for the hostname * and perform the replacement if it is. + * NB. this function must operate with a options having undefined members. */ static int check_follow_cname(char **namep, const char *cname) @@ -281,7 +292,7 @@ check_follow_cname(char **namep, const char *cname) * Don't attempt to canonicalize names that will be interpreted by * a proxy unless the user specifically requests so. */ - if (options.proxy_command != NULL && + if (!option_clear_or_none(options.proxy_command) && options.canonicalize_hostname != SSH_CANONICALISE_ALWAYS) return 0; debug3("%s: check \"%s\" CNAME \"%s\"", __func__, *namep, cname); @@ -305,9 +316,10 @@ check_follow_cname(char **namep, const char *cname) * Attempt to resolve the supplied hostname after applying the user's * canonicalization rules. Returns the address list for the host or NULL * if no name was found after canonicalization. + * NB. this function must operate with a options having undefined members. */ static struct addrinfo * -resolve_canonicalize(char **hostp, u_int port) +resolve_canonicalize(char **hostp, int port) { int i, ndots; char *cp, *fullhost, cname_target[NI_MAXHOST]; @@ -315,13 +327,15 @@ resolve_canonicalize(char **hostp, u_int port) 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. */ - if (options.proxy_command != NULL && + if (!option_clear_or_none(options.proxy_command) && options.canonicalize_hostname != SSH_CANONICALISE_ALWAYS) return NULL; + /* Don't apply canonicalization to sufficiently-qualified hostnames */ ndots = 0; for (cp = *hostp; *cp != '\0'; cp++) { @@ -338,7 +352,9 @@ resolve_canonicalize(char **hostp, u_int port) *cname_target = '\0'; xasprintf(&fullhost, "%s.%s.", *hostp, options.canonical_domains[i]); - if ((addrs = resolve_host(fullhost, options.port, 0, + debug3("%s: attempting \"%s\" => \"%s\"", __func__, + *hostp, fullhost); + if ((addrs = resolve_host(fullhost, port, 0, cname_target, sizeof(cname_target))) == NULL) { free(fullhost); continue; @@ -355,11 +371,41 @@ resolve_canonicalize(char **hostp, u_int port) return addrs; } if (!options.canonicalize_fallback_local) - fatal("%s: Could not resolve host \"%s\"", __progname, host); + fatal("%s: Could not resolve host \"%s\"", __progname, *hostp); + debug2("%s: host %s not found in any suffix", __func__, *hostp); return NULL; } /* + * Read per-user configuration file. Ignore the system wide config + * file if the user specifies a config file on the command line. + */ +static void +process_config_files(struct passwd *pw) +{ + char buf[MAXPATHLEN]; + int r; + + if (config != NULL) { + if (strcasecmp(config, "none") != 0 && + !read_config_file(config, pw, host, &options, + SSHCONF_USERCONF)) + fatal("Can't open user config file %.100s: " + "%.100s", config, strerror(errno)); + } else { + r = snprintf(buf, sizeof buf, "%s/%s", pw->pw_dir, + _PATH_SSH_USER_CONFFILE); + if (r > 0 && (size_t)r < sizeof(buf)) + (void)read_config_file(buf, pw, host, &options, + SSHCONF_CHECKPERM|SSHCONF_USERCONF); + + /* Read systemwide configuration file after user config. */ + (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, host, + &options, 0); + } +} + +/* * Main program for the ssh client. */ int @@ -832,31 +878,54 @@ main(int ac, char **av) if (debug_flag) logit("%s, %s", SSH_VERSION, SSLeay_version(SSLEAY_VERSION)); + /* Parse the configuration files */ + process_config_files(pw); + + /* Hostname canonicalisation needs a few options filled. */ + fill_default_options_for_canonicalization(&options); + + /* If the user has replaced the hostname then take it into use now */ + if (options.hostname != NULL) { + /* NB. Please keep in sync with readconf.c:match_cfg_line() */ + cp = percent_expand(options.hostname, + "h", host, (char *)NULL); + free(host); + host = cp; + } + + /* If canonicalization requested then try to apply it */ + lowercase(host); + if (options.canonicalize_hostname != SSH_CANONICALISE_NO) + addrs = resolve_canonicalize(&host, options.port); + /* - * Read per-user configuration file. Ignore the system wide config - * file if the user specifies a config file on the command line. + * If canonicalization not requested, or if it failed then try to + * resolve the bare hostname name using the system resolver's usual + * search rules. Skip the lookup if a ProxyCommand is being used + * unless the user has specifically requested canonicalisation. */ - if (config != NULL) { - if (strcasecmp(config, "none") != 0 && - !read_config_file(config, pw, host, &options, - SSHCONF_USERCONF)) - fatal("Can't open user config file %.100s: " - "%.100s", config, strerror(errno)); - } else { - r = snprintf(buf, sizeof buf, "%s/%s", pw->pw_dir, - _PATH_SSH_USER_CONFFILE); - if (r > 0 && (size_t)r < sizeof(buf)) - (void)read_config_file(buf, pw, host, &options, - SSHCONF_CHECKPERM|SSHCONF_USERCONF); + if (addrs == NULL && (option_clear_or_none(options.proxy_command) || + options.canonicalize_hostname == SSH_CANONICALISE_ALWAYS)) { + 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); + } - /* Read systemwide configuration file after user config. */ - (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, host, - &options, 0); + /* + * If the target hostname has changed as a result of canonicalisation + * then re-parse the configuration files as new stanzas may match. + */ + if (strcasecmp(host_arg, host) != 0) { + debug("Hostname has changed; re-reading configuration"); + process_config_files(pw); } /* Fill configuration defaults. */ fill_default_options(&options); + if (options.port == 0) + options.port = default_ssh_port(); channel_set_af(options.address_family); /* Tidy and check options */ @@ -899,37 +968,6 @@ main(int ac, char **av) if (options.user == NULL) options.user = xstrdup(pw->pw_name); - /* Get default port if port has not been set. */ - if (options.port == 0) - options.port = default_ssh_port(); - - /* preserve host name given on command line for %n expansion */ - if (options.hostname != NULL) { - /* NB. Please keep in sync with readconf.c:match_cfg_line() */ - cp = percent_expand(options.hostname, - "h", host, (char *)NULL); - free(host); - host = cp; - } - - /* If canonicalization requested then try to apply it */ - lowercase(host); - if (options.canonicalize_hostname != SSH_CANONICALISE_NO) - addrs = resolve_canonicalize(&host, options.port); - /* - * If canonicalization not requested, or if it failed then try to - * resolve the bare hostname name using the system resolver's usual - * search rules. Skip the lookup if a ProxyCommand is being used - * unless the user has specifically requested canonicalisation. - */ - if (addrs == NULL && (options.proxy_command == NULL || - options.canonicalize_hostname == SSH_CANONICALISE_ALWAYS)) { - 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) fatal("gethostname: %s", strerror(errno)); strlcpy(shorthost, thishost, sizeof(shorthost)); diff --git a/ssh_config.5 b/ssh_config.5 index 3cadcd76..b5803920 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -33,8 +33,8 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: ssh_config.5,v 1.184 2014/01/19 04:48:08 djm Exp $ -.Dd $Mdocdate: January 19 2014 $ +.\" $OpenBSD: ssh_config.5,v 1.185 2014/02/23 20:11:36 djm Exp $ +.Dd $Mdocdate: February 23 2014 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -263,6 +263,12 @@ If is set to .Dq always , then canonicalization is applied to proxied connections too. +.Pp +If this option is enabled and canonicalisation results in the target hostname +changing, then the configuration files are processed again using the new +target name to pick up any new configuration in matching +.Cm Host +stanzas. .It Cm CanonicalizeMaxDots Specifies the maximum number of dot characters in a hostname before canonicalization is disabled. |