1262566Sdes/* $OpenBSD: sftp.c,v 1.158 2013/11/20 20:54:10 deraadt Exp $ */
2224638Sbrooks/* $FreeBSD$ */
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
42255767Sdes#ifdef HAVE_LOCALE_H
43255767Sdes# include <locale.h>
44255767Sdes#endif
45146998Sdes#ifdef USE_LIBEDIT
46146998Sdes#include <histedit.h>
47146998Sdes#else
48146998Sdestypedef void EditLine;
49146998Sdes#endif
50162852Sdes#include <signal.h>
51162852Sdes#include <stdlib.h>
52162852Sdes#include <stdio.h>
53162852Sdes#include <string.h>
54162852Sdes#include <unistd.h>
55162852Sdes#include <stdarg.h>
56146998Sdes
57181111Sdes#ifdef HAVE_UTIL_H
58181111Sdes# include <util.h>
59181111Sdes#endif
60181111Sdes
6176259Sgreen#include "xmalloc.h"
6276259Sgreen#include "log.h"
6376259Sgreen#include "pathnames.h"
6492555Sdes#include "misc.h"
6576259Sgreen
6676259Sgreen#include "sftp.h"
67162852Sdes#include "buffer.h"
6876259Sgreen#include "sftp-common.h"
6976259Sgreen#include "sftp-client.h"
7076259Sgreen
71204917Sdes#define DEFAULT_COPY_BUFLEN	32768	/* Size of buffer for up/download */
72224638Sbrooks#define DEFAULT_NUM_REQUESTS	256	/* # concurrent outstanding requests */
73204917Sdes
74126274Sdes/* File to read commands from */
75126274SdesFILE* infile;
76126274Sdes
77126274Sdes/* Are we in batchfile mode? */
78126274Sdesint batchmode = 0;
79126274Sdes
80126274Sdes/* PID of ssh transport process */
81126274Sdesstatic pid_t sshpid = -1;
82126274Sdes
83255767Sdes/* Suppress diagnositic messages */
84255767Sdesint quiet = 0;
85255767Sdes
86126274Sdes/* This is set to 0 if the progressmeter is not desired. */
87128456Sdesint showprogress = 1;
88126274Sdes
89204917Sdes/* When this option is set, we always recursively download/upload directories */
90204917Sdesint global_rflag = 0;
91204917Sdes
92255767Sdes/* When this option is set, we resume download if possible */
93255767Sdesint global_aflag = 0;
94255767Sdes
95204917Sdes/* When this option is set, the file transfers will always preserve times */
96204917Sdesint global_pflag = 0;
97204917Sdes
98262566Sdes/* When this option is set, transfers will have fsync() called on each file */
99262566Sdesint global_fflag = 0;
100262566Sdes
101137015Sdes/* SIGINT received during command processing */
102137015Sdesvolatile sig_atomic_t interrupted = 0;
103137015Sdes
104137015Sdes/* I wish qsort() took a separate ctx for the comparison function...*/
105137015Sdesint sort_flag;
106137015Sdes
107204917Sdes/* Context used for commandline completion */
108204917Sdesstruct complete_ctx {
109204917Sdes	struct sftp_conn *conn;
110204917Sdes	char **remote_pathp;
111204917Sdes};
112204917Sdes
113126274Sdesint remote_glob(struct sftp_conn *, const char *, int,
114126274Sdes    int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
115126274Sdes
11698937Sdesextern char *__progname;
11798937Sdes
118126274Sdes/* Separators for interactive commands */
119126274Sdes#define WHITESPACE " \t\r\n"
12076259Sgreen
121137015Sdes/* ls flags */
122204917Sdes#define LS_LONG_VIEW	0x0001	/* Full view ala ls -l */
123204917Sdes#define LS_SHORT_VIEW	0x0002	/* Single row view ala ls -1 */
124204917Sdes#define LS_NUMERIC_VIEW	0x0004	/* Long view with numeric uid/gid */
125204917Sdes#define LS_NAME_SORT	0x0008	/* Sort by name (default) */
126204917Sdes#define LS_TIME_SORT	0x0010	/* Sort by mtime */
127204917Sdes#define LS_SIZE_SORT	0x0020	/* Sort by file size */
128204917Sdes#define LS_REVERSE_SORT	0x0040	/* Reverse sort order */
129204917Sdes#define LS_SHOW_ALL	0x0080	/* Don't skip filenames starting with '.' */
130204917Sdes#define LS_SI_UNITS	0x0100	/* Display sizes as K, M, G, etc. */
131113908Sdes
132204917Sdes#define VIEW_FLAGS	(LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
133137015Sdes#define SORT_FLAGS	(LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
134137015Sdes
135126274Sdes/* Commands for interactive mode */
136262566Sdesenum sftp_command {
137262566Sdes	I_CHDIR = 1,
138262566Sdes	I_CHGRP,
139262566Sdes	I_CHMOD,
140262566Sdes	I_CHOWN,
141262566Sdes	I_DF,
142262566Sdes	I_GET,
143262566Sdes	I_HELP,
144262566Sdes	I_LCHDIR,
145262566Sdes	I_LINK,
146262566Sdes	I_LLS,
147262566Sdes	I_LMKDIR,
148262566Sdes	I_LPWD,
149262566Sdes	I_LS,
150262566Sdes	I_LUMASK,
151262566Sdes	I_MKDIR,
152262566Sdes	I_PUT,
153262566Sdes	I_PWD,
154262566Sdes	I_QUIT,
155262566Sdes	I_RENAME,
156262566Sdes	I_RM,
157262566Sdes	I_RMDIR,
158262566Sdes	I_SHELL,
159262566Sdes	I_SYMLINK,
160262566Sdes	I_VERSION,
161262566Sdes	I_PROGRESS,
162262566Sdes	I_REGET,
163262566Sdes};
164126274Sdes
165126274Sdesstruct CMD {
166126274Sdes	const char *c;
167126274Sdes	const int n;
168204917Sdes	const int t;
169126274Sdes};
170126274Sdes
171204917Sdes/* Type of completion */
172204917Sdes#define NOARGS	0
173204917Sdes#define REMOTE	1
174204917Sdes#define LOCAL	2
175204917Sdes
176126274Sdesstatic const struct CMD cmds[] = {
177204917Sdes	{ "bye",	I_QUIT,		NOARGS	},
178204917Sdes	{ "cd",		I_CHDIR,	REMOTE	},
179204917Sdes	{ "chdir",	I_CHDIR,	REMOTE	},
180204917Sdes	{ "chgrp",	I_CHGRP,	REMOTE	},
181204917Sdes	{ "chmod",	I_CHMOD,	REMOTE	},
182204917Sdes	{ "chown",	I_CHOWN,	REMOTE	},
183204917Sdes	{ "df",		I_DF,		REMOTE	},
184204917Sdes	{ "dir",	I_LS,		REMOTE	},
185204917Sdes	{ "exit",	I_QUIT,		NOARGS	},
186204917Sdes	{ "get",	I_GET,		REMOTE	},
187204917Sdes	{ "help",	I_HELP,		NOARGS	},
188204917Sdes	{ "lcd",	I_LCHDIR,	LOCAL	},
189204917Sdes	{ "lchdir",	I_LCHDIR,	LOCAL	},
190204917Sdes	{ "lls",	I_LLS,		LOCAL	},
191204917Sdes	{ "lmkdir",	I_LMKDIR,	LOCAL	},
192221420Sdes	{ "ln",		I_LINK,		REMOTE	},
193204917Sdes	{ "lpwd",	I_LPWD,		LOCAL	},
194204917Sdes	{ "ls",		I_LS,		REMOTE	},
195204917Sdes	{ "lumask",	I_LUMASK,	NOARGS	},
196204917Sdes	{ "mkdir",	I_MKDIR,	REMOTE	},
197215116Sdes	{ "mget",	I_GET,		REMOTE	},
198215116Sdes	{ "mput",	I_PUT,		LOCAL	},
199204917Sdes	{ "progress",	I_PROGRESS,	NOARGS	},
200204917Sdes	{ "put",	I_PUT,		LOCAL	},
201204917Sdes	{ "pwd",	I_PWD,		REMOTE	},
202204917Sdes	{ "quit",	I_QUIT,		NOARGS	},
203255767Sdes	{ "reget",	I_REGET,	REMOTE	},
204204917Sdes	{ "rename",	I_RENAME,	REMOTE	},
205204917Sdes	{ "rm",		I_RM,		REMOTE	},
206204917Sdes	{ "rmdir",	I_RMDIR,	REMOTE	},
207204917Sdes	{ "symlink",	I_SYMLINK,	REMOTE	},
208204917Sdes	{ "version",	I_VERSION,	NOARGS	},
209204917Sdes	{ "!",		I_SHELL,	NOARGS	},
210204917Sdes	{ "?",		I_HELP,		NOARGS	},
211204917Sdes	{ NULL,		-1,		-1	}
212126274Sdes};
213126274Sdes
214204917Sdesint interactive_loop(struct sftp_conn *, char *file1, char *file2);
215126274Sdes
216181111Sdes/* ARGSUSED */
21792555Sdesstatic void
218137015Sdeskillchild(int signo)
219137015Sdes{
220146998Sdes	if (sshpid > 1) {
221137015Sdes		kill(sshpid, SIGTERM);
222146998Sdes		waitpid(sshpid, NULL, 0);
223146998Sdes	}
224137015Sdes
225137015Sdes	_exit(1);
226137015Sdes}
227137015Sdes
228181111Sdes/* ARGSUSED */
229137015Sdesstatic void
230137015Sdescmd_interrupt(int signo)
231137015Sdes{
232137015Sdes	const char msg[] = "\rInterrupt  \n";
233146998Sdes	int olderrno = errno;
234137015Sdes
235255767Sdes	(void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
236137015Sdes	interrupted = 1;
237146998Sdes	errno = olderrno;
238137015Sdes}
239137015Sdes
240137015Sdesstatic void
241126274Sdeshelp(void)
242126274Sdes{
243192595Sdes	printf("Available commands:\n"
244192595Sdes	    "bye                                Quit sftp\n"
245192595Sdes	    "cd path                            Change remote directory to 'path'\n"
246192595Sdes	    "chgrp grp path                     Change group of file 'path' to 'grp'\n"
247192595Sdes	    "chmod mode path                    Change permissions of file 'path' to 'mode'\n"
248192595Sdes	    "chown own path                     Change owner of file 'path' to 'own'\n"
249192595Sdes	    "df [-hi] [path]                    Display statistics for current directory or\n"
250192595Sdes	    "                                   filesystem containing 'path'\n"
251192595Sdes	    "exit                               Quit sftp\n"
252204917Sdes	    "get [-Ppr] remote [local]          Download file\n"
253255767Sdes	    "reget remote [local]		Resume download file\n"
254192595Sdes	    "help                               Display this help text\n"
255192595Sdes	    "lcd path                           Change local directory to 'path'\n"
256192595Sdes	    "lls [ls-options [path]]            Display local directory listing\n"
257192595Sdes	    "lmkdir path                        Create local directory\n"
258221420Sdes	    "ln [-s] oldpath newpath            Link remote file (-s for symlink)\n"
259192595Sdes	    "lpwd                               Print local working directory\n"
260204917Sdes	    "ls [-1afhlnrSt] [path]             Display remote directory listing\n"
261192595Sdes	    "lumask umask                       Set local umask to 'umask'\n"
262192595Sdes	    "mkdir path                         Create remote directory\n"
263192595Sdes	    "progress                           Toggle display of progress meter\n"
264204917Sdes	    "put [-Ppr] local [remote]          Upload file\n"
265192595Sdes	    "pwd                                Display remote working directory\n"
266192595Sdes	    "quit                               Quit sftp\n"
267192595Sdes	    "rename oldpath newpath             Rename remote file\n"
268192595Sdes	    "rm path                            Delete remote file\n"
269192595Sdes	    "rmdir path                         Remove remote directory\n"
270192595Sdes	    "symlink oldpath newpath            Symlink remote file\n"
271192595Sdes	    "version                            Show SFTP version\n"
272192595Sdes	    "!command                           Execute 'command' in local shell\n"
273192595Sdes	    "!                                  Escape to local shell\n"
274192595Sdes	    "?                                  Synonym for help\n");
275126274Sdes}
276126274Sdes
277126274Sdesstatic void
278126274Sdeslocal_do_shell(const char *args)
279126274Sdes{
280126274Sdes	int status;
281126274Sdes	char *shell;
282126274Sdes	pid_t pid;
283126274Sdes
284126274Sdes	if (!*args)
285126274Sdes		args = NULL;
286126274Sdes
287221420Sdes	if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
288126274Sdes		shell = _PATH_BSHELL;
289126274Sdes
290126274Sdes	if ((pid = fork()) == -1)
291126274Sdes		fatal("Couldn't fork: %s", strerror(errno));
292126274Sdes
293126274Sdes	if (pid == 0) {
294126274Sdes		/* XXX: child has pipe fds to ssh subproc open - issue? */
295126274Sdes		if (args) {
296126274Sdes			debug3("Executing %s -c \"%s\"", shell, args);
297126274Sdes			execl(shell, shell, "-c", args, (char *)NULL);
298126274Sdes		} else {
299126274Sdes			debug3("Executing %s", shell);
300126274Sdes			execl(shell, shell, (char *)NULL);
301126274Sdes		}
302126274Sdes		fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
303126274Sdes		    strerror(errno));
304126274Sdes		_exit(1);
305126274Sdes	}
306126274Sdes	while (waitpid(pid, &status, 0) == -1)
307126274Sdes		if (errno != EINTR)
308126274Sdes			fatal("Couldn't wait for child: %s", strerror(errno));
309126274Sdes	if (!WIFEXITED(status))
310162852Sdes		error("Shell exited abnormally");
311126274Sdes	else if (WEXITSTATUS(status))
312126274Sdes		error("Shell exited with status %d", WEXITSTATUS(status));
313126274Sdes}
314126274Sdes
315126274Sdesstatic void
316126274Sdeslocal_do_ls(const char *args)
317126274Sdes{
318126274Sdes	if (!args || !*args)
319126274Sdes		local_do_shell(_PATH_LS);
320126274Sdes	else {
321126274Sdes		int len = strlen(_PATH_LS " ") + strlen(args) + 1;
322126274Sdes		char *buf = xmalloc(len);
323126274Sdes
324126274Sdes		/* XXX: quoting - rip quoting code from ftp? */
325126274Sdes		snprintf(buf, len, _PATH_LS " %s", args);
326126274Sdes		local_do_shell(buf);
327255767Sdes		free(buf);
328126274Sdes	}
329126274Sdes}
330126274Sdes
331126274Sdes/* Strip one path (usually the pwd) from the start of another */
332126274Sdesstatic char *
333126274Sdespath_strip(char *path, char *strip)
334126274Sdes{
335126274Sdes	size_t len;
336126274Sdes
337126274Sdes	if (strip == NULL)
338126274Sdes		return (xstrdup(path));
339126274Sdes
340126274Sdes	len = strlen(strip);
341146998Sdes	if (strncmp(path, strip, len) == 0) {
342126274Sdes		if (strip[len - 1] != '/' && path[len] == '/')
343126274Sdes			len++;
344126274Sdes		return (xstrdup(path + len));
345126274Sdes	}
346126274Sdes
347126274Sdes	return (xstrdup(path));
348126274Sdes}
349126274Sdes
350126274Sdesstatic char *
351126274Sdesmake_absolute(char *p, char *pwd)
352126274Sdes{
353137015Sdes	char *abs_str;
354126274Sdes
355126274Sdes	/* Derelativise */
356126274Sdes	if (p && p[0] != '/') {
357137015Sdes		abs_str = path_append(pwd, p);
358255767Sdes		free(p);
359137015Sdes		return(abs_str);
360126274Sdes	} else
361126274Sdes		return(p);
362126274Sdes}
363126274Sdes
364126274Sdesstatic int
365255767Sdesparse_getput_flags(const char *cmd, char **argv, int argc,
366262566Sdes    int *aflag, int *fflag, int *pflag, int *rflag)
367126274Sdes{
368181111Sdes	extern int opterr, optind, optopt, optreset;
369181111Sdes	int ch;
370126274Sdes
371181111Sdes	optind = optreset = 1;
372181111Sdes	opterr = 0;
373181111Sdes
374262566Sdes	*aflag = *fflag = *rflag = *pflag = 0;
375262566Sdes	while ((ch = getopt(argc, argv, "afPpRr")) != -1) {
376181111Sdes		switch (ch) {
377255767Sdes		case 'a':
378255767Sdes			*aflag = 1;
379255767Sdes			break;
380262566Sdes		case 'f':
381262566Sdes			*fflag = 1;
382262566Sdes			break;
383126274Sdes		case 'p':
384126274Sdes		case 'P':
385126274Sdes			*pflag = 1;
386126274Sdes			break;
387204917Sdes		case 'r':
388204917Sdes		case 'R':
389204917Sdes			*rflag = 1;
390204917Sdes			break;
391126274Sdes		default:
392181111Sdes			error("%s: Invalid flag -%c", cmd, optopt);
393181111Sdes			return -1;
394126274Sdes		}
395126274Sdes	}
396126274Sdes
397181111Sdes	return optind;
398126274Sdes}
399126274Sdes
400126274Sdesstatic int
401221420Sdesparse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
402221420Sdes{
403221420Sdes	extern int opterr, optind, optopt, optreset;
404221420Sdes	int ch;
405221420Sdes
406221420Sdes	optind = optreset = 1;
407221420Sdes	opterr = 0;
408221420Sdes
409221420Sdes	*sflag = 0;
410221420Sdes	while ((ch = getopt(argc, argv, "s")) != -1) {
411221420Sdes		switch (ch) {
412221420Sdes		case 's':
413221420Sdes			*sflag = 1;
414221420Sdes			break;
415221420Sdes		default:
416221420Sdes			error("%s: Invalid flag -%c", cmd, optopt);
417221420Sdes			return -1;
418221420Sdes		}
419221420Sdes	}
420221420Sdes
421221420Sdes	return optind;
422221420Sdes}
423221420Sdes
424221420Sdesstatic int
425262566Sdesparse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
426262566Sdes{
427262566Sdes	extern int opterr, optind, optopt, optreset;
428262566Sdes	int ch;
429262566Sdes
430262566Sdes	optind = optreset = 1;
431262566Sdes	opterr = 0;
432262566Sdes
433262566Sdes	*lflag = 0;
434262566Sdes	while ((ch = getopt(argc, argv, "l")) != -1) {
435262566Sdes		switch (ch) {
436262566Sdes		case 'l':
437262566Sdes			*lflag = 1;
438262566Sdes			break;
439262566Sdes		default:
440262566Sdes			error("%s: Invalid flag -%c", cmd, optopt);
441262566Sdes			return -1;
442262566Sdes		}
443262566Sdes	}
444262566Sdes
445262566Sdes	return optind;
446262566Sdes}
447262566Sdes
448262566Sdesstatic int
449181111Sdesparse_ls_flags(char **argv, int argc, int *lflag)
450126274Sdes{
451181111Sdes	extern int opterr, optind, optopt, optreset;
452181111Sdes	int ch;
453126274Sdes
454181111Sdes	optind = optreset = 1;
455181111Sdes	opterr = 0;
456181111Sdes
457137015Sdes	*lflag = LS_NAME_SORT;
458204917Sdes	while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
459181111Sdes		switch (ch) {
460181111Sdes		case '1':
461181111Sdes			*lflag &= ~VIEW_FLAGS;
462181111Sdes			*lflag |= LS_SHORT_VIEW;
463181111Sdes			break;
464181111Sdes		case 'S':
465181111Sdes			*lflag &= ~SORT_FLAGS;
466181111Sdes			*lflag |= LS_SIZE_SORT;
467181111Sdes			break;
468181111Sdes		case 'a':
469181111Sdes			*lflag |= LS_SHOW_ALL;
470181111Sdes			break;
471181111Sdes		case 'f':
472181111Sdes			*lflag &= ~SORT_FLAGS;
473181111Sdes			break;
474204917Sdes		case 'h':
475204917Sdes			*lflag |= LS_SI_UNITS;
476204917Sdes			break;
477181111Sdes		case 'l':
478204917Sdes			*lflag &= ~LS_SHORT_VIEW;
479181111Sdes			*lflag |= LS_LONG_VIEW;
480181111Sdes			break;
481181111Sdes		case 'n':
482204917Sdes			*lflag &= ~LS_SHORT_VIEW;
483181111Sdes			*lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
484181111Sdes			break;
485181111Sdes		case 'r':
486181111Sdes			*lflag |= LS_REVERSE_SORT;
487181111Sdes			break;
488181111Sdes		case 't':
489181111Sdes			*lflag &= ~SORT_FLAGS;
490181111Sdes			*lflag |= LS_TIME_SORT;
491181111Sdes			break;
492181111Sdes		default:
493181111Sdes			error("ls: Invalid flag -%c", optopt);
494181111Sdes			return -1;
495126274Sdes		}
496126274Sdes	}
497126274Sdes
498181111Sdes	return optind;
499126274Sdes}
500126274Sdes
501126274Sdesstatic int
502181111Sdesparse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
503126274Sdes{
504181111Sdes	extern int opterr, optind, optopt, optreset;
505181111Sdes	int ch;
506126274Sdes
507181111Sdes	optind = optreset = 1;
508181111Sdes	opterr = 0;
509126274Sdes
510181111Sdes	*hflag = *iflag = 0;
511181111Sdes	while ((ch = getopt(argc, argv, "hi")) != -1) {
512181111Sdes		switch (ch) {
513181111Sdes		case 'h':
514181111Sdes			*hflag = 1;
515181111Sdes			break;
516181111Sdes		case 'i':
517181111Sdes			*iflag = 1;
518181111Sdes			break;
519181111Sdes		default:
520181111Sdes			error("%s: Invalid flag -%c", cmd, optopt);
521181111Sdes			return -1;
522126274Sdes		}
523126274Sdes	}
524126274Sdes
525181111Sdes	return optind;
526126274Sdes}
527126274Sdes
528126274Sdesstatic int
529262566Sdesparse_no_flags(const char *cmd, char **argv, int argc)
530262566Sdes{
531262566Sdes	extern int opterr, optind, optopt, optreset;
532262566Sdes	int ch;
533262566Sdes
534262566Sdes	optind = optreset = 1;
535262566Sdes	opterr = 0;
536262566Sdes
537262566Sdes	while ((ch = getopt(argc, argv, "")) != -1) {
538262566Sdes		switch (ch) {
539262566Sdes		default:
540262566Sdes			error("%s: Invalid flag -%c", cmd, optopt);
541262566Sdes			return -1;
542262566Sdes		}
543262566Sdes	}
544262566Sdes
545262566Sdes	return optind;
546262566Sdes}
547262566Sdes
548262566Sdesstatic int
549126274Sdesis_dir(char *path)
550126274Sdes{
551126274Sdes	struct stat sb;
552126274Sdes
553126274Sdes	/* XXX: report errors? */
554126274Sdes	if (stat(path, &sb) == -1)
555126274Sdes		return(0);
556126274Sdes
557162852Sdes	return(S_ISDIR(sb.st_mode));
558126274Sdes}
559126274Sdes
560126274Sdesstatic int
561126274Sdesremote_is_dir(struct sftp_conn *conn, char *path)
562126274Sdes{
563126274Sdes	Attrib *a;
564126274Sdes
565126274Sdes	/* XXX: report errors? */
566126274Sdes	if ((a = do_stat(conn, path, 1)) == NULL)
567126274Sdes		return(0);
568126274Sdes	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
569126274Sdes		return(0);
570162852Sdes	return(S_ISDIR(a->perm));
571126274Sdes}
572126274Sdes
573204917Sdes/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
574126274Sdesstatic int
575204917Sdespathname_is_dir(char *pathname)
576126274Sdes{
577204917Sdes	size_t l = strlen(pathname);
578204917Sdes
579204917Sdes	return l > 0 && pathname[l - 1] == '/';
580204917Sdes}
581204917Sdes
582204917Sdesstatic int
583204917Sdesprocess_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
584262566Sdes    int pflag, int rflag, int resume, int fflag)
585204917Sdes{
586126274Sdes	char *abs_src = NULL;
587126274Sdes	char *abs_dst = NULL;
588126274Sdes	glob_t g;
589204917Sdes	char *filename, *tmp=NULL;
590204917Sdes	int i, err = 0;
591126274Sdes
592126274Sdes	abs_src = xstrdup(src);
593126274Sdes	abs_src = make_absolute(abs_src, pwd);
594204917Sdes	memset(&g, 0, sizeof(g));
595126274Sdes
596126274Sdes	debug3("Looking up %s", abs_src);
597204917Sdes	if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
598126274Sdes		error("File \"%s\" not found.", abs_src);
599126274Sdes		err = -1;
600126274Sdes		goto out;
601126274Sdes	}
602126274Sdes
603204917Sdes	/*
604204917Sdes	 * If multiple matches then dst must be a directory or
605204917Sdes	 * unspecified.
606204917Sdes	 */
607204917Sdes	if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
608204917Sdes		error("Multiple source paths, but destination "
609204917Sdes		    "\"%s\" is not a directory", dst);
610126274Sdes		err = -1;
611126274Sdes		goto out;
612126274Sdes	}
613126274Sdes
614137015Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
615204917Sdes		tmp = xstrdup(g.gl_pathv[i]);
616204917Sdes		if ((filename = basename(tmp)) == NULL) {
617204917Sdes			error("basename %s: %s", tmp, strerror(errno));
618255767Sdes			free(tmp);
619126274Sdes			err = -1;
620126274Sdes			goto out;
621126274Sdes		}
622126274Sdes
623126274Sdes		if (g.gl_matchc == 1 && dst) {
624126274Sdes			if (is_dir(dst)) {
625204917Sdes				abs_dst = path_append(dst, filename);
626204917Sdes			} else {
627126274Sdes				abs_dst = xstrdup(dst);
628204917Sdes			}
629126274Sdes		} else if (dst) {
630204917Sdes			abs_dst = path_append(dst, filename);
631204917Sdes		} else {
632204917Sdes			abs_dst = xstrdup(filename);
633204917Sdes		}
634255767Sdes		free(tmp);
635126274Sdes
636255767Sdes		resume |= global_aflag;
637255767Sdes		if (!quiet && resume)
638255767Sdes			printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
639255767Sdes		else if (!quiet && !resume)
640255767Sdes			printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
641204917Sdes		if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
642255767Sdes			if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
643262566Sdes			    pflag || global_pflag, 1, resume,
644262566Sdes			    fflag || global_fflag) == -1)
645204917Sdes				err = -1;
646204917Sdes		} else {
647204917Sdes			if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
648262566Sdes			    pflag || global_pflag, resume,
649262566Sdes			    fflag || global_fflag) == -1)
650204917Sdes				err = -1;
651204917Sdes		}
652255767Sdes		free(abs_dst);
653126274Sdes		abs_dst = NULL;
654126274Sdes	}
655126274Sdes
656126274Sdesout:
657255767Sdes	free(abs_src);
658126274Sdes	globfree(&g);
659126274Sdes	return(err);
660126274Sdes}
661126274Sdes
662126274Sdesstatic int
663204917Sdesprocess_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
664262566Sdes    int pflag, int rflag, int fflag)
665126274Sdes{
666126274Sdes	char *tmp_dst = NULL;
667126274Sdes	char *abs_dst = NULL;
668204917Sdes	char *tmp = NULL, *filename = NULL;
669126274Sdes	glob_t g;
670126274Sdes	int err = 0;
671204917Sdes	int i, dst_is_dir = 1;
672181111Sdes	struct stat sb;
673126274Sdes
674126274Sdes	if (dst) {
675126274Sdes		tmp_dst = xstrdup(dst);
676126274Sdes		tmp_dst = make_absolute(tmp_dst, pwd);
677126274Sdes	}
678126274Sdes
679126274Sdes	memset(&g, 0, sizeof(g));
680126274Sdes	debug3("Looking up %s", src);
681204917Sdes	if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
682126274Sdes		error("File \"%s\" not found.", src);
683126274Sdes		err = -1;
684126274Sdes		goto out;
685126274Sdes	}
686126274Sdes
687204917Sdes	/* If we aren't fetching to pwd then stash this status for later */
688204917Sdes	if (tmp_dst != NULL)
689204917Sdes		dst_is_dir = remote_is_dir(conn, tmp_dst);
690204917Sdes
691126274Sdes	/* If multiple matches, dst may be directory or unspecified */
692204917Sdes	if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
693204917Sdes		error("Multiple paths match, but destination "
694204917Sdes		    "\"%s\" is not a directory", tmp_dst);
695126274Sdes		err = -1;
696126274Sdes		goto out;
697126274Sdes	}
698126274Sdes
699137015Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
700181111Sdes		if (stat(g.gl_pathv[i], &sb) == -1) {
701181111Sdes			err = -1;
702181111Sdes			error("stat %s: %s", g.gl_pathv[i], strerror(errno));
703181111Sdes			continue;
704181111Sdes		}
705262566Sdes
706204917Sdes		tmp = xstrdup(g.gl_pathv[i]);
707204917Sdes		if ((filename = basename(tmp)) == NULL) {
708204917Sdes			error("basename %s: %s", tmp, strerror(errno));
709255767Sdes			free(tmp);
710126274Sdes			err = -1;
711126274Sdes			goto out;
712126274Sdes		}
713126274Sdes
714126274Sdes		if (g.gl_matchc == 1 && tmp_dst) {
715126274Sdes			/* If directory specified, append filename */
716204917Sdes			if (dst_is_dir)
717204917Sdes				abs_dst = path_append(tmp_dst, filename);
718204917Sdes			else
719126274Sdes				abs_dst = xstrdup(tmp_dst);
720126274Sdes		} else if (tmp_dst) {
721204917Sdes			abs_dst = path_append(tmp_dst, filename);
722204917Sdes		} else {
723204917Sdes			abs_dst = make_absolute(xstrdup(filename), pwd);
724204917Sdes		}
725255767Sdes		free(tmp);
726126274Sdes
727255767Sdes		if (!quiet)
728255767Sdes			printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
729204917Sdes		if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
730204917Sdes			if (upload_dir(conn, g.gl_pathv[i], abs_dst,
731262566Sdes			    pflag || global_pflag, 1,
732262566Sdes			    fflag || global_fflag) == -1)
733204917Sdes				err = -1;
734204917Sdes		} else {
735204917Sdes			if (do_upload(conn, g.gl_pathv[i], abs_dst,
736262566Sdes			    pflag || global_pflag,
737262566Sdes			    fflag || global_fflag) == -1)
738204917Sdes				err = -1;
739204917Sdes		}
740126274Sdes	}
741126274Sdes
742126274Sdesout:
743255767Sdes	free(abs_dst);
744255767Sdes	free(tmp_dst);
745126274Sdes	globfree(&g);
746126274Sdes	return(err);
747126274Sdes}
748126274Sdes
749126274Sdesstatic int
750126274Sdessdirent_comp(const void *aa, const void *bb)
751126274Sdes{
752126274Sdes	SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
753126274Sdes	SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
754137015Sdes	int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
755126274Sdes
756137015Sdes#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
757137015Sdes	if (sort_flag & LS_NAME_SORT)
758137015Sdes		return (rmul * strcmp(a->filename, b->filename));
759137015Sdes	else if (sort_flag & LS_TIME_SORT)
760137015Sdes		return (rmul * NCMP(a->a.mtime, b->a.mtime));
761137015Sdes	else if (sort_flag & LS_SIZE_SORT)
762137015Sdes		return (rmul * NCMP(a->a.size, b->a.size));
763137015Sdes
764137015Sdes	fatal("Unknown ls sort type");
765126274Sdes}
766126274Sdes
767126274Sdes/* sftp ls.1 replacement for directories */
768126274Sdesstatic int
769126274Sdesdo_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
770126274Sdes{
771149749Sdes	int n;
772149749Sdes	u_int c = 1, colspace = 0, columns = 1;
773126274Sdes	SFTP_DIRENT **d;
774126274Sdes
775126274Sdes	if ((n = do_readdir(conn, path, &d)) != 0)
776126274Sdes		return (n);
777126274Sdes
778137015Sdes	if (!(lflag & LS_SHORT_VIEW)) {
779149749Sdes		u_int m = 0, width = 80;
780126274Sdes		struct winsize ws;
781126274Sdes		char *tmp;
782126274Sdes
783126274Sdes		/* Count entries for sort and find longest filename */
784137015Sdes		for (n = 0; d[n] != NULL; n++) {
785137015Sdes			if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
786137015Sdes				m = MAX(m, strlen(d[n]->filename));
787137015Sdes		}
788126274Sdes
789126274Sdes		/* Add any subpath that also needs to be counted */
790126274Sdes		tmp = path_strip(path, strip_path);
791126274Sdes		m += strlen(tmp);
792255767Sdes		free(tmp);
793126274Sdes
794126274Sdes		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
795126274Sdes			width = ws.ws_col;
796126274Sdes
797126274Sdes		columns = width / (m + 2);
798126274Sdes		columns = MAX(columns, 1);
799126274Sdes		colspace = width / columns;
800126274Sdes		colspace = MIN(colspace, width);
801126274Sdes	}
802126274Sdes
803137015Sdes	if (lflag & SORT_FLAGS) {
804157016Sdes		for (n = 0; d[n] != NULL; n++)
805157016Sdes			;	/* count entries */
806137015Sdes		sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
807137015Sdes		qsort(d, n, sizeof(*d), sdirent_comp);
808137015Sdes	}
809126274Sdes
810137015Sdes	for (n = 0; d[n] != NULL && !interrupted; n++) {
811126274Sdes		char *tmp, *fname;
812126274Sdes
813137015Sdes		if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
814137015Sdes			continue;
815137015Sdes
816126274Sdes		tmp = path_append(path, d[n]->filename);
817126274Sdes		fname = path_strip(tmp, strip_path);
818255767Sdes		free(tmp);
819126274Sdes
820137015Sdes		if (lflag & LS_LONG_VIEW) {
821204917Sdes			if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
822137015Sdes				char *lname;
823137015Sdes				struct stat sb;
824126274Sdes
825137015Sdes				memset(&sb, 0, sizeof(sb));
826137015Sdes				attrib_to_stat(&d[n]->a, &sb);
827204917Sdes				lname = ls_file(fname, &sb, 1,
828204917Sdes				    (lflag & LS_SI_UNITS));
829137015Sdes				printf("%s\n", lname);
830255767Sdes				free(lname);
831137015Sdes			} else
832137015Sdes				printf("%s\n", d[n]->longname);
833126274Sdes		} else {
834126274Sdes			printf("%-*s", colspace, fname);
835126274Sdes			if (c >= columns) {
836126274Sdes				printf("\n");
837126274Sdes				c = 1;
838126274Sdes			} else
839126274Sdes				c++;
840126274Sdes		}
841126274Sdes
842255767Sdes		free(fname);
843126274Sdes	}
844126274Sdes
845137015Sdes	if (!(lflag & LS_LONG_VIEW) && (c != 1))
846126274Sdes		printf("\n");
847126274Sdes
848126274Sdes	free_sftp_dirents(d);
849126274Sdes	return (0);
850126274Sdes}
851126274Sdes
852126274Sdes/* sftp ls.1 replacement which handles path globs */
853126274Sdesstatic int
854126274Sdesdo_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
855126274Sdes    int lflag)
856126274Sdes{
857221420Sdes	char *fname, *lname;
858126274Sdes	glob_t g;
859221420Sdes	int err;
860221420Sdes	struct winsize ws;
861221420Sdes	u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
862126274Sdes
863126274Sdes	memset(&g, 0, sizeof(g));
864126274Sdes
865221420Sdes	if (remote_glob(conn, path,
866240075Sdes	    GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
867240075Sdes	    NULL, &g) ||
868221420Sdes	    (g.gl_pathc && !g.gl_matchc)) {
869146998Sdes		if (g.gl_pathc)
870146998Sdes			globfree(&g);
871126274Sdes		error("Can't ls: \"%s\" not found", path);
872221420Sdes		return -1;
873126274Sdes	}
874126274Sdes
875137015Sdes	if (interrupted)
876137015Sdes		goto out;
877137015Sdes
878126274Sdes	/*
879146998Sdes	 * If the glob returns a single match and it is a directory,
880146998Sdes	 * then just list its contents.
881126274Sdes	 */
882221420Sdes	if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
883221420Sdes	    S_ISDIR(g.gl_statv[0]->st_mode)) {
884221420Sdes		err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
885221420Sdes		globfree(&g);
886221420Sdes		return err;
887126274Sdes	}
888126274Sdes
889221420Sdes	if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
890221420Sdes		width = ws.ws_col;
891221420Sdes
892137015Sdes	if (!(lflag & LS_SHORT_VIEW)) {
893126274Sdes		/* Count entries for sort and find longest filename */
894126274Sdes		for (i = 0; g.gl_pathv[i]; i++)
895126274Sdes			m = MAX(m, strlen(g.gl_pathv[i]));
896126274Sdes
897126274Sdes		columns = width / (m + 2);
898126274Sdes		columns = MAX(columns, 1);
899126274Sdes		colspace = width / columns;
900126274Sdes	}
901126274Sdes
902240075Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
903126274Sdes		fname = path_strip(g.gl_pathv[i], strip_path);
904137015Sdes		if (lflag & LS_LONG_VIEW) {
905221420Sdes			if (g.gl_statv[i] == NULL) {
906221420Sdes				error("no stat information for %s", fname);
907221420Sdes				continue;
908221420Sdes			}
909221420Sdes			lname = ls_file(fname, g.gl_statv[i], 1,
910221420Sdes			    (lflag & LS_SI_UNITS));
911126274Sdes			printf("%s\n", lname);
912255767Sdes			free(lname);
913126274Sdes		} else {
914126274Sdes			printf("%-*s", colspace, fname);
915126274Sdes			if (c >= columns) {
916126274Sdes				printf("\n");
917126274Sdes				c = 1;
918126274Sdes			} else
919126274Sdes				c++;
920126274Sdes		}
921255767Sdes		free(fname);
922126274Sdes	}
923126274Sdes
924137015Sdes	if (!(lflag & LS_LONG_VIEW) && (c != 1))
925126274Sdes		printf("\n");
926126274Sdes
927137015Sdes out:
928126274Sdes	if (g.gl_pathc)
929126274Sdes		globfree(&g);
930126274Sdes
931221420Sdes	return 0;
932126274Sdes}
933126274Sdes
934126274Sdesstatic int
935181111Sdesdo_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
936181111Sdes{
937181111Sdes	struct sftp_statvfs st;
938181111Sdes	char s_used[FMT_SCALED_STRSIZE];
939181111Sdes	char s_avail[FMT_SCALED_STRSIZE];
940181111Sdes	char s_root[FMT_SCALED_STRSIZE];
941181111Sdes	char s_total[FMT_SCALED_STRSIZE];
942204917Sdes	unsigned long long ffree;
943181111Sdes
944181111Sdes	if (do_statvfs(conn, path, &st, 1) == -1)
945181111Sdes		return -1;
946181111Sdes	if (iflag) {
947204917Sdes		ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
948181111Sdes		printf("     Inodes        Used       Avail      "
949181111Sdes		    "(root)    %%Capacity\n");
950181111Sdes		printf("%11llu %11llu %11llu %11llu         %3llu%%\n",
951181111Sdes		    (unsigned long long)st.f_files,
952181111Sdes		    (unsigned long long)(st.f_files - st.f_ffree),
953181111Sdes		    (unsigned long long)st.f_favail,
954204917Sdes		    (unsigned long long)st.f_ffree, ffree);
955181111Sdes	} else if (hflag) {
956181111Sdes		strlcpy(s_used, "error", sizeof(s_used));
957181111Sdes		strlcpy(s_avail, "error", sizeof(s_avail));
958181111Sdes		strlcpy(s_root, "error", sizeof(s_root));
959181111Sdes		strlcpy(s_total, "error", sizeof(s_total));
960181111Sdes		fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
961181111Sdes		fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
962181111Sdes		fmt_scaled(st.f_bfree * st.f_frsize, s_root);
963181111Sdes		fmt_scaled(st.f_blocks * st.f_frsize, s_total);
964181111Sdes		printf("    Size     Used    Avail   (root)    %%Capacity\n");
965181111Sdes		printf("%7sB %7sB %7sB %7sB         %3llu%%\n",
966181111Sdes		    s_total, s_used, s_avail, s_root,
967181111Sdes		    (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
968181111Sdes		    st.f_blocks));
969181111Sdes	} else {
970181111Sdes		printf("        Size         Used        Avail       "
971181111Sdes		    "(root)    %%Capacity\n");
972181111Sdes		printf("%12llu %12llu %12llu %12llu         %3llu%%\n",
973181111Sdes		    (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
974181111Sdes		    (unsigned long long)(st.f_frsize *
975181111Sdes		    (st.f_blocks - st.f_bfree) / 1024),
976181111Sdes		    (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
977181111Sdes		    (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
978181111Sdes		    (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
979181111Sdes		    st.f_blocks));
980181111Sdes	}
981181111Sdes	return 0;
982181111Sdes}
983181111Sdes
984181111Sdes/*
985181111Sdes * Undo escaping of glob sequences in place. Used to undo extra escaping
986181111Sdes * applied in makeargv() when the string is destined for a function that
987181111Sdes * does not glob it.
988181111Sdes */
989181111Sdesstatic void
990181111Sdesundo_glob_escape(char *s)
991181111Sdes{
992181111Sdes	size_t i, j;
993181111Sdes
994181111Sdes	for (i = j = 0;;) {
995181111Sdes		if (s[i] == '\0') {
996181111Sdes			s[j] = '\0';
997181111Sdes			return;
998181111Sdes		}
999181111Sdes		if (s[i] != '\\') {
1000181111Sdes			s[j++] = s[i++];
1001181111Sdes			continue;
1002181111Sdes		}
1003181111Sdes		/* s[i] == '\\' */
1004181111Sdes		++i;
1005181111Sdes		switch (s[i]) {
1006181111Sdes		case '?':
1007181111Sdes		case '[':
1008181111Sdes		case '*':
1009181111Sdes		case '\\':
1010181111Sdes			s[j++] = s[i++];
1011181111Sdes			break;
1012181111Sdes		case '\0':
1013181111Sdes			s[j++] = '\\';
1014181111Sdes			s[j] = '\0';
1015181111Sdes			return;
1016181111Sdes		default:
1017181111Sdes			s[j++] = '\\';
1018181111Sdes			s[j++] = s[i++];
1019181111Sdes			break;
1020181111Sdes		}
1021181111Sdes	}
1022181111Sdes}
1023181111Sdes
1024181111Sdes/*
1025181111Sdes * Split a string into an argument vector using sh(1)-style quoting,
1026181111Sdes * comment and escaping rules, but with some tweaks to handle glob(3)
1027181111Sdes * wildcards.
1028204917Sdes * The "sloppy" flag allows for recovery from missing terminating quote, for
1029204917Sdes * use in parsing incomplete commandlines during tab autocompletion.
1030204917Sdes *
1031181111Sdes * Returns NULL on error or a NULL-terminated array of arguments.
1032204917Sdes *
1033204917Sdes * If "lastquote" is not NULL, the quoting character used for the last
1034204917Sdes * argument is placed in *lastquote ("\0", "'" or "\"").
1035262566Sdes *
1036204917Sdes * If "terminated" is not NULL, *terminated will be set to 1 when the
1037204917Sdes * last argument's quote has been properly terminated or 0 otherwise.
1038204917Sdes * This parameter is only of use if "sloppy" is set.
1039181111Sdes */
1040181111Sdes#define MAXARGS 	128
1041181111Sdes#define MAXARGLEN	8192
1042181111Sdesstatic char **
1043204917Sdesmakeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1044204917Sdes    u_int *terminated)
1045181111Sdes{
1046181111Sdes	int argc, quot;
1047181111Sdes	size_t i, j;
1048181111Sdes	static char argvs[MAXARGLEN];
1049181111Sdes	static char *argv[MAXARGS + 1];
1050181111Sdes	enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1051181111Sdes
1052181111Sdes	*argcp = argc = 0;
1053181111Sdes	if (strlen(arg) > sizeof(argvs) - 1) {
1054181111Sdes args_too_longs:
1055181111Sdes		error("string too long");
1056181111Sdes		return NULL;
1057181111Sdes	}
1058204917Sdes	if (terminated != NULL)
1059204917Sdes		*terminated = 1;
1060204917Sdes	if (lastquote != NULL)
1061204917Sdes		*lastquote = '\0';
1062181111Sdes	state = MA_START;
1063181111Sdes	i = j = 0;
1064181111Sdes	for (;;) {
1065248619Sdes		if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
1066248619Sdes			error("Too many arguments.");
1067248619Sdes			return NULL;
1068248619Sdes		}
1069262566Sdes		if (isspace((unsigned char)arg[i])) {
1070181111Sdes			if (state == MA_UNQUOTED) {
1071181111Sdes				/* Terminate current argument */
1072181111Sdes				argvs[j++] = '\0';
1073181111Sdes				argc++;
1074181111Sdes				state = MA_START;
1075181111Sdes			} else if (state != MA_START)
1076181111Sdes				argvs[j++] = arg[i];
1077181111Sdes		} else if (arg[i] == '"' || arg[i] == '\'') {
1078181111Sdes			q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1079181111Sdes			if (state == MA_START) {
1080181111Sdes				argv[argc] = argvs + j;
1081181111Sdes				state = q;
1082204917Sdes				if (lastquote != NULL)
1083204917Sdes					*lastquote = arg[i];
1084262566Sdes			} else if (state == MA_UNQUOTED)
1085181111Sdes				state = q;
1086181111Sdes			else if (state == q)
1087181111Sdes				state = MA_UNQUOTED;
1088181111Sdes			else
1089181111Sdes				argvs[j++] = arg[i];
1090181111Sdes		} else if (arg[i] == '\\') {
1091181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE) {
1092181111Sdes				quot = state == MA_SQUOTE ? '\'' : '"';
1093181111Sdes				/* Unescape quote we are in */
1094181111Sdes				/* XXX support \n and friends? */
1095181111Sdes				if (arg[i + 1] == quot) {
1096181111Sdes					i++;
1097181111Sdes					argvs[j++] = arg[i];
1098181111Sdes				} else if (arg[i + 1] == '?' ||
1099181111Sdes				    arg[i + 1] == '[' || arg[i + 1] == '*') {
1100181111Sdes					/*
1101181111Sdes					 * Special case for sftp: append
1102181111Sdes					 * double-escaped glob sequence -
1103181111Sdes					 * glob will undo one level of
1104181111Sdes					 * escaping. NB. string can grow here.
1105181111Sdes					 */
1106181111Sdes					if (j >= sizeof(argvs) - 5)
1107181111Sdes						goto args_too_longs;
1108181111Sdes					argvs[j++] = '\\';
1109181111Sdes					argvs[j++] = arg[i++];
1110181111Sdes					argvs[j++] = '\\';
1111181111Sdes					argvs[j++] = arg[i];
1112181111Sdes				} else {
1113181111Sdes					argvs[j++] = arg[i++];
1114181111Sdes					argvs[j++] = arg[i];
1115181111Sdes				}
1116181111Sdes			} else {
1117181111Sdes				if (state == MA_START) {
1118181111Sdes					argv[argc] = argvs + j;
1119181111Sdes					state = MA_UNQUOTED;
1120204917Sdes					if (lastquote != NULL)
1121204917Sdes						*lastquote = '\0';
1122181111Sdes				}
1123181111Sdes				if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1124181111Sdes				    arg[i + 1] == '*' || arg[i + 1] == '\\') {
1125181111Sdes					/*
1126181111Sdes					 * Special case for sftp: append
1127181111Sdes					 * escaped glob sequence -
1128181111Sdes					 * glob will undo one level of
1129181111Sdes					 * escaping.
1130181111Sdes					 */
1131181111Sdes					argvs[j++] = arg[i++];
1132181111Sdes					argvs[j++] = arg[i];
1133181111Sdes				} else {
1134181111Sdes					/* Unescape everything */
1135181111Sdes					/* XXX support \n and friends? */
1136181111Sdes					i++;
1137181111Sdes					argvs[j++] = arg[i];
1138181111Sdes				}
1139181111Sdes			}
1140181111Sdes		} else if (arg[i] == '#') {
1141181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE)
1142181111Sdes				argvs[j++] = arg[i];
1143181111Sdes			else
1144181111Sdes				goto string_done;
1145181111Sdes		} else if (arg[i] == '\0') {
1146181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE) {
1147204917Sdes				if (sloppy) {
1148204917Sdes					state = MA_UNQUOTED;
1149204917Sdes					if (terminated != NULL)
1150204917Sdes						*terminated = 0;
1151204917Sdes					goto string_done;
1152204917Sdes				}
1153181111Sdes				error("Unterminated quoted argument");
1154181111Sdes				return NULL;
1155181111Sdes			}
1156181111Sdes string_done:
1157181111Sdes			if (state == MA_UNQUOTED) {
1158181111Sdes				argvs[j++] = '\0';
1159181111Sdes				argc++;
1160181111Sdes			}
1161181111Sdes			break;
1162181111Sdes		} else {
1163181111Sdes			if (state == MA_START) {
1164181111Sdes				argv[argc] = argvs + j;
1165181111Sdes				state = MA_UNQUOTED;
1166204917Sdes				if (lastquote != NULL)
1167204917Sdes					*lastquote = '\0';
1168181111Sdes			}
1169181111Sdes			if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1170181111Sdes			    (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1171181111Sdes				/*
1172181111Sdes				 * Special case for sftp: escape quoted
1173181111Sdes				 * glob(3) wildcards. NB. string can grow
1174181111Sdes				 * here.
1175181111Sdes				 */
1176181111Sdes				if (j >= sizeof(argvs) - 3)
1177181111Sdes					goto args_too_longs;
1178181111Sdes				argvs[j++] = '\\';
1179181111Sdes				argvs[j++] = arg[i];
1180181111Sdes			} else
1181181111Sdes				argvs[j++] = arg[i];
1182181111Sdes		}
1183181111Sdes		i++;
1184181111Sdes	}
1185181111Sdes	*argcp = argc;
1186181111Sdes	return argv;
1187181111Sdes}
1188181111Sdes
1189181111Sdesstatic int
1190262566Sdesparse_args(const char **cpp, int *ignore_errors, int *aflag, int *fflag,
1191262566Sdes    int *hflag, int *iflag, int *lflag, int *pflag, int *rflag, int *sflag,
1192262566Sdes    unsigned long *n_arg, char **path1, char **path2)
1193126274Sdes{
1194126274Sdes	const char *cmd, *cp = *cpp;
1195181111Sdes	char *cp2, **argv;
1196126274Sdes	int base = 0;
1197126274Sdes	long l;
1198181111Sdes	int i, cmdnum, optidx, argc;
1199126274Sdes
1200126274Sdes	/* Skip leading whitespace */
1201126274Sdes	cp = cp + strspn(cp, WHITESPACE);
1202126274Sdes
1203126274Sdes	/* Check for leading '-' (disable error processing) */
1204262566Sdes	*ignore_errors = 0;
1205126274Sdes	if (*cp == '-') {
1206262566Sdes		*ignore_errors = 1;
1207126274Sdes		cp++;
1208204917Sdes		cp = cp + strspn(cp, WHITESPACE);
1209126274Sdes	}
1210126274Sdes
1211204917Sdes	/* Ignore blank lines and lines which begin with comment '#' char */
1212204917Sdes	if (*cp == '\0' || *cp == '#')
1213204917Sdes		return (0);
1214204917Sdes
1215204917Sdes	if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
1216181111Sdes		return -1;
1217181111Sdes
1218126274Sdes	/* Figure out which command we have */
1219181111Sdes	for (i = 0; cmds[i].c != NULL; i++) {
1220248619Sdes		if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
1221126274Sdes			break;
1222126274Sdes	}
1223126274Sdes	cmdnum = cmds[i].n;
1224126274Sdes	cmd = cmds[i].c;
1225126274Sdes
1226126274Sdes	/* Special case */
1227126274Sdes	if (*cp == '!') {
1228126274Sdes		cp++;
1229126274Sdes		cmdnum = I_SHELL;
1230126274Sdes	} else if (cmdnum == -1) {
1231126274Sdes		error("Invalid command.");
1232181111Sdes		return -1;
1233126274Sdes	}
1234126274Sdes
1235126274Sdes	/* Get arguments and parse flags */
1236262566Sdes	*aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1237262566Sdes	*rflag = *sflag = 0;
1238126274Sdes	*path1 = *path2 = NULL;
1239181111Sdes	optidx = 1;
1240126274Sdes	switch (cmdnum) {
1241126274Sdes	case I_GET:
1242255767Sdes	case I_REGET:
1243126274Sdes	case I_PUT:
1244221420Sdes		if ((optidx = parse_getput_flags(cmd, argv, argc,
1245262566Sdes		    aflag, fflag, pflag, rflag)) == -1)
1246181111Sdes			return -1;
1247126274Sdes		/* Get first pathname (mandatory) */
1248181111Sdes		if (argc - optidx < 1) {
1249126274Sdes			error("You must specify at least one path after a "
1250126274Sdes			    "%s command.", cmd);
1251181111Sdes			return -1;
1252126274Sdes		}
1253181111Sdes		*path1 = xstrdup(argv[optidx]);
1254181111Sdes		/* Get second pathname (optional) */
1255181111Sdes		if (argc - optidx > 1) {
1256181111Sdes			*path2 = xstrdup(argv[optidx + 1]);
1257181111Sdes			/* Destination is not globbed */
1258181111Sdes			undo_glob_escape(*path2);
1259181111Sdes		}
1260255767Sdes		if (*aflag && cmdnum == I_PUT) {
1261255767Sdes			/* XXX implement resume for uploads */
1262255767Sdes			error("Resume is not supported for uploads");
1263255767Sdes			return -1;
1264255767Sdes		}
1265126274Sdes		break;
1266221420Sdes	case I_LINK:
1267221420Sdes		if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1268221420Sdes			return -1;
1269262566Sdes		goto parse_two_paths;
1270262566Sdes	case I_RENAME:
1271262566Sdes		if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1272262566Sdes			return -1;
1273262566Sdes		goto parse_two_paths;
1274221420Sdes	case I_SYMLINK:
1275262566Sdes		if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1276262566Sdes			return -1;
1277262566Sdes parse_two_paths:
1278181111Sdes		if (argc - optidx < 2) {
1279126274Sdes			error("You must specify two paths after a %s "
1280126274Sdes			    "command.", cmd);
1281181111Sdes			return -1;
1282126274Sdes		}
1283181111Sdes		*path1 = xstrdup(argv[optidx]);
1284181111Sdes		*path2 = xstrdup(argv[optidx + 1]);
1285181111Sdes		/* Paths are not globbed */
1286181111Sdes		undo_glob_escape(*path1);
1287181111Sdes		undo_glob_escape(*path2);
1288126274Sdes		break;
1289126274Sdes	case I_RM:
1290126274Sdes	case I_MKDIR:
1291126274Sdes	case I_RMDIR:
1292126274Sdes	case I_CHDIR:
1293126274Sdes	case I_LCHDIR:
1294126274Sdes	case I_LMKDIR:
1295262566Sdes		if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1296262566Sdes			return -1;
1297126274Sdes		/* Get pathname (mandatory) */
1298181111Sdes		if (argc - optidx < 1) {
1299126274Sdes			error("You must specify a path after a %s command.",
1300126274Sdes			    cmd);
1301181111Sdes			return -1;
1302126274Sdes		}
1303181111Sdes		*path1 = xstrdup(argv[optidx]);
1304181111Sdes		/* Only "rm" globs */
1305181111Sdes		if (cmdnum != I_RM)
1306181111Sdes			undo_glob_escape(*path1);
1307126274Sdes		break;
1308181111Sdes	case I_DF:
1309181111Sdes		if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1310181111Sdes		    iflag)) == -1)
1311181111Sdes			return -1;
1312181111Sdes		/* Default to current directory if no path specified */
1313181111Sdes		if (argc - optidx < 1)
1314181111Sdes			*path1 = NULL;
1315181111Sdes		else {
1316181111Sdes			*path1 = xstrdup(argv[optidx]);
1317181111Sdes			undo_glob_escape(*path1);
1318181111Sdes		}
1319181111Sdes		break;
1320126274Sdes	case I_LS:
1321181111Sdes		if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
1322126274Sdes			return(-1);
1323126274Sdes		/* Path is optional */
1324181111Sdes		if (argc - optidx > 0)
1325181111Sdes			*path1 = xstrdup(argv[optidx]);
1326126274Sdes		break;
1327126274Sdes	case I_LLS:
1328181111Sdes		/* Skip ls command and following whitespace */
1329181111Sdes		cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
1330126274Sdes	case I_SHELL:
1331126274Sdes		/* Uses the rest of the line */
1332126274Sdes		break;
1333126274Sdes	case I_LUMASK:
1334126274Sdes	case I_CHMOD:
1335126274Sdes		base = 8;
1336126274Sdes	case I_CHOWN:
1337126274Sdes	case I_CHGRP:
1338262566Sdes		if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1339262566Sdes			return -1;
1340126274Sdes		/* Get numeric arg (mandatory) */
1341181111Sdes		if (argc - optidx < 1)
1342181111Sdes			goto need_num_arg;
1343164146Sdes		errno = 0;
1344181111Sdes		l = strtol(argv[optidx], &cp2, base);
1345181111Sdes		if (cp2 == argv[optidx] || *cp2 != '\0' ||
1346181111Sdes		    ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1347181111Sdes		    l < 0) {
1348181111Sdes need_num_arg:
1349126274Sdes			error("You must supply a numeric argument "
1350126274Sdes			    "to the %s command.", cmd);
1351181111Sdes			return -1;
1352126274Sdes		}
1353126274Sdes		*n_arg = l;
1354181111Sdes		if (cmdnum == I_LUMASK)
1355126274Sdes			break;
1356126274Sdes		/* Get pathname (mandatory) */
1357181111Sdes		if (argc - optidx < 2) {
1358126274Sdes			error("You must specify a path after a %s command.",
1359126274Sdes			    cmd);
1360181111Sdes			return -1;
1361126274Sdes		}
1362181111Sdes		*path1 = xstrdup(argv[optidx + 1]);
1363126274Sdes		break;
1364126274Sdes	case I_QUIT:
1365126274Sdes	case I_PWD:
1366126274Sdes	case I_LPWD:
1367126274Sdes	case I_HELP:
1368126274Sdes	case I_VERSION:
1369126274Sdes	case I_PROGRESS:
1370262566Sdes		if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1371262566Sdes			return -1;
1372126274Sdes		break;
1373126274Sdes	default:
1374126274Sdes		fatal("Command not implemented");
1375126274Sdes	}
1376126274Sdes
1377126274Sdes	*cpp = cp;
1378126274Sdes	return(cmdnum);
1379126274Sdes}
1380126274Sdes
1381126274Sdesstatic int
1382126274Sdesparse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1383126274Sdes    int err_abort)
1384126274Sdes{
1385126274Sdes	char *path1, *path2, *tmp;
1386262566Sdes	int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0, iflag = 0;
1387262566Sdes	int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
1388221420Sdes	int cmdnum, i;
1389192595Sdes	unsigned long n_arg = 0;
1390126274Sdes	Attrib a, *aa;
1391126274Sdes	char path_buf[MAXPATHLEN];
1392126274Sdes	int err = 0;
1393126274Sdes	glob_t g;
1394126274Sdes
1395126274Sdes	path1 = path2 = NULL;
1396262566Sdes	cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1397262566Sdes	    &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1398262566Sdes	if (ignore_errors != 0)
1399126274Sdes		err_abort = 0;
1400126274Sdes
1401126274Sdes	memset(&g, 0, sizeof(g));
1402126274Sdes
1403126274Sdes	/* Perform command */
1404126274Sdes	switch (cmdnum) {
1405126274Sdes	case 0:
1406126274Sdes		/* Blank line */
1407126274Sdes		break;
1408126274Sdes	case -1:
1409126274Sdes		/* Unrecognized command */
1410126274Sdes		err = -1;
1411126274Sdes		break;
1412255767Sdes	case I_REGET:
1413255767Sdes		aflag = 1;
1414255767Sdes		/* FALLTHROUGH */
1415126274Sdes	case I_GET:
1416255767Sdes		err = process_get(conn, path1, path2, *pwd, pflag,
1417262566Sdes		    rflag, aflag, fflag);
1418126274Sdes		break;
1419126274Sdes	case I_PUT:
1420262566Sdes		err = process_put(conn, path1, path2, *pwd, pflag,
1421262566Sdes		    rflag, fflag);
1422126274Sdes		break;
1423126274Sdes	case I_RENAME:
1424126274Sdes		path1 = make_absolute(path1, *pwd);
1425126274Sdes		path2 = make_absolute(path2, *pwd);
1426262566Sdes		err = do_rename(conn, path1, path2, lflag);
1427126274Sdes		break;
1428126274Sdes	case I_SYMLINK:
1429221420Sdes		sflag = 1;
1430221420Sdes	case I_LINK:
1431262566Sdes		if (!sflag)
1432262566Sdes			path1 = make_absolute(path1, *pwd);
1433126274Sdes		path2 = make_absolute(path2, *pwd);
1434221420Sdes		err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
1435126274Sdes		break;
1436126274Sdes	case I_RM:
1437126274Sdes		path1 = make_absolute(path1, *pwd);
1438126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1439137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1440255767Sdes			if (!quiet)
1441255767Sdes				printf("Removing %s\n", g.gl_pathv[i]);
1442126274Sdes			err = do_rm(conn, g.gl_pathv[i]);
1443126274Sdes			if (err != 0 && err_abort)
1444126274Sdes				break;
1445126274Sdes		}
1446126274Sdes		break;
1447126274Sdes	case I_MKDIR:
1448126274Sdes		path1 = make_absolute(path1, *pwd);
1449126274Sdes		attrib_clear(&a);
1450126274Sdes		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1451126274Sdes		a.perm = 0777;
1452204917Sdes		err = do_mkdir(conn, path1, &a, 1);
1453126274Sdes		break;
1454126274Sdes	case I_RMDIR:
1455126274Sdes		path1 = make_absolute(path1, *pwd);
1456126274Sdes		err = do_rmdir(conn, path1);
1457126274Sdes		break;
1458126274Sdes	case I_CHDIR:
1459126274Sdes		path1 = make_absolute(path1, *pwd);
1460126274Sdes		if ((tmp = do_realpath(conn, path1)) == NULL) {
1461126274Sdes			err = 1;
1462126274Sdes			break;
1463126274Sdes		}
1464126274Sdes		if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1465255767Sdes			free(tmp);
1466126274Sdes			err = 1;
1467126274Sdes			break;
1468126274Sdes		}
1469126274Sdes		if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1470126274Sdes			error("Can't change directory: Can't check target");
1471255767Sdes			free(tmp);
1472126274Sdes			err = 1;
1473126274Sdes			break;
1474126274Sdes		}
1475126274Sdes		if (!S_ISDIR(aa->perm)) {
1476126274Sdes			error("Can't change directory: \"%s\" is not "
1477126274Sdes			    "a directory", tmp);
1478255767Sdes			free(tmp);
1479126274Sdes			err = 1;
1480126274Sdes			break;
1481126274Sdes		}
1482255767Sdes		free(*pwd);
1483126274Sdes		*pwd = tmp;
1484126274Sdes		break;
1485126274Sdes	case I_LS:
1486126274Sdes		if (!path1) {
1487215116Sdes			do_ls_dir(conn, *pwd, *pwd, lflag);
1488126274Sdes			break;
1489126274Sdes		}
1490126274Sdes
1491126274Sdes		/* Strip pwd off beginning of non-absolute paths */
1492126274Sdes		tmp = NULL;
1493126274Sdes		if (*path1 != '/')
1494126274Sdes			tmp = *pwd;
1495126274Sdes
1496126274Sdes		path1 = make_absolute(path1, *pwd);
1497126274Sdes		err = do_globbed_ls(conn, path1, tmp, lflag);
1498126274Sdes		break;
1499181111Sdes	case I_DF:
1500181111Sdes		/* Default to current directory if no path specified */
1501181111Sdes		if (path1 == NULL)
1502181111Sdes			path1 = xstrdup(*pwd);
1503181111Sdes		path1 = make_absolute(path1, *pwd);
1504181111Sdes		err = do_df(conn, path1, hflag, iflag);
1505181111Sdes		break;
1506126274Sdes	case I_LCHDIR:
1507126274Sdes		if (chdir(path1) == -1) {
1508126274Sdes			error("Couldn't change local directory to "
1509126274Sdes			    "\"%s\": %s", path1, strerror(errno));
1510126274Sdes			err = 1;
1511126274Sdes		}
1512126274Sdes		break;
1513126274Sdes	case I_LMKDIR:
1514126274Sdes		if (mkdir(path1, 0777) == -1) {
1515126274Sdes			error("Couldn't create local directory "
1516126274Sdes			    "\"%s\": %s", path1, strerror(errno));
1517126274Sdes			err = 1;
1518126274Sdes		}
1519126274Sdes		break;
1520126274Sdes	case I_LLS:
1521126274Sdes		local_do_ls(cmd);
1522126274Sdes		break;
1523126274Sdes	case I_SHELL:
1524126274Sdes		local_do_shell(cmd);
1525126274Sdes		break;
1526126274Sdes	case I_LUMASK:
1527126274Sdes		umask(n_arg);
1528126274Sdes		printf("Local umask: %03lo\n", n_arg);
1529126274Sdes		break;
1530126274Sdes	case I_CHMOD:
1531126274Sdes		path1 = make_absolute(path1, *pwd);
1532126274Sdes		attrib_clear(&a);
1533126274Sdes		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1534126274Sdes		a.perm = n_arg;
1535126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1536137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1537255767Sdes			if (!quiet)
1538255767Sdes				printf("Changing mode on %s\n", g.gl_pathv[i]);
1539126274Sdes			err = do_setstat(conn, g.gl_pathv[i], &a);
1540126274Sdes			if (err != 0 && err_abort)
1541126274Sdes				break;
1542126274Sdes		}
1543126274Sdes		break;
1544126274Sdes	case I_CHOWN:
1545126274Sdes	case I_CHGRP:
1546126274Sdes		path1 = make_absolute(path1, *pwd);
1547126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1548137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1549126274Sdes			if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
1550192595Sdes				if (err_abort) {
1551192595Sdes					err = -1;
1552126274Sdes					break;
1553192595Sdes				} else
1554126274Sdes					continue;
1555126274Sdes			}
1556126274Sdes			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1557126274Sdes				error("Can't get current ownership of "
1558126274Sdes				    "remote file \"%s\"", g.gl_pathv[i]);
1559192595Sdes				if (err_abort) {
1560192595Sdes					err = -1;
1561126274Sdes					break;
1562192595Sdes				} else
1563126274Sdes					continue;
1564126274Sdes			}
1565126274Sdes			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1566126274Sdes			if (cmdnum == I_CHOWN) {
1567255767Sdes				if (!quiet)
1568255767Sdes					printf("Changing owner on %s\n",
1569255767Sdes					    g.gl_pathv[i]);
1570126274Sdes				aa->uid = n_arg;
1571126274Sdes			} else {
1572255767Sdes				if (!quiet)
1573255767Sdes					printf("Changing group on %s\n",
1574255767Sdes					    g.gl_pathv[i]);
1575126274Sdes				aa->gid = n_arg;
1576126274Sdes			}
1577126274Sdes			err = do_setstat(conn, g.gl_pathv[i], aa);
1578126274Sdes			if (err != 0 && err_abort)
1579126274Sdes				break;
1580126274Sdes		}
1581126274Sdes		break;
1582126274Sdes	case I_PWD:
1583126274Sdes		printf("Remote working directory: %s\n", *pwd);
1584126274Sdes		break;
1585126274Sdes	case I_LPWD:
1586126274Sdes		if (!getcwd(path_buf, sizeof(path_buf))) {
1587126274Sdes			error("Couldn't get local cwd: %s", strerror(errno));
1588126274Sdes			err = -1;
1589126274Sdes			break;
1590126274Sdes		}
1591126274Sdes		printf("Local working directory: %s\n", path_buf);
1592126274Sdes		break;
1593126274Sdes	case I_QUIT:
1594126274Sdes		/* Processed below */
1595126274Sdes		break;
1596126274Sdes	case I_HELP:
1597126274Sdes		help();
1598126274Sdes		break;
1599126274Sdes	case I_VERSION:
1600126274Sdes		printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1601126274Sdes		break;
1602126274Sdes	case I_PROGRESS:
1603126274Sdes		showprogress = !showprogress;
1604126274Sdes		if (showprogress)
1605126274Sdes			printf("Progress meter enabled\n");
1606126274Sdes		else
1607126274Sdes			printf("Progress meter disabled\n");
1608126274Sdes		break;
1609126274Sdes	default:
1610126274Sdes		fatal("%d is not implemented", cmdnum);
1611126274Sdes	}
1612126274Sdes
1613126274Sdes	if (g.gl_pathc)
1614126274Sdes		globfree(&g);
1615255767Sdes	free(path1);
1616255767Sdes	free(path2);
1617126274Sdes
1618126274Sdes	/* If an unignored error occurs in batch mode we should abort. */
1619126274Sdes	if (err_abort && err != 0)
1620126274Sdes		return (-1);
1621126274Sdes	else if (cmdnum == I_QUIT)
1622126274Sdes		return (1);
1623126274Sdes
1624126274Sdes	return (0);
1625126274Sdes}
1626126274Sdes
1627146998Sdes#ifdef USE_LIBEDIT
1628146998Sdesstatic char *
1629146998Sdesprompt(EditLine *el)
1630146998Sdes{
1631146998Sdes	return ("sftp> ");
1632146998Sdes}
1633146998Sdes
1634204917Sdes/* Display entries in 'list' after skipping the first 'len' chars */
1635204917Sdesstatic void
1636204917Sdescomplete_display(char **list, u_int len)
1637204917Sdes{
1638204917Sdes	u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1639204917Sdes	struct winsize ws;
1640204917Sdes	char *tmp;
1641204917Sdes
1642204917Sdes	/* Count entries for sort and find longest */
1643262566Sdes	for (y = 0; list[y]; y++)
1644204917Sdes		m = MAX(m, strlen(list[y]));
1645204917Sdes
1646204917Sdes	if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1647204917Sdes		width = ws.ws_col;
1648204917Sdes
1649204917Sdes	m = m > len ? m - len : 0;
1650204917Sdes	columns = width / (m + 2);
1651204917Sdes	columns = MAX(columns, 1);
1652204917Sdes	colspace = width / columns;
1653204917Sdes	colspace = MIN(colspace, width);
1654204917Sdes
1655204917Sdes	printf("\n");
1656204917Sdes	m = 1;
1657204917Sdes	for (y = 0; list[y]; y++) {
1658204917Sdes		llen = strlen(list[y]);
1659204917Sdes		tmp = llen > len ? list[y] + len : "";
1660204917Sdes		printf("%-*s", colspace, tmp);
1661204917Sdes		if (m >= columns) {
1662204917Sdes			printf("\n");
1663204917Sdes			m = 1;
1664204917Sdes		} else
1665204917Sdes			m++;
1666204917Sdes	}
1667204917Sdes	printf("\n");
1668204917Sdes}
1669204917Sdes
1670204917Sdes/*
1671204917Sdes * Given a "list" of words that begin with a common prefix of "word",
1672204917Sdes * attempt to find an autocompletion to extends "word" by the next
1673204917Sdes * characters common to all entries in "list".
1674204917Sdes */
1675204917Sdesstatic char *
1676204917Sdescomplete_ambiguous(const char *word, char **list, size_t count)
1677204917Sdes{
1678204917Sdes	if (word == NULL)
1679204917Sdes		return NULL;
1680204917Sdes
1681204917Sdes	if (count > 0) {
1682204917Sdes		u_int y, matchlen = strlen(list[0]);
1683204917Sdes
1684204917Sdes		/* Find length of common stem */
1685204917Sdes		for (y = 1; list[y]; y++) {
1686204917Sdes			u_int x;
1687204917Sdes
1688262566Sdes			for (x = 0; x < matchlen; x++)
1689262566Sdes				if (list[0][x] != list[y][x])
1690204917Sdes					break;
1691204917Sdes
1692204917Sdes			matchlen = x;
1693204917Sdes		}
1694204917Sdes
1695204917Sdes		if (matchlen > strlen(word)) {
1696204917Sdes			char *tmp = xstrdup(list[0]);
1697204917Sdes
1698204917Sdes			tmp[matchlen] = '\0';
1699204917Sdes			return tmp;
1700204917Sdes		}
1701262566Sdes	}
1702204917Sdes
1703204917Sdes	return xstrdup(word);
1704204917Sdes}
1705204917Sdes
1706204917Sdes/* Autocomplete a sftp command */
1707204917Sdesstatic int
1708204917Sdescomplete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1709204917Sdes    int terminated)
1710204917Sdes{
1711204917Sdes	u_int y, count = 0, cmdlen, tmplen;
1712204917Sdes	char *tmp, **list, argterm[3];
1713204917Sdes	const LineInfo *lf;
1714204917Sdes
1715204917Sdes	list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1716204917Sdes
1717204917Sdes	/* No command specified: display all available commands */
1718204917Sdes	if (cmd == NULL) {
1719204917Sdes		for (y = 0; cmds[y].c; y++)
1720204917Sdes			list[count++] = xstrdup(cmds[y].c);
1721262566Sdes
1722204917Sdes		list[count] = NULL;
1723204917Sdes		complete_display(list, 0);
1724204917Sdes
1725262566Sdes		for (y = 0; list[y] != NULL; y++)
1726262566Sdes			free(list[y]);
1727255767Sdes		free(list);
1728204917Sdes		return count;
1729204917Sdes	}
1730204917Sdes
1731204917Sdes	/* Prepare subset of commands that start with "cmd" */
1732204917Sdes	cmdlen = strlen(cmd);
1733204917Sdes	for (y = 0; cmds[y].c; y++)  {
1734262566Sdes		if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1735204917Sdes			list[count++] = xstrdup(cmds[y].c);
1736204917Sdes	}
1737204917Sdes	list[count] = NULL;
1738204917Sdes
1739240075Sdes	if (count == 0) {
1740255767Sdes		free(list);
1741204917Sdes		return 0;
1742240075Sdes	}
1743204917Sdes
1744204917Sdes	/* Complete ambigious command */
1745204917Sdes	tmp = complete_ambiguous(cmd, list, count);
1746204917Sdes	if (count > 1)
1747204917Sdes		complete_display(list, 0);
1748204917Sdes
1749262566Sdes	for (y = 0; list[y]; y++)
1750262566Sdes		free(list[y]);
1751255767Sdes	free(list);
1752204917Sdes
1753204917Sdes	if (tmp != NULL) {
1754204917Sdes		tmplen = strlen(tmp);
1755204917Sdes		cmdlen = strlen(cmd);
1756204917Sdes		/* If cmd may be extended then do so */
1757204917Sdes		if (tmplen > cmdlen)
1758204917Sdes			if (el_insertstr(el, tmp + cmdlen) == -1)
1759204917Sdes				fatal("el_insertstr failed.");
1760204917Sdes		lf = el_line(el);
1761204917Sdes		/* Terminate argument cleanly */
1762204917Sdes		if (count == 1) {
1763204917Sdes			y = 0;
1764204917Sdes			if (!terminated)
1765204917Sdes				argterm[y++] = quote;
1766204917Sdes			if (lastarg || *(lf->cursor) != ' ')
1767204917Sdes				argterm[y++] = ' ';
1768204917Sdes			argterm[y] = '\0';
1769204917Sdes			if (y > 0 && el_insertstr(el, argterm) == -1)
1770204917Sdes				fatal("el_insertstr failed.");
1771204917Sdes		}
1772255767Sdes		free(tmp);
1773204917Sdes	}
1774204917Sdes
1775204917Sdes	return count;
1776204917Sdes}
1777204917Sdes
1778204917Sdes/*
1779204917Sdes * Determine whether a particular sftp command's arguments (if any)
1780204917Sdes * represent local or remote files.
1781204917Sdes */
1782204917Sdesstatic int
1783204917Sdescomplete_is_remote(char *cmd) {
1784204917Sdes	int i;
1785204917Sdes
1786204917Sdes	if (cmd == NULL)
1787204917Sdes		return -1;
1788204917Sdes
1789204917Sdes	for (i = 0; cmds[i].c; i++) {
1790262566Sdes		if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1791204917Sdes			return cmds[i].t;
1792204917Sdes	}
1793204917Sdes
1794204917Sdes	return -1;
1795204917Sdes}
1796204917Sdes
1797204917Sdes/* Autocomplete a filename "file" */
1798204917Sdesstatic int
1799204917Sdescomplete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1800204917Sdes    char *file, int remote, int lastarg, char quote, int terminated)
1801204917Sdes{
1802204917Sdes	glob_t g;
1803255767Sdes	char *tmp, *tmp2, ins[8];
1804248619Sdes	u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
1805255767Sdes	int clen;
1806204917Sdes	const LineInfo *lf;
1807262566Sdes
1808204917Sdes	/* Glob from "file" location */
1809204917Sdes	if (file == NULL)
1810204917Sdes		tmp = xstrdup("*");
1811204917Sdes	else
1812204917Sdes		xasprintf(&tmp, "%s*", file);
1813204917Sdes
1814248619Sdes	/* Check if the path is absolute. */
1815248619Sdes	isabs = tmp[0] == '/';
1816248619Sdes
1817204917Sdes	memset(&g, 0, sizeof(g));
1818204917Sdes	if (remote != LOCAL) {
1819204917Sdes		tmp = make_absolute(tmp, remote_path);
1820204917Sdes		remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1821262566Sdes	} else
1822204917Sdes		glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1823262566Sdes
1824204917Sdes	/* Determine length of pwd so we can trim completion display */
1825204917Sdes	for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1826204917Sdes		/* Terminate counting on first unescaped glob metacharacter */
1827204917Sdes		if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1828204917Sdes			if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1829204917Sdes				hadglob = 1;
1830204917Sdes			break;
1831204917Sdes		}
1832204917Sdes		if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1833204917Sdes			tmplen++;
1834204917Sdes		if (tmp[tmplen] == '/')
1835204917Sdes			pwdlen = tmplen + 1;	/* track last seen '/' */
1836204917Sdes	}
1837255767Sdes	free(tmp);
1838204917Sdes
1839262566Sdes	if (g.gl_matchc == 0)
1840204917Sdes		goto out;
1841204917Sdes
1842204917Sdes	if (g.gl_matchc > 1)
1843204917Sdes		complete_display(g.gl_pathv, pwdlen);
1844204917Sdes
1845204917Sdes	tmp = NULL;
1846204917Sdes	/* Don't try to extend globs */
1847204917Sdes	if (file == NULL || hadglob)
1848204917Sdes		goto out;
1849204917Sdes
1850204917Sdes	tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1851248619Sdes	tmp = path_strip(tmp2, isabs ? NULL : remote_path);
1852255767Sdes	free(tmp2);
1853204917Sdes
1854204917Sdes	if (tmp == NULL)
1855204917Sdes		goto out;
1856204917Sdes
1857204917Sdes	tmplen = strlen(tmp);
1858204917Sdes	filelen = strlen(file);
1859204917Sdes
1860248619Sdes	/* Count the number of escaped characters in the input string. */
1861248619Sdes	cesc = isesc = 0;
1862248619Sdes	for (i = 0; i < filelen; i++) {
1863248619Sdes		if (!isesc && file[i] == '\\' && i + 1 < filelen){
1864248619Sdes			isesc = 1;
1865248619Sdes			cesc++;
1866248619Sdes		} else
1867248619Sdes			isesc = 0;
1868248619Sdes	}
1869248619Sdes
1870248619Sdes	if (tmplen > (filelen - cesc)) {
1871248619Sdes		tmp2 = tmp + filelen - cesc;
1872262566Sdes		len = strlen(tmp2);
1873204917Sdes		/* quote argument on way out */
1874255767Sdes		for (i = 0; i < len; i += clen) {
1875255767Sdes			if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1876255767Sdes			    (size_t)clen > sizeof(ins) - 2)
1877255767Sdes				fatal("invalid multibyte character");
1878204917Sdes			ins[0] = '\\';
1879255767Sdes			memcpy(ins + 1, tmp2 + i, clen);
1880255767Sdes			ins[clen + 1] = '\0';
1881204917Sdes			switch (tmp2[i]) {
1882204917Sdes			case '\'':
1883204917Sdes			case '"':
1884204917Sdes			case '\\':
1885204917Sdes			case '\t':
1886221420Sdes			case '[':
1887204917Sdes			case ' ':
1888248619Sdes			case '#':
1889248619Sdes			case '*':
1890204917Sdes				if (quote == '\0' || tmp2[i] == quote) {
1891204917Sdes					if (el_insertstr(el, ins) == -1)
1892204917Sdes						fatal("el_insertstr "
1893204917Sdes						    "failed.");
1894204917Sdes					break;
1895204917Sdes				}
1896204917Sdes				/* FALLTHROUGH */
1897204917Sdes			default:
1898204917Sdes				if (el_insertstr(el, ins + 1) == -1)
1899204917Sdes					fatal("el_insertstr failed.");
1900204917Sdes				break;
1901204917Sdes			}
1902204917Sdes		}
1903204917Sdes	}
1904204917Sdes
1905204917Sdes	lf = el_line(el);
1906204917Sdes	if (g.gl_matchc == 1) {
1907204917Sdes		i = 0;
1908204917Sdes		if (!terminated)
1909204917Sdes			ins[i++] = quote;
1910204917Sdes		if (*(lf->cursor - 1) != '/' &&
1911204917Sdes		    (lastarg || *(lf->cursor) != ' '))
1912204917Sdes			ins[i++] = ' ';
1913204917Sdes		ins[i] = '\0';
1914204917Sdes		if (i > 0 && el_insertstr(el, ins) == -1)
1915204917Sdes			fatal("el_insertstr failed.");
1916204917Sdes	}
1917255767Sdes	free(tmp);
1918204917Sdes
1919204917Sdes out:
1920204917Sdes	globfree(&g);
1921204917Sdes	return g.gl_matchc;
1922204917Sdes}
1923204917Sdes
1924204917Sdes/* tab-completion hook function, called via libedit */
1925204917Sdesstatic unsigned char
1926204917Sdescomplete(EditLine *el, int ch)
1927204917Sdes{
1928262566Sdes	char **argv, *line, quote;
1929255767Sdes	int argc, carg;
1930255767Sdes	u_int cursor, len, terminated, ret = CC_ERROR;
1931204917Sdes	const LineInfo *lf;
1932204917Sdes	struct complete_ctx *complete_ctx;
1933204917Sdes
1934204917Sdes	lf = el_line(el);
1935204917Sdes	if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1936204917Sdes		fatal("%s: el_get failed", __func__);
1937204917Sdes
1938204917Sdes	/* Figure out which argument the cursor points to */
1939204917Sdes	cursor = lf->cursor - lf->buffer;
1940204917Sdes	line = (char *)xmalloc(cursor + 1);
1941204917Sdes	memcpy(line, lf->buffer, cursor);
1942204917Sdes	line[cursor] = '\0';
1943204917Sdes	argv = makeargv(line, &carg, 1, &quote, &terminated);
1944255767Sdes	free(line);
1945204917Sdes
1946204917Sdes	/* Get all the arguments on the line */
1947204917Sdes	len = lf->lastchar - lf->buffer;
1948204917Sdes	line = (char *)xmalloc(len + 1);
1949204917Sdes	memcpy(line, lf->buffer, len);
1950204917Sdes	line[len] = '\0';
1951204917Sdes	argv = makeargv(line, &argc, 1, NULL, NULL);
1952204917Sdes
1953204917Sdes	/* Ensure cursor is at EOL or a argument boundary */
1954204917Sdes	if (line[cursor] != ' ' && line[cursor] != '\0' &&
1955204917Sdes	    line[cursor] != '\n') {
1956255767Sdes		free(line);
1957204917Sdes		return ret;
1958204917Sdes	}
1959204917Sdes
1960204917Sdes	if (carg == 0) {
1961204917Sdes		/* Show all available commands */
1962204917Sdes		complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1963204917Sdes		ret = CC_REDISPLAY;
1964204917Sdes	} else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ')  {
1965204917Sdes		/* Handle the command parsing */
1966204917Sdes		if (complete_cmd_parse(el, argv[0], argc == carg,
1967262566Sdes		    quote, terminated) != 0)
1968204917Sdes			ret = CC_REDISPLAY;
1969204917Sdes	} else if (carg >= 1) {
1970204917Sdes		/* Handle file parsing */
1971204917Sdes		int remote = complete_is_remote(argv[0]);
1972204917Sdes		char *filematch = NULL;
1973204917Sdes
1974204917Sdes		if (carg > 1 && line[cursor-1] != ' ')
1975204917Sdes			filematch = argv[carg - 1];
1976204917Sdes
1977204917Sdes		if (remote != 0 &&
1978204917Sdes		    complete_match(el, complete_ctx->conn,
1979204917Sdes		    *complete_ctx->remote_pathp, filematch,
1980262566Sdes		    remote, carg == argc, quote, terminated) != 0)
1981204917Sdes			ret = CC_REDISPLAY;
1982204917Sdes	}
1983204917Sdes
1984262566Sdes	free(line);
1985204917Sdes	return ret;
1986204917Sdes}
1987204917Sdes#endif /* USE_LIBEDIT */
1988204917Sdes
1989126274Sdesint
1990204917Sdesinteractive_loop(struct sftp_conn *conn, char *file1, char *file2)
1991126274Sdes{
1992204917Sdes	char *remote_path;
1993126274Sdes	char *dir = NULL;
1994126274Sdes	char cmd[2048];
1995149749Sdes	int err, interactive;
1996146998Sdes	EditLine *el = NULL;
1997146998Sdes#ifdef USE_LIBEDIT
1998146998Sdes	History *hl = NULL;
1999146998Sdes	HistEvent hev;
2000146998Sdes	extern char *__progname;
2001204917Sdes	struct complete_ctx complete_ctx;
2002126274Sdes
2003146998Sdes	if (!batchmode && isatty(STDIN_FILENO)) {
2004146998Sdes		if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2005146998Sdes			fatal("Couldn't initialise editline");
2006146998Sdes		if ((hl = history_init()) == NULL)
2007146998Sdes			fatal("Couldn't initialise editline history");
2008146998Sdes		history(hl, &hev, H_SETSIZE, 100);
2009146998Sdes		el_set(el, EL_HIST, history, hl);
2010146998Sdes
2011146998Sdes		el_set(el, EL_PROMPT, prompt);
2012146998Sdes		el_set(el, EL_EDITOR, "emacs");
2013146998Sdes		el_set(el, EL_TERMINAL, NULL);
2014146998Sdes		el_set(el, EL_SIGNAL, 1);
2015146998Sdes		el_source(el, NULL);
2016204917Sdes
2017204917Sdes		/* Tab Completion */
2018262566Sdes		el_set(el, EL_ADDFN, "ftp-complete",
2019221420Sdes		    "Context sensitive argument completion", complete);
2020204917Sdes		complete_ctx.conn = conn;
2021204917Sdes		complete_ctx.remote_pathp = &remote_path;
2022204917Sdes		el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2023204917Sdes		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
2024262566Sdes		/* enable ctrl-left-arrow and ctrl-right-arrow */
2025262566Sdes		el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2026262566Sdes		el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2027262566Sdes		el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2028262566Sdes		el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
2029262566Sdes		/* make ^w match ksh behaviour */
2030262566Sdes		el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
2031146998Sdes	}
2032146998Sdes#endif /* USE_LIBEDIT */
2033146998Sdes
2034204917Sdes	remote_path = do_realpath(conn, ".");
2035204917Sdes	if (remote_path == NULL)
2036126274Sdes		fatal("Need cwd");
2037126274Sdes
2038126274Sdes	if (file1 != NULL) {
2039126274Sdes		dir = xstrdup(file1);
2040204917Sdes		dir = make_absolute(dir, remote_path);
2041126274Sdes
2042126274Sdes		if (remote_is_dir(conn, dir) && file2 == NULL) {
2043255767Sdes			if (!quiet)
2044255767Sdes				printf("Changing to: %s\n", dir);
2045126274Sdes			snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
2046204917Sdes			if (parse_dispatch_command(conn, cmd,
2047204917Sdes			    &remote_path, 1) != 0) {
2048255767Sdes				free(dir);
2049255767Sdes				free(remote_path);
2050255767Sdes				free(conn);
2051126274Sdes				return (-1);
2052146998Sdes			}
2053126274Sdes		} else {
2054248619Sdes			/* XXX this is wrong wrt quoting */
2055255767Sdes			snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2056255767Sdes			    global_aflag ? " -a" : "", dir,
2057255767Sdes			    file2 == NULL ? "" : " ",
2058255767Sdes			    file2 == NULL ? "" : file2);
2059204917Sdes			err = parse_dispatch_command(conn, cmd,
2060204917Sdes			    &remote_path, 1);
2061255767Sdes			free(dir);
2062255767Sdes			free(remote_path);
2063255767Sdes			free(conn);
2064126274Sdes			return (err);
2065126274Sdes		}
2066255767Sdes		free(dir);
2067126274Sdes	}
2068126274Sdes
2069149749Sdes	setlinebuf(stdout);
2070149749Sdes	setlinebuf(infile);
2071126274Sdes
2072149749Sdes	interactive = !batchmode && isatty(STDIN_FILENO);
2073126274Sdes	err = 0;
2074126274Sdes	for (;;) {
2075126274Sdes		char *cp;
2076126274Sdes
2077137015Sdes		signal(SIGINT, SIG_IGN);
2078137015Sdes
2079146998Sdes		if (el == NULL) {
2080149749Sdes			if (interactive)
2081149749Sdes				printf("sftp> ");
2082146998Sdes			if (fgets(cmd, sizeof(cmd), infile) == NULL) {
2083149749Sdes				if (interactive)
2084149749Sdes					printf("\n");
2085146998Sdes				break;
2086146998Sdes			}
2087149749Sdes			if (!interactive) { /* Echo command */
2088149749Sdes				printf("sftp> %s", cmd);
2089149749Sdes				if (strlen(cmd) > 0 &&
2090149749Sdes				    cmd[strlen(cmd) - 1] != '\n')
2091149749Sdes					printf("\n");
2092149749Sdes			}
2093146998Sdes		} else {
2094146998Sdes#ifdef USE_LIBEDIT
2095146998Sdes			const char *line;
2096146998Sdes			int count = 0;
2097126274Sdes
2098204917Sdes			if ((line = el_gets(el, &count)) == NULL ||
2099204917Sdes			    count <= 0) {
2100149749Sdes				printf("\n");
2101149749Sdes 				break;
2102149749Sdes			}
2103146998Sdes			history(hl, &hev, H_ENTER, line);
2104146998Sdes			if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2105146998Sdes				fprintf(stderr, "Error: input line too long\n");
2106146998Sdes				continue;
2107146998Sdes			}
2108146998Sdes#endif /* USE_LIBEDIT */
2109126274Sdes		}
2110126274Sdes
2111126274Sdes		cp = strrchr(cmd, '\n');
2112126274Sdes		if (cp)
2113126274Sdes			*cp = '\0';
2114126274Sdes
2115137015Sdes		/* Handle user interrupts gracefully during commands */
2116137015Sdes		interrupted = 0;
2117137015Sdes		signal(SIGINT, cmd_interrupt);
2118137015Sdes
2119204917Sdes		err = parse_dispatch_command(conn, cmd, &remote_path,
2120204917Sdes		    batchmode);
2121126274Sdes		if (err != 0)
2122126274Sdes			break;
2123126274Sdes	}
2124255767Sdes	free(remote_path);
2125255767Sdes	free(conn);
2126126274Sdes
2127149749Sdes#ifdef USE_LIBEDIT
2128149749Sdes	if (el != NULL)
2129149749Sdes		el_end(el);
2130149749Sdes#endif /* USE_LIBEDIT */
2131149749Sdes
2132126274Sdes	/* err == 1 signifies normal "quit" exit */
2133126274Sdes	return (err >= 0 ? 0 : -1);
2134126274Sdes}
2135126274Sdes
2136126274Sdesstatic void
2137124208Sdesconnect_to_server(char *path, char **args, int *in, int *out)
2138124208Sdes{
213976259Sgreen	int c_in, c_out;
214099060Sdes
214176259Sgreen#ifdef USE_PIPES
214276259Sgreen	int pin[2], pout[2];
214399060Sdes
214476259Sgreen	if ((pipe(pin) == -1) || (pipe(pout) == -1))
214576259Sgreen		fatal("pipe: %s", strerror(errno));
214676259Sgreen	*in = pin[0];
214776259Sgreen	*out = pout[1];
214876259Sgreen	c_in = pout[0];
214976259Sgreen	c_out = pin[1];
215076259Sgreen#else /* USE_PIPES */
215176259Sgreen	int inout[2];
215299060Sdes
215376259Sgreen	if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
215476259Sgreen		fatal("socketpair: %s", strerror(errno));
215576259Sgreen	*in = *out = inout[0];
215676259Sgreen	c_in = c_out = inout[1];
215776259Sgreen#endif /* USE_PIPES */
215876259Sgreen
2159124208Sdes	if ((sshpid = fork()) == -1)
216076259Sgreen		fatal("fork: %s", strerror(errno));
2161124208Sdes	else if (sshpid == 0) {
216276259Sgreen		if ((dup2(c_in, STDIN_FILENO) == -1) ||
216376259Sgreen		    (dup2(c_out, STDOUT_FILENO) == -1)) {
216476259Sgreen			fprintf(stderr, "dup2: %s\n", strerror(errno));
2165137015Sdes			_exit(1);
216676259Sgreen		}
216776259Sgreen		close(*in);
216876259Sgreen		close(*out);
216976259Sgreen		close(c_in);
217076259Sgreen		close(c_out);
2171137015Sdes
2172137015Sdes		/*
2173137015Sdes		 * The underlying ssh is in the same process group, so we must
2174137015Sdes		 * ignore SIGINT if we want to gracefully abort commands,
2175137015Sdes		 * otherwise the signal will make it to the ssh process and
2176204917Sdes		 * kill it too.  Contrawise, since sftp sends SIGTERMs to the
2177204917Sdes		 * underlying ssh, it must *not* ignore that signal.
2178137015Sdes		 */
2179137015Sdes		signal(SIGINT, SIG_IGN);
2180204917Sdes		signal(SIGTERM, SIG_DFL);
2181137015Sdes		execvp(path, args);
218292555Sdes		fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
2183137015Sdes		_exit(1);
218476259Sgreen	}
218576259Sgreen
2186124208Sdes	signal(SIGTERM, killchild);
2187124208Sdes	signal(SIGINT, killchild);
2188124208Sdes	signal(SIGHUP, killchild);
218976259Sgreen	close(c_in);
219076259Sgreen	close(c_out);
219176259Sgreen}
219276259Sgreen
219392555Sdesstatic void
219476259Sgreenusage(void)
219576259Sgreen{
219692555Sdes	extern char *__progname;
219798675Sdes
219892555Sdes	fprintf(stderr,
2199262566Sdes	    "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
2200204917Sdes	    "          [-D sftp_server_path] [-F ssh_config] "
2201221420Sdes	    "[-i identity_file] [-l limit]\n"
2202204917Sdes	    "          [-o ssh_option] [-P port] [-R num_requests] "
2203204917Sdes	    "[-S program]\n"
2204204917Sdes	    "          [-s subsystem | sftp_server] host\n"
2205192595Sdes	    "       %s [user@]host[:file ...]\n"
2206192595Sdes	    "       %s [user@]host[:dir[/]]\n"
2207204917Sdes	    "       %s -b batchfile [user@]host\n",
2208204917Sdes	    __progname, __progname, __progname, __progname);
220976259Sgreen	exit(1);
221076259Sgreen}
221176259Sgreen
221276259Sgreenint
221376259Sgreenmain(int argc, char **argv)
221476259Sgreen{
2215113908Sdes	int in, out, ch, err;
2216204917Sdes	char *host = NULL, *userhost, *cp, *file2 = NULL;
221792555Sdes	int debug_level = 0, sshver = 2;
221892555Sdes	char *file1 = NULL, *sftp_server = NULL;
221992555Sdes	char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
2220221420Sdes	const char *errstr;
222192555Sdes	LogLevel ll = SYSLOG_LEVEL_INFO;
222292555Sdes	arglist args;
222376259Sgreen	extern int optind;
222476259Sgreen	extern char *optarg;
2225204917Sdes	struct sftp_conn *conn;
2226204917Sdes	size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2227204917Sdes	size_t num_requests = DEFAULT_NUM_REQUESTS;
2228221420Sdes	long long limit_kbps = 0;
222976259Sgreen
2230157016Sdes	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2231157016Sdes	sanitise_stdfd();
2232255767Sdes	setlocale(LC_CTYPE, "");
2233157016Sdes
2234124208Sdes	__progname = ssh_get_progname(argv[0]);
2235157016Sdes	memset(&args, '\0', sizeof(args));
223692555Sdes	args.list = NULL;
2237162852Sdes	addargs(&args, "%s", ssh_program);
223892555Sdes	addargs(&args, "-oForwardX11 no");
223992555Sdes	addargs(&args, "-oForwardAgent no");
2240157016Sdes	addargs(&args, "-oPermitLocalCommand no");
224192555Sdes	addargs(&args, "-oClearAllForwardings yes");
2242126274Sdes
224392555Sdes	ll = SYSLOG_LEVEL_INFO;
2244126274Sdes	infile = stdin;
224576259Sgreen
2246204917Sdes	while ((ch = getopt(argc, argv,
2247262566Sdes	    "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
224876259Sgreen		switch (ch) {
2249204917Sdes		/* Passed through to ssh(1) */
2250204917Sdes		case '4':
2251204917Sdes		case '6':
225276259Sgreen		case 'C':
2253204917Sdes			addargs(&args, "-%c", ch);
225476259Sgreen			break;
2255204917Sdes		/* Passed through to ssh(1) with argument */
2256204917Sdes		case 'F':
2257204917Sdes		case 'c':
2258204917Sdes		case 'i':
2259204917Sdes		case 'o':
2260204917Sdes			addargs(&args, "-%c", ch);
2261204917Sdes			addargs(&args, "%s", optarg);
2262204917Sdes			break;
2263204917Sdes		case 'q':
2264255767Sdes			ll = SYSLOG_LEVEL_ERROR;
2265255767Sdes			quiet = 1;
2266204917Sdes			showprogress = 0;
2267204917Sdes			addargs(&args, "-%c", ch);
2268204917Sdes			break;
2269204917Sdes		case 'P':
2270204917Sdes			addargs(&args, "-oPort %s", optarg);
2271204917Sdes			break;
227276259Sgreen		case 'v':
227392555Sdes			if (debug_level < 3) {
227492555Sdes				addargs(&args, "-v");
227592555Sdes				ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
227692555Sdes			}
227792555Sdes			debug_level++;
227876259Sgreen			break;
227976259Sgreen		case '1':
228092555Sdes			sshver = 1;
228176259Sgreen			if (sftp_server == NULL)
228276259Sgreen				sftp_server = _PATH_SFTP_SERVER;
228376259Sgreen			break;
2284204917Sdes		case '2':
2285204917Sdes			sshver = 2;
228676259Sgreen			break;
2287255767Sdes		case 'a':
2288255767Sdes			global_aflag = 1;
2289255767Sdes			break;
2290204917Sdes		case 'B':
2291204917Sdes			copy_buffer_len = strtol(optarg, &cp, 10);
2292204917Sdes			if (copy_buffer_len == 0 || *cp != '\0')
2293204917Sdes				fatal("Invalid buffer size \"%s\"", optarg);
229476259Sgreen			break;
229576259Sgreen		case 'b':
2296126274Sdes			if (batchmode)
2297126274Sdes				fatal("Batch file already specified.");
2298126274Sdes
2299126274Sdes			/* Allow "-" as stdin */
2300137015Sdes			if (strcmp(optarg, "-") != 0 &&
2301149749Sdes			    (infile = fopen(optarg, "r")) == NULL)
2302126274Sdes				fatal("%s (%s).", strerror(errno), optarg);
2303113908Sdes			showprogress = 0;
2304255767Sdes			quiet = batchmode = 1;
2305146998Sdes			addargs(&args, "-obatchmode yes");
230676259Sgreen			break;
2307262566Sdes		case 'f':
2308262566Sdes			global_fflag = 1;
2309262566Sdes			break;
2310204917Sdes		case 'p':
2311204917Sdes			global_pflag = 1;
2312204917Sdes			break;
2313204917Sdes		case 'D':
231492555Sdes			sftp_direct = optarg;
231592555Sdes			break;
2316221420Sdes		case 'l':
2317221420Sdes			limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2318221420Sdes			    &errstr);
2319221420Sdes			if (errstr != NULL)
2320221420Sdes				usage();
2321221420Sdes			limit_kbps *= 1024; /* kbps */
2322221420Sdes			break;
2323204917Sdes		case 'r':
2324204917Sdes			global_rflag = 1;
232592555Sdes			break;
232692555Sdes		case 'R':
232792555Sdes			num_requests = strtol(optarg, &cp, 10);
232892555Sdes			if (num_requests == 0 || *cp != '\0')
232998675Sdes				fatal("Invalid number of requests \"%s\"",
233092555Sdes				    optarg);
233192555Sdes			break;
2332204917Sdes		case 's':
2333204917Sdes			sftp_server = optarg;
2334204917Sdes			break;
2335204917Sdes		case 'S':
2336204917Sdes			ssh_program = optarg;
2337204917Sdes			replacearg(&args, 0, "%s", ssh_program);
2338204917Sdes			break;
233976259Sgreen		case 'h':
234076259Sgreen		default:
234176259Sgreen			usage();
234276259Sgreen		}
234376259Sgreen	}
234476259Sgreen
2345128456Sdes	if (!isatty(STDERR_FILENO))
2346128456Sdes		showprogress = 0;
2347128456Sdes
234898675Sdes	log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
234998675Sdes
235092555Sdes	if (sftp_direct == NULL) {
235192555Sdes		if (optind == argc || argc > (optind + 2))
235292555Sdes			usage();
235376259Sgreen
235492555Sdes		userhost = xstrdup(argv[optind]);
235592555Sdes		file2 = argv[optind+1];
235676259Sgreen
2357113908Sdes		if ((host = strrchr(userhost, '@')) == NULL)
235892555Sdes			host = userhost;
235992555Sdes		else {
236092555Sdes			*host++ = '\0';
236192555Sdes			if (!userhost[0]) {
236292555Sdes				fprintf(stderr, "Missing username\n");
236392555Sdes				usage();
236492555Sdes			}
2365204917Sdes			addargs(&args, "-l");
2366204917Sdes			addargs(&args, "%s", userhost);
236792555Sdes		}
236892555Sdes
2369126274Sdes		if ((cp = colon(host)) != NULL) {
2370126274Sdes			*cp++ = '\0';
2371126274Sdes			file1 = cp;
2372126274Sdes		}
2373126274Sdes
237492555Sdes		host = cleanhostname(host);
237592555Sdes		if (!*host) {
237692555Sdes			fprintf(stderr, "Missing hostname\n");
237776259Sgreen			usage();
237876259Sgreen		}
237976259Sgreen
238092555Sdes		addargs(&args, "-oProtocol %d", sshver);
238176259Sgreen
238292555Sdes		/* no subsystem if the server-spec contains a '/' */
238392555Sdes		if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
238492555Sdes			addargs(&args, "-s");
238576259Sgreen
2386204917Sdes		addargs(&args, "--");
238792555Sdes		addargs(&args, "%s", host);
238898675Sdes		addargs(&args, "%s", (sftp_server != NULL ?
238992555Sdes		    sftp_server : "sftp"));
239076259Sgreen
2391124208Sdes		connect_to_server(ssh_program, args.list, &in, &out);
239292555Sdes	} else {
239392555Sdes		args.list = NULL;
239492555Sdes		addargs(&args, "sftp-server");
239576259Sgreen
2396124208Sdes		connect_to_server(sftp_direct, args.list, &in, &out);
239792555Sdes	}
2398157016Sdes	freeargs(&args);
239976259Sgreen
2400221420Sdes	conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
2401204917Sdes	if (conn == NULL)
2402204917Sdes		fatal("Couldn't initialise connection to server");
240376259Sgreen
2404255767Sdes	if (!quiet) {
2405204917Sdes		if (sftp_direct == NULL)
2406204917Sdes			fprintf(stderr, "Connected to %s.\n", host);
2407204917Sdes		else
2408204917Sdes			fprintf(stderr, "Attached to %s.\n", sftp_direct);
2409204917Sdes	}
2410204917Sdes
2411204917Sdes	err = interactive_loop(conn, file1, file2);
2412204917Sdes
241398937Sdes#if !defined(USE_PIPES)
2414149749Sdes	shutdown(in, SHUT_RDWR);
2415149749Sdes	shutdown(out, SHUT_RDWR);
241698937Sdes#endif
241798937Sdes
241876259Sgreen	close(in);
241976259Sgreen	close(out);
2420126274Sdes	if (batchmode)
242176259Sgreen		fclose(infile);
242276259Sgreen
242398675Sdes	while (waitpid(sshpid, NULL, 0) == -1)
242498675Sdes		if (errno != EINTR)
242598675Sdes			fatal("Couldn't wait for ssh process: %s",
242698675Sdes			    strerror(errno));
242776259Sgreen
2428113908Sdes	exit(err == 0 ? 0 : 1);
242976259Sgreen}
2430