diff --git a/crypto/openssh/readconf.c b/crypto/openssh/readconf.c index 0a59470a74ae..a5317990de1b 100644 --- a/crypto/openssh/readconf.c +++ b/crypto/openssh/readconf.c @@ -1,3511 +1,3490 @@ /* $OpenBSD: readconf.c,v 1.366 2022/02/08 08:59:12 dtucker Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Functions for reading the configuration files. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #include "includes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PATHS_H # include #endif #include #include #include #include #include #include #ifdef USE_SYSTEM_GLOB # include #else # include "openbsd-compat/glob.h" #endif #ifdef HAVE_UTIL_H #include #endif #if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) # include #endif #include "xmalloc.h" #include "ssh.h" #include "ssherr.h" #include "compat.h" #include "cipher.h" #include "pathnames.h" #include "log.h" #include "sshkey.h" #include "misc.h" #include "readconf.h" #include "match.h" #include "kex.h" #include "mac.h" #include "uidswap.h" #include "myproposal.h" #include "digest.h" -#include "version.h" /* Format of the configuration file: # Configuration data is parsed as follows: # 1. command line options # 2. user-specific file # 3. system-wide file # Any configuration value is only changed the first time it is set. # Thus, host-specific definitions should be at the beginning of the # configuration file, and defaults at the end. # Host-specific declarations. These may override anything above. A single # host may match multiple declarations; these are processed in the order # that they are given in. Host *.ngs.fi ngs.fi User foo Host fake.com Hostname another.host.name.real.org User blaah Port 34289 ForwardX11 no ForwardAgent no Host books.com RemoteForward 9999 shadows.cs.hut.fi:9999 Ciphers 3des-cbc Host fascist.blob.com Port 23123 User tylonen PasswordAuthentication no Host puukko.hut.fi User t35124p ProxyCommand ssh-proxy %h %p Host *.fr PublicKeyAuthentication no Host *.su Ciphers aes128-ctr PasswordAuthentication no Host vpn.fake.com Tunnel yes TunnelDevice 3 # Defaults for various options Host * ForwardAgent no ForwardX11 no PasswordAuthentication yes StrictHostKeyChecking yes TcpKeepAlive no IdentityFile ~/.ssh/identity Port 22 EscapeChar ~ */ static int read_config_file_depth(const char *filename, struct passwd *pw, const char *host, const char *original_host, Options *options, int flags, int *activep, int *want_final_pass, int depth); static int process_config_line_depth(Options *options, struct passwd *pw, const char *host, const char *original_host, char *line, const char *filename, int linenum, int *activep, int flags, int *want_final_pass, int depth); /* Keyword tokens. */ typedef enum { oBadOption, - oVersionAddendum, oHost, oMatch, oInclude, oForwardAgent, oForwardX11, oForwardX11Trusted, oForwardX11Timeout, oGatewayPorts, oExitOnForwardFailure, oPasswordAuthentication, oXAuthLocation, oIdentityFile, oHostname, oPort, oRemoteForward, oLocalForward, oPermitRemoteOpen, oCertificateFile, oAddKeysToAgent, oIdentityAgent, oUser, oEscapeChar, oProxyCommand, oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts, oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression, oTCPKeepAlive, oNumberOfPasswordPrompts, oLogFacility, oLogLevel, oLogVerbose, oCiphers, oMacs, oPubkeyAuthentication, oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias, oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication, oHostKeyAlgorithms, oBindAddress, oBindInterface, oPKCS11Provider, oClearAllForwardings, oNoHostAuthenticationForLocalhost, oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, oAddressFamily, oGssAuthentication, oGssDelegateCreds, oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, oSendEnv, oSetEnv, oControlPath, oControlMaster, oControlPersist, oHashKnownHosts, oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand, oRemoteCommand, oVisualHostKey, oKexAlgorithms, oIPQoS, oRequestTTY, oSessionType, oStdinNull, oForkAfterAuthentication, oIgnoreUnknown, oProxyUseFdpass, oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots, oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs, oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys, oFingerprintHash, oUpdateHostkeys, oHostbasedAcceptedAlgorithms, oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump, oSecurityKeyProvider, oKnownHostsCommand, oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported } OpCodes; /* Textual representations of the tokens. */ static struct { const char *name; OpCodes opcode; } keywords[] = { /* Deprecated options */ { "protocol", oIgnore }, /* NB. silently ignored */ { "cipher", oDeprecated }, { "fallbacktorsh", oDeprecated }, { "globalknownhostsfile2", oDeprecated }, { "rhostsauthentication", oDeprecated }, { "userknownhostsfile2", oDeprecated }, { "useroaming", oDeprecated }, { "usersh", oDeprecated }, { "useprivilegedport", oDeprecated }, /* Unsupported options */ { "afstokenpassing", oUnsupported }, { "kerberosauthentication", oUnsupported }, { "kerberostgtpassing", oUnsupported }, { "rsaauthentication", oUnsupported }, { "rhostsrsaauthentication", oUnsupported }, { "compressionlevel", oUnsupported }, /* Sometimes-unsupported options */ #if defined(GSSAPI) { "gssapiauthentication", oGssAuthentication }, { "gssapidelegatecredentials", oGssDelegateCreds }, # else { "gssapiauthentication", oUnsupported }, { "gssapidelegatecredentials", oUnsupported }, #endif #ifdef ENABLE_PKCS11 { "pkcs11provider", oPKCS11Provider }, { "smartcarddevice", oPKCS11Provider }, # else { "smartcarddevice", oUnsupported }, { "pkcs11provider", oUnsupported }, #endif { "forwardagent", oForwardAgent }, { "forwardx11", oForwardX11 }, { "forwardx11trusted", oForwardX11Trusted }, { "forwardx11timeout", oForwardX11Timeout }, { "exitonforwardfailure", oExitOnForwardFailure }, { "xauthlocation", oXAuthLocation }, { "gatewayports", oGatewayPorts }, { "passwordauthentication", oPasswordAuthentication }, { "kbdinteractiveauthentication", oKbdInteractiveAuthentication }, { "kbdinteractivedevices", oKbdInteractiveDevices }, { "challengeresponseauthentication", oKbdInteractiveAuthentication }, /* alias */ { "skeyauthentication", oKbdInteractiveAuthentication }, /* alias */ { "tisauthentication", oKbdInteractiveAuthentication }, /* alias */ { "pubkeyauthentication", oPubkeyAuthentication }, { "dsaauthentication", oPubkeyAuthentication }, /* alias */ { "hostbasedauthentication", oHostbasedAuthentication }, { "identityfile", oIdentityFile }, { "identityfile2", oIdentityFile }, /* obsolete */ { "identitiesonly", oIdentitiesOnly }, { "certificatefile", oCertificateFile }, { "addkeystoagent", oAddKeysToAgent }, { "identityagent", oIdentityAgent }, { "hostname", oHostname }, { "hostkeyalias", oHostKeyAlias }, { "proxycommand", oProxyCommand }, { "port", oPort }, { "ciphers", oCiphers }, { "macs", oMacs }, { "remoteforward", oRemoteForward }, { "localforward", oLocalForward }, { "permitremoteopen", oPermitRemoteOpen }, { "user", oUser }, { "host", oHost }, { "match", oMatch }, { "escapechar", oEscapeChar }, { "globalknownhostsfile", oGlobalKnownHostsFile }, { "userknownhostsfile", oUserKnownHostsFile }, { "connectionattempts", oConnectionAttempts }, { "batchmode", oBatchMode }, { "checkhostip", oCheckHostIP }, { "stricthostkeychecking", oStrictHostKeyChecking }, { "compression", oCompression }, { "tcpkeepalive", oTCPKeepAlive }, { "keepalive", oTCPKeepAlive }, /* obsolete */ { "numberofpasswordprompts", oNumberOfPasswordPrompts }, { "syslogfacility", oLogFacility }, { "loglevel", oLogLevel }, { "logverbose", oLogVerbose }, { "dynamicforward", oDynamicForward }, { "preferredauthentications", oPreferredAuthentications }, { "hostkeyalgorithms", oHostKeyAlgorithms }, { "casignaturealgorithms", oCASignatureAlgorithms }, { "bindaddress", oBindAddress }, { "bindinterface", oBindInterface }, { "clearallforwardings", oClearAllForwardings }, { "enablesshkeysign", oEnableSSHKeysign }, { "verifyhostkeydns", oVerifyHostKeyDNS }, { "nohostauthenticationforlocalhost", oNoHostAuthenticationForLocalhost }, { "rekeylimit", oRekeyLimit }, { "connecttimeout", oConnectTimeout }, { "addressfamily", oAddressFamily }, { "serveraliveinterval", oServerAliveInterval }, { "serveralivecountmax", oServerAliveCountMax }, { "sendenv", oSendEnv }, { "setenv", oSetEnv }, { "controlpath", oControlPath }, { "controlmaster", oControlMaster }, { "controlpersist", oControlPersist }, { "hashknownhosts", oHashKnownHosts }, { "include", oInclude }, { "tunnel", oTunnel }, { "tunneldevice", oTunnelDevice }, { "localcommand", oLocalCommand }, { "permitlocalcommand", oPermitLocalCommand }, { "remotecommand", oRemoteCommand }, { "visualhostkey", oVisualHostKey }, { "kexalgorithms", oKexAlgorithms }, { "ipqos", oIPQoS }, { "requesttty", oRequestTTY }, { "sessiontype", oSessionType }, { "stdinnull", oStdinNull }, { "forkafterauthentication", oForkAfterAuthentication }, { "proxyusefdpass", oProxyUseFdpass }, { "canonicaldomains", oCanonicalDomains }, { "canonicalizefallbacklocal", oCanonicalizeFallbackLocal }, { "canonicalizehostname", oCanonicalizeHostname }, { "canonicalizemaxdots", oCanonicalizeMaxDots }, { "canonicalizepermittedcnames", oCanonicalizePermittedCNAMEs }, { "streamlocalbindmask", oStreamLocalBindMask }, { "streamlocalbindunlink", oStreamLocalBindUnlink }, { "revokedhostkeys", oRevokedHostKeys }, { "fingerprinthash", oFingerprintHash }, { "updatehostkeys", oUpdateHostkeys }, { "hostbasedacceptedalgorithms", oHostbasedAcceptedAlgorithms }, { "hostbasedkeytypes", oHostbasedAcceptedAlgorithms }, /* obsolete */ { "pubkeyacceptedalgorithms", oPubkeyAcceptedAlgorithms }, { "pubkeyacceptedkeytypes", oPubkeyAcceptedAlgorithms }, /* obsolete */ { "ignoreunknown", oIgnoreUnknown }, { "proxyjump", oProxyJump }, { "securitykeyprovider", oSecurityKeyProvider }, { "knownhostscommand", oKnownHostsCommand }, { "hpndisabled", oDeprecated }, { "hpnbuffersize", oDeprecated }, { "tcprcvbufpoll", oDeprecated }, { "tcprcvbuf", oDeprecated }, { "noneenabled", oUnsupported }, { "noneswitch", oUnsupported }, - { "versionaddendum", oVersionAddendum }, + { "versionaddendum", oDeprecated }, { NULL, oBadOption } }; static const char *lookup_opcode_name(OpCodes code); const char * kex_default_pk_alg(void) { static char *pkalgs; if (pkalgs == NULL) { char *all_key; all_key = sshkey_alg_list(0, 0, 1, ','); pkalgs = match_filter_allowlist(KEX_DEFAULT_PK_ALG, all_key); free(all_key); } return pkalgs; } char * ssh_connection_hash(const char *thishost, const char *host, const char *portstr, const char *user) { struct ssh_digest_ctx *md; u_char conn_hash[SSH_DIGEST_MAX_LENGTH]; if ((md = ssh_digest_start(SSH_DIGEST_SHA1)) == NULL || ssh_digest_update(md, thishost, strlen(thishost)) < 0 || ssh_digest_update(md, host, strlen(host)) < 0 || ssh_digest_update(md, portstr, strlen(portstr)) < 0 || ssh_digest_update(md, user, strlen(user)) < 0 || ssh_digest_final(md, conn_hash, sizeof(conn_hash)) < 0) fatal_f("mux digest failed"); ssh_digest_free(md); return tohex(conn_hash, ssh_digest_bytes(SSH_DIGEST_SHA1)); } /* * Adds a local TCP/IP port forward to options. Never returns if there is an * error. */ void add_local_forward(Options *options, const struct Forward *newfwd) { struct Forward *fwd; int i; /* Don't add duplicates */ for (i = 0; i < options->num_local_forwards; i++) { if (forward_equals(newfwd, options->local_forwards + i)) return; } options->local_forwards = xreallocarray(options->local_forwards, options->num_local_forwards + 1, sizeof(*options->local_forwards)); fwd = &options->local_forwards[options->num_local_forwards++]; fwd->listen_host = newfwd->listen_host; fwd->listen_port = newfwd->listen_port; fwd->listen_path = newfwd->listen_path; fwd->connect_host = newfwd->connect_host; fwd->connect_port = newfwd->connect_port; fwd->connect_path = newfwd->connect_path; } /* * Adds a remote TCP/IP port forward to options. Never returns if there is * an error. */ void add_remote_forward(Options *options, const struct Forward *newfwd) { struct Forward *fwd; int i; /* Don't add duplicates */ for (i = 0; i < options->num_remote_forwards; i++) { if (forward_equals(newfwd, options->remote_forwards + i)) return; } options->remote_forwards = xreallocarray(options->remote_forwards, options->num_remote_forwards + 1, sizeof(*options->remote_forwards)); fwd = &options->remote_forwards[options->num_remote_forwards++]; fwd->listen_host = newfwd->listen_host; fwd->listen_port = newfwd->listen_port; fwd->listen_path = newfwd->listen_path; fwd->connect_host = newfwd->connect_host; fwd->connect_port = newfwd->connect_port; fwd->connect_path = newfwd->connect_path; fwd->handle = newfwd->handle; fwd->allocated_port = 0; } static void clear_forwardings(Options *options) { int i; for (i = 0; i < options->num_local_forwards; i++) { free(options->local_forwards[i].listen_host); free(options->local_forwards[i].listen_path); free(options->local_forwards[i].connect_host); free(options->local_forwards[i].connect_path); } if (options->num_local_forwards > 0) { free(options->local_forwards); options->local_forwards = NULL; } options->num_local_forwards = 0; for (i = 0; i < options->num_remote_forwards; i++) { free(options->remote_forwards[i].listen_host); free(options->remote_forwards[i].listen_path); free(options->remote_forwards[i].connect_host); free(options->remote_forwards[i].connect_path); } if (options->num_remote_forwards > 0) { free(options->remote_forwards); options->remote_forwards = NULL; } options->num_remote_forwards = 0; options->tun_open = SSH_TUNMODE_NO; } void add_certificate_file(Options *options, const char *path, int userprovided) { int i; if (options->num_certificate_files >= SSH_MAX_CERTIFICATE_FILES) fatal("Too many certificate files specified (max %d)", SSH_MAX_CERTIFICATE_FILES); /* Avoid registering duplicates */ for (i = 0; i < options->num_certificate_files; i++) { if (options->certificate_file_userprovided[i] == userprovided && strcmp(options->certificate_files[i], path) == 0) { debug2_f("ignoring duplicate key %s", path); return; } } options->certificate_file_userprovided[options->num_certificate_files] = userprovided; options->certificate_files[options->num_certificate_files++] = xstrdup(path); } void add_identity_file(Options *options, const char *dir, const char *filename, int userprovided) { char *path; int i; if (options->num_identity_files >= SSH_MAX_IDENTITY_FILES) fatal("Too many identity files specified (max %d)", SSH_MAX_IDENTITY_FILES); if (dir == NULL) /* no dir, filename is absolute */ path = xstrdup(filename); else if (xasprintf(&path, "%s%s", dir, filename) >= PATH_MAX) fatal("Identity file path %s too long", path); /* Avoid registering duplicates */ for (i = 0; i < options->num_identity_files; i++) { if (options->identity_file_userprovided[i] == userprovided && strcmp(options->identity_files[i], path) == 0) { debug2_f("ignoring duplicate key %s", path); free(path); return; } } options->identity_file_userprovided[options->num_identity_files] = userprovided; options->identity_files[options->num_identity_files++] = path; } int default_ssh_port(void) { static int port; struct servent *sp; if (port == 0) { sp = getservbyname(SSH_SERVICE_NAME, "tcp"); port = sp ? ntohs(sp->s_port) : SSH_DEFAULT_PORT; } return port; } /* * Execute a command in a shell. * Return its exit status or -1 on abnormal exit. */ static int execute_in_shell(const char *cmd) { char *shell; pid_t pid; int status; if ((shell = getenv("SHELL")) == NULL) shell = _PATH_BSHELL; if (access(shell, X_OK) == -1) { fatal("Shell \"%s\" is not executable: %s", shell, strerror(errno)); } debug("Executing command: '%.500s'", cmd); /* Fork and execute the command. */ if ((pid = fork()) == 0) { char *argv[4]; if (stdfd_devnull(1, 1, 0) == -1) fatal_f("stdfd_devnull failed"); closefrom(STDERR_FILENO + 1); argv[0] = shell; argv[1] = "-c"; argv[2] = xstrdup(cmd); argv[3] = NULL; execv(argv[0], argv); error("Unable to execute '%.100s': %s", cmd, strerror(errno)); /* Die with signal to make this error apparent to parent. */ ssh_signal(SIGTERM, SIG_DFL); kill(getpid(), SIGTERM); _exit(1); } /* Parent. */ if (pid == -1) fatal_f("fork: %.100s", strerror(errno)); while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR && errno != EAGAIN) fatal_f("waitpid: %s", strerror(errno)); } if (!WIFEXITED(status)) { error("command '%.100s' exited abnormally", cmd); return -1; } debug3("command returned status %d", WEXITSTATUS(status)); return WEXITSTATUS(status); } /* * Parse and execute a Match directive. */ static int match_cfg_line(Options *options, char **condition, struct passwd *pw, const char *host_arg, const char *original_host, int final_pass, int *want_final_pass, const char *filename, int linenum) { char *arg, *oattrib, *attrib, *cmd, *cp = *condition, *host, *criteria; const char *ruser; int r, port, this_result, result = 1, attributes = 0, negate; char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV]; char uidstr[32]; /* * Configuration is likely to be incomplete at this point so we * must be prepared to use default values. */ port = options->port <= 0 ? default_ssh_port() : options->port; ruser = options->user == NULL ? pw->pw_name : options->user; if (final_pass) { host = xstrdup(options->hostname); } else if (options->hostname != NULL) { /* NB. Please keep in sync with ssh.c:main() */ host = percent_expand(options->hostname, "h", host_arg, (char *)NULL); } else { host = xstrdup(host_arg); } debug2("checking match for '%s' host %s originally %s", cp, host, original_host); while ((oattrib = attrib = strdelim(&cp)) && *attrib != '\0') { /* Terminate on comment */ if (*attrib == '#') { cp = NULL; /* mark all arguments consumed */ break; } arg = criteria = NULL; this_result = 1; if ((negate = attrib[0] == '!')) attrib++; /* Criterion "all" has no argument and must appear alone */ if (strcasecmp(attrib, "all") == 0) { if (attributes > 1 || ((arg = strdelim(&cp)) != NULL && *arg != '\0' && *arg != '#')) { error("%.200s line %d: '%s' cannot be combined " "with other Match attributes", filename, linenum, oattrib); result = -1; goto out; } if (arg != NULL && *arg == '#') cp = NULL; /* mark all arguments consumed */ if (result) result = negate ? 0 : 1; goto out; } attributes++; /* criteria "final" and "canonical" have no argument */ if (strcasecmp(attrib, "canonical") == 0 || strcasecmp(attrib, "final") == 0) { /* * If the config requests "Match final" then remember * this so we can perform a second pass later. */ if (strcasecmp(attrib, "final") == 0 && want_final_pass != NULL) *want_final_pass = 1; r = !!final_pass; /* 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' || *arg == '#') { error("Missing Match criteria for %s", attrib); result = -1; goto out; } if (strcasecmp(attrib, "host") == 0) { criteria = xstrdup(host); r = match_hostname(host, arg) == 1; if (r == (negate ? 1 : 0)) this_result = result = 0; } else if (strcasecmp(attrib, "originalhost") == 0) { criteria = xstrdup(original_host); r = match_hostname(original_host, arg) == 1; if (r == (negate ? 1 : 0)) this_result = result = 0; } else if (strcasecmp(attrib, "user") == 0) { criteria = xstrdup(ruser); r = match_pattern_list(ruser, arg, 0) == 1; if (r == (negate ? 1 : 0)) this_result = result = 0; } else if (strcasecmp(attrib, "localuser") == 0) { criteria = xstrdup(pw->pw_name); r = match_pattern_list(pw->pw_name, arg, 0) == 1; if (r == (negate ? 1 : 0)) this_result = result = 0; } else if (strcasecmp(attrib, "exec") == 0) { char *conn_hash_hex, *keyalias; if (gethostname(thishost, sizeof(thishost)) == -1) fatal("gethostname: %s", strerror(errno)); strlcpy(shorthost, thishost, sizeof(shorthost)); shorthost[strcspn(thishost, ".")] = '\0'; snprintf(portstr, sizeof(portstr), "%d", port); snprintf(uidstr, sizeof(uidstr), "%llu", (unsigned long long)pw->pw_uid); conn_hash_hex = ssh_connection_hash(thishost, host, portstr, ruser); keyalias = options->host_key_alias ? options->host_key_alias : host; cmd = percent_expand(arg, "C", conn_hash_hex, "L", shorthost, "d", pw->pw_dir, "h", host, "k", keyalias, "l", thishost, "n", original_host, "p", portstr, "r", ruser, "u", pw->pw_name, "i", uidstr, (char *)NULL); free(conn_hash_hex); if (result != 1) { /* skip execution if prior predicate failed */ 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; } out: if (result != -1) debug2("match %sfound", result ? "" : "not "); *condition = cp; free(host); return result; } /* Remove environment variable by pattern */ static void rm_env(Options *options, const char *arg, const char *filename, int linenum) { int i, j, onum_send_env = options->num_send_env; char *cp; /* Remove an environment variable */ for (i = 0; i < options->num_send_env; ) { cp = xstrdup(options->send_env[i]); if (!match_pattern(cp, arg + 1)) { free(cp); i++; continue; } debug3("%s line %d: removing environment %s", filename, linenum, cp); free(cp); free(options->send_env[i]); options->send_env[i] = NULL; for (j = i; j < options->num_send_env - 1; j++) { options->send_env[j] = options->send_env[j + 1]; options->send_env[j + 1] = NULL; } options->num_send_env--; /* NB. don't increment i */ } if (onum_send_env != options->num_send_env) { options->send_env = xrecallocarray(options->send_env, onum_send_env, options->num_send_env, sizeof(*options->send_env)); } } /* * Returns the number of the token pointed to by cp or oBadOption. */ static OpCodes parse_token(const char *cp, const char *filename, int linenum, const char *ignored_unknown) { int i; for (i = 0; keywords[i].name; i++) if (strcmp(cp, keywords[i].name) == 0) return keywords[i].opcode; if (ignored_unknown != NULL && match_pattern_list(cp, ignored_unknown, 1) == 1) return oIgnoredUnknownOption; error("%s: line %d: Bad configuration option: %s", filename, linenum, cp); return oBadOption; } /* Multistate option parsing */ struct multistate { char *key; int value; }; static const struct multistate multistate_flag[] = { { "true", 1 }, { "false", 0 }, { "yes", 1 }, { "no", 0 }, { NULL, -1 } }; static const struct multistate multistate_yesnoask[] = { { "true", 1 }, { "false", 0 }, { "yes", 1 }, { "no", 0 }, { "ask", 2 }, { NULL, -1 } }; static const struct multistate multistate_strict_hostkey[] = { { "true", SSH_STRICT_HOSTKEY_YES }, { "false", SSH_STRICT_HOSTKEY_OFF }, { "yes", SSH_STRICT_HOSTKEY_YES }, { "no", SSH_STRICT_HOSTKEY_OFF }, { "ask", SSH_STRICT_HOSTKEY_ASK }, { "off", SSH_STRICT_HOSTKEY_OFF }, { "accept-new", SSH_STRICT_HOSTKEY_NEW }, { NULL, -1 } }; static const struct multistate multistate_yesnoaskconfirm[] = { { "true", 1 }, { "false", 0 }, { "yes", 1 }, { "no", 0 }, { "ask", 2 }, { "confirm", 3 }, { NULL, -1 } }; static const struct multistate multistate_addressfamily[] = { { "inet", AF_INET }, { "inet6", AF_INET6 }, { "any", AF_UNSPEC }, { NULL, -1 } }; static const struct multistate multistate_controlmaster[] = { { "true", SSHCTL_MASTER_YES }, { "yes", SSHCTL_MASTER_YES }, { "false", SSHCTL_MASTER_NO }, { "no", SSHCTL_MASTER_NO }, { "auto", SSHCTL_MASTER_AUTO }, { "ask", SSHCTL_MASTER_ASK }, { "autoask", SSHCTL_MASTER_AUTO_ASK }, { NULL, -1 } }; static const struct multistate multistate_tunnel[] = { { "ethernet", SSH_TUNMODE_ETHERNET }, { "point-to-point", SSH_TUNMODE_POINTOPOINT }, { "true", SSH_TUNMODE_DEFAULT }, { "yes", SSH_TUNMODE_DEFAULT }, { "false", SSH_TUNMODE_NO }, { "no", SSH_TUNMODE_NO }, { NULL, -1 } }; static const struct multistate multistate_requesttty[] = { { "true", REQUEST_TTY_YES }, { "yes", REQUEST_TTY_YES }, { "false", REQUEST_TTY_NO }, { "no", REQUEST_TTY_NO }, { "force", REQUEST_TTY_FORCE }, { "auto", REQUEST_TTY_AUTO }, { NULL, -1 } }; static const struct multistate multistate_sessiontype[] = { { "none", SESSION_TYPE_NONE }, { "subsystem", SESSION_TYPE_SUBSYSTEM }, { "default", SESSION_TYPE_DEFAULT }, { NULL, -1 } }; static const struct multistate multistate_canonicalizehostname[] = { { "true", SSH_CANONICALISE_YES }, { "false", SSH_CANONICALISE_NO }, { "yes", SSH_CANONICALISE_YES }, { "no", SSH_CANONICALISE_NO }, { "always", SSH_CANONICALISE_ALWAYS }, { NULL, -1 } }; static const struct multistate multistate_pubkey_auth[] = { { "true", SSH_PUBKEY_AUTH_ALL }, { "false", SSH_PUBKEY_AUTH_NO }, { "yes", SSH_PUBKEY_AUTH_ALL }, { "no", SSH_PUBKEY_AUTH_NO }, { "unbound", SSH_PUBKEY_AUTH_UNBOUND }, { "host-bound", SSH_PUBKEY_AUTH_HBOUND }, { NULL, -1 } }; static const struct multistate multistate_compression[] = { #ifdef WITH_ZLIB { "yes", COMP_ZLIB }, #endif { "no", COMP_NONE }, { NULL, -1 } }; static int parse_multistate_value(const char *arg, const char *filename, int linenum, const struct multistate *multistate_ptr) { int i; if (!arg || *arg == '\0') { error("%s line %d: missing argument.", filename, linenum); return -1; } for (i = 0; multistate_ptr[i].key != NULL; i++) { if (strcasecmp(arg, multistate_ptr[i].key) == 0) return multistate_ptr[i].value; } return -1; } /* * Processes a single option line as used in the configuration files. This * only sets those values that have not already been set. */ int process_config_line(Options *options, struct passwd *pw, const char *host, const char *original_host, char *line, const char *filename, int linenum, int *activep, int flags) { return process_config_line_depth(options, pw, host, original_host, line, filename, linenum, activep, flags, NULL, 0); } #define WHITESPACE " \t\r\n" static int process_config_line_depth(Options *options, struct passwd *pw, const char *host, const char *original_host, char *line, const char *filename, int linenum, int *activep, int flags, int *want_final_pass, int depth) { char *str, **charptr, *endofnumber, *keyword, *arg, *arg2, *p; char **cpptr, ***cppptr, fwdarg[256]; u_int i, *uintptr, uvalue, max_entries = 0; int r, oactive, negated, opcode, *intptr, value, value2, cmdline = 0; int remotefwd, dynamicfwd; LogLevel *log_level_ptr; SyslogFacility *log_facility_ptr; long long val64; size_t len; struct Forward fwd; const struct multistate *multistate_ptr; struct allowed_cname *cname; glob_t gl; const char *errstr; char **oav = NULL, **av; int oac = 0, ac; int ret = -1; if (activep == NULL) { /* We are processing a command line directive */ cmdline = 1; activep = &cmdline; } /* Strip trailing whitespace. Allow \f (form feed) at EOL only */ if ((len = strlen(line)) == 0) return 0; for (len--; len > 0; len--) { if (strchr(WHITESPACE "\f", line[len]) == NULL) break; line[len] = '\0'; } str = line; /* Get the keyword. (Each line is supposed to begin with a keyword). */ if ((keyword = strdelim(&str)) == NULL) return 0; /* Ignore leading whitespace. */ if (*keyword == '\0') keyword = strdelim(&str); if (keyword == NULL || !*keyword || *keyword == '\n' || *keyword == '#') return 0; /* Match lowercase keyword */ lowercase(keyword); /* Prepare to parse remainder of line */ if (str != NULL) str += strspn(str, WHITESPACE); if (str == NULL || *str == '\0') { error("%s line %d: no argument after keyword \"%s\"", filename, linenum, keyword); return -1; } opcode = parse_token(keyword, filename, linenum, options->ignored_unknown); if (argv_split(str, &oac, &oav, 1) != 0) { error("%s line %d: invalid quotes", filename, linenum); return -1; } ac = oac; av = oav; switch (opcode) { case oBadOption: /* don't panic, but count bad options */ goto out; case oIgnore: argv_consume(&ac); break; case oIgnoredUnknownOption: debug("%s line %d: Ignored unknown option \"%s\"", filename, linenum, keyword); argv_consume(&ac); break; case oConnectTimeout: intptr = &options->connection_timeout; parse_time: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%s line %d: missing time value.", filename, linenum); goto out; } if (strcmp(arg, "none") == 0) value = -1; else if ((value = convtime(arg)) == -1) { error("%s line %d: invalid time value.", filename, linenum); goto out; } if (*activep && *intptr == -1) *intptr = value; break; case oForwardAgent: intptr = &options->forward_agent; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%s line %d: missing argument.", filename, linenum); goto out; } value = -1; multistate_ptr = multistate_flag; for (i = 0; multistate_ptr[i].key != NULL; i++) { if (strcasecmp(arg, multistate_ptr[i].key) == 0) { value = multistate_ptr[i].value; break; } } if (value != -1) { if (*activep && *intptr == -1) *intptr = value; break; } /* ForwardAgent wasn't 'yes' or 'no', assume a path */ if (*activep && *intptr == -1) *intptr = 1; charptr = &options->forward_agent_sock_path; goto parse_agent_path; case oForwardX11: intptr = &options->forward_x11; parse_flag: multistate_ptr = multistate_flag; parse_multistate: arg = argv_next(&ac, &av); if ((value = parse_multistate_value(arg, filename, linenum, multistate_ptr)) == -1) { error("%s line %d: unsupported option \"%s\".", filename, linenum, arg); goto out; } if (*activep && *intptr == -1) *intptr = value; break; case oForwardX11Trusted: intptr = &options->forward_x11_trusted; goto parse_flag; case oForwardX11Timeout: intptr = &options->forward_x11_timeout; goto parse_time; case oGatewayPorts: intptr = &options->fwd_opts.gateway_ports; goto parse_flag; case oExitOnForwardFailure: intptr = &options->exit_on_forward_failure; goto parse_flag; case oPasswordAuthentication: intptr = &options->password_authentication; goto parse_flag; case oKbdInteractiveAuthentication: intptr = &options->kbd_interactive_authentication; goto parse_flag; case oKbdInteractiveDevices: charptr = &options->kbd_interactive_devices; goto parse_string; case oPubkeyAuthentication: multistate_ptr = multistate_pubkey_auth; intptr = &options->pubkey_authentication; goto parse_multistate; case oHostbasedAuthentication: intptr = &options->hostbased_authentication; goto parse_flag; case oGssAuthentication: intptr = &options->gss_authentication; goto parse_flag; case oGssDelegateCreds: intptr = &options->gss_deleg_creds; goto parse_flag; case oBatchMode: intptr = &options->batch_mode; goto parse_flag; case oCheckHostIP: intptr = &options->check_host_ip; goto parse_flag; case oVerifyHostKeyDNS: intptr = &options->verify_host_key_dns; multistate_ptr = multistate_yesnoask; goto parse_multistate; case oStrictHostKeyChecking: intptr = &options->strict_host_key_checking; multistate_ptr = multistate_strict_hostkey; goto parse_multistate; case oCompression: intptr = &options->compression; multistate_ptr = multistate_compression; goto parse_multistate; case oTCPKeepAlive: intptr = &options->tcp_keep_alive; goto parse_flag; case oNoHostAuthenticationForLocalhost: intptr = &options->no_host_authentication_for_localhost; goto parse_flag; case oNumberOfPasswordPrompts: intptr = &options->number_of_password_prompts; goto parse_int; case oRekeyLimit: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (strcmp(arg, "default") == 0) { val64 = 0; } else { if (scan_scaled(arg, &val64) == -1) { error("%.200s line %d: Bad number '%s': %s", filename, linenum, arg, strerror(errno)); goto out; } if (val64 != 0 && val64 < 16) { error("%.200s line %d: RekeyLimit too small", filename, linenum); goto out; } } if (*activep && options->rekey_limit == -1) options->rekey_limit = val64; if (ac != 0) { /* optional rekey interval present */ if (strcmp(av[0], "none") == 0) { (void)argv_next(&ac, &av); /* discard */ break; } intptr = &options->rekey_interval; goto parse_time; } break; case oIdentityFile: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (*activep) { intptr = &options->num_identity_files; if (*intptr >= SSH_MAX_IDENTITY_FILES) { error("%.200s line %d: Too many identity files " "specified (max %d).", filename, linenum, SSH_MAX_IDENTITY_FILES); goto out; } add_identity_file(options, NULL, arg, flags & SSHCONF_USERCONF); } break; case oCertificateFile: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (*activep) { intptr = &options->num_certificate_files; if (*intptr >= SSH_MAX_CERTIFICATE_FILES) { error("%.200s line %d: Too many certificate " "files specified (max %d).", filename, linenum, SSH_MAX_CERTIFICATE_FILES); goto out; } add_certificate_file(options, arg, flags & SSHCONF_USERCONF); } break; case oXAuthLocation: charptr=&options->xauth_location; goto parse_string; case oUser: charptr = &options->user; parse_string: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (*activep && *charptr == NULL) *charptr = xstrdup(arg); break; case oGlobalKnownHostsFile: cpptr = (char **)&options->system_hostfiles; uintptr = &options->num_system_hostfiles; max_entries = SSH_MAX_HOSTS_FILES; parse_char_array: i = 0; value = *uintptr == 0; /* was array empty when we started? */ while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0') { error("%s line %d: keyword %s empty argument", filename, linenum, keyword); goto out; } /* Allow "none" only in first position */ if (strcasecmp(arg, "none") == 0) { if (i > 0 || ac > 0) { error("%s line %d: keyword %s \"none\" " "argument must appear alone.", filename, linenum, keyword); goto out; } } i++; if (*activep && value) { if ((*uintptr) >= max_entries) { error("%s line %d: too many %s " "entries.", filename, linenum, keyword); goto out; } cpptr[(*uintptr)++] = xstrdup(arg); } } break; case oUserKnownHostsFile: cpptr = (char **)&options->user_hostfiles; uintptr = &options->num_user_hostfiles; max_entries = SSH_MAX_HOSTS_FILES; goto parse_char_array; case oHostname: charptr = &options->hostname; goto parse_string; case oHostKeyAlias: charptr = &options->host_key_alias; goto parse_string; case oPreferredAuthentications: charptr = &options->preferred_authentications; goto parse_string; case oBindAddress: charptr = &options->bind_address; goto parse_string; case oBindInterface: charptr = &options->bind_interface; goto parse_string; case oPKCS11Provider: charptr = &options->pkcs11_provider; goto parse_string; case oSecurityKeyProvider: charptr = &options->sk_provider; goto parse_string; case oKnownHostsCommand: charptr = &options->known_hosts_command; goto parse_command; case oProxyCommand: charptr = &options->proxy_command; /* Ignore ProxyCommand if ProxyJump already specified */ if (options->jump_host != NULL) charptr = &options->jump_host; /* Skip below */ parse_command: if (str == NULL) { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } len = strspn(str, WHITESPACE "="); if (*activep && *charptr == NULL) *charptr = xstrdup(str + len); argv_consume(&ac); break; case oProxyJump: if (str == NULL) { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } len = strspn(str, WHITESPACE "="); /* XXX use argv? */ if (parse_jump(str + len, options, *activep) == -1) { error("%.200s line %d: Invalid ProxyJump \"%s\"", filename, linenum, str + len); goto out; } argv_consume(&ac); break; case oPort: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } value = a2port(arg); if (value <= 0) { error("%.200s line %d: Bad port '%s'.", filename, linenum, arg); goto out; } if (*activep && options->port == -1) options->port = value; break; case oConnectionAttempts: intptr = &options->connection_attempts; parse_int: arg = argv_next(&ac, &av); if ((errstr = atoi_err(arg, &value)) != NULL) { error("%s line %d: integer value %s.", filename, linenum, errstr); goto out; } if (*activep && *intptr == -1) *intptr = value; break; case oCiphers: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (*arg != '-' && !ciphers_valid(*arg == '+' || *arg == '^' ? arg + 1 : arg)){ error("%.200s line %d: Bad SSH2 cipher spec '%s'.", filename, linenum, arg ? arg : ""); goto out; } if (*activep && options->ciphers == NULL) options->ciphers = xstrdup(arg); break; case oMacs: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (*arg != '-' && !mac_valid(*arg == '+' || *arg == '^' ? arg + 1 : arg)) { error("%.200s line %d: Bad SSH2 MAC spec '%s'.", filename, linenum, arg ? arg : ""); goto out; } if (*activep && options->macs == NULL) options->macs = xstrdup(arg); break; case oKexAlgorithms: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (*arg != '-' && !kex_names_valid(*arg == '+' || *arg == '^' ? arg + 1 : arg)) { error("%.200s line %d: Bad SSH2 KexAlgorithms '%s'.", filename, linenum, arg ? arg : ""); goto out; } if (*activep && options->kex_algorithms == NULL) options->kex_algorithms = xstrdup(arg); break; case oHostKeyAlgorithms: charptr = &options->hostkeyalgorithms; parse_pubkey_algos: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (*arg != '-' && !sshkey_names_valid2(*arg == '+' || *arg == '^' ? arg + 1 : arg, 1)) { error("%s line %d: Bad key types '%s'.", filename, linenum, arg ? arg : ""); goto out; } if (*activep && *charptr == NULL) *charptr = xstrdup(arg); break; case oCASignatureAlgorithms: charptr = &options->ca_sign_algorithms; goto parse_pubkey_algos; case oLogLevel: log_level_ptr = &options->log_level; arg = argv_next(&ac, &av); value = log_level_number(arg); if (value == SYSLOG_LEVEL_NOT_SET) { error("%.200s line %d: unsupported log level '%s'", filename, linenum, arg ? arg : ""); goto out; } if (*activep && *log_level_ptr == SYSLOG_LEVEL_NOT_SET) *log_level_ptr = (LogLevel) value; break; case oLogFacility: log_facility_ptr = &options->log_facility; arg = argv_next(&ac, &av); value = log_facility_number(arg); if (value == SYSLOG_FACILITY_NOT_SET) { error("%.200s line %d: unsupported log facility '%s'", filename, linenum, arg ? arg : ""); goto out; } if (*log_facility_ptr == -1) *log_facility_ptr = (SyslogFacility) value; break; case oLogVerbose: cppptr = &options->log_verbose; uintptr = &options->num_log_verbose; i = 0; while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0') { error("%s line %d: keyword %s empty argument", filename, linenum, keyword); goto out; } /* Allow "none" only in first position */ if (strcasecmp(arg, "none") == 0) { if (i > 0 || ac > 0) { error("%s line %d: keyword %s \"none\" " "argument must appear alone.", filename, linenum, keyword); goto out; } } i++; if (*activep && *uintptr == 0) { *cppptr = xrecallocarray(*cppptr, *uintptr, *uintptr + 1, sizeof(**cppptr)); (*cppptr)[(*uintptr)++] = xstrdup(arg); } } break; case oLocalForward: case oRemoteForward: case oDynamicForward: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } remotefwd = (opcode == oRemoteForward); dynamicfwd = (opcode == oDynamicForward); if (!dynamicfwd) { arg2 = argv_next(&ac, &av); if (arg2 == NULL || *arg2 == '\0') { if (remotefwd) dynamicfwd = 1; else { error("%.200s line %d: Missing target " "argument.", filename, linenum); goto out; } } else { /* construct a string for parse_forward */ snprintf(fwdarg, sizeof(fwdarg), "%s:%s", arg, arg2); } } if (dynamicfwd) strlcpy(fwdarg, arg, sizeof(fwdarg)); if (parse_forward(&fwd, fwdarg, dynamicfwd, remotefwd) == 0) { error("%.200s line %d: Bad forwarding specification.", filename, linenum); goto out; } if (*activep) { if (remotefwd) { add_remote_forward(options, &fwd); } else { add_local_forward(options, &fwd); } } break; case oPermitRemoteOpen: uintptr = &options->num_permitted_remote_opens; cppptr = &options->permitted_remote_opens; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') fatal("%s line %d: missing %s specification", filename, linenum, lookup_opcode_name(opcode)); uvalue = *uintptr; /* modified later */ if (strcmp(arg, "any") == 0 || strcmp(arg, "none") == 0) { if (*activep && uvalue == 0) { *uintptr = 1; *cppptr = xcalloc(1, sizeof(**cppptr)); (*cppptr)[0] = xstrdup(arg); } break; } while ((arg = argv_next(&ac, &av)) != NULL) { arg2 = xstrdup(arg); p = hpdelim(&arg); if (p == NULL) { fatal("%s line %d: missing host in %s", filename, linenum, lookup_opcode_name(opcode)); } p = cleanhostname(p); /* * don't want to use permitopen_port to avoid * dependency on channels.[ch] here. */ if (arg == NULL || (strcmp(arg, "*") != 0 && a2port(arg) <= 0)) { fatal("%s line %d: bad port number in %s", filename, linenum, lookup_opcode_name(opcode)); } if (*activep && uvalue == 0) { opt_array_append(filename, linenum, lookup_opcode_name(opcode), cppptr, uintptr, arg2); } free(arg2); } break; case oClearAllForwardings: intptr = &options->clear_forwardings; goto parse_flag; case oHost: if (cmdline) { error("Host directive not supported as a command-line " "option"); goto out; } *activep = 0; arg2 = NULL; while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0') { error("%s line %d: keyword %s empty argument", filename, linenum, keyword); goto out; } if ((flags & SSHCONF_NEVERMATCH) != 0) { argv_consume(&ac); break; } negated = *arg == '!'; if (negated) arg++; if (match_pattern(host, arg)) { if (negated) { debug("%.200s line %d: Skipping Host " "block because of negated match " "for %.100s", filename, linenum, arg); *activep = 0; argv_consume(&ac); break; } if (!*activep) arg2 = arg; /* logged below */ *activep = 1; } } if (*activep) debug("%.200s line %d: Applying options for %.100s", filename, linenum, arg2); break; case oMatch: if (cmdline) { error("Host directive not supported as a command-line " "option"); goto out; } value = match_cfg_line(options, &str, pw, host, original_host, flags & SSHCONF_FINAL, want_final_pass, filename, linenum); if (value < 0) { error("%.200s line %d: Bad Match condition", filename, linenum); goto out; } *activep = (flags & SSHCONF_NEVERMATCH) ? 0 : value; /* * If match_cfg_line() didn't consume all its arguments then * arrange for the extra arguments check below to fail. */ if (str == NULL || *str == '\0') argv_consume(&ac); break; case oEscapeChar: intptr = &options->escape_char; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if (strcmp(arg, "none") == 0) value = SSH_ESCAPECHAR_NONE; else if (arg[1] == '\0') value = (u_char) arg[0]; else if (arg[0] == '^' && arg[2] == 0 && (u_char) arg[1] >= 64 && (u_char) arg[1] < 128) value = (u_char) arg[1] & 31; else { error("%.200s line %d: Bad escape character.", filename, linenum); goto out; } if (*activep && *intptr == -1) *intptr = value; break; case oAddressFamily: intptr = &options->address_family; multistate_ptr = multistate_addressfamily; goto parse_multistate; case oEnableSSHKeysign: intptr = &options->enable_ssh_keysign; goto parse_flag; case oIdentitiesOnly: intptr = &options->identities_only; goto parse_flag; case oServerAliveInterval: intptr = &options->server_alive_interval; goto parse_time; case oServerAliveCountMax: intptr = &options->server_alive_count_max; goto parse_int; case oSendEnv: while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0' || strchr(arg, '=') != NULL) { error("%s line %d: Invalid environment name.", filename, linenum); goto out; } if (!*activep) continue; if (*arg == '-') { /* Removing an env var */ rm_env(options, arg, filename, linenum); continue; } else { /* Adding an env var */ if (options->num_send_env >= INT_MAX) { error("%s line %d: too many send env.", filename, linenum); goto out; } options->send_env = xrecallocarray( options->send_env, options->num_send_env, options->num_send_env + 1, sizeof(*options->send_env)); options->send_env[options->num_send_env++] = xstrdup(arg); } } break; case oSetEnv: value = options->num_setenv; while ((arg = argv_next(&ac, &av)) != NULL) { if (strchr(arg, '=') == NULL) { error("%s line %d: Invalid SetEnv.", filename, linenum); goto out; } if (!*activep || value != 0) continue; /* Adding a setenv var */ if (options->num_setenv >= INT_MAX) { error("%s line %d: too many SetEnv.", filename, linenum); goto out; } options->setenv = xrecallocarray( options->setenv, options->num_setenv, options->num_setenv + 1, sizeof(*options->setenv)); options->setenv[options->num_setenv++] = xstrdup(arg); } break; case oControlPath: charptr = &options->control_path; goto parse_string; case oControlMaster: intptr = &options->control_master; multistate_ptr = multistate_controlmaster; goto parse_multistate; case oControlPersist: /* no/false/yes/true, or a time spec */ intptr = &options->control_persist; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing ControlPersist" " argument.", filename, linenum); goto out; } value = 0; value2 = 0; /* timeout */ if (strcmp(arg, "no") == 0 || strcmp(arg, "false") == 0) value = 0; else if (strcmp(arg, "yes") == 0 || strcmp(arg, "true") == 0) value = 1; else if ((value2 = convtime(arg)) >= 0) value = 1; else { error("%.200s line %d: Bad ControlPersist argument.", filename, linenum); goto out; } if (*activep && *intptr == -1) { *intptr = value; options->control_persist_timeout = value2; } break; case oHashKnownHosts: intptr = &options->hash_known_hosts; goto parse_flag; case oTunnel: intptr = &options->tun_open; multistate_ptr = multistate_tunnel; goto parse_multistate; case oTunnelDevice: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } value = a2tun(arg, &value2); if (value == SSH_TUNID_ERR) { error("%.200s line %d: Bad tun device.", filename, linenum); goto out; } if (*activep && options->tun_local == -1) { options->tun_local = value; options->tun_remote = value2; } break; case oLocalCommand: charptr = &options->local_command; goto parse_command; case oPermitLocalCommand: intptr = &options->permit_local_command; goto parse_flag; case oRemoteCommand: charptr = &options->remote_command; goto parse_command; case oVisualHostKey: intptr = &options->visual_host_key; goto parse_flag; case oInclude: if (cmdline) { error("Include directive not supported as a " "command-line option"); goto out; } value = 0; while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0') { error("%s line %d: keyword %s empty argument", filename, linenum, keyword); goto out; } /* * Ensure all paths are anchored. User configuration * files may begin with '~/' but system configurations * must not. If the path is relative, then treat it * as living in ~/.ssh for user configurations or * /etc/ssh for system ones. */ if (*arg == '~' && (flags & SSHCONF_USERCONF) == 0) { error("%.200s line %d: bad include path %s.", filename, linenum, arg); goto out; } if (!path_absolute(arg) && *arg != '~') { xasprintf(&arg2, "%s/%s", (flags & SSHCONF_USERCONF) ? "~/" _PATH_SSH_USER_DIR : SSHDIR, arg); } else arg2 = xstrdup(arg); memset(&gl, 0, sizeof(gl)); r = glob(arg2, GLOB_TILDE, NULL, &gl); if (r == GLOB_NOMATCH) { debug("%.200s line %d: include %s matched no " "files",filename, linenum, arg2); free(arg2); continue; } else if (r != 0) { error("%.200s line %d: glob failed for %s.", filename, linenum, arg2); goto out; } free(arg2); oactive = *activep; for (i = 0; i < gl.gl_pathc; i++) { debug3("%.200s line %d: Including file %s " "depth %d%s", filename, linenum, gl.gl_pathv[i], depth, oactive ? "" : " (parse only)"); r = read_config_file_depth(gl.gl_pathv[i], pw, host, original_host, options, flags | SSHCONF_CHECKPERM | (oactive ? 0 : SSHCONF_NEVERMATCH), activep, want_final_pass, depth + 1); if (r != 1 && errno != ENOENT) { error("Can't open user config file " "%.100s: %.100s", gl.gl_pathv[i], strerror(errno)); globfree(&gl); goto out; } /* * don't let Match in includes clobber the * containing file's Match state. */ *activep = oactive; if (r != 1) value = -1; } globfree(&gl); } if (value != 0) ret = value; break; case oIPQoS: arg = argv_next(&ac, &av); if ((value = parse_ipqos(arg)) == -1) { error("%s line %d: Bad IPQoS value: %s", filename, linenum, arg); goto out; } arg = argv_next(&ac, &av); if (arg == NULL) value2 = value; else if ((value2 = parse_ipqos(arg)) == -1) { error("%s line %d: Bad IPQoS value: %s", filename, linenum, arg); goto out; } if (*activep && options->ip_qos_interactive == -1) { options->ip_qos_interactive = value; options->ip_qos_bulk = value2; } break; case oRequestTTY: intptr = &options->request_tty; multistate_ptr = multistate_requesttty; goto parse_multistate; case oSessionType: intptr = &options->session_type; multistate_ptr = multistate_sessiontype; goto parse_multistate; case oStdinNull: intptr = &options->stdin_null; goto parse_flag; case oForkAfterAuthentication: intptr = &options->fork_after_authentication; goto parse_flag; - case oVersionAddendum: - if (str == NULL) - fatal("%.200s line %d: Missing argument.", filename, - linenum); - len = strspn(str, WHITESPACE); - if (*activep && options->version_addendum == NULL) { - if (strcasecmp(str + len, "none") == 0) - options->version_addendum = xstrdup(""); - else if (strchr(str + len, '\r') != NULL) - fatal("%.200s line %d: Invalid argument", - filename, linenum); - else - options->version_addendum = xstrdup(str + len); - } - return 0; - case oIgnoreUnknown: charptr = &options->ignored_unknown; goto parse_string; case oProxyUseFdpass: intptr = &options->proxy_use_fdpass; goto parse_flag; case oCanonicalDomains: value = options->num_canonical_domains != 0; i = 0; while ((arg = argv_next(&ac, &av)) != NULL) { if (*arg == '\0') { error("%s line %d: keyword %s empty argument", filename, linenum, keyword); goto out; } /* Allow "none" only in first position */ if (strcasecmp(arg, "none") == 0) { if (i > 0 || ac > 0) { error("%s line %d: keyword %s \"none\" " "argument must appear alone.", filename, linenum, keyword); goto out; } } i++; if (!valid_domain(arg, 1, &errstr)) { error("%s line %d: %s", filename, linenum, errstr); goto out; } if (!*activep || value) continue; if (options->num_canonical_domains >= MAX_CANON_DOMAINS) { error("%s line %d: too many hostname suffixes.", filename, linenum); goto out; } options->canonical_domains[ options->num_canonical_domains++] = xstrdup(arg); } break; case oCanonicalizePermittedCNAMEs: value = options->num_permitted_cnames != 0; i = 0; while ((arg = argv_next(&ac, &av)) != NULL) { /* * Either 'none' (only in first position), '*' for * everything or 'list:list' */ if (strcasecmp(arg, "none") == 0) { if (i > 0 || ac > 0) { error("%s line %d: keyword %s \"none\" " "argument must appear alone.", filename, linenum, keyword); goto out; } arg2 = ""; } else if (strcmp(arg, "*") == 0) { arg2 = arg; } else { lowercase(arg); if ((arg2 = strchr(arg, ':')) == NULL || arg2[1] == '\0') { error("%s line %d: " "Invalid permitted CNAME \"%s\"", filename, linenum, arg); goto out; } *arg2 = '\0'; arg2++; } i++; if (!*activep || value) continue; if (options->num_permitted_cnames >= MAX_CANON_DOMAINS) { error("%s line %d: too many permitted CNAMEs.", filename, linenum); goto out; } cname = options->permitted_cnames + options->num_permitted_cnames++; cname->source_list = xstrdup(arg); cname->target_list = xstrdup(arg2); } break; case oCanonicalizeHostname: intptr = &options->canonicalize_hostname; multistate_ptr = multistate_canonicalizehostname; goto parse_multistate; case oCanonicalizeMaxDots: intptr = &options->canonicalize_max_dots; goto parse_int; case oCanonicalizeFallbackLocal: intptr = &options->canonicalize_fallback_local; goto parse_flag; case oStreamLocalBindMask: arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing StreamLocalBindMask " "argument.", filename, linenum); goto out; } /* Parse mode in octal format */ value = strtol(arg, &endofnumber, 8); if (arg == endofnumber || value < 0 || value > 0777) { error("%.200s line %d: Bad mask.", filename, linenum); goto out; } options->fwd_opts.streamlocal_bind_mask = (mode_t)value; break; case oStreamLocalBindUnlink: intptr = &options->fwd_opts.streamlocal_bind_unlink; goto parse_flag; case oRevokedHostKeys: charptr = &options->revoked_host_keys; goto parse_string; case oFingerprintHash: intptr = &options->fingerprint_hash; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } if ((value = ssh_digest_alg_by_name(arg)) == -1) { error("%.200s line %d: Invalid hash algorithm \"%s\".", filename, linenum, arg); goto out; } if (*activep && *intptr == -1) *intptr = value; break; case oUpdateHostkeys: intptr = &options->update_hostkeys; multistate_ptr = multistate_yesnoask; goto parse_multistate; case oHostbasedAcceptedAlgorithms: charptr = &options->hostbased_accepted_algos; goto parse_pubkey_algos; case oPubkeyAcceptedAlgorithms: charptr = &options->pubkey_accepted_algos; goto parse_pubkey_algos; case oAddKeysToAgent: arg = argv_next(&ac, &av); arg2 = argv_next(&ac, &av); value = parse_multistate_value(arg, filename, linenum, multistate_yesnoaskconfirm); value2 = 0; /* unlimited lifespan by default */ if (value == 3 && arg2 != NULL) { /* allow "AddKeysToAgent confirm 5m" */ if ((value2 = convtime(arg2)) == -1 || value2 > INT_MAX) { error("%s line %d: invalid time value.", filename, linenum); goto out; } } else if (value == -1 && arg2 == NULL) { if ((value2 = convtime(arg)) == -1 || value2 > INT_MAX) { error("%s line %d: unsupported option", filename, linenum); goto out; } value = 1; /* yes */ } else if (value == -1 || arg2 != NULL) { error("%s line %d: unsupported option", filename, linenum); goto out; } if (*activep && options->add_keys_to_agent == -1) { options->add_keys_to_agent = value; options->add_keys_to_agent_lifespan = value2; } break; case oIdentityAgent: charptr = &options->identity_agent; arg = argv_next(&ac, &av); if (!arg || *arg == '\0') { error("%.200s line %d: Missing argument.", filename, linenum); goto out; } parse_agent_path: /* Extra validation if the string represents an env var. */ if ((arg2 = dollar_expand(&r, arg)) == NULL || r) { error("%.200s line %d: Invalid environment expansion " "%s.", filename, linenum, arg); goto out; } free(arg2); /* check for legacy environment format */ if (arg[0] == '$' && arg[1] != '{' && !valid_env_name(arg + 1)) { error("%.200s line %d: Invalid environment name %s.", filename, linenum, arg); goto out; } if (*activep && *charptr == NULL) *charptr = xstrdup(arg); break; case oDeprecated: debug("%s line %d: Deprecated option \"%s\"", filename, linenum, keyword); argv_consume(&ac); break; case oUnsupported: error("%s line %d: Unsupported option \"%s\"", filename, linenum, keyword); argv_consume(&ac); break; default: error("%s line %d: Unimplemented opcode %d", filename, linenum, opcode); goto out; } /* Check that there is no garbage at end of line. */ if (ac > 0) { error("%.200s line %d: keyword %s extra arguments " "at end of line", filename, linenum, keyword); goto out; } /* success */ ret = 0; out: argv_free(oav, oac); return ret; } /* * Reads the config file and modifies the options accordingly. Options * should already be initialized before this call. This never returns if * there is an error. If the file does not exist, this returns 0. */ int read_config_file(const char *filename, struct passwd *pw, const char *host, const char *original_host, Options *options, int flags, int *want_final_pass) { int active = 1; return read_config_file_depth(filename, pw, host, original_host, options, flags, &active, want_final_pass, 0); } #define READCONF_MAX_DEPTH 16 static int read_config_file_depth(const char *filename, struct passwd *pw, const char *host, const char *original_host, Options *options, int flags, int *activep, int *want_final_pass, int depth) { FILE *f; char *line = NULL; size_t linesize = 0; int linenum; int bad_options = 0; if (depth < 0 || depth > READCONF_MAX_DEPTH) fatal("Too many recursive configuration includes"); if ((f = fopen(filename, "r")) == NULL) return 0; if (flags & SSHCONF_CHECKPERM) { struct stat sb; if (fstat(fileno(f), &sb) == -1) fatal("fstat %s: %s", filename, strerror(errno)); if (((sb.st_uid != 0 && sb.st_uid != getuid()) || (sb.st_mode & 022) != 0)) fatal("Bad owner or permissions on %s", filename); } debug("Reading configuration data %.200s", filename); /* * Mark that we are now processing the options. This flag is turned * on/off by Host specifications. */ linenum = 0; while (getline(&line, &linesize, f) != -1) { /* Update line number counter. */ linenum++; /* * Trim out comments and strip whitespace. * NB - preserve newlines, they are needed to reproduce * line numbers later for error messages. */ if (process_config_line_depth(options, pw, host, original_host, line, filename, linenum, activep, flags, want_final_pass, depth) != 0) bad_options++; } free(line); fclose(f); if (bad_options > 0) fatal("%s: terminating, %d bad configuration options", filename, bad_options); 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; } /* * Returns 1 if CanonicalizePermittedCNAMEs have been specified, 0 otherwise. * Allowed to be called on non-final configuration. */ int config_has_permitted_cnames(Options *options) { if (options->num_permitted_cnames == 1 && strcasecmp(options->permitted_cnames[0].source_list, "none") == 0 && strcmp(options->permitted_cnames[0].target_list, "") == 0) return 0; return options->num_permitted_cnames > 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 * are processed in the following order: command line, user config file, * system config file. Last, fill_default_options is called. */ void initialize_options(Options * options) { memset(options, 'X', sizeof(*options)); - options->version_addendum = NULL; options->forward_agent = -1; options->forward_agent_sock_path = NULL; options->forward_x11 = -1; options->forward_x11_trusted = -1; options->forward_x11_timeout = -1; options->stdio_forward_host = NULL; options->stdio_forward_port = 0; options->clear_forwardings = -1; options->exit_on_forward_failure = -1; options->xauth_location = NULL; options->fwd_opts.gateway_ports = -1; options->fwd_opts.streamlocal_bind_mask = (mode_t)-1; options->fwd_opts.streamlocal_bind_unlink = -1; options->pubkey_authentication = -1; options->gss_authentication = -1; options->gss_deleg_creds = -1; options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->kbd_interactive_devices = NULL; options->hostbased_authentication = -1; options->batch_mode = -1; options->check_host_ip = -1; options->strict_host_key_checking = -1; options->compression = -1; options->tcp_keep_alive = -1; options->port = -1; options->address_family = -1; options->connection_attempts = -1; options->connection_timeout = -1; options->number_of_password_prompts = -1; options->ciphers = NULL; options->macs = NULL; options->kex_algorithms = NULL; options->hostkeyalgorithms = NULL; options->ca_sign_algorithms = NULL; options->num_identity_files = 0; memset(options->identity_keys, 0, sizeof(options->identity_keys)); options->num_certificate_files = 0; memset(options->certificates, 0, sizeof(options->certificates)); options->hostname = NULL; options->host_key_alias = NULL; options->proxy_command = NULL; options->jump_user = NULL; options->jump_host = NULL; options->jump_port = -1; options->jump_extra = NULL; options->user = NULL; options->escape_char = -1; options->num_system_hostfiles = 0; options->num_user_hostfiles = 0; options->local_forwards = NULL; options->num_local_forwards = 0; options->remote_forwards = NULL; options->num_remote_forwards = 0; options->permitted_remote_opens = NULL; options->num_permitted_remote_opens = 0; options->log_facility = SYSLOG_FACILITY_NOT_SET; options->log_level = SYSLOG_LEVEL_NOT_SET; options->num_log_verbose = 0; options->log_verbose = NULL; options->preferred_authentications = NULL; options->bind_address = NULL; options->bind_interface = NULL; options->pkcs11_provider = NULL; options->sk_provider = NULL; options->enable_ssh_keysign = - 1; options->no_host_authentication_for_localhost = - 1; options->identities_only = - 1; options->rekey_limit = - 1; options->rekey_interval = -1; options->verify_host_key_dns = -1; options->server_alive_interval = -1; options->server_alive_count_max = -1; options->send_env = NULL; options->num_send_env = 0; options->setenv = NULL; options->num_setenv = 0; options->control_path = NULL; options->control_master = -1; options->control_persist = -1; options->control_persist_timeout = 0; options->hash_known_hosts = -1; options->tun_open = -1; options->tun_local = -1; options->tun_remote = -1; options->local_command = NULL; options->permit_local_command = -1; options->remote_command = NULL; options->add_keys_to_agent = -1; options->add_keys_to_agent_lifespan = -1; options->identity_agent = NULL; options->visual_host_key = -1; options->ip_qos_interactive = -1; options->ip_qos_bulk = -1; options->request_tty = -1; options->session_type = -1; options->stdin_null = -1; options->fork_after_authentication = -1; options->proxy_use_fdpass = -1; options->ignored_unknown = NULL; options->num_canonical_domains = 0; options->num_permitted_cnames = 0; options->canonicalize_max_dots = -1; options->canonicalize_fallback_local = -1; options->canonicalize_hostname = -1; options->revoked_host_keys = NULL; options->fingerprint_hash = -1; options->update_hostkeys = -1; options->hostbased_accepted_algos = NULL; options->pubkey_accepted_algos = NULL; options->known_hosts_command = NULL; } /* * 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. */ int fill_default_options(Options * options) { char *all_cipher, *all_mac, *all_kex, *all_key, *all_sig; char *def_cipher, *def_mac, *def_kex, *def_key, *def_sig; int ret = 0, r; if (options->forward_agent == -1) options->forward_agent = 0; if (options->forward_x11 == -1) options->forward_x11 = 0; if (options->forward_x11_trusted == -1) options->forward_x11_trusted = 0; if (options->forward_x11_timeout == -1) options->forward_x11_timeout = 1200; /* * stdio forwarding (-W) changes the default for these but we defer * setting the values so they can be overridden. */ if (options->exit_on_forward_failure == -1) options->exit_on_forward_failure = options->stdio_forward_host != NULL ? 1 : 0; if (options->clear_forwardings == -1) options->clear_forwardings = options->stdio_forward_host != NULL ? 1 : 0; if (options->clear_forwardings == 1) clear_forwardings(options); if (options->xauth_location == NULL) options->xauth_location = xstrdup(_PATH_XAUTH); if (options->fwd_opts.gateway_ports == -1) options->fwd_opts.gateway_ports = 0; if (options->fwd_opts.streamlocal_bind_mask == (mode_t)-1) options->fwd_opts.streamlocal_bind_mask = 0177; if (options->fwd_opts.streamlocal_bind_unlink == -1) options->fwd_opts.streamlocal_bind_unlink = 0; if (options->pubkey_authentication == -1) options->pubkey_authentication = SSH_PUBKEY_AUTH_ALL; if (options->gss_authentication == -1) options->gss_authentication = 0; if (options->gss_deleg_creds == -1) options->gss_deleg_creds = 0; if (options->password_authentication == -1) options->password_authentication = 1; if (options->kbd_interactive_authentication == -1) options->kbd_interactive_authentication = 1; if (options->hostbased_authentication == -1) options->hostbased_authentication = 0; if (options->batch_mode == -1) options->batch_mode = 0; if (options->check_host_ip == -1) options->check_host_ip = 0; if (options->strict_host_key_checking == -1) options->strict_host_key_checking = SSH_STRICT_HOSTKEY_ASK; if (options->compression == -1) options->compression = 0; if (options->tcp_keep_alive == -1) options->tcp_keep_alive = 1; if (options->port == -1) options->port = 0; /* Filled in ssh_connect. */ if (options->address_family == -1) options->address_family = AF_UNSPEC; if (options->connection_attempts == -1) options->connection_attempts = 1; if (options->number_of_password_prompts == -1) options->number_of_password_prompts = 3; /* options->hostkeyalgorithms, default set in myproposals.h */ if (options->add_keys_to_agent == -1) { options->add_keys_to_agent = 0; options->add_keys_to_agent_lifespan = 0; } if (options->num_identity_files == 0) { add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_RSA, 0); #ifdef OPENSSL_HAS_ECC add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_ECDSA, 0); add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_ECDSA_SK, 0); #endif add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_ED25519, 0); add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_ED25519_SK, 0); add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_XMSS, 0); add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_DSA, 0); } if (options->escape_char == -1) options->escape_char = '~'; if (options->num_system_hostfiles == 0) { options->system_hostfiles[options->num_system_hostfiles++] = xstrdup(_PATH_SSH_SYSTEM_HOSTFILE); options->system_hostfiles[options->num_system_hostfiles++] = xstrdup(_PATH_SSH_SYSTEM_HOSTFILE2); } if (options->update_hostkeys == -1) { if (options->verify_host_key_dns <= 0 && (options->num_user_hostfiles == 0 || (options->num_user_hostfiles == 1 && strcmp(options-> user_hostfiles[0], _PATH_SSH_USER_HOSTFILE) == 0))) options->update_hostkeys = SSH_UPDATE_HOSTKEYS_YES; else options->update_hostkeys = SSH_UPDATE_HOSTKEYS_NO; } if (options->num_user_hostfiles == 0) { options->user_hostfiles[options->num_user_hostfiles++] = xstrdup(_PATH_SSH_USER_HOSTFILE); options->user_hostfiles[options->num_user_hostfiles++] = xstrdup(_PATH_SSH_USER_HOSTFILE2); } if (options->log_level == SYSLOG_LEVEL_NOT_SET) options->log_level = SYSLOG_LEVEL_INFO; if (options->log_facility == SYSLOG_FACILITY_NOT_SET) options->log_facility = SYSLOG_FACILITY_USER; if (options->no_host_authentication_for_localhost == - 1) options->no_host_authentication_for_localhost = 0; if (options->identities_only == -1) options->identities_only = 0; if (options->enable_ssh_keysign == -1) options->enable_ssh_keysign = 0; if (options->rekey_limit == -1) options->rekey_limit = 0; if (options->rekey_interval == -1) options->rekey_interval = 0; #if HAVE_LDNS if (options->verify_host_key_dns == -1) /* automatically trust a verified SSHFP record */ options->verify_host_key_dns = 1; #else if (options->verify_host_key_dns == -1) options->verify_host_key_dns = 0; #endif if (options->server_alive_interval == -1) options->server_alive_interval = 0; if (options->server_alive_count_max == -1) options->server_alive_count_max = 3; if (options->control_master == -1) options->control_master = 0; if (options->control_persist == -1) { options->control_persist = 0; options->control_persist_timeout = 0; } if (options->hash_known_hosts == -1) options->hash_known_hosts = 0; if (options->tun_open == -1) options->tun_open = SSH_TUNMODE_NO; if (options->tun_local == -1) options->tun_local = SSH_TUNID_ANY; if (options->tun_remote == -1) options->tun_remote = SSH_TUNID_ANY; if (options->permit_local_command == -1) options->permit_local_command = 0; if (options->visual_host_key == -1) options->visual_host_key = 0; if (options->ip_qos_interactive == -1) options->ip_qos_interactive = IPTOS_DSCP_AF21; if (options->ip_qos_bulk == -1) options->ip_qos_bulk = IPTOS_DSCP_CS1; if (options->request_tty == -1) options->request_tty = REQUEST_TTY_AUTO; if (options->session_type == -1) options->session_type = SESSION_TYPE_DEFAULT; if (options->stdin_null == -1) options->stdin_null = 0; if (options->fork_after_authentication == -1) options->fork_after_authentication = 0; if (options->proxy_use_fdpass == -1) options->proxy_use_fdpass = 0; 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; if (options->fingerprint_hash == -1) options->fingerprint_hash = SSH_FP_HASH_DEFAULT; #ifdef ENABLE_SK_INTERNAL if (options->sk_provider == NULL) options->sk_provider = xstrdup("internal"); #else if (options->sk_provider == NULL) options->sk_provider = xstrdup("$SSH_SK_PROVIDER"); #endif /* Expand KEX name lists */ all_cipher = cipher_alg_list(',', 0); all_mac = mac_alg_list(','); all_kex = kex_alg_list(','); all_key = sshkey_alg_list(0, 0, 1, ','); all_sig = sshkey_alg_list(0, 1, 1, ','); /* remove unsupported algos from default lists */ def_cipher = match_filter_allowlist(KEX_CLIENT_ENCRYPT, all_cipher); def_mac = match_filter_allowlist(KEX_CLIENT_MAC, all_mac); def_kex = match_filter_allowlist(KEX_CLIENT_KEX, all_kex); def_key = match_filter_allowlist(KEX_DEFAULT_PK_ALG, all_key); def_sig = match_filter_allowlist(SSH_ALLOWED_CA_SIGALGS, all_sig); #define ASSEMBLE(what, defaults, all) \ do { \ if ((r = kex_assemble_names(&options->what, \ defaults, all)) != 0) { \ error_fr(r, "%s", #what); \ goto fail; \ } \ } while (0) ASSEMBLE(ciphers, def_cipher, all_cipher); ASSEMBLE(macs, def_mac, all_mac); ASSEMBLE(kex_algorithms, def_kex, all_kex); ASSEMBLE(hostbased_accepted_algos, def_key, all_key); ASSEMBLE(pubkey_accepted_algos, def_key, all_key); ASSEMBLE(ca_sign_algorithms, def_sig, all_sig); #undef ASSEMBLE #define CLEAR_ON_NONE(v) \ do { \ if (option_clear_or_none(v)) { \ free(v); \ v = NULL; \ } \ } while(0) CLEAR_ON_NONE(options->local_command); CLEAR_ON_NONE(options->remote_command); CLEAR_ON_NONE(options->proxy_command); CLEAR_ON_NONE(options->control_path); CLEAR_ON_NONE(options->revoked_host_keys); CLEAR_ON_NONE(options->pkcs11_provider); CLEAR_ON_NONE(options->sk_provider); CLEAR_ON_NONE(options->known_hosts_command); if (options->jump_host != NULL && strcmp(options->jump_host, "none") == 0 && options->jump_port == 0 && options->jump_user == NULL) { free(options->jump_host); options->jump_host = NULL; } if (options->num_permitted_cnames == 1 && !config_has_permitted_cnames(options)) { /* clean up CanonicalizePermittedCNAMEs=none */ free(options->permitted_cnames[0].source_list); free(options->permitted_cnames[0].target_list); memset(options->permitted_cnames, '\0', sizeof(*options->permitted_cnames)); options->num_permitted_cnames = 0; } /* options->identity_agent distinguishes NULL from 'none' */ /* options->user will be set in the main program if appropriate */ /* options->hostname will be set in the main program if appropriate */ /* options->host_key_alias should not be set by default */ /* options->preferred_authentications will be set in ssh */ - if (options->version_addendum == NULL) - options->version_addendum = xstrdup(SSH_VERSION_FREEBSD); /* success */ ret = 0; fail: free(all_cipher); free(all_mac); free(all_kex); free(all_key); free(all_sig); free(def_cipher); free(def_mac); free(def_kex); free(def_key); free(def_sig); return ret; } void free_options(Options *o) { int i; if (o == NULL) return; #define FREE_ARRAY(type, n, a) \ do { \ type _i; \ for (_i = 0; _i < (n); _i++) \ free((a)[_i]); \ } while (0) free(o->forward_agent_sock_path); free(o->xauth_location); FREE_ARRAY(u_int, o->num_log_verbose, o->log_verbose); free(o->log_verbose); free(o->ciphers); free(o->macs); free(o->hostkeyalgorithms); free(o->kex_algorithms); free(o->ca_sign_algorithms); free(o->hostname); free(o->host_key_alias); free(o->proxy_command); free(o->user); FREE_ARRAY(u_int, o->num_system_hostfiles, o->system_hostfiles); FREE_ARRAY(u_int, o->num_user_hostfiles, o->user_hostfiles); free(o->preferred_authentications); free(o->bind_address); free(o->bind_interface); free(o->pkcs11_provider); free(o->sk_provider); for (i = 0; i < o->num_identity_files; i++) { free(o->identity_files[i]); sshkey_free(o->identity_keys[i]); } for (i = 0; i < o->num_certificate_files; i++) { free(o->certificate_files[i]); sshkey_free(o->certificates[i]); } free(o->identity_agent); for (i = 0; i < o->num_local_forwards; i++) { free(o->local_forwards[i].listen_host); free(o->local_forwards[i].listen_path); free(o->local_forwards[i].connect_host); free(o->local_forwards[i].connect_path); } free(o->local_forwards); for (i = 0; i < o->num_remote_forwards; i++) { free(o->remote_forwards[i].listen_host); free(o->remote_forwards[i].listen_path); free(o->remote_forwards[i].connect_host); free(o->remote_forwards[i].connect_path); } free(o->remote_forwards); free(o->stdio_forward_host); FREE_ARRAY(int, o->num_send_env, o->send_env); free(o->send_env); FREE_ARRAY(int, o->num_setenv, o->setenv); free(o->setenv); free(o->control_path); free(o->local_command); free(o->remote_command); FREE_ARRAY(int, o->num_canonical_domains, o->canonical_domains); for (i = 0; i < o->num_permitted_cnames; i++) { free(o->permitted_cnames[i].source_list); free(o->permitted_cnames[i].target_list); } free(o->revoked_host_keys); free(o->hostbased_accepted_algos); free(o->pubkey_accepted_algos); free(o->jump_user); free(o->jump_host); free(o->jump_extra); free(o->ignored_unknown); explicit_bzero(o, sizeof(*o)); #undef FREE_ARRAY } struct fwdarg { char *arg; int ispath; }; /* * parse_fwd_field * parses the next field in a port forwarding specification. * sets fwd to the parsed field and advances p past the colon * or sets it to NULL at end of string. * returns 0 on success, else non-zero. */ static int parse_fwd_field(char **p, struct fwdarg *fwd) { char *ep, *cp = *p; int ispath = 0; if (*cp == '\0') { *p = NULL; return -1; /* end of string */ } /* * A field escaped with square brackets is used literally. * XXX - allow ']' to be escaped via backslash? */ if (*cp == '[') { /* find matching ']' */ for (ep = cp + 1; *ep != ']' && *ep != '\0'; ep++) { if (*ep == '/') ispath = 1; } /* no matching ']' or not at end of field. */ if (ep[0] != ']' || (ep[1] != ':' && ep[1] != '\0')) return -1; /* NUL terminate the field and advance p past the colon */ *ep++ = '\0'; if (*ep != '\0') *ep++ = '\0'; fwd->arg = cp + 1; fwd->ispath = ispath; *p = ep; return 0; } for (cp = *p; *cp != '\0'; cp++) { switch (*cp) { case '\\': memmove(cp, cp + 1, strlen(cp + 1) + 1); if (*cp == '\0') return -1; break; case '/': ispath = 1; break; case ':': *cp++ = '\0'; goto done; } } done: fwd->arg = *p; fwd->ispath = ispath; *p = cp; return 0; } /* * parse_forward * parses a string containing a port forwarding specification of the form: * dynamicfwd == 0 * [listenhost:]listenport|listenpath:connecthost:connectport|connectpath * listenpath:connectpath * dynamicfwd == 1 * [listenhost:]listenport * returns number of arguments parsed or zero on error */ int parse_forward(struct Forward *fwd, const char *fwdspec, int dynamicfwd, int remotefwd) { struct fwdarg fwdargs[4]; char *p, *cp; int i, err; memset(fwd, 0, sizeof(*fwd)); memset(fwdargs, 0, sizeof(fwdargs)); /* * We expand environment variables before checking if we think they're * paths so that if ${VAR} expands to a fully qualified path it is * treated as a path. */ cp = p = dollar_expand(&err, fwdspec); if (p == NULL || err) return 0; /* skip leading spaces */ while (isspace((u_char)*cp)) cp++; for (i = 0; i < 4; ++i) { if (parse_fwd_field(&cp, &fwdargs[i]) != 0) break; } /* Check for trailing garbage */ if (cp != NULL && *cp != '\0') { i = 0; /* failure */ } switch (i) { case 1: if (fwdargs[0].ispath) { fwd->listen_path = xstrdup(fwdargs[0].arg); fwd->listen_port = PORT_STREAMLOCAL; } else { fwd->listen_host = NULL; fwd->listen_port = a2port(fwdargs[0].arg); } fwd->connect_host = xstrdup("socks"); break; case 2: if (fwdargs[0].ispath && fwdargs[1].ispath) { fwd->listen_path = xstrdup(fwdargs[0].arg); fwd->listen_port = PORT_STREAMLOCAL; fwd->connect_path = xstrdup(fwdargs[1].arg); fwd->connect_port = PORT_STREAMLOCAL; } else if (fwdargs[1].ispath) { fwd->listen_host = NULL; fwd->listen_port = a2port(fwdargs[0].arg); fwd->connect_path = xstrdup(fwdargs[1].arg); fwd->connect_port = PORT_STREAMLOCAL; } else { fwd->listen_host = xstrdup(fwdargs[0].arg); fwd->listen_port = a2port(fwdargs[1].arg); fwd->connect_host = xstrdup("socks"); } break; case 3: if (fwdargs[0].ispath) { fwd->listen_path = xstrdup(fwdargs[0].arg); fwd->listen_port = PORT_STREAMLOCAL; fwd->connect_host = xstrdup(fwdargs[1].arg); fwd->connect_port = a2port(fwdargs[2].arg); } else if (fwdargs[2].ispath) { fwd->listen_host = xstrdup(fwdargs[0].arg); fwd->listen_port = a2port(fwdargs[1].arg); fwd->connect_path = xstrdup(fwdargs[2].arg); fwd->connect_port = PORT_STREAMLOCAL; } else { fwd->listen_host = NULL; fwd->listen_port = a2port(fwdargs[0].arg); fwd->connect_host = xstrdup(fwdargs[1].arg); fwd->connect_port = a2port(fwdargs[2].arg); } break; case 4: fwd->listen_host = xstrdup(fwdargs[0].arg); fwd->listen_port = a2port(fwdargs[1].arg); fwd->connect_host = xstrdup(fwdargs[2].arg); fwd->connect_port = a2port(fwdargs[3].arg); break; default: i = 0; /* failure */ } free(p); if (dynamicfwd) { if (!(i == 1 || i == 2)) goto fail_free; } else { if (!(i == 3 || i == 4)) { if (fwd->connect_path == NULL && fwd->listen_path == NULL) goto fail_free; } if (fwd->connect_port <= 0 && fwd->connect_path == NULL) goto fail_free; } if ((fwd->listen_port < 0 && fwd->listen_path == NULL) || (!remotefwd && fwd->listen_port == 0)) goto fail_free; if (fwd->connect_host != NULL && strlen(fwd->connect_host) >= NI_MAXHOST) goto fail_free; /* * XXX - if connecting to a remote socket, max sun len may not * match this host */ if (fwd->connect_path != NULL && strlen(fwd->connect_path) >= PATH_MAX_SUN) goto fail_free; if (fwd->listen_host != NULL && strlen(fwd->listen_host) >= NI_MAXHOST) goto fail_free; if (fwd->listen_path != NULL && strlen(fwd->listen_path) >= PATH_MAX_SUN) goto fail_free; return (i); fail_free: free(fwd->connect_host); fwd->connect_host = NULL; free(fwd->connect_path); fwd->connect_path = NULL; free(fwd->listen_host); fwd->listen_host = NULL; free(fwd->listen_path); fwd->listen_path = NULL; return (0); } int parse_jump(const char *s, Options *o, int active) { char *orig, *sdup, *cp; char *host = NULL, *user = NULL; int r, ret = -1, port = -1, first; active &= o->proxy_command == NULL && o->jump_host == NULL; orig = sdup = xstrdup(s); /* Remove comment and trailing whitespace */ if ((cp = strchr(orig, '#')) != NULL) *cp = '\0'; rtrim(orig); first = active; do { if (strcasecmp(s, "none") == 0) break; if ((cp = strrchr(sdup, ',')) == NULL) cp = sdup; /* last */ else *cp++ = '\0'; if (first) { /* First argument and configuration is active */ r = parse_ssh_uri(cp, &user, &host, &port); if (r == -1 || (r == 1 && parse_user_host_port(cp, &user, &host, &port) != 0)) goto out; } else { /* Subsequent argument or inactive configuration */ r = parse_ssh_uri(cp, NULL, NULL, NULL); if (r == -1 || (r == 1 && parse_user_host_port(cp, NULL, NULL, NULL) != 0)) goto out; } first = 0; /* only check syntax for subsequent hosts */ } while (cp != sdup); /* success */ if (active) { if (strcasecmp(s, "none") == 0) { o->jump_host = xstrdup("none"); o->jump_port = 0; } else { o->jump_user = user; o->jump_host = host; o->jump_port = port; o->proxy_command = xstrdup("none"); user = host = NULL; if ((cp = strrchr(s, ',')) != NULL && cp != s) { o->jump_extra = xstrdup(s); o->jump_extra[cp - s] = '\0'; } } } ret = 0; out: free(orig); free(user); free(host); return ret; } int parse_ssh_uri(const char *uri, char **userp, char **hostp, int *portp) { char *user = NULL, *host = NULL, *path = NULL; int r, port; r = parse_uri("ssh", uri, &user, &host, &port, &path); if (r == 0 && path != NULL) r = -1; /* path not allowed */ if (r == 0) { if (userp != NULL) { *userp = user; user = NULL; } if (hostp != NULL) { *hostp = host; host = NULL; } if (portp != NULL) *portp = port; } free(user); free(host); free(path); return r; } /* 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 oUpdateHostkeys: return fmt_multistate_int(val, multistate_yesnoask); case oStrictHostKeyChecking: return fmt_multistate_int(val, multistate_strict_hostkey); 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 oSessionType: return fmt_multistate_int(val, multistate_sessiontype); case oCanonicalizeHostname: return fmt_multistate_int(val, multistate_canonicalizehostname); case oAddKeysToAgent: return fmt_multistate_int(val, multistate_yesnoaskconfirm); case oPubkeyAuthentication: return fmt_multistate_int(val, multistate_pubkey_auth); case oFingerprintHash: return ssh_digest_alg_name(val); 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)); if (count == 0) printf(" none"); 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 && fwd->connect_host != NULL && strcmp(fwd->connect_host, "socks") != 0) continue; if (code == oLocalForward && fwd->connect_host != NULL && 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, r; char buf[8], *all_key; /* * Expand HostKeyAlgorithms name lists. This isn't handled in * fill_default_options() like the other algorithm lists because * the host key algorithms are by default dynamically chosen based * on the host's keys found in known_hosts. */ all_key = sshkey_alg_list(0, 0, 1, ','); if ((r = kex_assemble_names(&o->hostkeyalgorithms, kex_default_pk_alg(), all_key)) != 0) fatal_fr(r, "expand HostKeyAlgorithms"); free(all_key); /* 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(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(oClearAllForwardings, o->clear_forwardings); dump_cfg_fmtint(oExitOnForwardFailure, o->exit_on_forward_failure); dump_cfg_fmtint(oFingerprintHash, o->fingerprint_hash); 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(oProxyUseFdpass, o->proxy_use_fdpass); dump_cfg_fmtint(oPubkeyAuthentication, o->pubkey_authentication); dump_cfg_fmtint(oRequestTTY, o->request_tty); dump_cfg_fmtint(oSessionType, o->session_type); dump_cfg_fmtint(oStdinNull, o->stdin_null); dump_cfg_fmtint(oForkAfterAuthentication, o->fork_after_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(oVerifyHostKeyDNS, o->verify_host_key_dns); dump_cfg_fmtint(oVisualHostKey, o->visual_host_key); dump_cfg_fmtint(oUpdateHostkeys, o->update_hostkeys); /* Integer options */ dump_cfg_int(oCanonicalizeMaxDots, o->canonicalize_max_dots); 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(oBindInterface, o->bind_interface); dump_cfg_string(oCiphers, o->ciphers); dump_cfg_string(oControlPath, o->control_path); dump_cfg_string(oHostKeyAlgorithms, o->hostkeyalgorithms); dump_cfg_string(oHostKeyAlias, o->host_key_alias); dump_cfg_string(oHostbasedAcceptedAlgorithms, o->hostbased_accepted_algos); dump_cfg_string(oIdentityAgent, o->identity_agent); dump_cfg_string(oIgnoreUnknown, o->ignored_unknown); dump_cfg_string(oKbdInteractiveDevices, o->kbd_interactive_devices); dump_cfg_string(oKexAlgorithms, o->kex_algorithms); dump_cfg_string(oCASignatureAlgorithms, o->ca_sign_algorithms); dump_cfg_string(oLocalCommand, o->local_command); dump_cfg_string(oRemoteCommand, o->remote_command); dump_cfg_string(oLogLevel, log_level_name(o->log_level)); dump_cfg_string(oMacs, o->macs); #ifdef ENABLE_PKCS11 dump_cfg_string(oPKCS11Provider, o->pkcs11_provider); #endif dump_cfg_string(oSecurityKeyProvider, o->sk_provider); dump_cfg_string(oPreferredAuthentications, o->preferred_authentications); dump_cfg_string(oPubkeyAcceptedAlgorithms, o->pubkey_accepted_algos); dump_cfg_string(oRevokedHostKeys, o->revoked_host_keys); dump_cfg_string(oXAuthLocation, o->xauth_location); dump_cfg_string(oKnownHostsCommand, o->known_hosts_command); /* Forwards */ 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(oCertificateFile, o->num_certificate_files, o->certificate_files); 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); dump_cfg_strarray(oSetEnv, o->num_setenv, o->setenv); dump_cfg_strarray_oneline(oLogVerbose, o->num_log_verbose, o->log_verbose); /* Special cases */ /* PermitRemoteOpen */ if (o->num_permitted_remote_opens == 0) printf("%s any\n", lookup_opcode_name(oPermitRemoteOpen)); else dump_cfg_strarray_oneline(oPermitRemoteOpen, o->num_permitted_remote_opens, o->permitted_remote_opens); /* AddKeysToAgent */ if (o->add_keys_to_agent_lifespan <= 0) dump_cfg_fmtint(oAddKeysToAgent, o->add_keys_to_agent); else { printf("addkeystoagent%s %d\n", o->add_keys_to_agent == 3 ? " confirm" : "", o->add_keys_to_agent_lifespan); } /* oForwardAgent */ if (o->forward_agent_sock_path == NULL) dump_cfg_fmtint(oForwardAgent, o->forward_agent); else dump_cfg_string(oForwardAgent, o->forward_agent_sock_path); /* 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 */ printf("canonicalizePermittedcnames"); if (o->num_permitted_cnames == 0) printf(" none"); 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"); /* 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(buf, o->escape_char, VIS_WHITE, 0); printf("escapechar %s\n", buf); } /* oIPQoS */ printf("ipqos %s ", iptos2str(o->ip_qos_interactive)); printf("%s\n", iptos2str(o->ip_qos_bulk)); /* oRekeyLimit */ printf("rekeylimit %llu %d\n", (unsigned long long)o->rekey_limit, o->rekey_interval); /* oStreamLocalBindMask */ printf("streamlocalbindmask 0%o\n", o->fwd_opts.streamlocal_bind_mask); /* oLogFacility */ printf("syslogfacility %s\n", log_facility_name(o->log_facility)); /* oProxyCommand / oProxyJump */ if (o->jump_host == NULL) dump_cfg_string(oProxyCommand, o->proxy_command); else { /* Check for numeric addresses */ i = strchr(o->jump_host, ':') != NULL || strspn(o->jump_host, "1234567890.") == strlen(o->jump_host); snprintf(buf, sizeof(buf), "%d", o->jump_port); printf("proxyjump %s%s%s%s%s%s%s%s%s\n", /* optional additional jump spec */ o->jump_extra == NULL ? "" : o->jump_extra, o->jump_extra == NULL ? "" : ",", /* optional user */ o->jump_user == NULL ? "" : o->jump_user, o->jump_user == NULL ? "" : "@", /* opening [ if hostname is numeric */ i ? "[" : "", /* mandatory hostname */ o->jump_host, /* closing ] if hostname is numeric */ i ? "]" : "", /* optional port number */ o->jump_port <= 0 ? "" : ":", o->jump_port <= 0 ? "" : buf); } } diff --git a/crypto/openssh/readconf.h b/crypto/openssh/readconf.h index 31f4acb47084..ded13c943d3f 100644 --- a/crypto/openssh/readconf.h +++ b/crypto/openssh/readconf.h @@ -1,246 +1,244 @@ /* $OpenBSD: readconf.h,v 1.146 2021/12/19 22:14:47 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Functions for reading the configuration file. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #ifndef READCONF_H #define READCONF_H /* Data structure for representing option data. */ #define SSH_MAX_HOSTS_FILES 32 #define MAX_CANON_DOMAINS 32 #define PATH_MAX_SUN (sizeof((struct sockaddr_un *)0)->sun_path) struct allowed_cname { char *source_list; char *target_list; }; typedef struct { int forward_agent; /* Forward authentication agent. */ char *forward_agent_sock_path; /* Optional path of the agent. */ int forward_x11; /* Forward X11 display. */ int forward_x11_timeout; /* Expiration for Cookies */ int forward_x11_trusted; /* Trust Forward X11 display. */ int exit_on_forward_failure; /* Exit if bind(2) fails for -L/-R */ char *xauth_location; /* Location for xauth program */ struct ForwardOptions fwd_opts; /* forwarding options */ int pubkey_authentication; /* Try ssh2 pubkey authentication. */ int hostbased_authentication; /* ssh2's rhosts_rsa */ int gss_authentication; /* Try GSS authentication */ int gss_deleg_creds; /* Delegate GSS credentials */ int password_authentication; /* Try password * authentication. */ int kbd_interactive_authentication; /* Try keyboard-interactive auth. */ char *kbd_interactive_devices; /* Keyboard-interactive auth devices. */ int batch_mode; /* Batch mode: do not ask for passwords. */ int check_host_ip; /* Also keep track of keys for IP address */ int strict_host_key_checking; /* Strict host key checking. */ int compression; /* Compress packets in both directions. */ int tcp_keep_alive; /* Set SO_KEEPALIVE. */ int ip_qos_interactive; /* IP ToS/DSCP/class for interactive */ int ip_qos_bulk; /* IP ToS/DSCP/class for bulk traffic */ SyslogFacility log_facility; /* Facility for system logging. */ LogLevel log_level; /* Level for logging. */ u_int num_log_verbose; /* Verbose log overrides */ char **log_verbose; int port; /* Port to connect. */ int address_family; int connection_attempts; /* Max attempts (seconds) before * giving up */ int connection_timeout; /* Max time (seconds) before * aborting connection attempt */ int number_of_password_prompts; /* Max number of password * prompts. */ char *ciphers; /* SSH2 ciphers in order of preference. */ char *macs; /* SSH2 macs in order of preference. */ char *hostkeyalgorithms; /* SSH2 server key types in order of preference. */ char *kex_algorithms; /* SSH2 kex methods in order of preference. */ char *ca_sign_algorithms; /* Allowed CA signature algorithms */ char *hostname; /* Real host to connect. */ char *host_key_alias; /* hostname alias for .ssh/known_hosts */ char *proxy_command; /* Proxy command for connecting the host. */ char *user; /* User to log in as. */ int escape_char; /* Escape character; -2 = none */ u_int num_system_hostfiles; /* Paths for /etc/ssh/ssh_known_hosts */ char *system_hostfiles[SSH_MAX_HOSTS_FILES]; u_int num_user_hostfiles; /* Path for $HOME/.ssh/known_hosts */ char *user_hostfiles[SSH_MAX_HOSTS_FILES]; char *preferred_authentications; char *bind_address; /* local socket address for connection to sshd */ char *bind_interface; /* local interface for bind address */ char *pkcs11_provider; /* PKCS#11 provider */ char *sk_provider; /* Security key provider */ int verify_host_key_dns; /* Verify host key using DNS */ int num_identity_files; /* Number of files for RSA/DSA identities. */ char *identity_files[SSH_MAX_IDENTITY_FILES]; int identity_file_userprovided[SSH_MAX_IDENTITY_FILES]; struct sshkey *identity_keys[SSH_MAX_IDENTITY_FILES]; int num_certificate_files; /* Number of extra certificates for ssh. */ char *certificate_files[SSH_MAX_CERTIFICATE_FILES]; int certificate_file_userprovided[SSH_MAX_CERTIFICATE_FILES]; struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES]; int add_keys_to_agent; int add_keys_to_agent_lifespan; char *identity_agent; /* Optional path to ssh-agent socket */ /* Local TCP/IP forward requests. */ int num_local_forwards; struct Forward *local_forwards; /* Remote TCP/IP forward requests. */ int num_remote_forwards; struct Forward *remote_forwards; int clear_forwardings; /* Restrict remote dynamic forwarding */ char **permitted_remote_opens; u_int num_permitted_remote_opens; /* stdio forwarding (-W) host and port */ char *stdio_forward_host; int stdio_forward_port; int enable_ssh_keysign; int64_t rekey_limit; int rekey_interval; int no_host_authentication_for_localhost; int identities_only; int server_alive_interval; int server_alive_count_max; int num_send_env; char **send_env; int num_setenv; char **setenv; char *control_path; int control_master; int control_persist; /* ControlPersist flag */ int control_persist_timeout; /* ControlPersist timeout (seconds) */ int hash_known_hosts; int tun_open; /* tun(4) */ int tun_local; /* force tun device (optional) */ int tun_remote; /* force tun device (optional) */ char *local_command; int permit_local_command; char *remote_command; int visual_host_key; int request_tty; int session_type; int stdin_null; int fork_after_authentication; int proxy_use_fdpass; int num_canonical_domains; char *canonical_domains[MAX_CANON_DOMAINS]; int canonicalize_hostname; int canonicalize_max_dots; int canonicalize_fallback_local; int num_permitted_cnames; struct allowed_cname permitted_cnames[MAX_CANON_DOMAINS]; char *revoked_host_keys; int fingerprint_hash; int update_hostkeys; /* one of SSH_UPDATE_HOSTKEYS_* */ char *hostbased_accepted_algos; char *pubkey_accepted_algos; - char *version_addendum; /* Appended to SSH banner */ - char *jump_user; char *jump_host; int jump_port; char *jump_extra; char *known_hosts_command; char *ignored_unknown; /* Pattern list of unknown tokens to ignore */ } Options; #define SSH_PUBKEY_AUTH_NO 0x00 #define SSH_PUBKEY_AUTH_UNBOUND 0x01 #define SSH_PUBKEY_AUTH_HBOUND 0x02 #define SSH_PUBKEY_AUTH_ALL 0x03 #define SSH_CANONICALISE_NO 0 #define SSH_CANONICALISE_YES 1 #define SSH_CANONICALISE_ALWAYS 2 #define SSHCTL_MASTER_NO 0 #define SSHCTL_MASTER_YES 1 #define SSHCTL_MASTER_AUTO 2 #define SSHCTL_MASTER_ASK 3 #define SSHCTL_MASTER_AUTO_ASK 4 #define REQUEST_TTY_AUTO 0 #define REQUEST_TTY_NO 1 #define REQUEST_TTY_YES 2 #define REQUEST_TTY_FORCE 3 #define SESSION_TYPE_NONE 0 #define SESSION_TYPE_SUBSYSTEM 1 #define SESSION_TYPE_DEFAULT 2 #define SSHCONF_CHECKPERM 1 /* check permissions on config file */ #define SSHCONF_USERCONF 2 /* user provided config file not system */ #define SSHCONF_FINAL 4 /* Final pass over config, after canon. */ #define SSHCONF_NEVERMATCH 8 /* Match/Host never matches; internal only */ #define SSH_UPDATE_HOSTKEYS_NO 0 #define SSH_UPDATE_HOSTKEYS_YES 1 #define SSH_UPDATE_HOSTKEYS_ASK 2 #define SSH_STRICT_HOSTKEY_OFF 0 #define SSH_STRICT_HOSTKEY_NEW 1 #define SSH_STRICT_HOSTKEY_YES 2 #define SSH_STRICT_HOSTKEY_ASK 3 const char *kex_default_pk_alg(void); char *ssh_connection_hash(const char *thishost, const char *host, const char *portstr, const char *user); void initialize_options(Options *); int fill_default_options(Options *); void fill_default_options_for_canonicalization(Options *); void free_options(Options *o); 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 *, const char *, Options *, int, int *); int parse_forward(struct Forward *, const char *, int, int); int parse_jump(const char *, Options *, int); int parse_ssh_uri(const char *, char **, char **, int *); int default_ssh_port(void); int option_clear_or_none(const char *); int config_has_permitted_cnames(Options *); 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 *); void add_identity_file(Options *, const char *, const char *, int); void add_certificate_file(Options *, const char *, int); #endif /* READCONF_H */ diff --git a/crypto/openssh/ssh.1 b/crypto/openssh/ssh.1 index be43e87134b2..4a4f1683a82a 100644 --- a/crypto/openssh/ssh.1 +++ b/crypto/openssh/ssh.1 @@ -1,1779 +1,1778 @@ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland .\" All rights reserved .\" .\" As far as I am concerned, the code I have written for this software .\" can be used freely for any purpose. Any derived versions of this .\" software must be clearly marked as such, and if the derived work is .\" incompatible with the protocol description in the RFC file, it must be .\" called by a name other than "ssh" or "Secure Shell". .\" .\" Copyright (c) 1999,2000 Markus Friedl. All rights reserved. .\" Copyright (c) 1999 Aaron Campbell. All rights reserved. .\" Copyright (c) 1999 Theo de Raadt. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR .\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES .\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, .\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT .\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (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.430 2022/03/31 17:27:27 naddy Exp $ .Dd $Mdocdate: March 31 2022 $ .Dt SSH 1 .Os .Sh NAME .Nm ssh .Nd OpenSSH remote login client .Sh SYNOPSIS .Nm ssh .Op Fl 46AaCfGgKkMNnqsTtVvXxYy .Op Fl B Ar bind_interface .Op Fl b Ar bind_address .Op Fl c Ar cipher_spec .Op Fl D Oo Ar bind_address : Oc Ns Ar port .Op Fl E Ar log_file .Op Fl e Ar escape_char .Op Fl F Ar configfile .Op Fl I Ar pkcs11 .Op Fl i Ar identity_file .Op Fl J Ar destination .Op Fl L Ar address .Op Fl l Ar login_name .Op Fl m Ar mac_spec .Op Fl O Ar ctl_cmd .Op Fl o Ar option .Op Fl p Ar port .Op Fl Q Ar query_option .Op Fl R Ar address .Op Fl S Ar ctl_path .Op Fl W Ar host : Ns Ar port .Op Fl w Ar local_tun Ns Op : Ns Ar remote_tun .Ar destination .Op Ar command Op Ar argument ... .Sh DESCRIPTION .Nm (SSH client) is a program for logging into a remote machine and for executing commands on a remote machine. It is intended to provide secure encrypted communications between two untrusted hosts over an insecure network. X11 connections, arbitrary TCP ports and .Ux Ns -domain sockets can also be forwarded over the secure channel. .Pp .Nm connects and logs into the specified .Ar destination , which may be specified as either .Sm off .Oo user @ Oc hostname .Sm on or a URI of the form .Sm off .No ssh:// Oo user @ Oc hostname Op : port . .Sm on The user must prove their identity to the remote machine using one of several methods (see below). .Pp If a .Ar command is specified, it will be executed on the remote host instead of a login shell. A complete command line may be specified as .Ar command , or it may have additional arguments. If supplied, the arguments will be appended to the command, separated by spaces, before it is sent to the server to be executed. .Pp The options are as follows: .Pp .Bl -tag -width Ds -compact .It Fl 4 Forces .Nm to use IPv4 addresses only. .Pp .It Fl 6 Forces .Nm to use IPv6 addresses only. .Pp .It Fl A Enables forwarding of connections from an authentication agent such as .Xr ssh-agent 1 . This can also be specified on a per-host basis in a configuration file. .Pp Agent forwarding should be enabled with caution. Users with the ability to bypass file permissions on the remote host (for the agent's .Ux Ns -domain socket) can access the local agent through the forwarded connection. An attacker cannot obtain key material from the agent, however they can perform operations on the keys that enable them to authenticate using the identities loaded into the agent. A safer alternative may be to use a jump host (see .Fl J ) . .Pp .It Fl a Disables forwarding of the authentication agent connection. .Pp .It Fl B Ar bind_interface Bind to the address of .Ar bind_interface before attempting to connect to the destination host. This is only useful on systems with more than one address. .Pp .It Fl b Ar bind_address Use .Ar bind_address on the local machine as the source address of the connection. Only useful on systems with more than one address. .Pp .It Fl C Requests compression of all data (including stdin, stdout, stderr, and data for forwarded X11, TCP and .Ux Ns -domain connections). The compression algorithm is the same used by .Xr gzip 1 . Compression is desirable on modem lines and other slow connections, but will only slow down things on fast networks. The default value can be set on a host-by-host basis in the configuration files; see the .Cm Compression option. .Pp .It Fl c Ar cipher_spec Selects the cipher specification for encrypting the session. .Ar cipher_spec is a comma-separated list of ciphers listed in order of preference. See the .Cm Ciphers keyword in .Xr ssh_config 5 for more information. .Pp .It Fl D Xo .Sm off .Oo Ar bind_address : Oc .Ar port .Sm on .Xc Specifies a local .Dq dynamic application-level port forwarding. This works by allocating a socket to listen to .Ar port on the local side, optionally bound to the specified .Ar bind_address . Whenever a connection is made to this port, the connection is forwarded over the secure channel, and the application protocol is then used to determine where to connect to from the remote machine. Currently the SOCKS4 and SOCKS5 protocols are supported, and .Nm will act as a SOCKS server. Only root can forward privileged ports. Dynamic port forwardings can also be specified in the configuration file. .Pp IPv6 addresses can be specified by enclosing the address in square brackets. Only the superuser can forward privileged ports. By default, the local port is bound in accordance with the .Cm GatewayPorts setting. However, an explicit .Ar bind_address may be used to bind the connection to a specific address. The .Ar bind_address of .Dq localhost indicates that the listening port be bound for local use only, while an empty address or .Sq * indicates that the port should be available from all interfaces. .Pp .It Fl E Ar log_file Append debug logs to .Ar log_file instead of standard error. .Pp .It Fl e Ar escape_char Sets the escape character for sessions with a pty (default: .Ql ~ ) . The escape character is only recognized at the beginning of a line. The escape character followed by a dot .Pq Ql \&. closes the connection; followed by control-Z suspends the connection; and followed by itself sends the escape character once. Setting the character to .Dq none disables any escapes and makes the session fully transparent. .Pp .It Fl F Ar configfile Specifies an alternative per-user configuration file. If a configuration file is given on the command line, the system-wide configuration file .Pq Pa /etc/ssh/ssh_config will be ignored. The default for the per-user configuration file is .Pa ~/.ssh/config . If set to .Dq none , no configuration files will be read. .Pp .It Fl f Requests .Nm to go to background just before command execution. This is useful if .Nm is going to ask for passwords or passphrases, but the user wants it in the background. This implies .Fl n . The recommended way to start X11 programs at a remote site is with something like .Ic ssh -f host xterm . .Pp If the .Cm ExitOnForwardFailure configuration option is set to .Dq yes , then a client started with .Fl f will wait for all remote port forwards to be successfully established before placing itself in the background. Refer to the description of .Cm ForkAfterAuthentication in .Xr ssh_config 5 for details. .Pp .It Fl G Causes .Nm to print its configuration after evaluating .Cm Host and .Cm Match blocks and exit. .Pp .It Fl g Allows remote hosts to connect to local forwarded ports. If used on a multiplexed connection, then this option must be specified on the master process. .Pp .It Fl I Ar pkcs11 Specify the PKCS#11 shared library .Nm should use to communicate with a PKCS#11 token providing keys for user authentication. .Pp .It Fl i Ar identity_file Selects a file from which the identity (private key) for public key authentication is read. You can also specify a public key file to use the corresponding private key that is loaded in .Xr ssh-agent 1 when the private key file is not present locally. The default is .Pa ~/.ssh/id_rsa , .Pa ~/.ssh/id_ecdsa , .Pa ~/.ssh/id_ecdsa_sk , .Pa ~/.ssh/id_ed25519 , .Pa ~/.ssh/id_ed25519_sk and .Pa ~/.ssh/id_dsa . Identity files may also be specified on a per-host basis in the configuration file. It is possible to have multiple .Fl i options (and multiple identities specified in configuration files). If no certificates have been explicitly specified by the .Cm CertificateFile directive, .Nm will also try to load certificate information from the filename obtained by appending .Pa -cert.pub to identity filenames. .Pp .It Fl J Ar destination Connect to the target host by first making a .Nm connection to the jump host described by .Ar destination and then establishing a TCP forwarding to the ultimate destination from there. Multiple jump hops may be specified separated by comma characters. This is a shortcut to specify a .Cm ProxyJump configuration directive. Note that configuration directives supplied on the command-line generally apply to the destination host and not any specified jump hosts. Use .Pa ~/.ssh/config to specify configuration for jump hosts. .Pp .It Fl K Enables GSSAPI-based authentication and forwarding (delegation) of GSSAPI credentials to the server. .Pp .It Fl k Disables forwarding (delegation) of GSSAPI credentials to the server. .Pp .It Fl L Xo .Sm off .Oo Ar bind_address : Oc .Ar port : host : hostport .Sm on .Xc .It Fl L Xo .Sm off .Oo Ar bind_address : Oc .Ar port : remote_socket .Sm on .Xc .It Fl L Xo .Sm off .Ar local_socket : host : hostport .Sm on .Xc .It Fl L Xo .Sm off .Ar local_socket : remote_socket .Sm on .Xc Specifies that connections to the given TCP port or Unix socket on the local (client) host are to be forwarded to the given host and port, or Unix socket, on the remote side. This works by allocating a socket to listen to either a TCP .Ar port on the local side, optionally bound to the specified .Ar bind_address , or to a Unix socket. Whenever a connection is made to the local port or socket, the connection is forwarded over the secure channel, and a connection is made to either .Ar host port .Ar hostport , or the Unix socket .Ar remote_socket , from the remote machine. .Pp Port forwardings can also be specified in the configuration file. Only the superuser can forward privileged ports. IPv6 addresses can be specified by enclosing the address in square brackets. .Pp By default, the local port is bound in accordance with the .Cm GatewayPorts setting. However, an explicit .Ar bind_address may be used to bind the connection to a specific address. The .Ar bind_address of .Dq localhost indicates that the listening port be bound for local use only, while an empty address or .Sq * indicates that the port should be available from all interfaces. .Pp .It Fl l Ar login_name Specifies the user to log in as on the remote machine. This also may be specified on a per-host basis in the configuration file. .Pp .It Fl M Places the .Nm client into .Dq master mode for connection sharing. Multiple .Fl M options places .Nm into .Dq master mode but with confirmation required using .Xr ssh-askpass 1 before each operation that changes the multiplexing state (e.g. opening a new session). Refer to the description of .Cm ControlMaster in .Xr ssh_config 5 for details. .Pp .It Fl m Ar mac_spec A comma-separated list of MAC (message authentication code) algorithms, specified in order of preference. See the .Cm MACs keyword for more information. .Pp .It Fl N Do not execute a remote command. This is useful for just forwarding ports. Refer to the description of .Cm SessionType in .Xr ssh_config 5 for details. .Pp .It Fl n Redirects stdin from .Pa /dev/null (actually, prevents reading from stdin). This must be used when .Nm is run in the background. A common trick is to use this to run X11 programs on a remote machine. For example, .Ic ssh -n shadows.cs.hut.fi emacs & will start an emacs on shadows.cs.hut.fi, and the X11 connection will be automatically forwarded over an encrypted channel. The .Nm program will be put in the background. (This does not work if .Nm needs to ask for a password or passphrase; see also the .Fl f option.) Refer to the description of .Cm StdinNull in .Xr ssh_config 5 for details. .Pp .It Fl O Ar ctl_cmd Control an active connection multiplexing master process. When the .Fl O option is specified, the .Ar ctl_cmd argument is interpreted and passed to the master process. Valid commands are: .Dq check (check that the master process is running), .Dq forward (request forwardings without command execution), .Dq cancel (cancel forwardings), .Dq exit (request the master to exit), and .Dq stop (request the master to stop accepting further multiplexing requests). .Pp .It Fl o Ar option Can be used to give options in the format used in the configuration file. This is useful for specifying options for which there is no separate command-line flag. For full details of the options listed below, and their possible values, see .Xr ssh_config 5 . .Pp .Bl -tag -width Ds -offset indent -compact .It AddKeysToAgent .It AddressFamily .It BatchMode .It BindAddress .It CanonicalDomains .It CanonicalizeFallbackLocal .It CanonicalizeHostname .It CanonicalizeMaxDots .It CanonicalizePermittedCNAMEs .It CASignatureAlgorithms .It CertificateFile .It CheckHostIP .It Ciphers .It ClearAllForwardings .It Compression .It ConnectionAttempts .It ConnectTimeout .It ControlMaster .It ControlPath .It ControlPersist .It DynamicForward .It EscapeChar .It ExitOnForwardFailure .It FingerprintHash .It ForkAfterAuthentication .It ForwardAgent .It ForwardX11 .It ForwardX11Timeout .It ForwardX11Trusted .It GatewayPorts .It GlobalKnownHostsFile .It GSSAPIAuthentication .It GSSAPIDelegateCredentials .It HashKnownHosts .It Host .It HostbasedAcceptedAlgorithms .It HostbasedAuthentication .It HostKeyAlgorithms .It HostKeyAlias .It Hostname .It IdentitiesOnly .It IdentityAgent .It IdentityFile .It IPQoS .It KbdInteractiveAuthentication .It KbdInteractiveDevices .It KexAlgorithms .It KnownHostsCommand .It LocalCommand .It LocalForward .It LogLevel .It MACs .It Match .It NoHostAuthenticationForLocalhost .It NumberOfPasswordPrompts .It PasswordAuthentication .It PermitLocalCommand .It PermitRemoteOpen .It PKCS11Provider .It Port .It PreferredAuthentications .It ProxyCommand .It ProxyJump .It ProxyUseFdpass .It PubkeyAcceptedAlgorithms .It PubkeyAuthentication .It RekeyLimit .It RemoteCommand .It RemoteForward .It RequestTTY .It SendEnv .It ServerAliveInterval .It ServerAliveCountMax .It SessionType .It SetEnv .It StdinNull .It StreamLocalBindMask .It StreamLocalBindUnlink .It StrictHostKeyChecking .It TCPKeepAlive .It Tunnel .It TunnelDevice .It UpdateHostKeys .It User .It UserKnownHostsFile .It VerifyHostKeyDNS -.It VersionAddendum .It VisualHostKey .It XAuthLocation .El .Pp .It Fl p Ar port Port to connect to on the remote host. This can be specified on a per-host basis in the configuration file. .Pp .It Fl Q Ar query_option Queries for the algorithms supported by one of the following features: .Ar cipher (supported symmetric ciphers), .Ar cipher-auth (supported symmetric ciphers that support authenticated encryption), .Ar help (supported query terms for use with the .Fl Q flag), .Ar mac (supported message integrity codes), .Ar kex (key exchange algorithms), .Ar key (key types), .Ar key-cert (certificate key types), .Ar key-plain (non-certificate key types), .Ar key-sig (all key types and signature algorithms), .Ar protocol-version (supported SSH protocol versions), and .Ar sig (supported signature algorithms). Alternatively, any keyword from .Xr ssh_config 5 or .Xr sshd_config 5 that takes an algorithm list may be used as an alias for the corresponding query_option. .Pp .It Fl q Quiet mode. Causes most warning and diagnostic messages to be suppressed. .Pp .It Fl R Xo .Sm off .Oo Ar bind_address : Oc .Ar port : host : hostport .Sm on .Xc .It Fl R Xo .Sm off .Oo Ar bind_address : Oc .Ar port : local_socket .Sm on .Xc .It Fl R Xo .Sm off .Ar remote_socket : host : hostport .Sm on .Xc .It Fl R Xo .Sm off .Ar remote_socket : local_socket .Sm on .Xc .It Fl R Xo .Sm off .Oo Ar bind_address : Oc .Ar port .Sm on .Xc Specifies that connections to the given TCP port or Unix socket on the remote (server) host are to be forwarded to the local side. .Pp This works by allocating a socket to listen to either a TCP .Ar port or to a Unix socket on the remote side. Whenever a connection is made to this port or Unix socket, the connection is forwarded over the secure channel, and a connection is made from the local machine to either an explicit destination specified by .Ar host port .Ar hostport , or .Ar local_socket , or, if no explicit destination was specified, .Nm will act as a SOCKS 4/5 proxy and forward connections to the destinations requested by the remote SOCKS client. .Pp Port forwardings can also be specified in the configuration file. Privileged ports can be forwarded only when logging in as root on the remote machine. IPv6 addresses can be specified by enclosing the address in square brackets. .Pp By default, TCP listening sockets on the server will be bound to the loopback interface only. This may be overridden by specifying a .Ar bind_address . An empty .Ar bind_address , or the address .Ql * , indicates that the remote socket should listen on all interfaces. Specifying a remote .Ar bind_address will only succeed if the server's .Cm GatewayPorts option is enabled (see .Xr sshd_config 5 ) . .Pp If the .Ar port argument is .Ql 0 , the listen port will be dynamically allocated on the server and reported to the client at run time. When used together with .Ic -O forward , the allocated port will be printed to the standard output. .Pp .It Fl S Ar ctl_path Specifies the location of a control socket for connection sharing, or the string .Dq none to disable connection sharing. Refer to the description of .Cm ControlPath and .Cm ControlMaster in .Xr ssh_config 5 for details. .Pp .It Fl s May be used to request invocation of a subsystem on the remote system. Subsystems facilitate the use of SSH as a secure transport for other applications (e.g.\& .Xr sftp 1 ) . The subsystem is specified as the remote command. Refer to the description of .Cm SessionType in .Xr ssh_config 5 for details. .Pp .It Fl T Disable pseudo-terminal allocation. .Pp .It Fl t Force pseudo-terminal allocation. This can be used to execute arbitrary screen-based programs on a remote machine, which can be very useful, e.g. when implementing menu services. Multiple .Fl t options force tty allocation, even if .Nm has no local tty. .Pp .It Fl V Display the version number and exit. .Pp .It Fl v Verbose mode. Causes .Nm to print debugging messages about its progress. This is helpful in debugging connection, authentication, and configuration problems. Multiple .Fl v options increase the verbosity. The maximum is 3. .Pp .It Fl W Ar host : Ns Ar port Requests that standard input and output on the client be forwarded to .Ar host on .Ar port over the secure channel. Implies .Fl N , .Fl T , .Cm ExitOnForwardFailure and .Cm ClearAllForwardings , though these can be overridden in the configuration file or using .Fl o command line options. .Pp .It Fl w Xo .Ar local_tun Ns Op : Ns Ar remote_tun .Xc Requests tunnel device forwarding with the specified .Xr tun 4 devices between the client .Pq Ar local_tun and the server .Pq Ar remote_tun . .Pp The devices may be specified by numerical ID or the keyword .Dq any , which uses the next available tunnel device. If .Ar remote_tun is not specified, it defaults to .Dq any . See also the .Cm Tunnel and .Cm TunnelDevice directives in .Xr ssh_config 5 . .Pp If the .Cm Tunnel directive is unset, it will be set to the default tunnel mode, which is .Dq point-to-point . If a different .Cm Tunnel forwarding mode it desired, then it should be specified before .Fl w . .Pp .It Fl X Enables X11 forwarding. This can also be specified on a per-host basis in a configuration file. .Pp X11 forwarding should be enabled with caution. Users with the ability to bypass file permissions on the remote host (for the user's X authorization database) can access the local X11 display through the forwarded connection. An attacker may then be able to perform activities such as keystroke monitoring. .Pp For this reason, X11 forwarding is subjected to X11 SECURITY extension restrictions by default. Refer to the .Nm .Fl Y option and the .Cm ForwardX11Trusted directive in .Xr ssh_config 5 for more information. .Pp .It Fl x Disables X11 forwarding. .Pp .It Fl Y Enables trusted X11 forwarding. Trusted X11 forwardings are not subjected to the X11 SECURITY extension controls. .Pp .It Fl y Send log information using the .Xr syslog 3 system module. By default this information is sent to stderr. .El .Pp .Nm may additionally obtain configuration data from a per-user configuration file and a system-wide configuration file. The file format and configuration options are described in .Xr ssh_config 5 . .Sh AUTHENTICATION The OpenSSH SSH client supports SSH protocol 2. .Pp The methods available for authentication are: GSSAPI-based authentication, host-based authentication, public key authentication, keyboard-interactive authentication, and password authentication. Authentication methods are tried in the order specified above, though .Cm PreferredAuthentications can be used to change the default order. .Pp Host-based authentication works as follows: If the machine the user logs in from is listed in .Pa /etc/hosts.equiv or .Pa /etc/shosts.equiv on the remote machine, the user is non-root and the user names are the same on both sides, or if the files .Pa ~/.rhosts or .Pa ~/.shosts exist in the user's home directory on the remote machine and contain a line containing the name of the client machine and the name of the user on that machine, the user is considered for login. Additionally, the server .Em must be able to verify the client's host key (see the description of .Pa /etc/ssh/ssh_known_hosts and .Pa ~/.ssh/known_hosts , below) for login to be permitted. This authentication method closes security holes due to IP spoofing, DNS spoofing, and routing spoofing. [Note to the administrator: .Pa /etc/hosts.equiv , .Pa ~/.rhosts , and the rlogin/rsh protocol in general, are inherently insecure and should be disabled if security is desired.] .Pp Public key authentication works as follows: The scheme is based on public-key cryptography, using cryptosystems where encryption and decryption are done using separate keys, and it is unfeasible to derive the decryption key from the encryption key. The idea is that each user creates a public/private key pair for authentication purposes. The server knows the public key, and only the user knows the private key. .Nm implements public key authentication protocol automatically, using one of the DSA, ECDSA, Ed25519 or RSA algorithms. The HISTORY section of .Xr ssl 8 contains a brief discussion of the DSA and RSA algorithms. .Pp The file .Pa ~/.ssh/authorized_keys lists the public keys that are permitted for logging in. When the user logs in, the .Nm program tells the server which key pair it would like to use for authentication. The client proves that it has access to the private key and the server checks that the corresponding public key is authorized to accept the account. .Pp The server may inform the client of errors that prevented public key authentication from succeeding after authentication completes using a different method. These may be viewed by increasing the .Cm LogLevel to .Cm DEBUG or higher (e.g. by using the .Fl v flag). .Pp The user creates their key pair by running .Xr ssh-keygen 1 . This stores the private key in .Pa ~/.ssh/id_dsa (DSA), .Pa ~/.ssh/id_ecdsa (ECDSA), .Pa ~/.ssh/id_ecdsa_sk (authenticator-hosted ECDSA), .Pa ~/.ssh/id_ed25519 (Ed25519), .Pa ~/.ssh/id_ed25519_sk (authenticator-hosted Ed25519), or .Pa ~/.ssh/id_rsa (RSA) and stores the public key in .Pa ~/.ssh/id_dsa.pub (DSA), .Pa ~/.ssh/id_ecdsa.pub (ECDSA), .Pa ~/.ssh/id_ecdsa_sk.pub (authenticator-hosted ECDSA), .Pa ~/.ssh/id_ed25519.pub (Ed25519), .Pa ~/.ssh/id_ed25519_sk.pub (authenticator-hosted Ed25519), or .Pa ~/.ssh/id_rsa.pub (RSA) in the user's home directory. The user should then copy the public key to .Pa ~/.ssh/authorized_keys in their home directory on the remote machine. The .Pa authorized_keys file corresponds to the conventional .Pa ~/.rhosts file, and has one key per line, though the lines can be very long. After this, the user can log in without giving the password. .Pp A variation on public key authentication is available in the form of certificate authentication: instead of a set of public/private keys, signed certificates are used. This has the advantage that a single trusted certification authority can be used in place of many public/private keys. See the CERTIFICATES section of .Xr ssh-keygen 1 for more information. .Pp The most convenient way to use public key or certificate authentication may be with an authentication agent. See .Xr ssh-agent 1 and (optionally) the .Cm AddKeysToAgent directive in .Xr ssh_config 5 for more information. .Pp Keyboard-interactive authentication works as follows: The server sends an arbitrary .Qq challenge text and prompts for a response, possibly multiple times. Examples of keyboard-interactive authentication include .Bx Authentication (see .Xr login.conf 5 ) and PAM (some .Pf non- Ox systems). .Pp Finally, if other authentication methods fail, .Nm prompts the user for a password. The password is sent to the remote host for checking; however, since all communications are encrypted, the password cannot be seen by someone listening on the network. .Pp .Nm automatically maintains and checks a database containing identification for all hosts it has ever been used with. Host keys are stored in .Pa ~/.ssh/known_hosts in the user's home directory. Additionally, the file .Pa /etc/ssh/ssh_known_hosts is automatically checked for known hosts. Any new hosts are automatically added to the user's file. If a host's identification ever changes, .Nm warns about this and disables password authentication to prevent server spoofing or man-in-the-middle attacks, which could otherwise be used to circumvent the encryption. The .Cm StrictHostKeyChecking option can be used to control logins to machines whose host key is not known or has changed. .Pp When the user's identity has been accepted by the server, the server either executes the given command in a non-interactive session or, if no command has been specified, logs into the machine and gives the user a normal shell as an interactive session. All communication with the remote command or shell will be automatically encrypted. .Pp If an interactive session is requested, .Nm by default will only request a pseudo-terminal (pty) for interactive sessions when the client has one. The flags .Fl T and .Fl t can be used to override this behaviour. .Pp If a pseudo-terminal has been allocated, the user may use the escape characters noted below. .Pp If no pseudo-terminal has been allocated, the session is transparent and can be used to reliably transfer binary data. On most systems, setting the escape character to .Dq none will also make the session transparent even if a tty is used. .Pp The session terminates when the command or shell on the remote machine exits and all X11 and TCP connections have been closed. .Sh ESCAPE CHARACTERS When a pseudo-terminal has been requested, .Nm supports a number of functions through the use of an escape character. .Pp A single tilde character can be sent as .Ic ~~ or by following the tilde by a character other than those described below. The escape character must always follow a newline to be interpreted as special. The escape character can be changed in configuration files using the .Cm EscapeChar configuration directive or on the command line by the .Fl e option. .Pp The supported escapes (assuming the default .Ql ~ ) are: .Bl -tag -width Ds .It Cm ~. Disconnect. .It Cm ~^Z Background .Nm . .It Cm ~# List forwarded connections. .It Cm ~& Background .Nm at logout when waiting for forwarded connection / X11 sessions to terminate. .It Cm ~? Display a list of escape characters. .It Cm ~B Send a BREAK to the remote system (only useful if the peer supports it). .It Cm ~C Open command line. Currently this allows the addition of port forwardings using the .Fl L , .Fl R and .Fl D options (see above). It also allows the cancellation of existing port-forwardings with .Sm off .Fl KL Oo Ar bind_address : Oc Ar port .Sm on for local, .Sm off .Fl KR Oo Ar bind_address : Oc Ar port .Sm on for remote and .Sm off .Fl KD Oo Ar bind_address : Oc Ar port .Sm on for dynamic port-forwardings. .Ic !\& Ns Ar command allows the user to execute a local command if the .Ic PermitLocalCommand option is enabled in .Xr ssh_config 5 . Basic help is available, using the .Fl h option. .It Cm ~R Request rekeying of the connection (only useful if the peer supports it). .It Cm ~V Decrease the verbosity .Pq Ic LogLevel when errors are being written to stderr. .It Cm ~v Increase the verbosity .Pq Ic LogLevel when errors are being written to stderr. .El .Sh TCP FORWARDING Forwarding of arbitrary TCP connections over a secure channel can be specified either on the command line or in a configuration file. One possible application of TCP forwarding is a secure connection to a mail server; another is going through firewalls. .Pp In the example below, we look at encrypting communication for an IRC client, even though the IRC server it connects to does not directly support encrypted communication. This works as follows: the user connects to the remote host using .Nm , specifying the ports to be used to forward the connection. After that it is possible to start the program locally, and .Nm will encrypt and forward the connection to the remote server. .Pp The following example tunnels an IRC session from the client to an IRC server at .Dq server.example.com , joining channel .Dq #users , nickname .Dq pinky , using the standard IRC port, 6667: .Bd -literal -offset 4n $ ssh -f -L 6667:localhost:6667 server.example.com sleep 10 $ irc -c '#users' pinky IRC/127.0.0.1 .Ed .Pp The .Fl f option backgrounds .Nm and the remote command .Dq sleep 10 is specified to allow an amount of time (10 seconds, in the example) to start the program which is going to use the tunnel. If no connections are made within the time specified, .Nm will exit. .Sh X11 FORWARDING If the .Cm ForwardX11 variable is set to .Dq yes (or see the description of the .Fl X , .Fl x , and .Fl Y options above) and the user is using X11 (the .Ev DISPLAY environment variable is set), the connection to the X11 display is automatically forwarded to the remote side in such a way that any X11 programs started from the shell (or command) will go through the encrypted channel, and the connection to the real X server will be made from the local machine. The user should not manually set .Ev DISPLAY . Forwarding of X11 connections can be configured on the command line or in configuration files. .Pp The .Ev DISPLAY value set by .Nm will point to the server machine, but with a display number greater than zero. This is normal, and happens because .Nm creates a .Dq proxy X server on the server machine for forwarding the connections over the encrypted channel. .Pp .Nm will also automatically set up Xauthority data on the server machine. For this purpose, it will generate a random authorization cookie, store it in Xauthority on the server, and verify that any forwarded connections carry this cookie and replace it by the real cookie when the connection is opened. The real authentication cookie is never sent to the server machine (and no cookies are sent in the plain). .Pp If the .Cm ForwardAgent variable is set to .Dq yes (or see the description of the .Fl A and .Fl a options above) and the user is using an authentication agent, the connection to the agent is automatically forwarded to the remote side. .Sh VERIFYING HOST KEYS When connecting to a server for the first time, a fingerprint of the server's public key is presented to the user (unless the option .Cm StrictHostKeyChecking has been disabled). Fingerprints can be determined using .Xr ssh-keygen 1 : .Pp .Dl $ ssh-keygen -l -f /etc/ssh/ssh_host_rsa_key .Pp If the fingerprint is already known, it can be matched and the key can be accepted or rejected. If only legacy (MD5) fingerprints for the server are available, the .Xr ssh-keygen 1 .Fl E option may be used to downgrade the fingerprint algorithm to match. .Pp Because of the difficulty of comparing host keys just by looking at fingerprint strings, there is also support to compare host keys visually, using .Em random art . By setting the .Cm VisualHostKey option to .Dq yes , a small ASCII graphic gets displayed on every login to a server, no matter if the session itself is interactive or not. By learning the pattern a known server produces, a user can easily find out that the host key has changed when a completely different pattern is displayed. Because these patterns are not unambiguous however, a pattern that looks similar to the pattern remembered only gives a good probability that the host key is the same, not guaranteed proof. .Pp To get a listing of the fingerprints along with their random art for all known hosts, the following command line can be used: .Pp .Dl $ ssh-keygen -lv -f ~/.ssh/known_hosts .Pp If the fingerprint is unknown, an alternative method of verification is available: SSH fingerprints verified by DNS. An additional resource record (RR), SSHFP, is added to a zonefile and the connecting client is able to match the fingerprint with that of the key presented. .Pp In this example, we are connecting a client to a server, .Dq host.example.com . The SSHFP resource records should first be added to the zonefile for host.example.com: .Bd -literal -offset indent $ ssh-keygen -r host.example.com. .Ed .Pp The output lines will have to be added to the zonefile. To check that the zone is answering fingerprint queries: .Pp .Dl $ dig -t SSHFP host.example.com .Pp Finally the client connects: .Bd -literal -offset indent $ ssh -o "VerifyHostKeyDNS ask" host.example.com [...] Matching host key fingerprint found in DNS. Are you sure you want to continue connecting (yes/no)? .Ed .Pp See the .Cm VerifyHostKeyDNS option in .Xr ssh_config 5 for more information. .Sh SSH-BASED VIRTUAL PRIVATE NETWORKS .Nm contains support for Virtual Private Network (VPN) tunnelling using the .Xr tun 4 network pseudo-device, allowing two networks to be joined securely. The .Xr sshd_config 5 configuration option .Cm PermitTunnel controls whether the server supports this, and at what level (layer 2 or 3 traffic). .Pp The following example would connect client network 10.0.50.0/24 with remote network 10.0.99.0/24 using a point-to-point connection from 10.1.1.1 to 10.1.1.2, provided that the SSH server running on the gateway to the remote network, at 192.168.1.15, allows it. .Pp On the client: .Bd -literal -offset indent # ssh -f -w 0:1 192.168.1.15 true # ifconfig tun0 10.1.1.1 10.1.1.2 netmask 255.255.255.252 # route add 10.0.99.0/24 10.1.1.2 .Ed .Pp On the server: .Bd -literal -offset indent # ifconfig tun1 10.1.1.2 10.1.1.1 netmask 255.255.255.252 # route add 10.0.50.0/24 10.1.1.1 .Ed .Pp Client access may be more finely tuned via the .Pa /root/.ssh/authorized_keys file (see below) and the .Cm PermitRootLogin server option. The following entry would permit connections on .Xr tun 4 device 1 from user .Dq jane and on tun device 2 from user .Dq john , if .Cm PermitRootLogin is set to .Dq forced-commands-only : .Bd -literal -offset 2n tunnel="1",command="sh /etc/netstart tun1" ssh-rsa ... jane tunnel="2",command="sh /etc/netstart tun2" ssh-rsa ... john .Ed .Pp Since an SSH-based setup entails a fair amount of overhead, it may be more suited to temporary setups, such as for wireless VPNs. More permanent VPNs are better provided by tools such as .Xr ipsecctl 8 and .Xr isakmpd 8 . .Sh ENVIRONMENT .Nm will normally set the following environment variables: .Bl -tag -width "SSH_ORIGINAL_COMMAND" .It Ev DISPLAY The .Ev DISPLAY variable indicates the location of the X11 server. It is automatically set by .Nm to point to a value of the form .Dq hostname:n , where .Dq hostname indicates the host where the shell runs, and .Sq n is an integer \*(Ge 1. .Nm uses this special value to forward X11 connections over the secure channel. The user should normally not set .Ev DISPLAY explicitly, as that will render the X11 connection insecure (and will require the user to manually copy any required authorization cookies). .It Ev HOME Set to the path of the user's home directory. .It Ev LOGNAME Synonym for .Ev USER ; set for compatibility with systems that use this variable. .It Ev MAIL Set to the path of the user's mailbox. .It Ev PATH Set to the default .Ev PATH , as specified when compiling .Nm . .It Ev SSH_ASKPASS If .Nm needs a passphrase, it will read the passphrase from the current terminal if it was run from a terminal. If .Nm does not have a terminal associated with it but .Ev DISPLAY and .Ev SSH_ASKPASS are set, it will execute the program specified by .Ev SSH_ASKPASS and open an X11 window to read the passphrase. This is particularly useful when calling .Nm from a .Pa .xsession or related script. (Note that on some machines it may be necessary to redirect the input from .Pa /dev/null to make this work.) .It Ev SSH_ASKPASS_REQUIRE Allows further control over the use of an askpass program. If this variable is set to .Dq never then .Nm will never attempt to use one. If it is set to .Dq prefer , then .Nm will prefer to use the askpass program instead of the TTY when requesting passwords. Finally, if the variable is set to .Dq force , then the askpass program will be used for all passphrase input regardless of whether .Ev DISPLAY is set. .It Ev SSH_AUTH_SOCK Identifies the path of a .Ux Ns -domain socket used to communicate with the agent. .It Ev SSH_CONNECTION Identifies the client and server ends of the connection. The variable contains four space-separated values: client IP address, client port number, server IP address, and server port number. .It Ev SSH_ORIGINAL_COMMAND This variable contains the original command line if a forced command is executed. It can be used to extract the original arguments. .It Ev SSH_TTY This is set to the name of the tty (path to the device) associated with the current shell or command. If the current session has no tty, this variable is not set. .It Ev SSH_TUNNEL Optionally set by .Xr sshd 8 to contain the interface names assigned if tunnel forwarding was requested by the client. .It Ev SSH_USER_AUTH Optionally set by .Xr sshd 8 , this variable may contain a pathname to a file that lists the authentication methods successfully used when the session was established, including any public keys that were used. .It Ev TZ This variable is set to indicate the present time zone if it was set when the daemon was started (i.e. the daemon passes the value on to new connections). .It Ev USER Set to the name of the user logging in. .El .Pp Additionally, .Nm reads .Pa ~/.ssh/environment , and adds lines of the format .Dq VARNAME=value to the environment if the file exists and users are allowed to change their environment. For more information, see the .Cm PermitUserEnvironment option in .Xr sshd_config 5 . .Sh FILES .Bl -tag -width Ds -compact .It Pa ~/.rhosts This file is used for host-based authentication (see above). On some machines this file may need to be world-readable if the user's home directory is on an NFS partition, because .Xr sshd 8 reads it as root. Additionally, this file must be owned by the user, and must not have write permissions for anyone else. The recommended permission for most machines is read/write for the user, and not accessible by others. .Pp .It Pa ~/.shosts This file is used in exactly the same way as .Pa .rhosts , but allows host-based authentication without permitting login with rlogin/rsh. .Pp .It Pa ~/.ssh/ This directory is the default location for all user-specific configuration and authentication information. There is no general requirement to keep the entire contents of this directory secret, but the recommended permissions are read/write/execute for the user, and not accessible by others. .Pp .It Pa ~/.ssh/authorized_keys Lists the public keys (DSA, ECDSA, Ed25519, RSA) that can be used for logging in as this user. The format of this file is described in the .Xr sshd 8 manual page. This file is not highly sensitive, but the recommended permissions are read/write for the user, and not accessible by others. .Pp .It Pa ~/.ssh/config This is the per-user configuration file. The file format and configuration options are described in .Xr ssh_config 5 . Because of the potential for abuse, this file must have strict permissions: read/write for the user, and not writable by others. .Pp .It Pa ~/.ssh/environment Contains additional definitions for environment variables; see .Sx ENVIRONMENT , above. .Pp .It Pa ~/.ssh/id_dsa .It Pa ~/.ssh/id_ecdsa .It Pa ~/.ssh/id_ecdsa_sk .It Pa ~/.ssh/id_ed25519 .It Pa ~/.ssh/id_ed25519_sk .It Pa ~/.ssh/id_rsa Contains the private key for authentication. These files contain sensitive data and should be readable by the user but not accessible by others (read/write/execute). .Nm will simply ignore a private key file if it is accessible by others. It is possible to specify a passphrase when generating the key which will be used to encrypt the sensitive part of this file using AES-128. .Pp .It Pa ~/.ssh/id_dsa.pub .It Pa ~/.ssh/id_ecdsa.pub .It Pa ~/.ssh/id_ecdsa_sk.pub .It Pa ~/.ssh/id_ed25519.pub .It Pa ~/.ssh/id_ed25519_sk.pub .It Pa ~/.ssh/id_rsa.pub Contains the public key for authentication. These files are not sensitive and can (but need not) be readable by anyone. .Pp .It Pa ~/.ssh/known_hosts Contains a list of host keys for all hosts the user has logged into that are not already in the systemwide list of known host keys. See .Xr sshd 8 for further details of the format of this file. .Pp .It Pa ~/.ssh/rc Commands in this file are executed by .Nm when the user logs in, just before the user's shell (or command) is started. See the .Xr sshd 8 manual page for more information. .Pp .It Pa /etc/hosts.equiv This file is for host-based authentication (see above). It should only be writable by root. .Pp .It Pa /etc/shosts.equiv This file is used in exactly the same way as .Pa hosts.equiv , but allows host-based authentication without permitting login with rlogin/rsh. .Pp .It Pa /etc/ssh/ssh_config Systemwide configuration file. The file format and configuration options are described in .Xr ssh_config 5 . .Pp .It Pa /etc/ssh/ssh_host_key .It Pa /etc/ssh/ssh_host_dsa_key .It Pa /etc/ssh/ssh_host_ecdsa_key .It Pa /etc/ssh/ssh_host_ed25519_key .It Pa /etc/ssh/ssh_host_rsa_key These files contain the private parts of the host keys and are used for host-based authentication. .Pp .It Pa /etc/ssh/ssh_known_hosts Systemwide list of known host keys. This file should be prepared by the system administrator to contain the public host keys of all machines in the organization. It should be world-readable. See .Xr sshd 8 for further details of the format of this file. .Pp .It Pa /etc/ssh/sshrc Commands in this file are executed by .Nm when the user logs in, just before the user's shell (or command) is started. See the .Xr sshd 8 manual page for more information. .El .Sh EXIT STATUS .Nm exits with the exit status of the remote command or with 255 if an error occurred. .Sh SEE ALSO .Xr scp 1 , .Xr sftp 1 , .Xr ssh-add 1 , .Xr ssh-agent 1 , .Xr ssh-keygen 1 , .Xr ssh-keyscan 1 , .Xr tun 4 , .Xr ssh_config 5 , .Xr ssh-keysign 8 , .Xr sshd 8 .Sh STANDARDS .Rs .%A S. Lehtinen .%A C. Lonvick .%D January 2006 .%R RFC 4250 .%T The Secure Shell (SSH) Protocol Assigned Numbers .Re .Pp .Rs .%A T. Ylonen .%A C. Lonvick .%D January 2006 .%R RFC 4251 .%T The Secure Shell (SSH) Protocol Architecture .Re .Pp .Rs .%A T. Ylonen .%A C. Lonvick .%D January 2006 .%R RFC 4252 .%T The Secure Shell (SSH) Authentication Protocol .Re .Pp .Rs .%A T. Ylonen .%A C. Lonvick .%D January 2006 .%R RFC 4253 .%T The Secure Shell (SSH) Transport Layer Protocol .Re .Pp .Rs .%A T. Ylonen .%A C. Lonvick .%D January 2006 .%R RFC 4254 .%T The Secure Shell (SSH) Connection Protocol .Re .Pp .Rs .%A J. Schlyter .%A W. Griffin .%D January 2006 .%R RFC 4255 .%T Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints .Re .Pp .Rs .%A F. Cusack .%A M. Forssen .%D January 2006 .%R RFC 4256 .%T Generic Message Exchange Authentication for the Secure Shell Protocol (SSH) .Re .Pp .Rs .%A J. Galbraith .%A P. Remaker .%D January 2006 .%R RFC 4335 .%T The Secure Shell (SSH) Session Channel Break Extension .Re .Pp .Rs .%A M. Bellare .%A T. Kohno .%A C. Namprempre .%D January 2006 .%R RFC 4344 .%T The Secure Shell (SSH) Transport Layer Encryption Modes .Re .Pp .Rs .%A B. Harris .%D January 2006 .%R RFC 4345 .%T Improved Arcfour Modes for the Secure Shell (SSH) Transport Layer Protocol .Re .Pp .Rs .%A M. Friedl .%A N. Provos .%A W. Simpson .%D March 2006 .%R RFC 4419 .%T Diffie-Hellman Group Exchange for the Secure Shell (SSH) Transport Layer Protocol .Re .Pp .Rs .%A J. Galbraith .%A R. Thayer .%D November 2006 .%R RFC 4716 .%T The Secure Shell (SSH) Public Key File Format .Re .Pp .Rs .%A D. Stebila .%A J. Green .%D December 2009 .%R RFC 5656 .%T Elliptic Curve Algorithm Integration in the Secure Shell Transport Layer .Re .Pp .Rs .%A A. Perrig .%A D. Song .%D 1999 .%O International Workshop on Cryptographic Techniques and E-Commerce (CrypTEC '99) .%T Hash Visualization: a New Technique to improve Real-World Security .Re .Sh AUTHORS OpenSSH is a derivative of the original and free ssh 1.2.12 release by Tatu Ylonen. Aaron Campbell, Bob Beck, Markus Friedl, Niels Provos, Theo de Raadt and Dug Song removed many bugs, re-added newer features and created OpenSSH. Markus Friedl contributed the support for SSH protocol versions 1.5 and 2.0. diff --git a/crypto/openssh/ssh.c b/crypto/openssh/ssh.c index 860ad31954a0..359127df04b6 100644 --- a/crypto/openssh/ssh.c +++ b/crypto/openssh/ssh.c @@ -1,2390 +1,2383 @@ /* $OpenBSD: ssh.c,v 1.574 2022/03/30 04:33:09 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Ssh client program. This program can be used to log into a remote machine. * The software supports strong authentication, encryption, and forwarding * of X11, TCP/IP, and authentication connections. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * Copyright (c) 1999 Niels Provos. All rights reserved. * Copyright (c) 2000, 2001, 2002, 2003 Markus Friedl. All rights reserved. * * Modified to work with SSLeay by Niels Provos * in Canada (German citizen). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include #ifdef HAVE_SYS_STAT_H # include #endif #include #include #include #include #include #include #include #include #ifdef HAVE_PATHS_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_OPENSSL #include #include #endif #include "openbsd-compat/openssl-compat.h" #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "ssh.h" #include "ssh2.h" #include "canohost.h" #include "compat.h" #include "cipher.h" #include "packet.h" #include "sshbuf.h" #include "channels.h" #include "sshkey.h" #include "authfd.h" #include "authfile.h" #include "pathnames.h" #include "dispatch.h" #include "clientloop.h" #include "log.h" #include "misc.h" #include "readconf.h" #include "sshconnect.h" #include "kex.h" #include "mac.h" #include "sshpty.h" #include "match.h" #include "msg.h" #include "version.h" #include "ssherr.h" #include "myproposal.h" #include "utf8.h" #ifdef ENABLE_PKCS11 #include "ssh-pkcs11.h" #endif extern char *__progname; /* Saves a copy of argv for setproctitle emulation */ #ifndef HAVE_SETPROCTITLE static char **saved_av; #endif /* Flag indicating whether debug mode is on. May be set on the command line. */ int debug_flag = 0; /* Flag indicating whether a tty should be requested */ int tty_flag = 0; /* * Flag indicating that the current process should be backgrounded and * a new mux-client launched in the foreground for ControlPersist. */ int need_controlpersist_detach = 0; /* Copies of flags for ControlPersist foreground mux-client */ int ostdin_null_flag, osession_type, otty_flag, orequest_tty; /* * General data structure for command line options and options configurable * in configuration files. See readconf.h. */ Options options; /* optional user configfile */ char *config = NULL; /* * Name of the host we are connecting to. This is the name given on the * command line, or the Hostname specified for the user-supplied name in a * configuration file. */ char *host; /* * A config can specify a path to forward, overriding SSH_AUTH_SOCK. If this is * not NULL, forward the socket at this path instead. */ char *forward_agent_sock_path = NULL; /* socket address the host resolves to */ struct sockaddr_storage hostaddr; /* Private host keys. */ Sensitive sensitive_data; /* command to be executed */ struct sshbuf *command; /* # of replies received for global requests */ static int forward_confirms_pending = -1; /* mux.c */ extern int muxserver_sock; extern u_int muxclient_command; /* Prints a help message to the user. This function never returns. */ static void usage(void) { fprintf(stderr, "usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]\n" " [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]\n" " [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]\n" " [-i identity_file] [-J [user@]host[:port]] [-L address]\n" " [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]\n" " [-Q query_option] [-R address] [-S ctl_path] [-W host:port]\n" " [-w local_tun[:remote_tun]] destination [command [argument ...]]\n" ); exit(255); } static int ssh_session2(struct ssh *, const struct ssh_conn_info *); static void load_public_identity_files(const struct ssh_conn_info *); static void main_sigchld_handler(int); /* ~/ expand a list of paths. NB. assumes path[n] is heap-allocated. */ static void tilde_expand_paths(char **paths, u_int num_paths) { u_int i; char *cp; for (i = 0; i < num_paths; i++) { cp = tilde_expand_filename(paths[i], getuid()); free(paths[i]); paths[i] = cp; } } /* * Expands the set of percent_expand options used by the majority of keywords * in the client that support percent expansion. * Caller must free returned string. */ static char * default_client_percent_expand(const char *str, const struct ssh_conn_info *cinfo) { return percent_expand(str, DEFAULT_CLIENT_PERCENT_EXPAND_ARGS(cinfo), (char *)NULL); } /* * Expands the set of percent_expand options used by the majority of keywords * AND perform environment variable substitution. * Caller must free returned string. */ static char * default_client_percent_dollar_expand(const char *str, const struct ssh_conn_info *cinfo) { char *ret; ret = percent_dollar_expand(str, DEFAULT_CLIENT_PERCENT_EXPAND_ARGS(cinfo), (char *)NULL); if (ret == NULL) fatal("invalid environment variable expansion"); return ret; } /* * 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, int port, int logerr, char *cname, size_t clen) { char strport[NI_MAXSERV]; struct addrinfo hints, *res; int gaierr; LogLevel loglevel = SYSLOG_LEVEL_DEBUG1; if (port <= 0) port = default_ssh_port(); if (cname != NULL) *cname = '\0'; debug3_f("lookup %s:%d", name, port); snprintf(strport, sizeof strport, "%d", port); memset(&hints, 0, sizeof(hints)); 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; 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_f("host \"%s\" cname \"%s\" too long (max %lu)", name, res->ai_canonname, (u_long)clen); if (clen > 0) *cname = '\0'; } } 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. * Returns NULL on failure. * NB. this function must operate with a options having undefined members. */ static struct addrinfo * resolve_addr(const char *name, int port, char *caddr, size_t clen) { char addr[NI_MAXHOST], strport[NI_MAXSERV]; struct addrinfo hints, *res; int gaierr; if (port <= 0) port = default_ssh_port(); snprintf(strport, sizeof strport, "%u", 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 ((gaierr = getaddrinfo(name, strport, &hints, &res)) != 0) { debug2_f("could not resolve name %.100s as address: %s", name, ssh_gai_strerror(gaierr)); return NULL; } if (res == NULL) { debug_f("getaddrinfo %.100s returned no addresses", name); return NULL; } if (res->ai_next != NULL) { debug_f("getaddrinfo %.100s returned multiple addresses", name); goto fail; } if ((gaierr = getnameinfo(res->ai_addr, res->ai_addrlen, addr, sizeof(addr), NULL, 0, NI_NUMERICHOST)) != 0) { debug_f("Could not format address for name %.100s: %s", name, ssh_gai_strerror(gaierr)); goto fail; } if (strlcpy(caddr, addr, clen) >= clen) { error_f("host \"%s\" addr \"%s\" too long (max %lu)", name, addr, (u_long)clen); if (clen > 0) *caddr = '\0'; fail: freeaddrinfo(res); return NULL; } return res; } /* * 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(int direct, char **namep, const char *cname) { int i; struct allowed_cname *rule; if (*cname == '\0' || !config_has_permitted_cnames(&options) || strcmp(*namep, cname) == 0) return 0; if (options.canonicalize_hostname == SSH_CANONICALISE_NO) return 0; /* * Don't attempt to canonicalize names that will be interpreted by * a proxy or jump host unless the user specifically requests so. */ if (!direct && options.canonicalize_hostname != SSH_CANONICALISE_ALWAYS) return 0; debug3_f("check \"%s\" CNAME \"%s\"", *namep, cname); for (i = 0; i < options.num_permitted_cnames; i++) { rule = options.permitted_cnames + i; if (match_pattern_list(*namep, rule->source_list, 1) != 1 || match_pattern_list(cname, rule->target_list, 1) != 1) continue; verbose("Canonicalized 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 * 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, int port) { int i, direct, ndots; 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_f("hostname %.100s is address", *hostp); if (strcasecmp(*hostp, newname) != 0) { debug2_f("canonicalised address \"%s\" => \"%s\"", *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_f("hostname %.100s is an unrecognised address", *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_f("name is fully qualified"); fullhost = xstrdup(*hostp); if ((addrs = resolve_host(fullhost, port, 0, newname, sizeof(newname))) != NULL) goto found; free(fullhost); goto notfound; } /* Don't apply canonicalization to sufficiently-qualified hostnames */ ndots = 0; for (cp = *hostp; *cp != '\0'; cp++) { if (*cp == '.') ndots++; } if (ndots > options.canonicalize_max_dots) { debug3_f("not canonicalizing hostname \"%s\" (max dots %d)", *hostp, options.canonicalize_max_dots); return NULL; } /* Attempt each supplied suffix */ for (i = 0; i < options.num_canonical_domains; i++) { if (strcasecmp(options.canonical_domains[i], "none") == 0) break; xasprintf(&fullhost, "%s.%s.", *hostp, options.canonical_domains[i]); debug3_f("attempting \"%s\" => \"%s\"", *hostp, fullhost); if ((addrs = resolve_host(fullhost, port, 0, newname, sizeof(newname))) == NULL) { free(fullhost); continue; } found: /* Remove trailing '.' */ fullhost[strlen(fullhost) - 1] = '\0'; /* Follow CNAME if requested */ if (!check_follow_cname(direct, &fullhost, newname)) { debug("Canonicalized hostname \"%s\" => \"%s\"", *hostp, fullhost); } free(*hostp); *hostp = fullhost; return addrs; } notfound: if (!options.canonicalize_fallback_local) fatal("%s: Could not resolve host \"%s\"", __progname, *hostp); debug2_f("host %s not found in any suffix", *hostp); return NULL; } /* * Check the result of hostkey loading, ignoring some errors and * fatal()ing for others. */ static void check_load(int r, const char *path, const char *message) { switch (r) { case 0: break; case SSH_ERR_INTERNAL_ERROR: case SSH_ERR_ALLOC_FAIL: fatal_r(r, "load %s \"%s\"", message, path); case SSH_ERR_SYSTEM_ERROR: /* Ignore missing files */ if (errno == ENOENT) break; /* FALLTHROUGH */ default: error_r(r, "load %s \"%s\"", message, path); break; } } /* * 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(const char *host_name, struct passwd *pw, int final_pass, int *want_final_pass) { char buf[PATH_MAX]; int r; if (config != NULL) { if (strcasecmp(config, "none") != 0 && !read_config_file(config, pw, host, host_name, &options, SSHCONF_USERCONF | (final_pass ? SSHCONF_FINAL : 0), want_final_pass)) 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, host_name, &options, SSHCONF_CHECKPERM | SSHCONF_USERCONF | (final_pass ? SSHCONF_FINAL : 0), want_final_pass); /* Read systemwide configuration file after user config. */ (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, host, host_name, &options, final_pass ? SSHCONF_FINAL : 0, want_final_pass); } } /* 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; } } } static void ssh_conn_info_free(struct ssh_conn_info *cinfo) { if (cinfo == NULL) return; free(cinfo->conn_hash_hex); free(cinfo->shorthost); free(cinfo->uidstr); free(cinfo->keyalias); free(cinfo->thishost); free(cinfo->host_arg); free(cinfo->portstr); free(cinfo->remhost); free(cinfo->remuser); free(cinfo->homedir); free(cinfo->locuser); free(cinfo); } /* * Main program for the ssh client. */ int main(int ac, char **av) { struct ssh *ssh = NULL; int i, r, opt, exit_status, use_syslog, direct, timeout_ms; int was_addr, config_test = 0, opt_terminated = 0, want_final_pass = 0; char *p, *cp, *line, *argv0, *logfile, *host_arg; char cname[NI_MAXHOST], thishost[NI_MAXHOST]; struct stat st; struct passwd *pw; extern int optind, optreset; extern char *optarg; struct Forward fwd; struct addrinfo *addrs = NULL; size_t n, len; u_int j; struct ssh_conn_info *cinfo = NULL; /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); /* * Discard other fds that are hanging around. These can cause problem * with backgrounded ssh processes started by ControlPersist. */ closefrom(STDERR_FILENO + 1); __progname = ssh_get_progname(av[0]); #ifndef HAVE_SETPROCTITLE /* Prepare for later setproctitle emulation */ /* Save argv so it isn't clobbered by setproctitle() emulation */ saved_av = xcalloc(ac + 1, sizeof(*saved_av)); for (i = 0; i < ac; i++) saved_av[i] = xstrdup(av[i]); saved_av[i] = NULL; compat_init_setproctitle(ac, av); av = saved_av; #endif seed_rng(); /* Get user data. */ pw = getpwuid(getuid()); if (!pw) { logit("No user exists for uid %lu", (u_long)getuid()); exit(255); } /* Take a copy of the returned structure. */ pw = pwcopy(pw); /* * Set our umask to something reasonable, as some files are created * with the default umask. This will make them world-readable but * writable only by the owner, which is ok for all files for which we * don't set the modes explicitly. */ umask(022); msetlocale(); /* * Initialize option structure to indicate that no values have been * set. */ initialize_options(&options); /* * Prepare main ssh transport/connection structures */ if ((ssh = ssh_alloc_session_state()) == NULL) fatal("Couldn't allocate session state"); channel_init_channels(ssh); /* Parse command-line arguments. */ host = NULL; use_syslog = 0; logfile = NULL; argv0 = av[0]; again: while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" "AB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) { /* HUZdhjruz */ switch (opt) { case '1': fatal("SSH protocol v.1 is no longer supported"); break; case '2': /* Ignored */ break; case '4': options.address_family = AF_INET; break; case '6': options.address_family = AF_INET6; break; case 'n': options.stdin_null = 1; break; case 'f': options.fork_after_authentication = 1; options.stdin_null = 1; break; case 'x': options.forward_x11 = 0; break; case 'X': options.forward_x11 = 1; break; case 'y': use_syslog = 1; break; case 'E': logfile = optarg; break; case 'G': config_test = 1; break; case 'Y': options.forward_x11 = 1; options.forward_x11_trusted = 1; break; case 'g': options.fwd_opts.gateway_ports = 1; break; case 'O': if (options.stdio_forward_host != NULL) fatal("Cannot specify multiplexing " "command with -W"); else if (muxclient_command != 0) fatal("Multiplexing command already specified"); if (strcmp(optarg, "check") == 0) muxclient_command = SSHMUX_COMMAND_ALIVE_CHECK; else if (strcmp(optarg, "forward") == 0) muxclient_command = SSHMUX_COMMAND_FORWARD; else if (strcmp(optarg, "exit") == 0) muxclient_command = SSHMUX_COMMAND_TERMINATE; else if (strcmp(optarg, "stop") == 0) muxclient_command = SSHMUX_COMMAND_STOP; else if (strcmp(optarg, "cancel") == 0) muxclient_command = SSHMUX_COMMAND_CANCEL_FWD; else if (strcmp(optarg, "proxy") == 0) muxclient_command = SSHMUX_COMMAND_PROXY; else fatal("Invalid multiplex command."); break; case 'P': /* deprecated */ break; case 'Q': cp = NULL; if (strcmp(optarg, "cipher") == 0 || strcasecmp(optarg, "Ciphers") == 0) cp = cipher_alg_list('\n', 0); else if (strcmp(optarg, "cipher-auth") == 0) cp = cipher_alg_list('\n', 1); else if (strcmp(optarg, "mac") == 0 || strcasecmp(optarg, "MACs") == 0) cp = mac_alg_list('\n'); else if (strcmp(optarg, "kex") == 0 || strcasecmp(optarg, "KexAlgorithms") == 0) cp = kex_alg_list('\n'); else if (strcmp(optarg, "key") == 0) cp = sshkey_alg_list(0, 0, 0, '\n'); else if (strcmp(optarg, "key-cert") == 0) cp = sshkey_alg_list(1, 0, 0, '\n'); else if (strcmp(optarg, "key-plain") == 0) cp = sshkey_alg_list(0, 1, 0, '\n'); else if (strcmp(optarg, "key-sig") == 0 || strcasecmp(optarg, "PubkeyAcceptedKeyTypes") == 0 || /* deprecated name */ strcasecmp(optarg, "PubkeyAcceptedAlgorithms") == 0 || strcasecmp(optarg, "HostKeyAlgorithms") == 0 || strcasecmp(optarg, "HostbasedKeyTypes") == 0 || /* deprecated name */ strcasecmp(optarg, "HostbasedAcceptedKeyTypes") == 0 || /* deprecated name */ strcasecmp(optarg, "HostbasedAcceptedAlgorithms") == 0) cp = sshkey_alg_list(0, 0, 1, '\n'); else if (strcmp(optarg, "sig") == 0) cp = sshkey_alg_list(0, 1, 1, '\n'); else if (strcmp(optarg, "protocol-version") == 0) cp = xstrdup("2"); else if (strcmp(optarg, "compression") == 0) { cp = xstrdup(compression_alg_list(0)); len = strlen(cp); for (n = 0; n < len; n++) if (cp[n] == ',') cp[n] = '\n'; } else if (strcmp(optarg, "help") == 0) { cp = xstrdup( "cipher\ncipher-auth\ncompression\nkex\n" "key\nkey-cert\nkey-plain\nkey-sig\nmac\n" "protocol-version\nsig"); } if (cp == NULL) fatal("Unsupported query \"%s\"", optarg); printf("%s\n", cp); free(cp); exit(0); break; case 'a': options.forward_agent = 0; break; case 'A': options.forward_agent = 1; break; case 'k': options.gss_deleg_creds = 0; break; case 'K': options.gss_authentication = 1; options.gss_deleg_creds = 1; break; case 'i': p = tilde_expand_filename(optarg, getuid()); if (stat(p, &st) == -1) fprintf(stderr, "Warning: Identity file %s " "not accessible: %s.\n", p, strerror(errno)); else add_identity_file(&options, NULL, p, 1); free(p); break; case 'I': #ifdef ENABLE_PKCS11 free(options.pkcs11_provider); options.pkcs11_provider = xstrdup(optarg); #else fprintf(stderr, "no support for PKCS#11.\n"); #endif break; case 'J': if (options.jump_host != NULL) { fatal("Only a single -J option is permitted " "(use commas to separate multiple " "jump hops)"); } if (options.proxy_command != NULL) fatal("Cannot specify -J with ProxyCommand"); if (parse_jump(optarg, &options, 1) == -1) fatal("Invalid -J argument"); options.proxy_command = xstrdup("none"); break; case 't': if (options.request_tty == REQUEST_TTY_YES) options.request_tty = REQUEST_TTY_FORCE; else options.request_tty = REQUEST_TTY_YES; break; case 'v': if (debug_flag == 0) { debug_flag = 1; options.log_level = SYSLOG_LEVEL_DEBUG1; } else { if (options.log_level < SYSLOG_LEVEL_DEBUG3) { debug_flag++; options.log_level++; } } break; case 'V': - if (options.version_addendum != NULL && - *options.version_addendum != '\0') - fprintf(stderr, "%s %s, %s\n", SSH_RELEASE, - options.version_addendum, - SSH_OPENSSL_VERSION); - else - fprintf(stderr, "%s, %s\n", SSH_RELEASE, - SSH_OPENSSL_VERSION); + fprintf(stderr, "%s, %s\n", + SSH_RELEASE, SSH_OPENSSL_VERSION); if (opt == 'V') exit(0); break; case 'w': if (options.tun_open == -1) options.tun_open = SSH_TUNMODE_DEFAULT; options.tun_local = a2tun(optarg, &options.tun_remote); if (options.tun_local == SSH_TUNID_ERR) { fprintf(stderr, "Bad tun device '%s'\n", optarg); exit(255); } break; case 'W': if (options.stdio_forward_host != NULL) fatal("stdio forward already specified"); if (muxclient_command != 0) fatal("Cannot specify stdio forward with -O"); if (parse_forward(&fwd, optarg, 1, 0)) { options.stdio_forward_host = fwd.listen_host; options.stdio_forward_port = fwd.listen_port; free(fwd.connect_host); } else { fprintf(stderr, "Bad stdio forwarding specification '%s'\n", optarg); exit(255); } options.request_tty = REQUEST_TTY_NO; options.session_type = SESSION_TYPE_NONE; break; case 'q': options.log_level = SYSLOG_LEVEL_QUIET; break; case 'e': if (optarg[0] == '^' && optarg[2] == 0 && (u_char) optarg[1] >= 64 && (u_char) optarg[1] < 128) options.escape_char = (u_char) optarg[1] & 31; else if (strlen(optarg) == 1) options.escape_char = (u_char) optarg[0]; else if (strcmp(optarg, "none") == 0) options.escape_char = SSH_ESCAPECHAR_NONE; else { fprintf(stderr, "Bad escape character '%s'.\n", optarg); exit(255); } break; case 'c': if (!ciphers_valid(*optarg == '+' || *optarg == '^' ? optarg + 1 : optarg)) { fprintf(stderr, "Unknown cipher type '%s'\n", optarg); exit(255); } free(options.ciphers); options.ciphers = xstrdup(optarg); break; case 'm': if (mac_valid(optarg)) { free(options.macs); options.macs = xstrdup(optarg); } else { fprintf(stderr, "Unknown mac type '%s'\n", optarg); exit(255); } break; case 'M': if (options.control_master == SSHCTL_MASTER_YES) options.control_master = SSHCTL_MASTER_ASK; else options.control_master = SSHCTL_MASTER_YES; break; case 'p': if (options.port == -1) { options.port = a2port(optarg); if (options.port <= 0) { fprintf(stderr, "Bad port '%s'\n", optarg); exit(255); } } break; case 'l': if (options.user == NULL) options.user = optarg; break; case 'L': if (parse_forward(&fwd, optarg, 0, 0)) add_local_forward(&options, &fwd); else { fprintf(stderr, "Bad local forwarding specification '%s'\n", optarg); exit(255); } break; case 'R': if (parse_forward(&fwd, optarg, 0, 1) || parse_forward(&fwd, optarg, 1, 1)) { add_remote_forward(&options, &fwd); } else { fprintf(stderr, "Bad remote forwarding specification " "'%s'\n", optarg); exit(255); } break; case 'D': if (parse_forward(&fwd, optarg, 1, 0)) { add_local_forward(&options, &fwd); } else { fprintf(stderr, "Bad dynamic forwarding specification " "'%s'\n", optarg); exit(255); } break; case 'C': #ifdef WITH_ZLIB options.compression = 1; #else error("Compression not supported, disabling."); #endif break; case 'N': if (options.session_type != -1 && options.session_type != SESSION_TYPE_NONE) fatal("Cannot specify -N with -s/SessionType"); options.session_type = SESSION_TYPE_NONE; options.request_tty = REQUEST_TTY_NO; break; case 'T': options.request_tty = REQUEST_TTY_NO; break; case 'o': line = xstrdup(optarg); if (process_config_line(&options, pw, host ? host : "", host ? host : "", line, "command-line", 0, NULL, SSHCONF_USERCONF) != 0) exit(255); free(line); break; case 's': if (options.session_type != -1 && options.session_type != SESSION_TYPE_SUBSYSTEM) fatal("Cannot specify -s with -N/SessionType"); options.session_type = SESSION_TYPE_SUBSYSTEM; break; case 'S': free(options.control_path); options.control_path = xstrdup(optarg); break; case 'b': options.bind_address = optarg; break; case 'B': options.bind_interface = optarg; break; case 'F': config = optarg; break; default: usage(); } } if (optind > 1 && strcmp(av[optind - 1], "--") == 0) opt_terminated = 1; ac -= optind; av += optind; if (ac > 0 && !host) { int tport; char *tuser; switch (parse_ssh_uri(*av, &tuser, &host, &tport)) { case -1: usage(); break; case 0: if (options.user == NULL) { options.user = tuser; tuser = NULL; } free(tuser); if (options.port == -1 && tport != -1) options.port = tport; break; default: p = xstrdup(*av); cp = strrchr(p, '@'); if (cp != NULL) { if (cp == p) usage(); if (options.user == NULL) { options.user = p; p = NULL; } *cp++ = '\0'; host = xstrdup(cp); free(p); } else host = p; break; } if (ac > 1 && !opt_terminated) { optind = optreset = 1; goto again; } ac--, av++; } /* Check that we got a host name. */ if (!host) usage(); host_arg = xstrdup(host); /* Initialize the command to execute on remote host. */ if ((command = sshbuf_new()) == NULL) fatal("sshbuf_new failed"); /* * Save the command to execute on the remote host in a buffer. There * is no limit on the length of the command, except by the maximum * packet size. Also sets the tty flag if there is no command. */ if (!ac) { /* No command specified - execute shell on a tty. */ if (options.session_type == SESSION_TYPE_SUBSYSTEM) { fprintf(stderr, "You must specify a subsystem to invoke.\n"); usage(); } } else { /* A command has been specified. Store it into the buffer. */ for (i = 0; i < ac; i++) { if ((r = sshbuf_putf(command, "%s%s", i ? " " : "", av[i])) != 0) fatal_fr(r, "buffer error"); } } /* * Initialize "log" output. Since we are the client all output * goes to stderr unless otherwise specified by -y or -E. */ if (use_syslog && logfile != NULL) fatal("Can't specify both -y and -E"); if (logfile != NULL) log_redirect_stderr_to(logfile); log_init(argv0, options.log_level == SYSLOG_LEVEL_NOT_SET ? SYSLOG_LEVEL_INFO : options.log_level, options.log_facility == SYSLOG_FACILITY_NOT_SET ? SYSLOG_FACILITY_USER : options.log_facility, !use_syslog); if (debug_flag) - /* version_addendum is always NULL at this point */ logit("%s, %s", SSH_RELEASE, SSH_OPENSSL_VERSION); /* Parse the configuration files */ process_config_files(host_arg, pw, 0, &want_final_pass); if (want_final_pass) debug("configuration requests final Match pass"); /* 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; free(options.hostname); options.hostname = xstrdup(host); } /* 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); /* * If CanonicalizePermittedCNAMEs have been specified but * other canonicalization did not happen (by not being requested * or by failing with fallback) then the hostname may still be changed * as a result of CNAME following. * * Try to resolve the bare hostname name using the system resolver's * usual search rules and then apply the CNAME follow rules. * * Skip the lookup if a ProxyCommand is being used unless the user * has specifically requested canonicalisation for this case via * CanonicalizeHostname=always */ direct = option_clear_or_none(options.proxy_command) && options.jump_host == NULL; if (addrs == NULL && config_has_permitted_cnames(&options) && (direct || options.canonicalize_hostname == SSH_CANONICALISE_ALWAYS)) { if ((addrs = resolve_host(host, options.port, direct, cname, sizeof(cname))) == NULL) { /* Don't fatal proxied host names not in the DNS */ if (direct) cleanup_exit(255); /* logged in resolve_host */ } else check_follow_cname(direct, &host, cname); } /* * If canonicalisation is enabled then re-parse the configuration * files as new stanzas may match. */ if (options.canonicalize_hostname != 0 && !want_final_pass) { debug("hostname canonicalisation enabled, " "will re-parse configuration"); want_final_pass = 1; } if (want_final_pass) { debug("re-parsing configuration"); free(options.hostname); options.hostname = xstrdup(host); process_config_files(host_arg, pw, 1, NULL); /* * 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. */ if (fill_default_options(&options) != 0) cleanup_exit(255); if (options.user == NULL) options.user = xstrdup(pw->pw_name); /* * If ProxyJump option specified, then construct a ProxyCommand now. */ if (options.jump_host != NULL) { char port_s[8]; const char *jumpuser = options.jump_user, *sshbin = argv0; int port = options.port, jumpport = options.jump_port; if (port <= 0) port = default_ssh_port(); if (jumpport <= 0) jumpport = default_ssh_port(); if (jumpuser == NULL) jumpuser = options.user; if (strcmp(options.jump_host, host) == 0 && port == jumpport && strcmp(options.user, jumpuser) == 0) fatal("jumphost loop via %s", options.jump_host); /* * Try to use SSH indicated by argv[0], but fall back to * "ssh" if it appears unavailable. */ if (strchr(argv0, '/') != NULL && access(argv0, X_OK) != 0) sshbin = "ssh"; /* Consistency check */ if (options.proxy_command != NULL) fatal("inconsistent options: ProxyCommand+ProxyJump"); /* Never use FD passing for ProxyJump */ options.proxy_use_fdpass = 0; snprintf(port_s, sizeof(port_s), "%d", options.jump_port); xasprintf(&options.proxy_command, "%s%s%s%s%s%s%s%s%s%s%.*s -W '[%%h]:%%p' %s", sshbin, /* Optional "-l user" argument if jump_user set */ options.jump_user == NULL ? "" : " -l ", options.jump_user == NULL ? "" : options.jump_user, /* Optional "-p port" argument if jump_port set */ options.jump_port <= 0 ? "" : " -p ", options.jump_port <= 0 ? "" : port_s, /* Optional additional jump hosts ",..." */ options.jump_extra == NULL ? "" : " -J ", options.jump_extra == NULL ? "" : options.jump_extra, /* Optional "-F" argument if -F specified */ config == NULL ? "" : " -F ", config == NULL ? "" : config, /* Optional "-v" arguments if -v set */ debug_flag ? " -" : "", debug_flag, "vvv", /* Mandatory hostname */ options.jump_host); debug("Setting implicit ProxyCommand from ProxyJump: %s", options.proxy_command); } if (options.port == 0) options.port = default_ssh_port(); channel_set_af(ssh, options.address_family); /* Tidy and check options */ if (options.host_key_alias != NULL) lowercase(options.host_key_alias); if (options.proxy_command != NULL && strcmp(options.proxy_command, "-") == 0 && options.proxy_use_fdpass) fatal("ProxyCommand=- and ProxyUseFDPass are incompatible"); if (options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK) { if (options.control_persist && options.control_path != NULL) { debug("UpdateHostKeys=ask is incompatible with " "ControlPersist; disabling"); options.update_hostkeys = 0; } else if (sshbuf_len(command) != 0 || options.remote_command != NULL || options.request_tty == REQUEST_TTY_NO) { debug("UpdateHostKeys=ask is incompatible with " "remote command execution; disabling"); options.update_hostkeys = 0; } else if (options.log_level < SYSLOG_LEVEL_INFO) { /* no point logging anything; user won't see it */ options.update_hostkeys = 0; } } if (options.connection_attempts <= 0) fatal("Invalid number of ConnectionAttempts"); if (sshbuf_len(command) != 0 && options.remote_command != NULL) fatal("Cannot execute command-line and remote command."); /* Cannot fork to background if no command. */ if (options.fork_after_authentication && sshbuf_len(command) == 0 && options.remote_command == NULL && options.session_type != SESSION_TYPE_NONE) fatal("Cannot fork into background without a command " "to execute."); /* reinit */ log_init(argv0, options.log_level, options.log_facility, !use_syslog); for (j = 0; j < options.num_log_verbose; j++) { if (strcasecmp(options.log_verbose[j], "none") == 0) break; log_verbose_add(options.log_verbose[j]); } if (options.request_tty == REQUEST_TTY_YES || options.request_tty == REQUEST_TTY_FORCE) tty_flag = 1; /* Allocate a tty by default if no command specified. */ if (sshbuf_len(command) == 0 && options.remote_command == NULL) tty_flag = options.request_tty != REQUEST_TTY_NO; /* Force no tty */ if (options.request_tty == REQUEST_TTY_NO || (muxclient_command && muxclient_command != SSHMUX_COMMAND_PROXY) || options.session_type == SESSION_TYPE_NONE) tty_flag = 0; /* Do not allocate a tty if stdin is not a tty. */ if ((!isatty(fileno(stdin)) || options.stdin_null) && options.request_tty != REQUEST_TTY_FORCE) { if (tty_flag) logit("Pseudo-terminal will not be allocated because " "stdin is not a terminal."); tty_flag = 0; } /* Set up strings used to percent_expand() arguments */ cinfo = xcalloc(1, sizeof(*cinfo)); if (gethostname(thishost, sizeof(thishost)) == -1) fatal("gethostname: %s", strerror(errno)); cinfo->thishost = xstrdup(thishost); thishost[strcspn(thishost, ".")] = '\0'; cinfo->shorthost = xstrdup(thishost); xasprintf(&cinfo->portstr, "%d", options.port); xasprintf(&cinfo->uidstr, "%llu", (unsigned long long)pw->pw_uid); cinfo->keyalias = xstrdup(options.host_key_alias ? options.host_key_alias : host_arg); cinfo->conn_hash_hex = ssh_connection_hash(cinfo->thishost, host, cinfo->portstr, options.user); cinfo->host_arg = xstrdup(host_arg); cinfo->remhost = xstrdup(host); cinfo->remuser = xstrdup(options.user); cinfo->homedir = xstrdup(pw->pw_dir); cinfo->locuser = xstrdup(pw->pw_name); /* Find canonic host name. */ if (strchr(host, '.') == 0) { struct addrinfo hints; struct addrinfo *ai = NULL; int errgai; memset(&hints, 0, sizeof(hints)); hints.ai_family = options.address_family; hints.ai_flags = AI_CANONNAME; hints.ai_socktype = SOCK_STREAM; errgai = getaddrinfo(host, NULL, &hints, &ai); if (errgai == 0) { if (ai->ai_canonname != NULL) host = xstrdup(ai->ai_canonname); freeaddrinfo(ai); } } /* * Expand tokens in arguments. NB. LocalCommand is expanded later, * after port-forwarding is set up, so it may pick up any local * tunnel interface name allocated. */ if (options.remote_command != NULL) { debug3("expanding RemoteCommand: %s", options.remote_command); cp = options.remote_command; options.remote_command = default_client_percent_expand(cp, cinfo); debug3("expanded RemoteCommand: %s", options.remote_command); free(cp); if ((r = sshbuf_put(command, options.remote_command, strlen(options.remote_command))) != 0) fatal_fr(r, "buffer error"); } if (options.control_path != NULL) { cp = tilde_expand_filename(options.control_path, getuid()); free(options.control_path); options.control_path = default_client_percent_dollar_expand(cp, cinfo); free(cp); } if (options.identity_agent != NULL) { p = tilde_expand_filename(options.identity_agent, getuid()); cp = default_client_percent_dollar_expand(p, cinfo); free(p); free(options.identity_agent); options.identity_agent = cp; } if (options.forward_agent_sock_path != NULL) { p = tilde_expand_filename(options.forward_agent_sock_path, getuid()); cp = default_client_percent_dollar_expand(p, cinfo); free(p); free(options.forward_agent_sock_path); options.forward_agent_sock_path = cp; if (stat(options.forward_agent_sock_path, &st) != 0) { error("Cannot forward agent socket path \"%s\": %s", options.forward_agent_sock_path, strerror(errno)); if (options.exit_on_forward_failure) cleanup_exit(255); } } if (options.num_system_hostfiles > 0 && strcasecmp(options.system_hostfiles[0], "none") == 0) { if (options.num_system_hostfiles > 1) fatal("Invalid GlobalKnownHostsFiles: \"none\" " "appears with other entries"); free(options.system_hostfiles[0]); options.system_hostfiles[0] = NULL; options.num_system_hostfiles = 0; } if (options.num_user_hostfiles > 0 && strcasecmp(options.user_hostfiles[0], "none") == 0) { if (options.num_user_hostfiles > 1) fatal("Invalid UserKnownHostsFiles: \"none\" " "appears with other entries"); free(options.user_hostfiles[0]); options.user_hostfiles[0] = NULL; options.num_user_hostfiles = 0; } for (j = 0; j < options.num_user_hostfiles; j++) { if (options.user_hostfiles[j] == NULL) continue; cp = tilde_expand_filename(options.user_hostfiles[j], getuid()); p = default_client_percent_dollar_expand(cp, cinfo); if (strcmp(options.user_hostfiles[j], p) != 0) debug3("expanded UserKnownHostsFile '%s' -> " "'%s'", options.user_hostfiles[j], p); free(options.user_hostfiles[j]); free(cp); options.user_hostfiles[j] = p; } for (i = 0; i < options.num_local_forwards; i++) { if (options.local_forwards[i].listen_path != NULL) { cp = options.local_forwards[i].listen_path; p = options.local_forwards[i].listen_path = default_client_percent_expand(cp, cinfo); if (strcmp(cp, p) != 0) debug3("expanded LocalForward listen path " "'%s' -> '%s'", cp, p); free(cp); } if (options.local_forwards[i].connect_path != NULL) { cp = options.local_forwards[i].connect_path; p = options.local_forwards[i].connect_path = default_client_percent_expand(cp, cinfo); if (strcmp(cp, p) != 0) debug3("expanded LocalForward connect path " "'%s' -> '%s'", cp, p); free(cp); } } for (i = 0; i < options.num_remote_forwards; i++) { if (options.remote_forwards[i].listen_path != NULL) { cp = options.remote_forwards[i].listen_path; p = options.remote_forwards[i].listen_path = default_client_percent_expand(cp, cinfo); if (strcmp(cp, p) != 0) debug3("expanded RemoteForward listen path " "'%s' -> '%s'", cp, p); free(cp); } if (options.remote_forwards[i].connect_path != NULL) { cp = options.remote_forwards[i].connect_path; p = options.remote_forwards[i].connect_path = default_client_percent_expand(cp, cinfo); if (strcmp(cp, p) != 0) debug3("expanded RemoteForward connect path " "'%s' -> '%s'", cp, p); free(cp); } } if (config_test) { dump_client_config(&options, host); exit(0); } /* Expand SecurityKeyProvider if it refers to an environment variable */ if (options.sk_provider != NULL && *options.sk_provider == '$' && strlen(options.sk_provider) > 1) { if ((cp = getenv(options.sk_provider + 1)) == NULL) { debug("Authenticator provider %s did not resolve; " "disabling", options.sk_provider); free(options.sk_provider); options.sk_provider = NULL; } else { debug2("resolved SecurityKeyProvider %s => %s", options.sk_provider, cp); free(options.sk_provider); options.sk_provider = xstrdup(cp); } } if (muxclient_command != 0 && options.control_path == NULL) fatal("No ControlPath specified for \"-O\" command"); if (options.control_path != NULL) { int sock; if ((sock = muxclient(options.control_path)) >= 0) { ssh_packet_set_connection(ssh, sock, sock); ssh_packet_set_mux(ssh); goto skip_connect; } } /* * If hostname canonicalisation was not enabled, then we may not * have yet resolved the hostname. Do so now. */ if (addrs == NULL && options.proxy_command == NULL) { debug2("resolving \"%s\" port %d", host, options.port); if ((addrs = resolve_host(host, options.port, 1, cname, sizeof(cname))) == NULL) cleanup_exit(255); /* resolve_host logs the error */ } if (options.connection_timeout >= INT_MAX/1000) timeout_ms = INT_MAX; else timeout_ms = options.connection_timeout * 1000; /* Open a connection to the remote host. */ if (ssh_connect(ssh, host, host_arg, addrs, &hostaddr, options.port, options.connection_attempts, &timeout_ms, options.tcp_keep_alive) != 0) exit(255); if (addrs != NULL) freeaddrinfo(addrs); ssh_packet_set_timeout(ssh, options.server_alive_interval, options.server_alive_count_max); if (timeout_ms > 0) debug3("timeout: %d ms remain after connect", timeout_ms); /* * If we successfully made the connection and we have hostbased auth * enabled, load the public keys so we can later use the ssh-keysign * helper to sign challenges. */ sensitive_data.nkeys = 0; sensitive_data.keys = NULL; if (options.hostbased_authentication) { sensitive_data.nkeys = 10; sensitive_data.keys = xcalloc(sensitive_data.nkeys, sizeof(struct sshkey)); /* XXX check errors? */ #define L_PUBKEY(p,o) do { \ if ((o) >= sensitive_data.nkeys) \ fatal_f("pubkey out of array bounds"); \ check_load(sshkey_load_public(p, &(sensitive_data.keys[o]), NULL), \ p, "pubkey"); \ if (sensitive_data.keys[o] != NULL) \ debug2("hostbased key %d: %s key from \"%s\"", o, \ sshkey_ssh_name(sensitive_data.keys[o]), p); \ } while (0) #define L_CERT(p,o) do { \ if ((o) >= sensitive_data.nkeys) \ fatal_f("cert out of array bounds"); \ check_load(sshkey_load_cert(p, &(sensitive_data.keys[o])), p, "cert"); \ if (sensitive_data.keys[o] != NULL) \ debug2("hostbased key %d: %s cert from \"%s\"", o, \ sshkey_ssh_name(sensitive_data.keys[o]), p); \ } while (0) if (options.hostbased_authentication == 1) { L_CERT(_PATH_HOST_ECDSA_KEY_FILE, 0); L_CERT(_PATH_HOST_ED25519_KEY_FILE, 1); L_CERT(_PATH_HOST_RSA_KEY_FILE, 2); L_CERT(_PATH_HOST_DSA_KEY_FILE, 3); L_PUBKEY(_PATH_HOST_ECDSA_KEY_FILE, 4); L_PUBKEY(_PATH_HOST_ED25519_KEY_FILE, 5); L_PUBKEY(_PATH_HOST_RSA_KEY_FILE, 6); L_PUBKEY(_PATH_HOST_DSA_KEY_FILE, 7); L_CERT(_PATH_HOST_XMSS_KEY_FILE, 8); L_PUBKEY(_PATH_HOST_XMSS_KEY_FILE, 9); } } /* load options.identity_files */ load_public_identity_files(cinfo); /* optionally set the SSH_AUTHSOCKET_ENV_NAME variable */ if (options.identity_agent && strcmp(options.identity_agent, SSH_AUTHSOCKET_ENV_NAME) != 0) { if (strcmp(options.identity_agent, "none") == 0) { unsetenv(SSH_AUTHSOCKET_ENV_NAME); } else { cp = options.identity_agent; /* legacy (limited) format */ if (cp[0] == '$' && cp[1] != '{') { if (!valid_env_name(cp + 1)) { fatal("Invalid IdentityAgent " "environment variable name %s", cp); } if ((p = getenv(cp + 1)) == NULL) unsetenv(SSH_AUTHSOCKET_ENV_NAME); else setenv(SSH_AUTHSOCKET_ENV_NAME, p, 1); } else { /* identity_agent specifies a path directly */ setenv(SSH_AUTHSOCKET_ENV_NAME, cp, 1); } } } if (options.forward_agent && options.forward_agent_sock_path != NULL) { cp = options.forward_agent_sock_path; if (cp[0] == '$') { if (!valid_env_name(cp + 1)) { fatal("Invalid ForwardAgent environment variable name %s", cp); } if ((p = getenv(cp + 1)) != NULL) forward_agent_sock_path = xstrdup(p); else options.forward_agent = 0; free(cp); } else { forward_agent_sock_path = cp; } } /* Expand ~ in known host file names. */ tilde_expand_paths(options.system_hostfiles, options.num_system_hostfiles); tilde_expand_paths(options.user_hostfiles, options.num_user_hostfiles); ssh_signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE early */ ssh_signal(SIGCHLD, main_sigchld_handler); /* Log into the remote system. Never returns if the login fails. */ ssh_login(ssh, &sensitive_data, host, (struct sockaddr *)&hostaddr, options.port, pw, timeout_ms, cinfo); /* We no longer need the private host keys. Clear them now. */ if (sensitive_data.nkeys != 0) { for (i = 0; i < sensitive_data.nkeys; i++) { if (sensitive_data.keys[i] != NULL) { /* Destroys contents safely */ debug3("clear hostkey %d", i); sshkey_free(sensitive_data.keys[i]); sensitive_data.keys[i] = NULL; } } free(sensitive_data.keys); } for (i = 0; i < options.num_identity_files; i++) { free(options.identity_files[i]); options.identity_files[i] = NULL; if (options.identity_keys[i]) { sshkey_free(options.identity_keys[i]); options.identity_keys[i] = NULL; } } for (i = 0; i < options.num_certificate_files; i++) { free(options.certificate_files[i]); options.certificate_files[i] = NULL; } #ifdef ENABLE_PKCS11 (void)pkcs11_del_provider(options.pkcs11_provider); #endif skip_connect: exit_status = ssh_session2(ssh, cinfo); ssh_conn_info_free(cinfo); ssh_packet_close(ssh); if (options.control_path != NULL && muxserver_sock != -1) unlink(options.control_path); /* Kill ProxyCommand if it is running. */ ssh_kill_proxy_command(); return exit_status; } static void control_persist_detach(void) { pid_t pid; debug_f("backgrounding master process"); /* * master (current process) into the background, and make the * foreground process a client of the backgrounded master. */ switch ((pid = fork())) { case -1: fatal_f("fork: %s", strerror(errno)); case 0: /* Child: master process continues mainloop */ break; default: /* Parent: set up mux client to connect to backgrounded master */ debug2_f("background process is %ld", (long)pid); options.stdin_null = ostdin_null_flag; options.request_tty = orequest_tty; tty_flag = otty_flag; options.session_type = osession_type; close(muxserver_sock); muxserver_sock = -1; options.control_master = SSHCTL_MASTER_NO; muxclient(options.control_path); /* muxclient() doesn't return on success. */ fatal("Failed to connect to new control master"); } if (stdfd_devnull(1, 1, !(log_is_on_stderr() && debug_flag)) == -1) error_f("stdfd_devnull failed"); daemon(1, 1); setproctitle("%s [mux]", options.control_path); } /* Do fork() after authentication. Used by "ssh -f" */ static void fork_postauth(void) { if (need_controlpersist_detach) control_persist_detach(); debug("forking to background"); options.fork_after_authentication = 0; if (daemon(1, 1) == -1) fatal("daemon() failed: %.200s", strerror(errno)); if (stdfd_devnull(1, 1, !(log_is_on_stderr() && debug_flag)) == -1) error_f("stdfd_devnull failed"); } static void forwarding_success(void) { if (forward_confirms_pending == -1) return; if (--forward_confirms_pending == 0) { debug_f("all expected forwarding replies received"); if (options.fork_after_authentication) fork_postauth(); } else { debug2_f("%d expected forwarding replies remaining", forward_confirms_pending); } } /* Callback for remote forward global requests */ static void ssh_confirm_remote_forward(struct ssh *ssh, int type, u_int32_t seq, void *ctxt) { struct Forward *rfwd = (struct Forward *)ctxt; u_int port; int r; /* XXX verbose() on failure? */ debug("remote forward %s for: listen %s%s%d, connect %s:%d", type == SSH2_MSG_REQUEST_SUCCESS ? "success" : "failure", rfwd->listen_path ? rfwd->listen_path : rfwd->listen_host ? rfwd->listen_host : "", (rfwd->listen_path || rfwd->listen_host) ? ":" : "", rfwd->listen_port, rfwd->connect_path ? rfwd->connect_path : rfwd->connect_host, rfwd->connect_port); if (rfwd->listen_path == NULL && rfwd->listen_port == 0) { if (type == SSH2_MSG_REQUEST_SUCCESS) { if ((r = sshpkt_get_u32(ssh, &port)) != 0) fatal_fr(r, "parse packet"); if (port > 65535) { error("Invalid allocated port %u for remote " "forward to %s:%d", port, rfwd->connect_host, rfwd->connect_port); /* Ensure failure processing runs below */ type = SSH2_MSG_REQUEST_FAILURE; channel_update_permission(ssh, rfwd->handle, -1); } else { rfwd->allocated_port = (int)port; logit("Allocated port %u for remote " "forward to %s:%d", rfwd->allocated_port, rfwd->connect_path ? rfwd->connect_path : rfwd->connect_host, rfwd->connect_port); channel_update_permission(ssh, rfwd->handle, rfwd->allocated_port); } } else { channel_update_permission(ssh, rfwd->handle, -1); } } if (type == SSH2_MSG_REQUEST_FAILURE) { if (options.exit_on_forward_failure) { if (rfwd->listen_path != NULL) fatal("Error: remote port forwarding failed " "for listen path %s", rfwd->listen_path); else fatal("Error: remote port forwarding failed " "for listen port %d", rfwd->listen_port); } else { if (rfwd->listen_path != NULL) logit("Warning: remote port forwarding failed " "for listen path %s", rfwd->listen_path); else logit("Warning: remote port forwarding failed " "for listen port %d", rfwd->listen_port); } } forwarding_success(); } static void client_cleanup_stdio_fwd(struct ssh *ssh, int id, void *arg) { debug("stdio forwarding: done"); cleanup_exit(0); } static void ssh_stdio_confirm(struct ssh *ssh, int id, int success, void *arg) { if (!success) fatal("stdio forwarding failed"); } static void ssh_tun_confirm(struct ssh *ssh, int id, int success, void *arg) { if (!success) { error("Tunnel forwarding failed"); if (options.exit_on_forward_failure) cleanup_exit(255); } debug_f("tunnel forward established, id=%d", id); forwarding_success(); } static void ssh_init_stdio_forwarding(struct ssh *ssh) { Channel *c; int in, out; if (options.stdio_forward_host == NULL) return; debug3_f("%s:%d", options.stdio_forward_host, options.stdio_forward_port); if ((in = dup(STDIN_FILENO)) == -1 || (out = dup(STDOUT_FILENO)) == -1) fatal_f("dup() in/out failed"); if ((c = channel_connect_stdio_fwd(ssh, options.stdio_forward_host, options.stdio_forward_port, in, out, CHANNEL_NONBLOCK_STDIO)) == NULL) fatal_f("channel_connect_stdio_fwd failed"); channel_register_cleanup(ssh, c->self, client_cleanup_stdio_fwd, 0); channel_register_open_confirm(ssh, c->self, ssh_stdio_confirm, NULL); } static void ssh_init_forward_permissions(struct ssh *ssh, const char *what, char **opens, u_int num_opens) { u_int i; int port; char *addr, *arg, *oarg; int where = FORWARD_LOCAL; channel_clear_permission(ssh, FORWARD_ADM, where); if (num_opens == 0) return; /* permit any */ /* handle keywords: "any" / "none" */ if (num_opens == 1 && strcmp(opens[0], "any") == 0) return; if (num_opens == 1 && strcmp(opens[0], "none") == 0) { channel_disable_admin(ssh, where); return; } /* Otherwise treat it as a list of permitted host:port */ for (i = 0; i < num_opens; i++) { oarg = arg = xstrdup(opens[i]); addr = hpdelim(&arg); if (addr == NULL) fatal_f("missing host in %s", what); addr = cleanhostname(addr); if (arg == NULL || ((port = permitopen_port(arg)) < 0)) fatal_f("bad port number in %s", what); /* Send it to channels layer */ channel_add_permission(ssh, FORWARD_ADM, where, addr, port); free(oarg); } } static void ssh_init_forwarding(struct ssh *ssh, char **ifname) { int success = 0; int i; ssh_init_forward_permissions(ssh, "permitremoteopen", options.permitted_remote_opens, options.num_permitted_remote_opens); if (options.exit_on_forward_failure) forward_confirms_pending = 0; /* track pending requests */ /* Initiate local TCP/IP port forwardings. */ for (i = 0; i < options.num_local_forwards; i++) { debug("Local connections to %.200s:%d forwarded to remote " "address %.200s:%d", (options.local_forwards[i].listen_path != NULL) ? options.local_forwards[i].listen_path : (options.local_forwards[i].listen_host == NULL) ? (options.fwd_opts.gateway_ports ? "*" : "LOCALHOST") : options.local_forwards[i].listen_host, options.local_forwards[i].listen_port, (options.local_forwards[i].connect_path != NULL) ? options.local_forwards[i].connect_path : options.local_forwards[i].connect_host, options.local_forwards[i].connect_port); success += channel_setup_local_fwd_listener(ssh, &options.local_forwards[i], &options.fwd_opts); } if (i > 0 && success != i && options.exit_on_forward_failure) fatal("Could not request local forwarding."); if (i > 0 && success == 0) error("Could not request local forwarding."); /* Initiate remote TCP/IP port forwardings. */ for (i = 0; i < options.num_remote_forwards; i++) { debug("Remote connections from %.200s:%d forwarded to " "local address %.200s:%d", (options.remote_forwards[i].listen_path != NULL) ? options.remote_forwards[i].listen_path : (options.remote_forwards[i].listen_host == NULL) ? "LOCALHOST" : options.remote_forwards[i].listen_host, options.remote_forwards[i].listen_port, (options.remote_forwards[i].connect_path != NULL) ? options.remote_forwards[i].connect_path : options.remote_forwards[i].connect_host, options.remote_forwards[i].connect_port); if ((options.remote_forwards[i].handle = channel_request_remote_forwarding(ssh, &options.remote_forwards[i])) >= 0) { client_register_global_confirm( ssh_confirm_remote_forward, &options.remote_forwards[i]); forward_confirms_pending++; } else if (options.exit_on_forward_failure) fatal("Could not request remote forwarding."); else logit("Warning: Could not request remote forwarding."); } /* Initiate tunnel forwarding. */ if (options.tun_open != SSH_TUNMODE_NO) { if ((*ifname = client_request_tun_fwd(ssh, options.tun_open, options.tun_local, options.tun_remote, ssh_tun_confirm, NULL)) != NULL) forward_confirms_pending++; else if (options.exit_on_forward_failure) fatal("Could not request tunnel forwarding."); else error("Could not request tunnel forwarding."); } if (forward_confirms_pending > 0) { debug_f("expecting replies for %d forwards", forward_confirms_pending); } } static void check_agent_present(void) { int r; if (options.forward_agent) { /* Clear agent forwarding if we don't have an agent. */ if ((r = ssh_get_authentication_socket(NULL)) != 0) { options.forward_agent = 0; if (r != SSH_ERR_AGENT_NOT_PRESENT) debug_r(r, "ssh_get_authentication_socket"); } } } static void ssh_session2_setup(struct ssh *ssh, int id, int success, void *arg) { extern char **environ; const char *display, *term; int r, interactive = tty_flag; char *proto = NULL, *data = NULL; if (!success) return; /* No need for error message, channels code sens one */ display = getenv("DISPLAY"); if (display == NULL && options.forward_x11) debug("X11 forwarding requested but DISPLAY not set"); if (options.forward_x11 && client_x11_get_proto(ssh, display, options.xauth_location, options.forward_x11_trusted, options.forward_x11_timeout, &proto, &data) == 0) { /* Request forwarding with authentication spoofing. */ debug("Requesting X11 forwarding with authentication " "spoofing."); x11_request_forwarding_with_spoofing(ssh, id, display, proto, data, 1); client_expect_confirm(ssh, id, "X11 forwarding", CONFIRM_WARN); /* XXX exit_on_forward_failure */ interactive = 1; } check_agent_present(); if (options.forward_agent) { debug("Requesting authentication agent forwarding."); channel_request_start(ssh, id, "auth-agent-req@openssh.com", 0); if ((r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send packet"); } /* Tell the packet module whether this is an interactive session. */ ssh_packet_set_interactive(ssh, interactive, options.ip_qos_interactive, options.ip_qos_bulk); if ((term = lookup_env_in_list("TERM", options.setenv, options.num_setenv)) == NULL || *term == '\0') term = getenv("TERM"); client_session2_setup(ssh, id, tty_flag, options.session_type == SESSION_TYPE_SUBSYSTEM, term, NULL, fileno(stdin), command, environ); } /* open new channel for a session */ static int ssh_session2_open(struct ssh *ssh) { Channel *c; int window, packetmax, in, out, err; if (options.stdin_null) { in = open(_PATH_DEVNULL, O_RDONLY); } else { in = dup(STDIN_FILENO); } out = dup(STDOUT_FILENO); err = dup(STDERR_FILENO); if (in == -1 || out == -1 || err == -1) fatal("dup() in/out/err failed"); window = CHAN_SES_WINDOW_DEFAULT; packetmax = CHAN_SES_PACKET_DEFAULT; if (tty_flag) { window >>= 1; packetmax >>= 1; } c = channel_new(ssh, "session", SSH_CHANNEL_OPENING, in, out, err, window, packetmax, CHAN_EXTENDED_WRITE, "client-session", CHANNEL_NONBLOCK_STDIO); debug3_f("channel_new: %d", c->self); channel_send_open(ssh, c->self); if (options.session_type != SESSION_TYPE_NONE) channel_register_open_confirm(ssh, c->self, ssh_session2_setup, NULL); return c->self; } static int ssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo) { int r, id = -1; char *cp, *tun_fwd_ifname = NULL; /* XXX should be pre-session */ if (!options.control_persist) ssh_init_stdio_forwarding(ssh); ssh_init_forwarding(ssh, &tun_fwd_ifname); if (options.local_command != NULL) { debug3("expanding LocalCommand: %s", options.local_command); cp = options.local_command; options.local_command = percent_expand(cp, DEFAULT_CLIENT_PERCENT_EXPAND_ARGS(cinfo), "T", tun_fwd_ifname == NULL ? "NONE" : tun_fwd_ifname, (char *)NULL); debug3("expanded LocalCommand: %s", options.local_command); free(cp); } /* Start listening for multiplex clients */ if (!ssh_packet_get_mux(ssh)) muxserver_listen(ssh); /* * If we are in control persist mode and have a working mux listen * socket, then prepare to background ourselves and have a foreground * client attach as a control client. * NB. we must save copies of the flags that we override for * the backgrounding, since we defer attachment of the client until * after the connection is fully established (in particular, * async rfwd replies have been received for ExitOnForwardFailure). */ if (options.control_persist && muxserver_sock != -1) { ostdin_null_flag = options.stdin_null; osession_type = options.session_type; orequest_tty = options.request_tty; otty_flag = tty_flag; options.stdin_null = 1; options.session_type = SESSION_TYPE_NONE; tty_flag = 0; if (!options.fork_after_authentication && (osession_type != SESSION_TYPE_NONE || options.stdio_forward_host != NULL)) need_controlpersist_detach = 1; options.fork_after_authentication = 1; } /* * ControlPersist mux listen socket setup failed, attempt the * stdio forward setup that we skipped earlier. */ if (options.control_persist && muxserver_sock == -1) ssh_init_stdio_forwarding(ssh); if (options.session_type != SESSION_TYPE_NONE) id = ssh_session2_open(ssh); else { ssh_packet_set_interactive(ssh, options.control_master == SSHCTL_MASTER_NO, options.ip_qos_interactive, options.ip_qos_bulk); } /* If we don't expect to open a new session, then disallow it */ if (options.control_master == SSHCTL_MASTER_NO && (ssh->compat & SSH_NEW_OPENSSH)) { debug("Requesting no-more-sessions@openssh.com"); if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || (r = sshpkt_put_cstring(ssh, "no-more-sessions@openssh.com")) != 0 || (r = sshpkt_put_u8(ssh, 0)) != 0 || (r = sshpkt_send(ssh)) != 0) fatal_fr(r, "send packet"); } /* Execute a local command */ if (options.local_command != NULL && options.permit_local_command) ssh_local_cmd(options.local_command); /* * stdout is now owned by the session channel; clobber it here * so future channel closes are propagated to the local fd. * NB. this can only happen after LocalCommand has completed, * as it may want to write to stdout. */ if (!need_controlpersist_detach && stdfd_devnull(0, 1, 0) == -1) error_f("stdfd_devnull failed"); /* * If requested and we are not interested in replies to remote * forwarding requests, then let ssh continue in the background. */ if (options.fork_after_authentication) { if (options.exit_on_forward_failure && options.num_remote_forwards > 0) { debug("deferring postauth fork until remote forward " "confirmation received"); } else fork_postauth(); } return client_loop(ssh, tty_flag, tty_flag ? options.escape_char : SSH_ESCAPECHAR_NONE, id); } /* Loads all IdentityFile and CertificateFile keys */ static void load_public_identity_files(const struct ssh_conn_info *cinfo) { char *filename, *cp; struct sshkey *public; int i; u_int n_ids, n_certs; char *identity_files[SSH_MAX_IDENTITY_FILES]; struct sshkey *identity_keys[SSH_MAX_IDENTITY_FILES]; int identity_file_userprovided[SSH_MAX_IDENTITY_FILES]; char *certificate_files[SSH_MAX_CERTIFICATE_FILES]; struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES]; int certificate_file_userprovided[SSH_MAX_CERTIFICATE_FILES]; #ifdef ENABLE_PKCS11 struct sshkey **keys = NULL; char **comments = NULL; int nkeys; #endif /* PKCS11 */ n_ids = n_certs = 0; memset(identity_files, 0, sizeof(identity_files)); memset(identity_keys, 0, sizeof(identity_keys)); memset(identity_file_userprovided, 0, sizeof(identity_file_userprovided)); memset(certificate_files, 0, sizeof(certificate_files)); memset(certificates, 0, sizeof(certificates)); memset(certificate_file_userprovided, 0, sizeof(certificate_file_userprovided)); #ifdef ENABLE_PKCS11 if (options.pkcs11_provider != NULL && options.num_identity_files < SSH_MAX_IDENTITY_FILES && (pkcs11_init(!options.batch_mode) == 0) && (nkeys = pkcs11_add_provider(options.pkcs11_provider, NULL, &keys, &comments)) > 0) { for (i = 0; i < nkeys; i++) { if (n_ids >= SSH_MAX_IDENTITY_FILES) { sshkey_free(keys[i]); free(comments[i]); continue; } identity_keys[n_ids] = keys[i]; identity_files[n_ids] = comments[i]; /* transferred */ n_ids++; } free(keys); free(comments); } #endif /* ENABLE_PKCS11 */ for (i = 0; i < options.num_identity_files; i++) { if (n_ids >= SSH_MAX_IDENTITY_FILES || strcasecmp(options.identity_files[i], "none") == 0) { free(options.identity_files[i]); options.identity_files[i] = NULL; continue; } cp = tilde_expand_filename(options.identity_files[i], getuid()); filename = default_client_percent_dollar_expand(cp, cinfo); free(cp); check_load(sshkey_load_public(filename, &public, NULL), filename, "pubkey"); debug("identity file %s type %d", filename, public ? public->type : -1); free(options.identity_files[i]); identity_files[n_ids] = filename; identity_keys[n_ids] = public; identity_file_userprovided[n_ids] = options.identity_file_userprovided[i]; if (++n_ids >= SSH_MAX_IDENTITY_FILES) continue; /* * If no certificates have been explicitly listed then try * to add the default certificate variant too. */ if (options.num_certificate_files != 0) continue; xasprintf(&cp, "%s-cert", filename); check_load(sshkey_load_public(cp, &public, NULL), filename, "pubkey"); debug("identity file %s type %d", cp, public ? public->type : -1); if (public == NULL) { free(cp); continue; } if (!sshkey_is_cert(public)) { debug_f("key %s type %s is not a certificate", cp, sshkey_type(public)); sshkey_free(public); free(cp); continue; } /* NB. leave filename pointing to private key */ identity_files[n_ids] = xstrdup(filename); identity_keys[n_ids] = public; identity_file_userprovided[n_ids] = options.identity_file_userprovided[i]; n_ids++; } if (options.num_certificate_files > SSH_MAX_CERTIFICATE_FILES) fatal_f("too many certificates"); for (i = 0; i < options.num_certificate_files; i++) { cp = tilde_expand_filename(options.certificate_files[i], getuid()); filename = default_client_percent_dollar_expand(cp, cinfo); free(cp); check_load(sshkey_load_public(filename, &public, NULL), filename, "certificate"); debug("certificate file %s type %d", filename, public ? public->type : -1); free(options.certificate_files[i]); options.certificate_files[i] = NULL; if (public == NULL) { free(filename); continue; } if (!sshkey_is_cert(public)) { debug_f("key %s type %s is not a certificate", filename, sshkey_type(public)); sshkey_free(public); free(filename); continue; } certificate_files[n_certs] = filename; certificates[n_certs] = public; certificate_file_userprovided[n_certs] = options.certificate_file_userprovided[i]; ++n_certs; } options.num_identity_files = n_ids; memcpy(options.identity_files, identity_files, sizeof(identity_files)); memcpy(options.identity_keys, identity_keys, sizeof(identity_keys)); memcpy(options.identity_file_userprovided, identity_file_userprovided, sizeof(identity_file_userprovided)); options.num_certificate_files = n_certs; memcpy(options.certificate_files, certificate_files, sizeof(certificate_files)); memcpy(options.certificates, certificates, sizeof(certificates)); memcpy(options.certificate_file_userprovided, certificate_file_userprovided, sizeof(certificate_file_userprovided)); } static void main_sigchld_handler(int sig) { int save_errno = errno; pid_t pid; int status; while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || (pid == -1 && errno == EINTR)) ; errno = save_errno; } diff --git a/crypto/openssh/ssh_config b/crypto/openssh/ssh_config index 7c7ad542e4ef..306e8f950efd 100644 --- a/crypto/openssh/ssh_config +++ b/crypto/openssh/ssh_config @@ -1,48 +1,47 @@ # $OpenBSD: ssh_config,v 1.35 2020/07/17 03:43:42 dtucker Exp $ # This is the ssh client system-wide configuration file. See # ssh_config(5) for more information. This file provides defaults for # users, and the values can be changed in per-user configuration files # or on the command line. # Configuration data is parsed as follows: # 1. command line options # 2. user-specific file # 3. system-wide file # Any configuration value is only changed the first time it is set. # Thus, host-specific definitions should be at the beginning of the # configuration file, and defaults at the end. # Site-wide defaults for some commonly used options. For a comprehensive # list of available options, their meanings and defaults, please see the # ssh_config(5) man page. # Host * # ForwardAgent no # ForwardX11 no # PasswordAuthentication yes # HostbasedAuthentication no # GSSAPIAuthentication no # GSSAPIDelegateCredentials no # BatchMode no # CheckHostIP no # AddressFamily any # ConnectTimeout 0 # StrictHostKeyChecking ask # IdentityFile ~/.ssh/id_rsa # IdentityFile ~/.ssh/id_dsa # IdentityFile ~/.ssh/id_ecdsa # IdentityFile ~/.ssh/id_ed25519 # Port 22 # Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc # MACs hmac-md5,hmac-sha1,umac-64@openssh.com # EscapeChar ~ # Tunnel no # TunnelDevice any:any # PermitLocalCommand no # VisualHostKey no # ProxyCommand ssh -q -W %h:%p gateway.example.com # RekeyLimit 1G 1h # UserKnownHostsFile ~/.ssh/known_hosts.d/%k # VerifyHostKeyDNS yes -# VersionAddendum FreeBSD-20220415 diff --git a/crypto/openssh/ssh_config.5 b/crypto/openssh/ssh_config.5 index 04d66e24d743..f87bdd015311 100644 --- a/crypto/openssh/ssh_config.5 +++ b/crypto/openssh/ssh_config.5 @@ -1,2193 +1,2185 @@ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland .\" All rights reserved .\" .\" As far as I am concerned, the code I have written for this software .\" can be used freely for any purpose. Any derived versions of this .\" software must be clearly marked as such, and if the derived work is .\" incompatible with the protocol description in the RFC file, it must be .\" called by a name other than "ssh" or "Secure Shell". .\" .\" Copyright (c) 1999,2000 Markus Friedl. All rights reserved. .\" Copyright (c) 1999 Aaron Campbell. All rights reserved. .\" Copyright (c) 1999 Theo de Raadt. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR .\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES .\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, .\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT .\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (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.371 2022/03/31 17:58:44 naddy Exp $ -.Dd $Mdocdate: March 31 2022 $ +.Dd $Mdocdate: August 19 2022 $ .Dt SSH_CONFIG 5 .Os .Sh NAME .Nm ssh_config .Nd OpenSSH client configuration file .Sh DESCRIPTION .Xr ssh 1 obtains configuration data from the following sources in the following order: .Pp .Bl -enum -offset indent -compact .It command-line options .It user's configuration file .Pq Pa ~/.ssh/config .It system-wide configuration file .Pq Pa /etc/ssh/ssh_config .El .Pp For each parameter, the first obtained value will be used. The configuration files contain sections separated by .Cm 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 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 file, and general defaults at the end. .Pp The file contains keyword-argument pairs, one per line. Lines starting with .Ql # and empty lines are interpreted as comments. Arguments may optionally be enclosed in double quotes .Pq \&" in order to represent arguments containing spaces. Configuration options may be separated by whitespace or optional whitespace and exactly one .Ql = ; the latter format is useful to avoid the need to quote whitespace when specifying configuration options using the .Nm ssh , .Nm scp , and .Nm sftp .Fl o option. .Pp The possible keywords and their meanings are as follows (note that keywords are case-insensitive and arguments are case-sensitive): .Bl -tag -width Ds .It Cm Host Restricts the following declarations (up to the next .Cm Host or .Cm Match keyword) to be only for those hosts that match one of the patterns given after the keyword. If more than one pattern is provided, they should be separated by whitespace. A single .Ql * as a pattern can be used to provide global defaults for all hosts. The host is usually the .Ar hostname argument given on the command line (see the .Cm CanonicalizeHostname keyword for exceptions). .Pp A pattern entry may be negated by prefixing it with an exclamation mark .Pq Sq !\& . If a negated entry is matched, then the .Cm Host entry is ignored, regardless of whether any other patterns on the line match. Negated matches are therefore useful to provide exceptions for wildcard matches. .Pp See .Sx PATTERNS for more information on patterns. .It Cm Match Restricts the following declarations (up to the next .Cm Host or .Cm Match keyword) to be used only when the conditions following the .Cm Match keyword are satisfied. Match conditions are specified using one or more criteria or the single token .Cm all which always matches. The available criteria keywords are: .Cm canonical , .Cm final , .Cm exec , .Cm host , .Cm originalhost , .Cm user , and .Cm localuser . The .Cm all criteria must appear alone or immediately after .Cm canonical or .Cm final . Other criteria may be combined arbitrarily. All criteria but .Cm all , .Cm canonical , and .Cm final require an argument. Criteria may be negated by prepending an exclamation mark .Pq Sq !\& . .Pp The .Cm canonical keyword 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. .Pp The .Cm final keyword requests that the configuration be re-parsed (regardless of whether .Cm CanonicalizeHostname is enabled), and matches only during this final pass. If .Cm CanonicalizeHostname is enabled, then .Cm canonical and .Cm final match during the same pass. .Pp 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. Commands containing whitespace characters must be quoted. Arguments to .Cm exec accept the tokens described in the .Sx TOKENS section. .Pp The other keywords' criteria must be single entries or comma-separated lists and may use the wildcard and negation operators described in the .Sx PATTERNS section. The criteria for the .Cm host keyword are matched against the target hostname, after any substitution by the .Cm Hostname or .Cm CanonicalizeHostname options. The .Cm originalhost keyword matches against the hostname as it was specified on the command-line. The .Cm user keyword matches against the target username on the remote host. The .Cm localuser keyword matches against the name of the local user running .Xr ssh 1 (this keyword may be useful in system-wide .Nm files). .It Cm AddKeysToAgent Specifies whether keys should be automatically added to a running .Xr ssh-agent 1 . If this option is set to .Cm yes and a key is loaded from a file, the key and its passphrase are added to the agent with the default lifetime, as if by .Xr ssh-add 1 . If this option is set to .Cm ask , .Xr ssh 1 will require confirmation using the .Ev SSH_ASKPASS program before adding a key (see .Xr ssh-add 1 for details). If this option is set to .Cm confirm , each use of the key must be confirmed, as if the .Fl c option was specified to .Xr ssh-add 1 . If this option is set to .Cm no , no keys are added to the agent. Alternately, this option may be specified as a time interval using the format described in the .Sx TIME FORMATS section of .Xr sshd_config 5 to specify the key's lifetime in .Xr ssh-agent 1 , after which it will automatically be removed. The argument must be .Cm no (the default), .Cm yes , .Cm confirm (optionally followed by a time interval), .Cm ask or a time interval. .It Cm AddressFamily Specifies which address family to use when connecting. Valid arguments are .Cm any (the default), .Cm inet (use IPv4 only), or .Cm inet6 (use IPv6 only). .It Cm BatchMode If set to .Cm yes , user interaction such as password prompts and host key confirmation requests will be disabled. This option is useful in scripts and other batch jobs where no user is present to interact with .Xr ssh 1 . The argument must be .Cm yes or .Cm no (the default). .It Cm BindAddress Use the specified address on the local machine as the source address of the connection. Only useful on systems with more than one address. .It Cm BindInterface Use the address of the specified interface on the local machine as the source address of the connection. .It Cm CanonicalDomains When .Cm CanonicalizeHostname is enabled, this option specifies the list of domain suffixes in which to search for the specified destination host. .It Cm CanonicalizeFallbackLocal Specifies whether to fail with an error when hostname canonicalization fails. The default, .Cm yes , will attempt to look up the unqualified hostname using the system resolver's search rules. A value of .Cm no will cause .Xr ssh 1 to fail instantly if .Cm CanonicalizeHostname is enabled and the target hostname cannot be found in any of the domains specified by .Cm CanonicalDomains . .It Cm CanonicalizeHostname Controls whether explicit hostname canonicalization is performed. The default, .Cm no , is not to perform any name rewriting and let the system resolver handle all hostname lookups. If set to .Cm yes then, for connections that do not use a .Cm ProxyCommand or .Cm ProxyJump , .Xr ssh 1 will attempt to canonicalize the hostname specified on the command line using the .Cm CanonicalDomains suffixes and .Cm CanonicalizePermittedCNAMEs rules. If .Cm CanonicalizeHostname is set to .Cm always , then canonicalization is applied to proxied connections too. .Pp 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. A value of .Cm none disables the use of a .Cm ProxyJump host. .It Cm CanonicalizeMaxDots Specifies the maximum number of dot characters in a hostname before canonicalization is disabled. The default, 1, allows a single dot (i.e. hostname.subdomain). .It Cm CanonicalizePermittedCNAMEs Specifies rules to determine whether CNAMEs should be followed when canonicalizing hostnames. The rules consist of one or more arguments of .Ar source_domain_list : Ns Ar target_domain_list , where .Ar source_domain_list is a pattern-list of domains that may follow CNAMEs in canonicalization, and .Ar target_domain_list is a pattern-list of domains that they may resolve to. .Pp For example, .Qq *.a.example.com:*.b.example.com,*.c.example.com will allow hostnames matching .Qq *.a.example.com to be canonicalized to names in the .Qq *.b.example.com or .Qq *.c.example.com domains. .Pp A single argument of .Qq none causes no CNAMEs to be considered for canonicalization. This is the default behaviour. .It Cm CASignatureAlgorithms Specifies which algorithms are allowed for signing of certificates by certificate authorities (CAs). The default is: .Bd -literal -offset indent ssh-ed25519,ecdsa-sha2-nistp256, ecdsa-sha2-nistp384,ecdsa-sha2-nistp521, sk-ssh-ed25519@openssh.com, sk-ecdsa-sha2-nistp256@openssh.com, rsa-sha2-512,rsa-sha2-256 .Ed .Pp If the specified list begins with a .Sq + character, then the specified algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified algorithms (including wildcards) will be removed from the default set instead of replacing them. .Pp .Xr ssh 1 will not accept host certificates signed using algorithms other than those specified. .It Cm CertificateFile Specifies a file from which the user's certificate is read. A corresponding private key must be provided separately in order to use this certificate either from an .Cm IdentityFile directive or .Fl i flag to .Xr ssh 1 , via .Xr ssh-agent 1 , or via a .Cm PKCS11Provider or .Cm SecurityKeyProvider . .Pp Arguments to .Cm CertificateFile may use the tilde syntax to refer to a user's home directory, the tokens described in the .Sx TOKENS section and environment variables as described in the .Sx ENVIRONMENT VARIABLES section. .Pp It is possible to have multiple certificate files specified in configuration files; these certificates will be tried in sequence. Multiple .Cm CertificateFile directives will add to the list of certificates used for authentication. .It Cm CheckHostIP If set to .Cm yes , .Xr ssh 1 will additionally check the host IP address in the .Pa known_hosts file. This allows it to detect if a host key changed due to DNS spoofing and will add addresses of destination hosts to .Pa ~/.ssh/known_hosts in the process, regardless of the setting of .Cm StrictHostKeyChecking . If the option is set to .Cm no (the default), the check will not be executed. The default is .Cm no . .It Cm Ciphers Specifies the ciphers allowed and their order of preference. Multiple ciphers must be comma-separated. If the specified list begins with a .Sq + character, then the specified ciphers will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified ciphers (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified ciphers will be placed at the head of the default set. .Pp The supported ciphers are: .Bd -literal -offset indent 3des-cbc aes128-cbc aes192-cbc aes256-cbc aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com .Ed .Pp The default is: .Bd -literal -offset indent chacha20-poly1305@openssh.com, aes128-ctr,aes192-ctr,aes256-ctr, aes128-gcm@openssh.com,aes256-gcm@openssh.com .Ed .Pp The list of available ciphers may also be obtained using .Qq ssh -Q cipher . .It Cm ClearAllForwardings Specifies that all local, remote, and dynamic port forwardings specified in the configuration files or on the command line be cleared. This option is primarily useful when used from the .Xr ssh 1 command line to clear port forwardings set in configuration files, and is automatically set by .Xr scp 1 and .Xr sftp 1 . The argument must be .Cm yes or .Cm no (the default). .It Cm Compression Specifies whether to use compression. The argument must be .Cm yes or .Cm no (the default). .It Cm ConnectionAttempts Specifies the number of tries (one per second) to make before exiting. The argument must be an integer. This may be useful in scripts if the connection sometimes fails. The default is 1. .It Cm ConnectTimeout Specifies the timeout (in seconds) used when connecting to the SSH server, instead of using the default system TCP timeout. This timeout is applied both to establishing the connection and to performing the initial SSH protocol handshake and key exchange. .It Cm ControlMaster Enables the sharing of multiple sessions over a single network connection. When set to .Cm yes , .Xr ssh 1 will listen for connections on a control socket specified using the .Cm ControlPath argument. Additional sessions can connect to this socket using the same .Cm ControlPath with .Cm ControlMaster set to .Cm no (the default). These sessions will try to reuse the master instance's network connection rather than initiating new ones, but will fall back to connecting normally if the control socket does not exist, or is not listening. .Pp Setting this to .Cm ask will cause .Xr ssh 1 to listen for control connections, but require confirmation using .Xr ssh-askpass 1 . If the .Cm ControlPath cannot be opened, .Xr ssh 1 will continue without connecting to a master instance. .Pp X11 and .Xr ssh-agent 1 forwarding is supported over these multiplexed connections, however the display and agent forwarded will be the one belonging to the master connection i.e. it is not possible to forward multiple displays or agents. .Pp Two additional options allow for opportunistic multiplexing: try to use a master connection but fall back to creating a new one if one does not already exist. These options are: .Cm auto and .Cm autoask . The latter requires confirmation like the .Cm ask option. .It Cm ControlPath Specify the path to the control socket used for connection sharing as described in the .Cm ControlMaster section above or the string .Cm none to disable connection sharing. Arguments to .Cm ControlPath may use the tilde syntax to refer to a user's home directory, the tokens described in the .Sx TOKENS section and environment variables as described in the .Sx ENVIRONMENT VARIABLES section. It is recommended that any .Cm ControlPath used for opportunistic connection sharing include at least %h, %p, and %r (or alternatively %C) and be placed in a directory that is not writable by other users. This ensures that shared connections are uniquely identified. .It Cm ControlPersist When used in conjunction with .Cm ControlMaster , specifies that the master connection should remain open in the background (waiting for future client connections) after the initial client connection has been closed. If set to .Cm no (the default), then the master connection will not be placed into the background, and will close as soon as the initial client connection is closed. If set to .Cm yes or 0, then the master connection will remain in the background indefinitely (until killed or closed via a mechanism such as the .Qq ssh -O exit ) . If set to a time in seconds, or a time in any of the formats documented in .Xr sshd_config 5 , then the backgrounded master connection will automatically terminate after it has remained idle (with no client connections) for the specified time. .It Cm DynamicForward Specifies that a TCP port on the local machine be forwarded over the secure channel, and the application protocol is then used to determine where to connect to from the remote machine. .Pp The argument must be .Sm off .Oo Ar bind_address : Oc Ar port . .Sm on IPv6 addresses can be specified by enclosing addresses in square brackets. By default, the local port is bound in accordance with the .Cm GatewayPorts setting. However, an explicit .Ar bind_address may be used to bind the connection to a specific address. The .Ar bind_address of .Cm localhost indicates that the listening port be bound for local use only, while an empty address or .Sq * indicates that the port should be available from all interfaces. .Pp Currently the SOCKS4 and SOCKS5 protocols are supported, and .Xr ssh 1 will act as a SOCKS server. Multiple forwardings may be specified, and additional forwardings can be given on the command line. Only the superuser can forward privileged ports. .It Cm EnableSSHKeysign Setting this option to .Cm yes in the global client configuration file .Pa /etc/ssh/ssh_config enables the use of the helper program .Xr ssh-keysign 8 during .Cm HostbasedAuthentication . The argument must be .Cm yes or .Cm no (the default). This option should be placed in the non-hostspecific section. See .Xr ssh-keysign 8 for more information. .It Cm EscapeChar Sets the escape character (default: .Ql ~ ) . The escape character can also be set on the command line. The argument should be a single character, .Ql ^ followed by a letter, or .Cm none to disable the escape character entirely (making the connection transparent for binary data). .It Cm ExitOnForwardFailure Specifies whether .Xr ssh 1 should terminate the connection if it cannot set up all requested dynamic, tunnel, local, and remote port forwardings, (e.g.\& if either end is unable to bind and listen on a specified port). Note that .Cm ExitOnForwardFailure does not apply to connections made over port forwardings and will not, for example, cause .Xr ssh 1 to exit if TCP connections to the ultimate forwarding destination fail. The argument must be .Cm yes or .Cm no (the default). .It Cm FingerprintHash Specifies the hash algorithm used when displaying key fingerprints. Valid options are: .Cm md5 and .Cm sha256 (the default). .It Cm ForkAfterAuthentication Requests .Nm ssh to go to background just before command execution. This is useful if .Nm ssh is going to ask for passwords or passphrases, but the user wants it in the background. This implies the .Cm StdinNull configuration option being set to .Dq yes . The recommended way to start X11 programs at a remote site is with something like .Ic ssh -f host xterm , which is the same as .Ic ssh host xterm if the .Cm ForkAfterAuthentication configuration option is set to .Dq yes . .Pp If the .Cm ExitOnForwardFailure configuration option is set to .Dq yes , then a client started with the .Cm ForkAfterAuthentication configuration option being set to .Dq yes will wait for all remote port forwards to be successfully established before placing itself in the background. The argument to this keyword must be .Cm yes (same as the .Fl f option) or .Cm no (the default). .It Cm ForwardAgent Specifies whether the connection to the authentication agent (if any) will be forwarded to the remote machine. The argument may be .Cm yes , .Cm no (the default), an explicit path to an agent socket or the name of an environment variable (beginning with .Sq $ ) in which to find the path. .Pp Agent forwarding should be enabled with caution. Users with the ability to bypass file permissions on the remote host (for the agent's Unix-domain socket) can access the local agent through the forwarded connection. An attacker cannot obtain key material from the agent, however they can perform operations on the keys that enable them to authenticate using the identities loaded into the agent. .It Cm ForwardX11 Specifies whether X11 connections will be automatically redirected over the secure channel and .Ev DISPLAY set. The argument must be .Cm yes or .Cm no (the default). .Pp X11 forwarding should be enabled with caution. Users with the ability to bypass file permissions on the remote host (for the user's X11 authorization database) can access the local X11 display through the forwarded connection. An attacker may then be able to perform activities such as keystroke monitoring if the .Cm ForwardX11Trusted option is also enabled. .It Cm ForwardX11Timeout Specify a timeout for untrusted X11 forwarding using the format described in the .Sx TIME FORMATS section of .Xr sshd_config 5 . X11 connections received by .Xr ssh 1 after this time will be refused. Setting .Cm ForwardX11Timeout to zero will disable the timeout and permit X11 forwarding for the life of the connection. The default is to disable untrusted X11 forwarding after twenty minutes has elapsed. .It Cm ForwardX11Trusted If this option is set to .Cm yes , remote X11 clients will have full access to the original X11 display. .Pp If this option is set to .Cm no (the default), remote X11 clients will be considered untrusted and prevented from stealing or tampering with data belonging to trusted X11 clients. Furthermore, the .Xr xauth 1 token used for the session will be set to expire after 20 minutes. Remote clients will be refused access after this time. .Pp See the X11 SECURITY extension specification for full details on the restrictions imposed on untrusted clients. .It Cm GatewayPorts Specifies whether remote hosts are allowed to connect to local forwarded ports. By default, .Xr ssh 1 binds local port forwardings to the loopback address. This prevents other remote hosts from connecting to forwarded ports. .Cm GatewayPorts can be used to specify that ssh should bind local port forwardings to the wildcard address, thus allowing remote hosts to connect to forwarded ports. The argument must be .Cm yes or .Cm no (the default). .It Cm GlobalKnownHostsFile Specifies one or more files to use for the global host key database, separated by whitespace. The default is .Pa /etc/ssh/ssh_known_hosts , .Pa /etc/ssh/ssh_known_hosts2 . .It Cm GSSAPIAuthentication Specifies whether user authentication based on GSSAPI is allowed. The default is .Cm no . .It Cm GSSAPIDelegateCredentials Forward (delegate) credentials to the server. The default is .Cm no . .It Cm HashKnownHosts Indicates that .Xr ssh 1 should hash host names and addresses when they are added to .Pa ~/.ssh/known_hosts . These hashed names may be used normally by .Xr ssh 1 and .Xr sshd 8 , but they do not visually reveal identifying information if the file's contents are disclosed. The default is .Cm no . Note that existing names and addresses in known hosts files will not be converted automatically, but may be manually hashed using .Xr ssh-keygen 1 . .It Cm HostbasedAcceptedAlgorithms Specifies the signature algorithms that will be used for hostbased authentication as a comma-separated list of patterns. Alternately if the specified list begins with a .Sq + character, then the specified signature algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified signature algorithms (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified signature algorithms will be placed at the head of the default set. The default for this option is: .Bd -literal -offset 3n ssh-ed25519-cert-v01@openssh.com, ecdsa-sha2-nistp256-cert-v01@openssh.com, ecdsa-sha2-nistp384-cert-v01@openssh.com, ecdsa-sha2-nistp521-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, sk-ecdsa-sha2-nistp256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, ssh-ed25519, ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521, sk-ssh-ed25519@openssh.com, sk-ecdsa-sha2-nistp256@openssh.com, rsa-sha2-512,rsa-sha2-256 .Ed .Pp The .Fl Q option of .Xr ssh 1 may be used to list supported signature algorithms. This was formerly named HostbasedKeyTypes. .It Cm HostbasedAuthentication Specifies whether to try rhosts based authentication with public key authentication. The argument must be .Cm yes or .Cm no (the default). .It Cm HostKeyAlgorithms Specifies the host key signature algorithms that the client wants to use in order of preference. Alternately if the specified list begins with a .Sq + character, then the specified signature algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified signature algorithms (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified signature algorithms will be placed at the head of the default set. The default for this option is: .Bd -literal -offset 3n ssh-ed25519-cert-v01@openssh.com, ecdsa-sha2-nistp256-cert-v01@openssh.com, ecdsa-sha2-nistp384-cert-v01@openssh.com, ecdsa-sha2-nistp521-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, sk-ecdsa-sha2-nistp256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, ssh-ed25519, ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521, sk-ecdsa-sha2-nistp256@openssh.com, sk-ssh-ed25519@openssh.com, rsa-sha2-512,rsa-sha2-256 .Ed .Pp If hostkeys are known for the destination host then this default is modified to prefer their algorithms. .Pp The list of available signature algorithms may also be obtained using .Qq ssh -Q HostKeyAlgorithms . .It Cm HostKeyAlias Specifies an alias that should be used instead of the real host name when looking up or saving the host key in the host key database files and when validating host certificates. This option is useful for tunneling SSH connections or for multiple servers running on a single host. .It Cm Hostname Specifies the real host name to log into. This can be used to specify nicknames or abbreviations for hosts. Arguments to .Cm Hostname accept the tokens described in the .Sx TOKENS section. Numeric IP addresses are also permitted (both on the command line and in .Cm Hostname specifications). The default is the name given on the command line. .It Cm IdentitiesOnly Specifies that .Xr ssh 1 should only use the configured authentication identity and certificate files (either the default files, or those explicitly configured in the .Nm files or passed on the .Xr ssh 1 command-line), even if .Xr ssh-agent 1 or a .Cm PKCS11Provider or .Cm SecurityKeyProvider offers more identities. The argument to this keyword must be .Cm yes or .Cm no (the default). This option is intended for situations where ssh-agent offers many different identities. .It Cm IdentityAgent Specifies the .Ux Ns -domain socket used to communicate with the authentication agent. .Pp This option overrides the .Ev SSH_AUTH_SOCK environment variable and can be used to select a specific agent. Setting the socket name to .Cm none disables the use of an authentication agent. If the string .Qq SSH_AUTH_SOCK is specified, the location of the socket will be read from the .Ev SSH_AUTH_SOCK environment variable. Otherwise if the specified value begins with a .Sq $ character, then it will be treated as an environment variable containing the location of the socket. .Pp Arguments to .Cm IdentityAgent may use the tilde syntax to refer to a user's home directory, the tokens described in the .Sx TOKENS section and environment variables as described in the .Sx ENVIRONMENT VARIABLES section. .It Cm IdentityFile Specifies a file from which the user's DSA, ECDSA, authenticator-hosted ECDSA, Ed25519, authenticator-hosted Ed25519 or RSA authentication identity is read. The default is .Pa ~/.ssh/id_rsa , .Pa ~/.ssh/id_ecdsa , .Pa ~/.ssh/id_ecdsa_sk , .Pa ~/.ssh/id_ed25519 , .Pa ~/.ssh/id_ed25519_sk and .Pa ~/.ssh/id_dsa . Additionally, any identities represented by the authentication agent will be used for authentication unless .Cm IdentitiesOnly is set. If no certificates have been explicitly specified by .Cm CertificateFile , .Xr ssh 1 will try to load certificate information from the filename obtained by appending .Pa -cert.pub to the path of a specified .Cm IdentityFile . .Pp Arguments to .Cm IdentityFile may use the tilde syntax to refer to a user's home directory or the tokens described in the .Sx TOKENS section. .Pp It is possible to have multiple identity files specified in configuration files; all these identities will be tried in sequence. Multiple .Cm IdentityFile directives will add to the list of identities tried (this behaviour differs from that of other configuration directives). .Pp .Cm IdentityFile may be used in conjunction with .Cm IdentitiesOnly to select which identities in an agent are offered during authentication. .Cm IdentityFile may also be used in conjunction with .Cm CertificateFile in order to provide any certificate also needed for authentication with the identity. .It Cm IgnoreUnknown Specifies a pattern-list of unknown options to be ignored if they are encountered in configuration parsing. This may be used to suppress errors if .Nm contains options that are unrecognised by .Xr ssh 1 . It is recommended that .Cm IgnoreUnknown be listed early in the configuration file as it will not be applied to unknown options that appear before it. .It Cm Include Include the specified configuration file(s). Multiple pathnames may be specified and each pathname may contain .Xr glob 7 wildcards and, for user configurations, shell-like .Sq ~ references to user home directories. Wildcards will be expanded and processed in lexical order. Files without absolute paths are assumed to be in .Pa ~/.ssh if included in a user configuration file or .Pa /etc/ssh if included from the system configuration file. .Cm Include directive may appear inside a .Cm Match or .Cm Host block to perform conditional inclusion. .It Cm IPQoS Specifies the IPv4 type-of-service or DSCP class for connections. Accepted values are .Cm af11 , .Cm af12 , .Cm af13 , .Cm af21 , .Cm af22 , .Cm af23 , .Cm af31 , .Cm af32 , .Cm af33 , .Cm af41 , .Cm af42 , .Cm af43 , .Cm cs0 , .Cm cs1 , .Cm cs2 , .Cm cs3 , .Cm cs4 , .Cm cs5 , .Cm cs6 , .Cm cs7 , .Cm ef , .Cm le , .Cm lowdelay , .Cm throughput , .Cm reliability , a numeric value, or .Cm none to use the operating system default. This option may take one or two arguments, separated by whitespace. If one argument is specified, it is used as the packet class unconditionally. If two values are specified, the first is automatically selected for interactive sessions and the second for non-interactive sessions. The default is .Cm af21 (Low-Latency Data) for interactive sessions and .Cm cs1 (Lower Effort) for non-interactive sessions. .It Cm KbdInteractiveAuthentication Specifies whether to use keyboard-interactive authentication. The argument to this keyword must be .Cm yes (the default) or .Cm no . .Cm ChallengeResponseAuthentication is a deprecated alias for this. .It Cm KbdInteractiveDevices Specifies the list of methods to use in keyboard-interactive authentication. Multiple method names must be comma-separated. The default is to use the server specified list. The methods available vary depending on what the server supports. For an OpenSSH server, it may be zero or more of: .Cm bsdauth and .Cm pam . .It Cm KexAlgorithms Specifies the available KEX (Key Exchange) algorithms. Multiple algorithms must be comma-separated. If the specified list begins with a .Sq + character, then the specified algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified algorithms (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified algorithms will be placed at the head of the default set. The default is: .Bd -literal -offset indent sntrup761x25519-sha512@openssh.com, curve25519-sha256,curve25519-sha256@libssh.org, ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256 .Ed .Pp The list of available key exchange algorithms may also be obtained using .Qq ssh -Q kex . .It Cm KnownHostsCommand Specifies a command to use to obtain a list of host keys, in addition to those listed in .Cm UserKnownHostsFile and .Cm GlobalKnownHostsFile . This command is executed after the files have been read. It may write host key lines to standard output in identical format to the usual files (described in the .Sx VERIFYING HOST KEYS section in .Xr ssh 1 ) . Arguments to .Cm KnownHostsCommand accept the tokens described in the .Sx TOKENS section. The command may be invoked multiple times per connection: once when preparing the preference list of host key algorithms to use, again to obtain the host key for the requested host name and, if .Cm CheckHostIP is enabled, one more time to obtain the host key matching the server's address. If the command exits abnormally or returns a non-zero exit status then the connection is terminated. .It Cm LocalCommand Specifies a command to execute on the local machine after successfully connecting to the server. The command string extends to the end of the line, and is executed with the user's shell. Arguments to .Cm LocalCommand accept the tokens described in the .Sx TOKENS section. .Pp The command is run synchronously and does not have access to the session of the .Xr ssh 1 that spawned it. It should not be used for interactive commands. .Pp This directive is ignored unless .Cm PermitLocalCommand has been enabled. .It Cm LocalForward Specifies that a TCP port on the local machine be forwarded over the secure channel to the specified host and port from the remote machine. The first argument specifies the listener and may be .Sm off .Oo Ar bind_address : Oc Ar port .Sm on or a Unix domain socket path. The second argument is the destination and may be .Ar host : Ns Ar hostport or a Unix domain socket path if the remote host supports it. .Pp IPv6 addresses can be specified by enclosing addresses in square brackets. Multiple forwardings may be specified, and additional forwardings can be given on the command line. Only the superuser can forward privileged ports. By default, the local port is bound in accordance with the .Cm GatewayPorts setting. However, an explicit .Ar bind_address may be used to bind the connection to a specific address. The .Ar bind_address of .Cm localhost indicates that the listening port be bound for local use only, while an empty address or .Sq * indicates that the port should be available from all interfaces. Unix domain socket paths may use the tokens described in the .Sx TOKENS section and environment variables as described in the .Sx ENVIRONMENT VARIABLES section. .It Cm LogLevel Gives the verbosity level that is used when logging messages from .Xr ssh 1 . The possible values are: QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG, DEBUG1, DEBUG2, and DEBUG3. The default is INFO. DEBUG and DEBUG1 are equivalent. DEBUG2 and DEBUG3 each specify higher levels of verbose output. .It Cm LogVerbose Specify one or more overrides to LogLevel. An override consists of a pattern lists that matches the source file, function and line number to force detailed logging for. For example, an override pattern of: .Bd -literal -offset indent kex.c:*:1000,*:kex_exchange_identification():*,packet.c:* .Ed .Pp would enable detailed logging for line 1000 of .Pa kex.c , everything in the .Fn kex_exchange_identification function, and all code in the .Pa packet.c file. This option is intended for debugging and no overrides are enabled by default. .It Cm MACs Specifies the MAC (message authentication code) algorithms in order of preference. The MAC algorithm is used for data integrity protection. Multiple algorithms must be comma-separated. If the specified list begins with a .Sq + character, then the specified algorithms will be appended to the default set instead of replacing them. If the specified list begins with a .Sq - character, then the specified algorithms (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified algorithms will be placed at the head of the default set. .Pp The algorithms that contain .Qq -etm calculate the MAC after encryption (encrypt-then-mac). These are considered safer and their use recommended. .Pp The default is: .Bd -literal -offset indent umac-64-etm@openssh.com,umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com,umac-128@openssh.com, hmac-sha2-256,hmac-sha2-512,hmac-sha1 .Ed .Pp The list of available MAC algorithms may also be obtained using .Qq ssh -Q mac . .It Cm NoHostAuthenticationForLocalhost Disable host authentication for localhost (loopback addresses). The argument to this keyword must be .Cm yes or .Cm no (the default). .It Cm NumberOfPasswordPrompts Specifies the number of password prompts before giving up. The argument to this keyword must be an integer. The default is 3. .It Cm PasswordAuthentication Specifies whether to use password authentication. The argument to this keyword must be .Cm yes (the default) or .Cm no . .It Cm PermitLocalCommand Allow local command execution via the .Ic LocalCommand option or using the .Ic !\& Ns Ar command escape sequence in .Xr ssh 1 . The argument must be .Cm yes or .Cm no (the default). .It Cm PermitRemoteOpen Specifies the destinations to which remote TCP port forwarding is permitted when .Cm RemoteForward is used as a SOCKS proxy. The forwarding specification must be one of the following forms: .Pp .Bl -item -offset indent -compact .It .Cm PermitRemoteOpen .Sm off .Ar host : port .Sm on .It .Cm PermitRemoteOpen .Sm off .Ar IPv4_addr : port .Sm on .It .Cm PermitRemoteOpen .Sm off .Ar \&[ IPv6_addr \&] : port .Sm on .El .Pp Multiple forwards may be specified by separating them with whitespace. An argument of .Cm any can be used to remove all restrictions and permit any forwarding requests. An argument of .Cm none can be used to prohibit all forwarding requests. The wildcard .Sq * can be used for host or port to allow all hosts or ports respectively. Otherwise, no pattern matching or address lookups are performed on supplied names. .It Cm PKCS11Provider Specifies which PKCS#11 provider to use or .Cm none to indicate that no provider should be used (the default). The argument to this keyword is a path to the PKCS#11 shared library .Xr ssh 1 should use to communicate with a PKCS#11 token providing keys for user authentication. .It Cm Port Specifies the port number to connect on the remote host. The default is 22. .It Cm PreferredAuthentications Specifies the order in which the client should try authentication methods. This allows a client to prefer one method (e.g.\& .Cm keyboard-interactive ) over another method (e.g.\& .Cm password ) . The default is: .Bd -literal -offset indent gssapi-with-mic,hostbased,publickey, keyboard-interactive,password .Ed .It Cm ProxyCommand Specifies the command to use to connect to the server. The command string extends to the end of the line, and is executed using the user's shell .Ql exec directive to avoid a lingering shell process. .Pp Arguments to .Cm ProxyCommand accept the tokens described in the .Sx TOKENS section. The command can be basically anything, and should read from its standard input and write to its standard output. It should eventually connect an .Xr sshd 8 server running on some machine, or execute .Ic sshd -i somewhere. Host key management will be done using the .Cm Hostname of the host being connected (defaulting to the name typed by the user). Setting the command to .Cm none disables this option entirely. Note that .Cm CheckHostIP is not available for connects with a proxy command. .Pp This directive is useful in conjunction with .Xr nc 1 and its proxy support. For example, the following directive would connect via an HTTP proxy at 192.0.2.0: .Bd -literal -offset 3n ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p .Ed .It Cm ProxyJump Specifies one or more jump proxies as either .Xo .Sm off .Op Ar user No @ .Ar host .Op : Ns Ar port .Sm on or an ssh URI .Xc . Multiple proxies may be separated by comma characters and will be visited sequentially. Setting this option will cause .Xr ssh 1 to connect to the target host by first making a .Xr ssh 1 connection to the specified .Cm ProxyJump host and then establishing a TCP forwarding to the ultimate target from there. Setting the host to .Cm none disables this option entirely. .Pp Note that this option will compete with the .Cm ProxyCommand option - whichever is specified first will prevent later instances of the other from taking effect. .Pp Note also that the configuration for the destination host (either supplied via the command-line or the configuration file) is not generally applied to jump hosts. .Pa ~/.ssh/config should be used if specific configuration is required for jump hosts. .It Cm ProxyUseFdpass Specifies that .Cm ProxyCommand will pass a connected file descriptor back to .Xr ssh 1 instead of continuing to execute and pass data. The default is .Cm no . .It Cm PubkeyAcceptedAlgorithms Specifies the signature algorithms that will be used for public key authentication as a comma-separated list of patterns. If the specified list begins with a .Sq + character, then the algorithms after it will be appended to the default instead of replacing it. If the specified list begins with a .Sq - character, then the specified algorithms (including wildcards) will be removed from the default set instead of replacing them. If the specified list begins with a .Sq ^ character, then the specified algorithms will be placed at the head of the default set. The default for this option is: .Bd -literal -offset 3n ssh-ed25519-cert-v01@openssh.com, ecdsa-sha2-nistp256-cert-v01@openssh.com, ecdsa-sha2-nistp384-cert-v01@openssh.com, ecdsa-sha2-nistp521-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, sk-ecdsa-sha2-nistp256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, ssh-ed25519, ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521, sk-ssh-ed25519@openssh.com, sk-ecdsa-sha2-nistp256@openssh.com, rsa-sha2-512,rsa-sha2-256 .Ed .Pp The list of available signature algorithms may also be obtained using .Qq ssh -Q PubkeyAcceptedAlgorithms . .It Cm PubkeyAuthentication Specifies whether to try public key authentication. The argument to this keyword must be .Cm yes (the default), .Cm no , .Cm unbound or .Cm host-bound . The final two options enable public key authentication while respectively disabling or enabling the OpenSSH host-bound authentication protocol extension required for restricted .Xr ssh-agent 1 forwarding. .It Cm RekeyLimit Specifies the maximum amount of data that may be transmitted before the session key is renegotiated, optionally followed by a maximum amount of time that may pass before the session key is renegotiated. The first argument is specified in bytes and may have a suffix of .Sq K , .Sq M , or .Sq G to indicate Kilobytes, Megabytes, or Gigabytes, respectively. The default is between .Sq 1G and .Sq 4G , depending on the cipher. The optional second value is specified in seconds and may use any of the units documented in the TIME FORMATS section of .Xr sshd_config 5 . The default value for .Cm RekeyLimit is .Cm default none , which means that rekeying is performed after the cipher's default amount of data has been sent or received and no time based rekeying is done. .It Cm RemoteCommand Specifies a command to execute on the remote machine after successfully connecting to the server. The command string extends to the end of the line, and is executed with the user's shell. Arguments to .Cm RemoteCommand accept the tokens described in the .Sx TOKENS section. .It Cm RemoteForward Specifies that a TCP port on the remote machine be forwarded over the secure channel. The remote port may either be forwarded to a specified host and port from the local machine, or may act as a SOCKS 4/5 proxy that allows a remote client to connect to arbitrary destinations from the local machine. The first argument is the listening specification and may be .Sm off .Oo Ar bind_address : Oc Ar port .Sm on or, if the remote host supports it, a Unix domain socket path. If forwarding to a specific destination then the second argument must be .Ar host : Ns Ar hostport or a Unix domain socket path, otherwise if no destination argument is specified then the remote forwarding will be established as a SOCKS proxy. When acting as a SOCKS proxy, the destination of the connection can be restricted by .Cm PermitRemoteOpen . .Pp IPv6 addresses can be specified by enclosing addresses in square brackets. Multiple forwardings may be specified, and additional forwardings can be given on the command line. Privileged ports can be forwarded only when logging in as root on the remote machine. Unix domain socket paths may use the tokens described in the .Sx TOKENS section and environment variables as described in the .Sx ENVIRONMENT VARIABLES section. .Pp If the .Ar port argument is 0, the listen port will be dynamically allocated on the server and reported to the client at run time. .Pp If the .Ar bind_address is not specified, the default is to only bind to loopback addresses. If the .Ar bind_address is .Ql * or an empty string, then the forwarding is requested to listen on all interfaces. Specifying a remote .Ar bind_address will only succeed if the server's .Cm GatewayPorts option is enabled (see .Xr sshd_config 5 ) . .It Cm RequestTTY Specifies whether to request a pseudo-tty for the session. The argument may be one of: .Cm no (never request a TTY), .Cm yes (always request a TTY when standard input is a TTY), .Cm force (always request a TTY) or .Cm auto (request a TTY when opening a login session). This option mirrors the .Fl t and .Fl T flags for .Xr ssh 1 . .It Cm RevokedHostKeys Specifies revoked host public keys. Keys listed in this file will be refused for host authentication. Note that if this file does not exist or is not readable, then host authentication will be refused for all hosts. Keys may be specified as a text file, listing one public key per line, or as an OpenSSH Key Revocation List (KRL) as generated by .Xr ssh-keygen 1 . For more information on KRLs, see the KEY REVOCATION LISTS section in .Xr ssh-keygen 1 . .It Cm SecurityKeyProvider Specifies a path to a library that will be used when loading any FIDO authenticator-hosted keys, overriding the default of using the built-in USB HID support. .Pp If the specified value begins with a .Sq $ character, then it will be treated as an environment variable containing the path to the library. .It Cm SendEnv Specifies what variables from the local .Xr environ 7 should be sent to the server. The server must also support it, and the server must be configured to accept these environment variables. Note that the .Ev TERM environment variable is always sent whenever a pseudo-terminal is requested as it is required by the protocol. Refer to .Cm AcceptEnv in .Xr sshd_config 5 for how to configure the server. Variables are specified by name, which may contain wildcard characters. Multiple environment variables may be separated by whitespace or spread across multiple .Cm SendEnv directives. .Pp See .Sx PATTERNS for more information on patterns. .Pp It is possible to clear previously set .Cm SendEnv variable names by prefixing patterns with .Pa - . The default is not to send any environment variables. .It Cm ServerAliveCountMax Sets the number of server alive messages (see below) which may be sent without .Xr ssh 1 receiving any messages back from the server. If this threshold is reached while server alive messages are being sent, ssh will disconnect from the server, terminating the session. It is important to note that the use of server alive messages is very different from .Cm TCPKeepAlive (below). The server alive messages are sent through the encrypted channel and therefore will not be spoofable. The TCP keepalive option enabled by .Cm TCPKeepAlive is spoofable. The server alive mechanism is valuable when the client or server depend on knowing when a connection has become unresponsive. .Pp The default value is 3. If, for example, .Cm ServerAliveInterval (see below) is set to 15 and .Cm ServerAliveCountMax is left at the default, if the server becomes unresponsive, ssh will disconnect after approximately 45 seconds. .It Cm ServerAliveInterval Sets a timeout interval in seconds after which if no data has been received from the server, .Xr ssh 1 will send a message through the encrypted channel to request a response from the server. The default is 0, indicating that these messages will not be sent to the server. .It Cm SessionType May be used to either request invocation of a subsystem on the remote system, or to prevent the execution of a remote command at all. The latter is useful for just forwarding ports. The argument to this keyword must be .Cm none (same as the .Fl N option), .Cm subsystem (same as the .Fl s option) or .Cm default (shell or command execution). .It Cm SetEnv Directly specify one or more environment variables and their contents to be sent to the server. Similarly to .Cm SendEnv , with the exception of the .Ev TERM variable, the server must be prepared to accept the environment variable. .It Cm StdinNull Redirects stdin from .Pa /dev/null (actually, prevents reading from stdin). Either this or the equivalent .Fl n option must be used when .Nm ssh is run in the background. The argument to this keyword must be .Cm yes (same as the .Fl n option) or .Cm no (the default). .It Cm StreamLocalBindMask Sets the octal file creation mode mask .Pq umask used when creating a Unix-domain socket file for local or remote port forwarding. This option is only used for port forwarding to a Unix-domain socket file. .Pp The default value is 0177, which creates a Unix-domain socket file that is readable and writable only by the owner. Note that not all operating systems honor the file mode on Unix-domain socket files. .It Cm StreamLocalBindUnlink Specifies whether to remove an existing Unix-domain socket file for local or remote port forwarding before creating a new one. If the socket file already exists and .Cm StreamLocalBindUnlink is not enabled, .Nm ssh will be unable to forward the port to the Unix-domain socket file. This option is only used for port forwarding to a Unix-domain socket file. .Pp The argument must be .Cm yes or .Cm no (the default). .It Cm StrictHostKeyChecking If this flag is set to .Cm yes , .Xr ssh 1 will never automatically add host keys to the .Pa ~/.ssh/known_hosts file, and refuses to connect to hosts whose host key has changed. This provides maximum protection against man-in-the-middle (MITM) attacks, though it can be annoying when the .Pa /etc/ssh/ssh_known_hosts file is poorly maintained or when connections to new hosts are frequently made. This option forces the user to manually add all new hosts. .Pp If this flag is set to .Cm accept-new then ssh will automatically add new host keys to the user's .Pa known_hosts file, but will not permit connections to hosts with changed host keys. If this flag is set to .Cm no or .Cm off , ssh will automatically add new host keys to the user known hosts files and allow connections to hosts with changed hostkeys to proceed, subject to some restrictions. If this flag is set to .Cm ask (the default), new host keys will be added to the user known host files only after the user has confirmed that is what they really want to do, and ssh will refuse to connect to hosts whose host key has changed. The host keys of known hosts will be verified automatically in all cases. .It Cm SyslogFacility Gives the facility code that is used when logging messages from .Xr ssh 1 . The possible values are: DAEMON, USER, AUTH, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7. The default is USER. .It Cm TCPKeepAlive Specifies whether the system should send TCP keepalive messages to the other side. If they are sent, death of the connection or crash of one of the machines will be properly noticed. However, this means that connections will die if the route is down temporarily, and some people find it annoying. .Pp The default is .Cm yes (to send TCP keepalive messages), and the client will notice if the network goes down or the remote host dies. This is important in scripts, and many users want it too. .Pp To disable TCP keepalive messages, the value should be set to .Cm no . See also .Cm ServerAliveInterval for protocol-level keepalives. .It Cm Tunnel Request .Xr tun 4 device forwarding between the client and the server. The argument must be .Cm yes , .Cm point-to-point (layer 3), .Cm ethernet (layer 2), or .Cm no (the default). Specifying .Cm yes requests the default tunnel mode, which is .Cm point-to-point . .It Cm TunnelDevice Specifies the .Xr tun 4 devices to open on the client .Pq Ar local_tun and the server .Pq Ar remote_tun . .Pp The argument must be .Sm off .Ar local_tun Op : Ar remote_tun . .Sm on The devices may be specified by numerical ID or the keyword .Cm any , which uses the next available tunnel device. If .Ar remote_tun is not specified, it defaults to .Cm any . The default is .Cm any:any . .It Cm UpdateHostKeys Specifies whether .Xr ssh 1 should accept notifications of additional hostkeys from the server sent after authentication has completed and add them to .Cm UserKnownHostsFile . The argument must be .Cm yes , .Cm no or .Cm ask . This option allows learning alternate hostkeys for a server and supports graceful key rotation by allowing a server to send replacement public keys before old ones are removed. .Pp Additional hostkeys are only accepted if the key used to authenticate the host was already trusted or explicitly accepted by the user, the host was authenticated via .Cm UserKnownHostsFile (i.e. not .Cm GlobalKnownHostsFile ) and the host was authenticated using a plain key and not a certificate. .Pp .Cm UpdateHostKeys is enabled by default if the user has not overridden the default .Cm UserKnownHostsFile setting and has not enabled .Cm VerifyHostKeyDNS , otherwise .Cm UpdateHostKeys will be set to .Cm no . .Pp If .Cm UpdateHostKeys is set to .Cm ask , then the user is asked to confirm the modifications to the known_hosts file. Confirmation is currently incompatible with .Cm ControlPersist , and will be disabled if it is enabled. .Pp Presently, only .Xr sshd 8 from OpenSSH 6.8 and greater support the .Qq hostkeys@openssh.com protocol extension used to inform the client of all the server's hostkeys. .It Cm User Specifies the user to log in as. This can be useful when a different user name is used on different machines. This saves the trouble of having to remember to give the user name on the command line. .It Cm UserKnownHostsFile Specifies one or more files to use for the user host key database, separated by whitespace. Each filename may use tilde notation to refer to the user's home directory, the tokens described in the .Sx TOKENS section and environment variables as described in the .Sx ENVIRONMENT VARIABLES section. The default is .Pa ~/.ssh/known_hosts , .Pa ~/.ssh/known_hosts2 . .It Cm VerifyHostKeyDNS Specifies whether to verify the remote key using DNS and SSHFP resource records. If this option is set to .Cm yes , the client will implicitly trust keys that match a secure fingerprint from DNS. Insecure fingerprints will be handled as if this option was set to .Cm ask . If this option is set to .Cm ask , information on fingerprint match will be displayed, but the user will still need to confirm new host keys according to the .Cm StrictHostKeyChecking option. The default is .Cm yes if compiled with LDNS and .Cm no otherwise. .Pp See also .Sx VERIFYING HOST KEYS in .Xr ssh 1 . -.It Cm VersionAddendum -Specifies a string to append to the regular version string to identify -OS- or site-specific modifications. -The default is -.Dq FreeBSD-20220415 . -The value -.Cm none -may be used to disable this. .It Cm VisualHostKey If this flag is set to .Cm yes , an ASCII art representation of the remote host key fingerprint is printed in addition to the fingerprint string at login and for unknown host keys. If this flag is set to .Cm no (the default), no fingerprint strings are printed at login and only the fingerprint string will be printed for unknown host keys. .It Cm XAuthLocation Specifies the full pathname of the .Xr xauth 1 program. The default is .Pa /usr/local/bin/xauth . .El .Sh PATTERNS A .Em pattern consists of zero or more non-whitespace characters, .Sq * (a wildcard that matches zero or more characters), or .Sq ?\& (a wildcard that matches exactly one character). For example, to specify a set of declarations for any host in the .Qq .co.uk set of domains, the following pattern could be used: .Pp .Dl Host *.co.uk .Pp The following pattern would match any host in the 192.168.0.[0-9] network range: .Pp .Dl Host 192.168.0.? .Pp A .Em pattern-list is a comma-separated list of patterns. Patterns within pattern-lists may be negated by preceding them with an exclamation mark .Pq Sq !\& . For example, to allow a key to be used from anywhere within an organization except from the .Qq dialup pool, the following entry (in authorized_keys) could be used: .Pp .Dl from=\&"!*.dialup.example.com,*.example.com\&" .Pp Note that a negated match will never produce a positive result by itself. For example, attempting to match .Qq host3 against the following pattern-list will fail: .Pp .Dl from=\&"!host1,!host2\&" .Pp The solution here is to include a term that will yield a positive match, such as a wildcard: .Pp .Dl from=\&"!host1,!host2,*\&" .Sh TOKENS Arguments to some keywords can make use of tokens, which are expanded at runtime: .Pp .Bl -tag -width XXXX -offset indent -compact .It %% A literal .Sq % . .It \&%C Hash of %l%h%p%r. .It %d Local user's home directory. .It %f The fingerprint of the server's host key. .It %H The .Pa known_hosts hostname or address that is being searched for. .It %h The remote hostname. .It \%%I A string describing the reason for a .Cm KnownHostsCommand execution: either .Cm ADDRESS when looking up a host by address (only when .Cm CheckHostIP is enabled), .Cm HOSTNAME when searching by hostname, or .Cm ORDER when preparing the host key algorithm preference list to use for the destination host. .It %i The local user ID. .It %K The base64 encoded host key. .It %k The host key alias if specified, otherwise the original remote hostname given on the command line. .It %L The local hostname. .It %l The local hostname, including the domain name. .It %n The original remote hostname, as given on the command line. .It %p The remote port. .It %r The remote username. .It \&%T The local .Xr tun 4 or .Xr tap 4 network interface assigned if tunnel forwarding was requested, or .Qq NONE otherwise. .It %t The type of the server host key, e.g. .Cm ssh-ed25519 . .It %u The local username. .El .Pp .Cm CertificateFile , .Cm ControlPath , .Cm IdentityAgent , .Cm IdentityFile , .Cm KnownHostsCommand , .Cm LocalForward , .Cm Match exec , .Cm RemoteCommand , .Cm RemoteForward , and .Cm UserKnownHostsFile accept the tokens %%, %C, %d, %h, %i, %k, %L, %l, %n, %p, %r, and %u. .Pp .Cm KnownHostsCommand additionally accepts the tokens %f, %H, %I, %K and %t. .Pp .Cm Hostname accepts the tokens %% and %h. .Pp .Cm LocalCommand accepts all tokens. .Pp .Cm ProxyCommand accepts the tokens %%, %h, %n, %p, and %r. .Sh ENVIRONMENT VARIABLES Arguments to some keywords can be expanded at runtime from environment variables on the client by enclosing them in .Ic ${} , for example .Ic ${HOME}/.ssh would refer to the user's .ssh directory. If a specified environment variable does not exist then an error will be returned and the setting for that keyword will be ignored. .Pp The keywords .Cm CertificateFile , .Cm ControlPath , .Cm IdentityAgent , .Cm IdentityFile , .Cm KnownHostsCommand , and .Cm UserKnownHostsFile support environment variables. The keywords .Cm LocalForward and .Cm RemoteForward support environment variables only for Unix domain socket paths. .Sh FILES .Bl -tag -width Ds .It Pa ~/.ssh/config This is the per-user configuration file. The format of this file is described above. This file is used by the SSH client. Because of the potential for abuse, this file must have strict permissions: read/write for the user, and not writable by others. .It Pa /etc/ssh/ssh_config Systemwide configuration file. This file provides defaults for those values that are not specified in the user's configuration file, and for those users who do not have a configuration file. This file must be world-readable. .El .Sh SEE ALSO .Xr ssh 1 .Sh AUTHORS .An -nosplit OpenSSH is a derivative of the original and free ssh 1.2.12 release by .An Tatu Ylonen . .An Aaron Campbell , Bob Beck , Markus Friedl , .An Niels Provos , Theo de Raadt and .An Dug Song removed many bugs, re-added newer features and created OpenSSH. .An Markus Friedl contributed the support for SSH protocol versions 1.5 and 2.0. diff --git a/crypto/openssh/sshconnect.c b/crypto/openssh/sshconnect.c index 8030583c50aa..ebecc83747bb 100644 --- a/crypto/openssh/sshconnect.c +++ b/crypto/openssh/sshconnect.c @@ -1,1709 +1,1708 @@ /* $OpenBSD: sshconnect.c,v 1.356 2021/12/19 22:10:24 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Code to connect to a remote host, and to perform the client side of the * login (authentication) dialog. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". */ #include "includes.h" #include #include #include #include #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include #include #include #include #include #include #ifdef HAVE_PATHS_H #include #endif #include #ifdef HAVE_POLL_H #include #endif #include #include #include #include #include #include #ifdef HAVE_IFADDRS_H # include #endif #include "xmalloc.h" #include "hostfile.h" #include "ssh.h" #include "sshbuf.h" #include "packet.h" #include "compat.h" #include "sshkey.h" #include "sshconnect.h" #include "log.h" #include "misc.h" #include "readconf.h" #include "atomicio.h" #include "dns.h" #include "monitor_fdpass.h" #include "ssh2.h" #include "version.h" #include "authfile.h" #include "ssherr.h" #include "authfd.h" #include "kex.h" struct sshkey *previous_host_key = NULL; static int matching_host_key_dns = 0; static pid_t proxy_command_pid = 0; /* import */ extern int debug_flag; extern Options options; extern char *__progname; static int show_other_keys(struct hostkeys *, struct sshkey *); static void warn_changed_key(struct sshkey *); /* Expand a proxy command */ static char * expand_proxy_command(const char *proxy_command, const char *user, const char *host, const char *host_arg, int port) { char *tmp, *ret, strport[NI_MAXSERV]; const char *keyalias = options.host_key_alias ? options.host_key_alias : host_arg; snprintf(strport, sizeof strport, "%d", port); xasprintf(&tmp, "exec %s", proxy_command); ret = percent_expand(tmp, "h", host, "k", keyalias, "n", host_arg, "p", strport, "r", options.user, (char *)NULL); free(tmp); return ret; } /* * Connect to the given ssh server using a proxy command that passes a * a connected fd back to us. */ static int ssh_proxy_fdpass_connect(struct ssh *ssh, const char *host, const char *host_arg, u_short port, const char *proxy_command) { char *command_string; int sp[2], sock; pid_t pid; char *shell; if ((shell = getenv("SHELL")) == NULL) shell = _PATH_BSHELL; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) == -1) fatal("Could not create socketpair to communicate with " "proxy dialer: %.100s", strerror(errno)); command_string = expand_proxy_command(proxy_command, options.user, host, host_arg, port); debug("Executing proxy dialer command: %.500s", command_string); /* Fork and execute the proxy command. */ if ((pid = fork()) == 0) { char *argv[10]; close(sp[1]); /* Redirect stdin and stdout. */ if (sp[0] != 0) { if (dup2(sp[0], 0) == -1) perror("dup2 stdin"); } if (sp[0] != 1) { if (dup2(sp[0], 1) == -1) perror("dup2 stdout"); } if (sp[0] >= 2) close(sp[0]); /* * Stderr is left for non-ControlPersist connections is so * error messages may be printed on the user's terminal. */ if (!debug_flag && options.control_path != NULL && options.control_persist && stdfd_devnull(0, 0, 1) == -1) error_f("stdfd_devnull failed"); argv[0] = shell; argv[1] = "-c"; argv[2] = command_string; argv[3] = NULL; /* * Execute the proxy command. * Note that we gave up any extra privileges above. */ execv(argv[0], argv); perror(argv[0]); exit(1); } /* Parent. */ if (pid == -1) fatal("fork failed: %.100s", strerror(errno)); close(sp[0]); free(command_string); if ((sock = mm_receive_fd(sp[1])) == -1) fatal("proxy dialer did not pass back a connection"); close(sp[1]); while (waitpid(pid, NULL, 0) == -1) if (errno != EINTR) fatal("Couldn't wait for child: %s", strerror(errno)); /* Set the connection file descriptors. */ if (ssh_packet_set_connection(ssh, sock, sock) == NULL) return -1; /* ssh_packet_set_connection logs error */ return 0; } /* * Connect to the given ssh server using a proxy command. */ static int ssh_proxy_connect(struct ssh *ssh, const char *host, const char *host_arg, u_short port, const char *proxy_command) { char *command_string; int pin[2], pout[2]; pid_t pid; char *shell; if ((shell = getenv("SHELL")) == NULL || *shell == '\0') shell = _PATH_BSHELL; /* Create pipes for communicating with the proxy. */ if (pipe(pin) == -1 || pipe(pout) == -1) fatal("Could not create pipes to communicate with the proxy: %.100s", strerror(errno)); command_string = expand_proxy_command(proxy_command, options.user, host, host_arg, port); debug("Executing proxy command: %.500s", command_string); /* Fork and execute the proxy command. */ if ((pid = fork()) == 0) { char *argv[10]; /* Redirect stdin and stdout. */ close(pin[1]); if (pin[0] != 0) { if (dup2(pin[0], 0) == -1) perror("dup2 stdin"); close(pin[0]); } close(pout[0]); if (dup2(pout[1], 1) == -1) perror("dup2 stdout"); /* Cannot be 1 because pin allocated two descriptors. */ close(pout[1]); /* * Stderr is left for non-ControlPersist connections is so * error messages may be printed on the user's terminal. */ if (!debug_flag && options.control_path != NULL && options.control_persist && stdfd_devnull(0, 0, 1) == -1) error_f("stdfd_devnull failed"); argv[0] = shell; argv[1] = "-c"; argv[2] = command_string; argv[3] = NULL; /* * Execute the proxy command. Note that we gave up any * extra privileges above. */ ssh_signal(SIGPIPE, SIG_DFL); execv(argv[0], argv); perror(argv[0]); exit(1); } /* Parent. */ if (pid == -1) fatal("fork failed: %.100s", strerror(errno)); else proxy_command_pid = pid; /* save pid to clean up later */ /* Close child side of the descriptors. */ close(pin[0]); close(pout[1]); /* Free the command name. */ free(command_string); /* Set the connection file descriptors. */ if (ssh_packet_set_connection(ssh, pout[0], pin[1]) == NULL) return -1; /* ssh_packet_set_connection logs error */ return 0; } void ssh_kill_proxy_command(void) { /* * Send SIGHUP to proxy command if used. We don't wait() in * case it hangs and instead rely on init to reap the child */ if (proxy_command_pid > 1) kill(proxy_command_pid, SIGHUP); } #ifdef HAVE_IFADDRS_H /* * Search a interface address list (returned from getifaddrs(3)) for an * address that matches the desired address family on the specified interface. * Returns 0 and fills in *resultp and *rlenp on success. Returns -1 on failure. */ static int check_ifaddrs(const char *ifname, int af, const struct ifaddrs *ifaddrs, struct sockaddr_storage *resultp, socklen_t *rlenp) { struct sockaddr_in6 *sa6; struct sockaddr_in *sa; struct in6_addr *v6addr; const struct ifaddrs *ifa; int allow_local; /* * Prefer addresses that are not loopback or linklocal, but use them * if nothing else matches. */ for (allow_local = 0; allow_local < 2; allow_local++) { for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr == NULL || ifa->ifa_name == NULL || (ifa->ifa_flags & IFF_UP) == 0 || ifa->ifa_addr->sa_family != af || strcmp(ifa->ifa_name, options.bind_interface) != 0) continue; switch (ifa->ifa_addr->sa_family) { case AF_INET: sa = (struct sockaddr_in *)ifa->ifa_addr; if (!allow_local && sa->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) continue; if (*rlenp < sizeof(struct sockaddr_in)) { error_f("v4 addr doesn't fit"); return -1; } *rlenp = sizeof(struct sockaddr_in); memcpy(resultp, sa, *rlenp); return 0; case AF_INET6: sa6 = (struct sockaddr_in6 *)ifa->ifa_addr; v6addr = &sa6->sin6_addr; if (!allow_local && (IN6_IS_ADDR_LINKLOCAL(v6addr) || IN6_IS_ADDR_LOOPBACK(v6addr))) continue; if (*rlenp < sizeof(struct sockaddr_in6)) { error_f("v6 addr doesn't fit"); return -1; } *rlenp = sizeof(struct sockaddr_in6); memcpy(resultp, sa6, *rlenp); return 0; } } } return -1; } #endif /* * Creates a socket for use as the ssh connection. */ static int ssh_create_socket(struct addrinfo *ai) { int sock, r; struct sockaddr_storage bindaddr; socklen_t bindaddrlen = 0; struct addrinfo hints, *res = NULL; #ifdef HAVE_IFADDRS_H struct ifaddrs *ifaddrs = NULL; #endif char ntop[NI_MAXHOST]; sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock == -1) { error("socket: %s", strerror(errno)); return -1; } fcntl(sock, F_SETFD, FD_CLOEXEC); /* Use interactive QOS (if specified) until authentication completed */ if (options.ip_qos_interactive != INT_MAX) set_sock_tos(sock, options.ip_qos_interactive); /* Bind the socket to an alternative local IP address */ if (options.bind_address == NULL && options.bind_interface == NULL) return sock; if (options.bind_address != NULL) { memset(&hints, 0, sizeof(hints)); hints.ai_family = ai->ai_family; hints.ai_socktype = ai->ai_socktype; hints.ai_protocol = ai->ai_protocol; hints.ai_flags = AI_PASSIVE; if ((r = getaddrinfo(options.bind_address, NULL, &hints, &res)) != 0) { error("getaddrinfo: %s: %s", options.bind_address, ssh_gai_strerror(r)); goto fail; } if (res == NULL) { error("getaddrinfo: no addrs"); goto fail; } memcpy(&bindaddr, res->ai_addr, res->ai_addrlen); bindaddrlen = res->ai_addrlen; } else if (options.bind_interface != NULL) { #ifdef HAVE_IFADDRS_H if ((r = getifaddrs(&ifaddrs)) != 0) { error("getifaddrs: %s: %s", options.bind_interface, strerror(errno)); goto fail; } bindaddrlen = sizeof(bindaddr); if (check_ifaddrs(options.bind_interface, ai->ai_family, ifaddrs, &bindaddr, &bindaddrlen) != 0) { logit("getifaddrs: %s: no suitable addresses", options.bind_interface); goto fail; } #else error("BindInterface not supported on this platform."); #endif } if ((r = getnameinfo((struct sockaddr *)&bindaddr, bindaddrlen, ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST)) != 0) { error_f("getnameinfo failed: %s", ssh_gai_strerror(r)); goto fail; } if (bind(sock, (struct sockaddr *)&bindaddr, bindaddrlen) != 0) { error("bind %s: %s", ntop, strerror(errno)); goto fail; } debug_f("bound to %s", ntop); /* success */ goto out; fail: close(sock); sock = -1; out: if (res != NULL) freeaddrinfo(res); #ifdef HAVE_IFADDRS_H if (ifaddrs != NULL) freeifaddrs(ifaddrs); #endif return sock; } /* * Opens a TCP/IP connection to the remote server on the given host. * The address of the remote host will be returned in hostaddr. * If port is 0, the default port will be used. * Connection_attempts specifies the maximum number of tries (one per * second). If proxy_command is non-NULL, it specifies the command (with %h * and %p substituted for host and port, respectively) to use to contact * the daemon. */ static int ssh_connect_direct(struct ssh *ssh, const char *host, struct addrinfo *aitop, struct sockaddr_storage *hostaddr, u_short port, int connection_attempts, int *timeout_ms, int want_keepalive) { int on = 1, saved_timeout_ms = *timeout_ms; int oerrno, sock = -1, attempt; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; struct addrinfo *ai; debug3_f("entering"); memset(ntop, 0, sizeof(ntop)); memset(strport, 0, sizeof(strport)); for (attempt = 0; attempt < connection_attempts; attempt++) { if (attempt > 0) { /* Sleep a moment before retrying. */ sleep(1); debug("Trying again..."); } /* * Loop through addresses for this host, and try each one in * sequence until the connection succeeds. */ for (ai = aitop; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) { errno = EAFNOSUPPORT; continue; } if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { oerrno = errno; error_f("getnameinfo failed"); errno = oerrno; continue; } debug("Connecting to %.200s [%.100s] port %s.", host, ntop, strport); /* Create a socket for connecting. */ sock = ssh_create_socket(ai); if (sock < 0) { /* Any error is already output */ errno = 0; continue; } *timeout_ms = saved_timeout_ms; if (timeout_connect(sock, ai->ai_addr, ai->ai_addrlen, timeout_ms) >= 0) { /* Successful connection. */ memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); break; } else { oerrno = errno; debug("connect to address %s port %s: %s", ntop, strport, strerror(errno)); close(sock); sock = -1; errno = oerrno; } } if (sock != -1) break; /* Successful connection. */ } /* Return failure if we didn't get a successful connection. */ if (sock == -1) { error("ssh: connect to host %s port %s: %s", host, strport, errno == 0 ? "failure" : strerror(errno)); return -1; } debug("Connection established."); /* Set SO_KEEPALIVE if requested. */ if (want_keepalive && setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)) == -1) error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); /* Set the connection. */ if (ssh_packet_set_connection(ssh, sock, sock) == NULL) return -1; /* ssh_packet_set_connection logs error */ return 0; } int ssh_connect(struct ssh *ssh, const char *host, const char *host_arg, struct addrinfo *addrs, struct sockaddr_storage *hostaddr, u_short port, int connection_attempts, int *timeout_ms, int want_keepalive) { int in, out; if (options.proxy_command == NULL) { return ssh_connect_direct(ssh, host, addrs, hostaddr, port, connection_attempts, timeout_ms, want_keepalive); } else if (strcmp(options.proxy_command, "-") == 0) { if ((in = dup(STDIN_FILENO)) == -1 || (out = dup(STDOUT_FILENO)) == -1) { if (in >= 0) close(in); error_f("dup() in/out failed"); return -1; /* ssh_packet_set_connection logs error */ } if ((ssh_packet_set_connection(ssh, in, out)) == NULL) return -1; /* ssh_packet_set_connection logs error */ return 0; } else if (options.proxy_use_fdpass) { return ssh_proxy_fdpass_connect(ssh, host, host_arg, port, options.proxy_command); } return ssh_proxy_connect(ssh, host, host_arg, port, options.proxy_command); } /* defaults to 'no' */ static int confirm(const char *prompt, const char *fingerprint) { const char *msg, *again = "Please type 'yes' or 'no': "; const char *again_fp = "Please type 'yes', 'no' or the fingerprint: "; char *p, *cp; int ret = -1; if (options.batch_mode) return 0; for (msg = prompt;;msg = fingerprint ? again_fp : again) { cp = p = read_passphrase(msg, RP_ECHO); if (p == NULL) return 0; p += strspn(p, " \t"); /* skip leading whitespace */ p[strcspn(p, " \t\n")] = '\0'; /* remove trailing whitespace */ if (p[0] == '\0' || strcasecmp(p, "no") == 0) ret = 0; else if (strcasecmp(p, "yes") == 0 || (fingerprint != NULL && strcmp(p, fingerprint) == 0)) ret = 1; free(cp); if (ret != -1) return ret; } } static int sockaddr_is_local(struct sockaddr *hostaddr) { switch (hostaddr->sa_family) { case AF_INET: return (ntohl(((struct sockaddr_in *)hostaddr)-> sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; case AF_INET6: return IN6_IS_ADDR_LOOPBACK( &(((struct sockaddr_in6 *)hostaddr)->sin6_addr)); default: return 0; } } /* * Prepare the hostname and ip address strings that are used to lookup * host keys in known_hosts files. These may have a port number appended. */ void get_hostfile_hostname_ipaddr(char *hostname, struct sockaddr *hostaddr, u_short port, char **hostfile_hostname, char **hostfile_ipaddr) { char ntop[NI_MAXHOST]; socklen_t addrlen; switch (hostaddr == NULL ? -1 : hostaddr->sa_family) { case -1: addrlen = 0; break; case AF_INET: addrlen = sizeof(struct sockaddr_in); break; case AF_INET6: addrlen = sizeof(struct sockaddr_in6); break; default: addrlen = sizeof(struct sockaddr); break; } /* * We don't have the remote ip-address for connections * using a proxy command */ if (hostfile_ipaddr != NULL) { if (options.proxy_command == NULL) { if (getnameinfo(hostaddr, addrlen, ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST) != 0) fatal_f("getnameinfo failed"); *hostfile_ipaddr = put_host_port(ntop, port); } else { *hostfile_ipaddr = xstrdup(""); } } /* * Allow the user to record the key under a different name or * differentiate a non-standard port. This is useful for ssh * tunneling over forwarded connections or if you run multiple * sshd's on different ports on the same machine. */ if (hostfile_hostname != NULL) { if (options.host_key_alias != NULL) { *hostfile_hostname = xstrdup(options.host_key_alias); debug("using hostkeyalias: %s", *hostfile_hostname); } else { *hostfile_hostname = put_host_port(hostname, port); } } } /* returns non-zero if path appears in hostfiles, or 0 if not. */ static int path_in_hostfiles(const char *path, char **hostfiles, u_int num_hostfiles) { u_int i; for (i = 0; i < num_hostfiles; i++) { if (strcmp(path, hostfiles[i]) == 0) return 1; } return 0; } struct find_by_key_ctx { const char *host, *ip; const struct sshkey *key; char **names; u_int nnames; }; /* Try to replace home directory prefix (per $HOME) with a ~/ sequence */ static char * try_tilde_unexpand(const char *path) { char *home, *ret = NULL; size_t l; if (*path != '/') return xstrdup(path); if ((home = getenv("HOME")) == NULL || (l = strlen(home)) == 0) return xstrdup(path); if (strncmp(path, home, l) != 0) return xstrdup(path); /* * ensure we have matched on a path boundary: either the $HOME that * we just compared ends with a '/' or the next character of the path * must be a '/'. */ if (home[l - 1] != '/' && path[l] != '/') return xstrdup(path); if (path[l] == '/') l++; xasprintf(&ret, "~/%s", path + l); return ret; } static int hostkeys_find_by_key_cb(struct hostkey_foreach_line *l, void *_ctx) { struct find_by_key_ctx *ctx = (struct find_by_key_ctx *)_ctx; char *path; /* we are looking for keys with names that *do not* match */ if ((l->match & HKF_MATCH_HOST) != 0) return 0; /* not interested in marker lines */ if (l->marker != MRK_NONE) return 0; /* we are only interested in exact key matches */ if (l->key == NULL || !sshkey_equal(ctx->key, l->key)) return 0; path = try_tilde_unexpand(l->path); debug_f("found matching key in %s:%lu", path, l->linenum); ctx->names = xrecallocarray(ctx->names, ctx->nnames, ctx->nnames + 1, sizeof(*ctx->names)); xasprintf(&ctx->names[ctx->nnames], "%s:%lu: %s", path, l->linenum, strncmp(l->hosts, HASH_MAGIC, strlen(HASH_MAGIC)) == 0 ? "[hashed name]" : l->hosts); ctx->nnames++; free(path); return 0; } static int hostkeys_find_by_key_hostfile(const char *file, const char *which, struct find_by_key_ctx *ctx) { int r; debug3_f("trying %s hostfile \"%s\"", which, file); if ((r = hostkeys_foreach(file, hostkeys_find_by_key_cb, ctx, ctx->host, ctx->ip, HKF_WANT_PARSE_KEY, 0)) != 0) { if (r == SSH_ERR_SYSTEM_ERROR && errno == ENOENT) { debug_f("hostkeys file %s does not exist", file); return 0; } error_fr(r, "hostkeys_foreach failed for %s", file); return r; } return 0; } /* * Find 'key' in known hosts file(s) that do not match host/ip. * Used to display also-known-as information for previously-unseen hostkeys. */ static void hostkeys_find_by_key(const char *host, const char *ip, const struct sshkey *key, char **user_hostfiles, u_int num_user_hostfiles, char **system_hostfiles, u_int num_system_hostfiles, char ***names, u_int *nnames) { struct find_by_key_ctx ctx = {0, 0, 0, 0, 0}; u_int i; *names = NULL; *nnames = 0; if (key == NULL || sshkey_is_cert(key)) return; ctx.host = host; ctx.ip = ip; ctx.key = key; for (i = 0; i < num_user_hostfiles; i++) { if (hostkeys_find_by_key_hostfile(user_hostfiles[i], "user", &ctx) != 0) goto fail; } for (i = 0; i < num_system_hostfiles; i++) { if (hostkeys_find_by_key_hostfile(system_hostfiles[i], "system", &ctx) != 0) goto fail; } /* success */ *names = ctx.names; *nnames = ctx.nnames; ctx.names = NULL; ctx.nnames = 0; return; fail: for (i = 0; i < ctx.nnames; i++) free(ctx.names[i]); free(ctx.names); } #define MAX_OTHER_NAMES 8 /* Maximum number of names to list */ static char * other_hostkeys_message(const char *host, const char *ip, const struct sshkey *key, char **user_hostfiles, u_int num_user_hostfiles, char **system_hostfiles, u_int num_system_hostfiles) { char *ret = NULL, **othernames = NULL; u_int i, n, num_othernames = 0; hostkeys_find_by_key(host, ip, key, user_hostfiles, num_user_hostfiles, system_hostfiles, num_system_hostfiles, &othernames, &num_othernames); if (num_othernames == 0) return xstrdup("This key is not known by any other names"); xasprintf(&ret, "This host key is known by the following other " "names/addresses:"); n = num_othernames; if (n > MAX_OTHER_NAMES) n = MAX_OTHER_NAMES; for (i = 0; i < n; i++) { xextendf(&ret, "\n", " %s", othernames[i]); } if (n < num_othernames) { xextendf(&ret, "\n", " (%d additional names omitted)", num_othernames - n); } for (i = 0; i < num_othernames; i++) free(othernames[i]); free(othernames); return ret; } void load_hostkeys_command(struct hostkeys *hostkeys, const char *command_template, const char *invocation, const struct ssh_conn_info *cinfo, const struct sshkey *host_key, const char *hostfile_hostname) { int r, i, ac = 0; char *key_fp = NULL, *keytext = NULL, *tmp; char *command = NULL, *tag = NULL, **av = NULL; FILE *f = NULL; pid_t pid; void (*osigchld)(int); xasprintf(&tag, "KnownHostsCommand-%s", invocation); if (host_key != NULL) { if ((key_fp = sshkey_fingerprint(host_key, options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) fatal_f("sshkey_fingerprint failed"); if ((r = sshkey_to_base64(host_key, &keytext)) != 0) fatal_fr(r, "sshkey_to_base64 failed"); } /* * NB. all returns later this function should go via "out" to * ensure the original SIGCHLD handler is restored properly. */ osigchld = ssh_signal(SIGCHLD, SIG_DFL); /* Turn the command into an argument vector */ if (argv_split(command_template, &ac, &av, 0) != 0) { error("%s \"%s\" contains invalid quotes", tag, command_template); goto out; } if (ac == 0) { error("%s \"%s\" yielded no arguments", tag, command_template); goto out; } for (i = 1; i < ac; i++) { tmp = percent_dollar_expand(av[i], DEFAULT_CLIENT_PERCENT_EXPAND_ARGS(cinfo), "H", hostfile_hostname, "I", invocation, "t", host_key == NULL ? "NONE" : sshkey_ssh_name(host_key), "f", key_fp == NULL ? "NONE" : key_fp, "K", keytext == NULL ? "NONE" : keytext, (char *)NULL); if (tmp == NULL) fatal_f("percent_expand failed"); free(av[i]); av[i] = tmp; } /* Prepare a printable command for logs, etc. */ command = argv_assemble(ac, av); if ((pid = subprocess(tag, command, ac, av, &f, SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_UNSAFE_PATH| SSH_SUBPROCESS_PRESERVE_ENV, NULL, NULL, NULL)) == 0) goto out; load_hostkeys_file(hostkeys, hostfile_hostname, tag, f, 1); if (exited_cleanly(pid, tag, command, 0) != 0) fatal("KnownHostsCommand failed"); out: if (f != NULL) fclose(f); ssh_signal(SIGCHLD, osigchld); for (i = 0; i < ac; i++) free(av[i]); free(av); free(tag); free(command); free(key_fp); free(keytext); } /* * check whether the supplied host key is valid, return -1 if the key * is not valid. user_hostfile[0] will not be updated if 'readonly' is true. */ #define RDRW 0 #define RDONLY 1 #define ROQUIET 2 static int check_host_key(char *hostname, const struct ssh_conn_info *cinfo, struct sockaddr *hostaddr, u_short port, struct sshkey *host_key, int readonly, int clobber_port, char **user_hostfiles, u_int num_user_hostfiles, char **system_hostfiles, u_int num_system_hostfiles, const char *hostfile_command) { HostStatus host_status = -1, ip_status = -1; struct sshkey *raw_key = NULL; char *ip = NULL, *host = NULL; char hostline[1000], *hostp, *fp, *ra; char msg[1024]; const char *type, *fail_reason; const struct hostkey_entry *host_found = NULL, *ip_found = NULL; int len, cancelled_forwarding = 0, confirmed; int local = sockaddr_is_local(hostaddr); int r, want_cert = sshkey_is_cert(host_key), host_ip_differ = 0; int hostkey_trusted = 0; /* Known or explicitly accepted by user */ struct hostkeys *host_hostkeys, *ip_hostkeys; u_int i; /* * Force accepting of the host key for loopback/localhost. The * problem is that if the home directory is NFS-mounted to multiple * machines, localhost will refer to a different machine in each of * them, and the user will get bogus HOST_CHANGED warnings. This * essentially disables host authentication for localhost; however, * this is probably not a real problem. */ if (options.no_host_authentication_for_localhost == 1 && local && options.host_key_alias == NULL) { debug("Forcing accepting of host key for " "loopback/localhost."); options.update_hostkeys = 0; return 0; } /* * Prepare the hostname and address strings used for hostkey lookup. * In some cases, these will have a port number appended. */ get_hostfile_hostname_ipaddr(hostname, hostaddr, clobber_port ? 0 : port, &host, &ip); /* * Turn off check_host_ip if the connection is to localhost, via proxy * command or if we don't have a hostname to compare with */ if (options.check_host_ip && (local || strcmp(hostname, ip) == 0 || options.proxy_command != NULL)) options.check_host_ip = 0; host_hostkeys = init_hostkeys(); for (i = 0; i < num_user_hostfiles; i++) load_hostkeys(host_hostkeys, host, user_hostfiles[i], 0); for (i = 0; i < num_system_hostfiles; i++) load_hostkeys(host_hostkeys, host, system_hostfiles[i], 0); if (hostfile_command != NULL && !clobber_port) { load_hostkeys_command(host_hostkeys, hostfile_command, "HOSTNAME", cinfo, host_key, host); } ip_hostkeys = NULL; if (!want_cert && options.check_host_ip) { ip_hostkeys = init_hostkeys(); for (i = 0; i < num_user_hostfiles; i++) load_hostkeys(ip_hostkeys, ip, user_hostfiles[i], 0); for (i = 0; i < num_system_hostfiles; i++) load_hostkeys(ip_hostkeys, ip, system_hostfiles[i], 0); if (hostfile_command != NULL && !clobber_port) { load_hostkeys_command(ip_hostkeys, hostfile_command, "ADDRESS", cinfo, host_key, ip); } } retry: /* Reload these as they may have changed on cert->key downgrade */ want_cert = sshkey_is_cert(host_key); type = sshkey_type(host_key); /* * Check if the host key is present in the user's list of known * hosts or in the systemwide list. */ host_status = check_key_in_hostkeys(host_hostkeys, host_key, &host_found); /* * If there are no hostfiles, or if the hostkey was found via * KnownHostsCommand, then don't try to touch the disk. */ if (!readonly && (num_user_hostfiles == 0 || (host_found != NULL && host_found->note != 0))) readonly = RDONLY; /* * Also perform check for the ip address, skip the check if we are * localhost, looking for a certificate, or the hostname was an ip * address to begin with. */ if (!want_cert && ip_hostkeys != NULL) { ip_status = check_key_in_hostkeys(ip_hostkeys, host_key, &ip_found); if (host_status == HOST_CHANGED && (ip_status != HOST_CHANGED || (ip_found != NULL && !sshkey_equal(ip_found->key, host_found->key)))) host_ip_differ = 1; } else ip_status = host_status; switch (host_status) { case HOST_OK: /* The host is known and the key matches. */ debug("Host '%.200s' is known and matches the %s host %s.", host, type, want_cert ? "certificate" : "key"); debug("Found %s in %s:%lu", want_cert ? "CA key" : "key", host_found->file, host_found->line); if (want_cert) { if (sshkey_cert_check_host(host_key, options.host_key_alias == NULL ? hostname : options.host_key_alias, 0, options.ca_sign_algorithms, &fail_reason) != 0) { error("%s", fail_reason); goto fail; } /* * Do not attempt hostkey update if a certificate was * successfully matched. */ if (options.update_hostkeys != 0) { options.update_hostkeys = 0; debug3_f("certificate host key in use; " "disabling UpdateHostkeys"); } } /* Turn off UpdateHostkeys if key was in system known_hosts */ if (options.update_hostkeys != 0 && (path_in_hostfiles(host_found->file, system_hostfiles, num_system_hostfiles) || (ip_status == HOST_OK && ip_found != NULL && path_in_hostfiles(ip_found->file, system_hostfiles, num_system_hostfiles)))) { options.update_hostkeys = 0; debug3_f("host key found in GlobalKnownHostsFile; " "disabling UpdateHostkeys"); } if (options.update_hostkeys != 0 && host_found->note) { options.update_hostkeys = 0; debug3_f("host key found via KnownHostsCommand; " "disabling UpdateHostkeys"); } if (options.check_host_ip && ip_status == HOST_NEW) { if (readonly || want_cert) logit("%s host key for IP address " "'%.128s' not in list of known hosts.", type, ip); else if (!add_host_to_hostfile(user_hostfiles[0], ip, host_key, options.hash_known_hosts)) logit("Failed to add the %s host key for IP " "address '%.128s' to the list of known " "hosts (%.500s).", type, ip, user_hostfiles[0]); else logit("Warning: Permanently added the %s host " "key for IP address '%.128s' to the list " "of known hosts.", type, ip); } else if (options.visual_host_key) { fp = sshkey_fingerprint(host_key, options.fingerprint_hash, SSH_FP_DEFAULT); ra = sshkey_fingerprint(host_key, options.fingerprint_hash, SSH_FP_RANDOMART); if (fp == NULL || ra == NULL) fatal_f("sshkey_fingerprint failed"); logit("Host key fingerprint is %s\n%s", fp, ra); free(ra); free(fp); } hostkey_trusted = 1; break; case HOST_NEW: if (options.host_key_alias == NULL && port != 0 && port != SSH_DEFAULT_PORT && !clobber_port) { debug("checking without port identifier"); if (check_host_key(hostname, cinfo, hostaddr, 0, host_key, ROQUIET, 1, user_hostfiles, num_user_hostfiles, system_hostfiles, num_system_hostfiles, hostfile_command) == 0) { debug("found matching key w/out port"); break; } } if (readonly || want_cert) goto fail; /* The host is new. */ if (options.strict_host_key_checking == SSH_STRICT_HOSTKEY_YES) { /* * User has requested strict host key checking. We * will not add the host key automatically. The only * alternative left is to abort. */ error("No %s host key is known for %.200s and you " "have requested strict checking.", type, host); goto fail; } else if (options.strict_host_key_checking == SSH_STRICT_HOSTKEY_ASK) { char *msg1 = NULL, *msg2 = NULL; xasprintf(&msg1, "The authenticity of host " "'%.200s (%s)' can't be established", host, ip); if (show_other_keys(host_hostkeys, host_key)) { xextendf(&msg1, "\n", "but keys of different " "type are already known for this host."); } else xextendf(&msg1, "", "."); fp = sshkey_fingerprint(host_key, options.fingerprint_hash, SSH_FP_DEFAULT); ra = sshkey_fingerprint(host_key, options.fingerprint_hash, SSH_FP_RANDOMART); if (fp == NULL || ra == NULL) fatal_f("sshkey_fingerprint failed"); xextendf(&msg1, "\n", "%s key fingerprint is %s.", type, fp); if (options.visual_host_key) xextendf(&msg1, "\n", "%s", ra); if (options.verify_host_key_dns) { xextendf(&msg1, "\n", "%s host key fingerprint found in DNS.", matching_host_key_dns ? "Matching" : "No matching"); } /* msg2 informs for other names matching this key */ if ((msg2 = other_hostkeys_message(host, ip, host_key, user_hostfiles, num_user_hostfiles, system_hostfiles, num_system_hostfiles)) != NULL) xextendf(&msg1, "\n", "%s", msg2); xextendf(&msg1, "\n", "Are you sure you want to continue connecting " "(yes/no/[fingerprint])? "); confirmed = confirm(msg1, fp); free(ra); free(fp); free(msg1); free(msg2); if (!confirmed) goto fail; hostkey_trusted = 1; /* user explicitly confirmed */ } /* * If in "new" or "off" strict mode, add the key automatically * to the local known_hosts file. */ if (options.check_host_ip && ip_status == HOST_NEW) { snprintf(hostline, sizeof(hostline), "%s,%s", host, ip); hostp = hostline; if (options.hash_known_hosts) { /* Add hash of host and IP separately */ r = add_host_to_hostfile(user_hostfiles[0], host, host_key, options.hash_known_hosts) && add_host_to_hostfile(user_hostfiles[0], ip, host_key, options.hash_known_hosts); } else { /* Add unhashed "host,ip" */ r = add_host_to_hostfile(user_hostfiles[0], hostline, host_key, options.hash_known_hosts); } } else { r = add_host_to_hostfile(user_hostfiles[0], host, host_key, options.hash_known_hosts); hostp = host; } if (!r) logit("Failed to add the host to the list of known " "hosts (%.500s).", user_hostfiles[0]); else logit("Warning: Permanently added '%.200s' (%s) to the " "list of known hosts.", hostp, type); break; case HOST_REVOKED: error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("@ WARNING: REVOKED HOST KEY DETECTED! @"); error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("The %s host key for %s is marked as revoked.", type, host); error("This could mean that a stolen key is being used to"); error("impersonate this host."); /* * If strict host key checking is in use, the user will have * to edit the key manually and we can only abort. */ if (options.strict_host_key_checking != SSH_STRICT_HOSTKEY_OFF) { error("%s host key for %.200s was revoked and you have " "requested strict checking.", type, host); goto fail; } goto continue_unsafe; case HOST_CHANGED: if (want_cert) { /* * This is only a debug() since it is valid to have * CAs with wildcard DNS matches that don't match * all hosts that one might visit. */ debug("Host certificate authority does not " "match %s in %s:%lu", CA_MARKER, host_found->file, host_found->line); goto fail; } if (readonly == ROQUIET) goto fail; if (options.check_host_ip && host_ip_differ) { char *key_msg; if (ip_status == HOST_NEW) key_msg = "is unknown"; else if (ip_status == HOST_OK) key_msg = "is unchanged"; else key_msg = "has a different value"; error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("@ WARNING: POSSIBLE DNS SPOOFING DETECTED! @"); error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("The %s host key for %s has changed,", type, host); error("and the key for the corresponding IP address %s", ip); error("%s. This could either mean that", key_msg); error("DNS SPOOFING is happening or the IP address for the host"); error("and its host key have changed at the same time."); if (ip_status != HOST_NEW) error("Offending key for IP in %s:%lu", ip_found->file, ip_found->line); } /* The host key has changed. */ warn_changed_key(host_key); error("Add correct host key in %.100s to get rid of this message.", user_hostfiles[0]); error("Offending %s key in %s:%lu", sshkey_type(host_found->key), host_found->file, host_found->line); /* * If strict host key checking is in use, the user will have * to edit the key manually and we can only abort. */ if (options.strict_host_key_checking != SSH_STRICT_HOSTKEY_OFF) { error("Host key for %.200s has changed and you have " "requested strict checking.", host); goto fail; } continue_unsafe: /* * If strict host key checking has not been requested, allow * the connection but without MITM-able authentication or * forwarding. */ if (options.password_authentication) { error("Password authentication is disabled to avoid " "man-in-the-middle attacks."); options.password_authentication = 0; cancelled_forwarding = 1; } if (options.kbd_interactive_authentication) { error("Keyboard-interactive authentication is disabled" " to avoid man-in-the-middle attacks."); options.kbd_interactive_authentication = 0; cancelled_forwarding = 1; } if (options.forward_agent) { error("Agent forwarding is disabled to avoid " "man-in-the-middle attacks."); options.forward_agent = 0; cancelled_forwarding = 1; } if (options.forward_x11) { error("X11 forwarding is disabled to avoid " "man-in-the-middle attacks."); options.forward_x11 = 0; cancelled_forwarding = 1; } if (options.num_local_forwards > 0 || options.num_remote_forwards > 0) { error("Port forwarding is disabled to avoid " "man-in-the-middle attacks."); options.num_local_forwards = options.num_remote_forwards = 0; cancelled_forwarding = 1; } if (options.tun_open != SSH_TUNMODE_NO) { error("Tunnel forwarding is disabled to avoid " "man-in-the-middle attacks."); options.tun_open = SSH_TUNMODE_NO; cancelled_forwarding = 1; } if (options.update_hostkeys != 0) { error("UpdateHostkeys is disabled because the host " "key is not trusted."); options.update_hostkeys = 0; } if (options.exit_on_forward_failure && cancelled_forwarding) fatal("Error: forwarding disabled due to host key " "check failure"); /* * XXX Should permit the user to change to use the new id. * This could be done by converting the host key to an * identifying sentence, tell that the host identifies itself * by that sentence, and ask the user if they wish to * accept the authentication. */ break; case HOST_FOUND: fatal("internal error"); break; } if (options.check_host_ip && host_status != HOST_CHANGED && ip_status == HOST_CHANGED) { snprintf(msg, sizeof(msg), "Warning: the %s host key for '%.200s' " "differs from the key for the IP address '%.128s'" "\nOffending key for IP in %s:%lu", type, host, ip, ip_found->file, ip_found->line); if (host_status == HOST_OK) { len = strlen(msg); snprintf(msg + len, sizeof(msg) - len, "\nMatching host key in %s:%lu", host_found->file, host_found->line); } if (options.strict_host_key_checking == SSH_STRICT_HOSTKEY_ASK) { strlcat(msg, "\nAre you sure you want " "to continue connecting (yes/no)? ", sizeof(msg)); if (!confirm(msg, NULL)) goto fail; } else if (options.strict_host_key_checking != SSH_STRICT_HOSTKEY_OFF) { logit("%s", msg); error("Exiting, you have requested strict checking."); goto fail; } else { logit("%s", msg); } } if (!hostkey_trusted && options.update_hostkeys) { debug_f("hostkey not known or explicitly trusted: " "disabling UpdateHostkeys"); options.update_hostkeys = 0; } free(ip); free(host); if (host_hostkeys != NULL) free_hostkeys(host_hostkeys); if (ip_hostkeys != NULL) free_hostkeys(ip_hostkeys); return 0; fail: if (want_cert && host_status != HOST_REVOKED) { /* * No matching certificate. Downgrade cert to raw key and * search normally. */ debug("No matching CA found. Retry with plain key"); if ((r = sshkey_from_private(host_key, &raw_key)) != 0) fatal_fr(r, "decode key"); if ((r = sshkey_drop_cert(raw_key)) != 0) fatal_r(r, "Couldn't drop certificate"); host_key = raw_key; goto retry; } sshkey_free(raw_key); free(ip); free(host); if (host_hostkeys != NULL) free_hostkeys(host_hostkeys); if (ip_hostkeys != NULL) free_hostkeys(ip_hostkeys); return -1; } /* returns 0 if key verifies or -1 if key does NOT verify */ int verify_host_key(char *host, struct sockaddr *hostaddr, struct sshkey *host_key, const struct ssh_conn_info *cinfo) { u_int i; int r = -1, flags = 0; char valid[64], *fp = NULL, *cafp = NULL; struct sshkey *plain = NULL; if ((fp = sshkey_fingerprint(host_key, options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) { error_fr(r, "fingerprint host key"); r = -1; goto out; } if (sshkey_is_cert(host_key)) { if ((cafp = sshkey_fingerprint(host_key->cert->signature_key, options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) { error_fr(r, "fingerprint CA key"); r = -1; goto out; } sshkey_format_cert_validity(host_key->cert, valid, sizeof(valid)); debug("Server host certificate: %s %s, serial %llu " "ID \"%s\" CA %s %s valid %s", sshkey_ssh_name(host_key), fp, (unsigned long long)host_key->cert->serial, host_key->cert->key_id, sshkey_ssh_name(host_key->cert->signature_key), cafp, valid); for (i = 0; i < host_key->cert->nprincipals; i++) { debug2("Server host certificate hostname: %s", host_key->cert->principals[i]); } } else { debug("Server host key: %s %s", sshkey_ssh_name(host_key), fp); } if (sshkey_equal(previous_host_key, host_key)) { debug2_f("server host key %s %s matches cached key", sshkey_type(host_key), fp); r = 0; goto out; } /* Check in RevokedHostKeys file if specified */ if (options.revoked_host_keys != NULL) { r = sshkey_check_revoked(host_key, options.revoked_host_keys); switch (r) { case 0: break; /* not revoked */ case SSH_ERR_KEY_REVOKED: error("Host key %s %s revoked by file %s", sshkey_type(host_key), fp, options.revoked_host_keys); r = -1; goto out; default: error_r(r, "Error checking host key %s %s in " "revoked keys file %s", sshkey_type(host_key), fp, options.revoked_host_keys); r = -1; goto out; } } if (options.verify_host_key_dns) { /* * XXX certs are not yet supported for DNS, so downgrade * them and try the plain key. */ if ((r = sshkey_from_private(host_key, &plain)) != 0) goto out; if (sshkey_is_cert(plain)) sshkey_drop_cert(plain); if (verify_host_key_dns(host, hostaddr, plain, &flags) == 0) { if (flags & DNS_VERIFY_FOUND) { if (options.verify_host_key_dns == 1 && flags & DNS_VERIFY_MATCH && flags & DNS_VERIFY_SECURE) { r = 0; goto out; } if (flags & DNS_VERIFY_MATCH) { matching_host_key_dns = 1; } else { warn_changed_key(plain); error("Update the SSHFP RR in DNS " "with the new host key to get rid " "of this message."); } } } } r = check_host_key(host, cinfo, hostaddr, options.port, host_key, RDRW, 0, options.user_hostfiles, options.num_user_hostfiles, options.system_hostfiles, options.num_system_hostfiles, options.known_hosts_command); out: sshkey_free(plain); free(fp); free(cafp); if (r == 0 && host_key != NULL) { sshkey_free(previous_host_key); r = sshkey_from_private(host_key, &previous_host_key); } return r; } /* * Starts a dialog with the server, and authenticates the current user on the * server. This does not need any extra privileges. The basic connection * to the server must already have been established before this is called. * If login fails, this function prints an error and never returns. * This function does not require super-user privileges. */ void ssh_login(struct ssh *ssh, Sensitive *sensitive, const char *orighost, struct sockaddr *hostaddr, u_short port, struct passwd *pw, int timeout_ms, const struct ssh_conn_info *cinfo) { char *host; char *server_user, *local_user; int r; local_user = xstrdup(pw->pw_name); server_user = options.user ? options.user : local_user; /* Convert the user-supplied hostname into all lowercase. */ host = xstrdup(orighost); lowercase(host); /* Exchange protocol version identification strings with the server. */ - if ((r = kex_exchange_identification(ssh, timeout_ms, - options.version_addendum)) != 0) + if ((r = kex_exchange_identification(ssh, timeout_ms, NULL)) != 0) sshpkt_fatal(ssh, r, "banner exchange"); /* Put the connection into non-blocking mode. */ ssh_packet_set_nonblocking(ssh); /* key exchange */ /* authenticate user */ debug("Authenticating to %s:%d as '%s'", host, port, server_user); ssh_kex2(ssh, host, hostaddr, port, cinfo); ssh_userauth2(ssh, local_user, server_user, host, sensitive); free(local_user); free(host); } /* print all known host keys for a given host, but skip keys of given type */ static int show_other_keys(struct hostkeys *hostkeys, struct sshkey *key) { int type[] = { KEY_RSA, KEY_DSA, KEY_ECDSA, KEY_ED25519, KEY_XMSS, -1 }; int i, ret = 0; char *fp, *ra; const struct hostkey_entry *found; for (i = 0; type[i] != -1; i++) { if (type[i] == key->type) continue; if (!lookup_key_in_hostkeys_by_type(hostkeys, type[i], -1, &found)) continue; fp = sshkey_fingerprint(found->key, options.fingerprint_hash, SSH_FP_DEFAULT); ra = sshkey_fingerprint(found->key, options.fingerprint_hash, SSH_FP_RANDOMART); if (fp == NULL || ra == NULL) fatal_f("sshkey_fingerprint fail"); logit("WARNING: %s key found for host %s\n" "in %s:%lu\n" "%s key fingerprint %s.", sshkey_type(found->key), found->host, found->file, found->line, sshkey_type(found->key), fp); if (options.visual_host_key) logit("%s", ra); free(ra); free(fp); ret = 1; } return ret; } static void warn_changed_key(struct sshkey *host_key) { char *fp; fp = sshkey_fingerprint(host_key, options.fingerprint_hash, SSH_FP_DEFAULT); if (fp == NULL) fatal_f("sshkey_fingerprint fail"); error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @"); error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!"); error("Someone could be eavesdropping on you right now (man-in-the-middle attack)!"); error("It is also possible that a host key has just been changed."); error("The fingerprint for the %s key sent by the remote host is\n%s.", sshkey_type(host_key), fp); error("Please contact your system administrator."); free(fp); } /* * Execute a local command */ int ssh_local_cmd(const char *args) { char *shell; pid_t pid; int status; void (*osighand)(int); if (!options.permit_local_command || args == NULL || !*args) return (1); if ((shell = getenv("SHELL")) == NULL || *shell == '\0') shell = _PATH_BSHELL; osighand = ssh_signal(SIGCHLD, SIG_DFL); pid = fork(); if (pid == 0) { ssh_signal(SIGPIPE, SIG_DFL); debug3("Executing %s -c \"%s\"", shell, args); execl(shell, shell, "-c", args, (char *)NULL); error("Couldn't execute %s -c \"%s\": %s", shell, args, strerror(errno)); _exit(1); } else if (pid == -1) fatal("fork failed: %.100s", strerror(errno)); while (waitpid(pid, &status, 0) == -1) if (errno != EINTR) fatal("Couldn't wait for child: %s", strerror(errno)); ssh_signal(SIGCHLD, osighand); if (!WIFEXITED(status)) return (1); return (WEXITSTATUS(status)); } void maybe_add_key_to_agent(const char *authfile, struct sshkey *private, const char *comment, const char *passphrase) { int auth_sock = -1, r; const char *skprovider = NULL; if (options.add_keys_to_agent == 0) return; if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { debug3("no authentication agent, not adding key"); return; } if (options.add_keys_to_agent == 2 && !ask_permission("Add key %s (%s) to agent?", authfile, comment)) { debug3("user denied adding this key"); close(auth_sock); return; } if (sshkey_is_sk(private)) skprovider = options.sk_provider; if ((r = ssh_add_identity_constrained(auth_sock, private, comment == NULL ? authfile : comment, options.add_keys_to_agent_lifespan, (options.add_keys_to_agent == 3), 0, skprovider, NULL, 0)) == 0) debug("identity added to agent: %s", authfile); else debug("could not add identity to agent: %s (%d)", authfile, r); close(auth_sock); }