X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/a056dfa28a5a23ff846545d02289a1de899949ae..HEAD:/scp.c diff --git a/scp.c b/scp.c index 1407aa71..09efb82a 100644 --- a/scp.c +++ b/scp.c @@ -1,3 +1,4 @@ +/* $OpenBSD: scp.c,v 1.165 2009/12/20 07:28:36 guenther Exp $ */ /* * scp - secure remote copy. This is basically patched BSD rcp which * uses ssh to do the data transfer (instead of using rcmd). @@ -71,7 +72,40 @@ */ #include "includes.h" -RCSID("$OpenBSD: scp.c,v 1.125 2005/07/27 10:39:03 dtucker Exp $"); + +#include +#include +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef HAVE_POLL_H +#include +#else +# ifdef HAVE_SYS_POLL_H +# include +# endif +#endif +#ifdef HAVE_SYS_TIME_H +# include +#endif +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) +#include +#endif #include "xmalloc.h" #include "atomicio.h" @@ -82,6 +116,10 @@ RCSID("$OpenBSD: scp.c,v 1.125 2005/07/27 10:39:03 dtucker Exp $"); extern char *__progname; +#define COPY_BUFLEN 16384 + +int do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout); + void bwlimit(int); /* Struct for addargs */ @@ -118,6 +156,48 @@ killchild(int signo) exit(1); } +static int +do_local_cmd(arglist *a) +{ + u_int i; + int status; + pid_t pid; + + if (a->num == 0) + fatal("do_local_cmd: no arguments"); + + if (verbose_mode) { + fprintf(stderr, "Executing:"); + for (i = 0; i < a->num; i++) + fprintf(stderr, " %s", a->list[i]); + fprintf(stderr, "\n"); + } + if ((pid = fork()) == -1) + fatal("do_local_cmd: fork: %s", strerror(errno)); + + if (pid == 0) { + execvp(a->list[0], a->list); + perror(a->list[0]); + exit(1); + } + + do_cmd_pid = pid; + signal(SIGTERM, killchild); + signal(SIGINT, killchild); + signal(SIGHUP, killchild); + + while (waitpid(pid, &status, 0) == -1) + if (errno != EINTR) + fatal("do_local_cmd: waitpid: %s", strerror(errno)); + + do_cmd_pid = -1; + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + return (-1); + + return (0); +} + /* * This function executes the given command as the specified user on the * given host. This returns < 0 if execution fails, and >= 0 otherwise. This @@ -125,7 +205,7 @@ killchild(int signo) */ int -do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout, int argc) +do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout) { int pin[2], pout[2], reserved[2]; @@ -139,7 +219,8 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout, int argc) * Reserve two descriptors so that the real pipes won't get * descriptors 0 and 1 because that will screw up dup2 below. */ - pipe(reserved); + if (pipe(reserved) < 0) + fatal("pipe: %s", strerror(errno)); /* Create a socket pair for communicating with ssh. */ if (pipe(pin) < 0) @@ -162,9 +243,12 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout, int argc) close(pin[0]); close(pout[1]); - args.list[0] = ssh_program; - if (remuser != NULL) - addargs(&args, "-l%s", remuser); + replacearg(&args, 0, "%s", ssh_program); + if (remuser != NULL) { + addargs(&args, "-l"); + addargs(&args, "%s", remuser); + } + addargs(&args, "--"); addargs(&args, "%s", host); addargs(&args, "%s", cmd); @@ -192,7 +276,6 @@ typedef struct { BUF *allocbuf(BUF *, int, int); void lostconn(int); -void nospace(void); int okname(char *); void run_err(const char *,...); void verifydir(char *); @@ -211,23 +294,35 @@ void sink(int, char *[]); void source(int, char *[]); void tolocal(int, char *[]); void toremote(char *, int, char *[]); +size_t scpio(ssize_t (*)(int, void *, size_t), int, void *, size_t, off_t *); void usage(void); int main(int argc, char **argv) { - int ch, fflag, tflag, status; + int ch, fflag, tflag, status, n; double speed; - char *targ, *endp; + char *targ, *endp, **newargv; extern char *optarg; extern int optind; + /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ + sanitise_stdfd(); + + /* Copy argv, because we modify it */ + newargv = xcalloc(MAX(argc + 1, 1), sizeof(*newargv)); + for (n = 0; n < argc; n++) + newargv[n] = xstrdup(argv[n]); + argv = newargv; + __progname = ssh_get_progname(argv[0]); + memset(&args, '\0', sizeof(args)); args.list = NULL; - addargs(&args, "ssh"); /* overwritten with ssh_program */ + addargs(&args, "%s", ssh_program); addargs(&args, "-x"); addargs(&args, "-oForwardAgent no"); + addargs(&args, "-oPermitLocalCommand no"); addargs(&args, "-oClearAllForwardings yes"); fflag = tflag = 0; @@ -245,10 +340,12 @@ main(int argc, char **argv) case 'c': case 'i': case 'F': - addargs(&args, "-%c%s", ch, optarg); + addargs(&args, "-%c", ch); + addargs(&args, "%s", optarg); break; case 'P': - addargs(&args, "-p%s", optarg); + addargs(&args, "-p"); + addargs(&args, "%s", optarg); break; case 'B': addargs(&args, "-oBatchmode yes"); @@ -301,7 +398,7 @@ main(int argc, char **argv) if ((pwd = getpwuid(userid = getuid())) == NULL) fatal("unknown user %u", (u_int) userid); - if (!isatty(STDERR_FILENO)) + if (!isatty(STDOUT_FILENO)) showprogress = 0; remin = STDIN_FILENO; @@ -336,13 +433,13 @@ main(int argc, char **argv) if ((targ = colon(argv[argc - 1]))) /* Dest is remote host. */ toremote(targ, argc, argv); else { - tolocal(argc, argv); /* Dest is local host. */ if (targetshouldbedirectory) verifydir(argv[argc - 1]); + tolocal(argc, argv); /* Dest is local host. */ } /* * Finally check the exit status of the ssh process, if one was forked - * and no error has occured yet + * and no error has occurred yet */ if (do_cmd_pid != -1 && errs == 0) { if (remin != -1) @@ -359,11 +456,52 @@ main(int argc, char **argv) exit(errs != 0); } +/* + * atomicio-like wrapper that also applies bandwidth limits and updates + * the progressmeter counter. + */ +size_t +scpio(ssize_t (*f)(int, void *, size_t), int fd, void *_p, size_t l, off_t *c) +{ + u_char *p = (u_char *)_p; + size_t offset; + ssize_t r; + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = f == read ? POLLIN : POLLOUT; + for (offset = 0; offset < l;) { + r = f(fd, p + offset, l - offset); + if (r == 0) { + errno = EPIPE; + return offset; + } + if (r < 0) { + if (errno == EINTR) + continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) { + (void)poll(&pfd, 1, -1); /* Ignore errors */ + continue; + } + return offset; + } + offset += (size_t)r; + *c += (off_t)r; + if (limit_rate) + bwlimit(r); + } + return offset; +} + void toremote(char *targ, int argc, char **argv) { - int i, len; char *bp, *host, *src, *suser, *thost, *tuser, *arg; + arglist alist; + int i; + + memset(&alist, '\0', sizeof(alist)); + alist.list = NULL; *targ++ = 0; if (*targ == 0) @@ -381,64 +519,55 @@ toremote(char *targ, int argc, char **argv) tuser = NULL; } + if (tuser != NULL && !okname(tuser)) { + xfree(arg); + return; + } + for (i = 0; i < argc - 1; i++) { src = colon(argv[i]); if (src) { /* remote to remote */ - static char *ssh_options = - "-x -o'ClearAllForwardings yes'"; + freeargs(&alist); + addargs(&alist, "%s", ssh_program); + if (verbose_mode) + addargs(&alist, "-v"); + addargs(&alist, "-x"); + addargs(&alist, "-oClearAllForwardings yes"); + addargs(&alist, "-n"); + *src++ = 0; if (*src == 0) src = "."; host = strrchr(argv[i], '@'); - len = strlen(ssh_program) + strlen(argv[i]) + - strlen(src) + (tuser ? strlen(tuser) : 0) + - strlen(thost) + strlen(targ) + - strlen(ssh_options) + CMDNEEDS + 20; - bp = xmalloc(len); + if (host) { *host++ = 0; host = cleanhostname(host); suser = argv[i]; if (*suser == '\0') suser = pwd->pw_name; - else if (!okname(suser)) { - xfree(bp); + else if (!okname(suser)) continue; - } - if (tuser && !okname(tuser)) { - xfree(bp); - continue; - } - snprintf(bp, len, - "%s%s %s -n " - "-l %s %s %s %s '%s%s%s:%s'", - ssh_program, verbose_mode ? " -v" : "", - ssh_options, suser, host, cmd, src, - tuser ? tuser : "", tuser ? "@" : "", - thost, targ); + addargs(&alist, "-l"); + addargs(&alist, "%s", suser); } else { host = cleanhostname(argv[i]); - snprintf(bp, len, - "exec %s%s %s -n %s " - "%s %s '%s%s%s:%s'", - ssh_program, verbose_mode ? " -v" : "", - ssh_options, host, cmd, src, - tuser ? tuser : "", tuser ? "@" : "", - thost, targ); } - if (verbose_mode) - fprintf(stderr, "Executing: %s\n", bp); - if (system(bp) != 0) + addargs(&alist, "--"); + addargs(&alist, "%s", host); + addargs(&alist, "%s", cmd); + addargs(&alist, "%s", src); + addargs(&alist, "%s%s%s:%s", + tuser ? tuser : "", tuser ? "@" : "", + thost, targ); + if (do_local_cmd(&alist) != 0) errs = 1; - (void) xfree(bp); } else { /* local to remote */ if (remin == -1) { - len = strlen(targ) + CMDNEEDS + 20; - bp = xmalloc(len); - (void) snprintf(bp, len, "%s -t %s", cmd, targ); + xasprintf(&bp, "%s -t -- %s", cmd, targ); host = cleanhostname(thost); if (do_cmd(host, tuser, bp, &remin, - &remout, argc) < 0) + &remout) < 0) exit(1); if (response() < 0) exit(1); @@ -447,27 +576,32 @@ toremote(char *targ, int argc, char **argv) source(1, argv + i); } } + xfree(arg); } void tolocal(int argc, char **argv) { - int i, len; char *bp, *host, *src, *suser; + arglist alist; + int i; + + memset(&alist, '\0', sizeof(alist)); + alist.list = NULL; for (i = 0; i < argc - 1; i++) { if (!(src = colon(argv[i]))) { /* Local to local. */ - len = strlen(_PATH_CP) + strlen(argv[i]) + - strlen(argv[argc - 1]) + 20; - bp = xmalloc(len); - (void) snprintf(bp, len, "exec %s%s%s %s %s", _PATH_CP, - iamrecursive ? " -r" : "", pflag ? " -p" : "", - argv[i], argv[argc - 1]); - if (verbose_mode) - fprintf(stderr, "Executing: %s\n", bp); - if (system(bp)) + freeargs(&alist); + addargs(&alist, "%s", _PATH_CP); + if (iamrecursive) + addargs(&alist, "-r"); + if (pflag) + addargs(&alist, "-p"); + addargs(&alist, "--"); + addargs(&alist, "%s", argv[i]); + addargs(&alist, "%s", argv[argc-1]); + if (do_local_cmd(&alist)) ++errs; - (void) xfree(bp); continue; } *src++ = 0; @@ -483,10 +617,8 @@ tolocal(int argc, char **argv) suser = pwd->pw_name; } host = cleanhostname(host); - len = strlen(src) + CMDNEEDS + 20; - bp = xmalloc(len); - (void) snprintf(bp, len, "%s -f %s", cmd, src); - if (do_cmd(host, suser, bp, &remin, &remout, argc) < 0) { + xasprintf(&bp, "%s -f -- %s", cmd, src); + if (do_cmd(host, suser, bp, &remin, &remout) < 0) { (void) xfree(bp); ++errs; continue; @@ -504,10 +636,10 @@ source(int argc, char **argv) struct stat stb; static BUF buffer; BUF *bp; - off_t i, amt, statbytes; - size_t result; + off_t i, statbytes; + size_t amt; int fd = -1, haderr, indx; - char *last, *name, buf[2048]; + char *last, *name, buf[2048], encname[MAXPATHLEN]; int len; for (indx = 0; indx < argc; ++indx) { @@ -516,17 +648,21 @@ 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) + goto syserr; if (strchr(name, '\n') != NULL) { - run_err("%s: skipping, filename contains a newline", - name); - goto next; + strnvis(encname, name, sizeof(encname), VIS_NL); + name = encname; } - if ((fd = open(name, O_RDONLY, 0)) < 0) - goto syserr; if (fstat(fd, &stb) < 0) { syserr: run_err("%s: %s", name, strerror(errno)); goto next; } + if (stb.st_size < 0) { + run_err("%s: %s", name, "Negative file size"); + goto next; + } + unset_nonblock(fd); switch (stb.st_mode & S_IFMT) { case S_IFREG: break; @@ -551,8 +687,14 @@ syserr: run_err("%s: %s", name, strerror(errno)); * versions expecting microseconds. */ (void) snprintf(buf, sizeof buf, "T%lu 0 %lu 0\n", - (u_long) stb.st_mtime, - (u_long) stb.st_atime); + (u_long) (stb.st_mtime < 0 ? 0 : stb.st_mtime), + (u_long) (stb.st_atime < 0 ? 0 : stb.st_atime)); + if (verbose_mode) { + fprintf(stderr, "File mtime %ld atime %ld\n", + (long)stb.st_mtime, (long)stb.st_atime); + fprintf(stderr, "Sending file timestamps: %s", + buf); + } (void) atomicio(vwrite, remout, buf, strlen(buf)); if (response() < 0) goto next; @@ -560,45 +702,49 @@ syserr: run_err("%s: %s", name, strerror(errno)); #define FILEMODEMASK (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO) snprintf(buf, sizeof buf, "C%04o %lld %s\n", (u_int) (stb.st_mode & FILEMODEMASK), - (int64_t)stb.st_size, last); + (long long)stb.st_size, last); if (verbose_mode) { fprintf(stderr, "Sending file modes: %s", buf); } (void) atomicio(vwrite, remout, buf, strlen(buf)); if (response() < 0) goto next; - if ((bp = allocbuf(&buffer, fd, 2048)) == NULL) { -next: (void) close(fd); + if ((bp = allocbuf(&buffer, fd, COPY_BUFLEN)) == NULL) { +next: if (fd != -1) { + (void) close(fd); + fd = -1; + } continue; } if (showprogress) start_progress_meter(curfile, stb.st_size, &statbytes); - /* Keep writing after an error so that we stay sync'd up. */ + set_nonblock(remout); for (haderr = i = 0; i < stb.st_size; i += bp->cnt) { amt = bp->cnt; - if (i + amt > stb.st_size) + if (i + (off_t)amt > stb.st_size) amt = stb.st_size - i; if (!haderr) { - result = atomicio(read, fd, bp->buf, amt); - if (result != amt) + if (atomicio(read, fd, bp->buf, amt) != amt) haderr = errno; } - if (haderr) - (void) atomicio(vwrite, remout, bp->buf, amt); - else { - result = atomicio(vwrite, remout, bp->buf, amt); - if (result != amt) - haderr = errno; - statbytes += result; + /* Keep writing after error to retain sync */ + if (haderr) { + (void)atomicio(vwrite, remout, bp->buf, amt); + continue; } - if (limit_rate) - bwlimit(amt); + if (scpio(vwrite, remout, bp->buf, amt, + &statbytes) != amt) + haderr = errno; } + unset_nonblock(remout); if (showprogress) stop_progress_meter(); - if (close(fd) < 0 && !haderr) - haderr = errno; + if (fd != -1) { + if (close(fd) < 0 && !haderr) + haderr = errno; + fd = -1; + } if (!haderr) (void) atomicio(vwrite, remout, "", 1); else @@ -696,10 +842,10 @@ bwlimit(int amount) thresh /= 2; if (thresh < 2048) thresh = 2048; - } else if (bwend.tv_usec < 100) { + } else if (bwend.tv_usec < 10000) { thresh *= 2; - if (thresh > 32768) - thresh = 32768; + if (thresh > COPY_BUFLEN * 4) + thresh = COPY_BUFLEN * 4; } TIMEVAL_TO_TIMESPEC(&bwend, &ts); @@ -725,7 +871,8 @@ sink(int argc, char **argv) BUF *bp; off_t i; size_t j, count; - int amt, exists, first, mask, mode, ofd, omode; + int amt, exists, first, ofd; + mode_t mode, omode, mask; off_t size, statbytes; int setimes, targisdir, wrerrno = 0; char ch, *cp, *np, *targ, *why, *vect[1], buf[2048]; @@ -889,7 +1036,7 @@ bad: run_err("%s: %s", np, strerror(errno)); continue; } (void) atomicio(vwrite, remout, "", 1); - if ((bp = allocbuf(&buffer, ofd, 4096)) == NULL) { + if ((bp = allocbuf(&buffer, ofd, COPY_BUFLEN)) == NULL) { (void) close(ofd); continue; } @@ -899,26 +1046,24 @@ bad: run_err("%s: %s", np, strerror(errno)); statbytes = 0; if (showprogress) start_progress_meter(curfile, size, &statbytes); - for (count = i = 0; i < size; i += 4096) { - amt = 4096; + set_nonblock(remin); + for (count = i = 0; i < size; i += bp->cnt) { + amt = bp->cnt; if (i + amt > size) amt = size - i; count += amt; do { - j = atomicio(read, remin, cp, amt); + j = scpio(read, remin, cp, amt, &statbytes); if (j == 0) { - run_err("%s", j ? strerror(errno) : + run_err("%s", j != EPIPE ? + strerror(errno) : "dropped connection"); exit(1); } amt -= j; cp += j; - statbytes += j; } while (amt > 0); - if (limit_rate) - bwlimit(4096); - if (count == bp->cnt) { /* Keep reading so we stay sync'd up. */ if (wrerr == NO) { @@ -932,6 +1077,7 @@ bad: run_err("%s: %s", np, strerror(errno)); cp = bp->buf; } } + unset_nonblock(remin); if (showprogress) stop_progress_meter(); if (count != 0 && wrerr == NO && @@ -939,7 +1085,8 @@ bad: run_err("%s: %s", np, strerror(errno)); wrerr = YES; wrerrno = errno; } - if (wrerr == NO && ftruncate(ofd, size) != 0) { + if (wrerr == NO && (!exists || S_ISREG(stb.st_mode)) && + ftruncate(ofd, size) != 0) { run_err("%s: truncate: %s", np, strerror(errno)); wrerr = DISPLAYED; } @@ -1034,7 +1181,7 @@ usage(void) (void) fprintf(stderr, "usage: scp [-1246BCpqrv] [-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"); + " [[user@]host1:]file1 ... [[user@]host2:]file2\n"); exit(1); } @@ -1045,15 +1192,15 @@ run_err(const char *fmt,...) va_list ap; ++errs; - if (fp == NULL && !(fp = fdopen(remout, "w"))) - return; - (void) fprintf(fp, "%c", 0x01); - (void) fprintf(fp, "scp: "); - va_start(ap, fmt); - (void) vfprintf(fp, fmt, ap); - va_end(ap); - (void) fprintf(fp, "\n"); - (void) fflush(fp); + if (fp != NULL || (remout != -1 && (fp = fdopen(remout, "w")))) { + (void) fprintf(fp, "%c", 0x01); + (void) fprintf(fp, "scp: "); + va_start(ap, fmt); + (void) vfprintf(fp, fmt, ap); + va_end(ap); + (void) fprintf(fp, "\n"); + (void) fflush(fp); + } if (!iamremote) { va_start(ap, fmt); @@ -1129,7 +1276,7 @@ allocbuf(BUF *bp, int fd, int blksize) if (bp->buf == NULL) bp->buf = xmalloc(size); else - bp->buf = xrealloc(bp->buf, size); + bp->buf = xrealloc(bp->buf, 1, size); memset(bp->buf, 0, size); bp->cnt = size; return (bp);