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