Several sftp(1) commands will fail if the remote pwd contains glob(3)
meta characters. This is due to glob being called on the concatenated
pwd and command argument in order to generate a file list to operate
on. However, the pwd is not escaped before calling glob, so glob
will offer no useful results and sftp will error "not found".
The following commands will not work in some or all conditions when
called from one of these meta character directories: get, rm, chmod,
chown, chgrp, ls, and tab-completions.
The diff below extends the make_absolute() function to optionally
add escape characters to the pwd before generating the absolute
path version of the argument which will be sent to glob. I'd be
happy to rewrite if a different solution is preferable.
--
Christian
Index: sftp.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sftp.c,v
retrieving revision 1.171
diff -u -p -r1.171 sftp.c
--- sftp.c 20 Aug 2015 22:32:42 -0000 1.171
+++ sftp.c 27 Dec 2015 21:03:08 -0000
@@ -154,6 +154,10 @@ struct CMD {
#define REMOTE 1
#define LOCAL 2
+/* Options for handling glob meta chars in pwd */
+#define NOESCAPE_META 0
+#define ESCAPE_META 1
+
static const struct CMD cmds[] = {
{ "bye", I_QUIT, NOARGS },
{ "cd", I_CHDIR, REMOTE },
@@ -330,14 +334,63 @@ path_strip(char *path, char *strip)
return (xstrdup(path));
}
+/* Escape any glob(3) meta characters in the pwd */
static char *
-make_absolute(char *p, char *pwd)
+escape_glob_meta(char *pwd)
{
- char *abs_str;
+ size_t i, j, expand_len, len;
+ char *esc_pwd;
+
+ len = strlen(pwd);
+
+ expand_len = 0;
+ for (i = 0; i < len; i++) {
+ switch (pwd[i]) {
+ case '*':
+ case '?':
+ case '[':
+ case '\\':
+ expand_len++;
+ break;
+ }
+ }
+
+ esc_pwd = xmalloc(len + expand_len + 1);
+
+ for (i = j = 0; i < len; i++) {
+ switch (pwd[i]) {
+ case '*':
+ case '?':
+ case '[':
+ case '\\':
+ esc_pwd[j++] = '\\';
+ /* FALLTHROUGH */
+ default:
+ esc_pwd[j++] = pwd[i];
+ break;
+ }
+ }
+ esc_pwd[j] = 0;
+
+ return esc_pwd;
+}
+
+static char *
+make_absolute(char *p, char *pwd, int escopt)
+{
+ char *abs_str, *tmp_pwd;
/* Derelativise */
if (p && p[0] != '/') {
- abs_str = path_append(pwd, p);
+ if(escopt == ESCAPE_META)
+ tmp_pwd = escape_glob_meta(pwd);
+ else
+ tmp_pwd = pwd;
+
+ abs_str = path_append(tmp_pwd, p);
+
+ if(escopt == ESCAPE_META)
+ free(tmp_pwd);
free(p);
return(abs_str);
} else
@@ -573,7 +626,7 @@ process_get(struct sftp_conn *conn, char
int i, r, err = 0;
abs_src = xstrdup(src);
- abs_src = make_absolute(abs_src, pwd);
+ abs_src = make_absolute(abs_src, pwd, ESCAPE_META);
memset(&g, 0, sizeof(g));
debug3("Looking up %s", abs_src);
@@ -660,7 +713,7 @@ process_put(struct sftp_conn *conn, char
if (dst) {
tmp_dst = xstrdup(dst);
- tmp_dst = make_absolute(tmp_dst, pwd);
+ tmp_dst = make_absolute(tmp_dst, pwd, NOESCAPE_META);
}
memset(&g, 0, sizeof(g));
@@ -707,7 +760,8 @@ process_put(struct sftp_conn *conn, char
} else if (tmp_dst) {
abs_dst = path_append(tmp_dst, filename);
} else {
- abs_dst = make_absolute(xstrdup(filename), pwd);
+ abs_dst = make_absolute(xstrdup(filename), pwd,
+ NOESCAPE_META);
}
free(tmp);
@@ -1417,20 +1471,20 @@ parse_dispatch_command(struct sftp_conn
rflag, aflag, fflag);
break;
case I_RENAME:
- path1 = make_absolute(path1, *pwd);
- path2 = make_absolute(path2, *pwd);
+ path1 = make_absolute(path1, *pwd, NOESCAPE_META);
+ path2 = make_absolute(path2, *pwd, NOESCAPE_META);
err = do_rename(conn, path1, path2, lflag);
break;
case I_SYMLINK:
sflag = 1;
case I_LINK:
if (!sflag)
- path1 = make_absolute(path1, *pwd);
- path2 = make_absolute(path2, *pwd);
+ path1 = make_absolute(path1, *pwd, NOESCAPE_META);
+ path2 = make_absolute(path2, *pwd, NOESCAPE_META);
err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
break;
case I_RM:
- path1 = make_absolute(path1, *pwd);
+ path1 = make_absolute(path1, *pwd, ESCAPE_META);
remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
if (!quiet)
@@ -1441,18 +1495,18 @@ parse_dispatch_command(struct sftp_conn
}
break;
case I_MKDIR:
- path1 = make_absolute(path1, *pwd);
+ path1 = make_absolute(path1, *pwd, NOESCAPE_META);
attrib_clear(&a);
a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
a.perm = 0777;
err = do_mkdir(conn, path1, &a, 1);
break;
case I_RMDIR:
- path1 = make_absolute(path1, *pwd);
+ path1 = make_absolute(path1, *pwd, NOESCAPE_META);
err = do_rmdir(conn, path1);
break;
case I_CHDIR:
- path1 = make_absolute(path1, *pwd);
+ path1 = make_absolute(path1, *pwd, NOESCAPE_META);
if ((tmp = do_realpath(conn, path1)) == NULL) {
err = 1;
break;
@@ -1489,14 +1543,14 @@ parse_dispatch_command(struct sftp_conn
if (*path1 != '/')
tmp = *pwd;
- path1 = make_absolute(path1, *pwd);
+ path1 = make_absolute(path1, *pwd, ESCAPE_META);
err = do_globbed_ls(conn, path1, tmp, lflag);
break;
case I_DF:
/* Default to current directory if no path specified */
if (path1 == NULL)
path1 = xstrdup(*pwd);
- path1 = make_absolute(path1, *pwd);
+ path1 = make_absolute(path1, *pwd, NOESCAPE_META);
err = do_df(conn, path1, hflag, iflag);
break;
case I_LCHDIR:
@@ -1527,7 +1581,7 @@ parse_dispatch_command(struct sftp_conn
printf("Local umask: %03lo\n", n_arg);
break;
case I_CHMOD:
- path1 = make_absolute(path1, *pwd);
+ path1 = make_absolute(path1, *pwd, ESCAPE_META);
attrib_clear(&a);
a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
a.perm = n_arg;
@@ -1542,7 +1596,7 @@ parse_dispatch_command(struct sftp_conn
break;
case I_CHOWN:
case I_CHGRP:
- path1 = make_absolute(path1, *pwd);
+ path1 = make_absolute(path1, *pwd, ESCAPE_META);
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))) {
@@ -1814,7 +1868,7 @@ complete_match(EditLine *el, struct sftp
memset(&g, 0, sizeof(g));
if (remote != LOCAL) {
- tmp = make_absolute(tmp, remote_path);
+ tmp = make_absolute(tmp, remote_path, ESCAPE_META);
remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
} else
glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
@@ -2032,7 +2086,7 @@ interactive_loop(struct sftp_conn *conn,
if (file1 != NULL) {
dir = xstrdup(file1);
- dir = make_absolute(dir, remote_path);
+ dir = make_absolute(dir, remote_path, NOESCAPE_META);
if (remote_is_dir(conn, dir) && file2 == NULL) {
if (!quiet)
@@ -2046,6 +2100,10 @@ interactive_loop(struct sftp_conn *conn,
return (-1);
}
} else {
+ free(dir);
+ dir = xstrdup(file1);
+ dir = make_absolute(dir, remote_path, ESCAPE_META);
+
/* XXX this is wrong wrt quoting */
snprintf(cmd, sizeof cmd, "get%s %s%s%s",
global_aflag ? " -a" : "", dir,