sftp.c revision 192595
1192595Sdes/* $OpenBSD: sftp.c,v 1.107 2009/02/02 11:15:14 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
20162852Sdes#include <sys/types.h>
21162852Sdes#include <sys/ioctl.h>
22162852Sdes#ifdef HAVE_SYS_STAT_H
23162852Sdes# include <sys/stat.h>
24162852Sdes#endif
25162852Sdes#include <sys/param.h>
26162852Sdes#include <sys/socket.h>
27162852Sdes#include <sys/wait.h>
28181111Sdes#ifdef HAVE_SYS_STATVFS_H
29181111Sdes#include <sys/statvfs.h>
30181111Sdes#endif
3176259Sgreen
32181111Sdes#include <ctype.h>
33162852Sdes#include <errno.h>
34162852Sdes
35162852Sdes#ifdef HAVE_PATHS_H
36162852Sdes# include <paths.h>
37162852Sdes#endif
38146998Sdes#ifdef USE_LIBEDIT
39146998Sdes#include <histedit.h>
40146998Sdes#else
41146998Sdestypedef void EditLine;
42146998Sdes#endif
43162852Sdes#include <signal.h>
44162852Sdes#include <stdlib.h>
45162852Sdes#include <stdio.h>
46162852Sdes#include <string.h>
47162852Sdes#include <unistd.h>
48162852Sdes#include <stdarg.h>
49146998Sdes
50181111Sdes#ifdef HAVE_UTIL_H
51181111Sdes# include <util.h>
52181111Sdes#endif
53181111Sdes
54181111Sdes#ifdef HAVE_LIBUTIL_H
55181111Sdes# include <libutil.h>
56181111Sdes#endif
57181111Sdes
5876259Sgreen#include "xmalloc.h"
5976259Sgreen#include "log.h"
6076259Sgreen#include "pathnames.h"
6192555Sdes#include "misc.h"
6276259Sgreen
6376259Sgreen#include "sftp.h"
64162852Sdes#include "buffer.h"
6576259Sgreen#include "sftp-common.h"
6676259Sgreen#include "sftp-client.h"
6776259Sgreen
68126274Sdes/* File to read commands from */
69126274SdesFILE* infile;
70126274Sdes
71126274Sdes/* Are we in batchfile mode? */
72126274Sdesint batchmode = 0;
73126274Sdes
74126274Sdes/* Size of buffer used when copying files */
75126274Sdessize_t copy_buffer_len = 32768;
76126274Sdes
77126274Sdes/* Number of concurrent outstanding requests */
78181111Sdessize_t num_requests = 64;
79126274Sdes
80126274Sdes/* PID of ssh transport process */
81126274Sdesstatic pid_t sshpid = -1;
82126274Sdes
83126274Sdes/* This is set to 0 if the progressmeter is not desired. */
84128456Sdesint showprogress = 1;
85126274Sdes
86137015Sdes/* SIGINT received during command processing */
87137015Sdesvolatile sig_atomic_t interrupted = 0;
88137015Sdes
89137015Sdes/* I wish qsort() took a separate ctx for the comparison function...*/
90137015Sdesint sort_flag;
91137015Sdes
92126274Sdesint remote_glob(struct sftp_conn *, const char *, int,
93126274Sdes    int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
94126274Sdes
9598937Sdesextern char *__progname;
9698937Sdes
97126274Sdes/* Separators for interactive commands */
98126274Sdes#define WHITESPACE " \t\r\n"
9976259Sgreen
100137015Sdes/* ls flags */
101137015Sdes#define LS_LONG_VIEW	0x01	/* Full view ala ls -l */
102137015Sdes#define LS_SHORT_VIEW	0x02	/* Single row view ala ls -1 */
103137015Sdes#define LS_NUMERIC_VIEW	0x04	/* Long view with numeric uid/gid */
104137015Sdes#define LS_NAME_SORT	0x08	/* Sort by name (default) */
105137015Sdes#define LS_TIME_SORT	0x10	/* Sort by mtime */
106137015Sdes#define LS_SIZE_SORT	0x20	/* Sort by file size */
107137015Sdes#define LS_REVERSE_SORT	0x40	/* Reverse sort order */
108137015Sdes#define LS_SHOW_ALL	0x80	/* Don't skip filenames starting with '.' */
109113908Sdes
110137015Sdes#define VIEW_FLAGS	(LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW)
111137015Sdes#define SORT_FLAGS	(LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
112137015Sdes
113126274Sdes/* Commands for interactive mode */
114126274Sdes#define I_CHDIR		1
115126274Sdes#define I_CHGRP		2
116126274Sdes#define I_CHMOD		3
117126274Sdes#define I_CHOWN		4
118181111Sdes#define I_DF		24
119126274Sdes#define I_GET		5
120126274Sdes#define I_HELP		6
121126274Sdes#define I_LCHDIR	7
122126274Sdes#define I_LLS		8
123126274Sdes#define I_LMKDIR	9
124126274Sdes#define I_LPWD		10
125126274Sdes#define I_LS		11
126126274Sdes#define I_LUMASK	12
127126274Sdes#define I_MKDIR		13
128126274Sdes#define I_PUT		14
129126274Sdes#define I_PWD		15
130126274Sdes#define I_QUIT		16
131126274Sdes#define I_RENAME	17
132126274Sdes#define I_RM		18
133126274Sdes#define I_RMDIR		19
134126274Sdes#define I_SHELL		20
135126274Sdes#define I_SYMLINK	21
136126274Sdes#define I_VERSION	22
137126274Sdes#define I_PROGRESS	23
138126274Sdes
139126274Sdesstruct CMD {
140126274Sdes	const char *c;
141126274Sdes	const int n;
142126274Sdes};
143126274Sdes
144126274Sdesstatic const struct CMD cmds[] = {
145126274Sdes	{ "bye",	I_QUIT },
146126274Sdes	{ "cd",		I_CHDIR },
147126274Sdes	{ "chdir",	I_CHDIR },
148126274Sdes	{ "chgrp",	I_CHGRP },
149126274Sdes	{ "chmod",	I_CHMOD },
150126274Sdes	{ "chown",	I_CHOWN },
151181111Sdes	{ "df",		I_DF },
152126274Sdes	{ "dir",	I_LS },
153126274Sdes	{ "exit",	I_QUIT },
154126274Sdes	{ "get",	I_GET },
155126274Sdes	{ "mget",	I_GET },
156126274Sdes	{ "help",	I_HELP },
157126274Sdes	{ "lcd",	I_LCHDIR },
158126274Sdes	{ "lchdir",	I_LCHDIR },
159126274Sdes	{ "lls",	I_LLS },
160126274Sdes	{ "lmkdir",	I_LMKDIR },
161126274Sdes	{ "ln",		I_SYMLINK },
162126274Sdes	{ "lpwd",	I_LPWD },
163126274Sdes	{ "ls",		I_LS },
164126274Sdes	{ "lumask",	I_LUMASK },
165126274Sdes	{ "mkdir",	I_MKDIR },
166126274Sdes	{ "progress",	I_PROGRESS },
167126274Sdes	{ "put",	I_PUT },
168126274Sdes	{ "mput",	I_PUT },
169126274Sdes	{ "pwd",	I_PWD },
170126274Sdes	{ "quit",	I_QUIT },
171126274Sdes	{ "rename",	I_RENAME },
172126274Sdes	{ "rm",		I_RM },
173126274Sdes	{ "rmdir",	I_RMDIR },
174126274Sdes	{ "symlink",	I_SYMLINK },
175126274Sdes	{ "version",	I_VERSION },
176126274Sdes	{ "!",		I_SHELL },
177126274Sdes	{ "?",		I_HELP },
178126274Sdes	{ NULL,			-1}
179126274Sdes};
180126274Sdes
181126274Sdesint interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
182126274Sdes
183181111Sdes/* ARGSUSED */
18492555Sdesstatic void
185137015Sdeskillchild(int signo)
186137015Sdes{
187146998Sdes	if (sshpid > 1) {
188137015Sdes		kill(sshpid, SIGTERM);
189146998Sdes		waitpid(sshpid, NULL, 0);
190146998Sdes	}
191137015Sdes
192137015Sdes	_exit(1);
193137015Sdes}
194137015Sdes
195181111Sdes/* ARGSUSED */
196137015Sdesstatic void
197137015Sdescmd_interrupt(int signo)
198137015Sdes{
199137015Sdes	const char msg[] = "\rInterrupt  \n";
200146998Sdes	int olderrno = errno;
201137015Sdes
202137015Sdes	write(STDERR_FILENO, msg, sizeof(msg) - 1);
203137015Sdes	interrupted = 1;
204146998Sdes	errno = olderrno;
205137015Sdes}
206137015Sdes
207137015Sdesstatic void
208126274Sdeshelp(void)
209126274Sdes{
210192595Sdes	printf("Available commands:\n"
211192595Sdes	    "bye                                Quit sftp\n"
212192595Sdes	    "cd path                            Change remote directory to 'path'\n"
213192595Sdes	    "chgrp grp path                     Change group of file 'path' to 'grp'\n"
214192595Sdes	    "chmod mode path                    Change permissions of file 'path' to 'mode'\n"
215192595Sdes	    "chown own path                     Change owner of file 'path' to 'own'\n"
216192595Sdes	    "df [-hi] [path]                    Display statistics for current directory or\n"
217192595Sdes	    "                                   filesystem containing 'path'\n"
218192595Sdes	    "exit                               Quit sftp\n"
219192595Sdes	    "get [-P] remote-path [local-path]  Download file\n"
220192595Sdes	    "help                               Display this help text\n"
221192595Sdes	    "lcd path                           Change local directory to 'path'\n"
222192595Sdes	    "lls [ls-options [path]]            Display local directory listing\n"
223192595Sdes	    "lmkdir path                        Create local directory\n"
224192595Sdes	    "ln oldpath newpath                 Symlink remote file\n"
225192595Sdes	    "lpwd                               Print local working directory\n"
226192595Sdes	    "ls [-1aflnrSt] [path]              Display remote directory listing\n"
227192595Sdes	    "lumask umask                       Set local umask to 'umask'\n"
228192595Sdes	    "mkdir path                         Create remote directory\n"
229192595Sdes	    "progress                           Toggle display of progress meter\n"
230192595Sdes	    "put [-P] local-path [remote-path]  Upload file\n"
231192595Sdes	    "pwd                                Display remote working directory\n"
232192595Sdes	    "quit                               Quit sftp\n"
233192595Sdes	    "rename oldpath newpath             Rename remote file\n"
234192595Sdes	    "rm path                            Delete remote file\n"
235192595Sdes	    "rmdir path                         Remove remote directory\n"
236192595Sdes	    "symlink oldpath newpath            Symlink remote file\n"
237192595Sdes	    "version                            Show SFTP version\n"
238192595Sdes	    "!command                           Execute 'command' in local shell\n"
239192595Sdes	    "!                                  Escape to local shell\n"
240192595Sdes	    "?                                  Synonym for help\n");
241126274Sdes}
242126274Sdes
243126274Sdesstatic void
244126274Sdeslocal_do_shell(const char *args)
245126274Sdes{
246126274Sdes	int status;
247126274Sdes	char *shell;
248126274Sdes	pid_t pid;
249126274Sdes
250126274Sdes	if (!*args)
251126274Sdes		args = NULL;
252126274Sdes
253126274Sdes	if ((shell = getenv("SHELL")) == NULL)
254126274Sdes		shell = _PATH_BSHELL;
255126274Sdes
256126274Sdes	if ((pid = fork()) == -1)
257126274Sdes		fatal("Couldn't fork: %s", strerror(errno));
258126274Sdes
259126274Sdes	if (pid == 0) {
260126274Sdes		/* XXX: child has pipe fds to ssh subproc open - issue? */
261126274Sdes		if (args) {
262126274Sdes			debug3("Executing %s -c \"%s\"", shell, args);
263126274Sdes			execl(shell, shell, "-c", args, (char *)NULL);
264126274Sdes		} else {
265126274Sdes			debug3("Executing %s", shell);
266126274Sdes			execl(shell, shell, (char *)NULL);
267126274Sdes		}
268126274Sdes		fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
269126274Sdes		    strerror(errno));
270126274Sdes		_exit(1);
271126274Sdes	}
272126274Sdes	while (waitpid(pid, &status, 0) == -1)
273126274Sdes		if (errno != EINTR)
274126274Sdes			fatal("Couldn't wait for child: %s", strerror(errno));
275126274Sdes	if (!WIFEXITED(status))
276162852Sdes		error("Shell exited abnormally");
277126274Sdes	else if (WEXITSTATUS(status))
278126274Sdes		error("Shell exited with status %d", WEXITSTATUS(status));
279126274Sdes}
280126274Sdes
281126274Sdesstatic void
282126274Sdeslocal_do_ls(const char *args)
283126274Sdes{
284126274Sdes	if (!args || !*args)
285126274Sdes		local_do_shell(_PATH_LS);
286126274Sdes	else {
287126274Sdes		int len = strlen(_PATH_LS " ") + strlen(args) + 1;
288126274Sdes		char *buf = xmalloc(len);
289126274Sdes
290126274Sdes		/* XXX: quoting - rip quoting code from ftp? */
291126274Sdes		snprintf(buf, len, _PATH_LS " %s", args);
292126274Sdes		local_do_shell(buf);
293126274Sdes		xfree(buf);
294126274Sdes	}
295126274Sdes}
296126274Sdes
297126274Sdes/* Strip one path (usually the pwd) from the start of another */
298126274Sdesstatic char *
299126274Sdespath_strip(char *path, char *strip)
300126274Sdes{
301126274Sdes	size_t len;
302126274Sdes
303126274Sdes	if (strip == NULL)
304126274Sdes		return (xstrdup(path));
305126274Sdes
306126274Sdes	len = strlen(strip);
307146998Sdes	if (strncmp(path, strip, len) == 0) {
308126274Sdes		if (strip[len - 1] != '/' && path[len] == '/')
309126274Sdes			len++;
310126274Sdes		return (xstrdup(path + len));
311126274Sdes	}
312126274Sdes
313126274Sdes	return (xstrdup(path));
314126274Sdes}
315126274Sdes
316126274Sdesstatic char *
317126274Sdespath_append(char *p1, char *p2)
318126274Sdes{
319126274Sdes	char *ret;
320181111Sdes	size_t len = strlen(p1) + strlen(p2) + 2;
321126274Sdes
322126274Sdes	ret = xmalloc(len);
323126274Sdes	strlcpy(ret, p1, len);
324181111Sdes	if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
325126274Sdes		strlcat(ret, "/", len);
326126274Sdes	strlcat(ret, p2, len);
327126274Sdes
328126274Sdes	return(ret);
329126274Sdes}
330126274Sdes
331126274Sdesstatic char *
332126274Sdesmake_absolute(char *p, char *pwd)
333126274Sdes{
334137015Sdes	char *abs_str;
335126274Sdes
336126274Sdes	/* Derelativise */
337126274Sdes	if (p && p[0] != '/') {
338137015Sdes		abs_str = path_append(pwd, p);
339126274Sdes		xfree(p);
340137015Sdes		return(abs_str);
341126274Sdes	} else
342126274Sdes		return(p);
343126274Sdes}
344126274Sdes
345126274Sdesstatic int
346126274Sdesinfer_path(const char *p, char **ifp)
347126274Sdes{
348126274Sdes	char *cp;
349126274Sdes
350126274Sdes	cp = strrchr(p, '/');
351126274Sdes	if (cp == NULL) {
352126274Sdes		*ifp = xstrdup(p);
353126274Sdes		return(0);
354126274Sdes	}
355126274Sdes
356126274Sdes	if (!cp[1]) {
357126274Sdes		error("Invalid path");
358126274Sdes		return(-1);
359126274Sdes	}
360126274Sdes
361126274Sdes	*ifp = xstrdup(cp + 1);
362126274Sdes	return(0);
363126274Sdes}
364126274Sdes
365126274Sdesstatic int
366181111Sdesparse_getput_flags(const char *cmd, char **argv, int argc, int *pflag)
367126274Sdes{
368181111Sdes	extern int opterr, optind, optopt, optreset;
369181111Sdes	int ch;
370126274Sdes
371181111Sdes	optind = optreset = 1;
372181111Sdes	opterr = 0;
373181111Sdes
374181111Sdes	*pflag = 0;
375181111Sdes	while ((ch = getopt(argc, argv, "Pp")) != -1) {
376181111Sdes		switch (ch) {
377126274Sdes		case 'p':
378126274Sdes		case 'P':
379126274Sdes			*pflag = 1;
380126274Sdes			break;
381126274Sdes		default:
382181111Sdes			error("%s: Invalid flag -%c", cmd, optopt);
383181111Sdes			return -1;
384126274Sdes		}
385126274Sdes	}
386126274Sdes
387181111Sdes	return optind;
388126274Sdes}
389126274Sdes
390126274Sdesstatic int
391181111Sdesparse_ls_flags(char **argv, int argc, int *lflag)
392126274Sdes{
393181111Sdes	extern int opterr, optind, optopt, optreset;
394181111Sdes	int ch;
395126274Sdes
396181111Sdes	optind = optreset = 1;
397181111Sdes	opterr = 0;
398181111Sdes
399137015Sdes	*lflag = LS_NAME_SORT;
400181111Sdes	while ((ch = getopt(argc, argv, "1Saflnrt")) != -1) {
401181111Sdes		switch (ch) {
402181111Sdes		case '1':
403181111Sdes			*lflag &= ~VIEW_FLAGS;
404181111Sdes			*lflag |= LS_SHORT_VIEW;
405181111Sdes			break;
406181111Sdes		case 'S':
407181111Sdes			*lflag &= ~SORT_FLAGS;
408181111Sdes			*lflag |= LS_SIZE_SORT;
409181111Sdes			break;
410181111Sdes		case 'a':
411181111Sdes			*lflag |= LS_SHOW_ALL;
412181111Sdes			break;
413181111Sdes		case 'f':
414181111Sdes			*lflag &= ~SORT_FLAGS;
415181111Sdes			break;
416181111Sdes		case 'l':
417181111Sdes			*lflag &= ~VIEW_FLAGS;
418181111Sdes			*lflag |= LS_LONG_VIEW;
419181111Sdes			break;
420181111Sdes		case 'n':
421181111Sdes			*lflag &= ~VIEW_FLAGS;
422181111Sdes			*lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
423181111Sdes			break;
424181111Sdes		case 'r':
425181111Sdes			*lflag |= LS_REVERSE_SORT;
426181111Sdes			break;
427181111Sdes		case 't':
428181111Sdes			*lflag &= ~SORT_FLAGS;
429181111Sdes			*lflag |= LS_TIME_SORT;
430181111Sdes			break;
431181111Sdes		default:
432181111Sdes			error("ls: Invalid flag -%c", optopt);
433181111Sdes			return -1;
434126274Sdes		}
435126274Sdes	}
436126274Sdes
437181111Sdes	return optind;
438126274Sdes}
439126274Sdes
440126274Sdesstatic int
441181111Sdesparse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
442126274Sdes{
443181111Sdes	extern int opterr, optind, optopt, optreset;
444181111Sdes	int ch;
445126274Sdes
446181111Sdes	optind = optreset = 1;
447181111Sdes	opterr = 0;
448126274Sdes
449181111Sdes	*hflag = *iflag = 0;
450181111Sdes	while ((ch = getopt(argc, argv, "hi")) != -1) {
451181111Sdes		switch (ch) {
452181111Sdes		case 'h':
453181111Sdes			*hflag = 1;
454181111Sdes			break;
455181111Sdes		case 'i':
456181111Sdes			*iflag = 1;
457181111Sdes			break;
458181111Sdes		default:
459181111Sdes			error("%s: Invalid flag -%c", cmd, optopt);
460181111Sdes			return -1;
461126274Sdes		}
462126274Sdes	}
463126274Sdes
464181111Sdes	return optind;
465126274Sdes}
466126274Sdes
467126274Sdesstatic int
468126274Sdesis_dir(char *path)
469126274Sdes{
470126274Sdes	struct stat sb;
471126274Sdes
472126274Sdes	/* XXX: report errors? */
473126274Sdes	if (stat(path, &sb) == -1)
474126274Sdes		return(0);
475126274Sdes
476162852Sdes	return(S_ISDIR(sb.st_mode));
477126274Sdes}
478126274Sdes
479126274Sdesstatic int
480126274Sdesremote_is_dir(struct sftp_conn *conn, char *path)
481126274Sdes{
482126274Sdes	Attrib *a;
483126274Sdes
484126274Sdes	/* XXX: report errors? */
485126274Sdes	if ((a = do_stat(conn, path, 1)) == NULL)
486126274Sdes		return(0);
487126274Sdes	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
488126274Sdes		return(0);
489162852Sdes	return(S_ISDIR(a->perm));
490126274Sdes}
491126274Sdes
492126274Sdesstatic int
493126274Sdesprocess_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
494126274Sdes{
495126274Sdes	char *abs_src = NULL;
496126274Sdes	char *abs_dst = NULL;
497126274Sdes	char *tmp;
498126274Sdes	glob_t g;
499126274Sdes	int err = 0;
500126274Sdes	int i;
501126274Sdes
502126274Sdes	abs_src = xstrdup(src);
503126274Sdes	abs_src = make_absolute(abs_src, pwd);
504126274Sdes
505126274Sdes	memset(&g, 0, sizeof(g));
506126274Sdes	debug3("Looking up %s", abs_src);
507126274Sdes	if (remote_glob(conn, abs_src, 0, NULL, &g)) {
508126274Sdes		error("File \"%s\" not found.", abs_src);
509126274Sdes		err = -1;
510126274Sdes		goto out;
511126274Sdes	}
512126274Sdes
513126274Sdes	/* If multiple matches, dst must be a directory or unspecified */
514126274Sdes	if (g.gl_matchc > 1 && dst && !is_dir(dst)) {
515126274Sdes		error("Multiple files match, but \"%s\" is not a directory",
516126274Sdes		    dst);
517126274Sdes		err = -1;
518126274Sdes		goto out;
519126274Sdes	}
520126274Sdes
521137015Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
522126274Sdes		if (infer_path(g.gl_pathv[i], &tmp)) {
523126274Sdes			err = -1;
524126274Sdes			goto out;
525126274Sdes		}
526126274Sdes
527126274Sdes		if (g.gl_matchc == 1 && dst) {
528126274Sdes			/* If directory specified, append filename */
529162852Sdes			xfree(tmp);
530126274Sdes			if (is_dir(dst)) {
531126274Sdes				if (infer_path(g.gl_pathv[0], &tmp)) {
532126274Sdes					err = 1;
533126274Sdes					goto out;
534126274Sdes				}
535126274Sdes				abs_dst = path_append(dst, tmp);
536126274Sdes				xfree(tmp);
537126274Sdes			} else
538126274Sdes				abs_dst = xstrdup(dst);
539126274Sdes		} else if (dst) {
540126274Sdes			abs_dst = path_append(dst, tmp);
541126274Sdes			xfree(tmp);
542126274Sdes		} else
543126274Sdes			abs_dst = tmp;
544126274Sdes
545126274Sdes		printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
546126274Sdes		if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
547126274Sdes			err = -1;
548126274Sdes		xfree(abs_dst);
549126274Sdes		abs_dst = NULL;
550126274Sdes	}
551126274Sdes
552126274Sdesout:
553126274Sdes	xfree(abs_src);
554126274Sdes	globfree(&g);
555126274Sdes	return(err);
556126274Sdes}
557126274Sdes
558126274Sdesstatic int
559126274Sdesprocess_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
560126274Sdes{
561126274Sdes	char *tmp_dst = NULL;
562126274Sdes	char *abs_dst = NULL;
563126274Sdes	char *tmp;
564126274Sdes	glob_t g;
565126274Sdes	int err = 0;
566126274Sdes	int i;
567181111Sdes	struct stat sb;
568126274Sdes
569126274Sdes	if (dst) {
570126274Sdes		tmp_dst = xstrdup(dst);
571126274Sdes		tmp_dst = make_absolute(tmp_dst, pwd);
572126274Sdes	}
573126274Sdes
574126274Sdes	memset(&g, 0, sizeof(g));
575126274Sdes	debug3("Looking up %s", src);
576181111Sdes	if (glob(src, GLOB_NOCHECK, NULL, &g)) {
577126274Sdes		error("File \"%s\" not found.", src);
578126274Sdes		err = -1;
579126274Sdes		goto out;
580126274Sdes	}
581126274Sdes
582126274Sdes	/* If multiple matches, dst may be directory or unspecified */
583126274Sdes	if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) {
584126274Sdes		error("Multiple files match, but \"%s\" is not a directory",
585126274Sdes		    tmp_dst);
586126274Sdes		err = -1;
587126274Sdes		goto out;
588126274Sdes	}
589126274Sdes
590137015Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
591181111Sdes		if (stat(g.gl_pathv[i], &sb) == -1) {
592181111Sdes			err = -1;
593181111Sdes			error("stat %s: %s", g.gl_pathv[i], strerror(errno));
594181111Sdes			continue;
595181111Sdes		}
596181111Sdes
597181111Sdes		if (!S_ISREG(sb.st_mode)) {
598126274Sdes			error("skipping non-regular file %s",
599126274Sdes			    g.gl_pathv[i]);
600126274Sdes			continue;
601126274Sdes		}
602126274Sdes		if (infer_path(g.gl_pathv[i], &tmp)) {
603126274Sdes			err = -1;
604126274Sdes			goto out;
605126274Sdes		}
606126274Sdes
607126274Sdes		if (g.gl_matchc == 1 && tmp_dst) {
608126274Sdes			/* If directory specified, append filename */
609126274Sdes			if (remote_is_dir(conn, tmp_dst)) {
610126274Sdes				if (infer_path(g.gl_pathv[0], &tmp)) {
611126274Sdes					err = 1;
612126274Sdes					goto out;
613126274Sdes				}
614126274Sdes				abs_dst = path_append(tmp_dst, tmp);
615126274Sdes				xfree(tmp);
616126274Sdes			} else
617126274Sdes				abs_dst = xstrdup(tmp_dst);
618126274Sdes
619126274Sdes		} else if (tmp_dst) {
620126274Sdes			abs_dst = path_append(tmp_dst, tmp);
621126274Sdes			xfree(tmp);
622126274Sdes		} else
623126274Sdes			abs_dst = make_absolute(tmp, pwd);
624126274Sdes
625126274Sdes		printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
626126274Sdes		if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
627126274Sdes			err = -1;
628126274Sdes	}
629126274Sdes
630126274Sdesout:
631126274Sdes	if (abs_dst)
632126274Sdes		xfree(abs_dst);
633126274Sdes	if (tmp_dst)
634126274Sdes		xfree(tmp_dst);
635126274Sdes	globfree(&g);
636126274Sdes	return(err);
637126274Sdes}
638126274Sdes
639126274Sdesstatic int
640126274Sdessdirent_comp(const void *aa, const void *bb)
641126274Sdes{
642126274Sdes	SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
643126274Sdes	SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
644137015Sdes	int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
645126274Sdes
646137015Sdes#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
647137015Sdes	if (sort_flag & LS_NAME_SORT)
648137015Sdes		return (rmul * strcmp(a->filename, b->filename));
649137015Sdes	else if (sort_flag & LS_TIME_SORT)
650137015Sdes		return (rmul * NCMP(a->a.mtime, b->a.mtime));
651137015Sdes	else if (sort_flag & LS_SIZE_SORT)
652137015Sdes		return (rmul * NCMP(a->a.size, b->a.size));
653137015Sdes
654137015Sdes	fatal("Unknown ls sort type");
655126274Sdes}
656126274Sdes
657126274Sdes/* sftp ls.1 replacement for directories */
658126274Sdesstatic int
659126274Sdesdo_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
660126274Sdes{
661149749Sdes	int n;
662149749Sdes	u_int c = 1, colspace = 0, columns = 1;
663126274Sdes	SFTP_DIRENT **d;
664126274Sdes
665126274Sdes	if ((n = do_readdir(conn, path, &d)) != 0)
666126274Sdes		return (n);
667126274Sdes
668137015Sdes	if (!(lflag & LS_SHORT_VIEW)) {
669149749Sdes		u_int m = 0, width = 80;
670126274Sdes		struct winsize ws;
671126274Sdes		char *tmp;
672126274Sdes
673126274Sdes		/* Count entries for sort and find longest filename */
674137015Sdes		for (n = 0; d[n] != NULL; n++) {
675137015Sdes			if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
676137015Sdes				m = MAX(m, strlen(d[n]->filename));
677137015Sdes		}
678126274Sdes
679126274Sdes		/* Add any subpath that also needs to be counted */
680126274Sdes		tmp = path_strip(path, strip_path);
681126274Sdes		m += strlen(tmp);
682126274Sdes		xfree(tmp);
683126274Sdes
684126274Sdes		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
685126274Sdes			width = ws.ws_col;
686126274Sdes
687126274Sdes		columns = width / (m + 2);
688126274Sdes		columns = MAX(columns, 1);
689126274Sdes		colspace = width / columns;
690126274Sdes		colspace = MIN(colspace, width);
691126274Sdes	}
692126274Sdes
693137015Sdes	if (lflag & SORT_FLAGS) {
694157016Sdes		for (n = 0; d[n] != NULL; n++)
695157016Sdes			;	/* count entries */
696137015Sdes		sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
697137015Sdes		qsort(d, n, sizeof(*d), sdirent_comp);
698137015Sdes	}
699126274Sdes
700137015Sdes	for (n = 0; d[n] != NULL && !interrupted; n++) {
701126274Sdes		char *tmp, *fname;
702126274Sdes
703137015Sdes		if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
704137015Sdes			continue;
705137015Sdes
706126274Sdes		tmp = path_append(path, d[n]->filename);
707126274Sdes		fname = path_strip(tmp, strip_path);
708126274Sdes		xfree(tmp);
709126274Sdes
710137015Sdes		if (lflag & LS_LONG_VIEW) {
711137015Sdes			if (lflag & LS_NUMERIC_VIEW) {
712137015Sdes				char *lname;
713137015Sdes				struct stat sb;
714126274Sdes
715137015Sdes				memset(&sb, 0, sizeof(sb));
716137015Sdes				attrib_to_stat(&d[n]->a, &sb);
717137015Sdes				lname = ls_file(fname, &sb, 1);
718137015Sdes				printf("%s\n", lname);
719137015Sdes				xfree(lname);
720137015Sdes			} else
721137015Sdes				printf("%s\n", d[n]->longname);
722126274Sdes		} else {
723126274Sdes			printf("%-*s", colspace, fname);
724126274Sdes			if (c >= columns) {
725126274Sdes				printf("\n");
726126274Sdes				c = 1;
727126274Sdes			} else
728126274Sdes				c++;
729126274Sdes		}
730126274Sdes
731126274Sdes		xfree(fname);
732126274Sdes	}
733126274Sdes
734137015Sdes	if (!(lflag & LS_LONG_VIEW) && (c != 1))
735126274Sdes		printf("\n");
736126274Sdes
737126274Sdes	free_sftp_dirents(d);
738126274Sdes	return (0);
739126274Sdes}
740126274Sdes
741126274Sdes/* sftp ls.1 replacement which handles path globs */
742126274Sdesstatic int
743126274Sdesdo_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
744126274Sdes    int lflag)
745126274Sdes{
746126274Sdes	glob_t g;
747149749Sdes	u_int i, c = 1, colspace = 0, columns = 1;
748146998Sdes	Attrib *a = NULL;
749126274Sdes
750126274Sdes	memset(&g, 0, sizeof(g));
751126274Sdes
752126274Sdes	if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
753146998Sdes	    NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
754146998Sdes		if (g.gl_pathc)
755146998Sdes			globfree(&g);
756126274Sdes		error("Can't ls: \"%s\" not found", path);
757126274Sdes		return (-1);
758126274Sdes	}
759126274Sdes
760137015Sdes	if (interrupted)
761137015Sdes		goto out;
762137015Sdes
763126274Sdes	/*
764146998Sdes	 * If the glob returns a single match and it is a directory,
765146998Sdes	 * then just list its contents.
766126274Sdes	 */
767146998Sdes	if (g.gl_matchc == 1) {
768146998Sdes		if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
769126274Sdes			globfree(&g);
770126274Sdes			return (-1);
771126274Sdes		}
772126274Sdes		if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
773126274Sdes		    S_ISDIR(a->perm)) {
774146998Sdes			int err;
775146998Sdes
776146998Sdes			err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
777126274Sdes			globfree(&g);
778146998Sdes			return (err);
779126274Sdes		}
780126274Sdes	}
781126274Sdes
782137015Sdes	if (!(lflag & LS_SHORT_VIEW)) {
783149749Sdes		u_int m = 0, width = 80;
784126274Sdes		struct winsize ws;
785126274Sdes
786126274Sdes		/* Count entries for sort and find longest filename */
787126274Sdes		for (i = 0; g.gl_pathv[i]; i++)
788126274Sdes			m = MAX(m, strlen(g.gl_pathv[i]));
789126274Sdes
790126274Sdes		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
791126274Sdes			width = ws.ws_col;
792126274Sdes
793126274Sdes		columns = width / (m + 2);
794126274Sdes		columns = MAX(columns, 1);
795126274Sdes		colspace = width / columns;
796126274Sdes	}
797126274Sdes
798146998Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
799126274Sdes		char *fname;
800126274Sdes
801126274Sdes		fname = path_strip(g.gl_pathv[i], strip_path);
802126274Sdes
803137015Sdes		if (lflag & LS_LONG_VIEW) {
804126274Sdes			char *lname;
805126274Sdes			struct stat sb;
806126274Sdes
807126274Sdes			/*
808126274Sdes			 * XXX: this is slow - 1 roundtrip per path
809126274Sdes			 * A solution to this is to fork glob() and
810126274Sdes			 * build a sftp specific version which keeps the
811126274Sdes			 * attribs (which currently get thrown away)
812126274Sdes			 * that the server returns as well as the filenames.
813126274Sdes			 */
814126274Sdes			memset(&sb, 0, sizeof(sb));
815146998Sdes			if (a == NULL)
816146998Sdes				a = do_lstat(conn, g.gl_pathv[i], 1);
817126274Sdes			if (a != NULL)
818126274Sdes				attrib_to_stat(a, &sb);
819126274Sdes			lname = ls_file(fname, &sb, 1);
820126274Sdes			printf("%s\n", lname);
821126274Sdes			xfree(lname);
822126274Sdes		} else {
823126274Sdes			printf("%-*s", colspace, fname);
824126274Sdes			if (c >= columns) {
825126274Sdes				printf("\n");
826126274Sdes				c = 1;
827126274Sdes			} else
828126274Sdes				c++;
829126274Sdes		}
830126274Sdes		xfree(fname);
831126274Sdes	}
832126274Sdes
833137015Sdes	if (!(lflag & LS_LONG_VIEW) && (c != 1))
834126274Sdes		printf("\n");
835126274Sdes
836137015Sdes out:
837126274Sdes	if (g.gl_pathc)
838126274Sdes		globfree(&g);
839126274Sdes
840126274Sdes	return (0);
841126274Sdes}
842126274Sdes
843126274Sdesstatic int
844181111Sdesdo_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
845181111Sdes{
846181111Sdes	struct sftp_statvfs st;
847181111Sdes	char s_used[FMT_SCALED_STRSIZE];
848181111Sdes	char s_avail[FMT_SCALED_STRSIZE];
849181111Sdes	char s_root[FMT_SCALED_STRSIZE];
850181111Sdes	char s_total[FMT_SCALED_STRSIZE];
851181111Sdes
852181111Sdes	if (do_statvfs(conn, path, &st, 1) == -1)
853181111Sdes		return -1;
854181111Sdes	if (iflag) {
855181111Sdes		printf("     Inodes        Used       Avail      "
856181111Sdes		    "(root)    %%Capacity\n");
857181111Sdes		printf("%11llu %11llu %11llu %11llu         %3llu%%\n",
858181111Sdes		    (unsigned long long)st.f_files,
859181111Sdes		    (unsigned long long)(st.f_files - st.f_ffree),
860181111Sdes		    (unsigned long long)st.f_favail,
861181111Sdes		    (unsigned long long)st.f_ffree,
862181111Sdes		    (unsigned long long)(100 * (st.f_files - st.f_ffree) /
863181111Sdes		    st.f_files));
864181111Sdes	} else if (hflag) {
865181111Sdes		strlcpy(s_used, "error", sizeof(s_used));
866181111Sdes		strlcpy(s_avail, "error", sizeof(s_avail));
867181111Sdes		strlcpy(s_root, "error", sizeof(s_root));
868181111Sdes		strlcpy(s_total, "error", sizeof(s_total));
869181111Sdes		fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
870181111Sdes		fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
871181111Sdes		fmt_scaled(st.f_bfree * st.f_frsize, s_root);
872181111Sdes		fmt_scaled(st.f_blocks * st.f_frsize, s_total);
873181111Sdes		printf("    Size     Used    Avail   (root)    %%Capacity\n");
874181111Sdes		printf("%7sB %7sB %7sB %7sB         %3llu%%\n",
875181111Sdes		    s_total, s_used, s_avail, s_root,
876181111Sdes		    (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
877181111Sdes		    st.f_blocks));
878181111Sdes	} else {
879181111Sdes		printf("        Size         Used        Avail       "
880181111Sdes		    "(root)    %%Capacity\n");
881181111Sdes		printf("%12llu %12llu %12llu %12llu         %3llu%%\n",
882181111Sdes		    (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
883181111Sdes		    (unsigned long long)(st.f_frsize *
884181111Sdes		    (st.f_blocks - st.f_bfree) / 1024),
885181111Sdes		    (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
886181111Sdes		    (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
887181111Sdes		    (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
888181111Sdes		    st.f_blocks));
889181111Sdes	}
890181111Sdes	return 0;
891181111Sdes}
892181111Sdes
893181111Sdes/*
894181111Sdes * Undo escaping of glob sequences in place. Used to undo extra escaping
895181111Sdes * applied in makeargv() when the string is destined for a function that
896181111Sdes * does not glob it.
897181111Sdes */
898181111Sdesstatic void
899181111Sdesundo_glob_escape(char *s)
900181111Sdes{
901181111Sdes	size_t i, j;
902181111Sdes
903181111Sdes	for (i = j = 0;;) {
904181111Sdes		if (s[i] == '\0') {
905181111Sdes			s[j] = '\0';
906181111Sdes			return;
907181111Sdes		}
908181111Sdes		if (s[i] != '\\') {
909181111Sdes			s[j++] = s[i++];
910181111Sdes			continue;
911181111Sdes		}
912181111Sdes		/* s[i] == '\\' */
913181111Sdes		++i;
914181111Sdes		switch (s[i]) {
915181111Sdes		case '?':
916181111Sdes		case '[':
917181111Sdes		case '*':
918181111Sdes		case '\\':
919181111Sdes			s[j++] = s[i++];
920181111Sdes			break;
921181111Sdes		case '\0':
922181111Sdes			s[j++] = '\\';
923181111Sdes			s[j] = '\0';
924181111Sdes			return;
925181111Sdes		default:
926181111Sdes			s[j++] = '\\';
927181111Sdes			s[j++] = s[i++];
928181111Sdes			break;
929181111Sdes		}
930181111Sdes	}
931181111Sdes}
932181111Sdes
933181111Sdes/*
934181111Sdes * Split a string into an argument vector using sh(1)-style quoting,
935181111Sdes * comment and escaping rules, but with some tweaks to handle glob(3)
936181111Sdes * wildcards.
937181111Sdes * Returns NULL on error or a NULL-terminated array of arguments.
938181111Sdes */
939181111Sdes#define MAXARGS 	128
940181111Sdes#define MAXARGLEN	8192
941181111Sdesstatic char **
942181111Sdesmakeargv(const char *arg, int *argcp)
943181111Sdes{
944181111Sdes	int argc, quot;
945181111Sdes	size_t i, j;
946181111Sdes	static char argvs[MAXARGLEN];
947181111Sdes	static char *argv[MAXARGS + 1];
948181111Sdes	enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
949181111Sdes
950181111Sdes	*argcp = argc = 0;
951181111Sdes	if (strlen(arg) > sizeof(argvs) - 1) {
952181111Sdes args_too_longs:
953181111Sdes		error("string too long");
954181111Sdes		return NULL;
955181111Sdes	}
956181111Sdes	state = MA_START;
957181111Sdes	i = j = 0;
958181111Sdes	for (;;) {
959181111Sdes		if (isspace(arg[i])) {
960181111Sdes			if (state == MA_UNQUOTED) {
961181111Sdes				/* Terminate current argument */
962181111Sdes				argvs[j++] = '\0';
963181111Sdes				argc++;
964181111Sdes				state = MA_START;
965181111Sdes			} else if (state != MA_START)
966181111Sdes				argvs[j++] = arg[i];
967181111Sdes		} else if (arg[i] == '"' || arg[i] == '\'') {
968181111Sdes			q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
969181111Sdes			if (state == MA_START) {
970181111Sdes				argv[argc] = argvs + j;
971181111Sdes				state = q;
972181111Sdes			} else if (state == MA_UNQUOTED)
973181111Sdes				state = q;
974181111Sdes			else if (state == q)
975181111Sdes				state = MA_UNQUOTED;
976181111Sdes			else
977181111Sdes				argvs[j++] = arg[i];
978181111Sdes		} else if (arg[i] == '\\') {
979181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE) {
980181111Sdes				quot = state == MA_SQUOTE ? '\'' : '"';
981181111Sdes				/* Unescape quote we are in */
982181111Sdes				/* XXX support \n and friends? */
983181111Sdes				if (arg[i + 1] == quot) {
984181111Sdes					i++;
985181111Sdes					argvs[j++] = arg[i];
986181111Sdes				} else if (arg[i + 1] == '?' ||
987181111Sdes				    arg[i + 1] == '[' || arg[i + 1] == '*') {
988181111Sdes					/*
989181111Sdes					 * Special case for sftp: append
990181111Sdes					 * double-escaped glob sequence -
991181111Sdes					 * glob will undo one level of
992181111Sdes					 * escaping. NB. string can grow here.
993181111Sdes					 */
994181111Sdes					if (j >= sizeof(argvs) - 5)
995181111Sdes						goto args_too_longs;
996181111Sdes					argvs[j++] = '\\';
997181111Sdes					argvs[j++] = arg[i++];
998181111Sdes					argvs[j++] = '\\';
999181111Sdes					argvs[j++] = arg[i];
1000181111Sdes				} else {
1001181111Sdes					argvs[j++] = arg[i++];
1002181111Sdes					argvs[j++] = arg[i];
1003181111Sdes				}
1004181111Sdes			} else {
1005181111Sdes				if (state == MA_START) {
1006181111Sdes					argv[argc] = argvs + j;
1007181111Sdes					state = MA_UNQUOTED;
1008181111Sdes				}
1009181111Sdes				if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1010181111Sdes				    arg[i + 1] == '*' || arg[i + 1] == '\\') {
1011181111Sdes					/*
1012181111Sdes					 * Special case for sftp: append
1013181111Sdes					 * escaped glob sequence -
1014181111Sdes					 * glob will undo one level of
1015181111Sdes					 * escaping.
1016181111Sdes					 */
1017181111Sdes					argvs[j++] = arg[i++];
1018181111Sdes					argvs[j++] = arg[i];
1019181111Sdes				} else {
1020181111Sdes					/* Unescape everything */
1021181111Sdes					/* XXX support \n and friends? */
1022181111Sdes					i++;
1023181111Sdes					argvs[j++] = arg[i];
1024181111Sdes				}
1025181111Sdes			}
1026181111Sdes		} else if (arg[i] == '#') {
1027181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE)
1028181111Sdes				argvs[j++] = arg[i];
1029181111Sdes			else
1030181111Sdes				goto string_done;
1031181111Sdes		} else if (arg[i] == '\0') {
1032181111Sdes			if (state == MA_SQUOTE || state == MA_DQUOTE) {
1033181111Sdes				error("Unterminated quoted argument");
1034181111Sdes				return NULL;
1035181111Sdes			}
1036181111Sdes string_done:
1037181111Sdes			if (state == MA_UNQUOTED) {
1038181111Sdes				argvs[j++] = '\0';
1039181111Sdes				argc++;
1040181111Sdes			}
1041181111Sdes			break;
1042181111Sdes		} else {
1043181111Sdes			if (state == MA_START) {
1044181111Sdes				argv[argc] = argvs + j;
1045181111Sdes				state = MA_UNQUOTED;
1046181111Sdes			}
1047181111Sdes			if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1048181111Sdes			    (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1049181111Sdes				/*
1050181111Sdes				 * Special case for sftp: escape quoted
1051181111Sdes				 * glob(3) wildcards. NB. string can grow
1052181111Sdes				 * here.
1053181111Sdes				 */
1054181111Sdes				if (j >= sizeof(argvs) - 3)
1055181111Sdes					goto args_too_longs;
1056181111Sdes				argvs[j++] = '\\';
1057181111Sdes				argvs[j++] = arg[i];
1058181111Sdes			} else
1059181111Sdes				argvs[j++] = arg[i];
1060181111Sdes		}
1061181111Sdes		i++;
1062181111Sdes	}
1063181111Sdes	*argcp = argc;
1064181111Sdes	return argv;
1065181111Sdes}
1066181111Sdes
1067181111Sdesstatic int
1068181111Sdesparse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag,
1069126274Sdes    unsigned long *n_arg, char **path1, char **path2)
1070126274Sdes{
1071126274Sdes	const char *cmd, *cp = *cpp;
1072181111Sdes	char *cp2, **argv;
1073126274Sdes	int base = 0;
1074126274Sdes	long l;
1075181111Sdes	int i, cmdnum, optidx, argc;
1076126274Sdes
1077126274Sdes	/* Skip leading whitespace */
1078126274Sdes	cp = cp + strspn(cp, WHITESPACE);
1079126274Sdes
1080126274Sdes	/* Ignore blank lines and lines which begin with comment '#' char */
1081126274Sdes	if (*cp == '\0' || *cp == '#')
1082126274Sdes		return (0);
1083126274Sdes
1084126274Sdes	/* Check for leading '-' (disable error processing) */
1085126274Sdes	*iflag = 0;
1086126274Sdes	if (*cp == '-') {
1087126274Sdes		*iflag = 1;
1088126274Sdes		cp++;
1089126274Sdes	}
1090126274Sdes
1091181111Sdes	if ((argv = makeargv(cp, &argc)) == NULL)
1092181111Sdes		return -1;
1093181111Sdes
1094126274Sdes	/* Figure out which command we have */
1095181111Sdes	for (i = 0; cmds[i].c != NULL; i++) {
1096181111Sdes		if (strcasecmp(cmds[i].c, argv[0]) == 0)
1097126274Sdes			break;
1098126274Sdes	}
1099126274Sdes	cmdnum = cmds[i].n;
1100126274Sdes	cmd = cmds[i].c;
1101126274Sdes
1102126274Sdes	/* Special case */
1103126274Sdes	if (*cp == '!') {
1104126274Sdes		cp++;
1105126274Sdes		cmdnum = I_SHELL;
1106126274Sdes	} else if (cmdnum == -1) {
1107126274Sdes		error("Invalid command.");
1108181111Sdes		return -1;
1109126274Sdes	}
1110126274Sdes
1111126274Sdes	/* Get arguments and parse flags */
1112181111Sdes	*lflag = *pflag = *hflag = *n_arg = 0;
1113126274Sdes	*path1 = *path2 = NULL;
1114181111Sdes	optidx = 1;
1115126274Sdes	switch (cmdnum) {
1116126274Sdes	case I_GET:
1117126274Sdes	case I_PUT:
1118181111Sdes		if ((optidx = parse_getput_flags(cmd, argv, argc, pflag)) == -1)
1119181111Sdes			return -1;
1120126274Sdes		/* Get first pathname (mandatory) */
1121181111Sdes		if (argc - optidx < 1) {
1122126274Sdes			error("You must specify at least one path after a "
1123126274Sdes			    "%s command.", cmd);
1124181111Sdes			return -1;
1125126274Sdes		}
1126181111Sdes		*path1 = xstrdup(argv[optidx]);
1127181111Sdes		/* Get second pathname (optional) */
1128181111Sdes		if (argc - optidx > 1) {
1129181111Sdes			*path2 = xstrdup(argv[optidx + 1]);
1130181111Sdes			/* Destination is not globbed */
1131181111Sdes			undo_glob_escape(*path2);
1132181111Sdes		}
1133126274Sdes		break;
1134126274Sdes	case I_RENAME:
1135126274Sdes	case I_SYMLINK:
1136181111Sdes		if (argc - optidx < 2) {
1137126274Sdes			error("You must specify two paths after a %s "
1138126274Sdes			    "command.", cmd);
1139181111Sdes			return -1;
1140126274Sdes		}
1141181111Sdes		*path1 = xstrdup(argv[optidx]);
1142181111Sdes		*path2 = xstrdup(argv[optidx + 1]);
1143181111Sdes		/* Paths are not globbed */
1144181111Sdes		undo_glob_escape(*path1);
1145181111Sdes		undo_glob_escape(*path2);
1146126274Sdes		break;
1147126274Sdes	case I_RM:
1148126274Sdes	case I_MKDIR:
1149126274Sdes	case I_RMDIR:
1150126274Sdes	case I_CHDIR:
1151126274Sdes	case I_LCHDIR:
1152126274Sdes	case I_LMKDIR:
1153126274Sdes		/* Get pathname (mandatory) */
1154181111Sdes		if (argc - optidx < 1) {
1155126274Sdes			error("You must specify a path after a %s command.",
1156126274Sdes			    cmd);
1157181111Sdes			return -1;
1158126274Sdes		}
1159181111Sdes		*path1 = xstrdup(argv[optidx]);
1160181111Sdes		/* Only "rm" globs */
1161181111Sdes		if (cmdnum != I_RM)
1162181111Sdes			undo_glob_escape(*path1);
1163126274Sdes		break;
1164181111Sdes	case I_DF:
1165181111Sdes		if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1166181111Sdes		    iflag)) == -1)
1167181111Sdes			return -1;
1168181111Sdes		/* Default to current directory if no path specified */
1169181111Sdes		if (argc - optidx < 1)
1170181111Sdes			*path1 = NULL;
1171181111Sdes		else {
1172181111Sdes			*path1 = xstrdup(argv[optidx]);
1173181111Sdes			undo_glob_escape(*path1);
1174181111Sdes		}
1175181111Sdes		break;
1176126274Sdes	case I_LS:
1177181111Sdes		if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
1178126274Sdes			return(-1);
1179126274Sdes		/* Path is optional */
1180181111Sdes		if (argc - optidx > 0)
1181181111Sdes			*path1 = xstrdup(argv[optidx]);
1182126274Sdes		break;
1183126274Sdes	case I_LLS:
1184181111Sdes		/* Skip ls command and following whitespace */
1185181111Sdes		cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
1186126274Sdes	case I_SHELL:
1187126274Sdes		/* Uses the rest of the line */
1188126274Sdes		break;
1189126274Sdes	case I_LUMASK:
1190126274Sdes	case I_CHMOD:
1191126274Sdes		base = 8;
1192126274Sdes	case I_CHOWN:
1193126274Sdes	case I_CHGRP:
1194126274Sdes		/* Get numeric arg (mandatory) */
1195181111Sdes		if (argc - optidx < 1)
1196181111Sdes			goto need_num_arg;
1197164146Sdes		errno = 0;
1198181111Sdes		l = strtol(argv[optidx], &cp2, base);
1199181111Sdes		if (cp2 == argv[optidx] || *cp2 != '\0' ||
1200181111Sdes		    ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1201181111Sdes		    l < 0) {
1202181111Sdes need_num_arg:
1203126274Sdes			error("You must supply a numeric argument "
1204126274Sdes			    "to the %s command.", cmd);
1205181111Sdes			return -1;
1206126274Sdes		}
1207126274Sdes		*n_arg = l;
1208181111Sdes		if (cmdnum == I_LUMASK)
1209126274Sdes			break;
1210126274Sdes		/* Get pathname (mandatory) */
1211181111Sdes		if (argc - optidx < 2) {
1212126274Sdes			error("You must specify a path after a %s command.",
1213126274Sdes			    cmd);
1214181111Sdes			return -1;
1215126274Sdes		}
1216181111Sdes		*path1 = xstrdup(argv[optidx + 1]);
1217126274Sdes		break;
1218126274Sdes	case I_QUIT:
1219126274Sdes	case I_PWD:
1220126274Sdes	case I_LPWD:
1221126274Sdes	case I_HELP:
1222126274Sdes	case I_VERSION:
1223126274Sdes	case I_PROGRESS:
1224126274Sdes		break;
1225126274Sdes	default:
1226126274Sdes		fatal("Command not implemented");
1227126274Sdes	}
1228126274Sdes
1229126274Sdes	*cpp = cp;
1230126274Sdes	return(cmdnum);
1231126274Sdes}
1232126274Sdes
1233126274Sdesstatic int
1234126274Sdesparse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1235126274Sdes    int err_abort)
1236126274Sdes{
1237126274Sdes	char *path1, *path2, *tmp;
1238192595Sdes	int pflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
1239192595Sdes	unsigned long n_arg = 0;
1240126274Sdes	Attrib a, *aa;
1241126274Sdes	char path_buf[MAXPATHLEN];
1242126274Sdes	int err = 0;
1243126274Sdes	glob_t g;
1244126274Sdes
1245126274Sdes	path1 = path2 = NULL;
1246181111Sdes	cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &hflag, &n_arg,
1247126274Sdes	    &path1, &path2);
1248126274Sdes
1249126274Sdes	if (iflag != 0)
1250126274Sdes		err_abort = 0;
1251126274Sdes
1252126274Sdes	memset(&g, 0, sizeof(g));
1253126274Sdes
1254126274Sdes	/* Perform command */
1255126274Sdes	switch (cmdnum) {
1256126274Sdes	case 0:
1257126274Sdes		/* Blank line */
1258126274Sdes		break;
1259126274Sdes	case -1:
1260126274Sdes		/* Unrecognized command */
1261126274Sdes		err = -1;
1262126274Sdes		break;
1263126274Sdes	case I_GET:
1264126274Sdes		err = process_get(conn, path1, path2, *pwd, pflag);
1265126274Sdes		break;
1266126274Sdes	case I_PUT:
1267126274Sdes		err = process_put(conn, path1, path2, *pwd, pflag);
1268126274Sdes		break;
1269126274Sdes	case I_RENAME:
1270126274Sdes		path1 = make_absolute(path1, *pwd);
1271126274Sdes		path2 = make_absolute(path2, *pwd);
1272126274Sdes		err = do_rename(conn, path1, path2);
1273126274Sdes		break;
1274126274Sdes	case I_SYMLINK:
1275126274Sdes		path2 = make_absolute(path2, *pwd);
1276126274Sdes		err = do_symlink(conn, path1, path2);
1277126274Sdes		break;
1278126274Sdes	case I_RM:
1279126274Sdes		path1 = make_absolute(path1, *pwd);
1280126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1281137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1282126274Sdes			printf("Removing %s\n", g.gl_pathv[i]);
1283126274Sdes			err = do_rm(conn, g.gl_pathv[i]);
1284126274Sdes			if (err != 0 && err_abort)
1285126274Sdes				break;
1286126274Sdes		}
1287126274Sdes		break;
1288126274Sdes	case I_MKDIR:
1289126274Sdes		path1 = make_absolute(path1, *pwd);
1290126274Sdes		attrib_clear(&a);
1291126274Sdes		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1292126274Sdes		a.perm = 0777;
1293126274Sdes		err = do_mkdir(conn, path1, &a);
1294126274Sdes		break;
1295126274Sdes	case I_RMDIR:
1296126274Sdes		path1 = make_absolute(path1, *pwd);
1297126274Sdes		err = do_rmdir(conn, path1);
1298126274Sdes		break;
1299126274Sdes	case I_CHDIR:
1300126274Sdes		path1 = make_absolute(path1, *pwd);
1301126274Sdes		if ((tmp = do_realpath(conn, path1)) == NULL) {
1302126274Sdes			err = 1;
1303126274Sdes			break;
1304126274Sdes		}
1305126274Sdes		if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1306126274Sdes			xfree(tmp);
1307126274Sdes			err = 1;
1308126274Sdes			break;
1309126274Sdes		}
1310126274Sdes		if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1311126274Sdes			error("Can't change directory: Can't check target");
1312126274Sdes			xfree(tmp);
1313126274Sdes			err = 1;
1314126274Sdes			break;
1315126274Sdes		}
1316126274Sdes		if (!S_ISDIR(aa->perm)) {
1317126274Sdes			error("Can't change directory: \"%s\" is not "
1318126274Sdes			    "a directory", tmp);
1319126274Sdes			xfree(tmp);
1320126274Sdes			err = 1;
1321126274Sdes			break;
1322126274Sdes		}
1323126274Sdes		xfree(*pwd);
1324126274Sdes		*pwd = tmp;
1325126274Sdes		break;
1326126274Sdes	case I_LS:
1327126274Sdes		if (!path1) {
1328126274Sdes			do_globbed_ls(conn, *pwd, *pwd, lflag);
1329126274Sdes			break;
1330126274Sdes		}
1331126274Sdes
1332126274Sdes		/* Strip pwd off beginning of non-absolute paths */
1333126274Sdes		tmp = NULL;
1334126274Sdes		if (*path1 != '/')
1335126274Sdes			tmp = *pwd;
1336126274Sdes
1337126274Sdes		path1 = make_absolute(path1, *pwd);
1338126274Sdes		err = do_globbed_ls(conn, path1, tmp, lflag);
1339126274Sdes		break;
1340181111Sdes	case I_DF:
1341181111Sdes		/* Default to current directory if no path specified */
1342181111Sdes		if (path1 == NULL)
1343181111Sdes			path1 = xstrdup(*pwd);
1344181111Sdes		path1 = make_absolute(path1, *pwd);
1345181111Sdes		err = do_df(conn, path1, hflag, iflag);
1346181111Sdes		break;
1347126274Sdes	case I_LCHDIR:
1348126274Sdes		if (chdir(path1) == -1) {
1349126274Sdes			error("Couldn't change local directory to "
1350126274Sdes			    "\"%s\": %s", path1, strerror(errno));
1351126274Sdes			err = 1;
1352126274Sdes		}
1353126274Sdes		break;
1354126274Sdes	case I_LMKDIR:
1355126274Sdes		if (mkdir(path1, 0777) == -1) {
1356126274Sdes			error("Couldn't create local directory "
1357126274Sdes			    "\"%s\": %s", path1, strerror(errno));
1358126274Sdes			err = 1;
1359126274Sdes		}
1360126274Sdes		break;
1361126274Sdes	case I_LLS:
1362126274Sdes		local_do_ls(cmd);
1363126274Sdes		break;
1364126274Sdes	case I_SHELL:
1365126274Sdes		local_do_shell(cmd);
1366126274Sdes		break;
1367126274Sdes	case I_LUMASK:
1368126274Sdes		umask(n_arg);
1369126274Sdes		printf("Local umask: %03lo\n", n_arg);
1370126274Sdes		break;
1371126274Sdes	case I_CHMOD:
1372126274Sdes		path1 = make_absolute(path1, *pwd);
1373126274Sdes		attrib_clear(&a);
1374126274Sdes		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1375126274Sdes		a.perm = n_arg;
1376126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1377137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1378126274Sdes			printf("Changing mode on %s\n", g.gl_pathv[i]);
1379126274Sdes			err = do_setstat(conn, g.gl_pathv[i], &a);
1380126274Sdes			if (err != 0 && err_abort)
1381126274Sdes				break;
1382126274Sdes		}
1383126274Sdes		break;
1384126274Sdes	case I_CHOWN:
1385126274Sdes	case I_CHGRP:
1386126274Sdes		path1 = make_absolute(path1, *pwd);
1387126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1388137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1389126274Sdes			if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
1390192595Sdes				if (err_abort) {
1391192595Sdes					err = -1;
1392126274Sdes					break;
1393192595Sdes				} else
1394126274Sdes					continue;
1395126274Sdes			}
1396126274Sdes			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1397126274Sdes				error("Can't get current ownership of "
1398126274Sdes				    "remote file \"%s\"", g.gl_pathv[i]);
1399192595Sdes				if (err_abort) {
1400192595Sdes					err = -1;
1401126274Sdes					break;
1402192595Sdes				} else
1403126274Sdes					continue;
1404126274Sdes			}
1405126274Sdes			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1406126274Sdes			if (cmdnum == I_CHOWN) {
1407126274Sdes				printf("Changing owner on %s\n", g.gl_pathv[i]);
1408126274Sdes				aa->uid = n_arg;
1409126274Sdes			} else {
1410126274Sdes				printf("Changing group on %s\n", g.gl_pathv[i]);
1411126274Sdes				aa->gid = n_arg;
1412126274Sdes			}
1413126274Sdes			err = do_setstat(conn, g.gl_pathv[i], aa);
1414126274Sdes			if (err != 0 && err_abort)
1415126274Sdes				break;
1416126274Sdes		}
1417126274Sdes		break;
1418126274Sdes	case I_PWD:
1419126274Sdes		printf("Remote working directory: %s\n", *pwd);
1420126274Sdes		break;
1421126274Sdes	case I_LPWD:
1422126274Sdes		if (!getcwd(path_buf, sizeof(path_buf))) {
1423126274Sdes			error("Couldn't get local cwd: %s", strerror(errno));
1424126274Sdes			err = -1;
1425126274Sdes			break;
1426126274Sdes		}
1427126274Sdes		printf("Local working directory: %s\n", path_buf);
1428126274Sdes		break;
1429126274Sdes	case I_QUIT:
1430126274Sdes		/* Processed below */
1431126274Sdes		break;
1432126274Sdes	case I_HELP:
1433126274Sdes		help();
1434126274Sdes		break;
1435126274Sdes	case I_VERSION:
1436126274Sdes		printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1437126274Sdes		break;
1438126274Sdes	case I_PROGRESS:
1439126274Sdes		showprogress = !showprogress;
1440126274Sdes		if (showprogress)
1441126274Sdes			printf("Progress meter enabled\n");
1442126274Sdes		else
1443126274Sdes			printf("Progress meter disabled\n");
1444126274Sdes		break;
1445126274Sdes	default:
1446126274Sdes		fatal("%d is not implemented", cmdnum);
1447126274Sdes	}
1448126274Sdes
1449126274Sdes	if (g.gl_pathc)
1450126274Sdes		globfree(&g);
1451126274Sdes	if (path1)
1452126274Sdes		xfree(path1);
1453126274Sdes	if (path2)
1454126274Sdes		xfree(path2);
1455126274Sdes
1456126274Sdes	/* If an unignored error occurs in batch mode we should abort. */
1457126274Sdes	if (err_abort && err != 0)
1458126274Sdes		return (-1);
1459126274Sdes	else if (cmdnum == I_QUIT)
1460126274Sdes		return (1);
1461126274Sdes
1462126274Sdes	return (0);
1463126274Sdes}
1464126274Sdes
1465146998Sdes#ifdef USE_LIBEDIT
1466146998Sdesstatic char *
1467146998Sdesprompt(EditLine *el)
1468146998Sdes{
1469146998Sdes	return ("sftp> ");
1470146998Sdes}
1471146998Sdes#endif
1472146998Sdes
1473126274Sdesint
1474126274Sdesinteractive_loop(int fd_in, int fd_out, char *file1, char *file2)
1475126274Sdes{
1476126274Sdes	char *pwd;
1477126274Sdes	char *dir = NULL;
1478126274Sdes	char cmd[2048];
1479126274Sdes	struct sftp_conn *conn;
1480149749Sdes	int err, interactive;
1481146998Sdes	EditLine *el = NULL;
1482146998Sdes#ifdef USE_LIBEDIT
1483146998Sdes	History *hl = NULL;
1484146998Sdes	HistEvent hev;
1485146998Sdes	extern char *__progname;
1486126274Sdes
1487146998Sdes	if (!batchmode && isatty(STDIN_FILENO)) {
1488146998Sdes		if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1489146998Sdes			fatal("Couldn't initialise editline");
1490146998Sdes		if ((hl = history_init()) == NULL)
1491146998Sdes			fatal("Couldn't initialise editline history");
1492146998Sdes		history(hl, &hev, H_SETSIZE, 100);
1493146998Sdes		el_set(el, EL_HIST, history, hl);
1494146998Sdes
1495146998Sdes		el_set(el, EL_PROMPT, prompt);
1496146998Sdes		el_set(el, EL_EDITOR, "emacs");
1497146998Sdes		el_set(el, EL_TERMINAL, NULL);
1498146998Sdes		el_set(el, EL_SIGNAL, 1);
1499146998Sdes		el_source(el, NULL);
1500146998Sdes	}
1501146998Sdes#endif /* USE_LIBEDIT */
1502146998Sdes
1503126274Sdes	conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
1504126274Sdes	if (conn == NULL)
1505126274Sdes		fatal("Couldn't initialise connection to server");
1506126274Sdes
1507126274Sdes	pwd = do_realpath(conn, ".");
1508126274Sdes	if (pwd == NULL)
1509126274Sdes		fatal("Need cwd");
1510126274Sdes
1511126274Sdes	if (file1 != NULL) {
1512126274Sdes		dir = xstrdup(file1);
1513126274Sdes		dir = make_absolute(dir, pwd);
1514126274Sdes
1515126274Sdes		if (remote_is_dir(conn, dir) && file2 == NULL) {
1516126274Sdes			printf("Changing to: %s\n", dir);
1517126274Sdes			snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
1518146998Sdes			if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) {
1519146998Sdes				xfree(dir);
1520146998Sdes				xfree(pwd);
1521162852Sdes				xfree(conn);
1522126274Sdes				return (-1);
1523146998Sdes			}
1524126274Sdes		} else {
1525126274Sdes			if (file2 == NULL)
1526126274Sdes				snprintf(cmd, sizeof cmd, "get %s", dir);
1527126274Sdes			else
1528126274Sdes				snprintf(cmd, sizeof cmd, "get %s %s", dir,
1529126274Sdes				    file2);
1530126274Sdes
1531126274Sdes			err = parse_dispatch_command(conn, cmd, &pwd, 1);
1532126274Sdes			xfree(dir);
1533126274Sdes			xfree(pwd);
1534162852Sdes			xfree(conn);
1535126274Sdes			return (err);
1536126274Sdes		}
1537126274Sdes		xfree(dir);
1538126274Sdes	}
1539126274Sdes
1540149749Sdes#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
1541126274Sdes	setvbuf(stdout, NULL, _IOLBF, 0);
1542126274Sdes	setvbuf(infile, NULL, _IOLBF, 0);
1543126274Sdes#else
1544149749Sdes	setlinebuf(stdout);
1545149749Sdes	setlinebuf(infile);
1546126274Sdes#endif
1547126274Sdes
1548149749Sdes	interactive = !batchmode && isatty(STDIN_FILENO);
1549126274Sdes	err = 0;
1550126274Sdes	for (;;) {
1551126274Sdes		char *cp;
1552126274Sdes
1553137015Sdes		signal(SIGINT, SIG_IGN);
1554137015Sdes
1555146998Sdes		if (el == NULL) {
1556149749Sdes			if (interactive)
1557149749Sdes				printf("sftp> ");
1558146998Sdes			if (fgets(cmd, sizeof(cmd), infile) == NULL) {
1559149749Sdes				if (interactive)
1560149749Sdes					printf("\n");
1561146998Sdes				break;
1562146998Sdes			}
1563149749Sdes			if (!interactive) { /* Echo command */
1564149749Sdes				printf("sftp> %s", cmd);
1565149749Sdes				if (strlen(cmd) > 0 &&
1566149749Sdes				    cmd[strlen(cmd) - 1] != '\n')
1567149749Sdes					printf("\n");
1568149749Sdes			}
1569146998Sdes		} else {
1570146998Sdes#ifdef USE_LIBEDIT
1571146998Sdes			const char *line;
1572146998Sdes			int count = 0;
1573126274Sdes
1574149749Sdes			if ((line = el_gets(el, &count)) == NULL || count <= 0) {
1575149749Sdes				printf("\n");
1576149749Sdes 				break;
1577149749Sdes			}
1578146998Sdes			history(hl, &hev, H_ENTER, line);
1579146998Sdes			if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1580146998Sdes				fprintf(stderr, "Error: input line too long\n");
1581146998Sdes				continue;
1582146998Sdes			}
1583146998Sdes#endif /* USE_LIBEDIT */
1584126274Sdes		}
1585126274Sdes
1586126274Sdes		cp = strrchr(cmd, '\n');
1587126274Sdes		if (cp)
1588126274Sdes			*cp = '\0';
1589126274Sdes
1590137015Sdes		/* Handle user interrupts gracefully during commands */
1591137015Sdes		interrupted = 0;
1592137015Sdes		signal(SIGINT, cmd_interrupt);
1593137015Sdes
1594126274Sdes		err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
1595126274Sdes		if (err != 0)
1596126274Sdes			break;
1597126274Sdes	}
1598126274Sdes	xfree(pwd);
1599162852Sdes	xfree(conn);
1600126274Sdes
1601149749Sdes#ifdef USE_LIBEDIT
1602149749Sdes	if (el != NULL)
1603149749Sdes		el_end(el);
1604149749Sdes#endif /* USE_LIBEDIT */
1605149749Sdes
1606126274Sdes	/* err == 1 signifies normal "quit" exit */
1607126274Sdes	return (err >= 0 ? 0 : -1);
1608126274Sdes}
1609126274Sdes
1610126274Sdesstatic void
1611124208Sdesconnect_to_server(char *path, char **args, int *in, int *out)
1612124208Sdes{
161376259Sgreen	int c_in, c_out;
161499060Sdes
161576259Sgreen#ifdef USE_PIPES
161676259Sgreen	int pin[2], pout[2];
161799060Sdes
161876259Sgreen	if ((pipe(pin) == -1) || (pipe(pout) == -1))
161976259Sgreen		fatal("pipe: %s", strerror(errno));
162076259Sgreen	*in = pin[0];
162176259Sgreen	*out = pout[1];
162276259Sgreen	c_in = pout[0];
162376259Sgreen	c_out = pin[1];
162476259Sgreen#else /* USE_PIPES */
162576259Sgreen	int inout[2];
162699060Sdes
162776259Sgreen	if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
162876259Sgreen		fatal("socketpair: %s", strerror(errno));
162976259Sgreen	*in = *out = inout[0];
163076259Sgreen	c_in = c_out = inout[1];
163176259Sgreen#endif /* USE_PIPES */
163276259Sgreen
1633124208Sdes	if ((sshpid = fork()) == -1)
163476259Sgreen		fatal("fork: %s", strerror(errno));
1635124208Sdes	else if (sshpid == 0) {
163676259Sgreen		if ((dup2(c_in, STDIN_FILENO) == -1) ||
163776259Sgreen		    (dup2(c_out, STDOUT_FILENO) == -1)) {
163876259Sgreen			fprintf(stderr, "dup2: %s\n", strerror(errno));
1639137015Sdes			_exit(1);
164076259Sgreen		}
164176259Sgreen		close(*in);
164276259Sgreen		close(*out);
164376259Sgreen		close(c_in);
164476259Sgreen		close(c_out);
1645137015Sdes
1646137015Sdes		/*
1647137015Sdes		 * The underlying ssh is in the same process group, so we must
1648137015Sdes		 * ignore SIGINT if we want to gracefully abort commands,
1649137015Sdes		 * otherwise the signal will make it to the ssh process and
1650137015Sdes		 * kill it too
1651137015Sdes		 */
1652137015Sdes		signal(SIGINT, SIG_IGN);
1653137015Sdes		execvp(path, args);
165492555Sdes		fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
1655137015Sdes		_exit(1);
165676259Sgreen	}
165776259Sgreen
1658124208Sdes	signal(SIGTERM, killchild);
1659124208Sdes	signal(SIGINT, killchild);
1660124208Sdes	signal(SIGHUP, killchild);
166176259Sgreen	close(c_in);
166276259Sgreen	close(c_out);
166376259Sgreen}
166476259Sgreen
166592555Sdesstatic void
166676259Sgreenusage(void)
166776259Sgreen{
166892555Sdes	extern char *__progname;
166998675Sdes
167092555Sdes	fprintf(stderr,
1671126274Sdes	    "usage: %s [-1Cv] [-B buffer_size] [-b batchfile] [-F ssh_config]\n"
1672126274Sdes	    "            [-o ssh_option] [-P sftp_server_path] [-R num_requests]\n"
1673126274Sdes	    "            [-S program] [-s subsystem | sftp_server] host\n"
1674192595Sdes	    "       %s [user@]host[:file ...]\n"
1675192595Sdes	    "       %s [user@]host[:dir[/]]\n"
1676126274Sdes	    "       %s -b batchfile [user@]host\n", __progname, __progname, __progname, __progname);
167776259Sgreen	exit(1);
167876259Sgreen}
167976259Sgreen
168076259Sgreenint
168176259Sgreenmain(int argc, char **argv)
168276259Sgreen{
1683113908Sdes	int in, out, ch, err;
1684137015Sdes	char *host, *userhost, *cp, *file2 = NULL;
168592555Sdes	int debug_level = 0, sshver = 2;
168692555Sdes	char *file1 = NULL, *sftp_server = NULL;
168792555Sdes	char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
168892555Sdes	LogLevel ll = SYSLOG_LEVEL_INFO;
168992555Sdes	arglist args;
169076259Sgreen	extern int optind;
169176259Sgreen	extern char *optarg;
169276259Sgreen
1693157016Sdes	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
1694157016Sdes	sanitise_stdfd();
1695157016Sdes
1696124208Sdes	__progname = ssh_get_progname(argv[0]);
1697157016Sdes	memset(&args, '\0', sizeof(args));
169892555Sdes	args.list = NULL;
1699162852Sdes	addargs(&args, "%s", ssh_program);
170092555Sdes	addargs(&args, "-oForwardX11 no");
170192555Sdes	addargs(&args, "-oForwardAgent no");
1702157016Sdes	addargs(&args, "-oPermitLocalCommand no");
170392555Sdes	addargs(&args, "-oClearAllForwardings yes");
1704126274Sdes
170592555Sdes	ll = SYSLOG_LEVEL_INFO;
1706126274Sdes	infile = stdin;
170776259Sgreen
170892555Sdes	while ((ch = getopt(argc, argv, "1hvCo:s:S:b:B:F:P:R:")) != -1) {
170976259Sgreen		switch (ch) {
171076259Sgreen		case 'C':
171192555Sdes			addargs(&args, "-C");
171276259Sgreen			break;
171376259Sgreen		case 'v':
171492555Sdes			if (debug_level < 3) {
171592555Sdes				addargs(&args, "-v");
171692555Sdes				ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
171792555Sdes			}
171892555Sdes			debug_level++;
171976259Sgreen			break;
172092555Sdes		case 'F':
172176259Sgreen		case 'o':
172292555Sdes			addargs(&args, "-%c%s", ch, optarg);
172376259Sgreen			break;
172476259Sgreen		case '1':
172592555Sdes			sshver = 1;
172676259Sgreen			if (sftp_server == NULL)
172776259Sgreen				sftp_server = _PATH_SFTP_SERVER;
172876259Sgreen			break;
172976259Sgreen		case 's':
173076259Sgreen			sftp_server = optarg;
173176259Sgreen			break;
173276259Sgreen		case 'S':
173376259Sgreen			ssh_program = optarg;
1734157016Sdes			replacearg(&args, 0, "%s", ssh_program);
173576259Sgreen			break;
173676259Sgreen		case 'b':
1737126274Sdes			if (batchmode)
1738126274Sdes				fatal("Batch file already specified.");
1739126274Sdes
1740126274Sdes			/* Allow "-" as stdin */
1741137015Sdes			if (strcmp(optarg, "-") != 0 &&
1742149749Sdes			    (infile = fopen(optarg, "r")) == NULL)
1743126274Sdes				fatal("%s (%s).", strerror(errno), optarg);
1744113908Sdes			showprogress = 0;
1745126274Sdes			batchmode = 1;
1746146998Sdes			addargs(&args, "-obatchmode yes");
174776259Sgreen			break;
174892555Sdes		case 'P':
174992555Sdes			sftp_direct = optarg;
175092555Sdes			break;
175192555Sdes		case 'B':
175292555Sdes			copy_buffer_len = strtol(optarg, &cp, 10);
175392555Sdes			if (copy_buffer_len == 0 || *cp != '\0')
175492555Sdes				fatal("Invalid buffer size \"%s\"", optarg);
175592555Sdes			break;
175692555Sdes		case 'R':
175792555Sdes			num_requests = strtol(optarg, &cp, 10);
175892555Sdes			if (num_requests == 0 || *cp != '\0')
175998675Sdes				fatal("Invalid number of requests \"%s\"",
176092555Sdes				    optarg);
176192555Sdes			break;
176276259Sgreen		case 'h':
176376259Sgreen		default:
176476259Sgreen			usage();
176576259Sgreen		}
176676259Sgreen	}
176776259Sgreen
1768128456Sdes	if (!isatty(STDERR_FILENO))
1769128456Sdes		showprogress = 0;
1770128456Sdes
177198675Sdes	log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
177298675Sdes
177392555Sdes	if (sftp_direct == NULL) {
177492555Sdes		if (optind == argc || argc > (optind + 2))
177592555Sdes			usage();
177676259Sgreen
177792555Sdes		userhost = xstrdup(argv[optind]);
177892555Sdes		file2 = argv[optind+1];
177976259Sgreen
1780113908Sdes		if ((host = strrchr(userhost, '@')) == NULL)
178192555Sdes			host = userhost;
178292555Sdes		else {
178392555Sdes			*host++ = '\0';
178492555Sdes			if (!userhost[0]) {
178592555Sdes				fprintf(stderr, "Missing username\n");
178692555Sdes				usage();
178792555Sdes			}
1788181111Sdes			addargs(&args, "-l%s", userhost);
178992555Sdes		}
179092555Sdes
1791126274Sdes		if ((cp = colon(host)) != NULL) {
1792126274Sdes			*cp++ = '\0';
1793126274Sdes			file1 = cp;
1794126274Sdes		}
1795126274Sdes
179692555Sdes		host = cleanhostname(host);
179792555Sdes		if (!*host) {
179892555Sdes			fprintf(stderr, "Missing hostname\n");
179976259Sgreen			usage();
180076259Sgreen		}
180176259Sgreen
180292555Sdes		addargs(&args, "-oProtocol %d", sshver);
180376259Sgreen
180492555Sdes		/* no subsystem if the server-spec contains a '/' */
180592555Sdes		if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
180692555Sdes			addargs(&args, "-s");
180776259Sgreen
180892555Sdes		addargs(&args, "%s", host);
180998675Sdes		addargs(&args, "%s", (sftp_server != NULL ?
181092555Sdes		    sftp_server : "sftp"));
181176259Sgreen
1812126274Sdes		if (!batchmode)
1813126274Sdes			fprintf(stderr, "Connecting to %s...\n", host);
1814124208Sdes		connect_to_server(ssh_program, args.list, &in, &out);
181592555Sdes	} else {
181692555Sdes		args.list = NULL;
181792555Sdes		addargs(&args, "sftp-server");
181876259Sgreen
1819126274Sdes		if (!batchmode)
1820126274Sdes			fprintf(stderr, "Attaching to %s...\n", sftp_direct);
1821124208Sdes		connect_to_server(sftp_direct, args.list, &in, &out);
182292555Sdes	}
1823157016Sdes	freeargs(&args);
182476259Sgreen
1825113908Sdes	err = interactive_loop(in, out, file1, file2);
182676259Sgreen
182798937Sdes#if !defined(USE_PIPES)
1828149749Sdes	shutdown(in, SHUT_RDWR);
1829149749Sdes	shutdown(out, SHUT_RDWR);
183098937Sdes#endif
183198937Sdes
183276259Sgreen	close(in);
183376259Sgreen	close(out);
1834126274Sdes	if (batchmode)
183576259Sgreen		fclose(infile);
183676259Sgreen
183798675Sdes	while (waitpid(sshpid, NULL, 0) == -1)
183898675Sdes		if (errno != EINTR)
183998675Sdes			fatal("Couldn't wait for ssh process: %s",
184098675Sdes			    strerror(errno));
184176259Sgreen
1842113908Sdes	exit(err == 0 ? 0 : 1);
184376259Sgreen}
1844