diff options
author | djm@openbsd.org <djm@openbsd.org> | 2019-01-16 23:23:45 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2019-01-17 11:08:13 +1100 |
commit | 60d8c84e0887514c99c9ce071965fafaa1c3d34a (patch) | |
tree | 771daf5a0f48b41115daf2b9b552e4e6d10dba7d | |
parent | dbbc7e0eab7262f34b8e0cd6efecd1c77b905ed0 (diff) |
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
-rw-r--r-- | sftp-client.c | 42 | ||||
-rw-r--r-- | sftp-client.h | 5 | ||||
-rw-r--r-- | sftp.1 | 31 | ||||
-rw-r--r-- | 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 @@ | |||
1 | /* $OpenBSD: sftp-client.c,v 1.130 2018/07/31 03:07:24 djm Exp $ */ | 1 | /* $OpenBSD: sftp-client.c,v 1.131 2019/01/16 23:23:45 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> | 3 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> |
4 | * | 4 | * |
@@ -86,6 +86,7 @@ struct sftp_conn { | |||
86 | #define SFTP_EXT_FSTATVFS 0x00000004 | 86 | #define SFTP_EXT_FSTATVFS 0x00000004 |
87 | #define SFTP_EXT_HARDLINK 0x00000008 | 87 | #define SFTP_EXT_HARDLINK 0x00000008 |
88 | #define SFTP_EXT_FSYNC 0x00000010 | 88 | #define SFTP_EXT_FSYNC 0x00000010 |
89 | #define SFTP_EXT_LSETSTAT 0x00000020 | ||
89 | u_int exts; | 90 | u_int exts; |
90 | u_int64_t limit_kbps; | 91 | u_int64_t limit_kbps; |
91 | struct bwlimit bwlimit_in, bwlimit_out; | 92 | 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, | |||
463 | strcmp((char *)value, "1") == 0) { | 464 | strcmp((char *)value, "1") == 0) { |
464 | ret->exts |= SFTP_EXT_FSYNC; | 465 | ret->exts |= SFTP_EXT_FSYNC; |
465 | known = 1; | 466 | known = 1; |
467 | } else if (strcmp(name, "lsetstat@openssh.com") == 0 && | ||
468 | strcmp((char *)value, "1") == 0) { | ||
469 | ret->exts |= SFTP_EXT_LSETSTAT; | ||
470 | known = 1; | ||
466 | } | 471 | } |
467 | if (known) { | 472 | if (known) { |
468 | debug2("Server supports extension \"%s\" revision %s", | 473 | debug2("Server supports extension \"%s\" revision %s", |
@@ -1096,7 +1101,6 @@ do_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st, | |||
1096 | 1101 | ||
1097 | if ((msg = sshbuf_new()) == NULL) | 1102 | if ((msg = sshbuf_new()) == NULL) |
1098 | fatal("%s: sshbuf_new failed", __func__); | 1103 | fatal("%s: sshbuf_new failed", __func__); |
1099 | sshbuf_reset(msg); | ||
1100 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || | 1104 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || |
1101 | (r = sshbuf_put_u32(msg, id)) != 0 || | 1105 | (r = sshbuf_put_u32(msg, id)) != 0 || |
1102 | (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 || | 1106 | (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, | |||
1125 | 1129 | ||
1126 | if ((msg = sshbuf_new()) == NULL) | 1130 | if ((msg = sshbuf_new()) == NULL) |
1127 | fatal("%s: sshbuf_new failed", __func__); | 1131 | fatal("%s: sshbuf_new failed", __func__); |
1128 | sshbuf_reset(msg); | ||
1129 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || | 1132 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || |
1130 | (r = sshbuf_put_u32(msg, id)) != 0 || | 1133 | (r = sshbuf_put_u32(msg, id)) != 0 || |
1131 | (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 || | 1134 | (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, | |||
1138 | } | 1141 | } |
1139 | #endif | 1142 | #endif |
1140 | 1143 | ||
1144 | int | ||
1145 | do_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a) | ||
1146 | { | ||
1147 | struct sshbuf *msg; | ||
1148 | u_int status, id; | ||
1149 | int r; | ||
1150 | |||
1151 | if ((conn->exts & SFTP_EXT_LSETSTAT) == 0) { | ||
1152 | error("Server does not support lsetstat@openssh.com extension"); | ||
1153 | return -1; | ||
1154 | } | ||
1155 | |||
1156 | id = conn->msg_id++; | ||
1157 | if ((msg = sshbuf_new()) == NULL) | ||
1158 | fatal("%s: sshbuf_new failed", __func__); | ||
1159 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || | ||
1160 | (r = sshbuf_put_u32(msg, id)) != 0 || | ||
1161 | (r = sshbuf_put_cstring(msg, "lsetstat@openssh.com")) != 0 || | ||
1162 | (r = sshbuf_put_cstring(msg, path)) != 0 || | ||
1163 | (r = encode_attrib(msg, a)) != 0) | ||
1164 | fatal("%s: buffer error: %s", __func__, ssh_err(r)); | ||
1165 | send_msg(conn, msg); | ||
1166 | sshbuf_free(msg); | ||
1167 | |||
1168 | status = get_status(conn, id); | ||
1169 | if (status != SSH2_FX_OK) | ||
1170 | error("Couldn't setstat on \"%s\": %s", path, | ||
1171 | fx2txt(status)); | ||
1172 | |||
1173 | return status == SSH2_FX_OK ? 0 : -1; | ||
1174 | } | ||
1175 | |||
1141 | static void | 1176 | static void |
1142 | send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset, | 1177 | send_read_request(struct sftp_conn *conn, u_int id, u_int64_t offset, |
1143 | u_int len, const u_char *handle, u_int handle_len) | 1178 | 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, | |||
1147 | 1182 | ||
1148 | if ((msg = sshbuf_new()) == NULL) | 1183 | if ((msg = sshbuf_new()) == NULL) |
1149 | fatal("%s: sshbuf_new failed", __func__); | 1184 | fatal("%s: sshbuf_new failed", __func__); |
1150 | sshbuf_reset(msg); | ||
1151 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 || | 1185 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 || |
1152 | (r = sshbuf_put_u32(msg, id)) != 0 || | 1186 | (r = sshbuf_put_u32(msg, id)) != 0 || |
1153 | (r = sshbuf_put_string(msg, handle, handle_len)) != 0 || | 1187 | (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 @@ | |||
1 | /* $OpenBSD: sftp-client.h,v 1.27 2015/05/08 06:45:13 djm Exp $ */ | 1 | /* $OpenBSD: sftp-client.h,v 1.28 2019/01/16 23:23:45 djm Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> | 4 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> |
@@ -91,6 +91,9 @@ int do_setstat(struct sftp_conn *, const char *, Attrib *); | |||
91 | /* Set file attributes of open file 'handle' */ | 91 | /* Set file attributes of open file 'handle' */ |
92 | int do_fsetstat(struct sftp_conn *, const u_char *, u_int, Attrib *); | 92 | int do_fsetstat(struct sftp_conn *, const u_char *, u_int, Attrib *); |
93 | 93 | ||
94 | /* Set file attributes of 'path', not following symlinks */ | ||
95 | int do_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a); | ||
96 | |||
94 | /* Canonicalise 'path' - caller must free result */ | 97 | /* Canonicalise 'path' - caller must free result */ |
95 | char *do_realpath(struct sftp_conn *, const char *); | 98 | char *do_realpath(struct sftp_conn *, const char *); |
96 | 99 | ||
@@ -1,4 +1,4 @@ | |||
1 | .\" $OpenBSD: sftp.1,v 1.122 2018/11/16 02:30:20 djm Exp $ | 1 | .\" $OpenBSD: sftp.1,v 1.123 2019/01/16 23:23:45 djm Exp $ |
2 | .\" | 2 | .\" |
3 | .\" Copyright (c) 2001 Damien Miller. All rights reserved. | 3 | .\" Copyright (c) 2001 Damien Miller. All rights reserved. |
4 | .\" | 4 | .\" |
@@ -22,7 +22,7 @@ | |||
22 | .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 22 | .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
23 | .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 23 | .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | .\" | 24 | .\" |
25 | .Dd $Mdocdate: November 16 2018 $ | 25 | .Dd $Mdocdate: January 16 2019 $ |
26 | .Dt SFTP 1 | 26 | .Dt SFTP 1 |
27 | .Os | 27 | .Os |
28 | .Sh NAME | 28 | .Sh NAME |
@@ -316,31 +316,52 @@ Change remote directory to | |||
316 | If | 316 | If |
317 | .Ar path | 317 | .Ar path |
318 | is not specified, then change directory to the one the session started in. | 318 | is not specified, then change directory to the one the session started in. |
319 | .It Ic chgrp Ar grp Ar path | 319 | .It Xo Ic chgrp |
320 | .Op Fl h | ||
321 | .Ar grp | ||
322 | .Ar path | ||
323 | .Xc | ||
320 | Change group of file | 324 | Change group of file |
321 | .Ar path | 325 | .Ar path |
322 | to | 326 | to |
323 | .Ar grp . | 327 | .Ar grp . |
328 | If the | ||
329 | .Fl h | ||
330 | flag is specified, then symlinks will not be followed. | ||
324 | .Ar path | 331 | .Ar path |
325 | may contain | 332 | may contain |
326 | .Xr glob 7 | 333 | .Xr glob 7 |
327 | characters and may match multiple files. | 334 | characters and may match multiple files. |
328 | .Ar grp | 335 | .Ar grp |
329 | must be a numeric GID. | 336 | must be a numeric GID. |
330 | .It Ic chmod Ar mode Ar path | 337 | .It Xo Ic chmod |
338 | .Op Fl h | ||
339 | .Ar mode | ||
340 | .Ar path | ||
341 | .Xc | ||
331 | Change permissions of file | 342 | Change permissions of file |
332 | .Ar path | 343 | .Ar path |
333 | to | 344 | to |
334 | .Ar mode . | 345 | .Ar mode . |
346 | If the | ||
347 | .Fl h | ||
348 | flag is specified, then symlinks will not be followed. | ||
335 | .Ar path | 349 | .Ar path |
336 | may contain | 350 | may contain |
337 | .Xr glob 7 | 351 | .Xr glob 7 |
338 | characters and may match multiple files. | 352 | characters and may match multiple files. |
339 | .It Ic chown Ar own Ar path | 353 | .It Xo Ic chown |
354 | .Op Fl h | ||
355 | .Ar own | ||
356 | .Ar path | ||
357 | .Xc | ||
340 | Change owner of file | 358 | Change owner of file |
341 | .Ar path | 359 | .Ar path |
342 | to | 360 | to |
343 | .Ar own . | 361 | .Ar own . |
362 | If the | ||
363 | .Fl h | ||
364 | flag is specified, then symlinks will not be followed. | ||
344 | .Ar path | 365 | .Ar path |
345 | may contain | 366 | may contain |
346 | .Xr glob 7 | 367 | .Xr glob 7 |
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: sftp.c,v 1.188 2018/11/16 03:26:01 djm Exp $ */ | 1 | /* $OpenBSD: sftp.c,v 1.189 2019/01/16 23:23:45 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> | 3 | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> |
4 | * | 4 | * |
@@ -278,9 +278,9 @@ help(void) | |||
278 | printf("Available commands:\n" | 278 | printf("Available commands:\n" |
279 | "bye Quit sftp\n" | 279 | "bye Quit sftp\n" |
280 | "cd path Change remote directory to 'path'\n" | 280 | "cd path Change remote directory to 'path'\n" |
281 | "chgrp grp path Change group of file 'path' to 'grp'\n" | 281 | "chgrp [-h] grp path Change group of file 'path' to 'grp'\n" |
282 | "chmod mode path Change permissions of file 'path' to 'mode'\n" | 282 | "chmod [-h] mode path Change permissions of file 'path' to 'mode'\n" |
283 | "chown own path Change owner of file 'path' to 'own'\n" | 283 | "chown [-h] own path Change owner of file 'path' to 'own'\n" |
284 | "df [-hi] [path] Display statistics for current directory or\n" | 284 | "df [-hi] [path] Display statistics for current directory or\n" |
285 | " filesystem containing 'path'\n" | 285 | " filesystem containing 'path'\n" |
286 | "exit Quit sftp\n" | 286 | "exit Quit sftp\n" |
@@ -562,6 +562,30 @@ parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag) | |||
562 | } | 562 | } |
563 | 563 | ||
564 | static int | 564 | static int |
565 | parse_ch_flags(const char *cmd, char **argv, int argc, int *hflag) | ||
566 | { | ||
567 | extern int opterr, optind, optopt, optreset; | ||
568 | int ch; | ||
569 | |||
570 | optind = optreset = 1; | ||
571 | opterr = 0; | ||
572 | |||
573 | *hflag = 0; | ||
574 | while ((ch = getopt(argc, argv, "h")) != -1) { | ||
575 | switch (ch) { | ||
576 | case 'h': | ||
577 | *hflag = 1; | ||
578 | break; | ||
579 | default: | ||
580 | error("%s: Invalid flag -%c", cmd, optopt); | ||
581 | return -1; | ||
582 | } | ||
583 | } | ||
584 | |||
585 | return optind; | ||
586 | } | ||
587 | |||
588 | static int | ||
565 | parse_no_flags(const char *cmd, char **argv, int argc) | 589 | parse_no_flags(const char *cmd, char **argv, int argc) |
566 | { | 590 | { |
567 | extern int opterr, optind, optopt, optreset; | 591 | extern int opterr, optind, optopt, optreset; |
@@ -1456,7 +1480,7 @@ parse_args(const char **cpp, int *ignore_errors, int *disable_echo, int *aflag, | |||
1456 | /* FALLTHROUGH */ | 1480 | /* FALLTHROUGH */ |
1457 | case I_CHOWN: | 1481 | case I_CHOWN: |
1458 | case I_CHGRP: | 1482 | case I_CHGRP: |
1459 | if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) | 1483 | if ((optidx = parse_ch_flags(cmd, argv, argc, hflag)) == -1) |
1460 | return -1; | 1484 | return -1; |
1461 | /* Get numeric arg (mandatory) */ | 1485 | /* Get numeric arg (mandatory) */ |
1462 | if (argc - optidx < 1) | 1486 | if (argc - optidx < 1) |
@@ -1675,7 +1699,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, | |||
1675 | if (!quiet) | 1699 | if (!quiet) |
1676 | mprintf("Changing mode on %s\n", | 1700 | mprintf("Changing mode on %s\n", |
1677 | g.gl_pathv[i]); | 1701 | g.gl_pathv[i]); |
1678 | err = do_setstat(conn, g.gl_pathv[i], &a); | 1702 | err = (hflag ? do_lsetstat : do_setstat)(conn, |
1703 | g.gl_pathv[i], &a); | ||
1679 | if (err != 0 && err_abort) | 1704 | if (err != 0 && err_abort) |
1680 | break; | 1705 | break; |
1681 | } | 1706 | } |
@@ -1685,7 +1710,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, | |||
1685 | path1 = make_absolute(path1, *pwd); | 1710 | path1 = make_absolute(path1, *pwd); |
1686 | remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); | 1711 | remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); |
1687 | for (i = 0; g.gl_pathv[i] && !interrupted; i++) { | 1712 | for (i = 0; g.gl_pathv[i] && !interrupted; i++) { |
1688 | if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) { | 1713 | if (!(aa = (hflag ? do_lstat : do_stat)(conn, |
1714 | g.gl_pathv[i], 0))) { | ||
1689 | if (err_abort) { | 1715 | if (err_abort) { |
1690 | err = -1; | 1716 | err = -1; |
1691 | break; | 1717 | break; |
@@ -1713,7 +1739,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, | |||
1713 | g.gl_pathv[i]); | 1739 | g.gl_pathv[i]); |
1714 | aa->gid = n_arg; | 1740 | aa->gid = n_arg; |
1715 | } | 1741 | } |
1716 | err = do_setstat(conn, g.gl_pathv[i], aa); | 1742 | err = (hflag ? do_lsetstat : do_setstat)(conn, |
1743 | g.gl_pathv[i], aa); | ||
1717 | if (err != 0 && err_abort) | 1744 | if (err != 0 && err_abort) |
1718 | break; | 1745 | break; |
1719 | } | 1746 | } |