Skip to content

Commit

Permalink
SFTP protocol extension to allow the server to expand ~-prefixed
Browse files Browse the repository at this point in the history
paths, in particular ~user ones. Allows scp in sftp mode to accept
these paths, like scp in rcp mode does.

prompted by and much discussion deraadt@
ok markus@
  • Loading branch information
djmdjm committed Aug 9, 2021
1 parent 87f870c commit e4383b4
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 35 deletions.
21 changes: 20 additions & 1 deletion usr.bin/ssh/PROTOCOL
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,25 @@ limits.
This extension is advertised in the SSH_FXP_VERSION hello with version
"1".

3.9. sftp: Extension request "[email protected]"

This request supports canonicalisation of relative paths and
those that need tilde-expansion, i.e. "~", "~/..." and "~user/..."
These paths are expanded using shell-like rules and the resultant
path is canonicalised similarly to SSH2_FXP_REALPATH.

It is implemented as a SSH_FXP_EXTENDED request with the following
format:

uint32 id
string "[email protected]"
string path

Its reply is the same format as that of SSH2_FXP_REALPATH.

This extension is advertised in the SSH_FXP_VERSION hello with version
"1".

4. Miscellaneous changes

4.1 Public key format
Expand Down Expand Up @@ -556,4 +575,4 @@ OpenSSH's connection multiplexing uses messages as described in
PROTOCOL.mux over a Unix domain socket for communications between a
master instance and later clients.

$OpenBSD: PROTOCOL,v 1.41 2021/02/18 02:49:35 djm Exp $
$OpenBSD: PROTOCOL,v 1.42 2021/08/09 23:47:44 djm Exp $
49 changes: 35 additions & 14 deletions usr.bin/ssh/misc.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* $OpenBSD: misc.c,v 1.168 2021/07/12 06:22:57 dtucker Exp $ */
/* $OpenBSD: misc.c,v 1.169 2021/08/09 23:47:44 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
* Copyright (c) 2005-2020 Damien Miller. All rights reserved.
Expand Down Expand Up @@ -1069,29 +1069,37 @@ freeargs(arglist *args)
* Expands tildes in the file name. Returns data allocated by xmalloc.
* Warning: this calls getpw*.
*/
char *
tilde_expand_filename(const char *filename, uid_t uid)
int
tilde_expand(const char *filename, uid_t uid, char **retp)
{
const char *path, *sep;
char user[128], *ret;
struct passwd *pw;
u_int len, slash;

if (*filename != '~')
return (xstrdup(filename));
if (*filename != '~') {
*retp = xstrdup(filename);
return 0;
}
filename++;

path = strchr(filename, '/');
if (path != NULL && path > filename) { /* ~user/path */
slash = path - filename;
if (slash > sizeof(user) - 1)
fatal("tilde_expand_filename: ~username too long");
if (slash > sizeof(user) - 1) {
error_f("~username too long");
return -1;
}
memcpy(user, filename, slash);
user[slash] = '\0';
if ((pw = getpwnam(user)) == NULL)
fatal("tilde_expand_filename: No such user %s", user);
} else if ((pw = getpwuid(uid)) == NULL) /* ~/path */
fatal("tilde_expand_filename: No such uid %ld", (long)uid);
if ((pw = getpwnam(user)) == NULL) {
error_f("No such user %s", user);
return -1;
}
} else if ((pw = getpwuid(uid)) == NULL) { /* ~/path */
error_f("No such uid %ld", (long)uid);
return -1;
}

/* Make sure directory has a trailing '/' */
len = strlen(pw->pw_dir);
Expand All @@ -1104,10 +1112,23 @@ tilde_expand_filename(const char *filename, uid_t uid)
if (path != NULL)
filename = path + 1;

if (xasprintf(&ret, "%s%s%s", pw->pw_dir, sep, filename) >= PATH_MAX)
fatal("tilde_expand_filename: Path too long");
if (xasprintf(&ret, "%s%s%s", pw->pw_dir, sep, filename) >= PATH_MAX) {
error_f("Path too long");
return -1;
}

*retp = ret;
return 0;
}

return (ret);
char *
tilde_expand_filename(const char *filename, uid_t uid)
{
char *ret;

if (tilde_expand(filename, uid, &ret) != 0)
cleanup_exit(255);
return ret;
}

