[PATCH 4/4] [incomplete] sftp-client: initial experimental SFTPv4 support

Mike Frysinger vapier at gentoo.org
Fri Oct 1 06:49:22 AEST 2021


*********************************************************************
Note: This is mostly complete, but there is one sticking point -- the
way glob interacts with ls_file.  If we want to actually merge SFTPv4
support I can finish this up, but I didn't want to spend much more on
this if we're going to keep the client at SFTPv3.
*********************************************************************

This updates the client to handle SFTPv4.  It is not enabled by default
though -- SFTPv3 is still the advertised version.  Users have to pass
an explicit -V4 to use it for now.

This change is a bit more invasive as the client has to do more heavy
lifting when sending requests & parsing results for humans to read.
---
 sftp-client.c | 110 +++++++++++++++++++++++++++++++++++++-------------
 sftp-common.c |  30 ++++++++------
 sftp-common.h |   3 +-
 sftp-server.c |   2 +-
 sftp.c        |  74 ++++++++++++++++++++-------------
 5 files changed, 149 insertions(+), 70 deletions(-)

diff --git a/sftp-client.c b/sftp-client.c
index 99f2a5c28e95..76f33a4c1092 100644
--- a/sftp-client.c
+++ b/sftp-client.c
@@ -247,6 +247,25 @@ send_string_request(struct sftp_conn *conn, u_int id, u_int code, const char *s,
 	sshbuf_free(msg);
 }
 
+static void
+send_string_flags_request(struct sftp_conn *conn, u_int id, u_int code,
+    const char *s, u_int len, uint32_t flags)
+{
+	struct sshbuf *msg;
+	int r;
+
+	if ((msg = sshbuf_new()) == NULL)
+		fatal_f("sshbuf_new failed");
+	if ((r = sshbuf_put_u8(msg, code)) != 0 ||
+	    (r = sshbuf_put_u32(msg, id)) != 0 ||
+	    (r = sshbuf_put_string(msg, s, len)) != 0 ||
+	    (r = sshbuf_put_u32(msg, flags)) != 0)
+		fatal_fr(r, "compose");
+	send_msg(conn, msg);
+	debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id);
+	sshbuf_free(msg);
+}
+
 static void
 send_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code,
     const void *s, u_int len, Attrib *a)
@@ -770,18 +789,22 @@ do_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag,
 			break;
 		debug3("Received %d SSH2_FXP_NAME responses", count);
 		for (i = 0; i < count; i++) {
-			char *filename, *longname;
+			char *filename, *longname = NULL;
 			Attrib a;
 
-			if ((r = sshbuf_get_cstring(msg, &filename,
-			    NULL)) != 0 ||
-			    (r = sshbuf_get_cstring(msg, &longname,
-			    NULL)) != 0)
-				fatal_fr(r, "parse filenames");
+			if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0)
+				fatal_fr(r, "parse filename");
+			if (conn->version <= 3) {
+				if ((r = sshbuf_get_cstring(msg, &longname,
+				    NULL)) != 0)
+					fatal_fr(r, "parse longnames");
+			} else
+				longname = filename;
 			if ((r = decode_attrib(msg, &a)) != 0) {
 				error_fr(r, "couldn't decode attrib");
 				free(filename);
-				free(longname);
+				if (filename != longname)
+					free(longname);
 				goto out;
 			}
 
@@ -805,7 +828,8 @@ do_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag,
 				(*dir)[++ents] = NULL;
 			}
 			free(filename);
-			free(longname);
+			if (filename != longname)
+				free(longname);
 		}
 	}
 	status = 0;
@@ -903,9 +927,17 @@ do_stat(struct sftp_conn *conn, const char *path, int quiet)
 
 	id = conn->msg_id++;
 
-	send_string_request(conn, id,
-	    conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT,
-	    path, strlen(path));
+	switch (conn->version) {
+	case 0 ... 3:
+		send_string_request(conn, id,
+		    conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT,
+		    path, strlen(path));
+		break;
+	case 4 ... 6:
+		send_string_flags_request(conn, id, SSH2_FXP_STAT, path,
+		    strlen(path), 0);
+		break;
+	}
 
 	return(get_decode_stat(conn, id, quiet));
 }
