[openssh-commits] [openssh] 06/08: upstream: SFTP protocol extension to allow the server to expand

git+noreply at mindrot.org git+noreply at mindrot.org
Tue Aug 10 12:48:04 AEST 2021


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

djm pushed a commit to branch master
in repository openssh.

commit 2ab864010e0a93c5dd95116fb5ceaf430e2fc23c
Author: djm at openbsd.org <djm at openbsd.org>
Date:   Mon Aug 9 23:47:44 2021 +0000

    upstream: SFTP protocol extension to allow the server to expand
    
    ~-prefixed paths, in particular ~user ones. Allows scp in sftp mode to accept
    these paths, like scp in rcp mode does.
    
    prompted by and much discussion deraadt@
    ok markus@
    
    OpenBSD-Commit-ID: 7d794def9e4de348e1e777f6030fc9bafdfff392
---
 PROTOCOL      | 21 +++++++++++++++++++-
 misc.c        | 49 +++++++++++++++++++++++++++++++++-------------
 misc.h        |  3 ++-
 scp.c         | 17 ++++++++++------
 sftp-client.c | 59 +++++++++++++++++++++++++++++++++++++++++++++----------
 sftp-client.h | 10 ++++++++--
 sftp-server.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 7 files changed, 187 insertions(+), 35 deletions(-)

diff --git a/PROTOCOL b/PROTOCOL
index 0b2ea60a..3141cda6 100644
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -525,6 +525,25 @@ limits.
 This extension is advertised in the SSH_FXP_VERSION hello with version
 "1".
 
+3.9. sftp: Extension request "expand-path at openssh.com"
+
+This request supports canonicalisation of relative paths and
+those that need tilde-expansion, i.e. "~", "~/..." and "~user/..."
+These paths are expanded using shell-like rules and the resultant
+path is canonicalised similarly to SSH2_FXP_REALPATH.
+
+It is implemented as a SSH_FXP_EXTENDED request with the following
+format:
+
+	uint32		id
+	string		"expand-path at openssh.com"
+	string		path
+
+Its reply is the same format as that of SSH2_FXP_REALPATH.
+
+This extension is advertised in the SSH_FXP_VERSION hello with version
+"1".
+
 4. Miscellaneous changes
 
 4.1 Public key format
@@ -556,4 +575,4 @@ OpenSSH's connection multiplexing uses messages as described in
 PROTOCOL.mux over a Unix domain socket for communications between a
 master instance and later clients.
 
-$OpenBSD: PROTOCOL,v 1.41 2021/02/18 02:49:35 djm Exp $
+$OpenBSD: PROTOCOL,v 1.42 2021/08/09 23:47:44 djm Exp $
diff --git a/misc.c b/misc.c
index adfe9033..b8d1040d 100644
--- a/misc.c
+++ b/misc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.c,v 1.168 2021/07/12 06:22:57 dtucker Exp $ */
+/* $OpenBSD: misc.c,v 1.169 2021/08/09 23:47:44 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  * Copyright (c) 2005-2020 Damien Miller.  All rights reserved.
@@ -1115,29 +1115,37 @@ freeargs(arglist *args)
  * Expands tildes in the file name.  Returns data allocated by xmalloc.
  * Warning: this calls getpw*.
  */
-char *
-tilde_expand_filename(const char *filename, uid_t uid)
+int
+tilde_expand(const char *filename, uid_t uid, char **retp)
 {
 	const char *path, *sep;
 	char user[128], *ret;
 	struct passwd *pw;
 	u_int len, slash;
 
-	if (*filename != '~')
-		return (xstrdup(filename));
+	if (*filename != '~') {
+		*retp = xstrdup(filename);
+		return 0;
+	}
 	filename++;
 
 	path = strchr(filename, '/');
 	if (path != NULL && path > filename) {		/* ~user/path */
 		slash = path - filename;
-		if (slash > sizeof(user) - 1)
-			fatal("tilde_expand_filename: ~username too long");
+		if (slash > sizeof(user) - 1) {
+			error_f("~username too long");
+			return -1;
+		}
 		memcpy(user, filename, slash);
 		user[slash] = '\0';
-		if ((pw = getpwnam(user)) == NULL)
-			fatal("tilde_expand_filename: No such user %s", user);
-	} else if ((pw = getpwuid(uid)) == NULL)	/* ~/path */
-		fatal("tilde_expand_filename: No such uid %ld", (long)uid);
+		if ((pw = getpwnam(user)) == NULL) {
+			error_f("No such user %s", user);
+			return -1;
+		}
+	} else if ((pw = getpwuid(uid)) == NULL) {	/* ~/path */
+		error_f("No such uid %ld", (long)uid);
+		return -1;
+	}
 
 	/* Make sure directory has a trailing '/' */
 	len = strlen(pw->pw_dir);
