1323124Sdes/* $OpenBSD: sftp.c,v 1.175 2016/07/22 03:47:36 djm Exp $ */
276259Sgreen/*
3126274Sdes * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
476259Sgreen *
5126274Sdes * Permission to use, copy, modify, and distribute this software for any
6126274Sdes * purpose with or without fee is hereby granted, provided that the above
7126274Sdes * copyright notice and this permission notice appear in all copies.
876259Sgreen *
9126274Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10126274Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11126274Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12126274Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13126274Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14126274Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15126274Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1676259Sgreen */
1776259Sgreen
1876259Sgreen#include "includes.h"
1976259Sgreen
20295367Sdes#include <sys/param.h>	/* MIN MAX */
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
50295367Sdes#include <limits.h>
51162852Sdes#include <signal.h>
52323124Sdes#include <stdarg.h>
53162852Sdes#include <stdlib.h>
54162852Sdes#include <stdio.h>
55162852Sdes#include <string.h>
56162852Sdes#include <unistd.h>
57162852Sdes#include <stdarg.h>
58146998Sdes
59181111Sdes#ifdef HAVE_UTIL_H
60181111Sdes# include <util.h>
61181111Sdes#endif
62181111Sdes
6376259Sgreen#include "xmalloc.h"
6476259Sgreen#include "log.h"
6576259Sgreen#include "pathnames.h"
6692555Sdes#include "misc.h"
67323124Sdes#include "utf8.h"
6876259Sgreen
6976259Sgreen#include "sftp.h"
70295367Sdes#include "ssherr.h"
71295367Sdes#include "sshbuf.h"
7276259Sgreen#include "sftp-common.h"
7376259Sgreen#include "sftp-client.h"
7476259Sgreen
75204917Sdes#define DEFAULT_COPY_BUFLEN	32768	/* Size of buffer for up/download */
76294693Sdes#define DEFAULT_NUM_REQUESTS	64	/* # concurrent outstanding requests */
77204917Sdes
78126274Sdes/* File to read commands from */
79126274SdesFILE* infile;
80126274Sdes
81126274Sdes/* Are we in batchfile mode? */
82126274Sdesint batchmode = 0;
83126274Sdes
84126274Sdes/* PID of ssh transport process */
85126274Sdesstatic pid_t sshpid = -1;
86126274Sdes
87255767Sdes/* Suppress diagnositic messages */
88255767Sdesint quiet = 0;
89255767Sdes
90126274Sdes/* This is set to 0 if the progressmeter is not desired. */
91128456Sdesint showprogress = 1;
92126274Sdes
93204917Sdes/* When this option is set, we always recursively download/upload directories */
94204917Sdesint global_rflag = 0;
95204917Sdes
96295367Sdes/* When this option is set, we resume download or upload if possible */
97255767Sdesint global_aflag = 0;
98255767Sdes
99204917Sdes/* When this option is set, the file transfers will always preserve times */
100204917Sdesint global_pflag = 0;
101204917Sdes
102262566Sdes/* When this option is set, transfers will have fsync() called on each file */
103262566Sdesint global_fflag = 0;
104262566Sdes
105137015Sdes/* SIGINT received during command processing */
106137015Sdesvolatile sig_atomic_t interrupted = 0;
107137015Sdes
108137015Sdes/* I wish qsort() took a separate ctx for the comparison function...*/
109137015Sdesint sort_flag;
110137015Sdes
111204917Sdes/* Context used for commandline completion */
112204917Sdesstruct complete_ctx {
113204917Sdes	struct sftp_conn *conn;
114204917Sdes	char **remote_pathp;
115204917Sdes};
116204917Sdes
117126274Sdesint remote_glob(struct sftp_conn *, const char *, int,
118126274Sdes    int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
119126274Sdes
12098937Sdesextern char *__progname;
12198937Sdes
122126274Sdes/* Separators for interactive commands */
123126274Sdes#define WHITESPACE " \t\r\n"
12476259Sgreen
125137015Sdes/* ls flags */
126204917Sdes#define LS_LONG_VIEW	0x0001	/* Full view ala ls -l */
127204917Sdes#define LS_SHORT_VIEW	0x0002	/* Single row view ala ls -1 */
128204917Sdes#define LS_NUMERIC_VIEW	0x0004	/* Long view with numeric uid/gid */
129204917Sdes#define LS_NAME_SORT	0x0008	/* Sort by name (default) */
130204917Sdes#define LS_TIME_SORT	0x0010	/* Sort by mtime */
131204917Sdes#define LS_SIZE_SORT	0x0020	/* Sort by file size */
132204917Sdes#define LS_REVERSE_SORT	0x0040	/* Reverse sort order */
133204917Sdes#define LS_SHOW_ALL	0x0080	/* Don't skip filenames starting with '.' */
134204917Sdes#define LS_SI_UNITS	0x0100	/* Display sizes as K, M, G, etc. */
135113908Sdes
136204917Sdes#define VIEW_FLAGS	(LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
137137015Sdes#define SORT_FLAGS	(LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
138137015Sdes
139126274Sdes/* Commands for interactive mode */
140262566Sdesenum sftp_command {
141262566Sdes	I_CHDIR = 1,
142262566Sdes	I_CHGRP,
143262566Sdes	I_CHMOD,
144262566Sdes	I_CHOWN,
145262566Sdes	I_DF,
146262566Sdes	I_GET,
147262566Sdes	I_HELP,
148262566Sdes	I_LCHDIR,
149262566Sdes	I_LINK,
150262566Sdes	I_LLS,
151262566Sdes	I_LMKDIR,
152262566Sdes	I_LPWD,
153262566Sdes	I_LS,
154262566Sdes	I_LUMASK,
155262566Sdes	I_MKDIR,
156262566Sdes	I_PUT,
157262566Sdes	I_PWD,
158262566Sdes	I_QUIT,
159295367Sdes	I_REGET,
160262566Sdes	I_RENAME,
161295367Sdes	I_REPUT,
162262566Sdes	I_RM,
163262566Sdes	I_RMDIR,
164262566Sdes	I_SHELL,
165262566Sdes	I_SYMLINK,
166262566Sdes	I_VERSION,
167262566Sdes	I_PROGRESS,
168262566Sdes};
169126274Sdes
170126274Sdesstruct CMD {
171126274Sdes	const char *c;
172126274Sdes	const int n;
173204917Sdes	const int t;
174126274Sdes};
175126274Sdes
176204917Sdes/* Type of completion */
177204917Sdes#define NOARGS	0
178204917Sdes#define REMOTE	1
179204917Sdes#define LOCAL	2
180204917Sdes
181126274Sdesstatic const struct CMD cmds[] = {
182204917Sdes	{ "bye",	I_QUIT,		NOARGS	},
183204917Sdes	{ "cd",		I_CHDIR,	REMOTE	},
184204917Sdes	{ "chdir",	I_CHDIR,	REMOTE	},
185204917Sdes	{ "chgrp",	I_CHGRP,	REMOTE	},
186204917Sdes	{ "chmod",	I_CHMOD,	REMOTE	},
187204917Sdes	{ "chown",	I_CHOWN,	REMOTE	},
188204917Sdes	{ "df",		I_DF,		REMOTE	},
189204917Sdes	{ "dir",	I_LS,		REMOTE	},
190204917Sdes	{ "exit",	I_QUIT,		NOARGS	},
191204917Sdes	{ "get",	I_GET,		REMOTE	},
192204917Sdes	{ "help",	I_HELP,		NOARGS	},
193204917Sdes	{ "lcd",	I_LCHDIR,	LOCAL	},
194204917Sdes	{ "lchdir",	I_LCHDIR,	LOCAL	},
195204917Sdes	{ "lls",	I_LLS,		LOCAL	},
196204917Sdes	{ "lmkdir",	I_LMKDIR,	LOCAL	},
197221420Sdes	{ "ln",		I_LINK,		REMOTE	},
198204917Sdes	{ "lpwd",	I_LPWD,		LOCAL	},
199204917Sdes	{ "ls",		I_LS,		REMOTE	},
200204917Sdes	{ "lumask",	I_LUMASK,	NOARGS	},
201204917Sdes	{ "mkdir",	I_MKDIR,	REMOTE	},
202215116Sdes	{ "mget",	I_GET,		REMOTE	},
203215116Sdes	{ "mput",	I_PUT,		LOCAL	},
204204917Sdes	{ "progress",	I_PROGRESS,	NOARGS	},
205204917Sdes	{ "put",	I_PUT,		LOCAL	},
206204917Sdes	{ "pwd",	I_PWD,		REMOTE	},
207204917Sdes	{ "quit",	I_QUIT,		NOARGS	},
208255767Sdes	{ "reget",	I_REGET,	REMOTE	},
209204917Sdes	{ "rename",	I_RENAME,	REMOTE	},
210295367Sdes	{ "reput",	I_REPUT,	LOCAL	},
211204917Sdes	{ "rm",		I_RM,		REMOTE	},
212204917Sdes	{ "rmdir",	I_RMDIR,	REMOTE	},
213204917Sdes	{ "symlink",	I_SYMLINK,	REMOTE	},
214204917Sdes	{ "version",	I_VERSION,	NOARGS	},
215204917Sdes	{ "!",		I_SHELL,	NOARGS	},
216204917Sdes	{ "?",		I_HELP,		NOARGS	},
217204917Sdes	{ NULL,		-1,		-1	}
218126274Sdes};
219126274Sdes
220204917Sdesint interactive_loop(struct sftp_conn *, char *file1, char *file2);
221126274Sdes
222181111Sdes/* ARGSUSED */
22392555Sdesstatic void
224137015Sdeskillchild(int signo)
225137015Sdes{
226146998Sdes	if (sshpid > 1) {
227137015Sdes		kill(sshpid, SIGTERM);
228146998Sdes		waitpid(sshpid, NULL, 0);
229146998Sdes	}
230137015Sdes
231137015Sdes	_exit(1);
232137015Sdes}
233137015Sdes
234181111Sdes/* ARGSUSED */
235137015Sdesstatic void
236137015Sdescmd_interrupt(int signo)
237137015Sdes{
238137015Sdes	const char msg[] = "\rInterrupt  \n";
239146998Sdes	int olderrno = errno;
240137015Sdes
241255767Sdes	(void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
242137015Sdes	interrupted = 1;
243146998Sdes	errno = olderrno;
244137015Sdes}
245137015Sdes
246137015Sdesstatic void
247126274Sdeshelp(void)
248126274Sdes{
249192595Sdes	printf("Available commands:\n"
250192595Sdes	    "bye                                Quit sftp\n"
251192595Sdes	    "cd path                            Change remote directory to 'path'\n"
252192595Sdes	    "chgrp grp path                     Change group of file 'path' to 'grp'\n"
253192595Sdes	    "chmod mode path                    Change permissions of file 'path' to 'mode'\n"
254192595Sdes	    "chown own path                     Change owner of file 'path' to 'own'\n"
255192595Sdes	    "df [-hi] [path]                    Display statistics for current directory or\n"
256192595Sdes	    "                                   filesystem containing 'path'\n"
257192595Sdes	    "exit                               Quit sftp\n"
258295367Sdes	    "get [-afPpRr] remote [local]       Download file\n"
259295367Sdes	    "reget [-fPpRr] remote [local]      Resume download file\n"
260295367Sdes	    "reput [-fPpRr] [local] remote      Resume upload file\n"
261192595Sdes	    "help                               Display this help text\n"
262192595Sdes	    "lcd path                           Change local directory to 'path'\n"
263192595Sdes	    "lls [ls-options [path]]            Display local directory listing\n"
264192595Sdes	    "lmkdir path                        Create local directory\n"
265221420Sdes	    "ln [-s] oldpath newpath            Link remote file (-s for symlink)\n"
266192595Sdes	    "lpwd                               Print local working directory\n"
267204917Sdes	    "ls [-1afhlnrSt] [path]             Display remote directory listing\n"
268192595Sdes	    "lumask umask                       Set local umask to 'umask'\n"
269192595Sdes	    "mkdir path                         Create remote directory\n"
270192595Sdes	    "progress                           Toggle display of progress meter\n"
271295367Sdes	    "put [-afPpRr] local [remote]       Upload file\n"
272192595Sdes	    "pwd                                Display remote working directory\n"
273192595Sdes	    "quit                               Quit sftp\n"
274192595Sdes	    "rename oldpath newpath             Rename remote file\n"
275192595Sdes	    "rm path                            Delete remote file\n"
276192595Sdes	    "rmdir path                         Remove remote directory\n"
277192595Sdes	    "symlink oldpath newpath            Symlink remote file\n"
278192595Sdes	    "version                            Show SFTP version\n"
279192595Sdes	    "!command                           Execute 'command' in local shell\n"
280192595Sdes	    "!                                  Escape to local shell\n"
281192595Sdes	    "?                                  Synonym for help\n");
282126274Sdes}
283126274Sdes
284126274Sdesstatic void
285126274Sdeslocal_do_shell(const char *args)
286126274Sdes{
287126274Sdes	int status;
288126274Sdes	char *shell;
289126274Sdes	pid_t pid;
290126274Sdes
291126274Sdes	if (!*args)
292126274Sdes		args = NULL;
293126274Sdes
294221420Sdes	if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
295126274Sdes		shell = _PATH_BSHELL;
296126274Sdes
297126274Sdes	if ((pid = fork()) == -1)
298126274Sdes		fatal("Couldn't fork: %s", strerror(errno));
299126274Sdes
300126274Sdes	if (pid == 0) {
301126274Sdes		/* XXX: child has pipe fds to ssh subproc open - issue? */
302126274Sdes		if (args) {
303126274Sdes			debug3("Executing %s -c \"%s\"", shell, args);
304126274Sdes			execl(shell, shell, "-c", args, (char *)NULL);
305126274Sdes		} else {
306126274Sdes			debug3("Executing %s", shell);
307126274Sdes			execl(shell, shell, (char *)NULL);
308126274Sdes		}
309126274Sdes		fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
310126274Sdes		    strerror(errno));
311126274Sdes		_exit(1);
312126274Sdes	}
313126274Sdes	while (waitpid(pid, &status, 0) == -1)
314126274Sdes		if (errno != EINTR)
315126274Sdes			fatal("Couldn't wait for child: %s", strerror(errno));
316126274Sdes	if (!WIFEXITED(status))
317162852Sdes		error("Shell exited abnormally");
318126274Sdes	else if (WEXITSTATUS(status))
319126274Sdes		error("Shell exited with status %d", WEXITSTATUS(status));
320126274Sdes}
321126274Sdes
322126274Sdesstatic void
323126274Sdeslocal_do_ls(const char *args)
324126274Sdes{
325126274Sdes	if (!args || !*args)
326126274Sdes		local_do_shell(_PATH_LS);
327126274Sdes	else {
328126274Sdes		int len = strlen(_PATH_LS " ") + strlen(args) + 1;
329126274Sdes		char *buf = xmalloc(len);
330126274Sdes
331126274Sdes		/* XXX: quoting - rip quoting code from ftp? */
332126274Sdes		snprintf(buf, len, _PATH_LS " %s", args);
333126274Sdes		local_do_shell(buf);
334255767Sdes		free(buf);
335126274Sdes	}
336126274Sdes}
337126274Sdes
338126274Sdes/* Strip one path (usually the pwd) from the start of another */
339126274Sdesstatic char *
340323124Sdespath_strip(const char *path, const char *strip)
341126274Sdes{
342126274Sdes	size_t len;
343126274Sdes
344126274Sdes	if (strip == NULL)
345126274Sdes		return (xstrdup(path));
346126274Sdes
347126274Sdes	len = strlen(strip);
348146998Sdes	if (strncmp(path, strip, len) == 0) {
349126274Sdes		if (strip[len - 1] != '/' && path[len] == '/')
350126274Sdes			len++;
351126274Sdes		return (xstrdup(path + len));
352126274Sdes	}
353126274Sdes
354126274Sdes	return (xstrdup(path));
355126274Sdes}
356126274Sdes
357126274Sdesstatic char *
358323124Sdesmake_absolute(char *p, const char *pwd)
359126274Sdes{
360137015Sdes	char *abs_str;
361126274Sdes
362126274Sdes	/* Derelativise */
363126274Sdes	if (p && p[0] != '/') {
364137015Sdes		abs_str = path_append(pwd, p);
365255767Sdes		free(p);
366137015Sdes		return(abs_str);
367126274Sdes	} else
368126274Sdes		return(p);
369126274Sdes}
370126274Sdes
371126274Sdesstatic int
372255767Sdesparse_getput_flags(const char *cmd, char **argv, int argc,
373262566Sdes    int *aflag, int *fflag, int *pflag, int *rflag)
374126274Sdes{
375181111Sdes	extern int opterr, optind, optopt, optreset;
376181111Sdes	int ch;
377126274Sdes
378181111Sdes	optind = optreset = 1;
379181111Sdes	opterr = 0;
380181111Sdes
381262566Sdes	*aflag = *fflag = *rflag = *pflag = 0;
382262566Sdes	while ((ch = getopt(argc, argv, "afPpRr")) != -1) {
383181111Sdes		switch (ch) {
384255767Sdes		case 'a':
385255767Sdes			*aflag = 1;
386255767Sdes			break;
387262566Sdes		case 'f':
388262566Sdes			*fflag = 1;
389262566Sdes			break;
390126274Sdes		case 'p':
391126274Sdes		case 'P':
392126274Sdes			*pflag = 1;
393126274Sdes			break;
394204917Sdes		case 'r':
395204917Sdes		case 'R':
396204917Sdes			*rflag = 1;
397204917Sdes			break;
398126274Sdes		default:
399181111Sdes			error("%s: Invalid flag -%c", cmd, optopt);
400181111Sdes			return -1;
401126274Sdes		}
402126274Sdes	}
403126274Sdes
404181111Sdes	return optind;
405126274Sdes}
406126274Sdes
407126274Sdesstatic int
408221420Sdesparse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
409221420Sdes{
410221420Sdes	extern int opterr, optind, optopt, optreset;
411221420Sdes	int ch;
412221420Sdes
413221420Sdes	optind = optreset = 1;
414221420Sdes	opterr = 0;
415221420Sdes
416221420Sdes	*sflag = 0;
417221420Sdes	while ((ch = getopt(argc, argv, "s")) != -1) {
418221420Sdes		switch (ch) {
419221420Sdes		case 's':
420221420Sdes			*sflag = 1;
421221420Sdes			break;
422221420Sdes		default:
423221420Sdes			error("%s: Invalid flag -%c", cmd, optopt);
424221420Sdes			return -1;
425221420Sdes		}
426221420Sdes	}
427221420Sdes
428221420Sdes	return optind;
429221420Sdes}
430221420Sdes
431221420Sdesstatic int
432262566Sdesparse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
433262566Sdes{
434262566Sdes	extern int opterr, optind, optopt, optreset;
435262566Sdes	int ch;
436262566Sdes
437262566Sdes	optind = optreset = 1;
438262566Sdes	opterr = 0;
439262566Sdes
440262566Sdes	*lflag = 0;
441262566Sdes	while ((ch = getopt(argc, argv, "l")) != -1) {
442262566Sdes		switch (ch) {
443262566Sdes		case 'l':
444262566Sdes			*lflag = 1;
445262566Sdes			break;
446262566Sdes		default:
447262566Sdes			error("%s: Invalid flag -%c", cmd, optopt);
448262566Sdes			return -1;
449262566Sdes		}
450262566Sdes	}
451262566Sdes
452262566Sdes	return optind;
453262566Sdes}
454262566Sdes
455262566Sdesstatic int
456181111Sdesparse_ls_flags(char **argv, int argc, int *lflag)
457126274Sdes{
458181111Sdes	extern int opterr, optind, optopt, optreset;
459181111Sdes	int ch;
460126274Sdes
461181111Sdes	optind = optreset = 1;
462181111Sdes	opterr = 0;
463181111Sdes
464137015Sdes	*lflag = LS_NAME_SORT;
465204917Sdes	while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
466181111Sdes		switch (ch) {
467181111Sdes		case '1':
468181111Sdes			*lflag &= ~VIEW_FLAGS;
469181111Sdes			*lflag |= LS_SHORT_VIEW;
470181111Sdes			break;
471181111Sdes		case 'S':
472181111Sdes			*lflag &= ~SORT_FLAGS;
473181111Sdes			*lflag |= LS_SIZE_SORT;
474181111Sdes			break;
475181111Sdes		case 'a':
476181111Sdes			*lflag |= LS_SHOW_ALL;
477181111Sdes			break;
478181111Sdes		case 'f':
479181111Sdes			*lflag &= ~SORT_FLAGS;
480181111Sdes			break;
481204917Sdes		case 'h':
482204917Sdes			*lflag |= LS_SI_UNITS;
483204917Sdes			break;
484181111Sdes		case 'l':
485204917Sdes			*lflag &= ~LS_SHORT_VIEW;
486181111Sdes			*lflag |= LS_LONG_VIEW;
487181111Sdes			break;
488181111Sdes		case 'n':
489204917Sdes			*lflag &= ~LS_SHORT_VIEW;
490181111Sdes			*lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
491181111Sdes			break;
492181111Sdes		case 'r':
493181111Sdes			*lflag |= LS_REVERSE_SORT;
494181111Sdes			break;
495181111Sdes		case 't':
496181111Sdes			*lflag &= ~SORT_FLAGS;
497181111Sdes			*lflag |= LS_TIME_SORT;
498181111Sdes			break;
499181111Sdes		default:
500181111Sdes			error("ls: Invalid flag -%c", optopt);
501181111Sdes			return -1;
502126274Sdes		}
503126274Sdes	}
504126274Sdes
505181111Sdes	return optind;
506126274Sdes}
507126274Sdes
508126274Sdesstatic int
509181111Sdesparse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
510126274Sdes{
511181111Sdes	extern int opterr, optind, optopt, optreset;
512181111Sdes	int ch;
513126274Sdes
514181111Sdes	optind = optreset = 1;
515181111Sdes	opterr = 0;
516126274Sdes
517181111Sdes	*hflag = *iflag = 0;
518181111Sdes	while ((ch = getopt(argc, argv, "hi")) != -1) {
519181111Sdes		switch (ch) {
520181111Sdes		case 'h':
521181111Sdes			*hflag = 1;
522181111Sdes			break;
523181111Sdes		case 'i':
524181111Sdes			*iflag = 1;
525181111Sdes			break;
526181111Sdes		default:
527181111Sdes			error("%s: Invalid flag -%c", cmd, optopt);
528181111Sdes			return -1;
529126274Sdes		}
530126274Sdes	}
531126274Sdes
532181111Sdes	return optind;
533126274Sdes}
534126274Sdes
535126274Sdesstatic int
536262566Sdesparse_no_flags(const char *cmd, char **argv, int argc)
537262566Sdes{
538262566Sdes	extern int opterr, optind, optopt, optreset;
539262566Sdes	int ch;
540262566Sdes
541262566Sdes	optind = optreset = 1;
542262566Sdes	opterr = 0;
543262566Sdes
544262566Sdes	while ((ch = getopt(argc, argv, "")) != -1) {
545262566Sdes		switch (ch) {
546262566Sdes		default:
547262566Sdes			error("%s: Invalid flag -%c", cmd, optopt);
548262566Sdes			return -1;
549262566Sdes		}
550262566Sdes	}
551262566Sdes
552262566Sdes	return optind;
553262566Sdes}
554262566Sdes
555262566Sdesstatic int
556323124Sdesis_dir(const char *path)
557126274Sdes{
558126274Sdes	struct stat sb;
559126274Sdes
560126274Sdes	/* XXX: report errors? */
561126274Sdes	if (stat(path, &sb) == -1)
562126274Sdes		return(0);
563126274Sdes
564162852Sdes	return(S_ISDIR(sb.st_mode));
565126274Sdes}
566126274Sdes
567126274Sdesstatic int
568323124Sdesremote_is_dir(struct sftp_conn *conn, const char *path)
569126274Sdes{
570126274Sdes	Attrib *a;
571126274Sdes
572126274Sdes	/* XXX: report errors? */
573126274Sdes	if ((a = do_stat(conn, path, 1)) == NULL)
574126274Sdes		return(0);
575126274Sdes	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
576126274Sdes		return(0);
577162852Sdes	return(S_ISDIR(a->perm));
578126274Sdes}
579126274Sdes
580204917Sdes/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
581126274Sdesstatic int
582323124Sdespathname_is_dir(const char *pathname)
583126274Sdes{
584204917Sdes	size_t l = strlen(pathname);
585204917Sdes
586204917Sdes	return l > 0 && pathname[l - 1] == '/';
587204917Sdes}
588204917Sdes
589204917Sdesstatic int
590323124Sdesprocess_get(struct sftp_conn *conn, const char *src, const char *dst,
591323124Sdes    const char *pwd, int pflag, int rflag, int resume, int fflag)
592204917Sdes{
593126274Sdes	char *abs_src = NULL;
594126274Sdes	char *abs_dst = NULL;
595126274Sdes	glob_t g;
596204917Sdes	char *filename, *tmp=NULL;
597295367Sdes	int i, r, err = 0;
598126274Sdes
599126274Sdes	abs_src = xstrdup(src);
600126274Sdes	abs_src = make_absolute(abs_src, pwd);
601204917Sdes	memset(&g, 0, sizeof(g));
602126274Sdes
603126274Sdes	debug3("Looking up %s", abs_src);
604295367Sdes	if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
605295367Sdes		if (r == GLOB_NOSPACE) {
606295367Sdes			error("Too many matches for \"%s\".", abs_src);
607295367Sdes		} else {
608295367Sdes			error("File \"%s\" not found.", abs_src);
609295367Sdes		}
610126274Sdes		err = -1;
611126274Sdes		goto out;
612126274Sdes	}
613126274Sdes
614204917Sdes	/*
615204917Sdes	 * If multiple matches then dst must be a directory or
616204917Sdes	 * unspecified.
617204917Sdes	 */
618204917Sdes	if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
619204917Sdes		error("Multiple source paths, but destination "
620204917Sdes		    "\"%s\" is not a directory", dst);
621126274Sdes		err = -1;
622126274Sdes		goto out;
623126274Sdes	}
624126274Sdes
625137015Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
626204917Sdes		tmp = xstrdup(g.gl_pathv[i]);
627204917Sdes		if ((filename = basename(tmp)) == NULL) {
628204917Sdes			error("basename %s: %s", tmp, strerror(errno));
629255767Sdes			free(tmp);
630126274Sdes			err = -1;
631126274Sdes			goto out;
632126274Sdes		}
633126274Sdes
634126274Sdes		if (g.gl_matchc == 1 && dst) {
635126274Sdes			if (is_dir(dst)) {
636204917Sdes				abs_dst = path_append(dst, filename);
637204917Sdes			} else {
638126274Sdes				abs_dst = xstrdup(dst);
639204917Sdes			}
640126274Sdes		} else if (dst) {
641204917Sdes			abs_dst = path_append(dst, filename);
642204917Sdes		} else {
643204917Sdes			abs_dst = xstrdup(filename);
644204917Sdes		}
645255767Sdes		free(tmp);
646126274Sdes
647255767Sdes		resume |= global_aflag;
648255767Sdes		if (!quiet && resume)
649323124Sdes			mprintf("Resuming %s to %s\n",
650323124Sdes			    g.gl_pathv[i], abs_dst);
651255767Sdes		else if (!quiet && !resume)
652323124Sdes			mprintf("Fetching %s to %s\n",
653323124Sdes			    g.gl_pathv[i], abs_dst);
654204917Sdes		if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
655255767Sdes			if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
656262566Sdes			    pflag || global_pflag, 1, resume,
657262566Sdes			    fflag || global_fflag) == -1)
658204917Sdes				err = -1;
659204917Sdes		} else {
660204917Sdes			if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
661262566Sdes			    pflag || global_pflag, resume,
662262566Sdes			    fflag || global_fflag) == -1)
663204917Sdes				err = -1;
664204917Sdes		}
665255767Sdes		free(abs_dst);
666126274Sdes		abs_dst = NULL;
667126274Sdes	}
668126274Sdes
669126274Sdesout:
670255767Sdes	free(abs_src);
671126274Sdes	globfree(&g);
672126274Sdes	return(err);
673126274Sdes}
674126274Sdes
675126274Sdesstatic int
676323124Sdesprocess_put(struct sftp_conn *conn, const char *src, const char *dst,
677323124Sdes    const char *pwd, int pflag, int rflag, int resume, int fflag)
678126274Sdes{
679126274Sdes	char *tmp_dst = NULL;
680126274Sdes	char *abs_dst = NULL;
681204917Sdes	char *tmp = NULL, *filename = NULL;
682126274Sdes	glob_t g;
683126274Sdes	int err = 0;
684204917Sdes	int i, dst_is_dir = 1;
685181111Sdes	struct stat sb;
686126274Sdes
687126274Sdes	if (dst) {
688126274Sdes		tmp_dst = xstrdup(dst);
689126274Sdes		tmp_dst = make_absolute(tmp_dst, pwd);
690126274Sdes	}
691126274Sdes
692126274Sdes	memset(&g, 0, sizeof(g));
693126274Sdes	debug3("Looking up %s", src);
694204917Sdes	if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
695126274Sdes		error("File \"%s\" not found.", src);
696126274Sdes		err = -1;
697126274Sdes		goto out;
698126274Sdes	}
699126274Sdes
700204917Sdes	/* If we aren't fetching to pwd then stash this status for later */
701204917Sdes	if (tmp_dst != NULL)
702204917Sdes		dst_is_dir = remote_is_dir(conn, tmp_dst);
703204917Sdes
704126274Sdes	/* If multiple matches, dst may be directory or unspecified */
705204917Sdes	if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
706204917Sdes		error("Multiple paths match, but destination "
707204917Sdes		    "\"%s\" is not a directory", tmp_dst);
708126274Sdes		err = -1;
709126274Sdes		goto out;
710126274Sdes	}
711126274Sdes
712137015Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
713181111Sdes		if (stat(g.gl_pathv[i], &sb) == -1) {
714181111Sdes			err = -1;
715181111Sdes			error("stat %s: %s", g.gl_pathv[i], strerror(errno));
716181111Sdes			continue;
717181111Sdes		}
718262566Sdes
719204917Sdes		tmp = xstrdup(g.gl_pathv[i]);
720204917Sdes		if ((filename = basename(tmp)) == NULL) {
721204917Sdes			error("basename %s: %s", tmp, strerror(errno));
722255767Sdes			free(tmp);
723126274Sdes			err = -1;
724126274Sdes			goto out;
725126274Sdes		}
726126274Sdes
727126274Sdes		if (g.gl_matchc == 1 && tmp_dst) {
728126274Sdes			/* If directory specified, append filename */
729204917Sdes			if (dst_is_dir)
730204917Sdes				abs_dst = path_append(tmp_dst, filename);
731204917Sdes			else
732126274Sdes				abs_dst = xstrdup(tmp_dst);
733126274Sdes		} else if (tmp_dst) {
734204917Sdes			abs_dst = path_append(tmp_dst, filename);
735204917Sdes		} else {
736204917Sdes			abs_dst = make_absolute(xstrdup(filename), pwd);
737204917Sdes		}
738255767Sdes		free(tmp);
739126274Sdes
740295367Sdes                resume |= global_aflag;
741295367Sdes		if (!quiet && resume)
742323124Sdes			mprintf("Resuming upload of %s to %s\n",
743323124Sdes			    g.gl_pathv[i], abs_dst);
744295367Sdes		else if (!quiet && !resume)
745323124Sdes			mprintf("Uploading %s to %s\n",
746323124Sdes			    g.gl_pathv[i], abs_dst);
747204917Sdes		if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
748204917Sdes			if (upload_dir(conn, g.gl_pathv[i], abs_dst,
749295367Sdes			    pflag || global_pflag, 1, resume,
750262566Sdes			    fflag || global_fflag) == -1)
751204917Sdes				err = -1;
752204917Sdes		} else {
753204917Sdes			if (do_upload(conn, g.gl_pathv[i], abs_dst,
754295367Sdes			    pflag || global_pflag, resume,
755262566Sdes			    fflag || global_fflag) == -1)
756204917Sdes				err = -1;
757204917Sdes		}
758126274Sdes	}
759126274Sdes
760126274Sdesout:
761255767Sdes	free(abs_dst);
762255767Sdes	free(tmp_dst);
763126274Sdes	globfree(&g);
764126274Sdes	return(err);
765126274Sdes}
766126274Sdes
767126274Sdesstatic int
768126274Sdessdirent_comp(const void *aa, const void *bb)
769126274Sdes{
770126274Sdes	SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
771126274Sdes	SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
772137015Sdes	int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
773126274Sdes
774137015Sdes#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
775137015Sdes	if (sort_flag & LS_NAME_SORT)
776137015Sdes		return (rmul * strcmp(a->filename, b->filename));
777137015Sdes	else if (sort_flag & LS_TIME_SORT)
778137015Sdes		return (rmul * NCMP(a->a.mtime, b->a.mtime));
779137015Sdes	else if (sort_flag & LS_SIZE_SORT)
780137015Sdes		return (rmul * NCMP(a->a.size, b->a.size));
781137015Sdes
782137015Sdes	fatal("Unknown ls sort type");
783126274Sdes}
784126274Sdes
785126274Sdes/* sftp ls.1 replacement for directories */
786126274Sdesstatic int
787323124Sdesdo_ls_dir(struct sftp_conn *conn, const char *path,
788323124Sdes    const char *strip_path, int lflag)
789126274Sdes{
790149749Sdes	int n;
791149749Sdes	u_int c = 1, colspace = 0, columns = 1;
792126274Sdes	SFTP_DIRENT **d;
793126274Sdes
794126274Sdes	if ((n = do_readdir(conn, path, &d)) != 0)
795126274Sdes		return (n);
796126274Sdes
797137015Sdes	if (!(lflag & LS_SHORT_VIEW)) {
798149749Sdes		u_int m = 0, width = 80;
799126274Sdes		struct winsize ws;
800126274Sdes		char *tmp;
801126274Sdes
802126274Sdes		/* Count entries for sort and find longest filename */
803137015Sdes		for (n = 0; d[n] != NULL; n++) {
804137015Sdes			if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
805137015Sdes				m = MAX(m, strlen(d[n]->filename));
806137015Sdes		}
807126274Sdes
808126274Sdes		/* Add any subpath that also needs to be counted */
809126274Sdes		tmp = path_strip(path, strip_path);
810126274Sdes		m += strlen(tmp);
811255767Sdes		free(tmp);
812126274Sdes
813126274Sdes		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
814126274Sdes			width = ws.ws_col;
815126274Sdes
816126274Sdes		columns = width / (m + 2);
817126274Sdes		columns = MAX(columns, 1);
818126274Sdes		colspace = width / columns;
819126274Sdes		colspace = MIN(colspace, width);
820126274Sdes	}
821126274Sdes
822137015Sdes	if (lflag & SORT_FLAGS) {
823157016Sdes		for (n = 0; d[n] != NULL; n++)
824157016Sdes			;	/* count entries */
825137015Sdes		sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
826137015Sdes		qsort(d, n, sizeof(*d), sdirent_comp);
827137015Sdes	}
828126274Sdes
829137015Sdes	for (n = 0; d[n] != NULL && !interrupted; n++) {
830126274Sdes		char *tmp, *fname;
831126274Sdes
832137015Sdes		if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
833137015Sdes			continue;
834137015Sdes
835126274Sdes		tmp = path_append(path, d[n]->filename);
836126274Sdes		fname = path_strip(tmp, strip_path);
837255767Sdes		free(tmp);
838126274Sdes
839137015Sdes		if (lflag & LS_LONG_VIEW) {
840204917Sdes			if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
841137015Sdes				char *lname;
842137015Sdes				struct stat sb;
843126274Sdes
844137015Sdes				memset(&sb, 0, sizeof(sb));
845137015Sdes				attrib_to_stat(&d[n]->a, &sb);
846204917Sdes				lname = ls_file(fname, &sb, 1,
847204917Sdes				    (lflag & LS_SI_UNITS));
848323124Sdes				mprintf("%s\n", lname);
849255767Sdes				free(lname);
850137015Sdes			} else
851323124Sdes				mprintf("%s\n", d[n]->longname);
852126274Sdes		} else {
853323124Sdes			mprintf("%-*s", colspace, fname);
854126274Sdes			if (c >= columns) {
855126274Sdes				printf("\n");
856126274Sdes				c = 1;
857126274Sdes			} else
858126274Sdes				c++;
859126274Sdes		}
860126274Sdes
861255767Sdes		free(fname);
862126274Sdes	}
863126274Sdes
864137015Sdes	if (!(lflag & LS_LONG_VIEW) && (c != 1))
865126274Sdes		printf("\n");
866126274Sdes
867126274Sdes	free_sftp_dirents(d);
868126274Sdes	return (0);
869126274Sdes}
870126274Sdes
871126274Sdes/* sftp ls.1 replacement which handles path globs */
872126274Sdesstatic int
873323124Sdesdo_globbed_ls(struct sftp_conn *conn, const char *path,
874323124Sdes    const char *strip_path, int lflag)
875126274Sdes{
876221420Sdes	char *fname, *lname;
877126274Sdes	glob_t g;
878295367Sdes	int err, r;
879221420Sdes	struct winsize ws;
880221420Sdes	u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
881126274Sdes
882126274Sdes	memset(&g, 0, sizeof(g));
883126274Sdes
884295367Sdes	if ((r = remote_glob(conn, path,
885240075Sdes	    GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
886295367Sdes	    NULL, &g)) != 0 ||
887221420Sdes	    (g.gl_pathc && !g.gl_matchc)) {
888146998Sdes		if (g.gl_pathc)
889146998Sdes			globfree(&g);
890295367Sdes		if (r == GLOB_NOSPACE) {
891295367Sdes			error("Can't ls: Too many matches for \"%s\"", path);
892295367Sdes		} else {
893295367Sdes			error("Can't ls: \"%s\" not found", path);
894295367Sdes		}
895221420Sdes		return -1;
896126274Sdes	}
897126274Sdes
898137015Sdes	if (interrupted)
899137015Sdes		goto out;
900137015Sdes
901126274Sdes	/*
902146998Sdes	 * If the glob returns a single match and it is a directory,
903146998Sdes	 * then just list its contents.
904126274Sdes	 */
905221420Sdes	if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
906221420Sdes	    S_ISDIR(g.gl_statv[0]->st_mode)) {
907221420Sdes		err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
908221420Sdes		globfree(&g);
909221420Sdes		return err;
910126274Sdes	}
911126274Sdes
912221420Sdes	if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
913221420Sdes		width = ws.ws_col;
914221420Sdes
915137015Sdes	if (!(lflag & LS_SHORT_VIEW)) {
916126274Sdes		/* Count entries for sort and find longest filename */
917126274Sdes		for (i = 0; g.gl_pathv[i]; i++)
918126274Sdes			m = MAX(m, strlen(g.gl_pathv[i]));
919126274Sdes
920126274Sdes		columns = width / (m + 2);
921126274Sdes		columns = MAX(columns, 1);
922126274Sdes		colspace = width / columns;
923126274Sdes	}
924126274Sdes
925240075Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
926126274Sdes		fname = path_strip(g.gl_pathv[i], strip_path);
927137015Sdes		if (lflag & LS_LONG_VIEW) {
928221420Sdes			if (g.gl_statv[i] == NULL) {
929221420Sdes				error("no stat information for %s", fname);
930221420Sdes				continue;
931221420Sdes			}
932221420Sdes			lname = ls_file(fname, g.gl_statv[i], 1,
933221420Sdes			    (lflag & LS_SI_UNITS));
934323124Sdes			mprintf("%s\n", lname);
935255767Sdes			free(lname);
936126274Sdes		} else {
937323124Sdes			mprintf("%-*s", colspace, fname);
938126274Sdes			if (c >= columns) {
939126274Sdes				printf("\n");
940126274Sdes				c = 1;
941126274Sdes			} else
942126274Sdes				c++;
943126274Sdes		}
944255767Sdes		free(fname);
945126274Sdes	}
946126274Sdes
947137015Sdes	if (!(lflag & LS_LONG_VIEW) && (c != 1))
948126274Sdes		printf("\n");
949126274Sdes
950137015Sdes out:
951126274Sdes	if (g.gl_pathc)
952126274Sdes		globfree(&g);
953126274Sdes
954221420Sdes	return 0;
955126274Sdes}
956126274Sdes
957126274Sdesstatic int
958323124Sdesdo_df(struct sftp_conn *conn, const char *path, int hflag, int iflag)
959181111Sdes{
960181111Sdes	struct sftp_statvfs st;
961181111Sdes	char s_used[FMT_SCALED_STRSIZE];
962181111Sdes	char s_avail[FMT_SCALED_STRSIZE];
963181111Sdes	char s_root[FMT_SCALED_STRSIZE];
964181111Sdes	char s_total[FMT_SCALED_STRSIZE];
965204917Sdes	unsigned long long ffree;
966181111Sdes
967181111Sdes	if (do_statvfs(conn, path, &st, 1) == -1)
968181111Sdes		return -1;
969181111Sdes	if (iflag) {
970204917Sdes		ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
971181111Sdes		printf("     Inodes        Used       Avail      "
972181111Sdes		    "(root)    %%Capacity\n");
973181111Sdes		printf("%11llu %11llu %11llu %11llu         %3llu%%\n",
974181111Sdes		    (unsigned long long)st.f_files,
975181111Sdes		    (unsigned long long)(st.f_files - st.f_ffree),
976181111Sdes		    (unsigned long long)st.f_favail,
977204917Sdes		    (unsigned long long)st.f_ffree, ffree);
978181111Sdes	} else if (hflag) {
979181111Sdes		strlcpy(s_used, "error", sizeof(s_used));
980181111Sdes		strlcpy(s_avail, "error", sizeof(s_avail));
981181111Sdes		strlcpy(s_root, "error", sizeof(s_root));
982181111Sdes		strlcpy(s_total, "error", sizeof(s_total));
983181111Sdes		fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
984181111Sdes		fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
985181111Sdes		fmt_scaled(st.f_bfree * st.f_frsize, s_root);
986181111Sdes		fmt_scaled(st.f_blocks * st.f_frsize, s_total);
987181111Sdes		printf("    Size     Used    Avail   (root)    %%Capacity\n");
988181111Sdes		printf("%7sB %7sB %7sB %7sB         %3llu%%\n",
989181111Sdes		    s_total, s_used, s_avail, s_root,
990181111Sdes		    (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
991181111Sdes		    st.f_blocks));
992181111Sdes	} else {
993181111Sdes		printf("        Size         Used        Avail       "
994181111Sdes		    "(root)    %%Capacity\n");
995181111Sdes		printf("%12llu %12llu %12llu %12llu         %3llu%%\n",
996181111Sdes		    (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
997181111Sdes		    (unsigned long long)(st.f_frsize *
998181111Sdes		    (st.f_blocks - st.f_bfree) / 1024),
999181111Sdes		    (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
1000181111Sdes		    (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
1001181111Sdes		    (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
1002181111Sdes		    st.f_blocks));
1003181111Sdes	}
1004181111Sdes	return 0;
1005181111Sdes}
1006181111Sdes
1007181111Sdes/*
1008181111Sdes * Undo escaping of glob sequences in place. Used to undo extra escaping
1009181111Sdes * applied in makeargv() when the string is destined for a function that
1010181111Sdes * does not glob it.
1011181111Sdes */
1012181111Sdesstatic void
1013181111Sdesundo_glob_escape(char *s)
1014181111Sdes{
1015181111Sdes	size_t i, j;
1016181111Sdes
1017181111Sdes	for (i = j = 0;;) {
1018181111Sdes		if (s[i] == '\0') {
1019181111Sdes			s[j] = '\0';
1020181111Sdes			return;
1021181111Sdes		}
1022181111Sdes		if (s[i] != '\\') {
1023181111Sdes			s[j++] = s[i++];
1024181111Sdes			continue;
1025181111Sdes		}
1026181111Sdes		/* s[i] == '\\' */
1027181111Sdes		++i;
1028181111Sdes		switch (s[i]) {
1029181111Sdes		case '?':
1030181111Sdes		case '[':
1031181111Sdes		case '*':
1032181111Sdes		case '\\':
1033181111Sdes			s[j++] = s[i++];
1034181111Sdes			break;
1035181111Sdes		case '\0':
1036181111Sdes			s[j++] = '\\';
1037181111Sdes			s[j] = '\0';
1038181111Sdes			return;
1039181111Sdes		default:
1040181111Sdes			s[j++] = '\\';
1041181111Sdes			s[j++] = s[i++];
1042181111Sdes			break;
1043181111Sdes		}
1044181111Sdes	}
1045181111Sdes}
1046181111Sdes
1047181111Sdes/*
1048181111Sdes * Split a string into an argument vector using sh(1)-style quoting,
1049181111Sdes * comment and escaping rules, but with some tweaks to handle glob(3)
1050181111Sdes * wildcards.
1051204917Sdes * The "sloppy" flag allows for recovery from missing terminating quote, for
1052204917Sdes * use in parsing incomplete commandlines during tab autocompletion.
1053204917Sdes *
1054181111Sdes * Returns NULL on error or a NULL-terminated array of arguments.
1055204917Sdes *
1056204917Sdes * If "lastquote" is not NULL, the quoting character used for the last
1057204917Sdes * argument is placed in *lastquote ("\0", "'" or "\"").
1058262566Sdes *
1059204917Sdes * If "terminated" is not NULL, *terminated will be set to 1 when the
1060204917Sdes * last argument's quote has been properly terminated or 0 otherwise.
1061204917Sdes * This parameter is only of use if "sloppy" is set.
1062181111Sdes */
1063181111Sdes#define MAXARGS 	128
1064181111Sdes#define MAXARGLEN	8192
1065181111Sdesstatic char **
1066204917Sdesmakeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1067204917Sdes    u_int *terminated)
1068181111Sdes{
1069181111Sdes	int argc, quot;
1070181111Sdes	size_t i, j;
1071181111Sdes	static char argvs[MAXARGLEN];
1072181111Sdes	static char *argv[MAXARGS + 1];
1073181111Sdes	enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1074181111Sdes
1075181111Sdes	*argcp = argc = 0;
1076181111Sdes	if (strlen(arg) > sizeof(argvs) - 1) {
1077181111Sdes args_too_longs:
1078181111Sdes		error("string too long");
1079181111Sdes		return NULL;
1080181111Sdes	}
1081204917Sdes	if (terminated != NULL)
1082204917Sdes		*terminated = 1;
1083204917Sdes	if (lastquote != NULL)
1084204917Sdes		*lastquote = '\0';
1085181111Sdes	state = MA_START;
1086181111Sdes	i = j = 0;
1087181111Sdes	for (;;) {
1088248619Sdes		if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
1089248619Sdes			error("Too many arguments.");
1090248619Sdes			return NULL;
1091248619Sdes		}
1092262566Sdes		if (isspace((unsigned char)arg[i])) {
1093181111Sdes			if (state == MA_UNQUOTED) {
1094181111Sdes				/* Terminate current argument */
1095181111Sdes				argvs[j++] = '\0';
1096181111Sdes				argc++;
1097181111Sdes				state = MA_START;
1098181111Sdes			} else if (state != MA_START)
1099181111Sdes				argvs[j++] = arg[i];
1100181111Sdes		} else if (arg[i] == '"' || arg[i] == '\'') {
1101181111Sdes			q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1102181111Sdes			if (state == MA_START) {
1103181111Sdes				argv[argc] = argvs + j;
1104181111Sdes				state = q;
1105204917Sdes				if (lastquote != NULL)
1106204917Sdes					*lastquote = arg[i];
1107262566Sdes			} else if (state == MA_UNQUOTED)
1108181111Sdes				state = q;
1109181111Sdes			else if (state == q)
1110181111Sdes				state = MA_UNQUOTED;
1111181111Sdes			else
1112181111Sdes				argvs[j++] = arg[i];
1113181111Sdes		} else if (arg[i] == '\\') {
1114181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE) {
1115181111Sdes				quot = state == MA_SQUOTE ? '\'' : '"';
1116181111Sdes				/* Unescape quote we are in */
1117181111Sdes				/* XXX support \n and friends? */
1118181111Sdes				if (arg[i + 1] == quot) {
1119181111Sdes					i++;
1120181111Sdes					argvs[j++] = arg[i];
1121181111Sdes				} else if (arg[i + 1] == '?' ||
1122181111Sdes				    arg[i + 1] == '[' || arg[i + 1] == '*') {
1123181111Sdes					/*
1124181111Sdes					 * Special case for sftp: append
1125181111Sdes					 * double-escaped glob sequence -
1126181111Sdes					 * glob will undo one level of
1127181111Sdes					 * escaping. NB. string can grow here.
1128181111Sdes					 */
1129181111Sdes					if (j >= sizeof(argvs) - 5)
1130181111Sdes						goto args_too_longs;
1131181111Sdes					argvs[j++] = '\\';
1132181111Sdes					argvs[j++] = arg[i++];
1133181111Sdes					argvs[j++] = '\\';
1134181111Sdes					argvs[j++] = arg[i];
1135181111Sdes				} else {
1136181111Sdes					argvs[j++] = arg[i++];
1137181111Sdes					argvs[j++] = arg[i];
1138181111Sdes				}
1139181111Sdes			} else {
1140181111Sdes				if (state == MA_START) {
1141181111Sdes					argv[argc] = argvs + j;
1142181111Sdes					state = MA_UNQUOTED;
1143204917Sdes					if (lastquote != NULL)
1144204917Sdes						*lastquote = '\0';
1145181111Sdes				}
1146181111Sdes				if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1147181111Sdes				    arg[i + 1] == '*' || arg[i + 1] == '\\') {
1148181111Sdes					/*
1149181111Sdes					 * Special case for sftp: append
1150181111Sdes					 * escaped glob sequence -
1151181111Sdes					 * glob will undo one level of
1152181111Sdes					 * escaping.
1153181111Sdes					 */
1154181111Sdes					argvs[j++] = arg[i++];
1155181111Sdes					argvs[j++] = arg[i];
1156181111Sdes				} else {
1157181111Sdes					/* Unescape everything */
1158181111Sdes					/* XXX support \n and friends? */
1159181111Sdes					i++;
1160181111Sdes					argvs[j++] = arg[i];
1161181111Sdes				}
1162181111Sdes			}
1163181111Sdes		} else if (arg[i] == '#') {
1164181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE)
1165181111Sdes				argvs[j++] = arg[i];
1166181111Sdes			else
1167181111Sdes				goto string_done;
1168181111Sdes		} else if (arg[i] == '\0') {
1169181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE) {
1170204917Sdes				if (sloppy) {
1171204917Sdes					state = MA_UNQUOTED;
1172204917Sdes					if (terminated != NULL)
1173204917Sdes						*terminated = 0;
1174204917Sdes					goto string_done;
1175204917Sdes				}
1176181111Sdes				error("Unterminated quoted argument");
1177181111Sdes				return NULL;
1178181111Sdes			}
1179181111Sdes string_done:
1180181111Sdes			if (state == MA_UNQUOTED) {
1181181111Sdes				argvs[j++] = '\0';
1182181111Sdes				argc++;
1183181111Sdes			}
1184181111Sdes			break;
1185181111Sdes		} else {
1186181111Sdes			if (state == MA_START) {
1187181111Sdes				argv[argc] = argvs + j;
1188181111Sdes				state = MA_UNQUOTED;
1189204917Sdes				if (lastquote != NULL)
1190204917Sdes					*lastquote = '\0';
1191181111Sdes			}
1192181111Sdes			if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1193181111Sdes			    (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1194181111Sdes				/*
1195181111Sdes				 * Special case for sftp: escape quoted
1196181111Sdes				 * glob(3) wildcards. NB. string can grow
1197181111Sdes				 * here.
1198181111Sdes				 */
1199181111Sdes				if (j >= sizeof(argvs) - 3)
1200181111Sdes					goto args_too_longs;
1201181111Sdes				argvs[j++] = '\\';
1202181111Sdes				argvs[j++] = arg[i];
1203181111Sdes			} else
1204181111Sdes				argvs[j++] = arg[i];
1205181111Sdes		}
1206181111Sdes		i++;
1207181111Sdes	}
1208181111Sdes	*argcp = argc;
1209181111Sdes	return argv;
1210181111Sdes}
1211181111Sdes
1212181111Sdesstatic int
1213295367Sdesparse_args(const char **cpp, int *ignore_errors, int *aflag,
1214323124Sdes	  int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
1215295367Sdes	  int *rflag, int *sflag,
1216262566Sdes    unsigned long *n_arg, char **path1, char **path2)
1217126274Sdes{
1218126274Sdes	const char *cmd, *cp = *cpp;
1219181111Sdes	char *cp2, **argv;
1220126274Sdes	int base = 0;
1221126274Sdes	long l;
1222181111Sdes	int i, cmdnum, optidx, argc;
1223126274Sdes
1224126274Sdes	/* Skip leading whitespace */
1225126274Sdes	cp = cp + strspn(cp, WHITESPACE);
1226126274Sdes
1227126274Sdes	/* Check for leading '-' (disable error processing) */
1228262566Sdes	*ignore_errors = 0;
1229126274Sdes	if (*cp == '-') {
1230262566Sdes		*ignore_errors = 1;
1231126274Sdes		cp++;
1232204917Sdes		cp = cp + strspn(cp, WHITESPACE);
1233126274Sdes	}
1234126274Sdes
1235204917Sdes	/* Ignore blank lines and lines which begin with comment '#' char */
1236204917Sdes	if (*cp == '\0' || *cp == '#')
1237204917Sdes		return (0);
1238204917Sdes
1239204917Sdes	if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
1240181111Sdes		return -1;
1241181111Sdes
1242126274Sdes	/* Figure out which command we have */
1243181111Sdes	for (i = 0; cmds[i].c != NULL; i++) {
1244248619Sdes		if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
1245126274Sdes			break;
1246126274Sdes	}
1247126274Sdes	cmdnum = cmds[i].n;
1248126274Sdes	cmd = cmds[i].c;
1249126274Sdes
1250126274Sdes	/* Special case */
1251126274Sdes	if (*cp == '!') {
1252126274Sdes		cp++;
1253126274Sdes		cmdnum = I_SHELL;
1254126274Sdes	} else if (cmdnum == -1) {
1255126274Sdes		error("Invalid command.");
1256181111Sdes		return -1;
1257126274Sdes	}
1258126274Sdes
1259126274Sdes	/* Get arguments and parse flags */
1260262566Sdes	*aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1261262566Sdes	*rflag = *sflag = 0;
1262126274Sdes	*path1 = *path2 = NULL;
1263181111Sdes	optidx = 1;
1264126274Sdes	switch (cmdnum) {
1265126274Sdes	case I_GET:
1266255767Sdes	case I_REGET:
1267295367Sdes	case I_REPUT:
1268126274Sdes	case I_PUT:
1269221420Sdes		if ((optidx = parse_getput_flags(cmd, argv, argc,
1270262566Sdes		    aflag, fflag, pflag, rflag)) == -1)
1271181111Sdes			return -1;
1272126274Sdes		/* Get first pathname (mandatory) */
1273181111Sdes		if (argc - optidx < 1) {
1274126274Sdes			error("You must specify at least one path after a "
1275126274Sdes			    "%s command.", cmd);
1276181111Sdes			return -1;
1277126274Sdes		}
1278181111Sdes		*path1 = xstrdup(argv[optidx]);
1279181111Sdes		/* Get second pathname (optional) */
1280181111Sdes		if (argc - optidx > 1) {
1281181111Sdes			*path2 = xstrdup(argv[optidx + 1]);
1282181111Sdes			/* Destination is not globbed */
1283181111Sdes			undo_glob_escape(*path2);
1284181111Sdes		}
1285126274Sdes		break;
1286221420Sdes	case I_LINK:
1287221420Sdes		if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1288221420Sdes			return -1;
1289262566Sdes		goto parse_two_paths;
1290262566Sdes	case I_RENAME:
1291262566Sdes		if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1292262566Sdes			return -1;
1293262566Sdes		goto parse_two_paths;
1294221420Sdes	case I_SYMLINK:
1295262566Sdes		if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1296262566Sdes			return -1;
1297262566Sdes parse_two_paths:
1298181111Sdes		if (argc - optidx < 2) {
1299126274Sdes			error("You must specify two paths after a %s "
1300126274Sdes			    "command.", cmd);
1301181111Sdes			return -1;
1302126274Sdes		}
1303181111Sdes		*path1 = xstrdup(argv[optidx]);
1304181111Sdes		*path2 = xstrdup(argv[optidx + 1]);
1305181111Sdes		/* Paths are not globbed */
1306181111Sdes		undo_glob_escape(*path1);
1307181111Sdes		undo_glob_escape(*path2);
1308126274Sdes		break;
1309126274Sdes	case I_RM:
1310126274Sdes	case I_MKDIR:
1311126274Sdes	case I_RMDIR:
1312126274Sdes	case I_CHDIR:
1313126274Sdes	case I_LCHDIR:
1314126274Sdes	case I_LMKDIR:
1315262566Sdes		if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1316262566Sdes			return -1;
1317126274Sdes		/* Get pathname (mandatory) */
1318181111Sdes		if (argc - optidx < 1) {
1319126274Sdes			error("You must specify a path after a %s command.",
1320126274Sdes			    cmd);
1321181111Sdes			return -1;
1322126274Sdes		}
1323181111Sdes		*path1 = xstrdup(argv[optidx]);
1324181111Sdes		/* Only "rm" globs */
1325181111Sdes		if (cmdnum != I_RM)
1326181111Sdes			undo_glob_escape(*path1);
1327126274Sdes		break;
1328181111Sdes	case I_DF:
1329181111Sdes		if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1330181111Sdes		    iflag)) == -1)
1331181111Sdes			return -1;
1332181111Sdes		/* Default to current directory if no path specified */
1333181111Sdes		if (argc - optidx < 1)
1334181111Sdes			*path1 = NULL;
1335181111Sdes		else {
1336181111Sdes			*path1 = xstrdup(argv[optidx]);
1337181111Sdes			undo_glob_escape(*path1);
1338181111Sdes		}
1339181111Sdes		break;
1340126274Sdes	case I_LS:
1341181111Sdes		if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
1342126274Sdes			return(-1);
1343126274Sdes		/* Path is optional */
1344181111Sdes		if (argc - optidx > 0)
1345181111Sdes			*path1 = xstrdup(argv[optidx]);
1346126274Sdes		break;
1347126274Sdes	case I_LLS:
1348181111Sdes		/* Skip ls command and following whitespace */
1349181111Sdes		cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
1350126274Sdes	case I_SHELL:
1351126274Sdes		/* Uses the rest of the line */
1352126274Sdes		break;
1353126274Sdes	case I_LUMASK:
1354126274Sdes	case I_CHMOD:
1355126274Sdes		base = 8;
1356126274Sdes	case I_CHOWN:
1357126274Sdes	case I_CHGRP:
1358262566Sdes		if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1359262566Sdes			return -1;
1360126274Sdes		/* Get numeric arg (mandatory) */
1361181111Sdes		if (argc - optidx < 1)
1362181111Sdes			goto need_num_arg;
1363164146Sdes		errno = 0;
1364181111Sdes		l = strtol(argv[optidx], &cp2, base);
1365181111Sdes		if (cp2 == argv[optidx] || *cp2 != '\0' ||
1366181111Sdes		    ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1367181111Sdes		    l < 0) {
1368181111Sdes need_num_arg:
1369126274Sdes			error("You must supply a numeric argument "
1370126274Sdes			    "to the %s command.", cmd);
1371181111Sdes			return -1;
1372126274Sdes		}
1373126274Sdes		*n_arg = l;
1374181111Sdes		if (cmdnum == I_LUMASK)
1375126274Sdes			break;
1376126274Sdes		/* Get pathname (mandatory) */
1377181111Sdes		if (argc - optidx < 2) {
1378126274Sdes			error("You must specify a path after a %s command.",
1379126274Sdes			    cmd);
1380181111Sdes			return -1;
1381126274Sdes		}
1382181111Sdes		*path1 = xstrdup(argv[optidx + 1]);
1383126274Sdes		break;
1384126274Sdes	case I_QUIT:
1385126274Sdes	case I_PWD:
1386126274Sdes	case I_LPWD:
1387126274Sdes	case I_HELP:
1388126274Sdes	case I_VERSION:
1389126274Sdes	case I_PROGRESS:
1390262566Sdes		if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1391262566Sdes			return -1;
1392126274Sdes		break;
1393126274Sdes	default:
1394126274Sdes		fatal("Command not implemented");
1395126274Sdes	}
1396126274Sdes
1397126274Sdes	*cpp = cp;
1398126274Sdes	return(cmdnum);
1399126274Sdes}
1400126274Sdes
1401126274Sdesstatic int
1402126274Sdesparse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1403126274Sdes    int err_abort)
1404126274Sdes{
1405126274Sdes	char *path1, *path2, *tmp;
1406323124Sdes	int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0,
1407295367Sdes	iflag = 0;
1408262566Sdes	int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
1409221420Sdes	int cmdnum, i;
1410192595Sdes	unsigned long n_arg = 0;
1411126274Sdes	Attrib a, *aa;
1412295367Sdes	char path_buf[PATH_MAX];
1413126274Sdes	int err = 0;
1414126274Sdes	glob_t g;
1415126274Sdes
1416126274Sdes	path1 = path2 = NULL;
1417262566Sdes	cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1418262566Sdes	    &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1419262566Sdes	if (ignore_errors != 0)
1420126274Sdes		err_abort = 0;
1421126274Sdes
1422126274Sdes	memset(&g, 0, sizeof(g));
1423126274Sdes
1424126274Sdes	/* Perform command */
1425126274Sdes	switch (cmdnum) {
1426126274Sdes	case 0:
1427126274Sdes		/* Blank line */
1428126274Sdes		break;
1429126274Sdes	case -1:
1430126274Sdes		/* Unrecognized command */
1431126274Sdes		err = -1;
1432126274Sdes		break;
1433255767Sdes	case I_REGET:
1434255767Sdes		aflag = 1;
1435255767Sdes		/* FALLTHROUGH */
1436126274Sdes	case I_GET:
1437255767Sdes		err = process_get(conn, path1, path2, *pwd, pflag,
1438262566Sdes		    rflag, aflag, fflag);
1439126274Sdes		break;
1440295367Sdes	case I_REPUT:
1441295367Sdes		aflag = 1;
1442295367Sdes		/* FALLTHROUGH */
1443126274Sdes	case I_PUT:
1444262566Sdes		err = process_put(conn, path1, path2, *pwd, pflag,
1445295367Sdes		    rflag, aflag, fflag);
1446126274Sdes		break;
1447126274Sdes	case I_RENAME:
1448126274Sdes		path1 = make_absolute(path1, *pwd);
1449126274Sdes		path2 = make_absolute(path2, *pwd);
1450262566Sdes		err = do_rename(conn, path1, path2, lflag);
1451126274Sdes		break;
1452126274Sdes	case I_SYMLINK:
1453221420Sdes		sflag = 1;
1454221420Sdes	case I_LINK:
1455262566Sdes		if (!sflag)
1456262566Sdes			path1 = make_absolute(path1, *pwd);
1457126274Sdes		path2 = make_absolute(path2, *pwd);
1458221420Sdes		err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
1459126274Sdes		break;
1460126274Sdes	case I_RM:
1461126274Sdes		path1 = make_absolute(path1, *pwd);
1462126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1463137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1464255767Sdes			if (!quiet)
1465323124Sdes				mprintf("Removing %s\n", g.gl_pathv[i]);
1466126274Sdes			err = do_rm(conn, g.gl_pathv[i]);
1467126274Sdes			if (err != 0 && err_abort)
1468126274Sdes				break;
1469126274Sdes		}
1470126274Sdes		break;
1471126274Sdes	case I_MKDIR:
1472126274Sdes		path1 = make_absolute(path1, *pwd);
1473126274Sdes		attrib_clear(&a);
1474126274Sdes		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1475126274Sdes		a.perm = 0777;
1476204917Sdes		err = do_mkdir(conn, path1, &a, 1);
1477126274Sdes		break;
1478126274Sdes	case I_RMDIR:
1479126274Sdes		path1 = make_absolute(path1, *pwd);
1480126274Sdes		err = do_rmdir(conn, path1);
1481126274Sdes		break;
1482126274Sdes	case I_CHDIR:
1483126274Sdes		path1 = make_absolute(path1, *pwd);
1484126274Sdes		if ((tmp = do_realpath(conn, path1)) == NULL) {
1485126274Sdes			err = 1;
1486126274Sdes			break;
1487126274Sdes		}
1488126274Sdes		if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1489255767Sdes			free(tmp);
1490126274Sdes			err = 1;
1491126274Sdes			break;
1492126274Sdes		}
1493126274Sdes		if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1494126274Sdes			error("Can't change directory: Can't check target");
1495255767Sdes			free(tmp);
1496126274Sdes			err = 1;
1497126274Sdes			break;
1498126274Sdes		}
1499126274Sdes		if (!S_ISDIR(aa->perm)) {
1500126274Sdes			error("Can't change directory: \"%s\" is not "
1501126274Sdes			    "a directory", tmp);
1502255767Sdes			free(tmp);
1503126274Sdes			err = 1;
1504126274Sdes			break;
1505126274Sdes		}
1506255767Sdes		free(*pwd);
1507126274Sdes		*pwd = tmp;
1508126274Sdes		break;
1509126274Sdes	case I_LS:
1510126274Sdes		if (!path1) {
1511215116Sdes			do_ls_dir(conn, *pwd, *pwd, lflag);
1512126274Sdes			break;
1513126274Sdes		}
1514126274Sdes
1515126274Sdes		/* Strip pwd off beginning of non-absolute paths */
1516126274Sdes		tmp = NULL;
1517126274Sdes		if (*path1 != '/')
1518126274Sdes			tmp = *pwd;
1519126274Sdes
1520126274Sdes		path1 = make_absolute(path1, *pwd);
1521126274Sdes		err = do_globbed_ls(conn, path1, tmp, lflag);
1522126274Sdes		break;
1523181111Sdes	case I_DF:
1524181111Sdes		/* Default to current directory if no path specified */
1525181111Sdes		if (path1 == NULL)
1526181111Sdes			path1 = xstrdup(*pwd);
1527181111Sdes		path1 = make_absolute(path1, *pwd);
1528181111Sdes		err = do_df(conn, path1, hflag, iflag);
1529181111Sdes		break;
1530126274Sdes	case I_LCHDIR:
1531295367Sdes		tmp = tilde_expand_filename(path1, getuid());
1532295367Sdes		free(path1);
1533295367Sdes		path1 = tmp;
1534126274Sdes		if (chdir(path1) == -1) {
1535126274Sdes			error("Couldn't change local directory to "
1536126274Sdes			    "\"%s\": %s", path1, strerror(errno));
1537126274Sdes			err = 1;
1538126274Sdes		}
1539126274Sdes		break;
1540126274Sdes	case I_LMKDIR:
1541126274Sdes		if (mkdir(path1, 0777) == -1) {
1542126274Sdes			error("Couldn't create local directory "
1543126274Sdes			    "\"%s\": %s", path1, strerror(errno));
1544126274Sdes			err = 1;
1545126274Sdes		}
1546126274Sdes		break;
1547126274Sdes	case I_LLS:
1548126274Sdes		local_do_ls(cmd);
1549126274Sdes		break;
1550126274Sdes	case I_SHELL:
1551126274Sdes		local_do_shell(cmd);
1552126274Sdes		break;
1553126274Sdes	case I_LUMASK:
1554126274Sdes		umask(n_arg);
1555126274Sdes		printf("Local umask: %03lo\n", n_arg);
1556126274Sdes		break;
1557126274Sdes	case I_CHMOD:
1558126274Sdes		path1 = make_absolute(path1, *pwd);
1559126274Sdes		attrib_clear(&a);
1560126274Sdes		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1561126274Sdes		a.perm = n_arg;
1562126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1563137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1564255767Sdes			if (!quiet)
1565323124Sdes				mprintf("Changing mode on %s\n",
1566323124Sdes				    g.gl_pathv[i]);
1567126274Sdes			err = do_setstat(conn, g.gl_pathv[i], &a);
1568126274Sdes			if (err != 0 && err_abort)
1569126274Sdes				break;
1570126274Sdes		}
1571126274Sdes		break;
1572126274Sdes	case I_CHOWN:
1573126274Sdes	case I_CHGRP:
1574126274Sdes		path1 = make_absolute(path1, *pwd);
1575126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1576137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1577126274Sdes			if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
1578192595Sdes				if (err_abort) {
1579192595Sdes					err = -1;
1580126274Sdes					break;
1581192595Sdes				} else
1582126274Sdes					continue;
1583126274Sdes			}
1584126274Sdes			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1585126274Sdes				error("Can't get current ownership of "
1586126274Sdes				    "remote file \"%s\"", g.gl_pathv[i]);
1587192595Sdes				if (err_abort) {
1588192595Sdes					err = -1;
1589126274Sdes					break;
1590192595Sdes				} else
1591126274Sdes					continue;
1592126274Sdes			}
1593126274Sdes			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1594126274Sdes			if (cmdnum == I_CHOWN) {
1595255767Sdes				if (!quiet)
1596323124Sdes					mprintf("Changing owner on %s\n",
1597255767Sdes					    g.gl_pathv[i]);
1598126274Sdes				aa->uid = n_arg;
1599126274Sdes			} else {
1600255767Sdes				if (!quiet)
1601323124Sdes					mprintf("Changing group on %s\n",
1602255767Sdes					    g.gl_pathv[i]);
1603126274Sdes				aa->gid = n_arg;
1604126274Sdes			}
1605126274Sdes			err = do_setstat(conn, g.gl_pathv[i], aa);
1606126274Sdes			if (err != 0 && err_abort)
1607126274Sdes				break;
1608126274Sdes		}
1609126274Sdes		break;
1610126274Sdes	case I_PWD:
1611323124Sdes		mprintf("Remote working directory: %s\n", *pwd);
1612126274Sdes		break;
1613126274Sdes	case I_LPWD:
1614126274Sdes		if (!getcwd(path_buf, sizeof(path_buf))) {
1615126274Sdes			error("Couldn't get local cwd: %s", strerror(errno));
1616126274Sdes			err = -1;
1617126274Sdes			break;
1618126274Sdes		}
1619323124Sdes		mprintf("Local working directory: %s\n", path_buf);
1620126274Sdes		break;
1621126274Sdes	case I_QUIT:
1622126274Sdes		/* Processed below */
1623126274Sdes		break;
1624126274Sdes	case I_HELP:
1625126274Sdes		help();
1626126274Sdes		break;
1627126274Sdes	case I_VERSION:
1628126274Sdes		printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1629126274Sdes		break;
1630126274Sdes	case I_PROGRESS:
1631126274Sdes		showprogress = !showprogress;
1632126274Sdes		if (showprogress)
1633126274Sdes			printf("Progress meter enabled\n");
1634126274Sdes		else
1635126274Sdes			printf("Progress meter disabled\n");
1636126274Sdes		break;
1637126274Sdes	default:
1638126274Sdes		fatal("%d is not implemented", cmdnum);
1639126274Sdes	}
1640126274Sdes
1641126274Sdes	if (g.gl_pathc)
1642126274Sdes		globfree(&g);
1643255767Sdes	free(path1);
1644255767Sdes	free(path2);
1645126274Sdes
1646126274Sdes	/* If an unignored error occurs in batch mode we should abort. */
1647126274Sdes	if (err_abort && err != 0)
1648126274Sdes		return (-1);
1649126274Sdes	else if (cmdnum == I_QUIT)
1650126274Sdes		return (1);
1651126274Sdes
1652126274Sdes	return (0);
1653126274Sdes}
1654126274Sdes
1655146998Sdes#ifdef USE_LIBEDIT
1656146998Sdesstatic char *
1657146998Sdesprompt(EditLine *el)
1658146998Sdes{
1659146998Sdes	return ("sftp> ");
1660146998Sdes}
1661146998Sdes
1662204917Sdes/* Display entries in 'list' after skipping the first 'len' chars */
1663204917Sdesstatic void
1664204917Sdescomplete_display(char **list, u_int len)
1665204917Sdes{
1666204917Sdes	u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1667204917Sdes	struct winsize ws;
1668204917Sdes	char *tmp;
1669204917Sdes
1670204917Sdes	/* Count entries for sort and find longest */
1671262566Sdes	for (y = 0; list[y]; y++)
1672204917Sdes		m = MAX(m, strlen(list[y]));
1673204917Sdes
1674204917Sdes	if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1675204917Sdes		width = ws.ws_col;
1676204917Sdes
1677204917Sdes	m = m > len ? m - len : 0;
1678204917Sdes	columns = width / (m + 2);
1679204917Sdes	columns = MAX(columns, 1);
1680204917Sdes	colspace = width / columns;
1681204917Sdes	colspace = MIN(colspace, width);
1682204917Sdes
1683204917Sdes	printf("\n");
1684204917Sdes	m = 1;
1685204917Sdes	for (y = 0; list[y]; y++) {
1686204917Sdes		llen = strlen(list[y]);
1687204917Sdes		tmp = llen > len ? list[y] + len : "";
1688323124Sdes		mprintf("%-*s", colspace, tmp);
1689204917Sdes		if (m >= columns) {
1690204917Sdes			printf("\n");
1691204917Sdes			m = 1;
1692204917Sdes		} else
1693204917Sdes			m++;
1694204917Sdes	}
1695204917Sdes	printf("\n");
1696204917Sdes}
1697204917Sdes
1698204917Sdes/*
1699204917Sdes * Given a "list" of words that begin with a common prefix of "word",
1700204917Sdes * attempt to find an autocompletion to extends "word" by the next
1701204917Sdes * characters common to all entries in "list".
1702204917Sdes */
1703204917Sdesstatic char *
1704204917Sdescomplete_ambiguous(const char *word, char **list, size_t count)
1705204917Sdes{
1706204917Sdes	if (word == NULL)
1707204917Sdes		return NULL;
1708204917Sdes
1709204917Sdes	if (count > 0) {
1710204917Sdes		u_int y, matchlen = strlen(list[0]);
1711204917Sdes
1712204917Sdes		/* Find length of common stem */
1713204917Sdes		for (y = 1; list[y]; y++) {
1714204917Sdes			u_int x;
1715204917Sdes
1716262566Sdes			for (x = 0; x < matchlen; x++)
1717262566Sdes				if (list[0][x] != list[y][x])
1718204917Sdes					break;
1719204917Sdes
1720204917Sdes			matchlen = x;
1721204917Sdes		}
1722204917Sdes
1723204917Sdes		if (matchlen > strlen(word)) {
1724204917Sdes			char *tmp = xstrdup(list[0]);
1725204917Sdes
1726204917Sdes			tmp[matchlen] = '\0';
1727204917Sdes			return tmp;
1728204917Sdes		}
1729262566Sdes	}
1730204917Sdes
1731204917Sdes	return xstrdup(word);
1732204917Sdes}
1733204917Sdes
1734204917Sdes/* Autocomplete a sftp command */
1735204917Sdesstatic int
1736204917Sdescomplete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1737204917Sdes    int terminated)
1738204917Sdes{
1739204917Sdes	u_int y, count = 0, cmdlen, tmplen;
1740204917Sdes	char *tmp, **list, argterm[3];
1741204917Sdes	const LineInfo *lf;
1742204917Sdes
1743204917Sdes	list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1744204917Sdes
1745204917Sdes	/* No command specified: display all available commands */
1746204917Sdes	if (cmd == NULL) {
1747204917Sdes		for (y = 0; cmds[y].c; y++)
1748204917Sdes			list[count++] = xstrdup(cmds[y].c);
1749262566Sdes
1750204917Sdes		list[count] = NULL;
1751204917Sdes		complete_display(list, 0);
1752204917Sdes
1753262566Sdes		for (y = 0; list[y] != NULL; y++)
1754262566Sdes			free(list[y]);
1755255767Sdes		free(list);
1756204917Sdes		return count;
1757204917Sdes	}
1758204917Sdes
1759204917Sdes	/* Prepare subset of commands that start with "cmd" */
1760204917Sdes	cmdlen = strlen(cmd);
1761204917Sdes	for (y = 0; cmds[y].c; y++)  {
1762262566Sdes		if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1763204917Sdes			list[count++] = xstrdup(cmds[y].c);
1764204917Sdes	}
1765204917Sdes	list[count] = NULL;
1766204917Sdes
1767240075Sdes	if (count == 0) {
1768255767Sdes		free(list);
1769204917Sdes		return 0;
1770240075Sdes	}
1771204917Sdes
1772204917Sdes	/* Complete ambigious command */
1773204917Sdes	tmp = complete_ambiguous(cmd, list, count);
1774204917Sdes	if (count > 1)
1775204917Sdes		complete_display(list, 0);
1776204917Sdes
1777262566Sdes	for (y = 0; list[y]; y++)
1778262566Sdes		free(list[y]);
1779255767Sdes	free(list);
1780204917Sdes
1781204917Sdes	if (tmp != NULL) {
1782204917Sdes		tmplen = strlen(tmp);
1783204917Sdes		cmdlen = strlen(cmd);
1784204917Sdes		/* If cmd may be extended then do so */
1785204917Sdes		if (tmplen > cmdlen)
1786204917Sdes			if (el_insertstr(el, tmp + cmdlen) == -1)
1787204917Sdes				fatal("el_insertstr failed.");
1788204917Sdes		lf = el_line(el);
1789204917Sdes		/* Terminate argument cleanly */
1790204917Sdes		if (count == 1) {
1791204917Sdes			y = 0;
1792204917Sdes			if (!terminated)
1793204917Sdes				argterm[y++] = quote;
1794204917Sdes			if (lastarg || *(lf->cursor) != ' ')
1795204917Sdes				argterm[y++] = ' ';
1796204917Sdes			argterm[y] = '\0';
1797204917Sdes			if (y > 0 && el_insertstr(el, argterm) == -1)
1798204917Sdes				fatal("el_insertstr failed.");
1799204917Sdes		}
1800255767Sdes		free(tmp);
1801204917Sdes	}
1802204917Sdes
1803204917Sdes	return count;
1804204917Sdes}
1805204917Sdes
1806204917Sdes/*
1807204917Sdes * Determine whether a particular sftp command's arguments (if any)
1808204917Sdes * represent local or remote files.
1809204917Sdes */
1810204917Sdesstatic int
1811204917Sdescomplete_is_remote(char *cmd) {
1812204917Sdes	int i;
1813204917Sdes
1814204917Sdes	if (cmd == NULL)
1815204917Sdes		return -1;
1816204917Sdes
1817204917Sdes	for (i = 0; cmds[i].c; i++) {
1818262566Sdes		if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1819204917Sdes			return cmds[i].t;
1820204917Sdes	}
1821204917Sdes
1822204917Sdes	return -1;
1823204917Sdes}
1824204917Sdes
1825204917Sdes/* Autocomplete a filename "file" */
1826204917Sdesstatic int
1827204917Sdescomplete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1828204917Sdes    char *file, int remote, int lastarg, char quote, int terminated)
1829204917Sdes{
1830204917Sdes	glob_t g;
1831255767Sdes	char *tmp, *tmp2, ins[8];
1832248619Sdes	u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
1833255767Sdes	int clen;
1834204917Sdes	const LineInfo *lf;
1835262566Sdes
1836204917Sdes	/* Glob from "file" location */
1837204917Sdes	if (file == NULL)
1838204917Sdes		tmp = xstrdup("*");
1839204917Sdes	else
1840204917Sdes		xasprintf(&tmp, "%s*", file);
1841204917Sdes
1842248619Sdes	/* Check if the path is absolute. */
1843248619Sdes	isabs = tmp[0] == '/';
1844248619Sdes
1845204917Sdes	memset(&g, 0, sizeof(g));
1846204917Sdes	if (remote != LOCAL) {
1847204917Sdes		tmp = make_absolute(tmp, remote_path);
1848204917Sdes		remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1849262566Sdes	} else
1850204917Sdes		glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1851262566Sdes
1852204917Sdes	/* Determine length of pwd so we can trim completion display */
1853204917Sdes	for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1854204917Sdes		/* Terminate counting on first unescaped glob metacharacter */
1855204917Sdes		if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1856204917Sdes			if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1857204917Sdes				hadglob = 1;
1858204917Sdes			break;
1859204917Sdes		}
1860204917Sdes		if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1861204917Sdes			tmplen++;
1862204917Sdes		if (tmp[tmplen] == '/')
1863204917Sdes			pwdlen = tmplen + 1;	/* track last seen '/' */
1864204917Sdes	}
1865255767Sdes	free(tmp);
1866295367Sdes	tmp = NULL;
1867204917Sdes
1868262566Sdes	if (g.gl_matchc == 0)
1869204917Sdes		goto out;
1870204917Sdes
1871204917Sdes	if (g.gl_matchc > 1)
1872204917Sdes		complete_display(g.gl_pathv, pwdlen);
1873204917Sdes
1874204917Sdes	/* Don't try to extend globs */
1875204917Sdes	if (file == NULL || hadglob)
1876204917Sdes		goto out;
1877204917Sdes
1878204917Sdes	tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1879248619Sdes	tmp = path_strip(tmp2, isabs ? NULL : remote_path);
1880255767Sdes	free(tmp2);
1881204917Sdes
1882204917Sdes	if (tmp == NULL)
1883204917Sdes		goto out;
1884204917Sdes
1885204917Sdes	tmplen = strlen(tmp);
1886204917Sdes	filelen = strlen(file);
1887204917Sdes
1888248619Sdes	/* Count the number of escaped characters in the input string. */
1889248619Sdes	cesc = isesc = 0;
1890248619Sdes	for (i = 0; i < filelen; i++) {
1891248619Sdes		if (!isesc && file[i] == '\\' && i + 1 < filelen){
1892248619Sdes			isesc = 1;
1893248619Sdes			cesc++;
1894248619Sdes		} else
1895248619Sdes			isesc = 0;
1896248619Sdes	}
1897248619Sdes
1898248619Sdes	if (tmplen > (filelen - cesc)) {
1899248619Sdes		tmp2 = tmp + filelen - cesc;
1900262566Sdes		len = strlen(tmp2);
1901204917Sdes		/* quote argument on way out */
1902255767Sdes		for (i = 0; i < len; i += clen) {
1903255767Sdes			if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1904255767Sdes			    (size_t)clen > sizeof(ins) - 2)
1905255767Sdes				fatal("invalid multibyte character");
1906204917Sdes			ins[0] = '\\';
1907255767Sdes			memcpy(ins + 1, tmp2 + i, clen);
1908255767Sdes			ins[clen + 1] = '\0';
1909204917Sdes			switch (tmp2[i]) {
1910204917Sdes			case '\'':
1911204917Sdes			case '"':
1912204917Sdes			case '\\':
1913204917Sdes			case '\t':
1914221420Sdes			case '[':
1915204917Sdes			case ' ':
1916248619Sdes			case '#':
1917248619Sdes			case '*':
1918204917Sdes				if (quote == '\0' || tmp2[i] == quote) {
1919204917Sdes					if (el_insertstr(el, ins) == -1)
1920204917Sdes						fatal("el_insertstr "
1921204917Sdes						    "failed.");
1922204917Sdes					break;
1923204917Sdes				}
1924204917Sdes				/* FALLTHROUGH */
1925204917Sdes			default:
1926204917Sdes				if (el_insertstr(el, ins + 1) == -1)
1927204917Sdes					fatal("el_insertstr failed.");
1928204917Sdes				break;
1929204917Sdes			}
1930204917Sdes		}
1931204917Sdes	}
1932204917Sdes
1933204917Sdes	lf = el_line(el);
1934204917Sdes	if (g.gl_matchc == 1) {
1935204917Sdes		i = 0;
1936295367Sdes		if (!terminated && quote != '\0')
1937204917Sdes			ins[i++] = quote;
1938204917Sdes		if (*(lf->cursor - 1) != '/' &&
1939204917Sdes		    (lastarg || *(lf->cursor) != ' '))
1940204917Sdes			ins[i++] = ' ';
1941204917Sdes		ins[i] = '\0';
1942204917Sdes		if (i > 0 && el_insertstr(el, ins) == -1)
1943204917Sdes			fatal("el_insertstr failed.");
1944204917Sdes	}
1945255767Sdes	free(tmp);
1946204917Sdes
1947204917Sdes out:
1948204917Sdes	globfree(&g);
1949204917Sdes	return g.gl_matchc;
1950204917Sdes}
1951204917Sdes
1952204917Sdes/* tab-completion hook function, called via libedit */
1953204917Sdesstatic unsigned char
1954204917Sdescomplete(EditLine *el, int ch)
1955204917Sdes{
1956262566Sdes	char **argv, *line, quote;
1957255767Sdes	int argc, carg;
1958255767Sdes	u_int cursor, len, terminated, ret = CC_ERROR;
1959204917Sdes	const LineInfo *lf;
1960204917Sdes	struct complete_ctx *complete_ctx;
1961204917Sdes
1962204917Sdes	lf = el_line(el);
1963204917Sdes	if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1964204917Sdes		fatal("%s: el_get failed", __func__);
1965204917Sdes
1966204917Sdes	/* Figure out which argument the cursor points to */
1967204917Sdes	cursor = lf->cursor - lf->buffer;
1968295367Sdes	line = xmalloc(cursor + 1);
1969204917Sdes	memcpy(line, lf->buffer, cursor);
1970204917Sdes	line[cursor] = '\0';
1971204917Sdes	argv = makeargv(line, &carg, 1, &quote, &terminated);
1972255767Sdes	free(line);
1973204917Sdes
1974204917Sdes	/* Get all the arguments on the line */
1975204917Sdes	len = lf->lastchar - lf->buffer;
1976295367Sdes	line = xmalloc(len + 1);
1977204917Sdes	memcpy(line, lf->buffer, len);
1978204917Sdes	line[len] = '\0';
1979204917Sdes	argv = makeargv(line, &argc, 1, NULL, NULL);
1980204917Sdes
1981204917Sdes	/* Ensure cursor is at EOL or a argument boundary */
1982204917Sdes	if (line[cursor] != ' ' && line[cursor] != '\0' &&
1983204917Sdes	    line[cursor] != '\n') {
1984255767Sdes		free(line);
1985204917Sdes		return ret;
1986204917Sdes	}
1987204917Sdes
1988204917Sdes	if (carg == 0) {
1989204917Sdes		/* Show all available commands */
1990204917Sdes		complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1991204917Sdes		ret = CC_REDISPLAY;
1992204917Sdes	} else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ')  {
1993204917Sdes		/* Handle the command parsing */
1994204917Sdes		if (complete_cmd_parse(el, argv[0], argc == carg,
1995262566Sdes		    quote, terminated) != 0)
1996204917Sdes			ret = CC_REDISPLAY;
1997204917Sdes	} else if (carg >= 1) {
1998204917Sdes		/* Handle file parsing */
1999204917Sdes		int remote = complete_is_remote(argv[0]);
2000204917Sdes		char *filematch = NULL;
2001204917Sdes
2002204917Sdes		if (carg > 1 && line[cursor-1] != ' ')
2003204917Sdes			filematch = argv[carg - 1];
2004204917Sdes
2005204917Sdes		if (remote != 0 &&
2006204917Sdes		    complete_match(el, complete_ctx->conn,
2007204917Sdes		    *complete_ctx->remote_pathp, filematch,
2008262566Sdes		    remote, carg == argc, quote, terminated) != 0)
2009204917Sdes			ret = CC_REDISPLAY;
2010204917Sdes	}
2011204917Sdes
2012262566Sdes	free(line);
2013204917Sdes	return ret;
2014204917Sdes}
2015204917Sdes#endif /* USE_LIBEDIT */
2016204917Sdes
2017126274Sdesint
2018204917Sdesinteractive_loop(struct sftp_conn *conn, char *file1, char *file2)
2019126274Sdes{
2020204917Sdes	char *remote_path;
2021126274Sdes	char *dir = NULL;
2022126274Sdes	char cmd[2048];
2023149749Sdes	int err, interactive;
2024146998Sdes	EditLine *el = NULL;
2025146998Sdes#ifdef USE_LIBEDIT
2026146998Sdes	History *hl = NULL;
2027146998Sdes	HistEvent hev;
2028146998Sdes	extern char *__progname;
2029204917Sdes	struct complete_ctx complete_ctx;
2030126274Sdes
2031146998Sdes	if (!batchmode && isatty(STDIN_FILENO)) {
2032146998Sdes		if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2033146998Sdes			fatal("Couldn't initialise editline");
2034146998Sdes		if ((hl = history_init()) == NULL)
2035146998Sdes			fatal("Couldn't initialise editline history");
2036146998Sdes		history(hl, &hev, H_SETSIZE, 100);
2037146998Sdes		el_set(el, EL_HIST, history, hl);
2038146998Sdes
2039146998Sdes		el_set(el, EL_PROMPT, prompt);
2040146998Sdes		el_set(el, EL_EDITOR, "emacs");
2041146998Sdes		el_set(el, EL_TERMINAL, NULL);
2042146998Sdes		el_set(el, EL_SIGNAL, 1);
2043146998Sdes		el_source(el, NULL);
2044204917Sdes
2045204917Sdes		/* Tab Completion */
2046262566Sdes		el_set(el, EL_ADDFN, "ftp-complete",
2047221420Sdes		    "Context sensitive argument completion", complete);
2048204917Sdes		complete_ctx.conn = conn;
2049204917Sdes		complete_ctx.remote_pathp = &remote_path;
2050204917Sdes		el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2051204917Sdes		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
2052262566Sdes		/* enable ctrl-left-arrow and ctrl-right-arrow */
2053262566Sdes		el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2054262566Sdes		el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2055262566Sdes		el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2056262566Sdes		el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
2057262566Sdes		/* make ^w match ksh behaviour */
2058262566Sdes		el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
2059146998Sdes	}
2060146998Sdes#endif /* USE_LIBEDIT */
2061146998Sdes
2062204917Sdes	remote_path = do_realpath(conn, ".");
2063204917Sdes	if (remote_path == NULL)
2064126274Sdes		fatal("Need cwd");
2065126274Sdes
2066126274Sdes	if (file1 != NULL) {
2067126274Sdes		dir = xstrdup(file1);
2068204917Sdes		dir = make_absolute(dir, remote_path);
2069126274Sdes
2070126274Sdes		if (remote_is_dir(conn, dir) && file2 == NULL) {
2071255767Sdes			if (!quiet)
2072323124Sdes				mprintf("Changing to: %s\n", dir);
2073126274Sdes			snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
2074204917Sdes			if (parse_dispatch_command(conn, cmd,
2075204917Sdes			    &remote_path, 1) != 0) {
2076255767Sdes				free(dir);
2077255767Sdes				free(remote_path);
2078255767Sdes				free(conn);
2079126274Sdes				return (-1);
2080146998Sdes			}
2081126274Sdes		} else {
2082248619Sdes			/* XXX this is wrong wrt quoting */
2083255767Sdes			snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2084255767Sdes			    global_aflag ? " -a" : "", dir,
2085255767Sdes			    file2 == NULL ? "" : " ",
2086255767Sdes			    file2 == NULL ? "" : file2);
2087204917Sdes			err = parse_dispatch_command(conn, cmd,
2088204917Sdes			    &remote_path, 1);
2089255767Sdes			free(dir);
2090255767Sdes			free(remote_path);
2091255767Sdes			free(conn);
2092126274Sdes			return (err);
2093126274Sdes		}
2094255767Sdes		free(dir);
2095126274Sdes	}
2096126274Sdes
2097295367Sdes	setvbuf(stdout, NULL, _IOLBF, 0);
2098295367Sdes	setvbuf(infile, NULL, _IOLBF, 0);
2099126274Sdes
2100149749Sdes	interactive = !batchmode && isatty(STDIN_FILENO);
2101126274Sdes	err = 0;
2102126274Sdes	for (;;) {
2103126274Sdes		char *cp;
2104126274Sdes
2105137015Sdes		signal(SIGINT, SIG_IGN);
2106137015Sdes
2107146998Sdes		if (el == NULL) {
2108149749Sdes			if (interactive)
2109149749Sdes				printf("sftp> ");
2110146998Sdes			if (fgets(cmd, sizeof(cmd), infile) == NULL) {
2111149749Sdes				if (interactive)
2112149749Sdes					printf("\n");
2113146998Sdes				break;
2114146998Sdes			}
2115149749Sdes			if (!interactive) { /* Echo command */
2116323124Sdes				mprintf("sftp> %s", cmd);
2117149749Sdes				if (strlen(cmd) > 0 &&
2118149749Sdes				    cmd[strlen(cmd) - 1] != '\n')
2119149749Sdes					printf("\n");
2120149749Sdes			}
2121146998Sdes		} else {
2122146998Sdes#ifdef USE_LIBEDIT
2123146998Sdes			const char *line;
2124146998Sdes			int count = 0;
2125126274Sdes
2126204917Sdes			if ((line = el_gets(el, &count)) == NULL ||
2127204917Sdes			    count <= 0) {
2128149749Sdes				printf("\n");
2129149749Sdes 				break;
2130149749Sdes			}
2131146998Sdes			history(hl, &hev, H_ENTER, line);
2132146998Sdes			if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2133146998Sdes				fprintf(stderr, "Error: input line too long\n");
2134146998Sdes				continue;
2135146998Sdes			}
2136146998Sdes#endif /* USE_LIBEDIT */
2137126274Sdes		}
2138126274Sdes
2139126274Sdes		cp = strrchr(cmd, '\n');
2140126274Sdes		if (cp)
2141126274Sdes			*cp = '\0';
2142126274Sdes
2143137015Sdes		/* Handle user interrupts gracefully during commands */
2144137015Sdes		interrupted = 0;
2145137015Sdes		signal(SIGINT, cmd_interrupt);
2146137015Sdes
2147204917Sdes		err = parse_dispatch_command(conn, cmd, &remote_path,
2148204917Sdes		    batchmode);
2149126274Sdes		if (err != 0)
2150126274Sdes			break;
2151126274Sdes	}
2152255767Sdes	free(remote_path);
2153255767Sdes	free(conn);
2154126274Sdes
2155149749Sdes#ifdef USE_LIBEDIT
2156149749Sdes	if (el != NULL)
2157149749Sdes		el_end(el);
2158149749Sdes#endif /* USE_LIBEDIT */
2159149749Sdes
2160126274Sdes	/* err == 1 signifies normal "quit" exit */
2161126274Sdes	return (err >= 0 ? 0 : -1);
2162126274Sdes}
2163126274Sdes
2164126274Sdesstatic void
2165124208Sdesconnect_to_server(char *path, char **args, int *in, int *out)
2166124208Sdes{
216776259Sgreen	int c_in, c_out;
216899060Sdes
216976259Sgreen#ifdef USE_PIPES
217076259Sgreen	int pin[2], pout[2];
217199060Sdes
217276259Sgreen	if ((pipe(pin) == -1) || (pipe(pout) == -1))
217376259Sgreen		fatal("pipe: %s", strerror(errno));
217476259Sgreen	*in = pin[0];
217576259Sgreen	*out = pout[1];
217676259Sgreen	c_in = pout[0];
217776259Sgreen	c_out = pin[1];
217876259Sgreen#else /* USE_PIPES */
217976259Sgreen	int inout[2];
218099060Sdes
218176259Sgreen	if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
218276259Sgreen		fatal("socketpair: %s", strerror(errno));
218376259Sgreen	*in = *out = inout[0];
218476259Sgreen	c_in = c_out = inout[1];
218576259Sgreen#endif /* USE_PIPES */
218676259Sgreen
2187124208Sdes	if ((sshpid = fork()) == -1)
218876259Sgreen		fatal("fork: %s", strerror(errno));
2189124208Sdes	else if (sshpid == 0) {
219076259Sgreen		if ((dup2(c_in, STDIN_FILENO) == -1) ||
219176259Sgreen		    (dup2(c_out, STDOUT_FILENO) == -1)) {
219276259Sgreen			fprintf(stderr, "dup2: %s\n", strerror(errno));
2193137015Sdes			_exit(1);
219476259Sgreen		}
219576259Sgreen		close(*in);
219676259Sgreen		close(*out);
219776259Sgreen		close(c_in);
219876259Sgreen		close(c_out);
2199137015Sdes
2200137015Sdes		/*
2201137015Sdes		 * The underlying ssh is in the same process group, so we must
2202137015Sdes		 * ignore SIGINT if we want to gracefully abort commands,
2203137015Sdes		 * otherwise the signal will make it to the ssh process and
2204204917Sdes		 * kill it too.  Contrawise, since sftp sends SIGTERMs to the
2205204917Sdes		 * underlying ssh, it must *not* ignore that signal.
2206137015Sdes		 */
2207137015Sdes		signal(SIGINT, SIG_IGN);
2208204917Sdes		signal(SIGTERM, SIG_DFL);
2209137015Sdes		execvp(path, args);
221092555Sdes		fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
2211137015Sdes		_exit(1);
221276259Sgreen	}
221376259Sgreen
2214124208Sdes	signal(SIGTERM, killchild);
2215124208Sdes	signal(SIGINT, killchild);
2216124208Sdes	signal(SIGHUP, killchild);
221776259Sgreen	close(c_in);
221876259Sgreen	close(c_out);
221976259Sgreen}
222076259Sgreen
222192555Sdesstatic void
222276259Sgreenusage(void)
222376259Sgreen{
222492555Sdes	extern char *__progname;
222598675Sdes
222692555Sdes	fprintf(stderr,
2227262566Sdes	    "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
2228204917Sdes	    "          [-D sftp_server_path] [-F ssh_config] "
2229221420Sdes	    "[-i identity_file] [-l limit]\n"
2230204917Sdes	    "          [-o ssh_option] [-P port] [-R num_requests] "
2231204917Sdes	    "[-S program]\n"
2232204917Sdes	    "          [-s subsystem | sftp_server] host\n"
2233192595Sdes	    "       %s [user@]host[:file ...]\n"
2234192595Sdes	    "       %s [user@]host[:dir[/]]\n"
2235204917Sdes	    "       %s -b batchfile [user@]host\n",
2236204917Sdes	    __progname, __progname, __progname, __progname);
223776259Sgreen	exit(1);
223876259Sgreen}
223976259Sgreen
224076259Sgreenint
224176259Sgreenmain(int argc, char **argv)
224276259Sgreen{
2243113908Sdes	int in, out, ch, err;
2244204917Sdes	char *host = NULL, *userhost, *cp, *file2 = NULL;
224592555Sdes	int debug_level = 0, sshver = 2;
224692555Sdes	char *file1 = NULL, *sftp_server = NULL;
224792555Sdes	char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
2248221420Sdes	const char *errstr;
224992555Sdes	LogLevel ll = SYSLOG_LEVEL_INFO;
225092555Sdes	arglist args;
225176259Sgreen	extern int optind;
225276259Sgreen	extern char *optarg;
2253204917Sdes	struct sftp_conn *conn;
2254204917Sdes	size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2255204917Sdes	size_t num_requests = DEFAULT_NUM_REQUESTS;
2256221420Sdes	long long limit_kbps = 0;
225776259Sgreen
2258296781Sdes	ssh_malloc_init();	/* must be called before any mallocs */
2259157016Sdes	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2260157016Sdes	sanitise_stdfd();
2261255767Sdes	setlocale(LC_CTYPE, "");
2262157016Sdes
2263124208Sdes	__progname = ssh_get_progname(argv[0]);
2264157016Sdes	memset(&args, '\0', sizeof(args));
226592555Sdes	args.list = NULL;
2266162852Sdes	addargs(&args, "%s", ssh_program);
226792555Sdes	addargs(&args, "-oForwardX11 no");
226892555Sdes	addargs(&args, "-oForwardAgent no");
2269157016Sdes	addargs(&args, "-oPermitLocalCommand no");
227092555Sdes	addargs(&args, "-oClearAllForwardings yes");
2271126274Sdes
227292555Sdes	ll = SYSLOG_LEVEL_INFO;
2273126274Sdes	infile = stdin;
227476259Sgreen
2275204917Sdes	while ((ch = getopt(argc, argv,
2276262566Sdes	    "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
227776259Sgreen		switch (ch) {
2278204917Sdes		/* Passed through to ssh(1) */
2279204917Sdes		case '4':
2280204917Sdes		case '6':
228176259Sgreen		case 'C':
2282204917Sdes			addargs(&args, "-%c", ch);
228376259Sgreen			break;
2284204917Sdes		/* Passed through to ssh(1) with argument */
2285204917Sdes		case 'F':
2286204917Sdes		case 'c':
2287204917Sdes		case 'i':
2288204917Sdes		case 'o':
2289204917Sdes			addargs(&args, "-%c", ch);
2290204917Sdes			addargs(&args, "%s", optarg);
2291204917Sdes			break;
2292204917Sdes		case 'q':
2293255767Sdes			ll = SYSLOG_LEVEL_ERROR;
2294255767Sdes			quiet = 1;
2295204917Sdes			showprogress = 0;
2296204917Sdes			addargs(&args, "-%c", ch);
2297204917Sdes			break;
2298204917Sdes		case 'P':
2299204917Sdes			addargs(&args, "-oPort %s", optarg);
2300204917Sdes			break;
230176259Sgreen		case 'v':
230292555Sdes			if (debug_level < 3) {
230392555Sdes				addargs(&args, "-v");
230492555Sdes				ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
230592555Sdes			}
230692555Sdes			debug_level++;
230776259Sgreen			break;
230876259Sgreen		case '1':
230992555Sdes			sshver = 1;
231076259Sgreen			if (sftp_server == NULL)
231176259Sgreen				sftp_server = _PATH_SFTP_SERVER;
231276259Sgreen			break;
2313204917Sdes		case '2':
2314204917Sdes			sshver = 2;
231576259Sgreen			break;
2316255767Sdes		case 'a':
2317255767Sdes			global_aflag = 1;
2318255767Sdes			break;
2319204917Sdes		case 'B':
2320204917Sdes			copy_buffer_len = strtol(optarg, &cp, 10);
2321204917Sdes			if (copy_buffer_len == 0 || *cp != '\0')
2322204917Sdes				fatal("Invalid buffer size \"%s\"", optarg);
232376259Sgreen			break;
232476259Sgreen		case 'b':
2325126274Sdes			if (batchmode)
2326126274Sdes				fatal("Batch file already specified.");
2327126274Sdes
2328126274Sdes			/* Allow "-" as stdin */
2329137015Sdes			if (strcmp(optarg, "-") != 0 &&
2330149749Sdes			    (infile = fopen(optarg, "r")) == NULL)
2331126274Sdes				fatal("%s (%s).", strerror(errno), optarg);
2332113908Sdes			showprogress = 0;
2333255767Sdes			quiet = batchmode = 1;
2334146998Sdes			addargs(&args, "-obatchmode yes");
233576259Sgreen			break;
2336262566Sdes		case 'f':
2337262566Sdes			global_fflag = 1;
2338262566Sdes			break;
2339204917Sdes		case 'p':
2340204917Sdes			global_pflag = 1;
2341204917Sdes			break;
2342204917Sdes		case 'D':
234392555Sdes			sftp_direct = optarg;
234492555Sdes			break;
2345221420Sdes		case 'l':
2346221420Sdes			limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2347221420Sdes			    &errstr);
2348221420Sdes			if (errstr != NULL)
2349221420Sdes				usage();
2350221420Sdes			limit_kbps *= 1024; /* kbps */
2351221420Sdes			break;
2352204917Sdes		case 'r':
2353204917Sdes			global_rflag = 1;
235492555Sdes			break;
235592555Sdes		case 'R':
235692555Sdes			num_requests = strtol(optarg, &cp, 10);
235792555Sdes			if (num_requests == 0 || *cp != '\0')
235898675Sdes				fatal("Invalid number of requests \"%s\"",
235992555Sdes				    optarg);
236092555Sdes			break;
2361204917Sdes		case 's':
2362204917Sdes			sftp_server = optarg;
2363204917Sdes			break;
2364204917Sdes		case 'S':
2365204917Sdes			ssh_program = optarg;
2366204917Sdes			replacearg(&args, 0, "%s", ssh_program);
2367204917Sdes			break;
236876259Sgreen		case 'h':
236976259Sgreen		default:
237076259Sgreen			usage();
237176259Sgreen		}
237276259Sgreen	}
237376259Sgreen
2374128456Sdes	if (!isatty(STDERR_FILENO))
2375128456Sdes		showprogress = 0;
2376128456Sdes
237798675Sdes	log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
237898675Sdes
237992555Sdes	if (sftp_direct == NULL) {
238092555Sdes		if (optind == argc || argc > (optind + 2))
238192555Sdes			usage();
238276259Sgreen
238392555Sdes		userhost = xstrdup(argv[optind]);
238492555Sdes		file2 = argv[optind+1];
238576259Sgreen
2386113908Sdes		if ((host = strrchr(userhost, '@')) == NULL)
238792555Sdes			host = userhost;
238892555Sdes		else {
238992555Sdes			*host++ = '\0';
239092555Sdes			if (!userhost[0]) {
239192555Sdes				fprintf(stderr, "Missing username\n");
239292555Sdes				usage();
239392555Sdes			}
2394204917Sdes			addargs(&args, "-l");
2395204917Sdes			addargs(&args, "%s", userhost);
239692555Sdes		}
239792555Sdes
2398126274Sdes		if ((cp = colon(host)) != NULL) {
2399126274Sdes			*cp++ = '\0';
2400126274Sdes			file1 = cp;
2401126274Sdes		}
2402126274Sdes
240392555Sdes		host = cleanhostname(host);
240492555Sdes		if (!*host) {
240592555Sdes			fprintf(stderr, "Missing hostname\n");
240676259Sgreen			usage();
240776259Sgreen		}
240876259Sgreen
240992555Sdes		addargs(&args, "-oProtocol %d", sshver);
241076259Sgreen
241192555Sdes		/* no subsystem if the server-spec contains a '/' */
241292555Sdes		if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
241392555Sdes			addargs(&args, "-s");
241476259Sgreen
2415204917Sdes		addargs(&args, "--");
241692555Sdes		addargs(&args, "%s", host);
241798675Sdes		addargs(&args, "%s", (sftp_server != NULL ?
241892555Sdes		    sftp_server : "sftp"));
241976259Sgreen
2420124208Sdes		connect_to_server(ssh_program, args.list, &in, &out);
242192555Sdes	} else {
242292555Sdes		args.list = NULL;
242392555Sdes		addargs(&args, "sftp-server");
242476259Sgreen
2425124208Sdes		connect_to_server(sftp_direct, args.list, &in, &out);
242692555Sdes	}
2427157016Sdes	freeargs(&args);
242876259Sgreen
2429221420Sdes	conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
2430204917Sdes	if (conn == NULL)
2431204917Sdes		fatal("Couldn't initialise connection to server");
243276259Sgreen
2433255767Sdes	if (!quiet) {
2434204917Sdes		if (sftp_direct == NULL)
2435204917Sdes			fprintf(stderr, "Connected to %s.\n", host);
2436204917Sdes		else
2437204917Sdes			fprintf(stderr, "Attached to %s.\n", sftp_direct);
2438204917Sdes	}
2439204917Sdes
2440204917Sdes	err = interactive_loop(conn, file1, file2);
2441204917Sdes
244298937Sdes#if !defined(USE_PIPES)
2443149749Sdes	shutdown(in, SHUT_RDWR);
2444149749Sdes	shutdown(out, SHUT_RDWR);
244598937Sdes#endif
244698937Sdes
244776259Sgreen	close(in);
244876259Sgreen	close(out);
2449126274Sdes	if (batchmode)
245076259Sgreen		fclose(infile);
245176259Sgreen
245298675Sdes	while (waitpid(sshpid, NULL, 0) == -1)
245398675Sdes		if (errno != EINTR)
245498675Sdes			fatal("Couldn't wait for ssh process: %s",
245598675Sdes			    strerror(errno));
245676259Sgreen
2457113908Sdes	exit(err == 0 ? 0 : 1);
245876259Sgreen}
2459