********************************************************************* 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@xxxxxxxxxxx https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev