sftp.c revision 147001
1/*
2 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17#include "includes.h"
18
19RCSID("$OpenBSD: sftp.c,v 1.63 2005/03/10 22:01:05 deraadt Exp $");
20
21#ifdef USE_LIBEDIT
22#include <histedit.h>
23#else
24typedef void EditLine;
25#endif
26
27#include "buffer.h"
28#include "xmalloc.h"
29#include "log.h"
30#include "pathnames.h"
31#include "misc.h"
32
33#include "sftp.h"
34#include "sftp-common.h"
35#include "sftp-client.h"
36
37/* File to read commands from */
38FILE* infile;
39
40/* Are we in batchfile mode? */
41int batchmode = 0;
42
43/* Size of buffer used when copying files */
44size_t copy_buffer_len = 32768;
45
46/* Number of concurrent outstanding requests */
47size_t num_requests = 16;
48
49/* PID of ssh transport process */
50static pid_t sshpid = -1;
51
52/* This is set to 0 if the progressmeter is not desired. */
53int showprogress = 1;
54
55/* SIGINT received during command processing */
56volatile sig_atomic_t interrupted = 0;
57
58/* I wish qsort() took a separate ctx for the comparison function...*/
59int sort_flag;
60
61int remote_glob(struct sftp_conn *, const char *, int,
62    int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
63
64extern char *__progname;
65
66/* Separators for interactive commands */
67#define WHITESPACE " \t\r\n"
68
69/* ls flags */
70#define LS_LONG_VIEW	0x01	/* Full view ala ls -l */
71#define LS_SHORT_VIEW	0x02	/* Single row view ala ls -1 */
72#define LS_NUMERIC_VIEW	0x04	/* Long view with numeric uid/gid */
73#define LS_NAME_SORT	0x08	/* Sort by name (default) */
74#define LS_TIME_SORT	0x10	/* Sort by mtime */
75#define LS_SIZE_SORT	0x20	/* Sort by file size */
76#define LS_REVERSE_SORT	0x40	/* Reverse sort order */
77#define LS_SHOW_ALL	0x80	/* Don't skip filenames starting with '.' */
78
79#define VIEW_FLAGS	(LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW)
80#define SORT_FLAGS	(LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
81
82/* Commands for interactive mode */
83#define I_CHDIR		1
84#define I_CHGRP		2
85#define I_CHMOD		3
86#define I_CHOWN		4
87#define I_GET		5
88#define I_HELP		6
89#define I_LCHDIR	7
90#define I_LLS		8
91#define I_LMKDIR	9
92#define I_LPWD		10
93#define I_LS		11
94#define I_LUMASK	12
95#define I_MKDIR		13
96#define I_PUT		14
97#define I_PWD		15
98#define I_QUIT		16
99#define I_RENAME	17
100#define I_RM		18
101#define I_RMDIR		19
102#define I_SHELL		20
103#define I_SYMLINK	21
104#define I_VERSION	22
105#define I_PROGRESS	23
106
107struct CMD {
108	const char *c;
109	const int n;
110};
111
112static const struct CMD cmds[] = {
113	{ "bye",	I_QUIT },
114	{ "cd",		I_CHDIR },
115	{ "chdir",	I_CHDIR },
116	{ "chgrp",	I_CHGRP },
117	{ "chmod",	I_CHMOD },
118	{ "chown",	I_CHOWN },
119	{ "dir",	I_LS },
120	{ "exit",	I_QUIT },
121	{ "get",	I_GET },
122	{ "mget",	I_GET },
123	{ "help",	I_HELP },
124	{ "lcd",	I_LCHDIR },
125	{ "lchdir",	I_LCHDIR },
126	{ "lls",	I_LLS },
127	{ "lmkdir",	I_LMKDIR },
128	{ "ln",		I_SYMLINK },
129	{ "lpwd",	I_LPWD },
130	{ "ls",		I_LS },
131	{ "lumask",	I_LUMASK },
132	{ "mkdir",	I_MKDIR },
133	{ "progress",	I_PROGRESS },
134	{ "put",	I_PUT },
135	{ "mput",	I_PUT },
136	{ "pwd",	I_PWD },
137	{ "quit",	I_QUIT },
138	{ "rename",	I_RENAME },
139	{ "rm",		I_RM },
140	{ "rmdir",	I_RMDIR },
141	{ "symlink",	I_SYMLINK },
142	{ "version",	I_VERSION },
143	{ "!",		I_SHELL },
144	{ "?",		I_HELP },
145	{ NULL,			-1}
146};
147
148int interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
149
150static void
151killchild(int signo)
152{
153	if (sshpid > 1) {
154		kill(sshpid, SIGTERM);
155		waitpid(sshpid, NULL, 0);
156	}
157
158	_exit(1);
159}
160
161static void
162cmd_interrupt(int signo)
163{
164	const char msg[] = "\rInterrupt  \n";
165	int olderrno = errno;
166
167	write(STDERR_FILENO, msg, sizeof(msg) - 1);
168	interrupted = 1;
169	errno = olderrno;
170}
171
172static void
173help(void)
174{
175	printf("Available commands:\n");
176	printf("cd path                       Change remote directory to 'path'\n");
177	printf("lcd path                      Change local directory to 'path'\n");
178	printf("chgrp grp path                Change group of file 'path' to 'grp'\n");
179	printf("chmod mode path               Change permissions of file 'path' to 'mode'\n");
180	printf("chown own path                Change owner of file 'path' to 'own'\n");
181	printf("help                          Display this help text\n");
182	printf("get remote-path [local-path]  Download file\n");
183	printf("lls [ls-options [path]]       Display local directory listing\n");
184	printf("ln oldpath newpath            Symlink remote file\n");
185	printf("lmkdir path                   Create local directory\n");
186	printf("lpwd                          Print local working directory\n");
187	printf("ls [path]                     Display remote directory listing\n");
188	printf("lumask umask                  Set local umask to 'umask'\n");
189	printf("mkdir path                    Create remote directory\n");
190	printf("progress                      Toggle display of progress meter\n");
191	printf("put local-path [remote-path]  Upload file\n");
192	printf("pwd                           Display remote working directory\n");
193	printf("exit                          Quit sftp\n");
194	printf("quit                          Quit sftp\n");
195	printf("rename oldpath newpath        Rename remote file\n");
196	printf("rmdir path                    Remove remote directory\n");
197	printf("rm path                       Delete remote file\n");
198	printf("symlink oldpath newpath       Symlink remote file\n");
199	printf("version                       Show SFTP version\n");
200	printf("!command                      Execute 'command' in local shell\n");
201	printf("!                             Escape to local shell\n");
202	printf("?                             Synonym for help\n");
203}
204
205static void
206local_do_shell(const char *args)
207{
208	int status;
209	char *shell;
210	pid_t pid;
211
212	if (!*args)
213		args = NULL;
214
215	if ((shell = getenv("SHELL")) == NULL)
216		shell = _PATH_BSHELL;
217
218	if ((pid = fork()) == -1)
219		fatal("Couldn't fork: %s", strerror(errno));
220
221	if (pid == 0) {
222		/* XXX: child has pipe fds to ssh subproc open - issue? */
223		if (args) {
224			debug3("Executing %s -c \"%s\"", shell, args);
225			execl(shell, shell, "-c", args, (char *)NULL);
226		} else {
227			debug3("Executing %s", shell);
228			execl(shell, shell, (char *)NULL);
229		}
230		fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
231		    strerror(errno));
232		_exit(1);
233	}
234	while (waitpid(pid, &status, 0) == -1)
235		if (errno != EINTR)
236			fatal("Couldn't wait for child: %s", strerror(errno));
237	if (!WIFEXITED(status))
238		error("Shell exited abormally");
239	else if (WEXITSTATUS(status))
240		error("Shell exited with status %d", WEXITSTATUS(status));
241}
242
243static void
244local_do_ls(const char *args)
245{
246	if (!args || !*args)
247		local_do_shell(_PATH_LS);
248	else {
249		int len = strlen(_PATH_LS " ") + strlen(args) + 1;
250		char *buf = xmalloc(len);
251
252		/* XXX: quoting - rip quoting code from ftp? */
253		snprintf(buf, len, _PATH_LS " %s", args);
254		local_do_shell(buf);
255		xfree(buf);
256	}
257}
258
259/* Strip one path (usually the pwd) from the start of another */
260static char *
261path_strip(char *path, char *strip)
262{
263	size_t len;
264
265	if (strip == NULL)
266		return (xstrdup(path));
267
268	len = strlen(strip);
269	if (strncmp(path, strip, len) == 0) {
270		if (strip[len - 1] != '/' && path[len] == '/')
271			len++;
272		return (xstrdup(path + len));
273	}
274
275	return (xstrdup(path));
276}
277
278static char *
279path_append(char *p1, char *p2)
280{
281	char *ret;
282	int len = strlen(p1) + strlen(p2) + 2;
283
284	ret = xmalloc(len);
285	strlcpy(ret, p1, len);
286	if (p1[strlen(p1) - 1] != '/')
287		strlcat(ret, "/", len);
288	strlcat(ret, p2, len);
289
290	return(ret);
291}
292
293static char *
294make_absolute(char *p, char *pwd)
295{
296	char *abs_str;
297
298	/* Derelativise */
299	if (p && p[0] != '/') {
300		abs_str = path_append(pwd, p);
301		xfree(p);
302		return(abs_str);
303	} else
304		return(p);
305}
306
307static int
308infer_path(const char *p, char **ifp)
309{
310	char *cp;
311
312	cp = strrchr(p, '/');
313	if (cp == NULL) {
314		*ifp = xstrdup(p);
315		return(0);
316	}
317
318	if (!cp[1]) {
319		error("Invalid path");
320		return(-1);
321	}
322
323	*ifp = xstrdup(cp + 1);
324	return(0);
325}
326
327static int
328parse_getput_flags(const char **cpp, int *pflag)
329{
330	const char *cp = *cpp;
331
332	/* Check for flags */
333	if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
334		switch (cp[1]) {
335		case 'p':
336		case 'P':
337			*pflag = 1;
338			break;
339		default:
340			error("Invalid flag -%c", cp[1]);
341			return(-1);
342		}
343		cp += 2;
344		*cpp = cp + strspn(cp, WHITESPACE);
345	}
346
347	return(0);
348}
349
350static int
351parse_ls_flags(const char **cpp, int *lflag)
352{
353	const char *cp = *cpp;
354
355	/* Defaults */
356	*lflag = LS_NAME_SORT;
357
358	/* Check for flags */
359	if (cp++[0] == '-') {
360		for (; strchr(WHITESPACE, *cp) == NULL; cp++) {
361			switch (*cp) {
362			case 'l':
363				*lflag &= ~VIEW_FLAGS;
364				*lflag |= LS_LONG_VIEW;
365				break;
366			case '1':
367				*lflag &= ~VIEW_FLAGS;
368				*lflag |= LS_SHORT_VIEW;
369				break;
370			case 'n':
371				*lflag &= ~VIEW_FLAGS;
372				*lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
373				break;
374			case 'S':
375				*lflag &= ~SORT_FLAGS;
376				*lflag |= LS_SIZE_SORT;
377				break;
378			case 't':
379				*lflag &= ~SORT_FLAGS;
380				*lflag |= LS_TIME_SORT;
381				break;
382			case 'r':
383				*lflag |= LS_REVERSE_SORT;
384				break;
385			case 'f':
386				*lflag &= ~SORT_FLAGS;
387				break;
388			case 'a':
389				*lflag |= LS_SHOW_ALL;
390				break;
391			default:
392				error("Invalid flag -%c", *cp);
393				return(-1);
394			}
395		}
396		*cpp = cp + strspn(cp, WHITESPACE);
397	}
398
399	return(0);
400}
401
402static int
403get_pathname(const char **cpp, char **path)
404{
405	const char *cp = *cpp, *end;
406	char quot;
407	int i, j;
408
409	cp += strspn(cp, WHITESPACE);
410	if (!*cp) {
411		*cpp = cp;
412		*path = NULL;
413		return (0);
414	}
415
416	*path = xmalloc(strlen(cp) + 1);
417
418	/* Check for quoted filenames */
419	if (*cp == '\"' || *cp == '\'') {
420		quot = *cp++;
421
422		/* Search for terminating quote, unescape some chars */
423		for (i = j = 0; i <= strlen(cp); i++) {
424			if (cp[i] == quot) {	/* Found quote */
425				i++;
426				(*path)[j] = '\0';
427				break;
428			}
429			if (cp[i] == '\0') {	/* End of string */
430				error("Unterminated quote");
431				goto fail;
432			}
433			if (cp[i] == '\\') {	/* Escaped characters */
434				i++;
435				if (cp[i] != '\'' && cp[i] != '\"' &&
436				    cp[i] != '\\') {
437					error("Bad escaped character '\\%c'",
438					    cp[i]);
439					goto fail;
440				}
441			}
442			(*path)[j++] = cp[i];
443		}
444
445		if (j == 0) {
446			error("Empty quotes");
447			goto fail;
448		}
449		*cpp = cp + i + strspn(cp + i, WHITESPACE);
450	} else {
451		/* Read to end of filename */
452		end = strpbrk(cp, WHITESPACE);
453		if (end == NULL)
454			end = strchr(cp, '\0');
455		*cpp = end + strspn(end, WHITESPACE);
456
457		memcpy(*path, cp, end - cp);
458		(*path)[end - cp] = '\0';
459	}
460	return (0);
461
462 fail:
463	xfree(*path);
464	*path = NULL;
465	return (-1);
466}
467
468static int
469is_dir(char *path)
470{
471	struct stat sb;
472
473	/* XXX: report errors? */
474	if (stat(path, &sb) == -1)
475		return(0);
476
477	return(sb.st_mode & S_IFDIR);
478}
479
480static int
481is_reg(char *path)
482{
483	struct stat sb;
484
485	if (stat(path, &sb) == -1)
486		fatal("stat %s: %s", path, strerror(errno));
487
488	return(S_ISREG(sb.st_mode));
489}
490
491static int
492remote_is_dir(struct sftp_conn *conn, char *path)
493{
494	Attrib *a;
495
496	/* XXX: report errors? */
497	if ((a = do_stat(conn, path, 1)) == NULL)
498		return(0);
499	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
500		return(0);
501	return(a->perm & S_IFDIR);
502}
503
504static int
505process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
506{
507	char *abs_src = NULL;
508	char *abs_dst = NULL;
509	char *tmp;
510	glob_t g;
511	int err = 0;
512	int i;
513
514	abs_src = xstrdup(src);
515	abs_src = make_absolute(abs_src, pwd);
516
517	memset(&g, 0, sizeof(g));
518	debug3("Looking up %s", abs_src);
519	if (remote_glob(conn, abs_src, 0, NULL, &g)) {
520		error("File \"%s\" not found.", abs_src);
521		err = -1;
522		goto out;
523	}
524
525	/* If multiple matches, dst must be a directory or unspecified */
526	if (g.gl_matchc > 1 && dst && !is_dir(dst)) {
527		error("Multiple files match, but \"%s\" is not a directory",
528		    dst);
529		err = -1;
530		goto out;
531	}
532
533	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
534		if (infer_path(g.gl_pathv[i], &tmp)) {
535			err = -1;
536			goto out;
537		}
538
539		if (g.gl_matchc == 1 && dst) {
540			/* If directory specified, append filename */
541			if (is_dir(dst)) {
542				if (infer_path(g.gl_pathv[0], &tmp)) {
543					err = 1;
544					goto out;
545				}
546				abs_dst = path_append(dst, tmp);
547				xfree(tmp);
548			} else
549				abs_dst = xstrdup(dst);
550		} else if (dst) {
551			abs_dst = path_append(dst, tmp);
552			xfree(tmp);
553		} else
554			abs_dst = tmp;
555
556		printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
557		if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
558			err = -1;
559		xfree(abs_dst);
560		abs_dst = NULL;
561	}
562
563out:
564	xfree(abs_src);
565	if (abs_dst)
566		xfree(abs_dst);
567	globfree(&g);
568	return(err);
569}
570
571static int
572process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
573{
574	char *tmp_dst = NULL;
575	char *abs_dst = NULL;
576	char *tmp;
577	glob_t g;
578	int err = 0;
579	int i;
580
581	if (dst) {
582		tmp_dst = xstrdup(dst);
583		tmp_dst = make_absolute(tmp_dst, pwd);
584	}
585
586	memset(&g, 0, sizeof(g));
587	debug3("Looking up %s", src);
588	if (glob(src, 0, NULL, &g)) {
589		error("File \"%s\" not found.", src);
590		err = -1;
591		goto out;
592	}
593
594	/* If multiple matches, dst may be directory or unspecified */
595	if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) {
596		error("Multiple files match, but \"%s\" is not a directory",
597		    tmp_dst);
598		err = -1;
599		goto out;
600	}
601
602	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
603		if (!is_reg(g.gl_pathv[i])) {
604			error("skipping non-regular file %s",
605			    g.gl_pathv[i]);
606			continue;
607		}
608		if (infer_path(g.gl_pathv[i], &tmp)) {
609			err = -1;
610			goto out;
611		}
612
613		if (g.gl_matchc == 1 && tmp_dst) {
614			/* If directory specified, append filename */
615			if (remote_is_dir(conn, tmp_dst)) {
616				if (infer_path(g.gl_pathv[0], &tmp)) {
617					err = 1;
618					goto out;
619				}
620				abs_dst = path_append(tmp_dst, tmp);
621				xfree(tmp);
622			} else
623				abs_dst = xstrdup(tmp_dst);
624
625		} else if (tmp_dst) {
626			abs_dst = path_append(tmp_dst, tmp);
627			xfree(tmp);
628		} else
629			abs_dst = make_absolute(tmp, pwd);
630
631		printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
632		if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
633			err = -1;
634	}
635
636out:
637	if (abs_dst)
638		xfree(abs_dst);
639	if (tmp_dst)
640		xfree(tmp_dst);
641	globfree(&g);
642	return(err);
643}
644
645static int
646sdirent_comp(const void *aa, const void *bb)
647{
648	SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
649	SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
650	int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
651
652#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
653	if (sort_flag & LS_NAME_SORT)
654		return (rmul * strcmp(a->filename, b->filename));
655	else if (sort_flag & LS_TIME_SORT)
656		return (rmul * NCMP(a->a.mtime, b->a.mtime));
657	else if (sort_flag & LS_SIZE_SORT)
658		return (rmul * NCMP(a->a.size, b->a.size));
659
660	fatal("Unknown ls sort type");
661}
662
663/* sftp ls.1 replacement for directories */
664static int
665do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
666{
667	int n, c = 1, colspace = 0, columns = 1;
668	SFTP_DIRENT **d;
669
670	if ((n = do_readdir(conn, path, &d)) != 0)
671		return (n);
672
673	if (!(lflag & LS_SHORT_VIEW)) {
674		int m = 0, width = 80;
675		struct winsize ws;
676		char *tmp;
677
678		/* Count entries for sort and find longest filename */
679		for (n = 0; d[n] != NULL; n++) {
680			if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
681				m = MAX(m, strlen(d[n]->filename));
682		}
683
684		/* Add any subpath that also needs to be counted */
685		tmp = path_strip(path, strip_path);
686		m += strlen(tmp);
687		xfree(tmp);
688
689		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
690			width = ws.ws_col;
691
692		columns = width / (m + 2);
693		columns = MAX(columns, 1);
694		colspace = width / columns;
695		colspace = MIN(colspace, width);
696	}
697
698	if (lflag & SORT_FLAGS) {
699		sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
700		qsort(d, n, sizeof(*d), sdirent_comp);
701	}
702
703	for (n = 0; d[n] != NULL && !interrupted; n++) {
704		char *tmp, *fname;
705
706		if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
707			continue;
708
709		tmp = path_append(path, d[n]->filename);
710		fname = path_strip(tmp, strip_path);
711		xfree(tmp);
712
713		if (lflag & LS_LONG_VIEW) {
714			if (lflag & LS_NUMERIC_VIEW) {
715				char *lname;
716				struct stat sb;
717
718				memset(&sb, 0, sizeof(sb));
719				attrib_to_stat(&d[n]->a, &sb);
720				lname = ls_file(fname, &sb, 1);
721				printf("%s\n", lname);
722				xfree(lname);
723			} else
724				printf("%s\n", d[n]->longname);
725		} else {
726			printf("%-*s", colspace, fname);
727			if (c >= columns) {
728				printf("\n");
729				c = 1;
730			} else
731				c++;
732		}
733
734		xfree(fname);
735	}
736
737	if (!(lflag & LS_LONG_VIEW) && (c != 1))
738		printf("\n");
739
740	free_sftp_dirents(d);
741	return (0);
742}
743
744/* sftp ls.1 replacement which handles path globs */
745static int
746do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
747    int lflag)
748{
749	glob_t g;
750	int i, c = 1, colspace = 0, columns = 1;
751	Attrib *a = NULL;
752
753	memset(&g, 0, sizeof(g));
754
755	if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
756	    NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
757		if (g.gl_pathc)
758			globfree(&g);
759		error("Can't ls: \"%s\" not found", path);
760		return (-1);
761	}
762
763	if (interrupted)
764		goto out;
765
766	/*
767	 * If the glob returns a single match and it is a directory,
768	 * then just list its contents.
769	 */
770	if (g.gl_matchc == 1) {
771		if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
772			globfree(&g);
773			return (-1);
774		}
775		if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
776		    S_ISDIR(a->perm)) {
777			int err;
778
779			err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
780			globfree(&g);
781			return (err);
782		}
783	}
784
785	if (!(lflag & LS_SHORT_VIEW)) {
786		int m = 0, width = 80;
787		struct winsize ws;
788
789		/* Count entries for sort and find longest filename */
790		for (i = 0; g.gl_pathv[i]; i++)
791			m = MAX(m, strlen(g.gl_pathv[i]));
792
793		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
794			width = ws.ws_col;
795
796		columns = width / (m + 2);
797		columns = MAX(columns, 1);
798		colspace = width / columns;
799	}
800
801	for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
802		char *fname;
803
804		fname = path_strip(g.gl_pathv[i], strip_path);
805
806		if (lflag & LS_LONG_VIEW) {
807			char *lname;
808			struct stat sb;
809
810			/*
811			 * XXX: this is slow - 1 roundtrip per path
812			 * A solution to this is to fork glob() and
813			 * build a sftp specific version which keeps the
814			 * attribs (which currently get thrown away)
815			 * that the server returns as well as the filenames.
816			 */
817			memset(&sb, 0, sizeof(sb));
818			if (a == NULL)
819				a = do_lstat(conn, g.gl_pathv[i], 1);
820			if (a != NULL)
821				attrib_to_stat(a, &sb);
822			lname = ls_file(fname, &sb, 1);
823			printf("%s\n", lname);
824			xfree(lname);
825		} else {
826			printf("%-*s", colspace, fname);
827			if (c >= columns) {
828				printf("\n");
829				c = 1;
830			} else
831				c++;
832		}
833		xfree(fname);
834	}
835
836	if (!(lflag & LS_LONG_VIEW) && (c != 1))
837		printf("\n");
838
839 out:
840	if (g.gl_pathc)
841		globfree(&g);
842
843	return (0);
844}
845
846static int
847parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
848    unsigned long *n_arg, char **path1, char **path2)
849{
850	const char *cmd, *cp = *cpp;
851	char *cp2;
852	int base = 0;
853	long l;
854	int i, cmdnum;
855
856	/* Skip leading whitespace */
857	cp = cp + strspn(cp, WHITESPACE);
858
859	/* Ignore blank lines and lines which begin with comment '#' char */
860	if (*cp == '\0' || *cp == '#')
861		return (0);
862
863	/* Check for leading '-' (disable error processing) */
864	*iflag = 0;
865	if (*cp == '-') {
866		*iflag = 1;
867		cp++;
868	}
869
870	/* Figure out which command we have */
871	for (i = 0; cmds[i].c; i++) {
872		int cmdlen = strlen(cmds[i].c);
873
874		/* Check for command followed by whitespace */
875		if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
876		    strchr(WHITESPACE, cp[cmdlen])) {
877			cp += cmdlen;
878			cp = cp + strspn(cp, WHITESPACE);
879			break;
880		}
881	}
882	cmdnum = cmds[i].n;
883	cmd = cmds[i].c;
884
885	/* Special case */
886	if (*cp == '!') {
887		cp++;
888		cmdnum = I_SHELL;
889	} else if (cmdnum == -1) {
890		error("Invalid command.");
891		return (-1);
892	}
893
894	/* Get arguments and parse flags */
895	*lflag = *pflag = *n_arg = 0;
896	*path1 = *path2 = NULL;
897	switch (cmdnum) {
898	case I_GET:
899	case I_PUT:
900		if (parse_getput_flags(&cp, pflag))
901			return(-1);
902		/* Get first pathname (mandatory) */
903		if (get_pathname(&cp, path1))
904			return(-1);
905		if (*path1 == NULL) {
906			error("You must specify at least one path after a "
907			    "%s command.", cmd);
908			return(-1);
909		}
910		/* Try to get second pathname (optional) */
911		if (get_pathname(&cp, path2))
912			return(-1);
913		break;
914	case I_RENAME:
915	case I_SYMLINK:
916		if (get_pathname(&cp, path1))
917			return(-1);
918		if (get_pathname(&cp, path2))
919			return(-1);
920		if (!*path1 || !*path2) {
921			error("You must specify two paths after a %s "
922			    "command.", cmd);
923			return(-1);
924		}
925		break;
926	case I_RM:
927	case I_MKDIR:
928	case I_RMDIR:
929	case I_CHDIR:
930	case I_LCHDIR:
931	case I_LMKDIR:
932		/* Get pathname (mandatory) */
933		if (get_pathname(&cp, path1))
934			return(-1);
935		if (*path1 == NULL) {
936			error("You must specify a path after a %s command.",
937			    cmd);
938			return(-1);
939		}
940		break;
941	case I_LS:
942		if (parse_ls_flags(&cp, lflag))
943			return(-1);
944		/* Path is optional */
945		if (get_pathname(&cp, path1))
946			return(-1);
947		break;
948	case I_LLS:
949	case I_SHELL:
950		/* Uses the rest of the line */
951		break;
952	case I_LUMASK:
953		base = 8;
954	case I_CHMOD:
955		base = 8;
956	case I_CHOWN:
957	case I_CHGRP:
958		/* Get numeric arg (mandatory) */
959		l = strtol(cp, &cp2, base);
960		if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
961		    errno == ERANGE) || l < 0) {
962			error("You must supply a numeric argument "
963			    "to the %s command.", cmd);
964			return(-1);
965		}
966		cp = cp2;
967		*n_arg = l;
968		if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
969			break;
970		if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
971			error("You must supply a numeric argument "
972			    "to the %s command.", cmd);
973			return(-1);
974		}
975		cp += strspn(cp, WHITESPACE);
976
977		/* Get pathname (mandatory) */
978		if (get_pathname(&cp, path1))
979			return(-1);
980		if (*path1 == NULL) {
981			error("You must specify a path after a %s command.",
982			    cmd);
983			return(-1);
984		}
985		break;
986	case I_QUIT:
987	case I_PWD:
988	case I_LPWD:
989	case I_HELP:
990	case I_VERSION:
991	case I_PROGRESS:
992		break;
993	default:
994		fatal("Command not implemented");
995	}
996
997	*cpp = cp;
998	return(cmdnum);
999}
1000
1001static int
1002parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1003    int err_abort)
1004{
1005	char *path1, *path2, *tmp;
1006	int pflag, lflag, iflag, cmdnum, i;
1007	unsigned long n_arg;
1008	Attrib a, *aa;
1009	char path_buf[MAXPATHLEN];
1010	int err = 0;
1011	glob_t g;
1012
1013	path1 = path2 = NULL;
1014	cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &n_arg,
1015	    &path1, &path2);
1016
1017	if (iflag != 0)
1018		err_abort = 0;
1019
1020	memset(&g, 0, sizeof(g));
1021
1022	/* Perform command */
1023	switch (cmdnum) {
1024	case 0:
1025		/* Blank line */
1026		break;
1027	case -1:
1028		/* Unrecognized command */
1029		err = -1;
1030		break;
1031	case I_GET:
1032		err = process_get(conn, path1, path2, *pwd, pflag);
1033		break;
1034	case I_PUT:
1035		err = process_put(conn, path1, path2, *pwd, pflag);
1036		break;
1037	case I_RENAME:
1038		path1 = make_absolute(path1, *pwd);
1039		path2 = make_absolute(path2, *pwd);
1040		err = do_rename(conn, path1, path2);
1041		break;
1042	case I_SYMLINK:
1043		path2 = make_absolute(path2, *pwd);
1044		err = do_symlink(conn, path1, path2);
1045		break;
1046	case I_RM:
1047		path1 = make_absolute(path1, *pwd);
1048		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1049		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1050			printf("Removing %s\n", g.gl_pathv[i]);
1051			err = do_rm(conn, g.gl_pathv[i]);
1052			if (err != 0 && err_abort)
1053				break;
1054		}
1055		break;
1056	case I_MKDIR:
1057		path1 = make_absolute(path1, *pwd);
1058		attrib_clear(&a);
1059		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1060		a.perm = 0777;
1061		err = do_mkdir(conn, path1, &a);
1062		break;
1063	case I_RMDIR:
1064		path1 = make_absolute(path1, *pwd);
1065		err = do_rmdir(conn, path1);
1066		break;
1067	case I_CHDIR:
1068		path1 = make_absolute(path1, *pwd);
1069		if ((tmp = do_realpath(conn, path1)) == NULL) {
1070			err = 1;
1071			break;
1072		}
1073		if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1074			xfree(tmp);
1075			err = 1;
1076			break;
1077		}
1078		if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1079			error("Can't change directory: Can't check target");
1080			xfree(tmp);
1081			err = 1;
1082			break;
1083		}
1084		if (!S_ISDIR(aa->perm)) {
1085			error("Can't change directory: \"%s\" is not "
1086			    "a directory", tmp);
1087			xfree(tmp);
1088			err = 1;
1089			break;
1090		}
1091		xfree(*pwd);
1092		*pwd = tmp;
1093		break;
1094	case I_LS:
1095		if (!path1) {
1096			do_globbed_ls(conn, *pwd, *pwd, lflag);
1097			break;
1098		}
1099
1100		/* Strip pwd off beginning of non-absolute paths */
1101		tmp = NULL;
1102		if (*path1 != '/')
1103			tmp = *pwd;
1104
1105		path1 = make_absolute(path1, *pwd);
1106		err = do_globbed_ls(conn, path1, tmp, lflag);
1107		break;
1108	case I_LCHDIR:
1109		if (chdir(path1) == -1) {
1110			error("Couldn't change local directory to "
1111			    "\"%s\": %s", path1, strerror(errno));
1112			err = 1;
1113		}
1114		break;
1115	case I_LMKDIR:
1116		if (mkdir(path1, 0777) == -1) {
1117			error("Couldn't create local directory "
1118			    "\"%s\": %s", path1, strerror(errno));
1119			err = 1;
1120		}
1121		break;
1122	case I_LLS:
1123		local_do_ls(cmd);
1124		break;
1125	case I_SHELL:
1126		local_do_shell(cmd);
1127		break;
1128	case I_LUMASK:
1129		umask(n_arg);
1130		printf("Local umask: %03lo\n", n_arg);
1131		break;
1132	case I_CHMOD:
1133		path1 = make_absolute(path1, *pwd);
1134		attrib_clear(&a);
1135		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1136		a.perm = n_arg;
1137		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1138		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1139			printf("Changing mode on %s\n", g.gl_pathv[i]);
1140			err = do_setstat(conn, g.gl_pathv[i], &a);
1141			if (err != 0 && err_abort)
1142				break;
1143		}
1144		break;
1145	case I_CHOWN:
1146	case I_CHGRP:
1147		path1 = make_absolute(path1, *pwd);
1148		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1149		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1150			if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
1151				if (err != 0 && err_abort)
1152					break;
1153				else
1154					continue;
1155			}
1156			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1157				error("Can't get current ownership of "
1158				    "remote file \"%s\"", g.gl_pathv[i]);
1159				if (err != 0 && err_abort)
1160					break;
1161				else
1162					continue;
1163			}
1164			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1165			if (cmdnum == I_CHOWN) {
1166				printf("Changing owner on %s\n", g.gl_pathv[i]);
1167				aa->uid = n_arg;
1168			} else {
1169				printf("Changing group on %s\n", g.gl_pathv[i]);
1170				aa->gid = n_arg;
1171			}
1172			err = do_setstat(conn, g.gl_pathv[i], aa);
1173			if (err != 0 && err_abort)
1174				break;
1175		}
1176		break;
1177	case I_PWD:
1178		printf("Remote working directory: %s\n", *pwd);
1179		break;
1180	case I_LPWD:
1181		if (!getcwd(path_buf, sizeof(path_buf))) {
1182			error("Couldn't get local cwd: %s", strerror(errno));
1183			err = -1;
1184			break;
1185		}
1186		printf("Local working directory: %s\n", path_buf);
1187		break;
1188	case I_QUIT:
1189		/* Processed below */
1190		break;
1191	case I_HELP:
1192		help();
1193		break;
1194	case I_VERSION:
1195		printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1196		break;
1197	case I_PROGRESS:
1198		showprogress = !showprogress;
1199		if (showprogress)
1200			printf("Progress meter enabled\n");
1201		else
1202			printf("Progress meter disabled\n");
1203		break;
1204	default:
1205		fatal("%d is not implemented", cmdnum);
1206	}
1207
1208	if (g.gl_pathc)
1209		globfree(&g);
1210	if (path1)
1211		xfree(path1);
1212	if (path2)
1213		xfree(path2);
1214
1215	/* If an unignored error occurs in batch mode we should abort. */
1216	if (err_abort && err != 0)
1217		return (-1);
1218	else if (cmdnum == I_QUIT)
1219		return (1);
1220
1221	return (0);
1222}
1223
1224#ifdef USE_LIBEDIT
1225static char *
1226prompt(EditLine *el)
1227{
1228	return ("sftp> ");
1229}
1230#endif
1231
1232int
1233interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
1234{
1235	char *pwd;
1236	char *dir = NULL;
1237	char cmd[2048];
1238	struct sftp_conn *conn;
1239	int err;
1240	EditLine *el = NULL;
1241#ifdef USE_LIBEDIT
1242	History *hl = NULL;
1243	HistEvent hev;
1244	extern char *__progname;
1245
1246	if (!batchmode && isatty(STDIN_FILENO)) {
1247		if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1248			fatal("Couldn't initialise editline");
1249		if ((hl = history_init()) == NULL)
1250			fatal("Couldn't initialise editline history");
1251		history(hl, &hev, H_SETSIZE, 100);
1252		el_set(el, EL_HIST, history, hl);
1253
1254		el_set(el, EL_PROMPT, prompt);
1255		el_set(el, EL_EDITOR, "emacs");
1256		el_set(el, EL_TERMINAL, NULL);
1257		el_set(el, EL_SIGNAL, 1);
1258		el_source(el, NULL);
1259	}
1260#endif /* USE_LIBEDIT */
1261
1262	conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
1263	if (conn == NULL)
1264		fatal("Couldn't initialise connection to server");
1265
1266	pwd = do_realpath(conn, ".");
1267	if (pwd == NULL)
1268		fatal("Need cwd");
1269
1270	if (file1 != NULL) {
1271		dir = xstrdup(file1);
1272		dir = make_absolute(dir, pwd);
1273
1274		if (remote_is_dir(conn, dir) && file2 == NULL) {
1275			printf("Changing to: %s\n", dir);
1276			snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
1277			if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) {
1278				xfree(dir);
1279				xfree(pwd);
1280				return (-1);
1281			}
1282		} else {
1283			if (file2 == NULL)
1284				snprintf(cmd, sizeof cmd, "get %s", dir);
1285			else
1286				snprintf(cmd, sizeof cmd, "get %s %s", dir,
1287				    file2);
1288
1289			err = parse_dispatch_command(conn, cmd, &pwd, 1);
1290			xfree(dir);
1291			xfree(pwd);
1292			return (err);
1293		}
1294		xfree(dir);
1295	}
1296
1297#if HAVE_SETVBUF
1298	setvbuf(stdout, NULL, _IOLBF, 0);
1299	setvbuf(infile, NULL, _IOLBF, 0);
1300#else
1301       setlinebuf(stdout);
1302       setlinebuf(infile);
1303#endif
1304
1305	err = 0;
1306	for (;;) {
1307		char *cp;
1308
1309		signal(SIGINT, SIG_IGN);
1310
1311		if (el == NULL) {
1312			printf("sftp> ");
1313			if (fgets(cmd, sizeof(cmd), infile) == NULL) {
1314				printf("\n");
1315				break;
1316			}
1317			if (batchmode) /* Echo command */
1318				printf("%s", cmd);
1319		} else {
1320#ifdef USE_LIBEDIT
1321			const char *line;
1322			int count = 0;
1323
1324			if ((line = el_gets(el, &count)) == NULL || count <= 0)
1325				break;
1326			history(hl, &hev, H_ENTER, line);
1327			if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1328				fprintf(stderr, "Error: input line too long\n");
1329				continue;
1330			}
1331#endif /* USE_LIBEDIT */
1332		}
1333
1334		cp = strrchr(cmd, '\n');
1335		if (cp)
1336			*cp = '\0';
1337
1338		/* Handle user interrupts gracefully during commands */
1339		interrupted = 0;
1340		signal(SIGINT, cmd_interrupt);
1341
1342		err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
1343		if (err != 0)
1344			break;
1345	}
1346	xfree(pwd);
1347
1348	/* err == 1 signifies normal "quit" exit */
1349	return (err >= 0 ? 0 : -1);
1350}
1351
1352static void
1353connect_to_server(char *path, char **args, int *in, int *out)
1354{
1355	int c_in, c_out;
1356
1357#ifdef USE_PIPES
1358	int pin[2], pout[2];
1359
1360	if ((pipe(pin) == -1) || (pipe(pout) == -1))
1361		fatal("pipe: %s", strerror(errno));
1362	*in = pin[0];
1363	*out = pout[1];
1364	c_in = pout[0];
1365	c_out = pin[1];
1366#else /* USE_PIPES */
1367	int inout[2];
1368
1369	if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
1370		fatal("socketpair: %s", strerror(errno));
1371	*in = *out = inout[0];
1372	c_in = c_out = inout[1];
1373#endif /* USE_PIPES */
1374
1375	if ((sshpid = fork()) == -1)
1376		fatal("fork: %s", strerror(errno));
1377	else if (sshpid == 0) {
1378		if ((dup2(c_in, STDIN_FILENO) == -1) ||
1379		    (dup2(c_out, STDOUT_FILENO) == -1)) {
1380			fprintf(stderr, "dup2: %s\n", strerror(errno));
1381			_exit(1);
1382		}
1383		close(*in);
1384		close(*out);
1385		close(c_in);
1386		close(c_out);
1387
1388		/*
1389		 * The underlying ssh is in the same process group, so we must
1390		 * ignore SIGINT if we want to gracefully abort commands,
1391		 * otherwise the signal will make it to the ssh process and
1392		 * kill it too
1393		 */
1394		signal(SIGINT, SIG_IGN);
1395		execvp(path, args);
1396		fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
1397		_exit(1);
1398	}
1399
1400	signal(SIGTERM, killchild);
1401	signal(SIGINT, killchild);
1402	signal(SIGHUP, killchild);
1403	close(c_in);
1404	close(c_out);
1405}
1406
1407static void
1408usage(void)
1409{
1410	extern char *__progname;
1411
1412	fprintf(stderr,
1413	    "usage: %s [-1Cv] [-B buffer_size] [-b batchfile] [-F ssh_config]\n"
1414	    "            [-o ssh_option] [-P sftp_server_path] [-R num_requests]\n"
1415	    "            [-S program] [-s subsystem | sftp_server] host\n"
1416	    "       %s [[user@]host[:file [file]]]\n"
1417	    "       %s [[user@]host[:dir[/]]]\n"
1418	    "       %s -b batchfile [user@]host\n", __progname, __progname, __progname, __progname);
1419	exit(1);
1420}
1421
1422int
1423main(int argc, char **argv)
1424{
1425	int in, out, ch, err;
1426	char *host, *userhost, *cp, *file2 = NULL;
1427	int debug_level = 0, sshver = 2;
1428	char *file1 = NULL, *sftp_server = NULL;
1429	char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
1430	LogLevel ll = SYSLOG_LEVEL_INFO;
1431	arglist args;
1432	extern int optind;
1433	extern char *optarg;
1434
1435	__progname = ssh_get_progname(argv[0]);
1436	args.list = NULL;
1437	addargs(&args, "ssh");		/* overwritten with ssh_program */
1438	addargs(&args, "-oForwardX11 no");
1439	addargs(&args, "-oForwardAgent no");
1440	addargs(&args, "-oClearAllForwardings yes");
1441
1442	ll = SYSLOG_LEVEL_INFO;
1443	infile = stdin;
1444
1445	while ((ch = getopt(argc, argv, "1hvCo:s:S:b:B:F:P:R:")) != -1) {
1446		switch (ch) {
1447		case 'C':
1448			addargs(&args, "-C");
1449			break;
1450		case 'v':
1451			if (debug_level < 3) {
1452				addargs(&args, "-v");
1453				ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
1454			}
1455			debug_level++;
1456			break;
1457		case 'F':
1458		case 'o':
1459			addargs(&args, "-%c%s", ch, optarg);
1460			break;
1461		case '1':
1462			sshver = 1;
1463			if (sftp_server == NULL)
1464				sftp_server = _PATH_SFTP_SERVER;
1465			break;
1466		case 's':
1467			sftp_server = optarg;
1468			break;
1469		case 'S':
1470			ssh_program = optarg;
1471			break;
1472		case 'b':
1473			if (batchmode)
1474				fatal("Batch file already specified.");
1475
1476			/* Allow "-" as stdin */
1477			if (strcmp(optarg, "-") != 0 &&
1478			   (infile = fopen(optarg, "r")) == NULL)
1479				fatal("%s (%s).", strerror(errno), optarg);
1480			showprogress = 0;
1481			batchmode = 1;
1482			addargs(&args, "-obatchmode yes");
1483			break;
1484		case 'P':
1485			sftp_direct = optarg;
1486			break;
1487		case 'B':
1488			copy_buffer_len = strtol(optarg, &cp, 10);
1489			if (copy_buffer_len == 0 || *cp != '\0')
1490				fatal("Invalid buffer size \"%s\"", optarg);
1491			break;
1492		case 'R':
1493			num_requests = strtol(optarg, &cp, 10);
1494			if (num_requests == 0 || *cp != '\0')
1495				fatal("Invalid number of requests \"%s\"",
1496				    optarg);
1497			break;
1498		case 'h':
1499		default:
1500			usage();
1501		}
1502	}
1503
1504	if (!isatty(STDERR_FILENO))
1505		showprogress = 0;
1506
1507	log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
1508
1509	if (sftp_direct == NULL) {
1510		if (optind == argc || argc > (optind + 2))
1511			usage();
1512
1513		userhost = xstrdup(argv[optind]);
1514		file2 = argv[optind+1];
1515
1516		if ((host = strrchr(userhost, '@')) == NULL)
1517			host = userhost;
1518		else {
1519			*host++ = '\0';
1520			if (!userhost[0]) {
1521				fprintf(stderr, "Missing username\n");
1522				usage();
1523			}
1524			addargs(&args, "-l%s",userhost);
1525		}
1526
1527		if ((cp = colon(host)) != NULL) {
1528			*cp++ = '\0';
1529			file1 = cp;
1530		}
1531
1532		host = cleanhostname(host);
1533		if (!*host) {
1534			fprintf(stderr, "Missing hostname\n");
1535			usage();
1536		}
1537
1538		addargs(&args, "-oProtocol %d", sshver);
1539
1540		/* no subsystem if the server-spec contains a '/' */
1541		if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
1542			addargs(&args, "-s");
1543
1544		addargs(&args, "%s", host);
1545		addargs(&args, "%s", (sftp_server != NULL ?
1546		    sftp_server : "sftp"));
1547		args.list[0] = ssh_program;
1548
1549		if (!batchmode)
1550			fprintf(stderr, "Connecting to %s...\n", host);
1551		connect_to_server(ssh_program, args.list, &in, &out);
1552	} else {
1553		args.list = NULL;
1554		addargs(&args, "sftp-server");
1555
1556		if (!batchmode)
1557			fprintf(stderr, "Attaching to %s...\n", sftp_direct);
1558		connect_to_server(sftp_direct, args.list, &in, &out);
1559	}
1560
1561	err = interactive_loop(in, out, file1, file2);
1562
1563#if !defined(USE_PIPES)
1564       shutdown(in, SHUT_RDWR);
1565       shutdown(out, SHUT_RDWR);
1566#endif
1567
1568	close(in);
1569	close(out);
1570	if (batchmode)
1571		fclose(infile);
1572
1573	while (waitpid(sshpid, NULL, 0) == -1)
1574		if (errno != EINTR)
1575			fatal("Couldn't wait for ssh process: %s",
1576			    strerror(errno));
1577
1578	exit(err == 0 ? 0 : 1);
1579}
1580