@@ -915,17 +947,27 @@ do_lstat(struct sftp_conn *conn, const char *path, int quiet)
 {
 	u_int id;
 
-	if (conn->version == 0) {
+	switch (conn->version) {
+	case 0:
 		if (quiet)
 			debug("Server version does not support lstat operation");
 		else
 			logit("Server version does not support lstat operation");
 		return(do_stat(conn, path, quiet));
+	case 1 ... 3:
+		id = conn->msg_id++;
+		send_string_request(conn, id, SSH2_FXP_LSTAT, path,
+		    strlen(path));
+		break;
+	case 4 ... 6:
+		id = conn->msg_id++;
+		send_string_flags_request(conn, id, SSH2_FXP_LSTAT, path,
+		    strlen(path), 0);
+		break;
+	default:
+		fatal("Unhandled SFTP version %u", conn->version);
 	}
 
-	id = conn->msg_id++;
-	send_string_request(conn, id, SSH2_FXP_LSTAT, path,
-	    strlen(path));
 
 	return(get_decode_stat(conn, id, quiet));
 }
@@ -938,8 +980,16 @@ do_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
 	u_int id;
 
 	id = conn->msg_id++;
-	send_string_request(conn, id, SSH2_FXP_FSTAT, handle,
-	    handle_len);
+	switch (conn->version) {
+	case 0 ... 3:
+		send_string_request(conn, id, SSH2_FXP_FSTAT, handle,
+		    handle_len);
+		break;
+	case 4 ... 6:
+		send_string_flags_request(conn, id, SSH2_FXP_FSTAT, handle,
+		    handle_len, 0);
+		break;
+	}
 
 	return(get_decode_stat(conn, id, quiet));
 }
@@ -985,7 +1035,7 @@ do_realpath_expand(struct sftp_conn *conn, const char *path, int expand)
 {
 	struct sshbuf *msg;
 	u_int expected_id, count, id;
-	char *filename, *longname;
+	char *filename, *longname = NULL;
 	Attrib a;
 	u_char type;
 	int r;
@@ -1034,10 +1084,13 @@ do_realpath_expand(struct sftp_conn *conn, const char *path, int expand)
 	if (count != 1)
 		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");
+	if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0)
+		fatal_fr(r, "parse filename");
+	if (conn->version <= 3 &&
+	    (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0)
+		fatal_fr(r, "parse longname");
+	if ((r = decode_attrib(msg, &a)) != 0)
+		fatal_fr(r, "parse attrib");
 
 	debug3("%s %s -> %s", what, path, filename);
 
@@ -1334,7 +1387,7 @@ do_readlink(struct sftp_conn *conn, const char *path)
 {
 	struct sshbuf *msg;
 	u_int expected_id, count, id;
-	char *filename, *longname;
+	char *filename, *longname = NULL;
 	Attrib a;
 	u_char type;
 	int r;
@@ -1370,10 +1423,13 @@ do_readlink(struct sftp_conn *conn, const char *path)
 	if (count != 1)
 		fatal("Got multiple names (%d) from SSH_FXP_READLINK", count);
 
-	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 filenames/attrib");
+	if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0)
+		fatal_fr(r, "parse filename");
+	if (conn->version <= 3 &&
+	    (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0)
+		fatal_fr(r, "parse longname");
+	if ((r = decode_attrib(msg, &a)) != 0)
+		fatal_fr(r, "parse attrib");
 
 	debug3("SSH_FXP_READLINK %s -> %s", path, filename);
 
diff --git a/sftp-common.c b/sftp-common.c
index b81c12b90e2d..d34b14724d42 100644
--- a/sftp-common.c
+++ b/sftp-common.c
@@ -101,6 +101,8 @@ stat_to_attrib(const struct stat *st, Attrib *a)
 		a->mtime_nsec = st->st_mtim.tv_nsec;
 		break;
 	}
+
+	a->link_count = st->st_nlink;
 }
 
 /* Convert from filexfer attribs to struct stat */
@@ -333,31 +335,33 @@ fx2txt(int status)
  * drwxr-xr-x    5 markus   markus       1024 Jan 13 18:39 .ssh
  */
 char *