@@ -1150,10 +1158,23 @@ tilde_expand_filename(const char *filename, uid_t uid)
 	if (path != NULL)
 		filename = path + 1;
 
-	if (xasprintf(&ret, "%s%s%s", pw->pw_dir, sep, filename) >= PATH_MAX)
-		fatal("tilde_expand_filename: Path too long");
+	if (xasprintf(&ret, "%s%s%s", pw->pw_dir, sep, filename) >= PATH_MAX) {
+		error_f("Path too long");
+		return -1;
+	}
 
-	return (ret);
+	*retp = ret;
+	return 0;
+}
+
+char *
+tilde_expand_filename(const char *filename, uid_t uid)
+{
+	char *ret;
+
+	if (tilde_expand(filename, uid, &ret) != 0)
+		cleanup_exit(255);
+	return ret;
 }
 
 /*
diff --git a/misc.h b/misc.h
index dd899a32..2e2dca54 100644
--- a/misc.h
+++ b/misc.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.h,v 1.97 2021/06/08 06:54:40 djm Exp $ */
+/* $OpenBSD: misc.h,v 1.98 2021/08/09 23:47:44 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
@@ -71,6 +71,7 @@ int	 parse_user_host_port(const char *, char **, char **, int *);
 int	 parse_uri(const char *, const char *, char **, char **, int *, char **);
 int	 convtime(const char *);
 const char *fmt_timeframe(time_t t);
+int	 tilde_expand(const char *, uid_t, char **);
 char	*tilde_expand_filename(const char *, uid_t);
 
 char	*dollar_expand(int *, const char *string, ...);
diff --git a/scp.c b/scp.c
index fe3ac701..a0377c6c 100644
--- a/scp.c
+++ b/scp.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: scp.c,v 1.226 2021/08/09 23:44:32 djm Exp $ */
+/* $OpenBSD: scp.c,v 1.227 2021/08/09 23:47:44 djm Exp $ */
 /*
  * scp - secure remote copy.  This is basically patched BSD rcp which
  * uses ssh to do the data transfer (instead of using rcmd).
@@ -1255,10 +1255,14 @@ tolocal(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct)
 
 /* Canonicalise a remote path, handling ~ by assuming cwd is the homedir */
 static char *
