scp.c revision 215116
1215116Sdes/* $OpenBSD: scp.c,v 1.166 2010/07/01 13:06:59 millert Exp $ */
257429Smarkm/*
365668Skris * scp - secure remote copy.  This is basically patched BSD rcp which
465668Skris * uses ssh to do the data transfer (instead of using rcmd).
560573Skris *
665668Skris * NOTE: This version should NOT be suid root.  (This uses ssh to
765668Skris * do the transfer and ssh has the necessary privileges.)
860573Skris *
957429Smarkm * 1995 Timo Rinne <tri@iki.fi>, Tatu Ylonen <ylo@cs.hut.fi>
1060573Skris *
1165668Skris * As far as I am concerned, the code I have written for this software
1265668Skris * can be used freely for any purpose.  Any derived versions of this
1365668Skris * software must be clearly marked as such, and if the derived work is
1465668Skris * incompatible with the protocol description in the RFC file, it must be
1565668Skris * called by a name other than "ssh" or "Secure Shell".
1665668Skris */
1765668Skris/*
1876259Sgreen * Copyright (c) 1999 Theo de Raadt.  All rights reserved.
1976259Sgreen * Copyright (c) 1999 Aaron Campbell.  All rights reserved.
2065668Skris *
2165668Skris * Redistribution and use in source and binary forms, with or without
2265668Skris * modification, are permitted provided that the following conditions
2365668Skris * are met:
2465668Skris * 1. Redistributions of source code must retain the above copyright
2565668Skris *    notice, this list of conditions and the following disclaimer.
2665668Skris * 2. Redistributions in binary form must reproduce the above copyright
2765668Skris *    notice, this list of conditions and the following disclaimer in the
2865668Skris *    documentation and/or other materials provided with the distribution.
2965668Skris *
3065668Skris * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
3165668Skris * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
3265668Skris * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
3365668Skris * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
3465668Skris * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
3565668Skris * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
3665668Skris * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3765668Skris * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3865668Skris * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3965668Skris * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4065668Skris */
4157429Smarkm
4257429Smarkm/*
4365668Skris * Parts from:
4465668Skris *
4557429Smarkm * Copyright (c) 1983, 1990, 1992, 1993, 1995
4657429Smarkm *	The Regents of the University of California.  All rights reserved.
4757429Smarkm *
4857429Smarkm * Redistribution and use in source and binary forms, with or without
4957429Smarkm * modification, are permitted provided that the following conditions
5057429Smarkm * are met:
5157429Smarkm * 1. Redistributions of source code must retain the above copyright
5257429Smarkm *    notice, this list of conditions and the following disclaimer.
5357429Smarkm * 2. Redistributions in binary form must reproduce the above copyright
5457429Smarkm *    notice, this list of conditions and the following disclaimer in the
5557429Smarkm *    documentation and/or other materials provided with the distribution.
56124211Sdes * 3. Neither the name of the University nor the names of its contributors
5757429Smarkm *    may be used to endorse or promote products derived from this software
5857429Smarkm *    without specific prior written permission.
5957429Smarkm *
6057429Smarkm * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
6157429Smarkm * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
6257429Smarkm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
6357429Smarkm * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
6457429Smarkm * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
6557429Smarkm * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
6657429Smarkm * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
6757429Smarkm * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
6857429Smarkm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
6957429Smarkm * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
7057429Smarkm * SUCH DAMAGE.
7157429Smarkm *
7257429Smarkm */
7357429Smarkm
7457429Smarkm#include "includes.h"
7557429Smarkm
76162856Sdes#include <sys/types.h>
77162856Sdes#include <sys/param.h>
78162856Sdes#ifdef HAVE_SYS_STAT_H
79162856Sdes# include <sys/stat.h>
80162856Sdes#endif
81181111Sdes#ifdef HAVE_POLL_H
82181111Sdes#include <poll.h>
83181111Sdes#else
84181111Sdes# ifdef HAVE_SYS_POLL_H
85181111Sdes#  include <sys/poll.h>
86181111Sdes# endif
87181111Sdes#endif
88162856Sdes#ifdef HAVE_SYS_TIME_H
89162856Sdes# include <sys/time.h>
90162856Sdes#endif
91162856Sdes#include <sys/wait.h>
92162856Sdes#include <sys/uio.h>
93162856Sdes
94162856Sdes#include <ctype.h>
95162856Sdes#include <dirent.h>
96162856Sdes#include <errno.h>
97162856Sdes#include <fcntl.h>
98162856Sdes#include <pwd.h>
99162856Sdes#include <signal.h>
100162856Sdes#include <stdarg.h>
101162856Sdes#include <stdio.h>
102162856Sdes#include <stdlib.h>
103162856Sdes#include <string.h>
104162856Sdes#include <time.h>
105162856Sdes#include <unistd.h>
106181111Sdes#if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H)
107181111Sdes#include <vis.h>
108181111Sdes#endif
109162856Sdes
11057429Smarkm#include "xmalloc.h"
11176259Sgreen#include "atomicio.h"
11276259Sgreen#include "pathnames.h"
11376259Sgreen#include "log.h"
11492555Sdes#include "misc.h"
115113911Sdes#include "progressmeter.h"
11657429Smarkm
11798937Sdesextern char *__progname;
11898937Sdes
119181111Sdes#define COPY_BUFLEN	16384
120181111Sdes
121162856Sdesint do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout);
122162856Sdes
123113911Sdesvoid bwlimit(int);
12457429Smarkm
12592555Sdes/* Struct for addargs */
12692555Sdesarglist args;
12769587Sgreen
128113911Sdes/* Bandwidth limit */
129126277Sdesoff_t limit_rate = 0;
13057429Smarkm
13157429Smarkm/* Name of current file being transferred. */
13257429Smarkmchar *curfile;
13357429Smarkm
13457429Smarkm/* This is set to non-zero to enable verbose mode. */
13557429Smarkmint verbose_mode = 0;
13657429Smarkm
13757429Smarkm/* This is set to zero if the progressmeter is not desired. */
13857429Smarkmint showprogress = 1;
13957429Smarkm
14065668Skris/* This is the program to execute for the secured connection. ("ssh" or -S) */
14176259Sgreenchar *ssh_program = _PATH_SSH_PROGRAM;
14265668Skris
143113911Sdes/* This is used to store the pid of ssh_program */
144124211Sdespid_t do_cmd_pid = -1;
145113911Sdes
146124211Sdesstatic void
147124211Sdeskillchild(int signo)
148124211Sdes{
149147005Sdes	if (do_cmd_pid > 1) {
150149753Sdes		kill(do_cmd_pid, signo ? signo : SIGTERM);
151147005Sdes		waitpid(do_cmd_pid, NULL, 0);
152147005Sdes	}
153124211Sdes
154149753Sdes	if (signo)
155149753Sdes		_exit(1);
156149753Sdes	exit(1);
157124211Sdes}
158124211Sdes
159215116Sdesstatic void
160215116Sdessuspchild(int signo)
161215116Sdes{
162215116Sdes	int status;
163215116Sdes
164215116Sdes	if (do_cmd_pid > 1) {
165215116Sdes		kill(do_cmd_pid, signo);
166215116Sdes		while (waitpid(do_cmd_pid, &status, WUNTRACED) == -1 &&
167215116Sdes		    errno == EINTR)
168215116Sdes			;
169215116Sdes		kill(getpid(), SIGSTOP);
170215116Sdes	}
171215116Sdes}
172215116Sdes
173157019Sdesstatic int
174157019Sdesdo_local_cmd(arglist *a)
175157019Sdes{
176157019Sdes	u_int i;
177157019Sdes	int status;
178157019Sdes	pid_t pid;
179157019Sdes
180157019Sdes	if (a->num == 0)
181157019Sdes		fatal("do_local_cmd: no arguments");
182157019Sdes
183157019Sdes	if (verbose_mode) {
184157019Sdes		fprintf(stderr, "Executing:");
185157019Sdes		for (i = 0; i < a->num; i++)
186157019Sdes			fprintf(stderr, " %s", a->list[i]);
187157019Sdes		fprintf(stderr, "\n");
188157019Sdes	}
189157019Sdes	if ((pid = fork()) == -1)
190157019Sdes		fatal("do_local_cmd: fork: %s", strerror(errno));
191157019Sdes
192157019Sdes	if (pid == 0) {
193157019Sdes		execvp(a->list[0], a->list);
194157019Sdes		perror(a->list[0]);
195157019Sdes		exit(1);
196157019Sdes	}
197157019Sdes
198157019Sdes	do_cmd_pid = pid;
199157019Sdes	signal(SIGTERM, killchild);
200157019Sdes	signal(SIGINT, killchild);
201157019Sdes	signal(SIGHUP, killchild);
202157019Sdes
203157019Sdes	while (waitpid(pid, &status, 0) == -1)
204157019Sdes		if (errno != EINTR)
205157019Sdes			fatal("do_local_cmd: waitpid: %s", strerror(errno));
206157019Sdes
207157019Sdes	do_cmd_pid = -1;
208157019Sdes
209157019Sdes	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
210157019Sdes		return (-1);
211157019Sdes
212157019Sdes	return (0);
213157019Sdes}
214157019Sdes
21557429Smarkm/*
21657429Smarkm * This function executes the given command as the specified user on the
21757429Smarkm * given host.  This returns < 0 if execution fails, and >= 0 otherwise. This
21857429Smarkm * assigns the input and output file descriptors on success.
21957429Smarkm */
22057429Smarkm
22160573Skrisint
222162856Sdesdo_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout)
22357429Smarkm{
22457429Smarkm	int pin[2], pout[2], reserved[2];
22557429Smarkm
22657429Smarkm	if (verbose_mode)
22792555Sdes		fprintf(stderr,
22892555Sdes		    "Executing: program %s host %s, user %s, command %s\n",
22992555Sdes		    ssh_program, host,
23092555Sdes		    remuser ? remuser : "(unspecified)", cmd);
23157429Smarkm
23257429Smarkm	/*
23357429Smarkm	 * Reserve two descriptors so that the real pipes won't get
23457429Smarkm	 * descriptors 0 and 1 because that will screw up dup2 below.
23557429Smarkm	 */
236162856Sdes	if (pipe(reserved) < 0)
237162856Sdes		fatal("pipe: %s", strerror(errno));
23857429Smarkm
23957429Smarkm	/* Create a socket pair for communicating with ssh. */
24057429Smarkm	if (pipe(pin) < 0)
24157429Smarkm		fatal("pipe: %s", strerror(errno));
24257429Smarkm	if (pipe(pout) < 0)
24357429Smarkm		fatal("pipe: %s", strerror(errno));
24457429Smarkm
24557429Smarkm	/* Free the reserved descriptors. */
24657429Smarkm	close(reserved[0]);
24757429Smarkm	close(reserved[1]);
24857429Smarkm
249215116Sdes	signal(SIGTSTP, suspchild);
250215116Sdes	signal(SIGTTIN, suspchild);
251215116Sdes	signal(SIGTTOU, suspchild);
252215116Sdes
253124211Sdes	/* Fork a child to execute the command on the remote host using ssh. */
254113911Sdes	do_cmd_pid = fork();
255113911Sdes	if (do_cmd_pid == 0) {
25657429Smarkm		/* Child. */
25757429Smarkm		close(pin[1]);
25857429Smarkm		close(pout[0]);
25957429Smarkm		dup2(pin[0], 0);
26057429Smarkm		dup2(pout[1], 1);
26157429Smarkm		close(pin[0]);
26257429Smarkm		close(pout[1]);
26357429Smarkm
264157019Sdes		replacearg(&args, 0, "%s", ssh_program);
265204917Sdes		if (remuser != NULL) {
266204917Sdes			addargs(&args, "-l");
267204917Sdes			addargs(&args, "%s", remuser);
268204917Sdes		}
269204917Sdes		addargs(&args, "--");
27092555Sdes		addargs(&args, "%s", host);
27192555Sdes		addargs(&args, "%s", cmd);
27257429Smarkm
27369587Sgreen		execvp(ssh_program, args.list);
27465668Skris		perror(ssh_program);
27557429Smarkm		exit(1);
276113911Sdes	} else if (do_cmd_pid == -1) {
277113911Sdes		fatal("fork: %s", strerror(errno));
27857429Smarkm	}
27957429Smarkm	/* Parent.  Close the other side, and return the local side. */
28057429Smarkm	close(pin[0]);
28157429Smarkm	*fdout = pin[1];
28257429Smarkm	close(pout[1]);
28357429Smarkm	*fdin = pout[0];
284124211Sdes	signal(SIGTERM, killchild);
285124211Sdes	signal(SIGINT, killchild);
286124211Sdes	signal(SIGHUP, killchild);
28757429Smarkm	return 0;
28857429Smarkm}
28957429Smarkm
29057429Smarkmtypedef struct {
291149753Sdes	size_t cnt;
29257429Smarkm	char *buf;
29357429Smarkm} BUF;
29457429Smarkm
29557429SmarkmBUF *allocbuf(BUF *, int, int);
29657429Smarkmvoid lostconn(int);
29757429Smarkmint okname(char *);
29857429Smarkmvoid run_err(const char *,...);
29957429Smarkmvoid verifydir(char *);
30057429Smarkm
30157429Smarkmstruct passwd *pwd;
30257429Smarkmuid_t userid;
30357429Smarkmint errs, remin, remout;
30457429Smarkmint pflag, iamremote, iamrecursive, targetshouldbedirectory;
30557429Smarkm
30657429Smarkm#define	CMDNEEDS	64
30757429Smarkmchar cmd[CMDNEEDS];		/* must hold "rcp -r -p -d\0" */
30857429Smarkm
30957429Smarkmint response(void);
31057429Smarkmvoid rsource(char *, struct stat *);
31157429Smarkmvoid sink(int, char *[]);
31257429Smarkmvoid source(int, char *[]);
31357429Smarkmvoid tolocal(int, char *[]);
31457429Smarkmvoid toremote(char *, int, char *[]);
315181111Sdessize_t scpio(ssize_t (*)(int, void *, size_t), int, void *, size_t, off_t *);
31657429Smarkmvoid usage(void);
31757429Smarkm
31857429Smarkmint
319124211Sdesmain(int argc, char **argv)
32057429Smarkm{
321162856Sdes	int ch, fflag, tflag, status, n;
322113911Sdes	double speed;
323162856Sdes	char *targ, *endp, **newargv;
32457429Smarkm	extern char *optarg;
32557429Smarkm	extern int optind;
32657429Smarkm
327157019Sdes	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
328157019Sdes	sanitise_stdfd();
329157019Sdes
330162856Sdes	/* Copy argv, because we modify it */
331162856Sdes	newargv = xcalloc(MAX(argc + 1, 1), sizeof(*newargv));
332162856Sdes	for (n = 0; n < argc; n++)
333162856Sdes		newargv[n] = xstrdup(argv[n]);
334162856Sdes	argv = newargv;
335162856Sdes
336124211Sdes	__progname = ssh_get_progname(argv[0]);
33798937Sdes
338157019Sdes	memset(&args, '\0', sizeof(args));
33969587Sgreen	args.list = NULL;
340157019Sdes	addargs(&args, "%s", ssh_program);
34192555Sdes	addargs(&args, "-x");
34292555Sdes	addargs(&args, "-oForwardAgent no");
343157019Sdes	addargs(&args, "-oPermitLocalCommand no");
34492555Sdes	addargs(&args, "-oClearAllForwardings yes");
34569587Sgreen
34657429Smarkm	fflag = tflag = 0;
347113911Sdes	while ((ch = getopt(argc, argv, "dfl:prtvBCc:i:P:q1246S:o:F:")) != -1)
34857429Smarkm		switch (ch) {
34957429Smarkm		/* User-visible flags. */
350113911Sdes		case '1':
351113911Sdes		case '2':
35257429Smarkm		case '4':
35357429Smarkm		case '6':
35469587Sgreen		case 'C':
35592555Sdes			addargs(&args, "-%c", ch);
35657429Smarkm			break;
35769587Sgreen		case 'o':
35869587Sgreen		case 'c':
35969587Sgreen		case 'i':
36092555Sdes		case 'F':
361204917Sdes			addargs(&args, "-%c", ch);
362204917Sdes			addargs(&args, "%s", optarg);
36369587Sgreen			break;
36469587Sgreen		case 'P':
365204917Sdes			addargs(&args, "-p");
366204917Sdes			addargs(&args, "%s", optarg);
36769587Sgreen			break;
36869587Sgreen		case 'B':
36992555Sdes			addargs(&args, "-oBatchmode yes");
37069587Sgreen			break;
371113911Sdes		case 'l':
372113911Sdes			speed = strtod(optarg, &endp);
373113911Sdes			if (speed <= 0 || *endp != '\0')
374113911Sdes				usage();
375126277Sdes			limit_rate = speed * 1024;
376113911Sdes			break;
37757429Smarkm		case 'p':
37857429Smarkm			pflag = 1;
37957429Smarkm			break;
38057429Smarkm		case 'r':
38157429Smarkm			iamrecursive = 1;
38257429Smarkm			break;
38365668Skris		case 'S':
38469587Sgreen			ssh_program = xstrdup(optarg);
38565668Skris			break;
38669587Sgreen		case 'v':
38792555Sdes			addargs(&args, "-v");
38869587Sgreen			verbose_mode = 1;
38969587Sgreen			break;
39069587Sgreen		case 'q':
391126277Sdes			addargs(&args, "-q");
39269587Sgreen			showprogress = 0;
39369587Sgreen			break;
39465668Skris
39557429Smarkm		/* Server options. */
39657429Smarkm		case 'd':
39757429Smarkm			targetshouldbedirectory = 1;
39857429Smarkm			break;
39957429Smarkm		case 'f':	/* "from" */
40057429Smarkm			iamremote = 1;
40157429Smarkm			fflag = 1;
40257429Smarkm			break;
40357429Smarkm		case 't':	/* "to" */
40457429Smarkm			iamremote = 1;
40557429Smarkm			tflag = 1;
40698937Sdes#ifdef HAVE_CYGWIN
40798937Sdes			setmode(0, O_BINARY);
40898937Sdes#endif
40957429Smarkm			break;
41057429Smarkm		default:
41157429Smarkm			usage();
41257429Smarkm		}
41357429Smarkm	argc -= optind;
41457429Smarkm	argv += optind;
41557429Smarkm
41657429Smarkm	if ((pwd = getpwuid(userid = getuid())) == NULL)
417124211Sdes		fatal("unknown user %u", (u_int) userid);
41857429Smarkm
419181111Sdes	if (!isatty(STDOUT_FILENO))
42057429Smarkm		showprogress = 0;
42157429Smarkm
42257429Smarkm	remin = STDIN_FILENO;
42357429Smarkm	remout = STDOUT_FILENO;
42457429Smarkm
42576259Sgreen	if (fflag) {
42657429Smarkm		/* Follow "protocol", send data. */
42757429Smarkm		(void) response();
42857429Smarkm		source(argc, argv);
42957429Smarkm		exit(errs != 0);
43057429Smarkm	}
43157429Smarkm	if (tflag) {
43257429Smarkm		/* Receive data. */
43357429Smarkm		sink(argc, argv);
43457429Smarkm		exit(errs != 0);
43557429Smarkm	}
43657429Smarkm	if (argc < 2)
43757429Smarkm		usage();
43857429Smarkm	if (argc > 2)
43957429Smarkm		targetshouldbedirectory = 1;
44057429Smarkm
44157429Smarkm	remin = remout = -1;
442113911Sdes	do_cmd_pid = -1;
44357429Smarkm	/* Command to be executed on remote system using "ssh". */
44476259Sgreen	(void) snprintf(cmd, sizeof cmd, "scp%s%s%s%s",
44576259Sgreen	    verbose_mode ? " -v" : "",
44665668Skris	    iamrecursive ? " -r" : "", pflag ? " -p" : "",
44765668Skris	    targetshouldbedirectory ? " -d" : "");
44857429Smarkm
44957429Smarkm	(void) signal(SIGPIPE, lostconn);
45057429Smarkm
45157429Smarkm	if ((targ = colon(argv[argc - 1])))	/* Dest is remote host. */
45257429Smarkm		toremote(targ, argc, argv);
45357429Smarkm	else {
45457429Smarkm		if (targetshouldbedirectory)
45557429Smarkm			verifydir(argv[argc - 1]);
456157019Sdes		tolocal(argc, argv);	/* Dest is local host. */
45757429Smarkm	}
458113911Sdes	/*
459113911Sdes	 * Finally check the exit status of the ssh process, if one was forked
460192595Sdes	 * and no error has occurred yet
461113911Sdes	 */
462113911Sdes	if (do_cmd_pid != -1 && errs == 0) {
463113911Sdes		if (remin != -1)
464113911Sdes		    (void) close(remin);
465113911Sdes		if (remout != -1)
466113911Sdes		    (void) close(remout);
467113911Sdes		if (waitpid(do_cmd_pid, &status, 0) == -1)
468113911Sdes			errs = 1;
469113911Sdes		else {
470113911Sdes			if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
471113911Sdes				errs = 1;
472113911Sdes		}
473113911Sdes	}
47457429Smarkm	exit(errs != 0);
47557429Smarkm}
47657429Smarkm
477181111Sdes/*
478181111Sdes * atomicio-like wrapper that also applies bandwidth limits and updates
479181111Sdes * the progressmeter counter.
480181111Sdes */
481181111Sdessize_t
482181111Sdesscpio(ssize_t (*f)(int, void *, size_t), int fd, void *_p, size_t l, off_t *c)
483181111Sdes{
484181111Sdes	u_char *p = (u_char *)_p;
485181111Sdes	size_t offset;
486181111Sdes	ssize_t r;
487181111Sdes	struct pollfd pfd;
488181111Sdes
489181111Sdes	pfd.fd = fd;
490181111Sdes	pfd.events = f == read ? POLLIN : POLLOUT;
491181111Sdes	for (offset = 0; offset < l;) {
492181111Sdes		r = f(fd, p + offset, l - offset);
493181111Sdes		if (r == 0) {
494181111Sdes			errno = EPIPE;
495181111Sdes			return offset;
496181111Sdes		}
497181111Sdes		if (r < 0) {
498181111Sdes			if (errno == EINTR)
499181111Sdes				continue;
500181111Sdes			if (errno == EAGAIN || errno == EWOULDBLOCK) {
501181111Sdes				(void)poll(&pfd, 1, -1); /* Ignore errors */
502181111Sdes				continue;
503181111Sdes			}
504181111Sdes			return offset;
505181111Sdes		}
506181111Sdes		offset += (size_t)r;
507181111Sdes		*c += (off_t)r;
508181111Sdes		if (limit_rate)
509181111Sdes			bwlimit(r);
510181111Sdes	}
511181111Sdes	return offset;
512181111Sdes}
513181111Sdes
51457429Smarkmvoid
515124211Sdestoremote(char *targ, int argc, char **argv)
51657429Smarkm{
517147005Sdes	char *bp, *host, *src, *suser, *thost, *tuser, *arg;
518157019Sdes	arglist alist;
519162856Sdes	int i;
52057429Smarkm
521157019Sdes	memset(&alist, '\0', sizeof(alist));
522157019Sdes	alist.list = NULL;
523157019Sdes
52457429Smarkm	*targ++ = 0;
52557429Smarkm	if (*targ == 0)
52657429Smarkm		targ = ".";
52757429Smarkm
528147005Sdes	arg = xstrdup(argv[argc - 1]);
529147005Sdes	if ((thost = strrchr(arg, '@'))) {
53057429Smarkm		/* user@host */
53157429Smarkm		*thost++ = 0;
532147005Sdes		tuser = arg;
53357429Smarkm		if (*tuser == '\0')
53457429Smarkm			tuser = NULL;
53557429Smarkm	} else {
536147005Sdes		thost = arg;
53757429Smarkm		tuser = NULL;
53857429Smarkm	}
53957429Smarkm
540157019Sdes	if (tuser != NULL && !okname(tuser)) {
541157019Sdes		xfree(arg);
542157019Sdes		return;
543157019Sdes	}
544157019Sdes
54557429Smarkm	for (i = 0; i < argc - 1; i++) {
54657429Smarkm		src = colon(argv[i]);
54757429Smarkm		if (src) {	/* remote to remote */
548157019Sdes			freeargs(&alist);
549157019Sdes			addargs(&alist, "%s", ssh_program);
550157019Sdes			if (verbose_mode)
551157019Sdes				addargs(&alist, "-v");
552157019Sdes			addargs(&alist, "-x");
553157019Sdes			addargs(&alist, "-oClearAllForwardings yes");
554157019Sdes			addargs(&alist, "-n");
555157019Sdes
55657429Smarkm			*src++ = 0;
55757429Smarkm			if (*src == 0)
55857429Smarkm				src = ".";
559113911Sdes			host = strrchr(argv[i], '@');
560157019Sdes
56157429Smarkm			if (host) {
56257429Smarkm				*host++ = 0;
56357429Smarkm				host = cleanhostname(host);
56457429Smarkm				suser = argv[i];
56557429Smarkm				if (*suser == '\0')
56657429Smarkm					suser = pwd->pw_name;
567157019Sdes				else if (!okname(suser))
56857429Smarkm					continue;
569157019Sdes				addargs(&alist, "-l");
570157019Sdes				addargs(&alist, "%s", suser);
57157429Smarkm			} else {
57257429Smarkm				host = cleanhostname(argv[i]);
57357429Smarkm			}
574204917Sdes			addargs(&alist, "--");
575157019Sdes			addargs(&alist, "%s", host);
576157019Sdes			addargs(&alist, "%s", cmd);
577157019Sdes			addargs(&alist, "%s", src);
578157019Sdes			addargs(&alist, "%s%s%s:%s",
579157019Sdes			    tuser ? tuser : "", tuser ? "@" : "",
580157019Sdes			    thost, targ);
581157019Sdes			if (do_local_cmd(&alist) != 0)
582126277Sdes				errs = 1;
58357429Smarkm		} else {	/* local to remote */
58457429Smarkm			if (remin == -1) {
585204917Sdes				xasprintf(&bp, "%s -t -- %s", cmd, targ);
58657429Smarkm				host = cleanhostname(thost);
58765668Skris				if (do_cmd(host, tuser, bp, &remin,
588162856Sdes				    &remout) < 0)
58957429Smarkm					exit(1);
59057429Smarkm				if (response() < 0)
59157429Smarkm					exit(1);
59257429Smarkm				(void) xfree(bp);
59357429Smarkm			}
59457429Smarkm			source(1, argv + i);
59557429Smarkm		}
59657429Smarkm	}
597162856Sdes	xfree(arg);
59857429Smarkm}
59957429Smarkm
60057429Smarkmvoid
601124211Sdestolocal(int argc, char **argv)
60257429Smarkm{
60357429Smarkm	char *bp, *host, *src, *suser;
604157019Sdes	arglist alist;
605162856Sdes	int i;
60657429Smarkm
607157019Sdes	memset(&alist, '\0', sizeof(alist));
608157019Sdes	alist.list = NULL;
609157019Sdes
61057429Smarkm	for (i = 0; i < argc - 1; i++) {
61157429Smarkm		if (!(src = colon(argv[i]))) {	/* Local to local. */
612157019Sdes			freeargs(&alist);
613157019Sdes			addargs(&alist, "%s", _PATH_CP);
614157019Sdes			if (iamrecursive)
615157019Sdes				addargs(&alist, "-r");
616157019Sdes			if (pflag)
617157019Sdes				addargs(&alist, "-p");
618204917Sdes			addargs(&alist, "--");
619157019Sdes			addargs(&alist, "%s", argv[i]);
620157019Sdes			addargs(&alist, "%s", argv[argc-1]);
621157019Sdes			if (do_local_cmd(&alist))
62257429Smarkm				++errs;
62357429Smarkm			continue;
62457429Smarkm		}
62557429Smarkm		*src++ = 0;
62657429Smarkm		if (*src == 0)
62757429Smarkm			src = ".";
628113911Sdes		if ((host = strrchr(argv[i], '@')) == NULL) {
62957429Smarkm			host = argv[i];
63057429Smarkm			suser = NULL;
63157429Smarkm		} else {
63257429Smarkm			*host++ = 0;
63357429Smarkm			suser = argv[i];
63457429Smarkm			if (*suser == '\0')
63557429Smarkm				suser = pwd->pw_name;
63657429Smarkm		}
63757429Smarkm		host = cleanhostname(host);
638204917Sdes		xasprintf(&bp, "%s -f -- %s", cmd, src);
639162856Sdes		if (do_cmd(host, suser, bp, &remin, &remout) < 0) {
64057429Smarkm			(void) xfree(bp);
64157429Smarkm			++errs;
64257429Smarkm			continue;
64357429Smarkm		}
64457429Smarkm		xfree(bp);
64557429Smarkm		sink(1, argv + argc - 1);
64657429Smarkm		(void) close(remin);
64757429Smarkm		remin = remout = -1;
64857429Smarkm	}
64957429Smarkm}
65057429Smarkm
65157429Smarkmvoid
652124211Sdessource(int argc, char **argv)
65357429Smarkm{
65457429Smarkm	struct stat stb;
65557429Smarkm	static BUF buffer;
65657429Smarkm	BUF *bp;
657181111Sdes	off_t i, statbytes;
658181111Sdes	size_t amt;
659149753Sdes	int fd = -1, haderr, indx;
660181111Sdes	char *last, *name, buf[2048], encname[MAXPATHLEN];
66176259Sgreen	int len;
66257429Smarkm
66357429Smarkm	for (indx = 0; indx < argc; ++indx) {
66457429Smarkm		name = argv[indx];
66557429Smarkm		statbytes = 0;
66676259Sgreen		len = strlen(name);
66776259Sgreen		while (len > 1 && name[len-1] == '/')
66876259Sgreen			name[--len] = '\0';
669181111Sdes		if ((fd = open(name, O_RDONLY|O_NONBLOCK, 0)) < 0)
670181111Sdes			goto syserr;
67192555Sdes		if (strchr(name, '\n') != NULL) {
672181111Sdes			strnvis(encname, name, sizeof(encname), VIS_NL);
673181111Sdes			name = encname;
67492555Sdes		}
67557429Smarkm		if (fstat(fd, &stb) < 0) {
67657429Smarkmsyserr:			run_err("%s: %s", name, strerror(errno));
67757429Smarkm			goto next;
67857429Smarkm		}
679181111Sdes		if (stb.st_size < 0) {
680181111Sdes			run_err("%s: %s", name, "Negative file size");
681181111Sdes			goto next;
682181111Sdes		}
683181111Sdes		unset_nonblock(fd);
68457429Smarkm		switch (stb.st_mode & S_IFMT) {
68557429Smarkm		case S_IFREG:
68657429Smarkm			break;
68757429Smarkm		case S_IFDIR:
68857429Smarkm			if (iamrecursive) {
68957429Smarkm				rsource(name, &stb);
69057429Smarkm				goto next;
69157429Smarkm			}
69257429Smarkm			/* FALLTHROUGH */
69357429Smarkm		default:
69457429Smarkm			run_err("%s: not a regular file", name);
69557429Smarkm			goto next;
69657429Smarkm		}
69757429Smarkm		if ((last = strrchr(name, '/')) == NULL)
69857429Smarkm			last = name;
69957429Smarkm		else
70057429Smarkm			++last;
70157429Smarkm		curfile = last;
70257429Smarkm		if (pflag) {
70357429Smarkm			/*
70457429Smarkm			 * Make it compatible with possible future
70557429Smarkm			 * versions expecting microseconds.
70657429Smarkm			 */
70776259Sgreen			(void) snprintf(buf, sizeof buf, "T%lu 0 %lu 0\n",
708181111Sdes			    (u_long) (stb.st_mtime < 0 ? 0 : stb.st_mtime),
709181111Sdes			    (u_long) (stb.st_atime < 0 ? 0 : stb.st_atime));
710181111Sdes			if (verbose_mode) {
711181111Sdes				fprintf(stderr, "File mtime %ld atime %ld\n",
712181111Sdes				    (long)stb.st_mtime, (long)stb.st_atime);
713181111Sdes				fprintf(stderr, "Sending file timestamps: %s",
714181111Sdes				    buf);
715181111Sdes			}
716124211Sdes			(void) atomicio(vwrite, remout, buf, strlen(buf));
71757429Smarkm			if (response() < 0)
71857429Smarkm				goto next;
71957429Smarkm		}
72057429Smarkm#define	FILEMODEMASK	(S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
72176259Sgreen		snprintf(buf, sizeof buf, "C%04o %lld %s\n",
72276259Sgreen		    (u_int) (stb.st_mode & FILEMODEMASK),
723157019Sdes		    (long long)stb.st_size, last);
72457429Smarkm		if (verbose_mode) {
72557429Smarkm			fprintf(stderr, "Sending file modes: %s", buf);
72657429Smarkm		}
727124211Sdes		(void) atomicio(vwrite, remout, buf, strlen(buf));
72857429Smarkm		if (response() < 0)
72957429Smarkm			goto next;
730181111Sdes		if ((bp = allocbuf(&buffer, fd, COPY_BUFLEN)) == NULL) {
731157019Sdesnext:			if (fd != -1) {
732157019Sdes				(void) close(fd);
733157019Sdes				fd = -1;
734157019Sdes			}
73557429Smarkm			continue;
73657429Smarkm		}
737113911Sdes		if (showprogress)
738113911Sdes			start_progress_meter(curfile, stb.st_size, &statbytes);
739181111Sdes		set_nonblock(remout);
74057429Smarkm		for (haderr = i = 0; i < stb.st_size; i += bp->cnt) {
74157429Smarkm			amt = bp->cnt;
742181111Sdes			if (i + (off_t)amt > stb.st_size)
74357429Smarkm				amt = stb.st_size - i;
74457429Smarkm			if (!haderr) {
745181111Sdes				if (atomicio(read, fd, bp->buf, amt) != amt)
746149753Sdes					haderr = errno;
74757429Smarkm			}
748181111Sdes			/* Keep writing after error to retain sync */
749181111Sdes			if (haderr) {
750181111Sdes				(void)atomicio(vwrite, remout, bp->buf, amt);
751181111Sdes				continue;
75257429Smarkm			}
753181111Sdes			if (scpio(vwrite, remout, bp->buf, amt,
754181111Sdes			    &statbytes) != amt)
755181111Sdes				haderr = errno;
75657429Smarkm		}
757181111Sdes		unset_nonblock(remout);
75857429Smarkm		if (showprogress)
759113911Sdes			stop_progress_meter();
76057429Smarkm
761157019Sdes		if (fd != -1) {
762157019Sdes			if (close(fd) < 0 && !haderr)
763157019Sdes				haderr = errno;
764157019Sdes			fd = -1;
765157019Sdes		}
76657429Smarkm		if (!haderr)
767124211Sdes			(void) atomicio(vwrite, remout, "", 1);
76857429Smarkm		else
76957429Smarkm			run_err("%s: %s", name, strerror(haderr));
77057429Smarkm		(void) response();
77157429Smarkm	}
77257429Smarkm}
77357429Smarkm
77457429Smarkmvoid
775124211Sdesrsource(char *name, struct stat *statp)
77657429Smarkm{
77757429Smarkm	DIR *dirp;
77857429Smarkm	struct dirent *dp;
77957429Smarkm	char *last, *vect[1], path[1100];
78057429Smarkm
78157429Smarkm	if (!(dirp = opendir(name))) {
78257429Smarkm		run_err("%s: %s", name, strerror(errno));
78357429Smarkm		return;
78457429Smarkm	}
78557429Smarkm	last = strrchr(name, '/');
78657429Smarkm	if (last == 0)
78757429Smarkm		last = name;
78857429Smarkm	else
78957429Smarkm		last++;
79057429Smarkm	if (pflag) {
79176259Sgreen		(void) snprintf(path, sizeof(path), "T%lu 0 %lu 0\n",
79276259Sgreen		    (u_long) statp->st_mtime,
79376259Sgreen		    (u_long) statp->st_atime);
794124211Sdes		(void) atomicio(vwrite, remout, path, strlen(path));
79557429Smarkm		if (response() < 0) {
79657429Smarkm			closedir(dirp);
79757429Smarkm			return;
79857429Smarkm		}
79957429Smarkm	}
80076259Sgreen	(void) snprintf(path, sizeof path, "D%04o %d %.1024s\n",
80176259Sgreen	    (u_int) (statp->st_mode & FILEMODEMASK), 0, last);
80257429Smarkm	if (verbose_mode)
80357429Smarkm		fprintf(stderr, "Entering directory: %s", path);
804124211Sdes	(void) atomicio(vwrite, remout, path, strlen(path));
80557429Smarkm	if (response() < 0) {
80657429Smarkm		closedir(dirp);
80757429Smarkm		return;
80857429Smarkm	}
80976259Sgreen	while ((dp = readdir(dirp)) != NULL) {
81057429Smarkm		if (dp->d_ino == 0)
81157429Smarkm			continue;
81257429Smarkm		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
81357429Smarkm			continue;
81457429Smarkm		if (strlen(name) + 1 + strlen(dp->d_name) >= sizeof(path) - 1) {
81557429Smarkm			run_err("%s/%s: name too long", name, dp->d_name);
81657429Smarkm			continue;
81757429Smarkm		}
81876259Sgreen		(void) snprintf(path, sizeof path, "%s/%s", name, dp->d_name);
81957429Smarkm		vect[0] = path;
82057429Smarkm		source(1, vect);
82157429Smarkm	}
82257429Smarkm	(void) closedir(dirp);
823124211Sdes	(void) atomicio(vwrite, remout, "E\n", 2);
82457429Smarkm	(void) response();
82557429Smarkm}
82657429Smarkm
82757429Smarkmvoid
828113911Sdesbwlimit(int amount)
829113911Sdes{
830113911Sdes	static struct timeval bwstart, bwend;
831113911Sdes	static int lamt, thresh = 16384;
832137019Sdes	u_int64_t waitlen;
833113911Sdes	struct timespec ts, rm;
834113911Sdes
835113911Sdes	if (!timerisset(&bwstart)) {
836113911Sdes		gettimeofday(&bwstart, NULL);
837113911Sdes		return;
838113911Sdes	}
839113911Sdes
840113911Sdes	lamt += amount;
841113911Sdes	if (lamt < thresh)
842113911Sdes		return;
843113911Sdes
844113911Sdes	gettimeofday(&bwend, NULL);
845113911Sdes	timersub(&bwend, &bwstart, &bwend);
846113911Sdes	if (!timerisset(&bwend))
847113911Sdes		return;
848113911Sdes
849113911Sdes	lamt *= 8;
850137019Sdes	waitlen = (double)1000000L * lamt / limit_rate;
851113911Sdes
852137019Sdes	bwstart.tv_sec = waitlen / 1000000L;
853137019Sdes	bwstart.tv_usec = waitlen % 1000000L;
854113911Sdes
855113911Sdes	if (timercmp(&bwstart, &bwend, >)) {
856113911Sdes		timersub(&bwstart, &bwend, &bwend);
857113911Sdes
858113911Sdes		/* Adjust the wait time */
859113911Sdes		if (bwend.tv_sec) {
860113911Sdes			thresh /= 2;
861113911Sdes			if (thresh < 2048)
862113911Sdes				thresh = 2048;
863181111Sdes		} else if (bwend.tv_usec < 10000) {
864113911Sdes			thresh *= 2;
865181111Sdes			if (thresh > COPY_BUFLEN * 4)
866181111Sdes				thresh = COPY_BUFLEN * 4;
867113911Sdes		}
868113911Sdes
869113911Sdes		TIMEVAL_TO_TIMESPEC(&bwend, &ts);
870113911Sdes		while (nanosleep(&ts, &rm) == -1) {
871113911Sdes			if (errno != EINTR)
872113911Sdes				break;
873113911Sdes			ts = rm;
874113911Sdes		}
875113911Sdes	}
876113911Sdes
877113911Sdes	lamt = 0;
878113911Sdes	gettimeofday(&bwstart, NULL);
879113911Sdes}
880113911Sdes
881113911Sdesvoid
882124211Sdessink(int argc, char **argv)
88357429Smarkm{
88457429Smarkm	static BUF buffer;
88557429Smarkm	struct stat stb;
88657429Smarkm	enum {
88757429Smarkm		YES, NO, DISPLAYED
88857429Smarkm	} wrerr;
88957429Smarkm	BUF *bp;
890149753Sdes	off_t i;
891149753Sdes	size_t j, count;
892162856Sdes	int amt, exists, first, ofd;
893162856Sdes	mode_t mode, omode, mask;
894113911Sdes	off_t size, statbytes;
89565668Skris	int setimes, targisdir, wrerrno = 0;
89657429Smarkm	char ch, *cp, *np, *targ, *why, *vect[1], buf[2048];
89769587Sgreen	struct timeval tv[2];
89857429Smarkm
89976259Sgreen#define	atime	tv[0]
90076259Sgreen#define	mtime	tv[1]
901147005Sdes#define	SCREWUP(str)	{ why = str; goto screwup; }
90257429Smarkm
90357429Smarkm	setimes = targisdir = 0;
90457429Smarkm	mask = umask(0);
90557429Smarkm	if (!pflag)
90657429Smarkm		(void) umask(mask);
90757429Smarkm	if (argc != 1) {
90857429Smarkm		run_err("ambiguous target");
90957429Smarkm		exit(1);
91057429Smarkm	}
91157429Smarkm	targ = *argv;
91257429Smarkm	if (targetshouldbedirectory)
91357429Smarkm		verifydir(targ);
91457429Smarkm
915124211Sdes	(void) atomicio(vwrite, remout, "", 1);
91657429Smarkm	if (stat(targ, &stb) == 0 && S_ISDIR(stb.st_mode))
91757429Smarkm		targisdir = 1;
91857429Smarkm	for (first = 1;; first = 0) {
91957429Smarkm		cp = buf;
920149753Sdes		if (atomicio(read, remin, cp, 1) != 1)
92157429Smarkm			return;
92257429Smarkm		if (*cp++ == '\n')
92357429Smarkm			SCREWUP("unexpected <newline>");
92457429Smarkm		do {
92560573Skris			if (atomicio(read, remin, &ch, sizeof(ch)) != sizeof(ch))
92657429Smarkm				SCREWUP("lost connection");
92757429Smarkm			*cp++ = ch;
92857429Smarkm		} while (cp < &buf[sizeof(buf) - 1] && ch != '\n');
92957429Smarkm		*cp = 0;
930137019Sdes		if (verbose_mode)
931137019Sdes			fprintf(stderr, "Sink: %s", buf);
93257429Smarkm
93357429Smarkm		if (buf[0] == '\01' || buf[0] == '\02') {
93457429Smarkm			if (iamremote == 0)
935124211Sdes				(void) atomicio(vwrite, STDERR_FILENO,
93676259Sgreen				    buf + 1, strlen(buf + 1));
93757429Smarkm			if (buf[0] == '\02')
93857429Smarkm				exit(1);
93957429Smarkm			++errs;
94057429Smarkm			continue;
94157429Smarkm		}
94257429Smarkm		if (buf[0] == 'E') {
943124211Sdes			(void) atomicio(vwrite, remout, "", 1);
94457429Smarkm			return;
94557429Smarkm		}
94657429Smarkm		if (ch == '\n')
94757429Smarkm			*--cp = 0;
94857429Smarkm
94957429Smarkm		cp = buf;
95057429Smarkm		if (*cp == 'T') {
95157429Smarkm			setimes++;
95257429Smarkm			cp++;
95376259Sgreen			mtime.tv_sec = strtol(cp, &cp, 10);
95476259Sgreen			if (!cp || *cp++ != ' ')
95557429Smarkm				SCREWUP("mtime.sec not delimited");
95676259Sgreen			mtime.tv_usec = strtol(cp, &cp, 10);
95776259Sgreen			if (!cp || *cp++ != ' ')
95857429Smarkm				SCREWUP("mtime.usec not delimited");
95976259Sgreen			atime.tv_sec = strtol(cp, &cp, 10);
96076259Sgreen			if (!cp || *cp++ != ' ')
96157429Smarkm				SCREWUP("atime.sec not delimited");
96276259Sgreen			atime.tv_usec = strtol(cp, &cp, 10);
96376259Sgreen			if (!cp || *cp++ != '\0')
96457429Smarkm				SCREWUP("atime.usec not delimited");
965124211Sdes			(void) atomicio(vwrite, remout, "", 1);
96657429Smarkm			continue;
96757429Smarkm		}
96857429Smarkm		if (*cp != 'C' && *cp != 'D') {
96957429Smarkm			/*
97057429Smarkm			 * Check for the case "rcp remote:foo\* local:bar".
97157429Smarkm			 * In this case, the line "No match." can be returned
97257429Smarkm			 * by the shell before the rcp command on the remote is
97357429Smarkm			 * executed so the ^Aerror_message convention isn't
97457429Smarkm			 * followed.
97557429Smarkm			 */
97657429Smarkm			if (first) {
97757429Smarkm				run_err("%s", cp);
97857429Smarkm				exit(1);
97957429Smarkm			}
98057429Smarkm			SCREWUP("expected control record");
98157429Smarkm		}
98257429Smarkm		mode = 0;
98357429Smarkm		for (++cp; cp < buf + 5; cp++) {
98457429Smarkm			if (*cp < '0' || *cp > '7')
98557429Smarkm				SCREWUP("bad mode");
98657429Smarkm			mode = (mode << 3) | (*cp - '0');
98757429Smarkm		}
98857429Smarkm		if (*cp++ != ' ')
98957429Smarkm			SCREWUP("mode not delimited");
99057429Smarkm
99176259Sgreen		for (size = 0; isdigit(*cp);)
99257429Smarkm			size = size * 10 + (*cp++ - '0');
99357429Smarkm		if (*cp++ != ' ')
99457429Smarkm			SCREWUP("size not delimited");
995137019Sdes		if ((strchr(cp, '/') != NULL) || (strcmp(cp, "..") == 0)) {
996137019Sdes			run_err("error: unexpected filename: %s", cp);
997137019Sdes			exit(1);
998137019Sdes		}
99957429Smarkm		if (targisdir) {
100057429Smarkm			static char *namebuf;
1001149753Sdes			static size_t cursize;
100257429Smarkm			size_t need;
100357429Smarkm
100457429Smarkm			need = strlen(targ) + strlen(cp) + 250;
100576259Sgreen			if (need > cursize) {
100676259Sgreen				if (namebuf)
100776259Sgreen					xfree(namebuf);
100857429Smarkm				namebuf = xmalloc(need);
100976259Sgreen				cursize = need;
101076259Sgreen			}
101176259Sgreen			(void) snprintf(namebuf, need, "%s%s%s", targ,
101298675Sdes			    strcmp(targ, "/") ? "/" : "", cp);
101357429Smarkm			np = namebuf;
101457429Smarkm		} else
101557429Smarkm			np = targ;
101657429Smarkm		curfile = cp;
101757429Smarkm		exists = stat(np, &stb) == 0;
101857429Smarkm		if (buf[0] == 'D') {
101957429Smarkm			int mod_flag = pflag;
1020137019Sdes			if (!iamrecursive)
1021137019Sdes				SCREWUP("received directory without -r");
102257429Smarkm			if (exists) {
102357429Smarkm				if (!S_ISDIR(stb.st_mode)) {
102457429Smarkm					errno = ENOTDIR;
102557429Smarkm					goto bad;
102657429Smarkm				}
102757429Smarkm				if (pflag)
102857429Smarkm					(void) chmod(np, mode);
102957429Smarkm			} else {
103057429Smarkm				/* Handle copying from a read-only
103157429Smarkm				   directory */
103257429Smarkm				mod_flag = 1;
103357429Smarkm				if (mkdir(np, mode | S_IRWXU) < 0)
103457429Smarkm					goto bad;
103557429Smarkm			}
103676259Sgreen			vect[0] = xstrdup(np);
103757429Smarkm			sink(1, vect);
103857429Smarkm			if (setimes) {
103957429Smarkm				setimes = 0;
104076259Sgreen				if (utimes(vect[0], tv) < 0)
104157429Smarkm					run_err("%s: set times: %s",
104276259Sgreen					    vect[0], strerror(errno));
104357429Smarkm			}
104457429Smarkm			if (mod_flag)
104576259Sgreen				(void) chmod(vect[0], mode);
104676259Sgreen			if (vect[0])
104776259Sgreen				xfree(vect[0]);
104857429Smarkm			continue;
104957429Smarkm		}
105057429Smarkm		omode = mode;
105157429Smarkm		mode |= S_IWRITE;
105292555Sdes		if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) < 0) {
105357429Smarkmbad:			run_err("%s: %s", np, strerror(errno));
105457429Smarkm			continue;
105557429Smarkm		}
1056124211Sdes		(void) atomicio(vwrite, remout, "", 1);
1057181111Sdes		if ((bp = allocbuf(&buffer, ofd, COPY_BUFLEN)) == NULL) {
105857429Smarkm			(void) close(ofd);
105957429Smarkm			continue;
106057429Smarkm		}
106157429Smarkm		cp = bp->buf;
106257429Smarkm		wrerr = NO;
106357429Smarkm
106457429Smarkm		statbytes = 0;
1065113911Sdes		if (showprogress)
1066113911Sdes			start_progress_meter(curfile, size, &statbytes);
1067181111Sdes		set_nonblock(remin);
1068181111Sdes		for (count = i = 0; i < size; i += bp->cnt) {
1069181111Sdes			amt = bp->cnt;
107057429Smarkm			if (i + amt > size)
107157429Smarkm				amt = size - i;
107257429Smarkm			count += amt;
107357429Smarkm			do {
1074181111Sdes				j = scpio(read, remin, cp, amt, &statbytes);
1075149753Sdes				if (j == 0) {
1076181111Sdes					run_err("%s", j != EPIPE ?
1077181111Sdes					    strerror(errno) :
107876259Sgreen					    "dropped connection");
107957429Smarkm					exit(1);
108057429Smarkm				}
108157429Smarkm				amt -= j;
108257429Smarkm				cp += j;
108357429Smarkm			} while (amt > 0);
1084126277Sdes
108557429Smarkm			if (count == bp->cnt) {
108657429Smarkm				/* Keep reading so we stay sync'd up. */
108757429Smarkm				if (wrerr == NO) {
1088149753Sdes					if (atomicio(vwrite, ofd, bp->buf,
1089149753Sdes					    count) != count) {
109057429Smarkm						wrerr = YES;
1091149753Sdes						wrerrno = errno;
109257429Smarkm					}
109357429Smarkm				}
109457429Smarkm				count = 0;
109557429Smarkm				cp = bp->buf;
109657429Smarkm			}
109757429Smarkm		}
1098181111Sdes		unset_nonblock(remin);
109957429Smarkm		if (showprogress)
1100113911Sdes			stop_progress_meter();
110157429Smarkm		if (count != 0 && wrerr == NO &&
1102149753Sdes		    atomicio(vwrite, ofd, bp->buf, count) != count) {
110357429Smarkm			wrerr = YES;
1104149753Sdes			wrerrno = errno;
110557429Smarkm		}
1106181111Sdes		if (wrerr == NO && (!exists || S_ISREG(stb.st_mode)) &&
1107181111Sdes		    ftruncate(ofd, size) != 0) {
110857429Smarkm			run_err("%s: truncate: %s", np, strerror(errno));
110957429Smarkm			wrerr = DISPLAYED;
111057429Smarkm		}
111157429Smarkm		if (pflag) {
111257429Smarkm			if (exists || omode != mode)
111398937Sdes#ifdef HAVE_FCHMOD
1114137019Sdes				if (fchmod(ofd, omode)) {
111598937Sdes#else /* HAVE_FCHMOD */
1116137019Sdes				if (chmod(np, omode)) {
111798937Sdes#endif /* HAVE_FCHMOD */
111857429Smarkm					run_err("%s: set mode: %s",
111976259Sgreen					    np, strerror(errno));
1120137019Sdes					wrerr = DISPLAYED;
1121137019Sdes				}
112257429Smarkm		} else {
112357429Smarkm			if (!exists && omode != mode)
112498937Sdes#ifdef HAVE_FCHMOD
1125137019Sdes				if (fchmod(ofd, omode & ~mask)) {
112698937Sdes#else /* HAVE_FCHMOD */
1127137019Sdes				if (chmod(np, omode & ~mask)) {
112898937Sdes#endif /* HAVE_FCHMOD */
112957429Smarkm					run_err("%s: set mode: %s",
113076259Sgreen					    np, strerror(errno));
1131137019Sdes					wrerr = DISPLAYED;
1132137019Sdes				}
113357429Smarkm		}
113465668Skris		if (close(ofd) == -1) {
113565668Skris			wrerr = YES;
113665668Skris			wrerrno = errno;
113765668Skris		}
113857429Smarkm		(void) response();
113957429Smarkm		if (setimes && wrerr == NO) {
114057429Smarkm			setimes = 0;
114169587Sgreen			if (utimes(np, tv) < 0) {
114257429Smarkm				run_err("%s: set times: %s",
114376259Sgreen				    np, strerror(errno));
114457429Smarkm				wrerr = DISPLAYED;
114557429Smarkm			}
114657429Smarkm		}
114757429Smarkm		switch (wrerr) {
114857429Smarkm		case YES:
114957429Smarkm			run_err("%s: %s", np, strerror(wrerrno));
115057429Smarkm			break;
115157429Smarkm		case NO:
1152124211Sdes			(void) atomicio(vwrite, remout, "", 1);
115357429Smarkm			break;
115457429Smarkm		case DISPLAYED:
115557429Smarkm			break;
115657429Smarkm		}
115757429Smarkm	}
115857429Smarkmscrewup:
115957429Smarkm	run_err("protocol error: %s", why);
116057429Smarkm	exit(1);
116157429Smarkm}
116257429Smarkm
116357429Smarkmint
116492555Sdesresponse(void)
116557429Smarkm{
116657429Smarkm	char ch, *cp, resp, rbuf[2048];
116757429Smarkm
116860573Skris	if (atomicio(read, remin, &resp, sizeof(resp)) != sizeof(resp))
116957429Smarkm		lostconn(0);
117057429Smarkm
117157429Smarkm	cp = rbuf;
117257429Smarkm	switch (resp) {
117357429Smarkm	case 0:		/* ok */
117457429Smarkm		return (0);
117557429Smarkm	default:
117657429Smarkm		*cp++ = resp;
117757429Smarkm		/* FALLTHROUGH */
117857429Smarkm	case 1:		/* error, followed by error msg */
117957429Smarkm	case 2:		/* fatal error, "" */
118057429Smarkm		do {
118160573Skris			if (atomicio(read, remin, &ch, sizeof(ch)) != sizeof(ch))
118257429Smarkm				lostconn(0);
118357429Smarkm			*cp++ = ch;
118457429Smarkm		} while (cp < &rbuf[sizeof(rbuf) - 1] && ch != '\n');
118557429Smarkm
118657429Smarkm		if (!iamremote)
1187124211Sdes			(void) atomicio(vwrite, STDERR_FILENO, rbuf, cp - rbuf);
118857429Smarkm		++errs;
118957429Smarkm		if (resp == 1)
119057429Smarkm			return (-1);
119157429Smarkm		exit(1);
119257429Smarkm	}
119357429Smarkm	/* NOTREACHED */
119457429Smarkm}
119557429Smarkm
119657429Smarkmvoid
119792555Sdesusage(void)
119857429Smarkm{
119992555Sdes	(void) fprintf(stderr,
1200126277Sdes	    "usage: scp [-1246BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]\n"
1201126277Sdes	    "           [-l limit] [-o ssh_option] [-P port] [-S program]\n"
1202181111Sdes	    "           [[user@]host1:]file1 ... [[user@]host2:]file2\n");
120357429Smarkm	exit(1);
120457429Smarkm}
120557429Smarkm
120657429Smarkmvoid
120757429Smarkmrun_err(const char *fmt,...)
120857429Smarkm{
120957429Smarkm	static FILE *fp;
121057429Smarkm	va_list ap;
121157429Smarkm
121257429Smarkm	++errs;
1213162856Sdes	if (fp != NULL || (remout != -1 && (fp = fdopen(remout, "w")))) {
1214162856Sdes		(void) fprintf(fp, "%c", 0x01);
1215162856Sdes		(void) fprintf(fp, "scp: ");
1216162856Sdes		va_start(ap, fmt);
1217162856Sdes		(void) vfprintf(fp, fmt, ap);
1218162856Sdes		va_end(ap);
1219162856Sdes		(void) fprintf(fp, "\n");
1220162856Sdes		(void) fflush(fp);
1221162856Sdes	}
122257429Smarkm
122357429Smarkm	if (!iamremote) {
122492555Sdes		va_start(ap, fmt);
122557429Smarkm		vfprintf(stderr, fmt, ap);
122692555Sdes		va_end(ap);
122757429Smarkm		fprintf(stderr, "\n");
122857429Smarkm	}
122957429Smarkm}
123057429Smarkm
123157429Smarkmvoid
1232124211Sdesverifydir(char *cp)
123357429Smarkm{
123457429Smarkm	struct stat stb;
123557429Smarkm
123657429Smarkm	if (!stat(cp, &stb)) {
123757429Smarkm		if (S_ISDIR(stb.st_mode))
123857429Smarkm			return;
123957429Smarkm		errno = ENOTDIR;
124057429Smarkm	}
124157429Smarkm	run_err("%s: %s", cp, strerror(errno));
1242149753Sdes	killchild(0);
124357429Smarkm}
124457429Smarkm
124557429Smarkmint
1246124211Sdesokname(char *cp0)
124757429Smarkm{
124857429Smarkm	int c;
124957429Smarkm	char *cp;
125057429Smarkm
125157429Smarkm	cp = cp0;
125257429Smarkm	do {
125392555Sdes		c = (int)*cp;
125457429Smarkm		if (c & 0200)
125557429Smarkm			goto bad;
1256113911Sdes		if (!isalpha(c) && !isdigit(c)) {
1257113911Sdes			switch (c) {
1258113911Sdes			case '\'':
1259113911Sdes			case '"':
1260113911Sdes			case '`':
1261113911Sdes			case ' ':
1262113911Sdes			case '#':
1263113911Sdes				goto bad;
1264113911Sdes			default:
1265113911Sdes				break;
1266113911Sdes			}
1267113911Sdes		}
126857429Smarkm	} while (*++cp);
126957429Smarkm	return (1);
127057429Smarkm
127157429Smarkmbad:	fprintf(stderr, "%s: invalid user name\n", cp0);
127257429Smarkm	return (0);
127357429Smarkm}
127457429Smarkm
127557429SmarkmBUF *
1276124211Sdesallocbuf(BUF *bp, int fd, int blksize)
127757429Smarkm{
127857429Smarkm	size_t size;
127998937Sdes#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
128057429Smarkm	struct stat stb;
128157429Smarkm
128257429Smarkm	if (fstat(fd, &stb) < 0) {
128357429Smarkm		run_err("fstat: %s", strerror(errno));
128457429Smarkm		return (0);
128557429Smarkm	}
1286113911Sdes	size = roundup(stb.st_blksize, blksize);
1287113911Sdes	if (size == 0)
128857429Smarkm		size = blksize;
128998937Sdes#else /* HAVE_STRUCT_STAT_ST_BLKSIZE */
129098937Sdes	size = blksize;
129198937Sdes#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
129257429Smarkm	if (bp->cnt >= size)
129357429Smarkm		return (bp);
129457429Smarkm	if (bp->buf == NULL)
129557429Smarkm		bp->buf = xmalloc(size);
129657429Smarkm	else
1297162856Sdes		bp->buf = xrealloc(bp->buf, 1, size);
129892555Sdes	memset(bp->buf, 0, size);
129957429Smarkm	bp->cnt = size;
130057429Smarkm	return (bp);
130157429Smarkm}
130257429Smarkm
130357429Smarkmvoid
1304124211Sdeslostconn(int signo)
130557429Smarkm{
130657429Smarkm	if (!iamremote)
130792555Sdes		write(STDERR_FILENO, "lost connection\n", 16);
130892555Sdes	if (signo)
130992555Sdes		_exit(1);
131092555Sdes	else
131192555Sdes		exit(1);
131257429Smarkm}
1313