sftp.c revision 240075
1240075Sdes/* $OpenBSD: sftp.c,v 1.136 2012/06/22 14:36:33 dtucker Exp $ */
2224638Sbrooks/* $FreeBSD: head/crypto/openssh/sftp.c 240075 2012-09-03 16:51:41Z des $ */
376259Sgreen/*
4126274Sdes * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
576259Sgreen *
6126274Sdes * Permission to use, copy, modify, and distribute this software for any
7126274Sdes * purpose with or without fee is hereby granted, provided that the above
8126274Sdes * copyright notice and this permission notice appear in all copies.
976259Sgreen *
10126274Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11126274Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12126274Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13126274Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14126274Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15126274Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16126274Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1776259Sgreen */
1876259Sgreen
1976259Sgreen#include "includes.h"
2076259Sgreen
21162852Sdes#include <sys/types.h>
22162852Sdes#include <sys/ioctl.h>
23162852Sdes#ifdef HAVE_SYS_STAT_H
24162852Sdes# include <sys/stat.h>
25162852Sdes#endif
26162852Sdes#include <sys/param.h>
27162852Sdes#include <sys/socket.h>
28162852Sdes#include <sys/wait.h>
29181111Sdes#ifdef HAVE_SYS_STATVFS_H
30181111Sdes#include <sys/statvfs.h>
31181111Sdes#endif
3276259Sgreen
33181111Sdes#include <ctype.h>
34162852Sdes#include <errno.h>
35162852Sdes
36162852Sdes#ifdef HAVE_PATHS_H
37162852Sdes# include <paths.h>
38162852Sdes#endif
39204917Sdes#ifdef HAVE_LIBGEN_H
40204917Sdes#include <libgen.h>
41204917Sdes#endif
42146998Sdes#ifdef USE_LIBEDIT
43146998Sdes#include <histedit.h>
44146998Sdes#else
45146998Sdestypedef void EditLine;
46146998Sdes#endif
47162852Sdes#include <signal.h>
48162852Sdes#include <stdlib.h>
49162852Sdes#include <stdio.h>
50162852Sdes#include <string.h>
51162852Sdes#include <unistd.h>
52162852Sdes#include <stdarg.h>
53146998Sdes
54181111Sdes#ifdef HAVE_UTIL_H
55181111Sdes# include <util.h>
56181111Sdes#endif
57181111Sdes
58181111Sdes#ifdef HAVE_LIBUTIL_H
59181111Sdes# include <libutil.h>
60181111Sdes#endif
61181111Sdes
6276259Sgreen#include "xmalloc.h"
6376259Sgreen#include "log.h"
6476259Sgreen#include "pathnames.h"
6592555Sdes#include "misc.h"
6676259Sgreen
6776259Sgreen#include "sftp.h"
68162852Sdes#include "buffer.h"
6976259Sgreen#include "sftp-common.h"
7076259Sgreen#include "sftp-client.h"
7176259Sgreen
72204917Sdes#define DEFAULT_COPY_BUFLEN	32768	/* Size of buffer for up/download */
73224638Sbrooks#define DEFAULT_NUM_REQUESTS	256	/* # concurrent outstanding requests */
74204917Sdes
75126274Sdes/* File to read commands from */
76126274SdesFILE* infile;
77126274Sdes
78126274Sdes/* Are we in batchfile mode? */
79126274Sdesint batchmode = 0;
80126274Sdes
81126274Sdes/* PID of ssh transport process */
82126274Sdesstatic pid_t sshpid = -1;
83126274Sdes
84126274Sdes/* This is set to 0 if the progressmeter is not desired. */
85128456Sdesint showprogress = 1;
86126274Sdes
87204917Sdes/* When this option is set, we always recursively download/upload directories */
88204917Sdesint global_rflag = 0;
89204917Sdes
90204917Sdes/* When this option is set, the file transfers will always preserve times */
91204917Sdesint global_pflag = 0;
92204917Sdes
93137015Sdes/* SIGINT received during command processing */
94137015Sdesvolatile sig_atomic_t interrupted = 0;
95137015Sdes
96137015Sdes/* I wish qsort() took a separate ctx for the comparison function...*/
97137015Sdesint sort_flag;
98137015Sdes
99204917Sdes/* Context used for commandline completion */
100204917Sdesstruct complete_ctx {
101204917Sdes	struct sftp_conn *conn;
102204917Sdes	char **remote_pathp;
103204917Sdes};
104204917Sdes
105126274Sdesint remote_glob(struct sftp_conn *, const char *, int,
106126274Sdes    int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
107126274Sdes
10898937Sdesextern char *__progname;
10998937Sdes
110126274Sdes/* Separators for interactive commands */
111126274Sdes#define WHITESPACE " \t\r\n"
11276259Sgreen
113137015Sdes/* ls flags */
114204917Sdes#define LS_LONG_VIEW	0x0001	/* Full view ala ls -l */
115204917Sdes#define LS_SHORT_VIEW	0x0002	/* Single row view ala ls -1 */
116204917Sdes#define LS_NUMERIC_VIEW	0x0004	/* Long view with numeric uid/gid */
117204917Sdes#define LS_NAME_SORT	0x0008	/* Sort by name (default) */
118204917Sdes#define LS_TIME_SORT	0x0010	/* Sort by mtime */
119204917Sdes#define LS_SIZE_SORT	0x0020	/* Sort by file size */
120204917Sdes#define LS_REVERSE_SORT	0x0040	/* Reverse sort order */
121204917Sdes#define LS_SHOW_ALL	0x0080	/* Don't skip filenames starting with '.' */
122204917Sdes#define LS_SI_UNITS	0x0100	/* Display sizes as K, M, G, etc. */
123113908Sdes
124204917Sdes#define VIEW_FLAGS	(LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
125137015Sdes#define SORT_FLAGS	(LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
126137015Sdes
127126274Sdes/* Commands for interactive mode */
128126274Sdes#define I_CHDIR		1
129126274Sdes#define I_CHGRP		2
130126274Sdes#define I_CHMOD		3
131126274Sdes#define I_CHOWN		4
132181111Sdes#define I_DF		24
133126274Sdes#define I_GET		5
134126274Sdes#define I_HELP		6
135126274Sdes#define I_LCHDIR	7
136221420Sdes#define I_LINK		25
137126274Sdes#define I_LLS		8
138126274Sdes#define I_LMKDIR	9
139126274Sdes#define I_LPWD		10
140126274Sdes#define I_LS		11
141126274Sdes#define I_LUMASK	12
142126274Sdes#define I_MKDIR		13
143126274Sdes#define I_PUT		14
144126274Sdes#define I_PWD		15
145126274Sdes#define I_QUIT		16
146126274Sdes#define I_RENAME	17
147126274Sdes#define I_RM		18
148126274Sdes#define I_RMDIR		19
149126274Sdes#define I_SHELL		20
150126274Sdes#define I_SYMLINK	21
151126274Sdes#define I_VERSION	22
152126274Sdes#define I_PROGRESS	23
153126274Sdes
154126274Sdesstruct CMD {
155126274Sdes	const char *c;
156126274Sdes	const int n;
157204917Sdes	const int t;
158126274Sdes};
159126274Sdes
160204917Sdes/* Type of completion */
161204917Sdes#define NOARGS	0
162204917Sdes#define REMOTE	1
163204917Sdes#define LOCAL	2
164204917Sdes
165126274Sdesstatic const struct CMD cmds[] = {
166204917Sdes	{ "bye",	I_QUIT,		NOARGS	},
167204917Sdes	{ "cd",		I_CHDIR,	REMOTE	},
168204917Sdes	{ "chdir",	I_CHDIR,	REMOTE	},
169204917Sdes	{ "chgrp",	I_CHGRP,	REMOTE	},
170204917Sdes	{ "chmod",	I_CHMOD,	REMOTE	},
171204917Sdes	{ "chown",	I_CHOWN,	REMOTE	},
172204917Sdes	{ "df",		I_DF,		REMOTE	},
173204917Sdes	{ "dir",	I_LS,		REMOTE	},
174204917Sdes	{ "exit",	I_QUIT,		NOARGS	},
175204917Sdes	{ "get",	I_GET,		REMOTE	},
176204917Sdes	{ "help",	I_HELP,		NOARGS	},
177204917Sdes	{ "lcd",	I_LCHDIR,	LOCAL	},
178204917Sdes	{ "lchdir",	I_LCHDIR,	LOCAL	},
179204917Sdes	{ "lls",	I_LLS,		LOCAL	},
180204917Sdes	{ "lmkdir",	I_LMKDIR,	LOCAL	},
181221420Sdes	{ "ln",		I_LINK,		REMOTE	},
182204917Sdes	{ "lpwd",	I_LPWD,		LOCAL	},
183204917Sdes	{ "ls",		I_LS,		REMOTE	},
184204917Sdes	{ "lumask",	I_LUMASK,	NOARGS	},
185204917Sdes	{ "mkdir",	I_MKDIR,	REMOTE	},
186215116Sdes	{ "mget",	I_GET,		REMOTE	},
187215116Sdes	{ "mput",	I_PUT,		LOCAL	},
188204917Sdes	{ "progress",	I_PROGRESS,	NOARGS	},
189204917Sdes	{ "put",	I_PUT,		LOCAL	},
190204917Sdes	{ "pwd",	I_PWD,		REMOTE	},
191204917Sdes	{ "quit",	I_QUIT,		NOARGS	},
192204917Sdes	{ "rename",	I_RENAME,	REMOTE	},
193204917Sdes	{ "rm",		I_RM,		REMOTE	},
194204917Sdes	{ "rmdir",	I_RMDIR,	REMOTE	},
195204917Sdes	{ "symlink",	I_SYMLINK,	REMOTE	},
196204917Sdes	{ "version",	I_VERSION,	NOARGS	},
197204917Sdes	{ "!",		I_SHELL,	NOARGS	},
198204917Sdes	{ "?",		I_HELP,		NOARGS	},
199204917Sdes	{ NULL,		-1,		-1	}
200126274Sdes};
201126274Sdes
202204917Sdesint interactive_loop(struct sftp_conn *, char *file1, char *file2);
203126274Sdes
204181111Sdes/* ARGSUSED */
20592555Sdesstatic void
206137015Sdeskillchild(int signo)
207137015Sdes{
208146998Sdes	if (sshpid > 1) {
209137015Sdes		kill(sshpid, SIGTERM);
210146998Sdes		waitpid(sshpid, NULL, 0);
211146998Sdes	}
212137015Sdes
213137015Sdes	_exit(1);
214137015Sdes}
215137015Sdes
216181111Sdes/* ARGSUSED */
217137015Sdesstatic void
218137015Sdescmd_interrupt(int signo)
219137015Sdes{
220137015Sdes	const char msg[] = "\rInterrupt  \n";
221146998Sdes	int olderrno = errno;
222137015Sdes
223137015Sdes	write(STDERR_FILENO, msg, sizeof(msg) - 1);
224137015Sdes	interrupted = 1;
225146998Sdes	errno = olderrno;
226137015Sdes}
227137015Sdes
228137015Sdesstatic void
229126274Sdeshelp(void)
230126274Sdes{
231192595Sdes	printf("Available commands:\n"
232192595Sdes	    "bye                                Quit sftp\n"
233192595Sdes	    "cd path                            Change remote directory to 'path'\n"
234192595Sdes	    "chgrp grp path                     Change group of file 'path' to 'grp'\n"
235192595Sdes	    "chmod mode path                    Change permissions of file 'path' to 'mode'\n"
236192595Sdes	    "chown own path                     Change owner of file 'path' to 'own'\n"
237192595Sdes	    "df [-hi] [path]                    Display statistics for current directory or\n"
238192595Sdes	    "                                   filesystem containing 'path'\n"
239192595Sdes	    "exit                               Quit sftp\n"
240204917Sdes	    "get [-Ppr] remote [local]          Download file\n"
241192595Sdes	    "help                               Display this help text\n"
242192595Sdes	    "lcd path                           Change local directory to 'path'\n"
243192595Sdes	    "lls [ls-options [path]]            Display local directory listing\n"
244192595Sdes	    "lmkdir path                        Create local directory\n"
245221420Sdes	    "ln [-s] oldpath newpath            Link remote file (-s for symlink)\n"
246192595Sdes	    "lpwd                               Print local working directory\n"
247204917Sdes	    "ls [-1afhlnrSt] [path]             Display remote directory listing\n"
248192595Sdes	    "lumask umask                       Set local umask to 'umask'\n"
249192595Sdes	    "mkdir path                         Create remote directory\n"
250192595Sdes	    "progress                           Toggle display of progress meter\n"
251204917Sdes	    "put [-Ppr] local [remote]          Upload file\n"
252192595Sdes	    "pwd                                Display remote working directory\n"
253192595Sdes	    "quit                               Quit sftp\n"
254192595Sdes	    "rename oldpath newpath             Rename remote file\n"
255192595Sdes	    "rm path                            Delete remote file\n"
256192595Sdes	    "rmdir path                         Remove remote directory\n"
257192595Sdes	    "symlink oldpath newpath            Symlink remote file\n"
258192595Sdes	    "version                            Show SFTP version\n"
259192595Sdes	    "!command                           Execute 'command' in local shell\n"
260192595Sdes	    "!                                  Escape to local shell\n"
261192595Sdes	    "?                                  Synonym for help\n");
262126274Sdes}
263126274Sdes
264126274Sdesstatic void
265126274Sdeslocal_do_shell(const char *args)
266126274Sdes{
267126274Sdes	int status;
268126274Sdes	char *shell;
269126274Sdes	pid_t pid;
270126274Sdes
271126274Sdes	if (!*args)
272126274Sdes		args = NULL;
273126274Sdes
274221420Sdes	if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
275126274Sdes		shell = _PATH_BSHELL;
276126274Sdes
277126274Sdes	if ((pid = fork()) == -1)
278126274Sdes		fatal("Couldn't fork: %s", strerror(errno));
279126274Sdes
280126274Sdes	if (pid == 0) {
281126274Sdes		/* XXX: child has pipe fds to ssh subproc open - issue? */
282126274Sdes		if (args) {
283126274Sdes			debug3("Executing %s -c \"%s\"", shell, args);
284126274Sdes			execl(shell, shell, "-c", args, (char *)NULL);
285126274Sdes		} else {
286126274Sdes			debug3("Executing %s", shell);
287126274Sdes			execl(shell, shell, (char *)NULL);
288126274Sdes		}
289126274Sdes		fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
290126274Sdes		    strerror(errno));
291126274Sdes		_exit(1);
292126274Sdes	}
293126274Sdes	while (waitpid(pid, &status, 0) == -1)
294126274Sdes		if (errno != EINTR)
295126274Sdes			fatal("Couldn't wait for child: %s", strerror(errno));
296126274Sdes	if (!WIFEXITED(status))
297162852Sdes		error("Shell exited abnormally");
298126274Sdes	else if (WEXITSTATUS(status))
299126274Sdes		error("Shell exited with status %d", WEXITSTATUS(status));
300126274Sdes}
301126274Sdes
302126274Sdesstatic void
303126274Sdeslocal_do_ls(const char *args)
304126274Sdes{
305126274Sdes	if (!args || !*args)
306126274Sdes		local_do_shell(_PATH_LS);
307126274Sdes	else {
308126274Sdes		int len = strlen(_PATH_LS " ") + strlen(args) + 1;
309126274Sdes		char *buf = xmalloc(len);
310126274Sdes
311126274Sdes		/* XXX: quoting - rip quoting code from ftp? */
312126274Sdes		snprintf(buf, len, _PATH_LS " %s", args);
313126274Sdes		local_do_shell(buf);
314126274Sdes		xfree(buf);
315126274Sdes	}
316126274Sdes}
317126274Sdes
318126274Sdes/* Strip one path (usually the pwd) from the start of another */
319126274Sdesstatic char *
320126274Sdespath_strip(char *path, char *strip)
321126274Sdes{
322126274Sdes	size_t len;
323126274Sdes
324126274Sdes	if (strip == NULL)
325126274Sdes		return (xstrdup(path));
326126274Sdes
327126274Sdes	len = strlen(strip);
328146998Sdes	if (strncmp(path, strip, len) == 0) {
329126274Sdes		if (strip[len - 1] != '/' && path[len] == '/')
330126274Sdes			len++;
331126274Sdes		return (xstrdup(path + len));
332126274Sdes	}
333126274Sdes
334126274Sdes	return (xstrdup(path));
335126274Sdes}
336126274Sdes
337126274Sdesstatic char *
338126274Sdesmake_absolute(char *p, char *pwd)
339126274Sdes{
340137015Sdes	char *abs_str;
341126274Sdes
342126274Sdes	/* Derelativise */
343126274Sdes	if (p && p[0] != '/') {
344137015Sdes		abs_str = path_append(pwd, p);
345126274Sdes		xfree(p);
346137015Sdes		return(abs_str);
347126274Sdes	} else
348126274Sdes		return(p);
349126274Sdes}
350126274Sdes
351126274Sdesstatic int
352204917Sdesparse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
353204917Sdes    int *rflag)
354126274Sdes{
355181111Sdes	extern int opterr, optind, optopt, optreset;
356181111Sdes	int ch;
357126274Sdes
358181111Sdes	optind = optreset = 1;
359181111Sdes	opterr = 0;
360181111Sdes
361204917Sdes	*rflag = *pflag = 0;
362204917Sdes	while ((ch = getopt(argc, argv, "PpRr")) != -1) {
363181111Sdes		switch (ch) {
364126274Sdes		case 'p':
365126274Sdes		case 'P':
366126274Sdes			*pflag = 1;
367126274Sdes			break;
368204917Sdes		case 'r':
369204917Sdes		case 'R':
370204917Sdes			*rflag = 1;
371204917Sdes			break;
372126274Sdes		default:
373181111Sdes			error("%s: Invalid flag -%c", cmd, optopt);
374181111Sdes			return -1;
375126274Sdes		}
376126274Sdes	}
377126274Sdes
378181111Sdes	return optind;
379126274Sdes}
380126274Sdes
381126274Sdesstatic int
382221420Sdesparse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
383221420Sdes{
384221420Sdes	extern int opterr, optind, optopt, optreset;
385221420Sdes	int ch;
386221420Sdes
387221420Sdes	optind = optreset = 1;
388221420Sdes	opterr = 0;
389221420Sdes
390221420Sdes	*sflag = 0;
391221420Sdes	while ((ch = getopt(argc, argv, "s")) != -1) {
392221420Sdes		switch (ch) {
393221420Sdes		case 's':
394221420Sdes			*sflag = 1;
395221420Sdes			break;
396221420Sdes		default:
397221420Sdes			error("%s: Invalid flag -%c", cmd, optopt);
398221420Sdes			return -1;
399221420Sdes		}
400221420Sdes	}
401221420Sdes
402221420Sdes	return optind;
403221420Sdes}
404221420Sdes
405221420Sdesstatic int
406181111Sdesparse_ls_flags(char **argv, int argc, int *lflag)
407126274Sdes{
408181111Sdes	extern int opterr, optind, optopt, optreset;
409181111Sdes	int ch;
410126274Sdes
411181111Sdes	optind = optreset = 1;
412181111Sdes	opterr = 0;
413181111Sdes
414137015Sdes	*lflag = LS_NAME_SORT;
415204917Sdes	while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
416181111Sdes		switch (ch) {
417181111Sdes		case '1':
418181111Sdes			*lflag &= ~VIEW_FLAGS;
419181111Sdes			*lflag |= LS_SHORT_VIEW;
420181111Sdes			break;
421181111Sdes		case 'S':
422181111Sdes			*lflag &= ~SORT_FLAGS;
423181111Sdes			*lflag |= LS_SIZE_SORT;
424181111Sdes			break;
425181111Sdes		case 'a':
426181111Sdes			*lflag |= LS_SHOW_ALL;
427181111Sdes			break;
428181111Sdes		case 'f':
429181111Sdes			*lflag &= ~SORT_FLAGS;
430181111Sdes			break;
431204917Sdes		case 'h':
432204917Sdes			*lflag |= LS_SI_UNITS;
433204917Sdes			break;
434181111Sdes		case 'l':
435204917Sdes			*lflag &= ~LS_SHORT_VIEW;
436181111Sdes			*lflag |= LS_LONG_VIEW;
437181111Sdes			break;
438181111Sdes		case 'n':
439204917Sdes			*lflag &= ~LS_SHORT_VIEW;
440181111Sdes			*lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
441181111Sdes			break;
442181111Sdes		case 'r':
443181111Sdes			*lflag |= LS_REVERSE_SORT;
444181111Sdes			break;
445181111Sdes		case 't':
446181111Sdes			*lflag &= ~SORT_FLAGS;
447181111Sdes			*lflag |= LS_TIME_SORT;
448181111Sdes			break;
449181111Sdes		default:
450181111Sdes			error("ls: Invalid flag -%c", optopt);
451181111Sdes			return -1;
452126274Sdes		}
453126274Sdes	}
454126274Sdes
455181111Sdes	return optind;
456126274Sdes}
457126274Sdes
458126274Sdesstatic int
459181111Sdesparse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
460126274Sdes{
461181111Sdes	extern int opterr, optind, optopt, optreset;
462181111Sdes	int ch;
463126274Sdes
464181111Sdes	optind = optreset = 1;
465181111Sdes	opterr = 0;
466126274Sdes
467181111Sdes	*hflag = *iflag = 0;
468181111Sdes	while ((ch = getopt(argc, argv, "hi")) != -1) {
469181111Sdes		switch (ch) {
470181111Sdes		case 'h':
471181111Sdes			*hflag = 1;
472181111Sdes			break;
473181111Sdes		case 'i':
474181111Sdes			*iflag = 1;
475181111Sdes			break;
476181111Sdes		default:
477181111Sdes			error("%s: Invalid flag -%c", cmd, optopt);
478181111Sdes			return -1;
479126274Sdes		}
480126274Sdes	}
481126274Sdes
482181111Sdes	return optind;
483126274Sdes}
484126274Sdes
485126274Sdesstatic int
486126274Sdesis_dir(char *path)
487126274Sdes{
488126274Sdes	struct stat sb;
489126274Sdes
490126274Sdes	/* XXX: report errors? */
491126274Sdes	if (stat(path, &sb) == -1)
492126274Sdes		return(0);
493126274Sdes
494162852Sdes	return(S_ISDIR(sb.st_mode));
495126274Sdes}
496126274Sdes
497126274Sdesstatic int
498126274Sdesremote_is_dir(struct sftp_conn *conn, char *path)
499126274Sdes{
500126274Sdes	Attrib *a;
501126274Sdes
502126274Sdes	/* XXX: report errors? */
503126274Sdes	if ((a = do_stat(conn, path, 1)) == NULL)
504126274Sdes		return(0);
505126274Sdes	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
506126274Sdes		return(0);
507162852Sdes	return(S_ISDIR(a->perm));
508126274Sdes}
509126274Sdes
510204917Sdes/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
511126274Sdesstatic int
512204917Sdespathname_is_dir(char *pathname)
513126274Sdes{
514204917Sdes	size_t l = strlen(pathname);
515204917Sdes
516204917Sdes	return l > 0 && pathname[l - 1] == '/';
517204917Sdes}
518204917Sdes
519204917Sdesstatic int
520204917Sdesprocess_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
521204917Sdes    int pflag, int rflag)
522204917Sdes{
523126274Sdes	char *abs_src = NULL;
524126274Sdes	char *abs_dst = NULL;
525126274Sdes	glob_t g;
526204917Sdes	char *filename, *tmp=NULL;
527204917Sdes	int i, err = 0;
528126274Sdes
529126274Sdes	abs_src = xstrdup(src);
530126274Sdes	abs_src = make_absolute(abs_src, pwd);
531204917Sdes	memset(&g, 0, sizeof(g));
532126274Sdes
533126274Sdes	debug3("Looking up %s", abs_src);
534204917Sdes	if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
535126274Sdes		error("File \"%s\" not found.", abs_src);
536126274Sdes		err = -1;
537126274Sdes		goto out;
538126274Sdes	}
539126274Sdes
540204917Sdes	/*
541204917Sdes	 * If multiple matches then dst must be a directory or
542204917Sdes	 * unspecified.
543204917Sdes	 */
544204917Sdes	if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
545204917Sdes		error("Multiple source paths, but destination "
546204917Sdes		    "\"%s\" is not a directory", dst);
547126274Sdes		err = -1;
548126274Sdes		goto out;
549126274Sdes	}
550126274Sdes
551137015Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
552204917Sdes		tmp = xstrdup(g.gl_pathv[i]);
553204917Sdes		if ((filename = basename(tmp)) == NULL) {
554204917Sdes			error("basename %s: %s", tmp, strerror(errno));
555204917Sdes			xfree(tmp);
556126274Sdes			err = -1;
557126274Sdes			goto out;
558126274Sdes		}
559126274Sdes
560126274Sdes		if (g.gl_matchc == 1 && dst) {
561126274Sdes			if (is_dir(dst)) {
562204917Sdes				abs_dst = path_append(dst, filename);
563204917Sdes			} else {
564126274Sdes				abs_dst = xstrdup(dst);
565204917Sdes			}
566126274Sdes		} else if (dst) {
567204917Sdes			abs_dst = path_append(dst, filename);
568204917Sdes		} else {
569204917Sdes			abs_dst = xstrdup(filename);
570204917Sdes		}
571204917Sdes		xfree(tmp);
572126274Sdes
573126274Sdes		printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
574204917Sdes		if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
575204917Sdes			if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
576204917Sdes			    pflag || global_pflag, 1) == -1)
577204917Sdes				err = -1;
578204917Sdes		} else {
579204917Sdes			if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
580204917Sdes			    pflag || global_pflag) == -1)
581204917Sdes				err = -1;
582204917Sdes		}
583126274Sdes		xfree(abs_dst);
584126274Sdes		abs_dst = NULL;
585126274Sdes	}
586126274Sdes
587126274Sdesout:
588126274Sdes	xfree(abs_src);
589126274Sdes	globfree(&g);
590126274Sdes	return(err);
591126274Sdes}
592126274Sdes
593126274Sdesstatic int
594204917Sdesprocess_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
595204917Sdes    int pflag, int rflag)
596126274Sdes{
597126274Sdes	char *tmp_dst = NULL;
598126274Sdes	char *abs_dst = NULL;
599204917Sdes	char *tmp = NULL, *filename = NULL;
600126274Sdes	glob_t g;
601126274Sdes	int err = 0;
602204917Sdes	int i, dst_is_dir = 1;
603181111Sdes	struct stat sb;
604126274Sdes
605126274Sdes	if (dst) {
606126274Sdes		tmp_dst = xstrdup(dst);
607126274Sdes		tmp_dst = make_absolute(tmp_dst, pwd);
608126274Sdes	}
609126274Sdes
610126274Sdes	memset(&g, 0, sizeof(g));
611126274Sdes	debug3("Looking up %s", src);
612204917Sdes	if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
613126274Sdes		error("File \"%s\" not found.", src);
614126274Sdes		err = -1;
615126274Sdes		goto out;
616126274Sdes	}
617126274Sdes
618204917Sdes	/* If we aren't fetching to pwd then stash this status for later */
619204917Sdes	if (tmp_dst != NULL)
620204917Sdes		dst_is_dir = remote_is_dir(conn, tmp_dst);
621204917Sdes
622126274Sdes	/* If multiple matches, dst may be directory or unspecified */
623204917Sdes	if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
624204917Sdes		error("Multiple paths match, but destination "
625204917Sdes		    "\"%s\" is not a directory", tmp_dst);
626126274Sdes		err = -1;
627126274Sdes		goto out;
628126274Sdes	}
629126274Sdes
630137015Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
631181111Sdes		if (stat(g.gl_pathv[i], &sb) == -1) {
632181111Sdes			err = -1;
633181111Sdes			error("stat %s: %s", g.gl_pathv[i], strerror(errno));
634181111Sdes			continue;
635181111Sdes		}
636204917Sdes
637204917Sdes		tmp = xstrdup(g.gl_pathv[i]);
638204917Sdes		if ((filename = basename(tmp)) == NULL) {
639204917Sdes			error("basename %s: %s", tmp, strerror(errno));
640204917Sdes			xfree(tmp);
641126274Sdes			err = -1;
642126274Sdes			goto out;
643126274Sdes		}
644126274Sdes
645126274Sdes		if (g.gl_matchc == 1 && tmp_dst) {
646126274Sdes			/* If directory specified, append filename */
647204917Sdes			if (dst_is_dir)
648204917Sdes				abs_dst = path_append(tmp_dst, filename);
649204917Sdes			else
650126274Sdes				abs_dst = xstrdup(tmp_dst);
651126274Sdes		} else if (tmp_dst) {
652204917Sdes			abs_dst = path_append(tmp_dst, filename);
653204917Sdes		} else {
654204917Sdes			abs_dst = make_absolute(xstrdup(filename), pwd);
655204917Sdes		}
656204917Sdes		xfree(tmp);
657126274Sdes
658126274Sdes		printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
659204917Sdes		if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
660204917Sdes			if (upload_dir(conn, g.gl_pathv[i], abs_dst,
661204917Sdes			    pflag || global_pflag, 1) == -1)
662204917Sdes				err = -1;
663204917Sdes		} else {
664204917Sdes			if (do_upload(conn, g.gl_pathv[i], abs_dst,
665204917Sdes			    pflag || global_pflag) == -1)
666204917Sdes				err = -1;
667204917Sdes		}
668126274Sdes	}
669126274Sdes
670126274Sdesout:
671126274Sdes	if (abs_dst)
672126274Sdes		xfree(abs_dst);
673126274Sdes	if (tmp_dst)
674126274Sdes		xfree(tmp_dst);
675126274Sdes	globfree(&g);
676126274Sdes	return(err);
677126274Sdes}
678126274Sdes
679126274Sdesstatic int
680126274Sdessdirent_comp(const void *aa, const void *bb)
681126274Sdes{
682126274Sdes	SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
683126274Sdes	SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
684137015Sdes	int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
685126274Sdes
686137015Sdes#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
687137015Sdes	if (sort_flag & LS_NAME_SORT)
688137015Sdes		return (rmul * strcmp(a->filename, b->filename));
689137015Sdes	else if (sort_flag & LS_TIME_SORT)
690137015Sdes		return (rmul * NCMP(a->a.mtime, b->a.mtime));
691137015Sdes	else if (sort_flag & LS_SIZE_SORT)
692137015Sdes		return (rmul * NCMP(a->a.size, b->a.size));
693137015Sdes
694137015Sdes	fatal("Unknown ls sort type");
695126274Sdes}
696126274Sdes
697126274Sdes/* sftp ls.1 replacement for directories */
698126274Sdesstatic int
699126274Sdesdo_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
700126274Sdes{
701149749Sdes	int n;
702149749Sdes	u_int c = 1, colspace = 0, columns = 1;
703126274Sdes	SFTP_DIRENT **d;
704126274Sdes
705126274Sdes	if ((n = do_readdir(conn, path, &d)) != 0)
706126274Sdes		return (n);
707126274Sdes
708137015Sdes	if (!(lflag & LS_SHORT_VIEW)) {
709149749Sdes		u_int m = 0, width = 80;
710126274Sdes		struct winsize ws;
711126274Sdes		char *tmp;
712126274Sdes
713126274Sdes		/* Count entries for sort and find longest filename */
714137015Sdes		for (n = 0; d[n] != NULL; n++) {
715137015Sdes			if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
716137015Sdes				m = MAX(m, strlen(d[n]->filename));
717137015Sdes		}
718126274Sdes
719126274Sdes		/* Add any subpath that also needs to be counted */
720126274Sdes		tmp = path_strip(path, strip_path);
721126274Sdes		m += strlen(tmp);
722126274Sdes		xfree(tmp);
723126274Sdes
724126274Sdes		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
725126274Sdes			width = ws.ws_col;
726126274Sdes
727126274Sdes		columns = width / (m + 2);
728126274Sdes		columns = MAX(columns, 1);
729126274Sdes		colspace = width / columns;
730126274Sdes		colspace = MIN(colspace, width);
731126274Sdes	}
732126274Sdes
733137015Sdes	if (lflag & SORT_FLAGS) {
734157016Sdes		for (n = 0; d[n] != NULL; n++)
735157016Sdes			;	/* count entries */
736137015Sdes		sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
737137015Sdes		qsort(d, n, sizeof(*d), sdirent_comp);
738137015Sdes	}
739126274Sdes
740137015Sdes	for (n = 0; d[n] != NULL && !interrupted; n++) {
741126274Sdes		char *tmp, *fname;
742126274Sdes
743137015Sdes		if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
744137015Sdes			continue;
745137015Sdes
746126274Sdes		tmp = path_append(path, d[n]->filename);
747126274Sdes		fname = path_strip(tmp, strip_path);
748126274Sdes		xfree(tmp);
749126274Sdes
750137015Sdes		if (lflag & LS_LONG_VIEW) {
751204917Sdes			if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
752137015Sdes				char *lname;
753137015Sdes				struct stat sb;
754126274Sdes
755137015Sdes				memset(&sb, 0, sizeof(sb));
756137015Sdes				attrib_to_stat(&d[n]->a, &sb);
757204917Sdes				lname = ls_file(fname, &sb, 1,
758204917Sdes				    (lflag & LS_SI_UNITS));
759137015Sdes				printf("%s\n", lname);
760137015Sdes				xfree(lname);
761137015Sdes			} else
762137015Sdes				printf("%s\n", d[n]->longname);
763126274Sdes		} else {
764126274Sdes			printf("%-*s", colspace, fname);
765126274Sdes			if (c >= columns) {
766126274Sdes				printf("\n");
767126274Sdes				c = 1;
768126274Sdes			} else
769126274Sdes				c++;
770126274Sdes		}
771126274Sdes
772126274Sdes		xfree(fname);
773126274Sdes	}
774126274Sdes
775137015Sdes	if (!(lflag & LS_LONG_VIEW) && (c != 1))
776126274Sdes		printf("\n");
777126274Sdes
778126274Sdes	free_sftp_dirents(d);
779126274Sdes	return (0);
780126274Sdes}
781126274Sdes
782126274Sdes/* sftp ls.1 replacement which handles path globs */
783126274Sdesstatic int
784126274Sdesdo_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
785126274Sdes    int lflag)
786126274Sdes{
787221420Sdes	char *fname, *lname;
788126274Sdes	glob_t g;
789221420Sdes	int err;
790221420Sdes	struct winsize ws;
791221420Sdes	u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
792126274Sdes
793126274Sdes	memset(&g, 0, sizeof(g));
794126274Sdes
795221420Sdes	if (remote_glob(conn, path,
796240075Sdes	    GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
797240075Sdes	    NULL, &g) ||
798221420Sdes	    (g.gl_pathc && !g.gl_matchc)) {
799146998Sdes		if (g.gl_pathc)
800146998Sdes			globfree(&g);
801126274Sdes		error("Can't ls: \"%s\" not found", path);
802221420Sdes		return -1;
803126274Sdes	}
804126274Sdes
805137015Sdes	if (interrupted)
806137015Sdes		goto out;
807137015Sdes
808126274Sdes	/*
809146998Sdes	 * If the glob returns a single match and it is a directory,
810146998Sdes	 * then just list its contents.
811126274Sdes	 */
812221420Sdes	if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
813221420Sdes	    S_ISDIR(g.gl_statv[0]->st_mode)) {
814221420Sdes		err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
815221420Sdes		globfree(&g);
816221420Sdes		return err;
817126274Sdes	}
818126274Sdes
819221420Sdes	if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
820221420Sdes		width = ws.ws_col;
821221420Sdes
822137015Sdes	if (!(lflag & LS_SHORT_VIEW)) {
823126274Sdes		/* Count entries for sort and find longest filename */
824126274Sdes		for (i = 0; g.gl_pathv[i]; i++)
825126274Sdes			m = MAX(m, strlen(g.gl_pathv[i]));
826126274Sdes
827126274Sdes		columns = width / (m + 2);
828126274Sdes		columns = MAX(columns, 1);
829126274Sdes		colspace = width / columns;
830126274Sdes	}
831126274Sdes
832240075Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
833126274Sdes		fname = path_strip(g.gl_pathv[i], strip_path);
834137015Sdes		if (lflag & LS_LONG_VIEW) {
835221420Sdes			if (g.gl_statv[i] == NULL) {
836221420Sdes				error("no stat information for %s", fname);
837221420Sdes				continue;
838221420Sdes			}
839221420Sdes			lname = ls_file(fname, g.gl_statv[i], 1,
840221420Sdes			    (lflag & LS_SI_UNITS));
841126274Sdes			printf("%s\n", lname);
842126274Sdes			xfree(lname);
843126274Sdes		} else {
844126274Sdes			printf("%-*s", colspace, fname);
845126274Sdes			if (c >= columns) {
846126274Sdes				printf("\n");
847126274Sdes				c = 1;
848126274Sdes			} else
849126274Sdes				c++;
850126274Sdes		}
851126274Sdes		xfree(fname);
852126274Sdes	}
853126274Sdes
854137015Sdes	if (!(lflag & LS_LONG_VIEW) && (c != 1))
855126274Sdes		printf("\n");
856126274Sdes
857137015Sdes out:
858126274Sdes	if (g.gl_pathc)
859126274Sdes		globfree(&g);
860126274Sdes
861221420Sdes	return 0;
862126274Sdes}
863126274Sdes
864126274Sdesstatic int
865181111Sdesdo_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
866181111Sdes{
867181111Sdes	struct sftp_statvfs st;
868181111Sdes	char s_used[FMT_SCALED_STRSIZE];
869181111Sdes	char s_avail[FMT_SCALED_STRSIZE];
870181111Sdes	char s_root[FMT_SCALED_STRSIZE];
871181111Sdes	char s_total[FMT_SCALED_STRSIZE];
872204917Sdes	unsigned long long ffree;
873181111Sdes
874181111Sdes	if (do_statvfs(conn, path, &st, 1) == -1)
875181111Sdes		return -1;
876181111Sdes	if (iflag) {
877204917Sdes		ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
878181111Sdes		printf("     Inodes        Used       Avail      "
879181111Sdes		    "(root)    %%Capacity\n");
880181111Sdes		printf("%11llu %11llu %11llu %11llu         %3llu%%\n",
881181111Sdes		    (unsigned long long)st.f_files,
882181111Sdes		    (unsigned long long)(st.f_files - st.f_ffree),
883181111Sdes		    (unsigned long long)st.f_favail,
884204917Sdes		    (unsigned long long)st.f_ffree, ffree);
885181111Sdes	} else if (hflag) {
886181111Sdes		strlcpy(s_used, "error", sizeof(s_used));
887181111Sdes		strlcpy(s_avail, "error", sizeof(s_avail));
888181111Sdes		strlcpy(s_root, "error", sizeof(s_root));
889181111Sdes		strlcpy(s_total, "error", sizeof(s_total));
890181111Sdes		fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
891181111Sdes		fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
892181111Sdes		fmt_scaled(st.f_bfree * st.f_frsize, s_root);
893181111Sdes		fmt_scaled(st.f_blocks * st.f_frsize, s_total);
894181111Sdes		printf("    Size     Used    Avail   (root)    %%Capacity\n");
895181111Sdes		printf("%7sB %7sB %7sB %7sB         %3llu%%\n",
896181111Sdes		    s_total, s_used, s_avail, s_root,
897181111Sdes		    (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
898181111Sdes		    st.f_blocks));
899181111Sdes	} else {
900181111Sdes		printf("        Size         Used        Avail       "
901181111Sdes		    "(root)    %%Capacity\n");
902181111Sdes		printf("%12llu %12llu %12llu %12llu         %3llu%%\n",
903181111Sdes		    (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
904181111Sdes		    (unsigned long long)(st.f_frsize *
905181111Sdes		    (st.f_blocks - st.f_bfree) / 1024),
906181111Sdes		    (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
907181111Sdes		    (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
908181111Sdes		    (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
909181111Sdes		    st.f_blocks));
910181111Sdes	}
911181111Sdes	return 0;
912181111Sdes}
913181111Sdes
914181111Sdes/*
915181111Sdes * Undo escaping of glob sequences in place. Used to undo extra escaping
916181111Sdes * applied in makeargv() when the string is destined for a function that
917181111Sdes * does not glob it.
918181111Sdes */
919181111Sdesstatic void
920181111Sdesundo_glob_escape(char *s)
921181111Sdes{
922181111Sdes	size_t i, j;
923181111Sdes
924181111Sdes	for (i = j = 0;;) {
925181111Sdes		if (s[i] == '\0') {
926181111Sdes			s[j] = '\0';
927181111Sdes			return;
928181111Sdes		}
929181111Sdes		if (s[i] != '\\') {
930181111Sdes			s[j++] = s[i++];
931181111Sdes			continue;
932181111Sdes		}
933181111Sdes		/* s[i] == '\\' */
934181111Sdes		++i;
935181111Sdes		switch (s[i]) {
936181111Sdes		case '?':
937181111Sdes		case '[':
938181111Sdes		case '*':
939181111Sdes		case '\\':
940181111Sdes			s[j++] = s[i++];
941181111Sdes			break;
942181111Sdes		case '\0':
943181111Sdes			s[j++] = '\\';
944181111Sdes			s[j] = '\0';
945181111Sdes			return;
946181111Sdes		default:
947181111Sdes			s[j++] = '\\';
948181111Sdes			s[j++] = s[i++];
949181111Sdes			break;
950181111Sdes		}
951181111Sdes	}
952181111Sdes}
953181111Sdes
954181111Sdes/*
955181111Sdes * Split a string into an argument vector using sh(1)-style quoting,
956181111Sdes * comment and escaping rules, but with some tweaks to handle glob(3)
957181111Sdes * wildcards.
958204917Sdes * The "sloppy" flag allows for recovery from missing terminating quote, for
959204917Sdes * use in parsing incomplete commandlines during tab autocompletion.
960204917Sdes *
961181111Sdes * Returns NULL on error or a NULL-terminated array of arguments.
962204917Sdes *
963204917Sdes * If "lastquote" is not NULL, the quoting character used for the last
964204917Sdes * argument is placed in *lastquote ("\0", "'" or "\"").
965204917Sdes *
966204917Sdes * If "terminated" is not NULL, *terminated will be set to 1 when the
967204917Sdes * last argument's quote has been properly terminated or 0 otherwise.
968204917Sdes * This parameter is only of use if "sloppy" is set.
969181111Sdes */
970181111Sdes#define MAXARGS 	128
971181111Sdes#define MAXARGLEN	8192
972181111Sdesstatic char **
973204917Sdesmakeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
974204917Sdes    u_int *terminated)
975181111Sdes{
976181111Sdes	int argc, quot;
977181111Sdes	size_t i, j;
978181111Sdes	static char argvs[MAXARGLEN];
979181111Sdes	static char *argv[MAXARGS + 1];
980181111Sdes	enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
981181111Sdes
982181111Sdes	*argcp = argc = 0;
983181111Sdes	if (strlen(arg) > sizeof(argvs) - 1) {
984181111Sdes args_too_longs:
985181111Sdes		error("string too long");
986181111Sdes		return NULL;
987181111Sdes	}
988204917Sdes	if (terminated != NULL)
989204917Sdes		*terminated = 1;
990204917Sdes	if (lastquote != NULL)
991204917Sdes		*lastquote = '\0';
992181111Sdes	state = MA_START;
993181111Sdes	i = j = 0;
994181111Sdes	for (;;) {
995181111Sdes		if (isspace(arg[i])) {
996181111Sdes			if (state == MA_UNQUOTED) {
997181111Sdes				/* Terminate current argument */
998181111Sdes				argvs[j++] = '\0';
999181111Sdes				argc++;
1000181111Sdes				state = MA_START;
1001181111Sdes			} else if (state != MA_START)
1002181111Sdes				argvs[j++] = arg[i];
1003181111Sdes		} else if (arg[i] == '"' || arg[i] == '\'') {
1004181111Sdes			q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1005181111Sdes			if (state == MA_START) {
1006181111Sdes				argv[argc] = argvs + j;
1007181111Sdes				state = q;
1008204917Sdes				if (lastquote != NULL)
1009204917Sdes					*lastquote = arg[i];
1010181111Sdes			} else if (state == MA_UNQUOTED)
1011181111Sdes				state = q;
1012181111Sdes			else if (state == q)
1013181111Sdes				state = MA_UNQUOTED;
1014181111Sdes			else
1015181111Sdes				argvs[j++] = arg[i];
1016181111Sdes		} else if (arg[i] == '\\') {
1017181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE) {
1018181111Sdes				quot = state == MA_SQUOTE ? '\'' : '"';
1019181111Sdes				/* Unescape quote we are in */
1020181111Sdes				/* XXX support \n and friends? */
1021181111Sdes				if (arg[i + 1] == quot) {
1022181111Sdes					i++;
1023181111Sdes					argvs[j++] = arg[i];
1024181111Sdes				} else if (arg[i + 1] == '?' ||
1025181111Sdes				    arg[i + 1] == '[' || arg[i + 1] == '*') {
1026181111Sdes					/*
1027181111Sdes					 * Special case for sftp: append
1028181111Sdes					 * double-escaped glob sequence -
1029181111Sdes					 * glob will undo one level of
1030181111Sdes					 * escaping. NB. string can grow here.
1031181111Sdes					 */
1032181111Sdes					if (j >= sizeof(argvs) - 5)
1033181111Sdes						goto args_too_longs;
1034181111Sdes					argvs[j++] = '\\';
1035181111Sdes					argvs[j++] = arg[i++];
1036181111Sdes					argvs[j++] = '\\';
1037181111Sdes					argvs[j++] = arg[i];
1038181111Sdes				} else {
1039181111Sdes					argvs[j++] = arg[i++];
1040181111Sdes					argvs[j++] = arg[i];
1041181111Sdes				}
1042181111Sdes			} else {
1043181111Sdes				if (state == MA_START) {
1044181111Sdes					argv[argc] = argvs + j;
1045181111Sdes					state = MA_UNQUOTED;
1046204917Sdes					if (lastquote != NULL)
1047204917Sdes						*lastquote = '\0';
1048181111Sdes				}
1049181111Sdes				if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1050181111Sdes				    arg[i + 1] == '*' || arg[i + 1] == '\\') {
1051181111Sdes					/*
1052181111Sdes					 * Special case for sftp: append
1053181111Sdes					 * escaped glob sequence -
1054181111Sdes					 * glob will undo one level of
1055181111Sdes					 * escaping.
1056181111Sdes					 */
1057181111Sdes					argvs[j++] = arg[i++];
1058181111Sdes					argvs[j++] = arg[i];
1059181111Sdes				} else {
1060181111Sdes					/* Unescape everything */
1061181111Sdes					/* XXX support \n and friends? */
1062181111Sdes					i++;
1063181111Sdes					argvs[j++] = arg[i];
1064181111Sdes				}
1065181111Sdes			}
1066181111Sdes		} else if (arg[i] == '#') {
1067181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE)
1068181111Sdes				argvs[j++] = arg[i];
1069181111Sdes			else
1070181111Sdes				goto string_done;
1071181111Sdes		} else if (arg[i] == '\0') {
1072181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE) {
1073204917Sdes				if (sloppy) {
1074204917Sdes					state = MA_UNQUOTED;
1075204917Sdes					if (terminated != NULL)
1076204917Sdes						*terminated = 0;
1077204917Sdes					goto string_done;
1078204917Sdes				}
1079181111Sdes				error("Unterminated quoted argument");
1080181111Sdes				return NULL;
1081181111Sdes			}
1082181111Sdes string_done:
1083181111Sdes			if (state == MA_UNQUOTED) {
1084181111Sdes				argvs[j++] = '\0';
1085181111Sdes				argc++;
1086181111Sdes			}
1087181111Sdes			break;
1088181111Sdes		} else {
1089181111Sdes			if (state == MA_START) {
1090181111Sdes				argv[argc] = argvs + j;
1091181111Sdes				state = MA_UNQUOTED;
1092204917Sdes				if (lastquote != NULL)
1093204917Sdes					*lastquote = '\0';
1094181111Sdes			}
1095181111Sdes			if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1096181111Sdes			    (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1097181111Sdes				/*
1098181111Sdes				 * Special case for sftp: escape quoted
1099181111Sdes				 * glob(3) wildcards. NB. string can grow
1100181111Sdes				 * here.
1101181111Sdes				 */
1102181111Sdes				if (j >= sizeof(argvs) - 3)
1103181111Sdes					goto args_too_longs;
1104181111Sdes				argvs[j++] = '\\';
1105181111Sdes				argvs[j++] = arg[i];
1106181111Sdes			} else
1107181111Sdes				argvs[j++] = arg[i];
1108181111Sdes		}
1109181111Sdes		i++;
1110181111Sdes	}
1111181111Sdes	*argcp = argc;
1112181111Sdes	return argv;
1113181111Sdes}
1114181111Sdes
1115181111Sdesstatic int
1116204917Sdesparse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
1117221420Sdes    int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2)
1118126274Sdes{
1119126274Sdes	const char *cmd, *cp = *cpp;
1120181111Sdes	char *cp2, **argv;
1121126274Sdes	int base = 0;
1122126274Sdes	long l;
1123181111Sdes	int i, cmdnum, optidx, argc;
1124126274Sdes
1125126274Sdes	/* Skip leading whitespace */
1126126274Sdes	cp = cp + strspn(cp, WHITESPACE);
1127126274Sdes
1128126274Sdes	/* Check for leading '-' (disable error processing) */
1129126274Sdes	*iflag = 0;
1130126274Sdes	if (*cp == '-') {
1131126274Sdes		*iflag = 1;
1132126274Sdes		cp++;
1133204917Sdes		cp = cp + strspn(cp, WHITESPACE);
1134126274Sdes	}
1135126274Sdes
1136204917Sdes	/* Ignore blank lines and lines which begin with comment '#' char */
1137204917Sdes	if (*cp == '\0' || *cp == '#')
1138204917Sdes		return (0);
1139204917Sdes
1140204917Sdes	if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
1141181111Sdes		return -1;
1142181111Sdes
1143126274Sdes	/* Figure out which command we have */
1144181111Sdes	for (i = 0; cmds[i].c != NULL; i++) {
1145181111Sdes		if (strcasecmp(cmds[i].c, argv[0]) == 0)
1146126274Sdes			break;
1147126274Sdes	}
1148126274Sdes	cmdnum = cmds[i].n;
1149126274Sdes	cmd = cmds[i].c;
1150126274Sdes
1151126274Sdes	/* Special case */
1152126274Sdes	if (*cp == '!') {
1153126274Sdes		cp++;
1154126274Sdes		cmdnum = I_SHELL;
1155126274Sdes	} else if (cmdnum == -1) {
1156126274Sdes		error("Invalid command.");
1157181111Sdes		return -1;
1158126274Sdes	}
1159126274Sdes
1160126274Sdes	/* Get arguments and parse flags */
1161204917Sdes	*lflag = *pflag = *rflag = *hflag = *n_arg = 0;
1162126274Sdes	*path1 = *path2 = NULL;
1163181111Sdes	optidx = 1;
1164126274Sdes	switch (cmdnum) {
1165126274Sdes	case I_GET:
1166126274Sdes	case I_PUT:
1167221420Sdes		if ((optidx = parse_getput_flags(cmd, argv, argc,
1168221420Sdes		    pflag, rflag)) == -1)
1169181111Sdes			return -1;
1170126274Sdes		/* Get first pathname (mandatory) */
1171181111Sdes		if (argc - optidx < 1) {
1172126274Sdes			error("You must specify at least one path after a "
1173126274Sdes			    "%s command.", cmd);
1174181111Sdes			return -1;
1175126274Sdes		}
1176181111Sdes		*path1 = xstrdup(argv[optidx]);
1177181111Sdes		/* Get second pathname (optional) */
1178181111Sdes		if (argc - optidx > 1) {
1179181111Sdes			*path2 = xstrdup(argv[optidx + 1]);
1180181111Sdes			/* Destination is not globbed */
1181181111Sdes			undo_glob_escape(*path2);
1182181111Sdes		}
1183126274Sdes		break;
1184221420Sdes	case I_LINK:
1185221420Sdes		if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1186221420Sdes			return -1;
1187221420Sdes	case I_SYMLINK:
1188126274Sdes	case I_RENAME:
1189181111Sdes		if (argc - optidx < 2) {
1190126274Sdes			error("You must specify two paths after a %s "
1191126274Sdes			    "command.", cmd);
1192181111Sdes			return -1;
1193126274Sdes		}
1194181111Sdes		*path1 = xstrdup(argv[optidx]);
1195181111Sdes		*path2 = xstrdup(argv[optidx + 1]);
1196181111Sdes		/* Paths are not globbed */
1197181111Sdes		undo_glob_escape(*path1);
1198181111Sdes		undo_glob_escape(*path2);
1199126274Sdes		break;
1200126274Sdes	case I_RM:
1201126274Sdes	case I_MKDIR:
1202126274Sdes	case I_RMDIR:
1203126274Sdes	case I_CHDIR:
1204126274Sdes	case I_LCHDIR:
1205126274Sdes	case I_LMKDIR:
1206126274Sdes		/* Get pathname (mandatory) */
1207181111Sdes		if (argc - optidx < 1) {
1208126274Sdes			error("You must specify a path after a %s command.",
1209126274Sdes			    cmd);
1210181111Sdes			return -1;
1211126274Sdes		}
1212181111Sdes		*path1 = xstrdup(argv[optidx]);
1213181111Sdes		/* Only "rm" globs */
1214181111Sdes		if (cmdnum != I_RM)
1215181111Sdes			undo_glob_escape(*path1);
1216126274Sdes		break;
1217181111Sdes	case I_DF:
1218181111Sdes		if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1219181111Sdes		    iflag)) == -1)
1220181111Sdes			return -1;
1221181111Sdes		/* Default to current directory if no path specified */
1222181111Sdes		if (argc - optidx < 1)
1223181111Sdes			*path1 = NULL;
1224181111Sdes		else {
1225181111Sdes			*path1 = xstrdup(argv[optidx]);
1226181111Sdes			undo_glob_escape(*path1);
1227181111Sdes		}
1228181111Sdes		break;
1229126274Sdes	case I_LS:
1230181111Sdes		if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
1231126274Sdes			return(-1);
1232126274Sdes		/* Path is optional */
1233181111Sdes		if (argc - optidx > 0)
1234181111Sdes			*path1 = xstrdup(argv[optidx]);
1235126274Sdes		break;
1236126274Sdes	case I_LLS:
1237181111Sdes		/* Skip ls command and following whitespace */
1238181111Sdes		cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
1239126274Sdes	case I_SHELL:
1240126274Sdes		/* Uses the rest of the line */
1241126274Sdes		break;
1242126274Sdes	case I_LUMASK:
1243126274Sdes	case I_CHMOD:
1244126274Sdes		base = 8;
1245126274Sdes	case I_CHOWN:
1246126274Sdes	case I_CHGRP:
1247126274Sdes		/* Get numeric arg (mandatory) */
1248181111Sdes		if (argc - optidx < 1)
1249181111Sdes			goto need_num_arg;
1250164146Sdes		errno = 0;
1251181111Sdes		l = strtol(argv[optidx], &cp2, base);
1252181111Sdes		if (cp2 == argv[optidx] || *cp2 != '\0' ||
1253181111Sdes		    ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1254181111Sdes		    l < 0) {
1255181111Sdes need_num_arg:
1256126274Sdes			error("You must supply a numeric argument "
1257126274Sdes			    "to the %s command.", cmd);
1258181111Sdes			return -1;
1259126274Sdes		}
1260126274Sdes		*n_arg = l;
1261181111Sdes		if (cmdnum == I_LUMASK)
1262126274Sdes			break;
1263126274Sdes		/* Get pathname (mandatory) */
1264181111Sdes		if (argc - optidx < 2) {
1265126274Sdes			error("You must specify a path after a %s command.",
1266126274Sdes			    cmd);
1267181111Sdes			return -1;
1268126274Sdes		}
1269181111Sdes		*path1 = xstrdup(argv[optidx + 1]);
1270126274Sdes		break;
1271126274Sdes	case I_QUIT:
1272126274Sdes	case I_PWD:
1273126274Sdes	case I_LPWD:
1274126274Sdes	case I_HELP:
1275126274Sdes	case I_VERSION:
1276126274Sdes	case I_PROGRESS:
1277126274Sdes		break;
1278126274Sdes	default:
1279126274Sdes		fatal("Command not implemented");
1280126274Sdes	}
1281126274Sdes
1282126274Sdes	*cpp = cp;
1283126274Sdes	return(cmdnum);
1284126274Sdes}
1285126274Sdes
1286126274Sdesstatic int
1287126274Sdesparse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1288126274Sdes    int err_abort)
1289126274Sdes{
1290126274Sdes	char *path1, *path2, *tmp;
1291221420Sdes	int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0;
1292221420Sdes	int cmdnum, i;
1293192595Sdes	unsigned long n_arg = 0;
1294126274Sdes	Attrib a, *aa;
1295126274Sdes	char path_buf[MAXPATHLEN];
1296126274Sdes	int err = 0;
1297126274Sdes	glob_t g;
1298126274Sdes
1299126274Sdes	path1 = path2 = NULL;
1300221420Sdes	cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag,
1301221420Sdes	    &sflag, &n_arg, &path1, &path2);
1302126274Sdes
1303126274Sdes	if (iflag != 0)
1304126274Sdes		err_abort = 0;
1305126274Sdes
1306126274Sdes	memset(&g, 0, sizeof(g));
1307126274Sdes
1308126274Sdes	/* Perform command */
1309126274Sdes	switch (cmdnum) {
1310126274Sdes	case 0:
1311126274Sdes		/* Blank line */
1312126274Sdes		break;
1313126274Sdes	case -1:
1314126274Sdes		/* Unrecognized command */
1315126274Sdes		err = -1;
1316126274Sdes		break;
1317126274Sdes	case I_GET:
1318204917Sdes		err = process_get(conn, path1, path2, *pwd, pflag, rflag);
1319126274Sdes		break;
1320126274Sdes	case I_PUT:
1321204917Sdes		err = process_put(conn, path1, path2, *pwd, pflag, rflag);
1322126274Sdes		break;
1323126274Sdes	case I_RENAME:
1324126274Sdes		path1 = make_absolute(path1, *pwd);
1325126274Sdes		path2 = make_absolute(path2, *pwd);
1326126274Sdes		err = do_rename(conn, path1, path2);
1327126274Sdes		break;
1328126274Sdes	case I_SYMLINK:
1329221420Sdes		sflag = 1;
1330221420Sdes	case I_LINK:
1331221420Sdes		path1 = make_absolute(path1, *pwd);
1332126274Sdes		path2 = make_absolute(path2, *pwd);
1333221420Sdes		err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
1334126274Sdes		break;
1335126274Sdes	case I_RM:
1336126274Sdes		path1 = make_absolute(path1, *pwd);
1337126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1338137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1339126274Sdes			printf("Removing %s\n", g.gl_pathv[i]);
1340126274Sdes			err = do_rm(conn, g.gl_pathv[i]);
1341126274Sdes			if (err != 0 && err_abort)
1342126274Sdes				break;
1343126274Sdes		}
1344126274Sdes		break;
1345126274Sdes	case I_MKDIR:
1346126274Sdes		path1 = make_absolute(path1, *pwd);
1347126274Sdes		attrib_clear(&a);
1348126274Sdes		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1349126274Sdes		a.perm = 0777;
1350204917Sdes		err = do_mkdir(conn, path1, &a, 1);
1351126274Sdes		break;
1352126274Sdes	case I_RMDIR:
1353126274Sdes		path1 = make_absolute(path1, *pwd);
1354126274Sdes		err = do_rmdir(conn, path1);
1355126274Sdes		break;
1356126274Sdes	case I_CHDIR:
1357126274Sdes		path1 = make_absolute(path1, *pwd);
1358126274Sdes		if ((tmp = do_realpath(conn, path1)) == NULL) {
1359126274Sdes			err = 1;
1360126274Sdes			break;
1361126274Sdes		}
1362126274Sdes		if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1363126274Sdes			xfree(tmp);
1364126274Sdes			err = 1;
1365126274Sdes			break;
1366126274Sdes		}
1367126274Sdes		if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1368126274Sdes			error("Can't change directory: Can't check target");
1369126274Sdes			xfree(tmp);
1370126274Sdes			err = 1;
1371126274Sdes			break;
1372126274Sdes		}
1373126274Sdes		if (!S_ISDIR(aa->perm)) {
1374126274Sdes			error("Can't change directory: \"%s\" is not "
1375126274Sdes			    "a directory", tmp);
1376126274Sdes			xfree(tmp);
1377126274Sdes			err = 1;
1378126274Sdes			break;
1379126274Sdes		}
1380126274Sdes		xfree(*pwd);
1381126274Sdes		*pwd = tmp;
1382126274Sdes		break;
1383126274Sdes	case I_LS:
1384126274Sdes		if (!path1) {
1385215116Sdes			do_ls_dir(conn, *pwd, *pwd, lflag);
1386126274Sdes			break;
1387126274Sdes		}
1388126274Sdes
1389126274Sdes		/* Strip pwd off beginning of non-absolute paths */
1390126274Sdes		tmp = NULL;
1391126274Sdes		if (*path1 != '/')
1392126274Sdes			tmp = *pwd;
1393126274Sdes
1394126274Sdes		path1 = make_absolute(path1, *pwd);
1395126274Sdes		err = do_globbed_ls(conn, path1, tmp, lflag);
1396126274Sdes		break;
1397181111Sdes	case I_DF:
1398181111Sdes		/* Default to current directory if no path specified */
1399181111Sdes		if (path1 == NULL)
1400181111Sdes			path1 = xstrdup(*pwd);
1401181111Sdes		path1 = make_absolute(path1, *pwd);
1402181111Sdes		err = do_df(conn, path1, hflag, iflag);
1403181111Sdes		break;
1404126274Sdes	case I_LCHDIR:
1405126274Sdes		if (chdir(path1) == -1) {
1406126274Sdes			error("Couldn't change local directory to "
1407126274Sdes			    "\"%s\": %s", path1, strerror(errno));
1408126274Sdes			err = 1;
1409126274Sdes		}
1410126274Sdes		break;
1411126274Sdes	case I_LMKDIR:
1412126274Sdes		if (mkdir(path1, 0777) == -1) {
1413126274Sdes			error("Couldn't create local directory "
1414126274Sdes			    "\"%s\": %s", path1, strerror(errno));
1415126274Sdes			err = 1;
1416126274Sdes		}
1417126274Sdes		break;
1418126274Sdes	case I_LLS:
1419126274Sdes		local_do_ls(cmd);
1420126274Sdes		break;
1421126274Sdes	case I_SHELL:
1422126274Sdes		local_do_shell(cmd);
1423126274Sdes		break;
1424126274Sdes	case I_LUMASK:
1425126274Sdes		umask(n_arg);
1426126274Sdes		printf("Local umask: %03lo\n", n_arg);
1427126274Sdes		break;
1428126274Sdes	case I_CHMOD:
1429126274Sdes		path1 = make_absolute(path1, *pwd);
1430126274Sdes		attrib_clear(&a);
1431126274Sdes		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1432126274Sdes		a.perm = n_arg;
1433126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1434137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1435126274Sdes			printf("Changing mode on %s\n", g.gl_pathv[i]);
1436126274Sdes			err = do_setstat(conn, g.gl_pathv[i], &a);
1437126274Sdes			if (err != 0 && err_abort)
1438126274Sdes				break;
1439126274Sdes		}
1440126274Sdes		break;
1441126274Sdes	case I_CHOWN:
1442126274Sdes	case I_CHGRP:
1443126274Sdes		path1 = make_absolute(path1, *pwd);
1444126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1445137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1446126274Sdes			if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
1447192595Sdes				if (err_abort) {
1448192595Sdes					err = -1;
1449126274Sdes					break;
1450192595Sdes				} else
1451126274Sdes					continue;
1452126274Sdes			}
1453126274Sdes			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1454126274Sdes				error("Can't get current ownership of "
1455126274Sdes				    "remote file \"%s\"", g.gl_pathv[i]);
1456192595Sdes				if (err_abort) {
1457192595Sdes					err = -1;
1458126274Sdes					break;
1459192595Sdes				} else
1460126274Sdes					continue;
1461126274Sdes			}
1462126274Sdes			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1463126274Sdes			if (cmdnum == I_CHOWN) {
1464126274Sdes				printf("Changing owner on %s\n", g.gl_pathv[i]);
1465126274Sdes				aa->uid = n_arg;
1466126274Sdes			} else {
1467126274Sdes				printf("Changing group on %s\n", g.gl_pathv[i]);
1468126274Sdes				aa->gid = n_arg;
1469126274Sdes			}
1470126274Sdes			err = do_setstat(conn, g.gl_pathv[i], aa);
1471126274Sdes			if (err != 0 && err_abort)
1472126274Sdes				break;
1473126274Sdes		}
1474126274Sdes		break;
1475126274Sdes	case I_PWD:
1476126274Sdes		printf("Remote working directory: %s\n", *pwd);
1477126274Sdes		break;
1478126274Sdes	case I_LPWD:
1479126274Sdes		if (!getcwd(path_buf, sizeof(path_buf))) {
1480126274Sdes			error("Couldn't get local cwd: %s", strerror(errno));
1481126274Sdes			err = -1;
1482126274Sdes			break;
1483126274Sdes		}
1484126274Sdes		printf("Local working directory: %s\n", path_buf);
1485126274Sdes		break;
1486126274Sdes	case I_QUIT:
1487126274Sdes		/* Processed below */
1488126274Sdes		break;
1489126274Sdes	case I_HELP:
1490126274Sdes		help();
1491126274Sdes		break;
1492126274Sdes	case I_VERSION:
1493126274Sdes		printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1494126274Sdes		break;
1495126274Sdes	case I_PROGRESS:
1496126274Sdes		showprogress = !showprogress;
1497126274Sdes		if (showprogress)
1498126274Sdes			printf("Progress meter enabled\n");
1499126274Sdes		else
1500126274Sdes			printf("Progress meter disabled\n");
1501126274Sdes		break;
1502126274Sdes	default:
1503126274Sdes		fatal("%d is not implemented", cmdnum);
1504126274Sdes	}
1505126274Sdes
1506126274Sdes	if (g.gl_pathc)
1507126274Sdes		globfree(&g);
1508126274Sdes	if (path1)
1509126274Sdes		xfree(path1);
1510126274Sdes	if (path2)
1511126274Sdes		xfree(path2);
1512126274Sdes
1513126274Sdes	/* If an unignored error occurs in batch mode we should abort. */
1514126274Sdes	if (err_abort && err != 0)
1515126274Sdes		return (-1);
1516126274Sdes	else if (cmdnum == I_QUIT)
1517126274Sdes		return (1);
1518126274Sdes
1519126274Sdes	return (0);
1520126274Sdes}
1521126274Sdes
1522146998Sdes#ifdef USE_LIBEDIT
1523146998Sdesstatic char *
1524146998Sdesprompt(EditLine *el)
1525146998Sdes{
1526146998Sdes	return ("sftp> ");
1527146998Sdes}
1528146998Sdes
1529204917Sdes/* Display entries in 'list' after skipping the first 'len' chars */
1530204917Sdesstatic void
1531204917Sdescomplete_display(char **list, u_int len)
1532204917Sdes{
1533204917Sdes	u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1534204917Sdes	struct winsize ws;
1535204917Sdes	char *tmp;
1536204917Sdes
1537204917Sdes	/* Count entries for sort and find longest */
1538204917Sdes	for (y = 0; list[y]; y++)
1539204917Sdes		m = MAX(m, strlen(list[y]));
1540204917Sdes
1541204917Sdes	if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1542204917Sdes		width = ws.ws_col;
1543204917Sdes
1544204917Sdes	m = m > len ? m - len : 0;
1545204917Sdes	columns = width / (m + 2);
1546204917Sdes	columns = MAX(columns, 1);
1547204917Sdes	colspace = width / columns;
1548204917Sdes	colspace = MIN(colspace, width);
1549204917Sdes
1550204917Sdes	printf("\n");
1551204917Sdes	m = 1;
1552204917Sdes	for (y = 0; list[y]; y++) {
1553204917Sdes		llen = strlen(list[y]);
1554204917Sdes		tmp = llen > len ? list[y] + len : "";
1555204917Sdes		printf("%-*s", colspace, tmp);
1556204917Sdes		if (m >= columns) {
1557204917Sdes			printf("\n");
1558204917Sdes			m = 1;
1559204917Sdes		} else
1560204917Sdes			m++;
1561204917Sdes	}
1562204917Sdes	printf("\n");
1563204917Sdes}
1564204917Sdes
1565204917Sdes/*
1566204917Sdes * Given a "list" of words that begin with a common prefix of "word",
1567204917Sdes * attempt to find an autocompletion to extends "word" by the next
1568204917Sdes * characters common to all entries in "list".
1569204917Sdes */
1570204917Sdesstatic char *
1571204917Sdescomplete_ambiguous(const char *word, char **list, size_t count)
1572204917Sdes{
1573204917Sdes	if (word == NULL)
1574204917Sdes		return NULL;
1575204917Sdes
1576204917Sdes	if (count > 0) {
1577204917Sdes		u_int y, matchlen = strlen(list[0]);
1578204917Sdes
1579204917Sdes		/* Find length of common stem */
1580204917Sdes		for (y = 1; list[y]; y++) {
1581204917Sdes			u_int x;
1582204917Sdes
1583204917Sdes			for (x = 0; x < matchlen; x++)
1584204917Sdes				if (list[0][x] != list[y][x])
1585204917Sdes					break;
1586204917Sdes
1587204917Sdes			matchlen = x;
1588204917Sdes		}
1589204917Sdes
1590204917Sdes		if (matchlen > strlen(word)) {
1591204917Sdes			char *tmp = xstrdup(list[0]);
1592204917Sdes
1593204917Sdes			tmp[matchlen] = '\0';
1594204917Sdes			return tmp;
1595204917Sdes		}
1596204917Sdes	}
1597204917Sdes
1598204917Sdes	return xstrdup(word);
1599204917Sdes}
1600204917Sdes
1601204917Sdes/* Autocomplete a sftp command */
1602204917Sdesstatic int
1603204917Sdescomplete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1604204917Sdes    int terminated)
1605204917Sdes{
1606204917Sdes	u_int y, count = 0, cmdlen, tmplen;
1607204917Sdes	char *tmp, **list, argterm[3];
1608204917Sdes	const LineInfo *lf;
1609204917Sdes
1610204917Sdes	list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1611204917Sdes
1612204917Sdes	/* No command specified: display all available commands */
1613204917Sdes	if (cmd == NULL) {
1614204917Sdes		for (y = 0; cmds[y].c; y++)
1615204917Sdes			list[count++] = xstrdup(cmds[y].c);
1616204917Sdes
1617204917Sdes		list[count] = NULL;
1618204917Sdes		complete_display(list, 0);
1619204917Sdes
1620204917Sdes		for (y = 0; list[y] != NULL; y++)
1621204917Sdes			xfree(list[y]);
1622204917Sdes		xfree(list);
1623204917Sdes		return count;
1624204917Sdes	}
1625204917Sdes
1626204917Sdes	/* Prepare subset of commands that start with "cmd" */
1627204917Sdes	cmdlen = strlen(cmd);
1628204917Sdes	for (y = 0; cmds[y].c; y++)  {
1629204917Sdes		if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1630204917Sdes			list[count++] = xstrdup(cmds[y].c);
1631204917Sdes	}
1632204917Sdes	list[count] = NULL;
1633204917Sdes
1634240075Sdes	if (count == 0) {
1635240075Sdes		xfree(list);
1636204917Sdes		return 0;
1637240075Sdes	}
1638204917Sdes
1639204917Sdes	/* Complete ambigious command */
1640204917Sdes	tmp = complete_ambiguous(cmd, list, count);
1641204917Sdes	if (count > 1)
1642204917Sdes		complete_display(list, 0);
1643204917Sdes
1644204917Sdes	for (y = 0; list[y]; y++)
1645204917Sdes		xfree(list[y]);
1646204917Sdes	xfree(list);
1647204917Sdes
1648204917Sdes	if (tmp != NULL) {
1649204917Sdes		tmplen = strlen(tmp);
1650204917Sdes		cmdlen = strlen(cmd);
1651204917Sdes		/* If cmd may be extended then do so */
1652204917Sdes		if (tmplen > cmdlen)
1653204917Sdes			if (el_insertstr(el, tmp + cmdlen) == -1)
1654204917Sdes				fatal("el_insertstr failed.");
1655204917Sdes		lf = el_line(el);
1656204917Sdes		/* Terminate argument cleanly */
1657204917Sdes		if (count == 1) {
1658204917Sdes			y = 0;
1659204917Sdes			if (!terminated)
1660204917Sdes				argterm[y++] = quote;
1661204917Sdes			if (lastarg || *(lf->cursor) != ' ')
1662204917Sdes				argterm[y++] = ' ';
1663204917Sdes			argterm[y] = '\0';
1664204917Sdes			if (y > 0 && el_insertstr(el, argterm) == -1)
1665204917Sdes				fatal("el_insertstr failed.");
1666204917Sdes		}
1667204917Sdes		xfree(tmp);
1668204917Sdes	}
1669204917Sdes
1670204917Sdes	return count;
1671204917Sdes}
1672204917Sdes
1673204917Sdes/*
1674204917Sdes * Determine whether a particular sftp command's arguments (if any)
1675204917Sdes * represent local or remote files.
1676204917Sdes */
1677204917Sdesstatic int
1678204917Sdescomplete_is_remote(char *cmd) {
1679204917Sdes	int i;
1680204917Sdes
1681204917Sdes	if (cmd == NULL)
1682204917Sdes		return -1;
1683204917Sdes
1684204917Sdes	for (i = 0; cmds[i].c; i++) {
1685204917Sdes		if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1686204917Sdes			return cmds[i].t;
1687204917Sdes	}
1688204917Sdes
1689204917Sdes	return -1;
1690204917Sdes}
1691204917Sdes
1692204917Sdes/* Autocomplete a filename "file" */
1693204917Sdesstatic int
1694204917Sdescomplete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1695204917Sdes    char *file, int remote, int lastarg, char quote, int terminated)
1696204917Sdes{
1697204917Sdes	glob_t g;
1698204917Sdes	char *tmp, *tmp2, ins[3];
1699204917Sdes	u_int i, hadglob, pwdlen, len, tmplen, filelen;
1700204917Sdes	const LineInfo *lf;
1701204917Sdes
1702204917Sdes	/* Glob from "file" location */
1703204917Sdes	if (file == NULL)
1704204917Sdes		tmp = xstrdup("*");
1705204917Sdes	else
1706204917Sdes		xasprintf(&tmp, "%s*", file);
1707204917Sdes
1708204917Sdes	memset(&g, 0, sizeof(g));
1709204917Sdes	if (remote != LOCAL) {
1710204917Sdes		tmp = make_absolute(tmp, remote_path);
1711204917Sdes		remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1712204917Sdes	} else
1713204917Sdes		glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1714204917Sdes
1715204917Sdes	/* Determine length of pwd so we can trim completion display */
1716204917Sdes	for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1717204917Sdes		/* Terminate counting on first unescaped glob metacharacter */
1718204917Sdes		if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1719204917Sdes			if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1720204917Sdes				hadglob = 1;
1721204917Sdes			break;
1722204917Sdes		}
1723204917Sdes		if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1724204917Sdes			tmplen++;
1725204917Sdes		if (tmp[tmplen] == '/')
1726204917Sdes			pwdlen = tmplen + 1;	/* track last seen '/' */
1727204917Sdes	}
1728204917Sdes	xfree(tmp);
1729204917Sdes
1730204917Sdes	if (g.gl_matchc == 0)
1731204917Sdes		goto out;
1732204917Sdes
1733204917Sdes	if (g.gl_matchc > 1)
1734204917Sdes		complete_display(g.gl_pathv, pwdlen);
1735204917Sdes
1736204917Sdes	tmp = NULL;
1737204917Sdes	/* Don't try to extend globs */
1738204917Sdes	if (file == NULL || hadglob)
1739204917Sdes		goto out;
1740204917Sdes
1741204917Sdes	tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1742204917Sdes	tmp = path_strip(tmp2, remote_path);
1743204917Sdes	xfree(tmp2);
1744204917Sdes
1745204917Sdes	if (tmp == NULL)
1746204917Sdes		goto out;
1747204917Sdes
1748204917Sdes	tmplen = strlen(tmp);
1749204917Sdes	filelen = strlen(file);
1750204917Sdes
1751204917Sdes	if (tmplen > filelen)  {
1752204917Sdes		tmp2 = tmp + filelen;
1753204917Sdes		len = strlen(tmp2);
1754204917Sdes		/* quote argument on way out */
1755204917Sdes		for (i = 0; i < len; i++) {
1756204917Sdes			ins[0] = '\\';
1757204917Sdes			ins[1] = tmp2[i];
1758204917Sdes			ins[2] = '\0';
1759204917Sdes			switch (tmp2[i]) {
1760204917Sdes			case '\'':
1761204917Sdes			case '"':
1762204917Sdes			case '\\':
1763204917Sdes			case '\t':
1764221420Sdes			case '[':
1765204917Sdes			case ' ':
1766204917Sdes				if (quote == '\0' || tmp2[i] == quote) {
1767204917Sdes					if (el_insertstr(el, ins) == -1)
1768204917Sdes						fatal("el_insertstr "
1769204917Sdes						    "failed.");
1770204917Sdes					break;
1771204917Sdes				}
1772204917Sdes				/* FALLTHROUGH */
1773204917Sdes			default:
1774204917Sdes				if (el_insertstr(el, ins + 1) == -1)
1775204917Sdes					fatal("el_insertstr failed.");
1776204917Sdes				break;
1777204917Sdes			}
1778204917Sdes		}
1779204917Sdes	}
1780204917Sdes
1781204917Sdes	lf = el_line(el);
1782204917Sdes	if (g.gl_matchc == 1) {
1783204917Sdes		i = 0;
1784204917Sdes		if (!terminated)
1785204917Sdes			ins[i++] = quote;
1786204917Sdes		if (*(lf->cursor - 1) != '/' &&
1787204917Sdes		    (lastarg || *(lf->cursor) != ' '))
1788204917Sdes			ins[i++] = ' ';
1789204917Sdes		ins[i] = '\0';
1790204917Sdes		if (i > 0 && el_insertstr(el, ins) == -1)
1791204917Sdes			fatal("el_insertstr failed.");
1792204917Sdes	}
1793204917Sdes	xfree(tmp);
1794204917Sdes
1795204917Sdes out:
1796204917Sdes	globfree(&g);
1797204917Sdes	return g.gl_matchc;
1798204917Sdes}
1799204917Sdes
1800204917Sdes/* tab-completion hook function, called via libedit */
1801204917Sdesstatic unsigned char
1802204917Sdescomplete(EditLine *el, int ch)
1803204917Sdes{
1804204917Sdes	char **argv, *line, quote;
1805204917Sdes	u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1806204917Sdes	const LineInfo *lf;
1807204917Sdes	struct complete_ctx *complete_ctx;
1808204917Sdes
1809204917Sdes	lf = el_line(el);
1810204917Sdes	if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1811204917Sdes		fatal("%s: el_get failed", __func__);
1812204917Sdes
1813204917Sdes	/* Figure out which argument the cursor points to */
1814204917Sdes	cursor = lf->cursor - lf->buffer;
1815204917Sdes	line = (char *)xmalloc(cursor + 1);
1816204917Sdes	memcpy(line, lf->buffer, cursor);
1817204917Sdes	line[cursor] = '\0';
1818204917Sdes	argv = makeargv(line, &carg, 1, &quote, &terminated);
1819204917Sdes	xfree(line);
1820204917Sdes
1821204917Sdes	/* Get all the arguments on the line */
1822204917Sdes	len = lf->lastchar - lf->buffer;
1823204917Sdes	line = (char *)xmalloc(len + 1);
1824204917Sdes	memcpy(line, lf->buffer, len);
1825204917Sdes	line[len] = '\0';
1826204917Sdes	argv = makeargv(line, &argc, 1, NULL, NULL);
1827204917Sdes
1828204917Sdes	/* Ensure cursor is at EOL or a argument boundary */
1829204917Sdes	if (line[cursor] != ' ' && line[cursor] != '\0' &&
1830204917Sdes	    line[cursor] != '\n') {
1831204917Sdes		xfree(line);
1832204917Sdes		return ret;
1833204917Sdes	}
1834204917Sdes
1835204917Sdes	if (carg == 0) {
1836204917Sdes		/* Show all available commands */
1837204917Sdes		complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1838204917Sdes		ret = CC_REDISPLAY;
1839204917Sdes	} else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ')  {
1840204917Sdes		/* Handle the command parsing */
1841204917Sdes		if (complete_cmd_parse(el, argv[0], argc == carg,
1842204917Sdes		    quote, terminated) != 0)
1843204917Sdes			ret = CC_REDISPLAY;
1844204917Sdes	} else if (carg >= 1) {
1845204917Sdes		/* Handle file parsing */
1846204917Sdes		int remote = complete_is_remote(argv[0]);
1847204917Sdes		char *filematch = NULL;
1848204917Sdes
1849204917Sdes		if (carg > 1 && line[cursor-1] != ' ')
1850204917Sdes			filematch = argv[carg - 1];
1851204917Sdes
1852204917Sdes		if (remote != 0 &&
1853204917Sdes		    complete_match(el, complete_ctx->conn,
1854204917Sdes		    *complete_ctx->remote_pathp, filematch,
1855204917Sdes		    remote, carg == argc, quote, terminated) != 0)
1856204917Sdes			ret = CC_REDISPLAY;
1857204917Sdes	}
1858204917Sdes
1859204917Sdes	xfree(line);
1860204917Sdes	return ret;
1861204917Sdes}
1862204917Sdes#endif /* USE_LIBEDIT */
1863204917Sdes
1864126274Sdesint
1865204917Sdesinteractive_loop(struct sftp_conn *conn, char *file1, char *file2)
1866126274Sdes{
1867204917Sdes	char *remote_path;
1868126274Sdes	char *dir = NULL;
1869126274Sdes	char cmd[2048];
1870149749Sdes	int err, interactive;
1871146998Sdes	EditLine *el = NULL;
1872146998Sdes#ifdef USE_LIBEDIT
1873146998Sdes	History *hl = NULL;
1874146998Sdes	HistEvent hev;
1875146998Sdes	extern char *__progname;
1876204917Sdes	struct complete_ctx complete_ctx;
1877126274Sdes
1878146998Sdes	if (!batchmode && isatty(STDIN_FILENO)) {
1879146998Sdes		if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1880146998Sdes			fatal("Couldn't initialise editline");
1881146998Sdes		if ((hl = history_init()) == NULL)
1882146998Sdes			fatal("Couldn't initialise editline history");
1883146998Sdes		history(hl, &hev, H_SETSIZE, 100);
1884146998Sdes		el_set(el, EL_HIST, history, hl);
1885146998Sdes
1886146998Sdes		el_set(el, EL_PROMPT, prompt);
1887146998Sdes		el_set(el, EL_EDITOR, "emacs");
1888146998Sdes		el_set(el, EL_TERMINAL, NULL);
1889146998Sdes		el_set(el, EL_SIGNAL, 1);
1890146998Sdes		el_source(el, NULL);
1891204917Sdes
1892204917Sdes		/* Tab Completion */
1893204917Sdes		el_set(el, EL_ADDFN, "ftp-complete",
1894221420Sdes		    "Context sensitive argument completion", complete);
1895204917Sdes		complete_ctx.conn = conn;
1896204917Sdes		complete_ctx.remote_pathp = &remote_path;
1897204917Sdes		el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1898204917Sdes		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
1899146998Sdes	}
1900146998Sdes#endif /* USE_LIBEDIT */
1901146998Sdes
1902204917Sdes	remote_path = do_realpath(conn, ".");
1903204917Sdes	if (remote_path == NULL)
1904126274Sdes		fatal("Need cwd");
1905126274Sdes
1906126274Sdes	if (file1 != NULL) {
1907126274Sdes		dir = xstrdup(file1);
1908204917Sdes		dir = make_absolute(dir, remote_path);
1909126274Sdes
1910126274Sdes		if (remote_is_dir(conn, dir) && file2 == NULL) {
1911126274Sdes			printf("Changing to: %s\n", dir);
1912126274Sdes			snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
1913204917Sdes			if (parse_dispatch_command(conn, cmd,
1914204917Sdes			    &remote_path, 1) != 0) {
1915146998Sdes				xfree(dir);
1916204917Sdes				xfree(remote_path);
1917162852Sdes				xfree(conn);
1918126274Sdes				return (-1);
1919146998Sdes			}
1920126274Sdes		} else {
1921126274Sdes			if (file2 == NULL)
1922126274Sdes				snprintf(cmd, sizeof cmd, "get %s", dir);
1923126274Sdes			else
1924126274Sdes				snprintf(cmd, sizeof cmd, "get %s %s", dir,
1925126274Sdes				    file2);
1926126274Sdes
1927204917Sdes			err = parse_dispatch_command(conn, cmd,
1928204917Sdes			    &remote_path, 1);
1929126274Sdes			xfree(dir);
1930204917Sdes			xfree(remote_path);
1931162852Sdes			xfree(conn);
1932126274Sdes			return (err);
1933126274Sdes		}
1934126274Sdes		xfree(dir);
1935126274Sdes	}
1936126274Sdes
1937149749Sdes	setlinebuf(stdout);
1938149749Sdes	setlinebuf(infile);
1939126274Sdes
1940149749Sdes	interactive = !batchmode && isatty(STDIN_FILENO);
1941126274Sdes	err = 0;
1942126274Sdes	for (;;) {
1943126274Sdes		char *cp;
1944126274Sdes
1945137015Sdes		signal(SIGINT, SIG_IGN);
1946137015Sdes
1947146998Sdes		if (el == NULL) {
1948149749Sdes			if (interactive)
1949149749Sdes				printf("sftp> ");
1950146998Sdes			if (fgets(cmd, sizeof(cmd), infile) == NULL) {
1951149749Sdes				if (interactive)
1952149749Sdes					printf("\n");
1953146998Sdes				break;
1954146998Sdes			}
1955149749Sdes			if (!interactive) { /* Echo command */
1956149749Sdes				printf("sftp> %s", cmd);
1957149749Sdes				if (strlen(cmd) > 0 &&
1958149749Sdes				    cmd[strlen(cmd) - 1] != '\n')
1959149749Sdes					printf("\n");
1960149749Sdes			}
1961146998Sdes		} else {
1962146998Sdes#ifdef USE_LIBEDIT
1963146998Sdes			const char *line;
1964146998Sdes			int count = 0;
1965126274Sdes
1966204917Sdes			if ((line = el_gets(el, &count)) == NULL ||
1967204917Sdes			    count <= 0) {
1968149749Sdes				printf("\n");
1969149749Sdes 				break;
1970149749Sdes			}
1971146998Sdes			history(hl, &hev, H_ENTER, line);
1972146998Sdes			if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1973146998Sdes				fprintf(stderr, "Error: input line too long\n");
1974146998Sdes				continue;
1975146998Sdes			}
1976146998Sdes#endif /* USE_LIBEDIT */
1977126274Sdes		}
1978126274Sdes
1979126274Sdes		cp = strrchr(cmd, '\n');
1980126274Sdes		if (cp)
1981126274Sdes			*cp = '\0';
1982126274Sdes
1983137015Sdes		/* Handle user interrupts gracefully during commands */
1984137015Sdes		interrupted = 0;
1985137015Sdes		signal(SIGINT, cmd_interrupt);
1986137015Sdes
1987204917Sdes		err = parse_dispatch_command(conn, cmd, &remote_path,
1988204917Sdes		    batchmode);
1989126274Sdes		if (err != 0)
1990126274Sdes			break;
1991126274Sdes	}
1992204917Sdes	xfree(remote_path);
1993162852Sdes	xfree(conn);
1994126274Sdes
1995149749Sdes#ifdef USE_LIBEDIT
1996149749Sdes	if (el != NULL)
1997149749Sdes		el_end(el);
1998149749Sdes#endif /* USE_LIBEDIT */
1999149749Sdes
2000126274Sdes	/* err == 1 signifies normal "quit" exit */
2001126274Sdes	return (err >= 0 ? 0 : -1);
2002126274Sdes}
2003126274Sdes
2004126274Sdesstatic void
2005124208Sdesconnect_to_server(char *path, char **args, int *in, int *out)
2006124208Sdes{
200776259Sgreen	int c_in, c_out;
200899060Sdes
200976259Sgreen#ifdef USE_PIPES
201076259Sgreen	int pin[2], pout[2];
201199060Sdes
201276259Sgreen	if ((pipe(pin) == -1) || (pipe(pout) == -1))
201376259Sgreen		fatal("pipe: %s", strerror(errno));
201476259Sgreen	*in = pin[0];
201576259Sgreen	*out = pout[1];
201676259Sgreen	c_in = pout[0];
201776259Sgreen	c_out = pin[1];
201876259Sgreen#else /* USE_PIPES */
201976259Sgreen	int inout[2];
202099060Sdes
202176259Sgreen	if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
202276259Sgreen		fatal("socketpair: %s", strerror(errno));
202376259Sgreen	*in = *out = inout[0];
202476259Sgreen	c_in = c_out = inout[1];
202576259Sgreen#endif /* USE_PIPES */
202676259Sgreen
2027124208Sdes	if ((sshpid = fork()) == -1)
202876259Sgreen		fatal("fork: %s", strerror(errno));
2029124208Sdes	else if (sshpid == 0) {
203076259Sgreen		if ((dup2(c_in, STDIN_FILENO) == -1) ||
203176259Sgreen		    (dup2(c_out, STDOUT_FILENO) == -1)) {
203276259Sgreen			fprintf(stderr, "dup2: %s\n", strerror(errno));
2033137015Sdes			_exit(1);
203476259Sgreen		}
203576259Sgreen		close(*in);
203676259Sgreen		close(*out);
203776259Sgreen		close(c_in);
203876259Sgreen		close(c_out);
2039137015Sdes
2040137015Sdes		/*
2041137015Sdes		 * The underlying ssh is in the same process group, so we must
2042137015Sdes		 * ignore SIGINT if we want to gracefully abort commands,
2043137015Sdes		 * otherwise the signal will make it to the ssh process and
2044204917Sdes		 * kill it too.  Contrawise, since sftp sends SIGTERMs to the
2045204917Sdes		 * underlying ssh, it must *not* ignore that signal.
2046137015Sdes		 */
2047137015Sdes		signal(SIGINT, SIG_IGN);
2048204917Sdes		signal(SIGTERM, SIG_DFL);
2049137015Sdes		execvp(path, args);
205092555Sdes		fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
2051137015Sdes		_exit(1);
205276259Sgreen	}
205376259Sgreen
2054124208Sdes	signal(SIGTERM, killchild);
2055124208Sdes	signal(SIGINT, killchild);
2056124208Sdes	signal(SIGHUP, killchild);
205776259Sgreen	close(c_in);
205876259Sgreen	close(c_out);
205976259Sgreen}
206076259Sgreen
206192555Sdesstatic void
206276259Sgreenusage(void)
206376259Sgreen{
206492555Sdes	extern char *__progname;
206598675Sdes
206692555Sdes	fprintf(stderr,
2067204917Sdes	    "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
2068204917Sdes	    "          [-D sftp_server_path] [-F ssh_config] "
2069221420Sdes	    "[-i identity_file] [-l limit]\n"
2070204917Sdes	    "          [-o ssh_option] [-P port] [-R num_requests] "
2071204917Sdes	    "[-S program]\n"
2072204917Sdes	    "          [-s subsystem | sftp_server] host\n"
2073192595Sdes	    "       %s [user@]host[:file ...]\n"
2074192595Sdes	    "       %s [user@]host[:dir[/]]\n"
2075204917Sdes	    "       %s -b batchfile [user@]host\n",
2076204917Sdes	    __progname, __progname, __progname, __progname);
207776259Sgreen	exit(1);
207876259Sgreen}
207976259Sgreen
208076259Sgreenint
208176259Sgreenmain(int argc, char **argv)
208276259Sgreen{
2083113908Sdes	int in, out, ch, err;
2084204917Sdes	char *host = NULL, *userhost, *cp, *file2 = NULL;
208592555Sdes	int debug_level = 0, sshver = 2;
208692555Sdes	char *file1 = NULL, *sftp_server = NULL;
208792555Sdes	char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
2088221420Sdes	const char *errstr;
208992555Sdes	LogLevel ll = SYSLOG_LEVEL_INFO;
209092555Sdes	arglist args;
209176259Sgreen	extern int optind;
209276259Sgreen	extern char *optarg;
2093204917Sdes	struct sftp_conn *conn;
2094204917Sdes	size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2095204917Sdes	size_t num_requests = DEFAULT_NUM_REQUESTS;
2096221420Sdes	long long limit_kbps = 0;
209776259Sgreen
2098157016Sdes	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2099157016Sdes	sanitise_stdfd();
2100157016Sdes
2101124208Sdes	__progname = ssh_get_progname(argv[0]);
2102157016Sdes	memset(&args, '\0', sizeof(args));
210392555Sdes	args.list = NULL;
2104162852Sdes	addargs(&args, "%s", ssh_program);
210592555Sdes	addargs(&args, "-oForwardX11 no");
210692555Sdes	addargs(&args, "-oForwardAgent no");
2107157016Sdes	addargs(&args, "-oPermitLocalCommand no");
210892555Sdes	addargs(&args, "-oClearAllForwardings yes");
2109126274Sdes
211092555Sdes	ll = SYSLOG_LEVEL_INFO;
2111126274Sdes	infile = stdin;
211276259Sgreen
2113204917Sdes	while ((ch = getopt(argc, argv,
2114221420Sdes	    "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
211576259Sgreen		switch (ch) {
2116204917Sdes		/* Passed through to ssh(1) */
2117204917Sdes		case '4':
2118204917Sdes		case '6':
211976259Sgreen		case 'C':
2120204917Sdes			addargs(&args, "-%c", ch);
212176259Sgreen			break;
2122204917Sdes		/* Passed through to ssh(1) with argument */
2123204917Sdes		case 'F':
2124204917Sdes		case 'c':
2125204917Sdes		case 'i':
2126204917Sdes		case 'o':
2127204917Sdes			addargs(&args, "-%c", ch);
2128204917Sdes			addargs(&args, "%s", optarg);
2129204917Sdes			break;
2130204917Sdes		case 'q':
2131204917Sdes			showprogress = 0;
2132204917Sdes			addargs(&args, "-%c", ch);
2133204917Sdes			break;
2134204917Sdes		case 'P':
2135204917Sdes			addargs(&args, "-oPort %s", optarg);
2136204917Sdes			break;
213776259Sgreen		case 'v':
213892555Sdes			if (debug_level < 3) {
213992555Sdes				addargs(&args, "-v");
214092555Sdes				ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
214192555Sdes			}
214292555Sdes			debug_level++;
214376259Sgreen			break;
214476259Sgreen		case '1':
214592555Sdes			sshver = 1;
214676259Sgreen			if (sftp_server == NULL)
214776259Sgreen				sftp_server = _PATH_SFTP_SERVER;
214876259Sgreen			break;
2149204917Sdes		case '2':
2150204917Sdes			sshver = 2;
215176259Sgreen			break;
2152204917Sdes		case 'B':
2153204917Sdes			copy_buffer_len = strtol(optarg, &cp, 10);
2154204917Sdes			if (copy_buffer_len == 0 || *cp != '\0')
2155204917Sdes				fatal("Invalid buffer size \"%s\"", optarg);
215676259Sgreen			break;
215776259Sgreen		case 'b':
2158126274Sdes			if (batchmode)
2159126274Sdes				fatal("Batch file already specified.");
2160126274Sdes
2161126274Sdes			/* Allow "-" as stdin */
2162137015Sdes			if (strcmp(optarg, "-") != 0 &&
2163149749Sdes			    (infile = fopen(optarg, "r")) == NULL)
2164126274Sdes				fatal("%s (%s).", strerror(errno), optarg);
2165113908Sdes			showprogress = 0;
2166126274Sdes			batchmode = 1;
2167146998Sdes			addargs(&args, "-obatchmode yes");
216876259Sgreen			break;
2169204917Sdes		case 'p':
2170204917Sdes			global_pflag = 1;
2171204917Sdes			break;
2172204917Sdes		case 'D':
217392555Sdes			sftp_direct = optarg;
217492555Sdes			break;
2175221420Sdes		case 'l':
2176221420Sdes			limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2177221420Sdes			    &errstr);
2178221420Sdes			if (errstr != NULL)
2179221420Sdes				usage();
2180221420Sdes			limit_kbps *= 1024; /* kbps */
2181221420Sdes			break;
2182204917Sdes		case 'r':
2183204917Sdes			global_rflag = 1;
218492555Sdes			break;
218592555Sdes		case 'R':
218692555Sdes			num_requests = strtol(optarg, &cp, 10);
218792555Sdes			if (num_requests == 0 || *cp != '\0')
218898675Sdes				fatal("Invalid number of requests \"%s\"",
218992555Sdes				    optarg);
219092555Sdes			break;
2191204917Sdes		case 's':
2192204917Sdes			sftp_server = optarg;
2193204917Sdes			break;
2194204917Sdes		case 'S':
2195204917Sdes			ssh_program = optarg;
2196204917Sdes			replacearg(&args, 0, "%s", ssh_program);
2197204917Sdes			break;
219876259Sgreen		case 'h':
219976259Sgreen		default:
220076259Sgreen			usage();
220176259Sgreen		}
220276259Sgreen	}
220376259Sgreen
2204128456Sdes	if (!isatty(STDERR_FILENO))
2205128456Sdes		showprogress = 0;
2206128456Sdes
220798675Sdes	log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
220898675Sdes
220992555Sdes	if (sftp_direct == NULL) {
221092555Sdes		if (optind == argc || argc > (optind + 2))
221192555Sdes			usage();
221276259Sgreen
221392555Sdes		userhost = xstrdup(argv[optind]);
221492555Sdes		file2 = argv[optind+1];
221576259Sgreen
2216113908Sdes		if ((host = strrchr(userhost, '@')) == NULL)
221792555Sdes			host = userhost;
221892555Sdes		else {
221992555Sdes			*host++ = '\0';
222092555Sdes			if (!userhost[0]) {
222192555Sdes				fprintf(stderr, "Missing username\n");
222292555Sdes				usage();
222392555Sdes			}
2224204917Sdes			addargs(&args, "-l");
2225204917Sdes			addargs(&args, "%s", userhost);
222692555Sdes		}
222792555Sdes
2228126274Sdes		if ((cp = colon(host)) != NULL) {
2229126274Sdes			*cp++ = '\0';
2230126274Sdes			file1 = cp;
2231126274Sdes		}
2232126274Sdes
223392555Sdes		host = cleanhostname(host);
223492555Sdes		if (!*host) {
223592555Sdes			fprintf(stderr, "Missing hostname\n");
223676259Sgreen			usage();
223776259Sgreen		}
223876259Sgreen
223992555Sdes		addargs(&args, "-oProtocol %d", sshver);
224076259Sgreen
224192555Sdes		/* no subsystem if the server-spec contains a '/' */
224292555Sdes		if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
224392555Sdes			addargs(&args, "-s");
224476259Sgreen
2245204917Sdes		addargs(&args, "--");
224692555Sdes		addargs(&args, "%s", host);
224798675Sdes		addargs(&args, "%s", (sftp_server != NULL ?
224892555Sdes		    sftp_server : "sftp"));
224976259Sgreen
2250124208Sdes		connect_to_server(ssh_program, args.list, &in, &out);
225192555Sdes	} else {
225292555Sdes		args.list = NULL;
225392555Sdes		addargs(&args, "sftp-server");
225476259Sgreen
2255124208Sdes		connect_to_server(sftp_direct, args.list, &in, &out);
225692555Sdes	}
2257157016Sdes	freeargs(&args);
225876259Sgreen
2259221420Sdes	conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
2260204917Sdes	if (conn == NULL)
2261204917Sdes		fatal("Couldn't initialise connection to server");
226276259Sgreen
2263204917Sdes	if (!batchmode) {
2264204917Sdes		if (sftp_direct == NULL)
2265204917Sdes			fprintf(stderr, "Connected to %s.\n", host);
2266204917Sdes		else
2267204917Sdes			fprintf(stderr, "Attached to %s.\n", sftp_direct);
2268204917Sdes	}
2269204917Sdes
2270204917Sdes	err = interactive_loop(conn, file1, file2);
2271204917Sdes
227298937Sdes#if !defined(USE_PIPES)
2273149749Sdes	shutdown(in, SHUT_RDWR);
2274149749Sdes	shutdown(out, SHUT_RDWR);
227598937Sdes#endif
227698937Sdes
227776259Sgreen	close(in);
227876259Sgreen	close(out);
2279126274Sdes	if (batchmode)
228076259Sgreen		fclose(infile);
228176259Sgreen
228298675Sdes	while (waitpid(sshpid, NULL, 0) == -1)
228398675Sdes		if (errno != EINTR)
228498675Sdes			fatal("Couldn't wait for ssh process: %s",
228598675Sdes			    strerror(errno));
228676259Sgreen
2287113908Sdes	exit(err == 0 ? 0 : 1);
228876259Sgreen}
2289