diff options
author | Damien Miller <djm@cvs.openbsd.org> | 2014-10-08 22:20:26 +0000 |
---|---|---|
committer | Damien Miller <djm@cvs.openbsd.org> | 2014-10-08 22:20:26 +0000 |
commit | 98656283c1ca0d773ea8a9c798b0fbc0282cb1a2 (patch) | |
tree | 8a593f53846199f82d6ddf0180a0dcb1cad22ad8 | |
parent | 801c6c79547a10ece7e8677ad3abe4e73bd8a326 (diff) |
Tweak config reparsing with host canonicalisation
Make the second pass through the config files always run when
hostname canonicalisation is enabled.
Add a "Match canonical" criteria that allows ssh_config Match
blocks to trigger only in the second config pass.
Add a -G option to ssh that causes it to parse its configuration
and dump the result to stdout, similar to "sshd -T"
Allow ssh_config Port options set in the second config parse
phase to be applied (they were being ignored).
bz#2267 bz#2286; ok markus
-rw-r--r-- | usr.bin/ssh/readconf.c | 448 | ||||
-rw-r--r-- | usr.bin/ssh/readconf.h | 10 | ||||
-rw-r--r-- | usr.bin/ssh/ssh-keysign.c | 4 | ||||
-rw-r--r-- | usr.bin/ssh/ssh.1 | 14 | ||||
-rw-r--r-- | usr.bin/ssh/ssh.c | 80 | ||||
-rw-r--r-- | usr.bin/ssh/ssh_config.5 | 55 |
6 files changed, 502 insertions, 109 deletions
diff --git a/usr.bin/ssh/readconf.c b/usr.bin/ssh/readconf.c index 8c5195b8d41..1402ef6d7fb 100644 --- a/usr.bin/ssh/readconf.c +++ b/usr.bin/ssh/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.220 2014/07/15 15:54:14 millert Exp $ */ +/* $OpenBSD: readconf.c,v 1.221 2014/10/08 22:20:25 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -33,6 +33,7 @@ #include <string.h> #include <unistd.h> #include <util.h> +#include <vis.h> #include "xmalloc.h" #include "ssh.h" @@ -48,6 +49,7 @@ #include "kex.h" #include "mac.h" #include "uidswap.h" +#include "myproposal.h" /* Format of the configuration file: @@ -127,7 +129,7 @@ typedef enum { oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression, oCompressionLevel, oTCPKeepAlive, oNumberOfPasswordPrompts, oUsePrivilegedPort, oLogLevel, oCiphers, oProtocol, oMacs, - oGlobalKnownHostsFile2, oUserKnownHostsFile2, oPubkeyAuthentication, + oPubkeyAuthentication, oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias, oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication, oHostKeyAlgorithms, oBindAddress, oPKCS11Provider, @@ -204,7 +206,7 @@ static struct { { "globalknownhostsfile", oGlobalKnownHostsFile }, { "globalknownhostsfile2", oDeprecated }, { "userknownhostsfile", oUserKnownHostsFile }, - { "userknownhostsfile2", oDeprecated }, + { "userknownhostsfile2", oDeprecated }, { "connectionattempts", oConnectionAttempts }, { "batchmode", oBatchMode }, { "checkhostip", oCheckHostIP }, @@ -457,7 +459,7 @@ execute_in_shell(const char *cmd) if (!WIFEXITED(status)) { error("command '%.100s' exited abnormally", cmd); return -1; - } + } debug3("command returned status %d", WEXITSTATUS(status)); return WEXITSTATUS(status); } @@ -467,11 +469,12 @@ execute_in_shell(const char *cmd) */ static int match_cfg_line(Options *options, char **condition, struct passwd *pw, - const char *host_arg, const char *filename, int linenum) + const char *host_arg, const char *original_host, int post_canon, + const char *filename, int linenum) { - char *arg, *attrib, *cmd, *cp = *condition, *host; + char *arg, *oattrib, *attrib, *cmd, *cp = *condition, *host, *criteria; const char *ruser; - int r, port, result = 1, attributes = 0; + int r, port, this_result, result = 1, attributes = 0, negate; size_t len; char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV]; @@ -488,21 +491,38 @@ match_cfg_line(Options *options, char **condition, struct passwd *pw, } else host = xstrdup(host_arg); - debug3("checking match for '%s' host %s", cp, host); - while ((attrib = strdelim(&cp)) && *attrib != '\0') { - attributes++; + debug2("checking match for '%s' host %s originally %s", + cp, host, original_host); + while ((oattrib = attrib = strdelim(&cp)) && *attrib != '\0') { + criteria = NULL; + this_result = 1; + if ((negate = attrib[0] == '!')) + attrib++; + /* criteria "all" and "canonical" have no argument */ if (strcasecmp(attrib, "all") == 0) { - if (attributes != 1 || + if (attributes > 1 || ((arg = strdelim(&cp)) != NULL && *arg != '\0')) { - error("'all' cannot be combined with other " - "Match attributes"); + error("%.200s line %d: '%s' cannot be combined " + "with other Match attributes", + filename, linenum, oattrib); result = -1; goto out; } - *condition = cp; - result = 1; + if (result) + result = negate ? 0 : 1; goto out; } + attributes++; + if (strcasecmp(attrib, "canonical") == 0) { + r = !!post_canon; /* force bitmask member to boolean */ + if (r == (negate ? 1 : 0)) + this_result = result = 0; + debug3("%.200s line %d: %smatched '%s'", + filename, linenum, + this_result ? "" : "not ", oattrib); + continue; + } + /* All other criteria require an argument */ if ((arg = strdelim(&cp)) == NULL || *arg == '\0') { error("Missing Match criteria for %s", attrib); result = -1; @@ -510,31 +530,25 @@ match_cfg_line(Options *options, char **condition, struct passwd *pw, } len = strlen(arg); if (strcasecmp(attrib, "host") == 0) { - if (match_hostname(host, arg, len) != 1) - result = 0; - else - debug("%.200s line %d: matched 'Host %.100s' ", - filename, linenum, host); + criteria = xstrdup(host); + r = match_hostname(host, arg, len) == 1; + if (r == (negate ? 1 : 0)) + this_result = result = 0; } else if (strcasecmp(attrib, "originalhost") == 0) { - if (match_hostname(host_arg, arg, len) != 1) - result = 0; - else - debug("%.200s line %d: matched " - "'OriginalHost %.100s' ", - filename, linenum, host_arg); + criteria = xstrdup(original_host); + r = match_hostname(original_host, arg, len) == 1; + if (r == (negate ? 1 : 0)) + this_result = result = 0; } else if (strcasecmp(attrib, "user") == 0) { - if (match_pattern_list(ruser, arg, len, 0) != 1) - result = 0; - else - debug("%.200s line %d: matched 'User %.100s' ", - filename, linenum, ruser); + criteria = xstrdup(ruser); + r = match_pattern_list(ruser, arg, len, 0) == 1; + if (r == (negate ? 1 : 0)) + this_result = result = 0; } else if (strcasecmp(attrib, "localuser") == 0) { - if (match_pattern_list(pw->pw_name, arg, len, 0) != 1) - result = 0; - else - debug("%.200s line %d: matched " - "'LocalUser %.100s' ", - filename, linenum, pw->pw_name); + criteria = xstrdup(pw->pw_name); + r = match_pattern_list(pw->pw_name, arg, len, 0) == 1; + if (r == (negate ? 1 : 0)) + this_result = result = 0; } else if (strcasecmp(attrib, "exec") == 0) { if (gethostname(thishost, sizeof(thishost)) == -1) fatal("gethostname: %s", strerror(errno)); @@ -547,47 +561,49 @@ match_cfg_line(Options *options, char **condition, struct passwd *pw, "d", pw->pw_dir, "h", host, "l", thishost, - "n", host_arg, + "n", original_host, "p", portstr, "r", ruser, "u", pw->pw_name, (char *)NULL); if (result != 1) { /* skip execution if prior predicate failed */ - debug("%.200s line %d: skipped exec \"%.100s\"", - filename, linenum, cmd); - } else { - r = execute_in_shell(cmd); - if (r == -1) { - fatal("%.200s line %d: match exec " - "'%.100s' error", filename, - linenum, cmd); - } else if (r == 0) { - debug("%.200s line %d: matched " - "'exec \"%.100s\"'", filename, - linenum, cmd); - } else { - debug("%.200s line %d: no match " - "'exec \"%.100s\"'", filename, - linenum, cmd); - result = 0; - } + debug3("%.200s line %d: skipped exec " + "\"%.100s\"", filename, linenum, cmd); + free(cmd); + continue; } + r = execute_in_shell(cmd); + if (r == -1) { + fatal("%.200s line %d: match exec " + "'%.100s' error", filename, + linenum, cmd); + } + criteria = xstrdup(cmd); free(cmd); + /* Force exit status to boolean */ + r = r == 0; + if (r == (negate ? 1 : 0)) + this_result = result = 0; } else { error("Unsupported Match attribute %s", attrib); result = -1; goto out; } + debug3("%.200s line %d: %smatched '%s \"%.100s\"' ", + filename, linenum, this_result ? "": "not ", + oattrib, criteria); + free(criteria); } if (attributes == 0) { error("One or more attributes required for Match"); result = -1; goto out; } - debug3("match %sfound", result ? "" : "not "); - *condition = cp; out: + if (result != -1) + debug2("match %sfound", result ? "" : "not "); + *condition = cp; free(host); return result; } @@ -710,7 +726,8 @@ static const struct multistate multistate_canonicalizehostname[] = { #define WHITESPACE " \t\r\n" int process_config_line(Options *options, struct passwd *pw, const char *host, - char *line, const char *filename, int linenum, int *activep, int userconfig) + const char *original_host, char *line, const char *filename, + int linenum, int *activep, int flags) { char *s, **charptr, *endofnumber, *keyword, *arg, *arg2; char **cpptr, fwdarg[256]; @@ -766,7 +783,9 @@ parse_time: if (!arg || *arg == '\0') fatal("%s line %d: missing time value.", filename, linenum); - if ((value = convtime(arg)) == -1) + if (strcmp(arg, "none") == 0) + value = -1; + else if ((value = convtime(arg)) == -1) fatal("%s line %d: invalid time value.", filename, linenum); if (*activep && *intptr == -1) @@ -803,7 +822,7 @@ parse_time: case oForwardX11Trusted: intptr = &options->forward_x11_trusted; goto parse_flag; - + case oForwardX11Timeout: intptr = &options->forward_x11_timeout; goto parse_time; @@ -938,7 +957,8 @@ parse_time: if (*intptr >= SSH_MAX_IDENTITY_FILES) fatal("%.200s line %d: Too many identity files specified (max %d).", filename, linenum, SSH_MAX_IDENTITY_FILES); - add_identity_file(options, NULL, arg, userconfig); + add_identity_file(options, NULL, + arg, flags & SSHCONF_USERCONF); } break; @@ -1186,8 +1206,8 @@ parse_int: if (cmdline) fatal("Host directive not supported as a command-line " "option"); - value = match_cfg_line(options, &s, pw, host, - filename, linenum); + value = match_cfg_line(options, &s, pw, host, original_host, + flags & SSHCONF_POSTCANON, filename, linenum); if (value < 0) fatal("%.200s line %d: Bad Match condition", filename, linenum); @@ -1435,7 +1455,7 @@ parse_int: return 0; default: - fatal("process_config_line: Unimplemented opcode %d", opcode); + fatal("%s: Unimplemented opcode %d", __func__, opcode); } /* Check that there is no garbage at end of line. */ @@ -1455,7 +1475,7 @@ parse_int: int read_config_file(const char *filename, struct passwd *pw, const char *host, - Options *options, int flags) + const char *original_host, Options *options, int flags) { FILE *f; char line[1024]; @@ -1486,8 +1506,8 @@ read_config_file(const char *filename, struct passwd *pw, const char *host, while (fgets(line, sizeof(line), f)) { /* Update line number counter. */ linenum++; - if (process_config_line(options, pw, host, line, filename, - linenum, &active, flags & SSHCONF_USERCONF) != 0) + if (process_config_line(options, pw, host, original_host, + line, filename, linenum, &active, flags) != 0) bad_options++; } fclose(f); @@ -1998,3 +2018,295 @@ parse_forward(struct Forward *fwd, const char *fwdspec, int dynamicfwd, int remo fwd->listen_path = NULL; return (0); } + +/* XXX the following is a near-vebatim copy from servconf.c; refactor */ +static const char * +fmt_multistate_int(int val, const struct multistate *m) +{ + u_int i; + + for (i = 0; m[i].key != NULL; i++) { + if (m[i].value == val) + return m[i].key; + } + return "UNKNOWN"; +} + +static const char * +fmt_intarg(OpCodes code, int val) +{ + if (val == -1) + return "unset"; + switch (code) { + case oAddressFamily: + return fmt_multistate_int(val, multistate_addressfamily); + case oVerifyHostKeyDNS: + case oStrictHostKeyChecking: + return fmt_multistate_int(val, multistate_yesnoask); + case oControlMaster: + return fmt_multistate_int(val, multistate_controlmaster); + case oTunnel: + return fmt_multistate_int(val, multistate_tunnel); + case oRequestTTY: + return fmt_multistate_int(val, multistate_requesttty); + case oCanonicalizeHostname: + return fmt_multistate_int(val, multistate_canonicalizehostname); + case oProtocol: + switch (val) { + case SSH_PROTO_1: + return "1"; + case SSH_PROTO_2: + return "2"; + case (SSH_PROTO_1|SSH_PROTO_2): + return "2,1"; + default: + return "UNKNOWN"; + } + default: + switch (val) { + case 0: + return "no"; + case 1: + return "yes"; + default: + return "UNKNOWN"; + } + } +} + +static const char * +lookup_opcode_name(OpCodes code) +{ + u_int i; + + for (i = 0; keywords[i].name != NULL; i++) + if (keywords[i].opcode == code) + return(keywords[i].name); + return "UNKNOWN"; +} + +static void +dump_cfg_int(OpCodes code, int val) +{ + printf("%s %d\n", lookup_opcode_name(code), val); +} + +static void +dump_cfg_fmtint(OpCodes code, int val) +{ + printf("%s %s\n", lookup_opcode_name(code), fmt_intarg(code, val)); +} + +static void +dump_cfg_string(OpCodes code, const char *val) +{ + if (val == NULL) + return; + printf("%s %s\n", lookup_opcode_name(code), val); +} + +static void +dump_cfg_strarray(OpCodes code, u_int count, char **vals) +{ + u_int i; + + for (i = 0; i < count; i++) + printf("%s %s\n", lookup_opcode_name(code), vals[i]); +} + +static void +dump_cfg_strarray_oneline(OpCodes code, u_int count, char **vals) +{ + u_int i; + + printf("%s", lookup_opcode_name(code)); + for (i = 0; i < count; i++) + printf(" %s", vals[i]); + printf("\n"); +} + +static void +dump_cfg_forwards(OpCodes code, u_int count, const struct Forward *fwds) +{ + const struct Forward *fwd; + u_int i; + + /* oDynamicForward */ + for (i = 0; i < count; i++) { + fwd = &fwds[i]; + if (code == oDynamicForward && + strcmp(fwd->connect_host, "socks") != 0) + continue; + if (code == oLocalForward && + strcmp(fwd->connect_host, "socks") == 0) + continue; + printf("%s", lookup_opcode_name(code)); + if (fwd->listen_port == PORT_STREAMLOCAL) + printf(" %s", fwd->listen_path); + else if (fwd->listen_host == NULL) + printf(" %d", fwd->listen_port); + else { + printf(" [%s]:%d", + fwd->listen_host, fwd->listen_port); + } + if (code != oDynamicForward) { + if (fwd->connect_port == PORT_STREAMLOCAL) + printf(" %s", fwd->connect_path); + else if (fwd->connect_host == NULL) + printf(" %d", fwd->connect_port); + else { + printf(" [%s]:%d", + fwd->connect_host, fwd->connect_port); + } + } + printf("\n"); + } +} + +void +dump_client_config(Options *o, const char *host) +{ + int i; + char vbuf[5]; + + /* Most interesting options first: user, host, port */ + dump_cfg_string(oUser, o->user); + dump_cfg_string(oHostName, host); + dump_cfg_int(oPort, o->port); + + /* Flag options */ + dump_cfg_fmtint(oAddressFamily, o->address_family); + dump_cfg_fmtint(oBatchMode, o->batch_mode); + dump_cfg_fmtint(oCanonicalizeFallbackLocal, o->canonicalize_fallback_local); + dump_cfg_fmtint(oCanonicalizeHostname, o->canonicalize_hostname); + dump_cfg_fmtint(oChallengeResponseAuthentication, o->challenge_response_authentication); + dump_cfg_fmtint(oCheckHostIP, o->check_host_ip); + dump_cfg_fmtint(oCompression, o->compression); + dump_cfg_fmtint(oControlMaster, o->control_master); + dump_cfg_fmtint(oEnableSSHKeysign, o->enable_ssh_keysign); + dump_cfg_fmtint(oExitOnForwardFailure, o->exit_on_forward_failure); + dump_cfg_fmtint(oForwardAgent, o->forward_agent); + dump_cfg_fmtint(oForwardX11, o->forward_x11); + dump_cfg_fmtint(oForwardX11Trusted, o->forward_x11_trusted); + dump_cfg_fmtint(oGatewayPorts, o->fwd_opts.gateway_ports); +#ifdef GSSAPI + dump_cfg_fmtint(oGssAuthentication, o->gss_authentication); + dump_cfg_fmtint(oGssDelegateCreds, o->gss_deleg_creds); +#endif /* GSSAPI */ + dump_cfg_fmtint(oHashKnownHosts, o->hash_known_hosts); + dump_cfg_fmtint(oHostbasedAuthentication, o->hostbased_authentication); + dump_cfg_fmtint(oIdentitiesOnly, o->identities_only); + dump_cfg_fmtint(oKbdInteractiveAuthentication, o->kbd_interactive_authentication); + dump_cfg_fmtint(oNoHostAuthenticationForLocalhost, o->no_host_authentication_for_localhost); + dump_cfg_fmtint(oPasswordAuthentication, o->password_authentication); + dump_cfg_fmtint(oPermitLocalCommand, o->permit_local_command); + dump_cfg_fmtint(oProtocol, o->protocol); + dump_cfg_fmtint(oProxyUseFdpass, o->proxy_use_fdpass); + dump_cfg_fmtint(oPubkeyAuthentication, o->pubkey_authentication); + dump_cfg_fmtint(oRequestTTY, o->request_tty); + dump_cfg_fmtint(oRhostsRSAAuthentication, o->rhosts_rsa_authentication); + dump_cfg_fmtint(oRSAAuthentication, o->rsa_authentication); + dump_cfg_fmtint(oStreamLocalBindUnlink, o->fwd_opts.streamlocal_bind_unlink); + dump_cfg_fmtint(oStrictHostKeyChecking, o->strict_host_key_checking); + dump_cfg_fmtint(oTCPKeepAlive, o->tcp_keep_alive); + dump_cfg_fmtint(oTunnel, o->tun_open); + dump_cfg_fmtint(oUsePrivilegedPort, o->use_privileged_port); + dump_cfg_fmtint(oVerifyHostKeyDNS, o->verify_host_key_dns); + dump_cfg_fmtint(oVisualHostKey, o->visual_host_key); + + /* Integer options */ + dump_cfg_int(oCanonicalizeMaxDots, o->canonicalize_max_dots); + dump_cfg_int(oCompressionLevel, o->compression_level); + dump_cfg_int(oConnectionAttempts, o->connection_attempts); + dump_cfg_int(oForwardX11Timeout, o->forward_x11_timeout); + dump_cfg_int(oNumberOfPasswordPrompts, o->number_of_password_prompts); + dump_cfg_int(oServerAliveCountMax, o->server_alive_count_max); + dump_cfg_int(oServerAliveInterval, o->server_alive_interval); + + /* String options */ + dump_cfg_string(oBindAddress, o->bind_address); + dump_cfg_string(oCiphers, o->ciphers ? o->ciphers : KEX_CLIENT_ENCRYPT); + dump_cfg_string(oControlPath, o->control_path); + dump_cfg_string(oHostKeyAlgorithms, o->hostkeyalgorithms ? o->hostkeyalgorithms : KEX_DEFAULT_PK_ALG); + dump_cfg_string(oHostKeyAlias, o->host_key_alias); + dump_cfg_string(oKbdInteractiveDevices, o->kbd_interactive_devices); + dump_cfg_string(oKexAlgorithms, o->kex_algorithms ? o->kex_algorithms : KEX_CLIENT_KEX); + dump_cfg_string(oLocalCommand, o->local_command); + dump_cfg_string(oLogLevel, log_level_name(o->log_level)); + dump_cfg_string(oMacs, o->macs ? o->macs : KEX_CLIENT_MAC); + dump_cfg_string(oPKCS11Provider, o->pkcs11_provider); + dump_cfg_string(oPreferredAuthentications, o->preferred_authentications); + dump_cfg_string(oProxyCommand, o->proxy_command); + dump_cfg_string(oXAuthLocation, o->xauth_location); + + dump_cfg_forwards(oDynamicForward, o->num_local_forwards, o->local_forwards); + dump_cfg_forwards(oLocalForward, o->num_local_forwards, o->local_forwards); + dump_cfg_forwards(oRemoteForward, o->num_remote_forwards, o->remote_forwards); + + /* String array options */ + dump_cfg_strarray(oIdentityFile, o->num_identity_files, o->identity_files); + dump_cfg_strarray_oneline(oCanonicalDomains, o->num_canonical_domains, o->canonical_domains); + dump_cfg_strarray_oneline(oGlobalKnownHostsFile, o->num_system_hostfiles, o->system_hostfiles); + dump_cfg_strarray_oneline(oUserKnownHostsFile, o->num_user_hostfiles, o->user_hostfiles); + dump_cfg_strarray(oSendEnv, o->num_send_env, o->send_env); + + /* Special cases */ + + /* oConnectTimeout */ + if (o->connection_timeout == -1) + printf("connecttimeout none\n"); + else + dump_cfg_int(oConnectTimeout, o->connection_timeout); + + /* oTunnelDevice */ + printf("tunneldevice"); + if (o->tun_local == SSH_TUNID_ANY) + printf(" any"); + else + printf(" %d", o->tun_local); + if (o->tun_remote == SSH_TUNID_ANY) + printf(":any"); + else + printf(":%d", o->tun_remote); + printf("\n"); + + /* oCanonicalizePermittedCNAMEs */ + if ( o->num_permitted_cnames > 0) { + printf("canonicalizePermittedcnames"); + for (i = 0; i < o->num_permitted_cnames; i++) { + printf(" %s:%s", o->permitted_cnames[i].source_list, + o->permitted_cnames[i].target_list); + } + printf("\n"); + } + + /* oCipher */ + if (o->cipher != SSH_CIPHER_NOT_SET) + printf("Cipher %s\n", cipher_name(o->cipher)); + + /* oControlPersist */ + if (o->control_persist == 0 || o->control_persist_timeout == 0) + dump_cfg_fmtint(oControlPersist, o->control_persist); + else + dump_cfg_int(oControlPersist, o->control_persist_timeout); + + /* oEscapeChar */ + if (o->escape_char == SSH_ESCAPECHAR_NONE) + printf("escapechar none\n"); + else { + vis(vbuf, o->escape_char, VIS_WHITE, 0); + printf("escapechar %s\n", vbuf); + } + + /* oIPQoS */ + printf("ipqos %s ", iptos2str(o->ip_qos_interactive)); + printf("%s\n", iptos2str(o->ip_qos_bulk)); + + /* oRekeyLimit */ + printf("rekeylimit %lld %d\n", + (long long)o->rekey_limit, o->rekey_interval); + + /* oStreamLocalBindMask */ + printf("streamlocalbindmask 0%o\n", + o->fwd_opts.streamlocal_bind_mask); +} diff --git a/usr.bin/ssh/readconf.h b/usr.bin/ssh/readconf.h index 0b9cb777a67..7b58d01f39d 100644 --- a/usr.bin/ssh/readconf.h +++ b/usr.bin/ssh/readconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.102 2014/07/15 15:54:14 millert Exp $ */ +/* $OpenBSD: readconf.h,v 1.103 2014/10/08 22:20:25 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> @@ -164,17 +164,19 @@ typedef struct { #define SSHCONF_CHECKPERM 1 /* check permissions on config file */ #define SSHCONF_USERCONF 2 /* user provided config file not system */ +#define SSHCONF_POSTCANON 4 /* After hostname canonicalisation */ 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 process_config_line(Options *, struct passwd *, const char *, + const char *, char *, const char *, int, int *, int); int read_config_file(const char *, struct passwd *, const char *, - Options *, int); + const char *, Options *, int); int parse_forward(struct Forward *, const char *, int, int); int default_ssh_port(void); int option_clear_or_none(const char *); +void dump_client_config(Options *o, const char *host); void add_local_forward(Options *, const struct Forward *); void add_remote_forward(Options *, const struct Forward *); diff --git a/usr.bin/ssh/ssh-keysign.c b/usr.bin/ssh/ssh-keysign.c index 1da6eb7adce..79127adb073 100644 --- a/usr.bin/ssh/ssh-keysign.c +++ b/usr.bin/ssh/ssh-keysign.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keysign.c,v 1.42 2014/04/29 18:01:49 markus Exp $ */ +/* $OpenBSD: ssh-keysign.c,v 1.43 2014/10/08 22:20:25 djm Exp $ */ /* * Copyright (c) 2002 Markus Friedl. All rights reserved. * @@ -178,7 +178,7 @@ main(int argc, char **argv) /* verify that ssh-keysign is enabled by the admin */ initialize_options(&options); - (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, "", &options, 0); + (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, "", "", &options, 0); fill_default_options(&options); if (options.enable_ssh_keysign != 1) fatal("ssh-keysign not enabled in %s", diff --git a/usr.bin/ssh/ssh.1 b/usr.bin/ssh/ssh.1 index 1c889b6c4bf..34fe635e4bd 100644 --- a/usr.bin/ssh/ssh.1 +++ b/usr.bin/ssh/ssh.1 @@ -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.1,v 1.349 2014/08/30 15:33:50 sobrado Exp $ -.Dd $Mdocdate: August 30 2014 $ +.\" $OpenBSD: ssh.1,v 1.350 2014/10/08 22:20:25 djm Exp $ +.Dd $Mdocdate: October 8 2014 $ .Dt SSH 1 .Os .Sh NAME @@ -43,7 +43,7 @@ .Sh SYNOPSIS .Nm ssh .Bk -words -.Op Fl 1246AaCfgKkMNnqsTtVvXxYy +.Op Fl 1246AaCfgGKkMNnqsTtVvXxYy .Op Fl b Ar bind_address .Op Fl c Ar cipher_spec .Op Fl D Oo Ar bind_address : Oc Ns Ar port @@ -251,6 +251,14 @@ then a client started with .Fl f will wait for all remote port forwards to be successfully established before placing itself in the background. +.It Fl G +Causes +.Nm +to print its configuration after evaluating +.Cm Host +and +.Cm Match +blocks and exit. .It Fl g Allows remote hosts to connect to local forwarded ports. If used on a multiplexed connection, then this option must be specified diff --git a/usr.bin/ssh/ssh.c b/usr.bin/ssh/ssh.c index d83b023c232..ceffa11c186 100644 --- a/usr.bin/ssh/ssh.c +++ b/usr.bin/ssh/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.407 2014/07/17 07:22:19 djm Exp $ */ +/* $OpenBSD: ssh.c,v 1.408 2014/10/08 22:20:25 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -370,27 +370,49 @@ resolve_canonicalize(char **hostp, int port) * file if the user specifies a config file on the command line. */ static void -process_config_files(struct passwd *pw) +process_config_files(const char *host_arg, struct passwd *pw, int post_canon) { char buf[MAXPATHLEN]; int r; if (config != NULL) { if (strcasecmp(config, "none") != 0 && - !read_config_file(config, pw, host, &options, - SSHCONF_USERCONF)) + !read_config_file(config, pw, host, host_arg, &options, + SSHCONF_USERCONF | (post_canon ? SSHCONF_POSTCANON : 0))) 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); + (void)read_config_file(buf, pw, host, host_arg, + &options, SSHCONF_CHECKPERM | SSHCONF_USERCONF | + (post_canon ? SSHCONF_POSTCANON : 0)); /* Read systemwide configuration file after user config. */ - (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, host, - &options, 0); + (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, + host, host_arg, &options, + post_canon ? SSHCONF_POSTCANON : 0); + } +} + +/* Rewrite the port number in an addrinfo list of addresses */ +static void +set_addrinfo_port(struct addrinfo *addrs, int port) +{ + struct addrinfo *addr; + + for (addr = addrs; addr != NULL; addr = addr->ai_next) { + switch (addr->ai_family) { + case AF_INET: + ((struct sockaddr_in *)addr->ai_addr)-> + sin_port = htons(port); + break; + case AF_INET6: + ((struct sockaddr_in6 *)addr->ai_addr)-> + sin6_port = htons(port); + break; + } } } @@ -400,7 +422,7 @@ process_config_files(struct passwd *pw) int main(int ac, char **av) { - int i, r, opt, exit_status, use_syslog; + int i, r, opt, exit_status, use_syslog, config_test = 0; char *p, *cp, *line, *argv0, buf[MAXPATHLEN], *host_arg, *logfile; char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV]; char cname[NI_MAXHOST]; @@ -478,7 +500,7 @@ main(int ac, char **av) again: while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" - "ACD:E:F:I:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) { + "ACD:E:F:GI:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) { switch (opt) { case '1': options.protocol = SSH_PROTO_1; @@ -511,6 +533,9 @@ main(int ac, char **av) case 'E': logfile = xstrdup(optarg); break; + case 'G': + config_test = 1; + break; case 'Y': options.forward_x11 = 1; options.forward_x11_trusted = 1; @@ -759,9 +784,9 @@ main(int ac, char **av) break; case 'o': line = xstrdup(optarg); - if (process_config_line(&options, pw, host ? host : "", - line, "command-line", 0, NULL, SSHCONF_USERCONF) - != 0) + if (process_config_line(&options, pw, + host ? host : "", host ? host : "", line, + "command-line", 0, NULL, SSHCONF_USERCONF) != 0) exit(255); free(line); break; @@ -870,7 +895,7 @@ main(int ac, char **av) ); /* Parse the configuration files */ - process_config_files(pw); + process_config_files(host_arg, pw, 0); /* Hostname canonicalisation needs a few options filled. */ fill_default_options_for_canonicalization(&options); @@ -882,6 +907,8 @@ main(int ac, char **av) "h", host, (char *)NULL); free(host); host = cp; + free(options.hostname); + options.hostname = xstrdup(host); } /* If canonicalization requested then try to apply it */ @@ -916,12 +943,22 @@ main(int ac, char **av) } /* - * If the target hostname has changed as a result of canonicalisation - * then re-parse the configuration files as new stanzas may match. + * If canonicalisation is enabled 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); + if (options.canonicalize_hostname != 0) { + debug("Re-reading configuration after hostname " + "canonicalisation"); + free(options.hostname); + options.hostname = xstrdup(host); + process_config_files(host_arg, pw, 1); + /* + * Address resolution happens early with canonicalisation + * enabled and the port number may have changed since, so + * reset it in address list + */ + if (addrs != NULL && options.port > 0) + set_addrinfo_port(addrs, options.port); } /* Fill configuration defaults. */ @@ -1019,6 +1056,11 @@ main(int ac, char **av) } free(conn_hash_hex); + if (config_test) { + dump_client_config(&options, host); + exit(0); + } + if (muxclient_command != 0 && options.control_path == NULL) fatal("No ControlPath specified for \"-O\" command"); if (options.control_path != NULL) diff --git a/usr.bin/ssh/ssh_config.5 b/usr.bin/ssh/ssh_config.5 index 4396aa90787..b702e323ffe 100644 --- a/usr.bin/ssh/ssh_config.5 +++ b/usr.bin/ssh/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.192 2014/08/30 15:33:50 sobrado Exp $ -.Dd $Mdocdate: August 30 2014 $ +.\" $OpenBSD: ssh_config.5,v 1.193 2014/10/08 22:20:25 djm Exp $ +.Dd $Mdocdate: October 8 2014 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -65,7 +65,10 @@ The configuration files contain sections separated by .Dq Host specifications, and that section is only applied for hosts that match one of the patterns given in the specification. -The matched host name is the one given on the command line. +The matched host name is usually the one given on the command line +(see the +.Cm CanonicalizeHostname +option for exceptions.) .Pp Since the first obtained value for each parameter is used, more host-specific declarations should be given near the beginning of the @@ -109,10 +112,12 @@ A single .Ql * as a pattern can be used to provide global defaults for all hosts. -The host is the +The host is usually the .Ar hostname -argument given on the command line (i.e. the name is not converted to -a canonicalized host name before matching). +argument given on the command line +(see the +.Cm CanonicalizeHostname +option for exceptions.) .Pp A pattern entry may be negated by prefixing it with an exclamation mark .Pq Sq !\& . @@ -134,19 +139,40 @@ or keyword) to be used only when the conditions following the .Cm Match keyword are satisfied. -Match conditions are specified using one or more keyword/criteria pairs +Match conditions are specified using one or more critera or the single token .Cm all -which matches all criteria. -The available keywords are: +which always matches. +The available criteria keywords are: +.Cm canonical , .Cm exec , .Cm host , .Cm originalhost , .Cm user , and .Cm localuser . +The +.Cm all +criteria must appear alone or immediately after +.Cm canonical. +Other criteria may be combined arbitrarily. +All criteria but +.Cm all +and +.Cm canonical +require an argument. +Criteria may be negated by prepending an exclamation mark +.Pq Sq !\& . .Pp The +.Cm canonical +keywork matches only when the configuration file is being re-parsed +after hostname canonicalization (see the +.Cm CanonicalizeHostname +option.) +This may be useful to specify conditions that work with canonical host +names only. +The .Cm exec keyword executes the specified command under the user's shell. If the command returns a zero exit status then the condition is considered true. @@ -179,7 +205,9 @@ The criteria for the keyword are matched against the target hostname, after any substitution by the .Cm Hostname -option. +or +.Cm CanonicalizeHostname +options. The .Cm originalhost keyword matches against the hostname as it was specified on the command-line. @@ -264,10 +292,11 @@ 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 +If this option is enabled, then the configuration files are processed +again using the new target name to pick up any new configuration in matching .Cm Host +and +.Cm Match stanzas. .It Cm CanonicalizeMaxDots Specifies the maximum number of dot characters in a hostname before |