diff options
Diffstat (limited to 'ssh.c')
| -rw-r--r-- | ssh.c | 344 |
1 files changed, 225 insertions, 119 deletions
@@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.464 2017/09/21 19:16:53 markus Exp $ */ +/* $OpenBSD: ssh.c,v 1.475 2018/02/23 15:58:38 markus Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -168,6 +168,10 @@ char *config = NULL; */ char *host; +/* Various strings used to to percent_expand() arguments */ +static char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV]; +static char uidstr[32], *host_arg, *conn_hash_hex; + /* socket address the host resolves to */ struct sockaddr_storage hostaddr; @@ -197,19 +201,19 @@ static void usage(void) { fprintf(stderr, -"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec]\n" -" [-D [bind_address:]port] [-E log_file] [-e escape_char]\n" -" [-F configfile] [-I pkcs11] [-i identity_file]\n" -" [-J [user@]host[:port]] [-L address] [-l login_name] [-m mac_spec]\n" -" [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address]\n" -" [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]\n" -" [user@]hostname [command]\n" +"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]\n" ); exit(255); } -static int ssh_session2(struct ssh *); -static void load_public_identity_files(void); +static int ssh_session2(struct ssh *, struct passwd *); +static void load_public_identity_files(struct passwd *); static void main_sigchld_handler(int); /* ~/ expand a list of paths. NB. assumes path[n] is heap-allocated. */ @@ -267,6 +271,40 @@ resolve_host(const char *name, int port, int logerr, char *cname, size_t clen) return res; } +/* Returns non-zero if name can only be an address and not a hostname */ +static int +is_addr_fast(const char *name) +{ + return (strchr(name, '%') != NULL || strchr(name, ':') != NULL || + strspn(name, "0123456789.") == strlen(name)); +} + +/* Returns non-zero if name represents a valid, single address */ +static int +is_addr(const char *name) +{ + char strport[NI_MAXSERV]; + struct addrinfo hints, *res; + + if (is_addr_fast(name)) + return 1; + + snprintf(strport, sizeof strport, "%u", default_ssh_port()); + memset(&hints, 0, sizeof(hints)); + hints.ai_family = options.address_family == -1 ? + AF_UNSPEC : options.address_family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV; + if (getaddrinfo(name, strport, &hints, &res) != 0) + return 0; + if (res == NULL || res->ai_next != NULL) { + freeaddrinfo(res); + return 0; + } + freeaddrinfo(res); + return 1; +} + /* * Attempt to resolve a numeric host address / port to a single address. * Returns a canonical address string. @@ -372,20 +410,10 @@ resolve_canonicalize(char **hostp, int port) char *cp, *fullhost, newname[NI_MAXHOST]; struct addrinfo *addrs; - 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. + * Attempt to canonicalise addresses, regardless of + * whether hostname canonicalisation was requested */ - direct = option_clear_or_none(options.proxy_command) && - options.jump_host == NULL; - if (!direct && - options.canonicalize_hostname != SSH_CANONICALISE_ALWAYS) - return NULL; - - /* Try numeric hostnames first */ if ((addrs = resolve_addr(*hostp, port, newname, sizeof(newname))) != NULL) { debug2("%s: hostname %.100s is address", __func__, *hostp); @@ -398,6 +426,30 @@ resolve_canonicalize(char **hostp, int port) return addrs; } + /* + * If this looks like an address but didn't parse as one, it might + * be an address with an invalid interface scope. Skip further + * attempts at canonicalisation. + */ + if (is_addr_fast(*hostp)) { + debug("%s: hostname %.100s is an unrecognised address", + __func__, *hostp); + return NULL; + } + + if (options.canonicalize_hostname == SSH_CANONICALISE_NO) + return NULL; + + /* + * 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("%s: name is fully qualified", __func__); @@ -456,14 +508,14 @@ resolve_canonicalize(char **hostp, int port) * file if the user specifies a config file on the command line. */ static void -process_config_files(const char *host_arg, struct passwd *pw, int post_canon) +process_config_files(const char *host_name, struct passwd *pw, int post_canon) { char buf[PATH_MAX]; int r; if (config != NULL) { if (strcasecmp(config, "none") != 0 && - !read_config_file(config, pw, host, host_arg, &options, + !read_config_file(config, pw, host, host_name, &options, SSHCONF_USERCONF | (post_canon ? SSHCONF_POSTCANON : 0))) fatal("Can't open user config file %.100s: " "%.100s", config, strerror(errno)); @@ -471,13 +523,13 @@ process_config_files(const char *host_arg, struct passwd *pw, int post_canon) 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_arg, + (void)read_config_file(buf, pw, host, host_name, &options, SSHCONF_CHECKPERM | SSHCONF_USERCONF | (post_canon ? SSHCONF_POSTCANON : 0)); /* Read systemwide configuration file after user config. */ (void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, - host, host_arg, &options, + host, host_name, &options, post_canon ? SSHCONF_POSTCANON : 0); } } @@ -510,10 +562,9 @@ main(int ac, char **av) { struct ssh *ssh = NULL; int i, r, opt, exit_status, use_syslog, direct, timeout_ms; - int config_test = 0, opt_terminated = 0; - char *p, *cp, *line, *argv0, buf[PATH_MAX], *host_arg, *logfile; - char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV]; - char cname[NI_MAXHOST], uidstr[32], *conn_hash_hex; + int was_addr, config_test = 0, opt_terminated = 0; + char *p, *cp, *line, *argv0, buf[PATH_MAX], *logfile; + char cname[NI_MAXHOST]; struct stat st; struct passwd *pw; extern int optind, optreset; @@ -612,7 +663,7 @@ main(int ac, char **av) again: while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" - "ACD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) { + "AB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) { switch (opt) { case '1': fatal("SSH protocol v.1 is no longer supported"); @@ -846,14 +897,18 @@ main(int ac, char **av) options.control_master = SSHCTL_MASTER_YES; break; case 'p': - options.port = a2port(optarg); - if (options.port <= 0) { - fprintf(stderr, "Bad port '%s'\n", optarg); - exit(255); + if (options.port == -1) { + options.port = a2port(optarg); + if (options.port <= 0) { + fprintf(stderr, "Bad port '%s'\n", + optarg); + exit(255); + } } break; case 'l': - options.user = optarg; + if (options.user == NULL) + options.user = optarg; break; case 'L': @@ -918,6 +973,9 @@ main(int ac, char **av) case 'b': options.bind_address = optarg; break; + case 'B': + options.bind_interface = optarg; + break; case 'F': config = optarg; break; @@ -933,16 +991,38 @@ main(int ac, char **av) av += optind; if (ac > 0 && !host) { - if (strrchr(*av, '@')) { + 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 || cp == p) - usage(); - options.user = p; - *cp = '\0'; - host = xstrdup(++cp); - } else - host = xstrdup(*av); + 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; @@ -994,9 +1074,9 @@ main(int ac, char **av) if (logfile != NULL) log_redirect_stderr_to(logfile); log_init(argv0, - options.log_level == SYSLOG_LEVEL_NOT_SET ? + options.log_level == SYSLOG_LEVEL_NOT_SET ? SYSLOG_LEVEL_INFO : options.log_level, - options.log_facility == SYSLOG_FACILITY_NOT_SET ? + options.log_facility == SYSLOG_FACILITY_NOT_SET ? SYSLOG_FACILITY_USER : options.log_facility, !use_syslog); @@ -1026,16 +1106,22 @@ main(int ac, char **av) options.hostname = xstrdup(host); } - /* If canonicalization requested then try to apply it */ - lowercase(host); - if (options.canonicalize_hostname != SSH_CANONICALISE_NO) + /* Don't lowercase addresses, they will be explicitly canonicalised */ + if ((was_addr = is_addr(host)) == 0) + lowercase(host); + + /* + * Try to canonicalize if requested by configuration or the + * hostname is an address. + */ + if (options.canonicalize_hostname != SSH_CANONICALISE_NO || was_addr) addrs = resolve_canonicalize(&host, options.port); /* * 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. + * 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. @@ -1177,6 +1263,7 @@ main(int ac, char **av) if (options.user == NULL) options.user = xstrdup(pw->pw_name); + /* Set up strings used to percent_expand() arguments */ if (gethostname(thishost, sizeof(thishost)) == -1) fatal("gethostname: %s", strerror(errno)); strlcpy(shorthost, thishost, sizeof(shorthost)); @@ -1194,24 +1281,11 @@ main(int ac, char **av) ssh_digest_free(md); conn_hash_hex = tohex(conn_hash, ssh_digest_bytes(SSH_DIGEST_SHA1)); - if (options.local_command != NULL) { - debug3("expanding LocalCommand: %s", options.local_command); - cp = options.local_command; - options.local_command = percent_expand(cp, - "C", conn_hash_hex, - "L", shorthost, - "d", pw->pw_dir, - "h", host, - "l", thishost, - "n", host_arg, - "p", portstr, - "r", options.user, - "u", pw->pw_name, - (char *)NULL); - debug3("expanded LocalCommand: %s", options.local_command); - free(cp); - } - + /* + * 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; @@ -1230,7 +1304,6 @@ main(int ac, char **av) free(cp); buffer_append(&command, options.remote_command, strlen(options.remote_command)); - } if (options.control_path != NULL) { @@ -1311,7 +1384,7 @@ main(int ac, char **av) sensitive_data.keys = NULL; sensitive_data.external_keysign = 0; if (options.hostbased_authentication) { - sensitive_data.nkeys = 9; + sensitive_data.nkeys = 11; sensitive_data.keys = xcalloc(sensitive_data.nkeys, sizeof(struct sshkey)); /* XXX */ for (i = 0; i < sensitive_data.nkeys; i++) @@ -1338,6 +1411,10 @@ main(int ac, char **av) _PATH_HOST_RSA_KEY_FILE, "", NULL, NULL); sensitive_data.keys[8] = key_load_private_type(KEY_DSA, _PATH_HOST_DSA_KEY_FILE, "", NULL, NULL); + sensitive_data.keys[9] = key_load_private_cert(KEY_XMSS, + _PATH_HOST_XMSS_KEY_FILE, "", NULL); + sensitive_data.keys[10] = key_load_private_type(KEY_XMSS, + _PATH_HOST_XMSS_KEY_FILE, "", NULL, NULL); PRIV_END; if (options.hostbased_authentication == 1 && @@ -1345,7 +1422,8 @@ main(int ac, char **av) sensitive_data.keys[5] == NULL && sensitive_data.keys[6] == NULL && sensitive_data.keys[7] == NULL && - sensitive_data.keys[8] == NULL) { + sensitive_data.keys[8] == NULL && + sensitive_data.keys[9] == NULL) { #ifdef OPENSSL_HAS_ECC sensitive_data.keys[1] = key_load_cert( _PATH_HOST_ECDSA_KEY_FILE); @@ -1366,6 +1444,10 @@ main(int ac, char **av) _PATH_HOST_RSA_KEY_FILE, NULL); sensitive_data.keys[8] = key_load_public( _PATH_HOST_DSA_KEY_FILE, NULL); + sensitive_data.keys[9] = key_load_cert( + _PATH_HOST_XMSS_KEY_FILE); + sensitive_data.keys[10] = key_load_public( + _PATH_HOST_XMSS_KEY_FILE, NULL); sensitive_data.external_keysign = 1; } } @@ -1401,7 +1483,7 @@ main(int ac, char **av) } } /* load options.identity_files */ - load_public_identity_files(); + load_public_identity_files(pw); /* optionally set the SSH_AUTHSOCKET_ENV_NAME varibale */ if (options.identity_agent && @@ -1465,7 +1547,7 @@ main(int ac, char **av) } skip_connect: - exit_status = ssh_session2(ssh); + exit_status = ssh_session2(ssh, pw); packet_close(); if (options.control_path != NULL && muxserver_sock != -1) @@ -1485,29 +1567,29 @@ control_persist_detach(void) debug("%s: backgrounding master process", __func__); - /* - * master (current process) into the background, and make the - * foreground process a client of the backgrounded master. - */ + /* + * master (current process) into the background, and make the + * foreground process a client of the backgrounded master. + */ switch ((pid = fork())) { case -1: fatal("%s: fork: %s", __func__, strerror(errno)); case 0: /* Child: master process continues mainloop */ - break; - default: + break; + default: /* Parent: set up mux slave to connect to backgrounded master */ debug2("%s: background process is %ld", __func__, (long)pid); stdin_null_flag = ostdin_null_flag; options.request_tty = orequest_tty; tty_flag = otty_flag; - close(muxserver_sock); - muxserver_sock = -1; + close(muxserver_sock); + muxserver_sock = -1; options.control_master = SSHCTL_MASTER_NO; - muxclient(options.control_path); + muxclient(options.control_path); /* muxclient() doesn't return on success. */ - fatal("Failed to connect to new control master"); - } + fatal("Failed to connect to new control master"); + } if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { error("%s: open(\"/dev/null\"): %s", __func__, strerror(errno)); @@ -1562,7 +1644,7 @@ ssh_confirm_remote_forward(struct ssh *ssh, int type, u_int32_t seq, void *ctxt) channel_update_permitted_opens(ssh, rfwd->handle, -1); } } - + if (type == SSH2_MSG_REQUEST_FAILURE) { if (options.exit_on_forward_failure) { if (rfwd->listen_path != NULL) @@ -1624,7 +1706,7 @@ ssh_init_stdio_forwarding(struct ssh *ssh) } static void -ssh_init_forwarding(struct ssh *ssh) +ssh_init_forwarding(struct ssh *ssh, char **ifname) { int success = 0; int i; @@ -1682,14 +1764,15 @@ ssh_init_forwarding(struct ssh *ssh) /* Initiate tunnel forwarding. */ if (options.tun_open != SSH_TUNMODE_NO) { - if (client_request_tun_fwd(ssh, options.tun_open, - options.tun_local, options.tun_remote) == -1) { + if ((*ifname = client_request_tun_fwd(ssh, + options.tun_open, options.tun_local, + options.tun_remote)) == NULL) { if (options.exit_on_forward_failure) fatal("Could not request tunnel forwarding."); else error("Could not request tunnel forwarding."); } - } + } } static void @@ -1798,20 +1881,41 @@ ssh_session2_open(struct ssh *ssh) } static int -ssh_session2(struct ssh *ssh) +ssh_session2(struct ssh *ssh, struct passwd *pw) { - int id = -1; + int devnull, 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); + + 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, + "C", conn_hash_hex, + "L", shorthost, + "d", pw->pw_dir, + "h", host, + "l", thishost, + "n", host_arg, + "p", portstr, + "r", options.user, + "u", pw->pw_name, + "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 (!packet_get_mux()) 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 slave. @@ -1820,18 +1924,18 @@ ssh_session2(struct ssh *ssh) * after the connection is fully established (in particular, * async rfwd replies have been received for ExitOnForwardFailure). */ - if (options.control_persist && muxserver_sock != -1) { + if (options.control_persist && muxserver_sock != -1) { ostdin_null_flag = stdin_null_flag; ono_shell_flag = no_shell_flag; orequest_tty = options.request_tty; otty_flag = tty_flag; - stdin_null_flag = 1; - no_shell_flag = 1; - tty_flag = 0; + stdin_null_flag = 1; + no_shell_flag = 1; + tty_flag = 0; if (!fork_after_authentication_flag) need_controlpersist_detach = 1; fork_after_authentication_flag = 1; - } + } /* * ControlPersist mux listen socket setup failed, attempt the * stdio forward setup that we skipped earlier. @@ -1839,7 +1943,7 @@ ssh_session2(struct ssh *ssh) if (options.control_persist && muxserver_sock == -1) ssh_init_stdio_forwarding(ssh); - if (!no_shell_flag || (datafellows & SSH_BUG_DUMMYCHAN)) + if (!no_shell_flag) id = ssh_session2_open(ssh); else { packet_set_interactive( @@ -1863,6 +1967,22 @@ ssh_session2(struct ssh *ssh) 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) { + if ((devnull = open(_PATH_DEVNULL, O_WRONLY)) == -1) + error("%s: open %s: %s", __func__, + _PATH_DEVNULL, strerror(errno)); + if (dup2(devnull, STDOUT_FILENO) < 0) + fatal("%s: dup2() stdout failed", __func__); + if (devnull > STDERR_FILENO) + close(devnull); + } + + /* * If requested and we are not interested in replies to remote * forwarding requests, then let ssh continue in the background. */ @@ -1881,12 +2001,10 @@ ssh_session2(struct ssh *ssh) /* Loads all IdentityFile and CertificateFile keys */ static void -load_public_identity_files(void) +load_public_identity_files(struct passwd *pw) { - char *filename, *cp, thishost[NI_MAXHOST]; - char *pwdir = NULL, *pwname = NULL; + char *filename, *cp; struct sshkey *public; - struct passwd *pw; int i; u_int n_ids, n_certs; char *identity_files[SSH_MAX_IDENTITY_FILES]; @@ -1925,11 +2043,6 @@ load_public_identity_files(void) #endif /* ENABLE_PKCS11 */ if ((pw = getpwuid(original_real_uid)) == NULL) fatal("load_public_identity_files: getpwuid failed"); - pwname = xstrdup(pw->pw_name); - pwdir = xstrdup(pw->pw_dir); - if (gethostname(thishost, sizeof(thishost)) == -1) - fatal("load_public_identity_files: gethostname: %s", - strerror(errno)); for (i = 0; i < options.num_identity_files; i++) { if (n_ids >= SSH_MAX_IDENTITY_FILES || strcasecmp(options.identity_files[i], "none") == 0) { @@ -1939,8 +2052,8 @@ load_public_identity_files(void) } cp = tilde_expand_filename(options.identity_files[i], original_real_uid); - filename = percent_expand(cp, "d", pwdir, - "u", pwname, "l", thishost, "h", host, + filename = percent_expand(cp, "d", pw->pw_dir, + "u", pw->pw_name, "l", thishost, "h", host, "r", options.user, (char *)NULL); free(cp); public = key_load_public(filename, NULL); @@ -1985,8 +2098,8 @@ load_public_identity_files(void) for (i = 0; i < options.num_certificate_files; i++) { cp = tilde_expand_filename(options.certificate_files[i], original_real_uid); - filename = percent_expand(cp, "d", pwdir, - "u", pwname, "l", thishost, "h", host, + filename = percent_expand(cp, "d", pw->pw_dir, + "u", pw->pw_name, "l", thishost, "h", host, "r", options.user, (char *)NULL); free(cp); @@ -2019,11 +2132,6 @@ load_public_identity_files(void) memcpy(options.certificate_files, certificate_files, sizeof(certificate_files)); memcpy(options.certificates, certificates, sizeof(certificates)); - - explicit_bzero(pwname, strlen(pwname)); - free(pwname); - explicit_bzero(pwdir, strlen(pwdir)); - free(pwdir); } static void @@ -2036,7 +2144,5 @@ main_sigchld_handler(int sig) while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || (pid < 0 && errno == EINTR)) ; - - signal(sig, main_sigchld_handler); errno = save_errno; } |
