xref: /freebsd/crypto/openssh/sftp.c (revision cce7d3464f90246064915b2a37a95ef8a07c7370)
1cce7d346SDag-Erling Smørgrav /* $OpenBSD: sftp.c,v 1.107 2009/02/02 11:15:14 dtucker Exp $ */
21e8db6e2SBrian Feldman /*
3efcad6b7SDag-Erling Smørgrav  * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
41e8db6e2SBrian Feldman  *
5efcad6b7SDag-Erling Smørgrav  * Permission to use, copy, modify, and distribute this software for any
6efcad6b7SDag-Erling Smørgrav  * purpose with or without fee is hereby granted, provided that the above
7efcad6b7SDag-Erling Smørgrav  * copyright notice and this permission notice appear in all copies.
81e8db6e2SBrian Feldman  *
9efcad6b7SDag-Erling Smørgrav  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10efcad6b7SDag-Erling Smørgrav  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11efcad6b7SDag-Erling Smørgrav  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12efcad6b7SDag-Erling Smørgrav  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13efcad6b7SDag-Erling Smørgrav  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14efcad6b7SDag-Erling Smørgrav  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15efcad6b7SDag-Erling Smørgrav  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
161e8db6e2SBrian Feldman  */
171e8db6e2SBrian Feldman 
181e8db6e2SBrian Feldman #include "includes.h"
191e8db6e2SBrian Feldman 
20761efaa7SDag-Erling Smørgrav #include <sys/types.h>
21761efaa7SDag-Erling Smørgrav #include <sys/ioctl.h>
22761efaa7SDag-Erling Smørgrav #ifdef HAVE_SYS_STAT_H
23761efaa7SDag-Erling Smørgrav # include <sys/stat.h>
24761efaa7SDag-Erling Smørgrav #endif
25761efaa7SDag-Erling Smørgrav #include <sys/param.h>
26761efaa7SDag-Erling Smørgrav #include <sys/socket.h>
27761efaa7SDag-Erling Smørgrav #include <sys/wait.h>
28d4af9e69SDag-Erling Smørgrav #ifdef HAVE_SYS_STATVFS_H
29d4af9e69SDag-Erling Smørgrav #include <sys/statvfs.h>
30d4af9e69SDag-Erling Smørgrav #endif
315e8dbd04SDag-Erling Smørgrav 
32d4af9e69SDag-Erling Smørgrav #include <ctype.h>
33761efaa7SDag-Erling Smørgrav #include <errno.h>
34761efaa7SDag-Erling Smørgrav 
35761efaa7SDag-Erling Smørgrav #ifdef HAVE_PATHS_H
36761efaa7SDag-Erling Smørgrav # include <paths.h>
37761efaa7SDag-Erling Smørgrav #endif
385e8dbd04SDag-Erling Smørgrav #ifdef USE_LIBEDIT
395e8dbd04SDag-Erling Smørgrav #include <histedit.h>
405e8dbd04SDag-Erling Smørgrav #else
415e8dbd04SDag-Erling Smørgrav typedef void EditLine;
425e8dbd04SDag-Erling Smørgrav #endif
43761efaa7SDag-Erling Smørgrav #include <signal.h>
44761efaa7SDag-Erling Smørgrav #include <stdlib.h>
45761efaa7SDag-Erling Smørgrav #include <stdio.h>
46761efaa7SDag-Erling Smørgrav #include <string.h>
47761efaa7SDag-Erling Smørgrav #include <unistd.h>
48761efaa7SDag-Erling Smørgrav #include <stdarg.h>
491e8db6e2SBrian Feldman 
50d4af9e69SDag-Erling Smørgrav #ifdef HAVE_UTIL_H
51d4af9e69SDag-Erling Smørgrav # include <util.h>
52d4af9e69SDag-Erling Smørgrav #endif
53d4af9e69SDag-Erling Smørgrav 
54d4af9e69SDag-Erling Smørgrav #ifdef HAVE_LIBUTIL_H
55d4af9e69SDag-Erling Smørgrav # include <libutil.h>
56d4af9e69SDag-Erling Smørgrav #endif
57d4af9e69SDag-Erling Smørgrav 
581e8db6e2SBrian Feldman #include "xmalloc.h"
591e8db6e2SBrian Feldman #include "log.h"
601e8db6e2SBrian Feldman #include "pathnames.h"
61ae1f160dSDag-Erling Smørgrav #include "misc.h"
621e8db6e2SBrian Feldman 
631e8db6e2SBrian Feldman #include "sftp.h"
64761efaa7SDag-Erling Smørgrav #include "buffer.h"
651e8db6e2SBrian Feldman #include "sftp-common.h"
661e8db6e2SBrian Feldman #include "sftp-client.h"
67efcad6b7SDag-Erling Smørgrav 
68efcad6b7SDag-Erling Smørgrav /* File to read commands from */
69efcad6b7SDag-Erling Smørgrav FILE* infile;
70efcad6b7SDag-Erling Smørgrav 
71efcad6b7SDag-Erling Smørgrav /* Are we in batchfile mode? */
72efcad6b7SDag-Erling Smørgrav int batchmode = 0;
73efcad6b7SDag-Erling Smørgrav 
74efcad6b7SDag-Erling Smørgrav /* Size of buffer used when copying files */
75efcad6b7SDag-Erling Smørgrav size_t copy_buffer_len = 32768;
76efcad6b7SDag-Erling Smørgrav 
77efcad6b7SDag-Erling Smørgrav /* Number of concurrent outstanding requests */
78d4af9e69SDag-Erling Smørgrav size_t num_requests = 64;
79efcad6b7SDag-Erling Smørgrav 
80efcad6b7SDag-Erling Smørgrav /* PID of ssh transport process */
81efcad6b7SDag-Erling Smørgrav static pid_t sshpid = -1;
82efcad6b7SDag-Erling Smørgrav 
83efcad6b7SDag-Erling Smørgrav /* This is set to 0 if the progressmeter is not desired. */
8452028650SDag-Erling Smørgrav int showprogress = 1;
85efcad6b7SDag-Erling Smørgrav 
86d74d50a8SDag-Erling Smørgrav /* SIGINT received during command processing */
87d74d50a8SDag-Erling Smørgrav volatile sig_atomic_t interrupted = 0;
88d74d50a8SDag-Erling Smørgrav 
89d74d50a8SDag-Erling Smørgrav /* I wish qsort() took a separate ctx for the comparison function...*/
90d74d50a8SDag-Erling Smørgrav int sort_flag;
91d74d50a8SDag-Erling Smørgrav 
92efcad6b7SDag-Erling Smørgrav int remote_glob(struct sftp_conn *, const char *, int,
93efcad6b7SDag-Erling Smørgrav     int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
941e8db6e2SBrian Feldman 
9583d2307dSDag-Erling Smørgrav extern char *__progname;
9683d2307dSDag-Erling Smørgrav 
97efcad6b7SDag-Erling Smørgrav /* Separators for interactive commands */
98efcad6b7SDag-Erling Smørgrav #define WHITESPACE " \t\r\n"
991e8db6e2SBrian Feldman 
100d74d50a8SDag-Erling Smørgrav /* ls flags */
101d74d50a8SDag-Erling Smørgrav #define LS_LONG_VIEW	0x01	/* Full view ala ls -l */
102d74d50a8SDag-Erling Smørgrav #define LS_SHORT_VIEW	0x02	/* Single row view ala ls -1 */
103d74d50a8SDag-Erling Smørgrav #define LS_NUMERIC_VIEW	0x04	/* Long view with numeric uid/gid */
104d74d50a8SDag-Erling Smørgrav #define LS_NAME_SORT	0x08	/* Sort by name (default) */
105d74d50a8SDag-Erling Smørgrav #define LS_TIME_SORT	0x10	/* Sort by mtime */
106d74d50a8SDag-Erling Smørgrav #define LS_SIZE_SORT	0x20	/* Sort by file size */
107d74d50a8SDag-Erling Smørgrav #define LS_REVERSE_SORT	0x40	/* Reverse sort order */
108d74d50a8SDag-Erling Smørgrav #define LS_SHOW_ALL	0x80	/* Don't skip filenames starting with '.' */
109d74d50a8SDag-Erling Smørgrav 
110d74d50a8SDag-Erling Smørgrav #define VIEW_FLAGS	(LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW)
111d74d50a8SDag-Erling Smørgrav #define SORT_FLAGS	(LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
112efcad6b7SDag-Erling Smørgrav 
113efcad6b7SDag-Erling Smørgrav /* Commands for interactive mode */
114efcad6b7SDag-Erling Smørgrav #define I_CHDIR		1
115efcad6b7SDag-Erling Smørgrav #define I_CHGRP		2
116efcad6b7SDag-Erling Smørgrav #define I_CHMOD		3
117efcad6b7SDag-Erling Smørgrav #define I_CHOWN		4
118d4af9e69SDag-Erling Smørgrav #define I_DF		24
119efcad6b7SDag-Erling Smørgrav #define I_GET		5
120efcad6b7SDag-Erling Smørgrav #define I_HELP		6
121efcad6b7SDag-Erling Smørgrav #define I_LCHDIR	7
122efcad6b7SDag-Erling Smørgrav #define I_LLS		8
123efcad6b7SDag-Erling Smørgrav #define I_LMKDIR	9
124efcad6b7SDag-Erling Smørgrav #define I_LPWD		10
125efcad6b7SDag-Erling Smørgrav #define I_LS		11
126efcad6b7SDag-Erling Smørgrav #define I_LUMASK	12
127efcad6b7SDag-Erling Smørgrav #define I_MKDIR		13
128efcad6b7SDag-Erling Smørgrav #define I_PUT		14
129efcad6b7SDag-Erling Smørgrav #define I_PWD		15
130efcad6b7SDag-Erling Smørgrav #define I_QUIT		16
131efcad6b7SDag-Erling Smørgrav #define I_RENAME	17
132efcad6b7SDag-Erling Smørgrav #define I_RM		18
133efcad6b7SDag-Erling Smørgrav #define I_RMDIR		19
134efcad6b7SDag-Erling Smørgrav #define I_SHELL		20
135efcad6b7SDag-Erling Smørgrav #define I_SYMLINK	21
136efcad6b7SDag-Erling Smørgrav #define I_VERSION	22
137efcad6b7SDag-Erling Smørgrav #define I_PROGRESS	23
138efcad6b7SDag-Erling Smørgrav 
139efcad6b7SDag-Erling Smørgrav struct CMD {
140efcad6b7SDag-Erling Smørgrav 	const char *c;
141efcad6b7SDag-Erling Smørgrav 	const int n;
142efcad6b7SDag-Erling Smørgrav };
143efcad6b7SDag-Erling Smørgrav 
144efcad6b7SDag-Erling Smørgrav static const struct CMD cmds[] = {
145efcad6b7SDag-Erling Smørgrav 	{ "bye",	I_QUIT },
146efcad6b7SDag-Erling Smørgrav 	{ "cd",		I_CHDIR },
147efcad6b7SDag-Erling Smørgrav 	{ "chdir",	I_CHDIR },
148efcad6b7SDag-Erling Smørgrav 	{ "chgrp",	I_CHGRP },
149efcad6b7SDag-Erling Smørgrav 	{ "chmod",	I_CHMOD },
150efcad6b7SDag-Erling Smørgrav 	{ "chown",	I_CHOWN },
151d4af9e69SDag-Erling Smørgrav 	{ "df",		I_DF },
152efcad6b7SDag-Erling Smørgrav 	{ "dir",	I_LS },
153efcad6b7SDag-Erling Smørgrav 	{ "exit",	I_QUIT },
154efcad6b7SDag-Erling Smørgrav 	{ "get",	I_GET },
155efcad6b7SDag-Erling Smørgrav 	{ "mget",	I_GET },
156efcad6b7SDag-Erling Smørgrav 	{ "help",	I_HELP },
157efcad6b7SDag-Erling Smørgrav 	{ "lcd",	I_LCHDIR },
158efcad6b7SDag-Erling Smørgrav 	{ "lchdir",	I_LCHDIR },
159efcad6b7SDag-Erling Smørgrav 	{ "lls",	I_LLS },
160efcad6b7SDag-Erling Smørgrav 	{ "lmkdir",	I_LMKDIR },
161efcad6b7SDag-Erling Smørgrav 	{ "ln",		I_SYMLINK },
162efcad6b7SDag-Erling Smørgrav 	{ "lpwd",	I_LPWD },
163efcad6b7SDag-Erling Smørgrav 	{ "ls",		I_LS },
164efcad6b7SDag-Erling Smørgrav 	{ "lumask",	I_LUMASK },
165efcad6b7SDag-Erling Smørgrav 	{ "mkdir",	I_MKDIR },
166efcad6b7SDag-Erling Smørgrav 	{ "progress",	I_PROGRESS },
167efcad6b7SDag-Erling Smørgrav 	{ "put",	I_PUT },
168efcad6b7SDag-Erling Smørgrav 	{ "mput",	I_PUT },
169efcad6b7SDag-Erling Smørgrav 	{ "pwd",	I_PWD },
170efcad6b7SDag-Erling Smørgrav 	{ "quit",	I_QUIT },
171efcad6b7SDag-Erling Smørgrav 	{ "rename",	I_RENAME },
172efcad6b7SDag-Erling Smørgrav 	{ "rm",		I_RM },
173efcad6b7SDag-Erling Smørgrav 	{ "rmdir",	I_RMDIR },
174efcad6b7SDag-Erling Smørgrav 	{ "symlink",	I_SYMLINK },
175efcad6b7SDag-Erling Smørgrav 	{ "version",	I_VERSION },
176efcad6b7SDag-Erling Smørgrav 	{ "!",		I_SHELL },
177efcad6b7SDag-Erling Smørgrav 	{ "?",		I_HELP },
178efcad6b7SDag-Erling Smørgrav 	{ NULL,			-1}
179efcad6b7SDag-Erling Smørgrav };
180efcad6b7SDag-Erling Smørgrav 
181efcad6b7SDag-Erling Smørgrav int interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
182efcad6b7SDag-Erling Smørgrav 
183d4af9e69SDag-Erling Smørgrav /* ARGSUSED */
184efcad6b7SDag-Erling Smørgrav static void
185d74d50a8SDag-Erling Smørgrav killchild(int signo)
186d74d50a8SDag-Erling Smørgrav {
1875e8dbd04SDag-Erling Smørgrav 	if (sshpid > 1) {
188d74d50a8SDag-Erling Smørgrav 		kill(sshpid, SIGTERM);
1895e8dbd04SDag-Erling Smørgrav 		waitpid(sshpid, NULL, 0);
1905e8dbd04SDag-Erling Smørgrav 	}
191d74d50a8SDag-Erling Smørgrav 
192d74d50a8SDag-Erling Smørgrav 	_exit(1);
193d74d50a8SDag-Erling Smørgrav }
194d74d50a8SDag-Erling Smørgrav 
195d4af9e69SDag-Erling Smørgrav /* ARGSUSED */
196d74d50a8SDag-Erling Smørgrav static void
197d74d50a8SDag-Erling Smørgrav cmd_interrupt(int signo)
198d74d50a8SDag-Erling Smørgrav {
199d74d50a8SDag-Erling Smørgrav 	const char msg[] = "\rInterrupt  \n";
2005e8dbd04SDag-Erling Smørgrav 	int olderrno = errno;
201d74d50a8SDag-Erling Smørgrav 
202d74d50a8SDag-Erling Smørgrav 	write(STDERR_FILENO, msg, sizeof(msg) - 1);
203d74d50a8SDag-Erling Smørgrav 	interrupted = 1;
2045e8dbd04SDag-Erling Smørgrav 	errno = olderrno;
205d74d50a8SDag-Erling Smørgrav }
206d74d50a8SDag-Erling Smørgrav 
207d74d50a8SDag-Erling Smørgrav static void
208efcad6b7SDag-Erling Smørgrav help(void)
209efcad6b7SDag-Erling Smørgrav {
210cce7d346SDag-Erling Smørgrav 	printf("Available commands:\n"
211cce7d346SDag-Erling Smørgrav 	    "bye                                Quit sftp\n"
212cce7d346SDag-Erling Smørgrav 	    "cd path                            Change remote directory to 'path'\n"
213cce7d346SDag-Erling Smørgrav 	    "chgrp grp path                     Change group of file 'path' to 'grp'\n"
214cce7d346SDag-Erling Smørgrav 	    "chmod mode path                    Change permissions of file 'path' to 'mode'\n"
215cce7d346SDag-Erling Smørgrav 	    "chown own path                     Change owner of file 'path' to 'own'\n"
216cce7d346SDag-Erling Smørgrav 	    "df [-hi] [path]                    Display statistics for current directory or\n"
217cce7d346SDag-Erling Smørgrav 	    "                                   filesystem containing 'path'\n"
218cce7d346SDag-Erling Smørgrav 	    "exit                               Quit sftp\n"
219cce7d346SDag-Erling Smørgrav 	    "get [-P] remote-path [local-path]  Download file\n"
220cce7d346SDag-Erling Smørgrav 	    "help                               Display this help text\n"
221cce7d346SDag-Erling Smørgrav 	    "lcd path                           Change local directory to 'path'\n"
222cce7d346SDag-Erling Smørgrav 	    "lls [ls-options [path]]            Display local directory listing\n"
223cce7d346SDag-Erling Smørgrav 	    "lmkdir path                        Create local directory\n"
224cce7d346SDag-Erling Smørgrav 	    "ln oldpath newpath                 Symlink remote file\n"
225cce7d346SDag-Erling Smørgrav 	    "lpwd                               Print local working directory\n"
226cce7d346SDag-Erling Smørgrav 	    "ls [-1aflnrSt] [path]              Display remote directory listing\n"
227cce7d346SDag-Erling Smørgrav 	    "lumask umask                       Set local umask to 'umask'\n"
228cce7d346SDag-Erling Smørgrav 	    "mkdir path                         Create remote directory\n"
229cce7d346SDag-Erling Smørgrav 	    "progress                           Toggle display of progress meter\n"
230cce7d346SDag-Erling Smørgrav 	    "put [-P] local-path [remote-path]  Upload file\n"
231cce7d346SDag-Erling Smørgrav 	    "pwd                                Display remote working directory\n"
232cce7d346SDag-Erling Smørgrav 	    "quit                               Quit sftp\n"
233cce7d346SDag-Erling Smørgrav 	    "rename oldpath newpath             Rename remote file\n"
234cce7d346SDag-Erling Smørgrav 	    "rm path                            Delete remote file\n"
235cce7d346SDag-Erling Smørgrav 	    "rmdir path                         Remove remote directory\n"
236cce7d346SDag-Erling Smørgrav 	    "symlink oldpath newpath            Symlink remote file\n"
237cce7d346SDag-Erling Smørgrav 	    "version                            Show SFTP version\n"
238cce7d346SDag-Erling Smørgrav 	    "!command                           Execute 'command' in local shell\n"
239cce7d346SDag-Erling Smørgrav 	    "!                                  Escape to local shell\n"
240cce7d346SDag-Erling Smørgrav 	    "?                                  Synonym for help\n");
241efcad6b7SDag-Erling Smørgrav }
242efcad6b7SDag-Erling Smørgrav 
243efcad6b7SDag-Erling Smørgrav static void
244efcad6b7SDag-Erling Smørgrav local_do_shell(const char *args)
245efcad6b7SDag-Erling Smørgrav {
246efcad6b7SDag-Erling Smørgrav 	int status;
247efcad6b7SDag-Erling Smørgrav 	char *shell;
248efcad6b7SDag-Erling Smørgrav 	pid_t pid;
249efcad6b7SDag-Erling Smørgrav 
250efcad6b7SDag-Erling Smørgrav 	if (!*args)
251efcad6b7SDag-Erling Smørgrav 		args = NULL;
252efcad6b7SDag-Erling Smørgrav 
253efcad6b7SDag-Erling Smørgrav 	if ((shell = getenv("SHELL")) == NULL)
254efcad6b7SDag-Erling Smørgrav 		shell = _PATH_BSHELL;
255efcad6b7SDag-Erling Smørgrav 
256efcad6b7SDag-Erling Smørgrav 	if ((pid = fork()) == -1)
257efcad6b7SDag-Erling Smørgrav 		fatal("Couldn't fork: %s", strerror(errno));
258efcad6b7SDag-Erling Smørgrav 
259efcad6b7SDag-Erling Smørgrav 	if (pid == 0) {
260efcad6b7SDag-Erling Smørgrav 		/* XXX: child has pipe fds to ssh subproc open - issue? */
261efcad6b7SDag-Erling Smørgrav 		if (args) {
262efcad6b7SDag-Erling Smørgrav 			debug3("Executing %s -c \"%s\"", shell, args);
263efcad6b7SDag-Erling Smørgrav 			execl(shell, shell, "-c", args, (char *)NULL);
264efcad6b7SDag-Erling Smørgrav 		} else {
265efcad6b7SDag-Erling Smørgrav 			debug3("Executing %s", shell);
266efcad6b7SDag-Erling Smørgrav 			execl(shell, shell, (char *)NULL);
267efcad6b7SDag-Erling Smørgrav 		}
268efcad6b7SDag-Erling Smørgrav 		fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
269efcad6b7SDag-Erling Smørgrav 		    strerror(errno));
270efcad6b7SDag-Erling Smørgrav 		_exit(1);
271efcad6b7SDag-Erling Smørgrav 	}
272efcad6b7SDag-Erling Smørgrav 	while (waitpid(pid, &status, 0) == -1)
273efcad6b7SDag-Erling Smørgrav 		if (errno != EINTR)
274efcad6b7SDag-Erling Smørgrav 			fatal("Couldn't wait for child: %s", strerror(errno));
275efcad6b7SDag-Erling Smørgrav 	if (!WIFEXITED(status))
276761efaa7SDag-Erling Smørgrav 		error("Shell exited abnormally");
277efcad6b7SDag-Erling Smørgrav 	else if (WEXITSTATUS(status))
278efcad6b7SDag-Erling Smørgrav 		error("Shell exited with status %d", WEXITSTATUS(status));
279efcad6b7SDag-Erling Smørgrav }
280efcad6b7SDag-Erling Smørgrav 
281efcad6b7SDag-Erling Smørgrav static void
282efcad6b7SDag-Erling Smørgrav local_do_ls(const char *args)
283efcad6b7SDag-Erling Smørgrav {
284efcad6b7SDag-Erling Smørgrav 	if (!args || !*args)
285efcad6b7SDag-Erling Smørgrav 		local_do_shell(_PATH_LS);
286efcad6b7SDag-Erling Smørgrav 	else {
287efcad6b7SDag-Erling Smørgrav 		int len = strlen(_PATH_LS " ") + strlen(args) + 1;
288efcad6b7SDag-Erling Smørgrav 		char *buf = xmalloc(len);
289efcad6b7SDag-Erling Smørgrav 
290efcad6b7SDag-Erling Smørgrav 		/* XXX: quoting - rip quoting code from ftp? */
291efcad6b7SDag-Erling Smørgrav 		snprintf(buf, len, _PATH_LS " %s", args);
292efcad6b7SDag-Erling Smørgrav 		local_do_shell(buf);
293efcad6b7SDag-Erling Smørgrav 		xfree(buf);
294efcad6b7SDag-Erling Smørgrav 	}
295efcad6b7SDag-Erling Smørgrav }
296efcad6b7SDag-Erling Smørgrav 
297efcad6b7SDag-Erling Smørgrav /* Strip one path (usually the pwd) from the start of another */
298efcad6b7SDag-Erling Smørgrav static char *
299efcad6b7SDag-Erling Smørgrav path_strip(char *path, char *strip)
300efcad6b7SDag-Erling Smørgrav {
301efcad6b7SDag-Erling Smørgrav 	size_t len;
302efcad6b7SDag-Erling Smørgrav 
303efcad6b7SDag-Erling Smørgrav 	if (strip == NULL)
304efcad6b7SDag-Erling Smørgrav 		return (xstrdup(path));
305efcad6b7SDag-Erling Smørgrav 
306efcad6b7SDag-Erling Smørgrav 	len = strlen(strip);
3075e8dbd04SDag-Erling Smørgrav 	if (strncmp(path, strip, len) == 0) {
308efcad6b7SDag-Erling Smørgrav 		if (strip[len - 1] != '/' && path[len] == '/')
309efcad6b7SDag-Erling Smørgrav 			len++;
310efcad6b7SDag-Erling Smørgrav 		return (xstrdup(path + len));
311efcad6b7SDag-Erling Smørgrav 	}
312efcad6b7SDag-Erling Smørgrav 
313efcad6b7SDag-Erling Smørgrav 	return (xstrdup(path));
314efcad6b7SDag-Erling Smørgrav }
315efcad6b7SDag-Erling Smørgrav 
316efcad6b7SDag-Erling Smørgrav static char *
317efcad6b7SDag-Erling Smørgrav path_append(char *p1, char *p2)
318efcad6b7SDag-Erling Smørgrav {
319efcad6b7SDag-Erling Smørgrav 	char *ret;
320d4af9e69SDag-Erling Smørgrav 	size_t len = strlen(p1) + strlen(p2) + 2;
321efcad6b7SDag-Erling Smørgrav 
322efcad6b7SDag-Erling Smørgrav 	ret = xmalloc(len);
323efcad6b7SDag-Erling Smørgrav 	strlcpy(ret, p1, len);
324d4af9e69SDag-Erling Smørgrav 	if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
325efcad6b7SDag-Erling Smørgrav 		strlcat(ret, "/", len);
326efcad6b7SDag-Erling Smørgrav 	strlcat(ret, p2, len);
327efcad6b7SDag-Erling Smørgrav 
328efcad6b7SDag-Erling Smørgrav 	return(ret);
329efcad6b7SDag-Erling Smørgrav }
330efcad6b7SDag-Erling Smørgrav 
331efcad6b7SDag-Erling Smørgrav static char *
332efcad6b7SDag-Erling Smørgrav make_absolute(char *p, char *pwd)
333efcad6b7SDag-Erling Smørgrav {
334d74d50a8SDag-Erling Smørgrav 	char *abs_str;
335efcad6b7SDag-Erling Smørgrav 
336efcad6b7SDag-Erling Smørgrav 	/* Derelativise */
337efcad6b7SDag-Erling Smørgrav 	if (p && p[0] != '/') {
338d74d50a8SDag-Erling Smørgrav 		abs_str = path_append(pwd, p);
339efcad6b7SDag-Erling Smørgrav 		xfree(p);
340d74d50a8SDag-Erling Smørgrav 		return(abs_str);
341efcad6b7SDag-Erling Smørgrav 	} else
342efcad6b7SDag-Erling Smørgrav 		return(p);
343efcad6b7SDag-Erling Smørgrav }
344efcad6b7SDag-Erling Smørgrav 
345efcad6b7SDag-Erling Smørgrav static int
346efcad6b7SDag-Erling Smørgrav infer_path(const char *p, char **ifp)
347efcad6b7SDag-Erling Smørgrav {
348efcad6b7SDag-Erling Smørgrav 	char *cp;
349efcad6b7SDag-Erling Smørgrav 
350efcad6b7SDag-Erling Smørgrav 	cp = strrchr(p, '/');
351efcad6b7SDag-Erling Smørgrav 	if (cp == NULL) {
352efcad6b7SDag-Erling Smørgrav 		*ifp = xstrdup(p);
353efcad6b7SDag-Erling Smørgrav 		return(0);
354efcad6b7SDag-Erling Smørgrav 	}
355efcad6b7SDag-Erling Smørgrav 
356efcad6b7SDag-Erling Smørgrav 	if (!cp[1]) {
357efcad6b7SDag-Erling Smørgrav 		error("Invalid path");
358efcad6b7SDag-Erling Smørgrav 		return(-1);
359efcad6b7SDag-Erling Smørgrav 	}
360efcad6b7SDag-Erling Smørgrav 
361efcad6b7SDag-Erling Smørgrav 	*ifp = xstrdup(cp + 1);
362efcad6b7SDag-Erling Smørgrav 	return(0);
363efcad6b7SDag-Erling Smørgrav }
364efcad6b7SDag-Erling Smørgrav 
365efcad6b7SDag-Erling Smørgrav static int
366d4af9e69SDag-Erling Smørgrav parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag)
367efcad6b7SDag-Erling Smørgrav {
368d4af9e69SDag-Erling Smørgrav 	extern int opterr, optind, optopt, optreset;
369d4af9e69SDag-Erling Smørgrav 	int ch;
370efcad6b7SDag-Erling Smørgrav 
371d4af9e69SDag-Erling Smørgrav 	optind = optreset = 1;
372d4af9e69SDag-Erling Smørgrav 	opterr = 0;
373d4af9e69SDag-Erling Smørgrav 
374d4af9e69SDag-Erling Smørgrav 	*pflag = 0;
375d4af9e69SDag-Erling Smørgrav 	while ((ch = getopt(argc, argv, "Pp")) != -1) {
376d4af9e69SDag-Erling Smørgrav 		switch (ch) {
377efcad6b7SDag-Erling Smørgrav 		case 'p':
378efcad6b7SDag-Erling Smørgrav 		case 'P':
379efcad6b7SDag-Erling Smørgrav 			*pflag = 1;
380efcad6b7SDag-Erling Smørgrav 			break;
381efcad6b7SDag-Erling Smørgrav 		default:
382d4af9e69SDag-Erling Smørgrav 			error("%s: Invalid flag -%c", cmd, optopt);
383d4af9e69SDag-Erling Smørgrav 			return -1;
384efcad6b7SDag-Erling Smørgrav 		}
385efcad6b7SDag-Erling Smørgrav 	}
386efcad6b7SDag-Erling Smørgrav 
387d4af9e69SDag-Erling Smørgrav 	return optind;
388efcad6b7SDag-Erling Smørgrav }
389efcad6b7SDag-Erling Smørgrav 
390efcad6b7SDag-Erling Smørgrav static int
391d4af9e69SDag-Erling Smørgrav parse_ls_flags(char **argv, int argc, int *lflag)
392efcad6b7SDag-Erling Smørgrav {
393d4af9e69SDag-Erling Smørgrav 	extern int opterr, optind, optopt, optreset;
394d4af9e69SDag-Erling Smørgrav 	int ch;
395efcad6b7SDag-Erling Smørgrav 
396d4af9e69SDag-Erling Smørgrav 	optind = optreset = 1;
397d4af9e69SDag-Erling Smørgrav 	opterr = 0;
398d4af9e69SDag-Erling Smørgrav 
399d74d50a8SDag-Erling Smørgrav 	*lflag = LS_NAME_SORT;
400d4af9e69SDag-Erling Smørgrav 	while ((ch = getopt(argc, argv, "1Saflnrt")) != -1) {
401d4af9e69SDag-Erling Smørgrav 		switch (ch) {
402efcad6b7SDag-Erling Smørgrav 		case '1':
403d74d50a8SDag-Erling Smørgrav 			*lflag &= ~VIEW_FLAGS;
404d74d50a8SDag-Erling Smørgrav 			*lflag |= LS_SHORT_VIEW;
405d74d50a8SDag-Erling Smørgrav 			break;
406d74d50a8SDag-Erling Smørgrav 		case 'S':
407d74d50a8SDag-Erling Smørgrav 			*lflag &= ~SORT_FLAGS;
408d74d50a8SDag-Erling Smørgrav 			*lflag |= LS_SIZE_SORT;
409d74d50a8SDag-Erling Smørgrav 			break;
410d4af9e69SDag-Erling Smørgrav 		case 'a':
411d4af9e69SDag-Erling Smørgrav 			*lflag |= LS_SHOW_ALL;
412d74d50a8SDag-Erling Smørgrav 			break;
413d74d50a8SDag-Erling Smørgrav 		case 'f':
414d74d50a8SDag-Erling Smørgrav 			*lflag &= ~SORT_FLAGS;
415d74d50a8SDag-Erling Smørgrav 			break;
416d4af9e69SDag-Erling Smørgrav 		case 'l':
417d4af9e69SDag-Erling Smørgrav 			*lflag &= ~VIEW_FLAGS;
418d4af9e69SDag-Erling Smørgrav 			*lflag |= LS_LONG_VIEW;
419d4af9e69SDag-Erling Smørgrav 			break;
420d4af9e69SDag-Erling Smørgrav 		case 'n':
421d4af9e69SDag-Erling Smørgrav 			*lflag &= ~VIEW_FLAGS;
422d4af9e69SDag-Erling Smørgrav 			*lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
423d4af9e69SDag-Erling Smørgrav 			break;
424d4af9e69SDag-Erling Smørgrav 		case 'r':
425d4af9e69SDag-Erling Smørgrav 			*lflag |= LS_REVERSE_SORT;
426d4af9e69SDag-Erling Smørgrav 			break;
427d4af9e69SDag-Erling Smørgrav 		case 't':
428d4af9e69SDag-Erling Smørgrav 			*lflag &= ~SORT_FLAGS;
429d4af9e69SDag-Erling Smørgrav 			*lflag |= LS_TIME_SORT;
430efcad6b7SDag-Erling Smørgrav 			break;
431efcad6b7SDag-Erling Smørgrav 		default:
432d4af9e69SDag-Erling Smørgrav 			error("ls: Invalid flag -%c", optopt);
433d4af9e69SDag-Erling Smørgrav 			return -1;
434efcad6b7SDag-Erling Smørgrav 		}
435efcad6b7SDag-Erling Smørgrav 	}
436efcad6b7SDag-Erling Smørgrav 
437d4af9e69SDag-Erling Smørgrav 	return optind;
438efcad6b7SDag-Erling Smørgrav }
439efcad6b7SDag-Erling Smørgrav 
440efcad6b7SDag-Erling Smørgrav static int
441d4af9e69SDag-Erling Smørgrav parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
442efcad6b7SDag-Erling Smørgrav {
443d4af9e69SDag-Erling Smørgrav 	extern int opterr, optind, optopt, optreset;
444d4af9e69SDag-Erling Smørgrav 	int ch;
445efcad6b7SDag-Erling Smørgrav 
446d4af9e69SDag-Erling Smørgrav 	optind = optreset = 1;
447d4af9e69SDag-Erling Smørgrav 	opterr = 0;
448efcad6b7SDag-Erling Smørgrav 
449d4af9e69SDag-Erling Smørgrav 	*hflag = *iflag = 0;
450d4af9e69SDag-Erling Smørgrav 	while ((ch = getopt(argc, argv, "hi")) != -1) {
451d4af9e69SDag-Erling Smørgrav 		switch (ch) {
452d4af9e69SDag-Erling Smørgrav 		case 'h':
453d4af9e69SDag-Erling Smørgrav 			*hflag = 1;
454efcad6b7SDag-Erling Smørgrav 			break;
455d4af9e69SDag-Erling Smørgrav 		case 'i':
456d4af9e69SDag-Erling Smørgrav 			*iflag = 1;
457d4af9e69SDag-Erling Smørgrav 			break;
458d4af9e69SDag-Erling Smørgrav 		default:
459d4af9e69SDag-Erling Smørgrav 			error("%s: Invalid flag -%c", cmd, optopt);
460d4af9e69SDag-Erling Smørgrav 			return -1;
461efcad6b7SDag-Erling Smørgrav 		}
462efcad6b7SDag-Erling Smørgrav 	}
463efcad6b7SDag-Erling Smørgrav 
464d4af9e69SDag-Erling Smørgrav 	return optind;
465efcad6b7SDag-Erling Smørgrav }
466efcad6b7SDag-Erling Smørgrav 
467efcad6b7SDag-Erling Smørgrav static int
468efcad6b7SDag-Erling Smørgrav is_dir(char *path)
469efcad6b7SDag-Erling Smørgrav {
470efcad6b7SDag-Erling Smørgrav 	struct stat sb;
471efcad6b7SDag-Erling Smørgrav 
472efcad6b7SDag-Erling Smørgrav 	/* XXX: report errors? */
473efcad6b7SDag-Erling Smørgrav 	if (stat(path, &sb) == -1)
474efcad6b7SDag-Erling Smørgrav 		return(0);
475efcad6b7SDag-Erling Smørgrav 
476761efaa7SDag-Erling Smørgrav 	return(S_ISDIR(sb.st_mode));
477efcad6b7SDag-Erling Smørgrav }
478efcad6b7SDag-Erling Smørgrav 
479efcad6b7SDag-Erling Smørgrav static int
480efcad6b7SDag-Erling Smørgrav remote_is_dir(struct sftp_conn *conn, char *path)
481efcad6b7SDag-Erling Smørgrav {
482efcad6b7SDag-Erling Smørgrav 	Attrib *a;
483efcad6b7SDag-Erling Smørgrav 
484efcad6b7SDag-Erling Smørgrav 	/* XXX: report errors? */
485efcad6b7SDag-Erling Smørgrav 	if ((a = do_stat(conn, path, 1)) == NULL)
486efcad6b7SDag-Erling Smørgrav 		return(0);
487efcad6b7SDag-Erling Smørgrav 	if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
488efcad6b7SDag-Erling Smørgrav 		return(0);
489761efaa7SDag-Erling Smørgrav 	return(S_ISDIR(a->perm));
490efcad6b7SDag-Erling Smørgrav }
491efcad6b7SDag-Erling Smørgrav 
492efcad6b7SDag-Erling Smørgrav static int
493efcad6b7SDag-Erling Smørgrav process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
494efcad6b7SDag-Erling Smørgrav {
495efcad6b7SDag-Erling Smørgrav 	char *abs_src = NULL;
496efcad6b7SDag-Erling Smørgrav 	char *abs_dst = NULL;
497efcad6b7SDag-Erling Smørgrav 	char *tmp;
498efcad6b7SDag-Erling Smørgrav 	glob_t g;
499efcad6b7SDag-Erling Smørgrav 	int err = 0;
500efcad6b7SDag-Erling Smørgrav 	int i;
501efcad6b7SDag-Erling Smørgrav 
502efcad6b7SDag-Erling Smørgrav 	abs_src = xstrdup(src);
503efcad6b7SDag-Erling Smørgrav 	abs_src = make_absolute(abs_src, pwd);
504efcad6b7SDag-Erling Smørgrav 
505efcad6b7SDag-Erling Smørgrav 	memset(&g, 0, sizeof(g));
506efcad6b7SDag-Erling Smørgrav 	debug3("Looking up %s", abs_src);
507efcad6b7SDag-Erling Smørgrav 	if (remote_glob(conn, abs_src, 0, NULL, &g)) {
508efcad6b7SDag-Erling Smørgrav 		error("File \"%s\" not found.", abs_src);
509efcad6b7SDag-Erling Smørgrav 		err = -1;
510efcad6b7SDag-Erling Smørgrav 		goto out;
511efcad6b7SDag-Erling Smørgrav 	}
512efcad6b7SDag-Erling Smørgrav 
513efcad6b7SDag-Erling Smørgrav 	/* If multiple matches, dst must be a directory or unspecified */
514efcad6b7SDag-Erling Smørgrav 	if (g.gl_matchc > 1 && dst && !is_dir(dst)) {
515efcad6b7SDag-Erling Smørgrav 		error("Multiple files match, but \"%s\" is not a directory",
516efcad6b7SDag-Erling Smørgrav 		    dst);
517efcad6b7SDag-Erling Smørgrav 		err = -1;
518efcad6b7SDag-Erling Smørgrav 		goto out;
519efcad6b7SDag-Erling Smørgrav 	}
520efcad6b7SDag-Erling Smørgrav 
521d74d50a8SDag-Erling Smørgrav 	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
522efcad6b7SDag-Erling Smørgrav 		if (infer_path(g.gl_pathv[i], &tmp)) {
523efcad6b7SDag-Erling Smørgrav 			err = -1;
524efcad6b7SDag-Erling Smørgrav 			goto out;
525efcad6b7SDag-Erling Smørgrav 		}
526efcad6b7SDag-Erling Smørgrav 
527efcad6b7SDag-Erling Smørgrav 		if (g.gl_matchc == 1 && dst) {
528efcad6b7SDag-Erling Smørgrav 			/* If directory specified, append filename */
529761efaa7SDag-Erling Smørgrav 			xfree(tmp);
530efcad6b7SDag-Erling Smørgrav 			if (is_dir(dst)) {
531efcad6b7SDag-Erling Smørgrav 				if (infer_path(g.gl_pathv[0], &tmp)) {
532efcad6b7SDag-Erling Smørgrav 					err = 1;
533efcad6b7SDag-Erling Smørgrav 					goto out;
534efcad6b7SDag-Erling Smørgrav 				}
535efcad6b7SDag-Erling Smørgrav 				abs_dst = path_append(dst, tmp);
536efcad6b7SDag-Erling Smørgrav 				xfree(tmp);
537efcad6b7SDag-Erling Smørgrav 			} else
538efcad6b7SDag-Erling Smørgrav 				abs_dst = xstrdup(dst);
539efcad6b7SDag-Erling Smørgrav 		} else if (dst) {
540efcad6b7SDag-Erling Smørgrav 			abs_dst = path_append(dst, tmp);
541efcad6b7SDag-Erling Smørgrav 			xfree(tmp);
542efcad6b7SDag-Erling Smørgrav 		} else
543efcad6b7SDag-Erling Smørgrav 			abs_dst = tmp;
544efcad6b7SDag-Erling Smørgrav 
545efcad6b7SDag-Erling Smørgrav 		printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
546efcad6b7SDag-Erling Smørgrav 		if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
547efcad6b7SDag-Erling Smørgrav 			err = -1;
548efcad6b7SDag-Erling Smørgrav 		xfree(abs_dst);
549efcad6b7SDag-Erling Smørgrav 		abs_dst = NULL;
550efcad6b7SDag-Erling Smørgrav 	}
551efcad6b7SDag-Erling Smørgrav 
552efcad6b7SDag-Erling Smørgrav out:
553efcad6b7SDag-Erling Smørgrav 	xfree(abs_src);
554efcad6b7SDag-Erling Smørgrav 	globfree(&g);
555efcad6b7SDag-Erling Smørgrav 	return(err);
556efcad6b7SDag-Erling Smørgrav }
557efcad6b7SDag-Erling Smørgrav 
558efcad6b7SDag-Erling Smørgrav static int
559efcad6b7SDag-Erling Smørgrav process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
560efcad6b7SDag-Erling Smørgrav {
561efcad6b7SDag-Erling Smørgrav 	char *tmp_dst = NULL;
562efcad6b7SDag-Erling Smørgrav 	char *abs_dst = NULL;
563efcad6b7SDag-Erling Smørgrav 	char *tmp;
564efcad6b7SDag-Erling Smørgrav 	glob_t g;
565efcad6b7SDag-Erling Smørgrav 	int err = 0;
566efcad6b7SDag-Erling Smørgrav 	int i;
567d4af9e69SDag-Erling Smørgrav 	struct stat sb;
568efcad6b7SDag-Erling Smørgrav 
569efcad6b7SDag-Erling Smørgrav 	if (dst) {
570efcad6b7SDag-Erling Smørgrav 		tmp_dst = xstrdup(dst);
571efcad6b7SDag-Erling Smørgrav 		tmp_dst = make_absolute(tmp_dst, pwd);
572efcad6b7SDag-Erling Smørgrav 	}
573efcad6b7SDag-Erling Smørgrav 
574efcad6b7SDag-Erling Smørgrav 	memset(&g, 0, sizeof(g));
575efcad6b7SDag-Erling Smørgrav 	debug3("Looking up %s", src);
576d4af9e69SDag-Erling Smørgrav 	if (glob(src, GLOB_NOCHECK, NULL, &g)) {
577efcad6b7SDag-Erling Smørgrav 		error("File \"%s\" not found.", src);
578efcad6b7SDag-Erling Smørgrav 		err = -1;
579efcad6b7SDag-Erling Smørgrav 		goto out;
580efcad6b7SDag-Erling Smørgrav 	}
581efcad6b7SDag-Erling Smørgrav 
582efcad6b7SDag-Erling Smørgrav 	/* If multiple matches, dst may be directory or unspecified */
583efcad6b7SDag-Erling Smørgrav 	if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) {
584efcad6b7SDag-Erling Smørgrav 		error("Multiple files match, but \"%s\" is not a directory",
585efcad6b7SDag-Erling Smørgrav 		    tmp_dst);
586efcad6b7SDag-Erling Smørgrav 		err = -1;
587efcad6b7SDag-Erling Smørgrav 		goto out;
588efcad6b7SDag-Erling Smørgrav 	}
589efcad6b7SDag-Erling Smørgrav 
590d74d50a8SDag-Erling Smørgrav 	for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
591d4af9e69SDag-Erling Smørgrav 		if (stat(g.gl_pathv[i], &sb) == -1) {
592d4af9e69SDag-Erling Smørgrav 			err = -1;
593d4af9e69SDag-Erling Smørgrav 			error("stat %s: %s", g.gl_pathv[i], strerror(errno));
594d4af9e69SDag-Erling Smørgrav 			continue;
595d4af9e69SDag-Erling Smørgrav 		}
596d4af9e69SDag-Erling Smørgrav 
597d4af9e69SDag-Erling Smørgrav 		if (!S_ISREG(sb.st_mode)) {
598efcad6b7SDag-Erling Smørgrav 			error("skipping non-regular file %s",
599efcad6b7SDag-Erling Smørgrav 			    g.gl_pathv[i]);
600efcad6b7SDag-Erling Smørgrav 			continue;
601efcad6b7SDag-Erling Smørgrav 		}
602efcad6b7SDag-Erling Smørgrav 		if (infer_path(g.gl_pathv[i], &tmp)) {
603efcad6b7SDag-Erling Smørgrav 			err = -1;
604efcad6b7SDag-Erling Smørgrav 			goto out;
605efcad6b7SDag-Erling Smørgrav 		}
606efcad6b7SDag-Erling Smørgrav 
607efcad6b7SDag-Erling Smørgrav 		if (g.gl_matchc == 1 && tmp_dst) {
608efcad6b7SDag-Erling Smørgrav 			/* If directory specified, append filename */
609efcad6b7SDag-Erling Smørgrav 			if (remote_is_dir(conn, tmp_dst)) {
610efcad6b7SDag-Erling Smørgrav 				if (infer_path(g.gl_pathv[0], &tmp)) {
611efcad6b7SDag-Erling Smørgrav 					err = 1;
612efcad6b7SDag-Erling Smørgrav 					goto out;
613efcad6b7SDag-Erling Smørgrav 				}
614efcad6b7SDag-Erling Smørgrav 				abs_dst = path_append(tmp_dst, tmp);
615efcad6b7SDag-Erling Smørgrav 				xfree(tmp);
616efcad6b7SDag-Erling Smørgrav 			} else
617efcad6b7SDag-Erling Smørgrav 				abs_dst = xstrdup(tmp_dst);
618efcad6b7SDag-Erling Smørgrav 
619efcad6b7SDag-Erling Smørgrav 		} else if (tmp_dst) {
620efcad6b7SDag-Erling Smørgrav 			abs_dst = path_append(tmp_dst, tmp);
621efcad6b7SDag-Erling Smørgrav 			xfree(tmp);
622efcad6b7SDag-Erling Smørgrav 		} else
623efcad6b7SDag-Erling Smørgrav 			abs_dst = make_absolute(tmp, pwd);
624efcad6b7SDag-Erling Smørgrav 
625efcad6b7SDag-Erling Smørgrav 		printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
626efcad6b7SDag-Erling Smørgrav 		if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
627efcad6b7SDag-Erling Smørgrav 			err = -1;
628efcad6b7SDag-Erling Smørgrav 	}
629efcad6b7SDag-Erling Smørgrav 
630efcad6b7SDag-Erling Smørgrav out:
631efcad6b7SDag-Erling Smørgrav 	if (abs_dst)
632efcad6b7SDag-Erling Smørgrav 		xfree(abs_dst);
633efcad6b7SDag-Erling Smørgrav 	if (tmp_dst)
634efcad6b7SDag-Erling Smørgrav 		xfree(tmp_dst);
635efcad6b7SDag-Erling Smørgrav 	globfree(&g);
636efcad6b7SDag-Erling Smørgrav 	return(err);
637efcad6b7SDag-Erling Smørgrav }
638efcad6b7SDag-Erling Smørgrav 
639efcad6b7SDag-Erling Smørgrav static int
640efcad6b7SDag-Erling Smørgrav sdirent_comp(const void *aa, const void *bb)
641efcad6b7SDag-Erling Smørgrav {
642efcad6b7SDag-Erling Smørgrav 	SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
643efcad6b7SDag-Erling Smørgrav 	SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
644d74d50a8SDag-Erling Smørgrav 	int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
645efcad6b7SDag-Erling Smørgrav 
646d74d50a8SDag-Erling Smørgrav #define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
647d74d50a8SDag-Erling Smørgrav 	if (sort_flag & LS_NAME_SORT)
648d74d50a8SDag-Erling Smørgrav 		return (rmul * strcmp(a->filename, b->filename));
649d74d50a8SDag-Erling Smørgrav 	else if (sort_flag & LS_TIME_SORT)
650d74d50a8SDag-Erling Smørgrav 		return (rmul * NCMP(a->a.mtime, b->a.mtime));
651d74d50a8SDag-Erling Smørgrav 	else if (sort_flag & LS_SIZE_SORT)
652d74d50a8SDag-Erling Smørgrav 		return (rmul * NCMP(a->a.size, b->a.size));
653d74d50a8SDag-Erling Smørgrav 
654d74d50a8SDag-Erling Smørgrav 	fatal("Unknown ls sort type");
655efcad6b7SDag-Erling Smørgrav }
656efcad6b7SDag-Erling Smørgrav 
657efcad6b7SDag-Erling Smørgrav /* sftp ls.1 replacement for directories */
658efcad6b7SDag-Erling Smørgrav static int
659efcad6b7SDag-Erling Smørgrav do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
660efcad6b7SDag-Erling Smørgrav {
661043840dfSDag-Erling Smørgrav 	int n;
662043840dfSDag-Erling Smørgrav 	u_int c = 1, colspace = 0, columns = 1;
663efcad6b7SDag-Erling Smørgrav 	SFTP_DIRENT **d;
664efcad6b7SDag-Erling Smørgrav 
665efcad6b7SDag-Erling Smørgrav 	if ((n = do_readdir(conn, path, &d)) != 0)
666efcad6b7SDag-Erling Smørgrav 		return (n);
667efcad6b7SDag-Erling Smørgrav 
668d74d50a8SDag-Erling Smørgrav 	if (!(lflag & LS_SHORT_VIEW)) {
669043840dfSDag-Erling Smørgrav 		u_int m = 0, width = 80;
670efcad6b7SDag-Erling Smørgrav 		struct winsize ws;
671efcad6b7SDag-Erling Smørgrav 		char *tmp;
672efcad6b7SDag-Erling Smørgrav 
673efcad6b7SDag-Erling Smørgrav 		/* Count entries for sort and find longest filename */
674d74d50a8SDag-Erling Smørgrav 		for (n = 0; d[n] != NULL; n++) {
675d74d50a8SDag-Erling Smørgrav 			if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
676efcad6b7SDag-Erling Smørgrav 				m = MAX(m, strlen(d[n]->filename));
677d74d50a8SDag-Erling Smørgrav 		}
678efcad6b7SDag-Erling Smørgrav 
679efcad6b7SDag-Erling Smørgrav 		/* Add any subpath that also needs to be counted */
680efcad6b7SDag-Erling Smørgrav 		tmp = path_strip(path, strip_path);
681efcad6b7SDag-Erling Smørgrav 		m += strlen(tmp);
682efcad6b7SDag-Erling Smørgrav 		xfree(tmp);
683efcad6b7SDag-Erling Smørgrav 
684efcad6b7SDag-Erling Smørgrav 		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
685efcad6b7SDag-Erling Smørgrav 			width = ws.ws_col;
686efcad6b7SDag-Erling Smørgrav 
687efcad6b7SDag-Erling Smørgrav 		columns = width / (m + 2);
688efcad6b7SDag-Erling Smørgrav 		columns = MAX(columns, 1);
689efcad6b7SDag-Erling Smørgrav 		colspace = width / columns;
690efcad6b7SDag-Erling Smørgrav 		colspace = MIN(colspace, width);
691efcad6b7SDag-Erling Smørgrav 	}
692efcad6b7SDag-Erling Smørgrav 
693d74d50a8SDag-Erling Smørgrav 	if (lflag & SORT_FLAGS) {
694021d409fSDag-Erling Smørgrav 		for (n = 0; d[n] != NULL; n++)
695021d409fSDag-Erling Smørgrav 			;	/* count entries */
696d74d50a8SDag-Erling Smørgrav 		sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
697efcad6b7SDag-Erling Smørgrav 		qsort(d, n, sizeof(*d), sdirent_comp);
698d74d50a8SDag-Erling Smørgrav 	}
699efcad6b7SDag-Erling Smørgrav 
700d74d50a8SDag-Erling Smørgrav 	for (n = 0; d[n] != NULL && !interrupted; n++) {
701efcad6b7SDag-Erling Smørgrav 		char *tmp, *fname;
702efcad6b7SDag-Erling Smørgrav 
703d74d50a8SDag-Erling Smørgrav 		if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
704d74d50a8SDag-Erling Smørgrav 			continue;
705d74d50a8SDag-Erling Smørgrav 
706efcad6b7SDag-Erling Smørgrav 		tmp = path_append(path, d[n]->filename);
707efcad6b7SDag-Erling Smørgrav 		fname = path_strip(tmp, strip_path);
708efcad6b7SDag-Erling Smørgrav 		xfree(tmp);
709efcad6b7SDag-Erling Smørgrav 
710d74d50a8SDag-Erling Smørgrav 		if (lflag & LS_LONG_VIEW) {
711d74d50a8SDag-Erling Smørgrav 			if (lflag & LS_NUMERIC_VIEW) {
712efcad6b7SDag-Erling Smørgrav 				char *lname;
713efcad6b7SDag-Erling Smørgrav 				struct stat sb;
714efcad6b7SDag-Erling Smørgrav 
715efcad6b7SDag-Erling Smørgrav 				memset(&sb, 0, sizeof(sb));
716efcad6b7SDag-Erling Smørgrav 				attrib_to_stat(&d[n]->a, &sb);
717efcad6b7SDag-Erling Smørgrav 				lname = ls_file(fname, &sb, 1);
718efcad6b7SDag-Erling Smørgrav 				printf("%s\n", lname);
719efcad6b7SDag-Erling Smørgrav 				xfree(lname);
720d74d50a8SDag-Erling Smørgrav 			} else
721d74d50a8SDag-Erling Smørgrav 				printf("%s\n", d[n]->longname);
722efcad6b7SDag-Erling Smørgrav 		} else {
723efcad6b7SDag-Erling Smørgrav 			printf("%-*s", colspace, fname);
724efcad6b7SDag-Erling Smørgrav 			if (c >= columns) {
725efcad6b7SDag-Erling Smørgrav 				printf("\n");
726efcad6b7SDag-Erling Smørgrav 				c = 1;
727efcad6b7SDag-Erling Smørgrav 			} else
728efcad6b7SDag-Erling Smørgrav 				c++;
729efcad6b7SDag-Erling Smørgrav 		}
730efcad6b7SDag-Erling Smørgrav 
731efcad6b7SDag-Erling Smørgrav 		xfree(fname);
732efcad6b7SDag-Erling Smørgrav 	}
733efcad6b7SDag-Erling Smørgrav 
734d74d50a8SDag-Erling Smørgrav 	if (!(lflag & LS_LONG_VIEW) && (c != 1))
735efcad6b7SDag-Erling Smørgrav 		printf("\n");
736efcad6b7SDag-Erling Smørgrav 
737efcad6b7SDag-Erling Smørgrav 	free_sftp_dirents(d);
738efcad6b7SDag-Erling Smørgrav 	return (0);
739efcad6b7SDag-Erling Smørgrav }
740efcad6b7SDag-Erling Smørgrav 
741efcad6b7SDag-Erling Smørgrav /* sftp ls.1 replacement which handles path globs */
742efcad6b7SDag-Erling Smørgrav static int
743efcad6b7SDag-Erling Smørgrav do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
744efcad6b7SDag-Erling Smørgrav     int lflag)
745efcad6b7SDag-Erling Smørgrav {
746efcad6b7SDag-Erling Smørgrav 	glob_t g;
747043840dfSDag-Erling Smørgrav 	u_int i, c = 1, colspace = 0, columns = 1;
7485e8dbd04SDag-Erling Smørgrav 	Attrib *a = NULL;
749efcad6b7SDag-Erling Smørgrav 
750efcad6b7SDag-Erling Smørgrav 	memset(&g, 0, sizeof(g));
751efcad6b7SDag-Erling Smørgrav 
752efcad6b7SDag-Erling Smørgrav 	if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
7535e8dbd04SDag-Erling Smørgrav 	    NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
7545e8dbd04SDag-Erling Smørgrav 		if (g.gl_pathc)
7555e8dbd04SDag-Erling Smørgrav 			globfree(&g);
756efcad6b7SDag-Erling Smørgrav 		error("Can't ls: \"%s\" not found", path);
757efcad6b7SDag-Erling Smørgrav 		return (-1);
758efcad6b7SDag-Erling Smørgrav 	}
759efcad6b7SDag-Erling Smørgrav 
760d74d50a8SDag-Erling Smørgrav 	if (interrupted)
761d74d50a8SDag-Erling Smørgrav 		goto out;
762d74d50a8SDag-Erling Smørgrav 
763efcad6b7SDag-Erling Smørgrav 	/*
7645e8dbd04SDag-Erling Smørgrav 	 * If the glob returns a single match and it is a directory,
7655e8dbd04SDag-Erling Smørgrav 	 * then just list its contents.
766efcad6b7SDag-Erling Smørgrav 	 */
7675e8dbd04SDag-Erling Smørgrav 	if (g.gl_matchc == 1) {
7685e8dbd04SDag-Erling Smørgrav 		if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
769efcad6b7SDag-Erling Smørgrav 			globfree(&g);
770efcad6b7SDag-Erling Smørgrav 			return (-1);
771efcad6b7SDag-Erling Smørgrav 		}
772efcad6b7SDag-Erling Smørgrav 		if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
773efcad6b7SDag-Erling Smørgrav 		    S_ISDIR(a->perm)) {
7745e8dbd04SDag-Erling Smørgrav 			int err;
7755e8dbd04SDag-Erling Smørgrav 
7765e8dbd04SDag-Erling Smørgrav 			err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
777efcad6b7SDag-Erling Smørgrav 			globfree(&g);
7785e8dbd04SDag-Erling Smørgrav 			return (err);
779efcad6b7SDag-Erling Smørgrav 		}
780efcad6b7SDag-Erling Smørgrav 	}
781efcad6b7SDag-Erling Smørgrav 
782d74d50a8SDag-Erling Smørgrav 	if (!(lflag & LS_SHORT_VIEW)) {
783043840dfSDag-Erling Smørgrav 		u_int m = 0, width = 80;
784efcad6b7SDag-Erling Smørgrav 		struct winsize ws;
785efcad6b7SDag-Erling Smørgrav 
786efcad6b7SDag-Erling Smørgrav 		/* Count entries for sort and find longest filename */
787efcad6b7SDag-Erling Smørgrav 		for (i = 0; g.gl_pathv[i]; i++)
788efcad6b7SDag-Erling Smørgrav 			m = MAX(m, strlen(g.gl_pathv[i]));
789efcad6b7SDag-Erling Smørgrav 
790efcad6b7SDag-Erling Smørgrav 		if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
791efcad6b7SDag-Erling Smørgrav 			width = ws.ws_col;
792efcad6b7SDag-Erling Smørgrav 
793efcad6b7SDag-Erling Smørgrav 		columns = width / (m + 2);
794efcad6b7SDag-Erling Smørgrav 		columns = MAX(columns, 1);
795efcad6b7SDag-Erling Smørgrav 		colspace = width / columns;
796efcad6b7SDag-Erling Smørgrav 	}
797efcad6b7SDag-Erling Smørgrav 
7985e8dbd04SDag-Erling Smørgrav 	for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
799efcad6b7SDag-Erling Smørgrav 		char *fname;
800efcad6b7SDag-Erling Smørgrav 
801efcad6b7SDag-Erling Smørgrav 		fname = path_strip(g.gl_pathv[i], strip_path);
802efcad6b7SDag-Erling Smørgrav 
803d74d50a8SDag-Erling Smørgrav 		if (lflag & LS_LONG_VIEW) {
804efcad6b7SDag-Erling Smørgrav 			char *lname;
805efcad6b7SDag-Erling Smørgrav 			struct stat sb;
806efcad6b7SDag-Erling Smørgrav 
807efcad6b7SDag-Erling Smørgrav 			/*
808efcad6b7SDag-Erling Smørgrav 			 * XXX: this is slow - 1 roundtrip per path
809efcad6b7SDag-Erling Smørgrav 			 * A solution to this is to fork glob() and
810efcad6b7SDag-Erling Smørgrav 			 * build a sftp specific version which keeps the
811efcad6b7SDag-Erling Smørgrav 			 * attribs (which currently get thrown away)
812efcad6b7SDag-Erling Smørgrav 			 * that the server returns as well as the filenames.
813efcad6b7SDag-Erling Smørgrav 			 */
814efcad6b7SDag-Erling Smørgrav 			memset(&sb, 0, sizeof(sb));
8155e8dbd04SDag-Erling Smørgrav 			if (a == NULL)
816efcad6b7SDag-Erling Smørgrav 				a = do_lstat(conn, g.gl_pathv[i], 1);
817efcad6b7SDag-Erling Smørgrav 			if (a != NULL)
818efcad6b7SDag-Erling Smørgrav 				attrib_to_stat(a, &sb);
819efcad6b7SDag-Erling Smørgrav 			lname = ls_file(fname, &sb, 1);
820efcad6b7SDag-Erling Smørgrav 			printf("%s\n", lname);
821efcad6b7SDag-Erling Smørgrav 			xfree(lname);
822efcad6b7SDag-Erling Smørgrav 		} else {
823efcad6b7SDag-Erling Smørgrav 			printf("%-*s", colspace, fname);
824efcad6b7SDag-Erling Smørgrav 			if (c >= columns) {
825efcad6b7SDag-Erling Smørgrav 				printf("\n");
826efcad6b7SDag-Erling Smørgrav 				c = 1;
827efcad6b7SDag-Erling Smørgrav 			} else
828efcad6b7SDag-Erling Smørgrav 				c++;
829efcad6b7SDag-Erling Smørgrav 		}
830efcad6b7SDag-Erling Smørgrav 		xfree(fname);
831efcad6b7SDag-Erling Smørgrav 	}
832efcad6b7SDag-Erling Smørgrav 
833d74d50a8SDag-Erling Smørgrav 	if (!(lflag & LS_LONG_VIEW) && (c != 1))
834efcad6b7SDag-Erling Smørgrav 		printf("\n");
835efcad6b7SDag-Erling Smørgrav 
836d74d50a8SDag-Erling Smørgrav  out:
837efcad6b7SDag-Erling Smørgrav 	if (g.gl_pathc)
838efcad6b7SDag-Erling Smørgrav 		globfree(&g);
839efcad6b7SDag-Erling Smørgrav 
840efcad6b7SDag-Erling Smørgrav 	return (0);
841efcad6b7SDag-Erling Smørgrav }
842efcad6b7SDag-Erling Smørgrav 
843efcad6b7SDag-Erling Smørgrav static int
844d4af9e69SDag-Erling Smørgrav do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
845d4af9e69SDag-Erling Smørgrav {
846d4af9e69SDag-Erling Smørgrav 	struct sftp_statvfs st;
847d4af9e69SDag-Erling Smørgrav 	char s_used[FMT_SCALED_STRSIZE];
848d4af9e69SDag-Erling Smørgrav 	char s_avail[FMT_SCALED_STRSIZE];
849d4af9e69SDag-Erling Smørgrav 	char s_root[FMT_SCALED_STRSIZE];
850d4af9e69SDag-Erling Smørgrav 	char s_total[FMT_SCALED_STRSIZE];
851d4af9e69SDag-Erling Smørgrav 
852d4af9e69SDag-Erling Smørgrav 	if (do_statvfs(conn, path, &st, 1) == -1)
853d4af9e69SDag-Erling Smørgrav 		return -1;
854d4af9e69SDag-Erling Smørgrav 	if (iflag) {
855d4af9e69SDag-Erling Smørgrav 		printf("     Inodes        Used       Avail      "
856d4af9e69SDag-Erling Smørgrav 		    "(root)    %%Capacity\n");
857d4af9e69SDag-Erling Smørgrav 		printf("%11llu %11llu %11llu %11llu         %3llu%%\n",
858d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)st.f_files,
859d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)(st.f_files - st.f_ffree),
860d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)st.f_favail,
861d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)st.f_ffree,
862d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)(100 * (st.f_files - st.f_ffree) /
863d4af9e69SDag-Erling Smørgrav 		    st.f_files));
864d4af9e69SDag-Erling Smørgrav 	} else if (hflag) {
865d4af9e69SDag-Erling Smørgrav 		strlcpy(s_used, "error", sizeof(s_used));
866d4af9e69SDag-Erling Smørgrav 		strlcpy(s_avail, "error", sizeof(s_avail));
867d4af9e69SDag-Erling Smørgrav 		strlcpy(s_root, "error", sizeof(s_root));
868d4af9e69SDag-Erling Smørgrav 		strlcpy(s_total, "error", sizeof(s_total));
869d4af9e69SDag-Erling Smørgrav 		fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
870d4af9e69SDag-Erling Smørgrav 		fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
871d4af9e69SDag-Erling Smørgrav 		fmt_scaled(st.f_bfree * st.f_frsize, s_root);
872d4af9e69SDag-Erling Smørgrav 		fmt_scaled(st.f_blocks * st.f_frsize, s_total);
873d4af9e69SDag-Erling Smørgrav 		printf("    Size     Used    Avail   (root)    %%Capacity\n");
874d4af9e69SDag-Erling Smørgrav 		printf("%7sB %7sB %7sB %7sB         %3llu%%\n",
875d4af9e69SDag-Erling Smørgrav 		    s_total, s_used, s_avail, s_root,
876d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
877d4af9e69SDag-Erling Smørgrav 		    st.f_blocks));
878d4af9e69SDag-Erling Smørgrav 	} else {
879d4af9e69SDag-Erling Smørgrav 		printf("        Size         Used        Avail       "
880d4af9e69SDag-Erling Smørgrav 		    "(root)    %%Capacity\n");
881d4af9e69SDag-Erling Smørgrav 		printf("%12llu %12llu %12llu %12llu         %3llu%%\n",
882d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
883d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)(st.f_frsize *
884d4af9e69SDag-Erling Smørgrav 		    (st.f_blocks - st.f_bfree) / 1024),
885d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
886d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
887d4af9e69SDag-Erling Smørgrav 		    (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
888d4af9e69SDag-Erling Smørgrav 		    st.f_blocks));
889d4af9e69SDag-Erling Smørgrav 	}
890d4af9e69SDag-Erling Smørgrav 	return 0;
891d4af9e69SDag-Erling Smørgrav }
892d4af9e69SDag-Erling Smørgrav 
893d4af9e69SDag-Erling Smørgrav /*
894d4af9e69SDag-Erling Smørgrav  * Undo escaping of glob sequences in place. Used to undo extra escaping
895d4af9e69SDag-Erling Smørgrav  * applied in makeargv() when the string is destined for a function that
896d4af9e69SDag-Erling Smørgrav  * does not glob it.
897d4af9e69SDag-Erling Smørgrav  */
898d4af9e69SDag-Erling Smørgrav static void
899d4af9e69SDag-Erling Smørgrav undo_glob_escape(char *s)
900d4af9e69SDag-Erling Smørgrav {
901d4af9e69SDag-Erling Smørgrav 	size_t i, j;
902d4af9e69SDag-Erling Smørgrav 
903d4af9e69SDag-Erling Smørgrav 	for (i = j = 0;;) {
904d4af9e69SDag-Erling Smørgrav 		if (s[i] == '\0') {
905d4af9e69SDag-Erling Smørgrav 			s[j] = '\0';
906d4af9e69SDag-Erling Smørgrav 			return;
907d4af9e69SDag-Erling Smørgrav 		}
908d4af9e69SDag-Erling Smørgrav 		if (s[i] != '\\') {
909d4af9e69SDag-Erling Smørgrav 			s[j++] = s[i++];
910d4af9e69SDag-Erling Smørgrav 			continue;
911d4af9e69SDag-Erling Smørgrav 		}
912d4af9e69SDag-Erling Smørgrav 		/* s[i] == '\\' */
913d4af9e69SDag-Erling Smørgrav 		++i;
914d4af9e69SDag-Erling Smørgrav 		switch (s[i]) {
915d4af9e69SDag-Erling Smørgrav 		case '?':
916d4af9e69SDag-Erling Smørgrav 		case '[':
917d4af9e69SDag-Erling Smørgrav 		case '*':
918d4af9e69SDag-Erling Smørgrav 		case '\\':
919d4af9e69SDag-Erling Smørgrav 			s[j++] = s[i++];
920d4af9e69SDag-Erling Smørgrav 			break;
921d4af9e69SDag-Erling Smørgrav 		case '\0':
922d4af9e69SDag-Erling Smørgrav 			s[j++] = '\\';
923d4af9e69SDag-Erling Smørgrav 			s[j] = '\0';
924d4af9e69SDag-Erling Smørgrav 			return;
925d4af9e69SDag-Erling Smørgrav 		default:
926d4af9e69SDag-Erling Smørgrav 			s[j++] = '\\';
927d4af9e69SDag-Erling Smørgrav 			s[j++] = s[i++];
928d4af9e69SDag-Erling Smørgrav 			break;
929d4af9e69SDag-Erling Smørgrav 		}
930d4af9e69SDag-Erling Smørgrav 	}
931d4af9e69SDag-Erling Smørgrav }
932d4af9e69SDag-Erling Smørgrav 
933d4af9e69SDag-Erling Smørgrav /*
934d4af9e69SDag-Erling Smørgrav  * Split a string into an argument vector using sh(1)-style quoting,
935d4af9e69SDag-Erling Smørgrav  * comment and escaping rules, but with some tweaks to handle glob(3)
936d4af9e69SDag-Erling Smørgrav  * wildcards.
937d4af9e69SDag-Erling Smørgrav  * Returns NULL on error or a NULL-terminated array of arguments.
938d4af9e69SDag-Erling Smørgrav  */
939d4af9e69SDag-Erling Smørgrav #define MAXARGS 	128
940d4af9e69SDag-Erling Smørgrav #define MAXARGLEN	8192
941d4af9e69SDag-Erling Smørgrav static char **
942d4af9e69SDag-Erling Smørgrav makeargv(const char *arg, int *argcp)
943d4af9e69SDag-Erling Smørgrav {
944d4af9e69SDag-Erling Smørgrav 	int argc, quot;
945d4af9e69SDag-Erling Smørgrav 	size_t i, j;
946d4af9e69SDag-Erling Smørgrav 	static char argvs[MAXARGLEN];
947d4af9e69SDag-Erling Smørgrav 	static char *argv[MAXARGS + 1];
948d4af9e69SDag-Erling Smørgrav 	enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
949d4af9e69SDag-Erling Smørgrav 
950d4af9e69SDag-Erling Smørgrav 	*argcp = argc = 0;
951d4af9e69SDag-Erling Smørgrav 	if (strlen(arg) > sizeof(argvs) - 1) {
952d4af9e69SDag-Erling Smørgrav  args_too_longs:
953d4af9e69SDag-Erling Smørgrav 		error("string too long");
954d4af9e69SDag-Erling Smørgrav 		return NULL;
955d4af9e69SDag-Erling Smørgrav 	}
956d4af9e69SDag-Erling Smørgrav 	state = MA_START;
957d4af9e69SDag-Erling Smørgrav 	i = j = 0;
958d4af9e69SDag-Erling Smørgrav 	for (;;) {
959d4af9e69SDag-Erling Smørgrav 		if (isspace(arg[i])) {
960d4af9e69SDag-Erling Smørgrav 			if (state == MA_UNQUOTED) {
961d4af9e69SDag-Erling Smørgrav 				/* Terminate current argument */
962d4af9e69SDag-Erling Smørgrav 				argvs[j++] = '\0';
963d4af9e69SDag-Erling Smørgrav 				argc++;
964d4af9e69SDag-Erling Smørgrav 				state = MA_START;
965d4af9e69SDag-Erling Smørgrav 			} else if (state != MA_START)
966d4af9e69SDag-Erling Smørgrav 				argvs[j++] = arg[i];
967d4af9e69SDag-Erling Smørgrav 		} else if (arg[i] == '"' || arg[i] == '\'') {
968d4af9e69SDag-Erling Smørgrav 			q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
969d4af9e69SDag-Erling Smørgrav 			if (state == MA_START) {
970d4af9e69SDag-Erling Smørgrav 				argv[argc] = argvs + j;
971d4af9e69SDag-Erling Smørgrav 				state = q;
972d4af9e69SDag-Erling Smørgrav 			} else if (state == MA_UNQUOTED)
973d4af9e69SDag-Erling Smørgrav 				state = q;
974d4af9e69SDag-Erling Smørgrav 			else if (state == q)
975d4af9e69SDag-Erling Smørgrav 				state = MA_UNQUOTED;
976d4af9e69SDag-Erling Smørgrav 			else
977d4af9e69SDag-Erling Smørgrav 				argvs[j++] = arg[i];
978d4af9e69SDag-Erling Smørgrav 		} else if (arg[i] == '\\') {
979d4af9e69SDag-Erling Smørgrav 			if (state == MA_SQUOTE || state == MA_DQUOTE) {
980d4af9e69SDag-Erling Smørgrav 				quot = state == MA_SQUOTE ? '\'' : '"';
981d4af9e69SDag-Erling Smørgrav 				/* Unescape quote we are in */
982d4af9e69SDag-Erling Smørgrav 				/* XXX support \n and friends? */
983d4af9e69SDag-Erling Smørgrav 				if (arg[i + 1] == quot) {
984d4af9e69SDag-Erling Smørgrav 					i++;
985d4af9e69SDag-Erling Smørgrav 					argvs[j++] = arg[i];
986d4af9e69SDag-Erling Smørgrav 				} else if (arg[i + 1] == '?' ||
987d4af9e69SDag-Erling Smørgrav 				    arg[i + 1] == '[' || arg[i + 1] == '*') {
988d4af9e69SDag-Erling Smørgrav 					/*
989d4af9e69SDag-Erling Smørgrav 					 * Special case for sftp: append
990d4af9e69SDag-Erling Smørgrav 					 * double-escaped glob sequence -
991d4af9e69SDag-Erling Smørgrav 					 * glob will undo one level of
992d4af9e69SDag-Erling Smørgrav 					 * escaping. NB. string can grow here.
993d4af9e69SDag-Erling Smørgrav 					 */
994d4af9e69SDag-Erling Smørgrav 					if (j >= sizeof(argvs) - 5)
995d4af9e69SDag-Erling Smørgrav 						goto args_too_longs;
996d4af9e69SDag-Erling Smørgrav 					argvs[j++] = '\\';
997d4af9e69SDag-Erling Smørgrav 					argvs[j++] = arg[i++];
998d4af9e69SDag-Erling Smørgrav 					argvs[j++] = '\\';
999d4af9e69SDag-Erling Smørgrav 					argvs[j++] = arg[i];
1000d4af9e69SDag-Erling Smørgrav 				} else {
1001d4af9e69SDag-Erling Smørgrav 					argvs[j++] = arg[i++];
1002d4af9e69SDag-Erling Smørgrav 					argvs[j++] = arg[i];
1003d4af9e69SDag-Erling Smørgrav 				}
1004d4af9e69SDag-Erling Smørgrav 			} else {
1005d4af9e69SDag-Erling Smørgrav 				if (state == MA_START) {
1006d4af9e69SDag-Erling Smørgrav 					argv[argc] = argvs + j;
1007d4af9e69SDag-Erling Smørgrav 					state = MA_UNQUOTED;
1008d4af9e69SDag-Erling Smørgrav 				}
1009d4af9e69SDag-Erling Smørgrav 				if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1010d4af9e69SDag-Erling Smørgrav 				    arg[i + 1] == '*' || arg[i + 1] == '\\') {
1011d4af9e69SDag-Erling Smørgrav 					/*
1012d4af9e69SDag-Erling Smørgrav 					 * Special case for sftp: append
1013d4af9e69SDag-Erling Smørgrav 					 * escaped glob sequence -
1014d4af9e69SDag-Erling Smørgrav 					 * glob will undo one level of
1015d4af9e69SDag-Erling Smørgrav 					 * escaping.
1016d4af9e69SDag-Erling Smørgrav 					 */
1017d4af9e69SDag-Erling Smørgrav 					argvs[j++] = arg[i++];
1018d4af9e69SDag-Erling Smørgrav 					argvs[j++] = arg[i];
1019d4af9e69SDag-Erling Smørgrav 				} else {
1020d4af9e69SDag-Erling Smørgrav 					/* Unescape everything */
1021d4af9e69SDag-Erling Smørgrav 					/* XXX support \n and friends? */
1022d4af9e69SDag-Erling Smørgrav 					i++;
1023d4af9e69SDag-Erling Smørgrav 					argvs[j++] = arg[i];
1024d4af9e69SDag-Erling Smørgrav 				}
1025d4af9e69SDag-Erling Smørgrav 			}
1026d4af9e69SDag-Erling Smørgrav 		} else if (arg[i] == '#') {
1027d4af9e69SDag-Erling Smørgrav 			if (state == MA_SQUOTE || state == MA_DQUOTE)
1028d4af9e69SDag-Erling Smørgrav 				argvs[j++] = arg[i];
1029d4af9e69SDag-Erling Smørgrav 			else
1030d4af9e69SDag-Erling Smørgrav 				goto string_done;
1031d4af9e69SDag-Erling Smørgrav 		} else if (arg[i] == '\0') {
1032d4af9e69SDag-Erling Smørgrav 			if (state == MA_SQUOTE || state == MA_DQUOTE) {
1033d4af9e69SDag-Erling Smørgrav 				error("Unterminated quoted argument");
1034d4af9e69SDag-Erling Smørgrav 				return NULL;
1035d4af9e69SDag-Erling Smørgrav 			}
1036d4af9e69SDag-Erling Smørgrav  string_done:
1037d4af9e69SDag-Erling Smørgrav 			if (state == MA_UNQUOTED) {
1038d4af9e69SDag-Erling Smørgrav 				argvs[j++] = '\0';
1039d4af9e69SDag-Erling Smørgrav 				argc++;
1040d4af9e69SDag-Erling Smørgrav 			}
1041d4af9e69SDag-Erling Smørgrav 			break;
1042d4af9e69SDag-Erling Smørgrav 		} else {
1043d4af9e69SDag-Erling Smørgrav 			if (state == MA_START) {
1044d4af9e69SDag-Erling Smørgrav 				argv[argc] = argvs + j;
1045d4af9e69SDag-Erling Smørgrav 				state = MA_UNQUOTED;
1046d4af9e69SDag-Erling Smørgrav 			}
1047d4af9e69SDag-Erling Smørgrav 			if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1048d4af9e69SDag-Erling Smørgrav 			    (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1049d4af9e69SDag-Erling Smørgrav 				/*
1050d4af9e69SDag-Erling Smørgrav 				 * Special case for sftp: escape quoted
1051d4af9e69SDag-Erling Smørgrav 				 * glob(3) wildcards. NB. string can grow
1052d4af9e69SDag-Erling Smørgrav 				 * here.
1053d4af9e69SDag-Erling Smørgrav 				 */
1054d4af9e69SDag-Erling Smørgrav 				if (j >= sizeof(argvs) - 3)
1055d4af9e69SDag-Erling Smørgrav 					goto args_too_longs;
1056d4af9e69SDag-Erling Smørgrav 				argvs[j++] = '\\';
1057d4af9e69SDag-Erling Smørgrav 				argvs[j++] = arg[i];
1058d4af9e69SDag-Erling Smørgrav 			} else
1059d4af9e69SDag-Erling Smørgrav 				argvs[j++] = arg[i];
1060d4af9e69SDag-Erling Smørgrav 		}
1061d4af9e69SDag-Erling Smørgrav 		i++;
1062d4af9e69SDag-Erling Smørgrav 	}
1063d4af9e69SDag-Erling Smørgrav 	*argcp = argc;
1064d4af9e69SDag-Erling Smørgrav 	return argv;
1065d4af9e69SDag-Erling Smørgrav }
1066d4af9e69SDag-Erling Smørgrav 
1067d4af9e69SDag-Erling Smørgrav static int
1068d4af9e69SDag-Erling Smørgrav parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag,
1069efcad6b7SDag-Erling Smørgrav     unsigned long *n_arg, char **path1, char **path2)
1070efcad6b7SDag-Erling Smørgrav {
1071efcad6b7SDag-Erling Smørgrav 	const char *cmd, *cp = *cpp;
1072d4af9e69SDag-Erling Smørgrav 	char *cp2, **argv;
1073efcad6b7SDag-Erling Smørgrav 	int base = 0;
1074efcad6b7SDag-Erling Smørgrav 	long l;
1075d4af9e69SDag-Erling Smørgrav 	int i, cmdnum, optidx, argc;
1076efcad6b7SDag-Erling Smørgrav 
1077efcad6b7SDag-Erling Smørgrav 	/* Skip leading whitespace */
1078efcad6b7SDag-Erling Smørgrav 	cp = cp + strspn(cp, WHITESPACE);
1079efcad6b7SDag-Erling Smørgrav 
1080efcad6b7SDag-Erling Smørgrav 	/* Ignore blank lines and lines which begin with comment '#' char */
1081efcad6b7SDag-Erling Smørgrav 	if (*cp == '\0' || *cp == '#')
1082efcad6b7SDag-Erling Smørgrav 		return (0);
1083efcad6b7SDag-Erling Smørgrav 
1084efcad6b7SDag-Erling Smørgrav 	/* Check for leading '-' (disable error processing) */
1085efcad6b7SDag-Erling Smørgrav 	*iflag = 0;
1086efcad6b7SDag-Erling Smørgrav 	if (*cp == '-') {
1087efcad6b7SDag-Erling Smørgrav 		*iflag = 1;
1088efcad6b7SDag-Erling Smørgrav 		cp++;
1089efcad6b7SDag-Erling Smørgrav 	}
1090efcad6b7SDag-Erling Smørgrav 
1091d4af9e69SDag-Erling Smørgrav 	if ((argv = makeargv(cp, &argc)) == NULL)
1092d4af9e69SDag-Erling Smørgrav 		return -1;
1093efcad6b7SDag-Erling Smørgrav 
1094d4af9e69SDag-Erling Smørgrav 	/* Figure out which command we have */
1095d4af9e69SDag-Erling Smørgrav 	for (i = 0; cmds[i].c != NULL; i++) {
1096d4af9e69SDag-Erling Smørgrav 		if (strcasecmp(cmds[i].c, argv[0]) == 0)
1097efcad6b7SDag-Erling Smørgrav 			break;
1098efcad6b7SDag-Erling Smørgrav 	}
1099efcad6b7SDag-Erling Smørgrav 	cmdnum = cmds[i].n;
1100efcad6b7SDag-Erling Smørgrav 	cmd = cmds[i].c;
1101efcad6b7SDag-Erling Smørgrav 
1102efcad6b7SDag-Erling Smørgrav 	/* Special case */
1103efcad6b7SDag-Erling Smørgrav 	if (*cp == '!') {
1104efcad6b7SDag-Erling Smørgrav 		cp++;
1105efcad6b7SDag-Erling Smørgrav 		cmdnum = I_SHELL;
1106efcad6b7SDag-Erling Smørgrav 	} else if (cmdnum == -1) {
1107efcad6b7SDag-Erling Smørgrav 		error("Invalid command.");
1108d4af9e69SDag-Erling Smørgrav 		return -1;
1109efcad6b7SDag-Erling Smørgrav 	}
1110efcad6b7SDag-Erling Smørgrav 
1111efcad6b7SDag-Erling Smørgrav 	/* Get arguments and parse flags */
1112d4af9e69SDag-Erling Smørgrav 	*lflag = *pflag = *hflag = *n_arg = 0;
1113efcad6b7SDag-Erling Smørgrav 	*path1 = *path2 = NULL;
1114d4af9e69SDag-Erling Smørgrav 	optidx = 1;
1115efcad6b7SDag-Erling Smørgrav 	switch (cmdnum) {
1116efcad6b7SDag-Erling Smørgrav 	case I_GET:
1117efcad6b7SDag-Erling Smørgrav 	case I_PUT:
1118d4af9e69SDag-Erling Smørgrav 		if ((optidx = parse_getput_flags(cmd, argv, argc, pflag)) == -1)
1119d4af9e69SDag-Erling Smørgrav 			return -1;
1120efcad6b7SDag-Erling Smørgrav 		/* Get first pathname (mandatory) */
1121d4af9e69SDag-Erling Smørgrav 		if (argc - optidx < 1) {
1122efcad6b7SDag-Erling Smørgrav 			error("You must specify at least one path after a "
1123efcad6b7SDag-Erling Smørgrav 			    "%s command.", cmd);
1124d4af9e69SDag-Erling Smørgrav 			return -1;
1125efcad6b7SDag-Erling Smørgrav 		}
1126d4af9e69SDag-Erling Smørgrav 		*path1 = xstrdup(argv[optidx]);
1127d4af9e69SDag-Erling Smørgrav 		/* Get second pathname (optional) */
1128d4af9e69SDag-Erling Smørgrav 		if (argc - optidx > 1) {
1129d4af9e69SDag-Erling Smørgrav 			*path2 = xstrdup(argv[optidx + 1]);
1130d4af9e69SDag-Erling Smørgrav 			/* Destination is not globbed */
1131d4af9e69SDag-Erling Smørgrav 			undo_glob_escape(*path2);
1132d4af9e69SDag-Erling Smørgrav 		}
1133efcad6b7SDag-Erling Smørgrav 		break;
1134efcad6b7SDag-Erling Smørgrav 	case I_RENAME:
1135efcad6b7SDag-Erling Smørgrav 	case I_SYMLINK:
1136d4af9e69SDag-Erling Smørgrav 		if (argc - optidx < 2) {
1137efcad6b7SDag-Erling Smørgrav 			error("You must specify two paths after a %s "
1138efcad6b7SDag-Erling Smørgrav 			    "command.", cmd);
1139d4af9e69SDag-Erling Smørgrav 			return -1;
1140efcad6b7SDag-Erling Smørgrav 		}
1141d4af9e69SDag-Erling Smørgrav 		*path1 = xstrdup(argv[optidx]);
1142d4af9e69SDag-Erling Smørgrav 		*path2 = xstrdup(argv[optidx + 1]);
1143d4af9e69SDag-Erling Smørgrav 		/* Paths are not globbed */
1144d4af9e69SDag-Erling Smørgrav 		undo_glob_escape(*path1);
1145d4af9e69SDag-Erling Smørgrav 		undo_glob_escape(*path2);
1146efcad6b7SDag-Erling Smørgrav 		break;
1147efcad6b7SDag-Erling Smørgrav 	case I_RM:
1148efcad6b7SDag-Erling Smørgrav 	case I_MKDIR:
1149efcad6b7SDag-Erling Smørgrav 	case I_RMDIR:
1150efcad6b7SDag-Erling Smørgrav 	case I_CHDIR:
1151efcad6b7SDag-Erling Smørgrav 	case I_LCHDIR:
1152efcad6b7SDag-Erling Smørgrav 	case I_LMKDIR:
1153efcad6b7SDag-Erling Smørgrav 		/* Get pathname (mandatory) */
1154d4af9e69SDag-Erling Smørgrav 		if (argc - optidx < 1) {
1155efcad6b7SDag-Erling Smørgrav 			error("You must specify a path after a %s command.",
1156efcad6b7SDag-Erling Smørgrav 			    cmd);
1157d4af9e69SDag-Erling Smørgrav 			return -1;
1158d4af9e69SDag-Erling Smørgrav 		}
1159d4af9e69SDag-Erling Smørgrav 		*path1 = xstrdup(argv[optidx]);
1160d4af9e69SDag-Erling Smørgrav 		/* Only "rm" globs */
1161d4af9e69SDag-Erling Smørgrav 		if (cmdnum != I_RM)
1162d4af9e69SDag-Erling Smørgrav 			undo_glob_escape(*path1);
1163d4af9e69SDag-Erling Smørgrav 		break;
1164d4af9e69SDag-Erling Smørgrav 	case I_DF:
1165d4af9e69SDag-Erling Smørgrav 		if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1166d4af9e69SDag-Erling Smørgrav 		    iflag)) == -1)
1167d4af9e69SDag-Erling Smørgrav 			return -1;
1168d4af9e69SDag-Erling Smørgrav 		/* Default to current directory if no path specified */
1169d4af9e69SDag-Erling Smørgrav 		if (argc - optidx < 1)
1170d4af9e69SDag-Erling Smørgrav 			*path1 = NULL;
1171d4af9e69SDag-Erling Smørgrav 		else {
1172d4af9e69SDag-Erling Smørgrav 			*path1 = xstrdup(argv[optidx]);
1173d4af9e69SDag-Erling Smørgrav 			undo_glob_escape(*path1);
1174efcad6b7SDag-Erling Smørgrav 		}
1175efcad6b7SDag-Erling Smørgrav 		break;
1176efcad6b7SDag-Erling Smørgrav 	case I_LS:
1177d4af9e69SDag-Erling Smørgrav 		if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
1178efcad6b7SDag-Erling Smørgrav 			return(-1);
1179efcad6b7SDag-Erling Smørgrav 		/* Path is optional */
1180d4af9e69SDag-Erling Smørgrav 		if (argc - optidx > 0)
1181d4af9e69SDag-Erling Smørgrav 			*path1 = xstrdup(argv[optidx]);
1182efcad6b7SDag-Erling Smørgrav 		break;
1183efcad6b7SDag-Erling Smørgrav 	case I_LLS:
1184d4af9e69SDag-Erling Smørgrav 		/* Skip ls command and following whitespace */
1185d4af9e69SDag-Erling Smørgrav 		cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
1186efcad6b7SDag-Erling Smørgrav 	case I_SHELL:
1187efcad6b7SDag-Erling Smørgrav 		/* Uses the rest of the line */
1188efcad6b7SDag-Erling Smørgrav 		break;
1189efcad6b7SDag-Erling Smørgrav 	case I_LUMASK:
1190efcad6b7SDag-Erling Smørgrav 	case I_CHMOD:
1191efcad6b7SDag-Erling Smørgrav 		base = 8;
1192efcad6b7SDag-Erling Smørgrav 	case I_CHOWN:
1193efcad6b7SDag-Erling Smørgrav 	case I_CHGRP:
1194efcad6b7SDag-Erling Smørgrav 		/* Get numeric arg (mandatory) */
1195d4af9e69SDag-Erling Smørgrav 		if (argc - optidx < 1)
1196d4af9e69SDag-Erling Smørgrav 			goto need_num_arg;
119792eb0aa1SDag-Erling Smørgrav 		errno = 0;
1198d4af9e69SDag-Erling Smørgrav 		l = strtol(argv[optidx], &cp2, base);
1199d4af9e69SDag-Erling Smørgrav 		if (cp2 == argv[optidx] || *cp2 != '\0' ||
1200d4af9e69SDag-Erling Smørgrav 		    ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1201d4af9e69SDag-Erling Smørgrav 		    l < 0) {
1202d4af9e69SDag-Erling Smørgrav  need_num_arg:
1203efcad6b7SDag-Erling Smørgrav 			error("You must supply a numeric argument "
1204efcad6b7SDag-Erling Smørgrav 			    "to the %s command.", cmd);
1205d4af9e69SDag-Erling Smørgrav 			return -1;
1206efcad6b7SDag-Erling Smørgrav 		}
1207efcad6b7SDag-Erling Smørgrav 		*n_arg = l;
1208d4af9e69SDag-Erling Smørgrav 		if (cmdnum == I_LUMASK)
1209efcad6b7SDag-Erling Smørgrav 			break;
1210efcad6b7SDag-Erling Smørgrav 		/* Get pathname (mandatory) */
1211d4af9e69SDag-Erling Smørgrav 		if (argc - optidx < 2) {
1212efcad6b7SDag-Erling Smørgrav 			error("You must specify a path after a %s command.",
1213efcad6b7SDag-Erling Smørgrav 			    cmd);
1214d4af9e69SDag-Erling Smørgrav 			return -1;
1215efcad6b7SDag-Erling Smørgrav 		}
1216d4af9e69SDag-Erling Smørgrav 		*path1 = xstrdup(argv[optidx + 1]);
1217efcad6b7SDag-Erling Smørgrav 		break;
1218efcad6b7SDag-Erling Smørgrav 	case I_QUIT:
1219efcad6b7SDag-Erling Smørgrav 	case I_PWD:
1220efcad6b7SDag-Erling Smørgrav 	case I_LPWD:
1221efcad6b7SDag-Erling Smørgrav 	case I_HELP:
1222efcad6b7SDag-Erling Smørgrav 	case I_VERSION:
1223efcad6b7SDag-Erling Smørgrav 	case I_PROGRESS:
1224efcad6b7SDag-Erling Smørgrav 		break;
1225efcad6b7SDag-Erling Smørgrav 	default:
1226efcad6b7SDag-Erling Smørgrav 		fatal("Command not implemented");
1227efcad6b7SDag-Erling Smørgrav 	}
1228efcad6b7SDag-Erling Smørgrav 
1229efcad6b7SDag-Erling Smørgrav 	*cpp = cp;
1230efcad6b7SDag-Erling Smørgrav 	return(cmdnum);
1231efcad6b7SDag-Erling Smørgrav }
1232efcad6b7SDag-Erling Smørgrav 
1233efcad6b7SDag-Erling Smørgrav static int
1234efcad6b7SDag-Erling Smørgrav parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1235efcad6b7SDag-Erling Smørgrav     int err_abort)
1236efcad6b7SDag-Erling Smørgrav {
1237efcad6b7SDag-Erling Smørgrav 	char *path1, *path2, *tmp;
1238cce7d346SDag-Erling Smørgrav 	int pflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
1239cce7d346SDag-Erling Smørgrav 	unsigned long n_arg = 0;
1240efcad6b7SDag-Erling Smørgrav 	Attrib a, *aa;
1241efcad6b7SDag-Erling Smørgrav 	char path_buf[MAXPATHLEN];
1242efcad6b7SDag-Erling Smørgrav 	int err = 0;
1243efcad6b7SDag-Erling Smørgrav 	glob_t g;
1244efcad6b7SDag-Erling Smørgrav 
1245efcad6b7SDag-Erling Smørgrav 	path1 = path2 = NULL;
1246d4af9e69SDag-Erling Smørgrav 	cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &hflag, &n_arg,
1247efcad6b7SDag-Erling Smørgrav 	    &path1, &path2);
1248efcad6b7SDag-Erling Smørgrav 
1249efcad6b7SDag-Erling Smørgrav 	if (iflag != 0)
1250efcad6b7SDag-Erling Smørgrav 		err_abort = 0;
1251efcad6b7SDag-Erling Smørgrav 
1252efcad6b7SDag-Erling Smørgrav 	memset(&g, 0, sizeof(g));
1253efcad6b7SDag-Erling Smørgrav 
1254efcad6b7SDag-Erling Smørgrav 	/* Perform command */
1255efcad6b7SDag-Erling Smørgrav 	switch (cmdnum) {
1256efcad6b7SDag-Erling Smørgrav 	case 0:
1257efcad6b7SDag-Erling Smørgrav 		/* Blank line */
1258efcad6b7SDag-Erling Smørgrav 		break;
1259efcad6b7SDag-Erling Smørgrav 	case -1:
1260efcad6b7SDag-Erling Smørgrav 		/* Unrecognized command */
1261efcad6b7SDag-Erling Smørgrav 		err = -1;
1262efcad6b7SDag-Erling Smørgrav 		break;
1263efcad6b7SDag-Erling Smørgrav 	case I_GET:
1264efcad6b7SDag-Erling Smørgrav 		err = process_get(conn, path1, path2, *pwd, pflag);
1265efcad6b7SDag-Erling Smørgrav 		break;
1266efcad6b7SDag-Erling Smørgrav 	case I_PUT:
1267efcad6b7SDag-Erling Smørgrav 		err = process_put(conn, path1, path2, *pwd, pflag);
1268efcad6b7SDag-Erling Smørgrav 		break;
1269efcad6b7SDag-Erling Smørgrav 	case I_RENAME:
1270efcad6b7SDag-Erling Smørgrav 		path1 = make_absolute(path1, *pwd);
1271efcad6b7SDag-Erling Smørgrav 		path2 = make_absolute(path2, *pwd);
1272efcad6b7SDag-Erling Smørgrav 		err = do_rename(conn, path1, path2);
1273efcad6b7SDag-Erling Smørgrav 		break;
1274efcad6b7SDag-Erling Smørgrav 	case I_SYMLINK:
1275efcad6b7SDag-Erling Smørgrav 		path2 = make_absolute(path2, *pwd);
1276efcad6b7SDag-Erling Smørgrav 		err = do_symlink(conn, path1, path2);
1277efcad6b7SDag-Erling Smørgrav 		break;
1278efcad6b7SDag-Erling Smørgrav 	case I_RM:
1279efcad6b7SDag-Erling Smørgrav 		path1 = make_absolute(path1, *pwd);
1280efcad6b7SDag-Erling Smørgrav 		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1281d74d50a8SDag-Erling Smørgrav 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1282efcad6b7SDag-Erling Smørgrav 			printf("Removing %s\n", g.gl_pathv[i]);
1283efcad6b7SDag-Erling Smørgrav 			err = do_rm(conn, g.gl_pathv[i]);
1284efcad6b7SDag-Erling Smørgrav 			if (err != 0 && err_abort)
1285efcad6b7SDag-Erling Smørgrav 				break;
1286efcad6b7SDag-Erling Smørgrav 		}
1287efcad6b7SDag-Erling Smørgrav 		break;
1288efcad6b7SDag-Erling Smørgrav 	case I_MKDIR:
1289efcad6b7SDag-Erling Smørgrav 		path1 = make_absolute(path1, *pwd);
1290efcad6b7SDag-Erling Smørgrav 		attrib_clear(&a);
1291efcad6b7SDag-Erling Smørgrav 		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1292efcad6b7SDag-Erling Smørgrav 		a.perm = 0777;
1293efcad6b7SDag-Erling Smørgrav 		err = do_mkdir(conn, path1, &a);
1294efcad6b7SDag-Erling Smørgrav 		break;
1295efcad6b7SDag-Erling Smørgrav 	case I_RMDIR:
1296efcad6b7SDag-Erling Smørgrav 		path1 = make_absolute(path1, *pwd);
1297efcad6b7SDag-Erling Smørgrav 		err = do_rmdir(conn, path1);
1298efcad6b7SDag-Erling Smørgrav 		break;
1299efcad6b7SDag-Erling Smørgrav 	case I_CHDIR:
1300efcad6b7SDag-Erling Smørgrav 		path1 = make_absolute(path1, *pwd);
1301efcad6b7SDag-Erling Smørgrav 		if ((tmp = do_realpath(conn, path1)) == NULL) {
1302efcad6b7SDag-Erling Smørgrav 			err = 1;
1303efcad6b7SDag-Erling Smørgrav 			break;
1304efcad6b7SDag-Erling Smørgrav 		}
1305efcad6b7SDag-Erling Smørgrav 		if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1306efcad6b7SDag-Erling Smørgrav 			xfree(tmp);
1307efcad6b7SDag-Erling Smørgrav 			err = 1;
1308efcad6b7SDag-Erling Smørgrav 			break;
1309efcad6b7SDag-Erling Smørgrav 		}
1310efcad6b7SDag-Erling Smørgrav 		if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1311efcad6b7SDag-Erling Smørgrav 			error("Can't change directory: Can't check target");
1312efcad6b7SDag-Erling Smørgrav 			xfree(tmp);
1313efcad6b7SDag-Erling Smørgrav 			err = 1;
1314efcad6b7SDag-Erling Smørgrav 			break;
1315efcad6b7SDag-Erling Smørgrav 		}
1316efcad6b7SDag-Erling Smørgrav 		if (!S_ISDIR(aa->perm)) {
1317efcad6b7SDag-Erling Smørgrav 			error("Can't change directory: \"%s\" is not "
1318efcad6b7SDag-Erling Smørgrav 			    "a directory", tmp);
1319efcad6b7SDag-Erling Smørgrav 			xfree(tmp);
1320efcad6b7SDag-Erling Smørgrav 			err = 1;
1321efcad6b7SDag-Erling Smørgrav 			break;
1322efcad6b7SDag-Erling Smørgrav 		}
1323efcad6b7SDag-Erling Smørgrav 		xfree(*pwd);
1324efcad6b7SDag-Erling Smørgrav 		*pwd = tmp;
1325efcad6b7SDag-Erling Smørgrav 		break;
1326efcad6b7SDag-Erling Smørgrav 	case I_LS:
1327efcad6b7SDag-Erling Smørgrav 		if (!path1) {
1328efcad6b7SDag-Erling Smørgrav 			do_globbed_ls(conn, *pwd, *pwd, lflag);
1329efcad6b7SDag-Erling Smørgrav 			break;
1330efcad6b7SDag-Erling Smørgrav 		}
1331efcad6b7SDag-Erling Smørgrav 
1332efcad6b7SDag-Erling Smørgrav 		/* Strip pwd off beginning of non-absolute paths */
1333efcad6b7SDag-Erling Smørgrav 		tmp = NULL;
1334efcad6b7SDag-Erling Smørgrav 		if (*path1 != '/')
1335efcad6b7SDag-Erling Smørgrav 			tmp = *pwd;
1336efcad6b7SDag-Erling Smørgrav 
1337efcad6b7SDag-Erling Smørgrav 		path1 = make_absolute(path1, *pwd);
1338efcad6b7SDag-Erling Smørgrav 		err = do_globbed_ls(conn, path1, tmp, lflag);
1339efcad6b7SDag-Erling Smørgrav 		break;
1340d4af9e69SDag-Erling Smørgrav 	case I_DF:
1341d4af9e69SDag-Erling Smørgrav 		/* Default to current directory if no path specified */
1342d4af9e69SDag-Erling Smørgrav 		if (path1 == NULL)
1343d4af9e69SDag-Erling Smørgrav 			path1 = xstrdup(*pwd);
1344d4af9e69SDag-Erling Smørgrav 		path1 = make_absolute(path1, *pwd);
1345d4af9e69SDag-Erling Smørgrav 		err = do_df(conn, path1, hflag, iflag);
1346d4af9e69SDag-Erling Smørgrav 		break;
1347efcad6b7SDag-Erling Smørgrav 	case I_LCHDIR:
1348efcad6b7SDag-Erling Smørgrav 		if (chdir(path1) == -1) {
1349efcad6b7SDag-Erling Smørgrav 			error("Couldn't change local directory to "
1350efcad6b7SDag-Erling Smørgrav 			    "\"%s\": %s", path1, strerror(errno));
1351efcad6b7SDag-Erling Smørgrav 			err = 1;
1352efcad6b7SDag-Erling Smørgrav 		}
1353efcad6b7SDag-Erling Smørgrav 		break;
1354efcad6b7SDag-Erling Smørgrav 	case I_LMKDIR:
1355efcad6b7SDag-Erling Smørgrav 		if (mkdir(path1, 0777) == -1) {
1356efcad6b7SDag-Erling Smørgrav 			error("Couldn't create local directory "
1357efcad6b7SDag-Erling Smørgrav 			    "\"%s\": %s", path1, strerror(errno));
1358efcad6b7SDag-Erling Smørgrav 			err = 1;
1359efcad6b7SDag-Erling Smørgrav 		}
1360efcad6b7SDag-Erling Smørgrav 		break;
1361efcad6b7SDag-Erling Smørgrav 	case I_LLS:
1362efcad6b7SDag-Erling Smørgrav 		local_do_ls(cmd);
1363efcad6b7SDag-Erling Smørgrav 		break;
1364efcad6b7SDag-Erling Smørgrav 	case I_SHELL:
1365efcad6b7SDag-Erling Smørgrav 		local_do_shell(cmd);
1366efcad6b7SDag-Erling Smørgrav 		break;
1367efcad6b7SDag-Erling Smørgrav 	case I_LUMASK:
1368efcad6b7SDag-Erling Smørgrav 		umask(n_arg);
1369efcad6b7SDag-Erling Smørgrav 		printf("Local umask: %03lo\n", n_arg);
1370efcad6b7SDag-Erling Smørgrav 		break;
1371efcad6b7SDag-Erling Smørgrav 	case I_CHMOD:
1372efcad6b7SDag-Erling Smørgrav 		path1 = make_absolute(path1, *pwd);
1373efcad6b7SDag-Erling Smørgrav 		attrib_clear(&a);
1374efcad6b7SDag-Erling Smørgrav 		a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1375efcad6b7SDag-Erling Smørgrav 		a.perm = n_arg;
1376efcad6b7SDag-Erling Smørgrav 		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1377d74d50a8SDag-Erling Smørgrav 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1378efcad6b7SDag-Erling Smørgrav 			printf("Changing mode on %s\n", g.gl_pathv[i]);
1379efcad6b7SDag-Erling Smørgrav 			err = do_setstat(conn, g.gl_pathv[i], &a);
1380efcad6b7SDag-Erling Smørgrav 			if (err != 0 && err_abort)
1381efcad6b7SDag-Erling Smørgrav 				break;
1382efcad6b7SDag-Erling Smørgrav 		}
1383efcad6b7SDag-Erling Smørgrav 		break;
1384efcad6b7SDag-Erling Smørgrav 	case I_CHOWN:
1385efcad6b7SDag-Erling Smørgrav 	case I_CHGRP:
1386efcad6b7SDag-Erling Smørgrav 		path1 = make_absolute(path1, *pwd);
1387efcad6b7SDag-Erling Smørgrav 		remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1388d74d50a8SDag-Erling Smørgrav 		for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1389efcad6b7SDag-Erling Smørgrav 			if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
1390cce7d346SDag-Erling Smørgrav 				if (err_abort) {
1391cce7d346SDag-Erling Smørgrav 					err = -1;
1392efcad6b7SDag-Erling Smørgrav 					break;
1393cce7d346SDag-Erling Smørgrav 				} else
1394efcad6b7SDag-Erling Smørgrav 					continue;
1395efcad6b7SDag-Erling Smørgrav 			}
1396efcad6b7SDag-Erling Smørgrav 			if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1397efcad6b7SDag-Erling Smørgrav 				error("Can't get current ownership of "
1398efcad6b7SDag-Erling Smørgrav 				    "remote file \"%s\"", g.gl_pathv[i]);
1399cce7d346SDag-Erling Smørgrav 				if (err_abort) {
1400cce7d346SDag-Erling Smørgrav 					err = -1;
1401efcad6b7SDag-Erling Smørgrav 					break;
1402cce7d346SDag-Erling Smørgrav 				} else
1403efcad6b7SDag-Erling Smørgrav 					continue;
1404efcad6b7SDag-Erling Smørgrav 			}
1405efcad6b7SDag-Erling Smørgrav 			aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1406efcad6b7SDag-Erling Smørgrav 			if (cmdnum == I_CHOWN) {
1407efcad6b7SDag-Erling Smørgrav 				printf("Changing owner on %s\n", g.gl_pathv[i]);
1408efcad6b7SDag-Erling Smørgrav 				aa->uid = n_arg;
1409efcad6b7SDag-Erling Smørgrav 			} else {
1410efcad6b7SDag-Erling Smørgrav 				printf("Changing group on %s\n", g.gl_pathv[i]);
1411efcad6b7SDag-Erling Smørgrav 				aa->gid = n_arg;
1412efcad6b7SDag-Erling Smørgrav 			}
1413efcad6b7SDag-Erling Smørgrav 			err = do_setstat(conn, g.gl_pathv[i], aa);
1414efcad6b7SDag-Erling Smørgrav 			if (err != 0 && err_abort)
1415efcad6b7SDag-Erling Smørgrav 				break;
1416efcad6b7SDag-Erling Smørgrav 		}
1417efcad6b7SDag-Erling Smørgrav 		break;
1418efcad6b7SDag-Erling Smørgrav 	case I_PWD:
1419efcad6b7SDag-Erling Smørgrav 		printf("Remote working directory: %s\n", *pwd);
1420efcad6b7SDag-Erling Smørgrav 		break;
1421efcad6b7SDag-Erling Smørgrav 	case I_LPWD:
1422efcad6b7SDag-Erling Smørgrav 		if (!getcwd(path_buf, sizeof(path_buf))) {
1423efcad6b7SDag-Erling Smørgrav 			error("Couldn't get local cwd: %s", strerror(errno));
1424efcad6b7SDag-Erling Smørgrav 			err = -1;
1425efcad6b7SDag-Erling Smørgrav 			break;
1426efcad6b7SDag-Erling Smørgrav 		}
1427efcad6b7SDag-Erling Smørgrav 		printf("Local working directory: %s\n", path_buf);
1428efcad6b7SDag-Erling Smørgrav 		break;
1429efcad6b7SDag-Erling Smørgrav 	case I_QUIT:
1430efcad6b7SDag-Erling Smørgrav 		/* Processed below */
1431efcad6b7SDag-Erling Smørgrav 		break;
1432efcad6b7SDag-Erling Smørgrav 	case I_HELP:
1433efcad6b7SDag-Erling Smørgrav 		help();
1434efcad6b7SDag-Erling Smørgrav 		break;
1435efcad6b7SDag-Erling Smørgrav 	case I_VERSION:
1436efcad6b7SDag-Erling Smørgrav 		printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1437efcad6b7SDag-Erling Smørgrav 		break;
1438efcad6b7SDag-Erling Smørgrav 	case I_PROGRESS:
1439efcad6b7SDag-Erling Smørgrav 		showprogress = !showprogress;
1440efcad6b7SDag-Erling Smørgrav 		if (showprogress)
1441efcad6b7SDag-Erling Smørgrav 			printf("Progress meter enabled\n");
1442efcad6b7SDag-Erling Smørgrav 		else
1443efcad6b7SDag-Erling Smørgrav 			printf("Progress meter disabled\n");
1444efcad6b7SDag-Erling Smørgrav 		break;
1445efcad6b7SDag-Erling Smørgrav 	default:
1446efcad6b7SDag-Erling Smørgrav 		fatal("%d is not implemented", cmdnum);
1447efcad6b7SDag-Erling Smørgrav 	}
1448efcad6b7SDag-Erling Smørgrav 
1449efcad6b7SDag-Erling Smørgrav 	if (g.gl_pathc)
1450efcad6b7SDag-Erling Smørgrav 		globfree(&g);
1451efcad6b7SDag-Erling Smørgrav 	if (path1)
1452efcad6b7SDag-Erling Smørgrav 		xfree(path1);
1453efcad6b7SDag-Erling Smørgrav 	if (path2)
1454efcad6b7SDag-Erling Smørgrav 		xfree(path2);
1455efcad6b7SDag-Erling Smørgrav 
1456efcad6b7SDag-Erling Smørgrav 	/* If an unignored error occurs in batch mode we should abort. */
1457efcad6b7SDag-Erling Smørgrav 	if (err_abort && err != 0)
1458efcad6b7SDag-Erling Smørgrav 		return (-1);
1459efcad6b7SDag-Erling Smørgrav 	else if (cmdnum == I_QUIT)
1460efcad6b7SDag-Erling Smørgrav 		return (1);
1461efcad6b7SDag-Erling Smørgrav 
1462efcad6b7SDag-Erling Smørgrav 	return (0);
1463efcad6b7SDag-Erling Smørgrav }
1464efcad6b7SDag-Erling Smørgrav 
14655e8dbd04SDag-Erling Smørgrav #ifdef USE_LIBEDIT
14665e8dbd04SDag-Erling Smørgrav static char *
14675e8dbd04SDag-Erling Smørgrav prompt(EditLine *el)
14685e8dbd04SDag-Erling Smørgrav {
14695e8dbd04SDag-Erling Smørgrav 	return ("sftp> ");
14705e8dbd04SDag-Erling Smørgrav }
14715e8dbd04SDag-Erling Smørgrav #endif
14725e8dbd04SDag-Erling Smørgrav 
1473efcad6b7SDag-Erling Smørgrav int
1474efcad6b7SDag-Erling Smørgrav interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
1475efcad6b7SDag-Erling Smørgrav {
1476efcad6b7SDag-Erling Smørgrav 	char *pwd;
1477efcad6b7SDag-Erling Smørgrav 	char *dir = NULL;
1478efcad6b7SDag-Erling Smørgrav 	char cmd[2048];
1479efcad6b7SDag-Erling Smørgrav 	struct sftp_conn *conn;
1480043840dfSDag-Erling Smørgrav 	int err, interactive;
14815e8dbd04SDag-Erling Smørgrav 	EditLine *el = NULL;
14825e8dbd04SDag-Erling Smørgrav #ifdef USE_LIBEDIT
14835e8dbd04SDag-Erling Smørgrav 	History *hl = NULL;
14845e8dbd04SDag-Erling Smørgrav 	HistEvent hev;
14855e8dbd04SDag-Erling Smørgrav 	extern char *__progname;
14865e8dbd04SDag-Erling Smørgrav 
14875e8dbd04SDag-Erling Smørgrav 	if (!batchmode && isatty(STDIN_FILENO)) {
14885e8dbd04SDag-Erling Smørgrav 		if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
14895e8dbd04SDag-Erling Smørgrav 			fatal("Couldn't initialise editline");
14905e8dbd04SDag-Erling Smørgrav 		if ((hl = history_init()) == NULL)
14915e8dbd04SDag-Erling Smørgrav 			fatal("Couldn't initialise editline history");
14925e8dbd04SDag-Erling Smørgrav 		history(hl, &hev, H_SETSIZE, 100);
14935e8dbd04SDag-Erling Smørgrav 		el_set(el, EL_HIST, history, hl);
14945e8dbd04SDag-Erling Smørgrav 
14955e8dbd04SDag-Erling Smørgrav 		el_set(el, EL_PROMPT, prompt);
14965e8dbd04SDag-Erling Smørgrav 		el_set(el, EL_EDITOR, "emacs");
14975e8dbd04SDag-Erling Smørgrav 		el_set(el, EL_TERMINAL, NULL);
14985e8dbd04SDag-Erling Smørgrav 		el_set(el, EL_SIGNAL, 1);
14995e8dbd04SDag-Erling Smørgrav 		el_source(el, NULL);
15005e8dbd04SDag-Erling Smørgrav 	}
15015e8dbd04SDag-Erling Smørgrav #endif /* USE_LIBEDIT */
1502efcad6b7SDag-Erling Smørgrav 
1503efcad6b7SDag-Erling Smørgrav 	conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
1504efcad6b7SDag-Erling Smørgrav 	if (conn == NULL)
1505efcad6b7SDag-Erling Smørgrav 		fatal("Couldn't initialise connection to server");
1506efcad6b7SDag-Erling Smørgrav 
1507efcad6b7SDag-Erling Smørgrav 	pwd = do_realpath(conn, ".");
1508efcad6b7SDag-Erling Smørgrav 	if (pwd == NULL)
1509efcad6b7SDag-Erling Smørgrav 		fatal("Need cwd");
1510efcad6b7SDag-Erling Smørgrav 
1511efcad6b7SDag-Erling Smørgrav 	if (file1 != NULL) {
1512efcad6b7SDag-Erling Smørgrav 		dir = xstrdup(file1);
1513efcad6b7SDag-Erling Smørgrav 		dir = make_absolute(dir, pwd);
1514efcad6b7SDag-Erling Smørgrav 
1515efcad6b7SDag-Erling Smørgrav 		if (remote_is_dir(conn, dir) && file2 == NULL) {
1516efcad6b7SDag-Erling Smørgrav 			printf("Changing to: %s\n", dir);
1517efcad6b7SDag-Erling Smørgrav 			snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
15185e8dbd04SDag-Erling Smørgrav 			if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) {
15195e8dbd04SDag-Erling Smørgrav 				xfree(dir);
15205e8dbd04SDag-Erling Smørgrav 				xfree(pwd);
1521761efaa7SDag-Erling Smørgrav 				xfree(conn);
1522efcad6b7SDag-Erling Smørgrav 				return (-1);
15235e8dbd04SDag-Erling Smørgrav 			}
1524efcad6b7SDag-Erling Smørgrav 		} else {
1525efcad6b7SDag-Erling Smørgrav 			if (file2 == NULL)
1526efcad6b7SDag-Erling Smørgrav 				snprintf(cmd, sizeof cmd, "get %s", dir);
1527efcad6b7SDag-Erling Smørgrav 			else
1528efcad6b7SDag-Erling Smørgrav 				snprintf(cmd, sizeof cmd, "get %s %s", dir,
1529efcad6b7SDag-Erling Smørgrav 				    file2);
1530efcad6b7SDag-Erling Smørgrav 
1531efcad6b7SDag-Erling Smørgrav 			err = parse_dispatch_command(conn, cmd, &pwd, 1);
1532efcad6b7SDag-Erling Smørgrav 			xfree(dir);
1533efcad6b7SDag-Erling Smørgrav 			xfree(pwd);
1534761efaa7SDag-Erling Smørgrav 			xfree(conn);
1535efcad6b7SDag-Erling Smørgrav 			return (err);
1536efcad6b7SDag-Erling Smørgrav 		}
1537efcad6b7SDag-Erling Smørgrav 		xfree(dir);
1538efcad6b7SDag-Erling Smørgrav 	}
1539efcad6b7SDag-Erling Smørgrav 
1540043840dfSDag-Erling Smørgrav #if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
1541efcad6b7SDag-Erling Smørgrav 	setvbuf(stdout, NULL, _IOLBF, 0);
1542efcad6b7SDag-Erling Smørgrav 	setvbuf(infile, NULL, _IOLBF, 0);
1543efcad6b7SDag-Erling Smørgrav #else
1544efcad6b7SDag-Erling Smørgrav 	setlinebuf(stdout);
1545efcad6b7SDag-Erling Smørgrav 	setlinebuf(infile);
1546efcad6b7SDag-Erling Smørgrav #endif
1547efcad6b7SDag-Erling Smørgrav 
1548043840dfSDag-Erling Smørgrav 	interactive = !batchmode && isatty(STDIN_FILENO);
1549efcad6b7SDag-Erling Smørgrav 	err = 0;
1550efcad6b7SDag-Erling Smørgrav 	for (;;) {
1551efcad6b7SDag-Erling Smørgrav 		char *cp;
1552efcad6b7SDag-Erling Smørgrav 
1553d74d50a8SDag-Erling Smørgrav 		signal(SIGINT, SIG_IGN);
1554d74d50a8SDag-Erling Smørgrav 
15555e8dbd04SDag-Erling Smørgrav 		if (el == NULL) {
1556043840dfSDag-Erling Smørgrav 			if (interactive)
1557efcad6b7SDag-Erling Smørgrav 				printf("sftp> ");
1558efcad6b7SDag-Erling Smørgrav 			if (fgets(cmd, sizeof(cmd), infile) == NULL) {
1559043840dfSDag-Erling Smørgrav 				if (interactive)
1560efcad6b7SDag-Erling Smørgrav 					printf("\n");
1561efcad6b7SDag-Erling Smørgrav 				break;
1562efcad6b7SDag-Erling Smørgrav 			}
1563043840dfSDag-Erling Smørgrav 			if (!interactive) { /* Echo command */
1564043840dfSDag-Erling Smørgrav 				printf("sftp> %s", cmd);
1565043840dfSDag-Erling Smørgrav 				if (strlen(cmd) > 0 &&
1566043840dfSDag-Erling Smørgrav 				    cmd[strlen(cmd) - 1] != '\n')
1567043840dfSDag-Erling Smørgrav 					printf("\n");
1568043840dfSDag-Erling Smørgrav 			}
15695e8dbd04SDag-Erling Smørgrav 		} else {
15705e8dbd04SDag-Erling Smørgrav #ifdef USE_LIBEDIT
15715e8dbd04SDag-Erling Smørgrav 			const char *line;
15725e8dbd04SDag-Erling Smørgrav 			int count = 0;
15735e8dbd04SDag-Erling Smørgrav 
1574043840dfSDag-Erling Smørgrav 			if ((line = el_gets(el, &count)) == NULL || count <= 0) {
1575043840dfSDag-Erling Smørgrav 				printf("\n");
15765e8dbd04SDag-Erling Smørgrav  				break;
1577043840dfSDag-Erling Smørgrav 			}
15785e8dbd04SDag-Erling Smørgrav 			history(hl, &hev, H_ENTER, line);
15795e8dbd04SDag-Erling Smørgrav 			if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
15805e8dbd04SDag-Erling Smørgrav 				fprintf(stderr, "Error: input line too long\n");
15815e8dbd04SDag-Erling Smørgrav 				continue;
15825e8dbd04SDag-Erling Smørgrav 			}
15835e8dbd04SDag-Erling Smørgrav #endif /* USE_LIBEDIT */
15845e8dbd04SDag-Erling Smørgrav 		}
1585efcad6b7SDag-Erling Smørgrav 
1586efcad6b7SDag-Erling Smørgrav 		cp = strrchr(cmd, '\n');
1587efcad6b7SDag-Erling Smørgrav 		if (cp)
1588efcad6b7SDag-Erling Smørgrav 			*cp = '\0';
1589efcad6b7SDag-Erling Smørgrav 
1590d74d50a8SDag-Erling Smørgrav 		/* Handle user interrupts gracefully during commands */
1591d74d50a8SDag-Erling Smørgrav 		interrupted = 0;
1592d74d50a8SDag-Erling Smørgrav 		signal(SIGINT, cmd_interrupt);
1593d74d50a8SDag-Erling Smørgrav 
1594efcad6b7SDag-Erling Smørgrav 		err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
1595efcad6b7SDag-Erling Smørgrav 		if (err != 0)
1596efcad6b7SDag-Erling Smørgrav 			break;
1597efcad6b7SDag-Erling Smørgrav 	}
1598efcad6b7SDag-Erling Smørgrav 	xfree(pwd);
1599761efaa7SDag-Erling Smørgrav 	xfree(conn);
1600efcad6b7SDag-Erling Smørgrav 
1601043840dfSDag-Erling Smørgrav #ifdef USE_LIBEDIT
1602043840dfSDag-Erling Smørgrav 	if (el != NULL)
1603043840dfSDag-Erling Smørgrav 		el_end(el);
1604043840dfSDag-Erling Smørgrav #endif /* USE_LIBEDIT */
1605043840dfSDag-Erling Smørgrav 
1606efcad6b7SDag-Erling Smørgrav 	/* err == 1 signifies normal "quit" exit */
1607efcad6b7SDag-Erling Smørgrav 	return (err >= 0 ? 0 : -1);
1608efcad6b7SDag-Erling Smørgrav }
1609d0c8c0bcSDag-Erling Smørgrav 
1610ae1f160dSDag-Erling Smørgrav static void
1611d95e11bfSDag-Erling Smørgrav connect_to_server(char *path, char **args, int *in, int *out)
16121e8db6e2SBrian Feldman {
16131e8db6e2SBrian Feldman 	int c_in, c_out;
1614ee21a45fSDag-Erling Smørgrav 
16151e8db6e2SBrian Feldman #ifdef USE_PIPES
16161e8db6e2SBrian Feldman 	int pin[2], pout[2];
1617ee21a45fSDag-Erling Smørgrav 
16181e8db6e2SBrian Feldman 	if ((pipe(pin) == -1) || (pipe(pout) == -1))
16191e8db6e2SBrian Feldman 		fatal("pipe: %s", strerror(errno));
16201e8db6e2SBrian Feldman 	*in = pin[0];
16211e8db6e2SBrian Feldman 	*out = pout[1];
16221e8db6e2SBrian Feldman 	c_in = pout[0];
16231e8db6e2SBrian Feldman 	c_out = pin[1];
16241e8db6e2SBrian Feldman #else /* USE_PIPES */
16251e8db6e2SBrian Feldman 	int inout[2];
1626ee21a45fSDag-Erling Smørgrav 
16271e8db6e2SBrian Feldman 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
16281e8db6e2SBrian Feldman 		fatal("socketpair: %s", strerror(errno));
16291e8db6e2SBrian Feldman 	*in = *out = inout[0];
16301e8db6e2SBrian Feldman 	c_in = c_out = inout[1];
16311e8db6e2SBrian Feldman #endif /* USE_PIPES */
16321e8db6e2SBrian Feldman 
1633d95e11bfSDag-Erling Smørgrav 	if ((sshpid = fork()) == -1)
16341e8db6e2SBrian Feldman 		fatal("fork: %s", strerror(errno));
1635d95e11bfSDag-Erling Smørgrav 	else if (sshpid == 0) {
16361e8db6e2SBrian Feldman 		if ((dup2(c_in, STDIN_FILENO) == -1) ||
16371e8db6e2SBrian Feldman 		    (dup2(c_out, STDOUT_FILENO) == -1)) {
16381e8db6e2SBrian Feldman 			fprintf(stderr, "dup2: %s\n", strerror(errno));
1639d74d50a8SDag-Erling Smørgrav 			_exit(1);
16401e8db6e2SBrian Feldman 		}
16411e8db6e2SBrian Feldman 		close(*in);
16421e8db6e2SBrian Feldman 		close(*out);
16431e8db6e2SBrian Feldman 		close(c_in);
16441e8db6e2SBrian Feldman 		close(c_out);
1645d74d50a8SDag-Erling Smørgrav 
1646d74d50a8SDag-Erling Smørgrav 		/*
1647d74d50a8SDag-Erling Smørgrav 		 * The underlying ssh is in the same process group, so we must
1648d74d50a8SDag-Erling Smørgrav 		 * ignore SIGINT if we want to gracefully abort commands,
1649d74d50a8SDag-Erling Smørgrav 		 * otherwise the signal will make it to the ssh process and
1650d74d50a8SDag-Erling Smørgrav 		 * kill it too
1651d74d50a8SDag-Erling Smørgrav 		 */
1652d74d50a8SDag-Erling Smørgrav 		signal(SIGINT, SIG_IGN);
1653d74d50a8SDag-Erling Smørgrav 		execvp(path, args);
1654ae1f160dSDag-Erling Smørgrav 		fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
1655d74d50a8SDag-Erling Smørgrav 		_exit(1);
16561e8db6e2SBrian Feldman 	}
16571e8db6e2SBrian Feldman 
1658d95e11bfSDag-Erling Smørgrav 	signal(SIGTERM, killchild);
1659d95e11bfSDag-Erling Smørgrav 	signal(SIGINT, killchild);
1660d95e11bfSDag-Erling Smørgrav 	signal(SIGHUP, killchild);
16611e8db6e2SBrian Feldman 	close(c_in);
16621e8db6e2SBrian Feldman 	close(c_out);
16631e8db6e2SBrian Feldman }
16641e8db6e2SBrian Feldman 
1665ae1f160dSDag-Erling Smørgrav static void
16661e8db6e2SBrian Feldman usage(void)
16671e8db6e2SBrian Feldman {
1668ae1f160dSDag-Erling Smørgrav 	extern char *__progname;
1669ae1f160dSDag-Erling Smørgrav 
1670ae1f160dSDag-Erling Smørgrav 	fprintf(stderr,
1671efcad6b7SDag-Erling Smørgrav 	    "usage: %s [-1Cv] [-B buffer_size] [-b batchfile] [-F ssh_config]\n"
1672efcad6b7SDag-Erling Smørgrav 	    "            [-o ssh_option] [-P sftp_server_path] [-R num_requests]\n"
1673efcad6b7SDag-Erling Smørgrav 	    "            [-S program] [-s subsystem | sftp_server] host\n"
1674cce7d346SDag-Erling Smørgrav 	    "       %s [user@]host[:file ...]\n"
1675cce7d346SDag-Erling Smørgrav 	    "       %s [user@]host[:dir[/]]\n"
1676efcad6b7SDag-Erling Smørgrav 	    "       %s -b batchfile [user@]host\n", __progname, __progname, __progname, __progname);
16771e8db6e2SBrian Feldman 	exit(1);
16781e8db6e2SBrian Feldman }
16791e8db6e2SBrian Feldman 
16801e8db6e2SBrian Feldman int
16811e8db6e2SBrian Feldman main(int argc, char **argv)
16821e8db6e2SBrian Feldman {
1683d0c8c0bcSDag-Erling Smørgrav 	int in, out, ch, err;
1684d74d50a8SDag-Erling Smørgrav 	char *host, *userhost, *cp, *file2 = NULL;
1685ae1f160dSDag-Erling Smørgrav 	int debug_level = 0, sshver = 2;
1686ae1f160dSDag-Erling Smørgrav 	char *file1 = NULL, *sftp_server = NULL;
1687ae1f160dSDag-Erling Smørgrav 	char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
1688ae1f160dSDag-Erling Smørgrav 	LogLevel ll = SYSLOG_LEVEL_INFO;
1689ae1f160dSDag-Erling Smørgrav 	arglist args;
16901e8db6e2SBrian Feldman 	extern int optind;
16911e8db6e2SBrian Feldman 	extern char *optarg;
16921e8db6e2SBrian Feldman 
1693021d409fSDag-Erling Smørgrav 	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
1694021d409fSDag-Erling Smørgrav 	sanitise_stdfd();
1695021d409fSDag-Erling Smørgrav 
1696d95e11bfSDag-Erling Smørgrav 	__progname = ssh_get_progname(argv[0]);
1697021d409fSDag-Erling Smørgrav 	memset(&args, '\0', sizeof(args));
1698ae1f160dSDag-Erling Smørgrav 	args.list = NULL;
1699761efaa7SDag-Erling Smørgrav 	addargs(&args, "%s", ssh_program);
1700ae1f160dSDag-Erling Smørgrav 	addargs(&args, "-oForwardX11 no");
1701ae1f160dSDag-Erling Smørgrav 	addargs(&args, "-oForwardAgent no");
1702021d409fSDag-Erling Smørgrav 	addargs(&args, "-oPermitLocalCommand no");
1703ae1f160dSDag-Erling Smørgrav 	addargs(&args, "-oClearAllForwardings yes");
1704efcad6b7SDag-Erling Smørgrav 
1705ae1f160dSDag-Erling Smørgrav 	ll = SYSLOG_LEVEL_INFO;
1706efcad6b7SDag-Erling Smørgrav 	infile = stdin;
17071e8db6e2SBrian Feldman 
1708ae1f160dSDag-Erling Smørgrav 	while ((ch = getopt(argc, argv, "1hvCo:s:S:b:B:F:P:R:")) != -1) {
17091e8db6e2SBrian Feldman 		switch (ch) {
17101e8db6e2SBrian Feldman 		case 'C':
1711ae1f160dSDag-Erling Smørgrav 			addargs(&args, "-C");
17121e8db6e2SBrian Feldman 			break;
17131e8db6e2SBrian Feldman 		case 'v':
1714ae1f160dSDag-Erling Smørgrav 			if (debug_level < 3) {
1715ae1f160dSDag-Erling Smørgrav 				addargs(&args, "-v");
1716ae1f160dSDag-Erling Smørgrav 				ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
1717ae1f160dSDag-Erling Smørgrav 			}
1718ae1f160dSDag-Erling Smørgrav 			debug_level++;
17191e8db6e2SBrian Feldman 			break;
1720ae1f160dSDag-Erling Smørgrav 		case 'F':
17211e8db6e2SBrian Feldman 		case 'o':
1722ae1f160dSDag-Erling Smørgrav 			addargs(&args, "-%c%s", ch, optarg);
17231e8db6e2SBrian Feldman 			break;
17241e8db6e2SBrian Feldman 		case '1':
1725ae1f160dSDag-Erling Smørgrav 			sshver = 1;
17261e8db6e2SBrian Feldman 			if (sftp_server == NULL)
17271e8db6e2SBrian Feldman 				sftp_server = _PATH_SFTP_SERVER;
17281e8db6e2SBrian Feldman 			break;
17291e8db6e2SBrian Feldman 		case 's':
17301e8db6e2SBrian Feldman 			sftp_server = optarg;
17311e8db6e2SBrian Feldman 			break;
17321e8db6e2SBrian Feldman 		case 'S':
17331e8db6e2SBrian Feldman 			ssh_program = optarg;
1734021d409fSDag-Erling Smørgrav 			replacearg(&args, 0, "%s", ssh_program);
17351e8db6e2SBrian Feldman 			break;
17361e8db6e2SBrian Feldman 		case 'b':
1737efcad6b7SDag-Erling Smørgrav 			if (batchmode)
1738efcad6b7SDag-Erling Smørgrav 				fatal("Batch file already specified.");
1739efcad6b7SDag-Erling Smørgrav 
1740efcad6b7SDag-Erling Smørgrav 			/* Allow "-" as stdin */
1741efcad6b7SDag-Erling Smørgrav 			if (strcmp(optarg, "-") != 0 &&
1742efcad6b7SDag-Erling Smørgrav 			    (infile = fopen(optarg, "r")) == NULL)
17431e8db6e2SBrian Feldman 				fatal("%s (%s).", strerror(errno), optarg);
1744d0c8c0bcSDag-Erling Smørgrav 			showprogress = 0;
1745efcad6b7SDag-Erling Smørgrav 			batchmode = 1;
17465e8dbd04SDag-Erling Smørgrav 			addargs(&args, "-obatchmode yes");
17471e8db6e2SBrian Feldman 			break;
1748ae1f160dSDag-Erling Smørgrav 		case 'P':
1749ae1f160dSDag-Erling Smørgrav 			sftp_direct = optarg;
1750ae1f160dSDag-Erling Smørgrav 			break;
1751ae1f160dSDag-Erling Smørgrav 		case 'B':
1752ae1f160dSDag-Erling Smørgrav 			copy_buffer_len = strtol(optarg, &cp, 10);
1753ae1f160dSDag-Erling Smørgrav 			if (copy_buffer_len == 0 || *cp != '\0')
1754ae1f160dSDag-Erling Smørgrav 				fatal("Invalid buffer size \"%s\"", optarg);
1755ae1f160dSDag-Erling Smørgrav 			break;
1756ae1f160dSDag-Erling Smørgrav 		case 'R':
1757ae1f160dSDag-Erling Smørgrav 			num_requests = strtol(optarg, &cp, 10);
1758ae1f160dSDag-Erling Smørgrav 			if (num_requests == 0 || *cp != '\0')
1759ae1f160dSDag-Erling Smørgrav 				fatal("Invalid number of requests \"%s\"",
1760ae1f160dSDag-Erling Smørgrav 				    optarg);
1761ae1f160dSDag-Erling Smørgrav 			break;
17621e8db6e2SBrian Feldman 		case 'h':
17631e8db6e2SBrian Feldman 		default:
17641e8db6e2SBrian Feldman 			usage();
17651e8db6e2SBrian Feldman 		}
17661e8db6e2SBrian Feldman 	}
17671e8db6e2SBrian Feldman 
176852028650SDag-Erling Smørgrav 	if (!isatty(STDERR_FILENO))
176952028650SDag-Erling Smørgrav 		showprogress = 0;
177052028650SDag-Erling Smørgrav 
1771545d5ecaSDag-Erling Smørgrav 	log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
1772545d5ecaSDag-Erling Smørgrav 
1773ae1f160dSDag-Erling Smørgrav 	if (sftp_direct == NULL) {
17741e8db6e2SBrian Feldman 		if (optind == argc || argc > (optind + 2))
17751e8db6e2SBrian Feldman 			usage();
17761e8db6e2SBrian Feldman 
17771e8db6e2SBrian Feldman 		userhost = xstrdup(argv[optind]);
17781e8db6e2SBrian Feldman 		file2 = argv[optind+1];
17791e8db6e2SBrian Feldman 
1780d0c8c0bcSDag-Erling Smørgrav 		if ((host = strrchr(userhost, '@')) == NULL)
17811e8db6e2SBrian Feldman 			host = userhost;
17821e8db6e2SBrian Feldman 		else {
17831e8db6e2SBrian Feldman 			*host++ = '\0';
17841e8db6e2SBrian Feldman 			if (!userhost[0]) {
17851e8db6e2SBrian Feldman 				fprintf(stderr, "Missing username\n");
17861e8db6e2SBrian Feldman 				usage();
17871e8db6e2SBrian Feldman 			}
1788ae1f160dSDag-Erling Smørgrav 			addargs(&args, "-l%s", userhost);
17891e8db6e2SBrian Feldman 		}
17901e8db6e2SBrian Feldman 
1791efcad6b7SDag-Erling Smørgrav 		if ((cp = colon(host)) != NULL) {
1792efcad6b7SDag-Erling Smørgrav 			*cp++ = '\0';
1793efcad6b7SDag-Erling Smørgrav 			file1 = cp;
1794efcad6b7SDag-Erling Smørgrav 		}
1795efcad6b7SDag-Erling Smørgrav 
17961e8db6e2SBrian Feldman 		host = cleanhostname(host);
17971e8db6e2SBrian Feldman 		if (!*host) {
17981e8db6e2SBrian Feldman 			fprintf(stderr, "Missing hostname\n");
17991e8db6e2SBrian Feldman 			usage();
18001e8db6e2SBrian Feldman 		}
18011e8db6e2SBrian Feldman 
1802ae1f160dSDag-Erling Smørgrav 		addargs(&args, "-oProtocol %d", sshver);
18031e8db6e2SBrian Feldman 
1804ae1f160dSDag-Erling Smørgrav 		/* no subsystem if the server-spec contains a '/' */
1805ae1f160dSDag-Erling Smørgrav 		if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
1806ae1f160dSDag-Erling Smørgrav 			addargs(&args, "-s");
1807ae1f160dSDag-Erling Smørgrav 
1808ae1f160dSDag-Erling Smørgrav 		addargs(&args, "%s", host);
1809ae1f160dSDag-Erling Smørgrav 		addargs(&args, "%s", (sftp_server != NULL ?
1810ae1f160dSDag-Erling Smørgrav 		    sftp_server : "sftp"));
18111e8db6e2SBrian Feldman 
1812efcad6b7SDag-Erling Smørgrav 		if (!batchmode)
18131e8db6e2SBrian Feldman 			fprintf(stderr, "Connecting to %s...\n", host);
1814d95e11bfSDag-Erling Smørgrav 		connect_to_server(ssh_program, args.list, &in, &out);
1815ae1f160dSDag-Erling Smørgrav 	} else {
1816ae1f160dSDag-Erling Smørgrav 		args.list = NULL;
1817ae1f160dSDag-Erling Smørgrav 		addargs(&args, "sftp-server");
18181e8db6e2SBrian Feldman 
1819efcad6b7SDag-Erling Smørgrav 		if (!batchmode)
1820ae1f160dSDag-Erling Smørgrav 			fprintf(stderr, "Attaching to %s...\n", sftp_direct);
1821d95e11bfSDag-Erling Smørgrav 		connect_to_server(sftp_direct, args.list, &in, &out);
1822ae1f160dSDag-Erling Smørgrav 	}
1823021d409fSDag-Erling Smørgrav 	freeargs(&args);
18241e8db6e2SBrian Feldman 
1825d0c8c0bcSDag-Erling Smørgrav 	err = interactive_loop(in, out, file1, file2);
18261e8db6e2SBrian Feldman 
182783d2307dSDag-Erling Smørgrav #if !defined(USE_PIPES)
182883d2307dSDag-Erling Smørgrav 	shutdown(in, SHUT_RDWR);
182983d2307dSDag-Erling Smørgrav 	shutdown(out, SHUT_RDWR);
183083d2307dSDag-Erling Smørgrav #endif
183183d2307dSDag-Erling Smørgrav 
18321e8db6e2SBrian Feldman 	close(in);
18331e8db6e2SBrian Feldman 	close(out);
1834efcad6b7SDag-Erling Smørgrav 	if (batchmode)
18351e8db6e2SBrian Feldman 		fclose(infile);
18361e8db6e2SBrian Feldman 
1837545d5ecaSDag-Erling Smørgrav 	while (waitpid(sshpid, NULL, 0) == -1)
1838545d5ecaSDag-Erling Smørgrav 		if (errno != EINTR)
1839545d5ecaSDag-Erling Smørgrav 			fatal("Couldn't wait for ssh process: %s",
1840545d5ecaSDag-Erling Smørgrav 			    strerror(errno));
18411e8db6e2SBrian Feldman 
1842d0c8c0bcSDag-Erling Smørgrav 	exit(err == 0 ? 0 : 1);
18431e8db6e2SBrian Feldman }
1844