From a4821a592456c3add3cd325db433110cdaaa3e5c Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 24 Oct 2022 21:51:55 +0000 Subject: [PATCH] upstream: when scp(1) is using the SFTP protocol for transport (the default), better match scp/rcp's handling of globs that don't match the globbed characters but do match literally (e.g. trying to transfer "foo.[1]"). Previously scp(1) in SFTP mode would not match these pathnames but legacy scp/rcp mode would. Reported by Michael Yagliyan in bz3488; ok dtucker@ OpenBSD-Commit-ID: d8a3773f53015ba811fddba7473769a2fd343e11 --- scp.c | 39 +++++++++++++++++++++++++++++++++++---- sftp-glob.c | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/scp.c b/scp.c index f9ca5d393..73bfe2f56 100644 --- a/scp.c +++ b/scp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: scp.c,v 1.248 2022/05/13 06:31:50 djm Exp $ */ +/* $OpenBSD: scp.c,v 1.249 2022/10/24 21:51:55 djm Exp $ */ /* * scp - secure remote copy. This is basically patched BSD rcp which * uses ssh to do the data transfer (instead of using rcmd). @@ -1505,7 +1505,8 @@ sink_sftp(int argc, char *dst, const char *src, struct sftp_conn *conn) } debug3_f("copying remote %s to local %s", abs_src, dst); - if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) { + if ((r = remote_glob(conn, abs_src, GLOB_NOCHECK|GLOB_MARK, + NULL, &g)) != 0) { if (r == GLOB_NOSPACE) error("%s: too many glob matches", src); else @@ -1514,6 +1515,20 @@ sink_sftp(int argc, char *dst, const char *src, struct sftp_conn *conn) goto out; } + /* Did we actually get any matches back from the glob? */ + if (g.gl_matchc == 0 && g.gl_pathc == 1 && g.gl_pathv[0] != 0) { + /* + * If nothing matched but a path returned, then it's probably + * a GLOB_NOCHECK result. Check whether the unglobbed path + * exists so we can give a nice error message early. + */ + if (do_stat(conn, g.gl_pathv[0], 1) == NULL) { + error("%s: %s", src, strerror(ENOENT)); + err = -1; + goto out; + } + } + if ((r = stat(dst, &st)) != 0) debug2_f("stat local \"%s\": %s", dst, strerror(errno)); dst_is_dir = r == 0 && S_ISDIR(st.st_mode); @@ -1731,7 +1746,8 @@ sink(int argc, char **argv, const char *src) } if (npatterns > 0) { for (n = 0; n < npatterns; n++) { - if (fnmatch(patterns[n], cp, 0) == 0) + if (strcmp(patterns[n], cp) == 0 || + fnmatch(patterns[n], cp, 0) == 0) break; } if (n >= npatterns) @@ -1922,7 +1938,8 @@ throughlocal_sftp(struct sftp_conn *from, struct sftp_conn *to, } debug3_f("copying remote %s to remote %s", abs_src, target); - if ((r = remote_glob(from, abs_src, GLOB_MARK, NULL, &g)) != 0) { + if ((r = remote_glob(from, abs_src, GLOB_NOCHECK|GLOB_MARK, + NULL, &g)) != 0) { if (r == GLOB_NOSPACE) error("%s: too many glob matches", src); else @@ -1931,6 +1948,20 @@ throughlocal_sftp(struct sftp_conn *from, struct sftp_conn *to, goto out; } + /* Did we actually get any matches back from the glob? */ + if (g.gl_matchc == 0 && g.gl_pathc == 1 && g.gl_pathv[0] != 0) { + /* + * If nothing matched but a path returned, then it's probably + * a GLOB_NOCHECK result. Check whether the unglobbed path + * exists so we can give a nice error message early. + */ + if (do_stat(from, g.gl_pathv[0], 1) == NULL) { + error("%s: %s", src, strerror(ENOENT)); + err = -1; + goto out; + } + } + for (i = 0; g.gl_pathv[i] && !interrupted; i++) { tmp = xstrdup(g.gl_pathv[i]); if ((filename = basename(tmp)) == NULL) { diff --git a/sftp-glob.c b/sftp-glob.c index 764e99552..afeb15f9e 100644 --- a/sftp-glob.c +++ b/sftp-glob.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp-glob.c,v 1.30 2022/02/25 09:46:24 dtucker Exp $ */ +/* $OpenBSD: sftp-glob.c,v 1.31 2022/10/24 21:51:55 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller * @@ -137,6 +137,11 @@ int remote_glob(struct sftp_conn *conn, const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob) { + int r; + size_t l; + char *s; + struct stat sb; + pglob->gl_opendir = fudge_opendir; pglob->gl_readdir = (struct dirent *(*)(void *))fudge_readdir; pglob->gl_closedir = (void (*)(void *))fudge_closedir; @@ -146,5 +151,30 @@ remote_glob(struct sftp_conn *conn, const char *pattern, int flags, memset(&cur, 0, sizeof(cur)); cur.conn = conn; - return(glob(pattern, flags | GLOB_ALTDIRFUNC, errfunc, pglob)); + if ((r = glob(pattern, flags | GLOB_ALTDIRFUNC, errfunc, pglob)) != 0) + return r; + /* + * When both GLOB_NOCHECK and GLOB_MARK are active, a single gl_pathv + * entry has been returned and that entry has not already been marked, + * then check whether it needs a '/' appended as a directory mark. + * + * This ensures that a NOCHECK result is annotated as a directory. + * The glob(3) spec doesn't promise to mark NOCHECK entries, but doing + * it simplifies our callers (sftp/scp) considerably. + * + * XXX doesn't try to handle gl_offs. + */ + if ((flags & (GLOB_NOCHECK|GLOB_MARK)) == (GLOB_NOCHECK|GLOB_MARK) && + pglob->gl_matchc == 0 && pglob->gl_offs == 0 && + pglob->gl_pathc == 1 && (s = pglob->gl_pathv[0]) != NULL && + (l = strlen(s)) > 0 && s[l-1] != '/') { + if (fudge_stat(s, &sb) == 0 && S_ISDIR(sb.st_mode)) { + /* NOCHECK on a directory; annotate */ + if ((s = realloc(s, l + 2)) != NULL) { + memcpy(s + l, "/", 2); + pglob->gl_pathv[0] = s; + } + } + } + return 0; }