diff options
author | Todd C. Miller <millert@cvs.openbsd.org> | 2017-10-21 23:06:25 +0000 |
---|---|---|
committer | Todd C. Miller <millert@cvs.openbsd.org> | 2017-10-21 23:06:25 +0000 |
commit | 67552b52d1c0bf12fd40a560a0abc8366c47dc49 (patch) | |
tree | 9c1cebf65e14a6101e5226ca184df5e3740cd888 /usr.bin/ssh | |
parent | ec5af02fc35b27e1e42c14d537aa9121b24bbb6c (diff) |
Add URI support to ssh, sftp and scp. For example ssh://user@host
or sftp://user@host/path. The connection parameters described in
draft-ietf-secsh-scp-sftp-ssh-uri-04 are not implemented since the
ssh fingerprint format in the draft uses md5 with no way to specify
the hash function type. OK djm@
Diffstat (limited to 'usr.bin/ssh')
-rw-r--r-- | usr.bin/ssh/misc.c | 297 | ||||
-rw-r--r-- | usr.bin/ssh/misc.h | 5 | ||||
-rw-r--r-- | usr.bin/ssh/readconf.c | 54 | ||||
-rw-r--r-- | usr.bin/ssh/readconf.h | 3 | ||||
-rw-r--r-- | usr.bin/ssh/scp.1 | 41 | ||||
-rw-r--r-- | usr.bin/ssh/scp.c | 199 | ||||
-rw-r--r-- | usr.bin/ssh/sftp.1 | 77 | ||||
-rw-r--r-- | usr.bin/ssh/sftp.c | 58 | ||||
-rw-r--r-- | usr.bin/ssh/ssh.1 | 36 | ||||
-rw-r--r-- | usr.bin/ssh/ssh.c | 56 | ||||
-rw-r--r-- | usr.bin/ssh/ssh_config.5 | 7 |
11 files changed, 582 insertions, 251 deletions
diff --git a/usr.bin/ssh/misc.c b/usr.bin/ssh/misc.c index 672701d2e79..33e0c3a5972 100644 --- a/usr.bin/ssh/misc.c +++ b/usr.bin/ssh/misc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.113 2017/08/18 05:48:04 djm Exp $ */ +/* $OpenBSD: misc.c,v 1.114 2017/10/21 23:06:24 millert Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005,2006 Damien Miller. All rights reserved. @@ -375,11 +375,12 @@ put_host_port(const char *host, u_short port) * Search for next delimiter between hostnames/addresses and ports. * Argument may be modified (for termination). * Returns *cp if parsing succeeds. - * *cp is set to the start of the next delimiter, if one was found. + * *cp is set to the start of the next field, if one was found. + * The delimiter char, if present, is stored in delim. * If this is the last field, *cp is set to NULL. */ -char * -hpdelim(char **cp) +static char * +hpdelim2(char **cp, char *delim) { char *s, *old; @@ -402,6 +403,8 @@ hpdelim(char **cp) case ':': case '/': + if (delim != NULL) + *delim = *s; *s = '\0'; /* terminate */ *cp = s + 1; break; @@ -414,6 +417,12 @@ hpdelim(char **cp) } char * +hpdelim(char **cp) +{ + return hpdelim2(cp, NULL); +} + +char * cleanhostname(char *host) { if (*host == '[' && host[strlen(host) - 1] == ']') { @@ -447,6 +456,75 @@ colon(char *cp) } /* + * Parse a [user@]host:[path] string. + * Caller must free returned user, host and path. + * Any of the pointer return arguments may be NULL (useful for syntax checking). + * If user was not specified then *userp will be set to NULL. + * If host was not specified then *hostp will be set to NULL. + * If path was not specified then *pathp will be set to ".". + * Returns 0 on success, -1 on failure. + */ +int +parse_user_host_path(const char *s, char **userp, char **hostp, char **pathp) +{ + char *user = NULL, *host = NULL, *path = NULL; + char *sdup, *tmp; + int ret = -1; + + if (userp != NULL) + *userp = NULL; + if (hostp != NULL) + *hostp = NULL; + if (pathp != NULL) + *pathp = NULL; + + sdup = tmp = xstrdup(s); + + /* Check for remote syntax: [user@]host:[path] */ + if ((tmp = colon(sdup)) == NULL) + goto out; + + /* Extract optional path */ + *tmp++ = '\0'; + if (*tmp == '\0') + tmp = "."; + path = xstrdup(tmp); + + /* Extract optional user and mandatory host */ + tmp = strrchr(sdup, '@'); + if (tmp != NULL) { + *tmp++ = '\0'; + host = xstrdup(cleanhostname(tmp)); + if (*sdup != '\0') + user = xstrdup(sdup); + } else { + host = xstrdup(cleanhostname(sdup)); + user = NULL; + } + + /* Success */ + if (userp != NULL) { + *userp = user; + user = NULL; + } + if (hostp != NULL) { + *hostp = host; + host = NULL; + } + if (pathp != NULL) { + *pathp = path; + path = NULL; + } + ret = 0; +out: + free(sdup); + free(user); + free(host); + free(path); + return ret; +} + +/* * Parse a [user@]host[:port] string. * Caller must free returned user and host. * Any of the pointer return arguments may be NULL (useful for syntax checking). @@ -471,7 +549,7 @@ parse_user_host_port(const char *s, char **userp, char **hostp, int *portp) if ((sdup = tmp = strdup(s)) == NULL) return -1; /* Extract optional username */ - if ((cp = strchr(tmp, '@')) != NULL) { + if ((cp = strrchr(tmp, '@')) != NULL) { *cp = '\0'; if (*tmp == '\0') goto out; @@ -507,6 +585,168 @@ parse_user_host_port(const char *s, char **userp, char **hostp, int *portp) return ret; } +/* + * Converts a two-byte hex string to decimal. + * Returns the decimal value or -1 for invalid input. + */ +static int +hexchar(const char *s) +{ + unsigned char result[2]; + int i; + + for (i = 0; i < 2; i++) { + if (s[i] >= '0' && s[i] <= '9') + result[i] = (unsigned char)(s[i] - '0'); + else if (s[i] >= 'a' && s[i] <= 'f') + result[i] = (unsigned char)(s[i] - 'a') + 10; + else if (s[i] >= 'A' && s[i] <= 'F') + result[i] = (unsigned char)(s[i] - 'A') + 10; + else + return -1; + } + return (result[0] << 4) | result[1]; +} + +/* + * Decode an url-encoded string. + * Returns a newly allocated string on success or NULL on failure. + */ +static char * +urldecode(const char *src) +{ + char *ret, *dst; + int ch; + + ret = xmalloc(strlen(src) + 1); + for (dst = ret; *src != '\0'; src++) { + switch (*src) { + case '+': + *dst++ = ' '; + break; + case '%': + if (!isxdigit((unsigned char)src[1]) || + !isxdigit((unsigned char)src[2]) || + (ch = hexchar(src + 1)) == -1) { + free(ret); + return NULL; + } + *dst++ = ch; + src += 2; + break; + default: + *dst++ = *src; + break; + } + } + *dst = '\0'; + + return ret; +} + +/* + * Parse an (scp|ssh|sftp)://[user@]host[:port][/path] URI. + * See https://tools.ietf.org/html/draft-ietf-secsh-scp-sftp-ssh-uri-04 + * Either user or path may be url-encoded (but not host or port). + * Caller must free returned user, host and path. + * Any of the pointer return arguments may be NULL (useful for syntax checking) + * but the scheme must always be specified. + * If user was not specified then *userp will be set to NULL. + * If port was not specified then *portp will be -1. + * If path was not specified then *pathp will be set to NULL. + * Returns 0 on success, 1 if non-uri/wrong scheme, -1 on error/invalid uri. + */ +int +parse_uri(const char *scheme, const char *uri, char **userp, char **hostp, + int *portp, char **pathp) +{ + char *uridup, *cp, *tmp, ch; + char *user = NULL, *host = NULL, *path = NULL; + int port = -1, ret = -1; + size_t len; + + len = strlen(scheme); + if (strncmp(uri, scheme, len) != 0 || strncmp(uri + len, "://", 3) != 0) + return 1; + uri += len + 3; + + if (userp != NULL) + *userp = NULL; + if (hostp != NULL) + *hostp = NULL; + if (portp != NULL) + *portp = -1; + if (pathp != NULL) + *pathp = NULL; + + uridup = tmp = xstrdup(uri); + + /* Extract optional ssh-info (username + connection params) */ + if ((cp = strchr(tmp, '@')) != NULL) { + char *delim; + + *cp = '\0'; + /* Extract username and connection params */ + if ((delim = strchr(tmp, ';')) != NULL) { + /* Just ignore connection params for now */ + *delim = '\0'; + } + if (*tmp == '\0') { + /* Empty username */ + goto out; + } + if ((user = urldecode(tmp)) == NULL) + goto out; + tmp = cp + 1; + } + + /* Extract mandatory hostname */ + if ((cp = hpdelim2(&tmp, &ch)) == NULL || *cp == '\0') + goto out; + host = xstrdup(cleanhostname(cp)); + if (!valid_domain(host, 0, NULL)) + goto out; + + if (tmp != NULL && *tmp != '\0') { + if (ch == ':') { + /* Convert and verify port. */ + if ((cp = strchr(tmp, '/')) != NULL) + *cp = '\0'; + if ((port = a2port(tmp)) <= 0) + goto out; + tmp = cp ? cp + 1 : NULL; + } + if (tmp != NULL && *tmp != '\0') { + /* Extract optional path */ + if ((path = urldecode(tmp)) == NULL) + goto out; + } + } + + /* Success */ + if (userp != NULL) { + *userp = user; + user = NULL; + } + if (hostp != NULL) { + *hostp = host; + host = NULL; + } + if (portp != NULL) + *portp = port; + if (pathp != NULL) { + *pathp = path; + path = NULL; + } + ret = 0; + out: + free(uridup); + free(user); + free(host); + free(path); + return ret; +} + /* function to assist building execv() arguments */ void addargs(arglist *args, char *fmt, ...) @@ -1666,3 +1906,50 @@ child_set_env(char ***envp, u_int *envsizep, const char *name, snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value); } +/* + * Check and optionally lowercase a domain name, also removes trailing '.' + * Returns 1 on success and 0 on failure, storing an error message in errstr. + */ +int +valid_domain(char *name, int makelower, const char **errstr) +{ + size_t i, l = strlen(name); + u_char c, last = '\0'; + static char errbuf[256]; + + if (l == 0) { + strlcpy(errbuf, "empty domain name", sizeof(errbuf)); + goto bad; + } + if (!isalpha((u_char)name[0]) && !isdigit((u_char)name[0])) { + snprintf(errbuf, sizeof(errbuf), "domain name \"%.100s\" " + "starts with invalid character", name); + goto bad; + } + for (i = 0; i < l; i++) { + c = tolower((u_char)name[i]); + if (makelower) + name[i] = (char)c; + if (last == '.' && c == '.') { + snprintf(errbuf, sizeof(errbuf), "domain name " + "\"%.100s\" contains consecutive separators", name); + goto bad; + } + if (c != '.' && c != '-' && !isalnum(c) && + c != '_') /* technically invalid, but common */ { + snprintf(errbuf, sizeof(errbuf), "domain name " + "\"%.100s\" contains invalid characters", name); + goto bad; + } + last = c; + } + if (name[l - 1] == '.') + name[l - 1] = '\0'; + if (errstr != NULL) + *errstr = NULL; + return 1; +bad: + if (errstr != NULL) + *errstr = errbuf; + return 0; +} diff --git a/usr.bin/ssh/misc.h b/usr.bin/ssh/misc.h index f10347bc33d..b5ee75e235c 100644 --- a/usr.bin/ssh/misc.h +++ b/usr.bin/ssh/misc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.h,v 1.63 2017/08/18 05:48:04 djm Exp $ */ +/* $OpenBSD: misc.h,v 1.64 2017/10/21 23:06:24 millert Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> @@ -54,7 +54,9 @@ char *put_host_port(const char *, u_short); char *hpdelim(char **); char *cleanhostname(char *); char *colon(char *); +int parse_user_host_path(const char *, char **, char **, char **); int parse_user_host_port(const char *, char **, char **, int *); +int parse_uri(const char *, const char *, char **, char **, int *, char **); long convtime(const char *); char *tilde_expand_filename(const char *, uid_t); char *percent_expand(const char *, ...) __attribute__((__sentinel__)); @@ -66,6 +68,7 @@ time_t monotime(void); double monotime_double(void); void lowercase(char *s); int unix_listener(const char *, int, int); +int valid_domain(char *, int, const char **); struct passwd *pwcopy(struct passwd *); const char *ssh_gai_strerror(int); diff --git a/usr.bin/ssh/readconf.c b/usr.bin/ssh/readconf.c index eee3ba00382..e3a7e110d7a 100644 --- a/usr.bin/ssh/readconf.c +++ b/usr.bin/ssh/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.279 2017/09/21 19:16:53 markus Exp $ */ +/* $OpenBSD: readconf.c,v 1.280 2017/10/21 23:06:24 millert Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -668,34 +668,6 @@ match_cfg_line(Options *options, char **condition, struct passwd *pw, return result; } -/* Check and prepare a domain name: removes trailing '.' and lowercases */ -static void -valid_domain(char *name, const char *filename, int linenum) -{ - size_t i, l = strlen(name); - u_char c, last = '\0'; - - if (l == 0) - fatal("%s line %d: empty hostname suffix", filename, linenum); - if (!isalpha((u_char)name[0]) && !isdigit((u_char)name[0])) - fatal("%s line %d: hostname suffix \"%.100s\" " - "starts with invalid character", filename, linenum, name); - for (i = 0; i < l; i++) { - c = tolower((u_char)name[i]); - name[i] = (char)c; - if (last == '.' && c == '.') - fatal("%s line %d: hostname suffix \"%.100s\" contains " - "consecutive separators", filename, linenum, name); - if (c != '.' && c != '-' && !isalnum(c) && - c != '_') /* technically invalid, but common */ - fatal("%s line %d: hostname suffix \"%.100s\" contains " - "invalid characters", filename, linenum, name); - last = c; - } - if (name[l - 1] == '.') - name[l - 1] = '\0'; -} - /* * Returns the number of the token pointed to by cp or oBadOption. */ @@ -1547,7 +1519,11 @@ parse_keytypes: case oCanonicalDomains: value = options->num_canonical_domains != 0; while ((arg = strdelim(&s)) != NULL && *arg != '\0') { - valid_domain(arg, filename, linenum); + const char *errstr; + if (!valid_domain(arg, 1, &errstr)) { + fatal("%s line %d: %s", filename, linenum, + errstr); + } if (!*activep || value) continue; if (options->num_canonical_domains >= MAX_CANON_DOMAINS) @@ -2277,11 +2253,13 @@ parse_jump(const char *s, Options *o, int active) if (first) { /* First argument and configuration is active */ - if (parse_user_host_port(cp, &user, &host, &port) != 0) + if (parse_ssh_uri(cp, &user, &host, &port) == -1 || + parse_user_host_port(cp, &user, &host, &port) != 0) goto out; } else { /* Subsequent argument or inactive configuration */ - if (parse_user_host_port(cp, NULL, NULL, NULL) != 0) + if (parse_ssh_uri(cp, NULL, NULL, NULL) == -1 || + parse_user_host_port(cp, NULL, NULL, NULL) != 0) goto out; } first = 0; /* only check syntax for subsequent hosts */ @@ -2306,6 +2284,18 @@ parse_jump(const char *s, Options *o, int active) return ret; } +int +parse_ssh_uri(const char *uri, char **userp, char **hostp, int *portp) +{ + char *path; + int r; + + r = parse_uri("ssh", uri, userp, hostp, portp, &path); + if (r == 0 && path != NULL) + r = -1; /* path not allowed */ + 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) diff --git a/usr.bin/ssh/readconf.h b/usr.bin/ssh/readconf.h index 22fe5c1873c..34aad83cf5b 100644 --- a/usr.bin/ssh/readconf.h +++ b/usr.bin/ssh/readconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.123 2017/09/03 23:33:13 djm Exp $ */ +/* $OpenBSD: readconf.h,v 1.124 2017/10/21 23:06:24 millert Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> @@ -204,6 +204,7 @@ int read_config_file(const char *, struct passwd *, const char *, const char *, Options *, 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 *); void dump_client_config(Options *o, const char *host); diff --git a/usr.bin/ssh/scp.1 b/usr.bin/ssh/scp.1 index 76ce3336127..92b63f6f4ab 100644 --- a/usr.bin/ssh/scp.1 +++ b/usr.bin/ssh/scp.1 @@ -8,9 +8,9 @@ .\" .\" Created: Sun May 7 00:14:37 1995 ylo .\" -.\" $OpenBSD: scp.1,v 1.74 2017/05/03 21:49:18 naddy Exp $ +.\" $OpenBSD: scp.1,v 1.75 2017/10/21 23:06:24 millert Exp $ .\" -.Dd $Mdocdate: May 3 2017 $ +.Dd $Mdocdate: October 21 2017 $ .Dt SCP 1 .Os .Sh NAME @@ -27,20 +27,8 @@ .Op Fl o Ar ssh_option .Op Fl P Ar port .Op Fl S Ar program -.Sm off -.Oo -.Op Ar user No @ -.Ar host1 : -.Oc Ar file1 -.Sm on -.Ar ... -.Sm off -.Oo -.Op Ar user No @ -.Ar host2 : -.Oc Ar file2 -.Sm on -.Ek +.Ar source ... +.Ar target .Sh DESCRIPTION .Nm copies files between hosts on a network. @@ -53,15 +41,30 @@ same security as will ask for passwords or passphrases if they are needed for authentication. .Pp -File names may contain a user and host specification to indicate -that the file is to be copied to/from that host. +The +.Ar target +and +.Ar destination +may be specified as a local pathname, a remote host with optional path +in the form +.Oo Ar user Ns @ Oc Ns Ar host Ns : Ns Oo Ar path Oc , +or an scp URI in the form +.No scp:// Ns Oo Ar user Ns @ Oc Ns Ar host Ns +.Oo : Ns Ar port Oc Ns Oo / Ns Ar path Oc . Local file names can be made explicit using absolute or relative pathnames to avoid .Nm treating file names containing .Sq :\& as host specifiers. -Copies between two remote hosts are also permitted. +.Pp +When copying between two remote hosts, if the URI format is used, a +.Ar port +may only be specified on the +.Ar target +if the +.Fl 3 +option is used. .Pp The options are as follows: .Bl -tag -width Ds diff --git a/usr.bin/ssh/scp.c b/usr.bin/ssh/scp.c index 31cb02e6887..e8b62cab982 100644 --- a/usr.bin/ssh/scp.c +++ b/usr.bin/ssh/scp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: scp.c,v 1.192 2017/05/31 09:15:42 deraadt Exp $ */ +/* $OpenBSD: scp.c,v 1.193 2017/10/21 23:06:24 millert Exp $ */ /* * scp - secure remote copy. This is basically patched BSD rcp which * uses ssh to do the data transfer (instead of using rcmd). @@ -96,6 +96,7 @@ #include <vis.h> #include "xmalloc.h" +#include "ssh.h" #include "atomicio.h" #include "pathnames.h" #include "log.h" @@ -105,8 +106,8 @@ #define COPY_BUFLEN 16384 -int do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout); -int do_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout); +int do_cmd(char *host, char *remuser, int port, char *cmd, int *fdin, int *fdout); +int do_cmd2(char *host, char *remuser, int port, char *cmd, int fdin, int fdout); /* Struct for addargs */ arglist args; @@ -131,6 +132,9 @@ int showprogress = 1; */ int throughlocal = 0; +/* Non-standard port to use for the ssh connection or -1. */ +int sshport = -1; + /* This is the program to execute for the secured connection. ("ssh" or -S) */ char *ssh_program = _PATH_SSH_PROGRAM; @@ -213,7 +217,7 @@ do_local_cmd(arglist *a) */ int -do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout) +do_cmd(char *host, char *remuser, int port, char *cmd, int *fdin, int *fdout) { int pin[2], pout[2], reserved[2]; @@ -223,6 +227,9 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout) ssh_program, host, remuser ? remuser : "(unspecified)", cmd); + if (port == -1) + port = sshport; + /* * Reserve two descriptors so that the real pipes won't get * descriptors 0 and 1 because that will screw up dup2 below. @@ -256,6 +263,10 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout) close(pout[1]); replacearg(&args, 0, "%s", ssh_program); + if (port != -1) { + addargs(&args, "-p"); + addargs(&args, "%d", port); + } if (remuser != NULL) { addargs(&args, "-l"); addargs(&args, "%s", remuser); @@ -287,7 +298,7 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout) * This way the input and output of two commands can be connected. */ int -do_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout) +do_cmd2(char *host, char *remuser, int port, char *cmd, int fdin, int fdout) { pid_t pid; int status; @@ -298,6 +309,9 @@ do_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout) ssh_program, host, remuser ? remuser : "(unspecified)", cmd); + if (port == -1) + port = sshport; + /* Fork a child to execute the command on the remote host using ssh. */ pid = fork(); if (pid == 0) { @@ -305,6 +319,10 @@ do_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout) dup2(fdout, 1); replacearg(&args, 0, "%s", ssh_program); + if (port != -1) { + addargs(&args, "-p"); + addargs(&args, "%d", port); + } if (remuser != NULL) { addargs(&args, "-l"); addargs(&args, "%s", remuser); @@ -349,14 +367,14 @@ void rsource(char *, struct stat *); void sink(int, char *[]); void source(int, char *[]); void tolocal(int, char *[]); -void toremote(char *, int, char *[]); +void toremote(int, char *[]); void usage(void); int main(int argc, char **argv) { int ch, fflag, tflag, status, n; - char *targ, **newargv; + char **newargv; const char *errstr; extern char *optarg; extern int optind; @@ -410,10 +428,9 @@ main(int argc, char **argv) addargs(&args, "%s", optarg); break; case 'P': - addargs(&remote_remote_args, "-p"); - addargs(&remote_remote_args, "%s", optarg); - addargs(&args, "-p"); - addargs(&args, "%s", optarg); + sshport = a2port(optarg); + if (sshport <= 0) + fatal("bad port \"%s\"\n", optarg); break; case 'B': addargs(&remote_remote_args, "-oBatchmode=yes"); @@ -510,8 +527,8 @@ main(int argc, char **argv) (void) signal(SIGPIPE, lostconn); - if ((targ = colon(argv[argc - 1]))) /* Dest is remote host. */ - toremote(targ, argc, argv); + if (colon(argv[argc - 1])) /* Dest is remote host. */ + toremote(argc, argv); else { if (targetshouldbedirectory) verifydir(argv[argc - 1]); @@ -567,71 +584,65 @@ do_times(int fd, int verb, const struct stat *sb) } void -toremote(char *targ, int argc, char **argv) +toremote(int argc, char **argv) { - char *bp, *host, *src, *suser, *thost, *tuser, *arg; + char *suser = NULL, *host = NULL, *src = NULL; + char *bp, *tuser, *thost, *targ; + int sport = -1, tport = -1; arglist alist; - int i; + int i, r; u_int j; memset(&alist, '\0', sizeof(alist)); alist.list = NULL; - *targ++ = 0; - if (*targ == 0) - targ = "."; - - arg = xstrdup(argv[argc - 1]); - if ((thost = strrchr(arg, '@'))) { - /* user@host */ - *thost++ = 0; - tuser = arg; - if (*tuser == '\0') - tuser = NULL; - } else { - thost = arg; - tuser = NULL; - } - - if (tuser != NULL && !okname(tuser)) { - free(arg); - return; + /* Parse target */ + r = parse_uri("scp", argv[argc - 1], &tuser, &thost, &tport, &targ); + if (r == -1) + goto out; /* invalid URI */ + if (r != 0) { + if (parse_user_host_path(argv[argc - 1], &tuser, &thost, + &targ) == -1) + goto out; } + if (tuser != NULL && !okname(tuser)) + goto out; + /* Parse source files */ for (i = 0; i < argc - 1; i++) { - src = colon(argv[i]); - if (src && throughlocal) { /* extended remote to remote */ - *src++ = 0; - if (*src == 0) - src = "."; - host = strrchr(argv[i], '@'); - if (host) { - *host++ = 0; - host = cleanhostname(host); - suser = argv[i]; - if (*suser == '\0') - suser = pwd->pw_name; - else if (!okname(suser)) - continue; - } else { - host = cleanhostname(argv[i]); - suser = NULL; - } + free(suser); + free(host); + free(src); + r = parse_uri("scp", argv[i], &suser, &host, &sport, &src); + if (r == -1) + continue; /* invalid URI */ + if (r != 0) + parse_user_host_path(argv[i], &suser, &host, &src); + if (suser != NULL && !okname(suser)) { + ++errs; + continue; + } + if (host && throughlocal) { /* extended remote to remote */ xasprintf(&bp, "%s -f %s%s", cmd, *src == '-' ? "-- " : "", src); - if (do_cmd(host, suser, bp, &remin, &remout) < 0) + if (do_cmd(host, suser, sport, bp, &remin, &remout) < 0) exit(1); free(bp); - host = cleanhostname(thost); xasprintf(&bp, "%s -t %s%s", cmd, *targ == '-' ? "-- " : "", targ); - if (do_cmd2(host, tuser, bp, remin, remout) < 0) + if (do_cmd2(thost, tuser, tport, bp, remin, remout) < 0) exit(1); free(bp); (void) close(remin); (void) close(remout); remin = remout = -1; - } else if (src) { /* standard remote to remote */ + } else if (host) { /* standard remote to remote */ + if (tport != -1 && tport != SSH_DEFAULT_PORT) { + /* This would require the remote support URIs */ + fatal("target port not supported with two " + "remote hosts without the -3 option"); + } + freeargs(&alist); addargs(&alist, "%s", ssh_program); addargs(&alist, "-x"); @@ -641,23 +652,14 @@ toremote(char *targ, int argc, char **argv) addargs(&alist, "%s", remote_remote_args.list[j]); } - *src++ = 0; - if (*src == 0) - src = "."; - host = strrchr(argv[i], '@'); - - if (host) { - *host++ = 0; - host = cleanhostname(host); - suser = argv[i]; - if (*suser == '\0') - suser = pwd->pw_name; - else if (!okname(suser)) - continue; + + if (sport != -1) { + addargs(&alist, "-p"); + addargs(&alist, "%d", sport); + } + if (suser) { addargs(&alist, "-l"); addargs(&alist, "%s", suser); - } else { - host = cleanhostname(argv[i]); } addargs(&alist, "--"); addargs(&alist, "%s", host); @@ -672,8 +674,7 @@ toremote(char *targ, int argc, char **argv) if (remin == -1) { xasprintf(&bp, "%s -t %s%s", cmd, *targ == '-' ? "-- " : "", targ); - host = cleanhostname(thost); - if (do_cmd(host, tuser, bp, &remin, + if (do_cmd(thost, tuser, tport, bp, &remin, &remout) < 0) exit(1); if (response() < 0) @@ -683,21 +684,41 @@ toremote(char *targ, int argc, char **argv) source(1, argv + i); } } - free(arg); +out: + free(tuser); + free(thost); + free(targ); + free(suser); + free(host); + free(src); } void tolocal(int argc, char **argv) { - char *bp, *host, *src, *suser; + char *bp, *host = NULL, *src = NULL, *suser = NULL; arglist alist; - int i; + int i, r, sport = -1; memset(&alist, '\0', sizeof(alist)); alist.list = NULL; for (i = 0; i < argc - 1; i++) { - if (!(src = colon(argv[i]))) { /* Local to local. */ + free(suser); + free(host); + free(src); + r = parse_uri("scp", argv[i], &suser, &host, &sport, &src); + if (r == -1) { + ++errs; + continue; + } + if (r != 0) + parse_user_host_path(argv[i], &suser, &host, &src); + if (suser != NULL && !okname(suser)) { + ++errs; + continue; + } + if (!host) { /* Local to local. */ freeargs(&alist); addargs(&alist, "%s", _PATH_CP); if (iamrecursive) @@ -711,22 +732,10 @@ tolocal(int argc, char **argv) ++errs; continue; } - *src++ = 0; - if (*src == 0) - src = "."; - if ((host = strrchr(argv[i], '@')) == NULL) { - host = argv[i]; - suser = NULL; - } else { - *host++ = 0; - suser = argv[i]; - if (*suser == '\0') - suser = pwd->pw_name; - } - host = cleanhostname(host); + /* Remote to local. */ xasprintf(&bp, "%s -f %s%s", cmd, *src == '-' ? "-- " : "", src); - if (do_cmd(host, suser, bp, &remin, &remout) < 0) { + if (do_cmd(host, suser, sport, bp, &remin, &remout) < 0) { free(bp); ++errs; continue; @@ -736,6 +745,9 @@ tolocal(int argc, char **argv) (void) close(remin); remin = remout = -1; } + free(suser); + free(host); + free(src); } void @@ -1244,8 +1256,7 @@ usage(void) { (void) fprintf(stderr, "usage: scp [-346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]\n" - " [-l limit] [-o ssh_option] [-P port] [-S program]\n" - " [[user@]host1:]file1 ... [[user@]host2:]file2\n"); + " [-l limit] [-o ssh_option] [-P port] [-S program] source ... target\n"); exit(1); } diff --git a/usr.bin/ssh/sftp.1 b/usr.bin/ssh/sftp.1 index c218376fbf0..49f7febf157 100644 --- a/usr.bin/ssh/sftp.1 +++ b/usr.bin/ssh/sftp.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: sftp.1,v 1.110 2017/05/03 21:49:18 naddy Exp $ +.\" $OpenBSD: sftp.1,v 1.111 2017/10/21 23:06:24 millert Exp $ .\" .\" Copyright (c) 2001 Damien Miller. All rights reserved. .\" @@ -22,7 +22,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: May 3 2017 $ +.Dd $Mdocdate: October 21 2017 $ .Dt SFTP 1 .Os .Sh NAME @@ -44,54 +44,52 @@ .Op Fl R Ar num_requests .Op Fl S Ar program .Op Fl s Ar subsystem | sftp_server -.Ar host -.Ek -.Nm sftp -.Oo Ar user Ns @ Oc Ns -.Ar host Ns Op : Ns Ar -.Nm sftp -.Oo -.Ar user Ns @ Oc Ns -.Ar host Ns Oo : Ns Ar dir Ns -.Op Ar / -.Oc -.Nm sftp -.Fl b Ar batchfile -.Oo Ar user Ns @ Oc Ns Ar host +.Ar destination .Sh DESCRIPTION .Nm -is an interactive file transfer program, similar to +is a file transfer program, similar to .Xr ftp 1 , which performs all operations over an encrypted .Xr ssh 1 transport. It may also use many features of ssh, such as public key authentication and compression. -.Nm -connects and logs into the specified -.Ar host , -then enters an interactive command mode. .Pp -The second usage format will retrieve files automatically if a non-interactive +The +.Ar destination +may be specified either as +.Oo Ar user Ns @ Oc Ns Ar host Ns Oo : Ns Ar path Oc +or as an sftp URI in the form +.No sftp:// Ns Oo Ar user Ns @ Oc Ns Ar host Ns +.Oo : Ns Ar port Oc Ns Oo / Ns Ar path Oc . +.Pp +If the +.Ar destination +includes a +.Ar path +and it is not a directory, +.Nm +will retrieve files automatically if a non-interactive authentication method is used; otherwise it will do so after successful interactive authentication. .Pp -The third usage format allows +If no +.Ar path +is specified, or if the +.Ar path +is a directory, .Nm -to start in a remote directory. -.Pp -The final usage format allows for automated sessions using the -.Fl b -option. -In such cases, it is necessary to configure non-interactive authentication -to obviate the need to enter a password at connection time (see -.Xr sshd 8 -and -.Xr ssh-keygen 1 -for details). +will log in to the specified +.Ar host +and enter interactive command mode, changing to the remote directory +if one was specified. +An optional trailing slash can be used to force the +.Ar path +to be interpreted as a directory. .Pp -Since some usage formats use colon characters to delimit host names from path -names, IPv6 addresses must be enclosed in square brackets to avoid ambiguity. +Since the destination formats use colon characters to delimit host +names from path names or port numbers, IPv6 addresses must be +enclosed in square brackets to avoid ambiguity. .Pp The options are as follows: .Bl -tag -width Ds @@ -121,7 +119,12 @@ Batch mode reads a series of commands from an input instead of .Em stdin . Since it lacks user interaction it should be used in conjunction with -non-interactive authentication. +non-interactive authentication to obviate the need to enter a password +at connection time (see +.Xr sshd 8 +and +.Xr ssh-keygen 1 +for details). A .Ar batchfile of diff --git a/usr.bin/ssh/sftp.c b/usr.bin/ssh/sftp.c index 5204aeb2e29..7e9d02968f5 100644 --- a/usr.bin/ssh/sftp.c +++ b/usr.bin/ssh/sftp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp.c,v 1.180 2017/06/10 06:33:34 djm Exp $ */ +/* $OpenBSD: sftp.c,v 1.181 2017/10/21 23:06:24 millert Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> * @@ -2253,19 +2253,16 @@ usage(void) "[-i identity_file] [-l limit]\n" " [-o ssh_option] [-P port] [-R num_requests] " "[-S program]\n" - " [-s subsystem | sftp_server] host\n" - " %s [user@]host[:file ...]\n" - " %s [user@]host[:dir[/]]\n" - " %s -b batchfile [user@]host\n", - __progname, __progname, __progname, __progname); + " [-s subsystem | sftp_server] destination\n", + __progname); exit(1); } int main(int argc, char **argv) { - int in, out, ch, err; - char *host = NULL, *userhost, *cp, *file2 = NULL; + int in, out, ch, err, tmp, port = -1; + char *host = NULL, *user, *cp, *file2 = NULL; int debug_level = 0, sshver = 2; char *file1 = NULL, *sftp_server = NULL; char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL; @@ -2319,7 +2316,9 @@ main(int argc, char **argv) addargs(&args, "-%c", ch); break; case 'P': - addargs(&args, "-oPort %s", optarg); + port = a2port(optarg); + if (port <= 0) + fatal("Bad port \"%s\"\n", optarg); break; case 'v': if (debug_level < 3) { @@ -2402,33 +2401,38 @@ main(int argc, char **argv) if (sftp_direct == NULL) { if (optind == argc || argc > (optind + 2)) usage(); + argv += optind; - userhost = xstrdup(argv[optind]); - file2 = argv[optind+1]; - - if ((host = strrchr(userhost, '@')) == NULL) - host = userhost; - else { - *host++ = '\0'; - if (!userhost[0]) { - fprintf(stderr, "Missing username\n"); - usage(); + switch (parse_uri("sftp", *argv, &user, &host, &tmp, &file1)) { + case -1: + usage(); + break; + case 0: + if (tmp != -1) + port = tmp; + break; + default: + if (parse_user_host_path(*argv, &user, &host, + &file1) == -1) { + /* Treat as a plain hostname. */ + host = xstrdup(*argv); + host = cleanhostname(host); } - addargs(&args, "-l"); - addargs(&args, "%s", userhost); - } - - if ((cp = colon(host)) != NULL) { - *cp++ = '\0'; - file1 = cp; + break; } + file2 = *(argv + 1); - host = cleanhostname(host); if (!*host) { fprintf(stderr, "Missing hostname\n"); usage(); } + if (port != -1) + addargs(&args, "-oPort %d", port); + if (user != NULL) { + addargs(&args, "-l"); + addargs(&args, "%s", user); + } addargs(&args, "-oProtocol %d", sshver); /* no subsystem if the server-spec contains a '/' */ diff --git a/usr.bin/ssh/ssh.1 b/usr.bin/ssh/ssh.1 index 92092df1e89..310f34cc952 100644 --- a/usr.bin/ssh/ssh.1 +++ b/usr.bin/ssh/ssh.1 @@ -33,8 +33,8 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: ssh.1,v 1.385 2017/10/13 06:45:18 djm Exp $ -.Dd $Mdocdate: October 13 2017 $ +.\" $OpenBSD: ssh.1,v 1.386 2017/10/21 23:06:24 millert Exp $ +.Dd $Mdocdate: October 21 2017 $ .Dt SSH 1 .Os .Sh NAME @@ -52,7 +52,7 @@ .Op Fl F Ar configfile .Op Fl I Ar pkcs11 .Op Fl i Ar identity_file -.Op Fl J Oo Ar user Ns @ Oc Ns Ar host Ns Op : Ns Ar port +.Op Fl J Ar destination .Op Fl L Ar address .Op Fl l Ar login_name .Op Fl m Ar mac_spec @@ -64,7 +64,7 @@ .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 -.Oo Ar user Ns @ Oc Ns Ar hostname +.Ar destination .Op Ar command .Ek .Sh DESCRIPTION @@ -79,15 +79,23 @@ sockets can also be forwarded over the secure channel. .Pp .Nm connects and logs into the specified -.Ar hostname -(with optional +.Ar destination +which may be specified as either +.Oo Ar user Ns @ Oc Ns Ar hostname +where the +.Ar user +is optional, or an ssh URI of the form +.No ssh:// Ns Oo Ar user Ns @ Oc Ns Ar hostname Ns Oo : Ns Ar port Oc +where the .Ar user -name). +and +.Ar port +are optional. The user must prove his/her identity to the remote machine using one of several methods (see below). .Pp -If +If a .Ar command is specified, it is executed on the remote host instead of a login shell. @@ -287,17 +295,11 @@ by appending .Pa -cert.pub to identity filenames. .Pp -.It Fl J Xo -.Sm off -.Op Ar user No @ -.Ar host -.Op : Ar port -.Sm on -.Xc +.It Fl J Ar destination Connect to the target host by first making a .Nm -connection to the jump -.Ar host +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. diff --git a/usr.bin/ssh/ssh.c b/usr.bin/ssh/ssh.c index d4c25ea5088..b411635279e 100644 --- a/usr.bin/ssh/ssh.c +++ b/usr.bin/ssh/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.464 2017/09/21 19:16:53 markus Exp $ */ +/* $OpenBSD: ssh.c,v 1.465 2017/10/21 23:06:24 millert Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -187,7 +187,7 @@ usage(void) " [-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" +" destination [command]\n" ); exit(255); } @@ -815,14 +815,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': @@ -902,16 +906,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; diff --git a/usr.bin/ssh/ssh_config.5 b/usr.bin/ssh/ssh_config.5 index 96e6904bcf9..c04701044a0 100644 --- a/usr.bin/ssh/ssh_config.5 +++ b/usr.bin/ssh/ssh_config.5 @@ -33,8 +33,8 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: ssh_config.5,v 1.259 2017/10/18 05:36:59 jmc Exp $ -.Dd $Mdocdate: October 18 2017 $ +.\" $OpenBSD: ssh_config.5,v 1.260 2017/10/21 23:06:24 millert Exp $ +.Dd $Mdocdate: October 21 2017 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -1198,13 +1198,14 @@ For example, the following directive would connect via an HTTP proxy at 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 +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. |