sftp.c revision 128456
14Srgrimes/*
24Srgrimes * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
3295Sjtc *
44Srgrimes * Permission to use, copy, modify, and distribute this software for any
5295Sjtc * purpose with or without fee is hereby granted, provided that the above
6295Sjtc * copyright notice and this permission notice appear in all copies.
750471Speter *
84Srgrimes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9468Sjtc * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1062926Sse * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1192979Swollman * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1292979Swollman * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1392979Swollman * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1492979Swollman * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1592979Swollman */
1692979Swollman
1792979Swollman#include "includes.h"
184Srgrimes
194SrgrimesRCSID("$OpenBSD: sftp.c,v 1.45 2004/03/03 09:31:20 djm Exp $");
204Srgrimes
2162926Sse#include "buffer.h"
2292979Swollman#include "xmalloc.h"
2331Salm#include "log.h"
2492979Swollman#include "pathnames.h"
2592979Swollman#include "misc.h"
2692979Swollman
2792979Swollman#include "sftp.h"
2892979Swollman#include "sftp-common.h"
2992979Swollman#include "sftp-client.h"
304Srgrimes
3112378Sjoerg/* File to read commands from */
324SrgrimesFILE* infile;
3331Salm
344Srgrimes/* Are we in batchfile mode? */
354Srgrimesint batchmode = 0;
364Srgrimes
374Srgrimes/* Size of buffer used when copying files */
3892979Swollmansize_t copy_buffer_len = 32768;
394Srgrimes
4031Salm/* Number of concurrent outstanding requests */
414Srgrimessize_t num_requests = 16;
424Srgrimes
434Srgrimes/* PID of ssh transport process */
4492979Swollmanstatic pid_t sshpid = -1;
4592979Swollman
4692979Swollman/* This is set to 0 if the progressmeter is not desired. */
4792979Swollmanint showprogress = 1;
4890109Simp
4990109Simpint remote_glob(struct sftp_conn *, const char *, int,
5090109Simp    int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
5192979Swollman
5290109Simp#ifdef HAVE___PROGNAME
5390109Simpextern char *__progname;
5490109Simp#else
5590109Simpchar *__progname;
5690109Simp#endif
5790109Simp
5890109Simp/* Separators for interactive commands */
5990109Simp#define WHITESPACE " \t\r\n"
6090109Simp
6190109Simp/* Define what type of ls view (0 - multi-column) */
6290109Simp#define LONG_VIEW 1		/* Full view ala ls -l */
6390109Simp#define SHORT_VIEW 2		/* Single row view ala ls -1 */
6490109Simp
6590109Simp/* Commands for interactive mode */
6690109Simp#define I_CHDIR		1
6792979Swollman#define I_CHGRP		2
6890109Simp#define I_CHMOD		3
6990109Simp#define I_CHOWN		4
7090109Simp#define I_GET		5
7190109Simp#define I_HELP		6
7277244Skris#define I_LCHDIR	7
734Srgrimes#define I_LLS		8
744Srgrimes#define I_LMKDIR	9
754Srgrimes#define I_LPWD		10
764Srgrimes#define I_LS		11
774Srgrimes#define I_LUMASK	12
784Srgrimes#define I_MKDIR		13
794Srgrimes#define I_PUT		14
804Srgrimes#define I_PWD		15
814Srgrimes#define I_QUIT		16
824Srgrimes#define I_RENAME	17
834Srgrimes#define I_RM		18
844Srgrimes#define I_RMDIR		19
854Srgrimes#define I_SHELL		20
864Srgrimes#define I_SYMLINK	21
874Srgrimes#define I_VERSION	22
884Srgrimes#define I_PROGRESS	23
894Srgrimes
904Srgrimesstruct CMD {
914Srgrimes	const char *c;
924Srgrimes	const int n;
934Srgrimes};
944Srgrimes
954Srgrimesstatic const struct CMD cmds[] = {
964Srgrimes	{ "bye",	I_QUIT },
974Srgrimes	{ "cd",		I_CHDIR },
984Srgrimes	{ "chdir",	I_CHDIR },
994Srgrimes	{ "chgrp",	I_CHGRP },
1004Srgrimes	{ "chmod",	I_CHMOD },
1014Srgrimes	{ "chown",	I_CHOWN },
1024Srgrimes	{ "dir",	I_LS },
1034Srgrimes	{ "exit",	I_QUIT },
1044Srgrimes	{ "get",	I_GET },
1054Srgrimes	{ "mget",	I_GET },
1064Srgrimes	{ "help",	I_HELP },
1074Srgrimes	{ "lcd",	I_LCHDIR },
1084Srgrimes	{ "lchdir",	I_LCHDIR },
1094Srgrimes	{ "lls",	I_LLS },
1104Srgrimes	{ "lmkdir",	I_LMKDIR },
1114Srgrimes	{ "ln",		I_SYMLINK },
1124Srgrimes	{ "lpwd",	I_LPWD },
1134Srgrimes	{ "ls",		I_LS },
1144Srgrimes	{ "lumask",	I_LUMASK },
1154Srgrimes	{ "mkdir",	I_MKDIR },
1164Srgrimes	{ "progress",	I_PROGRESS },
11792979Swollman	{ "put",	I_PUT },
1184Srgrimes	{ "mput",	I_PUT },
1194Srgrimes	{ "pwd",	I_PWD },
1204Srgrimes	{ "quit",	I_QUIT },
1214Srgrimes	{ "rename",	I_RENAME },
1224Srgrimes	{ "rm",		I_RM },
12392979Swollman	{ "rmdir",	I_RMDIR },
1244Srgrimes	{ "symlink",	I_SYMLINK },
1254Srgrimes	{ "version",	I_VERSION },
1264Srgrimes	{ "!",		I_SHELL },
1274Srgrimes	{ "?",		I_HELP },
1284Srgrimes	{ NULL,			-1}
1294Srgrimes};
1304Srgrimes
1314Srgrimesint interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
13290109Simp
1334Srgrimesstatic void
1344Srgrimeshelp(void)
13592979Swollman{
1364Srgrimes	printf("Available commands:\n");
1374Srgrimes	printf("cd path                       Change remote directory to 'path'\n");
1384Srgrimes	printf("lcd path                      Change local directory to 'path'\n");
13992979Swollman	printf("chgrp grp path                Change group of file 'path' to 'grp'\n");
1404Srgrimes	printf("chmod mode path               Change permissions of file 'path' to 'mode'\n");
1414Srgrimes	printf("chown own path                Change owner of file 'path' to 'own'\n");
14292979Swollman	printf("help                          Display this help text\n");
14392979Swollman	printf("get remote-path [local-path]  Download file\n");
14492979Swollman	printf("lls [ls-options [path]]       Display local directory listing\n");
14592979Swollman	printf("ln oldpath newpath            Symlink remote file\n");
14692979Swollman	printf("lmkdir path                   Create local directory\n");
14792979Swollman	printf("lpwd                          Print local working directory\n");
14892979Swollman	printf("ls [path]                     Display remote directory listing\n");
14992979Swollman	printf("lumask umask                  Set local umask to 'umask'\n");
15092979Swollman	printf("mkdir path                    Create remote directory\n");
15192979Swollman	printf("progress                      Toggle display of progress meter\n");
15292979Swollman	printf("put local-path [remote-path]  Upload file\n");
15392979Swollman	printf("pwd                           Display remote working directory\n");
15492979Swollman	printf("exit                          Quit sftp\n");
15592979Swollman	printf("quit                          Quit sftp\n");
15692979Swollman	printf("rename oldpath newpath        Rename remote file\n");
15792979Swollman	printf("rmdir path                    Remove remote directory\n");
1589909Sjoerg	printf("rm path                       Delete remote file\n");
15992979Swollman	printf("symlink oldpath newpath       Symlink remote file\n");
16092979Swollman	printf("version                       Show SFTP version\n");
16192979Swollman	printf("!command                      Execute 'command' in local shell\n");
16212378Sjoerg	printf("!                             Escape to local shell\n");
1639909Sjoerg	printf("?                             Synonym for help\n");
1644Srgrimes}
1654Srgrimes
1664Srgrimesstatic void
1674Srgrimeslocal_do_shell(const char *args)
1684Srgrimes{
16990109Simp	int status;
1704Srgrimes	char *shell;
17112378Sjoerg	pid_t pid;
1724Srgrimes
1734Srgrimes	if (!*args)
1744Srgrimes		args = NULL;
1754Srgrimes
17692979Swollman	if ((shell = getenv("SHELL")) == NULL)
17790109Simp		shell = _PATH_BSHELL;
1784Srgrimes
17992979Swollman	if ((pid = fork()) == -1)
1804Srgrimes		fatal("Couldn't fork: %s", strerror(errno));
1814Srgrimes
1824Srgrimes	if (pid == 0) {
1834Srgrimes		/* XXX: child has pipe fds to ssh subproc open - issue? */
18412378Sjoerg		if (args) {
18512378Sjoerg			debug3("Executing %s -c \"%s\"", shell, args);
1864Srgrimes			execl(shell, shell, "-c", args, (char *)NULL);
18712378Sjoerg		} else {
18863755Sse			debug3("Executing %s", shell);
18992979Swollman			execl(shell, shell, (char *)NULL);
19092979Swollman		}
19192979Swollman		fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
19292979Swollman		    strerror(errno));
1934Srgrimes		_exit(1);
19412378Sjoerg	}
1954Srgrimes	while (waitpid(pid, &status, 0) == -1)
1964Srgrimes		if (errno != EINTR)
1974Srgrimes			fatal("Couldn't wait for child: %s", strerror(errno));
1984Srgrimes	if (!WIFEXITED(status))
1994Srgrimes		error("Shell exited abormally");
20090109Simp	else if (WEXITSTATUS(status))
2014Srgrimes		error("Shell exited with status %d", WEXITSTATUS(status));
2024Srgrimes}
2034Srgrimes
20412378Sjoergstatic void
2054Srgrimeslocal_do_ls(const char *args)
2064Srgrimes{
20792979Swollman	if (!args || !*args)
20892979Swollman		local_do_shell(_PATH_LS);
20992979Swollman	else {
21092979Swollman		int len = strlen(_PATH_LS " ") + strlen(args) + 1;
21192979Swollman		char *buf = xmalloc(len);
21292979Swollman
21392979Swollman		/* XXX: quoting - rip quoting code from ftp? */
21492979Swollman		snprintf(buf, len, _PATH_LS " %s", args);
21592979Swollman		local_do_shell(buf);
2164Srgrimes		xfree(buf);
21792979Swollman	}
2184Srgrimes}
2194Srgrimes
2204Srgrimes/* Strip one path (usually the pwd) from the start of another */
2214Srgrimesstatic char *
2224Srgrimespath_strip(char *path, char *strip)
2234Srgrimes{
22490109Simp	size_t len;
2254Srgrimes
22612378Sjoerg	if (strip == NULL)
2274Srgrimes		return (xstrdup(path));
2284Srgrimes
2294Srgrimes	len = strlen(strip);
2304Srgrimes	if (strip != NULL && strncmp(path, strip, len) == 0) {
2314Srgrimes		if (strip[len - 1] != '/' && path[len] == '/')
23290109Simp			len++;
2334Srgrimes		return (xstrdup(path + len));
2344Srgrimes	}
2354Srgrimes
2364Srgrimes	return (xstrdup(path));
2374Srgrimes}
2384Srgrimes
2394Srgrimesstatic char *
2404Srgrimespath_append(char *p1, char *p2)
2414Srgrimes{
2424Srgrimes	char *ret;
2434Srgrimes	int len = strlen(p1) + strlen(p2) + 2;
2444Srgrimes
2454Srgrimes	ret = xmalloc(len);
2464Srgrimes	strlcpy(ret, p1, len);
2474Srgrimes	if (p1[strlen(p1) - 1] != '/')
2484Srgrimes		strlcat(ret, "/", len);
2494Srgrimes	strlcat(ret, p2, len);
2504Srgrimes
2514Srgrimes	return(ret);
2524Srgrimes}
2534Srgrimes
2544Srgrimesstatic char *
2554Srgrimesmake_absolute(char *p, char *pwd)
2564Srgrimes{
25790109Simp	char *abs;
2584Srgrimes
2594Srgrimes	/* Derelativise */
2604Srgrimes	if (p && p[0] != '/') {
2614Srgrimes		abs = path_append(pwd, p);
262181Sconklin		xfree(p);
2634Srgrimes		return(abs);
2644Srgrimes	} else
2654Srgrimes		return(p);
2664Srgrimes}
26712378Sjoerg
26892979Swollmanstatic int
2694Srgrimesinfer_path(const char *p, char **ifp)
27092979Swollman{
27192979Swollman	char *cp;
272468Sjtc
27392979Swollman	cp = strrchr(p, '/');
27492979Swollman	if (cp == NULL) {
27592979Swollman		*ifp = xstrdup(p);
27692979Swollman		return(0);
27792979Swollman	}
27892979Swollman
279468Sjtc	if (!cp[1]) {
28092979Swollman		error("Invalid path");
2814Srgrimes		return(-1);
28292979Swollman	}
2834Srgrimes
2844Srgrimes	*ifp = xstrdup(cp + 1);
28592979Swollman	return(0);
2864Srgrimes}
28792979Swollman
2884Srgrimesstatic int
28992979Swollmanparse_getput_flags(const char **cpp, int *pflag)
2904Srgrimes{
2914Srgrimes	const char *cp = *cpp;
2924Srgrimes
29390109Simp	/* Check for flags */
2944Srgrimes	if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
29592979Swollman		switch (cp[1]) {
2964Srgrimes		case 'p':
2974Srgrimes		case 'P':
2984Srgrimes			*pflag = 1;
2994Srgrimes			break;
30090109Simp		default:
3014Srgrimes			error("Invalid flag -%c", cp[1]);
3024Srgrimes			return(-1);
3034Srgrimes		}
3044Srgrimes		cp += 2;
3054Srgrimes		*cpp = cp + strspn(cp, WHITESPACE);
3064Srgrimes	}
3074Srgrimes
3084Srgrimes	return(0);
3094Srgrimes}
3104Srgrimes
3114Srgrimesstatic int
31290109Simpparse_ls_flags(const char **cpp, int *lflag)
3134Srgrimes{
3144Srgrimes	const char *cp = *cpp;
3154Srgrimes
3164Srgrimes	/* Check for flags */
31792979Swollman	if (cp++[0] == '-') {
3184Srgrimes		for(; strchr(WHITESPACE, *cp) == NULL; cp++) {
3194Srgrimes			switch (*cp) {
3204Srgrimes			case 'l':
3214Srgrimes				*lflag = LONG_VIEW;
3224Srgrimes				break;
3234Srgrimes			case '1':
3244Srgrimes				*lflag = SHORT_VIEW;
32590109Simp				break;
3264Srgrimes			default:
3274Srgrimes				error("Invalid flag -%c", *cp);
3284Srgrimes				return(-1);
3294Srgrimes			}
3304Srgrimes		}
3314Srgrimes		*cpp = cp + strspn(cp, WHITESPACE);
33292979Swollman	}
3334Srgrimes
33412378Sjoerg	return(0);
33512378Sjoerg}
33692979Swollman
3374Srgrimesstatic int
3384Srgrimesget_pathname(const char **cpp, char **path)
3394Srgrimes{
3404Srgrimes	const char *cp = *cpp, *end;
3414Srgrimes	char quot;
3424Srgrimes	int i, j;
3434Srgrimes
3444Srgrimes	cp += strspn(cp, WHITESPACE);
34590109Simp	if (!*cp) {
3464Srgrimes		*cpp = cp;
3474Srgrimes		*path = NULL;
3484Srgrimes		return (0);
3494Srgrimes	}
3504Srgrimes
3514Srgrimes	*path = xmalloc(strlen(cp) + 1);
35292979Swollman
3534Srgrimes	/* Check for quoted filenames */
35412378Sjoerg	if (*cp == '\"' || *cp == '\'') {
35512378Sjoerg		quot = *cp++;
35692979Swollman
3574Srgrimes		/* Search for terminating quote, unescape some chars */
3584Srgrimes		for (i = j = 0; i <= strlen(cp); i++) {
3594Srgrimes			if (cp[i] == quot) {	/* Found quote */
3604Srgrimes				i++;
3614Srgrimes				(*path)[j] = '\0';
3624Srgrimes				break;
3634Srgrimes			}
3644Srgrimes			if (cp[i] == '\0') {	/* End of string */
36590109Simp				error("Unterminated quote");
3664Srgrimes				goto fail;
3674Srgrimes			}
3684Srgrimes			if (cp[i] == '\\') {	/* Escaped characters */
3694Srgrimes				i++;
3704Srgrimes				if (cp[i] != '\'' && cp[i] != '\"' &&
3714Srgrimes				    cp[i] != '\\') {
37292979Swollman					error("Bad escaped character '\%c'",
3734Srgrimes					    cp[i]);
37412378Sjoerg					goto fail;
37512378Sjoerg				}
37692979Swollman			}
3774Srgrimes			(*path)[j++] = cp[i];
3784Srgrimes		}
3794Srgrimes
3804Srgrimes		if (j == 0) {
3814Srgrimes			error("Empty quotes");
3824Srgrimes			goto fail;
3834Srgrimes		}
3844Srgrimes		*cpp = cp + i + strspn(cp + i, WHITESPACE);
38590109Simp	} else {
3864Srgrimes		/* Read to end of filename */
3874Srgrimes		end = strpbrk(cp, WHITESPACE);
3884Srgrimes		if (end == NULL)
3894Srgrimes			end = strchr(cp, '\0');
3904Srgrimes		*cpp = end + strspn(end, WHITESPACE);
3914Srgrimes
39292979Swollman		memcpy(*path, cp, end - cp);
3934Srgrimes		(*path)[end - cp] = '\0';
39412378Sjoerg	}
39512378Sjoerg	return (0);
39692979Swollman
3974Srgrimes fail:
3984Srgrimes	xfree(*path);
3994Srgrimes	*path = NULL;
4004Srgrimes	return (-1);
4014Srgrimes}
4024Srgrimes
4034Srgrimesstatic int
4044Srgrimesis_dir(char *path)
40590109Simp{
4064Srgrimes	struct stat sb;
4074Srgrimes
4084Srgrimes	/* XXX: report errors? */
4094Srgrimes	if (stat(path, &sb) == -1)
4104Srgrimes		return(0);
4114Srgrimes
41292979Swollman	return(sb.st_mode & S_IFDIR);
4134Srgrimes}
41412378Sjoerg
41512378Sjoergstatic int
41692979Swollmanis_reg(char *path)
4174Srgrimes{
4184Srgrimes	struct stat sb;
4194Srgrimes
4204Srgrimes	if (stat(path, &sb) == -1)
4214Srgrimes		fatal("stat %s: %s", path, strerror(errno));
4224Srgrimes
4234Srgrimes	return(S_ISREG(sb.st_mode));
4244Srgrimes}
42590109Simp
4264Srgrimesstatic int
4274Srgrimesremote_is_dir(struct sftp_conn *conn, char *path)
4284Srgrimes{
4294Srgrimes	Attrib *a;
4304Srgrimes
4314Srgrimes	/* XXX: report errors? */
43292979Swollman	if ((a = do_stat(conn, path, 1)) == NULL)
4334Srgrimes		return(0);
43412378Sjoerg	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
43512378Sjoerg		return(0);
43692979Swollman	return(a->perm & S_IFDIR);
4374Srgrimes}
4384Srgrimes
4394Srgrimesstatic int
4404Srgrimesprocess_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
4414Srgrimes{
4424Srgrimes	char *abs_src = NULL;
4434Srgrimes	char *abs_dst = NULL;
44463755Sse	char *tmp;
44592979Swollman	glob_t g;
44663755Sse	int err = 0;
44763755Sse	int i;
44863755Sse
44963755Sse	abs_src = xstrdup(src);
45063755Sse	abs_src = make_absolute(abs_src, pwd);
45163755Sse
45263755Sse	memset(&g, 0, sizeof(g));
45363755Sse	debug3("Looking up %s", abs_src);
45463755Sse	if (remote_glob(conn, abs_src, 0, NULL, &g)) {
45563755Sse		error("File \"%s\" not found.", abs_src);
45663755Sse		err = -1;
4574Srgrimes		goto out;
45890109Simp	}
4594Srgrimes
4604Srgrimes	/* If multiple matches, dst must be a directory or unspecified */
4614Srgrimes	if (g.gl_matchc > 1 && dst && !is_dir(dst)) {
4624Srgrimes		error("Multiple files match, but \"%s\" is not a directory",
46392979Swollman		    dst);
4644Srgrimes		err = -1;
4654Srgrimes		goto out;
46692979Swollman	}
46763755Sse
46892979Swollman	for (i = 0; g.gl_pathv[i]; i++) {
46963755Sse		if (infer_path(g.gl_pathv[i], &tmp)) {
4704Srgrimes			err = -1;
4714Srgrimes			goto out;
4724Srgrimes		}
4734Srgrimes
47463755Sse		if (g.gl_matchc == 1 && dst) {
47563755Sse			/* If directory specified, append filename */
47692979Swollman			if (is_dir(dst)) {
47763755Sse				if (infer_path(g.gl_pathv[0], &tmp)) {
47892979Swollman					err = 1;
47992979Swollman					goto out;
48063755Sse				}
48163755Sse				abs_dst = path_append(dst, tmp);
48263755Sse				xfree(tmp);
48363755Sse			} else
48463755Sse				abs_dst = xstrdup(dst);
48592979Swollman		} else if (dst) {
48663755Sse			abs_dst = path_append(dst, tmp);
48763755Sse			xfree(tmp);
48863755Sse		} else
4894Srgrimes			abs_dst = tmp;
49090109Simp
4914Srgrimes		printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
4924Srgrimes		if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
4934Srgrimes			err = -1;
4944Srgrimes		xfree(abs_dst);
49592979Swollman		abs_dst = NULL;
4964Srgrimes	}
4974Srgrimes
49892979Swollmanout:
49963755Sse	xfree(abs_src);
50092979Swollman	if (abs_dst)
50163755Sse		xfree(abs_dst);
5024Srgrimes	globfree(&g);
5034Srgrimes	return(err);
5044Srgrimes}
5054Srgrimes
50663755Ssestatic int
50763755Sseprocess_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
50892979Swollman{
50963755Sse	char *tmp_dst = NULL;
51063755Sse	char *abs_dst = NULL;
51163755Sse	char *tmp;
51263755Sse	glob_t g;
51363755Sse	int err = 0;
51463755Sse	int i;
51563755Sse
51663755Sse	if (dst) {
51763755Sse		tmp_dst = xstrdup(dst);
51863755Sse		tmp_dst = make_absolute(tmp_dst, pwd);
5194Srgrimes	}
52090109Simp
5214Srgrimes	memset(&g, 0, sizeof(g));
5224Srgrimes	debug3("Looking up %s", src);
5234Srgrimes	if (glob(src, 0, NULL, &g)) {
5244Srgrimes		error("File \"%s\" not found.", src);
52592979Swollman		err = -1;
5264Srgrimes		goto out;
5274Srgrimes	}
52892979Swollman
52963755Sse	/* If multiple matches, dst may be directory or unspecified */
53092979Swollman	if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) {
53163755Sse		error("Multiple files match, but \"%s\" is not a directory",
5324Srgrimes		    tmp_dst);
5334Srgrimes		err = -1;
5344Srgrimes		goto out;
5354Srgrimes	}
53663755Sse
53763755Sse	for (i = 0; g.gl_pathv[i]; i++) {
53892979Swollman		if (!is_reg(g.gl_pathv[i])) {
53963755Sse			error("skipping non-regular file %s",
54063755Sse			    g.gl_pathv[i]);
54192979Swollman			continue;
54292979Swollman		}
54363755Sse		if (infer_path(g.gl_pathv[i], &tmp)) {
54463755Sse			err = -1;
54563755Sse			goto out;
54663755Sse		}
54763755Sse
5484Srgrimes		if (g.gl_matchc == 1 && tmp_dst) {
54990109Simp			/* If directory specified, append filename */
5504Srgrimes			if (remote_is_dir(conn, tmp_dst)) {
5514Srgrimes				if (infer_path(g.gl_pathv[0], &tmp)) {
5524Srgrimes					err = 1;
5534Srgrimes					goto out;
55492979Swollman				}
5554Srgrimes				abs_dst = path_append(tmp_dst, tmp);
5564Srgrimes				xfree(tmp);
5574Srgrimes			} else
55892979Swollman				abs_dst = xstrdup(tmp_dst);
5594Srgrimes
5604Srgrimes		} else if (tmp_dst) {
56192979Swollman			abs_dst = path_append(tmp_dst, tmp);
56277244Skris			xfree(tmp);
56392979Swollman		} else
56463755Sse			abs_dst = make_absolute(tmp, pwd);
5654Srgrimes
5664Srgrimes		printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
5674Srgrimes		if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
5684Srgrimes			err = -1;
5694Srgrimes	}
5704Srgrimes
57190109Simpout:
5724Srgrimes	if (abs_dst)
5734Srgrimes		xfree(abs_dst);
5744Srgrimes	if (tmp_dst)
5754Srgrimes		xfree(tmp_dst);
57692979Swollman	globfree(&g);
5774Srgrimes	return(err);
5784Srgrimes}
5794Srgrimes
58092979Swollmanstatic int
5814Srgrimessdirent_comp(const void *aa, const void *bb)
5824Srgrimes{
58392979Swollman	SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
58463755Sse	SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
5854Srgrimes
5864Srgrimes	return (strcmp(a->filename, b->filename));
5874Srgrimes}
5884Srgrimes
5894Srgrimes/* sftp ls.1 replacement for directories */
5904Srgrimesstatic int
59190109Simpdo_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
5924Srgrimes{
59331Salm	int n, c = 1, colspace = 0, columns = 1;
594468Sjtc	SFTP_DIRENT **d;
59531Salm
59631Salm	if ((n = do_readdir(conn, path, &d)) != 0)
597181Sconklin		return (n);
5984Srgrimes
599181Sconklin	if (!(lflag & SHORT_VIEW)) {
600181Sconklin		int m = 0, width = 80;
601181Sconklin		struct winsize ws;
602181Sconklin		char *tmp;
60331Salm
604539Sjtc		/* Count entries for sort and find longest filename */
60531Salm		for (n = 0; d[n] != NULL; n++)
60692979Swollman			m = MAX(m, strlen(d[n]->filename));
6074Srgrimes
6084Srgrimes		/* Add any subpath that also needs to be counted */
60931Salm		tmp = path_strip(path, strip_path);
610539Sjtc		m += strlen(tmp);
61177244Skris		xfree(tmp);
61231Salm
613295Sjtc		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
61431Salm			width = ws.ws_col;
61531Salm
6164Srgrimes		columns = width / (m + 2);
61792979Swollman		columns = MAX(columns, 1);
6184Srgrimes		colspace = width / columns;
6194Srgrimes		colspace = MIN(colspace, width);
620295Sjtc	}
62192979Swollman
622295Sjtc	qsort(d, n, sizeof(*d), sdirent_comp);
623295Sjtc
624295Sjtc	for (n = 0; d[n] != NULL; n++) {
6254Srgrimes		char *tmp, *fname;
6264Srgrimes
62731Salm		tmp = path_append(path, d[n]->filename);
62831Salm		fname = path_strip(tmp, strip_path);
62931Salm		xfree(tmp);
63031Salm
63131Salm		if (lflag & LONG_VIEW) {
63231Salm			char *lname;
6334Srgrimes			struct stat sb;
634
635			memset(&sb, 0, sizeof(sb));
636			attrib_to_stat(&d[n]->a, &sb);
637			lname = ls_file(fname, &sb, 1);
638			printf("%s\n", lname);
639			xfree(lname);
640		} else {
641			printf("%-*s", colspace, fname);
642			if (c >= columns) {
643				printf("\n");
644				c = 1;
645			} else
646				c++;
647		}
648
649		xfree(fname);
650	}
651
652	if (!(lflag & LONG_VIEW) && (c != 1))
653		printf("\n");
654
655	free_sftp_dirents(d);
656	return (0);
657}
658
659/* sftp ls.1 replacement which handles path globs */
660static int
661do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
662    int lflag)
663{
664	glob_t g;
665	int i, c = 1, colspace = 0, columns = 1;
666	Attrib *a;
667
668	memset(&g, 0, sizeof(g));
669
670	if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
671	    NULL, &g)) {
672		error("Can't ls: \"%s\" not found", path);
673		return (-1);
674	}
675
676	/*
677	 * If the glob returns a single match, which is the same as the
678	 * input glob, and it is a directory, then just list its contents
679	 */
680	if (g.gl_pathc == 1 &&
681	    strncmp(path, g.gl_pathv[0], strlen(g.gl_pathv[0]) - 1) == 0) {
682		if ((a = do_lstat(conn, path, 1)) == NULL) {
683			globfree(&g);
684			return (-1);
685		}
686		if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
687		    S_ISDIR(a->perm)) {
688			globfree(&g);
689			return (do_ls_dir(conn, path, strip_path, lflag));
690		}
691	}
692
693	if (!(lflag & SHORT_VIEW)) {
694		int m = 0, width = 80;
695		struct winsize ws;
696
697		/* Count entries for sort and find longest filename */
698		for (i = 0; g.gl_pathv[i]; i++)
699			m = MAX(m, strlen(g.gl_pathv[i]));
700
701		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
702			width = ws.ws_col;
703
704		columns = width / (m + 2);
705		columns = MAX(columns, 1);
706		colspace = width / columns;
707	}
708
709	for (i = 0; g.gl_pathv[i]; i++) {
710		char *fname;
711
712		fname = path_strip(g.gl_pathv[i], strip_path);
713
714		if (lflag & LONG_VIEW) {
715			char *lname;
716			struct stat sb;
717
718			/*
719			 * XXX: this is slow - 1 roundtrip per path
720			 * A solution to this is to fork glob() and
721			 * build a sftp specific version which keeps the
722			 * attribs (which currently get thrown away)
723			 * that the server returns as well as the filenames.
724			 */
725			memset(&sb, 0, sizeof(sb));
726			a = do_lstat(conn, g.gl_pathv[i], 1);
727			if (a != NULL)
728				attrib_to_stat(a, &sb);
729			lname = ls_file(fname, &sb, 1);
730			printf("%s\n", lname);
731			xfree(lname);
732		} else {
733			printf("%-*s", colspace, fname);
734			if (c >= columns) {
735				printf("\n");
736				c = 1;
737			} else
738				c++;
739		}
740		xfree(fname);
741	}
742
743	if (!(lflag & LONG_VIEW) && (c != 1))
744		printf("\n");
745
746	if (g.gl_pathc)
747		globfree(&g);
748
749	return (0);
750}
751
752static int
753parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
754    unsigned long *n_arg, char **path1, char **path2)
755{
756	const char *cmd, *cp = *cpp;
757	char *cp2;
758	int base = 0;
759	long l;
760	int i, cmdnum;
761
762	/* Skip leading whitespace */
763	cp = cp + strspn(cp, WHITESPACE);
764
765	/* Ignore blank lines and lines which begin with comment '#' char */
766	if (*cp == '\0' || *cp == '#')
767		return (0);
768
769	/* Check for leading '-' (disable error processing) */
770	*iflag = 0;
771	if (*cp == '-') {
772		*iflag = 1;
773		cp++;
774	}
775
776	/* Figure out which command we have */
777	for (i = 0; cmds[i].c; i++) {
778		int cmdlen = strlen(cmds[i].c);
779
780		/* Check for command followed by whitespace */
781		if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
782		    strchr(WHITESPACE, cp[cmdlen])) {
783			cp += cmdlen;
784			cp = cp + strspn(cp, WHITESPACE);
785			break;
786		}
787	}
788	cmdnum = cmds[i].n;
789	cmd = cmds[i].c;
790
791	/* Special case */
792	if (*cp == '!') {
793		cp++;
794		cmdnum = I_SHELL;
795	} else if (cmdnum == -1) {
796		error("Invalid command.");
797		return (-1);
798	}
799
800	/* Get arguments and parse flags */
801	*lflag = *pflag = *n_arg = 0;
802	*path1 = *path2 = NULL;
803	switch (cmdnum) {
804	case I_GET:
805	case I_PUT:
806		if (parse_getput_flags(&cp, pflag))
807			return(-1);
808		/* Get first pathname (mandatory) */
809		if (get_pathname(&cp, path1))
810			return(-1);
811		if (*path1 == NULL) {
812			error("You must specify at least one path after a "
813			    "%s command.", cmd);
814			return(-1);
815		}
816		/* Try to get second pathname (optional) */
817		if (get_pathname(&cp, path2))
818			return(-1);
819		break;
820	case I_RENAME:
821	case I_SYMLINK:
822		if (get_pathname(&cp, path1))
823			return(-1);
824		if (get_pathname(&cp, path2))
825			return(-1);
826		if (!*path1 || !*path2) {
827			error("You must specify two paths after a %s "
828			    "command.", cmd);
829			return(-1);
830		}
831		break;
832	case I_RM:
833	case I_MKDIR:
834	case I_RMDIR:
835	case I_CHDIR:
836	case I_LCHDIR:
837	case I_LMKDIR:
838		/* Get pathname (mandatory) */
839		if (get_pathname(&cp, path1))
840			return(-1);
841		if (*path1 == NULL) {
842			error("You must specify a path after a %s command.",
843			    cmd);
844			return(-1);
845		}
846		break;
847	case I_LS:
848		if (parse_ls_flags(&cp, lflag))
849			return(-1);
850		/* Path is optional */
851		if (get_pathname(&cp, path1))
852			return(-1);
853		break;
854	case I_LLS:
855	case I_SHELL:
856		/* Uses the rest of the line */
857		break;
858	case I_LUMASK:
859		base = 8;
860	case I_CHMOD:
861		base = 8;
862	case I_CHOWN:
863	case I_CHGRP:
864		/* Get numeric arg (mandatory) */
865		l = strtol(cp, &cp2, base);
866		if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
867		    errno == ERANGE) || l < 0) {
868			error("You must supply a numeric argument "
869			    "to the %s command.", cmd);
870			return(-1);
871		}
872		cp = cp2;
873		*n_arg = l;
874		if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
875			break;
876		if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
877			error("You must supply a numeric argument "
878			    "to the %s command.", cmd);
879			return(-1);
880		}
881		cp += strspn(cp, WHITESPACE);
882
883		/* Get pathname (mandatory) */
884		if (get_pathname(&cp, path1))
885			return(-1);
886		if (*path1 == NULL) {
887			error("You must specify a path after a %s command.",
888			    cmd);
889			return(-1);
890		}
891		break;
892	case I_QUIT:
893	case I_PWD:
894	case I_LPWD:
895	case I_HELP:
896	case I_VERSION:
897	case I_PROGRESS:
898		break;
899	default:
900		fatal("Command not implemented");
901	}
902
903	*cpp = cp;
904	return(cmdnum);
905}
906
907static int
908parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
909    int err_abort)
910{
911	char *path1, *path2, *tmp;
912	int pflag, lflag, iflag, cmdnum, i;
913	unsigned long n_arg;
914	Attrib a, *aa;
915	char path_buf[MAXPATHLEN];
916	int err = 0;
917	glob_t g;
918
919	path1 = path2 = NULL;
920	cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &n_arg,
921	    &path1, &path2);
922
923	if (iflag != 0)
924		err_abort = 0;
925
926	memset(&g, 0, sizeof(g));
927
928	/* Perform command */
929	switch (cmdnum) {
930	case 0:
931		/* Blank line */
932		break;
933	case -1:
934		/* Unrecognized command */
935		err = -1;
936		break;
937	case I_GET:
938		err = process_get(conn, path1, path2, *pwd, pflag);
939		break;
940	case I_PUT:
941		err = process_put(conn, path1, path2, *pwd, pflag);
942		break;
943	case I_RENAME:
944		path1 = make_absolute(path1, *pwd);
945		path2 = make_absolute(path2, *pwd);
946		err = do_rename(conn, path1, path2);
947		break;
948	case I_SYMLINK:
949		path2 = make_absolute(path2, *pwd);
950		err = do_symlink(conn, path1, path2);
951		break;
952	case I_RM:
953		path1 = make_absolute(path1, *pwd);
954		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
955		for (i = 0; g.gl_pathv[i]; i++) {
956			printf("Removing %s\n", g.gl_pathv[i]);
957			err = do_rm(conn, g.gl_pathv[i]);
958			if (err != 0 && err_abort)
959				break;
960		}
961		break;
962	case I_MKDIR:
963		path1 = make_absolute(path1, *pwd);
964		attrib_clear(&a);
965		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
966		a.perm = 0777;
967		err = do_mkdir(conn, path1, &a);
968		break;
969	case I_RMDIR:
970		path1 = make_absolute(path1, *pwd);
971		err = do_rmdir(conn, path1);
972		break;
973	case I_CHDIR:
974		path1 = make_absolute(path1, *pwd);
975		if ((tmp = do_realpath(conn, path1)) == NULL) {
976			err = 1;
977			break;
978		}
979		if ((aa = do_stat(conn, tmp, 0)) == NULL) {
980			xfree(tmp);
981			err = 1;
982			break;
983		}
984		if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
985			error("Can't change directory: Can't check target");
986			xfree(tmp);
987			err = 1;
988			break;
989		}
990		if (!S_ISDIR(aa->perm)) {
991			error("Can't change directory: \"%s\" is not "
992			    "a directory", tmp);
993			xfree(tmp);
994			err = 1;
995			break;
996		}
997		xfree(*pwd);
998		*pwd = tmp;
999		break;
1000	case I_LS:
1001		if (!path1) {
1002			do_globbed_ls(conn, *pwd, *pwd, lflag);
1003			break;
1004		}
1005
1006		/* Strip pwd off beginning of non-absolute paths */
1007		tmp = NULL;
1008		if (*path1 != '/')
1009			tmp = *pwd;
1010
1011		path1 = make_absolute(path1, *pwd);
1012		err = do_globbed_ls(conn, path1, tmp, lflag);
1013		break;
1014	case I_LCHDIR:
1015		if (chdir(path1) == -1) {
1016			error("Couldn't change local directory to "
1017			    "\"%s\": %s", path1, strerror(errno));
1018			err = 1;
1019		}
1020		break;
1021	case I_LMKDIR:
1022		if (mkdir(path1, 0777) == -1) {
1023			error("Couldn't create local directory "
1024			    "\"%s\": %s", path1, strerror(errno));
1025			err = 1;
1026		}
1027		break;
1028	case I_LLS:
1029		local_do_ls(cmd);
1030		break;
1031	case I_SHELL:
1032		local_do_shell(cmd);
1033		break;
1034	case I_LUMASK:
1035		umask(n_arg);
1036		printf("Local umask: %03lo\n", n_arg);
1037		break;
1038	case I_CHMOD:
1039		path1 = make_absolute(path1, *pwd);
1040		attrib_clear(&a);
1041		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1042		a.perm = n_arg;
1043		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1044		for (i = 0; g.gl_pathv[i]; i++) {
1045			printf("Changing mode on %s\n", g.gl_pathv[i]);
1046			err = do_setstat(conn, g.gl_pathv[i], &a);
1047			if (err != 0 && err_abort)
1048				break;
1049		}
1050		break;
1051	case I_CHOWN:
1052	case I_CHGRP:
1053		path1 = make_absolute(path1, *pwd);
1054		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1055		for (i = 0; g.gl_pathv[i]; i++) {
1056			if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
1057				if (err != 0 && err_abort)
1058					break;
1059				else
1060					continue;
1061			}
1062			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1063				error("Can't get current ownership of "
1064				    "remote file \"%s\"", g.gl_pathv[i]);
1065				if (err != 0 && err_abort)
1066					break;
1067				else
1068					continue;
1069			}
1070			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1071			if (cmdnum == I_CHOWN) {
1072				printf("Changing owner on %s\n", g.gl_pathv[i]);
1073				aa->uid = n_arg;
1074			} else {
1075				printf("Changing group on %s\n", g.gl_pathv[i]);
1076				aa->gid = n_arg;
1077			}
1078			err = do_setstat(conn, g.gl_pathv[i], aa);
1079			if (err != 0 && err_abort)
1080				break;
1081		}
1082		break;
1083	case I_PWD:
1084		printf("Remote working directory: %s\n", *pwd);
1085		break;
1086	case I_LPWD:
1087		if (!getcwd(path_buf, sizeof(path_buf))) {
1088			error("Couldn't get local cwd: %s", strerror(errno));
1089			err = -1;
1090			break;
1091		}
1092		printf("Local working directory: %s\n", path_buf);
1093		break;
1094	case I_QUIT:
1095		/* Processed below */
1096		break;
1097	case I_HELP:
1098		help();
1099		break;
1100	case I_VERSION:
1101		printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1102		break;
1103	case I_PROGRESS:
1104		showprogress = !showprogress;
1105		if (showprogress)
1106			printf("Progress meter enabled\n");
1107		else
1108			printf("Progress meter disabled\n");
1109		break;
1110	default:
1111		fatal("%d is not implemented", cmdnum);
1112	}
1113
1114	if (g.gl_pathc)
1115		globfree(&g);
1116	if (path1)
1117		xfree(path1);
1118	if (path2)
1119		xfree(path2);
1120
1121	/* If an unignored error occurs in batch mode we should abort. */
1122	if (err_abort && err != 0)
1123		return (-1);
1124	else if (cmdnum == I_QUIT)
1125		return (1);
1126
1127	return (0);
1128}
1129
1130int
1131interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
1132{
1133	char *pwd;
1134	char *dir = NULL;
1135	char cmd[2048];
1136	struct sftp_conn *conn;
1137	int err;
1138
1139	conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
1140	if (conn == NULL)
1141		fatal("Couldn't initialise connection to server");
1142
1143	pwd = do_realpath(conn, ".");
1144	if (pwd == NULL)
1145		fatal("Need cwd");
1146
1147	if (file1 != NULL) {
1148		dir = xstrdup(file1);
1149		dir = make_absolute(dir, pwd);
1150
1151		if (remote_is_dir(conn, dir) && file2 == NULL) {
1152			printf("Changing to: %s\n", dir);
1153			snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
1154			if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0)
1155				return (-1);
1156		} else {
1157			if (file2 == NULL)
1158				snprintf(cmd, sizeof cmd, "get %s", dir);
1159			else
1160				snprintf(cmd, sizeof cmd, "get %s %s", dir,
1161				    file2);
1162
1163			err = parse_dispatch_command(conn, cmd, &pwd, 1);
1164			xfree(dir);
1165			xfree(pwd);
1166			return (err);
1167		}
1168		xfree(dir);
1169	}
1170
1171#if HAVE_SETVBUF
1172	setvbuf(stdout, NULL, _IOLBF, 0);
1173	setvbuf(infile, NULL, _IOLBF, 0);
1174#else
1175       setlinebuf(stdout);
1176       setlinebuf(infile);
1177#endif
1178
1179	err = 0;
1180	for (;;) {
1181		char *cp;
1182
1183		printf("sftp> ");
1184
1185		/* XXX: use libedit */
1186		if (fgets(cmd, sizeof(cmd), infile) == NULL) {
1187			printf("\n");
1188			break;
1189		}
1190
1191		if (batchmode) /* Echo command */
1192			printf("%s", cmd);
1193
1194		cp = strrchr(cmd, '\n');
1195		if (cp)
1196			*cp = '\0';
1197
1198		err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
1199		if (err != 0)
1200			break;
1201	}
1202	xfree(pwd);
1203
1204	/* err == 1 signifies normal "quit" exit */
1205	return (err >= 0 ? 0 : -1);
1206}
1207
1208static void
1209killchild(int signo)
1210{
1211	if (sshpid > 1)
1212		kill(sshpid, signo);
1213
1214	_exit(1);
1215}
1216
1217static void
1218connect_to_server(char *path, char **args, int *in, int *out)
1219{
1220	int c_in, c_out;
1221
1222#ifdef USE_PIPES
1223	int pin[2], pout[2];
1224
1225	if ((pipe(pin) == -1) || (pipe(pout) == -1))
1226		fatal("pipe: %s", strerror(errno));
1227	*in = pin[0];
1228	*out = pout[1];
1229	c_in = pout[0];
1230	c_out = pin[1];
1231#else /* USE_PIPES */
1232	int inout[2];
1233
1234	if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
1235		fatal("socketpair: %s", strerror(errno));
1236	*in = *out = inout[0];
1237	c_in = c_out = inout[1];
1238#endif /* USE_PIPES */
1239
1240	if ((sshpid = fork()) == -1)
1241		fatal("fork: %s", strerror(errno));
1242	else if (sshpid == 0) {
1243		if ((dup2(c_in, STDIN_FILENO) == -1) ||
1244		    (dup2(c_out, STDOUT_FILENO) == -1)) {
1245			fprintf(stderr, "dup2: %s\n", strerror(errno));
1246			exit(1);
1247		}
1248		close(*in);
1249		close(*out);
1250		close(c_in);
1251		close(c_out);
1252		execv(path, args);
1253		fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
1254		exit(1);
1255	}
1256
1257	signal(SIGTERM, killchild);
1258	signal(SIGINT, killchild);
1259	signal(SIGHUP, killchild);
1260	close(c_in);
1261	close(c_out);
1262}
1263
1264static void
1265usage(void)
1266{
1267	extern char *__progname;
1268
1269	fprintf(stderr,
1270	    "usage: %s [-1Cv] [-B buffer_size] [-b batchfile] [-F ssh_config]\n"
1271	    "            [-o ssh_option] [-P sftp_server_path] [-R num_requests]\n"
1272	    "            [-S program] [-s subsystem | sftp_server] host\n"
1273	    "       %s [[user@]host[:file [file]]]\n"
1274	    "       %s [[user@]host[:dir[/]]]\n"
1275	    "       %s -b batchfile [user@]host\n", __progname, __progname, __progname, __progname);
1276	exit(1);
1277}
1278
1279int
1280main(int argc, char **argv)
1281{
1282	int in, out, ch, err;
1283	char *host, *userhost, *cp, *file2;
1284	int debug_level = 0, sshver = 2;
1285	char *file1 = NULL, *sftp_server = NULL;
1286	char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
1287	LogLevel ll = SYSLOG_LEVEL_INFO;
1288	arglist args;
1289	extern int optind;
1290	extern char *optarg;
1291
1292	__progname = ssh_get_progname(argv[0]);
1293	args.list = NULL;
1294	addargs(&args, "ssh");		/* overwritten with ssh_program */
1295	addargs(&args, "-oForwardX11 no");
1296	addargs(&args, "-oForwardAgent no");
1297	addargs(&args, "-oClearAllForwardings yes");
1298
1299	ll = SYSLOG_LEVEL_INFO;
1300	infile = stdin;
1301
1302	while ((ch = getopt(argc, argv, "1hvCo:s:S:b:B:F:P:R:")) != -1) {
1303		switch (ch) {
1304		case 'C':
1305			addargs(&args, "-C");
1306			break;
1307		case 'v':
1308			if (debug_level < 3) {
1309				addargs(&args, "-v");
1310				ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
1311			}
1312			debug_level++;
1313			break;
1314		case 'F':
1315		case 'o':
1316			addargs(&args, "-%c%s", ch, optarg);
1317			break;
1318		case '1':
1319			sshver = 1;
1320			if (sftp_server == NULL)
1321				sftp_server = _PATH_SFTP_SERVER;
1322			break;
1323		case 's':
1324			sftp_server = optarg;
1325			break;
1326		case 'S':
1327			ssh_program = optarg;
1328			break;
1329		case 'b':
1330			if (batchmode)
1331				fatal("Batch file already specified.");
1332
1333			/* Allow "-" as stdin */
1334			if (strcmp(optarg, "-") != 0 &&
1335			   (infile = fopen(optarg, "r")) == NULL)
1336				fatal("%s (%s).", strerror(errno), optarg);
1337			showprogress = 0;
1338			batchmode = 1;
1339			break;
1340		case 'P':
1341			sftp_direct = optarg;
1342			break;
1343		case 'B':
1344			copy_buffer_len = strtol(optarg, &cp, 10);
1345			if (copy_buffer_len == 0 || *cp != '\0')
1346				fatal("Invalid buffer size \"%s\"", optarg);
1347			break;
1348		case 'R':
1349			num_requests = strtol(optarg, &cp, 10);
1350			if (num_requests == 0 || *cp != '\0')
1351				fatal("Invalid number of requests \"%s\"",
1352				    optarg);
1353			break;
1354		case 'h':
1355		default:
1356			usage();
1357		}
1358	}
1359
1360	if (!isatty(STDERR_FILENO))
1361		showprogress = 0;
1362
1363	log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
1364
1365	if (sftp_direct == NULL) {
1366		if (optind == argc || argc > (optind + 2))
1367			usage();
1368
1369		userhost = xstrdup(argv[optind]);
1370		file2 = argv[optind+1];
1371
1372		if ((host = strrchr(userhost, '@')) == NULL)
1373			host = userhost;
1374		else {
1375			*host++ = '\0';
1376			if (!userhost[0]) {
1377				fprintf(stderr, "Missing username\n");
1378				usage();
1379			}
1380			addargs(&args, "-l%s",userhost);
1381		}
1382
1383		if ((cp = colon(host)) != NULL) {
1384			*cp++ = '\0';
1385			file1 = cp;
1386		}
1387
1388		host = cleanhostname(host);
1389		if (!*host) {
1390			fprintf(stderr, "Missing hostname\n");
1391			usage();
1392		}
1393
1394		addargs(&args, "-oProtocol %d", sshver);
1395
1396		/* no subsystem if the server-spec contains a '/' */
1397		if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
1398			addargs(&args, "-s");
1399
1400		addargs(&args, "%s", host);
1401		addargs(&args, "%s", (sftp_server != NULL ?
1402		    sftp_server : "sftp"));
1403		args.list[0] = ssh_program;
1404
1405		if (!batchmode)
1406			fprintf(stderr, "Connecting to %s...\n", host);
1407		connect_to_server(ssh_program, args.list, &in, &out);
1408	} else {
1409		args.list = NULL;
1410		addargs(&args, "sftp-server");
1411
1412		if (!batchmode)
1413			fprintf(stderr, "Attaching to %s...\n", sftp_direct);
1414		connect_to_server(sftp_direct, args.list, &in, &out);
1415	}
1416
1417	err = interactive_loop(in, out, file1, file2);
1418
1419#if !defined(USE_PIPES)
1420       shutdown(in, SHUT_RDWR);
1421       shutdown(out, SHUT_RDWR);
1422#endif
1423
1424	close(in);
1425	close(out);
1426	if (batchmode)
1427		fclose(infile);
1428
1429	while (waitpid(sshpid, NULL, 0) == -1)
1430		if (errno != EINTR)
1431			fatal("Couldn't wait for ssh process: %s",
1432			    strerror(errno));
1433
1434	exit(err == 0 ? 0 : 1);
1435}
1436