summaryrefslogtreecommitdiff
path: root/scp.c
diff options
context:
space:
mode:
authorAlistair Delva <adelva@google.com>2020-08-21 00:00:13 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2020-08-21 00:00:13 +0000
commited358b3546c776c1c677fd88eb8f716cf6187510 (patch)
tree3c6134bcb2cda4b9dccc57b4a8b997a945aab62d /scp.c
parent22246b08952d746a7cc5a292570636cf4277598f (diff)
parent44a1065de8a58c51a021243a28bfa01e87822e4f (diff)
Merge changes I934c73d4,I28cdc9a0,I9e734da9,I3c079d86
* changes: UPSTREAM: depend UPSTREAM: upstream: avoid possible NULL deref; from Pedro Martelletto Revert "upstream: fix compilation with DEBUG_KEXDH; bz#3160 ok dtucker@" Merge upstream-master into master
Diffstat (limited to 'scp.c')
-rw-r--r--scp.c784
1 files changed, 583 insertions, 201 deletions
diff --git a/scp.c b/scp.c
index 887b014b..b4492a06 100644
--- a/scp.c
+++ b/scp.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: scp.c,v 1.181 2015/01/16 06:40:12 deraadt Exp $ */
+/* $OpenBSD: scp.c,v 1.210 2020/05/06 20:57:38 djm Exp $ */
/*
* scp - secure remote copy. This is basically patched BSD rcp which
* uses ssh to do the data transfer (instead of using rcmd).
@@ -74,7 +74,6 @@
#include "includes.h"
#include <sys/types.h>
-#include <sys/param.h>
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
@@ -95,10 +94,17 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
+#ifdef HAVE_FNMATCH_H
+#include <fnmatch.h>
+#endif
#include <limits.h>
+#include <locale.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -109,18 +115,20 @@
#endif
#include "xmalloc.h"
+#include "ssh.h"
#include "atomicio.h"
#include "pathnames.h"
#include "log.h"
#include "misc.h"
#include "progressmeter.h"
+#include "utf8.h"
extern char *__progname;
#define COPY_BUFLEN 16384
-int do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout);
-int do_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout);
+int do_cmd(char *host, char *remuser, int port, char *cmd, int *fdin, int *fdout);
+int do_cmd2(char *host, char *remuser, int port, char *cmd, int fdin, int fdout);
/* Struct for addargs */
arglist args;
@@ -145,6 +153,9 @@ int showprogress = 1;
*/
int throughlocal = 0;
+/* Non-standard port to use for the ssh connection or -1. */
+int sshport = -1;
+
/* This is the program to execute for the secured connection. ("ssh" or -S) */
char *ssh_program = _PATH_SSH_PROGRAM;
@@ -191,7 +202,7 @@ do_local_cmd(arglist *a)
if (verbose_mode) {
fprintf(stderr, "Executing:");
for (i = 0; i < a->num; i++)
- fprintf(stderr, " %s", a->list[i]);
+ fmprintf(stderr, " %s", a->list[i]);
fprintf(stderr, "\n");
}
if ((pid = fork()) == -1)
@@ -204,9 +215,9 @@ do_local_cmd(arglist *a)
}
do_cmd_pid = pid;
- signal(SIGTERM, killchild);
- signal(SIGINT, killchild);
- signal(SIGHUP, killchild);
+ ssh_signal(SIGTERM, killchild);
+ ssh_signal(SIGINT, killchild);
+ ssh_signal(SIGHUP, killchild);
while (waitpid(pid, &status, 0) == -1)
if (errno != EINTR)
@@ -227,36 +238,39 @@ do_local_cmd(arglist *a)
*/
int
-do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout)
+do_cmd(char *host, char *remuser, int port, char *cmd, int *fdin, int *fdout)
{
int pin[2], pout[2], reserved[2];
if (verbose_mode)
- fprintf(stderr,
+ fmprintf(stderr,
"Executing: program %s host %s, user %s, command %s\n",
ssh_program, host,
remuser ? remuser : "(unspecified)", cmd);
+ if (port == -1)
+ port = sshport;
+
/*
* Reserve two descriptors so that the real pipes won't get
* descriptors 0 and 1 because that will screw up dup2 below.
*/
- if (pipe(reserved) < 0)
+ if (pipe(reserved) == -1)
fatal("pipe: %s", strerror(errno));
/* Create a socket pair for communicating with ssh. */
- if (pipe(pin) < 0)
+ if (pipe(pin) == -1)
fatal("pipe: %s", strerror(errno));
- if (pipe(pout) < 0)
+ if (pipe(pout) == -1)
fatal("pipe: %s", strerror(errno));
/* Free the reserved descriptors. */
close(reserved[0]);
close(reserved[1]);
- signal(SIGTSTP, suspchild);
- signal(SIGTTIN, suspchild);
- signal(SIGTTOU, suspchild);
+ ssh_signal(SIGTSTP, suspchild);
+ ssh_signal(SIGTTIN, suspchild);
+ ssh_signal(SIGTTOU, suspchild);
/* Fork a child to execute the command on the remote host using ssh. */
do_cmd_pid = fork();
@@ -270,6 +284,10 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout)
close(pout[1]);
replacearg(&args, 0, "%s", ssh_program);
+ if (port != -1) {
+ addargs(&args, "-p");
+ addargs(&args, "%d", port);
+ }
if (remuser != NULL) {
addargs(&args, "-l");
addargs(&args, "%s", remuser);
@@ -289,29 +307,32 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout)
*fdout = pin[1];
close(pout[1]);
*fdin = pout[0];
- signal(SIGTERM, killchild);
- signal(SIGINT, killchild);
- signal(SIGHUP, killchild);
+ ssh_signal(SIGTERM, killchild);
+ ssh_signal(SIGINT, killchild);
+ ssh_signal(SIGHUP, killchild);
return 0;
}
/*
- * This functions executes a command simlar to do_cmd(), but expects the
+ * This function executes a command similar to do_cmd(), but expects the
* input and output descriptors to be setup by a previous call to do_cmd().
* This way the input and output of two commands can be connected.
*/
int
-do_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout)
+do_cmd2(char *host, char *remuser, int port, char *cmd, int fdin, int fdout)
{
pid_t pid;
int status;
if (verbose_mode)
- fprintf(stderr,
+ fmprintf(stderr,
"Executing: 2nd program %s host %s, user %s, command %s\n",
ssh_program, host,
remuser ? remuser : "(unspecified)", cmd);
+ if (port == -1)
+ port = sshport;
+
/* Fork a child to execute the command on the remote host using ssh. */
pid = fork();
if (pid == 0) {
@@ -319,10 +340,15 @@ do_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout)
dup2(fdout, 1);
replacearg(&args, 0, "%s", ssh_program);
+ if (port != -1) {
+ addargs(&args, "-p");
+ addargs(&args, "%d", port);
+ }
if (remuser != NULL) {
addargs(&args, "-l");
addargs(&args, "%s", remuser);
}
+ addargs(&args, "-oBatchMode=yes");
addargs(&args, "--");
addargs(&args, "%s", host);
addargs(&args, "%s", cmd);
@@ -348,29 +374,30 @@ BUF *allocbuf(BUF *, int, int);
void lostconn(int);
int okname(char *);
void run_err(const char *,...);
+int note_err(const char *,...);
void verifydir(char *);
struct passwd *pwd;
uid_t userid;
int errs, remin, remout;
-int pflag, iamremote, iamrecursive, targetshouldbedirectory;
+int Tflag, pflag, iamremote, iamrecursive, targetshouldbedirectory;
#define CMDNEEDS 64
char cmd[CMDNEEDS]; /* must hold "rcp -r -p -d\0" */
int response(void);
void rsource(char *, struct stat *);
-void sink(int, char *[]);
+void sink(int, char *[], const char *);
void source(int, char *[]);
void tolocal(int, char *[]);
-void toremote(char *, int, char *[]);
+void toremote(int, char *[]);
void usage(void);
int
main(int argc, char **argv)
{
int ch, fflag, tflag, status, n;
- char *targ, **newargv;
+ char **newargv;
const char *errstr;
extern char *optarg;
extern int optind;
@@ -378,8 +405,12 @@ main(int argc, char **argv)
/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
sanitise_stdfd();
+ seed_rng();
+
+ msetlocale();
+
/* Copy argv, because we modify it */
- newargv = xcalloc(MAX(argc + 1, 1), sizeof(*newargv));
+ newargv = xcalloc(MAXIMUM(argc + 1, 1), sizeof(*newargv));
for (n = 0; n < argc; n++)
newargv[n] = xstrdup(argv[n]);
argv = newargv;
@@ -394,13 +425,20 @@ main(int argc, char **argv)
addargs(&args, "-oForwardAgent=no");
addargs(&args, "-oPermitLocalCommand=no");
addargs(&args, "-oClearAllForwardings=yes");
+ addargs(&args, "-oRemoteCommand=none");
+ addargs(&args, "-oRequestTTY=no");
- fflag = tflag = 0;
- while ((ch = getopt(argc, argv, "dfl:prtvBCc:i:P:q12346S:o:F:")) != -1)
+ fflag = Tflag = tflag = 0;
+ while ((ch = getopt(argc, argv,
+ "dfl:prtTvBCc:i:P:q12346S:o:F:J:")) != -1) {
switch (ch) {
/* User-visible flags. */
case '1':
+ fatal("SSH protocol v.1 is no longer supported");
+ break;
case '2':
+ /* Ignored */
+ break;
case '4':
case '6':
case 'C':
@@ -414,16 +452,16 @@ main(int argc, char **argv)
case 'c':
case 'i':
case 'F':
+ case 'J':
addargs(&remote_remote_args, "-%c", ch);
addargs(&remote_remote_args, "%s", optarg);
addargs(&args, "-%c", ch);
addargs(&args, "%s", optarg);
break;
case 'P':
- addargs(&remote_remote_args, "-p");
- addargs(&remote_remote_args, "%s", optarg);
- addargs(&args, "-p");
- addargs(&args, "%s", optarg);
+ sshport = a2port(optarg);
+ if (sshport <= 0)
+ fatal("bad port \"%s\"\n", optarg);
break;
case 'B':
addargs(&remote_remote_args, "-oBatchmode=yes");
@@ -472,9 +510,13 @@ main(int argc, char **argv)
setmode(0, O_BINARY);
#endif
break;
+ case 'T':
+ Tflag = 1;
+ break;
default:
usage();
}
+ }
argc -= optind;
argv += optind;
@@ -484,6 +526,16 @@ main(int argc, char **argv)
if (!isatty(STDOUT_FILENO))
showprogress = 0;
+ if (pflag) {
+ /* Cannot pledge: -p allows setuid/setgid files... */
+ } else {
+ if (pledge("stdio rpath wpath cpath fattr tty proc exec",
+ NULL) == -1) {
+ perror("pledge");
+ exit(1);
+ }
+ }
+
remin = STDIN_FILENO;
remout = STDOUT_FILENO;
@@ -495,7 +547,7 @@ main(int argc, char **argv)
}
if (tflag) {
/* Receive data. */
- sink(argc, argv);
+ sink(argc, argv, NULL);
exit(errs != 0);
}
if (argc < 2)
@@ -511,10 +563,10 @@ main(int argc, char **argv)
iamrecursive ? " -r" : "", pflag ? " -p" : "",
targetshouldbedirectory ? " -d" : "");
- (void) signal(SIGPIPE, lostconn);
+ (void) ssh_signal(SIGPIPE, lostconn);
- if ((targ = colon(argv[argc - 1]))) /* Dest is remote host. */
- toremote(targ, argc, argv);
+ if (colon(argv[argc - 1])) /* Dest is remote host. */
+ toremote(argc, argv);
else {
if (targetshouldbedirectory)
verifydir(argv[argc - 1]);
@@ -546,6 +598,7 @@ scpio(void *_cnt, size_t s)
off_t *cnt = (off_t *)_cnt;
*cnt += s;
+ refresh_progress_meter(0);
if (limit_kbps > 0)
bandwidth_limit(&bwlimit, s);
return 0;
@@ -569,72 +622,337 @@ do_times(int fd, int verb, const struct stat *sb)
return (response());
}
+static int
+parse_scp_uri(const char *uri, char **userp, char **hostp, int *portp,
+ char **pathp)
+{
+ int r;
+
+ r = parse_uri("scp", uri, userp, hostp, portp, pathp);
+ if (r == 0 && *pathp == NULL)
+ *pathp = xstrdup(".");
+ return r;
+}
+
+/* Appends a string to an array; returns 0 on success, -1 on alloc failure */
+static int
+append(char *cp, char ***ap, size_t *np)
+{
+ char **tmp;
+
+ if ((tmp = reallocarray(*ap, *np + 1, sizeof(*tmp))) == NULL)
+ return -1;
+ tmp[(*np)] = cp;
+ (*np)++;
+ *ap = tmp;
+ return 0;
+}
+
+/*
+ * Finds the start and end of the first brace pair in the pattern.
+ * returns 0 on success or -1 for invalid patterns.
+ */
+static int
+find_brace(const char *pattern, int *startp, int *endp)
+{
+ int i;
+ int in_bracket, brace_level;
+
+ *startp = *endp = -1;
+ in_bracket = brace_level = 0;
+ for (i = 0; i < INT_MAX && *endp < 0 && pattern[i] != '\0'; i++) {
+ switch (pattern[i]) {
+ case '\\':
+ /* skip next character */
+ if (pattern[i + 1] != '\0')
+ i++;
+ break;
+ case '[':
+ in_bracket = 1;
+ break;
+ case ']':
+ in_bracket = 0;
+ break;
+ case '{':
+ if (in_bracket)
+ break;
+ if (pattern[i + 1] == '}') {
+ /* Protect a single {}, for find(1), like csh */
+ i++; /* skip */
+ break;
+ }
+ if (*startp == -1)
+ *startp = i;
+ brace_level++;
+ break;
+ case '}':
+ if (in_bracket)
+ break;
+ if (*startp < 0) {
+ /* Unbalanced brace */
+ return -1;
+ }
+ if (--brace_level <= 0)
+ *endp = i;
+ break;
+ }
+ }
+ /* unbalanced brackets/braces */
+ if (*endp < 0 && (*startp >= 0 || in_bracket))
+ return -1;
+ return 0;
+}
+
+/*
+ * Assembles and records a successfully-expanded pattern, returns -1 on
+ * alloc failure.
+ */
+static int
+emit_expansion(const char *pattern, int brace_start, int brace_end,
+ int sel_start, int sel_end, char ***patternsp, size_t *npatternsp)
+{
+ char *cp;
+ int o = 0, tail_len = strlen(pattern + brace_end + 1);
+
+ if ((cp = malloc(brace_start + (sel_end - sel_start) +
+ tail_len + 1)) == NULL)
+ return -1;
+
+ /* Pattern before initial brace */
+ if (brace_start > 0) {
+ memcpy(cp, pattern, brace_start);
+ o = brace_start;
+ }
+ /* Current braced selection */
+ if (sel_end - sel_start > 0) {
+ memcpy(cp + o, pattern + sel_start,
+ sel_end - sel_start);
+ o += sel_end - sel_start;
+ }
+ /* Remainder of pattern after closing brace */
+ if (tail_len > 0) {
+ memcpy(cp + o, pattern + brace_end + 1, tail_len);
+ o += tail_len;
+ }
+ cp[o] = '\0';
+ if (append(cp, patternsp, npatternsp) != 0) {
+ free(cp);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Expand the first encountered brace in pattern, appending the expanded
+ * patterns it yielded to the *patternsp array.
+ *
+ * Returns 0 on success or -1 on allocation failure.
+ *
+ * Signals whether expansion was performed via *expanded and whether
+ * pattern was invalid via *invalid.
+ */
+static int
+brace_expand_one(const char *pattern, char ***patternsp, size_t *npatternsp,
+ int *expanded, int *invalid)
+{
+ int i;
+ int in_bracket, brace_start, brace_end, brace_level;
+ int sel_start, sel_end;
+
+ *invalid = *expanded = 0;
+
+ if (find_brace(pattern, &brace_start, &brace_end) != 0) {
+ *invalid = 1;
+ return 0;
+ } else if (brace_start == -1)
+ return 0;
+
+ in_bracket = brace_level = 0;
+ for (i = sel_start = brace_start + 1; i < brace_end; i++) {
+ switch (pattern[i]) {
+ case '{':
+ if (in_bracket)
+ break;
+ brace_level++;
+ break;
+ case '}':
+ if (in_bracket)
+ break;
+ brace_level--;
+ break;
+ case '[':
+ in_bracket = 1;
+ break;
+ case ']':
+ in_bracket = 0;
+ break;
+ case '\\':
+ if (i < brace_end - 1)
+ i++; /* skip */
+ break;
+ }
+ if (pattern[i] == ',' || i == brace_end - 1) {
+ if (in_bracket || brace_level > 0)
+ continue;
+ /* End of a selection, emit an expanded pattern */
+
+ /* Adjust end index for last selection */
+ sel_end = (i == brace_end - 1) ? brace_end : i;
+ if (emit_expansion(pattern, brace_start, brace_end,
+ sel_start, sel_end, patternsp, npatternsp) != 0)
+ return -1;
+ /* move on to the next selection */
+ sel_start = i + 1;
+ continue;
+ }
+ }
+ if (in_bracket || brace_level > 0) {
+ *invalid = 1;
+ return 0;
+ }
+ /* success */
+ *expanded = 1;
+ return 0;
+}
+
+/* Expand braces from pattern. Returns 0 on success, -1 on failure */
+static int
+brace_expand(const char *pattern, char ***patternsp, size_t *npatternsp)
+{
+ char *cp, *cp2, **active = NULL, **done = NULL;
+ size_t i, nactive = 0, ndone = 0;
+ int ret = -1, invalid = 0, expanded = 0;
+
+ *patternsp = NULL;
+ *npatternsp = 0;
+
+ /* Start the worklist with the original pattern */
+ if ((cp = strdup(pattern)) == NULL)
+ return -1;
+ if (append(cp, &active, &nactive) != 0) {
+ free(cp);
+ return -1;
+ }
+ while (nactive > 0) {
+ cp = active[nactive - 1];
+ nactive--;
+ if (brace_expand_one(cp, &active, &nactive,
+ &expanded, &invalid) == -1) {
+ free(cp);
+ goto fail;
+ }
+ if (invalid)
+ fatal("%s: invalid brace pattern \"%s\"", __func__, cp);
+ if (expanded) {
+ /*
+ * Current entry expanded to new entries on the
+ * active list; discard the progenitor pattern.
+ */
+ free(cp);
+ continue;
+ }
+ /*
+ * Pattern did not expand; append the finename component to
+ * the completed list
+ */
+ if ((cp2 = strrchr(cp, '/')) != NULL)
+ *cp2++ = '\0';
+ else
+ cp2 = cp;
+ if (append(xstrdup(cp2), &done, &ndone) != 0) {
+ free(cp);
+ goto fail;
+ }
+ free(cp);
+ }
+ /* success */
+ *patternsp = done;
+ *npatternsp = ndone;
+ done = NULL;
+ ndone = 0;
+ ret = 0;
+ fail:
+ for (i = 0; i < nactive; i++)
+ free(active[i]);
+ free(active);
+ for (i = 0; i < ndone; i++)
+ free(done[i]);
+ free(done);
+ return ret;
+}
+
void
-toremote(char *targ, int argc, char **argv)
+toremote(int argc, char **argv)
{
- char *bp, *host, *src, *suser, *thost, *tuser, *arg;
+ char *suser = NULL, *host = NULL, *src = NULL;
+ char *bp, *tuser, *thost, *targ;
+ int sport = -1, tport = -1;
arglist alist;
- int i;
+ int i, r;
u_int j;
memset(&alist, '\0', sizeof(alist));
alist.list = NULL;
- *targ++ = 0;
- if (*targ == 0)
- targ = ".";
-
- arg = xstrdup(argv[argc - 1]);
- if ((thost = strrchr(arg, '@'))) {
- /* user@host */
- *thost++ = 0;
- tuser = arg;
- if (*tuser == '\0')
- tuser = NULL;
- } else {
- thost = arg;
- tuser = NULL;
+ /* Parse target */
+ r = parse_scp_uri(argv[argc - 1], &tuser, &thost, &tport, &targ);
+ if (r == -1) {
+ fmprintf(stderr, "%s: invalid uri\n", argv[argc - 1]);
+ ++errs;
+ goto out;
+ }
+ if (r != 0) {
+ if (parse_user_host_path(argv[argc - 1], &tuser, &thost,
+ &targ) == -1) {
+ fmprintf(stderr, "%s: invalid target\n", argv[argc - 1]);
+ ++errs;
+ goto out;
+ }
}
-
if (tuser != NULL && !okname(tuser)) {
- free(arg);
- return;
+ ++errs;
+ goto out;
}
+ /* Parse source files */
for (i = 0; i < argc - 1; i++) {
- src = colon(argv[i]);
- if (src && throughlocal) { /* extended remote to remote */
- *src++ = 0;
- if (*src == 0)
- src = ".";
- host = strrchr(argv[i], '@');
- if (host) {
- *host++ = 0;
- host = cleanhostname(host);
- suser = argv[i];
- if (*suser == '\0')
- suser = pwd->pw_name;
- else if (!okname(suser))
- continue;
- } else {
- host = cleanhostname(argv[i]);
- suser = NULL;
- }
+ free(suser);
+ free(host);
+ free(src);
+ r = parse_scp_uri(argv[i], &suser, &host, &sport, &src);
+ if (r == -1) {
+ fmprintf(stderr, "%s: invalid uri\n", argv[i]);
+ ++errs;
+ continue;
+ }
+ if (r != 0) {
+ parse_user_host_path(argv[i], &suser, &host, &src);
+ }
+ if (suser != NULL && !okname(suser)) {
+ ++errs;
+ continue;
+ }
+ if (host && throughlocal) { /* extended remote to remote */
xasprintf(&bp, "%s -f %s%s", cmd,
*src == '-' ? "-- " : "", src);
- if (do_cmd(host, suser, bp, &remin, &remout) < 0)
+ if (do_cmd(host, suser, sport, bp, &remin, &remout) < 0)
exit(1);
free(bp);
- host = cleanhostname(thost);
xasprintf(&bp, "%s -t %s%s", cmd,
*targ == '-' ? "-- " : "", targ);
- if (do_cmd2(host, tuser, bp, remin, remout) < 0)
+ if (do_cmd2(thost, tuser, tport, bp, remin, remout) < 0)
exit(1);
free(bp);
(void) close(remin);
(void) close(remout);
remin = remout = -1;
- } else if (src) { /* standard remote to remote */
+ } else if (host) { /* standard remote to remote */
+ if (tport != -1 && tport != SSH_DEFAULT_PORT) {
+ /* This would require the remote support URIs */
+ fatal("target port not supported with two "
+ "remote hosts without the -3 option");
+ }
+
freeargs(&alist);
addargs(&alist, "%s", ssh_program);
addargs(&alist, "-x");
@@ -644,23 +962,14 @@ toremote(char *targ, int argc, char **argv)
addargs(&alist, "%s",
remote_remote_args.list[j]);
}
- *src++ = 0;
- if (*src == 0)
- src = ".";
- host = strrchr(argv[i], '@');
-
- if (host) {
- *host++ = 0;
- host = cleanhostname(host);
- suser = argv[i];
- if (*suser == '\0')
- suser = pwd->pw_name;
- else if (!okname(suser))
- continue;
+
+ if (sport != -1) {
+ addargs(&alist, "-p");
+ addargs(&alist, "%d", sport);
+ }
+ if (suser) {
addargs(&alist, "-l");
addargs(&alist, "%s", suser);
- } else {
- host = cleanhostname(argv[i]);
}
addargs(&alist, "--");
addargs(&alist, "%s", host);
@@ -675,8 +984,7 @@ toremote(char *targ, int argc, char **argv)
if (remin == -1) {
xasprintf(&bp, "%s -t %s%s", cmd,
*targ == '-' ? "-- " : "", targ);
- host = cleanhostname(thost);
- if (do_cmd(host, tuser, bp, &remin,
+ if (do_cmd(thost, tuser, tport, bp, &remin,
&remout) < 0)
exit(1);
if (response() < 0)
@@ -686,21 +994,42 @@ toremote(char *targ, int argc, char **argv)
source(1, argv + i);
}
}
- free(arg);
+out:
+ free(tuser);
+ free(thost);
+ free(targ);
+ free(suser);
+ free(host);
+ free(src);
}
void
tolocal(int argc, char **argv)
{
- char *bp, *host, *src, *suser;
+ char *bp, *host = NULL, *src = NULL, *suser = NULL;
arglist alist;
- int i;
+ int i, r, sport = -1;
memset(&alist, '\0', sizeof(alist));
alist.list = NULL;
for (i = 0; i < argc - 1; i++) {
- if (!(src = colon(argv[i]))) { /* Local to local. */
+ free(suser);
+ free(host);
+ free(src);
+ r = parse_scp_uri(argv[i], &suser, &host, &sport, &src);
+ if (r == -1) {
+ fmprintf(stderr, "%s: invalid uri\n", argv[i]);
+ ++errs;
+ continue;
+ }
+ if (r != 0)
+ parse_user_host_path(argv[i], &suser, &host, &src);
+ if (suser != NULL && !okname(suser)) {
+ ++errs;
+ continue;
+ }
+ if (!host) { /* Local to local. */
freeargs(&alist);
addargs(&alist, "%s", _PATH_CP);
if (iamrecursive)
@@ -714,31 +1043,22 @@ tolocal(int argc, char **argv)
++errs;
continue;
}
- *src++ = 0;
- if (*src == 0)
- src = ".";
- if ((host = strrchr(argv[i], '@')) == NULL) {
- host = argv[i];
- suser = NULL;
- } else {
- *host++ = 0;
- suser = argv[i];
- if (*suser == '\0')
- suser = pwd->pw_name;
- }
- host = cleanhostname(host);
+ /* Remote to local. */
xasprintf(&bp, "%s -f %s%s",
cmd, *src == '-' ? "-- " : "", src);
- if (do_cmd(host, suser, bp, &remin, &remout) < 0) {
+ if (do_cmd(host, suser, sport, bp, &remin, &remout) < 0) {
free(bp);
++errs;
continue;
}
free(bp);
- sink(1, argv + argc - 1);
+ sink(1, argv + argc - 1, src);
(void) close(remin);
remin = remout = -1;
}
+ free(suser);
+ free(host);
+ free(src);
}
void
@@ -750,7 +1070,7 @@ source(int argc, char **argv)
off_t i, statbytes;
size_t amt, nr;
int fd = -1, haderr, indx;
- char *last, *name, buf[2048], encname[PATH_MAX];
+ char *last, *name, buf[PATH_MAX + 128], encname[PATH_MAX];
int len;
for (indx = 0; indx < argc; ++indx) {
@@ -759,13 +1079,13 @@ source(int argc, char **argv)
len = strlen(name);
while (len > 1 && name[len-1] == '/')
name[--len] = '\0';
- if ((fd = open(name, O_RDONLY|O_NONBLOCK, 0)) < 0)
+ if ((fd = open(name, O_RDONLY|O_NONBLOCK, 0)) == -1)
goto syserr;
if (strchr(name, '\n') != NULL) {
strnvis(encname, name, sizeof(encname), VIS_NL);
name = encname;
}
- if (fstat(fd, &stb) < 0) {
+ if (fstat(fd, &stb) == -1) {
syserr: run_err("%s: %s", name, strerror(errno));
goto next;
}
@@ -800,9 +1120,8 @@ syserr: run_err("%s: %s", name, strerror(errno));
snprintf(buf, sizeof buf, "C%04o %lld %s\n",
(u_int) (stb.st_mode & FILEMODEMASK),
(long long)stb.st_size, last);
- if (verbose_mode) {
- fprintf(stderr, "Sending file modes: %s", buf);
- }
+ if (verbose_mode)
+ fmprintf(stderr, "Sending file modes: %s", buf);
(void) atomicio(vwrite, remout, buf, strlen(buf));
if (response() < 0)
goto next;
@@ -838,11 +1157,9 @@ next: if (fd != -1) {
haderr = errno;
}
unset_nonblock(remout);
- if (showprogress)
- stop_progress_meter();
if (fd != -1) {
- if (close(fd) < 0 && !haderr)
+ if (close(fd) == -1 && !haderr)
haderr = errno;
fd = -1;
}
@@ -851,6 +1168,8 @@ next: if (fd != -1) {
else
run_err("%s: %s", name, strerror(haderr));
(void) response();
+ if (showprogress)
+ stop_progress_meter();
}
}
@@ -866,7 +1185,7 @@ rsource(char *name, struct stat *statp)
return;
}
last = strrchr(name, '/');
- if (last == 0)
+ if (last == NULL)
last = name;
else
last++;
@@ -879,7 +1198,7 @@ rsource(char *name, struct stat *statp)
(void) snprintf(path, sizeof path, "D%04o %d %.1024s\n",
(u_int) (statp->st_mode & FILEMODEMASK), 0, last);
if (verbose_mode)
- fprintf(stderr, "Entering directory: %s", path);
+ fmprintf(stderr, "Entering directory: %s", path);
(void) atomicio(vwrite, remout, path, strlen(path));
if (response() < 0) {
closedir(dirp);
@@ -903,14 +1222,16 @@ rsource(char *name, struct stat *statp)
(void) response();
}
+#define TYPE_OVERFLOW(type, val) \
+ ((sizeof(type) == 4 && (val) > INT32_MAX) || \
+ (sizeof(type) == 8 && (val) > INT64_MAX) || \
+ (sizeof(type) != 4 && sizeof(type) != 8))
+
void
-sink(int argc, char **argv)
+sink(int argc, char **argv, const char *src)
{
static BUF buffer;
struct stat stb;
- enum {
- YES, NO, DISPLAYED
- } wrerr;
BUF *bp;
off_t i;
size_t j, count;
@@ -918,14 +1239,19 @@ sink(int argc, char **argv)
mode_t mode, omode, mask;
off_t size, statbytes;
unsigned long long ull;
- int setimes, targisdir, wrerrno = 0;
- char ch, *cp, *np, *targ, *why, *vect[1], buf[2048];
+ int setimes, targisdir, wrerr;
+ char ch, *cp, *np, *targ, *why, *vect[1], buf[2048], visbuf[2048];
+ char **patterns = NULL;
+ size_t n, npatterns = 0;
struct timeval tv[2];
#define atime tv[0]
#define mtime tv[1]
#define SCREWUP(str) { why = str; goto screwup; }
+ if (TYPE_OVERFLOW(time_t, 0) || TYPE_OVERFLOW(off_t, 0))
+ SCREWUP("Unexpected off_t/time_t size");
+
setimes = targisdir = 0;
mask = umask(0);
if (!pflag)
@@ -941,10 +1267,18 @@ sink(int argc, char **argv)
(void) atomicio(vwrite, remout, "", 1);
if (stat(targ, &stb) == 0 && S_ISDIR(stb.st_mode))
targisdir = 1;
+ if (src != NULL && !iamrecursive && !Tflag) {
+ /*
+ * Prepare to try to restrict incoming filenames to match
+ * the requested destination file glob.
+ */
+ if (brace_expand(src, &patterns, &npatterns) != 0)
+ fatal("%s: could not expand pattern", __func__);
+ }
for (first = 1;; first = 0) {
cp = buf;
if (atomicio(read, remin, cp, 1) != 1)
- return;
+ goto done;
if (*cp++ == '\n')
SCREWUP("unexpected <newline>");
do {
@@ -954,12 +1288,15 @@ sink(int argc, char **argv)
} while (cp < &buf[sizeof(buf) - 1] && ch != '\n');
*cp = 0;
if (verbose_mode)
- fprintf(stderr, "Sink: %s", buf);
+ fmprintf(stderr, "Sink: %s", buf);
if (buf[0] == '\01' || buf[0] == '\02') {
- if (iamremote == 0)
+ if (iamremote == 0) {
+ (void) snmprintf(visbuf, sizeof(visbuf),
+ NULL, "%s", buf + 1);
(void) atomicio(vwrite, STDERR_FILENO,
- buf + 1, strlen(buf + 1));
+ visbuf, strlen(visbuf));
+ }
if (buf[0] == '\02')
exit(1);
++errs;
@@ -967,7 +1304,7 @@ sink(int argc, char **argv)
}
if (buf[0] == 'E') {
(void) atomicio(vwrite, remout, "", 1);
- return;
+ goto done;
}
if (ch == '\n')
*--cp = 0;
@@ -981,8 +1318,7 @@ sink(int argc, char **argv)
ull = strtoull(cp, &cp, 10);
if (!cp || *cp++ != ' ')
SCREWUP("mtime.sec not delimited");
- if ((time_t)ull < 0 ||
- (unsigned long long)(time_t)ull != ull)
+ if (TYPE_OVERFLOW(time_t, ull))
setimes = 0; /* out of range */
mtime.tv_sec = ull;
mtime.tv_usec = strtol(cp, &cp, 10);
@@ -994,8 +1330,7 @@ sink(int argc, char **argv)
ull = strtoull(cp, &cp, 10);
if (!cp || *cp++ != ' ')
SCREWUP("atime.sec not delimited");
- if ((time_t)ull < 0 ||
- (unsigned long long)(time_t)ull != ull)
+ if (TYPE_OVERFLOW(time_t, ull))
setimes = 0; /* out of range */
atime.tv_sec = ull;
atime.tv_usec = strtol(cp, &cp, 10);
@@ -1025,17 +1360,33 @@ sink(int argc, char **argv)
SCREWUP("bad mode");
mode = (mode << 3) | (*cp - '0');
}
+ if (!pflag)
+ mode &= ~mask;
if (*cp++ != ' ')
SCREWUP("mode not delimited");
- for (size = 0; isdigit((unsigned char)*cp);)
- size = size * 10 + (*cp++ - '0');
- if (*cp++ != ' ')
+ if (!isdigit((unsigned char)*cp))
+ SCREWUP("size not present");
+ ull = strtoull(cp, &cp, 10);
+ if (!cp || *cp++ != ' ')
SCREWUP("size not delimited");
- if ((strchr(cp, '/') != NULL) || (strcmp(cp, "..") == 0)) {
+ if (TYPE_OVERFLOW(off_t, ull))
+ SCREWUP("size out of range");
+ size = (off_t)ull;
+
+ if (*cp == '\0' || strchr(cp, '/') != NULL ||
+ strcmp(cp, ".") == 0 || strcmp(cp, "..") == 0) {
run_err("error: unexpected filename: %s", cp);
exit(1);
}
+ if (npatterns > 0) {
+ for (n = 0; n < npatterns; n++) {
+ if (fnmatch(patterns[n], cp, 0) == 0)
+ break;
+ }
+ if (n >= npatterns)
+ SCREWUP("filename does not match request");
+ }
if (targisdir) {
static char *namebuf;
static size_t cursize;
@@ -1069,16 +1420,14 @@ sink(int argc, char **argv)
/* Handle copying from a read-only
directory */
mod_flag = 1;
- if (mkdir(np, mode | S_IRWXU) < 0)
+ if (mkdir(np, mode | S_IRWXU) == -1)
goto bad;
}
vect[0] = xstrdup(np);
- sink(1, vect);
+ sink(1, vect, src);
if (setimes) {
setimes = 0;
- if (utimes(vect[0], tv) < 0)
- run_err("%s: set times: %s",
- vect[0], strerror(errno));
+ (void) utimes(vect[0], tv);
}
if (mod_flag)
(void) chmod(vect[0], mode);
@@ -1087,7 +1436,7 @@ sink(int argc, char **argv)
}
omode = mode;
mode |= S_IWUSR;
- if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) < 0) {
+ if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) == -1) {
bad: run_err("%s: %s", np, strerror(errno));
continue;
}
@@ -1097,8 +1446,13 @@ bad: run_err("%s: %s", np, strerror(errno));
continue;
}
cp = bp->buf;
- wrerr = NO;
+ wrerr = 0;
+ /*
+ * NB. do not use run_err() unless immediately followed by
+ * exit() below as it may send a spurious reply that might
+ * desyncronise us from the peer. Use note_err() instead.
+ */
statbytes = 0;
if (showprogress)
start_progress_meter(curfile, size, &statbytes);
@@ -1123,11 +1477,12 @@ bad: run_err("%s: %s", np, strerror(errno));
if (count == bp->cnt) {
/* Keep reading so we stay sync'd up. */
- if (wrerr == NO) {
+ if (!wrerr) {
if (atomicio(vwrite, ofd, bp->buf,
count) != count) {
- wrerr = YES;
- wrerrno = errno;
+ note_err("%s: %s", np,
+ strerror(errno));
+ wrerr = 1;
}
}
count = 0;
@@ -1135,18 +1490,14 @@ bad: run_err("%s: %s", np, strerror(errno));
}
}
unset_nonblock(remin);
- if (showprogress)
- stop_progress_meter();
- if (count != 0 && wrerr == NO &&
+ if (count != 0 && !wrerr &&
atomicio(vwrite, ofd, bp->buf, count) != count) {
- wrerr = YES;
- wrerrno = errno;
- }
- if (wrerr == NO && (!exists || S_ISREG(stb.st_mode)) &&
- ftruncate(ofd, size) != 0) {
- run_err("%s: truncate: %s", np, strerror(errno));
- wrerr = DISPLAYED;
+ note_err("%s: %s", np, strerror(errno));
+ wrerr = 1;
}
+ if (!wrerr && (!exists || S_ISREG(stb.st_mode)) &&
+ ftruncate(ofd, size) != 0)
+ note_err("%s: truncate: %s", np, strerror(errno));
if (pflag) {
if (exists || omode != mode)
#ifdef HAVE_FCHMOD
@@ -1154,9 +1505,8 @@ bad: run_err("%s: %s", np, strerror(errno));
#else /* HAVE_FCHMOD */
if (chmod(np, omode)) {
#endif /* HAVE_FCHMOD */
- run_err("%s: set mode: %s",
+ note_err("%s: set mode: %s",
np, strerror(errno));
- wrerr = DISPLAYED;
}
} else {
if (!exists && omode != mode)
@@ -1165,36 +1515,35 @@ bad: run_err("%s: %s", np, strerror(errno));
#else /* HAVE_FCHMOD */
if (chmod(np, omode & ~mask)) {
#endif /* HAVE_FCHMOD */
- run_err("%s: set mode: %s",
+ note_err("%s: set mode: %s",
np, strerror(errno));
- wrerr = DISPLAYED;
}
}
- if (close(ofd) == -1) {
- wrerr = YES;
- wrerrno = errno;
- }
+ if (close(ofd) == -1)
+ note_err(np, "%s: close: %s", np, strerror(errno));
(void) response();
- if (setimes && wrerr == NO) {
+ if (showprogress)
+ stop_progress_meter();
+ if (setimes && !wrerr) {
setimes = 0;
- if (utimes(np, tv) < 0) {
- run_err("%s: set times: %s",
+ if (utimes(np, tv) == -1) {
+ note_err("%s: set times: %s",
np, strerror(errno));
- wrerr = DISPLAYED;
}
}
- switch (wrerr) {
- case YES:
- run_err("%s: %s", np, strerror(wrerrno));
- break;
- case NO:
+ /* If no error was noted then signal success for this file */
+ if (note_err(NULL) == 0)
(void) atomicio(vwrite, remout, "", 1);
- break;
- case DISPLAYED:
- break;
- }
}
+done:
+ for (n = 0; n < npatterns; n++)
+ free(patterns[n]);
+ free(patterns);
+ return;
screwup:
+ for (n = 0; n < npatterns; n++)
+ free(patterns[n]);
+ free(patterns);
run_err("protocol error: %s", why);
exit(1);
}
@@ -1202,7 +1551,7 @@ screwup:
int
response(void)
{
- char ch, *cp, resp, rbuf[2048];
+ char ch, *cp, resp, rbuf[2048], visbuf[2048];
if (atomicio(read, remin, &resp, sizeof(resp)) != sizeof(resp))
lostconn(0);
@@ -1222,8 +1571,13 @@ response(void)
*cp++ = ch;
} while (cp < &rbuf[sizeof(rbuf) - 1] && ch != '\n');
- if (!iamremote)
- (void) atomicio(vwrite, STDERR_FILENO, rbuf, cp - rbuf);
+ if (!iamremote) {
+ cp[-1] = '\0';
+ (void) snmprintf(visbuf, sizeof(visbuf),
+ NULL, "%s\n", rbuf);
+ (void) atomicio(vwrite, STDERR_FILENO,
+ visbuf, strlen(visbuf));
+ }
++errs;
if (resp == 1)
return (-1);
@@ -1236,9 +1590,9 @@ void
usage(void)
{
(void) fprintf(stderr,
- "usage: scp [-12346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]\n"
- " [-l limit] [-o ssh_option] [-P port] [-S program]\n"
- " [[user@]host1:]file1 ... [[user@]host2:]file2\n");
+ "usage: scp [-346BCpqrTv] [-c cipher] [-F ssh_config] [-i identity_file]\n"
+ " [-J destination] [-l limit] [-o ssh_option] [-P port]\n"
+ " [-S program] source ... target\n");
exit(1);
}
@@ -1261,12 +1615,44 @@ run_err(const char *fmt,...)
if (!iamremote) {
va_start(ap, fmt);
- vfprintf(stderr, fmt, ap);
+ vfmprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
}
}
+/*
+ * Notes a sink error for sending at the end of a file transfer. Returns 0 if
+ * no error has been noted or -1 otherwise. Use note_err(NULL) to flush
+ * any active error at the end of the transfer.
+ */
+int
+note_err(const char *fmt, ...)
+{
+ static char *emsg;
+ va_list ap;
+
+ /* Replay any previously-noted error */
+ if (fmt == NULL) {
+ if (emsg == NULL)
+ return 0;
+ run_err("%s", emsg);
+ free(emsg);
+ emsg = NULL;
+ return -1;
+ }
+
+ errs++;
+ /* Prefer first-noted error */
+ if (emsg != NULL)
+ return -1;
+
+ va_start(ap, fmt);
+ vasnmprintf(&emsg, INT_MAX, NULL, fmt, ap);
+ va_end(ap);
+ return -1;
+}
+
void
verifydir(char *cp)
{
@@ -1307,7 +1693,7 @@ okname(char *cp0)
} while (*++cp);
return (1);
-bad: fprintf(stderr, "%s: invalid user name\n", cp0);
+bad: fmprintf(stderr, "%s: invalid user name\n", cp0);
return (0);
}
@@ -1318,11 +1704,11 @@ allocbuf(BUF *bp, int fd, int blksize)
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
struct stat stb;
- if (fstat(fd, &stb) < 0) {
+ if (fstat(fd, &stb) == -1) {
run_err("fstat: %s", strerror(errno));
return (0);
}
- size = roundup(stb.st_blksize, blksize);
+ size = ROUNDUP(stb.st_blksize, blksize);
if (size == 0)
size = blksize;
#else /* HAVE_STRUCT_STAT_ST_BLKSIZE */
@@ -1330,11 +1716,7 @@ allocbuf(BUF *bp, int fd, int blksize)
#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
if (bp->cnt >= size)
return (bp);
- if (bp->buf == NULL)
- bp->buf = xmalloc(size);
- else
- bp->buf = xrealloc(bp->buf, 1, size);
- memset(bp->buf, 0, size);
+ bp->buf = xrecallocarray(bp->buf, bp->cnt, size, 1);
bp->cnt = size;
return (bp);
}