xref: /titanic_50/usr/src/cmd/ssh/sftp/sftp.c (revision cf58b2543a341d4f5a661de47968a7016c3207ff)
17c478bd9Sstevel@tonic-gate /*
290685d2cSjp161948  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
37c478bd9Sstevel@tonic-gate  *
490685d2cSjp161948  * Permission to use, copy, modify, and distribute this software for any
590685d2cSjp161948  * purpose with or without fee is hereby granted, provided that the above
690685d2cSjp161948  * copyright notice and this permission notice appear in all copies.
77c478bd9Sstevel@tonic-gate  *
890685d2cSjp161948  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
990685d2cSjp161948  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1090685d2cSjp161948  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1190685d2cSjp161948  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1290685d2cSjp161948  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1390685d2cSjp161948  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1490685d2cSjp161948  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
157c478bd9Sstevel@tonic-gate  */
167c478bd9Sstevel@tonic-gate 
1790685d2cSjp161948 /* $OpenBSD: sftp.c,v 1.96 2007/01/03 04:09:15 stevesk Exp $ */
187c478bd9Sstevel@tonic-gate 
1990685d2cSjp161948 #include "includes.h"
207c478bd9Sstevel@tonic-gate 
2190685d2cSjp161948 #include <sys/types.h>
2290685d2cSjp161948 #include <sys/ioctl.h>
2390685d2cSjp161948 #ifdef HAVE_SYS_STAT_H
2490685d2cSjp161948 # include <sys/stat.h>
2590685d2cSjp161948 #endif
2690685d2cSjp161948 #include <sys/param.h>
2790685d2cSjp161948 #include <sys/socket.h>
2890685d2cSjp161948 #include <sys/wait.h>
2990685d2cSjp161948 
3090685d2cSjp161948 #include <errno.h>
3190685d2cSjp161948 
3290685d2cSjp161948 #ifdef HAVE_PATHS_H
3390685d2cSjp161948 # include <paths.h>
3490685d2cSjp161948 #endif
3584e198abSHuie-Ying Lee 
3690685d2cSjp161948 #ifdef USE_LIBEDIT
3790685d2cSjp161948 #include <histedit.h>
3890685d2cSjp161948 #else
3984e198abSHuie-Ying Lee #ifdef USE_LIBTECLA
4084e198abSHuie-Ying Lee #include <libtecla.h>
4184e198abSHuie-Ying Lee #define	MAX_LINE_LEN	2048
4284e198abSHuie-Ying Lee #define	MAX_CMD_HIST	10000
4384e198abSHuie-Ying Lee #endif /* USE_LIBTECLA */
4484e198abSHuie-Ying Lee #endif /* USE_LIBEDIT */
4584e198abSHuie-Ying Lee 
4690685d2cSjp161948 #include <signal.h>
4790685d2cSjp161948 #include <stdlib.h>
4890685d2cSjp161948 #include <stdio.h>
4990685d2cSjp161948 #include <string.h>
5090685d2cSjp161948 #include <unistd.h>
5190685d2cSjp161948 #include <stdarg.h>
5290685d2cSjp161948 
537c478bd9Sstevel@tonic-gate #include "xmalloc.h"
547c478bd9Sstevel@tonic-gate #include "log.h"
557c478bd9Sstevel@tonic-gate #include "pathnames.h"
567c478bd9Sstevel@tonic-gate #include "misc.h"
577c478bd9Sstevel@tonic-gate 
587c478bd9Sstevel@tonic-gate #include "sftp.h"
5990685d2cSjp161948 #include "buffer.h"
607c478bd9Sstevel@tonic-gate #include "sftp-common.h"
617c478bd9Sstevel@tonic-gate #include "sftp-client.h"
627c478bd9Sstevel@tonic-gate 
637c478bd9Sstevel@tonic-gate #ifdef HAVE___PROGNAME
647c478bd9Sstevel@tonic-gate extern char *__progname;
657c478bd9Sstevel@tonic-gate #else
667c478bd9Sstevel@tonic-gate char *__progname;
677c478bd9Sstevel@tonic-gate #endif
687c478bd9Sstevel@tonic-gate 
6990685d2cSjp161948 
7090685d2cSjp161948 /* File to read commands from */
717c478bd9Sstevel@tonic-gate FILE* infile;
7290685d2cSjp161948 
7390685d2cSjp161948 /* Are we in batchfile mode? */
7490685d2cSjp161948 int batchmode = 0;
7590685d2cSjp161948 
7690685d2cSjp161948 /* Size of buffer used when copying files */
777c478bd9Sstevel@tonic-gate size_t copy_buffer_len = 32768;
7890685d2cSjp161948 
7990685d2cSjp161948 /* Number of concurrent outstanding requests */
807c478bd9Sstevel@tonic-gate size_t num_requests = 16;
817c478bd9Sstevel@tonic-gate 
8290685d2cSjp161948 /* PID of ssh transport process */
8390685d2cSjp161948 static pid_t sshpid = -1;
8490685d2cSjp161948 
8590685d2cSjp161948 /* This is set to 0 if the progressmeter is not desired. */
8690685d2cSjp161948 int showprogress = 1;
8790685d2cSjp161948 
8890685d2cSjp161948 /* SIGINT received during command processing */
8990685d2cSjp161948 volatile sig_atomic_t interrupted = 0;
9090685d2cSjp161948 
9190685d2cSjp161948 /* I wish qsort() took a separate ctx for the comparison function...*/
9290685d2cSjp161948 int sort_flag;
9390685d2cSjp161948 
9490685d2cSjp161948 int remote_glob(struct sftp_conn *, const char *, int,
9590685d2cSjp161948     int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
9690685d2cSjp161948 
9790685d2cSjp161948 /* Separators for interactive commands */
9890685d2cSjp161948 #define WHITESPACE " \t\r\n"
9990685d2cSjp161948 
10090685d2cSjp161948 /* ls flags */
10190685d2cSjp161948 #define LS_LONG_VIEW	0x01	/* Full view ala ls -l */
10290685d2cSjp161948 #define LS_SHORT_VIEW	0x02	/* Single row view ala ls -1 */
10390685d2cSjp161948 #define LS_NUMERIC_VIEW	0x04	/* Long view with numeric uid/gid */
10490685d2cSjp161948 #define LS_NAME_SORT	0x08	/* Sort by name (default) */
10590685d2cSjp161948 #define LS_TIME_SORT	0x10	/* Sort by mtime */
10690685d2cSjp161948 #define LS_SIZE_SORT	0x20	/* Sort by file size */
10790685d2cSjp161948 #define LS_REVERSE_SORT	0x40	/* Reverse sort order */
10890685d2cSjp161948 #define LS_SHOW_ALL	0x80	/* Don't skip filenames starting with '.' */
10990685d2cSjp161948 
11090685d2cSjp161948 #define VIEW_FLAGS	(LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW)
11190685d2cSjp161948 #define SORT_FLAGS	(LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
11290685d2cSjp161948 
11390685d2cSjp161948 /* Commands for interactive mode */
11490685d2cSjp161948 #define I_CHDIR		1
11590685d2cSjp161948 #define I_CHGRP		2
11690685d2cSjp161948 #define I_CHMOD		3
11790685d2cSjp161948 #define I_CHOWN		4
11890685d2cSjp161948 #define I_GET		5
11990685d2cSjp161948 #define I_HELP		6
12090685d2cSjp161948 #define I_LCHDIR	7
12190685d2cSjp161948 #define I_LLS		8
12290685d2cSjp161948 #define I_LMKDIR	9
12390685d2cSjp161948 #define I_LPWD		10
12490685d2cSjp161948 #define I_LS		11
12590685d2cSjp161948 #define I_LUMASK	12
12690685d2cSjp161948 #define I_MKDIR		13
12790685d2cSjp161948 #define I_PUT		14
12890685d2cSjp161948 #define I_PWD		15
12990685d2cSjp161948 #define I_QUIT		16
13090685d2cSjp161948 #define I_RENAME	17
13190685d2cSjp161948 #define I_RM		18
13290685d2cSjp161948 #define I_RMDIR		19
13390685d2cSjp161948 #define I_SHELL		20
13490685d2cSjp161948 #define I_SYMLINK	21
13590685d2cSjp161948 #define I_VERSION	22
13690685d2cSjp161948 #define I_PROGRESS	23
13790685d2cSjp161948 
13890685d2cSjp161948 struct CMD {
13990685d2cSjp161948 	const char *c;
14090685d2cSjp161948 	const int n;
14190685d2cSjp161948 };
14290685d2cSjp161948 
14390685d2cSjp161948 static const struct CMD cmds[] = {
14490685d2cSjp161948 	{ "bye",	I_QUIT },
14590685d2cSjp161948 	{ "cd",		I_CHDIR },
14690685d2cSjp161948 	{ "chdir",	I_CHDIR },
14790685d2cSjp161948 	{ "chgrp",	I_CHGRP },
14890685d2cSjp161948 	{ "chmod",	I_CHMOD },
14990685d2cSjp161948 	{ "chown",	I_CHOWN },
15090685d2cSjp161948 	{ "dir",	I_LS },
15190685d2cSjp161948 	{ "exit",	I_QUIT },
15290685d2cSjp161948 	{ "get",	I_GET },
15390685d2cSjp161948 	{ "mget",	I_GET },
15490685d2cSjp161948 	{ "help",	I_HELP },
15590685d2cSjp161948 	{ "lcd",	I_LCHDIR },
15690685d2cSjp161948 	{ "lchdir",	I_LCHDIR },
15790685d2cSjp161948 	{ "lls",	I_LLS },
15890685d2cSjp161948 	{ "lmkdir",	I_LMKDIR },
15990685d2cSjp161948 	{ "ln",		I_SYMLINK },
16090685d2cSjp161948 	{ "lpwd",	I_LPWD },
16190685d2cSjp161948 	{ "ls",		I_LS },
16290685d2cSjp161948 	{ "lumask",	I_LUMASK },
16390685d2cSjp161948 	{ "mkdir",	I_MKDIR },
16490685d2cSjp161948 	{ "progress",	I_PROGRESS },
16590685d2cSjp161948 	{ "put",	I_PUT },
16690685d2cSjp161948 	{ "mput",	I_PUT },
16790685d2cSjp161948 	{ "pwd",	I_PWD },
16890685d2cSjp161948 	{ "quit",	I_QUIT },
16990685d2cSjp161948 	{ "rename",	I_RENAME },
17090685d2cSjp161948 	{ "rm",		I_RM },
17190685d2cSjp161948 	{ "rmdir",	I_RMDIR },
17290685d2cSjp161948 	{ "symlink",	I_SYMLINK },
17390685d2cSjp161948 	{ "version",	I_VERSION },
17490685d2cSjp161948 	{ "!",		I_SHELL },
17590685d2cSjp161948 	{ "?",		I_HELP },
17690685d2cSjp161948 	{ NULL,			-1}
17790685d2cSjp161948 };
17890685d2cSjp161948 
17990685d2cSjp161948 int interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
18090685d2cSjp161948 
18190685d2cSjp161948 /* ARGSUSED */
1827c478bd9Sstevel@tonic-gate static void
killchild(int signo)18390685d2cSjp161948 killchild(int signo)
18490685d2cSjp161948 {
18590685d2cSjp161948 	if (sshpid > 1) {
18690685d2cSjp161948 		kill(sshpid, SIGTERM);
18790685d2cSjp161948 		waitpid(sshpid, NULL, 0);
18890685d2cSjp161948 	}
18990685d2cSjp161948 
19090685d2cSjp161948 	_exit(1);
19190685d2cSjp161948 }
19290685d2cSjp161948 
19390685d2cSjp161948 /* ARGSUSED */
19490685d2cSjp161948 static void
cmd_interrupt(int signo)19590685d2cSjp161948 cmd_interrupt(int signo)
19690685d2cSjp161948 {
19790685d2cSjp161948 	const char msg[] = "\rInterrupt  \n";
19890685d2cSjp161948 	int olderrno = errno;
19990685d2cSjp161948 
20090685d2cSjp161948 	write(STDERR_FILENO, msg, sizeof(msg) - 1);
20190685d2cSjp161948 	interrupted = 1;
20290685d2cSjp161948 	errno = olderrno;
20390685d2cSjp161948 }
20490685d2cSjp161948 
20590685d2cSjp161948 static void
help(void)20690685d2cSjp161948 help(void)
20790685d2cSjp161948 {
20890685d2cSjp161948 	printf(gettext("Available commands:\n"
20990685d2cSjp161948 	    "cd path                       Change remote directory to 'path'\n"
21090685d2cSjp161948 	    "lcd path                      Change local directory to 'path'\n"
21190685d2cSjp161948 	    "chgrp grp path                Change group of file 'path' to 'grp'\n"
21290685d2cSjp161948 	    "chmod mode path               Change permissions of file 'path' to 'mode'\n"
21390685d2cSjp161948 	    "chown own path                Change owner of file 'path' to 'own'\n"
21490685d2cSjp161948 	    "help                          Display this help text\n"
21590685d2cSjp161948 	    "get remote-path [local-path]  Download file\n"
21690685d2cSjp161948 	    "lls [ls-options [path]]       Display local directory listing\n"
21790685d2cSjp161948 	    "ln oldpath newpath            Symlink remote file\n"
21890685d2cSjp161948 	    "lmkdir path                   Create local directory\n"
21990685d2cSjp161948 	    "lpwd                          Print local working directory\n"
22090685d2cSjp161948 	    "ls [path]                     Display remote directory listing\n"
22190685d2cSjp161948 	    "lumask umask                  Set local umask to 'umask'\n"
22290685d2cSjp161948 	    "mkdir path                    Create remote directory\n"
22390685d2cSjp161948 	    "progress                      Toggle display of progress meter\n"
22490685d2cSjp161948 	    "put local-path [remote-path]  Upload file\n"
22590685d2cSjp161948 	    "pwd                           Display remote working directory\n"
22690685d2cSjp161948 	    "exit                          Quit sftp\n"
22790685d2cSjp161948 	    "quit                          Quit sftp\n"
22890685d2cSjp161948 	    "rename oldpath newpath        Rename remote file\n"
22990685d2cSjp161948 	    "rmdir path                    Remove remote directory\n"
23090685d2cSjp161948 	    "rm path                       Delete remote file\n"
23190685d2cSjp161948 	    "symlink oldpath newpath       Symlink remote file\n"
23290685d2cSjp161948 	    "version                       Show SFTP version\n"
23390685d2cSjp161948 	    "!command                      Execute 'command' in local shell\n"
23490685d2cSjp161948 	    "!                             Escape to local shell\n"
23590685d2cSjp161948 	    "?                             Synonym for help\n"));
23690685d2cSjp161948 }
23790685d2cSjp161948 
23890685d2cSjp161948 static void
local_do_shell(const char * args)23990685d2cSjp161948 local_do_shell(const char *args)
24090685d2cSjp161948 {
24190685d2cSjp161948 	int status;
24290685d2cSjp161948 	char *shell;
24390685d2cSjp161948 	pid_t pid;
24490685d2cSjp161948 
24590685d2cSjp161948 	if (!*args)
24690685d2cSjp161948 		args = NULL;
24790685d2cSjp161948 
24890685d2cSjp161948 	if ((shell = getenv("SHELL")) == NULL)
24990685d2cSjp161948 		shell = _PATH_BSHELL;
25090685d2cSjp161948 
25190685d2cSjp161948 	if ((pid = fork()) == -1)
25290685d2cSjp161948 		fatal("Couldn't fork: %s", strerror(errno));
25390685d2cSjp161948 
25490685d2cSjp161948 	if (pid == 0) {
25590685d2cSjp161948 		/* XXX: child has pipe fds to ssh subproc open - issue? */
25690685d2cSjp161948 		if (args) {
25790685d2cSjp161948 			debug3("Executing %s -c \"%s\"", shell, args);
25890685d2cSjp161948 			execl(shell, shell, "-c", args, (char *)NULL);
25990685d2cSjp161948 		} else {
26090685d2cSjp161948 			debug3("Executing %s", shell);
26190685d2cSjp161948 			execl(shell, shell, (char *)NULL);
26290685d2cSjp161948 		}
26390685d2cSjp161948 		fprintf(stderr, gettext("Couldn't execute \"%s\": %s\n"), shell,
26490685d2cSjp161948 		    strerror(errno));
26590685d2cSjp161948 		_exit(1);
26690685d2cSjp161948 	}
26790685d2cSjp161948 	while (waitpid(pid, &status, 0) == -1)
26890685d2cSjp161948 		if (errno != EINTR)
26990685d2cSjp161948 			fatal("Couldn't wait for child: %s", strerror(errno));
27090685d2cSjp161948 	if (!WIFEXITED(status))
27190685d2cSjp161948 		error("Shell exited abnormally");
27290685d2cSjp161948 	else if (WEXITSTATUS(status))
27390685d2cSjp161948 		error("Shell exited with status %d", WEXITSTATUS(status));
27490685d2cSjp161948 }
27590685d2cSjp161948 
27690685d2cSjp161948 static void
local_do_ls(const char * args)27790685d2cSjp161948 local_do_ls(const char *args)
27890685d2cSjp161948 {
27990685d2cSjp161948 	if (!args || !*args)
28090685d2cSjp161948 		local_do_shell(_PATH_LS);
28190685d2cSjp161948 	else {
28290685d2cSjp161948 		int len = strlen(_PATH_LS " ") + strlen(args) + 1;
28390685d2cSjp161948 		char *buf = xmalloc(len);
28490685d2cSjp161948 
28590685d2cSjp161948 		/* XXX: quoting - rip quoting code from ftp? */
28690685d2cSjp161948 		snprintf(buf, len, _PATH_LS " %s", args);
28790685d2cSjp161948 		local_do_shell(buf);
28890685d2cSjp161948 		xfree(buf);
28990685d2cSjp161948 	}
29090685d2cSjp161948 }
29190685d2cSjp161948 
29290685d2cSjp161948 /* Strip one path (usually the pwd) from the start of another */
29390685d2cSjp161948 static char *
path_strip(char * path,char * strip)29490685d2cSjp161948 path_strip(char *path, char *strip)
29590685d2cSjp161948 {
29690685d2cSjp161948 	size_t len;
29790685d2cSjp161948 
29890685d2cSjp161948 	if (strip == NULL)
29990685d2cSjp161948 		return (xstrdup(path));
30090685d2cSjp161948 
30190685d2cSjp161948 	len = strlen(strip);
30290685d2cSjp161948 	if (strncmp(path, strip, len) == 0) {
30390685d2cSjp161948 		if (strip[len - 1] != '/' && path[len] == '/')
30490685d2cSjp161948 			len++;
30590685d2cSjp161948 		return (xstrdup(path + len));
30690685d2cSjp161948 	}
30790685d2cSjp161948 
30890685d2cSjp161948 	return (xstrdup(path));
30990685d2cSjp161948 }
31090685d2cSjp161948 
31190685d2cSjp161948 static char *
path_append(char * p1,char * p2)31290685d2cSjp161948 path_append(char *p1, char *p2)
31390685d2cSjp161948 {
31490685d2cSjp161948 	char *ret;
31590685d2cSjp161948 	size_t len = strlen(p1) + strlen(p2) + 2;
31690685d2cSjp161948 
31790685d2cSjp161948 	ret = xmalloc(len);
31890685d2cSjp161948 	strlcpy(ret, p1, len);
31990685d2cSjp161948 	if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
32090685d2cSjp161948 		strlcat(ret, "/", len);
32190685d2cSjp161948 	strlcat(ret, p2, len);
32290685d2cSjp161948 
32390685d2cSjp161948 	return(ret);
32490685d2cSjp161948 }
32590685d2cSjp161948 
32690685d2cSjp161948 static char *
make_absolute(char * p,char * pwd)32790685d2cSjp161948 make_absolute(char *p, char *pwd)
32890685d2cSjp161948 {
32990685d2cSjp161948 	char *abs_str;
33090685d2cSjp161948 
33190685d2cSjp161948 	/* Derelativise */
33290685d2cSjp161948 	if (p && p[0] != '/') {
33390685d2cSjp161948 		abs_str = path_append(pwd, p);
33490685d2cSjp161948 		xfree(p);
33590685d2cSjp161948 		return(abs_str);
33690685d2cSjp161948 	} else
33790685d2cSjp161948 		return(p);
33890685d2cSjp161948 }
33990685d2cSjp161948 
34090685d2cSjp161948 static int
infer_path(const char * p,char ** ifp)34190685d2cSjp161948 infer_path(const char *p, char **ifp)
34290685d2cSjp161948 {
34390685d2cSjp161948 	char *cp;
34490685d2cSjp161948 
34590685d2cSjp161948 	cp = strrchr(p, '/');
34690685d2cSjp161948 	if (cp == NULL) {
34790685d2cSjp161948 		*ifp = xstrdup(p);
34890685d2cSjp161948 		return(0);
34990685d2cSjp161948 	}
35090685d2cSjp161948 
35190685d2cSjp161948 	if (!cp[1]) {
35290685d2cSjp161948 		error("Invalid path");
35390685d2cSjp161948 		return(-1);
35490685d2cSjp161948 	}
35590685d2cSjp161948 
35690685d2cSjp161948 	*ifp = xstrdup(cp + 1);
35790685d2cSjp161948 	return(0);
35890685d2cSjp161948 }
35990685d2cSjp161948 
36090685d2cSjp161948 static int
parse_getput_flags(const char ** cpp,int * pflag)36190685d2cSjp161948 parse_getput_flags(const char **cpp, int *pflag)
36290685d2cSjp161948 {
36390685d2cSjp161948 	const char *cp = *cpp;
36490685d2cSjp161948 
36590685d2cSjp161948 	/* Check for flags */
36690685d2cSjp161948 	if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
36790685d2cSjp161948 		switch (cp[1]) {
36890685d2cSjp161948 		case 'p':
36990685d2cSjp161948 		case 'P':
37090685d2cSjp161948 			*pflag = 1;
37190685d2cSjp161948 			break;
37290685d2cSjp161948 		default:
37390685d2cSjp161948 			error("Invalid flag -%c", cp[1]);
37490685d2cSjp161948 			return(-1);
37590685d2cSjp161948 		}
37690685d2cSjp161948 		cp += 2;
37790685d2cSjp161948 		*cpp = cp + strspn(cp, WHITESPACE);
37890685d2cSjp161948 	}
37990685d2cSjp161948 
38090685d2cSjp161948 	return(0);
38190685d2cSjp161948 }
38290685d2cSjp161948 
38390685d2cSjp161948 static int
parse_ls_flags(const char ** cpp,int * lflag)38490685d2cSjp161948 parse_ls_flags(const char **cpp, int *lflag)
38590685d2cSjp161948 {
38690685d2cSjp161948 	const char *cp = *cpp;
38790685d2cSjp161948 
38890685d2cSjp161948 	/* Defaults */
38990685d2cSjp161948 	*lflag = LS_NAME_SORT;
39090685d2cSjp161948 
39190685d2cSjp161948 	/* Check for flags */
39290685d2cSjp161948 	if (cp++[0] == '-') {
39390685d2cSjp161948 		for (; strchr(WHITESPACE, *cp) == NULL; cp++) {
39490685d2cSjp161948 			switch (*cp) {
39590685d2cSjp161948 			case 'l':
39690685d2cSjp161948 				*lflag &= ~VIEW_FLAGS;
39790685d2cSjp161948 				*lflag |= LS_LONG_VIEW;
39890685d2cSjp161948 				break;
39990685d2cSjp161948 			case '1':
40090685d2cSjp161948 				*lflag &= ~VIEW_FLAGS;
40190685d2cSjp161948 				*lflag |= LS_SHORT_VIEW;
40290685d2cSjp161948 				break;
40390685d2cSjp161948 			case 'n':
40490685d2cSjp161948 				*lflag &= ~VIEW_FLAGS;
40590685d2cSjp161948 				*lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
40690685d2cSjp161948 				break;
40790685d2cSjp161948 			case 'S':
40890685d2cSjp161948 				*lflag &= ~SORT_FLAGS;
40990685d2cSjp161948 				*lflag |= LS_SIZE_SORT;
41090685d2cSjp161948 				break;
41190685d2cSjp161948 			case 't':
41290685d2cSjp161948 				*lflag &= ~SORT_FLAGS;
41390685d2cSjp161948 				*lflag |= LS_TIME_SORT;
41490685d2cSjp161948 				break;
41590685d2cSjp161948 			case 'r':
41690685d2cSjp161948 				*lflag |= LS_REVERSE_SORT;
41790685d2cSjp161948 				break;
41890685d2cSjp161948 			case 'f':
41990685d2cSjp161948 				*lflag &= ~SORT_FLAGS;
42090685d2cSjp161948 				break;
42190685d2cSjp161948 			case 'a':
42290685d2cSjp161948 				*lflag |= LS_SHOW_ALL;
42390685d2cSjp161948 				break;
42490685d2cSjp161948 			default:
42590685d2cSjp161948 				error("Invalid flag -%c", *cp);
42690685d2cSjp161948 				return(-1);
42790685d2cSjp161948 			}
42890685d2cSjp161948 		}
42990685d2cSjp161948 		*cpp = cp + strspn(cp, WHITESPACE);
43090685d2cSjp161948 	}
43190685d2cSjp161948 
43290685d2cSjp161948 	return(0);
43390685d2cSjp161948 }
43490685d2cSjp161948 
43590685d2cSjp161948 static int
get_pathname(const char ** cpp,char ** path)43690685d2cSjp161948 get_pathname(const char **cpp, char **path)
43790685d2cSjp161948 {
43890685d2cSjp161948 	const char *cp = *cpp, *end;
43990685d2cSjp161948 	char quot;
44090685d2cSjp161948 	u_int i, j;
44190685d2cSjp161948 
44290685d2cSjp161948 	cp += strspn(cp, WHITESPACE);
44390685d2cSjp161948 	if (!*cp) {
44490685d2cSjp161948 		*cpp = cp;
44590685d2cSjp161948 		*path = NULL;
44690685d2cSjp161948 		return (0);
44790685d2cSjp161948 	}
44890685d2cSjp161948 
44990685d2cSjp161948 	*path = xmalloc(strlen(cp) + 1);
45090685d2cSjp161948 
45190685d2cSjp161948 	/* Check for quoted filenames */
45290685d2cSjp161948 	if (*cp == '\"' || *cp == '\'') {
45390685d2cSjp161948 		quot = *cp++;
45490685d2cSjp161948 
45590685d2cSjp161948 		/* Search for terminating quote, unescape some chars */
45690685d2cSjp161948 		for (i = j = 0; i <= strlen(cp); i++) {
45790685d2cSjp161948 			if (cp[i] == quot) {	/* Found quote */
45890685d2cSjp161948 				i++;
45990685d2cSjp161948 				(*path)[j] = '\0';
46090685d2cSjp161948 				break;
46190685d2cSjp161948 			}
46290685d2cSjp161948 			if (cp[i] == '\0') {	/* End of string */
46390685d2cSjp161948 				error("Unterminated quote");
46490685d2cSjp161948 				goto fail;
46590685d2cSjp161948 			}
46690685d2cSjp161948 			if (cp[i] == '\\') {	/* Escaped characters */
46790685d2cSjp161948 				i++;
46890685d2cSjp161948 				if (cp[i] != '\'' && cp[i] != '\"' &&
46990685d2cSjp161948 				    cp[i] != '\\') {
47090685d2cSjp161948 					error("Bad escaped character '\\%c'",
47190685d2cSjp161948 					    cp[i]);
47290685d2cSjp161948 					goto fail;
47390685d2cSjp161948 				}
47490685d2cSjp161948 			}
47590685d2cSjp161948 			(*path)[j++] = cp[i];
47690685d2cSjp161948 		}
47790685d2cSjp161948 
47890685d2cSjp161948 		if (j == 0) {
47990685d2cSjp161948 			error("Empty quotes");
48090685d2cSjp161948 			goto fail;
48190685d2cSjp161948 		}
48290685d2cSjp161948 		*cpp = cp + i + strspn(cp + i, WHITESPACE);
48390685d2cSjp161948 	} else {
48490685d2cSjp161948 		/* Read to end of filename */
48590685d2cSjp161948 		end = strpbrk(cp, WHITESPACE);
48690685d2cSjp161948 		if (end == NULL)
48790685d2cSjp161948 			end = strchr(cp, '\0');
48890685d2cSjp161948 		*cpp = end + strspn(end, WHITESPACE);
48990685d2cSjp161948 
49090685d2cSjp161948 		memcpy(*path, cp, end - cp);
49190685d2cSjp161948 		(*path)[end - cp] = '\0';
49290685d2cSjp161948 	}
49390685d2cSjp161948 	return (0);
49490685d2cSjp161948 
49590685d2cSjp161948  fail:
49690685d2cSjp161948 	xfree(*path);
49790685d2cSjp161948 	*path = NULL;
49890685d2cSjp161948 	return (-1);
49990685d2cSjp161948 }
50090685d2cSjp161948 
50190685d2cSjp161948 static int
is_dir(char * path)50290685d2cSjp161948 is_dir(char *path)
50390685d2cSjp161948 {
50490685d2cSjp161948 	struct stat sb;
50590685d2cSjp161948 
50690685d2cSjp161948 	/* XXX: report errors? */
50790685d2cSjp161948 	if (stat(path, &sb) == -1)
50890685d2cSjp161948 		return(0);
50990685d2cSjp161948 
51090685d2cSjp161948 	return(S_ISDIR(sb.st_mode));
51190685d2cSjp161948 }
51290685d2cSjp161948 
51390685d2cSjp161948 static int
is_reg(char * path)51490685d2cSjp161948 is_reg(char *path)
51590685d2cSjp161948 {
51690685d2cSjp161948 	struct stat sb;
51790685d2cSjp161948 
51890685d2cSjp161948 	if (stat(path, &sb) == -1)
51990685d2cSjp161948 		fatal("stat %s: %s", path, strerror(errno));
52090685d2cSjp161948 
52190685d2cSjp161948 	return(S_ISREG(sb.st_mode));
52290685d2cSjp161948 }
52390685d2cSjp161948 
52490685d2cSjp161948 static int
remote_is_dir(struct sftp_conn * conn,char * path)52590685d2cSjp161948 remote_is_dir(struct sftp_conn *conn, char *path)
52690685d2cSjp161948 {
52790685d2cSjp161948 	Attrib *a;
52890685d2cSjp161948 
52990685d2cSjp161948 	/* XXX: report errors? */
53090685d2cSjp161948 	if ((a = do_stat(conn, path, 1)) == NULL)
53190685d2cSjp161948 		return(0);
53290685d2cSjp161948 	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
53390685d2cSjp161948 		return(0);
53490685d2cSjp161948 	return(S_ISDIR(a->perm));
53590685d2cSjp161948 }
53690685d2cSjp161948 
53790685d2cSjp161948 static int
process_get(struct sftp_conn * conn,char * src,char * dst,char * pwd,int pflag)53890685d2cSjp161948 process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
53990685d2cSjp161948 {
54090685d2cSjp161948 	char *abs_src = NULL;
54190685d2cSjp161948 	char *abs_dst = NULL;
54290685d2cSjp161948 	char *tmp;
54390685d2cSjp161948 	glob_t g;
54490685d2cSjp161948 	int err = 0;
54590685d2cSjp161948 	int i;
54690685d2cSjp161948 
54790685d2cSjp161948 	abs_src = xstrdup(src);
54890685d2cSjp161948 	abs_src = make_absolute(abs_src, pwd);
54990685d2cSjp161948 
55090685d2cSjp161948 	memset(&g, 0, sizeof(g));
55190685d2cSjp161948 	debug3("Looking up %s", abs_src);
55290685d2cSjp161948 	if (remote_glob(conn, abs_src, 0, NULL, &g)) {
55390685d2cSjp161948 		error("File \"%s\" not found.", abs_src);
55490685d2cSjp161948 		err = -1;
55590685d2cSjp161948 		goto out;
55690685d2cSjp161948 	}
55790685d2cSjp161948 
55890685d2cSjp161948 	/* If multiple matches, dst must be a directory or unspecified */
55990685d2cSjp161948 	if (g.gl_matchc > 1 && dst && !is_dir(dst)) {
56090685d2cSjp161948 		error("Multiple files match, but \"%s\" is not a directory",
56190685d2cSjp161948 		    dst);
56290685d2cSjp161948 		err = -1;
56390685d2cSjp161948 		goto out;
56490685d2cSjp161948 	}
56590685d2cSjp161948 
56690685d2cSjp161948 	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
56790685d2cSjp161948 		if (infer_path(g.gl_pathv[i], &tmp)) {
56890685d2cSjp161948 			err = -1;
56990685d2cSjp161948 			goto out;
57090685d2cSjp161948 		}
57190685d2cSjp161948 
57290685d2cSjp161948 		if (g.gl_matchc == 1 && dst) {
57390685d2cSjp161948 			/* If directory specified, append filename */
57490685d2cSjp161948 			xfree(tmp);
57590685d2cSjp161948 			if (is_dir(dst)) {
57690685d2cSjp161948 				if (infer_path(g.gl_pathv[0], &tmp)) {
57790685d2cSjp161948 					err = 1;
57890685d2cSjp161948 					goto out;
57990685d2cSjp161948 				}
58090685d2cSjp161948 				abs_dst = path_append(dst, tmp);
58190685d2cSjp161948 				xfree(tmp);
58290685d2cSjp161948 			} else
58390685d2cSjp161948 				abs_dst = xstrdup(dst);
58490685d2cSjp161948 		} else if (dst) {
58590685d2cSjp161948 			abs_dst = path_append(dst, tmp);
58690685d2cSjp161948 			xfree(tmp);
58790685d2cSjp161948 		} else
58890685d2cSjp161948 			abs_dst = tmp;
58990685d2cSjp161948 
59090685d2cSjp161948 		printf(gettext("Fetching %s to %s\n"), g.gl_pathv[i], abs_dst);
59190685d2cSjp161948 		if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
59290685d2cSjp161948 			err = -1;
59390685d2cSjp161948 		xfree(abs_dst);
59490685d2cSjp161948 		abs_dst = NULL;
59590685d2cSjp161948 	}
59690685d2cSjp161948 
59790685d2cSjp161948 out:
59890685d2cSjp161948 	xfree(abs_src);
59990685d2cSjp161948 	globfree(&g);
60090685d2cSjp161948 	return(err);
60190685d2cSjp161948 }
60290685d2cSjp161948 
60390685d2cSjp161948 static int
process_put(struct sftp_conn * conn,char * src,char * dst,char * pwd,int pflag)60490685d2cSjp161948 process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
60590685d2cSjp161948 {
60690685d2cSjp161948 	char *tmp_dst = NULL;
60790685d2cSjp161948 	char *abs_dst = NULL;
60890685d2cSjp161948 	char *tmp;
60990685d2cSjp161948 	glob_t g;
61090685d2cSjp161948 	int err = 0;
61190685d2cSjp161948 	int i;
61290685d2cSjp161948 
61390685d2cSjp161948 	if (dst) {
61490685d2cSjp161948 		tmp_dst = xstrdup(dst);
61590685d2cSjp161948 		tmp_dst = make_absolute(tmp_dst, pwd);
61690685d2cSjp161948 	}
61790685d2cSjp161948 
61890685d2cSjp161948 	memset(&g, 0, sizeof(g));
61990685d2cSjp161948 	debug3("Looking up %s", src);
620*cf58b254SGary Mills 	if (glob(src, GLOB_LIMIT, NULL, &g)) {
62190685d2cSjp161948 		error("File \"%s\" not found.", src);
62290685d2cSjp161948 		err = -1;
62390685d2cSjp161948 		goto out;
62490685d2cSjp161948 	}
62590685d2cSjp161948 
62690685d2cSjp161948 	/* If multiple matches, dst may be directory or unspecified */
62790685d2cSjp161948 	if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) {
62890685d2cSjp161948 		error("Multiple files match, but \"%s\" is not a directory",
62990685d2cSjp161948 		    tmp_dst);
63090685d2cSjp161948 		err = -1;
63190685d2cSjp161948 		goto out;
63290685d2cSjp161948 	}
63390685d2cSjp161948 
63490685d2cSjp161948 	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
63590685d2cSjp161948 		if (!is_reg(g.gl_pathv[i])) {
63690685d2cSjp161948 			error("skipping non-regular file %s",
63790685d2cSjp161948 			    g.gl_pathv[i]);
63890685d2cSjp161948 			continue;
63990685d2cSjp161948 		}
64090685d2cSjp161948 		if (infer_path(g.gl_pathv[i], &tmp)) {
64190685d2cSjp161948 			err = -1;
64290685d2cSjp161948 			goto out;
64390685d2cSjp161948 		}
64490685d2cSjp161948 
64590685d2cSjp161948 		if (g.gl_matchc == 1 && tmp_dst) {
64690685d2cSjp161948 			/* If directory specified, append filename */
64790685d2cSjp161948 			if (remote_is_dir(conn, tmp_dst)) {
64890685d2cSjp161948 				if (infer_path(g.gl_pathv[0], &tmp)) {
64990685d2cSjp161948 					err = 1;
65090685d2cSjp161948 					goto out;
65190685d2cSjp161948 				}
65290685d2cSjp161948 				abs_dst = path_append(tmp_dst, tmp);
65390685d2cSjp161948 				xfree(tmp);
65490685d2cSjp161948 			} else
65590685d2cSjp161948 				abs_dst = xstrdup(tmp_dst);
65690685d2cSjp161948 
65790685d2cSjp161948 		} else if (tmp_dst) {
65890685d2cSjp161948 			abs_dst = path_append(tmp_dst, tmp);
65990685d2cSjp161948 			xfree(tmp);
66090685d2cSjp161948 		} else
66190685d2cSjp161948 			abs_dst = make_absolute(tmp, pwd);
66290685d2cSjp161948 
66390685d2cSjp161948 		printf(gettext("Uploading %s to %s\n"), g.gl_pathv[i], abs_dst);
66490685d2cSjp161948 		if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
66590685d2cSjp161948 			err = -1;
66690685d2cSjp161948 	}
66790685d2cSjp161948 
66890685d2cSjp161948 out:
66990685d2cSjp161948 	if (abs_dst)
67090685d2cSjp161948 		xfree(abs_dst);
67190685d2cSjp161948 	if (tmp_dst)
67290685d2cSjp161948 		xfree(tmp_dst);
67390685d2cSjp161948 	globfree(&g);
67490685d2cSjp161948 	return(err);
67590685d2cSjp161948 }
67690685d2cSjp161948 
67790685d2cSjp161948 static int
sdirent_comp(const void * aa,const void * bb)67890685d2cSjp161948 sdirent_comp(const void *aa, const void *bb)
67990685d2cSjp161948 {
68090685d2cSjp161948 	SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
68190685d2cSjp161948 	SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
68290685d2cSjp161948 	int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
68390685d2cSjp161948 
68490685d2cSjp161948 #define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
68590685d2cSjp161948 	if (sort_flag & LS_NAME_SORT)
68690685d2cSjp161948 		return (rmul * strcmp(a->filename, b->filename));
68790685d2cSjp161948 	else if (sort_flag & LS_TIME_SORT)
68890685d2cSjp161948 		return (rmul * NCMP(a->a.mtime, b->a.mtime));
68990685d2cSjp161948 	else if (sort_flag & LS_SIZE_SORT)
69090685d2cSjp161948 		return (rmul * NCMP(a->a.size, b->a.size));
69190685d2cSjp161948 
69290685d2cSjp161948 	fatal("Unknown ls sort type");
69390685d2cSjp161948 
69490685d2cSjp161948 	/* NOTREACHED */
69590685d2cSjp161948 	return (0);
69690685d2cSjp161948 }
69790685d2cSjp161948 
69890685d2cSjp161948 /* sftp ls.1 replacement for directories */
69990685d2cSjp161948 static int
do_ls_dir(struct sftp_conn * conn,char * path,char * strip_path,int lflag)70090685d2cSjp161948 do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
70190685d2cSjp161948 {
70290685d2cSjp161948 	int n;
70390685d2cSjp161948 	u_int c = 1, colspace = 0, columns = 1;
70490685d2cSjp161948 	SFTP_DIRENT **d;
70590685d2cSjp161948 
70690685d2cSjp161948 	if ((n = do_readdir(conn, path, &d)) != 0)
70790685d2cSjp161948 		return (n);
70890685d2cSjp161948 
70990685d2cSjp161948 	if (!(lflag & LS_SHORT_VIEW)) {
71090685d2cSjp161948 		u_int m = 0, width = 80;
71190685d2cSjp161948 		struct winsize ws;
71290685d2cSjp161948 		char *tmp;
71390685d2cSjp161948 
71490685d2cSjp161948 		/* Count entries for sort and find longest filename */
71590685d2cSjp161948 		for (n = 0; d[n] != NULL; n++) {
71690685d2cSjp161948 			if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
71790685d2cSjp161948 				m = MAX(m, strlen(d[n]->filename));
71890685d2cSjp161948 		}
71990685d2cSjp161948 
72090685d2cSjp161948 		/* Add any subpath that also needs to be counted */
72190685d2cSjp161948 		tmp = path_strip(path, strip_path);
72290685d2cSjp161948 		m += strlen(tmp);
72390685d2cSjp161948 		xfree(tmp);
72490685d2cSjp161948 
72590685d2cSjp161948 		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
72690685d2cSjp161948 			width = ws.ws_col;
72790685d2cSjp161948 
72890685d2cSjp161948 		columns = width / (m + 2);
72990685d2cSjp161948 		columns = MAX(columns, 1);
73090685d2cSjp161948 		colspace = width / columns;
73190685d2cSjp161948 		colspace = MIN(colspace, width);
73290685d2cSjp161948 	}
73390685d2cSjp161948 
73490685d2cSjp161948 	if (lflag & SORT_FLAGS) {
73590685d2cSjp161948 		for (n = 0; d[n] != NULL; n++)
73690685d2cSjp161948 			;	/* count entries */
73790685d2cSjp161948 		sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
73890685d2cSjp161948 		qsort(d, n, sizeof(*d), sdirent_comp);
73990685d2cSjp161948 	}
74090685d2cSjp161948 
74190685d2cSjp161948 	for (n = 0; d[n] != NULL && !interrupted; n++) {
74290685d2cSjp161948 		char *tmp, *fname;
74390685d2cSjp161948 
74490685d2cSjp161948 		if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
74590685d2cSjp161948 			continue;
74690685d2cSjp161948 
74790685d2cSjp161948 		tmp = path_append(path, d[n]->filename);
74890685d2cSjp161948 		fname = path_strip(tmp, strip_path);
74990685d2cSjp161948 		xfree(tmp);
75090685d2cSjp161948 
75190685d2cSjp161948 		if (lflag & LS_LONG_VIEW) {
75290685d2cSjp161948 			if (lflag & LS_NUMERIC_VIEW) {
75390685d2cSjp161948 				char *lname;
75490685d2cSjp161948 				struct stat sb;
75590685d2cSjp161948 
75690685d2cSjp161948 				memset(&sb, 0, sizeof(sb));
75790685d2cSjp161948 				attrib_to_stat(&d[n]->a, &sb);
75890685d2cSjp161948 				lname = ls_file(fname, &sb, 1);
75990685d2cSjp161948 				printf("%s\n", lname);
76090685d2cSjp161948 				xfree(lname);
76190685d2cSjp161948 			} else
76290685d2cSjp161948 				printf("%s\n", d[n]->longname);
76390685d2cSjp161948 		} else {
76490685d2cSjp161948 			printf("%-*s", colspace, fname);
76590685d2cSjp161948 			if (c >= columns) {
76690685d2cSjp161948 				printf("\n");
76790685d2cSjp161948 				c = 1;
76890685d2cSjp161948 			} else
76990685d2cSjp161948 				c++;
77090685d2cSjp161948 		}
77190685d2cSjp161948 
77290685d2cSjp161948 		xfree(fname);
77390685d2cSjp161948 	}
77490685d2cSjp161948 
77590685d2cSjp161948 	if (!(lflag & LS_LONG_VIEW) && (c != 1))
77690685d2cSjp161948 		printf("\n");
77790685d2cSjp161948 
77890685d2cSjp161948 	free_sftp_dirents(d);
77990685d2cSjp161948 	return (0);
78090685d2cSjp161948 }
78190685d2cSjp161948 
78290685d2cSjp161948 /* sftp ls.1 replacement which handles path globs */
78390685d2cSjp161948 static int
do_globbed_ls(struct sftp_conn * conn,char * path,char * strip_path,int lflag)78490685d2cSjp161948 do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
78590685d2cSjp161948     int lflag)
78690685d2cSjp161948 {
78790685d2cSjp161948 	glob_t g;
78890685d2cSjp161948 	u_int i, c = 1, colspace = 0, columns = 1;
78990685d2cSjp161948 	Attrib *a = NULL;
79090685d2cSjp161948 
79190685d2cSjp161948 	memset(&g, 0, sizeof(g));
79290685d2cSjp161948 
79390685d2cSjp161948 	if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
79490685d2cSjp161948 	    NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
79590685d2cSjp161948 		if (g.gl_pathc)
79690685d2cSjp161948 			globfree(&g);
79790685d2cSjp161948 		error("Can't ls: \"%s\" not found", path);
79890685d2cSjp161948 		return (-1);
79990685d2cSjp161948 	}
80090685d2cSjp161948 
80190685d2cSjp161948 	if (interrupted)
80290685d2cSjp161948 		goto out;
80390685d2cSjp161948 
80490685d2cSjp161948 	/*
80590685d2cSjp161948 	 * If the glob returns a single match and it is a directory,
80690685d2cSjp161948 	 * then just list its contents.
80790685d2cSjp161948 	 */
80890685d2cSjp161948 	if (g.gl_matchc == 1) {
80990685d2cSjp161948 		if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
81090685d2cSjp161948 			globfree(&g);
81190685d2cSjp161948 			return (-1);
81290685d2cSjp161948 		}
81390685d2cSjp161948 		if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
81490685d2cSjp161948 		    S_ISDIR(a->perm)) {
81590685d2cSjp161948 			int err;
81690685d2cSjp161948 
81790685d2cSjp161948 			err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
81890685d2cSjp161948 			globfree(&g);
81990685d2cSjp161948 			return (err);
82090685d2cSjp161948 		}
82190685d2cSjp161948 	}
82290685d2cSjp161948 
82390685d2cSjp161948 	if (!(lflag & LS_SHORT_VIEW)) {
82490685d2cSjp161948 		u_int m = 0, width = 80;
82590685d2cSjp161948 		struct winsize ws;
82690685d2cSjp161948 
82790685d2cSjp161948 		/* Count entries for sort and find longest filename */
82890685d2cSjp161948 		for (i = 0; g.gl_pathv[i]; i++)
82990685d2cSjp161948 			m = MAX(m, strlen(g.gl_pathv[i]));
83090685d2cSjp161948 
83190685d2cSjp161948 		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
83290685d2cSjp161948 			width = ws.ws_col;
83390685d2cSjp161948 
83490685d2cSjp161948 		columns = width / (m + 2);
83590685d2cSjp161948 		columns = MAX(columns, 1);
83690685d2cSjp161948 		colspace = width / columns;
83790685d2cSjp161948 	}
83890685d2cSjp161948 
83990685d2cSjp161948 	for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
84090685d2cSjp161948 		char *fname;
84190685d2cSjp161948 
84290685d2cSjp161948 		fname = path_strip(g.gl_pathv[i], strip_path);
84390685d2cSjp161948 
84490685d2cSjp161948 		if (lflag & LS_LONG_VIEW) {
84590685d2cSjp161948 			char *lname;
84690685d2cSjp161948 			struct stat sb;
84790685d2cSjp161948 
84890685d2cSjp161948 			/*
84990685d2cSjp161948 			 * XXX: this is slow - 1 roundtrip per path
85090685d2cSjp161948 			 * A solution to this is to fork glob() and
85190685d2cSjp161948 			 * build a sftp specific version which keeps the
85290685d2cSjp161948 			 * attribs (which currently get thrown away)
85390685d2cSjp161948 			 * that the server returns as well as the filenames.
85490685d2cSjp161948 			 */
85590685d2cSjp161948 			memset(&sb, 0, sizeof(sb));
85690685d2cSjp161948 			if (a == NULL)
85790685d2cSjp161948 				a = do_lstat(conn, g.gl_pathv[i], 1);
85890685d2cSjp161948 			if (a != NULL)
85990685d2cSjp161948 				attrib_to_stat(a, &sb);
86090685d2cSjp161948 			lname = ls_file(fname, &sb, 1);
86190685d2cSjp161948 			printf("%s\n", lname);
86290685d2cSjp161948 			xfree(lname);
86390685d2cSjp161948 		} else {
86490685d2cSjp161948 			printf("%-*s", colspace, fname);
86590685d2cSjp161948 			if (c >= columns) {
86690685d2cSjp161948 				printf("\n");
86790685d2cSjp161948 				c = 1;
86890685d2cSjp161948 			} else
86990685d2cSjp161948 				c++;
87090685d2cSjp161948 		}
87190685d2cSjp161948 		xfree(fname);
87290685d2cSjp161948 	}
87390685d2cSjp161948 
87490685d2cSjp161948 	if (!(lflag & LS_LONG_VIEW) && (c != 1))
87590685d2cSjp161948 		printf("\n");
87690685d2cSjp161948 
87790685d2cSjp161948  out:
87890685d2cSjp161948 	if (g.gl_pathc)
87990685d2cSjp161948 		globfree(&g);
88090685d2cSjp161948 
88190685d2cSjp161948 	return (0);
88290685d2cSjp161948 }
88390685d2cSjp161948 
88490685d2cSjp161948 static int
parse_args(const char ** cpp,int * pflag,int * lflag,int * iflag,unsigned long * n_arg,char ** path1,char ** path2)88590685d2cSjp161948 parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
88690685d2cSjp161948     unsigned long *n_arg, char **path1, char **path2)
88790685d2cSjp161948 {
88890685d2cSjp161948 	const char *cmd, *cp = *cpp;
88990685d2cSjp161948 	char *cp2;
89090685d2cSjp161948 	int base = 0;
89190685d2cSjp161948 	long l;
89290685d2cSjp161948 	int i, cmdnum;
89390685d2cSjp161948 
89490685d2cSjp161948 	/* Skip leading whitespace */
89590685d2cSjp161948 	cp = cp + strspn(cp, WHITESPACE);
89690685d2cSjp161948 
89790685d2cSjp161948 	/* Ignore blank lines and lines which begin with comment '#' char */
89890685d2cSjp161948 	if (*cp == '\0' || *cp == '#')
89990685d2cSjp161948 		return (0);
90090685d2cSjp161948 
90190685d2cSjp161948 	/* Check for leading '-' (disable error processing) */
90290685d2cSjp161948 	*iflag = 0;
90390685d2cSjp161948 	if (*cp == '-') {
90490685d2cSjp161948 		*iflag = 1;
90590685d2cSjp161948 		cp++;
90690685d2cSjp161948 	}
90790685d2cSjp161948 
90890685d2cSjp161948 	/* Figure out which command we have */
90990685d2cSjp161948 	for (i = 0; cmds[i].c; i++) {
91090685d2cSjp161948 		int cmdlen = strlen(cmds[i].c);
91190685d2cSjp161948 
91290685d2cSjp161948 		/* Check for command followed by whitespace */
91390685d2cSjp161948 		if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
91490685d2cSjp161948 		    strchr(WHITESPACE, cp[cmdlen])) {
91590685d2cSjp161948 			cp += cmdlen;
91690685d2cSjp161948 			cp = cp + strspn(cp, WHITESPACE);
91790685d2cSjp161948 			break;
91890685d2cSjp161948 		}
91990685d2cSjp161948 	}
92090685d2cSjp161948 	cmdnum = cmds[i].n;
92190685d2cSjp161948 	cmd = cmds[i].c;
92290685d2cSjp161948 
92390685d2cSjp161948 	/* Special case */
92490685d2cSjp161948 	if (*cp == '!') {
92590685d2cSjp161948 		cp++;
92690685d2cSjp161948 		cmdnum = I_SHELL;
92790685d2cSjp161948 	} else if (cmdnum == -1) {
92890685d2cSjp161948 		error("Invalid command.");
92990685d2cSjp161948 		return (-1);
93090685d2cSjp161948 	}
93190685d2cSjp161948 
93290685d2cSjp161948 	/* Get arguments and parse flags */
93390685d2cSjp161948 	*lflag = *pflag = *n_arg = 0;
93490685d2cSjp161948 	*path1 = *path2 = NULL;
93590685d2cSjp161948 	switch (cmdnum) {
93690685d2cSjp161948 	case I_GET:
93790685d2cSjp161948 	case I_PUT:
93890685d2cSjp161948 		if (parse_getput_flags(&cp, pflag))
93990685d2cSjp161948 			return(-1);
94090685d2cSjp161948 		/* Get first pathname (mandatory) */
94190685d2cSjp161948 		if (get_pathname(&cp, path1))
94290685d2cSjp161948 			return(-1);
94390685d2cSjp161948 		if (*path1 == NULL) {
94490685d2cSjp161948 			error("You must specify at least one path after a "
94590685d2cSjp161948 			    "%s command.", cmd);
94690685d2cSjp161948 			return(-1);
94790685d2cSjp161948 		}
94890685d2cSjp161948 		/* Try to get second pathname (optional) */
94990685d2cSjp161948 		if (get_pathname(&cp, path2))
95090685d2cSjp161948 			return(-1);
95190685d2cSjp161948 		break;
95290685d2cSjp161948 	case I_RENAME:
95390685d2cSjp161948 	case I_SYMLINK:
95490685d2cSjp161948 		if (get_pathname(&cp, path1))
95590685d2cSjp161948 			return(-1);
95690685d2cSjp161948 		if (get_pathname(&cp, path2))
95790685d2cSjp161948 			return(-1);
95890685d2cSjp161948 		if (!*path1 || !*path2) {
95990685d2cSjp161948 			error("You must specify two paths after a %s "
96090685d2cSjp161948 			    "command.", cmd);
96190685d2cSjp161948 			return(-1);
96290685d2cSjp161948 		}
96390685d2cSjp161948 		break;
96490685d2cSjp161948 	case I_RM:
96590685d2cSjp161948 	case I_MKDIR:
96690685d2cSjp161948 	case I_RMDIR:
96790685d2cSjp161948 	case I_CHDIR:
96890685d2cSjp161948 	case I_LCHDIR:
96990685d2cSjp161948 	case I_LMKDIR:
97090685d2cSjp161948 		/* Get pathname (mandatory) */
97190685d2cSjp161948 		if (get_pathname(&cp, path1))
97290685d2cSjp161948 			return(-1);
97390685d2cSjp161948 		if (*path1 == NULL) {
97490685d2cSjp161948 			error("You must specify a path after a %s command.",
97590685d2cSjp161948 			    cmd);
97690685d2cSjp161948 			return(-1);
97790685d2cSjp161948 		}
97890685d2cSjp161948 		break;
97990685d2cSjp161948 	case I_LS:
98090685d2cSjp161948 		if (parse_ls_flags(&cp, lflag))
98190685d2cSjp161948 			return(-1);
98290685d2cSjp161948 		/* Path is optional */
98390685d2cSjp161948 		if (get_pathname(&cp, path1))
98490685d2cSjp161948 			return(-1);
98590685d2cSjp161948 		break;
98690685d2cSjp161948 	case I_LLS:
98790685d2cSjp161948 	case I_SHELL:
98890685d2cSjp161948 		/* Uses the rest of the line */
98990685d2cSjp161948 		break;
99090685d2cSjp161948 	case I_LUMASK:
99190685d2cSjp161948 		base = 8;
99290685d2cSjp161948 		/* FALLTHRU */
99390685d2cSjp161948 	case I_CHMOD:
99490685d2cSjp161948 		base = 8;
99590685d2cSjp161948 		/* FALLTHRU */
99690685d2cSjp161948 	case I_CHOWN:
99790685d2cSjp161948 	case I_CHGRP:
99890685d2cSjp161948 		/* Get numeric arg (mandatory) */
99990685d2cSjp161948 		errno = 0;
100090685d2cSjp161948 		l = strtol(cp, &cp2, base);
100190685d2cSjp161948 		if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
100290685d2cSjp161948 		    errno == ERANGE) || l < 0) {
100390685d2cSjp161948 			error("You must supply a numeric argument "
100490685d2cSjp161948 			    "to the %s command.", cmd);
100590685d2cSjp161948 			return(-1);
100690685d2cSjp161948 		}
100790685d2cSjp161948 		cp = cp2;
100890685d2cSjp161948 		*n_arg = l;
100990685d2cSjp161948 		if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
101090685d2cSjp161948 			break;
101190685d2cSjp161948 		if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
101290685d2cSjp161948 			error("You must supply a numeric argument "
101390685d2cSjp161948 			    "to the %s command.", cmd);
101490685d2cSjp161948 			return(-1);
101590685d2cSjp161948 		}
101690685d2cSjp161948 		cp += strspn(cp, WHITESPACE);
101790685d2cSjp161948 
101890685d2cSjp161948 		/* Get pathname (mandatory) */
101990685d2cSjp161948 		if (get_pathname(&cp, path1))
102090685d2cSjp161948 			return(-1);
102190685d2cSjp161948 		if (*path1 == NULL) {
102290685d2cSjp161948 			error("You must specify a path after a %s command.",
102390685d2cSjp161948 			    cmd);
102490685d2cSjp161948 			return(-1);
102590685d2cSjp161948 		}
102690685d2cSjp161948 		break;
102790685d2cSjp161948 	case I_QUIT:
102890685d2cSjp161948 	case I_PWD:
102990685d2cSjp161948 	case I_LPWD:
103090685d2cSjp161948 	case I_HELP:
103190685d2cSjp161948 	case I_VERSION:
103290685d2cSjp161948 	case I_PROGRESS:
103390685d2cSjp161948 		break;
103490685d2cSjp161948 	default:
103590685d2cSjp161948 		fatal("Command not implemented");
103690685d2cSjp161948 	}
103790685d2cSjp161948 
103890685d2cSjp161948 	*cpp = cp;
103990685d2cSjp161948 	return(cmdnum);
104090685d2cSjp161948 }
104190685d2cSjp161948 
104290685d2cSjp161948 static int
parse_dispatch_command(struct sftp_conn * conn,const char * cmd,char ** pwd,int err_abort)104390685d2cSjp161948 parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
104490685d2cSjp161948     int err_abort)
104590685d2cSjp161948 {
104690685d2cSjp161948 	char *path1, *path2, *tmp;
104790685d2cSjp161948 	int pflag, lflag, iflag, cmdnum, i;
104890685d2cSjp161948 	unsigned long n_arg;
104990685d2cSjp161948 	Attrib a, *aa;
105090685d2cSjp161948 	char path_buf[MAXPATHLEN];
105190685d2cSjp161948 	int err = 0;
105290685d2cSjp161948 	glob_t g;
105390685d2cSjp161948 
105490685d2cSjp161948 	path1 = path2 = NULL;
105590685d2cSjp161948 	cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &n_arg,
105690685d2cSjp161948 	    &path1, &path2);
105790685d2cSjp161948 
105890685d2cSjp161948 	if (iflag != 0)
105990685d2cSjp161948 		err_abort = 0;
106090685d2cSjp161948 
106190685d2cSjp161948 	memset(&g, 0, sizeof(g));
106290685d2cSjp161948 
106390685d2cSjp161948 	/* Perform command */
106490685d2cSjp161948 	switch (cmdnum) {
106590685d2cSjp161948 	case 0:
106690685d2cSjp161948 		/* Blank line */
106790685d2cSjp161948 		break;
106890685d2cSjp161948 	case -1:
106990685d2cSjp161948 		/* Unrecognized command */
107090685d2cSjp161948 		err = -1;
107190685d2cSjp161948 		break;
107290685d2cSjp161948 	case I_GET:
107390685d2cSjp161948 		err = process_get(conn, path1, path2, *pwd, pflag);
107490685d2cSjp161948 		break;
107590685d2cSjp161948 	case I_PUT:
107690685d2cSjp161948 		err = process_put(conn, path1, path2, *pwd, pflag);
107790685d2cSjp161948 		break;
107890685d2cSjp161948 	case I_RENAME:
107990685d2cSjp161948 		path1 = make_absolute(path1, *pwd);
108090685d2cSjp161948 		path2 = make_absolute(path2, *pwd);
108190685d2cSjp161948 		err = do_rename(conn, path1, path2);
108290685d2cSjp161948 		break;
108390685d2cSjp161948 	case I_SYMLINK:
108490685d2cSjp161948 		path2 = make_absolute(path2, *pwd);
108590685d2cSjp161948 		err = do_symlink(conn, path1, path2);
108690685d2cSjp161948 		break;
108790685d2cSjp161948 	case I_RM:
108890685d2cSjp161948 		path1 = make_absolute(path1, *pwd);
108990685d2cSjp161948 		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
109090685d2cSjp161948 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
109190685d2cSjp161948 			printf(gettext("Removing %s\n"), g.gl_pathv[i]);
109290685d2cSjp161948 			err = do_rm(conn, g.gl_pathv[i]);
109390685d2cSjp161948 			if (err != 0 && err_abort)
109490685d2cSjp161948 				break;
109590685d2cSjp161948 		}
109690685d2cSjp161948 		break;
109790685d2cSjp161948 	case I_MKDIR:
109890685d2cSjp161948 		path1 = make_absolute(path1, *pwd);
109990685d2cSjp161948 		attrib_clear(&a);
110090685d2cSjp161948 		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
110190685d2cSjp161948 		a.perm = 0777;
110290685d2cSjp161948 		err = do_mkdir(conn, path1, &a);
110390685d2cSjp161948 		break;
110490685d2cSjp161948 	case I_RMDIR:
110590685d2cSjp161948 		path1 = make_absolute(path1, *pwd);
110690685d2cSjp161948 		err = do_rmdir(conn, path1);
110790685d2cSjp161948 		break;
110890685d2cSjp161948 	case I_CHDIR:
110990685d2cSjp161948 		path1 = make_absolute(path1, *pwd);
111090685d2cSjp161948 		if ((tmp = do_realpath(conn, path1)) == NULL) {
111190685d2cSjp161948 			err = 1;
111290685d2cSjp161948 			break;
111390685d2cSjp161948 		}
111490685d2cSjp161948 		if ((aa = do_stat(conn, tmp, 0)) == NULL) {
111590685d2cSjp161948 			xfree(tmp);
111690685d2cSjp161948 			err = 1;
111790685d2cSjp161948 			break;
111890685d2cSjp161948 		}
111990685d2cSjp161948 		if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
112090685d2cSjp161948 			error("Can't change directory: Can't check target");
112190685d2cSjp161948 			xfree(tmp);
112290685d2cSjp161948 			err = 1;
112390685d2cSjp161948 			break;
112490685d2cSjp161948 		}
112590685d2cSjp161948 		if (!S_ISDIR(aa->perm)) {
112690685d2cSjp161948 			error("Can't change directory: \"%s\" is not "
112790685d2cSjp161948 			    "a directory", tmp);
112890685d2cSjp161948 			xfree(tmp);
112990685d2cSjp161948 			err = 1;
113090685d2cSjp161948 			break;
113190685d2cSjp161948 		}
113290685d2cSjp161948 		xfree(*pwd);
113390685d2cSjp161948 		*pwd = tmp;
113490685d2cSjp161948 		break;
113590685d2cSjp161948 	case I_LS:
113690685d2cSjp161948 		if (!path1) {
113790685d2cSjp161948 			do_globbed_ls(conn, *pwd, *pwd, lflag);
113890685d2cSjp161948 			break;
113990685d2cSjp161948 		}
114090685d2cSjp161948 
114190685d2cSjp161948 		/* Strip pwd off beginning of non-absolute paths */
114290685d2cSjp161948 		tmp = NULL;
114390685d2cSjp161948 		if (*path1 != '/')
114490685d2cSjp161948 			tmp = *pwd;
114590685d2cSjp161948 
114690685d2cSjp161948 		path1 = make_absolute(path1, *pwd);
114790685d2cSjp161948 		err = do_globbed_ls(conn, path1, tmp, lflag);
114890685d2cSjp161948 		break;
114990685d2cSjp161948 	case I_LCHDIR:
115090685d2cSjp161948 		if (chdir(path1) == -1) {
115190685d2cSjp161948 			error("Couldn't change local directory to "
115290685d2cSjp161948 			    "\"%s\": %s", path1, strerror(errno));
115390685d2cSjp161948 			err = 1;
115490685d2cSjp161948 		}
115590685d2cSjp161948 		break;
115690685d2cSjp161948 	case I_LMKDIR:
115790685d2cSjp161948 		if (mkdir(path1, 0777) == -1) {
115890685d2cSjp161948 			error("Couldn't create local directory "
115990685d2cSjp161948 			    "\"%s\": %s", path1, strerror(errno));
116090685d2cSjp161948 			err = 1;
116190685d2cSjp161948 		}
116290685d2cSjp161948 		break;
116390685d2cSjp161948 	case I_LLS:
116490685d2cSjp161948 		local_do_ls(cmd);
116590685d2cSjp161948 		break;
116690685d2cSjp161948 	case I_SHELL:
116790685d2cSjp161948 		local_do_shell(cmd);
116890685d2cSjp161948 		break;
116990685d2cSjp161948 	case I_LUMASK:
117090685d2cSjp161948 		umask(n_arg);
117190685d2cSjp161948 		printf(gettext("Local umask: %03lo\n"), n_arg);
117290685d2cSjp161948 		break;
117390685d2cSjp161948 	case I_CHMOD:
117490685d2cSjp161948 		path1 = make_absolute(path1, *pwd);
117590685d2cSjp161948 		attrib_clear(&a);
117690685d2cSjp161948 		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
117790685d2cSjp161948 		a.perm = n_arg;
117890685d2cSjp161948 		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
117990685d2cSjp161948 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
118090685d2cSjp161948 			printf(gettext("Changing mode on %s\n"), g.gl_pathv[i]);
118190685d2cSjp161948 			err = do_setstat(conn, g.gl_pathv[i], &a);
118290685d2cSjp161948 			if (err != 0 && err_abort)
118390685d2cSjp161948 				break;
118490685d2cSjp161948 		}
118590685d2cSjp161948 		break;
118690685d2cSjp161948 	case I_CHOWN:
118790685d2cSjp161948 	case I_CHGRP:
118890685d2cSjp161948 		path1 = make_absolute(path1, *pwd);
118990685d2cSjp161948 		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
119090685d2cSjp161948 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
119190685d2cSjp161948 			if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
119290685d2cSjp161948 				if (err != 0 && err_abort)
119390685d2cSjp161948 					break;
119490685d2cSjp161948 				else
119590685d2cSjp161948 					continue;
119690685d2cSjp161948 			}
119790685d2cSjp161948 			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
119890685d2cSjp161948 				error("Can't get current ownership of "
119990685d2cSjp161948 				    "remote file \"%s\"", g.gl_pathv[i]);
120090685d2cSjp161948 				if (err != 0 && err_abort)
120190685d2cSjp161948 					break;
120290685d2cSjp161948 				else
120390685d2cSjp161948 					continue;
120490685d2cSjp161948 			}
120590685d2cSjp161948 			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
120690685d2cSjp161948 			if (cmdnum == I_CHOWN) {
120790685d2cSjp161948 				printf(gettext("Changing owner on %s\n"), g.gl_pathv[i]);
120890685d2cSjp161948 				aa->uid = n_arg;
120990685d2cSjp161948 			} else {
121090685d2cSjp161948 				printf(gettext("Changing group on %s\n"), g.gl_pathv[i]);
121190685d2cSjp161948 				aa->gid = n_arg;
121290685d2cSjp161948 			}
121390685d2cSjp161948 			err = do_setstat(conn, g.gl_pathv[i], aa);
121490685d2cSjp161948 			if (err != 0 && err_abort)
121590685d2cSjp161948 				break;
121690685d2cSjp161948 		}
121790685d2cSjp161948 		break;
121890685d2cSjp161948 	case I_PWD:
121990685d2cSjp161948 		printf(gettext("Remote working directory: %s\n"), *pwd);
122090685d2cSjp161948 		break;
122190685d2cSjp161948 	case I_LPWD:
122290685d2cSjp161948 		if (!getcwd(path_buf, sizeof(path_buf))) {
122390685d2cSjp161948 			error("Couldn't get local cwd: %s", strerror(errno));
122490685d2cSjp161948 			err = -1;
122590685d2cSjp161948 			break;
122690685d2cSjp161948 		}
122790685d2cSjp161948 		printf(gettext("Local working directory: %s\n"), path_buf);
122890685d2cSjp161948 		break;
122990685d2cSjp161948 	case I_QUIT:
123090685d2cSjp161948 		/* Processed below */
123190685d2cSjp161948 		break;
123290685d2cSjp161948 	case I_HELP:
123390685d2cSjp161948 		help();
123490685d2cSjp161948 		break;
123590685d2cSjp161948 	case I_VERSION:
123690685d2cSjp161948 		printf(gettext("SFTP protocol version %u\n"), sftp_proto_version(conn));
123790685d2cSjp161948 		break;
123890685d2cSjp161948 	case I_PROGRESS:
123990685d2cSjp161948 		showprogress = !showprogress;
124090685d2cSjp161948 		if (showprogress)
124190685d2cSjp161948 			printf("Progress meter enabled\n");
124290685d2cSjp161948 		else
124390685d2cSjp161948 			printf("Progress meter disabled\n");
124490685d2cSjp161948 		break;
124590685d2cSjp161948 	default:
124690685d2cSjp161948 		fatal("%d is not implemented", cmdnum);
124790685d2cSjp161948 	}
124890685d2cSjp161948 
124990685d2cSjp161948 	if (g.gl_pathc)
125090685d2cSjp161948 		globfree(&g);
125190685d2cSjp161948 	if (path1)
125290685d2cSjp161948 		xfree(path1);
125390685d2cSjp161948 	if (path2)
125490685d2cSjp161948 		xfree(path2);
125590685d2cSjp161948 
125690685d2cSjp161948 	/* If an unignored error occurs in batch mode we should abort. */
125790685d2cSjp161948 	if (err_abort && err != 0)
125890685d2cSjp161948 		return (-1);
125990685d2cSjp161948 	else if (cmdnum == I_QUIT)
126090685d2cSjp161948 		return (1);
126190685d2cSjp161948 
126290685d2cSjp161948 	return (0);
126390685d2cSjp161948 }
126490685d2cSjp161948 
126590685d2cSjp161948 #ifdef USE_LIBEDIT
126690685d2cSjp161948 static char *
prompt(EditLine * el)126790685d2cSjp161948 prompt(EditLine *el)
126890685d2cSjp161948 {
126990685d2cSjp161948 	return ("sftp> ");
127090685d2cSjp161948 }
127184e198abSHuie-Ying Lee #else
127284e198abSHuie-Ying Lee #ifdef USE_LIBTECLA
127384e198abSHuie-Ying Lee /*
127484e198abSHuie-Ying Lee  * Disable default TAB completion for filenames, because it displays local
127584e198abSHuie-Ying Lee  * files for every commands, which is not desirable.
127684e198abSHuie-Ying Lee  */
127784e198abSHuie-Ying Lee static
CPL_MATCH_FN(nomatch)127884e198abSHuie-Ying Lee CPL_MATCH_FN(nomatch)
127984e198abSHuie-Ying Lee {
128084e198abSHuie-Ying Lee 	return (0);
128184e198abSHuie-Ying Lee }
128284e198abSHuie-Ying Lee #endif /* USE_LIBTECLA */
128384e198abSHuie-Ying Lee #endif /* USE_LIBEDIT */
128490685d2cSjp161948 
128590685d2cSjp161948 int
interactive_loop(int fd_in,int fd_out,char * file1,char * file2)128690685d2cSjp161948 interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
128790685d2cSjp161948 {
128890685d2cSjp161948 	char *pwd;
128990685d2cSjp161948 	char *dir = NULL;
129090685d2cSjp161948 	char cmd[2048];
129190685d2cSjp161948 	struct sftp_conn *conn;
129290685d2cSjp161948 	int err, interactive;
129384e198abSHuie-Ying Lee 	void *il = NULL;
129484e198abSHuie-Ying Lee 
129590685d2cSjp161948 #ifdef USE_LIBEDIT
129684e198abSHuie-Ying Lee         EditLine *el = NULL;
129790685d2cSjp161948 	History *hl = NULL;
129890685d2cSjp161948 	HistEvent hev;
129990685d2cSjp161948 
130090685d2cSjp161948 	if (!batchmode && isatty(STDIN_FILENO)) {
130184e198abSHuie-Ying Lee 		if ((il = el = el_init(__progname, stdin, stdout, stderr)) == NULL)
130290685d2cSjp161948 			fatal("Couldn't initialise editline");
130390685d2cSjp161948 		if ((hl = history_init()) == NULL)
130490685d2cSjp161948 			fatal("Couldn't initialise editline history");
130590685d2cSjp161948 		history(hl, &hev, H_SETSIZE, 100);
130690685d2cSjp161948 		el_set(el, EL_HIST, history, hl);
130790685d2cSjp161948 
130890685d2cSjp161948 		el_set(el, EL_PROMPT, prompt);
130990685d2cSjp161948 		el_set(el, EL_EDITOR, "emacs");
131090685d2cSjp161948 		el_set(el, EL_TERMINAL, NULL);
131190685d2cSjp161948 		el_set(el, EL_SIGNAL, 1);
131290685d2cSjp161948 		el_source(el, NULL);
131390685d2cSjp161948 	}
131484e198abSHuie-Ying Lee #else
131584e198abSHuie-Ying Lee #ifdef USE_LIBTECLA
131684e198abSHuie-Ying Lee 	GetLine *gl = NULL;
131784e198abSHuie-Ying Lee 
131884e198abSHuie-Ying Lee 	if (!batchmode && isatty(STDIN_FILENO)) {
131984e198abSHuie-Ying Lee 		if ((il = gl = new_GetLine(MAX_LINE_LEN, MAX_CMD_HIST)) == NULL)
132084e198abSHuie-Ying Lee 			fatal("Couldn't initialize GetLine");
132184e198abSHuie-Ying Lee 		if (gl_customize_completion(gl, NULL, nomatch) != 0) {
132284e198abSHuie-Ying Lee 			(void) del_GetLine(gl);
132384e198abSHuie-Ying Lee 			fatal("Couldn't register completion function");
132484e198abSHuie-Ying Lee 		}
132584e198abSHuie-Ying Lee 	}
132684e198abSHuie-Ying Lee #endif /* USE_LIBTECLA */
132790685d2cSjp161948 #endif /* USE_LIBEDIT */
132890685d2cSjp161948 
132990685d2cSjp161948 	conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
133090685d2cSjp161948 	if (conn == NULL)
133190685d2cSjp161948 		fatal("Couldn't initialise connection to server");
133290685d2cSjp161948 
133390685d2cSjp161948 	pwd = do_realpath(conn, ".");
133490685d2cSjp161948 	if (pwd == NULL)
133590685d2cSjp161948 		fatal("Need cwd");
133690685d2cSjp161948 
133790685d2cSjp161948 	if (file1 != NULL) {
133890685d2cSjp161948 		dir = xstrdup(file1);
133990685d2cSjp161948 		dir = make_absolute(dir, pwd);
134090685d2cSjp161948 
134190685d2cSjp161948 		if (remote_is_dir(conn, dir) && file2 == NULL) {
134290685d2cSjp161948 			printf(gettext("Changing to: %s\n"), dir);
134390685d2cSjp161948 			snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
134490685d2cSjp161948 			if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) {
134590685d2cSjp161948 				xfree(dir);
134690685d2cSjp161948 				xfree(pwd);
134790685d2cSjp161948 				xfree(conn);
134890685d2cSjp161948 				return (-1);
134990685d2cSjp161948 			}
135090685d2cSjp161948 		} else {
135190685d2cSjp161948 			if (file2 == NULL)
135290685d2cSjp161948 				snprintf(cmd, sizeof cmd, "get %s", dir);
135390685d2cSjp161948 			else
135490685d2cSjp161948 				snprintf(cmd, sizeof cmd, "get %s %s", dir,
135590685d2cSjp161948 				    file2);
135690685d2cSjp161948 
135790685d2cSjp161948 			err = parse_dispatch_command(conn, cmd, &pwd, 1);
135890685d2cSjp161948 			xfree(dir);
135990685d2cSjp161948 			xfree(pwd);
136090685d2cSjp161948 			xfree(conn);
136190685d2cSjp161948 			return (err);
136290685d2cSjp161948 		}
136390685d2cSjp161948 		xfree(dir);
136490685d2cSjp161948 	}
136590685d2cSjp161948 
136690685d2cSjp161948 #if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
136790685d2cSjp161948 	setvbuf(stdout, NULL, _IOLBF, 0);
136890685d2cSjp161948 	setvbuf(infile, NULL, _IOLBF, 0);
136990685d2cSjp161948 #else
137090685d2cSjp161948 	setlinebuf(stdout);
137190685d2cSjp161948 	setlinebuf(infile);
137290685d2cSjp161948 #endif
137390685d2cSjp161948 
137490685d2cSjp161948 	interactive = !batchmode && isatty(STDIN_FILENO);
137590685d2cSjp161948 	err = 0;
137690685d2cSjp161948 	for (;;) {
137790685d2cSjp161948 		char *cp;
137890685d2cSjp161948 
137990685d2cSjp161948 		signal(SIGINT, SIG_IGN);
138090685d2cSjp161948 
138184e198abSHuie-Ying Lee 		if (il == NULL) {
138290685d2cSjp161948 			if (interactive)
138390685d2cSjp161948 				printf("sftp> ");
138490685d2cSjp161948 			if (fgets(cmd, sizeof(cmd), infile) == NULL) {
138590685d2cSjp161948 				if (interactive)
138690685d2cSjp161948 					printf("\n");
138790685d2cSjp161948 				break;
138890685d2cSjp161948 			}
138990685d2cSjp161948 			if (!interactive) { /* Echo command */
139090685d2cSjp161948 				printf("sftp> %s", cmd);
139190685d2cSjp161948 				if (strlen(cmd) > 0 &&
139290685d2cSjp161948 				    cmd[strlen(cmd) - 1] != '\n')
139390685d2cSjp161948 					printf("\n");
139490685d2cSjp161948 			}
139590685d2cSjp161948 		}
139690685d2cSjp161948 #ifdef USE_LIBEDIT
139790685d2cSjp161948 		else {
139890685d2cSjp161948 			const char *line;
139990685d2cSjp161948 			int count = 0;
140090685d2cSjp161948 
140190685d2cSjp161948 			if ((line = el_gets(el, &count)) == NULL || count <= 0) {
140290685d2cSjp161948 				printf("\n");
140390685d2cSjp161948  				break;
140490685d2cSjp161948 			}
140590685d2cSjp161948 			history(hl, &hev, H_ENTER, line);
140690685d2cSjp161948 			if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
140790685d2cSjp161948 				fprintf(stderr, gettext("Error: input line too long\n"));
140890685d2cSjp161948 				continue;
140990685d2cSjp161948 			}
141090685d2cSjp161948 		}
141184e198abSHuie-Ying Lee #else
141284e198abSHuie-Ying Lee #ifdef USE_LIBTECLA
141384e198abSHuie-Ying Lee 		else {
141484e198abSHuie-Ying Lee 			const char *line;
141584e198abSHuie-Ying Lee 
141684e198abSHuie-Ying Lee 			line = gl_get_line(gl, "sftp> ", NULL, -1);
141784e198abSHuie-Ying Lee 			if (line != NULL) {
141884e198abSHuie-Ying Lee 				if (strlcpy(cmd, line, sizeof(cmd)) >=
141984e198abSHuie-Ying Lee 				    sizeof(cmd)) {
142084e198abSHuie-Ying Lee 					fprintf(stderr, gettext(
142184e198abSHuie-Ying Lee 					    "Error: input line too long\n"));
142284e198abSHuie-Ying Lee 					continue;
142384e198abSHuie-Ying Lee 				}
142484e198abSHuie-Ying Lee 			} else {
142584e198abSHuie-Ying Lee 				GlReturnStatus rtn;
142684e198abSHuie-Ying Lee 
142784e198abSHuie-Ying Lee 				rtn = gl_return_status(gl);
142884e198abSHuie-Ying Lee 				if (rtn == GLR_SIGNAL) {
142984e198abSHuie-Ying Lee 					gl_abandon_line(gl);
143084e198abSHuie-Ying Lee 					continue;
143184e198abSHuie-Ying Lee 				} else if (rtn == GLR_ERROR) {
143284e198abSHuie-Ying Lee 					fprintf(stderr, gettext(
143384e198abSHuie-Ying Lee 					    "Error reading terminal: %s/\n"),
143484e198abSHuie-Ying Lee 					    gl_error_message(gl, NULL, 0));
143584e198abSHuie-Ying Lee 				}
143684e198abSHuie-Ying Lee 				break;
143784e198abSHuie-Ying Lee 			}
143884e198abSHuie-Ying Lee 		}
143984e198abSHuie-Ying Lee #endif /* USE_LIBTECLA */
144090685d2cSjp161948 #endif /* USE_LIBEDIT */
144190685d2cSjp161948 
144290685d2cSjp161948 		cp = strrchr(cmd, '\n');
144390685d2cSjp161948 		if (cp)
144490685d2cSjp161948 			*cp = '\0';
144590685d2cSjp161948 
144690685d2cSjp161948 		/* Handle user interrupts gracefully during commands */
144790685d2cSjp161948 		interrupted = 0;
144890685d2cSjp161948 		signal(SIGINT, cmd_interrupt);
144990685d2cSjp161948 
145090685d2cSjp161948 		err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
145190685d2cSjp161948 		if (err != 0)
145290685d2cSjp161948 			break;
145390685d2cSjp161948 	}
145490685d2cSjp161948 	xfree(pwd);
145590685d2cSjp161948 	xfree(conn);
145690685d2cSjp161948 
145790685d2cSjp161948 #ifdef USE_LIBEDIT
145890685d2cSjp161948 	if (el != NULL)
145990685d2cSjp161948 		el_end(el);
146084e198abSHuie-Ying Lee #else
146184e198abSHuie-Ying Lee #ifdef USE_LIBTECLA
146284e198abSHuie-Ying Lee 	if (gl != NULL)
146384e198abSHuie-Ying Lee 		(void) del_GetLine(gl);
146484e198abSHuie-Ying Lee #endif /* USE_LIBTECLA */
146590685d2cSjp161948 #endif /* USE_LIBEDIT */
146690685d2cSjp161948 
146790685d2cSjp161948 	/* err == 1 signifies normal "quit" exit */
146890685d2cSjp161948 	return (err >= 0 ? 0 : -1);
146990685d2cSjp161948 }
147090685d2cSjp161948 
147190685d2cSjp161948 static void
connect_to_server(char * path,char ** args,int * in,int * out)147290685d2cSjp161948 connect_to_server(char *path, char **args, int *in, int *out)
14737c478bd9Sstevel@tonic-gate {
14747c478bd9Sstevel@tonic-gate 	int c_in, c_out;
14757c478bd9Sstevel@tonic-gate 
14767c478bd9Sstevel@tonic-gate 	int inout[2];
14777c478bd9Sstevel@tonic-gate 
14787c478bd9Sstevel@tonic-gate 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
14797c478bd9Sstevel@tonic-gate 		fatal("socketpair: %s", strerror(errno));
14807c478bd9Sstevel@tonic-gate 	*in = *out = inout[0];
14817c478bd9Sstevel@tonic-gate 	c_in = c_out = inout[1];
14827c478bd9Sstevel@tonic-gate 
148390685d2cSjp161948 	if ((sshpid = fork()) == -1)
14847c478bd9Sstevel@tonic-gate 		fatal("fork: %s", strerror(errno));
148590685d2cSjp161948 	else if (sshpid == 0) {
14867c478bd9Sstevel@tonic-gate 		if ((dup2(c_in, STDIN_FILENO) == -1) ||
14877c478bd9Sstevel@tonic-gate 		    (dup2(c_out, STDOUT_FILENO) == -1)) {
14887c478bd9Sstevel@tonic-gate 			fprintf(stderr, "dup2: %s\n", strerror(errno));
148990685d2cSjp161948 			_exit(1);
14907c478bd9Sstevel@tonic-gate 		}
14917c478bd9Sstevel@tonic-gate 		close(*in);
14927c478bd9Sstevel@tonic-gate 		close(*out);
14937c478bd9Sstevel@tonic-gate 		close(c_in);
14947c478bd9Sstevel@tonic-gate 		close(c_out);
149590685d2cSjp161948 
149690685d2cSjp161948 		/*
149790685d2cSjp161948 		 * The underlying ssh is in the same process group, so we must
149890685d2cSjp161948 		 * ignore SIGINT if we want to gracefully abort commands,
149990685d2cSjp161948 		 * otherwise the signal will make it to the ssh process and
150090685d2cSjp161948 		 * kill it too
150190685d2cSjp161948 		 */
150290685d2cSjp161948 		signal(SIGINT, SIG_IGN);
150390685d2cSjp161948 		execvp(path, args);
15047c478bd9Sstevel@tonic-gate 		fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
150590685d2cSjp161948 		_exit(1);
15067c478bd9Sstevel@tonic-gate 	}
15077c478bd9Sstevel@tonic-gate 
150890685d2cSjp161948 	signal(SIGTERM, killchild);
150990685d2cSjp161948 	signal(SIGINT, killchild);
151090685d2cSjp161948 	signal(SIGHUP, killchild);
15117c478bd9Sstevel@tonic-gate 	close(c_in);
15127c478bd9Sstevel@tonic-gate 	close(c_out);
15137c478bd9Sstevel@tonic-gate }
15147c478bd9Sstevel@tonic-gate 
15157c478bd9Sstevel@tonic-gate static void
usage(void)15167c478bd9Sstevel@tonic-gate usage(void)
15177c478bd9Sstevel@tonic-gate {
15187c478bd9Sstevel@tonic-gate 	fprintf(stderr,
151990685d2cSjp161948 	    gettext("Usage: %s [-1Cv] [-b batchfile] [-B buffer_size]\n"
152090685d2cSjp161948 	    "            [-F ssh_config] [-o ssh_option] [-P sftp_server_path]\n"
152190685d2cSjp161948 	    "            [-R num_requests] [-s subsystem | sftp_server]\n"
152290685d2cSjp161948 	    "            [-S program] [user@]host[:dir[/] | :file [file]]\n"),
152390685d2cSjp161948 	    __progname, __progname, __progname, __progname);
15247c478bd9Sstevel@tonic-gate 	exit(1);
15257c478bd9Sstevel@tonic-gate }
15267c478bd9Sstevel@tonic-gate 
15277c478bd9Sstevel@tonic-gate int
main(int argc,char ** argv)15287c478bd9Sstevel@tonic-gate main(int argc, char **argv)
15297c478bd9Sstevel@tonic-gate {
153090685d2cSjp161948 	int in, out, ch, err;
153190685d2cSjp161948 	char *host, *userhost, *cp, *file2 = NULL;
15327c478bd9Sstevel@tonic-gate 	int debug_level = 0, sshver = 2;
15337c478bd9Sstevel@tonic-gate 	char *file1 = NULL, *sftp_server = NULL;
15347c478bd9Sstevel@tonic-gate 	char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
15357c478bd9Sstevel@tonic-gate 	LogLevel ll = SYSLOG_LEVEL_INFO;
15367c478bd9Sstevel@tonic-gate 	arglist args;
15377c478bd9Sstevel@tonic-gate 	extern int optind;
15387c478bd9Sstevel@tonic-gate 	extern char *optarg;
15397c478bd9Sstevel@tonic-gate 
154090685d2cSjp161948 	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
154190685d2cSjp161948 	sanitise_stdfd();
154290685d2cSjp161948 
15437c478bd9Sstevel@tonic-gate 	__progname = get_progname(argv[0]);
15447c478bd9Sstevel@tonic-gate 
15457c478bd9Sstevel@tonic-gate 	(void) g11n_setlocale(LC_ALL, "");
154690685d2cSjp161948 
154790685d2cSjp161948 	memset(&args, '\0', sizeof(args));
15487c478bd9Sstevel@tonic-gate 	args.list = NULL;
154990685d2cSjp161948 	addargs(&args, "%s", ssh_program);
15507c478bd9Sstevel@tonic-gate 	addargs(&args, "-oForwardX11 no");
15517c478bd9Sstevel@tonic-gate 	addargs(&args, "-oForwardAgent no");
15527c478bd9Sstevel@tonic-gate 	addargs(&args, "-oClearAllForwardings yes");
155390685d2cSjp161948 
15547c478bd9Sstevel@tonic-gate 	ll = SYSLOG_LEVEL_INFO;
155590685d2cSjp161948 	infile = stdin;
15567c478bd9Sstevel@tonic-gate 
15577c478bd9Sstevel@tonic-gate 	while ((ch = getopt(argc, argv, "1hvCo:s:S:b:B:F:P:R:")) != -1) {
15587c478bd9Sstevel@tonic-gate 		switch (ch) {
15597c478bd9Sstevel@tonic-gate 		case 'C':
15607c478bd9Sstevel@tonic-gate 			addargs(&args, "-C");
15617c478bd9Sstevel@tonic-gate 			break;
15627c478bd9Sstevel@tonic-gate 		case 'v':
15637c478bd9Sstevel@tonic-gate 			if (debug_level < 3) {
15647c478bd9Sstevel@tonic-gate 				addargs(&args, "-v");
15657c478bd9Sstevel@tonic-gate 				ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
15667c478bd9Sstevel@tonic-gate 			}
15677c478bd9Sstevel@tonic-gate 			debug_level++;
15687c478bd9Sstevel@tonic-gate 			break;
15697c478bd9Sstevel@tonic-gate 		case 'F':
15707c478bd9Sstevel@tonic-gate 		case 'o':
15717c478bd9Sstevel@tonic-gate 			addargs(&args, "-%c%s", ch, optarg);
15727c478bd9Sstevel@tonic-gate 			break;
15737c478bd9Sstevel@tonic-gate 		case '1':
15747c478bd9Sstevel@tonic-gate 			sshver = 1;
15757c478bd9Sstevel@tonic-gate 			if (sftp_server == NULL)
15767c478bd9Sstevel@tonic-gate 				sftp_server = _PATH_SFTP_SERVER;
15777c478bd9Sstevel@tonic-gate 			break;
15787c478bd9Sstevel@tonic-gate 		case 's':
15797c478bd9Sstevel@tonic-gate 			sftp_server = optarg;
15807c478bd9Sstevel@tonic-gate 			break;
15817c478bd9Sstevel@tonic-gate 		case 'S':
15827c478bd9Sstevel@tonic-gate 			ssh_program = optarg;
158390685d2cSjp161948 			replacearg(&args, 0, "%s", ssh_program);
15847c478bd9Sstevel@tonic-gate 			break;
15857c478bd9Sstevel@tonic-gate 		case 'b':
158690685d2cSjp161948 			if (batchmode)
158790685d2cSjp161948 				fatal("Batch file already specified.");
158890685d2cSjp161948 
158990685d2cSjp161948 			/* Allow "-" as stdin */
159090685d2cSjp161948 			if (strcmp(optarg, "-") != 0 &&
159190685d2cSjp161948 			    (infile = fopen(optarg, "r")) == NULL)
15927c478bd9Sstevel@tonic-gate 				fatal("%s (%s).", strerror(errno), optarg);
159390685d2cSjp161948 			showprogress = 0;
159490685d2cSjp161948 			batchmode = 1;
159590685d2cSjp161948 			addargs(&args, "-obatchmode yes");
15967c478bd9Sstevel@tonic-gate 			break;
15977c478bd9Sstevel@tonic-gate 		case 'P':
15987c478bd9Sstevel@tonic-gate 			sftp_direct = optarg;
15997c478bd9Sstevel@tonic-gate 			break;
16007c478bd9Sstevel@tonic-gate 		case 'B':
16017c478bd9Sstevel@tonic-gate 			copy_buffer_len = strtol(optarg, &cp, 10);
16027c478bd9Sstevel@tonic-gate 			if (copy_buffer_len == 0 || *cp != '\0')
16037c478bd9Sstevel@tonic-gate 				fatal("Invalid buffer size \"%s\"", optarg);
16047c478bd9Sstevel@tonic-gate 			break;
16057c478bd9Sstevel@tonic-gate 		case 'R':
16067c478bd9Sstevel@tonic-gate 			num_requests = strtol(optarg, &cp, 10);
16077c478bd9Sstevel@tonic-gate 			if (num_requests == 0 || *cp != '\0')
16087c478bd9Sstevel@tonic-gate 				fatal("Invalid number of requests \"%s\"",
16097c478bd9Sstevel@tonic-gate 				    optarg);
16107c478bd9Sstevel@tonic-gate 			break;
16117c478bd9Sstevel@tonic-gate 		case 'h':
16127c478bd9Sstevel@tonic-gate 		default:
16137c478bd9Sstevel@tonic-gate 			usage();
16147c478bd9Sstevel@tonic-gate 		}
16157c478bd9Sstevel@tonic-gate 	}
16167c478bd9Sstevel@tonic-gate 
161790685d2cSjp161948 	if (!isatty(STDERR_FILENO))
161890685d2cSjp161948 		showprogress = 0;
161990685d2cSjp161948 
16207c478bd9Sstevel@tonic-gate 	log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
16217c478bd9Sstevel@tonic-gate 
16227c478bd9Sstevel@tonic-gate 	if (sftp_direct == NULL) {
16237c478bd9Sstevel@tonic-gate 		if (optind == argc || argc > (optind + 2))
16247c478bd9Sstevel@tonic-gate 			usage();
16257c478bd9Sstevel@tonic-gate 
16267c478bd9Sstevel@tonic-gate 		userhost = xstrdup(argv[optind]);
16277c478bd9Sstevel@tonic-gate 		file2 = argv[optind+1];
16287c478bd9Sstevel@tonic-gate 
162990685d2cSjp161948 		if ((host = strrchr(userhost, '@')) == NULL)
16307c478bd9Sstevel@tonic-gate 			host = userhost;
16317c478bd9Sstevel@tonic-gate 		else {
16327c478bd9Sstevel@tonic-gate 			*host++ = '\0';
16337c478bd9Sstevel@tonic-gate 			if (!userhost[0]) {
16347c478bd9Sstevel@tonic-gate 				fprintf(stderr, gettext("Missing username\n"));
16357c478bd9Sstevel@tonic-gate 				usage();
16367c478bd9Sstevel@tonic-gate 			}
16377c478bd9Sstevel@tonic-gate 			addargs(&args, "-l%s", userhost);
16387c478bd9Sstevel@tonic-gate 		}
16397c478bd9Sstevel@tonic-gate 
164090685d2cSjp161948 		if ((cp = colon(host)) != NULL) {
164190685d2cSjp161948 			*cp++ = '\0';
164290685d2cSjp161948 			file1 = cp;
164390685d2cSjp161948 		}
164490685d2cSjp161948 
16457c478bd9Sstevel@tonic-gate 		host = cleanhostname(host);
16467c478bd9Sstevel@tonic-gate 		if (!*host) {
16477c478bd9Sstevel@tonic-gate 			fprintf(stderr, gettext("Missing hostname\n"));
16487c478bd9Sstevel@tonic-gate 			usage();
16497c478bd9Sstevel@tonic-gate 		}
16507c478bd9Sstevel@tonic-gate 
16517c478bd9Sstevel@tonic-gate 		addargs(&args, "-oProtocol %d", sshver);
16527c478bd9Sstevel@tonic-gate 
16537c478bd9Sstevel@tonic-gate 		/* no subsystem if the server-spec contains a '/' */
16547c478bd9Sstevel@tonic-gate 		if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
16557c478bd9Sstevel@tonic-gate 			addargs(&args, "-s");
16567c478bd9Sstevel@tonic-gate 
16577c478bd9Sstevel@tonic-gate 		addargs(&args, "%s", host);
16587c478bd9Sstevel@tonic-gate 		addargs(&args, "%s", (sftp_server != NULL ?
16597c478bd9Sstevel@tonic-gate 		    sftp_server : "sftp"));
16607c478bd9Sstevel@tonic-gate 
166190685d2cSjp161948 		if (!batchmode)
16627c478bd9Sstevel@tonic-gate 			fprintf(stderr, gettext("Connecting to %s...\n"), host);
166390685d2cSjp161948 		connect_to_server(ssh_program, args.list, &in, &out);
16647c478bd9Sstevel@tonic-gate 	} else {
16657c478bd9Sstevel@tonic-gate 		args.list = NULL;
16667c478bd9Sstevel@tonic-gate 		addargs(&args, "sftp-server");
16677c478bd9Sstevel@tonic-gate 
166890685d2cSjp161948 		if (!batchmode)
16697c478bd9Sstevel@tonic-gate 			fprintf(stderr, gettext("Attaching to %s...\n"), sftp_direct);
167090685d2cSjp161948 		connect_to_server(sftp_direct, args.list, &in, &out);
16717c478bd9Sstevel@tonic-gate 	}
167290685d2cSjp161948 	freeargs(&args);
16737c478bd9Sstevel@tonic-gate 
167490685d2cSjp161948 	err = interactive_loop(in, out, file1, file2);
16757c478bd9Sstevel@tonic-gate 
16767c478bd9Sstevel@tonic-gate 	shutdown(in, SHUT_RDWR);
16777c478bd9Sstevel@tonic-gate 	shutdown(out, SHUT_RDWR);
16787c478bd9Sstevel@tonic-gate 
16797c478bd9Sstevel@tonic-gate 	close(in);
16807c478bd9Sstevel@tonic-gate 	close(out);
168190685d2cSjp161948 	if (batchmode)
16827c478bd9Sstevel@tonic-gate 		fclose(infile);
16837c478bd9Sstevel@tonic-gate 
16847c478bd9Sstevel@tonic-gate 	while (waitpid(sshpid, NULL, 0) == -1)
16857c478bd9Sstevel@tonic-gate 		if (errno != EINTR)
16867c478bd9Sstevel@tonic-gate 			fatal("Couldn't wait for ssh process: %s",
16877c478bd9Sstevel@tonic-gate 			    strerror(errno));
16887c478bd9Sstevel@tonic-gate 
168990685d2cSjp161948 	return (err == 0 ? 0 : 1);
16907c478bd9Sstevel@tonic-gate }
1691