This brings SFTPv4 support to the sftp client & server. I split them up so it's easy to see & merge just server support if we decide to not do the client. I also haven't touched scp. There are concerns over the SFTPv4 spec being a bit bloated. I discuss those in the server patch, but the tl;dr is that I think we can include the new features we want (e.g. better timestamps) while omitting the parts we don't (e.g. text mode & ACLs). This keeps the actual code size manageable. Implementation wise, I thought about making the code more object-oriented (i.e. structs of func pointers for each SFTP version), but after comparing the actual spec differences, it seems that things are minor enough that inlining the version tests isn't terrible. A few funcs are a little bit harder to follow (largely the file attribute serializers), but all the rest seem to be OK. Or, it at least seems better when compared to the amount of duplicated logic we'd have if there were v3 & v4 variants of these functions. NB: The patch series might not apply cleanly because they're based on other SFTP features I've posted to the mailing list. I can easily respin once those get merged ;). Mike Frysinger (4): scp/sftp: add -V for setting SFTP protocol version add SFTPv4 constants & handle error & file mode strings sftp-server: initial experimental SFTPv4 support [incomplete] sftp-client: initial experimental SFTPv4 support PROTOCOL | 6 +- scp.c | 13 +++- sftp-client.c | 117 +++++++++++++++++++++------- sftp-common.c | 211 ++++++++++++++++++++++++++++++++++++++++---------- sftp-common.h | 17 +++- sftp-server.c | 198 ++++++++++++++++++++++++++++++++++++---------- sftp.c | 83 +++++++++++++------- sftp.h | 30 +++++-- 8 files changed, 524 insertions(+), 151 deletions(-) -- 2.33.0
Mike Frysinger
2021-Sep-30 20:49 UTC
[PATCH 1/4] scp/sftp: add -V for setting SFTP protocol version
This makes it easier to force specific protocol versions, and will make it easier to add experimental SFTPv4 support while keeping v3 as the default. I'm not super in love with using -V for this (since that is often a shorthand for --version), but it's one of the few remaining short options that haven't been allocated and can be consistent across the 3 tools that utilize SFTP. --- scp.c | 13 +++++++++++-- sftp-client.c | 7 ++++++- sftp-common.c | 3 +++ sftp-common.h | 2 ++ sftp-server.c | 17 +++++++++++++++-- sftp.c | 11 ++++++++++- sftp.h | 3 --- 7 files changed, 47 insertions(+), 9 deletions(-) diff --git a/scp.c b/scp.c index 73ce8554d788..9623c310a5ba 100644 --- a/scp.c +++ b/scp.c @@ -134,6 +134,9 @@ #include "sftp-common.h" #include "sftp-client.h" +/* The max SFTP version we support */ +#define SSH2_FILEXFER_VERSION_MAX 3 + extern char *__progname; #define COPY_BUFLEN 16384 @@ -444,7 +447,7 @@ int main(int argc, char **argv) { int ch, fflag, tflag, status, n; - char **newargv, *argv0; + char **newargv, *argv0, *cp; const char *errstr; extern char *optarg; extern int optind; @@ -481,7 +484,7 @@ main(int argc, char **argv) fflag = Tflag = tflag = 0; while ((ch = getopt(argc, argv, - "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:")) != -1) { + "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:V:")) != -1) { switch (ch) { /* User-visible flags. */ case '1': @@ -562,6 +565,12 @@ main(int argc, char **argv) addargs(&remote_remote_args, "-q"); showprogress = 0; break; + case 'V': + sftp_version = strtol(optarg, &cp, 10); + /* Assume min version is 0 to avoid compiler warning */ + if (sftp_version > SSH2_FILEXFER_VERSION_MAX) + fatal("Unsupported SFTP version %i", sftp_version); + break; /* Server options. */ case 'd': diff --git a/sftp-client.c b/sftp-client.c index dc55ee7a4594..99f2a5c28e95 100644 --- a/sftp-client.c +++ b/sftp-client.c @@ -471,7 +471,7 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); if ((r = sshbuf_put_u8(msg, SSH2_FXP_INIT)) != 0 || - (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0) + (r = sshbuf_put_u32(msg, sftp_version)) != 0) fatal_fr(r, "parse"); send_msg(ret, msg); @@ -492,6 +492,11 @@ do_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, fatal_fr(r, "parse version"); debug2("Remote version: %u", ret->version); + if (ret->version != sftp_version) { + debug("Changing local SFTP version from %u to %u", sftp_version, + ret->version); + sftp_version = ret->version; + } /* Check for extensions */ while (sshbuf_len(msg) > 0) { diff --git a/sftp-common.c b/sftp-common.c index 3ad57673d41e..7681206728a7 100644 --- a/sftp-common.c +++ b/sftp-common.c @@ -50,6 +50,9 @@ #include "sftp.h" #include "sftp-common.h" +/* The version of SFTP to use by default */ +unsigned int sftp_version = 3; + /* Clear contents of attributes structure */ void attrib_clear(Attrib *a) diff --git a/sftp-common.h b/sftp-common.h index 2e778a9ca0ba..5fc8b45da39a 100644 --- a/sftp-common.h +++ b/sftp-common.h @@ -31,6 +31,8 @@ struct sshbuf; typedef struct Attrib Attrib; +extern unsigned int sftp_version; + /* File attributes */ struct Attrib { u_int32_t flags; diff --git a/sftp-server.c b/sftp-server.c index 11fdb46de16d..859e93f31111 100644 --- a/sftp-server.c +++ b/sftp-server.c @@ -53,6 +53,9 @@ #include "sftp.h" #include "sftp-common.h" +/* The max SFTP version we support */ +#define SSH2_FILEXFER_VERSION_MAX 3 + char *sftp_realpath(const char *, char *); /* sftp-realpath.c */ /* Maximum data read that we are willing to accept */ @@ -703,8 +706,12 @@ process_init(void) verbose("received client version %u", version); if ((msg = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); + if (version < sftp_version) { + verbose("downgrading server version from %u", sftp_version); + sftp_version = version; + } if ((r = sshbuf_put_u8(msg, SSH2_FXP_VERSION)) != 0 || - (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0) + (r = sshbuf_put_u32(msg, sftp_version)) != 0) fatal_fr(r, "compose"); /* extension advertisments */ @@ -1844,7 +1851,7 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw) pw = pwcopy(user_pw); while (!skipargs && (ch = getopt(argc, argv, - "d:f:l:P:p:Q:u:cehR")) != -1) { + "d:f:l:P:p:Q:u:cehRV:")) != -1) { switch (ch) { case 'Q': if (strcasecmp(optarg, "requests") != 0) { @@ -1906,6 +1913,12 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw) fatal("Invalid umask \"%s\"", optarg); (void)umask((mode_t)mask); break; + case 'V': + sftp_version = strtol(optarg, &cp, 10); + /* Assume min version is 0 to avoid compiler warning */ + if (sftp_version > SSH2_FILEXFER_VERSION_MAX) + fatal("Unsupported SFTP version %i", sftp_version); + break; case 'h': default: sftp_server_usage(); diff --git a/sftp.c b/sftp.c index 04a1cf72d45f..725ce2872b4a 100644 --- a/sftp.c +++ b/sftp.c @@ -70,6 +70,9 @@ typedef void EditLine; #include "sftp-common.h" #include "sftp-client.h" +/* The max SFTP version we support */ +#define SSH2_FILEXFER_VERSION_MAX 3 + /* File to read commands from */ FILE* infile; @@ -2395,7 +2398,7 @@ main(int argc, char **argv) infile = stdin; while ((ch = getopt(argc, argv, - "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:")) != -1) { + "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:V:")) != -1) { switch (ch) { /* Passed through to ssh(1) */ case 'A': @@ -2492,6 +2495,12 @@ main(int argc, char **argv) ssh_program = optarg; replacearg(&args, 0, "%s", ssh_program); break; + case 'V': + sftp_version = strtol(optarg, &cp, 10); + /* Assume min version is 0 to avoid compiler warning */ + if (sftp_version > SSH2_FILEXFER_VERSION_MAX) + fatal("Unsupported SFTP version %i", sftp_version); + break; case 'h': default: usage(); diff --git a/sftp.h b/sftp.h index 2bde8bb7ff0a..4c22bcc81bb6 100644 --- a/sftp.h +++ b/sftp.h @@ -28,9 +28,6 @@ * draft-ietf-secsh-filexfer-01.txt */ -/* version */ -#define SSH2_FILEXFER_VERSION 3 - /* client to server */ #define SSH2_FXP_INIT 1 #define SSH2_FXP_OPEN 3 -- 2.33.0
Mike Frysinger
2021-Sep-30 20:49 UTC
[PATCH 2/4] add SFTPv4 constants & handle error & file mode strings
Just register the new SFTPv4 constants, and fill out the utility funcs that translate some of them to strings. No code actually uses SFTPv4. --- sftp-server.c | 23 +++++++++++++++++++++-- sftp.h | 27 ++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/sftp-server.c b/sftp-server.c index 859e93f31111..bef7b6c18aa7 100644 --- a/sftp-server.c +++ b/sftp-server.c @@ -222,11 +222,15 @@ errno_to_portable(int unixerrno) case 0: ret = SSH2_FX_OK; break; + case EBADF: + ret = sftp_version <= 3 ? SSH2_FX_NO_SUCH_FILE : + SSH2_FX_INVALID_HANDLE; + break; case ENOENT: case ENOTDIR: - case EBADF: case ELOOP: - ret = SSH2_FX_NO_SUCH_FILE; + ret = sftp_version <= 3 ? SSH2_FX_NO_SUCH_PATH : + SSH2_FX_NO_SUCH_FILE; break; case EPERM: case EACCES: @@ -240,6 +244,14 @@ errno_to_portable(int unixerrno) case ENOSYS: ret = SSH2_FX_OP_UNSUPPORTED; break; + case EEXIST: + ret = sftp_version <= 3 ? SSH2_FX_FAILURE : + SSH2_FX_FILE_ALREADY_EXISTS; + break; + case EROFS: + ret = sftp_version <= 3 ? SSH2_FX_FAILURE : + SSH2_FX_WRITE_PROTECT; + break; default: ret = SSH2_FX_FAILURE; break; @@ -296,6 +308,8 @@ string_from_portable(int pflags) PAPPEND("TRUNCATE") if (pflags & SSH2_FXF_EXCL) PAPPEND("EXCL") + if (sftp_version >= 4 && pflags & SSH2_FXF_TEXT) + PAPPEND("TEXT") return ret; } @@ -536,6 +550,11 @@ status_to_message(u_int32_t status) "No connection", /* SSH_FX_NO_CONNECTION */ "Connection lost", /* SSH_FX_CONNECTION_LOST */ "Operation unsupported", /* SSH_FX_OP_UNSUPPORTED */ + "Invalid handle", /* SSH_FX_INVALID_HANDLE */ + "No such path", /* SSH_FX_NO_SUCH_PATH */ + "File already exists", /* SSH_FX_FILE_ALREADY_EXISTS */ + "Write protected", /* SSH_FX_WRITE_PROTECT */ + "No media", /* SSH_FX_NO_MEDIA */ "Unknown error" /* Others */ }; return (status_messages[MINIMUM(status,SSH2_FX_MAX)]); diff --git a/sftp.h b/sftp.h index 4c22bcc81bb6..2489a212d36d 100644 --- a/sftp.h +++ b/sftp.h @@ -25,7 +25,7 @@ */ /* - * draft-ietf-secsh-filexfer-01.txt + * draft-ietf-secsh-filexfer-02.txt & draft-ietf-secsh-filexfer-04.txt */ /* client to server */ @@ -61,12 +61,26 @@ #define SSH2_FXP_EXTENDED 200 #define SSH2_FXP_EXTENDED_REPLY 201 -/* attributes */ +/* SFTPv3 attributes */ #define SSH2_FILEXFER_ATTR_SIZE 0x00000001 #define SSH2_FILEXFER_ATTR_UIDGID 0x00000002 #define SSH2_FILEXFER_ATTR_PERMISSIONS 0x00000004 #define SSH2_FILEXFER_ATTR_ACMODTIME 0x00000008 #define SSH2_FILEXFER_ATTR_EXTENDED 0x80000000 +/* SFTPv4 attributes */ +#define SSH2_FILEXFER_ATTR_ACCESSTIME 0x00000008 +#define SSH2_FILEXFER_ATTR_CREATETIME 0x00000010 +#define SSH2_FILEXFER_ATTR_MODIFYTIME 0x00000020 +#define SSH2_FILEXFER_ATTR_ACL 0x00000040 +#define SSH2_FILEXFER_ATTR_OWNERGROUP 0x00000080 +#define SSH2_FILEXFER_ATTR_SUBSECOND_TIMES 0x00000100 + +/* SFTPv4 types */ +#define SSH2_FILEXFER_TYPE_REGULAR 1 +#define SSH2_FILEXFER_TYPE_DIRECTORY 2 +#define SSH2_FILEXFER_TYPE_SYMLINK 3 +#define SSH2_FILEXFER_TYPE_SPECIAL 4 +#define SSH2_FILEXFER_TYPE_UNKNOWN 5 /* portable open modes */ #define SSH2_FXF_READ 0x00000001 @@ -75,6 +89,7 @@ #define SSH2_FXF_CREAT 0x00000008 #define SSH2_FXF_TRUNC 0x00000010 #define SSH2_FXF_EXCL 0x00000020 +#define SSH2_FXF_TEXT 0x00000040 /* statvfs at openssh.com f_flag flags */ #define SSH2_FXE_STATVFS_ST_RDONLY 0x00000001 @@ -90,7 +105,13 @@ #define SSH2_FX_NO_CONNECTION 6 #define SSH2_FX_CONNECTION_LOST 7 #define SSH2_FX_OP_UNSUPPORTED 8 -#define SSH2_FX_MAX 8 +/* SFTPv4 errors */ +#define SSH2_FX_INVALID_HANDLE 9 +#define SSH2_FX_NO_SUCH_PATH 10 +#define SSH2_FX_FILE_ALREADY_EXISTS 11 +#define SSH2_FX_WRITE_PROTECT 12 +#define SSH2_FX_NO_MEDIA 13 +#define SSH2_FX_MAX 13 struct passwd; -- 2.33.0
Mike Frysinger
2021-Sep-30 20:49 UTC
[PATCH 3/4] sftp-server: initial experimental SFTPv4 support
This updates the server 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. Here's a summary of protocol differences from v3 to v4 and how they're handled. * symlink argument order OpenSSH has a long standing known incompatibility with the SFTPv3 spec where it accidentally swapped symlink's path arguments. We can take the opportunity to fix that for SFTPv4+. * 64-bit time & nanoseconds The main driver here is the expanded stat structure: we get 64-bit time (important for y2k38 compatibility), and we get nanosecond resolution. SFTPv4 also includes support for creation time (not to be confused with last status change time aka "ctime"), but we don't currently provide it. OS support for accessing the field is still pretty new and has not been standardized (e.g. Linux has statx() vs st_birthtime in *BSD). This could be added in the future, but let's just skip it for now. * User & group names We also get account names (user & group) instead of numeric ids which helps with system portability. Numeric ids still work, they just get serialized into a string first (for client->server requests). * UTF-8 paths The specification clarifies that paths are to be treated as UTF-8. We don't make any changes to the server side here -- paths are just bytes. As long as the server filesystems are all UTF-8 encoded, everything will work fine. If the client attempts to use non-UTF-8 encoded paths, that is an error on the client side, and the server is allowed to do whatever it wants (we just opt for SFTPv3 behavior). The specification also talks about the client having to accept normalized forms (NFC) that the server may provide, but it does not require that the server always use NFC. So from a protocol point-of-view, we don't have to do anything server-side, and it's left to the person running the server to be compliant. * Text mode Attempts to use the new "text" mode when opening files is not supported. This is not trivial to implement, and doesn't seem like it's worth the effort to support. We'll return an explicit "not supported" error, and clients will have to continue treating everything as binary (like SFTPv3 and older do). Along those lines, the "newline" extension is not implemented. Since it isn't advertised, clients shouldn't try to use it. * ACLs The other big concern people have is for the new ACL format. This field in the file attributes is simply ignored. It's an opaque string, so it's trivial to consume & skip. We don't have to define or process any of the fields it provides. The specification is unclear where the new structure and its fields even show up. The server will never emit ACL information in the file attributes. If a client includes ACL info while trying to set file attributes, or open a file, the server will warn & ignore rather than abort. --- PROTOCOL | 6 +- sftp-common.c | 178 +++++++++++++++++++++++++++++++++++++++++--------- sftp-common.h | 12 +++- sftp-server.c | 160 +++++++++++++++++++++++++++++++++++---------- 4 files changed, 286 insertions(+), 70 deletions(-) diff --git a/PROTOCOL b/PROTOCOL index 42c040d65a04..9a66f25a9d41 100644 --- a/PROTOCOL +++ b/PROTOCOL @@ -344,19 +344,21 @@ BSD-derived systems. 3. SFTP protocol changes -3.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK +3.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK in SFTPv3 When OpenSSH's sftp-server was implemented, the order of the arguments to the SSH_FXP_SYMLINK method was inadvertently reversed. Unfortunately, the reversal was not noticed until the server was widely deployed. Since fixing this to follow the specification would cause incompatibility, the current order was retained. For correct operation, clients should send -SSH_FXP_SYMLINK as follows: +SSH_FXP_SYMLINK as follows when using SFTPv3 or older: uint32 id string targetpath string linkpath +This has been fixed for SFTPv4 and newer. + 3.2. sftp: Server extension announcement in SSH_FXP_VERSION OpenSSH's sftp-server lists the extensions it supports using the diff --git a/sftp-common.c b/sftp-common.c index 7681206728a7..b81c12b90e2d 100644 --- a/sftp-common.c +++ b/sftp-common.c @@ -57,13 +57,7 @@ unsigned int sftp_version = 3; void attrib_clear(Attrib *a) { - a->flags = 0; - a->size = 0; - a->uid = 0; - a->gid = 0; - a->perm = 0; - a->atime = 0; - a->mtime = 0; + memset(a, 0, sizeof(*a)); } /* Convert from struct stat to filexfer attribs */ @@ -74,14 +68,39 @@ stat_to_attrib(const struct stat *st, Attrib *a) a->flags = 0; a->flags |= SSH2_FILEXFER_ATTR_SIZE; a->size = st->st_size; - a->flags |= SSH2_FILEXFER_ATTR_UIDGID; - a->uid = st->st_uid; - a->gid = st->st_gid; a->flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; a->perm = st->st_mode; - a->flags |= SSH2_FILEXFER_ATTR_ACMODTIME; - a->atime = st->st_atime; - a->mtime = st->st_mtime; + + switch (sftp_version) { + case 0 ... 3: + a->flags |= SSH2_FILEXFER_ATTR_UIDGID; + a->uid = st->st_uid; + a->gid = st->st_gid; + a->flags |= SSH2_FILEXFER_ATTR_ACMODTIME; + a->atime = st->st_atime; + a->mtime = st->st_mtime; + break; + case 4 ... 6: + if (S_ISREG(st->st_mode)) + a->type = SSH2_FILEXFER_TYPE_REGULAR; + else if (S_ISDIR(st->st_mode)) + a->type = SSH2_FILEXFER_TYPE_DIRECTORY; + else if (S_ISLNK(st->st_mode)) + a->type = SSH2_FILEXFER_TYPE_SYMLINK; + else + a->type = SSH2_FILEXFER_TYPE_UNKNOWN; + a->flags |= SSH2_FILEXFER_ATTR_OWNERGROUP; + a->owner = user_from_uid(st->st_uid, 0); + a->group = group_from_gid(st->st_gid, 0); + a->flags |= SSH2_FILEXFER_ATTR_ACCESSTIME; + a->atime = st->st_atime; + a->flags |= SSH2_FILEXFER_ATTR_MODIFYTIME; + a->mtime = st->st_mtime; + a->flags |= SSH2_FILEXFER_ATTR_SUBSECOND_TIMES; + a->atime_nsec = st->st_atim.tv_nsec; + a->mtime_nsec = st->st_mtim.tv_nsec; + break; + } } /* Convert from filexfer attribs to struct stat */ @@ -92,15 +111,32 @@ attrib_to_stat(const Attrib *a, struct stat *st) if (a->flags & SSH2_FILEXFER_ATTR_SIZE) st->st_size = a->size; - if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { - st->st_uid = a->uid; - st->st_gid = a->gid; - } if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) st->st_mode = a->perm; - if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { - st->st_atime = a->atime; - st->st_mtime = a->mtime; + switch (sftp_version) { + case 0 ... 3: + if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { + st->st_uid = a->uid; + st->st_gid = a->gid; + } + if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { + st->st_atime = a->atime; + st->st_mtime = a->mtime; + } + break; + case 4 ... 6: + /* NB: Owner & group handling omitted for now. */ + if (a->flags & SSH2_FILEXFER_ATTR_ACCESSTIME) { + st->st_atime = a->atime; + if (a->flags & SSH2_FILEXFER_ATTR_SUBSECOND_TIMES) + st->st_atim.tv_nsec = a->atime_nsec; + } + if (a->flags & SSH2_FILEXFER_ATTR_MODIFYTIME) { + st->st_mtime = a->mtime; + if (a->flags & SSH2_FILEXFER_ATTR_SUBSECOND_TIMES) + st->st_mtim.tv_nsec = a->mtime_nsec; + } + break; } } @@ -113,23 +149,66 @@ decode_attrib(struct sshbuf *b, Attrib *a) attrib_clear(a); if ((r = sshbuf_get_u32(b, &a->flags)) != 0) return r; + if (sftp_version >= 4) { + if ((r = sshbuf_get_u8(b, &a->type)) != 0) + return r; + } if (a->flags & SSH2_FILEXFER_ATTR_SIZE) { if ((r = sshbuf_get_u64(b, &a->size)) != 0) return r; } - if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { + if (sftp_version <= 3 && a->flags & SSH2_FILEXFER_ATTR_UIDGID) { if ((r = sshbuf_get_u32(b, &a->uid)) != 0 || (r = sshbuf_get_u32(b, &a->gid)) != 0) return r; } + if (sftp_version >= 4 && a->flags & SSH2_FILEXFER_ATTR_OWNERGROUP) { + if ((r = sshbuf_get_cstring(b, &a->owner, NULL)) != 0 || + (r = sshbuf_get_cstring(b, &a->group, NULL)) != 0) + return r; + } if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { if ((r = sshbuf_get_u32(b, &a->perm)) != 0) return r; } - if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { - if ((r = sshbuf_get_u32(b, &a->atime)) != 0 || - (r = sshbuf_get_u32(b, &a->mtime)) != 0) - return r; + if (sftp_version <= 3) { + if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { + u_int32_t atime, mtime; + if ((r = sshbuf_get_u32(b, &atime)) != 0 || + (r = sshbuf_get_u32(b, &mtime)) != 0) + return r; + a->atime = atime; + a->mtime = mtime; + } + } else if (sftp_version >= 4) { + if (a->flags & SSH2_FILEXFER_ATTR_ACCESSTIME) { + if ((r = sshbuf_get_u64(b, &a->atime)) != 0) + return r; + if (a->flags & SSH2_FILEXFER_ATTR_SUBSECOND_TIMES && + (r = sshbuf_get_u32(b, &a->atime_nsec)) != 0) + return r; + } + if (a->flags & SSH2_FILEXFER_ATTR_CREATETIME) { + if ((r = sshbuf_get_u64(b, &a->createtime)) != 0) + return r; + if (a->flags & SSH2_FILEXFER_ATTR_SUBSECOND_TIMES && + (r = sshbuf_get_u32(b, &a->createtime_nsec)) != 0) + return r; + } + if (a->flags & SSH2_FILEXFER_ATTR_MODIFYTIME) { + if ((r = sshbuf_get_u64(b, &a->mtime)) != 0) + return r; + if (a->flags & SSH2_FILEXFER_ATTR_SUBSECOND_TIMES && + (r = sshbuf_get_u32(b, &a->mtime_nsec)) != 0) + return r; + } + if (a->flags & SSH2_FILEXFER_ATTR_ACL) { + char *acl; + debug("Ignoring ACL attributes"); + if ((r = sshbuf_get_cstring(b, &acl, NULL)) != 0) + return r; + free(acl); + } } /* vendor-specific extensions */ if (a->flags & SSH2_FILEXFER_ATTR_EXTENDED) { @@ -161,23 +240,62 @@ encode_attrib(struct sshbuf *b, const Attrib *a) if ((r = sshbuf_put_u32(b, a->flags)) != 0) return r; + if (sftp_version >= 4) { + if ((r = sshbuf_put_u8(b, a->type)) != 0) + return r; + } if (a->flags & SSH2_FILEXFER_ATTR_SIZE) { if ((r = sshbuf_put_u64(b, a->size)) != 0) return r; } - if (a->flags & SSH2_FILEXFER_ATTR_UIDGID) { + if (sftp_version <= 3 && a->flags & SSH2_FILEXFER_ATTR_UIDGID) { if ((r = sshbuf_put_u32(b, a->uid)) != 0 || (r = sshbuf_put_u32(b, a->gid)) != 0) return r; } + if (sftp_version >= 4 && a->flags & SSH2_FILEXFER_ATTR_OWNERGROUP) { + if ((r = sshbuf_put_cstring(b, a->owner)) != 0 || + (r = sshbuf_put_cstring(b, a->group)) != 0) + return r; + } if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { if ((r = sshbuf_put_u32(b, a->perm)) != 0) return r; } - if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { - if ((r = sshbuf_put_u32(b, a->atime)) != 0 || - (r = sshbuf_put_u32(b, a->mtime)) != 0) - return r; + switch (sftp_version) { + case 0 ... 3: + if (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { + if ((r = sshbuf_put_u32(b, a->atime)) != 0 || + (r = sshbuf_put_u32(b, a->mtime)) != 0) + return r; + } + break; + case 4 ... 6: + if (a->flags & SSH2_FILEXFER_ATTR_ACCESSTIME) { + if ((r = sshbuf_put_u64(b, a->atime)) != 0) + return r; + if (a->flags & SSH2_FILEXFER_ATTR_SUBSECOND_TIMES && + (r = sshbuf_put_u32(b, a->atime_nsec)) != 0) + return r; + } + if (a->flags & SSH2_FILEXFER_ATTR_CREATETIME) { + if ((r = sshbuf_put_u64(b, a->atime)) != 0) + return r; + if (a->flags & SSH2_FILEXFER_ATTR_SUBSECOND_TIMES && + (r = sshbuf_put_u32(b, a->atime_nsec)) != 0) + return r; + } + if (a->flags & SSH2_FILEXFER_ATTR_MODIFYTIME) { + if ((r = sshbuf_put_u64(b, a->atime)) != 0) + return r; + if (a->flags & SSH2_FILEXFER_ATTR_SUBSECOND_TIMES && + (r = sshbuf_put_u32(b, a->atime_nsec)) != 0) + return r; + } + if (a->flags & SSH2_FILEXFER_ATTR_ACL) { + debug("Ignoring ACL attributes"); + } + break; } return 0; } diff --git a/sftp-common.h b/sftp-common.h index 5fc8b45da39a..0dc37dc364a9 100644 --- a/sftp-common.h +++ b/sftp-common.h @@ -36,12 +36,20 @@ extern unsigned int sftp_version; /* File attributes */ struct Attrib { u_int32_t flags; + u_int8_t type; u_int64_t size; u_int32_t uid; u_int32_t gid; + /* NB: These are not allocated and thus never freed. */ + char *owner; + char *group; u_int32_t perm; - u_int32_t atime; - u_int32_t mtime; + int64_t atime; + u_int32_t atime_nsec; + int64_t createtime; + u_int32_t createtime_nsec; + int64_t mtime; + u_int32_t mtime_nsec; }; void attrib_clear(Attrib *); diff --git a/sftp-server.c b/sftp-server.c index bef7b6c18aa7..cb5577ec5d7d 100644 --- a/sftp-server.c +++ b/sftp-server.c @@ -33,6 +33,7 @@ #include <dirent.h> #include <errno.h> #include <fcntl.h> +#include <grp.h> #include <pwd.h> #include <stdlib.h> #include <stdio.h> @@ -54,7 +55,7 @@ #include "sftp-common.h" /* The max SFTP version we support */ -#define SSH2_FILEXFER_VERSION_MAX 3 +#define SSH2_FILEXFER_VERSION_MAX 4 char *sftp_realpath(const char *, char *); /* sftp-realpath.c */ @@ -314,6 +315,27 @@ string_from_portable(int pflags) return ret; } +/* Convert string account names to local system ids. */ +static void +portable_accts_to_id(const char *owner, const char *group, uid_t *uid, + gid_t *gid) +{ + struct passwd *owner_pw; + struct group *group_grp; + + owner_pw = getpwnam(owner); + if (owner_pw) + *uid = owner_pw->pw_uid; + else + *uid = strtoul(owner, NULL, 10); + + group_grp = getgrnam(group); + if (group_grp) + *gid = group_grp->gr_gid; + else + *gid = strtoul(group, NULL, 10); +} + /* handle handles */ typedef struct Handle Handle; @@ -634,10 +656,13 @@ send_names(u_int32_t id, int count, const Stat *stats) fatal_fr(r, "compose"); debug("request %u: sent names count %d", id, count); for (i = 0; i < count; i++) { - if ((r = sshbuf_put_cstring(msg, stats[i].name)) != 0 || - (r = sshbuf_put_cstring(msg, stats[i].long_name)) != 0 || - (r = encode_attrib(msg, &stats[i].attrib)) != 0) - fatal_fr(r, "compose filenames/attrib"); + if ((r = sshbuf_put_cstring(msg, stats[i].name)) != 0) + fatal_fr(r, "compose filename"); + if (sftp_version <= 3 && + (r = sshbuf_put_cstring(msg, stats[i].long_name)) != 0) + fatal_fr(r, "compose longname"); + if ((r = encode_attrib(msg, &stats[i].attrib)) != 0) + fatal_fr(r, "compose attrs"); } send_msg(msg); sshbuf_free(msg); @@ -763,6 +788,12 @@ process_open(u_int32_t id) fatal_fr(r, "parse"); debug3("request %u: open flags %d", id, pflags); + if (sftp_version >= 4 && a.flags & SSH2_FXF_TEXT) { + verbose("Text mode not supported"); + status = SSH2_FX_OP_UNSUPPORTED; + goto out; + } + flags = flags_from_portable(pflags); mode = (a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) ? a.perm : 0666; logit("open \"%s\" flags %s mode 0%o", @@ -786,6 +817,8 @@ process_open(u_int32_t id) } } } + + out: if (status != SSH2_FX_OK) send_status(id, status); free(name); @@ -976,9 +1009,9 @@ attrib_to_tv(const Attrib *a) static struct timeval tv[2]; tv[0].tv_sec = a->atime; - tv[0].tv_usec = 0; + tv[0].tv_usec = a->atime_nsec / 1000; tv[1].tv_sec = a->mtime; - tv[1].tv_usec = 0; + tv[1].tv_usec = a->mtime_nsec / 1000; return tv; } @@ -988,9 +1021,9 @@ attrib_to_ts(const Attrib *a) static struct timespec ts[2]; ts[0].tv_sec = a->atime; - ts[0].tv_nsec = 0; + ts[0].tv_nsec = a->atime_nsec; ts[1].tv_sec = a->mtime; - ts[1].tv_nsec = 0; + ts[1].tv_nsec = a->mtime_nsec; return ts; } @@ -1019,7 +1052,9 @@ process_setstat(u_int32_t id) if (r == -1) status = errno_to_portable(errno); } - if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) { + + if ((sftp_version <= 3 && a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) || + (sftp_version >= 4 && a.flags & SSH2_FILEXFER_ATTR_ACCESSTIME)) { char buf[64]; time_t t = a.mtime; @@ -1030,13 +1065,25 @@ process_setstat(u_int32_t id) if (r == -1) status = errno_to_portable(errno); } - if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) { - logit("set \"%s\" owner %lu group %lu", name, - (u_long)a.uid, (u_long)a.gid); - r = chown(name, a.uid, a.gid); - if (r == -1) - status = errno_to_portable(errno); + + if (sftp_version <= 3) { + if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) { + attr_uidgid: + logit("set \"%s\" owner %lu group %lu", name, + (u_long)a.uid, (u_long)a.gid); + r = chown(name, a.uid, a.gid); + if (r == -1) + status = errno_to_portable(errno); + } + } else { + if (a.flags & SSH2_FILEXFER_ATTR_OWNERGROUP) { + logit("set \"%s\" owner %s group %s", name, + a.owner, a.group); + portable_accts_to_id(a.owner, a.group, &a.uid, &a.gid); + goto attr_uidgid; + } } + send_status(id, status); free(name); } @@ -1076,7 +1123,10 @@ process_fsetstat(u_int32_t id) if (r == -1) status = errno_to_portable(errno); } - if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) { + if ((sftp_version <= 3 && + a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) || + (sftp_version >= 4 && + a.flags & SSH2_FILEXFER_ATTR_ACCESSTIME)) { char buf[64]; time_t t = a.mtime; @@ -1091,16 +1141,27 @@ process_fsetstat(u_int32_t id) if (r == -1) status = errno_to_portable(errno); } - if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) { - logit("set \"%s\" owner %lu group %lu", name, - (u_long)a.uid, (u_long)a.gid); + if (sftp_version <= 3) { + if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) { + attr_uidgid: + logit("set \"%s\" owner %lu group %lu", name, + (u_long)a.uid, (u_long)a.gid); #ifdef HAVE_FCHOWN - r = fchown(fd, a.uid, a.gid); + r = fchown(fd, a.uid, a.gid); #else - r = chown(name, a.uid, a.gid); + r = chown(name, a.uid, a.gid); #endif - if (r == -1) - status = errno_to_portable(errno); + if (r == -1) + status = errno_to_portable(errno); + } + } else { + if (a.flags & SSH2_FILEXFER_ATTR_OWNERGROUP) { + logit("set \"%s\" owner %s group %s", name, + a.owner, a.group); + portable_accts_to_id(a.owner, a.group, &a.uid, + &a.gid); + goto attr_uidgid; + } } } send_status(id, status); @@ -1172,7 +1233,11 @@ process_readdir(u_int32_t id) continue; stat_to_attrib(&st, &(stats[count].attrib)); stats[count].name = xstrdup(dp->d_name); - stats[count].long_name = ls_file(dp->d_name, &st, 0, 0); + if (sftp_version <= 3) + stats[count].long_name = ls_file(dp->d_name, + &st, 0, 0); + else + stats[count].long_name = NULL; count++; /* send up to 100 entries in one message */ /* XXX check packet size instead */ @@ -1365,9 +1430,19 @@ process_symlink(u_int32_t id) char *oldpath, *newpath; int r, status; - if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 || - (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0) - fatal_fr(r, "parse"); + /* + * For SFTPv3 and older, the arguments were implemented in the incorrect + * order. It's fixed for SFTPv4+. See PROTOCOL for more info. + */ + if (sftp_version <= 3) { + if ((r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0 || + (r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0) + fatal_fr(r, "parse"); + } else { + if ((r = sshbuf_get_cstring(iqueue, &newpath, NULL)) != 0 || + (r = sshbuf_get_cstring(iqueue, &oldpath, NULL)) != 0) + fatal_fr(r, "parse"); + } debug3("request %u: symlink", id); logit("symlink old \"%s\" new \"%s\"", oldpath, newpath); @@ -1498,7 +1573,9 @@ process_extended_lsetstat(u_int32_t id) if (r == -1) status = errno_to_portable(errno); } - if (a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) { + + if ((sftp_version <= 3 && a.flags & SSH2_FILEXFER_ATTR_ACMODTIME) || + (sftp_version >= 4 && a.flags & SSH2_FILEXFER_ATTR_ACCESSTIME)) { char buf[64]; time_t t = a.mtime; @@ -1510,13 +1587,24 @@ process_extended_lsetstat(u_int32_t id) if (r == -1) status = errno_to_portable(errno); } - if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) { - logit("set \"%s\" owner %lu group %lu", name, - (u_long)a.uid, (u_long)a.gid); - r = fchownat(AT_FDCWD, name, a.uid, a.gid, - AT_SYMLINK_NOFOLLOW); - if (r == -1) - status = errno_to_portable(errno); + + if (sftp_version <= 3) { + if (a.flags & SSH2_FILEXFER_ATTR_UIDGID) { + attr_uidgid: + logit("set \"%s\" owner %lu group %lu", name, + (u_long)a.uid, (u_long)a.gid); + r = fchownat(AT_FDCWD, name, a.uid, a.gid, + AT_SYMLINK_NOFOLLOW); + if (r == -1) + status = errno_to_portable(errno); + } + } else { + if (a.flags & SSH2_FILEXFER_ATTR_OWNERGROUP) { + logit("set \"%s\" owner %s group %s", name, + a.owner, a.group); + portable_accts_to_id(a.owner, a.group, &a.uid, &a.gid); + goto attr_uidgid; + } } out: send_status(id, status); -- 2.33.0
Mike Frysinger
2021-Sep-30 20:49 UTC
[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
Not much changed, just rebased. Still waiting to hear back on the proposal in general. Mike Frysinger (4): scp/sftp: add -V for setting SFTP protocol version add SFTPv4 constants & handle error & file mode strings sftp-server: initial experimental SFTPv4 support [incomplete] sftp-client: initial experimental SFTPv4 support PROTOCOL | 6 +- scp.c | 13 +++- sftp-client.c | 117 +++++++++++++++++++++------- sftp-common.c | 211 ++++++++++++++++++++++++++++++++++++++++---------- sftp-common.h | 17 +++- sftp-server.c | 198 ++++++++++++++++++++++++++++++++++++---------- sftp.c | 83 +++++++++++++------- sftp.h | 30 +++++-- 8 files changed, 524 insertions(+), 151 deletions(-) -- 2.33.0