-absolute_remote_path(const char *path, const char *remote_path)
+absolute_remote_path(struct sftp_conn *conn, const char *path,
+    const char *remote_path)
 {
 	char *ret;
 
+	if (can_expand_path(conn))
+		return do_expand_path(conn, path);
+
 	/* Handle ~ prefixed paths */
 	if (*path != '~')
 		ret = xstrdup(path);
@@ -1296,7 +1300,7 @@ source_sftp(int argc, char *src, char *targ,
 	 * No need to glob here - the local shell already took care of
 	 * the expansions
 	 */
-	if ((target = absolute_remote_path(targ, *remote_path)) == NULL)
+	if ((target = absolute_remote_path(conn, targ, *remote_path)) == NULL)
 		cleanup_exit(255);
 	target_is_dir = remote_is_dir(conn, target);
 	if (targetshouldbedirectory && !target_is_dir) {
@@ -1508,7 +1512,7 @@ sink_sftp(int argc, char *dst, const char *src, struct sftp_conn *conn)
 		goto out;
 	}
 
-	if ((abs_src = absolute_remote_path(src, remote_path)) == NULL) {
+	if ((abs_src = absolute_remote_path(conn, src, remote_path)) == NULL) {
 		err = -1;
 		goto out;
 	}
@@ -1920,8 +1924,9 @@ throughlocal_sftp(struct sftp_conn *from, struct sftp_conn *to,
 	if ((filename = basename(src)) == NULL)
 		fatal("basename %s: %s", src, strerror(errno));
 
-	if ((abs_src = absolute_remote_path(src, from_remote_path)) == NULL ||
-	    (target = absolute_remote_path(targ, *to_remote_path)) == NULL)
+	if ((abs_src = absolute_remote_path(from, src,
+	    from_remote_path)) == NULL ||
+	    (target = absolute_remote_path(to, targ, *to_remote_path)) == NULL)
 		cleanup_exit(255);
 	free(from_remote_path);
 	memset(&g, 0, sizeof(g));
diff --git a/sftp-client.c b/sftp-client.c
index ce31c35a..5bfff90d 100644
--- a/sftp-client.c
+++ b/sftp-client.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-client.c,v 1.153 2021/08/09 07:16:09 djm Exp $ */
+/* $OpenBSD: sftp-client.c,v 1.154 2021/08/09 23:47:44 djm Exp $ */
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm at openbsd.org>
  *
@@ -102,6 +102,7 @@ struct sftp_conn {
 #define SFTP_EXT_FSYNC		0x00000010
 #define SFTP_EXT_LSETSTAT	0x00000020
 #define SFTP_EXT_LIMITS		0x00000040
+#define SFTP_EXT_PATH_EXPAND	0x00000080
 	u_int exts;
 	u_int64_t limit_kbps;
 	struct bwlimit bwlimit_in, bwlimit_out;
@@ -529,6 +530,10 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests,
 		    strcmp((char *)value, "1") == 0) {
 			ret->exts |= SFTP_EXT_LIMITS;
 			known = 1;
+		} else if (strcmp(name, "expand-path at openssh.com") == 0 &&
+		    strcmp((char *)value, "1") == 0) {
+			ret->exts |= SFTP_EXT_PATH_EXPAND;
+			known = 1;
 		}
 		if (known) {
 			debug2("Server supports extension \"%s\" revision %s",
@@ -964,8 +969,9 @@ do_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
 	return status == SSH2_FX_OK ? 0 : -1;
 }
 
-char *
-do_realpath(struct sftp_conn *conn, const char *path)
+/* Implements both the realpath and expand-path operations */
+static char *
+do_realpath_expand(struct sftp_conn *conn, const char *path, int expand)
 {
 	struct sshbuf *msg;
 	u_int expected_id, count, id;
@@ -973,14 +979,26 @@ do_realpath(struct sftp_conn *conn, const char *path)
 	Attrib a;
 	u_char type;
 	int r;
+	const char *what = "SSH2_FXP_REALPATH";
 
-	expected_id = id = conn->msg_id++;
-	send_string_request(conn, id, SSH2_FXP_REALPATH, path,
-	    strlen(path));
-
+	if (expand)
+		what = "expand-path at openssh.com";
 	if ((msg = sshbuf_new()) == NULL)
 		fatal_f("sshbuf_new failed");
 
+	expected_id = id = conn->msg_id++;
+	if (expand) {
+		if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
+		    (r = sshbuf_put_u32(msg, id)) != 0 ||
+		    (r = sshbuf_put_cstring(msg,
+		    "expand-path at openssh.com")) != 0 ||
+		    (r = sshbuf_put_cstring(msg, path)) != 0)
+			fatal_fr(r, "compose %s", what);
+		send_msg(conn, msg);
+	} else {
+		send_string_request(conn, id, SSH2_FXP_REALPATH,
+		    path, strlen(path));
+	}
 	get_msg(conn, msg);
 	if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
 	    (r = sshbuf_get_u32(msg, &id)) != 0)
@@ -1004,15 +1022,14 @@ do_realpath(struct sftp_conn *conn, const char *path)
 	if ((r = sshbuf_get_u32(msg, &count)) != 0)
 		fatal_fr(r, "parse count");
 	if (count != 1)
-		fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count);
+		fatal("Got multiple names (%d) from %s", count, what);
 
 	if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 ||
 	    (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 ||
 	    (r = decode_attrib(msg, &a)) != 0)
 		fatal_fr(r, "parse filename/attrib");
 
-	debug3("SSH_FXP_REALPATH %s -> %s size %lu", path, filename,
-	    (unsigned long)a.size);
+	debug3("%s %s -> %s", what, path, filename);
 
 	free(longname);
 
@@ -1021,6 +1038,28 @@ do_realpath(struct sftp_conn *conn, const char *path)
 	return(filename);
 }
 
+char *
+do_realpath(struct sftp_conn *conn, const char *path)
+{
+	return do_realpath_expand(conn, path, 0);
+}
+
+int
+can_expand_path(struct sftp_conn *conn)
+{
+	return (conn->exts & SFTP_EXT_PATH_EXPAND) != 0;
+}
+
+char *
+do_expand_path(struct sftp_conn *conn, const char *path)
+{
+	if (!can_expand_path(conn)) {
+		debug3_f("no server support, fallback to realpath");
+		return do_realpath_expand(conn, path, 0);
+	}
+	return do_realpath_expand(conn, path, 1);
+}
+
 int
 do_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath,
     int force_legacy)
diff --git a/sftp-client.h b/sftp-client.h
index 00707f7c..7d0bd12a 100644
--- a/sftp-client.h
+++ b/sftp-client.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-client.h,v 1.33 2021/08/07 00:12:09 djm Exp $ */
+/* $OpenBSD: sftp-client.h,v 1.34 2021/08/09 23:47:44 djm Exp $ */
 
 /*
  * Copyright (c) 2001-2004 Damien Miller <djm at openbsd.org>
@@ -113,11 +113,17 @@ int do_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a);
 /* Canonicalise 'path' - caller must free result */
 char *do_realpath(struct sftp_conn *, const char *);
 
+/* Canonicalisation with tilde expansion (requires server extension) */
+char *do_expand_path(struct sftp_conn *, const char *);
+
+/* Returns non-zero if server can tilde-expand paths */
+int can_expand_path(struct sftp_conn *);
+
 /* Get statistics for filesystem hosting file at "path" */
 int do_statvfs(struct sftp_conn *, const char *, struct sftp_statvfs *, int);
 
 /* Rename 'oldpath' to 'newpath' */
-int do_rename(struct sftp_conn *, const char *, const char *, int force_legacy);
+int do_rename(struct sftp_conn *, const char *, const char *, int);
 
 /* Link 'oldpath' to 'newpath' */
 int do_hardlink(struct sftp_conn *, const char *, const char *);
diff --git a/sftp-server.c b/sftp-server.c
index c89c1f42..18d19491 100644
--- a/sftp-server.c
+++ b/sftp-server.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-server.c,v 1.128 2021/06/06 03:15:39 djm Exp $ */
+/* $OpenBSD: sftp-server.c,v 1.129 2021/08/09 23:47:44 djm Exp $ */
 /*
  * Copyright (c) 2000-2004 Markus Friedl.  All rights reserved.
  *
@@ -115,6 +115,7 @@ static void process_extended_hardlink(u_int32_t id);
 static void process_extended_fsync(u_int32_t id);
 static void process_extended_lsetstat(u_int32_t id);
 static void process_extended_limits(u_int32_t id);
+static void process_extended_expand(u_int32_t id);
 static void process_extended(u_int32_t id);
 
 struct sftp_handler {
@@ -158,6 +159,8 @@ static const struct sftp_handler extended_handlers[] = {
 	{ "fsync", "fsync at openssh.com", 0, process_extended_fsync, 1 },
 	{ "lsetstat", "lsetstat at openssh.com", 0, process_extended_lsetstat, 1 },
 	{ "limits", "limits at openssh.com", 0, process_extended_limits, 0 },
+	{ "expand-path", "expand-path at openssh.com", 0,
+	    process_extended_expand, 0 },
 	{ NULL, NULL, 0, NULL, 0 }
 };
 
@@ -706,6 +709,7 @@ process_init(void)
 	compose_extension(msg, "fsync at openssh.com", "1");
 	compose_extension(msg, "lsetstat at openssh.com", "1");
 	compose_extension(msg, "limits at openssh.com", "1");
+	compose_extension(msg, "expand-path at openssh.com", "1");
 
 	send_msg(msg);
 	sshbuf_free(msg);
@@ -1519,6 +1523,63 @@ process_extended_limits(u_int32_t id)
 	sshbuf_free(msg);
 }
 
+static void
+process_extended_expand(u_int32_t id)
+{
+	char cwd[PATH_MAX], resolvedname[PATH_MAX];
+	char *path, *npath;
+	int r;
+	Stat s;
+
+	if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
+		fatal_fr(r, "parse");
+	if (getcwd(cwd, sizeof(cwd)) == NULL) {
+		send_status(id, errno_to_portable(errno));
+		goto out;
+	}
+
+	debug3("request %u: expand, original \"%s\"", id, path);
+	if (path[0] == '\0') {
+		/* empty path */
+		free(path);
+		path = xstrdup(".");
+	} else if (*path == '~') {
+		/* ~ expand path */
+		/* Special-case for "~" and "~/" to respect homedir flag */
+		if (strcmp(path, "~") == 0) {
+			free(path);
+			path = xstrdup(cwd);
+		} else if (strncmp(path, "~/", 2) == 0) {
+			npath = xstrdup(path + 2);
+			free(path);
+			xasprintf(&path, "%s/%s", cwd, npath);
+		} else {
+			/* ~user expansions */
+			if (tilde_expand(path, pw->pw_uid, &npath) != 0) {
+				send_status(id, errno_to_portable(EINVAL));
+				goto out;
+			}
+			free(path);
+			path = npath;
+		}
+	} else if (*path != '/') {
+		/* relative path */
+		xasprintf(&npath, "%s/%s", cwd, path);
+		free(path);
+		path = npath;
+	}
+	verbose("expand \"%s\"", path);
+	if (sftp_realpath(path, resolvedname) == NULL) {
+		send_status(id, errno_to_portable(errno));
+		goto out;
+	}
+	attrib_clear(&s.attrib);
+	s.name = s.long_name = resolvedname;
+	send_names(id, 1, &s);
+ out:
+	free(path);
+}
+
 static void
 process_extended(u_int32_t id)
 {

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


More information about the openssh-commits mailing list