1323124Sdes/* $OpenBSD: scp.c,v 1.186 2016/05/25 23:48:45 schwarze 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>
98295367Sdes#include <limits.h>
99323124Sdes#include <locale.h>
100162856Sdes#include <pwd.h>
101162856Sdes#include <signal.h>
102162856Sdes#include <stdarg.h>
103162856Sdes#include <stdio.h>
104162856Sdes#include <stdlib.h>
105162856Sdes#include <string.h>
106162856Sdes#include <time.h>
107162856Sdes#include <unistd.h>
108248619Sdes#if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS)
109181111Sdes#include <vis.h>
110181111Sdes#endif
111162856Sdes
11257429Smarkm#include "xmalloc.h"
11376259Sgreen#include "atomicio.h"
11476259Sgreen#include "pathnames.h"
11576259Sgreen#include "log.h"
11692555Sdes#include "misc.h"
117113911Sdes#include "progressmeter.h"
118323124Sdes#include "utf8.h"
11957429Smarkm
12098937Sdesextern char *__progname;
12198937Sdes
122181111Sdes#define COPY_BUFLEN	16384
123181111Sdes
124162856Sdesint do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout);
125221420Sdesint do_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout);
126162856Sdes
12792555Sdes/* Struct for addargs */
12892555Sdesarglist args;
129221420Sdesarglist remote_remote_args;
13069587Sgreen
131113911Sdes/* Bandwidth limit */
132221420Sdeslong long limit_kbps = 0;
133221420Sdesstruct bwlimit bwlimit;
13457429Smarkm
13557429Smarkm/* Name of current file being transferred. */
13657429Smarkmchar *curfile;
13757429Smarkm
13857429Smarkm/* This is set to non-zero to enable verbose mode. */
13957429Smarkmint verbose_mode = 0;
14057429Smarkm
14157429Smarkm/* This is set to zero if the progressmeter is not desired. */
14257429Smarkmint showprogress = 1;
14357429Smarkm
144221420Sdes/*
145221420Sdes * This is set to non-zero if remote-remote copy should be piped
146221420Sdes * through this process.
147221420Sdes */
148221420Sdesint throughlocal = 0;
149221420Sdes
15065668Skris/* This is the program to execute for the secured connection. ("ssh" or -S) */
15176259Sgreenchar *ssh_program = _PATH_SSH_PROGRAM;
15265668Skris
153113911Sdes/* This is used to store the pid of ssh_program */
154124211Sdespid_t do_cmd_pid = -1;
155113911Sdes
156124211Sdesstatic void
157124211Sdeskillchild(int signo)
158124211Sdes{
159147005Sdes	if (do_cmd_pid > 1) {
160149753Sdes		kill(do_cmd_pid, signo ? signo : SIGTERM);
161147005Sdes		waitpid(do_cmd_pid, NULL, 0);
162147005Sdes	}
163124211Sdes
164149753Sdes	if (signo)
165149753Sdes		_exit(1);
166149753Sdes	exit(1);
167124211Sdes}
168124211Sdes
169215116Sdesstatic void
170215116Sdessuspchild(int signo)
171215116Sdes{
172215116Sdes	int status;
173215116Sdes
174215116Sdes	if (do_cmd_pid > 1) {
175215116Sdes		kill(do_cmd_pid, signo);
176215116Sdes		while (waitpid(do_cmd_pid, &status, WUNTRACED) == -1 &&
177215116Sdes		    errno == EINTR)
178215116Sdes			;
179215116Sdes		kill(getpid(), SIGSTOP);
180215116Sdes	}
181215116Sdes}
182215116Sdes
183157019Sdesstatic int
184157019Sdesdo_local_cmd(arglist *a)
185157019Sdes{
186157019Sdes	u_int i;
187157019Sdes	int status;
188157019Sdes	pid_t pid;
189157019Sdes
190157019Sdes	if (a->num == 0)
191157019Sdes		fatal("do_local_cmd: no arguments");
192157019Sdes
193157019Sdes	if (verbose_mode) {
194157019Sdes		fprintf(stderr, "Executing:");
195157019Sdes		for (i = 0; i < a->num; i++)
196323124Sdes			fmprintf(stderr, " %s", a->list[i]);
197157019Sdes		fprintf(stderr, "\n");
198157019Sdes	}
199157019Sdes	if ((pid = fork()) == -1)
200157019Sdes		fatal("do_local_cmd: fork: %s", strerror(errno));
201157019Sdes
202157019Sdes	if (pid == 0) {
203157019Sdes		execvp(a->list[0], a->list);
204157019Sdes		perror(a->list[0]);
205157019Sdes		exit(1);
206157019Sdes	}
207157019Sdes
208157019Sdes	do_cmd_pid = pid;
209157019Sdes	signal(SIGTERM, killchild);
210157019Sdes	signal(SIGINT, killchild);
211157019Sdes	signal(SIGHUP, killchild);
212157019Sdes
213157019Sdes	while (waitpid(pid, &status, 0) == -1)
214157019Sdes		if (errno != EINTR)
215157019Sdes			fatal("do_local_cmd: waitpid: %s", strerror(errno));
216157019Sdes
217157019Sdes	do_cmd_pid = -1;
218157019Sdes
219157019Sdes	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
220157019Sdes		return (-1);
221157019Sdes
222157019Sdes	return (0);
223157019Sdes}
224157019Sdes
22557429Smarkm/*
22657429Smarkm * This function executes the given command as the specified user on the
22757429Smarkm * given host.  This returns < 0 if execution fails, and >= 0 otherwise. This
22857429Smarkm * assigns the input and output file descriptors on success.
22957429Smarkm */
23057429Smarkm
23160573Skrisint
232162856Sdesdo_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout)
23357429Smarkm{
23457429Smarkm	int pin[2], pout[2], reserved[2];
23557429Smarkm
23657429Smarkm	if (verbose_mode)
237323124Sdes		fmprintf(stderr,
23892555Sdes		    "Executing: program %s host %s, user %s, command %s\n",
23992555Sdes		    ssh_program, host,
24092555Sdes		    remuser ? remuser : "(unspecified)", cmd);
24157429Smarkm
24257429Smarkm	/*
24357429Smarkm	 * Reserve two descriptors so that the real pipes won't get
24457429Smarkm	 * descriptors 0 and 1 because that will screw up dup2 below.
24557429Smarkm	 */
246162856Sdes	if (pipe(reserved) < 0)
247162856Sdes		fatal("pipe: %s", strerror(errno));
24857429Smarkm
24957429Smarkm	/* Create a socket pair for communicating with ssh. */
25057429Smarkm	if (pipe(pin) < 0)
25157429Smarkm		fatal("pipe: %s", strerror(errno));
25257429Smarkm	if (pipe(pout) < 0)
25357429Smarkm		fatal("pipe: %s", strerror(errno));
25457429Smarkm
25557429Smarkm	/* Free the reserved descriptors. */
25657429Smarkm	close(reserved[0]);
25757429Smarkm	close(reserved[1]);
25857429Smarkm
259215116Sdes	signal(SIGTSTP, suspchild);
260215116Sdes	signal(SIGTTIN, suspchild);
261215116Sdes	signal(SIGTTOU, suspchild);
262215116Sdes
263124211Sdes	/* Fork a child to execute the command on the remote host using ssh. */
264113911Sdes	do_cmd_pid = fork();
265113911Sdes	if (do_cmd_pid == 0) {
26657429Smarkm		/* Child. */
26757429Smarkm		close(pin[1]);
26857429Smarkm		close(pout[0]);
26957429Smarkm		dup2(pin[0], 0);
27057429Smarkm		dup2(pout[1], 1);
27157429Smarkm		close(pin[0]);
27257429Smarkm		close(pout[1]);
27357429Smarkm
274157019Sdes		replacearg(&args, 0, "%s", ssh_program);
275204917Sdes		if (remuser != NULL) {
276204917Sdes			addargs(&args, "-l");
277204917Sdes			addargs(&args, "%s", remuser);
278204917Sdes		}
279204917Sdes		addargs(&args, "--");
28092555Sdes		addargs(&args, "%s", host);
28192555Sdes		addargs(&args, "%s", cmd);
28257429Smarkm
28369587Sgreen		execvp(ssh_program, args.list);
28465668Skris		perror(ssh_program);
28557429Smarkm		exit(1);
286113911Sdes	} else if (do_cmd_pid == -1) {
287113911Sdes		fatal("fork: %s", strerror(errno));
28857429Smarkm	}
28957429Smarkm	/* Parent.  Close the other side, and return the local side. */
29057429Smarkm	close(pin[0]);
29157429Smarkm	*fdout = pin[1];
29257429Smarkm	close(pout[1]);
29357429Smarkm	*fdin = pout[0];
294124211Sdes	signal(SIGTERM, killchild);
295124211Sdes	signal(SIGINT, killchild);
296124211Sdes	signal(SIGHUP, killchild);
29757429Smarkm	return 0;
29857429Smarkm}
29957429Smarkm
300221420Sdes/*
301221420Sdes * This functions executes a command simlar to do_cmd(), but expects the
302221420Sdes * input and output descriptors to be setup by a previous call to do_cmd().
303221420Sdes * This way the input and output of two commands can be connected.
304221420Sdes */
305221420Sdesint
306221420Sdesdo_cmd2(char *host, char *remuser, char *cmd, int fdin, int fdout)
307221420Sdes{
308221420Sdes	pid_t pid;
309221420Sdes	int status;
310221420Sdes
311221420Sdes	if (verbose_mode)
312323124Sdes		fmprintf(stderr,
313221420Sdes		    "Executing: 2nd program %s host %s, user %s, command %s\n",
314221420Sdes		    ssh_program, host,
315221420Sdes		    remuser ? remuser : "(unspecified)", cmd);
316221420Sdes
317221420Sdes	/* Fork a child to execute the command on the remote host using ssh. */
318221420Sdes	pid = fork();
319221420Sdes	if (pid == 0) {
320221420Sdes		dup2(fdin, 0);
321221420Sdes		dup2(fdout, 1);
322221420Sdes
323221420Sdes		replacearg(&args, 0, "%s", ssh_program);
324221420Sdes		if (remuser != NULL) {
325221420Sdes			addargs(&args, "-l");
326221420Sdes			addargs(&args, "%s", remuser);
327221420Sdes		}
328221420Sdes		addargs(&args, "--");
329221420Sdes		addargs(&args, "%s", host);
330221420Sdes		addargs(&args, "%s", cmd);
331221420Sdes
332221420Sdes		execvp(ssh_program, args.list);
333221420Sdes		perror(ssh_program);
334221420Sdes		exit(1);
335221420Sdes	} else if (pid == -1) {
336221420Sdes		fatal("fork: %s", strerror(errno));
337221420Sdes	}
338221420Sdes	while (waitpid(pid, &status, 0) == -1)
339221420Sdes		if (errno != EINTR)
340221420Sdes			fatal("do_cmd2: waitpid: %s", strerror(errno));
341221420Sdes	return 0;
342221420Sdes}
343221420Sdes
34457429Smarkmtypedef struct {
345149753Sdes	size_t cnt;
34657429Smarkm	char *buf;
34757429Smarkm} BUF;
34857429Smarkm
34957429SmarkmBUF *allocbuf(BUF *, int, int);
35057429Smarkmvoid lostconn(int);
35157429Smarkmint okname(char *);
35257429Smarkmvoid run_err(const char *,...);
35357429Smarkmvoid verifydir(char *);
35457429Smarkm
35557429Smarkmstruct passwd *pwd;
35657429Smarkmuid_t userid;
35757429Smarkmint errs, remin, remout;
35857429Smarkmint pflag, iamremote, iamrecursive, targetshouldbedirectory;
35957429Smarkm
36057429Smarkm#define	CMDNEEDS	64
36157429Smarkmchar cmd[CMDNEEDS];		/* must hold "rcp -r -p -d\0" */
36257429Smarkm
36357429Smarkmint response(void);
36457429Smarkmvoid rsource(char *, struct stat *);
36557429Smarkmvoid sink(int, char *[]);
36657429Smarkmvoid source(int, char *[]);
36757429Smarkmvoid tolocal(int, char *[]);
36857429Smarkmvoid toremote(char *, int, char *[]);
36957429Smarkmvoid usage(void);
37057429Smarkm
37157429Smarkmint
372124211Sdesmain(int argc, char **argv)
37357429Smarkm{
374162856Sdes	int ch, fflag, tflag, status, n;
375221420Sdes	char *targ, **newargv;
376221420Sdes	const char *errstr;
37757429Smarkm	extern char *optarg;
37857429Smarkm	extern int optind;
37957429Smarkm
380157019Sdes	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
381157019Sdes	sanitise_stdfd();
382157019Sdes
383323124Sdes	setlocale(LC_CTYPE, "");
384323124Sdes
385162856Sdes	/* Copy argv, because we modify it */
386162856Sdes	newargv = xcalloc(MAX(argc + 1, 1), sizeof(*newargv));
387162856Sdes	for (n = 0; n < argc; n++)
388162856Sdes		newargv[n] = xstrdup(argv[n]);
389162856Sdes	argv = newargv;
390162856Sdes
391124211Sdes	__progname = ssh_get_progname(argv[0]);
39298937Sdes
393157019Sdes	memset(&args, '\0', sizeof(args));
394221420Sdes	memset(&remote_remote_args, '\0', sizeof(remote_remote_args));
395221420Sdes	args.list = remote_remote_args.list = NULL;
396157019Sdes	addargs(&args, "%s", ssh_program);
39792555Sdes	addargs(&args, "-x");
398221420Sdes	addargs(&args, "-oForwardAgent=no");
399221420Sdes	addargs(&args, "-oPermitLocalCommand=no");
400221420Sdes	addargs(&args, "-oClearAllForwardings=yes");
40169587Sgreen
40257429Smarkm	fflag = tflag = 0;
403221420Sdes	while ((ch = getopt(argc, argv, "dfl:prtvBCc:i:P:q12346S:o:F:")) != -1)
40457429Smarkm		switch (ch) {
40557429Smarkm		/* User-visible flags. */
406113911Sdes		case '1':
407113911Sdes		case '2':
40857429Smarkm		case '4':
40957429Smarkm		case '6':
41069587Sgreen		case 'C':
41192555Sdes			addargs(&args, "-%c", ch);
412221420Sdes			addargs(&remote_remote_args, "-%c", ch);
41357429Smarkm			break;
414221420Sdes		case '3':
415221420Sdes			throughlocal = 1;
416221420Sdes			break;
41769587Sgreen		case 'o':
41869587Sgreen		case 'c':
41969587Sgreen		case 'i':
42092555Sdes		case 'F':
421221420Sdes			addargs(&remote_remote_args, "-%c", ch);
422221420Sdes			addargs(&remote_remote_args, "%s", optarg);
423204917Sdes			addargs(&args, "-%c", ch);
424204917Sdes			addargs(&args, "%s", optarg);
42569587Sgreen			break;
42669587Sgreen		case 'P':
427221420Sdes			addargs(&remote_remote_args, "-p");
428221420Sdes			addargs(&remote_remote_args, "%s", optarg);
429204917Sdes			addargs(&args, "-p");
430204917Sdes			addargs(&args, "%s", optarg);
43169587Sgreen			break;
43269587Sgreen		case 'B':
433221420Sdes			addargs(&remote_remote_args, "-oBatchmode=yes");
434221420Sdes			addargs(&args, "-oBatchmode=yes");
43569587Sgreen			break;
436113911Sdes		case 'l':
437221420Sdes			limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
438221420Sdes			    &errstr);
439221420Sdes			if (errstr != NULL)
440113911Sdes				usage();
441221420Sdes			limit_kbps *= 1024; /* kbps */
442221420Sdes			bandwidth_limit_init(&bwlimit, limit_kbps, COPY_BUFLEN);
443113911Sdes			break;
44457429Smarkm		case 'p':
44557429Smarkm			pflag = 1;
44657429Smarkm			break;
44757429Smarkm		case 'r':
44857429Smarkm			iamrecursive = 1;
44957429Smarkm			break;
45065668Skris		case 'S':
45169587Sgreen			ssh_program = xstrdup(optarg);
45265668Skris			break;
45369587Sgreen		case 'v':
45492555Sdes			addargs(&args, "-v");
455221420Sdes			addargs(&remote_remote_args, "-v");
45669587Sgreen			verbose_mode = 1;
45769587Sgreen			break;
45869587Sgreen		case 'q':
459126277Sdes			addargs(&args, "-q");
460221420Sdes			addargs(&remote_remote_args, "-q");
46169587Sgreen			showprogress = 0;
46269587Sgreen			break;
46365668Skris
46457429Smarkm		/* Server options. */
46557429Smarkm		case 'd':
46657429Smarkm			targetshouldbedirectory = 1;
46757429Smarkm			break;
46857429Smarkm		case 'f':	/* "from" */
46957429Smarkm			iamremote = 1;
47057429Smarkm			fflag = 1;
47157429Smarkm			break;
47257429Smarkm		case 't':	/* "to" */
47357429Smarkm			iamremote = 1;
47457429Smarkm			tflag = 1;
47598937Sdes#ifdef HAVE_CYGWIN
47698937Sdes			setmode(0, O_BINARY);
47798937Sdes#endif
47857429Smarkm			break;
47957429Smarkm		default:
48057429Smarkm			usage();
48157429Smarkm		}
48257429Smarkm	argc -= optind;
48357429Smarkm	argv += optind;
48457429Smarkm
48557429Smarkm	if ((pwd = getpwuid(userid = getuid())) == NULL)
486124211Sdes		fatal("unknown user %u", (u_int) userid);
48757429Smarkm
488181111Sdes	if (!isatty(STDOUT_FILENO))
48957429Smarkm		showprogress = 0;
49057429Smarkm
491296781Sdes	if (pflag) {
492296781Sdes		/* Cannot pledge: -p allows setuid/setgid files... */
493296781Sdes	} else {
494296781Sdes		if (pledge("stdio rpath wpath cpath fattr tty proc exec",
495296781Sdes		    NULL) == -1) {
496296781Sdes			perror("pledge");
497296781Sdes			exit(1);
498296781Sdes		}
499296781Sdes	}
500296781Sdes
50157429Smarkm	remin = STDIN_FILENO;
50257429Smarkm	remout = STDOUT_FILENO;
50357429Smarkm
50476259Sgreen	if (fflag) {
50557429Smarkm		/* Follow "protocol", send data. */
50657429Smarkm		(void) response();
50757429Smarkm		source(argc, argv);
50857429Smarkm		exit(errs != 0);
50957429Smarkm	}
51057429Smarkm	if (tflag) {
51157429Smarkm		/* Receive data. */
51257429Smarkm		sink(argc, argv);
51357429Smarkm		exit(errs != 0);
51457429Smarkm	}
51557429Smarkm	if (argc < 2)
51657429Smarkm		usage();
51757429Smarkm	if (argc > 2)
51857429Smarkm		targetshouldbedirectory = 1;
51957429Smarkm
52057429Smarkm	remin = remout = -1;
521113911Sdes	do_cmd_pid = -1;
52257429Smarkm	/* Command to be executed on remote system using "ssh". */
52376259Sgreen	(void) snprintf(cmd, sizeof cmd, "scp%s%s%s%s",
52476259Sgreen	    verbose_mode ? " -v" : "",
52565668Skris	    iamrecursive ? " -r" : "", pflag ? " -p" : "",
52665668Skris	    targetshouldbedirectory ? " -d" : "");
52757429Smarkm
52857429Smarkm	(void) signal(SIGPIPE, lostconn);
52957429Smarkm
53057429Smarkm	if ((targ = colon(argv[argc - 1])))	/* Dest is remote host. */
53157429Smarkm		toremote(targ, argc, argv);
53257429Smarkm	else {
53357429Smarkm		if (targetshouldbedirectory)
53457429Smarkm			verifydir(argv[argc - 1]);
535157019Sdes		tolocal(argc, argv);	/* Dest is local host. */
53657429Smarkm	}
537113911Sdes	/*
538113911Sdes	 * Finally check the exit status of the ssh process, if one was forked
539192595Sdes	 * and no error has occurred yet
540113911Sdes	 */
541113911Sdes	if (do_cmd_pid != -1 && errs == 0) {
542113911Sdes		if (remin != -1)
543113911Sdes		    (void) close(remin);
544113911Sdes		if (remout != -1)
545113911Sdes		    (void) close(remout);
546113911Sdes		if (waitpid(do_cmd_pid, &status, 0) == -1)
547113911Sdes			errs = 1;
548113911Sdes		else {
549113911Sdes			if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
550113911Sdes				errs = 1;
551113911Sdes		}
552113911Sdes	}
55357429Smarkm	exit(errs != 0);
55457429Smarkm}
55557429Smarkm
556221420Sdes/* Callback from atomicio6 to update progress meter and limit bandwidth */
557221420Sdesstatic int
558221420Sdesscpio(void *_cnt, size_t s)
559181111Sdes{
560221420Sdes	off_t *cnt = (off_t *)_cnt;
561181111Sdes
562221420Sdes	*cnt += s;
563221420Sdes	if (limit_kbps > 0)
564221420Sdes		bandwidth_limit(&bwlimit, s);
565221420Sdes	return 0;
566181111Sdes}
567181111Sdes
568255767Sdesstatic int
569255767Sdesdo_times(int fd, int verb, const struct stat *sb)
570255767Sdes{
571255767Sdes	/* strlen(2^64) == 20; strlen(10^6) == 7 */
572255767Sdes	char buf[(20 + 7 + 2) * 2 + 2];
573255767Sdes
574255767Sdes	(void)snprintf(buf, sizeof(buf), "T%llu 0 %llu 0\n",
575255767Sdes	    (unsigned long long) (sb->st_mtime < 0 ? 0 : sb->st_mtime),
576255767Sdes	    (unsigned long long) (sb->st_atime < 0 ? 0 : sb->st_atime));
577255767Sdes	if (verb) {
578255767Sdes		fprintf(stderr, "File mtime %lld atime %lld\n",
579255767Sdes		    (long long)sb->st_mtime, (long long)sb->st_atime);
580255767Sdes		fprintf(stderr, "Sending file timestamps: %s", buf);
581255767Sdes	}
582255767Sdes	(void) atomicio(vwrite, fd, buf, strlen(buf));
583255767Sdes	return (response());
584255767Sdes}
585255767Sdes
58657429Smarkmvoid
587124211Sdestoremote(char *targ, int argc, char **argv)
58857429Smarkm{
589147005Sdes	char *bp, *host, *src, *suser, *thost, *tuser, *arg;
590157019Sdes	arglist alist;
591162856Sdes	int i;
592221420Sdes	u_int j;
59357429Smarkm
594157019Sdes	memset(&alist, '\0', sizeof(alist));
595157019Sdes	alist.list = NULL;
596157019Sdes
59757429Smarkm	*targ++ = 0;
59857429Smarkm	if (*targ == 0)
59957429Smarkm		targ = ".";
60057429Smarkm
601147005Sdes	arg = xstrdup(argv[argc - 1]);
602147005Sdes	if ((thost = strrchr(arg, '@'))) {
60357429Smarkm		/* user@host */
60457429Smarkm		*thost++ = 0;
605147005Sdes		tuser = arg;
60657429Smarkm		if (*tuser == '\0')
60757429Smarkm			tuser = NULL;
60857429Smarkm	} else {
609147005Sdes		thost = arg;
61057429Smarkm		tuser = NULL;
61157429Smarkm	}
61257429Smarkm
613157019Sdes	if (tuser != NULL && !okname(tuser)) {
614255767Sdes		free(arg);
615157019Sdes		return;
616157019Sdes	}
617157019Sdes
61857429Smarkm	for (i = 0; i < argc - 1; i++) {
61957429Smarkm		src = colon(argv[i]);
620221420Sdes		if (src && throughlocal) {	/* extended remote to remote */
621221420Sdes			*src++ = 0;
622221420Sdes			if (*src == 0)
623221420Sdes				src = ".";
624221420Sdes			host = strrchr(argv[i], '@');
625221420Sdes			if (host) {
626221420Sdes				*host++ = 0;
627221420Sdes				host = cleanhostname(host);
628221420Sdes				suser = argv[i];
629221420Sdes				if (*suser == '\0')
630221420Sdes					suser = pwd->pw_name;
631221420Sdes				else if (!okname(suser))
632221420Sdes					continue;
633221420Sdes			} else {
634221420Sdes				host = cleanhostname(argv[i]);
635221420Sdes				suser = NULL;
636221420Sdes			}
637240075Sdes			xasprintf(&bp, "%s -f %s%s", cmd,
638240075Sdes			    *src == '-' ? "-- " : "", src);
639221420Sdes			if (do_cmd(host, suser, bp, &remin, &remout) < 0)
640221420Sdes				exit(1);
641255767Sdes			free(bp);
642221420Sdes			host = cleanhostname(thost);
643240075Sdes			xasprintf(&bp, "%s -t %s%s", cmd,
644240075Sdes			    *targ == '-' ? "-- " : "", targ);
645221420Sdes			if (do_cmd2(host, tuser, bp, remin, remout) < 0)
646221420Sdes				exit(1);
647255767Sdes			free(bp);
648221420Sdes			(void) close(remin);
649221420Sdes			(void) close(remout);
650221420Sdes			remin = remout = -1;
651221420Sdes		} else if (src) {	/* standard remote to remote */
652157019Sdes			freeargs(&alist);
653157019Sdes			addargs(&alist, "%s", ssh_program);
654157019Sdes			addargs(&alist, "-x");
655221420Sdes			addargs(&alist, "-oClearAllForwardings=yes");
656157019Sdes			addargs(&alist, "-n");
657221420Sdes			for (j = 0; j < remote_remote_args.num; j++) {
658221420Sdes				addargs(&alist, "%s",
659221420Sdes				    remote_remote_args.list[j]);
660221420Sdes			}
66157429Smarkm			*src++ = 0;
66257429Smarkm			if (*src == 0)
66357429Smarkm				src = ".";
664113911Sdes			host = strrchr(argv[i], '@');
665157019Sdes
66657429Smarkm			if (host) {
66757429Smarkm				*host++ = 0;
66857429Smarkm				host = cleanhostname(host);
66957429Smarkm				suser = argv[i];
67057429Smarkm				if (*suser == '\0')
67157429Smarkm					suser = pwd->pw_name;
672157019Sdes				else if (!okname(suser))
67357429Smarkm					continue;
674157019Sdes				addargs(&alist, "-l");
675157019Sdes				addargs(&alist, "%s", suser);
67657429Smarkm			} else {
67757429Smarkm				host = cleanhostname(argv[i]);
67857429Smarkm			}
679204917Sdes			addargs(&alist, "--");
680157019Sdes			addargs(&alist, "%s", host);
681157019Sdes			addargs(&alist, "%s", cmd);
682157019Sdes			addargs(&alist, "%s", src);
683157019Sdes			addargs(&alist, "%s%s%s:%s",
684157019Sdes			    tuser ? tuser : "", tuser ? "@" : "",
685157019Sdes			    thost, targ);
686157019Sdes			if (do_local_cmd(&alist) != 0)
687126277Sdes				errs = 1;
68857429Smarkm		} else {	/* local to remote */
68957429Smarkm			if (remin == -1) {
690240075Sdes				xasprintf(&bp, "%s -t %s%s", cmd,
691240075Sdes				    *targ == '-' ? "-- " : "", targ);
69257429Smarkm				host = cleanhostname(thost);
69365668Skris				if (do_cmd(host, tuser, bp, &remin,
694162856Sdes				    &remout) < 0)
69557429Smarkm					exit(1);
69657429Smarkm				if (response() < 0)
69757429Smarkm					exit(1);
698255767Sdes				free(bp);
69957429Smarkm			}
70057429Smarkm			source(1, argv + i);
70157429Smarkm		}
70257429Smarkm	}
703255767Sdes	free(arg);
70457429Smarkm}
70557429Smarkm
70657429Smarkmvoid
707124211Sdestolocal(int argc, char **argv)
70857429Smarkm{
70957429Smarkm	char *bp, *host, *src, *suser;
710157019Sdes	arglist alist;
711162856Sdes	int i;
71257429Smarkm
713157019Sdes	memset(&alist, '\0', sizeof(alist));
714157019Sdes	alist.list = NULL;
715157019Sdes
71657429Smarkm	for (i = 0; i < argc - 1; i++) {
71757429Smarkm		if (!(src = colon(argv[i]))) {	/* Local to local. */
718157019Sdes			freeargs(&alist);
719157019Sdes			addargs(&alist, "%s", _PATH_CP);
720157019Sdes			if (iamrecursive)
721157019Sdes				addargs(&alist, "-r");
722157019Sdes			if (pflag)
723157019Sdes				addargs(&alist, "-p");
724204917Sdes			addargs(&alist, "--");
725157019Sdes			addargs(&alist, "%s", argv[i]);
726157019Sdes			addargs(&alist, "%s", argv[argc-1]);
727157019Sdes			if (do_local_cmd(&alist))
72857429Smarkm				++errs;
72957429Smarkm			continue;
73057429Smarkm		}
73157429Smarkm		*src++ = 0;
73257429Smarkm		if (*src == 0)
73357429Smarkm			src = ".";
734113911Sdes		if ((host = strrchr(argv[i], '@')) == NULL) {
73557429Smarkm			host = argv[i];
73657429Smarkm			suser = NULL;
73757429Smarkm		} else {
73857429Smarkm			*host++ = 0;
73957429Smarkm			suser = argv[i];
74057429Smarkm			if (*suser == '\0')
74157429Smarkm				suser = pwd->pw_name;
74257429Smarkm		}
74357429Smarkm		host = cleanhostname(host);
744240075Sdes		xasprintf(&bp, "%s -f %s%s",
745240075Sdes		    cmd, *src == '-' ? "-- " : "", src);
746162856Sdes		if (do_cmd(host, suser, bp, &remin, &remout) < 0) {
747255767Sdes			free(bp);
74857429Smarkm			++errs;
74957429Smarkm			continue;
75057429Smarkm		}
751255767Sdes		free(bp);
75257429Smarkm		sink(1, argv + argc - 1);
75357429Smarkm		(void) close(remin);
75457429Smarkm		remin = remout = -1;
75557429Smarkm	}
75657429Smarkm}
75757429Smarkm
75857429Smarkmvoid
759124211Sdessource(int argc, char **argv)
76057429Smarkm{
76157429Smarkm	struct stat stb;
76257429Smarkm	static BUF buffer;
76357429Smarkm	BUF *bp;
764181111Sdes	off_t i, statbytes;
765295367Sdes	size_t amt, nr;
766149753Sdes	int fd = -1, haderr, indx;
767295367Sdes	char *last, *name, buf[2048], encname[PATH_MAX];
76876259Sgreen	int len;
76957429Smarkm
77057429Smarkm	for (indx = 0; indx < argc; ++indx) {
77157429Smarkm		name = argv[indx];
77257429Smarkm		statbytes = 0;
77376259Sgreen		len = strlen(name);
77476259Sgreen		while (len > 1 && name[len-1] == '/')
77576259Sgreen			name[--len] = '\0';
776181111Sdes		if ((fd = open(name, O_RDONLY|O_NONBLOCK, 0)) < 0)
777181111Sdes			goto syserr;
77892555Sdes		if (strchr(name, '\n') != NULL) {
779181111Sdes			strnvis(encname, name, sizeof(encname), VIS_NL);
780181111Sdes			name = encname;
78192555Sdes		}
78257429Smarkm		if (fstat(fd, &stb) < 0) {
78357429Smarkmsyserr:			run_err("%s: %s", name, strerror(errno));
78457429Smarkm			goto next;
78557429Smarkm		}
786181111Sdes		if (stb.st_size < 0) {
787181111Sdes			run_err("%s: %s", name, "Negative file size");
788181111Sdes			goto next;
789181111Sdes		}
790181111Sdes		unset_nonblock(fd);
79157429Smarkm		switch (stb.st_mode & S_IFMT) {
79257429Smarkm		case S_IFREG:
79357429Smarkm			break;
79457429Smarkm		case S_IFDIR:
79557429Smarkm			if (iamrecursive) {
79657429Smarkm				rsource(name, &stb);
79757429Smarkm				goto next;
79857429Smarkm			}
79957429Smarkm			/* FALLTHROUGH */
80057429Smarkm		default:
80157429Smarkm			run_err("%s: not a regular file", name);
80257429Smarkm			goto next;
80357429Smarkm		}
80457429Smarkm		if ((last = strrchr(name, '/')) == NULL)
80557429Smarkm			last = name;
80657429Smarkm		else
80757429Smarkm			++last;
80857429Smarkm		curfile = last;
80957429Smarkm		if (pflag) {
810255767Sdes			if (do_times(remout, verbose_mode, &stb) < 0)
81157429Smarkm				goto next;
81257429Smarkm		}
81357429Smarkm#define	FILEMODEMASK	(S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
81476259Sgreen		snprintf(buf, sizeof buf, "C%04o %lld %s\n",
81576259Sgreen		    (u_int) (stb.st_mode & FILEMODEMASK),
816157019Sdes		    (long long)stb.st_size, last);
817323124Sdes		if (verbose_mode)
818323124Sdes			fmprintf(stderr, "Sending file modes: %s", buf);
819124211Sdes		(void) atomicio(vwrite, remout, buf, strlen(buf));
82057429Smarkm		if (response() < 0)
82157429Smarkm			goto next;
822181111Sdes		if ((bp = allocbuf(&buffer, fd, COPY_BUFLEN)) == NULL) {
823157019Sdesnext:			if (fd != -1) {
824157019Sdes				(void) close(fd);
825157019Sdes				fd = -1;
826157019Sdes			}
82757429Smarkm			continue;
82857429Smarkm		}
829113911Sdes		if (showprogress)
830113911Sdes			start_progress_meter(curfile, stb.st_size, &statbytes);
831181111Sdes		set_nonblock(remout);
83257429Smarkm		for (haderr = i = 0; i < stb.st_size; i += bp->cnt) {
83357429Smarkm			amt = bp->cnt;
834181111Sdes			if (i + (off_t)amt > stb.st_size)
83557429Smarkm				amt = stb.st_size - i;
83657429Smarkm			if (!haderr) {
837295367Sdes				if ((nr = atomicio(read, fd,
838295367Sdes				    bp->buf, amt)) != amt) {
839149753Sdes					haderr = errno;
840295367Sdes					memset(bp->buf + nr, 0, amt - nr);
841295367Sdes				}
84257429Smarkm			}
843181111Sdes			/* Keep writing after error to retain sync */
844181111Sdes			if (haderr) {
845181111Sdes				(void)atomicio(vwrite, remout, bp->buf, amt);
846295367Sdes				memset(bp->buf, 0, amt);
847181111Sdes				continue;
84857429Smarkm			}
849221420Sdes			if (atomicio6(vwrite, remout, bp->buf, amt, scpio,
850181111Sdes			    &statbytes) != amt)
851181111Sdes				haderr = errno;
85257429Smarkm		}
853181111Sdes		unset_nonblock(remout);
85457429Smarkm
855157019Sdes		if (fd != -1) {
856157019Sdes			if (close(fd) < 0 && !haderr)
857157019Sdes				haderr = errno;
858157019Sdes			fd = -1;
859157019Sdes		}
86057429Smarkm		if (!haderr)
861124211Sdes			(void) atomicio(vwrite, remout, "", 1);
86257429Smarkm		else
86357429Smarkm			run_err("%s: %s", name, strerror(haderr));
86457429Smarkm		(void) response();
865323124Sdes		if (showprogress)
866323124Sdes			stop_progress_meter();
86757429Smarkm	}
86857429Smarkm}
86957429Smarkm
87057429Smarkmvoid
871124211Sdesrsource(char *name, struct stat *statp)
87257429Smarkm{
87357429Smarkm	DIR *dirp;
87457429Smarkm	struct dirent *dp;
875295367Sdes	char *last, *vect[1], path[PATH_MAX];
87657429Smarkm
87757429Smarkm	if (!(dirp = opendir(name))) {
87857429Smarkm		run_err("%s: %s", name, strerror(errno));
87957429Smarkm		return;
88057429Smarkm	}
88157429Smarkm	last = strrchr(name, '/');
882296781Sdes	if (last == NULL)
88357429Smarkm		last = name;
88457429Smarkm	else
88557429Smarkm		last++;
88657429Smarkm	if (pflag) {
887255767Sdes		if (do_times(remout, verbose_mode, statp) < 0) {
88857429Smarkm			closedir(dirp);
88957429Smarkm			return;
89057429Smarkm		}
89157429Smarkm	}
89276259Sgreen	(void) snprintf(path, sizeof path, "D%04o %d %.1024s\n",
89376259Sgreen	    (u_int) (statp->st_mode & FILEMODEMASK), 0, last);
89457429Smarkm	if (verbose_mode)
895323124Sdes		fmprintf(stderr, "Entering directory: %s", path);
896124211Sdes	(void) atomicio(vwrite, remout, path, strlen(path));
89757429Smarkm	if (response() < 0) {
89857429Smarkm		closedir(dirp);
89957429Smarkm		return;
90057429Smarkm	}
90176259Sgreen	while ((dp = readdir(dirp)) != NULL) {
90257429Smarkm		if (dp->d_ino == 0)
90357429Smarkm			continue;
90457429Smarkm		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
90557429Smarkm			continue;
90657429Smarkm		if (strlen(name) + 1 + strlen(dp->d_name) >= sizeof(path) - 1) {
90757429Smarkm			run_err("%s/%s: name too long", name, dp->d_name);
90857429Smarkm			continue;
90957429Smarkm		}
91076259Sgreen		(void) snprintf(path, sizeof path, "%s/%s", name, dp->d_name);
91157429Smarkm		vect[0] = path;
91257429Smarkm		source(1, vect);
91357429Smarkm	}
91457429Smarkm	(void) closedir(dirp);
915124211Sdes	(void) atomicio(vwrite, remout, "E\n", 2);
91657429Smarkm	(void) response();
91757429Smarkm}
91857429Smarkm
91957429Smarkmvoid
920124211Sdessink(int argc, char **argv)
92157429Smarkm{
92257429Smarkm	static BUF buffer;
92357429Smarkm	struct stat stb;
92457429Smarkm	enum {
92557429Smarkm		YES, NO, DISPLAYED
92657429Smarkm	} wrerr;
92757429Smarkm	BUF *bp;
928149753Sdes	off_t i;
929149753Sdes	size_t j, count;
930162856Sdes	int amt, exists, first, ofd;
931162856Sdes	mode_t mode, omode, mask;
932113911Sdes	off_t size, statbytes;
933255767Sdes	unsigned long long ull;
93465668Skris	int setimes, targisdir, wrerrno = 0;
935323124Sdes	char ch, *cp, *np, *targ, *why, *vect[1], buf[2048], visbuf[2048];
93669587Sgreen	struct timeval tv[2];
93757429Smarkm
93876259Sgreen#define	atime	tv[0]
93976259Sgreen#define	mtime	tv[1]
940147005Sdes#define	SCREWUP(str)	{ why = str; goto screwup; }
94157429Smarkm
94257429Smarkm	setimes = targisdir = 0;
94357429Smarkm	mask = umask(0);
94457429Smarkm	if (!pflag)
94557429Smarkm		(void) umask(mask);
94657429Smarkm	if (argc != 1) {
94757429Smarkm		run_err("ambiguous target");
94857429Smarkm		exit(1);
94957429Smarkm	}
95057429Smarkm	targ = *argv;
95157429Smarkm	if (targetshouldbedirectory)
95257429Smarkm		verifydir(targ);
95357429Smarkm
954124211Sdes	(void) atomicio(vwrite, remout, "", 1);
95557429Smarkm	if (stat(targ, &stb) == 0 && S_ISDIR(stb.st_mode))
95657429Smarkm		targisdir = 1;
95757429Smarkm	for (first = 1;; first = 0) {
95857429Smarkm		cp = buf;
959149753Sdes		if (atomicio(read, remin, cp, 1) != 1)
96057429Smarkm			return;
96157429Smarkm		if (*cp++ == '\n')
96257429Smarkm			SCREWUP("unexpected <newline>");
96357429Smarkm		do {
96460573Skris			if (atomicio(read, remin, &ch, sizeof(ch)) != sizeof(ch))
96557429Smarkm				SCREWUP("lost connection");
96657429Smarkm			*cp++ = ch;
96757429Smarkm		} while (cp < &buf[sizeof(buf) - 1] && ch != '\n');
96857429Smarkm		*cp = 0;
969137019Sdes		if (verbose_mode)
970323124Sdes			fmprintf(stderr, "Sink: %s", buf);
97157429Smarkm
97257429Smarkm		if (buf[0] == '\01' || buf[0] == '\02') {
973323124Sdes			if (iamremote == 0) {
974323124Sdes				(void) snmprintf(visbuf, sizeof(visbuf),
975323124Sdes				    NULL, "%s", buf + 1);
976124211Sdes				(void) atomicio(vwrite, STDERR_FILENO,
977323124Sdes				    visbuf, strlen(visbuf));
978323124Sdes			}
97957429Smarkm			if (buf[0] == '\02')
98057429Smarkm				exit(1);
98157429Smarkm			++errs;
98257429Smarkm			continue;
98357429Smarkm		}
98457429Smarkm		if (buf[0] == 'E') {
985124211Sdes			(void) atomicio(vwrite, remout, "", 1);
98657429Smarkm			return;
98757429Smarkm		}
98857429Smarkm		if (ch == '\n')
98957429Smarkm			*--cp = 0;
99057429Smarkm
99157429Smarkm		cp = buf;
99257429Smarkm		if (*cp == 'T') {
99357429Smarkm			setimes++;
99457429Smarkm			cp++;
995255767Sdes			if (!isdigit((unsigned char)*cp))
996255767Sdes				SCREWUP("mtime.sec not present");
997255767Sdes			ull = strtoull(cp, &cp, 10);
99876259Sgreen			if (!cp || *cp++ != ' ')
99957429Smarkm				SCREWUP("mtime.sec not delimited");
1000255767Sdes			if ((time_t)ull < 0 ||
1001255767Sdes			    (unsigned long long)(time_t)ull != ull)
1002255767Sdes				setimes = 0;	/* out of range */
1003255767Sdes			mtime.tv_sec = ull;
100476259Sgreen			mtime.tv_usec = strtol(cp, &cp, 10);
1005255767Sdes			if (!cp || *cp++ != ' ' || mtime.tv_usec < 0 ||
1006255767Sdes			    mtime.tv_usec > 999999)
100757429Smarkm				SCREWUP("mtime.usec not delimited");
1008255767Sdes			if (!isdigit((unsigned char)*cp))
1009255767Sdes				SCREWUP("atime.sec not present");
1010255767Sdes			ull = strtoull(cp, &cp, 10);
101176259Sgreen			if (!cp || *cp++ != ' ')
101257429Smarkm				SCREWUP("atime.sec not delimited");
1013255767Sdes			if ((time_t)ull < 0 ||
1014255767Sdes			    (unsigned long long)(time_t)ull != ull)
1015255767Sdes				setimes = 0;	/* out of range */
1016255767Sdes			atime.tv_sec = ull;
101776259Sgreen			atime.tv_usec = strtol(cp, &cp, 10);
1018255767Sdes			if (!cp || *cp++ != '\0' || atime.tv_usec < 0 ||
1019255767Sdes			    atime.tv_usec > 999999)
102057429Smarkm				SCREWUP("atime.usec not delimited");
1021124211Sdes			(void) atomicio(vwrite, remout, "", 1);
102257429Smarkm			continue;
102357429Smarkm		}
102457429Smarkm		if (*cp != 'C' && *cp != 'D') {
102557429Smarkm			/*
102657429Smarkm			 * Check for the case "rcp remote:foo\* local:bar".
102757429Smarkm			 * In this case, the line "No match." can be returned
102857429Smarkm			 * by the shell before the rcp command on the remote is
102957429Smarkm			 * executed so the ^Aerror_message convention isn't
103057429Smarkm			 * followed.
103157429Smarkm			 */
103257429Smarkm			if (first) {
103357429Smarkm				run_err("%s", cp);
103457429Smarkm				exit(1);
103557429Smarkm			}
103657429Smarkm			SCREWUP("expected control record");
103757429Smarkm		}
103857429Smarkm		mode = 0;
103957429Smarkm		for (++cp; cp < buf + 5; cp++) {
104057429Smarkm			if (*cp < '0' || *cp > '7')
104157429Smarkm				SCREWUP("bad mode");
104257429Smarkm			mode = (mode << 3) | (*cp - '0');
104357429Smarkm		}
104457429Smarkm		if (*cp++ != ' ')
104557429Smarkm			SCREWUP("mode not delimited");
104657429Smarkm
1047262566Sdes		for (size = 0; isdigit((unsigned char)*cp);)
104857429Smarkm			size = size * 10 + (*cp++ - '0');
104957429Smarkm		if (*cp++ != ' ')
105057429Smarkm			SCREWUP("size not delimited");
1051343098Semaste		if (*cp == '\0' || strchr(cp, '/') != NULL ||
1052343098Semaste		    strcmp(cp, ".") == 0 || strcmp(cp, "..") == 0) {
1053137019Sdes			run_err("error: unexpected filename: %s", cp);
1054137019Sdes			exit(1);
1055137019Sdes		}
105657429Smarkm		if (targisdir) {
105757429Smarkm			static char *namebuf;
1058149753Sdes			static size_t cursize;
105957429Smarkm			size_t need;
106057429Smarkm
106157429Smarkm			need = strlen(targ) + strlen(cp) + 250;
106276259Sgreen			if (need > cursize) {
1063255767Sdes				free(namebuf);
106457429Smarkm				namebuf = xmalloc(need);
106576259Sgreen				cursize = need;
106676259Sgreen			}
106776259Sgreen			(void) snprintf(namebuf, need, "%s%s%s", targ,
106898675Sdes			    strcmp(targ, "/") ? "/" : "", cp);
106957429Smarkm			np = namebuf;
107057429Smarkm		} else
107157429Smarkm			np = targ;
107257429Smarkm		curfile = cp;
107357429Smarkm		exists = stat(np, &stb) == 0;
107457429Smarkm		if (buf[0] == 'D') {
107557429Smarkm			int mod_flag = pflag;
1076137019Sdes			if (!iamrecursive)
1077137019Sdes				SCREWUP("received directory without -r");
107857429Smarkm			if (exists) {
107957429Smarkm				if (!S_ISDIR(stb.st_mode)) {
108057429Smarkm					errno = ENOTDIR;
108157429Smarkm					goto bad;
108257429Smarkm				}
108357429Smarkm				if (pflag)
108457429Smarkm					(void) chmod(np, mode);
108557429Smarkm			} else {
108657429Smarkm				/* Handle copying from a read-only
108757429Smarkm				   directory */
108857429Smarkm				mod_flag = 1;
108957429Smarkm				if (mkdir(np, mode | S_IRWXU) < 0)
109057429Smarkm					goto bad;
109157429Smarkm			}
109276259Sgreen			vect[0] = xstrdup(np);
109357429Smarkm			sink(1, vect);
109457429Smarkm			if (setimes) {
109557429Smarkm				setimes = 0;
109676259Sgreen				if (utimes(vect[0], tv) < 0)
109757429Smarkm					run_err("%s: set times: %s",
109876259Sgreen					    vect[0], strerror(errno));
109957429Smarkm			}
110057429Smarkm			if (mod_flag)
110176259Sgreen				(void) chmod(vect[0], mode);
1102255767Sdes			free(vect[0]);
110357429Smarkm			continue;
110457429Smarkm		}
110557429Smarkm		omode = mode;
1106255767Sdes		mode |= S_IWUSR;
110792555Sdes		if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) < 0) {
110857429Smarkmbad:			run_err("%s: %s", np, strerror(errno));
110957429Smarkm			continue;
111057429Smarkm		}
1111124211Sdes		(void) atomicio(vwrite, remout, "", 1);
1112181111Sdes		if ((bp = allocbuf(&buffer, ofd, COPY_BUFLEN)) == NULL) {
111357429Smarkm			(void) close(ofd);
111457429Smarkm			continue;
111557429Smarkm		}
111657429Smarkm		cp = bp->buf;
111757429Smarkm		wrerr = NO;
111857429Smarkm
111957429Smarkm		statbytes = 0;
1120113911Sdes		if (showprogress)
1121113911Sdes			start_progress_meter(curfile, size, &statbytes);
1122181111Sdes		set_nonblock(remin);
1123181111Sdes		for (count = i = 0; i < size; i += bp->cnt) {
1124181111Sdes			amt = bp->cnt;
112557429Smarkm			if (i + amt > size)
112657429Smarkm				amt = size - i;
112757429Smarkm			count += amt;
112857429Smarkm			do {
1129221420Sdes				j = atomicio6(read, remin, cp, amt,
1130221420Sdes				    scpio, &statbytes);
1131149753Sdes				if (j == 0) {
1132181111Sdes					run_err("%s", j != EPIPE ?
1133181111Sdes					    strerror(errno) :
113476259Sgreen					    "dropped connection");
113557429Smarkm					exit(1);
113657429Smarkm				}
113757429Smarkm				amt -= j;
113857429Smarkm				cp += j;
113957429Smarkm			} while (amt > 0);
1140126277Sdes
114157429Smarkm			if (count == bp->cnt) {
114257429Smarkm				/* Keep reading so we stay sync'd up. */
114357429Smarkm				if (wrerr == NO) {
1144149753Sdes					if (atomicio(vwrite, ofd, bp->buf,
1145149753Sdes					    count) != count) {
114657429Smarkm						wrerr = YES;
1147149753Sdes						wrerrno = errno;
114857429Smarkm					}
114957429Smarkm				}
115057429Smarkm				count = 0;
115157429Smarkm				cp = bp->buf;
115257429Smarkm			}
115357429Smarkm		}
1154181111Sdes		unset_nonblock(remin);
115557429Smarkm		if (count != 0 && wrerr == NO &&
1156149753Sdes		    atomicio(vwrite, ofd, bp->buf, count) != count) {
115757429Smarkm			wrerr = YES;
1158149753Sdes			wrerrno = errno;
115957429Smarkm		}
1160181111Sdes		if (wrerr == NO && (!exists || S_ISREG(stb.st_mode)) &&
1161181111Sdes		    ftruncate(ofd, size) != 0) {
116257429Smarkm			run_err("%s: truncate: %s", np, strerror(errno));
116357429Smarkm			wrerr = DISPLAYED;
116457429Smarkm		}
116557429Smarkm		if (pflag) {
116657429Smarkm			if (exists || omode != mode)
116798937Sdes#ifdef HAVE_FCHMOD
1168137019Sdes				if (fchmod(ofd, omode)) {
116998937Sdes#else /* HAVE_FCHMOD */
1170137019Sdes				if (chmod(np, omode)) {
117198937Sdes#endif /* HAVE_FCHMOD */
117257429Smarkm					run_err("%s: set mode: %s",
117376259Sgreen					    np, strerror(errno));
1174137019Sdes					wrerr = DISPLAYED;
1175137019Sdes				}
117657429Smarkm		} else {
117757429Smarkm			if (!exists && omode != mode)
117898937Sdes#ifdef HAVE_FCHMOD
1179137019Sdes				if (fchmod(ofd, omode & ~mask)) {
118098937Sdes#else /* HAVE_FCHMOD */
1181137019Sdes				if (chmod(np, omode & ~mask)) {
118298937Sdes#endif /* HAVE_FCHMOD */
118357429Smarkm					run_err("%s: set mode: %s",
118476259Sgreen					    np, strerror(errno));
1185137019Sdes					wrerr = DISPLAYED;
1186137019Sdes				}
118757429Smarkm		}
118865668Skris		if (close(ofd) == -1) {
118965668Skris			wrerr = YES;
119065668Skris			wrerrno = errno;
119165668Skris		}
119257429Smarkm		(void) response();
1193323124Sdes		if (showprogress)
1194323124Sdes			stop_progress_meter();
119557429Smarkm		if (setimes && wrerr == NO) {
119657429Smarkm			setimes = 0;
119769587Sgreen			if (utimes(np, tv) < 0) {
119857429Smarkm				run_err("%s: set times: %s",
119976259Sgreen				    np, strerror(errno));
120057429Smarkm				wrerr = DISPLAYED;
120157429Smarkm			}
120257429Smarkm		}
120357429Smarkm		switch (wrerr) {
120457429Smarkm		case YES:
120557429Smarkm			run_err("%s: %s", np, strerror(wrerrno));
120657429Smarkm			break;
120757429Smarkm		case NO:
1208124211Sdes			(void) atomicio(vwrite, remout, "", 1);
120957429Smarkm			break;
121057429Smarkm		case DISPLAYED:
121157429Smarkm			break;
121257429Smarkm		}
121357429Smarkm	}
121457429Smarkmscrewup:
121557429Smarkm	run_err("protocol error: %s", why);
121657429Smarkm	exit(1);
121757429Smarkm}
121857429Smarkm
121957429Smarkmint
122092555Sdesresponse(void)
122157429Smarkm{
1222323124Sdes	char ch, *cp, resp, rbuf[2048], visbuf[2048];
122357429Smarkm
122460573Skris	if (atomicio(read, remin, &resp, sizeof(resp)) != sizeof(resp))
122557429Smarkm		lostconn(0);
122657429Smarkm
122757429Smarkm	cp = rbuf;
122857429Smarkm	switch (resp) {
122957429Smarkm	case 0:		/* ok */
123057429Smarkm		return (0);
123157429Smarkm	default:
123257429Smarkm		*cp++ = resp;
123357429Smarkm		/* FALLTHROUGH */
123457429Smarkm	case 1:		/* error, followed by error msg */
123557429Smarkm	case 2:		/* fatal error, "" */
123657429Smarkm		do {
123760573Skris			if (atomicio(read, remin, &ch, sizeof(ch)) != sizeof(ch))
123857429Smarkm				lostconn(0);
123957429Smarkm			*cp++ = ch;
124057429Smarkm		} while (cp < &rbuf[sizeof(rbuf) - 1] && ch != '\n');
124157429Smarkm
1242323124Sdes		if (!iamremote) {
1243323124Sdes			cp[-1] = '\0';
1244323124Sdes			(void) snmprintf(visbuf, sizeof(visbuf),
1245323124Sdes			    NULL, "%s\n", rbuf);
1246323124Sdes			(void) atomicio(vwrite, STDERR_FILENO,
1247323124Sdes			    visbuf, strlen(visbuf));
1248323124Sdes		}
124957429Smarkm		++errs;
125057429Smarkm		if (resp == 1)
125157429Smarkm			return (-1);
125257429Smarkm		exit(1);
125357429Smarkm	}
125457429Smarkm	/* NOTREACHED */
125557429Smarkm}
125657429Smarkm
125757429Smarkmvoid
125892555Sdesusage(void)
125957429Smarkm{
126092555Sdes	(void) fprintf(stderr,
1261221420Sdes	    "usage: scp [-12346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]\n"
1262126277Sdes	    "           [-l limit] [-o ssh_option] [-P port] [-S program]\n"
1263181111Sdes	    "           [[user@]host1:]file1 ... [[user@]host2:]file2\n");
126457429Smarkm	exit(1);
126557429Smarkm}
126657429Smarkm
126757429Smarkmvoid
126857429Smarkmrun_err(const char *fmt,...)
126957429Smarkm{
127057429Smarkm	static FILE *fp;
127157429Smarkm	va_list ap;
127257429Smarkm
127357429Smarkm	++errs;
1274162856Sdes	if (fp != NULL || (remout != -1 && (fp = fdopen(remout, "w")))) {
1275162856Sdes		(void) fprintf(fp, "%c", 0x01);
1276162856Sdes		(void) fprintf(fp, "scp: ");
1277162856Sdes		va_start(ap, fmt);
1278162856Sdes		(void) vfprintf(fp, fmt, ap);
1279162856Sdes		va_end(ap);
1280162856Sdes		(void) fprintf(fp, "\n");
1281162856Sdes		(void) fflush(fp);
1282162856Sdes	}
128357429Smarkm
128457429Smarkm	if (!iamremote) {
128592555Sdes		va_start(ap, fmt);
1286323124Sdes		vfmprintf(stderr, fmt, ap);
128792555Sdes		va_end(ap);
128857429Smarkm		fprintf(stderr, "\n");
128957429Smarkm	}
129057429Smarkm}
129157429Smarkm
129257429Smarkmvoid
1293124211Sdesverifydir(char *cp)
129457429Smarkm{
129557429Smarkm	struct stat stb;
129657429Smarkm
129757429Smarkm	if (!stat(cp, &stb)) {
129857429Smarkm		if (S_ISDIR(stb.st_mode))
129957429Smarkm			return;
130057429Smarkm		errno = ENOTDIR;
130157429Smarkm	}
130257429Smarkm	run_err("%s: %s", cp, strerror(errno));
1303149753Sdes	killchild(0);
130457429Smarkm}
130557429Smarkm
130657429Smarkmint
1307124211Sdesokname(char *cp0)
130857429Smarkm{
130957429Smarkm	int c;
131057429Smarkm	char *cp;
131157429Smarkm
131257429Smarkm	cp = cp0;
131357429Smarkm	do {
131492555Sdes		c = (int)*cp;
131557429Smarkm		if (c & 0200)
131657429Smarkm			goto bad;
1317262566Sdes		if (!isalpha(c) && !isdigit((unsigned char)c)) {
1318113911Sdes			switch (c) {
1319113911Sdes			case '\'':
1320113911Sdes			case '"':
1321113911Sdes			case '`':
1322113911Sdes			case ' ':
1323113911Sdes			case '#':
1324113911Sdes				goto bad;
1325113911Sdes			default:
1326113911Sdes				break;
1327113911Sdes			}
1328113911Sdes		}
132957429Smarkm	} while (*++cp);
133057429Smarkm	return (1);
133157429Smarkm
1332323124Sdesbad:	fmprintf(stderr, "%s: invalid user name\n", cp0);
133357429Smarkm	return (0);
133457429Smarkm}
133557429Smarkm
133657429SmarkmBUF *
1337124211Sdesallocbuf(BUF *bp, int fd, int blksize)
133857429Smarkm{
133957429Smarkm	size_t size;
134098937Sdes#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
134157429Smarkm	struct stat stb;
134257429Smarkm
134357429Smarkm	if (fstat(fd, &stb) < 0) {
134457429Smarkm		run_err("fstat: %s", strerror(errno));
134557429Smarkm		return (0);
134657429Smarkm	}
1347113911Sdes	size = roundup(stb.st_blksize, blksize);
1348113911Sdes	if (size == 0)
134957429Smarkm		size = blksize;
135098937Sdes#else /* HAVE_STRUCT_STAT_ST_BLKSIZE */
135198937Sdes	size = blksize;
135298937Sdes#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
135357429Smarkm	if (bp->cnt >= size)
135457429Smarkm		return (bp);
135557429Smarkm	if (bp->buf == NULL)
135657429Smarkm		bp->buf = xmalloc(size);
135757429Smarkm	else
1358295367Sdes		bp->buf = xreallocarray(bp->buf, 1, size);
135992555Sdes	memset(bp->buf, 0, size);
136057429Smarkm	bp->cnt = size;
136157429Smarkm	return (bp);
136257429Smarkm}
136357429Smarkm
136457429Smarkmvoid
1365124211Sdeslostconn(int signo)
136657429Smarkm{
136757429Smarkm	if (!iamremote)
1368255767Sdes		(void)write(STDERR_FILENO, "lost connection\n", 16);
136992555Sdes	if (signo)
137092555Sdes		_exit(1);
137192555Sdes	else
137292555Sdes		exit(1);
137357429Smarkm}
1374