-ls_file(const char *name, const struct stat *st, int remote, int si_units)
+ls_file(const char *name, const Attrib *a, int remote, int si_units)
 {
 	int ulen, glen, sz = 0;
-	struct tm *ltime = localtime(&st->st_mtime);
+	struct tm *ltime = localtime(&a->mtime);
 	const char *user, *group;
 	char buf[1024], lc[8], mode[11+1], tbuf[12+1], ubuf[11+1], gbuf[11+1];
 	char sbuf[FMT_SCALED_STRSIZE];
 	time_t now;
 
-	strmode(st->st_mode, mode);
+	strmode(a->perm, mode);
 	if (remote) {
-		snprintf(ubuf, sizeof ubuf, "%u", (u_int)st->st_uid);
+		snprintf(ubuf, sizeof ubuf, "%u", (u_int)a->uid);
 		user = ubuf;
-		snprintf(gbuf, sizeof gbuf, "%u", (u_int)st->st_gid);
+		snprintf(gbuf, sizeof gbuf, "%u", (u_int)a->gid);
 		group = gbuf;
-		strlcpy(lc, "?", sizeof(lc));
 	} else {
-		user = user_from_uid(st->st_uid, 0);
-		group = group_from_gid(st->st_gid, 0);
-		snprintf(lc, sizeof(lc), "%u", (u_int)st->st_nlink);
+		user = user_from_uid(a->uid, 0);
+		group = group_from_gid(a->gid, 0);
 	}
+	if (a->link_count)
+		snprintf(lc, sizeof(lc), "%u", (u_int)a->link_count);
+	else
+		strlcpy(lc, "?", sizeof(lc));
 	if (ltime != NULL) {
 		now = time(NULL);
-		if (now - (365*24*60*60)/2 < st->st_mtime &&
-		    now >= st->st_mtime)
+		if (now - (365*24*60*60)/2 < a->mtime &&
+		    now >= a->mtime)
 			sz = strftime(tbuf, sizeof tbuf, "%b %e %H:%M", ltime);
 		else
 			sz = strftime(tbuf, sizeof tbuf, "%b %e  %Y", ltime);
@@ -367,14 +371,14 @@ ls_file(const char *name, const struct stat *st, int remote, int si_units)
 	ulen = MAXIMUM(strlen(user), 8);
 	glen = MAXIMUM(strlen(group), 8);
 	if (si_units) {
-		fmt_scaled((long long)st->st_size, sbuf);
+		fmt_scaled((long long)a->size, sbuf);
 		snprintf(buf, sizeof buf, "%s %3s %-*s %-*s %8s %s %s",
 		    mode, lc, ulen, user, glen, group,
 		    sbuf, tbuf, name);
 	} else {
 		snprintf(buf, sizeof buf, "%s %3s %-*s %-*s %8llu %s %s",
 		    mode, lc, ulen, user, glen, group,
-		    (unsigned long long)st->st_size, tbuf, name);
+		    (unsigned long long)a->size, tbuf, name);
 	}
 	return xstrdup(buf);
 }
diff --git a/sftp-common.h b/sftp-common.h
index 0dc37dc364a9..89f226086810 100644
--- a/sftp-common.h
+++ b/sftp-common.h
@@ -50,6 +50,7 @@ struct Attrib {
 	u_int32_t	createtime_nsec;
 	int64_t		mtime;
 	u_int32_t	mtime_nsec;
+	u_int32_t	link_count;
 };
 
 void	 attrib_clear(Attrib *);
@@ -57,6 +58,6 @@ void	 stat_to_attrib(const struct stat *, Attrib *);
 void	 attrib_to_stat(const Attrib *, struct stat *);
 int	 decode_attrib(struct sshbuf *, Attrib *);
 int	 encode_attrib(struct sshbuf *, const Attrib *);
-char	*ls_file(const char *, const struct stat *, int, int);
+char	*ls_file(const char *, const Attrib *, int, int);
 
 const char *fx2txt(int);
diff --git a/sftp-server.c b/sftp-server.c
index cb5577ec5d7d..789ef1a17545 100644
--- a/sftp-server.c
+++ b/sftp-server.c
@@ -1235,7 +1235,7 @@ process_readdir(u_int32_t id)
 			stats[count].name = xstrdup(dp->d_name);
 			if (sftp_version <= 3)
 				stats[count].long_name = ls_file(dp->d_name,
-				    &st, 0, 0);
+				    &stats[count].attrib, 0, 0);
 			else
 				stats[count].long_name = NULL;
 			count++;
diff --git a/sftp.c b/sftp.c
index 725ce2872b4a..f46c56276e0e 100644
--- a/sftp.c
+++ b/sftp.c
@@ -71,7 +71,7 @@ typedef void EditLine;
 #include "sftp-client.h"
 
 /* The max SFTP version we support */
-#define SSH2_FILEXFER_VERSION_MAX	3
+#define SSH2_FILEXFER_VERSION_MAX	4
 
 /* File to read commands from */
 FILE* infile;
@@ -860,12 +860,8 @@ do_ls_dir(struct sftp_conn *conn, const char *path,
 		if (lflag & LS_LONG_VIEW) {
 			if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
 				char *lname;
-				struct stat sb;
 
-				memset(&sb, 0, sizeof(sb));
-				attrib_to_stat(&d[n]->a, &sb);
-				lname = ls_file(fname, &sb, 1,
-				    (lflag & LS_SI_UNITS));
+				lname = ls_file(fname, &d[n]->a, 1, (lflag & LS_SI_UNITS));
 				mprintf("%s\n", lname);
 				free(lname);
 			} else
@@ -1291,9 +1287,9 @@ makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
 }
 
 static int
