From 60d8c84e0887514c99c9ce071965fafaa1c3d34a Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Wed, 16 Jan 2019 23:23:45 +0000 Subject: [PATCH] upstream: Add "-h" flag to sftp chown/chgrp/chmod commands to request they do not follow symlinks. Requires recently-committed lsetstat@openssh.com extension on the server side. ok markus@ dtucker@ OpenBSD-Commit-ID: f93bb3f6f7eb2fb7ef1e59126e72714f1626d604 --- sftp-client.c | 42 ++++++++++++++++++++++++++++++++++++++---- sftp-client.h | 5 ++++- sftp.1 | 31 ++++++++++++++++++++++++++----- sftp.c | 43 +++++++++++++++++++++++++++++++++++-------- 4 files changed, 103 insertions(+), 18 deletions(-) diff --git a/sftp-client.c b/sftp-client.c index 4986d6d8d..d3f80e5a0 100644 --- a/sftp-client.c +++ b/sftp-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp-client.c,v 1.130 2018/07/31 03:07:24 djm Exp $ */ +/* $OpenBSD: sftp-client.c,v 1.131 2019/01/16 23:23:45 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller * @@ -86,6 +86,7 @@ struct sftp_conn { #define SFTP_EXT_FSTATVFS 0x00000004 #define SFTP_EXT_HARDLINK 0x00000008 #define SFTP_EXT_FSYNC 0x00000010 +#define SFTP_EXT_LSETSTAT 0x00000020 u_int exts; u_int64_t limit_kbps; struct bwlimit bwlimit_in, bwlimit_out; @@ -463,6 +464,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_FSYNC; known = 1; + } else if (strcmp(name, "lsetstat@openssh.com") == 0 && + strcmp((char *)value, "1") == 0) { + ret->exts |= SFTP_EXT_LSETSTAT; + known = 1; } if (known) { debug2("Server supports extension \"%s\" revision %s", @@ -1096,7 +1101,6 @@ do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st, if ((msg = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); - sshbuf_reset(msg); if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 || @@ -1125,7 +1129,6 @@ do_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len, if ((msg = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); - sshbuf_reset(msg); if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 || @@ -1138,6 +1141,38 @@ do_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len, } #endif +int +do_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a) +{ + struct sshbuf *msg; + u_int status, id; + int r; + + if ((conn->exts & SFTP_EXT_LSETSTAT) == 0) { + error("Server does not support lsetstat@openssh.com extension"); + return -1; + } + + id = conn->msg_id++; + if ((msg = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); + if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || + (r = sshbuf_put_u32(msg, id)) != 0 || + (r = sshbuf_put_cstring(msg, "lsetstat@openssh.com")) != 0 || + (r = sshbuf_put_cstring(msg, path)) != 0 || + (r = encode_attrib(msg, a)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + send_msg(conn, msg); + sshbuf_free(msg); + + status = get_status(conn, id); + if (status != SSH2_FX_OK) + error("Couldn't setstat on \"%s\": %s", path, + fx2txt(status)); + + return status == SSH2_FX_OK ? 0 : -1; +} + static void send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset, u_int len, const u_char *handle, u_int handle_len) @@ -1147,7 +1182,6 @@ send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset, if ((msg = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); - sshbuf_reset(msg); if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 || (r = sshbuf_put_u32(msg, id)) != 0 || (r = sshbuf_put_string(msg, handle, handle_len)) != 0 || diff --git a/sftp-client.h b/sftp-client.h index 14a3b8182..63a9b8b13 100644 --- a/sftp-client.h +++ b/sftp-client.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp-client.h,v 1.27 2015/05/08 06:45:13 djm Exp $ */ +/* $OpenBSD: sftp-client.h,v 1.28 2019/01/16 23:23:45 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller @@ -91,6 +91,9 @@ int do_setstat(struct sftp_conn *, const char *, Attrib *); /* Set file attributes of open file 'handle' */ int do_fsetstat(struct sftp_conn *, const u_char *, u_int, Attrib *); +/* Set file attributes of 'path', not following symlinks */ +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 *); diff --git a/sftp.1 b/sftp.1 index 7140bc19b..722a34419 100644 --- a/sftp.1 +++ b/sftp.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: sftp.1,v 1.122 2018/11/16 02:30:20 djm Exp $ +.\" $OpenBSD: sftp.1,v 1.123 2019/01/16 23:23:45 djm Exp $ .\" .\" Copyright (c) 2001 Damien Miller. All rights reserved. .\" @@ -22,7 +22,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: November 16 2018 $ +.Dd $Mdocdate: January 16 2019 $ .Dt SFTP 1 .Os .Sh NAME @@ -316,31 +316,52 @@ Change remote directory to If .Ar path is not specified, then change directory to the one the session started in. -.It Ic chgrp Ar grp Ar path +.It Xo Ic chgrp +.Op Fl h +.Ar grp +.Ar path +.Xc Change group of file .Ar path to .Ar grp . +If the +.Fl h +flag is specified, then symlinks will not be followed. .Ar path may contain .Xr glob 7 characters and may match multiple files. .Ar grp must be a numeric GID. -.It Ic chmod Ar mode Ar path +.It Xo Ic chmod +.Op Fl h +.Ar mode +.Ar path +.Xc Change permissions of file .Ar path to .Ar mode . +If the +.Fl h +flag is specified, then symlinks will not be followed. .Ar path may contain .Xr glob 7 characters and may match multiple files. -.It Ic chown Ar own Ar path +.It Xo Ic chown +.Op Fl h +.Ar own +.Ar path +.Xc Change owner of file .Ar path to .Ar own . +If the +.Fl h +flag is specified, then symlinks will not be followed. .Ar path may contain .Xr glob 7 diff --git a/sftp.c b/sftp.c index f886b330b..0f3f89d33 100644 --- a/sftp.c +++ b/sftp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp.c,v 1.188 2018/11/16 03:26:01 djm Exp $ */ +/* $OpenBSD: sftp.c,v 1.189 2019/01/16 23:23:45 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller * @@ -278,9 +278,9 @@ help(void) printf("Available commands:\n" "bye Quit sftp\n" "cd path Change remote directory to 'path'\n" - "chgrp grp path Change group of file 'path' to 'grp'\n" - "chmod mode path Change permissions of file 'path' to 'mode'\n" - "chown own path Change owner of file 'path' to 'own'\n" + "chgrp [-h] grp path Change group of file 'path' to 'grp'\n" + "chmod [-h] mode path Change permissions of file 'path' to 'mode'\n" + "chown [-h] own path Change owner of file 'path' to 'own'\n" "df [-hi] [path] Display statistics for current directory or\n" " filesystem containing 'path'\n" "exit Quit sftp\n" @@ -561,6 +561,30 @@ parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag) return optind; } +static int +parse_ch_flags(const char *cmd, char **argv, int argc, int *hflag) +{ + extern int opterr, optind, optopt, optreset; + int ch; + + optind = optreset = 1; + opterr = 0; + + *hflag = 0; + while ((ch = getopt(argc, argv, "h")) != -1) { + switch (ch) { + case 'h': + *hflag = 1; + break; + default: + error("%s: Invalid flag -%c", cmd, optopt); + return -1; + } + } + + return optind; +} + static int parse_no_flags(const char *cmd, char **argv, int argc) { @@ -1456,7 +1480,7 @@ parse_args(const char **cpp, int *ignore_errors, int *disable_echo, int *aflag, /* FALLTHROUGH */ case I_CHOWN: case I_CHGRP: - if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) + if ((optidx = parse_ch_flags(cmd, argv, argc, hflag)) == -1) return -1; /* Get numeric arg (mandatory) */ if (argc - optidx < 1) @@ -1675,7 +1699,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, if (!quiet) mprintf("Changing mode on %s\n", g.gl_pathv[i]); - err = do_setstat(conn, g.gl_pathv[i], &a); + err = (hflag ? do_lsetstat : do_setstat)(conn, + g.gl_pathv[i], &a); if (err != 0 && err_abort) break; } @@ -1685,7 +1710,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, path1 = make_absolute(path1, *pwd); remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); for (i = 0; g.gl_pathv[i] && !interrupted; i++) { - if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) { + if (!(aa = (hflag ? do_lstat : do_stat)(conn, + g.gl_pathv[i], 0))) { if (err_abort) { err = -1; break; @@ -1713,7 +1739,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, g.gl_pathv[i]); aa->gid = n_arg; } - err = do_setstat(conn, g.gl_pathv[i], aa); + err = (hflag ? do_lsetstat : do_setstat)(conn, + g.gl_pathv[i], aa); if (err != 0 && err_abort) break; }