1296853Sdes/* $OpenBSD: sftp.c,v 1.172 2016/02/15 09:47:49 dtucker 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>
52162852Sdes#include <stdlib.h>
53162852Sdes#include <stdio.h>
54162852Sdes#include <string.h>
55162852Sdes#include <unistd.h>
56162852Sdes#include <stdarg.h>
57146998Sdes
58181111Sdes#ifdef HAVE_UTIL_H
59181111Sdes# include <util.h>
60181111Sdes#endif
61181111Sdes
6276259Sgreen#include "xmalloc.h"
6376259Sgreen#include "log.h"
6476259Sgreen#include "pathnames.h"
6592555Sdes#include "misc.h"
6676259Sgreen
6776259Sgreen#include "sftp.h"
68295367Sdes#include "ssherr.h"
69295367Sdes#include "sshbuf.h"
7076259Sgreen#include "sftp-common.h"
7176259Sgreen#include "sftp-client.h"
7276259Sgreen
73204917Sdes#define DEFAULT_COPY_BUFLEN	32768	/* Size of buffer for up/download */
74294693Sdes#define DEFAULT_NUM_REQUESTS	64	/* # concurrent outstanding requests */
75204917Sdes
76126274Sdes/* File to read commands from */
77126274SdesFILE* infile;
78126274Sdes
79126274Sdes/* Are we in batchfile mode? */
80126274Sdesint batchmode = 0;
81126274Sdes
82126274Sdes/* PID of ssh transport process */
83126274Sdesstatic pid_t sshpid = -1;
84126274Sdes
85255767Sdes/* Suppress diagnositic messages */
86255767Sdesint quiet = 0;
87255767Sdes
88126274Sdes/* This is set to 0 if the progressmeter is not desired. */
89128456Sdesint showprogress = 1;
90126274Sdes
91204917Sdes/* When this option is set, we always recursively download/upload directories */
92204917Sdesint global_rflag = 0;
93204917Sdes
94295367Sdes/* When this option is set, we resume download or upload if possible */
95255767Sdesint global_aflag = 0;
96255767Sdes
97204917Sdes/* When this option is set, the file transfers will always preserve times */
98204917Sdesint global_pflag = 0;
99204917Sdes
100262566Sdes/* When this option is set, transfers will have fsync() called on each file */
101262566Sdesint global_fflag = 0;
102262566Sdes
103137015Sdes/* SIGINT received during command processing */
104137015Sdesvolatile sig_atomic_t interrupted = 0;
105137015Sdes
106137015Sdes/* I wish qsort() took a separate ctx for the comparison function...*/
107137015Sdesint sort_flag;
108137015Sdes
109204917Sdes/* Context used for commandline completion */
110204917Sdesstruct complete_ctx {
111204917Sdes	struct sftp_conn *conn;
112204917Sdes	char **remote_pathp;
113204917Sdes};
114204917Sdes
115126274Sdesint remote_glob(struct sftp_conn *, const char *, int,
116126274Sdes    int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
117126274Sdes
11898937Sdesextern char *__progname;
11998937Sdes
120126274Sdes/* Separators for interactive commands */
121126274Sdes#define WHITESPACE " \t\r\n"
12276259Sgreen
123137015Sdes/* ls flags */
124204917Sdes#define LS_LONG_VIEW	0x0001	/* Full view ala ls -l */
125204917Sdes#define LS_SHORT_VIEW	0x0002	/* Single row view ala ls -1 */
126204917Sdes#define LS_NUMERIC_VIEW	0x0004	/* Long view with numeric uid/gid */
127204917Sdes#define LS_NAME_SORT	0x0008	/* Sort by name (default) */
128204917Sdes#define LS_TIME_SORT	0x0010	/* Sort by mtime */
129204917Sdes#define LS_SIZE_SORT	0x0020	/* Sort by file size */
130204917Sdes#define LS_REVERSE_SORT	0x0040	/* Reverse sort order */
131204917Sdes#define LS_SHOW_ALL	0x0080	/* Don't skip filenames starting with '.' */
132204917Sdes#define LS_SI_UNITS	0x0100	/* Display sizes as K, M, G, etc. */
133113908Sdes
134204917Sdes#define VIEW_FLAGS	(LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
135137015Sdes#define SORT_FLAGS	(LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
136137015Sdes
137126274Sdes/* Commands for interactive mode */
138262566Sdesenum sftp_command {
139262566Sdes	I_CHDIR = 1,
140262566Sdes	I_CHGRP,
141262566Sdes	I_CHMOD,
142262566Sdes	I_CHOWN,
143262566Sdes	I_DF,
144262566Sdes	I_GET,
145262566Sdes	I_HELP,
146262566Sdes	I_LCHDIR,
147262566Sdes	I_LINK,
148262566Sdes	I_LLS,
149262566Sdes	I_LMKDIR,
150262566Sdes	I_LPWD,
151262566Sdes	I_LS,
152262566Sdes	I_LUMASK,
153262566Sdes	I_MKDIR,
154262566Sdes	I_PUT,
155262566Sdes	I_PWD,
156262566Sdes	I_QUIT,
157295367Sdes	I_REGET,
158262566Sdes	I_RENAME,
159295367Sdes	I_REPUT,
160262566Sdes	I_RM,
161262566Sdes	I_RMDIR,
162262566Sdes	I_SHELL,
163262566Sdes	I_SYMLINK,
164262566Sdes	I_VERSION,
165262566Sdes	I_PROGRESS,
166262566Sdes};
167126274Sdes
168126274Sdesstruct CMD {
169126274Sdes	const char *c;
170126274Sdes	const int n;
171204917Sdes	const int t;
172126274Sdes};
173126274Sdes
174204917Sdes/* Type of completion */
175204917Sdes#define NOARGS	0
176204917Sdes#define REMOTE	1
177204917Sdes#define LOCAL	2
178204917Sdes
179126274Sdesstatic const struct CMD cmds[] = {
180204917Sdes	{ "bye",	I_QUIT,		NOARGS	},
181204917Sdes	{ "cd",		I_CHDIR,	REMOTE	},
182204917Sdes	{ "chdir",	I_CHDIR,	REMOTE	},
183204917Sdes	{ "chgrp",	I_CHGRP,	REMOTE	},
184204917Sdes	{ "chmod",	I_CHMOD,	REMOTE	},
185204917Sdes	{ "chown",	I_CHOWN,	REMOTE	},
186204917Sdes	{ "df",		I_DF,		REMOTE	},
187204917Sdes	{ "dir",	I_LS,		REMOTE	},
188204917Sdes	{ "exit",	I_QUIT,		NOARGS	},
189204917Sdes	{ "get",	I_GET,		REMOTE	},
190204917Sdes	{ "help",	I_HELP,		NOARGS	},
191204917Sdes	{ "lcd",	I_LCHDIR,	LOCAL	},
192204917Sdes	{ "lchdir",	I_LCHDIR,	LOCAL	},
193204917Sdes	{ "lls",	I_LLS,		LOCAL	},
194204917Sdes	{ "lmkdir",	I_LMKDIR,	LOCAL	},
195221420Sdes	{ "ln",		I_LINK,		REMOTE	},
196204917Sdes	{ "lpwd",	I_LPWD,		LOCAL	},
197204917Sdes	{ "ls",		I_LS,		REMOTE	},
198204917Sdes	{ "lumask",	I_LUMASK,	NOARGS	},
199204917Sdes	{ "mkdir",	I_MKDIR,	REMOTE	},
200215116Sdes	{ "mget",	I_GET,		REMOTE	},
201215116Sdes	{ "mput",	I_PUT,		LOCAL	},
202204917Sdes	{ "progress",	I_PROGRESS,	NOARGS	},
203204917Sdes	{ "put",	I_PUT,		LOCAL	},
204204917Sdes	{ "pwd",	I_PWD,		REMOTE	},
205204917Sdes	{ "quit",	I_QUIT,		NOARGS	},
206255767Sdes	{ "reget",	I_REGET,	REMOTE	},
207204917Sdes	{ "rename",	I_RENAME,	REMOTE	},
208295367Sdes	{ "reput",	I_REPUT,	LOCAL	},
209204917Sdes	{ "rm",		I_RM,		REMOTE	},
210204917Sdes	{ "rmdir",	I_RMDIR,	REMOTE	},
211204917Sdes	{ "symlink",	I_SYMLINK,	REMOTE	},
212204917Sdes	{ "version",	I_VERSION,	NOARGS	},
213204917Sdes	{ "!",		I_SHELL,	NOARGS	},
214204917Sdes	{ "?",		I_HELP,		NOARGS	},
215204917Sdes	{ NULL,		-1,		-1	}
216126274Sdes};
217126274Sdes
218204917Sdesint interactive_loop(struct sftp_conn *, char *file1, char *file2);
219126274Sdes
220181111Sdes/* ARGSUSED */
22192555Sdesstatic void
222137015Sdeskillchild(int signo)
223137015Sdes{
224146998Sdes	if (sshpid > 1) {
225137015Sdes		kill(sshpid, SIGTERM);
226146998Sdes		waitpid(sshpid, NULL, 0);
227146998Sdes	}
228137015Sdes
229137015Sdes	_exit(1);
230137015Sdes}
231137015Sdes
232181111Sdes/* ARGSUSED */
233137015Sdesstatic void
234137015Sdescmd_interrupt(int signo)
235137015Sdes{
236137015Sdes	const char msg[] = "\rInterrupt  \n";
237146998Sdes	int olderrno = errno;
238137015Sdes
239255767Sdes	(void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
240137015Sdes	interrupted = 1;
241146998Sdes	errno = olderrno;
242137015Sdes}
243137015Sdes
244137015Sdesstatic void
245126274Sdeshelp(void)
246126274Sdes{
247192595Sdes	printf("Available commands:\n"
248192595Sdes	    "bye                                Quit sftp\n"
249192595Sdes	    "cd path                            Change remote directory to 'path'\n"
250192595Sdes	    "chgrp grp path                     Change group of file 'path' to 'grp'\n"
251192595Sdes	    "chmod mode path                    Change permissions of file 'path' to 'mode'\n"
252192595Sdes	    "chown own path                     Change owner of file 'path' to 'own'\n"
253192595Sdes	    "df [-hi] [path]                    Display statistics for current directory or\n"
254192595Sdes	    "                                   filesystem containing 'path'\n"
255192595Sdes	    "exit                               Quit sftp\n"
256295367Sdes	    "get [-afPpRr] remote [local]       Download file\n"
257295367Sdes	    "reget [-fPpRr] remote [local]      Resume download file\n"
258295367Sdes	    "reput [-fPpRr] [local] remote      Resume upload file\n"
259192595Sdes	    "help                               Display this help text\n"
260192595Sdes	    "lcd path                           Change local directory to 'path'\n"
261192595Sdes	    "lls [ls-options [path]]            Display local directory listing\n"
262192595Sdes	    "lmkdir path                        Create local directory\n"
263221420Sdes	    "ln [-s] oldpath newpath            Link remote file (-s for symlink)\n"
264192595Sdes	    "lpwd                               Print local working directory\n"
265204917Sdes	    "ls [-1afhlnrSt] [path]             Display remote directory listing\n"
266192595Sdes	    "lumask umask                       Set local umask to 'umask'\n"
267192595Sdes	    "mkdir path                         Create remote directory\n"
268192595Sdes	    "progress                           Toggle display of progress meter\n"
269295367Sdes	    "put [-afPpRr] local [remote]       Upload file\n"
270192595Sdes	    "pwd                                Display remote working directory\n"
271192595Sdes	    "quit                               Quit sftp\n"
272192595Sdes	    "rename oldpath newpath             Rename remote file\n"
273192595Sdes	    "rm path                            Delete remote file\n"
274192595Sdes	    "rmdir path                         Remove remote directory\n"
275192595Sdes	    "symlink oldpath newpath            Symlink remote file\n"
276192595Sdes	    "version                            Show SFTP version\n"
277192595Sdes	    "!command                           Execute 'command' in local shell\n"
278192595Sdes	    "!                                  Escape to local shell\n"
279192595Sdes	    "?                                  Synonym for help\n");
280126274Sdes}
281126274Sdes
282126274Sdesstatic void
283126274Sdeslocal_do_shell(const char *args)
284126274Sdes{
285126274Sdes	int status;
286126274Sdes	char *shell;
287126274Sdes	pid_t pid;
288126274Sdes
289126274Sdes	if (!*args)
290126274Sdes		args = NULL;
291126274Sdes
292221420Sdes	if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
293126274Sdes		shell = _PATH_BSHELL;
294126274Sdes
295126274Sdes	if ((pid = fork()) == -1)
296126274Sdes		fatal("Couldn't fork: %s", strerror(errno));
297126274Sdes
298126274Sdes	if (pid == 0) {
299126274Sdes		/* XXX: child has pipe fds to ssh subproc open - issue? */
300126274Sdes		if (args) {
301126274Sdes			debug3("Executing %s -c \"%s\"", shell, args);
302126274Sdes			execl(shell, shell, "-c", args, (char *)NULL);
303126274Sdes		} else {
304126274Sdes			debug3("Executing %s", shell);
305126274Sdes			execl(shell, shell, (char *)NULL);
306126274Sdes		}
307126274Sdes		fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
308126274Sdes		    strerror(errno));
309126274Sdes		_exit(1);
310126274Sdes	}
311126274Sdes	while (waitpid(pid, &status, 0) == -1)
312126274Sdes		if (errno != EINTR)
313126274Sdes			fatal("Couldn't wait for child: %s", strerror(errno));
314126274Sdes	if (!WIFEXITED(status))
315162852Sdes		error("Shell exited abnormally");
316126274Sdes	else if (WEXITSTATUS(status))
317126274Sdes		error("Shell exited with status %d", WEXITSTATUS(status));
318126274Sdes}
319126274Sdes
320126274Sdesstatic void
321126274Sdeslocal_do_ls(const char *args)
322126274Sdes{
323126274Sdes	if (!args || !*args)
324126274Sdes		local_do_shell(_PATH_LS);
325126274Sdes	else {
326126274Sdes		int len = strlen(_PATH_LS " ") + strlen(args) + 1;
327126274Sdes		char *buf = xmalloc(len);
328126274Sdes
329126274Sdes		/* XXX: quoting - rip quoting code from ftp? */
330126274Sdes		snprintf(buf, len, _PATH_LS " %s", args);
331126274Sdes		local_do_shell(buf);
332255767Sdes		free(buf);
333126274Sdes	}
334126274Sdes}
335126274Sdes
336126274Sdes/* Strip one path (usually the pwd) from the start of another */
337126274Sdesstatic char *
338126274Sdespath_strip(char *path, char *strip)
339126274Sdes{
340126274Sdes	size_t len;
341126274Sdes
342126274Sdes	if (strip == NULL)
343126274Sdes		return (xstrdup(path));
344126274Sdes
345126274Sdes	len = strlen(strip);
346146998Sdes	if (strncmp(path, strip, len) == 0) {
347126274Sdes		if (strip[len - 1] != '/' && path[len] == '/')
348126274Sdes			len++;
349126274Sdes		return (xstrdup(path + len));
350126274Sdes	}
351126274Sdes
352126274Sdes	return (xstrdup(path));
353126274Sdes}
354126274Sdes
355126274Sdesstatic char *
356126274Sdesmake_absolute(char *p, char *pwd)
357126274Sdes{
358137015Sdes	char *abs_str;
359126274Sdes
360126274Sdes	/* Derelativise */
361126274Sdes	if (p && p[0] != '/') {
362137015Sdes		abs_str = path_append(pwd, p);
363255767Sdes		free(p);
364137015Sdes		return(abs_str);
365126274Sdes	} else
366126274Sdes		return(p);
367126274Sdes}
368126274Sdes
369126274Sdesstatic int
370255767Sdesparse_getput_flags(const char *cmd, char **argv, int argc,
371262566Sdes    int *aflag, int *fflag, int *pflag, int *rflag)
372126274Sdes{
373181111Sdes	extern int opterr, optind, optopt, optreset;
374181111Sdes	int ch;
375126274Sdes
376181111Sdes	optind = optreset = 1;
377181111Sdes	opterr = 0;
378181111Sdes
379262566Sdes	*aflag = *fflag = *rflag = *pflag = 0;
380262566Sdes	while ((ch = getopt(argc, argv, "afPpRr")) != -1) {
381181111Sdes		switch (ch) {
382255767Sdes		case 'a':
383255767Sdes			*aflag = 1;
384255767Sdes			break;
385262566Sdes		case 'f':
386262566Sdes			*fflag = 1;
387262566Sdes			break;
388126274Sdes		case 'p':
389126274Sdes		case 'P':
390126274Sdes			*pflag = 1;
391126274Sdes			break;
392204917Sdes		case 'r':
393204917Sdes		case 'R':
394204917Sdes			*rflag = 1;
395204917Sdes			break;
396126274Sdes		default:
397181111Sdes			error("%s: Invalid flag -%c", cmd, optopt);
398181111Sdes			return -1;
399126274Sdes		}
400126274Sdes	}
401126274Sdes
402181111Sdes	return optind;
403126274Sdes}
404126274Sdes
405126274Sdesstatic int
406221420Sdesparse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
407221420Sdes{
408221420Sdes	extern int opterr, optind, optopt, optreset;
409221420Sdes	int ch;
410221420Sdes
411221420Sdes	optind = optreset = 1;
412221420Sdes	opterr = 0;
413221420Sdes
414221420Sdes	*sflag = 0;
415221420Sdes	while ((ch = getopt(argc, argv, "s")) != -1) {
416221420Sdes		switch (ch) {
417221420Sdes		case 's':
418221420Sdes			*sflag = 1;
419221420Sdes			break;
420221420Sdes		default:
421221420Sdes			error("%s: Invalid flag -%c", cmd, optopt);
422221420Sdes			return -1;
423221420Sdes		}
424221420Sdes	}
425221420Sdes
426221420Sdes	return optind;
427221420Sdes}
428221420Sdes
429221420Sdesstatic int
430262566Sdesparse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
431262566Sdes{
432262566Sdes	extern int opterr, optind, optopt, optreset;
433262566Sdes	int ch;
434262566Sdes
435262566Sdes	optind = optreset = 1;
436262566Sdes	opterr = 0;
437262566Sdes
438262566Sdes	*lflag = 0;
439262566Sdes	while ((ch = getopt(argc, argv, "l")) != -1) {
440262566Sdes		switch (ch) {
441262566Sdes		case 'l':
442262566Sdes			*lflag = 1;
443262566Sdes			break;
444262566Sdes		default:
445262566Sdes			error("%s: Invalid flag -%c", cmd, optopt);
446262566Sdes			return -1;
447262566Sdes		}
448262566Sdes	}
449262566Sdes
450262566Sdes	return optind;
451262566Sdes}
452262566Sdes
453262566Sdesstatic int
454181111Sdesparse_ls_flags(char **argv, int argc, int *lflag)
455126274Sdes{
456181111Sdes	extern int opterr, optind, optopt, optreset;
457181111Sdes	int ch;
458126274Sdes
459181111Sdes	optind = optreset = 1;
460181111Sdes	opterr = 0;
461181111Sdes
462137015Sdes	*lflag = LS_NAME_SORT;
463204917Sdes	while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
464181111Sdes		switch (ch) {
465181111Sdes		case '1':
466181111Sdes			*lflag &= ~VIEW_FLAGS;
467181111Sdes			*lflag |= LS_SHORT_VIEW;
468181111Sdes			break;
469181111Sdes		case 'S':
470181111Sdes			*lflag &= ~SORT_FLAGS;
471181111Sdes			*lflag |= LS_SIZE_SORT;
472181111Sdes			break;
473181111Sdes		case 'a':
474181111Sdes			*lflag |= LS_SHOW_ALL;
475181111Sdes			break;
476181111Sdes		case 'f':
477181111Sdes			*lflag &= ~SORT_FLAGS;
478181111Sdes			break;
479204917Sdes		case 'h':
480204917Sdes			*lflag |= LS_SI_UNITS;
481204917Sdes			break;
482181111Sdes		case 'l':
483204917Sdes			*lflag &= ~LS_SHORT_VIEW;
484181111Sdes			*lflag |= LS_LONG_VIEW;
485181111Sdes			break;
486181111Sdes		case 'n':
487204917Sdes			*lflag &= ~LS_SHORT_VIEW;
488181111Sdes			*lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
489181111Sdes			break;
490181111Sdes		case 'r':
491181111Sdes			*lflag |= LS_REVERSE_SORT;
492181111Sdes			break;
493181111Sdes		case 't':
494181111Sdes			*lflag &= ~SORT_FLAGS;
495181111Sdes			*lflag |= LS_TIME_SORT;
496181111Sdes			break;
497181111Sdes		default:
498181111Sdes			error("ls: Invalid flag -%c", optopt);
499181111Sdes			return -1;
500126274Sdes		}
501126274Sdes	}
502126274Sdes
503181111Sdes	return optind;
504126274Sdes}
505126274Sdes
506126274Sdesstatic int
507181111Sdesparse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
508126274Sdes{
509181111Sdes	extern int opterr, optind, optopt, optreset;
510181111Sdes	int ch;
511126274Sdes
512181111Sdes	optind = optreset = 1;
513181111Sdes	opterr = 0;
514126274Sdes
515181111Sdes	*hflag = *iflag = 0;
516181111Sdes	while ((ch = getopt(argc, argv, "hi")) != -1) {
517181111Sdes		switch (ch) {
518181111Sdes		case 'h':
519181111Sdes			*hflag = 1;
520181111Sdes			break;
521181111Sdes		case 'i':
522181111Sdes			*iflag = 1;
523181111Sdes			break;
524181111Sdes		default:
525181111Sdes			error("%s: Invalid flag -%c", cmd, optopt);
526181111Sdes			return -1;
527126274Sdes		}
528126274Sdes	}
529126274Sdes
530181111Sdes	return optind;
531126274Sdes}
532126274Sdes
533126274Sdesstatic int
534262566Sdesparse_no_flags(const char *cmd, char **argv, int argc)
535262566Sdes{
536262566Sdes	extern int opterr, optind, optopt, optreset;
537262566Sdes	int ch;
538262566Sdes
539262566Sdes	optind = optreset = 1;
540262566Sdes	opterr = 0;
541262566Sdes
542262566Sdes	while ((ch = getopt(argc, argv, "")) != -1) {
543262566Sdes		switch (ch) {
544262566Sdes		default:
545262566Sdes			error("%s: Invalid flag -%c", cmd, optopt);
546262566Sdes			return -1;
547262566Sdes		}
548262566Sdes	}
549262566Sdes
550262566Sdes	return optind;
551262566Sdes}
552262566Sdes
553262566Sdesstatic int
554126274Sdesis_dir(char *path)
555126274Sdes{
556126274Sdes	struct stat sb;
557126274Sdes
558126274Sdes	/* XXX: report errors? */
559126274Sdes	if (stat(path, &sb) == -1)
560126274Sdes		return(0);
561126274Sdes
562162852Sdes	return(S_ISDIR(sb.st_mode));
563126274Sdes}
564126274Sdes
565126274Sdesstatic int
566126274Sdesremote_is_dir(struct sftp_conn *conn, char *path)
567126274Sdes{
568126274Sdes	Attrib *a;
569126274Sdes
570126274Sdes	/* XXX: report errors? */
571126274Sdes	if ((a = do_stat(conn, path, 1)) == NULL)
572126274Sdes		return(0);
573126274Sdes	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
574126274Sdes		return(0);
575162852Sdes	return(S_ISDIR(a->perm));
576126274Sdes}
577126274Sdes
578204917Sdes/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
579126274Sdesstatic int
580204917Sdespathname_is_dir(char *pathname)
581126274Sdes{
582204917Sdes	size_t l = strlen(pathname);
583204917Sdes
584204917Sdes	return l > 0 && pathname[l - 1] == '/';
585204917Sdes}
586204917Sdes
587204917Sdesstatic int
588204917Sdesprocess_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
589262566Sdes    int pflag, int rflag, int resume, int fflag)
590204917Sdes{
591126274Sdes	char *abs_src = NULL;
592126274Sdes	char *abs_dst = NULL;
593126274Sdes	glob_t g;
594204917Sdes	char *filename, *tmp=NULL;
595295367Sdes	int i, r, err = 0;
596126274Sdes
597126274Sdes	abs_src = xstrdup(src);
598126274Sdes	abs_src = make_absolute(abs_src, pwd);
599204917Sdes	memset(&g, 0, sizeof(g));
600126274Sdes
601126274Sdes	debug3("Looking up %s", abs_src);
602295367Sdes	if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
603295367Sdes		if (r == GLOB_NOSPACE) {
604295367Sdes			error("Too many matches for \"%s\".", abs_src);
605295367Sdes		} else {
606295367Sdes			error("File \"%s\" not found.", abs_src);
607295367Sdes		}
608126274Sdes		err = -1;
609126274Sdes		goto out;
610126274Sdes	}
611126274Sdes
612204917Sdes	/*
613204917Sdes	 * If multiple matches then dst must be a directory or
614204917Sdes	 * unspecified.
615204917Sdes	 */
616204917Sdes	if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
617204917Sdes		error("Multiple source paths, but destination "
618204917Sdes		    "\"%s\" is not a directory", dst);
619126274Sdes		err = -1;
620126274Sdes		goto out;
621126274Sdes	}
622126274Sdes
623137015Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
624204917Sdes		tmp = xstrdup(g.gl_pathv[i]);
625204917Sdes		if ((filename = basename(tmp)) == NULL) {
626204917Sdes			error("basename %s: %s", tmp, strerror(errno));
627255767Sdes			free(tmp);
628126274Sdes			err = -1;
629126274Sdes			goto out;
630126274Sdes		}
631126274Sdes
632126274Sdes		if (g.gl_matchc == 1 && dst) {
633126274Sdes			if (is_dir(dst)) {
634204917Sdes				abs_dst = path_append(dst, filename);
635204917Sdes			} else {
636126274Sdes				abs_dst = xstrdup(dst);
637204917Sdes			}
638126274Sdes		} else if (dst) {
639204917Sdes			abs_dst = path_append(dst, filename);
640204917Sdes		} else {
641204917Sdes			abs_dst = xstrdup(filename);
642204917Sdes		}
643255767Sdes		free(tmp);
644126274Sdes
645255767Sdes		resume |= global_aflag;
646255767Sdes		if (!quiet && resume)
647255767Sdes			printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
648255767Sdes		else if (!quiet && !resume)
649255767Sdes			printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
650204917Sdes		if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
651255767Sdes			if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
652262566Sdes			    pflag || global_pflag, 1, resume,
653262566Sdes			    fflag || global_fflag) == -1)
654204917Sdes				err = -1;
655204917Sdes		} else {
656204917Sdes			if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
657262566Sdes			    pflag || global_pflag, resume,
658262566Sdes			    fflag || global_fflag) == -1)
659204917Sdes				err = -1;
660204917Sdes		}
661255767Sdes		free(abs_dst);
662126274Sdes		abs_dst = NULL;
663126274Sdes	}
664126274Sdes
665126274Sdesout:
666255767Sdes	free(abs_src);
667126274Sdes	globfree(&g);
668126274Sdes	return(err);
669126274Sdes}
670126274Sdes
671126274Sdesstatic int
672204917Sdesprocess_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
673295367Sdes    int pflag, int rflag, int resume, int fflag)
674126274Sdes{
675126274Sdes	char *tmp_dst = NULL;
676126274Sdes	char *abs_dst = NULL;
677204917Sdes	char *tmp = NULL, *filename = NULL;
678126274Sdes	glob_t g;
679126274Sdes	int err = 0;
680204917Sdes	int i, dst_is_dir = 1;
681181111Sdes	struct stat sb;
682126274Sdes
683126274Sdes	if (dst) {
684126274Sdes		tmp_dst = xstrdup(dst);
685126274Sdes		tmp_dst = make_absolute(tmp_dst, pwd);
686126274Sdes	}
687126274Sdes
688126274Sdes	memset(&g, 0, sizeof(g));
689126274Sdes	debug3("Looking up %s", src);
690204917Sdes	if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
691126274Sdes		error("File \"%s\" not found.", src);
692126274Sdes		err = -1;
693126274Sdes		goto out;
694126274Sdes	}
695126274Sdes
696204917Sdes	/* If we aren't fetching to pwd then stash this status for later */
697204917Sdes	if (tmp_dst != NULL)
698204917Sdes		dst_is_dir = remote_is_dir(conn, tmp_dst);
699204917Sdes
700126274Sdes	/* If multiple matches, dst may be directory or unspecified */
701204917Sdes	if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
702204917Sdes		error("Multiple paths match, but destination "
703204917Sdes		    "\"%s\" is not a directory", tmp_dst);
704126274Sdes		err = -1;
705126274Sdes		goto out;
706126274Sdes	}
707126274Sdes
708137015Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
709181111Sdes		if (stat(g.gl_pathv[i], &sb) == -1) {
710181111Sdes			err = -1;
711181111Sdes			error("stat %s: %s", g.gl_pathv[i], strerror(errno));
712181111Sdes			continue;
713181111Sdes		}
714262566Sdes
715204917Sdes		tmp = xstrdup(g.gl_pathv[i]);
716204917Sdes		if ((filename = basename(tmp)) == NULL) {
717204917Sdes			error("basename %s: %s", tmp, strerror(errno));
718255767Sdes			free(tmp);
719126274Sdes			err = -1;
720126274Sdes			goto out;
721126274Sdes		}
722126274Sdes
723126274Sdes		if (g.gl_matchc == 1 && tmp_dst) {
724126274Sdes			/* If directory specified, append filename */
725204917Sdes			if (dst_is_dir)
726204917Sdes				abs_dst = path_append(tmp_dst, filename);
727204917Sdes			else
728126274Sdes				abs_dst = xstrdup(tmp_dst);
729126274Sdes		} else if (tmp_dst) {
730204917Sdes			abs_dst = path_append(tmp_dst, filename);
731204917Sdes		} else {
732204917Sdes			abs_dst = make_absolute(xstrdup(filename), pwd);
733204917Sdes		}
734255767Sdes		free(tmp);
735126274Sdes
736295367Sdes                resume |= global_aflag;
737295367Sdes		if (!quiet && resume)
738295367Sdes			printf("Resuming upload of %s to %s\n", g.gl_pathv[i],
739295367Sdes				abs_dst);
740295367Sdes		else if (!quiet && !resume)
741255767Sdes			printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
742204917Sdes		if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
743204917Sdes			if (upload_dir(conn, g.gl_pathv[i], abs_dst,
744295367Sdes			    pflag || global_pflag, 1, resume,
745262566Sdes			    fflag || global_fflag) == -1)
746204917Sdes				err = -1;
747204917Sdes		} else {
748204917Sdes			if (do_upload(conn, g.gl_pathv[i], abs_dst,
749295367Sdes			    pflag || global_pflag, resume,
750262566Sdes			    fflag || global_fflag) == -1)
751204917Sdes				err = -1;
752204917Sdes		}
753126274Sdes	}
754126274Sdes
755126274Sdesout:
756255767Sdes	free(abs_dst);
757255767Sdes	free(tmp_dst);
758126274Sdes	globfree(&g);
759126274Sdes	return(err);
760126274Sdes}
761126274Sdes
762126274Sdesstatic int
763126274Sdessdirent_comp(const void *aa, const void *bb)
764126274Sdes{
765126274Sdes	SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
766126274Sdes	SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
767137015Sdes	int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
768126274Sdes
769137015Sdes#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
770137015Sdes	if (sort_flag & LS_NAME_SORT)
771137015Sdes		return (rmul * strcmp(a->filename, b->filename));
772137015Sdes	else if (sort_flag & LS_TIME_SORT)
773137015Sdes		return (rmul * NCMP(a->a.mtime, b->a.mtime));
774137015Sdes	else if (sort_flag & LS_SIZE_SORT)
775137015Sdes		return (rmul * NCMP(a->a.size, b->a.size));
776137015Sdes
777137015Sdes	fatal("Unknown ls sort type");
778126274Sdes}
779126274Sdes
780126274Sdes/* sftp ls.1 replacement for directories */
781126274Sdesstatic int
782126274Sdesdo_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
783126274Sdes{
784149749Sdes	int n;
785149749Sdes	u_int c = 1, colspace = 0, columns = 1;
786126274Sdes	SFTP_DIRENT **d;
787126274Sdes
788126274Sdes	if ((n = do_readdir(conn, path, &d)) != 0)
789126274Sdes		return (n);
790126274Sdes
791137015Sdes	if (!(lflag & LS_SHORT_VIEW)) {
792149749Sdes		u_int m = 0, width = 80;
793126274Sdes		struct winsize ws;
794126274Sdes		char *tmp;
795126274Sdes
796126274Sdes		/* Count entries for sort and find longest filename */
797137015Sdes		for (n = 0; d[n] != NULL; n++) {
798137015Sdes			if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
799137015Sdes				m = MAX(m, strlen(d[n]->filename));
800137015Sdes		}
801126274Sdes
802126274Sdes		/* Add any subpath that also needs to be counted */
803126274Sdes		tmp = path_strip(path, strip_path);
804126274Sdes		m += strlen(tmp);
805255767Sdes		free(tmp);
806126274Sdes
807126274Sdes		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
808126274Sdes			width = ws.ws_col;
809126274Sdes
810126274Sdes		columns = width / (m + 2);
811126274Sdes		columns = MAX(columns, 1);
812126274Sdes		colspace = width / columns;
813126274Sdes		colspace = MIN(colspace, width);
814126274Sdes	}
815126274Sdes
816137015Sdes	if (lflag & SORT_FLAGS) {
817157016Sdes		for (n = 0; d[n] != NULL; n++)
818157016Sdes			;	/* count entries */
819137015Sdes		sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
820137015Sdes		qsort(d, n, sizeof(*d), sdirent_comp);
821137015Sdes	}
822126274Sdes
823137015Sdes	for (n = 0; d[n] != NULL && !interrupted; n++) {
824126274Sdes		char *tmp, *fname;
825126274Sdes
826137015Sdes		if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
827137015Sdes			continue;
828137015Sdes
829126274Sdes		tmp = path_append(path, d[n]->filename);
830126274Sdes		fname = path_strip(tmp, strip_path);
831255767Sdes		free(tmp);
832126274Sdes
833137015Sdes		if (lflag & LS_LONG_VIEW) {
834204917Sdes			if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
835137015Sdes				char *lname;
836137015Sdes				struct stat sb;
837126274Sdes
838137015Sdes				memset(&sb, 0, sizeof(sb));
839137015Sdes				attrib_to_stat(&d[n]->a, &sb);
840204917Sdes				lname = ls_file(fname, &sb, 1,
841204917Sdes				    (lflag & LS_SI_UNITS));
842137015Sdes				printf("%s\n", lname);
843255767Sdes				free(lname);
844137015Sdes			} else
845137015Sdes				printf("%s\n", d[n]->longname);
846126274Sdes		} else {
847126274Sdes			printf("%-*s", colspace, fname);
848126274Sdes			if (c >= columns) {
849126274Sdes				printf("\n");
850126274Sdes				c = 1;
851126274Sdes			} else
852126274Sdes				c++;
853126274Sdes		}
854126274Sdes
855255767Sdes		free(fname);
856126274Sdes	}
857126274Sdes
858137015Sdes	if (!(lflag & LS_LONG_VIEW) && (c != 1))
859126274Sdes		printf("\n");
860126274Sdes
861126274Sdes	free_sftp_dirents(d);
862126274Sdes	return (0);
863126274Sdes}
864126274Sdes
865126274Sdes/* sftp ls.1 replacement which handles path globs */
866126274Sdesstatic int
867126274Sdesdo_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
868126274Sdes    int lflag)
869126274Sdes{
870221420Sdes	char *fname, *lname;
871126274Sdes	glob_t g;
872295367Sdes	int err, r;
873221420Sdes	struct winsize ws;
874221420Sdes	u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
875126274Sdes
876126274Sdes	memset(&g, 0, sizeof(g));
877126274Sdes
878295367Sdes	if ((r = remote_glob(conn, path,
879240075Sdes	    GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
880295367Sdes	    NULL, &g)) != 0 ||
881221420Sdes	    (g.gl_pathc && !g.gl_matchc)) {
882146998Sdes		if (g.gl_pathc)
883146998Sdes			globfree(&g);
884295367Sdes		if (r == GLOB_NOSPACE) {
885295367Sdes			error("Can't ls: Too many matches for \"%s\"", path);
886295367Sdes		} else {
887295367Sdes			error("Can't ls: \"%s\" not found", path);
888295367Sdes		}
889221420Sdes		return -1;
890126274Sdes	}
891126274Sdes
892137015Sdes	if (interrupted)
893137015Sdes		goto out;
894137015Sdes
895126274Sdes	/*
896146998Sdes	 * If the glob returns a single match and it is a directory,
897146998Sdes	 * then just list its contents.
898126274Sdes	 */
899221420Sdes	if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
900221420Sdes	    S_ISDIR(g.gl_statv[0]->st_mode)) {
901221420Sdes		err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
902221420Sdes		globfree(&g);
903221420Sdes		return err;
904126274Sdes	}
905126274Sdes
906221420Sdes	if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
907221420Sdes		width = ws.ws_col;
908221420Sdes
909137015Sdes	if (!(lflag & LS_SHORT_VIEW)) {
910126274Sdes		/* Count entries for sort and find longest filename */
911126274Sdes		for (i = 0; g.gl_pathv[i]; i++)
912126274Sdes			m = MAX(m, strlen(g.gl_pathv[i]));
913126274Sdes
914126274Sdes		columns = width / (m + 2);
915126274Sdes		columns = MAX(columns, 1);
916126274Sdes		colspace = width / columns;
917126274Sdes	}
918126274Sdes
919240075Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
920126274Sdes		fname = path_strip(g.gl_pathv[i], strip_path);
921137015Sdes		if (lflag & LS_LONG_VIEW) {
922221420Sdes			if (g.gl_statv[i] == NULL) {
923221420Sdes				error("no stat information for %s", fname);
924221420Sdes				continue;
925221420Sdes			}
926221420Sdes			lname = ls_file(fname, g.gl_statv[i], 1,
927221420Sdes			    (lflag & LS_SI_UNITS));
928126274Sdes			printf("%s\n", lname);
929255767Sdes			free(lname);
930126274Sdes		} else {
931126274Sdes			printf("%-*s", colspace, fname);
932126274Sdes			if (c >= columns) {
933126274Sdes				printf("\n");
934126274Sdes				c = 1;
935126274Sdes			} else
936126274Sdes				c++;
937126274Sdes		}
938255767Sdes		free(fname);
939126274Sdes	}
940126274Sdes
941137015Sdes	if (!(lflag & LS_LONG_VIEW) && (c != 1))
942126274Sdes		printf("\n");
943126274Sdes
944137015Sdes out:
945126274Sdes	if (g.gl_pathc)
946126274Sdes		globfree(&g);
947126274Sdes
948221420Sdes	return 0;
949126274Sdes}
950126274Sdes
951126274Sdesstatic int
952181111Sdesdo_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
953181111Sdes{
954181111Sdes	struct sftp_statvfs st;
955181111Sdes	char s_used[FMT_SCALED_STRSIZE];
956181111Sdes	char s_avail[FMT_SCALED_STRSIZE];
957181111Sdes	char s_root[FMT_SCALED_STRSIZE];
958181111Sdes	char s_total[FMT_SCALED_STRSIZE];
959204917Sdes	unsigned long long ffree;
960181111Sdes
961181111Sdes	if (do_statvfs(conn, path, &st, 1) == -1)
962181111Sdes		return -1;
963181111Sdes	if (iflag) {
964204917Sdes		ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
965181111Sdes		printf("     Inodes        Used       Avail      "
966181111Sdes		    "(root)    %%Capacity\n");
967181111Sdes		printf("%11llu %11llu %11llu %11llu         %3llu%%\n",
968181111Sdes		    (unsigned long long)st.f_files,
969181111Sdes		    (unsigned long long)(st.f_files - st.f_ffree),
970181111Sdes		    (unsigned long long)st.f_favail,
971204917Sdes		    (unsigned long long)st.f_ffree, ffree);
972181111Sdes	} else if (hflag) {
973181111Sdes		strlcpy(s_used, "error", sizeof(s_used));
974181111Sdes		strlcpy(s_avail, "error", sizeof(s_avail));
975181111Sdes		strlcpy(s_root, "error", sizeof(s_root));
976181111Sdes		strlcpy(s_total, "error", sizeof(s_total));
977181111Sdes		fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
978181111Sdes		fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
979181111Sdes		fmt_scaled(st.f_bfree * st.f_frsize, s_root);
980181111Sdes		fmt_scaled(st.f_blocks * st.f_frsize, s_total);
981181111Sdes		printf("    Size     Used    Avail   (root)    %%Capacity\n");
982181111Sdes		printf("%7sB %7sB %7sB %7sB         %3llu%%\n",
983181111Sdes		    s_total, s_used, s_avail, s_root,
984181111Sdes		    (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
985181111Sdes		    st.f_blocks));
986181111Sdes	} else {
987181111Sdes		printf("        Size         Used        Avail       "
988181111Sdes		    "(root)    %%Capacity\n");
989181111Sdes		printf("%12llu %12llu %12llu %12llu         %3llu%%\n",
990181111Sdes		    (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
991181111Sdes		    (unsigned long long)(st.f_frsize *
992181111Sdes		    (st.f_blocks - st.f_bfree) / 1024),
993181111Sdes		    (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
994181111Sdes		    (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
995181111Sdes		    (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
996181111Sdes		    st.f_blocks));
997181111Sdes	}
998181111Sdes	return 0;
999181111Sdes}
1000181111Sdes
1001181111Sdes/*
1002181111Sdes * Undo escaping of glob sequences in place. Used to undo extra escaping
1003181111Sdes * applied in makeargv() when the string is destined for a function that
1004181111Sdes * does not glob it.
1005181111Sdes */
1006181111Sdesstatic void
1007181111Sdesundo_glob_escape(char *s)
1008181111Sdes{
1009181111Sdes	size_t i, j;
1010181111Sdes
1011181111Sdes	for (i = j = 0;;) {
1012181111Sdes		if (s[i] == '\0') {
1013181111Sdes			s[j] = '\0';
1014181111Sdes			return;
1015181111Sdes		}
1016181111Sdes		if (s[i] != '\\') {
1017181111Sdes			s[j++] = s[i++];
1018181111Sdes			continue;
1019181111Sdes		}
1020181111Sdes		/* s[i] == '\\' */
1021181111Sdes		++i;
1022181111Sdes		switch (s[i]) {
1023181111Sdes		case '?':
1024181111Sdes		case '[':
1025181111Sdes		case '*':
1026181111Sdes		case '\\':
1027181111Sdes			s[j++] = s[i++];
1028181111Sdes			break;
1029181111Sdes		case '\0':
1030181111Sdes			s[j++] = '\\';
1031181111Sdes			s[j] = '\0';
1032181111Sdes			return;
1033181111Sdes		default:
1034181111Sdes			s[j++] = '\\';
1035181111Sdes			s[j++] = s[i++];
1036181111Sdes			break;
1037181111Sdes		}
1038181111Sdes	}
1039181111Sdes}
1040181111Sdes
1041181111Sdes/*
1042181111Sdes * Split a string into an argument vector using sh(1)-style quoting,
1043181111Sdes * comment and escaping rules, but with some tweaks to handle glob(3)
1044181111Sdes * wildcards.
1045204917Sdes * The "sloppy" flag allows for recovery from missing terminating quote, for
1046204917Sdes * use in parsing incomplete commandlines during tab autocompletion.
1047204917Sdes *
1048181111Sdes * Returns NULL on error or a NULL-terminated array of arguments.
1049204917Sdes *
1050204917Sdes * If "lastquote" is not NULL, the quoting character used for the last
1051204917Sdes * argument is placed in *lastquote ("\0", "'" or "\"").
1052262566Sdes *
1053204917Sdes * If "terminated" is not NULL, *terminated will be set to 1 when the
1054204917Sdes * last argument's quote has been properly terminated or 0 otherwise.
1055204917Sdes * This parameter is only of use if "sloppy" is set.
1056181111Sdes */
1057181111Sdes#define MAXARGS 	128
1058181111Sdes#define MAXARGLEN	8192
1059181111Sdesstatic char **
1060204917Sdesmakeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1061204917Sdes    u_int *terminated)
1062181111Sdes{
1063181111Sdes	int argc, quot;
1064181111Sdes	size_t i, j;
1065181111Sdes	static char argvs[MAXARGLEN];
1066181111Sdes	static char *argv[MAXARGS + 1];
1067181111Sdes	enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1068181111Sdes
1069181111Sdes	*argcp = argc = 0;
1070181111Sdes	if (strlen(arg) > sizeof(argvs) - 1) {
1071181111Sdes args_too_longs:
1072181111Sdes		error("string too long");
1073181111Sdes		return NULL;
1074181111Sdes	}
1075204917Sdes	if (terminated != NULL)
1076204917Sdes		*terminated = 1;
1077204917Sdes	if (lastquote != NULL)
1078204917Sdes		*lastquote = '\0';
1079181111Sdes	state = MA_START;
1080181111Sdes	i = j = 0;
1081181111Sdes	for (;;) {
1082248619Sdes		if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
1083248619Sdes			error("Too many arguments.");
1084248619Sdes			return NULL;
1085248619Sdes		}
1086262566Sdes		if (isspace((unsigned char)arg[i])) {
1087181111Sdes			if (state == MA_UNQUOTED) {
1088181111Sdes				/* Terminate current argument */
1089181111Sdes				argvs[j++] = '\0';
1090181111Sdes				argc++;
1091181111Sdes				state = MA_START;
1092181111Sdes			} else if (state != MA_START)
1093181111Sdes				argvs[j++] = arg[i];
1094181111Sdes		} else if (arg[i] == '"' || arg[i] == '\'') {
1095181111Sdes			q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1096181111Sdes			if (state == MA_START) {
1097181111Sdes				argv[argc] = argvs + j;
1098181111Sdes				state = q;
1099204917Sdes				if (lastquote != NULL)
1100204917Sdes					*lastquote = arg[i];
1101262566Sdes			} else if (state == MA_UNQUOTED)
1102181111Sdes				state = q;
1103181111Sdes			else if (state == q)
1104181111Sdes				state = MA_UNQUOTED;
1105181111Sdes			else
1106181111Sdes				argvs[j++] = arg[i];
1107181111Sdes		} else if (arg[i] == '\\') {
1108181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE) {
1109181111Sdes				quot = state == MA_SQUOTE ? '\'' : '"';
1110181111Sdes				/* Unescape quote we are in */
1111181111Sdes				/* XXX support \n and friends? */
1112181111Sdes				if (arg[i + 1] == quot) {
1113181111Sdes					i++;
1114181111Sdes					argvs[j++] = arg[i];
1115181111Sdes				} else if (arg[i + 1] == '?' ||
1116181111Sdes				    arg[i + 1] == '[' || arg[i + 1] == '*') {
1117181111Sdes					/*
1118181111Sdes					 * Special case for sftp: append
1119181111Sdes					 * double-escaped glob sequence -
1120181111Sdes					 * glob will undo one level of
1121181111Sdes					 * escaping. NB. string can grow here.
1122181111Sdes					 */
1123181111Sdes					if (j >= sizeof(argvs) - 5)
1124181111Sdes						goto args_too_longs;
1125181111Sdes					argvs[j++] = '\\';
1126181111Sdes					argvs[j++] = arg[i++];
1127181111Sdes					argvs[j++] = '\\';
1128181111Sdes					argvs[j++] = arg[i];
1129181111Sdes				} else {
1130181111Sdes					argvs[j++] = arg[i++];
1131181111Sdes					argvs[j++] = arg[i];
1132181111Sdes				}
1133181111Sdes			} else {
1134181111Sdes				if (state == MA_START) {
1135181111Sdes					argv[argc] = argvs + j;
1136181111Sdes					state = MA_UNQUOTED;
1137204917Sdes					if (lastquote != NULL)
1138204917Sdes						*lastquote = '\0';
1139181111Sdes				}
1140181111Sdes				if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1141181111Sdes				    arg[i + 1] == '*' || arg[i + 1] == '\\') {
1142181111Sdes					/*
1143181111Sdes					 * Special case for sftp: append
1144181111Sdes					 * escaped glob sequence -
1145181111Sdes					 * glob will undo one level of
1146181111Sdes					 * escaping.
1147181111Sdes					 */
1148181111Sdes					argvs[j++] = arg[i++];
1149181111Sdes					argvs[j++] = arg[i];
1150181111Sdes				} else {
1151181111Sdes					/* Unescape everything */
1152181111Sdes					/* XXX support \n and friends? */
1153181111Sdes					i++;
1154181111Sdes					argvs[j++] = arg[i];
1155181111Sdes				}
1156181111Sdes			}
1157181111Sdes		} else if (arg[i] == '#') {
1158181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE)
1159181111Sdes				argvs[j++] = arg[i];
1160181111Sdes			else
1161181111Sdes				goto string_done;
1162181111Sdes		} else if (arg[i] == '\0') {
1163181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE) {
1164204917Sdes				if (sloppy) {
1165204917Sdes					state = MA_UNQUOTED;
1166204917Sdes					if (terminated != NULL)
1167204917Sdes						*terminated = 0;
1168204917Sdes					goto string_done;
1169204917Sdes				}
1170181111Sdes				error("Unterminated quoted argument");
1171181111Sdes				return NULL;
1172181111Sdes			}
1173181111Sdes string_done:
1174181111Sdes			if (state == MA_UNQUOTED) {
1175181111Sdes				argvs[j++] = '\0';
1176181111Sdes				argc++;
1177181111Sdes			}
1178181111Sdes			break;
1179181111Sdes		} else {
1180181111Sdes			if (state == MA_START) {
1181181111Sdes				argv[argc] = argvs + j;
1182181111Sdes				state = MA_UNQUOTED;
1183204917Sdes				if (lastquote != NULL)
1184204917Sdes					*lastquote = '\0';
1185181111Sdes			}
1186181111Sdes			if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1187181111Sdes			    (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1188181111Sdes				/*
1189181111Sdes				 * Special case for sftp: escape quoted
1190181111Sdes				 * glob(3) wildcards. NB. string can grow
1191181111Sdes				 * here.
1192181111Sdes				 */
1193181111Sdes				if (j >= sizeof(argvs) - 3)
1194181111Sdes					goto args_too_longs;
1195181111Sdes				argvs[j++] = '\\';
1196181111Sdes				argvs[j++] = arg[i];
1197181111Sdes			} else
1198181111Sdes				argvs[j++] = arg[i];
1199181111Sdes		}
1200181111Sdes		i++;
1201181111Sdes	}
1202181111Sdes	*argcp = argc;
1203181111Sdes	return argv;
1204181111Sdes}
1205181111Sdes
1206181111Sdesstatic int
1207295367Sdesparse_args(const char **cpp, int *ignore_errors, int *aflag,
1208295367Sdes	  int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
1209295367Sdes	  int *rflag, int *sflag,
1210262566Sdes    unsigned long *n_arg, char **path1, char **path2)
1211126274Sdes{
1212126274Sdes	const char *cmd, *cp = *cpp;
1213181111Sdes	char *cp2, **argv;
1214126274Sdes	int base = 0;
1215126274Sdes	long l;
1216181111Sdes	int i, cmdnum, optidx, argc;
1217126274Sdes
1218126274Sdes	/* Skip leading whitespace */
1219126274Sdes	cp = cp + strspn(cp, WHITESPACE);
1220126274Sdes
1221126274Sdes	/* Check for leading '-' (disable error processing) */
1222262566Sdes	*ignore_errors = 0;
1223126274Sdes	if (*cp == '-') {
1224262566Sdes		*ignore_errors = 1;
1225126274Sdes		cp++;
1226204917Sdes		cp = cp + strspn(cp, WHITESPACE);
1227126274Sdes	}
1228126274Sdes
1229204917Sdes	/* Ignore blank lines and lines which begin with comment '#' char */
1230204917Sdes	if (*cp == '\0' || *cp == '#')
1231204917Sdes		return (0);
1232204917Sdes
1233204917Sdes	if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
1234181111Sdes		return -1;
1235181111Sdes
1236126274Sdes	/* Figure out which command we have */
1237181111Sdes	for (i = 0; cmds[i].c != NULL; i++) {
1238248619Sdes		if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
1239126274Sdes			break;
1240126274Sdes	}
1241126274Sdes	cmdnum = cmds[i].n;
1242126274Sdes	cmd = cmds[i].c;
1243126274Sdes
1244126274Sdes	/* Special case */
1245126274Sdes	if (*cp == '!') {
1246126274Sdes		cp++;
1247126274Sdes		cmdnum = I_SHELL;
1248126274Sdes	} else if (cmdnum == -1) {
1249126274Sdes		error("Invalid command.");
1250181111Sdes		return -1;
1251126274Sdes	}
1252126274Sdes
1253126274Sdes	/* Get arguments and parse flags */
1254262566Sdes	*aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1255262566Sdes	*rflag = *sflag = 0;
1256126274Sdes	*path1 = *path2 = NULL;
1257181111Sdes	optidx = 1;
1258126274Sdes	switch (cmdnum) {
1259126274Sdes	case I_GET:
1260255767Sdes	case I_REGET:
1261295367Sdes	case I_REPUT:
1262126274Sdes	case I_PUT:
1263221420Sdes		if ((optidx = parse_getput_flags(cmd, argv, argc,
1264262566Sdes		    aflag, fflag, pflag, rflag)) == -1)
1265181111Sdes			return -1;
1266126274Sdes		/* Get first pathname (mandatory) */
1267181111Sdes		if (argc - optidx < 1) {
1268126274Sdes			error("You must specify at least one path after a "
1269126274Sdes			    "%s command.", cmd);
1270181111Sdes			return -1;
1271126274Sdes		}
1272181111Sdes		*path1 = xstrdup(argv[optidx]);
1273181111Sdes		/* Get second pathname (optional) */
1274181111Sdes		if (argc - optidx > 1) {
1275181111Sdes			*path2 = xstrdup(argv[optidx + 1]);
1276181111Sdes			/* Destination is not globbed */
1277181111Sdes			undo_glob_escape(*path2);
1278181111Sdes		}
1279126274Sdes		break;
1280221420Sdes	case I_LINK:
1281221420Sdes		if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1282221420Sdes			return -1;
1283262566Sdes		goto parse_two_paths;
1284262566Sdes	case I_RENAME:
1285262566Sdes		if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1286262566Sdes			return -1;
1287262566Sdes		goto parse_two_paths;
1288221420Sdes	case I_SYMLINK:
1289262566Sdes		if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1290262566Sdes			return -1;
1291262566Sdes parse_two_paths:
1292181111Sdes		if (argc - optidx < 2) {
1293126274Sdes			error("You must specify two paths after a %s "
1294126274Sdes			    "command.", cmd);
1295181111Sdes			return -1;
1296126274Sdes		}
1297181111Sdes		*path1 = xstrdup(argv[optidx]);
1298181111Sdes		*path2 = xstrdup(argv[optidx + 1]);
1299181111Sdes		/* Paths are not globbed */
1300181111Sdes		undo_glob_escape(*path1);
1301181111Sdes		undo_glob_escape(*path2);
1302126274Sdes		break;
1303126274Sdes	case I_RM:
1304126274Sdes	case I_MKDIR:
1305126274Sdes	case I_RMDIR:
1306126274Sdes	case I_CHDIR:
1307126274Sdes	case I_LCHDIR:
1308126274Sdes	case I_LMKDIR:
1309262566Sdes		if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1310262566Sdes			return -1;
1311126274Sdes		/* Get pathname (mandatory) */
1312181111Sdes		if (argc - optidx < 1) {
1313126274Sdes			error("You must specify a path after a %s command.",
1314126274Sdes			    cmd);
1315181111Sdes			return -1;
1316126274Sdes		}
1317181111Sdes		*path1 = xstrdup(argv[optidx]);
1318181111Sdes		/* Only "rm" globs */
1319181111Sdes		if (cmdnum != I_RM)
1320181111Sdes			undo_glob_escape(*path1);
1321126274Sdes		break;
1322181111Sdes	case I_DF:
1323181111Sdes		if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1324181111Sdes		    iflag)) == -1)
1325181111Sdes			return -1;
1326181111Sdes		/* Default to current directory if no path specified */
1327181111Sdes		if (argc - optidx < 1)
1328181111Sdes			*path1 = NULL;
1329181111Sdes		else {
1330181111Sdes			*path1 = xstrdup(argv[optidx]);
1331181111Sdes			undo_glob_escape(*path1);
1332181111Sdes		}
1333181111Sdes		break;
1334126274Sdes	case I_LS:
1335181111Sdes		if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
1336126274Sdes			return(-1);
1337126274Sdes		/* Path is optional */
1338181111Sdes		if (argc - optidx > 0)
1339181111Sdes			*path1 = xstrdup(argv[optidx]);
1340126274Sdes		break;
1341126274Sdes	case I_LLS:
1342181111Sdes		/* Skip ls command and following whitespace */
1343181111Sdes		cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
1344126274Sdes	case I_SHELL:
1345126274Sdes		/* Uses the rest of the line */
1346126274Sdes		break;
1347126274Sdes	case I_LUMASK:
1348126274Sdes	case I_CHMOD:
1349126274Sdes		base = 8;
1350126274Sdes	case I_CHOWN:
1351126274Sdes	case I_CHGRP:
1352262566Sdes		if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1353262566Sdes			return -1;
1354126274Sdes		/* Get numeric arg (mandatory) */
1355181111Sdes		if (argc - optidx < 1)
1356181111Sdes			goto need_num_arg;
1357164146Sdes		errno = 0;
1358181111Sdes		l = strtol(argv[optidx], &cp2, base);
1359181111Sdes		if (cp2 == argv[optidx] || *cp2 != '\0' ||
1360181111Sdes		    ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1361181111Sdes		    l < 0) {
1362181111Sdes need_num_arg:
1363126274Sdes			error("You must supply a numeric argument "
1364126274Sdes			    "to the %s command.", cmd);
1365181111Sdes			return -1;
1366126274Sdes		}
1367126274Sdes		*n_arg = l;
1368181111Sdes		if (cmdnum == I_LUMASK)
1369126274Sdes			break;
1370126274Sdes		/* Get pathname (mandatory) */
1371181111Sdes		if (argc - optidx < 2) {
1372126274Sdes			error("You must specify a path after a %s command.",
1373126274Sdes			    cmd);
1374181111Sdes			return -1;
1375126274Sdes		}
1376181111Sdes		*path1 = xstrdup(argv[optidx + 1]);
1377126274Sdes		break;
1378126274Sdes	case I_QUIT:
1379126274Sdes	case I_PWD:
1380126274Sdes	case I_LPWD:
1381126274Sdes	case I_HELP:
1382126274Sdes	case I_VERSION:
1383126274Sdes	case I_PROGRESS:
1384262566Sdes		if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1385262566Sdes			return -1;
1386126274Sdes		break;
1387126274Sdes	default:
1388126274Sdes		fatal("Command not implemented");
1389126274Sdes	}
1390126274Sdes
1391126274Sdes	*cpp = cp;
1392126274Sdes	return(cmdnum);
1393126274Sdes}
1394126274Sdes
1395126274Sdesstatic int
1396126274Sdesparse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1397126274Sdes    int err_abort)
1398126274Sdes{
1399126274Sdes	char *path1, *path2, *tmp;
1400295367Sdes	int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0,
1401295367Sdes	iflag = 0;
1402262566Sdes	int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
1403221420Sdes	int cmdnum, i;
1404192595Sdes	unsigned long n_arg = 0;
1405126274Sdes	Attrib a, *aa;
1406295367Sdes	char path_buf[PATH_MAX];
1407126274Sdes	int err = 0;
1408126274Sdes	glob_t g;
1409126274Sdes
1410126274Sdes	path1 = path2 = NULL;
1411262566Sdes	cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1412262566Sdes	    &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1413262566Sdes	if (ignore_errors != 0)
1414126274Sdes		err_abort = 0;
1415126274Sdes
1416126274Sdes	memset(&g, 0, sizeof(g));
1417126274Sdes
1418126274Sdes	/* Perform command */
1419126274Sdes	switch (cmdnum) {
1420126274Sdes	case 0:
1421126274Sdes		/* Blank line */
1422126274Sdes		break;
1423126274Sdes	case -1:
1424126274Sdes		/* Unrecognized command */
1425126274Sdes		err = -1;
1426126274Sdes		break;
1427255767Sdes	case I_REGET:
1428255767Sdes		aflag = 1;
1429255767Sdes		/* FALLTHROUGH */
1430126274Sdes	case I_GET:
1431255767Sdes		err = process_get(conn, path1, path2, *pwd, pflag,
1432262566Sdes		    rflag, aflag, fflag);
1433126274Sdes		break;
1434295367Sdes	case I_REPUT:
1435295367Sdes		aflag = 1;
1436295367Sdes		/* FALLTHROUGH */
1437126274Sdes	case I_PUT:
1438262566Sdes		err = process_put(conn, path1, path2, *pwd, pflag,
1439295367Sdes		    rflag, aflag, fflag);
1440126274Sdes		break;
1441126274Sdes	case I_RENAME:
1442126274Sdes		path1 = make_absolute(path1, *pwd);
1443126274Sdes		path2 = make_absolute(path2, *pwd);
1444262566Sdes		err = do_rename(conn, path1, path2, lflag);
1445126274Sdes		break;
1446126274Sdes	case I_SYMLINK:
1447221420Sdes		sflag = 1;
1448221420Sdes	case I_LINK:
1449262566Sdes		if (!sflag)
1450262566Sdes			path1 = make_absolute(path1, *pwd);
1451126274Sdes		path2 = make_absolute(path2, *pwd);
1452221420Sdes		err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
1453126274Sdes		break;
1454126274Sdes	case I_RM:
1455126274Sdes		path1 = make_absolute(path1, *pwd);
1456126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1457137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1458255767Sdes			if (!quiet)
1459255767Sdes				printf("Removing %s\n", g.gl_pathv[i]);
1460126274Sdes			err = do_rm(conn, g.gl_pathv[i]);
1461126274Sdes			if (err != 0 && err_abort)
1462126274Sdes				break;
1463126274Sdes		}
1464126274Sdes		break;
1465126274Sdes	case I_MKDIR:
1466126274Sdes		path1 = make_absolute(path1, *pwd);
1467126274Sdes		attrib_clear(&a);
1468126274Sdes		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1469126274Sdes		a.perm = 0777;
1470204917Sdes		err = do_mkdir(conn, path1, &a, 1);
1471126274Sdes		break;
1472126274Sdes	case I_RMDIR:
1473126274Sdes		path1 = make_absolute(path1, *pwd);
1474126274Sdes		err = do_rmdir(conn, path1);
1475126274Sdes		break;
1476126274Sdes	case I_CHDIR:
1477126274Sdes		path1 = make_absolute(path1, *pwd);
1478126274Sdes		if ((tmp = do_realpath(conn, path1)) == NULL) {
1479126274Sdes			err = 1;
1480126274Sdes			break;
1481126274Sdes		}
1482126274Sdes		if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1483255767Sdes			free(tmp);
1484126274Sdes			err = 1;
1485126274Sdes			break;
1486126274Sdes		}
1487126274Sdes		if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1488126274Sdes			error("Can't change directory: Can't check target");
1489255767Sdes			free(tmp);
1490126274Sdes			err = 1;
1491126274Sdes			break;
1492126274Sdes		}
1493126274Sdes		if (!S_ISDIR(aa->perm)) {
1494126274Sdes			error("Can't change directory: \"%s\" is not "
1495126274Sdes			    "a directory", tmp);
1496255767Sdes			free(tmp);
1497126274Sdes			err = 1;
1498126274Sdes			break;
1499126274Sdes		}
1500255767Sdes		free(*pwd);
1501126274Sdes		*pwd = tmp;
1502126274Sdes		break;
1503126274Sdes	case I_LS:
1504126274Sdes		if (!path1) {
1505215116Sdes			do_ls_dir(conn, *pwd, *pwd, lflag);
1506126274Sdes			break;
1507126274Sdes		}
1508126274Sdes
1509126274Sdes		/* Strip pwd off beginning of non-absolute paths */
1510126274Sdes		tmp = NULL;
1511126274Sdes		if (*path1 != '/')
1512126274Sdes			tmp = *pwd;
1513126274Sdes
1514126274Sdes		path1 = make_absolute(path1, *pwd);
1515126274Sdes		err = do_globbed_ls(conn, path1, tmp, lflag);
1516126274Sdes		break;
1517181111Sdes	case I_DF:
1518181111Sdes		/* Default to current directory if no path specified */
1519181111Sdes		if (path1 == NULL)
1520181111Sdes			path1 = xstrdup(*pwd);
1521181111Sdes		path1 = make_absolute(path1, *pwd);
1522181111Sdes		err = do_df(conn, path1, hflag, iflag);
1523181111Sdes		break;
1524126274Sdes	case I_LCHDIR:
1525295367Sdes		tmp = tilde_expand_filename(path1, getuid());
1526295367Sdes		free(path1);
1527295367Sdes		path1 = tmp;
1528126274Sdes		if (chdir(path1) == -1) {
1529126274Sdes			error("Couldn't change local directory to "
1530126274Sdes			    "\"%s\": %s", path1, strerror(errno));
1531126274Sdes			err = 1;
1532126274Sdes		}
1533126274Sdes		break;
1534126274Sdes	case I_LMKDIR:
1535126274Sdes		if (mkdir(path1, 0777) == -1) {
1536126274Sdes			error("Couldn't create local directory "
1537126274Sdes			    "\"%s\": %s", path1, strerror(errno));
1538126274Sdes			err = 1;
1539126274Sdes		}
1540126274Sdes		break;
1541126274Sdes	case I_LLS:
1542126274Sdes		local_do_ls(cmd);
1543126274Sdes		break;
1544126274Sdes	case I_SHELL:
1545126274Sdes		local_do_shell(cmd);
1546126274Sdes		break;
1547126274Sdes	case I_LUMASK:
1548126274Sdes		umask(n_arg);
1549126274Sdes		printf("Local umask: %03lo\n", n_arg);
1550126274Sdes		break;
1551126274Sdes	case I_CHMOD:
1552126274Sdes		path1 = make_absolute(path1, *pwd);
1553126274Sdes		attrib_clear(&a);
1554126274Sdes		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1555126274Sdes		a.perm = n_arg;
1556126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1557137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1558255767Sdes			if (!quiet)
1559255767Sdes				printf("Changing mode on %s\n", g.gl_pathv[i]);
1560126274Sdes			err = do_setstat(conn, g.gl_pathv[i], &a);
1561126274Sdes			if (err != 0 && err_abort)
1562126274Sdes				break;
1563126274Sdes		}
1564126274Sdes		break;
1565126274Sdes	case I_CHOWN:
1566126274Sdes	case I_CHGRP:
1567126274Sdes		path1 = make_absolute(path1, *pwd);
1568126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1569137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1570126274Sdes			if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
1571192595Sdes				if (err_abort) {
1572192595Sdes					err = -1;
1573126274Sdes					break;
1574192595Sdes				} else
1575126274Sdes					continue;
1576126274Sdes			}
1577126274Sdes			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1578126274Sdes				error("Can't get current ownership of "
1579126274Sdes				    "remote file \"%s\"", g.gl_pathv[i]);
1580192595Sdes				if (err_abort) {
1581192595Sdes					err = -1;
1582126274Sdes					break;
1583192595Sdes				} else
1584126274Sdes					continue;
1585126274Sdes			}
1586126274Sdes			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1587126274Sdes			if (cmdnum == I_CHOWN) {
1588255767Sdes				if (!quiet)
1589255767Sdes					printf("Changing owner on %s\n",
1590255767Sdes					    g.gl_pathv[i]);
1591126274Sdes				aa->uid = n_arg;
1592126274Sdes			} else {
1593255767Sdes				if (!quiet)
1594255767Sdes					printf("Changing group on %s\n",
1595255767Sdes					    g.gl_pathv[i]);
1596126274Sdes				aa->gid = n_arg;
1597126274Sdes			}
1598126274Sdes			err = do_setstat(conn, g.gl_pathv[i], aa);
1599126274Sdes			if (err != 0 && err_abort)
1600126274Sdes				break;
1601126274Sdes		}
1602126274Sdes		break;
1603126274Sdes	case I_PWD:
1604126274Sdes		printf("Remote working directory: %s\n", *pwd);
1605126274Sdes		break;
1606126274Sdes	case I_LPWD:
1607126274Sdes		if (!getcwd(path_buf, sizeof(path_buf))) {
1608126274Sdes			error("Couldn't get local cwd: %s", strerror(errno));
1609126274Sdes			err = -1;
1610126274Sdes			break;
1611126274Sdes		}
1612126274Sdes		printf("Local working directory: %s\n", path_buf);
1613126274Sdes		break;
1614126274Sdes	case I_QUIT:
1615126274Sdes		/* Processed below */
1616126274Sdes		break;
1617126274Sdes	case I_HELP:
1618126274Sdes		help();
1619126274Sdes		break;
1620126274Sdes	case I_VERSION:
1621126274Sdes		printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1622126274Sdes		break;
1623126274Sdes	case I_PROGRESS:
1624126274Sdes		showprogress = !showprogress;
1625126274Sdes		if (showprogress)
1626126274Sdes			printf("Progress meter enabled\n");
1627126274Sdes		else
1628126274Sdes			printf("Progress meter disabled\n");
1629126274Sdes		break;
1630126274Sdes	default:
1631126274Sdes		fatal("%d is not implemented", cmdnum);
1632126274Sdes	}
1633126274Sdes
1634126274Sdes	if (g.gl_pathc)
1635126274Sdes		globfree(&g);
1636255767Sdes	free(path1);
1637255767Sdes	free(path2);
1638126274Sdes
1639126274Sdes	/* If an unignored error occurs in batch mode we should abort. */
1640126274Sdes	if (err_abort && err != 0)
1641126274Sdes		return (-1);
1642126274Sdes	else if (cmdnum == I_QUIT)
1643126274Sdes		return (1);
1644126274Sdes
1645126274Sdes	return (0);
1646126274Sdes}
1647126274Sdes
1648146998Sdes#ifdef USE_LIBEDIT
1649146998Sdesstatic char *
1650146998Sdesprompt(EditLine *el)
1651146998Sdes{
1652146998Sdes	return ("sftp> ");
1653146998Sdes}
1654146998Sdes
1655204917Sdes/* Display entries in 'list' after skipping the first 'len' chars */
1656204917Sdesstatic void
1657204917Sdescomplete_display(char **list, u_int len)
1658204917Sdes{
1659204917Sdes	u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1660204917Sdes	struct winsize ws;
1661204917Sdes	char *tmp;
1662204917Sdes
1663204917Sdes	/* Count entries for sort and find longest */
1664262566Sdes	for (y = 0; list[y]; y++)
1665204917Sdes		m = MAX(m, strlen(list[y]));
1666204917Sdes
1667204917Sdes	if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1668204917Sdes		width = ws.ws_col;
1669204917Sdes
1670204917Sdes	m = m > len ? m - len : 0;
1671204917Sdes	columns = width / (m + 2);
1672204917Sdes	columns = MAX(columns, 1);
1673204917Sdes	colspace = width / columns;
1674204917Sdes	colspace = MIN(colspace, width);
1675204917Sdes
1676204917Sdes	printf("\n");
1677204917Sdes	m = 1;
1678204917Sdes	for (y = 0; list[y]; y++) {
1679204917Sdes		llen = strlen(list[y]);
1680204917Sdes		tmp = llen > len ? list[y] + len : "";
1681204917Sdes		printf("%-*s", colspace, tmp);
1682204917Sdes		if (m >= columns) {
1683204917Sdes			printf("\n");
1684204917Sdes			m = 1;
1685204917Sdes		} else
1686204917Sdes			m++;
1687204917Sdes	}
1688204917Sdes	printf("\n");
1689204917Sdes}
1690204917Sdes
1691204917Sdes/*
1692204917Sdes * Given a "list" of words that begin with a common prefix of "word",
1693204917Sdes * attempt to find an autocompletion to extends "word" by the next
1694204917Sdes * characters common to all entries in "list".
1695204917Sdes */
1696204917Sdesstatic char *
1697204917Sdescomplete_ambiguous(const char *word, char **list, size_t count)
1698204917Sdes{
1699204917Sdes	if (word == NULL)
1700204917Sdes		return NULL;
1701204917Sdes
1702204917Sdes	if (count > 0) {
1703204917Sdes		u_int y, matchlen = strlen(list[0]);
1704204917Sdes
1705204917Sdes		/* Find length of common stem */
1706204917Sdes		for (y = 1; list[y]; y++) {
1707204917Sdes			u_int x;
1708204917Sdes
1709262566Sdes			for (x = 0; x < matchlen; x++)
1710262566Sdes				if (list[0][x] != list[y][x])
1711204917Sdes					break;
1712204917Sdes
1713204917Sdes			matchlen = x;
1714204917Sdes		}
1715204917Sdes
1716204917Sdes		if (matchlen > strlen(word)) {
1717204917Sdes			char *tmp = xstrdup(list[0]);
1718204917Sdes
1719204917Sdes			tmp[matchlen] = '\0';
1720204917Sdes			return tmp;
1721204917Sdes		}
1722262566Sdes	}
1723204917Sdes
1724204917Sdes	return xstrdup(word);
1725204917Sdes}
1726204917Sdes
1727204917Sdes/* Autocomplete a sftp command */
1728204917Sdesstatic int
1729204917Sdescomplete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1730204917Sdes    int terminated)
1731204917Sdes{
1732204917Sdes	u_int y, count = 0, cmdlen, tmplen;
1733204917Sdes	char *tmp, **list, argterm[3];
1734204917Sdes	const LineInfo *lf;
1735204917Sdes
1736204917Sdes	list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1737204917Sdes
1738204917Sdes	/* No command specified: display all available commands */
1739204917Sdes	if (cmd == NULL) {
1740204917Sdes		for (y = 0; cmds[y].c; y++)
1741204917Sdes			list[count++] = xstrdup(cmds[y].c);
1742262566Sdes
1743204917Sdes		list[count] = NULL;
1744204917Sdes		complete_display(list, 0);
1745204917Sdes
1746262566Sdes		for (y = 0; list[y] != NULL; y++)
1747262566Sdes			free(list[y]);
1748255767Sdes		free(list);
1749204917Sdes		return count;
1750204917Sdes	}
1751204917Sdes
1752204917Sdes	/* Prepare subset of commands that start with "cmd" */
1753204917Sdes	cmdlen = strlen(cmd);
1754204917Sdes	for (y = 0; cmds[y].c; y++)  {
1755262566Sdes		if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1756204917Sdes			list[count++] = xstrdup(cmds[y].c);
1757204917Sdes	}
1758204917Sdes	list[count] = NULL;
1759204917Sdes
1760240075Sdes	if (count == 0) {
1761255767Sdes		free(list);
1762204917Sdes		return 0;
1763240075Sdes	}
1764204917Sdes
1765204917Sdes	/* Complete ambigious command */
1766204917Sdes	tmp = complete_ambiguous(cmd, list, count);
1767204917Sdes	if (count > 1)
1768204917Sdes		complete_display(list, 0);
1769204917Sdes
1770262566Sdes	for (y = 0; list[y]; y++)
1771262566Sdes		free(list[y]);
1772255767Sdes	free(list);
1773204917Sdes
1774204917Sdes	if (tmp != NULL) {
1775204917Sdes		tmplen = strlen(tmp);
1776204917Sdes		cmdlen = strlen(cmd);
1777204917Sdes		/* If cmd may be extended then do so */
1778204917Sdes		if (tmplen > cmdlen)
1779204917Sdes			if (el_insertstr(el, tmp + cmdlen) == -1)
1780204917Sdes				fatal("el_insertstr failed.");
1781204917Sdes		lf = el_line(el);
1782204917Sdes		/* Terminate argument cleanly */
1783204917Sdes		if (count == 1) {
1784204917Sdes			y = 0;
1785204917Sdes			if (!terminated)
1786204917Sdes				argterm[y++] = quote;
1787204917Sdes			if (lastarg || *(lf->cursor) != ' ')
1788204917Sdes				argterm[y++] = ' ';
1789204917Sdes			argterm[y] = '\0';
1790204917Sdes			if (y > 0 && el_insertstr(el, argterm) == -1)
1791204917Sdes				fatal("el_insertstr failed.");
1792204917Sdes		}
1793255767Sdes		free(tmp);
1794204917Sdes	}
1795204917Sdes
1796204917Sdes	return count;
1797204917Sdes}
1798204917Sdes
1799204917Sdes/*
1800204917Sdes * Determine whether a particular sftp command's arguments (if any)
1801204917Sdes * represent local or remote files.
1802204917Sdes */
1803204917Sdesstatic int
1804204917Sdescomplete_is_remote(char *cmd) {
1805204917Sdes	int i;
1806204917Sdes
1807204917Sdes	if (cmd == NULL)
1808204917Sdes		return -1;
1809204917Sdes
1810204917Sdes	for (i = 0; cmds[i].c; i++) {
1811262566Sdes		if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1812204917Sdes			return cmds[i].t;
1813204917Sdes	}
1814204917Sdes
1815204917Sdes	return -1;
1816204917Sdes}
1817204917Sdes
1818204917Sdes/* Autocomplete a filename "file" */
1819204917Sdesstatic int
1820204917Sdescomplete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1821204917Sdes    char *file, int remote, int lastarg, char quote, int terminated)
1822204917Sdes{
1823204917Sdes	glob_t g;
1824255767Sdes	char *tmp, *tmp2, ins[8];
1825248619Sdes	u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
1826255767Sdes	int clen;
1827204917Sdes	const LineInfo *lf;
1828262566Sdes
1829204917Sdes	/* Glob from "file" location */
1830204917Sdes	if (file == NULL)
1831204917Sdes		tmp = xstrdup("*");
1832204917Sdes	else
1833204917Sdes		xasprintf(&tmp, "%s*", file);
1834204917Sdes
1835248619Sdes	/* Check if the path is absolute. */
1836248619Sdes	isabs = tmp[0] == '/';
1837248619Sdes
1838204917Sdes	memset(&g, 0, sizeof(g));
1839204917Sdes	if (remote != LOCAL) {
1840204917Sdes		tmp = make_absolute(tmp, remote_path);
1841204917Sdes		remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1842262566Sdes	} else
1843204917Sdes		glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1844262566Sdes
1845204917Sdes	/* Determine length of pwd so we can trim completion display */
1846204917Sdes	for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1847204917Sdes		/* Terminate counting on first unescaped glob metacharacter */
1848204917Sdes		if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1849204917Sdes			if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1850204917Sdes				hadglob = 1;
1851204917Sdes			break;
1852204917Sdes		}
1853204917Sdes		if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1854204917Sdes			tmplen++;
1855204917Sdes		if (tmp[tmplen] == '/')
1856204917Sdes			pwdlen = tmplen + 1;	/* track last seen '/' */
1857204917Sdes	}
1858255767Sdes	free(tmp);
1859295367Sdes	tmp = NULL;
1860204917Sdes
1861262566Sdes	if (g.gl_matchc == 0)
1862204917Sdes		goto out;
1863204917Sdes
1864204917Sdes	if (g.gl_matchc > 1)
1865204917Sdes		complete_display(g.gl_pathv, pwdlen);
1866204917Sdes
1867204917Sdes	/* Don't try to extend globs */
1868204917Sdes	if (file == NULL || hadglob)
1869204917Sdes		goto out;
1870204917Sdes
1871204917Sdes	tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1872248619Sdes	tmp = path_strip(tmp2, isabs ? NULL : remote_path);
1873255767Sdes	free(tmp2);
1874204917Sdes
1875204917Sdes	if (tmp == NULL)
1876204917Sdes		goto out;
1877204917Sdes
1878204917Sdes	tmplen = strlen(tmp);
1879204917Sdes	filelen = strlen(file);
1880204917Sdes
1881248619Sdes	/* Count the number of escaped characters in the input string. */
1882248619Sdes	cesc = isesc = 0;
1883248619Sdes	for (i = 0; i < filelen; i++) {
1884248619Sdes		if (!isesc && file[i] == '\\' && i + 1 < filelen){
1885248619Sdes			isesc = 1;
1886248619Sdes			cesc++;
1887248619Sdes		} else
1888248619Sdes			isesc = 0;
1889248619Sdes	}
1890248619Sdes
1891248619Sdes	if (tmplen > (filelen - cesc)) {
1892248619Sdes		tmp2 = tmp + filelen - cesc;
1893262566Sdes		len = strlen(tmp2);
1894204917Sdes		/* quote argument on way out */
1895255767Sdes		for (i = 0; i < len; i += clen) {
1896255767Sdes			if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1897255767Sdes			    (size_t)clen > sizeof(ins) - 2)
1898255767Sdes				fatal("invalid multibyte character");
1899204917Sdes			ins[0] = '\\';
1900255767Sdes			memcpy(ins + 1, tmp2 + i, clen);
1901255767Sdes			ins[clen + 1] = '\0';
1902204917Sdes			switch (tmp2[i]) {
1903204917Sdes			case '\'':
1904204917Sdes			case '"':
1905204917Sdes			case '\\':
1906204917Sdes			case '\t':
1907221420Sdes			case '[':
1908204917Sdes			case ' ':
1909248619Sdes			case '#':
1910248619Sdes			case '*':
1911204917Sdes				if (quote == '\0' || tmp2[i] == quote) {
1912204917Sdes					if (el_insertstr(el, ins) == -1)
1913204917Sdes						fatal("el_insertstr "
1914204917Sdes						    "failed.");
1915204917Sdes					break;
1916204917Sdes				}
1917204917Sdes				/* FALLTHROUGH */
1918204917Sdes			default:
1919204917Sdes				if (el_insertstr(el, ins + 1) == -1)
1920204917Sdes					fatal("el_insertstr failed.");
1921204917Sdes				break;
1922204917Sdes			}
1923204917Sdes		}
1924204917Sdes	}
1925204917Sdes
1926204917Sdes	lf = el_line(el);
1927204917Sdes	if (g.gl_matchc == 1) {
1928204917Sdes		i = 0;
1929295367Sdes		if (!terminated && quote != '\0')
1930204917Sdes			ins[i++] = quote;
1931204917Sdes		if (*(lf->cursor - 1) != '/' &&
1932204917Sdes		    (lastarg || *(lf->cursor) != ' '))
1933204917Sdes			ins[i++] = ' ';
1934204917Sdes		ins[i] = '\0';
1935204917Sdes		if (i > 0 && el_insertstr(el, ins) == -1)
1936204917Sdes			fatal("el_insertstr failed.");
1937204917Sdes	}
1938255767Sdes	free(tmp);
1939204917Sdes
1940204917Sdes out:
1941204917Sdes	globfree(&g);
1942204917Sdes	return g.gl_matchc;
1943204917Sdes}
1944204917Sdes
1945204917Sdes/* tab-completion hook function, called via libedit */
1946204917Sdesstatic unsigned char
1947204917Sdescomplete(EditLine *el, int ch)
1948204917Sdes{
1949262566Sdes	char **argv, *line, quote;
1950255767Sdes	int argc, carg;
1951255767Sdes	u_int cursor, len, terminated, ret = CC_ERROR;
1952204917Sdes	const LineInfo *lf;
1953204917Sdes	struct complete_ctx *complete_ctx;
1954204917Sdes
1955204917Sdes	lf = el_line(el);
1956204917Sdes	if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1957204917Sdes		fatal("%s: el_get failed", __func__);
1958204917Sdes
1959204917Sdes	/* Figure out which argument the cursor points to */
1960204917Sdes	cursor = lf->cursor - lf->buffer;
1961295367Sdes	line = xmalloc(cursor + 1);
1962204917Sdes	memcpy(line, lf->buffer, cursor);
1963204917Sdes	line[cursor] = '\0';
1964204917Sdes	argv = makeargv(line, &carg, 1, &quote, &terminated);
1965255767Sdes	free(line);
1966204917Sdes
1967204917Sdes	/* Get all the arguments on the line */
1968204917Sdes	len = lf->lastchar - lf->buffer;
1969295367Sdes	line = xmalloc(len + 1);
1970204917Sdes	memcpy(line, lf->buffer, len);
1971204917Sdes	line[len] = '\0';
1972204917Sdes	argv = makeargv(line, &argc, 1, NULL, NULL);
1973204917Sdes
1974204917Sdes	/* Ensure cursor is at EOL or a argument boundary */
1975204917Sdes	if (line[cursor] != ' ' && line[cursor] != '\0' &&
1976204917Sdes	    line[cursor] != '\n') {
1977255767Sdes		free(line);
1978204917Sdes		return ret;
1979204917Sdes	}
1980204917Sdes
1981204917Sdes	if (carg == 0) {
1982204917Sdes		/* Show all available commands */
1983204917Sdes		complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1984204917Sdes		ret = CC_REDISPLAY;
1985204917Sdes	} else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ')  {
1986204917Sdes		/* Handle the command parsing */
1987204917Sdes		if (complete_cmd_parse(el, argv[0], argc == carg,
1988262566Sdes		    quote, terminated) != 0)
1989204917Sdes			ret = CC_REDISPLAY;
1990204917Sdes	} else if (carg >= 1) {
1991204917Sdes		/* Handle file parsing */
1992204917Sdes		int remote = complete_is_remote(argv[0]);
1993204917Sdes		char *filematch = NULL;
1994204917Sdes
1995204917Sdes		if (carg > 1 && line[cursor-1] != ' ')
1996204917Sdes			filematch = argv[carg - 1];
1997204917Sdes
1998204917Sdes		if (remote != 0 &&
1999204917Sdes		    complete_match(el, complete_ctx->conn,
2000204917Sdes		    *complete_ctx->remote_pathp, filematch,
2001262566Sdes		    remote, carg == argc, quote, terminated) != 0)
2002204917Sdes			ret = CC_REDISPLAY;
2003204917Sdes	}
2004204917Sdes
2005262566Sdes	free(line);
2006204917Sdes	return ret;
2007204917Sdes}
2008204917Sdes#endif /* USE_LIBEDIT */
2009204917Sdes
2010126274Sdesint
2011204917Sdesinteractive_loop(struct sftp_conn *conn, char *file1, char *file2)
2012126274Sdes{
2013204917Sdes	char *remote_path;
2014126274Sdes	char *dir = NULL;
2015126274Sdes	char cmd[2048];
2016149749Sdes	int err, interactive;
2017146998Sdes	EditLine *el = NULL;
2018146998Sdes#ifdef USE_LIBEDIT
2019146998Sdes	History *hl = NULL;
2020146998Sdes	HistEvent hev;
2021146998Sdes	extern char *__progname;
2022204917Sdes	struct complete_ctx complete_ctx;
2023126274Sdes
2024146998Sdes	if (!batchmode && isatty(STDIN_FILENO)) {
2025146998Sdes		if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2026146998Sdes			fatal("Couldn't initialise editline");
2027146998Sdes		if ((hl = history_init()) == NULL)
2028146998Sdes			fatal("Couldn't initialise editline history");
2029146998Sdes		history(hl, &hev, H_SETSIZE, 100);
2030146998Sdes		el_set(el, EL_HIST, history, hl);
2031146998Sdes
2032146998Sdes		el_set(el, EL_PROMPT, prompt);
2033146998Sdes		el_set(el, EL_EDITOR, "emacs");
2034146998Sdes		el_set(el, EL_TERMINAL, NULL);
2035146998Sdes		el_set(el, EL_SIGNAL, 1);
2036146998Sdes		el_source(el, NULL);
2037204917Sdes
2038204917Sdes		/* Tab Completion */
2039262566Sdes		el_set(el, EL_ADDFN, "ftp-complete",
2040221420Sdes		    "Context sensitive argument completion", complete);
2041204917Sdes		complete_ctx.conn = conn;
2042204917Sdes		complete_ctx.remote_pathp = &remote_path;
2043204917Sdes		el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2044204917Sdes		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
2045262566Sdes		/* enable ctrl-left-arrow and ctrl-right-arrow */
2046262566Sdes		el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2047262566Sdes		el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2048262566Sdes		el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2049262566Sdes		el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
2050262566Sdes		/* make ^w match ksh behaviour */
2051262566Sdes		el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
2052146998Sdes	}
2053146998Sdes#endif /* USE_LIBEDIT */
2054146998Sdes
2055204917Sdes	remote_path = do_realpath(conn, ".");
2056204917Sdes	if (remote_path == NULL)
2057126274Sdes		fatal("Need cwd");
2058126274Sdes
2059126274Sdes	if (file1 != NULL) {
2060126274Sdes		dir = xstrdup(file1);
2061204917Sdes		dir = make_absolute(dir, remote_path);
2062126274Sdes
2063126274Sdes		if (remote_is_dir(conn, dir) && file2 == NULL) {
2064255767Sdes			if (!quiet)
2065255767Sdes				printf("Changing to: %s\n", dir);
2066126274Sdes			snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
2067204917Sdes			if (parse_dispatch_command(conn, cmd,
2068204917Sdes			    &remote_path, 1) != 0) {
2069255767Sdes				free(dir);
2070255767Sdes				free(remote_path);
2071255767Sdes				free(conn);
2072126274Sdes				return (-1);
2073146998Sdes			}
2074126274Sdes		} else {
2075248619Sdes			/* XXX this is wrong wrt quoting */
2076255767Sdes			snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2077255767Sdes			    global_aflag ? " -a" : "", dir,
2078255767Sdes			    file2 == NULL ? "" : " ",
2079255767Sdes			    file2 == NULL ? "" : file2);
2080204917Sdes			err = parse_dispatch_command(conn, cmd,
2081204917Sdes			    &remote_path, 1);
2082255767Sdes			free(dir);
2083255767Sdes			free(remote_path);
2084255767Sdes			free(conn);
2085126274Sdes			return (err);
2086126274Sdes		}
2087255767Sdes		free(dir);
2088126274Sdes	}
2089126274Sdes
2090295367Sdes	setvbuf(stdout, NULL, _IOLBF, 0);
2091295367Sdes	setvbuf(infile, NULL, _IOLBF, 0);
2092126274Sdes
2093149749Sdes	interactive = !batchmode && isatty(STDIN_FILENO);
2094126274Sdes	err = 0;
2095126274Sdes	for (;;) {
2096126274Sdes		char *cp;
2097126274Sdes
2098137015Sdes		signal(SIGINT, SIG_IGN);
2099137015Sdes
2100146998Sdes		if (el == NULL) {
2101149749Sdes			if (interactive)
2102149749Sdes				printf("sftp> ");
2103146998Sdes			if (fgets(cmd, sizeof(cmd), infile) == NULL) {
2104149749Sdes				if (interactive)
2105149749Sdes					printf("\n");
2106146998Sdes				break;
2107146998Sdes			}
2108149749Sdes			if (!interactive) { /* Echo command */
2109149749Sdes				printf("sftp> %s", cmd);
2110149749Sdes				if (strlen(cmd) > 0 &&
2111149749Sdes				    cmd[strlen(cmd) - 1] != '\n')
2112149749Sdes					printf("\n");
2113149749Sdes			}
2114146998Sdes		} else {
2115146998Sdes#ifdef USE_LIBEDIT
2116146998Sdes			const char *line;
2117146998Sdes			int count = 0;
2118126274Sdes
2119204917Sdes			if ((line = el_gets(el, &count)) == NULL ||
2120204917Sdes			    count <= 0) {
2121149749Sdes				printf("\n");
2122149749Sdes 				break;
2123149749Sdes			}
2124146998Sdes			history(hl, &hev, H_ENTER, line);
2125146998Sdes			if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2126146998Sdes				fprintf(stderr, "Error: input line too long\n");
2127146998Sdes				continue;
2128146998Sdes			}
2129146998Sdes#endif /* USE_LIBEDIT */
2130126274Sdes		}
2131126274Sdes
2132126274Sdes		cp = strrchr(cmd, '\n');
2133126274Sdes		if (cp)
2134126274Sdes			*cp = '\0';
2135126274Sdes
2136137015Sdes		/* Handle user interrupts gracefully during commands */
2137137015Sdes		interrupted = 0;
2138137015Sdes		signal(SIGINT, cmd_interrupt);
2139137015Sdes
2140204917Sdes		err = parse_dispatch_command(conn, cmd, &remote_path,
2141204917Sdes		    batchmode);
2142126274Sdes		if (err != 0)
2143126274Sdes			break;
2144126274Sdes	}
2145255767Sdes	free(remote_path);
2146255767Sdes	free(conn);
2147126274Sdes
2148149749Sdes#ifdef USE_LIBEDIT
2149149749Sdes	if (el != NULL)
2150149749Sdes		el_end(el);
2151149749Sdes#endif /* USE_LIBEDIT */
2152149749Sdes
2153126274Sdes	/* err == 1 signifies normal "quit" exit */
2154126274Sdes	return (err >= 0 ? 0 : -1);
2155126274Sdes}
2156126274Sdes
2157126274Sdesstatic void
2158124208Sdesconnect_to_server(char *path, char **args, int *in, int *out)
2159124208Sdes{
216076259Sgreen	int c_in, c_out;
216199060Sdes
216276259Sgreen#ifdef USE_PIPES
216376259Sgreen	int pin[2], pout[2];
216499060Sdes
216576259Sgreen	if ((pipe(pin) == -1) || (pipe(pout) == -1))
216676259Sgreen		fatal("pipe: %s", strerror(errno));
216776259Sgreen	*in = pin[0];
216876259Sgreen	*out = pout[1];
216976259Sgreen	c_in = pout[0];
217076259Sgreen	c_out = pin[1];
217176259Sgreen#else /* USE_PIPES */
217276259Sgreen	int inout[2];
217399060Sdes
217476259Sgreen	if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
217576259Sgreen		fatal("socketpair: %s", strerror(errno));
217676259Sgreen	*in = *out = inout[0];
217776259Sgreen	c_in = c_out = inout[1];
217876259Sgreen#endif /* USE_PIPES */
217976259Sgreen
2180124208Sdes	if ((sshpid = fork()) == -1)
218176259Sgreen		fatal("fork: %s", strerror(errno));
2182124208Sdes	else if (sshpid == 0) {
218376259Sgreen		if ((dup2(c_in, STDIN_FILENO) == -1) ||
218476259Sgreen		    (dup2(c_out, STDOUT_FILENO) == -1)) {
218576259Sgreen			fprintf(stderr, "dup2: %s\n", strerror(errno));
2186137015Sdes			_exit(1);
218776259Sgreen		}
218876259Sgreen		close(*in);
218976259Sgreen		close(*out);
219076259Sgreen		close(c_in);
219176259Sgreen		close(c_out);
2192137015Sdes
2193137015Sdes		/*
2194137015Sdes		 * The underlying ssh is in the same process group, so we must
2195137015Sdes		 * ignore SIGINT if we want to gracefully abort commands,
2196137015Sdes		 * otherwise the signal will make it to the ssh process and
2197204917Sdes		 * kill it too.  Contrawise, since sftp sends SIGTERMs to the
2198204917Sdes		 * underlying ssh, it must *not* ignore that signal.
2199137015Sdes		 */
2200137015Sdes		signal(SIGINT, SIG_IGN);
2201204917Sdes		signal(SIGTERM, SIG_DFL);
2202137015Sdes		execvp(path, args);
220392555Sdes		fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
2204137015Sdes		_exit(1);
220576259Sgreen	}
220676259Sgreen
2207124208Sdes	signal(SIGTERM, killchild);
2208124208Sdes	signal(SIGINT, killchild);
2209124208Sdes	signal(SIGHUP, killchild);
221076259Sgreen	close(c_in);
221176259Sgreen	close(c_out);
221276259Sgreen}
221376259Sgreen
221492555Sdesstatic void
221576259Sgreenusage(void)
221676259Sgreen{
221792555Sdes	extern char *__progname;
221898675Sdes
221992555Sdes	fprintf(stderr,
2220262566Sdes	    "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
2221204917Sdes	    "          [-D sftp_server_path] [-F ssh_config] "
2222221420Sdes	    "[-i identity_file] [-l limit]\n"
2223204917Sdes	    "          [-o ssh_option] [-P port] [-R num_requests] "
2224204917Sdes	    "[-S program]\n"
2225204917Sdes	    "          [-s subsystem | sftp_server] host\n"
2226192595Sdes	    "       %s [user@]host[:file ...]\n"
2227192595Sdes	    "       %s [user@]host[:dir[/]]\n"
2228204917Sdes	    "       %s -b batchfile [user@]host\n",
2229204917Sdes	    __progname, __progname, __progname, __progname);
223076259Sgreen	exit(1);
223176259Sgreen}
223276259Sgreen
223376259Sgreenint
223476259Sgreenmain(int argc, char **argv)
223576259Sgreen{
2236113908Sdes	int in, out, ch, err;
2237204917Sdes	char *host = NULL, *userhost, *cp, *file2 = NULL;
223892555Sdes	int debug_level = 0, sshver = 2;
223992555Sdes	char *file1 = NULL, *sftp_server = NULL;
224092555Sdes	char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
2241221420Sdes	const char *errstr;
224292555Sdes	LogLevel ll = SYSLOG_LEVEL_INFO;
224392555Sdes	arglist args;
224476259Sgreen	extern int optind;
224576259Sgreen	extern char *optarg;
2246204917Sdes	struct sftp_conn *conn;
2247204917Sdes	size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2248204917Sdes	size_t num_requests = DEFAULT_NUM_REQUESTS;
2249221420Sdes	long long limit_kbps = 0;
225076259Sgreen
2251296853Sdes	ssh_malloc_init();	/* must be called before any mallocs */
2252157016Sdes	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2253157016Sdes	sanitise_stdfd();
2254255767Sdes	setlocale(LC_CTYPE, "");
2255157016Sdes
2256124208Sdes	__progname = ssh_get_progname(argv[0]);
2257157016Sdes	memset(&args, '\0', sizeof(args));
225892555Sdes	args.list = NULL;
2259162852Sdes	addargs(&args, "%s", ssh_program);
226092555Sdes	addargs(&args, "-oForwardX11 no");
226192555Sdes	addargs(&args, "-oForwardAgent no");
2262157016Sdes	addargs(&args, "-oPermitLocalCommand no");
226392555Sdes	addargs(&args, "-oClearAllForwardings yes");
2264126274Sdes
226592555Sdes	ll = SYSLOG_LEVEL_INFO;
2266126274Sdes	infile = stdin;
226776259Sgreen
2268204917Sdes	while ((ch = getopt(argc, argv,
2269262566Sdes	    "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
227076259Sgreen		switch (ch) {
2271204917Sdes		/* Passed through to ssh(1) */
2272204917Sdes		case '4':
2273204917Sdes		case '6':
227476259Sgreen		case 'C':
2275204917Sdes			addargs(&args, "-%c", ch);
227676259Sgreen			break;
2277204917Sdes		/* Passed through to ssh(1) with argument */
2278204917Sdes		case 'F':
2279204917Sdes		case 'c':
2280204917Sdes		case 'i':
2281204917Sdes		case 'o':
2282204917Sdes			addargs(&args, "-%c", ch);
2283204917Sdes			addargs(&args, "%s", optarg);
2284204917Sdes			break;
2285204917Sdes		case 'q':
2286255767Sdes			ll = SYSLOG_LEVEL_ERROR;
2287255767Sdes			quiet = 1;
2288204917Sdes			showprogress = 0;
2289204917Sdes			addargs(&args, "-%c", ch);
2290204917Sdes			break;
2291204917Sdes		case 'P':
2292204917Sdes			addargs(&args, "-oPort %s", optarg);
2293204917Sdes			break;
229476259Sgreen		case 'v':
229592555Sdes			if (debug_level < 3) {
229692555Sdes				addargs(&args, "-v");
229792555Sdes				ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
229892555Sdes			}
229992555Sdes			debug_level++;
230076259Sgreen			break;
230176259Sgreen		case '1':
230292555Sdes			sshver = 1;
230376259Sgreen			if (sftp_server == NULL)
230476259Sgreen				sftp_server = _PATH_SFTP_SERVER;
230576259Sgreen			break;
2306204917Sdes		case '2':
2307204917Sdes			sshver = 2;
230876259Sgreen			break;
2309255767Sdes		case 'a':
2310255767Sdes			global_aflag = 1;
2311255767Sdes			break;
2312204917Sdes		case 'B':
2313204917Sdes			copy_buffer_len = strtol(optarg, &cp, 10);
2314204917Sdes			if (copy_buffer_len == 0 || *cp != '\0')
2315204917Sdes				fatal("Invalid buffer size \"%s\"", optarg);
231676259Sgreen			break;
231776259Sgreen		case 'b':
2318126274Sdes			if (batchmode)
2319126274Sdes				fatal("Batch file already specified.");
2320126274Sdes
2321126274Sdes			/* Allow "-" as stdin */
2322137015Sdes			if (strcmp(optarg, "-") != 0 &&
2323149749Sdes			    (infile = fopen(optarg, "r")) == NULL)
2324126274Sdes				fatal("%s (%s).", strerror(errno), optarg);
2325113908Sdes			showprogress = 0;
2326255767Sdes			quiet = batchmode = 1;
2327146998Sdes			addargs(&args, "-obatchmode yes");
232876259Sgreen			break;
2329262566Sdes		case 'f':
2330262566Sdes			global_fflag = 1;
2331262566Sdes			break;
2332204917Sdes		case 'p':
2333204917Sdes			global_pflag = 1;
2334204917Sdes			break;
2335204917Sdes		case 'D':
233692555Sdes			sftp_direct = optarg;
233792555Sdes			break;
2338221420Sdes		case 'l':
2339221420Sdes			limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2340221420Sdes			    &errstr);
2341221420Sdes			if (errstr != NULL)
2342221420Sdes				usage();
2343221420Sdes			limit_kbps *= 1024; /* kbps */
2344221420Sdes			break;
2345204917Sdes		case 'r':
2346204917Sdes			global_rflag = 1;
234792555Sdes			break;
234892555Sdes		case 'R':
234992555Sdes			num_requests = strtol(optarg, &cp, 10);
235092555Sdes			if (num_requests == 0 || *cp != '\0')
235198675Sdes				fatal("Invalid number of requests \"%s\"",
235292555Sdes				    optarg);
235392555Sdes			break;
2354204917Sdes		case 's':
2355204917Sdes			sftp_server = optarg;
2356204917Sdes			break;
2357204917Sdes		case 'S':
2358204917Sdes			ssh_program = optarg;
2359204917Sdes			replacearg(&args, 0, "%s", ssh_program);
2360204917Sdes			break;
236176259Sgreen		case 'h':
236276259Sgreen		default:
236376259Sgreen			usage();
236476259Sgreen		}
236576259Sgreen	}
236676259Sgreen
2367128456Sdes	if (!isatty(STDERR_FILENO))
2368128456Sdes		showprogress = 0;
2369128456Sdes
237098675Sdes	log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
237198675Sdes
237292555Sdes	if (sftp_direct == NULL) {
237392555Sdes		if (optind == argc || argc > (optind + 2))
237492555Sdes			usage();
237576259Sgreen
237692555Sdes		userhost = xstrdup(argv[optind]);
237792555Sdes		file2 = argv[optind+1];
237876259Sgreen
2379113908Sdes		if ((host = strrchr(userhost, '@')) == NULL)
238092555Sdes			host = userhost;
238192555Sdes		else {
238292555Sdes			*host++ = '\0';
238392555Sdes			if (!userhost[0]) {
238492555Sdes				fprintf(stderr, "Missing username\n");
238592555Sdes				usage();
238692555Sdes			}
2387204917Sdes			addargs(&args, "-l");
2388204917Sdes			addargs(&args, "%s", userhost);
238992555Sdes		}
239092555Sdes
2391126274Sdes		if ((cp = colon(host)) != NULL) {
2392126274Sdes			*cp++ = '\0';
2393126274Sdes			file1 = cp;
2394126274Sdes		}
2395126274Sdes
239692555Sdes		host = cleanhostname(host);
239792555Sdes		if (!*host) {
239892555Sdes			fprintf(stderr, "Missing hostname\n");
239976259Sgreen			usage();
240076259Sgreen		}
240176259Sgreen
240292555Sdes		addargs(&args, "-oProtocol %d", sshver);
240376259Sgreen
240492555Sdes		/* no subsystem if the server-spec contains a '/' */
240592555Sdes		if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
240692555Sdes			addargs(&args, "-s");
240776259Sgreen
2408204917Sdes		addargs(&args, "--");
240992555Sdes		addargs(&args, "%s", host);
241098675Sdes		addargs(&args, "%s", (sftp_server != NULL ?
241192555Sdes		    sftp_server : "sftp"));
241276259Sgreen
2413124208Sdes		connect_to_server(ssh_program, args.list, &in, &out);
241492555Sdes	} else {
241592555Sdes		args.list = NULL;
241692555Sdes		addargs(&args, "sftp-server");
241776259Sgreen
2418124208Sdes		connect_to_server(sftp_direct, args.list, &in, &out);
241992555Sdes	}
2420157016Sdes	freeargs(&args);
242176259Sgreen
2422221420Sdes	conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
2423204917Sdes	if (conn == NULL)
2424204917Sdes		fatal("Couldn't initialise connection to server");
242576259Sgreen
2426255767Sdes	if (!quiet) {
2427204917Sdes		if (sftp_direct == NULL)
2428204917Sdes			fprintf(stderr, "Connected to %s.\n", host);
2429204917Sdes		else
2430204917Sdes			fprintf(stderr, "Attached to %s.\n", sftp_direct);
2431204917Sdes	}
2432204917Sdes
2433204917Sdes	err = interactive_loop(conn, file1, file2);
2434204917Sdes
243598937Sdes#if !defined(USE_PIPES)
2436149749Sdes	shutdown(in, SHUT_RDWR);
2437149749Sdes	shutdown(out, SHUT_RDWR);
243898937Sdes#endif
243998937Sdes
244076259Sgreen	close(in);
244176259Sgreen	close(out);
2442126274Sdes	if (batchmode)
244376259Sgreen		fclose(infile);
244476259Sgreen
244598675Sdes	while (waitpid(sshpid, NULL, 0) == -1)
244698675Sdes		if (errno != EINTR)
244798675Sdes			fatal("Couldn't wait for ssh process: %s",
244898675Sdes			    strerror(errno));
244976259Sgreen
2450113908Sdes	exit(err == 0 ? 0 : 1);
245176259Sgreen}
2452