-parse_args(const char **cpp, int *ignore_errors, int *disable_echo, int *aflag,
-	  int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
-	  int *rflag, int *sflag,
+parse_args(struct sftp_conn *conn, const char **cpp, int *ignore_errors,
+    int *disable_echo, int *aflag, int *fflag, int *hflag, int *iflag,
+    int *lflag, int *pflag, int *rflag, int *sflag,
     unsigned long *n_arg, char **path1, char **path2)
 {
 	const char *cmd, *cp = *cpp;
@@ -1457,20 +1453,31 @@ parse_args(const char **cpp, int *ignore_errors, int *disable_echo, int *aflag,
 	case I_CHGRP:
 		if ((optidx = parse_ch_flags(cmd, argv, argc, hflag)) == -1)
 			return -1;
-		/* Get numeric arg (mandatory) */
-		if (argc - optidx < 1)
-			goto need_num_arg;
-		errno = 0;
-		ll = strtoll(argv[optidx], &cp2, base);
-		if (cp2 == argv[optidx] || *cp2 != '\0' ||
-		    ((ll == LLONG_MIN || ll == LLONG_MAX) && errno == ERANGE) ||
-		    ll < 0 || ll > UINT32_MAX) {
+		if (sftp_proto_version(conn) >= 4 &&
+		    (cmdnum == I_CHOWN || cmdnum == I_CHGRP)) {
+			if (argc - optidx < 1) {
+				error("%s: Missing account.", cmd);
+				return -1;
+			}
+			/* Not a path, but we need a char* pointer to pass back. */
+			*path2 = xstrdup(argv[optidx]);
+		} else {
+			/* Get numeric arg (mandatory) */
+			if (argc - optidx < 1)
+				goto need_num_arg;
+			errno = 0;
+			ll = strtoll(argv[optidx], &cp2, base);
+			if (cp2 == argv[optidx] || *cp2 != '\0' ||
+			    ((ll == LLONG_MIN || ll == LLONG_MAX) &&
+				errno == ERANGE) ||
+			    ll < 0 || ll > UINT32_MAX) {
  need_num_arg:
-			error("You must supply a numeric argument "
-			    "to the %s command.", cmd);
-			return -1;
+				error("You must supply a numeric argument "
+				    "to the %s command.", cmd);
+				return -1;
+			}
+			*n_arg = ll;
 		}
-		*n_arg = ll;
 		if (cmdnum == I_LUMASK)
 			break;
 		/* Get pathname (mandatory) */
@@ -1515,8 +1522,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 	glob_t g;
 
 	path1 = path2 = NULL;
-	cmdnum = parse_args(&cmd, &ignore_errors, &disable_echo, &aflag, &fflag,
-	    &hflag, &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg,
+	cmdnum = parse_args(conn, &cmd, &ignore_errors, &disable_echo, &aflag,
+	    &fflag, &hflag, &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg,
 	    &path1, &path2);
 	if (ignore_errors != 0)
 		err_abort = 0;
@@ -1686,7 +1693,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 		}
 		break;
 	case I_CHOWN:
-	case I_CHGRP:
+	case I_CHGRP: {
+		u_int32_t flag;
 		path1 = make_absolute(path1, *pwd);
 		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
@@ -1698,7 +1706,10 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 				} else
 					continue;
 			}
-			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
+			flag = sftp_proto_version(conn) <= 3 ?
+			    SSH2_FILEXFER_ATTR_UIDGID :
+			    SSH2_FILEXFER_ATTR_OWNERGROUP;
+			if (!(aa->flags & flag)) {
 				error("Can't get current ownership of "
 				    "remote file \"%s\"", g.gl_pathv[i]);
 				if (err_abort) {
@@ -1707,17 +1718,23 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 				} else
 					continue;
 			}
-			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
+			aa->flags &= flag;
 			if (cmdnum == I_CHOWN) {
 				if (!quiet)
 					mprintf("Changing owner on %s\n",
 					    g.gl_pathv[i]);
-				aa->uid = n_arg;
+				if (flag == SSH2_FILEXFER_ATTR_UIDGID)
+					aa->uid = n_arg;
+				else
+					aa->owner = path2;
 			} else {
 				if (!quiet)
 					mprintf("Changing group on %s\n",
 					    g.gl_pathv[i]);
-				aa->gid = n_arg;
+				if (flag == SSH2_FILEXFER_ATTR_UIDGID)
+					aa->gid = n_arg;
+				else
+					aa->group = path2;
 			}
 			err = (hflag ? do_lsetstat : do_setstat)(conn,
 			    g.gl_pathv[i], aa);
@@ -1725,6 +1742,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
 				break;
 		}
 		break;
+	}
 	case I_PWD:
 		mprintf("Remote working directory: %s\n", *pwd);
 		break;
-- 
2.33.0



More information about the openssh-unix-dev mailing list