sftp.c revision 162852
1162852Sdes/* $OpenBSD: sftp.c,v 1.92 2006/09/19 05:52:23 otto 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>
2876259Sgreen
29162852Sdes#include <errno.h>
30162852Sdes
31162852Sdes#ifdef HAVE_PATHS_H
32162852Sdes# include <paths.h>
33162852Sdes#endif
34146998Sdes#ifdef USE_LIBEDIT
35146998Sdes#include <histedit.h>
36146998Sdes#else
37146998Sdestypedef void EditLine;
38146998Sdes#endif
39162852Sdes#include <signal.h>
40162852Sdes#include <stdlib.h>
41162852Sdes#include <stdio.h>
42162852Sdes#include <string.h>
43162852Sdes#include <unistd.h>
44162852Sdes#include <stdarg.h>
45146998Sdes
4676259Sgreen#include "xmalloc.h"
4776259Sgreen#include "log.h"
4876259Sgreen#include "pathnames.h"
4992555Sdes#include "misc.h"
5076259Sgreen
5176259Sgreen#include "sftp.h"
52162852Sdes#include "buffer.h"
5376259Sgreen#include "sftp-common.h"
5476259Sgreen#include "sftp-client.h"
5576259Sgreen
56126274Sdes/* File to read commands from */
57126274SdesFILE* infile;
58126274Sdes
59126274Sdes/* Are we in batchfile mode? */
60126274Sdesint batchmode = 0;
61126274Sdes
62126274Sdes/* Size of buffer used when copying files */
63126274Sdessize_t copy_buffer_len = 32768;
64126274Sdes
65126274Sdes/* Number of concurrent outstanding requests */
66126274Sdessize_t num_requests = 16;
67126274Sdes
68126274Sdes/* PID of ssh transport process */
69126274Sdesstatic pid_t sshpid = -1;
70126274Sdes
71126274Sdes/* This is set to 0 if the progressmeter is not desired. */
72128456Sdesint showprogress = 1;
73126274Sdes
74137015Sdes/* SIGINT received during command processing */
75137015Sdesvolatile sig_atomic_t interrupted = 0;
76137015Sdes
77137015Sdes/* I wish qsort() took a separate ctx for the comparison function...*/
78137015Sdesint sort_flag;
79137015Sdes
80126274Sdesint remote_glob(struct sftp_conn *, const char *, int,
81126274Sdes    int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
82126274Sdes
8398937Sdesextern char *__progname;
8498937Sdes
85126274Sdes/* Separators for interactive commands */
86126274Sdes#define WHITESPACE " \t\r\n"
8776259Sgreen
88137015Sdes/* ls flags */
89137015Sdes#define LS_LONG_VIEW	0x01	/* Full view ala ls -l */
90137015Sdes#define LS_SHORT_VIEW	0x02	/* Single row view ala ls -1 */
91137015Sdes#define LS_NUMERIC_VIEW	0x04	/* Long view with numeric uid/gid */
92137015Sdes#define LS_NAME_SORT	0x08	/* Sort by name (default) */
93137015Sdes#define LS_TIME_SORT	0x10	/* Sort by mtime */
94137015Sdes#define LS_SIZE_SORT	0x20	/* Sort by file size */
95137015Sdes#define LS_REVERSE_SORT	0x40	/* Reverse sort order */
96137015Sdes#define LS_SHOW_ALL	0x80	/* Don't skip filenames starting with '.' */
97113908Sdes
98137015Sdes#define VIEW_FLAGS	(LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW)
99137015Sdes#define SORT_FLAGS	(LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
100137015Sdes
101126274Sdes/* Commands for interactive mode */
102126274Sdes#define I_CHDIR		1
103126274Sdes#define I_CHGRP		2
104126274Sdes#define I_CHMOD		3
105126274Sdes#define I_CHOWN		4
106126274Sdes#define I_GET		5
107126274Sdes#define I_HELP		6
108126274Sdes#define I_LCHDIR	7
109126274Sdes#define I_LLS		8
110126274Sdes#define I_LMKDIR	9
111126274Sdes#define I_LPWD		10
112126274Sdes#define I_LS		11
113126274Sdes#define I_LUMASK	12
114126274Sdes#define I_MKDIR		13
115126274Sdes#define I_PUT		14
116126274Sdes#define I_PWD		15
117126274Sdes#define I_QUIT		16
118126274Sdes#define I_RENAME	17
119126274Sdes#define I_RM		18
120126274Sdes#define I_RMDIR		19
121126274Sdes#define I_SHELL		20
122126274Sdes#define I_SYMLINK	21
123126274Sdes#define I_VERSION	22
124126274Sdes#define I_PROGRESS	23
125126274Sdes
126126274Sdesstruct CMD {
127126274Sdes	const char *c;
128126274Sdes	const int n;
129126274Sdes};
130126274Sdes
131126274Sdesstatic const struct CMD cmds[] = {
132126274Sdes	{ "bye",	I_QUIT },
133126274Sdes	{ "cd",		I_CHDIR },
134126274Sdes	{ "chdir",	I_CHDIR },
135126274Sdes	{ "chgrp",	I_CHGRP },
136126274Sdes	{ "chmod",	I_CHMOD },
137126274Sdes	{ "chown",	I_CHOWN },
138126274Sdes	{ "dir",	I_LS },
139126274Sdes	{ "exit",	I_QUIT },
140126274Sdes	{ "get",	I_GET },
141126274Sdes	{ "mget",	I_GET },
142126274Sdes	{ "help",	I_HELP },
143126274Sdes	{ "lcd",	I_LCHDIR },
144126274Sdes	{ "lchdir",	I_LCHDIR },
145126274Sdes	{ "lls",	I_LLS },
146126274Sdes	{ "lmkdir",	I_LMKDIR },
147126274Sdes	{ "ln",		I_SYMLINK },
148126274Sdes	{ "lpwd",	I_LPWD },
149126274Sdes	{ "ls",		I_LS },
150126274Sdes	{ "lumask",	I_LUMASK },
151126274Sdes	{ "mkdir",	I_MKDIR },
152126274Sdes	{ "progress",	I_PROGRESS },
153126274Sdes	{ "put",	I_PUT },
154126274Sdes	{ "mput",	I_PUT },
155126274Sdes	{ "pwd",	I_PWD },
156126274Sdes	{ "quit",	I_QUIT },
157126274Sdes	{ "rename",	I_RENAME },
158126274Sdes	{ "rm",		I_RM },
159126274Sdes	{ "rmdir",	I_RMDIR },
160126274Sdes	{ "symlink",	I_SYMLINK },
161126274Sdes	{ "version",	I_VERSION },
162126274Sdes	{ "!",		I_SHELL },
163126274Sdes	{ "?",		I_HELP },
164126274Sdes	{ NULL,			-1}
165126274Sdes};
166126274Sdes
167126274Sdesint interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
168126274Sdes
16992555Sdesstatic void
170137015Sdeskillchild(int signo)
171137015Sdes{
172146998Sdes	if (sshpid > 1) {
173137015Sdes		kill(sshpid, SIGTERM);
174146998Sdes		waitpid(sshpid, NULL, 0);
175146998Sdes	}
176137015Sdes
177137015Sdes	_exit(1);
178137015Sdes}
179137015Sdes
180137015Sdesstatic void
181137015Sdescmd_interrupt(int signo)
182137015Sdes{
183137015Sdes	const char msg[] = "\rInterrupt  \n";
184146998Sdes	int olderrno = errno;
185137015Sdes
186137015Sdes	write(STDERR_FILENO, msg, sizeof(msg) - 1);
187137015Sdes	interrupted = 1;
188146998Sdes	errno = olderrno;
189137015Sdes}
190137015Sdes
191137015Sdesstatic void
192126274Sdeshelp(void)
193126274Sdes{
194126274Sdes	printf("Available commands:\n");
195126274Sdes	printf("cd path                       Change remote directory to 'path'\n");
196126274Sdes	printf("lcd path                      Change local directory to 'path'\n");
197126274Sdes	printf("chgrp grp path                Change group of file 'path' to 'grp'\n");
198126274Sdes	printf("chmod mode path               Change permissions of file 'path' to 'mode'\n");
199126274Sdes	printf("chown own path                Change owner of file 'path' to 'own'\n");
200126274Sdes	printf("help                          Display this help text\n");
201126274Sdes	printf("get remote-path [local-path]  Download file\n");
202126274Sdes	printf("lls [ls-options [path]]       Display local directory listing\n");
203126274Sdes	printf("ln oldpath newpath            Symlink remote file\n");
204126274Sdes	printf("lmkdir path                   Create local directory\n");
205126274Sdes	printf("lpwd                          Print local working directory\n");
206126274Sdes	printf("ls [path]                     Display remote directory listing\n");
207126274Sdes	printf("lumask umask                  Set local umask to 'umask'\n");
208126274Sdes	printf("mkdir path                    Create remote directory\n");
209126274Sdes	printf("progress                      Toggle display of progress meter\n");
210126274Sdes	printf("put local-path [remote-path]  Upload file\n");
211126274Sdes	printf("pwd                           Display remote working directory\n");
212126274Sdes	printf("exit                          Quit sftp\n");
213126274Sdes	printf("quit                          Quit sftp\n");
214126274Sdes	printf("rename oldpath newpath        Rename remote file\n");
215126274Sdes	printf("rmdir path                    Remove remote directory\n");
216126274Sdes	printf("rm path                       Delete remote file\n");
217126274Sdes	printf("symlink oldpath newpath       Symlink remote file\n");
218126274Sdes	printf("version                       Show SFTP version\n");
219126274Sdes	printf("!command                      Execute 'command' in local shell\n");
220126274Sdes	printf("!                             Escape to local shell\n");
221126274Sdes	printf("?                             Synonym for help\n");
222126274Sdes}
223126274Sdes
224126274Sdesstatic void
225126274Sdeslocal_do_shell(const char *args)
226126274Sdes{
227126274Sdes	int status;
228126274Sdes	char *shell;
229126274Sdes	pid_t pid;
230126274Sdes
231126274Sdes	if (!*args)
232126274Sdes		args = NULL;
233126274Sdes
234126274Sdes	if ((shell = getenv("SHELL")) == NULL)
235126274Sdes		shell = _PATH_BSHELL;
236126274Sdes
237126274Sdes	if ((pid = fork()) == -1)
238126274Sdes		fatal("Couldn't fork: %s", strerror(errno));
239126274Sdes
240126274Sdes	if (pid == 0) {
241126274Sdes		/* XXX: child has pipe fds to ssh subproc open - issue? */
242126274Sdes		if (args) {
243126274Sdes			debug3("Executing %s -c \"%s\"", shell, args);
244126274Sdes			execl(shell, shell, "-c", args, (char *)NULL);
245126274Sdes		} else {
246126274Sdes			debug3("Executing %s", shell);
247126274Sdes			execl(shell, shell, (char *)NULL);
248126274Sdes		}
249126274Sdes		fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
250126274Sdes		    strerror(errno));
251126274Sdes		_exit(1);
252126274Sdes	}
253126274Sdes	while (waitpid(pid, &status, 0) == -1)
254126274Sdes		if (errno != EINTR)
255126274Sdes			fatal("Couldn't wait for child: %s", strerror(errno));
256126274Sdes	if (!WIFEXITED(status))
257162852Sdes		error("Shell exited abnormally");
258126274Sdes	else if (WEXITSTATUS(status))
259126274Sdes		error("Shell exited with status %d", WEXITSTATUS(status));
260126274Sdes}
261126274Sdes
262126274Sdesstatic void
263126274Sdeslocal_do_ls(const char *args)
264126274Sdes{
265126274Sdes	if (!args || !*args)
266126274Sdes		local_do_shell(_PATH_LS);
267126274Sdes	else {
268126274Sdes		int len = strlen(_PATH_LS " ") + strlen(args) + 1;
269126274Sdes		char *buf = xmalloc(len);
270126274Sdes
271126274Sdes		/* XXX: quoting - rip quoting code from ftp? */
272126274Sdes		snprintf(buf, len, _PATH_LS " %s", args);
273126274Sdes		local_do_shell(buf);
274126274Sdes		xfree(buf);
275126274Sdes	}
276126274Sdes}
277126274Sdes
278126274Sdes/* Strip one path (usually the pwd) from the start of another */
279126274Sdesstatic char *
280126274Sdespath_strip(char *path, char *strip)
281126274Sdes{
282126274Sdes	size_t len;
283126274Sdes
284126274Sdes	if (strip == NULL)
285126274Sdes		return (xstrdup(path));
286126274Sdes
287126274Sdes	len = strlen(strip);
288146998Sdes	if (strncmp(path, strip, len) == 0) {
289126274Sdes		if (strip[len - 1] != '/' && path[len] == '/')
290126274Sdes			len++;
291126274Sdes		return (xstrdup(path + len));
292126274Sdes	}
293126274Sdes
294126274Sdes	return (xstrdup(path));
295126274Sdes}
296126274Sdes
297126274Sdesstatic char *
298126274Sdespath_append(char *p1, char *p2)
299126274Sdes{
300126274Sdes	char *ret;
301126274Sdes	int len = strlen(p1) + strlen(p2) + 2;
302126274Sdes
303126274Sdes	ret = xmalloc(len);
304126274Sdes	strlcpy(ret, p1, len);
305126274Sdes	if (p1[strlen(p1) - 1] != '/')
306126274Sdes		strlcat(ret, "/", len);
307126274Sdes	strlcat(ret, p2, len);
308126274Sdes
309126274Sdes	return(ret);
310126274Sdes}
311126274Sdes
312126274Sdesstatic char *
313126274Sdesmake_absolute(char *p, char *pwd)
314126274Sdes{
315137015Sdes	char *abs_str;
316126274Sdes
317126274Sdes	/* Derelativise */
318126274Sdes	if (p && p[0] != '/') {
319137015Sdes		abs_str = path_append(pwd, p);
320126274Sdes		xfree(p);
321137015Sdes		return(abs_str);
322126274Sdes	} else
323126274Sdes		return(p);
324126274Sdes}
325126274Sdes
326126274Sdesstatic int
327126274Sdesinfer_path(const char *p, char **ifp)
328126274Sdes{
329126274Sdes	char *cp;
330126274Sdes
331126274Sdes	cp = strrchr(p, '/');
332126274Sdes	if (cp == NULL) {
333126274Sdes		*ifp = xstrdup(p);
334126274Sdes		return(0);
335126274Sdes	}
336126274Sdes
337126274Sdes	if (!cp[1]) {
338126274Sdes		error("Invalid path");
339126274Sdes		return(-1);
340126274Sdes	}
341126274Sdes
342126274Sdes	*ifp = xstrdup(cp + 1);
343126274Sdes	return(0);
344126274Sdes}
345126274Sdes
346126274Sdesstatic int
347126274Sdesparse_getput_flags(const char **cpp, int *pflag)
348126274Sdes{
349126274Sdes	const char *cp = *cpp;
350126274Sdes
351126274Sdes	/* Check for flags */
352126274Sdes	if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
353126274Sdes		switch (cp[1]) {
354126274Sdes		case 'p':
355126274Sdes		case 'P':
356126274Sdes			*pflag = 1;
357126274Sdes			break;
358126274Sdes		default:
359126274Sdes			error("Invalid flag -%c", cp[1]);
360126274Sdes			return(-1);
361126274Sdes		}
362126274Sdes		cp += 2;
363126274Sdes		*cpp = cp + strspn(cp, WHITESPACE);
364126274Sdes	}
365126274Sdes
366126274Sdes	return(0);
367126274Sdes}
368126274Sdes
369126274Sdesstatic int
370126274Sdesparse_ls_flags(const char **cpp, int *lflag)
371126274Sdes{
372126274Sdes	const char *cp = *cpp;
373126274Sdes
374137015Sdes	/* Defaults */
375137015Sdes	*lflag = LS_NAME_SORT;
376137015Sdes
377126274Sdes	/* Check for flags */
378126274Sdes	if (cp++[0] == '-') {
379147001Sdes		for (; strchr(WHITESPACE, *cp) == NULL; cp++) {
380126274Sdes			switch (*cp) {
381126274Sdes			case 'l':
382137015Sdes				*lflag &= ~VIEW_FLAGS;
383137015Sdes				*lflag |= LS_LONG_VIEW;
384126274Sdes				break;
385126274Sdes			case '1':
386137015Sdes				*lflag &= ~VIEW_FLAGS;
387137015Sdes				*lflag |= LS_SHORT_VIEW;
388126274Sdes				break;
389137015Sdes			case 'n':
390137015Sdes				*lflag &= ~VIEW_FLAGS;
391137015Sdes				*lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
392137015Sdes				break;
393137015Sdes			case 'S':
394137015Sdes				*lflag &= ~SORT_FLAGS;
395137015Sdes				*lflag |= LS_SIZE_SORT;
396137015Sdes				break;
397137015Sdes			case 't':
398137015Sdes				*lflag &= ~SORT_FLAGS;
399137015Sdes				*lflag |= LS_TIME_SORT;
400137015Sdes				break;
401137015Sdes			case 'r':
402137015Sdes				*lflag |= LS_REVERSE_SORT;
403137015Sdes				break;
404137015Sdes			case 'f':
405137015Sdes				*lflag &= ~SORT_FLAGS;
406137015Sdes				break;
407137015Sdes			case 'a':
408137015Sdes				*lflag |= LS_SHOW_ALL;
409137015Sdes				break;
410126274Sdes			default:
411126274Sdes				error("Invalid flag -%c", *cp);
412126274Sdes				return(-1);
413126274Sdes			}
414126274Sdes		}
415126274Sdes		*cpp = cp + strspn(cp, WHITESPACE);
416126274Sdes	}
417126274Sdes
418126274Sdes	return(0);
419126274Sdes}
420126274Sdes
421126274Sdesstatic int
422126274Sdesget_pathname(const char **cpp, char **path)
423126274Sdes{
424126274Sdes	const char *cp = *cpp, *end;
425126274Sdes	char quot;
426149749Sdes	u_int i, j;
427126274Sdes
428126274Sdes	cp += strspn(cp, WHITESPACE);
429126274Sdes	if (!*cp) {
430126274Sdes		*cpp = cp;
431126274Sdes		*path = NULL;
432126274Sdes		return (0);
433126274Sdes	}
434126274Sdes
435126274Sdes	*path = xmalloc(strlen(cp) + 1);
436126274Sdes
437126274Sdes	/* Check for quoted filenames */
438126274Sdes	if (*cp == '\"' || *cp == '\'') {
439126274Sdes		quot = *cp++;
440126274Sdes
441126274Sdes		/* Search for terminating quote, unescape some chars */
442126274Sdes		for (i = j = 0; i <= strlen(cp); i++) {
443126274Sdes			if (cp[i] == quot) {	/* Found quote */
444126274Sdes				i++;
445126274Sdes				(*path)[j] = '\0';
446126274Sdes				break;
447126274Sdes			}
448126274Sdes			if (cp[i] == '\0') {	/* End of string */
449126274Sdes				error("Unterminated quote");
450126274Sdes				goto fail;
451126274Sdes			}
452126274Sdes			if (cp[i] == '\\') {	/* Escaped characters */
453126274Sdes				i++;
454126274Sdes				if (cp[i] != '\'' && cp[i] != '\"' &&
455126274Sdes				    cp[i] != '\\') {
456137015Sdes					error("Bad escaped character '\\%c'",
457126274Sdes					    cp[i]);
458126274Sdes					goto fail;
459126274Sdes				}
460126274Sdes			}
461126274Sdes			(*path)[j++] = cp[i];
462126274Sdes		}
463126274Sdes
464126274Sdes		if (j == 0) {
465126274Sdes			error("Empty quotes");
466126274Sdes			goto fail;
467126274Sdes		}
468126274Sdes		*cpp = cp + i + strspn(cp + i, WHITESPACE);
469126274Sdes	} else {
470126274Sdes		/* Read to end of filename */
471126274Sdes		end = strpbrk(cp, WHITESPACE);
472126274Sdes		if (end == NULL)
473126274Sdes			end = strchr(cp, '\0');
474126274Sdes		*cpp = end + strspn(end, WHITESPACE);
475126274Sdes
476126274Sdes		memcpy(*path, cp, end - cp);
477126274Sdes		(*path)[end - cp] = '\0';
478126274Sdes	}
479126274Sdes	return (0);
480126274Sdes
481126274Sdes fail:
482126274Sdes	xfree(*path);
483126274Sdes	*path = NULL;
484126274Sdes	return (-1);
485126274Sdes}
486126274Sdes
487126274Sdesstatic int
488126274Sdesis_dir(char *path)
489126274Sdes{
490126274Sdes	struct stat sb;
491126274Sdes
492126274Sdes	/* XXX: report errors? */
493126274Sdes	if (stat(path, &sb) == -1)
494126274Sdes		return(0);
495126274Sdes
496162852Sdes	return(S_ISDIR(sb.st_mode));
497126274Sdes}
498126274Sdes
499126274Sdesstatic int
500126274Sdesis_reg(char *path)
501126274Sdes{
502126274Sdes	struct stat sb;
503126274Sdes
504126274Sdes	if (stat(path, &sb) == -1)
505126274Sdes		fatal("stat %s: %s", path, strerror(errno));
506126274Sdes
507126274Sdes	return(S_ISREG(sb.st_mode));
508126274Sdes}
509126274Sdes
510126274Sdesstatic int
511126274Sdesremote_is_dir(struct sftp_conn *conn, char *path)
512126274Sdes{
513126274Sdes	Attrib *a;
514126274Sdes
515126274Sdes	/* XXX: report errors? */
516126274Sdes	if ((a = do_stat(conn, path, 1)) == NULL)
517126274Sdes		return(0);
518126274Sdes	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
519126274Sdes		return(0);
520162852Sdes	return(S_ISDIR(a->perm));
521126274Sdes}
522126274Sdes
523126274Sdesstatic int
524126274Sdesprocess_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
525126274Sdes{
526126274Sdes	char *abs_src = NULL;
527126274Sdes	char *abs_dst = NULL;
528126274Sdes	char *tmp;
529126274Sdes	glob_t g;
530126274Sdes	int err = 0;
531126274Sdes	int i;
532126274Sdes
533126274Sdes	abs_src = xstrdup(src);
534126274Sdes	abs_src = make_absolute(abs_src, pwd);
535126274Sdes
536126274Sdes	memset(&g, 0, sizeof(g));
537126274Sdes	debug3("Looking up %s", abs_src);
538126274Sdes	if (remote_glob(conn, abs_src, 0, NULL, &g)) {
539126274Sdes		error("File \"%s\" not found.", abs_src);
540126274Sdes		err = -1;
541126274Sdes		goto out;
542126274Sdes	}
543126274Sdes
544126274Sdes	/* If multiple matches, dst must be a directory or unspecified */
545126274Sdes	if (g.gl_matchc > 1 && dst && !is_dir(dst)) {
546126274Sdes		error("Multiple files match, but \"%s\" is not a directory",
547126274Sdes		    dst);
548126274Sdes		err = -1;
549126274Sdes		goto out;
550126274Sdes	}
551126274Sdes
552137015Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
553126274Sdes		if (infer_path(g.gl_pathv[i], &tmp)) {
554126274Sdes			err = -1;
555126274Sdes			goto out;
556126274Sdes		}
557126274Sdes
558126274Sdes		if (g.gl_matchc == 1 && dst) {
559126274Sdes			/* If directory specified, append filename */
560162852Sdes			xfree(tmp);
561126274Sdes			if (is_dir(dst)) {
562126274Sdes				if (infer_path(g.gl_pathv[0], &tmp)) {
563126274Sdes					err = 1;
564126274Sdes					goto out;
565126274Sdes				}
566126274Sdes				abs_dst = path_append(dst, tmp);
567126274Sdes				xfree(tmp);
568126274Sdes			} else
569126274Sdes				abs_dst = xstrdup(dst);
570126274Sdes		} else if (dst) {
571126274Sdes			abs_dst = path_append(dst, tmp);
572126274Sdes			xfree(tmp);
573126274Sdes		} else
574126274Sdes			abs_dst = tmp;
575126274Sdes
576126274Sdes		printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
577126274Sdes		if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
578126274Sdes			err = -1;
579126274Sdes		xfree(abs_dst);
580126274Sdes		abs_dst = NULL;
581126274Sdes	}
582126274Sdes
583126274Sdesout:
584126274Sdes	xfree(abs_src);
585126274Sdes	globfree(&g);
586126274Sdes	return(err);
587126274Sdes}
588126274Sdes
589126274Sdesstatic int
590126274Sdesprocess_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
591126274Sdes{
592126274Sdes	char *tmp_dst = NULL;
593126274Sdes	char *abs_dst = NULL;
594126274Sdes	char *tmp;
595126274Sdes	glob_t g;
596126274Sdes	int err = 0;
597126274Sdes	int i;
598126274Sdes
599126274Sdes	if (dst) {
600126274Sdes		tmp_dst = xstrdup(dst);
601126274Sdes		tmp_dst = make_absolute(tmp_dst, pwd);
602126274Sdes	}
603126274Sdes
604126274Sdes	memset(&g, 0, sizeof(g));
605126274Sdes	debug3("Looking up %s", src);
606126274Sdes	if (glob(src, 0, NULL, &g)) {
607126274Sdes		error("File \"%s\" not found.", src);
608126274Sdes		err = -1;
609126274Sdes		goto out;
610126274Sdes	}
611126274Sdes
612126274Sdes	/* If multiple matches, dst may be directory or unspecified */
613126274Sdes	if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) {
614126274Sdes		error("Multiple files match, but \"%s\" is not a directory",
615126274Sdes		    tmp_dst);
616126274Sdes		err = -1;
617126274Sdes		goto out;
618126274Sdes	}
619126274Sdes
620137015Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
621126274Sdes		if (!is_reg(g.gl_pathv[i])) {
622126274Sdes			error("skipping non-regular file %s",
623126274Sdes			    g.gl_pathv[i]);
624126274Sdes			continue;
625126274Sdes		}
626126274Sdes		if (infer_path(g.gl_pathv[i], &tmp)) {
627126274Sdes			err = -1;
628126274Sdes			goto out;
629126274Sdes		}
630126274Sdes
631126274Sdes		if (g.gl_matchc == 1 && tmp_dst) {
632126274Sdes			/* If directory specified, append filename */
633126274Sdes			if (remote_is_dir(conn, tmp_dst)) {
634126274Sdes				if (infer_path(g.gl_pathv[0], &tmp)) {
635126274Sdes					err = 1;
636126274Sdes					goto out;
637126274Sdes				}
638126274Sdes				abs_dst = path_append(tmp_dst, tmp);
639126274Sdes				xfree(tmp);
640126274Sdes			} else
641126274Sdes				abs_dst = xstrdup(tmp_dst);
642126274Sdes
643126274Sdes		} else if (tmp_dst) {
644126274Sdes			abs_dst = path_append(tmp_dst, tmp);
645126274Sdes			xfree(tmp);
646126274Sdes		} else
647126274Sdes			abs_dst = make_absolute(tmp, pwd);
648126274Sdes
649126274Sdes		printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
650126274Sdes		if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
651126274Sdes			err = -1;
652126274Sdes	}
653126274Sdes
654126274Sdesout:
655126274Sdes	if (abs_dst)
656126274Sdes		xfree(abs_dst);
657126274Sdes	if (tmp_dst)
658126274Sdes		xfree(tmp_dst);
659126274Sdes	globfree(&g);
660126274Sdes	return(err);
661126274Sdes}
662126274Sdes
663126274Sdesstatic int
664126274Sdessdirent_comp(const void *aa, const void *bb)
665126274Sdes{
666126274Sdes	SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
667126274Sdes	SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
668137015Sdes	int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
669126274Sdes
670137015Sdes#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
671137015Sdes	if (sort_flag & LS_NAME_SORT)
672137015Sdes		return (rmul * strcmp(a->filename, b->filename));
673137015Sdes	else if (sort_flag & LS_TIME_SORT)
674137015Sdes		return (rmul * NCMP(a->a.mtime, b->a.mtime));
675137015Sdes	else if (sort_flag & LS_SIZE_SORT)
676137015Sdes		return (rmul * NCMP(a->a.size, b->a.size));
677137015Sdes
678137015Sdes	fatal("Unknown ls sort type");
679126274Sdes}
680126274Sdes
681126274Sdes/* sftp ls.1 replacement for directories */
682126274Sdesstatic int
683126274Sdesdo_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
684126274Sdes{
685149749Sdes	int n;
686149749Sdes	u_int c = 1, colspace = 0, columns = 1;
687126274Sdes	SFTP_DIRENT **d;
688126274Sdes
689126274Sdes	if ((n = do_readdir(conn, path, &d)) != 0)
690126274Sdes		return (n);
691126274Sdes
692137015Sdes	if (!(lflag & LS_SHORT_VIEW)) {
693149749Sdes		u_int m = 0, width = 80;
694126274Sdes		struct winsize ws;
695126274Sdes		char *tmp;
696126274Sdes
697126274Sdes		/* Count entries for sort and find longest filename */
698137015Sdes		for (n = 0; d[n] != NULL; n++) {
699137015Sdes			if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
700137015Sdes				m = MAX(m, strlen(d[n]->filename));
701137015Sdes		}
702126274Sdes
703126274Sdes		/* Add any subpath that also needs to be counted */
704126274Sdes		tmp = path_strip(path, strip_path);
705126274Sdes		m += strlen(tmp);
706126274Sdes		xfree(tmp);
707126274Sdes
708126274Sdes		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
709126274Sdes			width = ws.ws_col;
710126274Sdes
711126274Sdes		columns = width / (m + 2);
712126274Sdes		columns = MAX(columns, 1);
713126274Sdes		colspace = width / columns;
714126274Sdes		colspace = MIN(colspace, width);
715126274Sdes	}
716126274Sdes
717137015Sdes	if (lflag & SORT_FLAGS) {
718157016Sdes		for (n = 0; d[n] != NULL; n++)
719157016Sdes			;	/* count entries */
720137015Sdes		sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
721137015Sdes		qsort(d, n, sizeof(*d), sdirent_comp);
722137015Sdes	}
723126274Sdes
724137015Sdes	for (n = 0; d[n] != NULL && !interrupted; n++) {
725126274Sdes		char *tmp, *fname;
726126274Sdes
727137015Sdes		if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
728137015Sdes			continue;
729137015Sdes
730126274Sdes		tmp = path_append(path, d[n]->filename);
731126274Sdes		fname = path_strip(tmp, strip_path);
732126274Sdes		xfree(tmp);
733126274Sdes
734137015Sdes		if (lflag & LS_LONG_VIEW) {
735137015Sdes			if (lflag & LS_NUMERIC_VIEW) {
736137015Sdes				char *lname;
737137015Sdes				struct stat sb;
738126274Sdes
739137015Sdes				memset(&sb, 0, sizeof(sb));
740137015Sdes				attrib_to_stat(&d[n]->a, &sb);
741137015Sdes				lname = ls_file(fname, &sb, 1);
742137015Sdes				printf("%s\n", lname);
743137015Sdes				xfree(lname);
744137015Sdes			} else
745137015Sdes				printf("%s\n", d[n]->longname);
746126274Sdes		} else {
747126274Sdes			printf("%-*s", colspace, fname);
748126274Sdes			if (c >= columns) {
749126274Sdes				printf("\n");
750126274Sdes				c = 1;
751126274Sdes			} else
752126274Sdes				c++;
753126274Sdes		}
754126274Sdes
755126274Sdes		xfree(fname);
756126274Sdes	}
757126274Sdes
758137015Sdes	if (!(lflag & LS_LONG_VIEW) && (c != 1))
759126274Sdes		printf("\n");
760126274Sdes
761126274Sdes	free_sftp_dirents(d);
762126274Sdes	return (0);
763126274Sdes}
764126274Sdes
765126274Sdes/* sftp ls.1 replacement which handles path globs */
766126274Sdesstatic int
767126274Sdesdo_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
768126274Sdes    int lflag)
769126274Sdes{
770126274Sdes	glob_t g;
771149749Sdes	u_int i, c = 1, colspace = 0, columns = 1;
772146998Sdes	Attrib *a = NULL;
773126274Sdes
774126274Sdes	memset(&g, 0, sizeof(g));
775126274Sdes
776126274Sdes	if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
777146998Sdes	    NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
778146998Sdes		if (g.gl_pathc)
779146998Sdes			globfree(&g);
780126274Sdes		error("Can't ls: \"%s\" not found", path);
781126274Sdes		return (-1);
782126274Sdes	}
783126274Sdes
784137015Sdes	if (interrupted)
785137015Sdes		goto out;
786137015Sdes
787126274Sdes	/*
788146998Sdes	 * If the glob returns a single match and it is a directory,
789146998Sdes	 * then just list its contents.
790126274Sdes	 */
791146998Sdes	if (g.gl_matchc == 1) {
792146998Sdes		if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
793126274Sdes			globfree(&g);
794126274Sdes			return (-1);
795126274Sdes		}
796126274Sdes		if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
797126274Sdes		    S_ISDIR(a->perm)) {
798146998Sdes			int err;
799146998Sdes
800146998Sdes			err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
801126274Sdes			globfree(&g);
802146998Sdes			return (err);
803126274Sdes		}
804126274Sdes	}
805126274Sdes
806137015Sdes	if (!(lflag & LS_SHORT_VIEW)) {
807149749Sdes		u_int m = 0, width = 80;
808126274Sdes		struct winsize ws;
809126274Sdes
810126274Sdes		/* Count entries for sort and find longest filename */
811126274Sdes		for (i = 0; g.gl_pathv[i]; i++)
812126274Sdes			m = MAX(m, strlen(g.gl_pathv[i]));
813126274Sdes
814126274Sdes		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
815126274Sdes			width = ws.ws_col;
816126274Sdes
817126274Sdes		columns = width / (m + 2);
818126274Sdes		columns = MAX(columns, 1);
819126274Sdes		colspace = width / columns;
820126274Sdes	}
821126274Sdes
822146998Sdes	for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
823126274Sdes		char *fname;
824126274Sdes
825126274Sdes		fname = path_strip(g.gl_pathv[i], strip_path);
826126274Sdes
827137015Sdes		if (lflag & LS_LONG_VIEW) {
828126274Sdes			char *lname;
829126274Sdes			struct stat sb;
830126274Sdes
831126274Sdes			/*
832126274Sdes			 * XXX: this is slow - 1 roundtrip per path
833126274Sdes			 * A solution to this is to fork glob() and
834126274Sdes			 * build a sftp specific version which keeps the
835126274Sdes			 * attribs (which currently get thrown away)
836126274Sdes			 * that the server returns as well as the filenames.
837126274Sdes			 */
838126274Sdes			memset(&sb, 0, sizeof(sb));
839146998Sdes			if (a == NULL)
840146998Sdes				a = do_lstat(conn, g.gl_pathv[i], 1);
841126274Sdes			if (a != NULL)
842126274Sdes				attrib_to_stat(a, &sb);
843126274Sdes			lname = ls_file(fname, &sb, 1);
844126274Sdes			printf("%s\n", lname);
845126274Sdes			xfree(lname);
846126274Sdes		} else {
847126274Sdes			printf("%-*s", colspace, fname);
848126274Sdes			if (c >= columns) {
849126274Sdes				printf("\n");
850126274Sdes				c = 1;
851126274Sdes			} else
852126274Sdes				c++;
853126274Sdes		}
854126274Sdes		xfree(fname);
855126274Sdes	}
856126274Sdes
857137015Sdes	if (!(lflag & LS_LONG_VIEW) && (c != 1))
858126274Sdes		printf("\n");
859126274Sdes
860137015Sdes out:
861126274Sdes	if (g.gl_pathc)
862126274Sdes		globfree(&g);
863126274Sdes
864126274Sdes	return (0);
865126274Sdes}
866126274Sdes
867126274Sdesstatic int
868126274Sdesparse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
869126274Sdes    unsigned long *n_arg, char **path1, char **path2)
870126274Sdes{
871126274Sdes	const char *cmd, *cp = *cpp;
872126274Sdes	char *cp2;
873126274Sdes	int base = 0;
874126274Sdes	long l;
875126274Sdes	int i, cmdnum;
876126274Sdes
877126274Sdes	/* Skip leading whitespace */
878126274Sdes	cp = cp + strspn(cp, WHITESPACE);
879126274Sdes
880126274Sdes	/* Ignore blank lines and lines which begin with comment '#' char */
881126274Sdes	if (*cp == '\0' || *cp == '#')
882126274Sdes		return (0);
883126274Sdes
884126274Sdes	/* Check for leading '-' (disable error processing) */
885126274Sdes	*iflag = 0;
886126274Sdes	if (*cp == '-') {
887126274Sdes		*iflag = 1;
888126274Sdes		cp++;
889126274Sdes	}
890126274Sdes
891126274Sdes	/* Figure out which command we have */
892126274Sdes	for (i = 0; cmds[i].c; i++) {
893126274Sdes		int cmdlen = strlen(cmds[i].c);
894126274Sdes
895126274Sdes		/* Check for command followed by whitespace */
896126274Sdes		if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
897126274Sdes		    strchr(WHITESPACE, cp[cmdlen])) {
898126274Sdes			cp += cmdlen;
899126274Sdes			cp = cp + strspn(cp, WHITESPACE);
900126274Sdes			break;
901126274Sdes		}
902126274Sdes	}
903126274Sdes	cmdnum = cmds[i].n;
904126274Sdes	cmd = cmds[i].c;
905126274Sdes
906126274Sdes	/* Special case */
907126274Sdes	if (*cp == '!') {
908126274Sdes		cp++;
909126274Sdes		cmdnum = I_SHELL;
910126274Sdes	} else if (cmdnum == -1) {
911126274Sdes		error("Invalid command.");
912126274Sdes		return (-1);
913126274Sdes	}
914126274Sdes
915126274Sdes	/* Get arguments and parse flags */
916126274Sdes	*lflag = *pflag = *n_arg = 0;
917126274Sdes	*path1 = *path2 = NULL;
918126274Sdes	switch (cmdnum) {
919126274Sdes	case I_GET:
920126274Sdes	case I_PUT:
921126274Sdes		if (parse_getput_flags(&cp, pflag))
922126274Sdes			return(-1);
923126274Sdes		/* Get first pathname (mandatory) */
924126274Sdes		if (get_pathname(&cp, path1))
925126274Sdes			return(-1);
926126274Sdes		if (*path1 == NULL) {
927126274Sdes			error("You must specify at least one path after a "
928126274Sdes			    "%s command.", cmd);
929126274Sdes			return(-1);
930126274Sdes		}
931126274Sdes		/* Try to get second pathname (optional) */
932126274Sdes		if (get_pathname(&cp, path2))
933126274Sdes			return(-1);
934126274Sdes		break;
935126274Sdes	case I_RENAME:
936126274Sdes	case I_SYMLINK:
937126274Sdes		if (get_pathname(&cp, path1))
938126274Sdes			return(-1);
939126274Sdes		if (get_pathname(&cp, path2))
940126274Sdes			return(-1);
941126274Sdes		if (!*path1 || !*path2) {
942126274Sdes			error("You must specify two paths after a %s "
943126274Sdes			    "command.", cmd);
944126274Sdes			return(-1);
945126274Sdes		}
946126274Sdes		break;
947126274Sdes	case I_RM:
948126274Sdes	case I_MKDIR:
949126274Sdes	case I_RMDIR:
950126274Sdes	case I_CHDIR:
951126274Sdes	case I_LCHDIR:
952126274Sdes	case I_LMKDIR:
953126274Sdes		/* Get pathname (mandatory) */
954126274Sdes		if (get_pathname(&cp, path1))
955126274Sdes			return(-1);
956126274Sdes		if (*path1 == NULL) {
957126274Sdes			error("You must specify a path after a %s command.",
958126274Sdes			    cmd);
959126274Sdes			return(-1);
960126274Sdes		}
961126274Sdes		break;
962126274Sdes	case I_LS:
963126274Sdes		if (parse_ls_flags(&cp, lflag))
964126274Sdes			return(-1);
965126274Sdes		/* Path is optional */
966126274Sdes		if (get_pathname(&cp, path1))
967126274Sdes			return(-1);
968126274Sdes		break;
969126274Sdes	case I_LLS:
970126274Sdes	case I_SHELL:
971126274Sdes		/* Uses the rest of the line */
972126274Sdes		break;
973126274Sdes	case I_LUMASK:
974126274Sdes		base = 8;
975126274Sdes	case I_CHMOD:
976126274Sdes		base = 8;
977126274Sdes	case I_CHOWN:
978126274Sdes	case I_CHGRP:
979126274Sdes		/* Get numeric arg (mandatory) */
980126274Sdes		l = strtol(cp, &cp2, base);
981126274Sdes		if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
982126274Sdes		    errno == ERANGE) || l < 0) {
983126274Sdes			error("You must supply a numeric argument "
984126274Sdes			    "to the %s command.", cmd);
985126274Sdes			return(-1);
986126274Sdes		}
987126274Sdes		cp = cp2;
988126274Sdes		*n_arg = l;
989126274Sdes		if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
990126274Sdes			break;
991126274Sdes		if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
992126274Sdes			error("You must supply a numeric argument "
993126274Sdes			    "to the %s command.", cmd);
994126274Sdes			return(-1);
995126274Sdes		}
996126274Sdes		cp += strspn(cp, WHITESPACE);
997126274Sdes
998126274Sdes		/* Get pathname (mandatory) */
999126274Sdes		if (get_pathname(&cp, path1))
1000126274Sdes			return(-1);
1001126274Sdes		if (*path1 == NULL) {
1002126274Sdes			error("You must specify a path after a %s command.",
1003126274Sdes			    cmd);
1004126274Sdes			return(-1);
1005126274Sdes		}
1006126274Sdes		break;
1007126274Sdes	case I_QUIT:
1008126274Sdes	case I_PWD:
1009126274Sdes	case I_LPWD:
1010126274Sdes	case I_HELP:
1011126274Sdes	case I_VERSION:
1012126274Sdes	case I_PROGRESS:
1013126274Sdes		break;
1014126274Sdes	default:
1015126274Sdes		fatal("Command not implemented");
1016126274Sdes	}
1017126274Sdes
1018126274Sdes	*cpp = cp;
1019126274Sdes	return(cmdnum);
1020126274Sdes}
1021126274Sdes
1022126274Sdesstatic int
1023126274Sdesparse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1024126274Sdes    int err_abort)
1025126274Sdes{
1026126274Sdes	char *path1, *path2, *tmp;
1027126274Sdes	int pflag, lflag, iflag, cmdnum, i;
1028126274Sdes	unsigned long n_arg;
1029126274Sdes	Attrib a, *aa;
1030126274Sdes	char path_buf[MAXPATHLEN];
1031126274Sdes	int err = 0;
1032126274Sdes	glob_t g;
1033126274Sdes
1034126274Sdes	path1 = path2 = NULL;
1035126274Sdes	cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &n_arg,
1036126274Sdes	    &path1, &path2);
1037126274Sdes
1038126274Sdes	if (iflag != 0)
1039126274Sdes		err_abort = 0;
1040126274Sdes
1041126274Sdes	memset(&g, 0, sizeof(g));
1042126274Sdes
1043126274Sdes	/* Perform command */
1044126274Sdes	switch (cmdnum) {
1045126274Sdes	case 0:
1046126274Sdes		/* Blank line */
1047126274Sdes		break;
1048126274Sdes	case -1:
1049126274Sdes		/* Unrecognized command */
1050126274Sdes		err = -1;
1051126274Sdes		break;
1052126274Sdes	case I_GET:
1053126274Sdes		err = process_get(conn, path1, path2, *pwd, pflag);
1054126274Sdes		break;
1055126274Sdes	case I_PUT:
1056126274Sdes		err = process_put(conn, path1, path2, *pwd, pflag);
1057126274Sdes		break;
1058126274Sdes	case I_RENAME:
1059126274Sdes		path1 = make_absolute(path1, *pwd);
1060126274Sdes		path2 = make_absolute(path2, *pwd);
1061126274Sdes		err = do_rename(conn, path1, path2);
1062126274Sdes		break;
1063126274Sdes	case I_SYMLINK:
1064126274Sdes		path2 = make_absolute(path2, *pwd);
1065126274Sdes		err = do_symlink(conn, path1, path2);
1066126274Sdes		break;
1067126274Sdes	case I_RM:
1068126274Sdes		path1 = make_absolute(path1, *pwd);
1069126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1070137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1071126274Sdes			printf("Removing %s\n", g.gl_pathv[i]);
1072126274Sdes			err = do_rm(conn, g.gl_pathv[i]);
1073126274Sdes			if (err != 0 && err_abort)
1074126274Sdes				break;
1075126274Sdes		}
1076126274Sdes		break;
1077126274Sdes	case I_MKDIR:
1078126274Sdes		path1 = make_absolute(path1, *pwd);
1079126274Sdes		attrib_clear(&a);
1080126274Sdes		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1081126274Sdes		a.perm = 0777;
1082126274Sdes		err = do_mkdir(conn, path1, &a);
1083126274Sdes		break;
1084126274Sdes	case I_RMDIR:
1085126274Sdes		path1 = make_absolute(path1, *pwd);
1086126274Sdes		err = do_rmdir(conn, path1);
1087126274Sdes		break;
1088126274Sdes	case I_CHDIR:
1089126274Sdes		path1 = make_absolute(path1, *pwd);
1090126274Sdes		if ((tmp = do_realpath(conn, path1)) == NULL) {
1091126274Sdes			err = 1;
1092126274Sdes			break;
1093126274Sdes		}
1094126274Sdes		if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1095126274Sdes			xfree(tmp);
1096126274Sdes			err = 1;
1097126274Sdes			break;
1098126274Sdes		}
1099126274Sdes		if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1100126274Sdes			error("Can't change directory: Can't check target");
1101126274Sdes			xfree(tmp);
1102126274Sdes			err = 1;
1103126274Sdes			break;
1104126274Sdes		}
1105126274Sdes		if (!S_ISDIR(aa->perm)) {
1106126274Sdes			error("Can't change directory: \"%s\" is not "
1107126274Sdes			    "a directory", tmp);
1108126274Sdes			xfree(tmp);
1109126274Sdes			err = 1;
1110126274Sdes			break;
1111126274Sdes		}
1112126274Sdes		xfree(*pwd);
1113126274Sdes		*pwd = tmp;
1114126274Sdes		break;
1115126274Sdes	case I_LS:
1116126274Sdes		if (!path1) {
1117126274Sdes			do_globbed_ls(conn, *pwd, *pwd, lflag);
1118126274Sdes			break;
1119126274Sdes		}
1120126274Sdes
1121126274Sdes		/* Strip pwd off beginning of non-absolute paths */
1122126274Sdes		tmp = NULL;
1123126274Sdes		if (*path1 != '/')
1124126274Sdes			tmp = *pwd;
1125126274Sdes
1126126274Sdes		path1 = make_absolute(path1, *pwd);
1127126274Sdes		err = do_globbed_ls(conn, path1, tmp, lflag);
1128126274Sdes		break;
1129126274Sdes	case I_LCHDIR:
1130126274Sdes		if (chdir(path1) == -1) {
1131126274Sdes			error("Couldn't change local directory to "
1132126274Sdes			    "\"%s\": %s", path1, strerror(errno));
1133126274Sdes			err = 1;
1134126274Sdes		}
1135126274Sdes		break;
1136126274Sdes	case I_LMKDIR:
1137126274Sdes		if (mkdir(path1, 0777) == -1) {
1138126274Sdes			error("Couldn't create local directory "
1139126274Sdes			    "\"%s\": %s", path1, strerror(errno));
1140126274Sdes			err = 1;
1141126274Sdes		}
1142126274Sdes		break;
1143126274Sdes	case I_LLS:
1144126274Sdes		local_do_ls(cmd);
1145126274Sdes		break;
1146126274Sdes	case I_SHELL:
1147126274Sdes		local_do_shell(cmd);
1148126274Sdes		break;
1149126274Sdes	case I_LUMASK:
1150126274Sdes		umask(n_arg);
1151126274Sdes		printf("Local umask: %03lo\n", n_arg);
1152126274Sdes		break;
1153126274Sdes	case I_CHMOD:
1154126274Sdes		path1 = make_absolute(path1, *pwd);
1155126274Sdes		attrib_clear(&a);
1156126274Sdes		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1157126274Sdes		a.perm = n_arg;
1158126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1159137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1160126274Sdes			printf("Changing mode on %s\n", g.gl_pathv[i]);
1161126274Sdes			err = do_setstat(conn, g.gl_pathv[i], &a);
1162126274Sdes			if (err != 0 && err_abort)
1163126274Sdes				break;
1164126274Sdes		}
1165126274Sdes		break;
1166126274Sdes	case I_CHOWN:
1167126274Sdes	case I_CHGRP:
1168126274Sdes		path1 = make_absolute(path1, *pwd);
1169126274Sdes		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1170137015Sdes		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1171126274Sdes			if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
1172126274Sdes				if (err != 0 && err_abort)
1173126274Sdes					break;
1174126274Sdes				else
1175126274Sdes					continue;
1176126274Sdes			}
1177126274Sdes			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1178126274Sdes				error("Can't get current ownership of "
1179126274Sdes				    "remote file \"%s\"", g.gl_pathv[i]);
1180126274Sdes				if (err != 0 && err_abort)
1181126274Sdes					break;
1182126274Sdes				else
1183126274Sdes					continue;
1184126274Sdes			}
1185126274Sdes			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1186126274Sdes			if (cmdnum == I_CHOWN) {
1187126274Sdes				printf("Changing owner on %s\n", g.gl_pathv[i]);
1188126274Sdes				aa->uid = n_arg;
1189126274Sdes			} else {
1190126274Sdes				printf("Changing group on %s\n", g.gl_pathv[i]);
1191126274Sdes				aa->gid = n_arg;
1192126274Sdes			}
1193126274Sdes			err = do_setstat(conn, g.gl_pathv[i], aa);
1194126274Sdes			if (err != 0 && err_abort)
1195126274Sdes				break;
1196126274Sdes		}
1197126274Sdes		break;
1198126274Sdes	case I_PWD:
1199126274Sdes		printf("Remote working directory: %s\n", *pwd);
1200126274Sdes		break;
1201126274Sdes	case I_LPWD:
1202126274Sdes		if (!getcwd(path_buf, sizeof(path_buf))) {
1203126274Sdes			error("Couldn't get local cwd: %s", strerror(errno));
1204126274Sdes			err = -1;
1205126274Sdes			break;
1206126274Sdes		}
1207126274Sdes		printf("Local working directory: %s\n", path_buf);
1208126274Sdes		break;
1209126274Sdes	case I_QUIT:
1210126274Sdes		/* Processed below */
1211126274Sdes		break;
1212126274Sdes	case I_HELP:
1213126274Sdes		help();
1214126274Sdes		break;
1215126274Sdes	case I_VERSION:
1216126274Sdes		printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1217126274Sdes		break;
1218126274Sdes	case I_PROGRESS:
1219126274Sdes		showprogress = !showprogress;
1220126274Sdes		if (showprogress)
1221126274Sdes			printf("Progress meter enabled\n");
1222126274Sdes		else
1223126274Sdes			printf("Progress meter disabled\n");
1224126274Sdes		break;
1225126274Sdes	default:
1226126274Sdes		fatal("%d is not implemented", cmdnum);
1227126274Sdes	}
1228126274Sdes
1229126274Sdes	if (g.gl_pathc)
1230126274Sdes		globfree(&g);
1231126274Sdes	if (path1)
1232126274Sdes		xfree(path1);
1233126274Sdes	if (path2)
1234126274Sdes		xfree(path2);
1235126274Sdes
1236126274Sdes	/* If an unignored error occurs in batch mode we should abort. */
1237126274Sdes	if (err_abort && err != 0)
1238126274Sdes		return (-1);
1239126274Sdes	else if (cmdnum == I_QUIT)
1240126274Sdes		return (1);
1241126274Sdes
1242126274Sdes	return (0);
1243126274Sdes}
1244126274Sdes
1245146998Sdes#ifdef USE_LIBEDIT
1246146998Sdesstatic char *
1247146998Sdesprompt(EditLine *el)
1248146998Sdes{
1249146998Sdes	return ("sftp> ");
1250146998Sdes}
1251146998Sdes#endif
1252146998Sdes
1253126274Sdesint
1254126274Sdesinteractive_loop(int fd_in, int fd_out, char *file1, char *file2)
1255126274Sdes{
1256126274Sdes	char *pwd;
1257126274Sdes	char *dir = NULL;
1258126274Sdes	char cmd[2048];
1259126274Sdes	struct sftp_conn *conn;
1260149749Sdes	int err, interactive;
1261146998Sdes	EditLine *el = NULL;
1262146998Sdes#ifdef USE_LIBEDIT
1263146998Sdes	History *hl = NULL;
1264146998Sdes	HistEvent hev;
1265146998Sdes	extern char *__progname;
1266126274Sdes
1267146998Sdes	if (!batchmode && isatty(STDIN_FILENO)) {
1268146998Sdes		if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1269146998Sdes			fatal("Couldn't initialise editline");
1270146998Sdes		if ((hl = history_init()) == NULL)
1271146998Sdes			fatal("Couldn't initialise editline history");
1272146998Sdes		history(hl, &hev, H_SETSIZE, 100);
1273146998Sdes		el_set(el, EL_HIST, history, hl);
1274146998Sdes
1275146998Sdes		el_set(el, EL_PROMPT, prompt);
1276146998Sdes		el_set(el, EL_EDITOR, "emacs");
1277146998Sdes		el_set(el, EL_TERMINAL, NULL);
1278146998Sdes		el_set(el, EL_SIGNAL, 1);
1279146998Sdes		el_source(el, NULL);
1280146998Sdes	}
1281146998Sdes#endif /* USE_LIBEDIT */
1282146998Sdes
1283126274Sdes	conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
1284126274Sdes	if (conn == NULL)
1285126274Sdes		fatal("Couldn't initialise connection to server");
1286126274Sdes
1287126274Sdes	pwd = do_realpath(conn, ".");
1288126274Sdes	if (pwd == NULL)
1289126274Sdes		fatal("Need cwd");
1290126274Sdes
1291126274Sdes	if (file1 != NULL) {
1292126274Sdes		dir = xstrdup(file1);
1293126274Sdes		dir = make_absolute(dir, pwd);
1294126274Sdes
1295126274Sdes		if (remote_is_dir(conn, dir) && file2 == NULL) {
1296126274Sdes			printf("Changing to: %s\n", dir);
1297126274Sdes			snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
1298146998Sdes			if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) {
1299146998Sdes				xfree(dir);
1300146998Sdes				xfree(pwd);
1301162852Sdes				xfree(conn);
1302126274Sdes				return (-1);
1303146998Sdes			}
1304126274Sdes		} else {
1305126274Sdes			if (file2 == NULL)
1306126274Sdes				snprintf(cmd, sizeof cmd, "get %s", dir);
1307126274Sdes			else
1308126274Sdes				snprintf(cmd, sizeof cmd, "get %s %s", dir,
1309126274Sdes				    file2);
1310126274Sdes
1311126274Sdes			err = parse_dispatch_command(conn, cmd, &pwd, 1);
1312126274Sdes			xfree(dir);
1313126274Sdes			xfree(pwd);
1314162852Sdes			xfree(conn);
1315126274Sdes			return (err);
1316126274Sdes		}
1317126274Sdes		xfree(dir);
1318126274Sdes	}
1319126274Sdes
1320149749Sdes#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
1321126274Sdes	setvbuf(stdout, NULL, _IOLBF, 0);
1322126274Sdes	setvbuf(infile, NULL, _IOLBF, 0);
1323126274Sdes#else
1324149749Sdes	setlinebuf(stdout);
1325149749Sdes	setlinebuf(infile);
1326126274Sdes#endif
1327126274Sdes
1328149749Sdes	interactive = !batchmode && isatty(STDIN_FILENO);
1329126274Sdes	err = 0;
1330126274Sdes	for (;;) {
1331126274Sdes		char *cp;
1332126274Sdes
1333137015Sdes		signal(SIGINT, SIG_IGN);
1334137015Sdes
1335146998Sdes		if (el == NULL) {
1336149749Sdes			if (interactive)
1337149749Sdes				printf("sftp> ");
1338146998Sdes			if (fgets(cmd, sizeof(cmd), infile) == NULL) {
1339149749Sdes				if (interactive)
1340149749Sdes					printf("\n");
1341146998Sdes				break;
1342146998Sdes			}
1343149749Sdes			if (!interactive) { /* Echo command */
1344149749Sdes				printf("sftp> %s", cmd);
1345149749Sdes				if (strlen(cmd) > 0 &&
1346149749Sdes				    cmd[strlen(cmd) - 1] != '\n')
1347149749Sdes					printf("\n");
1348149749Sdes			}
1349146998Sdes		} else {
1350146998Sdes#ifdef USE_LIBEDIT
1351146998Sdes			const char *line;
1352146998Sdes			int count = 0;
1353126274Sdes
1354149749Sdes			if ((line = el_gets(el, &count)) == NULL || count <= 0) {
1355149749Sdes				printf("\n");
1356149749Sdes 				break;
1357149749Sdes			}
1358146998Sdes			history(hl, &hev, H_ENTER, line);
1359146998Sdes			if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1360146998Sdes				fprintf(stderr, "Error: input line too long\n");
1361146998Sdes				continue;
1362146998Sdes			}
1363146998Sdes#endif /* USE_LIBEDIT */
1364126274Sdes		}
1365126274Sdes
1366126274Sdes		cp = strrchr(cmd, '\n');
1367126274Sdes		if (cp)
1368126274Sdes			*cp = '\0';
1369126274Sdes
1370137015Sdes		/* Handle user interrupts gracefully during commands */
1371137015Sdes		interrupted = 0;
1372137015Sdes		signal(SIGINT, cmd_interrupt);
1373137015Sdes
1374126274Sdes		err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
1375126274Sdes		if (err != 0)
1376126274Sdes			break;
1377126274Sdes	}
1378126274Sdes	xfree(pwd);
1379162852Sdes	xfree(conn);
1380126274Sdes
1381149749Sdes#ifdef USE_LIBEDIT
1382149749Sdes	if (el != NULL)
1383149749Sdes		el_end(el);
1384149749Sdes#endif /* USE_LIBEDIT */
1385149749Sdes
1386126274Sdes	/* err == 1 signifies normal "quit" exit */
1387126274Sdes	return (err >= 0 ? 0 : -1);
1388126274Sdes}
1389126274Sdes
1390126274Sdesstatic void
1391124208Sdesconnect_to_server(char *path, char **args, int *in, int *out)
1392124208Sdes{
139376259Sgreen	int c_in, c_out;
139499060Sdes
139576259Sgreen#ifdef USE_PIPES
139676259Sgreen	int pin[2], pout[2];
139799060Sdes
139876259Sgreen	if ((pipe(pin) == -1) || (pipe(pout) == -1))
139976259Sgreen		fatal("pipe: %s", strerror(errno));
140076259Sgreen	*in = pin[0];
140176259Sgreen	*out = pout[1];
140276259Sgreen	c_in = pout[0];
140376259Sgreen	c_out = pin[1];
140476259Sgreen#else /* USE_PIPES */
140576259Sgreen	int inout[2];
140699060Sdes
140776259Sgreen	if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
140876259Sgreen		fatal("socketpair: %s", strerror(errno));
140976259Sgreen	*in = *out = inout[0];
141076259Sgreen	c_in = c_out = inout[1];
141176259Sgreen#endif /* USE_PIPES */
141276259Sgreen
1413124208Sdes	if ((sshpid = fork()) == -1)
141476259Sgreen		fatal("fork: %s", strerror(errno));
1415124208Sdes	else if (sshpid == 0) {
141676259Sgreen		if ((dup2(c_in, STDIN_FILENO) == -1) ||
141776259Sgreen		    (dup2(c_out, STDOUT_FILENO) == -1)) {
141876259Sgreen			fprintf(stderr, "dup2: %s\n", strerror(errno));
1419137015Sdes			_exit(1);
142076259Sgreen		}
142176259Sgreen		close(*in);
142276259Sgreen		close(*out);
142376259Sgreen		close(c_in);
142476259Sgreen		close(c_out);
1425137015Sdes
1426137015Sdes		/*
1427137015Sdes		 * The underlying ssh is in the same process group, so we must
1428137015Sdes		 * ignore SIGINT if we want to gracefully abort commands,
1429137015Sdes		 * otherwise the signal will make it to the ssh process and
1430137015Sdes		 * kill it too
1431137015Sdes		 */
1432137015Sdes		signal(SIGINT, SIG_IGN);
1433137015Sdes		execvp(path, args);
143492555Sdes		fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
1435137015Sdes		_exit(1);
143676259Sgreen	}
143776259Sgreen
1438124208Sdes	signal(SIGTERM, killchild);
1439124208Sdes	signal(SIGINT, killchild);
1440124208Sdes	signal(SIGHUP, killchild);
144176259Sgreen	close(c_in);
144276259Sgreen	close(c_out);
144376259Sgreen}
144476259Sgreen
144592555Sdesstatic void
144676259Sgreenusage(void)
144776259Sgreen{
144892555Sdes	extern char *__progname;
144998675Sdes
145092555Sdes	fprintf(stderr,
1451126274Sdes	    "usage: %s [-1Cv] [-B buffer_size] [-b batchfile] [-F ssh_config]\n"
1452126274Sdes	    "            [-o ssh_option] [-P sftp_server_path] [-R num_requests]\n"
1453126274Sdes	    "            [-S program] [-s subsystem | sftp_server] host\n"
1454126274Sdes	    "       %s [[user@]host[:file [file]]]\n"
1455126274Sdes	    "       %s [[user@]host[:dir[/]]]\n"
1456126274Sdes	    "       %s -b batchfile [user@]host\n", __progname, __progname, __progname, __progname);
145776259Sgreen	exit(1);
145876259Sgreen}
145976259Sgreen
146076259Sgreenint
146176259Sgreenmain(int argc, char **argv)
146276259Sgreen{
1463113908Sdes	int in, out, ch, err;
1464137015Sdes	char *host, *userhost, *cp, *file2 = NULL;
146592555Sdes	int debug_level = 0, sshver = 2;
146692555Sdes	char *file1 = NULL, *sftp_server = NULL;
146792555Sdes	char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
146892555Sdes	LogLevel ll = SYSLOG_LEVEL_INFO;
146992555Sdes	arglist args;
147076259Sgreen	extern int optind;
147176259Sgreen	extern char *optarg;
147276259Sgreen
1473157016Sdes	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
1474157016Sdes	sanitise_stdfd();
1475157016Sdes
1476124208Sdes	__progname = ssh_get_progname(argv[0]);
1477157016Sdes	memset(&args, '\0', sizeof(args));
147892555Sdes	args.list = NULL;
1479162852Sdes	addargs(&args, "%s", ssh_program);
148092555Sdes	addargs(&args, "-oForwardX11 no");
148192555Sdes	addargs(&args, "-oForwardAgent no");
1482157016Sdes	addargs(&args, "-oPermitLocalCommand no");
148392555Sdes	addargs(&args, "-oClearAllForwardings yes");
1484126274Sdes
148592555Sdes	ll = SYSLOG_LEVEL_INFO;
1486126274Sdes	infile = stdin;
148776259Sgreen
148892555Sdes	while ((ch = getopt(argc, argv, "1hvCo:s:S:b:B:F:P:R:")) != -1) {
148976259Sgreen		switch (ch) {
149076259Sgreen		case 'C':
149192555Sdes			addargs(&args, "-C");
149276259Sgreen			break;
149376259Sgreen		case 'v':
149492555Sdes			if (debug_level < 3) {
149592555Sdes				addargs(&args, "-v");
149692555Sdes				ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
149792555Sdes			}
149892555Sdes			debug_level++;
149976259Sgreen			break;
150092555Sdes		case 'F':
150176259Sgreen		case 'o':
150292555Sdes			addargs(&args, "-%c%s", ch, optarg);
150376259Sgreen			break;
150476259Sgreen		case '1':
150592555Sdes			sshver = 1;
150676259Sgreen			if (sftp_server == NULL)
150776259Sgreen				sftp_server = _PATH_SFTP_SERVER;
150876259Sgreen			break;
150976259Sgreen		case 's':
151076259Sgreen			sftp_server = optarg;
151176259Sgreen			break;
151276259Sgreen		case 'S':
151376259Sgreen			ssh_program = optarg;
1514157016Sdes			replacearg(&args, 0, "%s", ssh_program);
151576259Sgreen			break;
151676259Sgreen		case 'b':
1517126274Sdes			if (batchmode)
1518126274Sdes				fatal("Batch file already specified.");
1519126274Sdes
1520126274Sdes			/* Allow "-" as stdin */
1521137015Sdes			if (strcmp(optarg, "-") != 0 &&
1522149749Sdes			    (infile = fopen(optarg, "r")) == NULL)
1523126274Sdes				fatal("%s (%s).", strerror(errno), optarg);
1524113908Sdes			showprogress = 0;
1525126274Sdes			batchmode = 1;
1526146998Sdes			addargs(&args, "-obatchmode yes");
152776259Sgreen			break;
152892555Sdes		case 'P':
152992555Sdes			sftp_direct = optarg;
153092555Sdes			break;
153192555Sdes		case 'B':
153292555Sdes			copy_buffer_len = strtol(optarg, &cp, 10);
153392555Sdes			if (copy_buffer_len == 0 || *cp != '\0')
153492555Sdes				fatal("Invalid buffer size \"%s\"", optarg);
153592555Sdes			break;
153692555Sdes		case 'R':
153792555Sdes			num_requests = strtol(optarg, &cp, 10);
153892555Sdes			if (num_requests == 0 || *cp != '\0')
153998675Sdes				fatal("Invalid number of requests \"%s\"",
154092555Sdes				    optarg);
154192555Sdes			break;
154276259Sgreen		case 'h':
154376259Sgreen		default:
154476259Sgreen			usage();
154576259Sgreen		}
154676259Sgreen	}
154776259Sgreen
1548128456Sdes	if (!isatty(STDERR_FILENO))
1549128456Sdes		showprogress = 0;
1550128456Sdes
155198675Sdes	log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
155298675Sdes
155392555Sdes	if (sftp_direct == NULL) {
155492555Sdes		if (optind == argc || argc > (optind + 2))
155592555Sdes			usage();
155676259Sgreen
155792555Sdes		userhost = xstrdup(argv[optind]);
155892555Sdes		file2 = argv[optind+1];
155976259Sgreen
1560113908Sdes		if ((host = strrchr(userhost, '@')) == NULL)
156192555Sdes			host = userhost;
156292555Sdes		else {
156392555Sdes			*host++ = '\0';
156492555Sdes			if (!userhost[0]) {
156592555Sdes				fprintf(stderr, "Missing username\n");
156692555Sdes				usage();
156792555Sdes			}
156892555Sdes			addargs(&args, "-l%s",userhost);
156992555Sdes		}
157092555Sdes
1571126274Sdes		if ((cp = colon(host)) != NULL) {
1572126274Sdes			*cp++ = '\0';
1573126274Sdes			file1 = cp;
1574126274Sdes		}
1575126274Sdes
157692555Sdes		host = cleanhostname(host);
157792555Sdes		if (!*host) {
157892555Sdes			fprintf(stderr, "Missing hostname\n");
157976259Sgreen			usage();
158076259Sgreen		}
158176259Sgreen
158292555Sdes		addargs(&args, "-oProtocol %d", sshver);
158376259Sgreen
158492555Sdes		/* no subsystem if the server-spec contains a '/' */
158592555Sdes		if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
158692555Sdes			addargs(&args, "-s");
158776259Sgreen
158892555Sdes		addargs(&args, "%s", host);
158998675Sdes		addargs(&args, "%s", (sftp_server != NULL ?
159092555Sdes		    sftp_server : "sftp"));
159176259Sgreen
1592126274Sdes		if (!batchmode)
1593126274Sdes			fprintf(stderr, "Connecting to %s...\n", host);
1594124208Sdes		connect_to_server(ssh_program, args.list, &in, &out);
159592555Sdes	} else {
159692555Sdes		args.list = NULL;
159792555Sdes		addargs(&args, "sftp-server");
159876259Sgreen
1599126274Sdes		if (!batchmode)
1600126274Sdes			fprintf(stderr, "Attaching to %s...\n", sftp_direct);
1601124208Sdes		connect_to_server(sftp_direct, args.list, &in, &out);
160292555Sdes	}
1603157016Sdes	freeargs(&args);
160476259Sgreen
1605113908Sdes	err = interactive_loop(in, out, file1, file2);
160676259Sgreen
160798937Sdes#if !defined(USE_PIPES)
1608149749Sdes	shutdown(in, SHUT_RDWR);
1609149749Sdes	shutdown(out, SHUT_RDWR);
161098937Sdes#endif
161198937Sdes
161276259Sgreen	close(in);
161376259Sgreen	close(out);
1614126274Sdes	if (batchmode)
161576259Sgreen		fclose(infile);
161676259Sgreen
161798675Sdes	while (waitpid(sshpid, NULL, 0) == -1)
161898675Sdes		if (errno != EINTR)
161998675Sdes			fatal("Couldn't wait for ssh process: %s",
162098675Sdes			    strerror(errno));
162176259Sgreen
1622113908Sdes	exit(err == 0 ? 0 : 1);
162376259Sgreen}
1624