]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
upstream commit
authormillert@openbsd.org <millert@openbsd.org>
Sat, 21 Oct 2017 23:06:24 +0000 (23:06 +0000)
committerDamien Miller <djm@mindrot.org>
Mon, 23 Oct 2017 05:10:08 +0000 (16:10 +1100)
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
misc.h
readconf.c
readconf.h
scp.1
scp.c
sftp.1
sftp.c
ssh.1
ssh.c
ssh_config.5

diff --git a/misc.c b/misc.c
index 05950a471246fad08ad8577a8d352a0d9b262b43..d4d0e44a592fa44f8b23799fdb7d4128cd163482 100644 (file)
--- 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 153d11375bf56e05358e13d2c38d530797195da8..b6f502b3e5fe7e089b47d42bce2cf425e0442949 100644 (file)
--- 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);
 
index f63894f9ca158bd1db59859e89718104a051efde..63baa7d7874d378596de10c7228f02bc9d9bd2bf 100644 (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)
index 22fe5c1873c3b79f4cdee02e97633a9c23a2c2d4..34aad83cf5bc4540900b401a1fde508f1538f029 100644 (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);
diff --git a/scp.1 b/scp.1
index 76ce3336127314365445630646af062c90e9e1ff..92b63f6f4ab1511d6b24ed47c120a69690661e06 100644 (file)
--- 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
 .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 a533eb09741224aaef4fc2dce973354f94c900c8..2103a54ea2c083235bb832808152ac1c7f50b554 100644 (file)
--- 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).
 #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 c218376fbf0ccd0b337b7d49cd78685a68efa2bc..49f7febf157f6536bb6b76405ddc117eb27d9f05 100644 (file)
--- 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
 .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 67110f738f79e176d46627b4c15c4836b2363a44..9aee2fafecde306008838c5ccb4d54f6e8a30113 100644 (file)
--- 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 92092df1e89c5a8920685c09b5a0d6f57307bb8a..310f34cc952546ba048980aa77751ee82fcf962d 100644 (file)
--- 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 ae37432bd47e358a076e38a73a3d15786c4f8c0a..213c35e77fec9995013e0f9c8d41cb444e3d2677 100644 (file)
--- 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;
index 96e6904bcf990d47f4b63540477c8a2b59acf87d..c04701044a043964418ebbb484a4879d7853ec3a 100644 (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.