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
This commit is contained in:
millert@openbsd.org 2017-10-21 23:06:24 +00:00 committed by Damien Miller
parent d27bff293c
commit 887669ef03
11 changed files with 581 additions and 250 deletions

297
misc.c
View File

@ -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;
}

5
misc.h
View File

@ -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);

View File

@ -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)

View File

@ -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);

41
scp.1
View File

@ -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

197
scp.c
View File

@ -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);
}

77
sftp.1
View File

@ -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.
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

58
sftp.c
View File

@ -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);
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 '/' */

36
ssh.1
View File

@ -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.

56
ssh.c
View File

@ -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;

View File

@ -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.