/*
Expand Down
3 changes: 2 additions & 1 deletion usr.bin/ssh/misc.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* $OpenBSD: misc.h,v 1.97 2021/06/08 06:54:40 djm Exp $ */
/* $OpenBSD: misc.h,v 1.98 2021/08/09 23:47:44 djm Exp $ */

/*
* Author: Tatu Ylonen <[email protected]>
Expand Down Expand Up @@ -71,6 +71,7 @@ int parse_user_host_port(const char *, char **, char **, int *);
int parse_uri(const char *, const char *, char **, char **, int *, char **);
int convtime(const char *);
const char *fmt_timeframe(time_t t);
int tilde_expand(const char *, uid_t, char **);
char *tilde_expand_filename(const char *, uid_t);

char *dollar_expand(int *, const char *string, ...);
Expand Down
17 changes: 11 additions & 6 deletions usr.bin/ssh/scp.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* $OpenBSD: scp.c,v 1.226 2021/08/09 23:44:32 djm Exp $ */
/* $OpenBSD: scp.c,v 1.227 2021/08/09 23:47:44 djm Exp $ */
/*
* scp - secure remote copy. This is basically patched BSD rcp which
* uses ssh to do the data transfer (instead of using rcmd).
Expand Down Expand Up @@ -1222,10 +1222,14 @@ tolocal(int argc, char **argv, enum scp_mode_e mode, char *sftp_direct)

/* Canonicalise a remote path, handling ~ by assuming cwd is the homedir */
static char *
absolute_remote_path(const char *path, const char *remote_path)
absolute_remote_path(struct sftp_conn *conn, const char *path,
const char *remote_path)
{
char *ret;

if (can_expand_path(conn))
return do_expand_path(conn, path);

/* Handle ~ prefixed paths */
if (*path != '~')
ret = xstrdup(path);
Expand Down Expand Up @@ -1263,7 +1267,7 @@ source_sftp(int argc, char *src, char *targ,
* No need to glob here - the local shell already took care of
* the expansions
*/
if ((target = absolute_remote_path(targ, *remote_path)) == NULL)
if ((target = absolute_remote_path(conn, targ, *remote_path)) == NULL)
cleanup_exit(255);
target_is_dir = remote_is_dir(conn, target);
if (targetshouldbedirectory && !target_is_dir) {
Expand Down Expand Up @@ -1475,7 +1479,7 @@ sink_sftp(int argc, char *dst, const char *src, struct sftp_conn *conn)
goto out;
}

if ((abs_src = absolute_remote_path(src, remote_path)) == NULL) {
if ((abs_src = absolute_remote_path(conn, src, remote_path)) == NULL) {
err = -1;
goto out;
}
Expand Down Expand Up @@ -1879,8 +1883,9 @@ throughlocal_sftp(struct sftp_conn *from, struct sftp_conn *to,
if ((filename = basename(src)) == NULL)
fatal("basename %s: %s", src, strerror(errno));

if ((abs_src = absolute_remote_path(src, from_remote_path)) == NULL ||
(target = absolute_remote_path(targ, *to_remote_path)) == NULL)
if ((abs_src = absolute_remote_path(from, src,
from_remote_path)) == NULL ||
(target = absolute_remote_path(to, targ, *to_remote_path)) == NULL)
cleanup_exit(255);
free(from_remote_path);
memset(&g, 0, sizeof(g));
Expand Down
59 changes: 49 additions & 10 deletions usr.bin/ssh/sftp-client.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* $OpenBSD: sftp-client.c,v 1.153 2021/08/09 07:16:09 djm Exp $ */
/* $OpenBSD: sftp-client.c,v 1.154 2021/08/09 23:47:44 djm Exp $ */
/*
* Copyright (c) 2001-2004 Damien Miller <[email protected]>
*
Expand Down Expand Up @@ -82,6 +82,7 @@ struct sftp_conn {
#define SFTP_EXT_FSYNC 0x00000010
#define SFTP_EXT_LSETSTAT 0x00000020
#define SFTP_EXT_LIMITS 0x00000040
#define SFTP_EXT_PATH_EXPAND 0x00000080
u_int exts;
u_int64_t limit_kbps;
struct bwlimit bwlimit_in, bwlimit_out;
Expand Down Expand Up @@ -509,6 +510,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_LIMITS;
known = 1;
} else if (strcmp(name, "[email protected]") == 0 &&
strcmp((char *)value, "1") == 0) {
ret->exts |= SFTP_EXT_PATH_EXPAND;
known = 1;
}
if (known) {
debug2("Server supports extension \"%s\" revision %s",
Expand Down Expand Up @@ -944,23 +949,36 @@ do_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len,
return status == SSH2_FX_OK ? 0 : -1;
}

char *
do_realpath(struct sftp_conn *conn, const char *path)
/* Implements both the realpath and expand-path operations */
static char *
do_realpath_expand(struct sftp_conn *conn, const char *path, int expand)
{
struct sshbuf *msg;
u_int expected_id, count, id;
char *filename, *longname;
Attrib a;
u_char type;
int r;
const char *what = "SSH2_FXP_REALPATH";

expected_id = id = conn->msg_id++;
send_string_request(conn, id, SSH2_FXP_REALPATH, path,
strlen(path));

if (expand)
what = "[email protected]";
if ((msg = sshbuf_new()) == NULL)
fatal_f("sshbuf_new failed");

expected_id = id = conn->msg_id++;
if (expand) {
if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 ||
(r = sshbuf_put_u32(msg, id)) != 0 ||
(r = sshbuf_put_cstring(msg,
"[email protected]")) != 0 ||
(r = sshbuf_put_cstring(msg, path)) != 0)
fatal_fr(r, "compose %s", what);
send_msg(conn, msg);
} else {
send_string_request(conn, id, SSH2_FXP_REALPATH,
path, strlen(path));
}
get_msg(conn, msg);
if ((r = sshbuf_get_u8(msg, &type)) != 0 ||
(r = sshbuf_get_u32(msg, &id)) != 0)
Expand All @@ -984,15 +1002,14 @@ do_realpath(struct sftp_conn *conn, const char *path)
if ((r = sshbuf_get_u32(msg, &count)) != 0)
fatal_fr(r, "parse count");
if (count != 1)
fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count);
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");

debug3("SSH_FXP_REALPATH %s -> %s size %lu", path, filename,
(unsigned long)a.size);
debug3("%s %s -> %s", what, path, filename);

free(longname);

Expand All @@ -1001,6 +1018,28 @@ do_realpath(struct sftp_conn *conn, const char *path)
return(filename);
}

char *
do_realpath(struct sftp_conn *conn, const char *path)
{
return do_realpath_expand(conn, path, 0);
}

int
can_expand_path(struct sftp_conn *conn)
{
return (conn->exts & SFTP_EXT_PATH_EXPAND) != 0;
}

char *
do_expand_path(struct sftp_conn *conn, const char *path)
{
if (!can_expand_path(conn)) {
debug3_f("no server support, fallback to realpath");
return do_realpath_expand(conn, path, 0);
}
return do_realpath_expand(conn, path, 1);
}

int
do_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath,
int force_legacy)
Expand Down
10 changes: 8 additions & 2 deletions usr.bin/ssh/sftp-client.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* $OpenBSD: sftp-client.h,v 1.33 2021/08/07 00:12:09 djm Exp $ */
/* $OpenBSD: sftp-client.h,v 1.34 2021/08/09 23:47:44 djm Exp $ */

/*
* Copyright (c) 2001-2004 Damien Miller <[email protected]>
Expand Down Expand Up @@ -107,11 +107,17 @@ 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 *);

/* Canonicalisation with tilde expansion (requires server extension) */
char *do_expand_path(struct sftp_conn *, const char *);

/* Returns non-zero if server can tilde-expand paths */
int can_expand_path(struct sftp_conn *);

/* Get statistics for filesystem hosting file at "path" */
int do_statvfs(struct sftp_conn *, const char *, struct sftp_statvfs *, int);

/* Rename 'oldpath' to 'newpath' */
int do_rename(struct sftp_conn *, const char *, const char *, int force_legacy);
int do_rename(struct sftp_conn *, const char *, const char *, int);

/* Link 'oldpath' to 'newpath' */
int do_hardlink(struct sftp_conn *, const char *, const char *);
Expand Down
Loading

0 comments on commit e4383b4

Please sign in to comment.