-/* $OpenBSD: sftp.c,v 1.99 2008/01/20 00:38:30 djm Exp $ */
+/* $OpenBSD: sftp.c,v 1.118 2010/01/09 11:13:02 dtucker Exp $ */
/*
* Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
*
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/wait.h>
+#ifdef HAVE_SYS_STATVFS_H
+#include <sys/statvfs.h>
+#endif
#include <ctype.h>
#include <errno.h>
#ifdef HAVE_PATHS_H
# include <paths.h>
#endif
+#ifdef HAVE_LIBGEN_H
+#include <libgen.h>
+#endif
#ifdef USE_LIBEDIT
#include <histedit.h>
#else
#include <unistd.h>
#include <stdarg.h>
+#ifdef HAVE_UTIL_H
+# include <util.h>
+#endif
+
+#ifdef HAVE_LIBUTIL_H
+# include <libutil.h>
+#endif
+
#include "xmalloc.h"
#include "log.h"
#include "pathnames.h"
#include "sftp-common.h"
#include "sftp-client.h"
+#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
+#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
+
/* File to read commands from */
FILE* infile;
/* Are we in batchfile mode? */
int batchmode = 0;
-/* Size of buffer used when copying files */
-size_t copy_buffer_len = 32768;
-
-/* Number of concurrent outstanding requests */
-size_t num_requests = 16;
-
/* PID of ssh transport process */
static pid_t sshpid = -1;
/* This is set to 0 if the progressmeter is not desired. */
int showprogress = 1;
+/* When this option is set, we always recursively download/upload directories */
+int global_rflag = 0;
+
+/* When this option is set, the file transfers will always preserve times */
+int global_pflag = 0;
+
/* SIGINT received during command processing */
volatile sig_atomic_t interrupted = 0;
/* I wish qsort() took a separate ctx for the comparison function...*/
int sort_flag;
+/* Context used for commandline completion */
+struct complete_ctx {
+ struct sftp_conn *conn;
+ char **remote_pathp;
+};
+
int remote_glob(struct sftp_conn *, const char *, int,
int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
#define I_CHGRP 2
#define I_CHMOD 3
#define I_CHOWN 4
+#define I_DF 24
#define I_GET 5
#define I_HELP 6
#define I_LCHDIR 7
struct CMD {
const char *c;
const int n;
+ const int t;
};
+/* Type of completion */
+#define NOARGS 0
+#define REMOTE 1
+#define LOCAL 2
+
static const struct CMD cmds[] = {
- { "bye", I_QUIT },
- { "cd", I_CHDIR },
- { "chdir", I_CHDIR },
- { "chgrp", I_CHGRP },
- { "chmod", I_CHMOD },
- { "chown", I_CHOWN },
- { "dir", I_LS },
- { "exit", I_QUIT },
- { "get", I_GET },
- { "mget", I_GET },
- { "help", I_HELP },
- { "lcd", I_LCHDIR },
- { "lchdir", I_LCHDIR },
- { "lls", I_LLS },
- { "lmkdir", I_LMKDIR },
- { "ln", I_SYMLINK },
- { "lpwd", I_LPWD },
- { "ls", I_LS },
- { "lumask", I_LUMASK },
- { "mkdir", I_MKDIR },
- { "progress", I_PROGRESS },
- { "put", I_PUT },
- { "mput", I_PUT },
- { "pwd", I_PWD },
- { "quit", I_QUIT },
- { "rename", I_RENAME },
- { "rm", I_RM },
- { "rmdir", I_RMDIR },
- { "symlink", I_SYMLINK },
- { "version", I_VERSION },
- { "!", I_SHELL },
- { "?", I_HELP },
- { NULL, -1}
+ { "bye", I_QUIT, NOARGS },
+ { "cd", I_CHDIR, REMOTE },
+ { "chdir", I_CHDIR, REMOTE },
+ { "chgrp", I_CHGRP, REMOTE },
+ { "chmod", I_CHMOD, REMOTE },
+ { "chown", I_CHOWN, REMOTE },
+ { "df", I_DF, REMOTE },
+ { "dir", I_LS, REMOTE },
+ { "exit", I_QUIT, NOARGS },
+ { "get", I_GET, REMOTE },
+ { "help", I_HELP, NOARGS },
+ { "lcd", I_LCHDIR, LOCAL },
+ { "lchdir", I_LCHDIR, LOCAL },
+ { "lls", I_LLS, LOCAL },
+ { "lmkdir", I_LMKDIR, LOCAL },
+ { "ln", I_SYMLINK, REMOTE },
+ { "lpwd", I_LPWD, LOCAL },
+ { "ls", I_LS, REMOTE },
+ { "lumask", I_LUMASK, NOARGS },
+ { "mkdir", I_MKDIR, REMOTE },
+ { "progress", I_PROGRESS, NOARGS },
+ { "put", I_PUT, LOCAL },
+ { "pwd", I_PWD, REMOTE },
+ { "quit", I_QUIT, NOARGS },
+ { "rename", I_RENAME, REMOTE },
+ { "rm", I_RM, REMOTE },
+ { "rmdir", I_RMDIR, REMOTE },
+ { "symlink", I_SYMLINK, REMOTE },
+ { "version", I_VERSION, NOARGS },
+ { "!", I_SHELL, NOARGS },
+ { "?", I_HELP, NOARGS },
+ { NULL, -1, -1 }
};
-int interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
+int interactive_loop(struct sftp_conn *, char *file1, char *file2);
/* ARGSUSED */
static void
static void
help(void)
{
- printf("Available commands:\n");
- printf("cd path Change remote directory to 'path'\n");
- printf("lcd path Change local directory to 'path'\n");
- printf("chgrp grp path Change group of file 'path' to 'grp'\n");
- printf("chmod mode path Change permissions of file 'path' to 'mode'\n");
- printf("chown own path Change owner of file 'path' to 'own'\n");
- printf("help Display this help text\n");
- printf("get remote-path [local-path] Download file\n");
- printf("lls [ls-options [path]] Display local directory listing\n");
- printf("ln oldpath newpath Symlink remote file\n");
- printf("lmkdir path Create local directory\n");
- printf("lpwd Print local working directory\n");
- printf("ls [path] Display remote directory listing\n");
- printf("lumask umask Set local umask to 'umask'\n");
- printf("mkdir path Create remote directory\n");
- printf("progress Toggle display of progress meter\n");
- printf("put local-path [remote-path] Upload file\n");
- printf("pwd Display remote working directory\n");
- printf("exit Quit sftp\n");
- printf("quit Quit sftp\n");
- printf("rename oldpath newpath Rename remote file\n");
- printf("rmdir path Remove remote directory\n");
- printf("rm path Delete remote file\n");
- printf("symlink oldpath newpath Symlink remote file\n");
- printf("version Show SFTP version\n");
- printf("!command Execute 'command' in local shell\n");
- printf("! Escape to local shell\n");
- printf("? Synonym for help\n");
+ printf("Available commands:\n"
+ "bye Quit sftp\n"
+ "cd path Change remote directory to 'path'\n"
+ "chgrp grp path Change group of file 'path' to 'grp'\n"
+ "chmod mode path Change permissions of file 'path' to 'mode'\n"
+ "chown own path Change owner of file 'path' to 'own'\n"
+ "df [-hi] [path] Display statistics for current directory or\n"
+ " filesystem containing 'path'\n"
+ "exit Quit sftp\n"
+ "get [-Pr] remote-path [local-path] Download file\n"
+ "help Display this help text\n"
+ "lcd path Change local directory to 'path'\n"
+ "lls [ls-options [path]] Display local directory listing\n"
+ "lmkdir path Create local directory\n"
+ "ln oldpath newpath Symlink remote file\n"
+ "lpwd Print local working directory\n"
+ "ls [-1aflnrSt] [path] Display remote directory listing\n"
+ "lumask umask Set local umask to 'umask'\n"
+ "mkdir path Create remote directory\n"
+ "progress Toggle display of progress meter\n"
+ "put [-Pr] local-path [remote-path] Upload file\n"
+ "pwd Display remote working directory\n"
+ "quit Quit sftp\n"
+ "rename oldpath newpath Rename remote file\n"
+ "rm path Delete remote file\n"
+ "rmdir path Remove remote directory\n"
+ "symlink oldpath newpath Symlink remote file\n"
+ "version Show SFTP version\n"
+ "!command Execute 'command' in local shell\n"
+ "! Escape to local shell\n"
+ "? Synonym for help\n");
}
static void
return (xstrdup(path));
}
-static char *
-path_append(char *p1, char *p2)
-{
- char *ret;
- size_t len = strlen(p1) + strlen(p2) + 2;
-
- ret = xmalloc(len);
- strlcpy(ret, p1, len);
- if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
- strlcat(ret, "/", len);
- strlcat(ret, p2, len);
-
- return(ret);
-}
-
static char *
make_absolute(char *p, char *pwd)
{
}
static int
-infer_path(const char *p, char **ifp)
-{
- char *cp;
-
- cp = strrchr(p, '/');
- if (cp == NULL) {
- *ifp = xstrdup(p);
- return(0);
- }
-
- if (!cp[1]) {
- error("Invalid path");
- return(-1);
- }
-
- *ifp = xstrdup(cp + 1);
- return(0);
-}
-
-static int
-parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag)
+parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
+ int *rflag)
{
- extern int optind, optreset, opterr;
+ extern int opterr, optind, optopt, optreset;
int ch;
optind = optreset = 1;
opterr = 0;
- *pflag = 0;
- while ((ch = getopt(argc, argv, "Pp")) != -1) {
+ *rflag = *pflag = 0;
+ while ((ch = getopt(argc, argv, "PpRr")) != -1) {
switch (ch) {
case 'p':
case 'P':
*pflag = 1;
break;
+ case 'r':
+ case 'R':
+ *rflag = 1;
+ break;
default:
- error("%s: Invalid flag -%c", cmd, ch);
+ error("%s: Invalid flag -%c", cmd, optopt);
return -1;
}
}
static int
parse_ls_flags(char **argv, int argc, int *lflag)
{
- extern int optind, optreset, opterr;
+ extern int opterr, optind, optopt, optreset;
int ch;
optind = optreset = 1;
*lflag |= LS_TIME_SORT;
break;
default:
- error("ls: Invalid flag -%c", ch);
+ error("ls: Invalid flag -%c", optopt);
+ return -1;
+ }
+ }
+
+ return optind;
+}
+
+static int
+parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
+{
+ extern int opterr, optind, optopt, optreset;
+ int ch;
+
+ optind = optreset = 1;
+ opterr = 0;
+
+ *hflag = *iflag = 0;
+ while ((ch = getopt(argc, argv, "hi")) != -1) {
+ switch (ch) {
+ case 'h':
+ *hflag = 1;
+ break;
+ case 'i':
+ *iflag = 1;
+ break;
+ default:
+ error("%s: Invalid flag -%c", cmd, optopt);
return -1;
}
}
return(S_ISDIR(a->perm));
}
+/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
+static int
+pathname_is_dir(char *pathname)
+{
+ size_t l = strlen(pathname);
+
+ return l > 0 && pathname[l - 1] == '/';
+}
+
static int
-process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
+process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
+ int pflag, int rflag)
{
char *abs_src = NULL;
char *abs_dst = NULL;
- char *tmp;
glob_t g;
- int err = 0;
- int i;
+ char *filename, *tmp=NULL;
+ int i, err = 0;
abs_src = xstrdup(src);
abs_src = make_absolute(abs_src, pwd);
-
memset(&g, 0, sizeof(g));
+
debug3("Looking up %s", abs_src);
- if (remote_glob(conn, abs_src, 0, NULL, &g)) {
+ if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
error("File \"%s\" not found.", abs_src);
err = -1;
goto out;
}
- /* If multiple matches, dst must be a directory or unspecified */
- if (g.gl_matchc > 1 && dst && !is_dir(dst)) {
- error("Multiple files match, but \"%s\" is not a directory",
- dst);
+ /*
+ * If multiple matches then dst must be a directory or
+ * unspecified.
+ */
+ if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
+ error("Multiple source paths, but destination "
+ "\"%s\" is not a directory", dst);
err = -1;
goto out;
}
for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
- if (infer_path(g.gl_pathv[i], &tmp)) {
+ tmp = xstrdup(g.gl_pathv[i]);
+ if ((filename = basename(tmp)) == NULL) {
+ error("basename %s: %s", tmp, strerror(errno));
+ xfree(tmp);
err = -1;
goto out;
}
if (g.gl_matchc == 1 && dst) {
- /* If directory specified, append filename */
- xfree(tmp);
if (is_dir(dst)) {
- if (infer_path(g.gl_pathv[0], &tmp)) {
- err = 1;
- goto out;
- }
- abs_dst = path_append(dst, tmp);
- xfree(tmp);
- } else
+ abs_dst = path_append(dst, filename);
+ } else {
abs_dst = xstrdup(dst);
+ }
} else if (dst) {
- abs_dst = path_append(dst, tmp);
- xfree(tmp);
- } else
- abs_dst = tmp;
+ abs_dst = path_append(dst, filename);
+ } else {
+ abs_dst = xstrdup(filename);
+ }
+ xfree(tmp);
printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
- if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
- err = -1;
+ if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
+ if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
+ pflag || global_pflag, 1) == -1)
+ err = -1;
+ } else {
+ if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
+ pflag || global_pflag) == -1)
+ err = -1;
+ }
xfree(abs_dst);
abs_dst = NULL;
}
}
static int
-process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
+process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
+ int pflag, int rflag)
{
char *tmp_dst = NULL;
char *abs_dst = NULL;
- char *tmp;
+ char *tmp = NULL, *filename = NULL;
glob_t g;
int err = 0;
- int i;
+ int i, dst_is_dir = 1;
struct stat sb;
if (dst) {
memset(&g, 0, sizeof(g));
debug3("Looking up %s", src);
- if (glob(src, GLOB_NOCHECK, NULL, &g)) {
+ if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
error("File \"%s\" not found.", src);
err = -1;
goto out;
}
+ /* If we aren't fetching to pwd then stash this status for later */
+ if (tmp_dst != NULL)
+ dst_is_dir = remote_is_dir(conn, tmp_dst);
+
/* If multiple matches, dst may be directory or unspecified */
- if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) {
- error("Multiple files match, but \"%s\" is not a directory",
- tmp_dst);
+ if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
+ error("Multiple paths match, but destination "
+ "\"%s\" is not a directory", tmp_dst);
err = -1;
goto out;
}
error("stat %s: %s", g.gl_pathv[i], strerror(errno));
continue;
}
-
- if (!S_ISREG(sb.st_mode)) {
- error("skipping non-regular file %s",
- g.gl_pathv[i]);
- continue;
- }
- if (infer_path(g.gl_pathv[i], &tmp)) {
+
+ tmp = xstrdup(g.gl_pathv[i]);
+ if ((filename = basename(tmp)) == NULL) {
+ error("basename %s: %s", tmp, strerror(errno));
+ xfree(tmp);
err = -1;
goto out;
}
if (g.gl_matchc == 1 && tmp_dst) {
/* If directory specified, append filename */
- if (remote_is_dir(conn, tmp_dst)) {
- if (infer_path(g.gl_pathv[0], &tmp)) {
- err = 1;
- goto out;
- }
- abs_dst = path_append(tmp_dst, tmp);
- xfree(tmp);
- } else
+ if (dst_is_dir)
+ abs_dst = path_append(tmp_dst, filename);
+ else
abs_dst = xstrdup(tmp_dst);
-
} else if (tmp_dst) {
- abs_dst = path_append(tmp_dst, tmp);
- xfree(tmp);
- } else
- abs_dst = make_absolute(tmp, pwd);
+ abs_dst = path_append(tmp_dst, filename);
+ } else {
+ abs_dst = make_absolute(xstrdup(filename), pwd);
+ }
+ xfree(tmp);
printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
- if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
- err = -1;
+ if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
+ if (upload_dir(conn, g.gl_pathv[i], abs_dst,
+ pflag || global_pflag, 1) == -1)
+ err = -1;
+ } else {
+ if (do_upload(conn, g.gl_pathv[i], abs_dst,
+ pflag || global_pflag) == -1)
+ err = -1;
+ }
}
out:
return (0);
}
+static int
+do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
+{
+ struct sftp_statvfs st;
+ char s_used[FMT_SCALED_STRSIZE];
+ char s_avail[FMT_SCALED_STRSIZE];
+ char s_root[FMT_SCALED_STRSIZE];
+ char s_total[FMT_SCALED_STRSIZE];
+ unsigned long long ffree;
+
+ if (do_statvfs(conn, path, &st, 1) == -1)
+ return -1;
+ if (iflag) {
+ ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
+ printf(" Inodes Used Avail "
+ "(root) %%Capacity\n");
+ printf("%11llu %11llu %11llu %11llu %3llu%%\n",
+ (unsigned long long)st.f_files,
+ (unsigned long long)(st.f_files - st.f_ffree),
+ (unsigned long long)st.f_favail,
+ (unsigned long long)st.f_ffree, ffree);
+ } else if (hflag) {
+ strlcpy(s_used, "error", sizeof(s_used));
+ strlcpy(s_avail, "error", sizeof(s_avail));
+ strlcpy(s_root, "error", sizeof(s_root));
+ strlcpy(s_total, "error", sizeof(s_total));
+ fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
+ fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
+ fmt_scaled(st.f_bfree * st.f_frsize, s_root);
+ fmt_scaled(st.f_blocks * st.f_frsize, s_total);
+ printf(" Size Used Avail (root) %%Capacity\n");
+ printf("%7sB %7sB %7sB %7sB %3llu%%\n",
+ s_total, s_used, s_avail, s_root,
+ (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
+ st.f_blocks));
+ } else {
+ printf(" Size Used Avail "
+ "(root) %%Capacity\n");
+ printf("%12llu %12llu %12llu %12llu %3llu%%\n",
+ (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
+ (unsigned long long)(st.f_frsize *
+ (st.f_blocks - st.f_bfree) / 1024),
+ (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
+ (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
+ (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
+ st.f_blocks));
+ }
+ return 0;
+}
+
/*
* Undo escaping of glob sequences in place. Used to undo extra escaping
* applied in makeargv() when the string is destined for a function that
* Split a string into an argument vector using sh(1)-style quoting,
* comment and escaping rules, but with some tweaks to handle glob(3)
* wildcards.
+ * The "sloppy" flag allows for recovery from missing terminating quote, for
+ * use in parsing incomplete commandlines during tab autocompletion.
+ *
* Returns NULL on error or a NULL-terminated array of arguments.
+ *
+ * If "lastquote" is not NULL, the quoting character used for the last
+ * argument is placed in *lastquote ("\0", "'" or "\"").
+ *
+ * If "terminated" is not NULL, *terminated will be set to 1 when the
+ * last argument's quote has been properly terminated or 0 otherwise.
+ * This parameter is only of use if "sloppy" is set.
*/
#define MAXARGS 128
#define MAXARGLEN 8192
static char **
-makeargv(const char *arg, int *argcp)
+makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
+ u_int *terminated)
{
int argc, quot;
size_t i, j;
error("string too long");
return NULL;
}
+ if (terminated != NULL)
+ *terminated = 1;
+ if (lastquote != NULL)
+ *lastquote = '\0';
state = MA_START;
i = j = 0;
for (;;) {
if (state == MA_START) {
argv[argc] = argvs + j;
state = q;
+ if (lastquote != NULL)
+ *lastquote = arg[i];
} else if (state == MA_UNQUOTED)
state = q;
else if (state == q)
if (state == MA_START) {
argv[argc] = argvs + j;
state = MA_UNQUOTED;
+ if (lastquote != NULL)
+ *lastquote = '\0';
}
if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
arg[i + 1] == '*' || arg[i + 1] == '\\') {
goto string_done;
} else if (arg[i] == '\0') {
if (state == MA_SQUOTE || state == MA_DQUOTE) {
+ if (sloppy) {
+ state = MA_UNQUOTED;
+ if (terminated != NULL)
+ *terminated = 0;
+ goto string_done;
+ }
error("Unterminated quoted argument");
return NULL;
}
if (state == MA_START) {
argv[argc] = argvs + j;
state = MA_UNQUOTED;
+ if (lastquote != NULL)
+ *lastquote = '\0';
}
if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
(arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
}
static int
-parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
- unsigned long *n_arg, char **path1, char **path2)
+parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
+ int *hflag, unsigned long *n_arg, char **path1, char **path2)
{
const char *cmd, *cp = *cpp;
char *cp2, **argv;
/* Skip leading whitespace */
cp = cp + strspn(cp, WHITESPACE);
- /* Ignore blank lines and lines which begin with comment '#' char */
- if (*cp == '\0' || *cp == '#')
- return (0);
-
/* Check for leading '-' (disable error processing) */
*iflag = 0;
if (*cp == '-') {
*iflag = 1;
cp++;
+ cp = cp + strspn(cp, WHITESPACE);
}
- if ((argv = makeargv(cp, &argc)) == NULL)
+ /* Ignore blank lines and lines which begin with comment '#' char */
+ if (*cp == '\0' || *cp == '#')
+ return (0);
+
+ if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
return -1;
/* Figure out which command we have */
}
/* Get arguments and parse flags */
- *lflag = *pflag = *n_arg = 0;
+ *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
*path1 = *path2 = NULL;
optidx = 1;
switch (cmdnum) {
case I_GET:
case I_PUT:
- if ((optidx = parse_getput_flags(cmd, argv, argc, pflag)) == -1)
+ if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1)
return -1;
/* Get first pathname (mandatory) */
if (argc - optidx < 1) {
if (cmdnum != I_RM)
undo_glob_escape(*path1);
break;
+ case I_DF:
+ if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
+ iflag)) == -1)
+ return -1;
+ /* Default to current directory if no path specified */
+ if (argc - optidx < 1)
+ *path1 = NULL;
+ else {
+ *path1 = xstrdup(argv[optidx]);
+ undo_glob_escape(*path1);
+ }
+ break;
case I_LS:
if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
return(-1);
int err_abort)
{
char *path1, *path2, *tmp;
- int pflag, lflag, iflag, cmdnum, i;
- unsigned long n_arg;
+ int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
+ unsigned long n_arg = 0;
Attrib a, *aa;
char path_buf[MAXPATHLEN];
int err = 0;
glob_t g;
path1 = path2 = NULL;
- cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &n_arg,
+ cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg,
&path1, &path2);
if (iflag != 0)
err = -1;
break;
case I_GET:
- err = process_get(conn, path1, path2, *pwd, pflag);
+ err = process_get(conn, path1, path2, *pwd, pflag, rflag);
break;
case I_PUT:
- err = process_put(conn, path1, path2, *pwd, pflag);
+ err = process_put(conn, path1, path2, *pwd, pflag, rflag);
break;
case I_RENAME:
path1 = make_absolute(path1, *pwd);
attrib_clear(&a);
a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
a.perm = 0777;
- err = do_mkdir(conn, path1, &a);
+ err = do_mkdir(conn, path1, &a, 1);
break;
case I_RMDIR:
path1 = make_absolute(path1, *pwd);
path1 = make_absolute(path1, *pwd);
err = do_globbed_ls(conn, path1, tmp, lflag);
break;
+ case I_DF:
+ /* Default to current directory if no path specified */
+ if (path1 == NULL)
+ path1 = xstrdup(*pwd);
+ path1 = make_absolute(path1, *pwd);
+ err = do_df(conn, path1, hflag, iflag);
+ break;
case I_LCHDIR:
if (chdir(path1) == -1) {
error("Couldn't change local directory to "
remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
- if (err != 0 && err_abort)
+ if (err_abort) {
+ err = -1;
break;
- else
+ } else
continue;
}
if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
error("Can't get current ownership of "
"remote file \"%s\"", g.gl_pathv[i]);
- if (err != 0 && err_abort)
+ if (err_abort) {
+ err = -1;
break;
- else
+ } else
continue;
}
aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
{
return ("sftp> ");
}
-#endif
+
+/* Display entries in 'list' after skipping the first 'len' chars */
+static void
+complete_display(char **list, u_int len)
+{
+ u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
+ struct winsize ws;
+ char *tmp;
+
+ /* Count entries for sort and find longest */
+ for (y = 0; list[y]; y++)
+ m = MAX(m, strlen(list[y]));
+
+ if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
+ width = ws.ws_col;
+
+ m = m > len ? m - len : 0;
+ columns = width / (m + 2);
+ columns = MAX(columns, 1);
+ colspace = width / columns;
+ colspace = MIN(colspace, width);
+
+ printf("\n");
+ m = 1;
+ for (y = 0; list[y]; y++) {
+ llen = strlen(list[y]);
+ tmp = llen > len ? list[y] + len : "";
+ printf("%-*s", colspace, tmp);
+ if (m >= columns) {
+ printf("\n");
+ m = 1;
+ } else
+ m++;
+ }
+ printf("\n");
+}
+
+/*
+ * Given a "list" of words that begin with a common prefix of "word",
+ * attempt to find an autocompletion to extends "word" by the next
+ * characters common to all entries in "list".
+ */
+static char *
+complete_ambiguous(const char *word, char **list, size_t count)
+{
+ if (word == NULL)
+ return NULL;
+
+ if (count > 0) {
+ u_int y, matchlen = strlen(list[0]);
+
+ /* Find length of common stem */
+ for (y = 1; list[y]; y++) {
+ u_int x;
+
+ for (x = 0; x < matchlen; x++)
+ if (list[0][x] != list[y][x])
+ break;
+
+ matchlen = x;
+ }
+
+ if (matchlen > strlen(word)) {
+ char *tmp = xstrdup(list[0]);
+
+ tmp[matchlen] = '\0';
+ return tmp;
+ }
+ }
+
+ return xstrdup(word);
+}
+
+/* Autocomplete a sftp command */
+static int
+complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
+ int terminated)
+{
+ u_int y, count = 0, cmdlen, tmplen;
+ char *tmp, **list, argterm[3];
+ const LineInfo *lf;
+
+ list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
+
+ /* No command specified: display all available commands */
+ if (cmd == NULL) {
+ for (y = 0; cmds[y].c; y++)
+ list[count++] = xstrdup(cmds[y].c);
+
+ list[count] = NULL;
+ complete_display(list, 0);
+
+ for (y = 0; list[y] != NULL; y++)
+ xfree(list[y]);
+ xfree(list);
+ return count;
+ }
+
+ /* Prepare subset of commands that start with "cmd" */
+ cmdlen = strlen(cmd);
+ for (y = 0; cmds[y].c; y++) {
+ if (!strncasecmp(cmd, cmds[y].c, cmdlen))
+ list[count++] = xstrdup(cmds[y].c);
+ }
+ list[count] = NULL;
+
+ if (count == 0)
+ return 0;
+
+ /* Complete ambigious command */
+ tmp = complete_ambiguous(cmd, list, count);
+ if (count > 1)
+ complete_display(list, 0);
+
+ for (y = 0; list[y]; y++)
+ xfree(list[y]);
+ xfree(list);
+
+ if (tmp != NULL) {
+ tmplen = strlen(tmp);
+ cmdlen = strlen(cmd);
+ /* If cmd may be extended then do so */
+ if (tmplen > cmdlen)
+ if (el_insertstr(el, tmp + cmdlen) == -1)
+ fatal("el_insertstr failed.");
+ lf = el_line(el);
+ /* Terminate argument cleanly */
+ if (count == 1) {
+ y = 0;
+ if (!terminated)
+ argterm[y++] = quote;
+ if (lastarg || *(lf->cursor) != ' ')
+ argterm[y++] = ' ';
+ argterm[y] = '\0';
+ if (y > 0 && el_insertstr(el, argterm) == -1)
+ fatal("el_insertstr failed.");
+ }
+ xfree(tmp);
+ }
+
+ return count;
+}
+
+/*
+ * Determine whether a particular sftp command's arguments (if any)
+ * represent local or remote files.
+ */
+static int
+complete_is_remote(char *cmd) {
+ int i;
+
+ if (cmd == NULL)
+ return -1;
+
+ for (i = 0; cmds[i].c; i++) {
+ if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
+ return cmds[i].t;
+ }
+
+ return -1;
+}
+
+/* Autocomplete a filename "file" */
+static int
+complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
+ char *file, int remote, int lastarg, char quote, int terminated)
+{
+ glob_t g;
+ char *tmp, *tmp2, ins[3];
+ u_int i, hadglob, pwdlen, len, tmplen, filelen;
+ const LineInfo *lf;
+
+ /* Glob from "file" location */
+ if (file == NULL)
+ tmp = xstrdup("*");
+ else
+ xasprintf(&tmp, "%s*", file);
+
+ memset(&g, 0, sizeof(g));
+ if (remote != LOCAL) {
+ tmp = make_absolute(tmp, remote_path);
+ remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
+ } else
+ glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
+
+ /* Determine length of pwd so we can trim completion display */
+ for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
+ /* Terminate counting on first unescaped glob metacharacter */
+ if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
+ if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
+ hadglob = 1;
+ break;
+ }
+ if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
+ tmplen++;
+ if (tmp[tmplen] == '/')
+ pwdlen = tmplen + 1; /* track last seen '/' */
+ }
+ xfree(tmp);
+
+ if (g.gl_matchc == 0)
+ goto out;
+
+ if (g.gl_matchc > 1)
+ complete_display(g.gl_pathv, pwdlen);
+
+ tmp = NULL;
+ /* Don't try to extend globs */
+ if (file == NULL || hadglob)
+ goto out;
+
+ tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
+ tmp = path_strip(tmp2, remote_path);
+ xfree(tmp2);
+
+ if (tmp == NULL)
+ goto out;
+
+ tmplen = strlen(tmp);
+ filelen = strlen(file);
+
+ if (tmplen > filelen) {
+ tmp2 = tmp + filelen;
+ len = strlen(tmp2);
+ /* quote argument on way out */
+ for (i = 0; i < len; i++) {
+ ins[0] = '\\';
+ ins[1] = tmp2[i];
+ ins[2] = '\0';
+ switch (tmp2[i]) {
+ case '\'':
+ case '"':
+ case '\\':
+ case '\t':
+ case ' ':
+ if (quote == '\0' || tmp2[i] == quote) {
+ if (el_insertstr(el, ins) == -1)
+ fatal("el_insertstr "
+ "failed.");
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ if (el_insertstr(el, ins + 1) == -1)
+ fatal("el_insertstr failed.");
+ break;
+ }
+ }
+ }
+
+ lf = el_line(el);
+ /*
+ * XXX should we really extend here? the user may not be done if
+ * the filename is a directory.
+ */
+ if (g.gl_matchc == 1) {
+ i = 0;
+ if (!terminated)
+ ins[i++] = quote;
+ if (lastarg || *(lf->cursor) != ' ')
+ ins[i++] = ' ';
+ ins[i] = '\0';
+ if (i > 0 && el_insertstr(el, ins) == -1)
+ fatal("el_insertstr failed.");
+ }
+ xfree(tmp);
+
+ out:
+ globfree(&g);
+ return g.gl_matchc;
+}
+
+/* tab-completion hook function, called via libedit */
+static unsigned char
+complete(EditLine *el, int ch)
+{
+ char **argv, *line, quote;
+ u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
+ const LineInfo *lf;
+ struct complete_ctx *complete_ctx;
+
+ lf = el_line(el);
+ if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
+ fatal("%s: el_get failed", __func__);
+
+ /* Figure out which argument the cursor points to */
+ cursor = lf->cursor - lf->buffer;
+ line = (char *)xmalloc(cursor + 1);
+ memcpy(line, lf->buffer, cursor);
+ line[cursor] = '\0';
+ argv = makeargv(line, &carg, 1, "e, &terminated);
+ xfree(line);
+
+ /* Get all the arguments on the line */
+ len = lf->lastchar - lf->buffer;
+ line = (char *)xmalloc(len + 1);
+ memcpy(line, lf->buffer, len);
+ line[len] = '\0';
+ argv = makeargv(line, &argc, 1, NULL, NULL);
+
+ /* Ensure cursor is at EOL or a argument boundary */
+ if (line[cursor] != ' ' && line[cursor] != '\0' &&
+ line[cursor] != '\n') {
+ xfree(line);
+ return ret;
+ }
+
+ if (carg == 0) {
+ /* Show all available commands */
+ complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
+ ret = CC_REDISPLAY;
+ } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
+ /* Handle the command parsing */
+ if (complete_cmd_parse(el, argv[0], argc == carg,
+ quote, terminated) != 0)
+ ret = CC_REDISPLAY;
+ } else if (carg >= 1) {
+ /* Handle file parsing */
+ int remote = complete_is_remote(argv[0]);
+ char *filematch = NULL;
+
+ if (carg > 1 && line[cursor-1] != ' ')
+ filematch = argv[carg - 1];
+
+ if (remote != 0 &&
+ complete_match(el, complete_ctx->conn,
+ *complete_ctx->remote_pathp, filematch,
+ remote, carg == argc, quote, terminated) != 0)
+ ret = CC_REDISPLAY;
+ }
+
+ xfree(line);
+ return ret;
+}
+#endif /* USE_LIBEDIT */
int
-interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
+interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
{
- char *pwd;
+ char *remote_path;
char *dir = NULL;
char cmd[2048];
- struct sftp_conn *conn;
int err, interactive;
EditLine *el = NULL;
#ifdef USE_LIBEDIT
History *hl = NULL;
HistEvent hev;
extern char *__progname;
+ struct complete_ctx complete_ctx;
if (!batchmode && isatty(STDIN_FILENO)) {
if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
el_set(el, EL_TERMINAL, NULL);
el_set(el, EL_SIGNAL, 1);
el_source(el, NULL);
+
+ /* Tab Completion */
+ el_set(el, EL_ADDFN, "ftp-complete",
+ "Context senstive argument completion", complete);
+ complete_ctx.conn = conn;
+ complete_ctx.remote_pathp = &remote_path;
+ el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
+ el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
}
#endif /* USE_LIBEDIT */
- conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
- if (conn == NULL)
- fatal("Couldn't initialise connection to server");
-
- pwd = do_realpath(conn, ".");
- if (pwd == NULL)
+ remote_path = do_realpath(conn, ".");
+ if (remote_path == NULL)
fatal("Need cwd");
if (file1 != NULL) {
dir = xstrdup(file1);
- dir = make_absolute(dir, pwd);
+ dir = make_absolute(dir, remote_path);
if (remote_is_dir(conn, dir) && file2 == NULL) {
printf("Changing to: %s\n", dir);
snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
- if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) {
+ if (parse_dispatch_command(conn, cmd,
+ &remote_path, 1) != 0) {
xfree(dir);
- xfree(pwd);
+ xfree(remote_path);
xfree(conn);
return (-1);
}
snprintf(cmd, sizeof cmd, "get %s %s", dir,
file2);
- err = parse_dispatch_command(conn, cmd, &pwd, 1);
+ err = parse_dispatch_command(conn, cmd,
+ &remote_path, 1);
xfree(dir);
- xfree(pwd);
+ xfree(remote_path);
xfree(conn);
return (err);
}
const char *line;
int count = 0;
- if ((line = el_gets(el, &count)) == NULL || count <= 0) {
+ if ((line = el_gets(el, &count)) == NULL ||
+ count <= 0) {
printf("\n");
break;
}
interrupted = 0;
signal(SIGINT, cmd_interrupt);
- err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
+ err = parse_dispatch_command(conn, cmd, &remote_path,
+ batchmode);
if (err != 0)
break;
}
- xfree(pwd);
+ xfree(remote_path);
xfree(conn);
#ifdef USE_LIBEDIT
extern char *__progname;
fprintf(stderr,
- "usage: %s [-1Cv] [-B buffer_size] [-b batchfile] [-F ssh_config]\n"
- " [-o ssh_option] [-P sftp_server_path] [-R num_requests]\n"
- " [-S program] [-s subsystem | sftp_server] host\n"
- " %s [[user@]host[:file [file]]]\n"
- " %s [[user@]host[:dir[/]]]\n"
- " %s -b batchfile [user@]host\n", __progname, __progname, __progname, __progname);
+ "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
+ " [-D sftp_server_path] [-F ssh_config] "
+ "[-i identity_file]\n"
+ " [-o ssh_option] [-P port] [-R num_requests] "
+ "[-S program]\n"
+ " [-s subsystem | sftp_server] host\n"
+ " %s [user@]host[:file ...]\n"
+ " %s [user@]host[:dir[/]]\n"
+ " %s -b batchfile [user@]host\n",
+ __progname, __progname, __progname, __progname);
exit(1);
}
main(int argc, char **argv)
{
int in, out, ch, err;
- char *host, *userhost, *cp, *file2 = NULL;
+ char *host = NULL, *userhost, *cp, *file2 = NULL;
int debug_level = 0, sshver = 2;
char *file1 = NULL, *sftp_server = NULL;
char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
arglist args;
extern int optind;
extern char *optarg;
+ struct sftp_conn *conn;
+ size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
+ size_t num_requests = DEFAULT_NUM_REQUESTS;
/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
sanitise_stdfd();
ll = SYSLOG_LEVEL_INFO;
infile = stdin;
- while ((ch = getopt(argc, argv, "1hvCo:s:S:b:B:F:P:R:")) != -1) {
+ while ((ch = getopt(argc, argv,
+ "1246hqrvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
switch (ch) {
+ /* Passed through to ssh(1) */
+ case '4':
+ case '6':
case 'C':
- addargs(&args, "-C");
+ addargs(&args, "-%c", ch);
+ break;
+ /* Passed through to ssh(1) with argument */
+ case 'F':
+ case 'c':
+ case 'i':
+ case 'o':
+ addargs(&args, "-%c", ch);
+ addargs(&args, "%s", optarg);
+ break;
+ case 'q':
+ showprogress = 0;
+ addargs(&args, "-%c", ch);
+ break;
+ case 'P':
+ addargs(&args, "-oPort %s", optarg);
break;
case 'v':
if (debug_level < 3) {
}
debug_level++;
break;
- case 'F':
- case 'o':
- addargs(&args, "-%c%s", ch, optarg);
- break;
case '1':
sshver = 1;
if (sftp_server == NULL)
sftp_server = _PATH_SFTP_SERVER;
break;
- case 's':
- sftp_server = optarg;
+ case '2':
+ sshver = 2;
break;
- case 'S':
- ssh_program = optarg;
- replacearg(&args, 0, "%s", ssh_program);
+ case 'B':
+ copy_buffer_len = strtol(optarg, &cp, 10);
+ if (copy_buffer_len == 0 || *cp != '\0')
+ fatal("Invalid buffer size \"%s\"", optarg);
break;
case 'b':
if (batchmode)
batchmode = 1;
addargs(&args, "-obatchmode yes");
break;
- case 'P':
+ case 'p':
+ global_pflag = 1;
+ break;
+ case 'D':
sftp_direct = optarg;
break;
- case 'B':
- copy_buffer_len = strtol(optarg, &cp, 10);
- if (copy_buffer_len == 0 || *cp != '\0')
- fatal("Invalid buffer size \"%s\"", optarg);
+ case 'r':
+ global_rflag = 1;
break;
case 'R':
num_requests = strtol(optarg, &cp, 10);
fatal("Invalid number of requests \"%s\"",
optarg);
break;
+ case 's':
+ sftp_server = optarg;
+ break;
+ case 'S':
+ ssh_program = optarg;
+ replacearg(&args, 0, "%s", ssh_program);
+ break;
case 'h':
default:
usage();
fprintf(stderr, "Missing username\n");
usage();
}
- addargs(&args, "-l%s", userhost);
+ addargs(&args, "-l");
+ addargs(&args, "%s", userhost);
}
if ((cp = colon(host)) != NULL) {
if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
addargs(&args, "-s");
+ addargs(&args, "--");
addargs(&args, "%s", host);
addargs(&args, "%s", (sftp_server != NULL ?
sftp_server : "sftp"));
- if (!batchmode)
- fprintf(stderr, "Connecting to %s...\n", host);
connect_to_server(ssh_program, args.list, &in, &out);
} else {
args.list = NULL;
addargs(&args, "sftp-server");
- if (!batchmode)
- fprintf(stderr, "Attaching to %s...\n", sftp_direct);
connect_to_server(sftp_direct, args.list, &in, &out);
}
freeargs(&args);
- err = interactive_loop(in, out, file1, file2);
+ conn = do_init(in, out, copy_buffer_len, num_requests);
+ if (conn == NULL)
+ fatal("Couldn't initialise connection to server");
+
+ if (!batchmode) {
+ if (sftp_direct == NULL)
+ fprintf(stderr, "Connected to %s.\n", host);
+ else
+ fprintf(stderr, "Attached to %s.\n", sftp_direct);
+ }
+
+ err = interactive_loop(conn, file1, file2);
#if !defined(USE_PIPES)
shutdown(in, SHUT_RDWR);