1262566Sdes/* $OpenBSD: scp.c,v 1.179 2013/11/20 20:53:10 deraadt 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>
106248619Sdes#if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS)
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
553255767Sdesstatic int
554255767Sdesdo_times(int fd, int verb, const struct stat *sb)
555255767Sdes{
556255767Sdes	/* strlen(2^64) == 20; strlen(10^6) == 7 */
557255767Sdes	char buf[(20 + 7 + 2) * 2 + 2];
558255767Sdes
559255767Sdes	(void)snprintf(buf, sizeof(buf), "T%llu 0 %llu 0\n",
560255767Sdes	    (unsigned long long) (sb->st_mtime < 0 ? 0 : sb->st_mtime),
561255767Sdes	    (unsigned long long) (sb->st_atime < 0 ? 0 : sb->st_atime));
562255767Sdes	if (verb) {
563255767Sdes		fprintf(stderr, "File mtime %lld atime %lld\n",
564255767Sdes		    (long long)sb->st_mtime, (long long)sb->st_atime);
565255767Sdes		fprintf(stderr, "Sending file timestamps: %s", buf);
566255767Sdes	}
567255767Sdes	(void) atomicio(vwrite, fd, buf, strlen(buf));
568255767Sdes	return (response());
569255767Sdes}
570255767Sdes
57157429Smarkmvoid
572124211Sdestoremote(char *targ, int argc, char **argv)
57357429Smarkm{
574147005Sdes	char *bp, *host, *src, *suser, *thost, *tuser, *arg;
575157019Sdes	arglist alist;
576162856Sdes	int i;
577221420Sdes	u_int j;
57857429Smarkm
579157019Sdes	memset(&alist, '\0', sizeof(alist));
580157019Sdes	alist.list = NULL;
581157019Sdes
58257429Smarkm	*targ++ = 0;
58357429Smarkm	if (*targ == 0)
58457429Smarkm		targ = ".";
58557429Smarkm
586147005Sdes	arg = xstrdup(argv[argc - 1]);
587147005Sdes	if ((thost = strrchr(arg, '@'))) {
58857429Smarkm		/* user@host */
58957429Smarkm		*thost++ = 0;
590147005Sdes		tuser = arg;
59157429Smarkm		if (*tuser == '\0')
59257429Smarkm			tuser = NULL;
59357429Smarkm	} else {
594147005Sdes		thost = arg;
59557429Smarkm		tuser = NULL;
59657429Smarkm	}
59757429Smarkm
598157019Sdes	if (tuser != NULL && !okname(tuser)) {
599255767Sdes		free(arg);
600157019Sdes		return;
601157019Sdes	}
602157019Sdes
60357429Smarkm	for (i = 0; i < argc - 1; i++) {
60457429Smarkm		src = colon(argv[i]);
605221420Sdes		if (src && throughlocal) {	/* extended remote to remote */
606221420Sdes			*src++ = 0;
607221420Sdes			if (*src == 0)
608221420Sdes				src = ".";
609221420Sdes			host = strrchr(argv[i], '@');
610221420Sdes			if (host) {
611221420Sdes				*host++ = 0;
612221420Sdes				host = cleanhostname(host);
613221420Sdes				suser = argv[i];
614221420Sdes				if (*suser == '\0')
615221420Sdes					suser = pwd->pw_name;
616221420Sdes				else if (!okname(suser))
617221420Sdes					continue;
618221420Sdes			} else {
619221420Sdes				host = cleanhostname(argv[i]);
620221420Sdes				suser = NULL;
621221420Sdes			}
622240075Sdes			xasprintf(&bp, "%s -f %s%s", cmd,
623240075Sdes			    *src == '-' ? "-- " : "", src);
624221420Sdes			if (do_cmd(host, suser, bp, &remin, &remout) < 0)
625221420Sdes				exit(1);
626255767Sdes			free(bp);
627221420Sdes			host = cleanhostname(thost);
628240075Sdes			xasprintf(&bp, "%s -t %s%s", cmd,
629240075Sdes			    *targ == '-' ? "-- " : "", targ);
630221420Sdes			if (do_cmd2(host, tuser, bp, remin, remout) < 0)
631221420Sdes				exit(1);
632255767Sdes			free(bp);
633221420Sdes			(void) close(remin);
634221420Sdes			(void) close(remout);
635221420Sdes			remin = remout = -1;
636221420Sdes		} else if (src) {	/* standard remote to remote */
637157019Sdes			freeargs(&alist);
638157019Sdes			addargs(&alist, "%s", ssh_program);
639157019Sdes			addargs(&alist, "-x");
640221420Sdes			addargs(&alist, "-oClearAllForwardings=yes");
641157019Sdes			addargs(&alist, "-n");
642221420Sdes			for (j = 0; j < remote_remote_args.num; j++) {
643221420Sdes				addargs(&alist, "%s",
644221420Sdes				    remote_remote_args.list[j]);
645221420Sdes			}
64657429Smarkm			*src++ = 0;
64757429Smarkm			if (*src == 0)
64857429Smarkm				src = ".";
649113911Sdes			host = strrchr(argv[i], '@');
650157019Sdes
65157429Smarkm			if (host) {
65257429Smarkm				*host++ = 0;
65357429Smarkm				host = cleanhostname(host);
65457429Smarkm				suser = argv[i];
65557429Smarkm				if (*suser == '\0')
65657429Smarkm					suser = pwd->pw_name;
657157019Sdes				else if (!okname(suser))
65857429Smarkm					continue;
659157019Sdes				addargs(&alist, "-l");
660157019Sdes				addargs(&alist, "%s", suser);
66157429Smarkm			} else {
66257429Smarkm				host = cleanhostname(argv[i]);
66357429Smarkm			}
664204917Sdes			addargs(&alist, "--");
665157019Sdes			addargs(&alist, "%s", host);
666157019Sdes			addargs(&alist, "%s", cmd);
667157019Sdes			addargs(&alist, "%s", src);
668157019Sdes			addargs(&alist, "%s%s%s:%s",
669157019Sdes			    tuser ? tuser : "", tuser ? "@" : "",
670157019Sdes			    thost, targ);
671157019Sdes			if (do_local_cmd(&alist) != 0)
672126277Sdes				errs = 1;
67357429Smarkm		} else {	/* local to remote */
67457429Smarkm			if (remin == -1) {
675240075Sdes				xasprintf(&bp, "%s -t %s%s", cmd,
676240075Sdes				    *targ == '-' ? "-- " : "", targ);
67757429Smarkm				host = cleanhostname(thost);
67865668Skris				if (do_cmd(host, tuser, bp, &remin,
679162856Sdes				    &remout) < 0)
68057429Smarkm					exit(1);
68157429Smarkm				if (response() < 0)
68257429Smarkm					exit(1);
683255767Sdes				free(bp);
68457429Smarkm			}
68557429Smarkm			source(1, argv + i);
68657429Smarkm		}
68757429Smarkm	}
688255767Sdes	free(arg);
68957429Smarkm}
69057429Smarkm
69157429Smarkmvoid
692124211Sdestolocal(int argc, char **argv)
69357429Smarkm{
69457429Smarkm	char *bp, *host, *src, *suser;
695157019Sdes	arglist alist;
696162856Sdes	int i;
69757429Smarkm
698157019Sdes	memset(&alist, '\0', sizeof(alist));
699157019Sdes	alist.list = NULL;
700157019Sdes
70157429Smarkm	for (i = 0; i < argc - 1; i++) {
70257429Smarkm		if (!(src = colon(argv[i]))) {	/* Local to local. */
703157019Sdes			freeargs(&alist);
704157019Sdes			addargs(&alist, "%s", _PATH_CP);
705157019Sdes			if (iamrecursive)
706157019Sdes				addargs(&alist, "-r");
707157019Sdes			if (pflag)
708157019Sdes				addargs(&alist, "-p");
709204917Sdes			addargs(&alist, "--");
710157019Sdes			addargs(&alist, "%s", argv[i]);
711157019Sdes			addargs(&alist, "%s", argv[argc-1]);
712157019Sdes			if (do_local_cmd(&alist))
71357429Smarkm				++errs;
71457429Smarkm			continue;
71557429Smarkm		}
71657429Smarkm		*src++ = 0;
71757429Smarkm		if (*src == 0)
71857429Smarkm			src = ".";
719113911Sdes		if ((host = strrchr(argv[i], '@')) == NULL) {
72057429Smarkm			host = argv[i];
72157429Smarkm			suser = NULL;
72257429Smarkm		} else {
72357429Smarkm			*host++ = 0;
72457429Smarkm			suser = argv[i];
72557429Smarkm			if (*suser == '\0')
72657429Smarkm				suser = pwd->pw_name;
72757429Smarkm		}
72857429Smarkm		host = cleanhostname(host);
729240075Sdes		xasprintf(&bp, "%s -f %s%s",
730240075Sdes		    cmd, *src == '-' ? "-- " : "", src);
731162856Sdes		if (do_cmd(host, suser, bp, &remin, &remout) < 0) {
732255767Sdes			free(bp);
73357429Smarkm			++errs;
73457429Smarkm			continue;
73557429Smarkm		}
736255767Sdes		free(bp);
73757429Smarkm		sink(1, argv + argc - 1);
73857429Smarkm		(void) close(remin);
73957429Smarkm		remin = remout = -1;
74057429Smarkm	}
74157429Smarkm}
74257429Smarkm
74357429Smarkmvoid
744124211Sdessource(int argc, char **argv)
74557429Smarkm{
74657429Smarkm	struct stat stb;
74757429Smarkm	static BUF buffer;
74857429Smarkm	BUF *bp;
749181111Sdes	off_t i, statbytes;
750181111Sdes	size_t amt;
751149753Sdes	int fd = -1, haderr, indx;
752181111Sdes	char *last, *name, buf[2048], encname[MAXPATHLEN];
75376259Sgreen	int len;
75457429Smarkm
75557429Smarkm	for (indx = 0; indx < argc; ++indx) {
75657429Smarkm		name = argv[indx];
75757429Smarkm		statbytes = 0;
75876259Sgreen		len = strlen(name);
75976259Sgreen		while (len > 1 && name[len-1] == '/')
76076259Sgreen			name[--len] = '\0';
761181111Sdes		if ((fd = open(name, O_RDONLY|O_NONBLOCK, 0)) < 0)
762181111Sdes			goto syserr;
76392555Sdes		if (strchr(name, '\n') != NULL) {
764181111Sdes			strnvis(encname, name, sizeof(encname), VIS_NL);
765181111Sdes			name = encname;
76692555Sdes		}
76757429Smarkm		if (fstat(fd, &stb) < 0) {
76857429Smarkmsyserr:			run_err("%s: %s", name, strerror(errno));
76957429Smarkm			goto next;
77057429Smarkm		}
771181111Sdes		if (stb.st_size < 0) {
772181111Sdes			run_err("%s: %s", name, "Negative file size");
773181111Sdes			goto next;
774181111Sdes		}
775181111Sdes		unset_nonblock(fd);
77657429Smarkm		switch (stb.st_mode & S_IFMT) {
77757429Smarkm		case S_IFREG:
77857429Smarkm			break;
77957429Smarkm		case S_IFDIR:
78057429Smarkm			if (iamrecursive) {
78157429Smarkm				rsource(name, &stb);
78257429Smarkm				goto next;
78357429Smarkm			}
78457429Smarkm			/* FALLTHROUGH */
78557429Smarkm		default:
78657429Smarkm			run_err("%s: not a regular file", name);
78757429Smarkm			goto next;
78857429Smarkm		}
78957429Smarkm		if ((last = strrchr(name, '/')) == NULL)
79057429Smarkm			last = name;
79157429Smarkm		else
79257429Smarkm			++last;
79357429Smarkm		curfile = last;
79457429Smarkm		if (pflag) {
795255767Sdes			if (do_times(remout, verbose_mode, &stb) < 0)
79657429Smarkm				goto next;
79757429Smarkm		}
79857429Smarkm#define	FILEMODEMASK	(S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
79976259Sgreen		snprintf(buf, sizeof buf, "C%04o %lld %s\n",
80076259Sgreen		    (u_int) (stb.st_mode & FILEMODEMASK),
801157019Sdes		    (long long)stb.st_size, last);
80257429Smarkm		if (verbose_mode) {
80357429Smarkm			fprintf(stderr, "Sending file modes: %s", buf);
80457429Smarkm		}
805124211Sdes		(void) atomicio(vwrite, remout, buf, strlen(buf));
80657429Smarkm		if (response() < 0)
80757429Smarkm			goto next;
808181111Sdes		if ((bp = allocbuf(&buffer, fd, COPY_BUFLEN)) == NULL) {
809157019Sdesnext:			if (fd != -1) {
810157019Sdes				(void) close(fd);
811157019Sdes				fd = -1;
812157019Sdes			}
81357429Smarkm			continue;
81457429Smarkm		}
815113911Sdes		if (showprogress)
816113911Sdes			start_progress_meter(curfile, stb.st_size, &statbytes);
817181111Sdes		set_nonblock(remout);
81857429Smarkm		for (haderr = i = 0; i < stb.st_size; i += bp->cnt) {
81957429Smarkm			amt = bp->cnt;
820181111Sdes			if (i + (off_t)amt > stb.st_size)
82157429Smarkm				amt = stb.st_size - i;
82257429Smarkm			if (!haderr) {
823181111Sdes				if (atomicio(read, fd, bp->buf, amt) != amt)
824149753Sdes					haderr = errno;
82557429Smarkm			}
826181111Sdes			/* Keep writing after error to retain sync */
827181111Sdes			if (haderr) {
828181111Sdes				(void)atomicio(vwrite, remout, bp->buf, amt);
829181111Sdes				continue;
83057429Smarkm			}
831221420Sdes			if (atomicio6(vwrite, remout, bp->buf, amt, scpio,
832181111Sdes			    &statbytes) != amt)
833181111Sdes				haderr = errno;
83457429Smarkm		}
835181111Sdes		unset_nonblock(remout);
83657429Smarkm		if (showprogress)
837113911Sdes			stop_progress_meter();
83857429Smarkm
839157019Sdes		if (fd != -1) {
840157019Sdes			if (close(fd) < 0 && !haderr)
841157019Sdes				haderr = errno;
842157019Sdes			fd = -1;
843157019Sdes		}
84457429Smarkm		if (!haderr)
845124211Sdes			(void) atomicio(vwrite, remout, "", 1);
84657429Smarkm		else
84757429Smarkm			run_err("%s: %s", name, strerror(haderr));
84857429Smarkm		(void) response();
84957429Smarkm	}
85057429Smarkm}
85157429Smarkm
85257429Smarkmvoid
853124211Sdesrsource(char *name, struct stat *statp)
85457429Smarkm{
85557429Smarkm	DIR *dirp;
85657429Smarkm	struct dirent *dp;
857255767Sdes	char *last, *vect[1], path[MAXPATHLEN];
85857429Smarkm
85957429Smarkm	if (!(dirp = opendir(name))) {
86057429Smarkm		run_err("%s: %s", name, strerror(errno));
86157429Smarkm		return;
86257429Smarkm	}
86357429Smarkm	last = strrchr(name, '/');
86457429Smarkm	if (last == 0)
86557429Smarkm		last = name;
86657429Smarkm	else
86757429Smarkm		last++;
86857429Smarkm	if (pflag) {
869255767Sdes		if (do_times(remout, verbose_mode, statp) < 0) {
87057429Smarkm			closedir(dirp);
87157429Smarkm			return;
87257429Smarkm		}
87357429Smarkm	}
87476259Sgreen	(void) snprintf(path, sizeof path, "D%04o %d %.1024s\n",
87576259Sgreen	    (u_int) (statp->st_mode & FILEMODEMASK), 0, last);
87657429Smarkm	if (verbose_mode)
87757429Smarkm		fprintf(stderr, "Entering directory: %s", path);
878124211Sdes	(void) atomicio(vwrite, remout, path, strlen(path));
87957429Smarkm	if (response() < 0) {
88057429Smarkm		closedir(dirp);
88157429Smarkm		return;
88257429Smarkm	}
88376259Sgreen	while ((dp = readdir(dirp)) != NULL) {
88457429Smarkm		if (dp->d_ino == 0)
88557429Smarkm			continue;
88657429Smarkm		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
88757429Smarkm			continue;
88857429Smarkm		if (strlen(name) + 1 + strlen(dp->d_name) >= sizeof(path) - 1) {
88957429Smarkm			run_err("%s/%s: name too long", name, dp->d_name);
89057429Smarkm			continue;
89157429Smarkm		}
89276259Sgreen		(void) snprintf(path, sizeof path, "%s/%s", name, dp->d_name);
89357429Smarkm		vect[0] = path;
89457429Smarkm		source(1, vect);
89557429Smarkm	}
89657429Smarkm	(void) closedir(dirp);
897124211Sdes	(void) atomicio(vwrite, remout, "E\n", 2);
89857429Smarkm	(void) response();
89957429Smarkm}
90057429Smarkm
90157429Smarkmvoid
902124211Sdessink(int argc, char **argv)
90357429Smarkm{
90457429Smarkm	static BUF buffer;
90557429Smarkm	struct stat stb;
90657429Smarkm	enum {
90757429Smarkm		YES, NO, DISPLAYED
90857429Smarkm	} wrerr;
90957429Smarkm	BUF *bp;
910149753Sdes	off_t i;
911149753Sdes	size_t j, count;
912162856Sdes	int amt, exists, first, ofd;
913162856Sdes	mode_t mode, omode, mask;
914113911Sdes	off_t size, statbytes;
915255767Sdes	unsigned long long ull;
91665668Skris	int setimes, targisdir, wrerrno = 0;
91757429Smarkm	char ch, *cp, *np, *targ, *why, *vect[1], buf[2048];
91869587Sgreen	struct timeval tv[2];
91957429Smarkm
92076259Sgreen#define	atime	tv[0]
92176259Sgreen#define	mtime	tv[1]
922147005Sdes#define	SCREWUP(str)	{ why = str; goto screwup; }
92357429Smarkm
92457429Smarkm	setimes = targisdir = 0;
92557429Smarkm	mask = umask(0);
92657429Smarkm	if (!pflag)
92757429Smarkm		(void) umask(mask);
92857429Smarkm	if (argc != 1) {
92957429Smarkm		run_err("ambiguous target");
93057429Smarkm		exit(1);
93157429Smarkm	}
93257429Smarkm	targ = *argv;
93357429Smarkm	if (targetshouldbedirectory)
93457429Smarkm		verifydir(targ);
93557429Smarkm
936124211Sdes	(void) atomicio(vwrite, remout, "", 1);
93757429Smarkm	if (stat(targ, &stb) == 0 && S_ISDIR(stb.st_mode))
93857429Smarkm		targisdir = 1;
93957429Smarkm	for (first = 1;; first = 0) {
94057429Smarkm		cp = buf;
941149753Sdes		if (atomicio(read, remin, cp, 1) != 1)
94257429Smarkm			return;
94357429Smarkm		if (*cp++ == '\n')
94457429Smarkm			SCREWUP("unexpected <newline>");
94557429Smarkm		do {
94660573Skris			if (atomicio(read, remin, &ch, sizeof(ch)) != sizeof(ch))
94757429Smarkm				SCREWUP("lost connection");
94857429Smarkm			*cp++ = ch;
94957429Smarkm		} while (cp < &buf[sizeof(buf) - 1] && ch != '\n');
95057429Smarkm		*cp = 0;
951137019Sdes		if (verbose_mode)
952137019Sdes			fprintf(stderr, "Sink: %s", buf);
95357429Smarkm
95457429Smarkm		if (buf[0] == '\01' || buf[0] == '\02') {
95557429Smarkm			if (iamremote == 0)
956124211Sdes				(void) atomicio(vwrite, STDERR_FILENO,
95776259Sgreen				    buf + 1, strlen(buf + 1));
95857429Smarkm			if (buf[0] == '\02')
95957429Smarkm				exit(1);
96057429Smarkm			++errs;
96157429Smarkm			continue;
96257429Smarkm		}
96357429Smarkm		if (buf[0] == 'E') {
964124211Sdes			(void) atomicio(vwrite, remout, "", 1);
96557429Smarkm			return;
96657429Smarkm		}
96757429Smarkm		if (ch == '\n')
96857429Smarkm			*--cp = 0;
96957429Smarkm
97057429Smarkm		cp = buf;
97157429Smarkm		if (*cp == 'T') {
97257429Smarkm			setimes++;
97357429Smarkm			cp++;
974255767Sdes			if (!isdigit((unsigned char)*cp))
975255767Sdes				SCREWUP("mtime.sec not present");
976255767Sdes			ull = strtoull(cp, &cp, 10);
97776259Sgreen			if (!cp || *cp++ != ' ')
97857429Smarkm				SCREWUP("mtime.sec not delimited");
979255767Sdes			if ((time_t)ull < 0 ||
980255767Sdes			    (unsigned long long)(time_t)ull != ull)
981255767Sdes				setimes = 0;	/* out of range */
982255767Sdes			mtime.tv_sec = ull;
98376259Sgreen			mtime.tv_usec = strtol(cp, &cp, 10);
984255767Sdes			if (!cp || *cp++ != ' ' || mtime.tv_usec < 0 ||
985255767Sdes			    mtime.tv_usec > 999999)
98657429Smarkm				SCREWUP("mtime.usec not delimited");
987255767Sdes			if (!isdigit((unsigned char)*cp))
988255767Sdes				SCREWUP("atime.sec not present");
989255767Sdes			ull = strtoull(cp, &cp, 10);
99076259Sgreen			if (!cp || *cp++ != ' ')
99157429Smarkm				SCREWUP("atime.sec not delimited");
992255767Sdes			if ((time_t)ull < 0 ||
993255767Sdes			    (unsigned long long)(time_t)ull != ull)
994255767Sdes				setimes = 0;	/* out of range */
995255767Sdes			atime.tv_sec = ull;
99676259Sgreen			atime.tv_usec = strtol(cp, &cp, 10);
997255767Sdes			if (!cp || *cp++ != '\0' || atime.tv_usec < 0 ||
998255767Sdes			    atime.tv_usec > 999999)
99957429Smarkm				SCREWUP("atime.usec not delimited");
1000124211Sdes			(void) atomicio(vwrite, remout, "", 1);
100157429Smarkm			continue;
100257429Smarkm		}
100357429Smarkm		if (*cp != 'C' && *cp != 'D') {
100457429Smarkm			/*
100557429Smarkm			 * Check for the case "rcp remote:foo\* local:bar".
100657429Smarkm			 * In this case, the line "No match." can be returned
100757429Smarkm			 * by the shell before the rcp command on the remote is
100857429Smarkm			 * executed so the ^Aerror_message convention isn't
100957429Smarkm			 * followed.
101057429Smarkm			 */
101157429Smarkm			if (first) {
101257429Smarkm				run_err("%s", cp);
101357429Smarkm				exit(1);
101457429Smarkm			}
101557429Smarkm			SCREWUP("expected control record");
101657429Smarkm		}
101757429Smarkm		mode = 0;
101857429Smarkm		for (++cp; cp < buf + 5; cp++) {
101957429Smarkm			if (*cp < '0' || *cp > '7')
102057429Smarkm				SCREWUP("bad mode");
102157429Smarkm			mode = (mode << 3) | (*cp - '0');
102257429Smarkm		}
102357429Smarkm		if (*cp++ != ' ')
102457429Smarkm			SCREWUP("mode not delimited");
102557429Smarkm
1026262566Sdes		for (size = 0; isdigit((unsigned char)*cp);)
102757429Smarkm			size = size * 10 + (*cp++ - '0');
102857429Smarkm		if (*cp++ != ' ')
102957429Smarkm			SCREWUP("size not delimited");
1030137019Sdes		if ((strchr(cp, '/') != NULL) || (strcmp(cp, "..") == 0)) {
1031137019Sdes			run_err("error: unexpected filename: %s", cp);
1032137019Sdes			exit(1);
1033137019Sdes		}
103457429Smarkm		if (targisdir) {
103557429Smarkm			static char *namebuf;
1036149753Sdes			static size_t cursize;
103757429Smarkm			size_t need;
103857429Smarkm
103957429Smarkm			need = strlen(targ) + strlen(cp) + 250;
104076259Sgreen			if (need > cursize) {
1041255767Sdes				free(namebuf);
104257429Smarkm				namebuf = xmalloc(need);
104376259Sgreen				cursize = need;
104476259Sgreen			}
104576259Sgreen			(void) snprintf(namebuf, need, "%s%s%s", targ,
104698675Sdes			    strcmp(targ, "/") ? "/" : "", cp);
104757429Smarkm			np = namebuf;
104857429Smarkm		} else
104957429Smarkm			np = targ;
105057429Smarkm		curfile = cp;
105157429Smarkm		exists = stat(np, &stb) == 0;
105257429Smarkm		if (buf[0] == 'D') {
105357429Smarkm			int mod_flag = pflag;
1054137019Sdes			if (!iamrecursive)
1055137019Sdes				SCREWUP("received directory without -r");
105657429Smarkm			if (exists) {
105757429Smarkm				if (!S_ISDIR(stb.st_mode)) {
105857429Smarkm					errno = ENOTDIR;
105957429Smarkm					goto bad;
106057429Smarkm				}
106157429Smarkm				if (pflag)
106257429Smarkm					(void) chmod(np, mode);
106357429Smarkm			} else {
106457429Smarkm				/* Handle copying from a read-only
106557429Smarkm				   directory */
106657429Smarkm				mod_flag = 1;
106757429Smarkm				if (mkdir(np, mode | S_IRWXU) < 0)
106857429Smarkm					goto bad;
106957429Smarkm			}
107076259Sgreen			vect[0] = xstrdup(np);
107157429Smarkm			sink(1, vect);
107257429Smarkm			if (setimes) {
107357429Smarkm				setimes = 0;
107476259Sgreen				if (utimes(vect[0], tv) < 0)
107557429Smarkm					run_err("%s: set times: %s",
107676259Sgreen					    vect[0], strerror(errno));
107757429Smarkm			}
107857429Smarkm			if (mod_flag)
107976259Sgreen				(void) chmod(vect[0], mode);
1080255767Sdes			free(vect[0]);
108157429Smarkm			continue;
108257429Smarkm		}
108357429Smarkm		omode = mode;
1084255767Sdes		mode |= S_IWUSR;
108592555Sdes		if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) < 0) {
108657429Smarkmbad:			run_err("%s: %s", np, strerror(errno));
108757429Smarkm			continue;
108857429Smarkm		}
1089124211Sdes		(void) atomicio(vwrite, remout, "", 1);
1090181111Sdes		if ((bp = allocbuf(&buffer, ofd, COPY_BUFLEN)) == NULL) {
109157429Smarkm			(void) close(ofd);
109257429Smarkm			continue;
109357429Smarkm		}
109457429Smarkm		cp = bp->buf;
109557429Smarkm		wrerr = NO;
109657429Smarkm
109757429Smarkm		statbytes = 0;
1098113911Sdes		if (showprogress)
1099113911Sdes			start_progress_meter(curfile, size, &statbytes);
1100181111Sdes		set_nonblock(remin);
1101181111Sdes		for (count = i = 0; i < size; i += bp->cnt) {
1102181111Sdes			amt = bp->cnt;
110357429Smarkm			if (i + amt > size)
110457429Smarkm				amt = size - i;
110557429Smarkm			count += amt;
110657429Smarkm			do {
1107221420Sdes				j = atomicio6(read, remin, cp, amt,
1108221420Sdes				    scpio, &statbytes);
1109149753Sdes				if (j == 0) {
1110181111Sdes					run_err("%s", j != EPIPE ?
1111181111Sdes					    strerror(errno) :
111276259Sgreen					    "dropped connection");
111357429Smarkm					exit(1);
111457429Smarkm				}
111557429Smarkm				amt -= j;
111657429Smarkm				cp += j;
111757429Smarkm			} while (amt > 0);
1118126277Sdes
111957429Smarkm			if (count == bp->cnt) {
112057429Smarkm				/* Keep reading so we stay sync'd up. */
112157429Smarkm				if (wrerr == NO) {
1122149753Sdes					if (atomicio(vwrite, ofd, bp->buf,
1123149753Sdes					    count) != count) {
112457429Smarkm						wrerr = YES;
1125149753Sdes						wrerrno = errno;
112657429Smarkm					}
112757429Smarkm				}
112857429Smarkm				count = 0;
112957429Smarkm				cp = bp->buf;
113057429Smarkm			}
113157429Smarkm		}
1132181111Sdes		unset_nonblock(remin);
113357429Smarkm		if (showprogress)
1134113911Sdes			stop_progress_meter();
113557429Smarkm		if (count != 0 && wrerr == NO &&
1136149753Sdes		    atomicio(vwrite, ofd, bp->buf, count) != count) {
113757429Smarkm			wrerr = YES;
1138149753Sdes			wrerrno = errno;
113957429Smarkm		}
1140181111Sdes		if (wrerr == NO && (!exists || S_ISREG(stb.st_mode)) &&
1141181111Sdes		    ftruncate(ofd, size) != 0) {
114257429Smarkm			run_err("%s: truncate: %s", np, strerror(errno));
114357429Smarkm			wrerr = DISPLAYED;
114457429Smarkm		}
114557429Smarkm		if (pflag) {
114657429Smarkm			if (exists || omode != mode)
114798937Sdes#ifdef HAVE_FCHMOD
1148137019Sdes				if (fchmod(ofd, omode)) {
114998937Sdes#else /* HAVE_FCHMOD */
1150137019Sdes				if (chmod(np, omode)) {
115198937Sdes#endif /* HAVE_FCHMOD */
115257429Smarkm					run_err("%s: set mode: %s",
115376259Sgreen					    np, strerror(errno));
1154137019Sdes					wrerr = DISPLAYED;
1155137019Sdes				}
115657429Smarkm		} else {
115757429Smarkm			if (!exists && omode != mode)
115898937Sdes#ifdef HAVE_FCHMOD
1159137019Sdes				if (fchmod(ofd, omode & ~mask)) {
116098937Sdes#else /* HAVE_FCHMOD */
1161137019Sdes				if (chmod(np, omode & ~mask)) {
116298937Sdes#endif /* HAVE_FCHMOD */
116357429Smarkm					run_err("%s: set mode: %s",
116476259Sgreen					    np, strerror(errno));
1165137019Sdes					wrerr = DISPLAYED;
1166137019Sdes				}
116757429Smarkm		}
116865668Skris		if (close(ofd) == -1) {
116965668Skris			wrerr = YES;
117065668Skris			wrerrno = errno;
117165668Skris		}
117257429Smarkm		(void) response();
117357429Smarkm		if (setimes && wrerr == NO) {
117457429Smarkm			setimes = 0;
117569587Sgreen			if (utimes(np, tv) < 0) {
117657429Smarkm				run_err("%s: set times: %s",
117776259Sgreen				    np, strerror(errno));
117857429Smarkm				wrerr = DISPLAYED;
117957429Smarkm			}
118057429Smarkm		}
118157429Smarkm		switch (wrerr) {
118257429Smarkm		case YES:
118357429Smarkm			run_err("%s: %s", np, strerror(wrerrno));
118457429Smarkm			break;
118557429Smarkm		case NO:
1186124211Sdes			(void) atomicio(vwrite, remout, "", 1);
118757429Smarkm			break;
118857429Smarkm		case DISPLAYED:
118957429Smarkm			break;
119057429Smarkm		}
119157429Smarkm	}
119257429Smarkmscrewup:
119357429Smarkm	run_err("protocol error: %s", why);
119457429Smarkm	exit(1);
119557429Smarkm}
119657429Smarkm
119757429Smarkmint
119892555Sdesresponse(void)
119957429Smarkm{
120057429Smarkm	char ch, *cp, resp, rbuf[2048];
120157429Smarkm
120260573Skris	if (atomicio(read, remin, &resp, sizeof(resp)) != sizeof(resp))
120357429Smarkm		lostconn(0);
120457429Smarkm
120557429Smarkm	cp = rbuf;
120657429Smarkm	switch (resp) {
120757429Smarkm	case 0:		/* ok */
120857429Smarkm		return (0);
120957429Smarkm	default:
121057429Smarkm		*cp++ = resp;
121157429Smarkm		/* FALLTHROUGH */
121257429Smarkm	case 1:		/* error, followed by error msg */
121357429Smarkm	case 2:		/* fatal error, "" */
121457429Smarkm		do {
121560573Skris			if (atomicio(read, remin, &ch, sizeof(ch)) != sizeof(ch))
121657429Smarkm				lostconn(0);
121757429Smarkm			*cp++ = ch;
121857429Smarkm		} while (cp < &rbuf[sizeof(rbuf) - 1] && ch != '\n');
121957429Smarkm
122057429Smarkm		if (!iamremote)
1221124211Sdes			(void) atomicio(vwrite, STDERR_FILENO, rbuf, cp - rbuf);
122257429Smarkm		++errs;
122357429Smarkm		if (resp == 1)
122457429Smarkm			return (-1);
122557429Smarkm		exit(1);
122657429Smarkm	}
122757429Smarkm	/* NOTREACHED */
122857429Smarkm}
122957429Smarkm
123057429Smarkmvoid
123192555Sdesusage(void)
123257429Smarkm{
123392555Sdes	(void) fprintf(stderr,
1234221420Sdes	    "usage: scp [-12346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]\n"
1235126277Sdes	    "           [-l limit] [-o ssh_option] [-P port] [-S program]\n"
1236181111Sdes	    "           [[user@]host1:]file1 ... [[user@]host2:]file2\n");
123757429Smarkm	exit(1);
123857429Smarkm}
123957429Smarkm
124057429Smarkmvoid
124157429Smarkmrun_err(const char *fmt,...)
124257429Smarkm{
124357429Smarkm	static FILE *fp;
124457429Smarkm	va_list ap;
124557429Smarkm
124657429Smarkm	++errs;
1247162856Sdes	if (fp != NULL || (remout != -1 && (fp = fdopen(remout, "w")))) {
1248162856Sdes		(void) fprintf(fp, "%c", 0x01);
1249162856Sdes		(void) fprintf(fp, "scp: ");
1250162856Sdes		va_start(ap, fmt);
1251162856Sdes		(void) vfprintf(fp, fmt, ap);
1252162856Sdes		va_end(ap);
1253162856Sdes		(void) fprintf(fp, "\n");
1254162856Sdes		(void) fflush(fp);
1255162856Sdes	}
125657429Smarkm
125757429Smarkm	if (!iamremote) {
125892555Sdes		va_start(ap, fmt);
125957429Smarkm		vfprintf(stderr, fmt, ap);
126092555Sdes		va_end(ap);
126157429Smarkm		fprintf(stderr, "\n");
126257429Smarkm	}
126357429Smarkm}
126457429Smarkm
126557429Smarkmvoid
1266124211Sdesverifydir(char *cp)
126757429Smarkm{
126857429Smarkm	struct stat stb;
126957429Smarkm
127057429Smarkm	if (!stat(cp, &stb)) {
127157429Smarkm		if (S_ISDIR(stb.st_mode))
127257429Smarkm			return;
127357429Smarkm		errno = ENOTDIR;
127457429Smarkm	}
127557429Smarkm	run_err("%s: %s", cp, strerror(errno));
1276149753Sdes	killchild(0);
127757429Smarkm}
127857429Smarkm
127957429Smarkmint
1280124211Sdesokname(char *cp0)
128157429Smarkm{
128257429Smarkm	int c;
128357429Smarkm	char *cp;
128457429Smarkm
128557429Smarkm	cp = cp0;
128657429Smarkm	do {
128792555Sdes		c = (int)*cp;
128857429Smarkm		if (c & 0200)
128957429Smarkm			goto bad;
1290262566Sdes		if (!isalpha(c) && !isdigit((unsigned char)c)) {
1291113911Sdes			switch (c) {
1292113911Sdes			case '\'':
1293113911Sdes			case '"':
1294113911Sdes			case '`':
1295113911Sdes			case ' ':
1296113911Sdes			case '#':
1297113911Sdes				goto bad;
1298113911Sdes			default:
1299113911Sdes				break;
1300113911Sdes			}
1301113911Sdes		}
130257429Smarkm	} while (*++cp);
130357429Smarkm	return (1);
130457429Smarkm
130557429Smarkmbad:	fprintf(stderr, "%s: invalid user name\n", cp0);
130657429Smarkm	return (0);
130757429Smarkm}
130857429Smarkm
130957429SmarkmBUF *
1310124211Sdesallocbuf(BUF *bp, int fd, int blksize)
131157429Smarkm{
131257429Smarkm	size_t size;
131398937Sdes#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
131457429Smarkm	struct stat stb;
131557429Smarkm
131657429Smarkm	if (fstat(fd, &stb) < 0) {
131757429Smarkm		run_err("fstat: %s", strerror(errno));
131857429Smarkm		return (0);
131957429Smarkm	}
1320113911Sdes	size = roundup(stb.st_blksize, blksize);
1321113911Sdes	if (size == 0)
132257429Smarkm		size = blksize;
132398937Sdes#else /* HAVE_STRUCT_STAT_ST_BLKSIZE */
132498937Sdes	size = blksize;
132598937Sdes#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
132657429Smarkm	if (bp->cnt >= size)
132757429Smarkm		return (bp);
132857429Smarkm	if (bp->buf == NULL)
132957429Smarkm		bp->buf = xmalloc(size);
133057429Smarkm	else
1331162856Sdes		bp->buf = xrealloc(bp->buf, 1, size);
133292555Sdes	memset(bp->buf, 0, size);
133357429Smarkm	bp->cnt = size;
133457429Smarkm	return (bp);
133557429Smarkm}
133657429Smarkm
133757429Smarkmvoid
1338124211Sdeslostconn(int signo)
133957429Smarkm{
134057429Smarkm	if (!iamremote)
1341255767Sdes		(void)write(STDERR_FILENO, "lost connection\n", 16);
134292555Sdes	if (signo)
134392555Sdes		_exit(1);
134492555Sdes	else
134592555Sdes		exit(1);
134657429Smarkm}
1347