scp.c revision 221420
1221420Sdes/* $OpenBSD: scp.c,v 1.170 2010/12/09 14:13:33 jmc 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);
122221420Sdesint do_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout);
123162856Sdes
12492555Sdes/* Struct for addargs */
12592555Sdesarglist args;
126221420Sdesarglist remote_remote_args;
12769587Sgreen
128113911Sdes/* Bandwidth limit */
129221420Sdeslong long limit_kbps = 0;
130221420Sdesstruct bwlimit bwlimit;
13157429Smarkm
13257429Smarkm/* Name of current file being transferred. */
13357429Smarkmchar *curfile;
13457429Smarkm
13557429Smarkm/* This is set to non-zero to enable verbose mode. */
13657429Smarkmint verbose_mode = 0;
13757429Smarkm
13857429Smarkm/* This is set to zero if the progressmeter is not desired. */
13957429Smarkmint showprogress = 1;
14057429Smarkm
141221420Sdes/*
142221420Sdes * This is set to non-zero if remote-remote copy should be piped
143221420Sdes * through this process.
144221420Sdes */
145221420Sdesint throughlocal = 0;
146221420Sdes
14765668Skris/* This is the program to execute for the secured connection. ("ssh" or -S) */
14876259Sgreenchar *ssh_program = _PATH_SSH_PROGRAM;
14965668Skris
150113911Sdes/* This is used to store the pid of ssh_program */
151124211Sdespid_t do_cmd_pid = -1;
152113911Sdes
153124211Sdesstatic void
154124211Sdeskillchild(int signo)
155124211Sdes{
156147005Sdes	if (do_cmd_pid > 1) {
157149753Sdes		kill(do_cmd_pid, signo ? signo : SIGTERM);
158147005Sdes		waitpid(do_cmd_pid, NULL, 0);
159147005Sdes	}
160124211Sdes
161149753Sdes	if (signo)
162149753Sdes		_exit(1);
163149753Sdes	exit(1);
164124211Sdes}
165124211Sdes
166215116Sdesstatic void
167215116Sdessuspchild(int signo)
168215116Sdes{
169215116Sdes	int status;
170215116Sdes
171215116Sdes	if (do_cmd_pid > 1) {
172215116Sdes		kill(do_cmd_pid, signo);
173215116Sdes		while (waitpid(do_cmd_pid, &status, WUNTRACED) == -1 &&
174215116Sdes		    errno == EINTR)
175215116Sdes			;
176215116Sdes		kill(getpid(), SIGSTOP);
177215116Sdes	}
178215116Sdes}
179215116Sdes
180157019Sdesstatic int
181157019Sdesdo_local_cmd(arglist *a)
182157019Sdes{
183157019Sdes	u_int i;
184157019Sdes	int status;
185157019Sdes	pid_t pid;
186157019Sdes
187157019Sdes	if (a->num == 0)
188157019Sdes		fatal("do_local_cmd: no arguments");
189157019Sdes
190157019Sdes	if (verbose_mode) {
191157019Sdes		fprintf(stderr, "Executing:");
192157019Sdes		for (i = 0; i < a->num; i++)
193157019Sdes			fprintf(stderr, " %s", a->list[i]);
194157019Sdes		fprintf(stderr, "\n");
195157019Sdes	}
196157019Sdes	if ((pid = fork()) == -1)
197157019Sdes		fatal("do_local_cmd: fork: %s", strerror(errno));
198157019Sdes
199157019Sdes	if (pid == 0) {
200157019Sdes		execvp(a->list[0], a->list);
201157019Sdes		perror(a->list[0]);
202157019Sdes		exit(1);
203157019Sdes	}
204157019Sdes
205157019Sdes	do_cmd_pid = pid;
206157019Sdes	signal(SIGTERM, killchild);
207157019Sdes	signal(SIGINT, killchild);
208157019Sdes	signal(SIGHUP, killchild);
209157019Sdes
210157019Sdes	while (waitpid(pid, &status, 0) == -1)
211157019Sdes		if (errno != EINTR)
212157019Sdes			fatal("do_local_cmd: waitpid: %s", strerror(errno));
213157019Sdes
214157019Sdes	do_cmd_pid = -1;
215157019Sdes
216157019Sdes	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
217157019Sdes		return (-1);
218157019Sdes
219157019Sdes	return (0);
220157019Sdes}
221157019Sdes
22257429Smarkm/*
22357429Smarkm * This function executes the given command as the specified user on the
22457429Smarkm * given host.  This returns < 0 if execution fails, and >= 0 otherwise. This
22557429Smarkm * assigns the input and output file descriptors on success.
22657429Smarkm */
22757429Smarkm
22860573Skrisint
229162856Sdesdo_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout)
23057429Smarkm{
23157429Smarkm	int pin[2], pout[2], reserved[2];
23257429Smarkm
23357429Smarkm	if (verbose_mode)
23492555Sdes		fprintf(stderr,
23592555Sdes		    "Executing: program %s host %s, user %s, command %s\n",
23692555Sdes		    ssh_program, host,
23792555Sdes		    remuser ? remuser : "(unspecified)", cmd);
23857429Smarkm
23957429Smarkm	/*
24057429Smarkm	 * Reserve two descriptors so that the real pipes won't get
24157429Smarkm	 * descriptors 0 and 1 because that will screw up dup2 below.
24257429Smarkm	 */
243162856Sdes	if (pipe(reserved) < 0)
244162856Sdes		fatal("pipe: %s", strerror(errno));
24557429Smarkm
24657429Smarkm	/* Create a socket pair for communicating with ssh. */
24757429Smarkm	if (pipe(pin) < 0)
24857429Smarkm		fatal("pipe: %s", strerror(errno));
24957429Smarkm	if (pipe(pout) < 0)
25057429Smarkm		fatal("pipe: %s", strerror(errno));
25157429Smarkm
25257429Smarkm	/* Free the reserved descriptors. */
25357429Smarkm	close(reserved[0]);
25457429Smarkm	close(reserved[1]);
25557429Smarkm
256215116Sdes	signal(SIGTSTP, suspchild);
257215116Sdes	signal(SIGTTIN, suspchild);
258215116Sdes	signal(SIGTTOU, suspchild);
259215116Sdes
260124211Sdes	/* Fork a child to execute the command on the remote host using ssh. */
261113911Sdes	do_cmd_pid = fork();
262113911Sdes	if (do_cmd_pid == 0) {
26357429Smarkm		/* Child. */
26457429Smarkm		close(pin[1]);
26557429Smarkm		close(pout[0]);
26657429Smarkm		dup2(pin[0], 0);
26757429Smarkm		dup2(pout[1], 1);
26857429Smarkm		close(pin[0]);
26957429Smarkm		close(pout[1]);
27057429Smarkm
271157019Sdes		replacearg(&args, 0, "%s", ssh_program);
272204917Sdes		if (remuser != NULL) {
273204917Sdes			addargs(&args, "-l");
274204917Sdes			addargs(&args, "%s", remuser);
275204917Sdes		}
276204917Sdes		addargs(&args, "--");
27792555Sdes		addargs(&args, "%s", host);
27892555Sdes		addargs(&args, "%s", cmd);
27957429Smarkm
28069587Sgreen		execvp(ssh_program, args.list);
28165668Skris		perror(ssh_program);
28257429Smarkm		exit(1);
283113911Sdes	} else if (do_cmd_pid == -1) {
284113911Sdes		fatal("fork: %s", strerror(errno));
28557429Smarkm	}
28657429Smarkm	/* Parent.  Close the other side, and return the local side. */
28757429Smarkm	close(pin[0]);
28857429Smarkm	*fdout = pin[1];
28957429Smarkm	close(pout[1]);
29057429Smarkm	*fdin = pout[0];
291124211Sdes	signal(SIGTERM, killchild);
292124211Sdes	signal(SIGINT, killchild);
293124211Sdes	signal(SIGHUP, killchild);
29457429Smarkm	return 0;
29557429Smarkm}
29657429Smarkm
297221420Sdes/*
298221420Sdes * This functions executes a command simlar to do_cmd(), but expects the
299221420Sdes * input and output descriptors to be setup by a previous call to do_cmd().
300221420Sdes * This way the input and output of two commands can be connected.
301221420Sdes */
302221420Sdesint
303221420Sdesdo_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout)
304221420Sdes{
305221420Sdes	pid_t pid;
306221420Sdes	int status;
307221420Sdes
308221420Sdes	if (verbose_mode)
309221420Sdes		fprintf(stderr,
310221420Sdes		    "Executing: 2nd program %s host %s, user %s, command %s\n",
311221420Sdes		    ssh_program, host,
312221420Sdes		    remuser ? remuser : "(unspecified)", cmd);
313221420Sdes
314221420Sdes	/* Fork a child to execute the command on the remote host using ssh. */
315221420Sdes	pid = fork();
316221420Sdes	if (pid == 0) {
317221420Sdes		dup2(fdin, 0);
318221420Sdes		dup2(fdout, 1);
319221420Sdes
320221420Sdes		replacearg(&args, 0, "%s", ssh_program);
321221420Sdes		if (remuser != NULL) {
322221420Sdes			addargs(&args, "-l");
323221420Sdes			addargs(&args, "%s", remuser);
324221420Sdes		}
325221420Sdes		addargs(&args, "--");
326221420Sdes		addargs(&args, "%s", host);
327221420Sdes		addargs(&args, "%s", cmd);
328221420Sdes
329221420Sdes		execvp(ssh_program, args.list);
330221420Sdes		perror(ssh_program);
331221420Sdes		exit(1);
332221420Sdes	} else if (pid == -1) {
333221420Sdes		fatal("fork: %s", strerror(errno));
334221420Sdes	}
335221420Sdes	while (waitpid(pid, &status, 0) == -1)
336221420Sdes		if (errno != EINTR)
337221420Sdes			fatal("do_cmd2: waitpid: %s", strerror(errno));
338221420Sdes	return 0;
339221420Sdes}
340221420Sdes
34157429Smarkmtypedef struct {
342149753Sdes	size_t cnt;
34357429Smarkm	char *buf;
34457429Smarkm} BUF;
34557429Smarkm
34657429SmarkmBUF *allocbuf(BUF *, int, int);
34757429Smarkmvoid lostconn(int);
34857429Smarkmint okname(char *);
34957429Smarkmvoid run_err(const char *,...);
35057429Smarkmvoid verifydir(char *);
35157429Smarkm
35257429Smarkmstruct passwd *pwd;
35357429Smarkmuid_t userid;
35457429Smarkmint errs, remin, remout;
35557429Smarkmint pflag, iamremote, iamrecursive, targetshouldbedirectory;
35657429Smarkm
35757429Smarkm#define	CMDNEEDS	64
35857429Smarkmchar cmd[CMDNEEDS];		/* must hold "rcp -r -p -d\0" */
35957429Smarkm
36057429Smarkmint response(void);
36157429Smarkmvoid rsource(char *, struct stat *);
36257429Smarkmvoid sink(int, char *[]);
36357429Smarkmvoid source(int, char *[]);
36457429Smarkmvoid tolocal(int, char *[]);
36557429Smarkmvoid toremote(char *, int, char *[]);
36657429Smarkmvoid usage(void);
36757429Smarkm
36857429Smarkmint
369124211Sdesmain(int argc, char **argv)
37057429Smarkm{
371162856Sdes	int ch, fflag, tflag, status, n;
372221420Sdes	char *targ, **newargv;
373221420Sdes	const char *errstr;
37457429Smarkm	extern char *optarg;
37557429Smarkm	extern int optind;
37657429Smarkm
377157019Sdes	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
378157019Sdes	sanitise_stdfd();
379157019Sdes
380162856Sdes	/* Copy argv, because we modify it */
381162856Sdes	newargv = xcalloc(MAX(argc + 1, 1), sizeof(*newargv));
382162856Sdes	for (n = 0; n < argc; n++)
383162856Sdes		newargv[n] = xstrdup(argv[n]);
384162856Sdes	argv = newargv;
385162856Sdes
386124211Sdes	__progname = ssh_get_progname(argv[0]);
38798937Sdes
388157019Sdes	memset(&args, '\0', sizeof(args));
389221420Sdes	memset(&remote_remote_args, '\0', sizeof(remote_remote_args));
390221420Sdes	args.list = remote_remote_args.list = NULL;
391157019Sdes	addargs(&args, "%s", ssh_program);
39292555Sdes	addargs(&args, "-x");
393221420Sdes	addargs(&args, "-oForwardAgent=no");
394221420Sdes	addargs(&args, "-oPermitLocalCommand=no");
395221420Sdes	addargs(&args, "-oClearAllForwardings=yes");
39669587Sgreen
39757429Smarkm	fflag = tflag = 0;
398221420Sdes	while ((ch = getopt(argc, argv, "dfl:prtvBCc:i:P:q12346S:o:F:")) != -1)
39957429Smarkm		switch (ch) {
40057429Smarkm		/* User-visible flags. */
401113911Sdes		case '1':
402113911Sdes		case '2':
40357429Smarkm		case '4':
40457429Smarkm		case '6':
40569587Sgreen		case 'C':
40692555Sdes			addargs(&args, "-%c", ch);
407221420Sdes			addargs(&remote_remote_args, "-%c", ch);
40857429Smarkm			break;
409221420Sdes		case '3':
410221420Sdes			throughlocal = 1;
411221420Sdes			break;
41269587Sgreen		case 'o':
41369587Sgreen		case 'c':
41469587Sgreen		case 'i':
41592555Sdes		case 'F':
416221420Sdes			addargs(&remote_remote_args, "-%c", ch);
417221420Sdes			addargs(&remote_remote_args, "%s", optarg);
418204917Sdes			addargs(&args, "-%c", ch);
419204917Sdes			addargs(&args, "%s", optarg);
42069587Sgreen			break;
42169587Sgreen		case 'P':
422221420Sdes			addargs(&remote_remote_args, "-p");
423221420Sdes			addargs(&remote_remote_args, "%s", optarg);
424204917Sdes			addargs(&args, "-p");
425204917Sdes			addargs(&args, "%s", optarg);
42669587Sgreen			break;
42769587Sgreen		case 'B':
428221420Sdes			addargs(&remote_remote_args, "-oBatchmode=yes");
429221420Sdes			addargs(&args, "-oBatchmode=yes");
43069587Sgreen			break;
431113911Sdes		case 'l':
432221420Sdes			limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
433221420Sdes			    &errstr);
434221420Sdes			if (errstr != NULL)
435113911Sdes				usage();
436221420Sdes			limit_kbps *= 1024; /* kbps */
437221420Sdes			bandwidth_limit_init(&bwlimit, limit_kbps, COPY_BUFLEN);
438113911Sdes			break;
43957429Smarkm		case 'p':
44057429Smarkm			pflag = 1;
44157429Smarkm			break;
44257429Smarkm		case 'r':
44357429Smarkm			iamrecursive = 1;
44457429Smarkm			break;
44565668Skris		case 'S':
44669587Sgreen			ssh_program = xstrdup(optarg);
44765668Skris			break;
44869587Sgreen		case 'v':
44992555Sdes			addargs(&args, "-v");
450221420Sdes			addargs(&remote_remote_args, "-v");
45169587Sgreen			verbose_mode = 1;
45269587Sgreen			break;
45369587Sgreen		case 'q':
454126277Sdes			addargs(&args, "-q");
455221420Sdes			addargs(&remote_remote_args, "-q");
45669587Sgreen			showprogress = 0;
45769587Sgreen			break;
45865668Skris
45957429Smarkm		/* Server options. */
46057429Smarkm		case 'd':
46157429Smarkm			targetshouldbedirectory = 1;
46257429Smarkm			break;
46357429Smarkm		case 'f':	/* "from" */
46457429Smarkm			iamremote = 1;
46557429Smarkm			fflag = 1;
46657429Smarkm			break;
46757429Smarkm		case 't':	/* "to" */
46857429Smarkm			iamremote = 1;
46957429Smarkm			tflag = 1;
47098937Sdes#ifdef HAVE_CYGWIN
47198937Sdes			setmode(0, O_BINARY);
47298937Sdes#endif
47357429Smarkm			break;
47457429Smarkm		default:
47557429Smarkm			usage();
47657429Smarkm		}
47757429Smarkm	argc -= optind;
47857429Smarkm	argv += optind;
47957429Smarkm
48057429Smarkm	if ((pwd = getpwuid(userid = getuid())) == NULL)
481124211Sdes		fatal("unknown user %u", (u_int) userid);
48257429Smarkm
483181111Sdes	if (!isatty(STDOUT_FILENO))
48457429Smarkm		showprogress = 0;
48557429Smarkm
48657429Smarkm	remin = STDIN_FILENO;
48757429Smarkm	remout = STDOUT_FILENO;
48857429Smarkm
48976259Sgreen	if (fflag) {
49057429Smarkm		/* Follow "protocol", send data. */
49157429Smarkm		(void) response();
49257429Smarkm		source(argc, argv);
49357429Smarkm		exit(errs != 0);
49457429Smarkm	}
49557429Smarkm	if (tflag) {
49657429Smarkm		/* Receive data. */
49757429Smarkm		sink(argc, argv);
49857429Smarkm		exit(errs != 0);
49957429Smarkm	}
50057429Smarkm	if (argc < 2)
50157429Smarkm		usage();
50257429Smarkm	if (argc > 2)
50357429Smarkm		targetshouldbedirectory = 1;
50457429Smarkm
50557429Smarkm	remin = remout = -1;
506113911Sdes	do_cmd_pid = -1;
50757429Smarkm	/* Command to be executed on remote system using "ssh". */
50876259Sgreen	(void) snprintf(cmd, sizeof cmd, "scp%s%s%s%s",
50976259Sgreen	    verbose_mode ? " -v" : "",
51065668Skris	    iamrecursive ? " -r" : "", pflag ? " -p" : "",
51165668Skris	    targetshouldbedirectory ? " -d" : "");
51257429Smarkm
51357429Smarkm	(void) signal(SIGPIPE, lostconn);
51457429Smarkm
51557429Smarkm	if ((targ = colon(argv[argc - 1])))	/* Dest is remote host. */
51657429Smarkm		toremote(targ, argc, argv);
51757429Smarkm	else {
51857429Smarkm		if (targetshouldbedirectory)
51957429Smarkm			verifydir(argv[argc - 1]);
520157019Sdes		tolocal(argc, argv);	/* Dest is local host. */
52157429Smarkm	}
522113911Sdes	/*
523113911Sdes	 * Finally check the exit status of the ssh process, if one was forked
524192595Sdes	 * and no error has occurred yet
525113911Sdes	 */
526113911Sdes	if (do_cmd_pid != -1 && errs == 0) {
527113911Sdes		if (remin != -1)
528113911Sdes		    (void) close(remin);
529113911Sdes		if (remout != -1)
530113911Sdes		    (void) close(remout);
531113911Sdes		if (waitpid(do_cmd_pid, &status, 0) == -1)
532113911Sdes			errs = 1;
533113911Sdes		else {
534113911Sdes			if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
535113911Sdes				errs = 1;
536113911Sdes		}
537113911Sdes	}
53857429Smarkm	exit(errs != 0);
53957429Smarkm}
54057429Smarkm
541221420Sdes/* Callback from atomicio6 to update progress meter and limit bandwidth */
542221420Sdesstatic int
543221420Sdesscpio(void *_cnt, size_t s)
544181111Sdes{
545221420Sdes	off_t *cnt = (off_t *)_cnt;
546181111Sdes
547221420Sdes	*cnt += s;
548221420Sdes	if (limit_kbps > 0)
549221420Sdes		bandwidth_limit(&bwlimit, s);
550221420Sdes	return 0;
551181111Sdes}
552181111Sdes
55357429Smarkmvoid
554124211Sdestoremote(char *targ, int argc, char **argv)
55557429Smarkm{
556147005Sdes	char *bp, *host, *src, *suser, *thost, *tuser, *arg;
557157019Sdes	arglist alist;
558162856Sdes	int i;
559221420Sdes	u_int j;
56057429Smarkm
561157019Sdes	memset(&alist, '\0', sizeof(alist));
562157019Sdes	alist.list = NULL;
563157019Sdes
56457429Smarkm	*targ++ = 0;
56557429Smarkm	if (*targ == 0)
56657429Smarkm		targ = ".";
56757429Smarkm
568147005Sdes	arg = xstrdup(argv[argc - 1]);
569147005Sdes	if ((thost = strrchr(arg, '@'))) {
57057429Smarkm		/* user@host */
57157429Smarkm		*thost++ = 0;
572147005Sdes		tuser = arg;
57357429Smarkm		if (*tuser == '\0')
57457429Smarkm			tuser = NULL;
57557429Smarkm	} else {
576147005Sdes		thost = arg;
57757429Smarkm		tuser = NULL;
57857429Smarkm	}
57957429Smarkm
580157019Sdes	if (tuser != NULL && !okname(tuser)) {
581157019Sdes		xfree(arg);
582157019Sdes		return;
583157019Sdes	}
584157019Sdes
58557429Smarkm	for (i = 0; i < argc - 1; i++) {
58657429Smarkm		src = colon(argv[i]);
587221420Sdes		if (src && throughlocal) {	/* extended remote to remote */
588221420Sdes			*src++ = 0;
589221420Sdes			if (*src == 0)
590221420Sdes				src = ".";
591221420Sdes			host = strrchr(argv[i], '@');
592221420Sdes			if (host) {
593221420Sdes				*host++ = 0;
594221420Sdes				host = cleanhostname(host);
595221420Sdes				suser = argv[i];
596221420Sdes				if (*suser == '\0')
597221420Sdes					suser = pwd->pw_name;
598221420Sdes				else if (!okname(suser))
599221420Sdes					continue;
600221420Sdes			} else {
601221420Sdes				host = cleanhostname(argv[i]);
602221420Sdes				suser = NULL;
603221420Sdes			}
604221420Sdes			xasprintf(&bp, "%s -f -- %s", cmd, src);
605221420Sdes			if (do_cmd(host, suser, bp, &remin, &remout) < 0)
606221420Sdes				exit(1);
607221420Sdes			(void) xfree(bp);
608221420Sdes			host = cleanhostname(thost);
609221420Sdes			xasprintf(&bp, "%s -t -- %s", cmd, targ);
610221420Sdes			if (do_cmd2(host, tuser, bp, remin, remout) < 0)
611221420Sdes				exit(1);
612221420Sdes			(void) xfree(bp);
613221420Sdes			(void) close(remin);
614221420Sdes			(void) close(remout);
615221420Sdes			remin = remout = -1;
616221420Sdes		} else if (src) {	/* standard remote to remote */
617157019Sdes			freeargs(&alist);
618157019Sdes			addargs(&alist, "%s", ssh_program);
619157019Sdes			addargs(&alist, "-x");
620221420Sdes			addargs(&alist, "-oClearAllForwardings=yes");
621157019Sdes			addargs(&alist, "-n");
622221420Sdes			for (j = 0; j < remote_remote_args.num; j++) {
623221420Sdes				addargs(&alist, "%s",
624221420Sdes				    remote_remote_args.list[j]);
625221420Sdes			}
62657429Smarkm			*src++ = 0;
62757429Smarkm			if (*src == 0)
62857429Smarkm				src = ".";
629113911Sdes			host = strrchr(argv[i], '@');
630157019Sdes
63157429Smarkm			if (host) {
63257429Smarkm				*host++ = 0;
63357429Smarkm				host = cleanhostname(host);
63457429Smarkm				suser = argv[i];
63557429Smarkm				if (*suser == '\0')
63657429Smarkm					suser = pwd->pw_name;
637157019Sdes				else if (!okname(suser))
63857429Smarkm					continue;
639157019Sdes				addargs(&alist, "-l");
640157019Sdes				addargs(&alist, "%s", suser);
64157429Smarkm			} else {
64257429Smarkm				host = cleanhostname(argv[i]);
64357429Smarkm			}
644204917Sdes			addargs(&alist, "--");
645157019Sdes			addargs(&alist, "%s", host);
646157019Sdes			addargs(&alist, "%s", cmd);
647157019Sdes			addargs(&alist, "%s", src);
648157019Sdes			addargs(&alist, "%s%s%s:%s",
649157019Sdes			    tuser ? tuser : "", tuser ? "@" : "",
650157019Sdes			    thost, targ);
651157019Sdes			if (do_local_cmd(&alist) != 0)
652126277Sdes				errs = 1;
65357429Smarkm		} else {	/* local to remote */
65457429Smarkm			if (remin == -1) {
655204917Sdes				xasprintf(&bp, "%s -t -- %s", cmd, targ);
65657429Smarkm				host = cleanhostname(thost);
65765668Skris				if (do_cmd(host, tuser, bp, &remin,
658162856Sdes				    &remout) < 0)
65957429Smarkm					exit(1);
66057429Smarkm				if (response() < 0)
66157429Smarkm					exit(1);
66257429Smarkm				(void) xfree(bp);
66357429Smarkm			}
66457429Smarkm			source(1, argv + i);
66557429Smarkm		}
66657429Smarkm	}
667162856Sdes	xfree(arg);
66857429Smarkm}
66957429Smarkm
67057429Smarkmvoid
671124211Sdestolocal(int argc, char **argv)
67257429Smarkm{
67357429Smarkm	char *bp, *host, *src, *suser;
674157019Sdes	arglist alist;
675162856Sdes	int i;
67657429Smarkm
677157019Sdes	memset(&alist, '\0', sizeof(alist));
678157019Sdes	alist.list = NULL;
679157019Sdes
68057429Smarkm	for (i = 0; i < argc - 1; i++) {
68157429Smarkm		if (!(src = colon(argv[i]))) {	/* Local to local. */
682157019Sdes			freeargs(&alist);
683157019Sdes			addargs(&alist, "%s", _PATH_CP);
684157019Sdes			if (iamrecursive)
685157019Sdes				addargs(&alist, "-r");
686157019Sdes			if (pflag)
687157019Sdes				addargs(&alist, "-p");
688204917Sdes			addargs(&alist, "--");
689157019Sdes			addargs(&alist, "%s", argv[i]);
690157019Sdes			addargs(&alist, "%s", argv[argc-1]);
691157019Sdes			if (do_local_cmd(&alist))
69257429Smarkm				++errs;
69357429Smarkm			continue;
69457429Smarkm		}
69557429Smarkm		*src++ = 0;
69657429Smarkm		if (*src == 0)
69757429Smarkm			src = ".";
698113911Sdes		if ((host = strrchr(argv[i], '@')) == NULL) {
69957429Smarkm			host = argv[i];
70057429Smarkm			suser = NULL;
70157429Smarkm		} else {
70257429Smarkm			*host++ = 0;
70357429Smarkm			suser = argv[i];
70457429Smarkm			if (*suser == '\0')
70557429Smarkm				suser = pwd->pw_name;
70657429Smarkm		}
70757429Smarkm		host = cleanhostname(host);
708204917Sdes		xasprintf(&bp, "%s -f -- %s", cmd, src);
709162856Sdes		if (do_cmd(host, suser, bp, &remin, &remout) < 0) {
71057429Smarkm			(void) xfree(bp);
71157429Smarkm			++errs;
71257429Smarkm			continue;
71357429Smarkm		}
71457429Smarkm		xfree(bp);
71557429Smarkm		sink(1, argv + argc - 1);
71657429Smarkm		(void) close(remin);
71757429Smarkm		remin = remout = -1;
71857429Smarkm	}
71957429Smarkm}
72057429Smarkm
72157429Smarkmvoid
722124211Sdessource(int argc, char **argv)
72357429Smarkm{
72457429Smarkm	struct stat stb;
72557429Smarkm	static BUF buffer;
72657429Smarkm	BUF *bp;
727181111Sdes	off_t i, statbytes;
728181111Sdes	size_t amt;
729149753Sdes	int fd = -1, haderr, indx;
730181111Sdes	char *last, *name, buf[2048], encname[MAXPATHLEN];
73176259Sgreen	int len;
73257429Smarkm
73357429Smarkm	for (indx = 0; indx < argc; ++indx) {
73457429Smarkm		name = argv[indx];
73557429Smarkm		statbytes = 0;
73676259Sgreen		len = strlen(name);
73776259Sgreen		while (len > 1 && name[len-1] == '/')
73876259Sgreen			name[--len] = '\0';
739181111Sdes		if ((fd = open(name, O_RDONLY|O_NONBLOCK, 0)) < 0)
740181111Sdes			goto syserr;
74192555Sdes		if (strchr(name, '\n') != NULL) {
742181111Sdes			strnvis(encname, name, sizeof(encname), VIS_NL);
743181111Sdes			name = encname;
74492555Sdes		}
74557429Smarkm		if (fstat(fd, &stb) < 0) {
74657429Smarkmsyserr:			run_err("%s: %s", name, strerror(errno));
74757429Smarkm			goto next;
74857429Smarkm		}
749181111Sdes		if (stb.st_size < 0) {
750181111Sdes			run_err("%s: %s", name, "Negative file size");
751181111Sdes			goto next;
752181111Sdes		}
753181111Sdes		unset_nonblock(fd);
75457429Smarkm		switch (stb.st_mode & S_IFMT) {
75557429Smarkm		case S_IFREG:
75657429Smarkm			break;
75757429Smarkm		case S_IFDIR:
75857429Smarkm			if (iamrecursive) {
75957429Smarkm				rsource(name, &stb);
76057429Smarkm				goto next;
76157429Smarkm			}
76257429Smarkm			/* FALLTHROUGH */
76357429Smarkm		default:
76457429Smarkm			run_err("%s: not a regular file", name);
76557429Smarkm			goto next;
76657429Smarkm		}
76757429Smarkm		if ((last = strrchr(name, '/')) == NULL)
76857429Smarkm			last = name;
76957429Smarkm		else
77057429Smarkm			++last;
77157429Smarkm		curfile = last;
77257429Smarkm		if (pflag) {
77357429Smarkm			/*
77457429Smarkm			 * Make it compatible with possible future
77557429Smarkm			 * versions expecting microseconds.
77657429Smarkm			 */
77776259Sgreen			(void) snprintf(buf, sizeof buf, "T%lu 0 %lu 0\n",
778181111Sdes			    (u_long) (stb.st_mtime < 0 ? 0 : stb.st_mtime),
779181111Sdes			    (u_long) (stb.st_atime < 0 ? 0 : stb.st_atime));
780181111Sdes			if (verbose_mode) {
781181111Sdes				fprintf(stderr, "File mtime %ld atime %ld\n",
782181111Sdes				    (long)stb.st_mtime, (long)stb.st_atime);
783181111Sdes				fprintf(stderr, "Sending file timestamps: %s",
784181111Sdes				    buf);
785181111Sdes			}
786124211Sdes			(void) atomicio(vwrite, remout, buf, strlen(buf));
78757429Smarkm			if (response() < 0)
78857429Smarkm				goto next;
78957429Smarkm		}
79057429Smarkm#define	FILEMODEMASK	(S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
79176259Sgreen		snprintf(buf, sizeof buf, "C%04o %lld %s\n",
79276259Sgreen		    (u_int) (stb.st_mode & FILEMODEMASK),
793157019Sdes		    (long long)stb.st_size, last);
79457429Smarkm		if (verbose_mode) {
79557429Smarkm			fprintf(stderr, "Sending file modes: %s", buf);
79657429Smarkm		}
797124211Sdes		(void) atomicio(vwrite, remout, buf, strlen(buf));
79857429Smarkm		if (response() < 0)
79957429Smarkm			goto next;
800181111Sdes		if ((bp = allocbuf(&buffer, fd, COPY_BUFLEN)) == NULL) {
801157019Sdesnext:			if (fd != -1) {
802157019Sdes				(void) close(fd);
803157019Sdes				fd = -1;
804157019Sdes			}
80557429Smarkm			continue;
80657429Smarkm		}
807113911Sdes		if (showprogress)
808113911Sdes			start_progress_meter(curfile, stb.st_size, &statbytes);
809181111Sdes		set_nonblock(remout);
81057429Smarkm		for (haderr = i = 0; i < stb.st_size; i += bp->cnt) {
81157429Smarkm			amt = bp->cnt;
812181111Sdes			if (i + (off_t)amt > stb.st_size)
81357429Smarkm				amt = stb.st_size - i;
81457429Smarkm			if (!haderr) {
815181111Sdes				if (atomicio(read, fd, bp->buf, amt) != amt)
816149753Sdes					haderr = errno;
81757429Smarkm			}
818181111Sdes			/* Keep writing after error to retain sync */
819181111Sdes			if (haderr) {
820181111Sdes				(void)atomicio(vwrite, remout, bp->buf, amt);
821181111Sdes				continue;
82257429Smarkm			}
823221420Sdes			if (atomicio6(vwrite, remout, bp->buf, amt, scpio,
824181111Sdes			    &statbytes) != amt)
825181111Sdes				haderr = errno;
82657429Smarkm		}
827181111Sdes		unset_nonblock(remout);
82857429Smarkm		if (showprogress)
829113911Sdes			stop_progress_meter();
83057429Smarkm
831157019Sdes		if (fd != -1) {
832157019Sdes			if (close(fd) < 0 && !haderr)
833157019Sdes				haderr = errno;
834157019Sdes			fd = -1;
835157019Sdes		}
83657429Smarkm		if (!haderr)
837124211Sdes			(void) atomicio(vwrite, remout, "", 1);
83857429Smarkm		else
83957429Smarkm			run_err("%s: %s", name, strerror(haderr));
84057429Smarkm		(void) response();
84157429Smarkm	}
84257429Smarkm}
84357429Smarkm
84457429Smarkmvoid
845124211Sdesrsource(char *name, struct stat *statp)
84657429Smarkm{
84757429Smarkm	DIR *dirp;
84857429Smarkm	struct dirent *dp;
84957429Smarkm	char *last, *vect[1], path[1100];
85057429Smarkm
85157429Smarkm	if (!(dirp = opendir(name))) {
85257429Smarkm		run_err("%s: %s", name, strerror(errno));
85357429Smarkm		return;
85457429Smarkm	}
85557429Smarkm	last = strrchr(name, '/');
85657429Smarkm	if (last == 0)
85757429Smarkm		last = name;
85857429Smarkm	else
85957429Smarkm		last++;
86057429Smarkm	if (pflag) {
86176259Sgreen		(void) snprintf(path, sizeof(path), "T%lu 0 %lu 0\n",
86276259Sgreen		    (u_long) statp->st_mtime,
86376259Sgreen		    (u_long) statp->st_atime);
864124211Sdes		(void) atomicio(vwrite, remout, path, strlen(path));
86557429Smarkm		if (response() < 0) {
86657429Smarkm			closedir(dirp);
86757429Smarkm			return;
86857429Smarkm		}
86957429Smarkm	}
87076259Sgreen	(void) snprintf(path, sizeof path, "D%04o %d %.1024s\n",
87176259Sgreen	    (u_int) (statp->st_mode & FILEMODEMASK), 0, last);
87257429Smarkm	if (verbose_mode)
87357429Smarkm		fprintf(stderr, "Entering directory: %s", path);
874124211Sdes	(void) atomicio(vwrite, remout, path, strlen(path));
87557429Smarkm	if (response() < 0) {
87657429Smarkm		closedir(dirp);
87757429Smarkm		return;
87857429Smarkm	}
87976259Sgreen	while ((dp = readdir(dirp)) != NULL) {
88057429Smarkm		if (dp->d_ino == 0)
88157429Smarkm			continue;
88257429Smarkm		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
88357429Smarkm			continue;
88457429Smarkm		if (strlen(name) + 1 + strlen(dp->d_name) >= sizeof(path) - 1) {
88557429Smarkm			run_err("%s/%s: name too long", name, dp->d_name);
88657429Smarkm			continue;
88757429Smarkm		}
88876259Sgreen		(void) snprintf(path, sizeof path, "%s/%s", name, dp->d_name);
88957429Smarkm		vect[0] = path;
89057429Smarkm		source(1, vect);
89157429Smarkm	}
89257429Smarkm	(void) closedir(dirp);
893124211Sdes	(void) atomicio(vwrite, remout, "E\n", 2);
89457429Smarkm	(void) response();
89557429Smarkm}
89657429Smarkm
89757429Smarkmvoid
898124211Sdessink(int argc, char **argv)
89957429Smarkm{
90057429Smarkm	static BUF buffer;
90157429Smarkm	struct stat stb;
90257429Smarkm	enum {
90357429Smarkm		YES, NO, DISPLAYED
90457429Smarkm	} wrerr;
90557429Smarkm	BUF *bp;
906149753Sdes	off_t i;
907149753Sdes	size_t j, count;
908162856Sdes	int amt, exists, first, ofd;
909162856Sdes	mode_t mode, omode, mask;
910113911Sdes	off_t size, statbytes;
91165668Skris	int setimes, targisdir, wrerrno = 0;
91257429Smarkm	char ch, *cp, *np, *targ, *why, *vect[1], buf[2048];
91369587Sgreen	struct timeval tv[2];
91457429Smarkm
91576259Sgreen#define	atime	tv[0]
91676259Sgreen#define	mtime	tv[1]
917147005Sdes#define	SCREWUP(str)	{ why = str; goto screwup; }
91857429Smarkm
91957429Smarkm	setimes = targisdir = 0;
92057429Smarkm	mask = umask(0);
92157429Smarkm	if (!pflag)
92257429Smarkm		(void) umask(mask);
92357429Smarkm	if (argc != 1) {
92457429Smarkm		run_err("ambiguous target");
92557429Smarkm		exit(1);
92657429Smarkm	}
92757429Smarkm	targ = *argv;
92857429Smarkm	if (targetshouldbedirectory)
92957429Smarkm		verifydir(targ);
93057429Smarkm
931124211Sdes	(void) atomicio(vwrite, remout, "", 1);
93257429Smarkm	if (stat(targ, &stb) == 0 && S_ISDIR(stb.st_mode))
93357429Smarkm		targisdir = 1;
93457429Smarkm	for (first = 1;; first = 0) {
93557429Smarkm		cp = buf;
936149753Sdes		if (atomicio(read, remin, cp, 1) != 1)
93757429Smarkm			return;
93857429Smarkm		if (*cp++ == '\n')
93957429Smarkm			SCREWUP("unexpected <newline>");
94057429Smarkm		do {
94160573Skris			if (atomicio(read, remin, &ch, sizeof(ch)) != sizeof(ch))
94257429Smarkm				SCREWUP("lost connection");
94357429Smarkm			*cp++ = ch;
94457429Smarkm		} while (cp < &buf[sizeof(buf) - 1] && ch != '\n');
94557429Smarkm		*cp = 0;
946137019Sdes		if (verbose_mode)
947137019Sdes			fprintf(stderr, "Sink: %s", buf);
94857429Smarkm
94957429Smarkm		if (buf[0] == '\01' || buf[0] == '\02') {
95057429Smarkm			if (iamremote == 0)
951124211Sdes				(void) atomicio(vwrite, STDERR_FILENO,
95276259Sgreen				    buf + 1, strlen(buf + 1));
95357429Smarkm			if (buf[0] == '\02')
95457429Smarkm				exit(1);
95557429Smarkm			++errs;
95657429Smarkm			continue;
95757429Smarkm		}
95857429Smarkm		if (buf[0] == 'E') {
959124211Sdes			(void) atomicio(vwrite, remout, "", 1);
96057429Smarkm			return;
96157429Smarkm		}
96257429Smarkm		if (ch == '\n')
96357429Smarkm			*--cp = 0;
96457429Smarkm
96557429Smarkm		cp = buf;
96657429Smarkm		if (*cp == 'T') {
96757429Smarkm			setimes++;
96857429Smarkm			cp++;
96976259Sgreen			mtime.tv_sec = strtol(cp, &cp, 10);
97076259Sgreen			if (!cp || *cp++ != ' ')
97157429Smarkm				SCREWUP("mtime.sec not delimited");
97276259Sgreen			mtime.tv_usec = strtol(cp, &cp, 10);
97376259Sgreen			if (!cp || *cp++ != ' ')
97457429Smarkm				SCREWUP("mtime.usec not delimited");
97576259Sgreen			atime.tv_sec = strtol(cp, &cp, 10);
97676259Sgreen			if (!cp || *cp++ != ' ')
97757429Smarkm				SCREWUP("atime.sec not delimited");
97876259Sgreen			atime.tv_usec = strtol(cp, &cp, 10);
97976259Sgreen			if (!cp || *cp++ != '\0')
98057429Smarkm				SCREWUP("atime.usec not delimited");
981124211Sdes			(void) atomicio(vwrite, remout, "", 1);
98257429Smarkm			continue;
98357429Smarkm		}
98457429Smarkm		if (*cp != 'C' && *cp != 'D') {
98557429Smarkm			/*
98657429Smarkm			 * Check for the case "rcp remote:foo\* local:bar".
98757429Smarkm			 * In this case, the line "No match." can be returned
98857429Smarkm			 * by the shell before the rcp command on the remote is
98957429Smarkm			 * executed so the ^Aerror_message convention isn't
99057429Smarkm			 * followed.
99157429Smarkm			 */
99257429Smarkm			if (first) {
99357429Smarkm				run_err("%s", cp);
99457429Smarkm				exit(1);
99557429Smarkm			}
99657429Smarkm			SCREWUP("expected control record");
99757429Smarkm		}
99857429Smarkm		mode = 0;
99957429Smarkm		for (++cp; cp < buf + 5; cp++) {
100057429Smarkm			if (*cp < '0' || *cp > '7')
100157429Smarkm				SCREWUP("bad mode");
100257429Smarkm			mode = (mode << 3) | (*cp - '0');
100357429Smarkm		}
100457429Smarkm		if (*cp++ != ' ')
100557429Smarkm			SCREWUP("mode not delimited");
100657429Smarkm
100776259Sgreen		for (size = 0; isdigit(*cp);)
100857429Smarkm			size = size * 10 + (*cp++ - '0');
100957429Smarkm		if (*cp++ != ' ')
101057429Smarkm			SCREWUP("size not delimited");
1011137019Sdes		if ((strchr(cp, '/') != NULL) || (strcmp(cp, "..") == 0)) {
1012137019Sdes			run_err("error: unexpected filename: %s", cp);
1013137019Sdes			exit(1);
1014137019Sdes		}
101557429Smarkm		if (targisdir) {
101657429Smarkm			static char *namebuf;
1017149753Sdes			static size_t cursize;
101857429Smarkm			size_t need;
101957429Smarkm
102057429Smarkm			need = strlen(targ) + strlen(cp) + 250;
102176259Sgreen			if (need > cursize) {
102276259Sgreen				if (namebuf)
102376259Sgreen					xfree(namebuf);
102457429Smarkm				namebuf = xmalloc(need);
102576259Sgreen				cursize = need;
102676259Sgreen			}
102776259Sgreen			(void) snprintf(namebuf, need, "%s%s%s", targ,
102898675Sdes			    strcmp(targ, "/") ? "/" : "", cp);
102957429Smarkm			np = namebuf;
103057429Smarkm		} else
103157429Smarkm			np = targ;
103257429Smarkm		curfile = cp;
103357429Smarkm		exists = stat(np, &stb) == 0;
103457429Smarkm		if (buf[0] == 'D') {
103557429Smarkm			int mod_flag = pflag;
1036137019Sdes			if (!iamrecursive)
1037137019Sdes				SCREWUP("received directory without -r");
103857429Smarkm			if (exists) {
103957429Smarkm				if (!S_ISDIR(stb.st_mode)) {
104057429Smarkm					errno = ENOTDIR;
104157429Smarkm					goto bad;
104257429Smarkm				}
104357429Smarkm				if (pflag)
104457429Smarkm					(void) chmod(np, mode);
104557429Smarkm			} else {
104657429Smarkm				/* Handle copying from a read-only
104757429Smarkm				   directory */
104857429Smarkm				mod_flag = 1;
104957429Smarkm				if (mkdir(np, mode | S_IRWXU) < 0)
105057429Smarkm					goto bad;
105157429Smarkm			}
105276259Sgreen			vect[0] = xstrdup(np);
105357429Smarkm			sink(1, vect);
105457429Smarkm			if (setimes) {
105557429Smarkm				setimes = 0;
105676259Sgreen				if (utimes(vect[0], tv) < 0)
105757429Smarkm					run_err("%s: set times: %s",
105876259Sgreen					    vect[0], strerror(errno));
105957429Smarkm			}
106057429Smarkm			if (mod_flag)
106176259Sgreen				(void) chmod(vect[0], mode);
106276259Sgreen			if (vect[0])
106376259Sgreen				xfree(vect[0]);
106457429Smarkm			continue;
106557429Smarkm		}
106657429Smarkm		omode = mode;
106757429Smarkm		mode |= S_IWRITE;
106892555Sdes		if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) < 0) {
106957429Smarkmbad:			run_err("%s: %s", np, strerror(errno));
107057429Smarkm			continue;
107157429Smarkm		}
1072124211Sdes		(void) atomicio(vwrite, remout, "", 1);
1073181111Sdes		if ((bp = allocbuf(&buffer, ofd, COPY_BUFLEN)) == NULL) {
107457429Smarkm			(void) close(ofd);
107557429Smarkm			continue;
107657429Smarkm		}
107757429Smarkm		cp = bp->buf;
107857429Smarkm		wrerr = NO;
107957429Smarkm
108057429Smarkm		statbytes = 0;
1081113911Sdes		if (showprogress)
1082113911Sdes			start_progress_meter(curfile, size, &statbytes);
1083181111Sdes		set_nonblock(remin);
1084181111Sdes		for (count = i = 0; i < size; i += bp->cnt) {
1085181111Sdes			amt = bp->cnt;
108657429Smarkm			if (i + amt > size)
108757429Smarkm				amt = size - i;
108857429Smarkm			count += amt;
108957429Smarkm			do {
1090221420Sdes				j = atomicio6(read, remin, cp, amt,
1091221420Sdes				    scpio, &statbytes);
1092149753Sdes				if (j == 0) {
1093181111Sdes					run_err("%s", j != EPIPE ?
1094181111Sdes					    strerror(errno) :
109576259Sgreen					    "dropped connection");
109657429Smarkm					exit(1);
109757429Smarkm				}
109857429Smarkm				amt -= j;
109957429Smarkm				cp += j;
110057429Smarkm			} while (amt > 0);
1101126277Sdes
110257429Smarkm			if (count == bp->cnt) {
110357429Smarkm				/* Keep reading so we stay sync'd up. */
110457429Smarkm				if (wrerr == NO) {
1105149753Sdes					if (atomicio(vwrite, ofd, bp->buf,
1106149753Sdes					    count) != count) {
110757429Smarkm						wrerr = YES;
1108149753Sdes						wrerrno = errno;
110957429Smarkm					}
111057429Smarkm				}
111157429Smarkm				count = 0;
111257429Smarkm				cp = bp->buf;
111357429Smarkm			}
111457429Smarkm		}
1115181111Sdes		unset_nonblock(remin);
111657429Smarkm		if (showprogress)
1117113911Sdes			stop_progress_meter();
111857429Smarkm		if (count != 0 && wrerr == NO &&
1119149753Sdes		    atomicio(vwrite, ofd, bp->buf, count) != count) {
112057429Smarkm			wrerr = YES;
1121149753Sdes			wrerrno = errno;
112257429Smarkm		}
1123181111Sdes		if (wrerr == NO && (!exists || S_ISREG(stb.st_mode)) &&
1124181111Sdes		    ftruncate(ofd, size) != 0) {
112557429Smarkm			run_err("%s: truncate: %s", np, strerror(errno));
112657429Smarkm			wrerr = DISPLAYED;
112757429Smarkm		}
112857429Smarkm		if (pflag) {
112957429Smarkm			if (exists || omode != mode)
113098937Sdes#ifdef HAVE_FCHMOD
1131137019Sdes				if (fchmod(ofd, omode)) {
113298937Sdes#else /* HAVE_FCHMOD */
1133137019Sdes				if (chmod(np, omode)) {
113498937Sdes#endif /* HAVE_FCHMOD */
113557429Smarkm					run_err("%s: set mode: %s",
113676259Sgreen					    np, strerror(errno));
1137137019Sdes					wrerr = DISPLAYED;
1138137019Sdes				}
113957429Smarkm		} else {
114057429Smarkm			if (!exists && omode != mode)
114198937Sdes#ifdef HAVE_FCHMOD
1142137019Sdes				if (fchmod(ofd, omode & ~mask)) {
114398937Sdes#else /* HAVE_FCHMOD */
1144137019Sdes				if (chmod(np, omode & ~mask)) {
114598937Sdes#endif /* HAVE_FCHMOD */
114657429Smarkm					run_err("%s: set mode: %s",
114776259Sgreen					    np, strerror(errno));
1148137019Sdes					wrerr = DISPLAYED;
1149137019Sdes				}
115057429Smarkm		}
115165668Skris		if (close(ofd) == -1) {
115265668Skris			wrerr = YES;
115365668Skris			wrerrno = errno;
115465668Skris		}
115557429Smarkm		(void) response();
115657429Smarkm		if (setimes && wrerr == NO) {
115757429Smarkm			setimes = 0;
115869587Sgreen			if (utimes(np, tv) < 0) {
115957429Smarkm				run_err("%s: set times: %s",
116076259Sgreen				    np, strerror(errno));
116157429Smarkm				wrerr = DISPLAYED;
116257429Smarkm			}
116357429Smarkm		}
116457429Smarkm		switch (wrerr) {
116557429Smarkm		case YES:
116657429Smarkm			run_err("%s: %s", np, strerror(wrerrno));
116757429Smarkm			break;
116857429Smarkm		case NO:
1169124211Sdes			(void) atomicio(vwrite, remout, "", 1);
117057429Smarkm			break;
117157429Smarkm		case DISPLAYED:
117257429Smarkm			break;
117357429Smarkm		}
117457429Smarkm	}
117557429Smarkmscrewup:
117657429Smarkm	run_err("protocol error: %s", why);
117757429Smarkm	exit(1);
117857429Smarkm}
117957429Smarkm
118057429Smarkmint
118192555Sdesresponse(void)
118257429Smarkm{
118357429Smarkm	char ch, *cp, resp, rbuf[2048];
118457429Smarkm
118560573Skris	if (atomicio(read, remin, &resp, sizeof(resp)) != sizeof(resp))
118657429Smarkm		lostconn(0);
118757429Smarkm
118857429Smarkm	cp = rbuf;
118957429Smarkm	switch (resp) {
119057429Smarkm	case 0:		/* ok */
119157429Smarkm		return (0);
119257429Smarkm	default:
119357429Smarkm		*cp++ = resp;
119457429Smarkm		/* FALLTHROUGH */
119557429Smarkm	case 1:		/* error, followed by error msg */
119657429Smarkm	case 2:		/* fatal error, "" */
119757429Smarkm		do {
119860573Skris			if (atomicio(read, remin, &ch, sizeof(ch)) != sizeof(ch))
119957429Smarkm				lostconn(0);
120057429Smarkm			*cp++ = ch;
120157429Smarkm		} while (cp < &rbuf[sizeof(rbuf) - 1] && ch != '\n');
120257429Smarkm
120357429Smarkm		if (!iamremote)
1204124211Sdes			(void) atomicio(vwrite, STDERR_FILENO, rbuf, cp - rbuf);
120557429Smarkm		++errs;
120657429Smarkm		if (resp == 1)
120757429Smarkm			return (-1);
120857429Smarkm		exit(1);
120957429Smarkm	}
121057429Smarkm	/* NOTREACHED */
121157429Smarkm}
121257429Smarkm
121357429Smarkmvoid
121492555Sdesusage(void)
121557429Smarkm{
121692555Sdes	(void) fprintf(stderr,
1217221420Sdes	    "usage: scp [-12346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]\n"
1218126277Sdes	    "           [-l limit] [-o ssh_option] [-P port] [-S program]\n"
1219181111Sdes	    "           [[user@]host1:]file1 ... [[user@]host2:]file2\n");
122057429Smarkm	exit(1);
122157429Smarkm}
122257429Smarkm
122357429Smarkmvoid
122457429Smarkmrun_err(const char *fmt,...)
122557429Smarkm{
122657429Smarkm	static FILE *fp;
122757429Smarkm	va_list ap;
122857429Smarkm
122957429Smarkm	++errs;
1230162856Sdes	if (fp != NULL || (remout != -1 && (fp = fdopen(remout, "w")))) {
1231162856Sdes		(void) fprintf(fp, "%c", 0x01);
1232162856Sdes		(void) fprintf(fp, "scp: ");
1233162856Sdes		va_start(ap, fmt);
1234162856Sdes		(void) vfprintf(fp, fmt, ap);
1235162856Sdes		va_end(ap);
1236162856Sdes		(void) fprintf(fp, "\n");
1237162856Sdes		(void) fflush(fp);
1238162856Sdes	}
123957429Smarkm
124057429Smarkm	if (!iamremote) {
124192555Sdes		va_start(ap, fmt);
124257429Smarkm		vfprintf(stderr, fmt, ap);
124392555Sdes		va_end(ap);
124457429Smarkm		fprintf(stderr, "\n");
124557429Smarkm	}
124657429Smarkm}
124757429Smarkm
124857429Smarkmvoid
1249124211Sdesverifydir(char *cp)
125057429Smarkm{
125157429Smarkm	struct stat stb;
125257429Smarkm
125357429Smarkm	if (!stat(cp, &stb)) {
125457429Smarkm		if (S_ISDIR(stb.st_mode))
125557429Smarkm			return;
125657429Smarkm		errno = ENOTDIR;
125757429Smarkm	}
125857429Smarkm	run_err("%s: %s", cp, strerror(errno));
1259149753Sdes	killchild(0);
126057429Smarkm}
126157429Smarkm
126257429Smarkmint
1263124211Sdesokname(char *cp0)
126457429Smarkm{
126557429Smarkm	int c;
126657429Smarkm	char *cp;
126757429Smarkm
126857429Smarkm	cp = cp0;
126957429Smarkm	do {
127092555Sdes		c = (int)*cp;
127157429Smarkm		if (c & 0200)
127257429Smarkm			goto bad;
1273113911Sdes		if (!isalpha(c) && !isdigit(c)) {
1274113911Sdes			switch (c) {
1275113911Sdes			case '\'':
1276113911Sdes			case '"':
1277113911Sdes			case '`':
1278113911Sdes			case ' ':
1279113911Sdes			case '#':
1280113911Sdes				goto bad;
1281113911Sdes			default:
1282113911Sdes				break;
1283113911Sdes			}
1284113911Sdes		}
128557429Smarkm	} while (*++cp);
128657429Smarkm	return (1);
128757429Smarkm
128857429Smarkmbad:	fprintf(stderr, "%s: invalid user name\n", cp0);
128957429Smarkm	return (0);
129057429Smarkm}
129157429Smarkm
129257429SmarkmBUF *
1293124211Sdesallocbuf(BUF *bp, int fd, int blksize)
129457429Smarkm{
129557429Smarkm	size_t size;
129698937Sdes#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
129757429Smarkm	struct stat stb;
129857429Smarkm
129957429Smarkm	if (fstat(fd, &stb) < 0) {
130057429Smarkm		run_err("fstat: %s", strerror(errno));
130157429Smarkm		return (0);
130257429Smarkm	}
1303113911Sdes	size = roundup(stb.st_blksize, blksize);
1304113911Sdes	if (size == 0)
130557429Smarkm		size = blksize;
130698937Sdes#else /* HAVE_STRUCT_STAT_ST_BLKSIZE */
130798937Sdes	size = blksize;
130898937Sdes#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
130957429Smarkm	if (bp->cnt >= size)
131057429Smarkm		return (bp);
131157429Smarkm	if (bp->buf == NULL)
131257429Smarkm		bp->buf = xmalloc(size);
131357429Smarkm	else
1314162856Sdes		bp->buf = xrealloc(bp->buf, 1, size);
131592555Sdes	memset(bp->buf, 0, size);
131657429Smarkm	bp->cnt = size;
131757429Smarkm	return (bp);
131857429Smarkm}
131957429Smarkm
132057429Smarkmvoid
1321124211Sdeslostconn(int signo)
132257429Smarkm{
132357429Smarkm	if (!iamremote)
132492555Sdes		write(STDERR_FILENO, "lost connection\n", 16);
132592555Sdes	if (signo)
132692555Sdes		_exit(1);
132792555Sdes	else
132892555Sdes		exit(1);
132957429Smarkm}
1330