17c478bd9Sstevel@tonic-gate /*
290685d2cSjp161948 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
37c478bd9Sstevel@tonic-gate *
490685d2cSjp161948 * Permission to use, copy, modify, and distribute this software for any
590685d2cSjp161948 * purpose with or without fee is hereby granted, provided that the above
690685d2cSjp161948 * copyright notice and this permission notice appear in all copies.
77c478bd9Sstevel@tonic-gate *
890685d2cSjp161948 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
990685d2cSjp161948 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1090685d2cSjp161948 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1190685d2cSjp161948 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1290685d2cSjp161948 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1390685d2cSjp161948 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1490685d2cSjp161948 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
157c478bd9Sstevel@tonic-gate */
167c478bd9Sstevel@tonic-gate
1790685d2cSjp161948 /* $OpenBSD: sftp.c,v 1.96 2007/01/03 04:09:15 stevesk Exp $ */
187c478bd9Sstevel@tonic-gate
1990685d2cSjp161948 #include "includes.h"
207c478bd9Sstevel@tonic-gate
2190685d2cSjp161948 #include <sys/types.h>
2290685d2cSjp161948 #include <sys/ioctl.h>
2390685d2cSjp161948 #ifdef HAVE_SYS_STAT_H
2490685d2cSjp161948 # include <sys/stat.h>
2590685d2cSjp161948 #endif
2690685d2cSjp161948 #include <sys/param.h>
2790685d2cSjp161948 #include <sys/socket.h>
2890685d2cSjp161948 #include <sys/wait.h>
2990685d2cSjp161948
3090685d2cSjp161948 #include <errno.h>
3190685d2cSjp161948
3290685d2cSjp161948 #ifdef HAVE_PATHS_H
3390685d2cSjp161948 # include <paths.h>
3490685d2cSjp161948 #endif
3584e198abSHuie-Ying Lee
3690685d2cSjp161948 #ifdef USE_LIBEDIT
3790685d2cSjp161948 #include <histedit.h>
3890685d2cSjp161948 #else
3984e198abSHuie-Ying Lee #ifdef USE_LIBTECLA
4084e198abSHuie-Ying Lee #include <libtecla.h>
4184e198abSHuie-Ying Lee #define MAX_LINE_LEN 2048
4284e198abSHuie-Ying Lee #define MAX_CMD_HIST 10000
4384e198abSHuie-Ying Lee #endif /* USE_LIBTECLA */
4484e198abSHuie-Ying Lee #endif /* USE_LIBEDIT */
4584e198abSHuie-Ying Lee
4690685d2cSjp161948 #include <signal.h>
4790685d2cSjp161948 #include <stdlib.h>
4890685d2cSjp161948 #include <stdio.h>
4990685d2cSjp161948 #include <string.h>
5090685d2cSjp161948 #include <unistd.h>
5190685d2cSjp161948 #include <stdarg.h>
5290685d2cSjp161948
537c478bd9Sstevel@tonic-gate #include "xmalloc.h"
547c478bd9Sstevel@tonic-gate #include "log.h"
557c478bd9Sstevel@tonic-gate #include "pathnames.h"
567c478bd9Sstevel@tonic-gate #include "misc.h"
577c478bd9Sstevel@tonic-gate
587c478bd9Sstevel@tonic-gate #include "sftp.h"
5990685d2cSjp161948 #include "buffer.h"
607c478bd9Sstevel@tonic-gate #include "sftp-common.h"
617c478bd9Sstevel@tonic-gate #include "sftp-client.h"
627c478bd9Sstevel@tonic-gate
637c478bd9Sstevel@tonic-gate #ifdef HAVE___PROGNAME
647c478bd9Sstevel@tonic-gate extern char *__progname;
657c478bd9Sstevel@tonic-gate #else
667c478bd9Sstevel@tonic-gate char *__progname;
677c478bd9Sstevel@tonic-gate #endif
687c478bd9Sstevel@tonic-gate
6990685d2cSjp161948
7090685d2cSjp161948 /* File to read commands from */
717c478bd9Sstevel@tonic-gate FILE* infile;
7290685d2cSjp161948
7390685d2cSjp161948 /* Are we in batchfile mode? */
7490685d2cSjp161948 int batchmode = 0;
7590685d2cSjp161948
7690685d2cSjp161948 /* Size of buffer used when copying files */
777c478bd9Sstevel@tonic-gate size_t copy_buffer_len = 32768;
7890685d2cSjp161948
7990685d2cSjp161948 /* Number of concurrent outstanding requests */
807c478bd9Sstevel@tonic-gate size_t num_requests = 16;
817c478bd9Sstevel@tonic-gate
8290685d2cSjp161948 /* PID of ssh transport process */
8390685d2cSjp161948 static pid_t sshpid = -1;
8490685d2cSjp161948
8590685d2cSjp161948 /* This is set to 0 if the progressmeter is not desired. */
8690685d2cSjp161948 int showprogress = 1;
8790685d2cSjp161948
8890685d2cSjp161948 /* SIGINT received during command processing */
8990685d2cSjp161948 volatile sig_atomic_t interrupted = 0;
9090685d2cSjp161948
9190685d2cSjp161948 /* I wish qsort() took a separate ctx for the comparison function...*/
9290685d2cSjp161948 int sort_flag;
9390685d2cSjp161948
9490685d2cSjp161948 int remote_glob(struct sftp_conn *, const char *, int,
9590685d2cSjp161948 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
9690685d2cSjp161948
9790685d2cSjp161948 /* Separators for interactive commands */
9890685d2cSjp161948 #define WHITESPACE " \t\r\n"
9990685d2cSjp161948
10090685d2cSjp161948 /* ls flags */
10190685d2cSjp161948 #define LS_LONG_VIEW 0x01 /* Full view ala ls -l */
10290685d2cSjp161948 #define LS_SHORT_VIEW 0x02 /* Single row view ala ls -1 */
10390685d2cSjp161948 #define LS_NUMERIC_VIEW 0x04 /* Long view with numeric uid/gid */
10490685d2cSjp161948 #define LS_NAME_SORT 0x08 /* Sort by name (default) */
10590685d2cSjp161948 #define LS_TIME_SORT 0x10 /* Sort by mtime */
10690685d2cSjp161948 #define LS_SIZE_SORT 0x20 /* Sort by file size */
10790685d2cSjp161948 #define LS_REVERSE_SORT 0x40 /* Reverse sort order */
10890685d2cSjp161948 #define LS_SHOW_ALL 0x80 /* Don't skip filenames starting with '.' */
10990685d2cSjp161948
11090685d2cSjp161948 #define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW)
11190685d2cSjp161948 #define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
11290685d2cSjp161948
11390685d2cSjp161948 /* Commands for interactive mode */
11490685d2cSjp161948 #define I_CHDIR 1
11590685d2cSjp161948 #define I_CHGRP 2
11690685d2cSjp161948 #define I_CHMOD 3
11790685d2cSjp161948 #define I_CHOWN 4
11890685d2cSjp161948 #define I_GET 5
11990685d2cSjp161948 #define I_HELP 6
12090685d2cSjp161948 #define I_LCHDIR 7
12190685d2cSjp161948 #define I_LLS 8
12290685d2cSjp161948 #define I_LMKDIR 9
12390685d2cSjp161948 #define I_LPWD 10
12490685d2cSjp161948 #define I_LS 11
12590685d2cSjp161948 #define I_LUMASK 12
12690685d2cSjp161948 #define I_MKDIR 13
12790685d2cSjp161948 #define I_PUT 14
12890685d2cSjp161948 #define I_PWD 15
12990685d2cSjp161948 #define I_QUIT 16
13090685d2cSjp161948 #define I_RENAME 17
13190685d2cSjp161948 #define I_RM 18
13290685d2cSjp161948 #define I_RMDIR 19
13390685d2cSjp161948 #define I_SHELL 20
13490685d2cSjp161948 #define I_SYMLINK 21
13590685d2cSjp161948 #define I_VERSION 22
13690685d2cSjp161948 #define I_PROGRESS 23
13790685d2cSjp161948
13890685d2cSjp161948 struct CMD {
13990685d2cSjp161948 const char *c;
14090685d2cSjp161948 const int n;
14190685d2cSjp161948 };
14290685d2cSjp161948
14390685d2cSjp161948 static const struct CMD cmds[] = {
14490685d2cSjp161948 { "bye", I_QUIT },
14590685d2cSjp161948 { "cd", I_CHDIR },
14690685d2cSjp161948 { "chdir", I_CHDIR },
14790685d2cSjp161948 { "chgrp", I_CHGRP },
14890685d2cSjp161948 { "chmod", I_CHMOD },
14990685d2cSjp161948 { "chown", I_CHOWN },
15090685d2cSjp161948 { "dir", I_LS },
15190685d2cSjp161948 { "exit", I_QUIT },
15290685d2cSjp161948 { "get", I_GET },
15390685d2cSjp161948 { "mget", I_GET },
15490685d2cSjp161948 { "help", I_HELP },
15590685d2cSjp161948 { "lcd", I_LCHDIR },
15690685d2cSjp161948 { "lchdir", I_LCHDIR },
15790685d2cSjp161948 { "lls", I_LLS },
15890685d2cSjp161948 { "lmkdir", I_LMKDIR },
15990685d2cSjp161948 { "ln", I_SYMLINK },
16090685d2cSjp161948 { "lpwd", I_LPWD },
16190685d2cSjp161948 { "ls", I_LS },
16290685d2cSjp161948 { "lumask", I_LUMASK },
16390685d2cSjp161948 { "mkdir", I_MKDIR },
16490685d2cSjp161948 { "progress", I_PROGRESS },
16590685d2cSjp161948 { "put", I_PUT },
16690685d2cSjp161948 { "mput", I_PUT },
16790685d2cSjp161948 { "pwd", I_PWD },
16890685d2cSjp161948 { "quit", I_QUIT },
16990685d2cSjp161948 { "rename", I_RENAME },
17090685d2cSjp161948 { "rm", I_RM },
17190685d2cSjp161948 { "rmdir", I_RMDIR },
17290685d2cSjp161948 { "symlink", I_SYMLINK },
17390685d2cSjp161948 { "version", I_VERSION },
17490685d2cSjp161948 { "!", I_SHELL },
17590685d2cSjp161948 { "?", I_HELP },
17690685d2cSjp161948 { NULL, -1}
17790685d2cSjp161948 };
17890685d2cSjp161948
17990685d2cSjp161948 int interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
18090685d2cSjp161948
18190685d2cSjp161948 /* ARGSUSED */
1827c478bd9Sstevel@tonic-gate static void
killchild(int signo)18390685d2cSjp161948 killchild(int signo)
18490685d2cSjp161948 {
18590685d2cSjp161948 if (sshpid > 1) {
18690685d2cSjp161948 kill(sshpid, SIGTERM);
18790685d2cSjp161948 waitpid(sshpid, NULL, 0);
18890685d2cSjp161948 }
18990685d2cSjp161948
19090685d2cSjp161948 _exit(1);
19190685d2cSjp161948 }
19290685d2cSjp161948
19390685d2cSjp161948 /* ARGSUSED */
19490685d2cSjp161948 static void
cmd_interrupt(int signo)19590685d2cSjp161948 cmd_interrupt(int signo)
19690685d2cSjp161948 {
19790685d2cSjp161948 const char msg[] = "\rInterrupt \n";
19890685d2cSjp161948 int olderrno = errno;
19990685d2cSjp161948
20090685d2cSjp161948 write(STDERR_FILENO, msg, sizeof(msg) - 1);
20190685d2cSjp161948 interrupted = 1;
20290685d2cSjp161948 errno = olderrno;
20390685d2cSjp161948 }
20490685d2cSjp161948
20590685d2cSjp161948 static void
help(void)20690685d2cSjp161948 help(void)
20790685d2cSjp161948 {
20890685d2cSjp161948 printf(gettext("Available commands:\n"
20990685d2cSjp161948 "cd path Change remote directory to 'path'\n"
21090685d2cSjp161948 "lcd path Change local directory to 'path'\n"
21190685d2cSjp161948 "chgrp grp path Change group of file 'path' to 'grp'\n"
21290685d2cSjp161948 "chmod mode path Change permissions of file 'path' to 'mode'\n"
21390685d2cSjp161948 "chown own path Change owner of file 'path' to 'own'\n"
21490685d2cSjp161948 "help Display this help text\n"
21590685d2cSjp161948 "get remote-path [local-path] Download file\n"
21690685d2cSjp161948 "lls [ls-options [path]] Display local directory listing\n"
21790685d2cSjp161948 "ln oldpath newpath Symlink remote file\n"
21890685d2cSjp161948 "lmkdir path Create local directory\n"
21990685d2cSjp161948 "lpwd Print local working directory\n"
22090685d2cSjp161948 "ls [path] Display remote directory listing\n"
22190685d2cSjp161948 "lumask umask Set local umask to 'umask'\n"
22290685d2cSjp161948 "mkdir path Create remote directory\n"
22390685d2cSjp161948 "progress Toggle display of progress meter\n"
22490685d2cSjp161948 "put local-path [remote-path] Upload file\n"
22590685d2cSjp161948 "pwd Display remote working directory\n"
22690685d2cSjp161948 "exit Quit sftp\n"
22790685d2cSjp161948 "quit Quit sftp\n"
22890685d2cSjp161948 "rename oldpath newpath Rename remote file\n"
22990685d2cSjp161948 "rmdir path Remove remote directory\n"
23090685d2cSjp161948 "rm path Delete remote file\n"
23190685d2cSjp161948 "symlink oldpath newpath Symlink remote file\n"
23290685d2cSjp161948 "version Show SFTP version\n"
23390685d2cSjp161948 "!command Execute 'command' in local shell\n"
23490685d2cSjp161948 "! Escape to local shell\n"
23590685d2cSjp161948 "? Synonym for help\n"));
23690685d2cSjp161948 }
23790685d2cSjp161948
23890685d2cSjp161948 static void
local_do_shell(const char * args)23990685d2cSjp161948 local_do_shell(const char *args)
24090685d2cSjp161948 {
24190685d2cSjp161948 int status;
24290685d2cSjp161948 char *shell;
24390685d2cSjp161948 pid_t pid;
24490685d2cSjp161948
24590685d2cSjp161948 if (!*args)
24690685d2cSjp161948 args = NULL;
24790685d2cSjp161948
24890685d2cSjp161948 if ((shell = getenv("SHELL")) == NULL)
24990685d2cSjp161948 shell = _PATH_BSHELL;
25090685d2cSjp161948
25190685d2cSjp161948 if ((pid = fork()) == -1)
25290685d2cSjp161948 fatal("Couldn't fork: %s", strerror(errno));
25390685d2cSjp161948
25490685d2cSjp161948 if (pid == 0) {
25590685d2cSjp161948 /* XXX: child has pipe fds to ssh subproc open - issue? */
25690685d2cSjp161948 if (args) {
25790685d2cSjp161948 debug3("Executing %s -c \"%s\"", shell, args);
25890685d2cSjp161948 execl(shell, shell, "-c", args, (char *)NULL);
25990685d2cSjp161948 } else {
26090685d2cSjp161948 debug3("Executing %s", shell);
26190685d2cSjp161948 execl(shell, shell, (char *)NULL);
26290685d2cSjp161948 }
26390685d2cSjp161948 fprintf(stderr, gettext("Couldn't execute \"%s\": %s\n"), shell,
26490685d2cSjp161948 strerror(errno));
26590685d2cSjp161948 _exit(1);
26690685d2cSjp161948 }
26790685d2cSjp161948 while (waitpid(pid, &status, 0) == -1)
26890685d2cSjp161948 if (errno != EINTR)
26990685d2cSjp161948 fatal("Couldn't wait for child: %s", strerror(errno));
27090685d2cSjp161948 if (!WIFEXITED(status))
27190685d2cSjp161948 error("Shell exited abnormally");
27290685d2cSjp161948 else if (WEXITSTATUS(status))
27390685d2cSjp161948 error("Shell exited with status %d", WEXITSTATUS(status));
27490685d2cSjp161948 }
27590685d2cSjp161948
27690685d2cSjp161948 static void
local_do_ls(const char * args)27790685d2cSjp161948 local_do_ls(const char *args)
27890685d2cSjp161948 {
27990685d2cSjp161948 if (!args || !*args)
28090685d2cSjp161948 local_do_shell(_PATH_LS);
28190685d2cSjp161948 else {
28290685d2cSjp161948 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
28390685d2cSjp161948 char *buf = xmalloc(len);
28490685d2cSjp161948
28590685d2cSjp161948 /* XXX: quoting - rip quoting code from ftp? */
28690685d2cSjp161948 snprintf(buf, len, _PATH_LS " %s", args);
28790685d2cSjp161948 local_do_shell(buf);
28890685d2cSjp161948 xfree(buf);
28990685d2cSjp161948 }
29090685d2cSjp161948 }
29190685d2cSjp161948
29290685d2cSjp161948 /* Strip one path (usually the pwd) from the start of another */
29390685d2cSjp161948 static char *
path_strip(char * path,char * strip)29490685d2cSjp161948 path_strip(char *path, char *strip)
29590685d2cSjp161948 {
29690685d2cSjp161948 size_t len;
29790685d2cSjp161948
29890685d2cSjp161948 if (strip == NULL)
29990685d2cSjp161948 return (xstrdup(path));
30090685d2cSjp161948
30190685d2cSjp161948 len = strlen(strip);
30290685d2cSjp161948 if (strncmp(path, strip, len) == 0) {
30390685d2cSjp161948 if (strip[len - 1] != '/' && path[len] == '/')
30490685d2cSjp161948 len++;
30590685d2cSjp161948 return (xstrdup(path + len));
30690685d2cSjp161948 }
30790685d2cSjp161948
30890685d2cSjp161948 return (xstrdup(path));
30990685d2cSjp161948 }
31090685d2cSjp161948
31190685d2cSjp161948 static char *
path_append(char * p1,char * p2)31290685d2cSjp161948 path_append(char *p1, char *p2)
31390685d2cSjp161948 {
31490685d2cSjp161948 char *ret;
31590685d2cSjp161948 size_t len = strlen(p1) + strlen(p2) + 2;
31690685d2cSjp161948
31790685d2cSjp161948 ret = xmalloc(len);
31890685d2cSjp161948 strlcpy(ret, p1, len);
31990685d2cSjp161948 if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
32090685d2cSjp161948 strlcat(ret, "/", len);
32190685d2cSjp161948 strlcat(ret, p2, len);
32290685d2cSjp161948
32390685d2cSjp161948 return(ret);
32490685d2cSjp161948 }
32590685d2cSjp161948
32690685d2cSjp161948 static char *
make_absolute(char * p,char * pwd)32790685d2cSjp161948 make_absolute(char *p, char *pwd)
32890685d2cSjp161948 {
32990685d2cSjp161948 char *abs_str;
33090685d2cSjp161948
33190685d2cSjp161948 /* Derelativise */
33290685d2cSjp161948 if (p && p[0] != '/') {
33390685d2cSjp161948 abs_str = path_append(pwd, p);
33490685d2cSjp161948 xfree(p);
33590685d2cSjp161948 return(abs_str);
33690685d2cSjp161948 } else
33790685d2cSjp161948 return(p);
33890685d2cSjp161948 }
33990685d2cSjp161948
34090685d2cSjp161948 static int
infer_path(const char * p,char ** ifp)34190685d2cSjp161948 infer_path(const char *p, char **ifp)
34290685d2cSjp161948 {
34390685d2cSjp161948 char *cp;
34490685d2cSjp161948
34590685d2cSjp161948 cp = strrchr(p, '/');
34690685d2cSjp161948 if (cp == NULL) {
34790685d2cSjp161948 *ifp = xstrdup(p);
34890685d2cSjp161948 return(0);
34990685d2cSjp161948 }
35090685d2cSjp161948
35190685d2cSjp161948 if (!cp[1]) {
35290685d2cSjp161948 error("Invalid path");
35390685d2cSjp161948 return(-1);
35490685d2cSjp161948 }
35590685d2cSjp161948
35690685d2cSjp161948 *ifp = xstrdup(cp + 1);
35790685d2cSjp161948 return(0);
35890685d2cSjp161948 }
35990685d2cSjp161948
36090685d2cSjp161948 static int
parse_getput_flags(const char ** cpp,int * pflag)36190685d2cSjp161948 parse_getput_flags(const char **cpp, int *pflag)
36290685d2cSjp161948 {
36390685d2cSjp161948 const char *cp = *cpp;
36490685d2cSjp161948
36590685d2cSjp161948 /* Check for flags */
36690685d2cSjp161948 if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
36790685d2cSjp161948 switch (cp[1]) {
36890685d2cSjp161948 case 'p':
36990685d2cSjp161948 case 'P':
37090685d2cSjp161948 *pflag = 1;
37190685d2cSjp161948 break;
37290685d2cSjp161948 default:
37390685d2cSjp161948 error("Invalid flag -%c", cp[1]);
37490685d2cSjp161948 return(-1);
37590685d2cSjp161948 }
37690685d2cSjp161948 cp += 2;
37790685d2cSjp161948 *cpp = cp + strspn(cp, WHITESPACE);
37890685d2cSjp161948 }
37990685d2cSjp161948
38090685d2cSjp161948 return(0);
38190685d2cSjp161948 }
38290685d2cSjp161948
38390685d2cSjp161948 static int
parse_ls_flags(const char ** cpp,int * lflag)38490685d2cSjp161948 parse_ls_flags(const char **cpp, int *lflag)
38590685d2cSjp161948 {
38690685d2cSjp161948 const char *cp = *cpp;
38790685d2cSjp161948
38890685d2cSjp161948 /* Defaults */
38990685d2cSjp161948 *lflag = LS_NAME_SORT;
39090685d2cSjp161948
39190685d2cSjp161948 /* Check for flags */
39290685d2cSjp161948 if (cp++[0] == '-') {
39390685d2cSjp161948 for (; strchr(WHITESPACE, *cp) == NULL; cp++) {
39490685d2cSjp161948 switch (*cp) {
39590685d2cSjp161948 case 'l':
39690685d2cSjp161948 *lflag &= ~VIEW_FLAGS;
39790685d2cSjp161948 *lflag |= LS_LONG_VIEW;
39890685d2cSjp161948 break;
39990685d2cSjp161948 case '1':
40090685d2cSjp161948 *lflag &= ~VIEW_FLAGS;
40190685d2cSjp161948 *lflag |= LS_SHORT_VIEW;
40290685d2cSjp161948 break;
40390685d2cSjp161948 case 'n':
40490685d2cSjp161948 *lflag &= ~VIEW_FLAGS;
40590685d2cSjp161948 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
40690685d2cSjp161948 break;
40790685d2cSjp161948 case 'S':
40890685d2cSjp161948 *lflag &= ~SORT_FLAGS;
40990685d2cSjp161948 *lflag |= LS_SIZE_SORT;
41090685d2cSjp161948 break;
41190685d2cSjp161948 case 't':
41290685d2cSjp161948 *lflag &= ~SORT_FLAGS;
41390685d2cSjp161948 *lflag |= LS_TIME_SORT;
41490685d2cSjp161948 break;
41590685d2cSjp161948 case 'r':
41690685d2cSjp161948 *lflag |= LS_REVERSE_SORT;
41790685d2cSjp161948 break;
41890685d2cSjp161948 case 'f':
41990685d2cSjp161948 *lflag &= ~SORT_FLAGS;
42090685d2cSjp161948 break;
42190685d2cSjp161948 case 'a':
42290685d2cSjp161948 *lflag |= LS_SHOW_ALL;
42390685d2cSjp161948 break;
42490685d2cSjp161948 default:
42590685d2cSjp161948 error("Invalid flag -%c", *cp);
42690685d2cSjp161948 return(-1);
42790685d2cSjp161948 }
42890685d2cSjp161948 }
42990685d2cSjp161948 *cpp = cp + strspn(cp, WHITESPACE);
43090685d2cSjp161948 }
43190685d2cSjp161948
43290685d2cSjp161948 return(0);
43390685d2cSjp161948 }
43490685d2cSjp161948
43590685d2cSjp161948 static int
get_pathname(const char ** cpp,char ** path)43690685d2cSjp161948 get_pathname(const char **cpp, char **path)
43790685d2cSjp161948 {
43890685d2cSjp161948 const char *cp = *cpp, *end;
43990685d2cSjp161948 char quot;
44090685d2cSjp161948 u_int i, j;
44190685d2cSjp161948
44290685d2cSjp161948 cp += strspn(cp, WHITESPACE);
44390685d2cSjp161948 if (!*cp) {
44490685d2cSjp161948 *cpp = cp;
44590685d2cSjp161948 *path = NULL;
44690685d2cSjp161948 return (0);
44790685d2cSjp161948 }
44890685d2cSjp161948
44990685d2cSjp161948 *path = xmalloc(strlen(cp) + 1);
45090685d2cSjp161948
45190685d2cSjp161948 /* Check for quoted filenames */
45290685d2cSjp161948 if (*cp == '\"' || *cp == '\'') {
45390685d2cSjp161948 quot = *cp++;
45490685d2cSjp161948
45590685d2cSjp161948 /* Search for terminating quote, unescape some chars */
45690685d2cSjp161948 for (i = j = 0; i <= strlen(cp); i++) {
45790685d2cSjp161948 if (cp[i] == quot) { /* Found quote */
45890685d2cSjp161948 i++;
45990685d2cSjp161948 (*path)[j] = '\0';
46090685d2cSjp161948 break;
46190685d2cSjp161948 }
46290685d2cSjp161948 if (cp[i] == '\0') { /* End of string */
46390685d2cSjp161948 error("Unterminated quote");
46490685d2cSjp161948 goto fail;
46590685d2cSjp161948 }
46690685d2cSjp161948 if (cp[i] == '\\') { /* Escaped characters */
46790685d2cSjp161948 i++;
46890685d2cSjp161948 if (cp[i] != '\'' && cp[i] != '\"' &&
46990685d2cSjp161948 cp[i] != '\\') {
47090685d2cSjp161948 error("Bad escaped character '\\%c'",
47190685d2cSjp161948 cp[i]);
47290685d2cSjp161948 goto fail;
47390685d2cSjp161948 }
47490685d2cSjp161948 }
47590685d2cSjp161948 (*path)[j++] = cp[i];
47690685d2cSjp161948 }
47790685d2cSjp161948
47890685d2cSjp161948 if (j == 0) {
47990685d2cSjp161948 error("Empty quotes");
48090685d2cSjp161948 goto fail;
48190685d2cSjp161948 }
48290685d2cSjp161948 *cpp = cp + i + strspn(cp + i, WHITESPACE);
48390685d2cSjp161948 } else {
48490685d2cSjp161948 /* Read to end of filename */
48590685d2cSjp161948 end = strpbrk(cp, WHITESPACE);
48690685d2cSjp161948 if (end == NULL)
48790685d2cSjp161948 end = strchr(cp, '\0');
48890685d2cSjp161948 *cpp = end + strspn(end, WHITESPACE);
48990685d2cSjp161948
49090685d2cSjp161948 memcpy(*path, cp, end - cp);
49190685d2cSjp161948 (*path)[end - cp] = '\0';
49290685d2cSjp161948 }
49390685d2cSjp161948 return (0);
49490685d2cSjp161948
49590685d2cSjp161948 fail:
49690685d2cSjp161948 xfree(*path);
49790685d2cSjp161948 *path = NULL;
49890685d2cSjp161948 return (-1);
49990685d2cSjp161948 }
50090685d2cSjp161948
50190685d2cSjp161948 static int
is_dir(char * path)50290685d2cSjp161948 is_dir(char *path)
50390685d2cSjp161948 {
50490685d2cSjp161948 struct stat sb;
50590685d2cSjp161948
50690685d2cSjp161948 /* XXX: report errors? */
50790685d2cSjp161948 if (stat(path, &sb) == -1)
50890685d2cSjp161948 return(0);
50990685d2cSjp161948
51090685d2cSjp161948 return(S_ISDIR(sb.st_mode));
51190685d2cSjp161948 }
51290685d2cSjp161948
51390685d2cSjp161948 static int
is_reg(char * path)51490685d2cSjp161948 is_reg(char *path)
51590685d2cSjp161948 {
51690685d2cSjp161948 struct stat sb;
51790685d2cSjp161948
51890685d2cSjp161948 if (stat(path, &sb) == -1)
51990685d2cSjp161948 fatal("stat %s: %s", path, strerror(errno));
52090685d2cSjp161948
52190685d2cSjp161948 return(S_ISREG(sb.st_mode));
52290685d2cSjp161948 }
52390685d2cSjp161948
52490685d2cSjp161948 static int
remote_is_dir(struct sftp_conn * conn,char * path)52590685d2cSjp161948 remote_is_dir(struct sftp_conn *conn, char *path)
52690685d2cSjp161948 {
52790685d2cSjp161948 Attrib *a;
52890685d2cSjp161948
52990685d2cSjp161948 /* XXX: report errors? */
53090685d2cSjp161948 if ((a = do_stat(conn, path, 1)) == NULL)
53190685d2cSjp161948 return(0);
53290685d2cSjp161948 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
53390685d2cSjp161948 return(0);
53490685d2cSjp161948 return(S_ISDIR(a->perm));
53590685d2cSjp161948 }
53690685d2cSjp161948
53790685d2cSjp161948 static int
process_get(struct sftp_conn * conn,char * src,char * dst,char * pwd,int pflag)53890685d2cSjp161948 process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
53990685d2cSjp161948 {
54090685d2cSjp161948 char *abs_src = NULL;
54190685d2cSjp161948 char *abs_dst = NULL;
54290685d2cSjp161948 char *tmp;
54390685d2cSjp161948 glob_t g;
54490685d2cSjp161948 int err = 0;
54590685d2cSjp161948 int i;
54690685d2cSjp161948
54790685d2cSjp161948 abs_src = xstrdup(src);
54890685d2cSjp161948 abs_src = make_absolute(abs_src, pwd);
54990685d2cSjp161948
55090685d2cSjp161948 memset(&g, 0, sizeof(g));
55190685d2cSjp161948 debug3("Looking up %s", abs_src);
55290685d2cSjp161948 if (remote_glob(conn, abs_src, 0, NULL, &g)) {
55390685d2cSjp161948 error("File \"%s\" not found.", abs_src);
55490685d2cSjp161948 err = -1;
55590685d2cSjp161948 goto out;
55690685d2cSjp161948 }
55790685d2cSjp161948
55890685d2cSjp161948 /* If multiple matches, dst must be a directory or unspecified */
55990685d2cSjp161948 if (g.gl_matchc > 1 && dst && !is_dir(dst)) {
56090685d2cSjp161948 error("Multiple files match, but \"%s\" is not a directory",
56190685d2cSjp161948 dst);
56290685d2cSjp161948 err = -1;
56390685d2cSjp161948 goto out;
56490685d2cSjp161948 }
56590685d2cSjp161948
56690685d2cSjp161948 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
56790685d2cSjp161948 if (infer_path(g.gl_pathv[i], &tmp)) {
56890685d2cSjp161948 err = -1;
56990685d2cSjp161948 goto out;
57090685d2cSjp161948 }
57190685d2cSjp161948
57290685d2cSjp161948 if (g.gl_matchc == 1 && dst) {
57390685d2cSjp161948 /* If directory specified, append filename */
57490685d2cSjp161948 xfree(tmp);
57590685d2cSjp161948 if (is_dir(dst)) {
57690685d2cSjp161948 if (infer_path(g.gl_pathv[0], &tmp)) {
57790685d2cSjp161948 err = 1;
57890685d2cSjp161948 goto out;
57990685d2cSjp161948 }
58090685d2cSjp161948 abs_dst = path_append(dst, tmp);
58190685d2cSjp161948 xfree(tmp);
58290685d2cSjp161948 } else
58390685d2cSjp161948 abs_dst = xstrdup(dst);
58490685d2cSjp161948 } else if (dst) {
58590685d2cSjp161948 abs_dst = path_append(dst, tmp);
58690685d2cSjp161948 xfree(tmp);
58790685d2cSjp161948 } else
58890685d2cSjp161948 abs_dst = tmp;
58990685d2cSjp161948
59090685d2cSjp161948 printf(gettext("Fetching %s to %s\n"), g.gl_pathv[i], abs_dst);
59190685d2cSjp161948 if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
59290685d2cSjp161948 err = -1;
59390685d2cSjp161948 xfree(abs_dst);
59490685d2cSjp161948 abs_dst = NULL;
59590685d2cSjp161948 }
59690685d2cSjp161948
59790685d2cSjp161948 out:
59890685d2cSjp161948 xfree(abs_src);
59990685d2cSjp161948 globfree(&g);
60090685d2cSjp161948 return(err);
60190685d2cSjp161948 }
60290685d2cSjp161948
60390685d2cSjp161948 static int
process_put(struct sftp_conn * conn,char * src,char * dst,char * pwd,int pflag)60490685d2cSjp161948 process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
60590685d2cSjp161948 {
60690685d2cSjp161948 char *tmp_dst = NULL;
60790685d2cSjp161948 char *abs_dst = NULL;
60890685d2cSjp161948 char *tmp;
60990685d2cSjp161948 glob_t g;
61090685d2cSjp161948 int err = 0;
61190685d2cSjp161948 int i;
61290685d2cSjp161948
61390685d2cSjp161948 if (dst) {
61490685d2cSjp161948 tmp_dst = xstrdup(dst);
61590685d2cSjp161948 tmp_dst = make_absolute(tmp_dst, pwd);
61690685d2cSjp161948 }
61790685d2cSjp161948
61890685d2cSjp161948 memset(&g, 0, sizeof(g));
61990685d2cSjp161948 debug3("Looking up %s", src);
620*cf58b254SGary Mills if (glob(src, GLOB_LIMIT, NULL, &g)) {
62190685d2cSjp161948 error("File \"%s\" not found.", src);
62290685d2cSjp161948 err = -1;
62390685d2cSjp161948 goto out;
62490685d2cSjp161948 }
62590685d2cSjp161948
62690685d2cSjp161948 /* If multiple matches, dst may be directory or unspecified */
62790685d2cSjp161948 if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) {
62890685d2cSjp161948 error("Multiple files match, but \"%s\" is not a directory",
62990685d2cSjp161948 tmp_dst);
63090685d2cSjp161948 err = -1;
63190685d2cSjp161948 goto out;
63290685d2cSjp161948 }
63390685d2cSjp161948
63490685d2cSjp161948 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
63590685d2cSjp161948 if (!is_reg(g.gl_pathv[i])) {
63690685d2cSjp161948 error("skipping non-regular file %s",
63790685d2cSjp161948 g.gl_pathv[i]);
63890685d2cSjp161948 continue;
63990685d2cSjp161948 }
64090685d2cSjp161948 if (infer_path(g.gl_pathv[i], &tmp)) {
64190685d2cSjp161948 err = -1;
64290685d2cSjp161948 goto out;
64390685d2cSjp161948 }
64490685d2cSjp161948
64590685d2cSjp161948 if (g.gl_matchc == 1 && tmp_dst) {
64690685d2cSjp161948 /* If directory specified, append filename */
64790685d2cSjp161948 if (remote_is_dir(conn, tmp_dst)) {
64890685d2cSjp161948 if (infer_path(g.gl_pathv[0], &tmp)) {
64990685d2cSjp161948 err = 1;
65090685d2cSjp161948 goto out;
65190685d2cSjp161948 }
65290685d2cSjp161948 abs_dst = path_append(tmp_dst, tmp);
65390685d2cSjp161948 xfree(tmp);
65490685d2cSjp161948 } else
65590685d2cSjp161948 abs_dst = xstrdup(tmp_dst);
65690685d2cSjp161948
65790685d2cSjp161948 } else if (tmp_dst) {
65890685d2cSjp161948 abs_dst = path_append(tmp_dst, tmp);
65990685d2cSjp161948 xfree(tmp);
66090685d2cSjp161948 } else
66190685d2cSjp161948 abs_dst = make_absolute(tmp, pwd);
66290685d2cSjp161948
66390685d2cSjp161948 printf(gettext("Uploading %s to %s\n"), g.gl_pathv[i], abs_dst);
66490685d2cSjp161948 if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
66590685d2cSjp161948 err = -1;
66690685d2cSjp161948 }
66790685d2cSjp161948
66890685d2cSjp161948 out:
66990685d2cSjp161948 if (abs_dst)
67090685d2cSjp161948 xfree(abs_dst);
67190685d2cSjp161948 if (tmp_dst)
67290685d2cSjp161948 xfree(tmp_dst);
67390685d2cSjp161948 globfree(&g);
67490685d2cSjp161948 return(err);
67590685d2cSjp161948 }
67690685d2cSjp161948
67790685d2cSjp161948 static int
sdirent_comp(const void * aa,const void * bb)67890685d2cSjp161948 sdirent_comp(const void *aa, const void *bb)
67990685d2cSjp161948 {
68090685d2cSjp161948 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
68190685d2cSjp161948 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
68290685d2cSjp161948 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
68390685d2cSjp161948
68490685d2cSjp161948 #define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
68590685d2cSjp161948 if (sort_flag & LS_NAME_SORT)
68690685d2cSjp161948 return (rmul * strcmp(a->filename, b->filename));
68790685d2cSjp161948 else if (sort_flag & LS_TIME_SORT)
68890685d2cSjp161948 return (rmul * NCMP(a->a.mtime, b->a.mtime));
68990685d2cSjp161948 else if (sort_flag & LS_SIZE_SORT)
69090685d2cSjp161948 return (rmul * NCMP(a->a.size, b->a.size));
69190685d2cSjp161948
69290685d2cSjp161948 fatal("Unknown ls sort type");
69390685d2cSjp161948
69490685d2cSjp161948 /* NOTREACHED */
69590685d2cSjp161948 return (0);
69690685d2cSjp161948 }
69790685d2cSjp161948
69890685d2cSjp161948 /* sftp ls.1 replacement for directories */
69990685d2cSjp161948 static int
do_ls_dir(struct sftp_conn * conn,char * path,char * strip_path,int lflag)70090685d2cSjp161948 do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
70190685d2cSjp161948 {
70290685d2cSjp161948 int n;
70390685d2cSjp161948 u_int c = 1, colspace = 0, columns = 1;
70490685d2cSjp161948 SFTP_DIRENT **d;
70590685d2cSjp161948
70690685d2cSjp161948 if ((n = do_readdir(conn, path, &d)) != 0)
70790685d2cSjp161948 return (n);
70890685d2cSjp161948
70990685d2cSjp161948 if (!(lflag & LS_SHORT_VIEW)) {
71090685d2cSjp161948 u_int m = 0, width = 80;
71190685d2cSjp161948 struct winsize ws;
71290685d2cSjp161948 char *tmp;
71390685d2cSjp161948
71490685d2cSjp161948 /* Count entries for sort and find longest filename */
71590685d2cSjp161948 for (n = 0; d[n] != NULL; n++) {
71690685d2cSjp161948 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
71790685d2cSjp161948 m = MAX(m, strlen(d[n]->filename));
71890685d2cSjp161948 }
71990685d2cSjp161948
72090685d2cSjp161948 /* Add any subpath that also needs to be counted */
72190685d2cSjp161948 tmp = path_strip(path, strip_path);
72290685d2cSjp161948 m += strlen(tmp);
72390685d2cSjp161948 xfree(tmp);
72490685d2cSjp161948
72590685d2cSjp161948 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
72690685d2cSjp161948 width = ws.ws_col;
72790685d2cSjp161948
72890685d2cSjp161948 columns = width / (m + 2);
72990685d2cSjp161948 columns = MAX(columns, 1);
73090685d2cSjp161948 colspace = width / columns;
73190685d2cSjp161948 colspace = MIN(colspace, width);
73290685d2cSjp161948 }
73390685d2cSjp161948
73490685d2cSjp161948 if (lflag & SORT_FLAGS) {
73590685d2cSjp161948 for (n = 0; d[n] != NULL; n++)
73690685d2cSjp161948 ; /* count entries */
73790685d2cSjp161948 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
73890685d2cSjp161948 qsort(d, n, sizeof(*d), sdirent_comp);
73990685d2cSjp161948 }
74090685d2cSjp161948
74190685d2cSjp161948 for (n = 0; d[n] != NULL && !interrupted; n++) {
74290685d2cSjp161948 char *tmp, *fname;
74390685d2cSjp161948
74490685d2cSjp161948 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
74590685d2cSjp161948 continue;
74690685d2cSjp161948
74790685d2cSjp161948 tmp = path_append(path, d[n]->filename);
74890685d2cSjp161948 fname = path_strip(tmp, strip_path);
74990685d2cSjp161948 xfree(tmp);
75090685d2cSjp161948
75190685d2cSjp161948 if (lflag & LS_LONG_VIEW) {
75290685d2cSjp161948 if (lflag & LS_NUMERIC_VIEW) {
75390685d2cSjp161948 char *lname;
75490685d2cSjp161948 struct stat sb;
75590685d2cSjp161948
75690685d2cSjp161948 memset(&sb, 0, sizeof(sb));
75790685d2cSjp161948 attrib_to_stat(&d[n]->a, &sb);
75890685d2cSjp161948 lname = ls_file(fname, &sb, 1);
75990685d2cSjp161948 printf("%s\n", lname);
76090685d2cSjp161948 xfree(lname);
76190685d2cSjp161948 } else
76290685d2cSjp161948 printf("%s\n", d[n]->longname);
76390685d2cSjp161948 } else {
76490685d2cSjp161948 printf("%-*s", colspace, fname);
76590685d2cSjp161948 if (c >= columns) {
76690685d2cSjp161948 printf("\n");
76790685d2cSjp161948 c = 1;
76890685d2cSjp161948 } else
76990685d2cSjp161948 c++;
77090685d2cSjp161948 }
77190685d2cSjp161948
77290685d2cSjp161948 xfree(fname);
77390685d2cSjp161948 }
77490685d2cSjp161948
77590685d2cSjp161948 if (!(lflag & LS_LONG_VIEW) && (c != 1))
77690685d2cSjp161948 printf("\n");
77790685d2cSjp161948
77890685d2cSjp161948 free_sftp_dirents(d);
77990685d2cSjp161948 return (0);
78090685d2cSjp161948 }
78190685d2cSjp161948
78290685d2cSjp161948 /* sftp ls.1 replacement which handles path globs */
78390685d2cSjp161948 static int
do_globbed_ls(struct sftp_conn * conn,char * path,char * strip_path,int lflag)78490685d2cSjp161948 do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
78590685d2cSjp161948 int lflag)
78690685d2cSjp161948 {
78790685d2cSjp161948 glob_t g;
78890685d2cSjp161948 u_int i, c = 1, colspace = 0, columns = 1;
78990685d2cSjp161948 Attrib *a = NULL;
79090685d2cSjp161948
79190685d2cSjp161948 memset(&g, 0, sizeof(g));
79290685d2cSjp161948
79390685d2cSjp161948 if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
79490685d2cSjp161948 NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
79590685d2cSjp161948 if (g.gl_pathc)
79690685d2cSjp161948 globfree(&g);
79790685d2cSjp161948 error("Can't ls: \"%s\" not found", path);
79890685d2cSjp161948 return (-1);
79990685d2cSjp161948 }
80090685d2cSjp161948
80190685d2cSjp161948 if (interrupted)
80290685d2cSjp161948 goto out;
80390685d2cSjp161948
80490685d2cSjp161948 /*
80590685d2cSjp161948 * If the glob returns a single match and it is a directory,
80690685d2cSjp161948 * then just list its contents.
80790685d2cSjp161948 */
80890685d2cSjp161948 if (g.gl_matchc == 1) {
80990685d2cSjp161948 if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
81090685d2cSjp161948 globfree(&g);
81190685d2cSjp161948 return (-1);
81290685d2cSjp161948 }
81390685d2cSjp161948 if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
81490685d2cSjp161948 S_ISDIR(a->perm)) {
81590685d2cSjp161948 int err;
81690685d2cSjp161948
81790685d2cSjp161948 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
81890685d2cSjp161948 globfree(&g);
81990685d2cSjp161948 return (err);
82090685d2cSjp161948 }
82190685d2cSjp161948 }
82290685d2cSjp161948
82390685d2cSjp161948 if (!(lflag & LS_SHORT_VIEW)) {
82490685d2cSjp161948 u_int m = 0, width = 80;
82590685d2cSjp161948 struct winsize ws;
82690685d2cSjp161948
82790685d2cSjp161948 /* Count entries for sort and find longest filename */
82890685d2cSjp161948 for (i = 0; g.gl_pathv[i]; i++)
82990685d2cSjp161948 m = MAX(m, strlen(g.gl_pathv[i]));
83090685d2cSjp161948
83190685d2cSjp161948 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
83290685d2cSjp161948 width = ws.ws_col;
83390685d2cSjp161948
83490685d2cSjp161948 columns = width / (m + 2);
83590685d2cSjp161948 columns = MAX(columns, 1);
83690685d2cSjp161948 colspace = width / columns;
83790685d2cSjp161948 }
83890685d2cSjp161948
83990685d2cSjp161948 for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
84090685d2cSjp161948 char *fname;
84190685d2cSjp161948
84290685d2cSjp161948 fname = path_strip(g.gl_pathv[i], strip_path);
84390685d2cSjp161948
84490685d2cSjp161948 if (lflag & LS_LONG_VIEW) {
84590685d2cSjp161948 char *lname;
84690685d2cSjp161948 struct stat sb;
84790685d2cSjp161948
84890685d2cSjp161948 /*
84990685d2cSjp161948 * XXX: this is slow - 1 roundtrip per path
85090685d2cSjp161948 * A solution to this is to fork glob() and
85190685d2cSjp161948 * build a sftp specific version which keeps the
85290685d2cSjp161948 * attribs (which currently get thrown away)
85390685d2cSjp161948 * that the server returns as well as the filenames.
85490685d2cSjp161948 */
85590685d2cSjp161948 memset(&sb, 0, sizeof(sb));
85690685d2cSjp161948 if (a == NULL)
85790685d2cSjp161948 a = do_lstat(conn, g.gl_pathv[i], 1);
85890685d2cSjp161948 if (a != NULL)
85990685d2cSjp161948 attrib_to_stat(a, &sb);
86090685d2cSjp161948 lname = ls_file(fname, &sb, 1);
86190685d2cSjp161948 printf("%s\n", lname);
86290685d2cSjp161948 xfree(lname);
86390685d2cSjp161948 } else {
86490685d2cSjp161948 printf("%-*s", colspace, fname);
86590685d2cSjp161948 if (c >= columns) {
86690685d2cSjp161948 printf("\n");
86790685d2cSjp161948 c = 1;
86890685d2cSjp161948 } else
86990685d2cSjp161948 c++;
87090685d2cSjp161948 }
87190685d2cSjp161948 xfree(fname);
87290685d2cSjp161948 }
87390685d2cSjp161948
87490685d2cSjp161948 if (!(lflag & LS_LONG_VIEW) && (c != 1))
87590685d2cSjp161948 printf("\n");
87690685d2cSjp161948
87790685d2cSjp161948 out:
87890685d2cSjp161948 if (g.gl_pathc)
87990685d2cSjp161948 globfree(&g);
88090685d2cSjp161948
88190685d2cSjp161948 return (0);
88290685d2cSjp161948 }
88390685d2cSjp161948
88490685d2cSjp161948 static int
parse_args(const char ** cpp,int * pflag,int * lflag,int * iflag,unsigned long * n_arg,char ** path1,char ** path2)88590685d2cSjp161948 parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
88690685d2cSjp161948 unsigned long *n_arg, char **path1, char **path2)
88790685d2cSjp161948 {
88890685d2cSjp161948 const char *cmd, *cp = *cpp;
88990685d2cSjp161948 char *cp2;
89090685d2cSjp161948 int base = 0;
89190685d2cSjp161948 long l;
89290685d2cSjp161948 int i, cmdnum;
89390685d2cSjp161948
89490685d2cSjp161948 /* Skip leading whitespace */
89590685d2cSjp161948 cp = cp + strspn(cp, WHITESPACE);
89690685d2cSjp161948
89790685d2cSjp161948 /* Ignore blank lines and lines which begin with comment '#' char */
89890685d2cSjp161948 if (*cp == '\0' || *cp == '#')
89990685d2cSjp161948 return (0);
90090685d2cSjp161948
90190685d2cSjp161948 /* Check for leading '-' (disable error processing) */
90290685d2cSjp161948 *iflag = 0;
90390685d2cSjp161948 if (*cp == '-') {
90490685d2cSjp161948 *iflag = 1;
90590685d2cSjp161948 cp++;
90690685d2cSjp161948 }
90790685d2cSjp161948
90890685d2cSjp161948 /* Figure out which command we have */
90990685d2cSjp161948 for (i = 0; cmds[i].c; i++) {
91090685d2cSjp161948 int cmdlen = strlen(cmds[i].c);
91190685d2cSjp161948
91290685d2cSjp161948 /* Check for command followed by whitespace */
91390685d2cSjp161948 if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
91490685d2cSjp161948 strchr(WHITESPACE, cp[cmdlen])) {
91590685d2cSjp161948 cp += cmdlen;
91690685d2cSjp161948 cp = cp + strspn(cp, WHITESPACE);
91790685d2cSjp161948 break;
91890685d2cSjp161948 }
91990685d2cSjp161948 }
92090685d2cSjp161948 cmdnum = cmds[i].n;
92190685d2cSjp161948 cmd = cmds[i].c;
92290685d2cSjp161948
92390685d2cSjp161948 /* Special case */
92490685d2cSjp161948 if (*cp == '!') {
92590685d2cSjp161948 cp++;
92690685d2cSjp161948 cmdnum = I_SHELL;
92790685d2cSjp161948 } else if (cmdnum == -1) {
92890685d2cSjp161948 error("Invalid command.");
92990685d2cSjp161948 return (-1);
93090685d2cSjp161948 }
93190685d2cSjp161948
93290685d2cSjp161948 /* Get arguments and parse flags */
93390685d2cSjp161948 *lflag = *pflag = *n_arg = 0;
93490685d2cSjp161948 *path1 = *path2 = NULL;
93590685d2cSjp161948 switch (cmdnum) {
93690685d2cSjp161948 case I_GET:
93790685d2cSjp161948 case I_PUT:
93890685d2cSjp161948 if (parse_getput_flags(&cp, pflag))
93990685d2cSjp161948 return(-1);
94090685d2cSjp161948 /* Get first pathname (mandatory) */
94190685d2cSjp161948 if (get_pathname(&cp, path1))
94290685d2cSjp161948 return(-1);
94390685d2cSjp161948 if (*path1 == NULL) {
94490685d2cSjp161948 error("You must specify at least one path after a "
94590685d2cSjp161948 "%s command.", cmd);
94690685d2cSjp161948 return(-1);
94790685d2cSjp161948 }
94890685d2cSjp161948 /* Try to get second pathname (optional) */
94990685d2cSjp161948 if (get_pathname(&cp, path2))
95090685d2cSjp161948 return(-1);
95190685d2cSjp161948 break;
95290685d2cSjp161948 case I_RENAME:
95390685d2cSjp161948 case I_SYMLINK:
95490685d2cSjp161948 if (get_pathname(&cp, path1))
95590685d2cSjp161948 return(-1);
95690685d2cSjp161948 if (get_pathname(&cp, path2))
95790685d2cSjp161948 return(-1);
95890685d2cSjp161948 if (!*path1 || !*path2) {
95990685d2cSjp161948 error("You must specify two paths after a %s "
96090685d2cSjp161948 "command.", cmd);
96190685d2cSjp161948 return(-1);
96290685d2cSjp161948 }
96390685d2cSjp161948 break;
96490685d2cSjp161948 case I_RM:
96590685d2cSjp161948 case I_MKDIR:
96690685d2cSjp161948 case I_RMDIR:
96790685d2cSjp161948 case I_CHDIR:
96890685d2cSjp161948 case I_LCHDIR:
96990685d2cSjp161948 case I_LMKDIR:
97090685d2cSjp161948 /* Get pathname (mandatory) */
97190685d2cSjp161948 if (get_pathname(&cp, path1))
97290685d2cSjp161948 return(-1);
97390685d2cSjp161948 if (*path1 == NULL) {
97490685d2cSjp161948 error("You must specify a path after a %s command.",
97590685d2cSjp161948 cmd);
97690685d2cSjp161948 return(-1);
97790685d2cSjp161948 }
97890685d2cSjp161948 break;
97990685d2cSjp161948 case I_LS:
98090685d2cSjp161948 if (parse_ls_flags(&cp, lflag))
98190685d2cSjp161948 return(-1);
98290685d2cSjp161948 /* Path is optional */
98390685d2cSjp161948 if (get_pathname(&cp, path1))
98490685d2cSjp161948 return(-1);
98590685d2cSjp161948 break;
98690685d2cSjp161948 case I_LLS:
98790685d2cSjp161948 case I_SHELL:
98890685d2cSjp161948 /* Uses the rest of the line */
98990685d2cSjp161948 break;
99090685d2cSjp161948 case I_LUMASK:
99190685d2cSjp161948 base = 8;
99290685d2cSjp161948 /* FALLTHRU */
99390685d2cSjp161948 case I_CHMOD:
99490685d2cSjp161948 base = 8;
99590685d2cSjp161948 /* FALLTHRU */
99690685d2cSjp161948 case I_CHOWN:
99790685d2cSjp161948 case I_CHGRP:
99890685d2cSjp161948 /* Get numeric arg (mandatory) */
99990685d2cSjp161948 errno = 0;
100090685d2cSjp161948 l = strtol(cp, &cp2, base);
100190685d2cSjp161948 if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
100290685d2cSjp161948 errno == ERANGE) || l < 0) {
100390685d2cSjp161948 error("You must supply a numeric argument "
100490685d2cSjp161948 "to the %s command.", cmd);
100590685d2cSjp161948 return(-1);
100690685d2cSjp161948 }
100790685d2cSjp161948 cp = cp2;
100890685d2cSjp161948 *n_arg = l;
100990685d2cSjp161948 if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
101090685d2cSjp161948 break;
101190685d2cSjp161948 if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
101290685d2cSjp161948 error("You must supply a numeric argument "
101390685d2cSjp161948 "to the %s command.", cmd);
101490685d2cSjp161948 return(-1);
101590685d2cSjp161948 }
101690685d2cSjp161948 cp += strspn(cp, WHITESPACE);
101790685d2cSjp161948
101890685d2cSjp161948 /* Get pathname (mandatory) */
101990685d2cSjp161948 if (get_pathname(&cp, path1))
102090685d2cSjp161948 return(-1);
102190685d2cSjp161948 if (*path1 == NULL) {
102290685d2cSjp161948 error("You must specify a path after a %s command.",
102390685d2cSjp161948 cmd);
102490685d2cSjp161948 return(-1);
102590685d2cSjp161948 }
102690685d2cSjp161948 break;
102790685d2cSjp161948 case I_QUIT:
102890685d2cSjp161948 case I_PWD:
102990685d2cSjp161948 case I_LPWD:
103090685d2cSjp161948 case I_HELP:
103190685d2cSjp161948 case I_VERSION:
103290685d2cSjp161948 case I_PROGRESS:
103390685d2cSjp161948 break;
103490685d2cSjp161948 default:
103590685d2cSjp161948 fatal("Command not implemented");
103690685d2cSjp161948 }
103790685d2cSjp161948
103890685d2cSjp161948 *cpp = cp;
103990685d2cSjp161948 return(cmdnum);
104090685d2cSjp161948 }
104190685d2cSjp161948
104290685d2cSjp161948 static int
parse_dispatch_command(struct sftp_conn * conn,const char * cmd,char ** pwd,int err_abort)104390685d2cSjp161948 parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
104490685d2cSjp161948 int err_abort)
104590685d2cSjp161948 {
104690685d2cSjp161948 char *path1, *path2, *tmp;
104790685d2cSjp161948 int pflag, lflag, iflag, cmdnum, i;
104890685d2cSjp161948 unsigned long n_arg;
104990685d2cSjp161948 Attrib a, *aa;
105090685d2cSjp161948 char path_buf[MAXPATHLEN];
105190685d2cSjp161948 int err = 0;
105290685d2cSjp161948 glob_t g;
105390685d2cSjp161948
105490685d2cSjp161948 path1 = path2 = NULL;
105590685d2cSjp161948 cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &n_arg,
105690685d2cSjp161948 &path1, &path2);
105790685d2cSjp161948
105890685d2cSjp161948 if (iflag != 0)
105990685d2cSjp161948 err_abort = 0;
106090685d2cSjp161948
106190685d2cSjp161948 memset(&g, 0, sizeof(g));
106290685d2cSjp161948
106390685d2cSjp161948 /* Perform command */
106490685d2cSjp161948 switch (cmdnum) {
106590685d2cSjp161948 case 0:
106690685d2cSjp161948 /* Blank line */
106790685d2cSjp161948 break;
106890685d2cSjp161948 case -1:
106990685d2cSjp161948 /* Unrecognized command */
107090685d2cSjp161948 err = -1;
107190685d2cSjp161948 break;
107290685d2cSjp161948 case I_GET:
107390685d2cSjp161948 err = process_get(conn, path1, path2, *pwd, pflag);
107490685d2cSjp161948 break;
107590685d2cSjp161948 case I_PUT:
107690685d2cSjp161948 err = process_put(conn, path1, path2, *pwd, pflag);
107790685d2cSjp161948 break;
107890685d2cSjp161948 case I_RENAME:
107990685d2cSjp161948 path1 = make_absolute(path1, *pwd);
108090685d2cSjp161948 path2 = make_absolute(path2, *pwd);
108190685d2cSjp161948 err = do_rename(conn, path1, path2);
108290685d2cSjp161948 break;
108390685d2cSjp161948 case I_SYMLINK:
108490685d2cSjp161948 path2 = make_absolute(path2, *pwd);
108590685d2cSjp161948 err = do_symlink(conn, path1, path2);
108690685d2cSjp161948 break;
108790685d2cSjp161948 case I_RM:
108890685d2cSjp161948 path1 = make_absolute(path1, *pwd);
108990685d2cSjp161948 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
109090685d2cSjp161948 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
109190685d2cSjp161948 printf(gettext("Removing %s\n"), g.gl_pathv[i]);
109290685d2cSjp161948 err = do_rm(conn, g.gl_pathv[i]);
109390685d2cSjp161948 if (err != 0 && err_abort)
109490685d2cSjp161948 break;
109590685d2cSjp161948 }
109690685d2cSjp161948 break;
109790685d2cSjp161948 case I_MKDIR:
109890685d2cSjp161948 path1 = make_absolute(path1, *pwd);
109990685d2cSjp161948 attrib_clear(&a);
110090685d2cSjp161948 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
110190685d2cSjp161948 a.perm = 0777;
110290685d2cSjp161948 err = do_mkdir(conn, path1, &a);
110390685d2cSjp161948 break;
110490685d2cSjp161948 case I_RMDIR:
110590685d2cSjp161948 path1 = make_absolute(path1, *pwd);
110690685d2cSjp161948 err = do_rmdir(conn, path1);
110790685d2cSjp161948 break;
110890685d2cSjp161948 case I_CHDIR:
110990685d2cSjp161948 path1 = make_absolute(path1, *pwd);
111090685d2cSjp161948 if ((tmp = do_realpath(conn, path1)) == NULL) {
111190685d2cSjp161948 err = 1;
111290685d2cSjp161948 break;
111390685d2cSjp161948 }
111490685d2cSjp161948 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
111590685d2cSjp161948 xfree(tmp);
111690685d2cSjp161948 err = 1;
111790685d2cSjp161948 break;
111890685d2cSjp161948 }
111990685d2cSjp161948 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
112090685d2cSjp161948 error("Can't change directory: Can't check target");
112190685d2cSjp161948 xfree(tmp);
112290685d2cSjp161948 err = 1;
112390685d2cSjp161948 break;
112490685d2cSjp161948 }
112590685d2cSjp161948 if (!S_ISDIR(aa->perm)) {
112690685d2cSjp161948 error("Can't change directory: \"%s\" is not "
112790685d2cSjp161948 "a directory", tmp);
112890685d2cSjp161948 xfree(tmp);
112990685d2cSjp161948 err = 1;
113090685d2cSjp161948 break;
113190685d2cSjp161948 }
113290685d2cSjp161948 xfree(*pwd);
113390685d2cSjp161948 *pwd = tmp;
113490685d2cSjp161948 break;
113590685d2cSjp161948 case I_LS:
113690685d2cSjp161948 if (!path1) {
113790685d2cSjp161948 do_globbed_ls(conn, *pwd, *pwd, lflag);
113890685d2cSjp161948 break;
113990685d2cSjp161948 }
114090685d2cSjp161948
114190685d2cSjp161948 /* Strip pwd off beginning of non-absolute paths */
114290685d2cSjp161948 tmp = NULL;
114390685d2cSjp161948 if (*path1 != '/')
114490685d2cSjp161948 tmp = *pwd;
114590685d2cSjp161948
114690685d2cSjp161948 path1 = make_absolute(path1, *pwd);
114790685d2cSjp161948 err = do_globbed_ls(conn, path1, tmp, lflag);
114890685d2cSjp161948 break;
114990685d2cSjp161948 case I_LCHDIR:
115090685d2cSjp161948 if (chdir(path1) == -1) {
115190685d2cSjp161948 error("Couldn't change local directory to "
115290685d2cSjp161948 "\"%s\": %s", path1, strerror(errno));
115390685d2cSjp161948 err = 1;
115490685d2cSjp161948 }
115590685d2cSjp161948 break;
115690685d2cSjp161948 case I_LMKDIR:
115790685d2cSjp161948 if (mkdir(path1, 0777) == -1) {
115890685d2cSjp161948 error("Couldn't create local directory "
115990685d2cSjp161948 "\"%s\": %s", path1, strerror(errno));
116090685d2cSjp161948 err = 1;
116190685d2cSjp161948 }
116290685d2cSjp161948 break;
116390685d2cSjp161948 case I_LLS:
116490685d2cSjp161948 local_do_ls(cmd);
116590685d2cSjp161948 break;
116690685d2cSjp161948 case I_SHELL:
116790685d2cSjp161948 local_do_shell(cmd);
116890685d2cSjp161948 break;
116990685d2cSjp161948 case I_LUMASK:
117090685d2cSjp161948 umask(n_arg);
117190685d2cSjp161948 printf(gettext("Local umask: %03lo\n"), n_arg);
117290685d2cSjp161948 break;
117390685d2cSjp161948 case I_CHMOD:
117490685d2cSjp161948 path1 = make_absolute(path1, *pwd);
117590685d2cSjp161948 attrib_clear(&a);
117690685d2cSjp161948 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
117790685d2cSjp161948 a.perm = n_arg;
117890685d2cSjp161948 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
117990685d2cSjp161948 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
118090685d2cSjp161948 printf(gettext("Changing mode on %s\n"), g.gl_pathv[i]);
118190685d2cSjp161948 err = do_setstat(conn, g.gl_pathv[i], &a);
118290685d2cSjp161948 if (err != 0 && err_abort)
118390685d2cSjp161948 break;
118490685d2cSjp161948 }
118590685d2cSjp161948 break;
118690685d2cSjp161948 case I_CHOWN:
118790685d2cSjp161948 case I_CHGRP:
118890685d2cSjp161948 path1 = make_absolute(path1, *pwd);
118990685d2cSjp161948 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
119090685d2cSjp161948 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
119190685d2cSjp161948 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
119290685d2cSjp161948 if (err != 0 && err_abort)
119390685d2cSjp161948 break;
119490685d2cSjp161948 else
119590685d2cSjp161948 continue;
119690685d2cSjp161948 }
119790685d2cSjp161948 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
119890685d2cSjp161948 error("Can't get current ownership of "
119990685d2cSjp161948 "remote file \"%s\"", g.gl_pathv[i]);
120090685d2cSjp161948 if (err != 0 && err_abort)
120190685d2cSjp161948 break;
120290685d2cSjp161948 else
120390685d2cSjp161948 continue;
120490685d2cSjp161948 }
120590685d2cSjp161948 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
120690685d2cSjp161948 if (cmdnum == I_CHOWN) {
120790685d2cSjp161948 printf(gettext("Changing owner on %s\n"), g.gl_pathv[i]);
120890685d2cSjp161948 aa->uid = n_arg;
120990685d2cSjp161948 } else {
121090685d2cSjp161948 printf(gettext("Changing group on %s\n"), g.gl_pathv[i]);
121190685d2cSjp161948 aa->gid = n_arg;
121290685d2cSjp161948 }
121390685d2cSjp161948 err = do_setstat(conn, g.gl_pathv[i], aa);
121490685d2cSjp161948 if (err != 0 && err_abort)
121590685d2cSjp161948 break;
121690685d2cSjp161948 }
121790685d2cSjp161948 break;
121890685d2cSjp161948 case I_PWD:
121990685d2cSjp161948 printf(gettext("Remote working directory: %s\n"), *pwd);
122090685d2cSjp161948 break;
122190685d2cSjp161948 case I_LPWD:
122290685d2cSjp161948 if (!getcwd(path_buf, sizeof(path_buf))) {
122390685d2cSjp161948 error("Couldn't get local cwd: %s", strerror(errno));
122490685d2cSjp161948 err = -1;
122590685d2cSjp161948 break;
122690685d2cSjp161948 }
122790685d2cSjp161948 printf(gettext("Local working directory: %s\n"), path_buf);
122890685d2cSjp161948 break;
122990685d2cSjp161948 case I_QUIT:
123090685d2cSjp161948 /* Processed below */
123190685d2cSjp161948 break;
123290685d2cSjp161948 case I_HELP:
123390685d2cSjp161948 help();
123490685d2cSjp161948 break;
123590685d2cSjp161948 case I_VERSION:
123690685d2cSjp161948 printf(gettext("SFTP protocol version %u\n"), sftp_proto_version(conn));
123790685d2cSjp161948 break;
123890685d2cSjp161948 case I_PROGRESS:
123990685d2cSjp161948 showprogress = !showprogress;
124090685d2cSjp161948 if (showprogress)
124190685d2cSjp161948 printf("Progress meter enabled\n");
124290685d2cSjp161948 else
124390685d2cSjp161948 printf("Progress meter disabled\n");
124490685d2cSjp161948 break;
124590685d2cSjp161948 default:
124690685d2cSjp161948 fatal("%d is not implemented", cmdnum);
124790685d2cSjp161948 }
124890685d2cSjp161948
124990685d2cSjp161948 if (g.gl_pathc)
125090685d2cSjp161948 globfree(&g);
125190685d2cSjp161948 if (path1)
125290685d2cSjp161948 xfree(path1);
125390685d2cSjp161948 if (path2)
125490685d2cSjp161948 xfree(path2);
125590685d2cSjp161948
125690685d2cSjp161948 /* If an unignored error occurs in batch mode we should abort. */
125790685d2cSjp161948 if (err_abort && err != 0)
125890685d2cSjp161948 return (-1);
125990685d2cSjp161948 else if (cmdnum == I_QUIT)
126090685d2cSjp161948 return (1);
126190685d2cSjp161948
126290685d2cSjp161948 return (0);
126390685d2cSjp161948 }
126490685d2cSjp161948
126590685d2cSjp161948 #ifdef USE_LIBEDIT
126690685d2cSjp161948 static char *
prompt(EditLine * el)126790685d2cSjp161948 prompt(EditLine *el)
126890685d2cSjp161948 {
126990685d2cSjp161948 return ("sftp> ");
127090685d2cSjp161948 }
127184e198abSHuie-Ying Lee #else
127284e198abSHuie-Ying Lee #ifdef USE_LIBTECLA
127384e198abSHuie-Ying Lee /*
127484e198abSHuie-Ying Lee * Disable default TAB completion for filenames, because it displays local
127584e198abSHuie-Ying Lee * files for every commands, which is not desirable.
127684e198abSHuie-Ying Lee */
127784e198abSHuie-Ying Lee static
CPL_MATCH_FN(nomatch)127884e198abSHuie-Ying Lee CPL_MATCH_FN(nomatch)
127984e198abSHuie-Ying Lee {
128084e198abSHuie-Ying Lee return (0);
128184e198abSHuie-Ying Lee }
128284e198abSHuie-Ying Lee #endif /* USE_LIBTECLA */
128384e198abSHuie-Ying Lee #endif /* USE_LIBEDIT */
128490685d2cSjp161948
128590685d2cSjp161948 int
interactive_loop(int fd_in,int fd_out,char * file1,char * file2)128690685d2cSjp161948 interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
128790685d2cSjp161948 {
128890685d2cSjp161948 char *pwd;
128990685d2cSjp161948 char *dir = NULL;
129090685d2cSjp161948 char cmd[2048];
129190685d2cSjp161948 struct sftp_conn *conn;
129290685d2cSjp161948 int err, interactive;
129384e198abSHuie-Ying Lee void *il = NULL;
129484e198abSHuie-Ying Lee
129590685d2cSjp161948 #ifdef USE_LIBEDIT
129684e198abSHuie-Ying Lee EditLine *el = NULL;
129790685d2cSjp161948 History *hl = NULL;
129890685d2cSjp161948 HistEvent hev;
129990685d2cSjp161948
130090685d2cSjp161948 if (!batchmode && isatty(STDIN_FILENO)) {
130184e198abSHuie-Ying Lee if ((il = el = el_init(__progname, stdin, stdout, stderr)) == NULL)
130290685d2cSjp161948 fatal("Couldn't initialise editline");
130390685d2cSjp161948 if ((hl = history_init()) == NULL)
130490685d2cSjp161948 fatal("Couldn't initialise editline history");
130590685d2cSjp161948 history(hl, &hev, H_SETSIZE, 100);
130690685d2cSjp161948 el_set(el, EL_HIST, history, hl);
130790685d2cSjp161948
130890685d2cSjp161948 el_set(el, EL_PROMPT, prompt);
130990685d2cSjp161948 el_set(el, EL_EDITOR, "emacs");
131090685d2cSjp161948 el_set(el, EL_TERMINAL, NULL);
131190685d2cSjp161948 el_set(el, EL_SIGNAL, 1);
131290685d2cSjp161948 el_source(el, NULL);
131390685d2cSjp161948 }
131484e198abSHuie-Ying Lee #else
131584e198abSHuie-Ying Lee #ifdef USE_LIBTECLA
131684e198abSHuie-Ying Lee GetLine *gl = NULL;
131784e198abSHuie-Ying Lee
131884e198abSHuie-Ying Lee if (!batchmode && isatty(STDIN_FILENO)) {
131984e198abSHuie-Ying Lee if ((il = gl = new_GetLine(MAX_LINE_LEN, MAX_CMD_HIST)) == NULL)
132084e198abSHuie-Ying Lee fatal("Couldn't initialize GetLine");
132184e198abSHuie-Ying Lee if (gl_customize_completion(gl, NULL, nomatch) != 0) {
132284e198abSHuie-Ying Lee (void) del_GetLine(gl);
132384e198abSHuie-Ying Lee fatal("Couldn't register completion function");
132484e198abSHuie-Ying Lee }
132584e198abSHuie-Ying Lee }
132684e198abSHuie-Ying Lee #endif /* USE_LIBTECLA */
132790685d2cSjp161948 #endif /* USE_LIBEDIT */
132890685d2cSjp161948
132990685d2cSjp161948 conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
133090685d2cSjp161948 if (conn == NULL)
133190685d2cSjp161948 fatal("Couldn't initialise connection to server");
133290685d2cSjp161948
133390685d2cSjp161948 pwd = do_realpath(conn, ".");
133490685d2cSjp161948 if (pwd == NULL)
133590685d2cSjp161948 fatal("Need cwd");
133690685d2cSjp161948
133790685d2cSjp161948 if (file1 != NULL) {
133890685d2cSjp161948 dir = xstrdup(file1);
133990685d2cSjp161948 dir = make_absolute(dir, pwd);
134090685d2cSjp161948
134190685d2cSjp161948 if (remote_is_dir(conn, dir) && file2 == NULL) {
134290685d2cSjp161948 printf(gettext("Changing to: %s\n"), dir);
134390685d2cSjp161948 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
134490685d2cSjp161948 if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) {
134590685d2cSjp161948 xfree(dir);
134690685d2cSjp161948 xfree(pwd);
134790685d2cSjp161948 xfree(conn);
134890685d2cSjp161948 return (-1);
134990685d2cSjp161948 }
135090685d2cSjp161948 } else {
135190685d2cSjp161948 if (file2 == NULL)
135290685d2cSjp161948 snprintf(cmd, sizeof cmd, "get %s", dir);
135390685d2cSjp161948 else
135490685d2cSjp161948 snprintf(cmd, sizeof cmd, "get %s %s", dir,
135590685d2cSjp161948 file2);
135690685d2cSjp161948
135790685d2cSjp161948 err = parse_dispatch_command(conn, cmd, &pwd, 1);
135890685d2cSjp161948 xfree(dir);
135990685d2cSjp161948 xfree(pwd);
136090685d2cSjp161948 xfree(conn);
136190685d2cSjp161948 return (err);
136290685d2cSjp161948 }
136390685d2cSjp161948 xfree(dir);
136490685d2cSjp161948 }
136590685d2cSjp161948
136690685d2cSjp161948 #if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
136790685d2cSjp161948 setvbuf(stdout, NULL, _IOLBF, 0);
136890685d2cSjp161948 setvbuf(infile, NULL, _IOLBF, 0);
136990685d2cSjp161948 #else
137090685d2cSjp161948 setlinebuf(stdout);
137190685d2cSjp161948 setlinebuf(infile);
137290685d2cSjp161948 #endif
137390685d2cSjp161948
137490685d2cSjp161948 interactive = !batchmode && isatty(STDIN_FILENO);
137590685d2cSjp161948 err = 0;
137690685d2cSjp161948 for (;;) {
137790685d2cSjp161948 char *cp;
137890685d2cSjp161948
137990685d2cSjp161948 signal(SIGINT, SIG_IGN);
138090685d2cSjp161948
138184e198abSHuie-Ying Lee if (il == NULL) {
138290685d2cSjp161948 if (interactive)
138390685d2cSjp161948 printf("sftp> ");
138490685d2cSjp161948 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
138590685d2cSjp161948 if (interactive)
138690685d2cSjp161948 printf("\n");
138790685d2cSjp161948 break;
138890685d2cSjp161948 }
138990685d2cSjp161948 if (!interactive) { /* Echo command */
139090685d2cSjp161948 printf("sftp> %s", cmd);
139190685d2cSjp161948 if (strlen(cmd) > 0 &&
139290685d2cSjp161948 cmd[strlen(cmd) - 1] != '\n')
139390685d2cSjp161948 printf("\n");
139490685d2cSjp161948 }
139590685d2cSjp161948 }
139690685d2cSjp161948 #ifdef USE_LIBEDIT
139790685d2cSjp161948 else {
139890685d2cSjp161948 const char *line;
139990685d2cSjp161948 int count = 0;
140090685d2cSjp161948
140190685d2cSjp161948 if ((line = el_gets(el, &count)) == NULL || count <= 0) {
140290685d2cSjp161948 printf("\n");
140390685d2cSjp161948 break;
140490685d2cSjp161948 }
140590685d2cSjp161948 history(hl, &hev, H_ENTER, line);
140690685d2cSjp161948 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
140790685d2cSjp161948 fprintf(stderr, gettext("Error: input line too long\n"));
140890685d2cSjp161948 continue;
140990685d2cSjp161948 }
141090685d2cSjp161948 }
141184e198abSHuie-Ying Lee #else
141284e198abSHuie-Ying Lee #ifdef USE_LIBTECLA
141384e198abSHuie-Ying Lee else {
141484e198abSHuie-Ying Lee const char *line;
141584e198abSHuie-Ying Lee
141684e198abSHuie-Ying Lee line = gl_get_line(gl, "sftp> ", NULL, -1);
141784e198abSHuie-Ying Lee if (line != NULL) {
141884e198abSHuie-Ying Lee if (strlcpy(cmd, line, sizeof(cmd)) >=
141984e198abSHuie-Ying Lee sizeof(cmd)) {
142084e198abSHuie-Ying Lee fprintf(stderr, gettext(
142184e198abSHuie-Ying Lee "Error: input line too long\n"));
142284e198abSHuie-Ying Lee continue;
142384e198abSHuie-Ying Lee }
142484e198abSHuie-Ying Lee } else {
142584e198abSHuie-Ying Lee GlReturnStatus rtn;
142684e198abSHuie-Ying Lee
142784e198abSHuie-Ying Lee rtn = gl_return_status(gl);
142884e198abSHuie-Ying Lee if (rtn == GLR_SIGNAL) {
142984e198abSHuie-Ying Lee gl_abandon_line(gl);
143084e198abSHuie-Ying Lee continue;
143184e198abSHuie-Ying Lee } else if (rtn == GLR_ERROR) {
143284e198abSHuie-Ying Lee fprintf(stderr, gettext(
143384e198abSHuie-Ying Lee "Error reading terminal: %s/\n"),
143484e198abSHuie-Ying Lee gl_error_message(gl, NULL, 0));
143584e198abSHuie-Ying Lee }
143684e198abSHuie-Ying Lee break;
143784e198abSHuie-Ying Lee }
143884e198abSHuie-Ying Lee }
143984e198abSHuie-Ying Lee #endif /* USE_LIBTECLA */
144090685d2cSjp161948 #endif /* USE_LIBEDIT */
144190685d2cSjp161948
144290685d2cSjp161948 cp = strrchr(cmd, '\n');
144390685d2cSjp161948 if (cp)
144490685d2cSjp161948 *cp = '\0';
144590685d2cSjp161948
144690685d2cSjp161948 /* Handle user interrupts gracefully during commands */
144790685d2cSjp161948 interrupted = 0;
144890685d2cSjp161948 signal(SIGINT, cmd_interrupt);
144990685d2cSjp161948
145090685d2cSjp161948 err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
145190685d2cSjp161948 if (err != 0)
145290685d2cSjp161948 break;
145390685d2cSjp161948 }
145490685d2cSjp161948 xfree(pwd);
145590685d2cSjp161948 xfree(conn);
145690685d2cSjp161948
145790685d2cSjp161948 #ifdef USE_LIBEDIT
145890685d2cSjp161948 if (el != NULL)
145990685d2cSjp161948 el_end(el);
146084e198abSHuie-Ying Lee #else
146184e198abSHuie-Ying Lee #ifdef USE_LIBTECLA
146284e198abSHuie-Ying Lee if (gl != NULL)
146384e198abSHuie-Ying Lee (void) del_GetLine(gl);
146484e198abSHuie-Ying Lee #endif /* USE_LIBTECLA */
146590685d2cSjp161948 #endif /* USE_LIBEDIT */
146690685d2cSjp161948
146790685d2cSjp161948 /* err == 1 signifies normal "quit" exit */
146890685d2cSjp161948 return (err >= 0 ? 0 : -1);
146990685d2cSjp161948 }
147090685d2cSjp161948
147190685d2cSjp161948 static void
connect_to_server(char * path,char ** args,int * in,int * out)147290685d2cSjp161948 connect_to_server(char *path, char **args, int *in, int *out)
14737c478bd9Sstevel@tonic-gate {
14747c478bd9Sstevel@tonic-gate int c_in, c_out;
14757c478bd9Sstevel@tonic-gate
14767c478bd9Sstevel@tonic-gate int inout[2];
14777c478bd9Sstevel@tonic-gate
14787c478bd9Sstevel@tonic-gate if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
14797c478bd9Sstevel@tonic-gate fatal("socketpair: %s", strerror(errno));
14807c478bd9Sstevel@tonic-gate *in = *out = inout[0];
14817c478bd9Sstevel@tonic-gate c_in = c_out = inout[1];
14827c478bd9Sstevel@tonic-gate
148390685d2cSjp161948 if ((sshpid = fork()) == -1)
14847c478bd9Sstevel@tonic-gate fatal("fork: %s", strerror(errno));
148590685d2cSjp161948 else if (sshpid == 0) {
14867c478bd9Sstevel@tonic-gate if ((dup2(c_in, STDIN_FILENO) == -1) ||
14877c478bd9Sstevel@tonic-gate (dup2(c_out, STDOUT_FILENO) == -1)) {
14887c478bd9Sstevel@tonic-gate fprintf(stderr, "dup2: %s\n", strerror(errno));
148990685d2cSjp161948 _exit(1);
14907c478bd9Sstevel@tonic-gate }
14917c478bd9Sstevel@tonic-gate close(*in);
14927c478bd9Sstevel@tonic-gate close(*out);
14937c478bd9Sstevel@tonic-gate close(c_in);
14947c478bd9Sstevel@tonic-gate close(c_out);
149590685d2cSjp161948
149690685d2cSjp161948 /*
149790685d2cSjp161948 * The underlying ssh is in the same process group, so we must
149890685d2cSjp161948 * ignore SIGINT if we want to gracefully abort commands,
149990685d2cSjp161948 * otherwise the signal will make it to the ssh process and
150090685d2cSjp161948 * kill it too
150190685d2cSjp161948 */
150290685d2cSjp161948 signal(SIGINT, SIG_IGN);
150390685d2cSjp161948 execvp(path, args);
15047c478bd9Sstevel@tonic-gate fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
150590685d2cSjp161948 _exit(1);
15067c478bd9Sstevel@tonic-gate }
15077c478bd9Sstevel@tonic-gate
150890685d2cSjp161948 signal(SIGTERM, killchild);
150990685d2cSjp161948 signal(SIGINT, killchild);
151090685d2cSjp161948 signal(SIGHUP, killchild);
15117c478bd9Sstevel@tonic-gate close(c_in);
15127c478bd9Sstevel@tonic-gate close(c_out);
15137c478bd9Sstevel@tonic-gate }
15147c478bd9Sstevel@tonic-gate
15157c478bd9Sstevel@tonic-gate static void
usage(void)15167c478bd9Sstevel@tonic-gate usage(void)
15177c478bd9Sstevel@tonic-gate {
15187c478bd9Sstevel@tonic-gate fprintf(stderr,
151990685d2cSjp161948 gettext("Usage: %s [-1Cv] [-b batchfile] [-B buffer_size]\n"
152090685d2cSjp161948 " [-F ssh_config] [-o ssh_option] [-P sftp_server_path]\n"
152190685d2cSjp161948 " [-R num_requests] [-s subsystem | sftp_server]\n"
152290685d2cSjp161948 " [-S program] [user@]host[:dir[/] | :file [file]]\n"),
152390685d2cSjp161948 __progname, __progname, __progname, __progname);
15247c478bd9Sstevel@tonic-gate exit(1);
15257c478bd9Sstevel@tonic-gate }
15267c478bd9Sstevel@tonic-gate
15277c478bd9Sstevel@tonic-gate int
main(int argc,char ** argv)15287c478bd9Sstevel@tonic-gate main(int argc, char **argv)
15297c478bd9Sstevel@tonic-gate {
153090685d2cSjp161948 int in, out, ch, err;
153190685d2cSjp161948 char *host, *userhost, *cp, *file2 = NULL;
15327c478bd9Sstevel@tonic-gate int debug_level = 0, sshver = 2;
15337c478bd9Sstevel@tonic-gate char *file1 = NULL, *sftp_server = NULL;
15347c478bd9Sstevel@tonic-gate char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
15357c478bd9Sstevel@tonic-gate LogLevel ll = SYSLOG_LEVEL_INFO;
15367c478bd9Sstevel@tonic-gate arglist args;
15377c478bd9Sstevel@tonic-gate extern int optind;
15387c478bd9Sstevel@tonic-gate extern char *optarg;
15397c478bd9Sstevel@tonic-gate
154090685d2cSjp161948 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
154190685d2cSjp161948 sanitise_stdfd();
154290685d2cSjp161948
15437c478bd9Sstevel@tonic-gate __progname = get_progname(argv[0]);
15447c478bd9Sstevel@tonic-gate
15457c478bd9Sstevel@tonic-gate (void) g11n_setlocale(LC_ALL, "");
154690685d2cSjp161948
154790685d2cSjp161948 memset(&args, '\0', sizeof(args));
15487c478bd9Sstevel@tonic-gate args.list = NULL;
154990685d2cSjp161948 addargs(&args, "%s", ssh_program);
15507c478bd9Sstevel@tonic-gate addargs(&args, "-oForwardX11 no");
15517c478bd9Sstevel@tonic-gate addargs(&args, "-oForwardAgent no");
15527c478bd9Sstevel@tonic-gate addargs(&args, "-oClearAllForwardings yes");
155390685d2cSjp161948
15547c478bd9Sstevel@tonic-gate ll = SYSLOG_LEVEL_INFO;
155590685d2cSjp161948 infile = stdin;
15567c478bd9Sstevel@tonic-gate
15577c478bd9Sstevel@tonic-gate while ((ch = getopt(argc, argv, "1hvCo:s:S:b:B:F:P:R:")) != -1) {
15587c478bd9Sstevel@tonic-gate switch (ch) {
15597c478bd9Sstevel@tonic-gate case 'C':
15607c478bd9Sstevel@tonic-gate addargs(&args, "-C");
15617c478bd9Sstevel@tonic-gate break;
15627c478bd9Sstevel@tonic-gate case 'v':
15637c478bd9Sstevel@tonic-gate if (debug_level < 3) {
15647c478bd9Sstevel@tonic-gate addargs(&args, "-v");
15657c478bd9Sstevel@tonic-gate ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
15667c478bd9Sstevel@tonic-gate }
15677c478bd9Sstevel@tonic-gate debug_level++;
15687c478bd9Sstevel@tonic-gate break;
15697c478bd9Sstevel@tonic-gate case 'F':
15707c478bd9Sstevel@tonic-gate case 'o':
15717c478bd9Sstevel@tonic-gate addargs(&args, "-%c%s", ch, optarg);
15727c478bd9Sstevel@tonic-gate break;
15737c478bd9Sstevel@tonic-gate case '1':
15747c478bd9Sstevel@tonic-gate sshver = 1;
15757c478bd9Sstevel@tonic-gate if (sftp_server == NULL)
15767c478bd9Sstevel@tonic-gate sftp_server = _PATH_SFTP_SERVER;
15777c478bd9Sstevel@tonic-gate break;
15787c478bd9Sstevel@tonic-gate case 's':
15797c478bd9Sstevel@tonic-gate sftp_server = optarg;
15807c478bd9Sstevel@tonic-gate break;
15817c478bd9Sstevel@tonic-gate case 'S':
15827c478bd9Sstevel@tonic-gate ssh_program = optarg;
158390685d2cSjp161948 replacearg(&args, 0, "%s", ssh_program);
15847c478bd9Sstevel@tonic-gate break;
15857c478bd9Sstevel@tonic-gate case 'b':
158690685d2cSjp161948 if (batchmode)
158790685d2cSjp161948 fatal("Batch file already specified.");
158890685d2cSjp161948
158990685d2cSjp161948 /* Allow "-" as stdin */
159090685d2cSjp161948 if (strcmp(optarg, "-") != 0 &&
159190685d2cSjp161948 (infile = fopen(optarg, "r")) == NULL)
15927c478bd9Sstevel@tonic-gate fatal("%s (%s).", strerror(errno), optarg);
159390685d2cSjp161948 showprogress = 0;
159490685d2cSjp161948 batchmode = 1;
159590685d2cSjp161948 addargs(&args, "-obatchmode yes");
15967c478bd9Sstevel@tonic-gate break;
15977c478bd9Sstevel@tonic-gate case 'P':
15987c478bd9Sstevel@tonic-gate sftp_direct = optarg;
15997c478bd9Sstevel@tonic-gate break;
16007c478bd9Sstevel@tonic-gate case 'B':
16017c478bd9Sstevel@tonic-gate copy_buffer_len = strtol(optarg, &cp, 10);
16027c478bd9Sstevel@tonic-gate if (copy_buffer_len == 0 || *cp != '\0')
16037c478bd9Sstevel@tonic-gate fatal("Invalid buffer size \"%s\"", optarg);
16047c478bd9Sstevel@tonic-gate break;
16057c478bd9Sstevel@tonic-gate case 'R':
16067c478bd9Sstevel@tonic-gate num_requests = strtol(optarg, &cp, 10);
16077c478bd9Sstevel@tonic-gate if (num_requests == 0 || *cp != '\0')
16087c478bd9Sstevel@tonic-gate fatal("Invalid number of requests \"%s\"",
16097c478bd9Sstevel@tonic-gate optarg);
16107c478bd9Sstevel@tonic-gate break;
16117c478bd9Sstevel@tonic-gate case 'h':
16127c478bd9Sstevel@tonic-gate default:
16137c478bd9Sstevel@tonic-gate usage();
16147c478bd9Sstevel@tonic-gate }
16157c478bd9Sstevel@tonic-gate }
16167c478bd9Sstevel@tonic-gate
161790685d2cSjp161948 if (!isatty(STDERR_FILENO))
161890685d2cSjp161948 showprogress = 0;
161990685d2cSjp161948
16207c478bd9Sstevel@tonic-gate log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
16217c478bd9Sstevel@tonic-gate
16227c478bd9Sstevel@tonic-gate if (sftp_direct == NULL) {
16237c478bd9Sstevel@tonic-gate if (optind == argc || argc > (optind + 2))
16247c478bd9Sstevel@tonic-gate usage();
16257c478bd9Sstevel@tonic-gate
16267c478bd9Sstevel@tonic-gate userhost = xstrdup(argv[optind]);
16277c478bd9Sstevel@tonic-gate file2 = argv[optind+1];
16287c478bd9Sstevel@tonic-gate
162990685d2cSjp161948 if ((host = strrchr(userhost, '@')) == NULL)
16307c478bd9Sstevel@tonic-gate host = userhost;
16317c478bd9Sstevel@tonic-gate else {
16327c478bd9Sstevel@tonic-gate *host++ = '\0';
16337c478bd9Sstevel@tonic-gate if (!userhost[0]) {
16347c478bd9Sstevel@tonic-gate fprintf(stderr, gettext("Missing username\n"));
16357c478bd9Sstevel@tonic-gate usage();
16367c478bd9Sstevel@tonic-gate }
16377c478bd9Sstevel@tonic-gate addargs(&args, "-l%s", userhost);
16387c478bd9Sstevel@tonic-gate }
16397c478bd9Sstevel@tonic-gate
164090685d2cSjp161948 if ((cp = colon(host)) != NULL) {
164190685d2cSjp161948 *cp++ = '\0';
164290685d2cSjp161948 file1 = cp;
164390685d2cSjp161948 }
164490685d2cSjp161948
16457c478bd9Sstevel@tonic-gate host = cleanhostname(host);
16467c478bd9Sstevel@tonic-gate if (!*host) {
16477c478bd9Sstevel@tonic-gate fprintf(stderr, gettext("Missing hostname\n"));
16487c478bd9Sstevel@tonic-gate usage();
16497c478bd9Sstevel@tonic-gate }
16507c478bd9Sstevel@tonic-gate
16517c478bd9Sstevel@tonic-gate addargs(&args, "-oProtocol %d", sshver);
16527c478bd9Sstevel@tonic-gate
16537c478bd9Sstevel@tonic-gate /* no subsystem if the server-spec contains a '/' */
16547c478bd9Sstevel@tonic-gate if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
16557c478bd9Sstevel@tonic-gate addargs(&args, "-s");
16567c478bd9Sstevel@tonic-gate
16577c478bd9Sstevel@tonic-gate addargs(&args, "%s", host);
16587c478bd9Sstevel@tonic-gate addargs(&args, "%s", (sftp_server != NULL ?
16597c478bd9Sstevel@tonic-gate sftp_server : "sftp"));
16607c478bd9Sstevel@tonic-gate
166190685d2cSjp161948 if (!batchmode)
16627c478bd9Sstevel@tonic-gate fprintf(stderr, gettext("Connecting to %s...\n"), host);
166390685d2cSjp161948 connect_to_server(ssh_program, args.list, &in, &out);
16647c478bd9Sstevel@tonic-gate } else {
16657c478bd9Sstevel@tonic-gate args.list = NULL;
16667c478bd9Sstevel@tonic-gate addargs(&args, "sftp-server");
16677c478bd9Sstevel@tonic-gate
166890685d2cSjp161948 if (!batchmode)
16697c478bd9Sstevel@tonic-gate fprintf(stderr, gettext("Attaching to %s...\n"), sftp_direct);
167090685d2cSjp161948 connect_to_server(sftp_direct, args.list, &in, &out);
16717c478bd9Sstevel@tonic-gate }
167290685d2cSjp161948 freeargs(&args);
16737c478bd9Sstevel@tonic-gate
167490685d2cSjp161948 err = interactive_loop(in, out, file1, file2);
16757c478bd9Sstevel@tonic-gate
16767c478bd9Sstevel@tonic-gate shutdown(in, SHUT_RDWR);
16777c478bd9Sstevel@tonic-gate shutdown(out, SHUT_RDWR);
16787c478bd9Sstevel@tonic-gate
16797c478bd9Sstevel@tonic-gate close(in);
16807c478bd9Sstevel@tonic-gate close(out);
168190685d2cSjp161948 if (batchmode)
16827c478bd9Sstevel@tonic-gate fclose(infile);
16837c478bd9Sstevel@tonic-gate
16847c478bd9Sstevel@tonic-gate while (waitpid(sshpid, NULL, 0) == -1)
16857c478bd9Sstevel@tonic-gate if (errno != EINTR)
16867c478bd9Sstevel@tonic-gate fatal("Couldn't wait for ssh process: %s",
16877c478bd9Sstevel@tonic-gate strerror(errno));
16887c478bd9Sstevel@tonic-gate
168990685d2cSjp161948 return (err == 0 ? 0 : 1);
16907c478bd9Sstevel@tonic-gate }
1691