summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormillert@openbsd.org <millert@openbsd.org>2017-10-21 23:06:24 +0000
committerDamien Miller <djm@mindrot.org>2017-10-23 16:10:08 +1100
commit887669ef032d63cf07f53cada216fa8a0c9a7d72 (patch)
tree089b20255da21a489d7bc796a8ee86bd0b8f028f
parentd27bff293cfeb2252f4c7a58babe5ad3262c6c98 (diff)
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
-rw-r--r--misc.c297
-rw-r--r--misc.h5
-rw-r--r--readconf.c54
-rw-r--r--readconf.h3
-rw-r--r--scp.141
-rw-r--r--scp.c199
-rw-r--r--sftp.177
-rw-r--r--sftp.c58
-rw-r--r--ssh.136
-rw-r--r--ssh.c56
-rw-r--r--ssh_config.57
11 files changed, 582 insertions, 251 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;
@@ -434,6 +437,12 @@ hpdelim(char **cp)
}
char *
+hpdelim(char **cp)
+{
+ return hpdelim2(cp, NULL);
+}
+
+char *
cleanhostname(char *host)
{
if (*host == '[' && host[strlen(host) - 1] == ']') {
@@ -467,6 +476,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).
@@ -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@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@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@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@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@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.
-.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/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@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);
- }
-
- 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/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
+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/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@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@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.