[openssh-commits] [openssh] 01/02: upstream commit

git+noreply at mindrot.org git+noreply at mindrot.org
Mon Oct 23 16:23:26 AEDT 2017


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch master
in repository openssh.

commit 887669ef032d63cf07f53cada216fa8a0c9a7d72
Author: millert at openbsd.org <millert at openbsd.org>
Date:   Sat Oct 21 23:06:24 2017 +0000

    upstream commit
    
    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@
    
    Upstream-ID: 4ba3768b662d6722de59e6ecb00abf2d4bf9cacc
---
 misc.c       | 297 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 misc.h       |   5 +-
 readconf.c   |  54 +++++------
 readconf.h   |   3 +-
 scp.1        |  41 +++++----
 scp.c        | 197 ++++++++++++++++++++-------------------
 sftp.1       |  77 ++++++++--------
 sftp.c       |  58 ++++++------
 ssh.1        |  36 ++++----
 ssh.c        |  56 ++++++++---
 ssh_config.5 |   7 +-
 11 files changed, 581 insertions(+), 250 deletions(-)

diff --git a/misc.c b/misc.c
index 05950a47..d4d0e44a 100644
--- a/misc.c
+++ b/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.
@@ -395,11 +395,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;
 
@@ -422,6 +423,8 @@ hpdelim(char **cp)
 
 	case ':':
 	case '/':
+		if (delim != NULL)
+			*delim = *s;
 		*s = '\0';	/* terminate */
 		*cp = s + 1;
 		break;
@@ -433,6 +436,12 @@ hpdelim(char **cp)
 	return old;
 }
 
+char *
+hpdelim(char **cp)
+{
+	return hpdelim2(cp, NULL);
+}
+
 char *
 cleanhostname(char *host)
 {
@@ -466,6 +475,75 @@ colon(char *cp)
 	return NULL;
 }
 
+/*
+ * 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.
@@ -491,7 +569,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;
@@ -527,6 +605,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, ...)
@@ -1743,3 +1983,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/misc.h b/misc.h
index 153d1137..b6f502b3 100644
--- a/misc.h
+++ b/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 at 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 **);
 
 void	 sock_set_v6only(int);
 
diff --git a/readconf.c b/readconf.c
index f63894f9..63baa7d7 100644
--- a/readconf.c
+++ b/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 at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -683,34 +683,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.
  */
@@ -1562,7 +1534,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)
@@ -2294,11 +2270,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 */
@@ -2323,6 +2301,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/readconf.h b/readconf.h
index 22fe5c18..34aad83c 100644
--- a/readconf.h
+++ b/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 at 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/scp.1 b/scp.1
index 76ce3336..92b63f6f 100644
--- a/scp.1
+++ b/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/scp.c b/scp.c
index a533eb09..2103a54e 100644
--- a/scp.c
+++ b/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).
@@ -112,6 +112,7 @@
 #endif
 
 #include "xmalloc.h"
+#include "ssh.h"
 #include "atomicio.h"
 #include "pathnames.h"
 #include "log.h"
@@ -123,8 +124,8 @@ extern char *__progname;
 
 #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;
@@ -149,6 +150,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;
 
@@ -231,7 +235,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];
 
@@ -241,6 +245,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.
@@ -274,6 +281,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);
@@ -305,7 +316,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;
@@ -316,6 +327,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) {
@@ -323,6 +337,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);
@@ -367,14 +385,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;
@@ -430,10 +448,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");
@@ -533,8 +550,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]);
@@ -590,71 +607,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 at 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");
@@ -664,23 +675,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);
@@ -695,8 +697,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)
@@ -706,21 +707,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)
@@ -734,22 +755,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;
@@ -759,6 +768,9 @@ tolocal(int argc, char **argv)
 		(void) close(remin);
 		remin = remout = -1;
 	}
+	free(suser);
+	free(host);
+	free(src);
 }
 
 void
@@ -1275,8 +1287,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/sftp.1 b/sftp.1
index c218376f..49f7febf 100644
--- a/sftp.1
+++ b/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.
+.Pp
+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
-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
+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.
+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
-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).
-.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/sftp.c b/sftp.c
index 67110f73..9aee2faf 100644
--- a/sftp.c
+++ b/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 at openbsd.org>
  *
@@ -2301,19 +2301,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;
@@ -2368,7 +2365,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) {
@@ -2451,33 +2450,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);
+			break;
 		}
+		file2 = *(argv + 1);
 
-		if ((cp = colon(host)) != NULL) {
-			*cp++ = '\0';
-			file1 = cp;
-		}
-
-		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/ssh.1 b/ssh.1
index 92092df1..310f34cc 100644
--- a/ssh.1
+++ b/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
-name).
+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
+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/ssh.c b/ssh.c
index ae37432b..213c35e7 100644
--- a/ssh.c
+++ b/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 at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -203,7 +203,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);
 }
@@ -846,14 +846,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':
@@ -933,16 +937,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/ssh_config.5 b/ssh_config.5
index 96e6904b..c0470104 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -33,8 +33,8 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: ssh_config.5,v 1.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.

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list