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