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