Mailing List Archive

[PATCH 4/4] [incomplete] sftp-client: initial experimental SFTPv4 support
*********************************************************************
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

_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev