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