diff options
Diffstat (limited to 'sftp.c')
-rw-r--r-- | sftp.c | 457 |
1 files changed, 296 insertions, 161 deletions
@@ -1,4 +1,4 @@ -/* $OpenBSD: sftp.c,v 1.134 2011/11/16 12:24:28 oga Exp $ */ +/* $OpenBSD: sftp.c,v 1.158 2013/11/20 20:54:10 deraadt Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> * @@ -38,6 +38,9 @@ #ifdef HAVE_LIBGEN_H #include <libgen.h> #endif +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif #ifdef USE_LIBEDIT #include <histedit.h> #else @@ -54,10 +57,6 @@ typedef void EditLine; # include <util.h> #endif -#ifdef HAVE_LIBUTIL_H -# include <libutil.h> -#endif - #include "xmalloc.h" #include "log.h" #include "pathnames.h" @@ -80,15 +79,24 @@ int batchmode = 0; /* PID of ssh transport process */ static pid_t sshpid = -1; +/* Suppress diagnositic messages */ +int quiet = 0; + /* 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, we resume download if possible */ +int global_aflag = 0; + /* When this option is set, the file transfers will always preserve times */ int global_pflag = 0; +/* When this option is set, transfers will have fsync() called on each file */ +int global_fflag = 0; + /* SIGINT received during command processing */ volatile sig_atomic_t interrupted = 0; @@ -124,31 +132,34 @@ extern char *__progname; #define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT) /* Commands for interactive mode */ -#define I_CHDIR 1 -#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 -#define I_LINK 25 -#define I_LLS 8 -#define I_LMKDIR 9 -#define I_LPWD 10 -#define I_LS 11 -#define I_LUMASK 12 -#define I_MKDIR 13 -#define I_PUT 14 -#define I_PWD 15 -#define I_QUIT 16 -#define I_RENAME 17 -#define I_RM 18 -#define I_RMDIR 19 -#define I_SHELL 20 -#define I_SYMLINK 21 -#define I_VERSION 22 -#define I_PROGRESS 23 +enum sftp_command { + I_CHDIR = 1, + I_CHGRP, + I_CHMOD, + I_CHOWN, + I_DF, + I_GET, + I_HELP, + I_LCHDIR, + I_LINK, + I_LLS, + I_LMKDIR, + I_LPWD, + I_LS, + I_LUMASK, + I_MKDIR, + I_PUT, + I_PWD, + I_QUIT, + I_RENAME, + I_RM, + I_RMDIR, + I_SHELL, + I_SYMLINK, + I_VERSION, + I_PROGRESS, + I_REGET, +}; struct CMD { const char *c; @@ -188,6 +199,7 @@ static const struct CMD cmds[] = { { "put", I_PUT, LOCAL }, { "pwd", I_PWD, REMOTE }, { "quit", I_QUIT, NOARGS }, + { "reget", I_REGET, REMOTE }, { "rename", I_RENAME, REMOTE }, { "rm", I_RM, REMOTE }, { "rmdir", I_RMDIR, REMOTE }, @@ -219,7 +231,7 @@ cmd_interrupt(int signo) const char msg[] = "\rInterrupt \n"; int olderrno = errno; - write(STDERR_FILENO, msg, sizeof(msg) - 1); + (void)write(STDERR_FILENO, msg, sizeof(msg) - 1); interrupted = 1; errno = olderrno; } @@ -237,6 +249,7 @@ help(void) " filesystem containing 'path'\n" "exit Quit sftp\n" "get [-Ppr] remote [local] Download file\n" + "reget remote [local] Resume 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" @@ -310,7 +323,7 @@ local_do_ls(const char *args) /* XXX: quoting - rip quoting code from ftp? */ snprintf(buf, len, _PATH_LS " %s", args); local_do_shell(buf); - xfree(buf); + free(buf); } } @@ -341,15 +354,15 @@ make_absolute(char *p, char *pwd) /* Derelativise */ if (p && p[0] != '/') { abs_str = path_append(pwd, p); - xfree(p); + free(p); return(abs_str); } else return(p); } static int -parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag, - int *rflag) +parse_getput_flags(const char *cmd, char **argv, int argc, + int *aflag, int *fflag, int *pflag, int *rflag) { extern int opterr, optind, optopt, optreset; int ch; @@ -357,9 +370,15 @@ parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag, optind = optreset = 1; opterr = 0; - *rflag = *pflag = 0; - while ((ch = getopt(argc, argv, "PpRr")) != -1) { + *aflag = *fflag = *rflag = *pflag = 0; + while ((ch = getopt(argc, argv, "afPpRr")) != -1) { switch (ch) { + case 'a': + *aflag = 1; + break; + case 'f': + *fflag = 1; + break; case 'p': case 'P': *pflag = 1; @@ -402,6 +421,30 @@ parse_link_flags(const char *cmd, char **argv, int argc, int *sflag) } static int +parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag) +{ + extern int opterr, optind, optopt, optreset; + int ch; + + optind = optreset = 1; + opterr = 0; + + *lflag = 0; + while ((ch = getopt(argc, argv, "l")) != -1) { + switch (ch) { + case 'l': + *lflag = 1; + break; + default: + error("%s: Invalid flag -%c", cmd, optopt); + return -1; + } + } + + return optind; +} + +static int parse_ls_flags(char **argv, int argc, int *lflag) { extern int opterr, optind, optopt, optreset; @@ -482,6 +525,26 @@ parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag) } static int +parse_no_flags(const char *cmd, char **argv, int argc) +{ + extern int opterr, optind, optopt, optreset; + int ch; + + optind = optreset = 1; + opterr = 0; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + error("%s: Invalid flag -%c", cmd, optopt); + return -1; + } + } + + return optind; +} + +static int is_dir(char *path) { struct stat sb; @@ -517,7 +580,7 @@ pathname_is_dir(char *pathname) static int process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, - int pflag, int rflag) + int pflag, int rflag, int resume, int fflag) { char *abs_src = NULL; char *abs_dst = NULL; @@ -551,7 +614,7 @@ process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, tmp = xstrdup(g.gl_pathv[i]); if ((filename = basename(tmp)) == NULL) { error("basename %s: %s", tmp, strerror(errno)); - xfree(tmp); + free(tmp); err = -1; goto out; } @@ -567,31 +630,37 @@ process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, } else { abs_dst = xstrdup(filename); } - xfree(tmp); + free(tmp); - printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst); + resume |= global_aflag; + if (!quiet && resume) + printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst); + else if (!quiet && !resume) + printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst); 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) + if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL, + pflag || global_pflag, 1, resume, + fflag || global_fflag) == -1) err = -1; } else { if (do_download(conn, g.gl_pathv[i], abs_dst, NULL, - pflag || global_pflag) == -1) + pflag || global_pflag, resume, + fflag || global_fflag) == -1) err = -1; } - xfree(abs_dst); + free(abs_dst); abs_dst = NULL; } out: - xfree(abs_src); + free(abs_src); globfree(&g); return(err); } static int process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, - int pflag, int rflag) + int pflag, int rflag, int fflag) { char *tmp_dst = NULL; char *abs_dst = NULL; @@ -632,11 +701,11 @@ process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, error("stat %s: %s", g.gl_pathv[i], strerror(errno)); continue; } - + tmp = xstrdup(g.gl_pathv[i]); if ((filename = basename(tmp)) == NULL) { error("basename %s: %s", tmp, strerror(errno)); - xfree(tmp); + free(tmp); err = -1; goto out; } @@ -652,25 +721,26 @@ process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, } else { abs_dst = make_absolute(xstrdup(filename), pwd); } - xfree(tmp); + free(tmp); - printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst); + if (!quiet) + printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst); 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) + pflag || global_pflag, 1, + fflag || global_fflag) == -1) err = -1; } else { if (do_upload(conn, g.gl_pathv[i], abs_dst, - pflag || global_pflag) == -1) + pflag || global_pflag, + fflag || global_fflag) == -1) err = -1; } } out: - if (abs_dst) - xfree(abs_dst); - if (tmp_dst) - xfree(tmp_dst); + free(abs_dst); + free(tmp_dst); globfree(&g); return(err); } @@ -718,7 +788,7 @@ do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) /* Add any subpath that also needs to be counted */ tmp = path_strip(path, strip_path); m += strlen(tmp); - xfree(tmp); + free(tmp); if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) width = ws.ws_col; @@ -744,7 +814,7 @@ do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) tmp = path_append(path, d[n]->filename); fname = path_strip(tmp, strip_path); - xfree(tmp); + free(tmp); if (lflag & LS_LONG_VIEW) { if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) { @@ -756,7 +826,7 @@ do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) lname = ls_file(fname, &sb, 1, (lflag & LS_SI_UNITS)); printf("%s\n", lname); - xfree(lname); + free(lname); } else printf("%s\n", d[n]->longname); } else { @@ -768,7 +838,7 @@ do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) c++; } - xfree(fname); + free(fname); } if (!(lflag & LS_LONG_VIEW) && (c != 1)) @@ -783,7 +853,6 @@ static int do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, int lflag) { - Attrib *a = NULL; char *fname, *lname; glob_t g; int err; @@ -829,7 +898,7 @@ do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, colspace = width / columns; } - for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) { + for (i = 0; g.gl_pathv[i] && !interrupted; i++) { fname = path_strip(g.gl_pathv[i], strip_path); if (lflag & LS_LONG_VIEW) { if (g.gl_statv[i] == NULL) { @@ -839,7 +908,7 @@ do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, lname = ls_file(fname, g.gl_statv[i], 1, (lflag & LS_SI_UNITS)); printf("%s\n", lname); - xfree(lname); + free(lname); } else { printf("%-*s", colspace, fname); if (c >= columns) { @@ -848,7 +917,7 @@ do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, } else c++; } - xfree(fname); + free(fname); } if (!(lflag & LS_LONG_VIEW) && (c != 1)) @@ -962,7 +1031,7 @@ undo_glob_escape(char *s) * * 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. @@ -992,7 +1061,11 @@ makeargv(const char *arg, int *argcp, int sloppy, char *lastquote, state = MA_START; i = j = 0; for (;;) { - if (isspace(arg[i])) { + if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){ + error("Too many arguments."); + return NULL; + } + if (isspace((unsigned char)arg[i])) { if (state == MA_UNQUOTED) { /* Terminate current argument */ argvs[j++] = '\0'; @@ -1007,7 +1080,7 @@ makeargv(const char *arg, int *argcp, int sloppy, char *lastquote, state = q; if (lastquote != NULL) *lastquote = arg[i]; - } else if (state == MA_UNQUOTED) + } else if (state == MA_UNQUOTED) state = q; else if (state == q) state = MA_UNQUOTED; @@ -1113,8 +1186,9 @@ makeargv(const char *arg, int *argcp, int sloppy, char *lastquote, } static int -parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, - int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2) +parse_args(const char **cpp, int *ignore_errors, int *aflag, int *fflag, + int *hflag, int *iflag, int *lflag, int *pflag, int *rflag, int *sflag, + unsigned long *n_arg, char **path1, char **path2) { const char *cmd, *cp = *cpp; char *cp2, **argv; @@ -1126,9 +1200,9 @@ parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, cp = cp + strspn(cp, WHITESPACE); /* Check for leading '-' (disable error processing) */ - *iflag = 0; + *ignore_errors = 0; if (*cp == '-') { - *iflag = 1; + *ignore_errors = 1; cp++; cp = cp + strspn(cp, WHITESPACE); } @@ -1142,7 +1216,7 @@ parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, /* Figure out which command we have */ for (i = 0; cmds[i].c != NULL; i++) { - if (strcasecmp(cmds[i].c, argv[0]) == 0) + if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0) break; } cmdnum = cmds[i].n; @@ -1158,14 +1232,16 @@ parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, } /* Get arguments and parse flags */ - *lflag = *pflag = *rflag = *hflag = *n_arg = 0; + *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0; + *rflag = *sflag = 0; *path1 = *path2 = NULL; optidx = 1; switch (cmdnum) { case I_GET: + case I_REGET: case I_PUT: if ((optidx = parse_getput_flags(cmd, argv, argc, - pflag, rflag)) == -1) + aflag, fflag, pflag, rflag)) == -1) return -1; /* Get first pathname (mandatory) */ if (argc - optidx < 1) { @@ -1180,12 +1256,24 @@ parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, /* Destination is not globbed */ undo_glob_escape(*path2); } + if (*aflag && cmdnum == I_PUT) { + /* XXX implement resume for uploads */ + error("Resume is not supported for uploads"); + return -1; + } break; case I_LINK: if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1) return -1; - case I_SYMLINK: + goto parse_two_paths; case I_RENAME: + if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1) + return -1; + goto parse_two_paths; + case I_SYMLINK: + if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) + return -1; + parse_two_paths: if (argc - optidx < 2) { error("You must specify two paths after a %s " "command.", cmd); @@ -1203,6 +1291,8 @@ parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, case I_CHDIR: case I_LCHDIR: case I_LMKDIR: + if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) + return -1; /* Get pathname (mandatory) */ if (argc - optidx < 1) { error("You must specify a path after a %s command.", @@ -1244,6 +1334,8 @@ parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, base = 8; case I_CHOWN: case I_CHGRP: + if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) + return -1; /* Get numeric arg (mandatory) */ if (argc - optidx < 1) goto need_num_arg; @@ -1274,6 +1366,8 @@ parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, case I_HELP: case I_VERSION: case I_PROGRESS: + if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) + return -1; break; default: fatal("Command not implemented"); @@ -1288,7 +1382,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, int err_abort) { char *path1, *path2, *tmp; - int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0; + int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0, iflag = 0; + int lflag = 0, pflag = 0, rflag = 0, sflag = 0; int cmdnum, i; unsigned long n_arg = 0; Attrib a, *aa; @@ -1297,10 +1392,9 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, glob_t g; path1 = path2 = NULL; - cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, - &sflag, &n_arg, &path1, &path2); - - if (iflag != 0) + cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag, + &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2); + if (ignore_errors != 0) err_abort = 0; memset(&g, 0, sizeof(g)); @@ -1314,21 +1408,27 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, /* Unrecognized command */ err = -1; break; + case I_REGET: + aflag = 1; + /* FALLTHROUGH */ case I_GET: - err = process_get(conn, path1, path2, *pwd, pflag, rflag); + err = process_get(conn, path1, path2, *pwd, pflag, + rflag, aflag, fflag); break; case I_PUT: - err = process_put(conn, path1, path2, *pwd, pflag, rflag); + err = process_put(conn, path1, path2, *pwd, pflag, + rflag, fflag); break; case I_RENAME: path1 = make_absolute(path1, *pwd); path2 = make_absolute(path2, *pwd); - err = do_rename(conn, path1, path2); + err = do_rename(conn, path1, path2, lflag); break; case I_SYMLINK: sflag = 1; case I_LINK: - path1 = make_absolute(path1, *pwd); + if (!sflag) + path1 = make_absolute(path1, *pwd); path2 = make_absolute(path2, *pwd); err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2); break; @@ -1336,7 +1436,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, path1 = make_absolute(path1, *pwd); remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); for (i = 0; g.gl_pathv[i] && !interrupted; i++) { - printf("Removing %s\n", g.gl_pathv[i]); + if (!quiet) + printf("Removing %s\n", g.gl_pathv[i]); err = do_rm(conn, g.gl_pathv[i]); if (err != 0 && err_abort) break; @@ -1360,24 +1461,24 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, break; } if ((aa = do_stat(conn, tmp, 0)) == NULL) { - xfree(tmp); + free(tmp); err = 1; break; } if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { error("Can't change directory: Can't check target"); - xfree(tmp); + free(tmp); err = 1; break; } if (!S_ISDIR(aa->perm)) { error("Can't change directory: \"%s\" is not " "a directory", tmp); - xfree(tmp); + free(tmp); err = 1; break; } - xfree(*pwd); + free(*pwd); *pwd = tmp; break; case I_LS: @@ -1432,7 +1533,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, a.perm = n_arg; remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); for (i = 0; g.gl_pathv[i] && !interrupted; i++) { - printf("Changing mode on %s\n", g.gl_pathv[i]); + if (!quiet) + printf("Changing mode on %s\n", g.gl_pathv[i]); err = do_setstat(conn, g.gl_pathv[i], &a); if (err != 0 && err_abort) break; @@ -1461,10 +1563,14 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, } aa->flags &= SSH2_FILEXFER_ATTR_UIDGID; if (cmdnum == I_CHOWN) { - printf("Changing owner on %s\n", g.gl_pathv[i]); + if (!quiet) + printf("Changing owner on %s\n", + g.gl_pathv[i]); aa->uid = n_arg; } else { - printf("Changing group on %s\n", g.gl_pathv[i]); + if (!quiet) + printf("Changing group on %s\n", + g.gl_pathv[i]); aa->gid = n_arg; } err = do_setstat(conn, g.gl_pathv[i], aa); @@ -1505,10 +1611,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, if (g.gl_pathc) globfree(&g); - if (path1) - xfree(path1); - if (path2) - xfree(path2); + free(path1); + free(path2); /* If an unignored error occurs in batch mode we should abort. */ if (err_abort && err != 0) @@ -1535,7 +1639,7 @@ complete_display(char **list, u_int len) char *tmp; /* Count entries for sort and find longest */ - for (y = 0; list[y]; y++) + for (y = 0; list[y]; y++) m = MAX(m, strlen(list[y])); if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) @@ -1580,8 +1684,8 @@ complete_ambiguous(const char *word, char **list, size_t count) for (y = 1; list[y]; y++) { u_int x; - for (x = 0; x < matchlen; x++) - if (list[0][x] != list[y][x]) + for (x = 0; x < matchlen; x++) + if (list[0][x] != list[y][x]) break; matchlen = x; @@ -1593,7 +1697,7 @@ complete_ambiguous(const char *word, char **list, size_t count) tmp[matchlen] = '\0'; return tmp; } - } + } return xstrdup(word); } @@ -1613,26 +1717,26 @@ complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote, 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); + for (y = 0; list[y] != NULL; y++) + free(list[y]); + free(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)) + if (!strncasecmp(cmd, cmds[y].c, cmdlen)) list[count++] = xstrdup(cmds[y].c); } list[count] = NULL; if (count == 0) { - xfree(list); + free(list); return 0; } @@ -1641,9 +1745,9 @@ complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote, if (count > 1) complete_display(list, 0); - for (y = 0; list[y]; y++) - xfree(list[y]); - xfree(list); + for (y = 0; list[y]; y++) + free(list[y]); + free(list); if (tmp != NULL) { tmplen = strlen(tmp); @@ -1664,7 +1768,7 @@ complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote, if (y > 0 && el_insertstr(el, argterm) == -1) fatal("el_insertstr failed."); } - xfree(tmp); + free(tmp); } return count; @@ -1682,7 +1786,7 @@ complete_is_remote(char *cmd) { return -1; for (i = 0; cmds[i].c; i++) { - if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c))) + if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c))) return cmds[i].t; } @@ -1695,23 +1799,27 @@ 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; + char *tmp, *tmp2, ins[8]; + u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs; + int clen; const LineInfo *lf; - + /* Glob from "file" location */ if (file == NULL) tmp = xstrdup("*"); else xasprintf(&tmp, "%s*", file); + /* Check if the path is absolute. */ + isabs = tmp[0] == '/'; + 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 + } 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 */ @@ -1725,9 +1833,9 @@ complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path, if (tmp[tmplen] == '/') pwdlen = tmplen + 1; /* track last seen '/' */ } - xfree(tmp); + free(tmp); - if (g.gl_matchc == 0) + if (g.gl_matchc == 0) goto out; if (g.gl_matchc > 1) @@ -1739,8 +1847,8 @@ complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path, goto out; tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc); - tmp = path_strip(tmp2, remote_path); - xfree(tmp2); + tmp = path_strip(tmp2, isabs ? NULL : remote_path); + free(tmp2); if (tmp == NULL) goto out; @@ -1748,14 +1856,27 @@ complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path, tmplen = strlen(tmp); filelen = strlen(file); - if (tmplen > filelen) { - tmp2 = tmp + filelen; - len = strlen(tmp2); + /* Count the number of escaped characters in the input string. */ + cesc = isesc = 0; + for (i = 0; i < filelen; i++) { + if (!isesc && file[i] == '\\' && i + 1 < filelen){ + isesc = 1; + cesc++; + } else + isesc = 0; + } + + if (tmplen > (filelen - cesc)) { + tmp2 = tmp + filelen - cesc; + len = strlen(tmp2); /* quote argument on way out */ - for (i = 0; i < len; i++) { + for (i = 0; i < len; i += clen) { + if ((clen = mblen(tmp2 + i, len - i)) < 0 || + (size_t)clen > sizeof(ins) - 2) + fatal("invalid multibyte character"); ins[0] = '\\'; - ins[1] = tmp2[i]; - ins[2] = '\0'; + memcpy(ins + 1, tmp2 + i, clen); + ins[clen + 1] = '\0'; switch (tmp2[i]) { case '\'': case '"': @@ -1763,6 +1884,8 @@ complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path, case '\t': case '[': case ' ': + case '#': + case '*': if (quote == '\0' || tmp2[i] == quote) { if (el_insertstr(el, ins) == -1) fatal("el_insertstr " @@ -1790,7 +1913,7 @@ complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path, if (i > 0 && el_insertstr(el, ins) == -1) fatal("el_insertstr failed."); } - xfree(tmp); + free(tmp); out: globfree(&g); @@ -1801,8 +1924,9 @@ complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path, static unsigned char complete(EditLine *el, int ch) { - char **argv, *line, quote; - u_int argc, carg, cursor, len, terminated, ret = CC_ERROR; + char **argv, *line, quote; + int argc, carg; + u_int cursor, len, terminated, ret = CC_ERROR; const LineInfo *lf; struct complete_ctx *complete_ctx; @@ -1816,7 +1940,7 @@ complete(EditLine *el, int ch) memcpy(line, lf->buffer, cursor); line[cursor] = '\0'; argv = makeargv(line, &carg, 1, "e, &terminated); - xfree(line); + free(line); /* Get all the arguments on the line */ len = lf->lastchar - lf->buffer; @@ -1828,7 +1952,7 @@ complete(EditLine *el, int ch) /* Ensure cursor is at EOL or a argument boundary */ if (line[cursor] != ' ' && line[cursor] != '\0' && line[cursor] != '\n') { - xfree(line); + free(line); return ret; } @@ -1839,7 +1963,7 @@ complete(EditLine *el, int ch) } 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) + quote, terminated) != 0) ret = CC_REDISPLAY; } else if (carg >= 1) { /* Handle file parsing */ @@ -1852,11 +1976,11 @@ complete(EditLine *el, int ch) if (remote != 0 && complete_match(el, complete_ctx->conn, *complete_ctx->remote_pathp, filematch, - remote, carg == argc, quote, terminated) != 0) + remote, carg == argc, quote, terminated) != 0) ret = CC_REDISPLAY; } - xfree(line); + free(line); return ret; } #endif /* USE_LIBEDIT */ @@ -1890,12 +2014,19 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2) el_source(el, NULL); /* Tab Completion */ - el_set(el, EL_ADDFN, "ftp-complete", + el_set(el, EL_ADDFN, "ftp-complete", "Context sensitive 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); + /* enable ctrl-left-arrow and ctrl-right-arrow */ + el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL); + el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL); + el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL); + el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL); + /* make ^w match ksh behaviour */ + el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL); } #endif /* USE_LIBEDIT */ @@ -1908,39 +2039,34 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2) dir = make_absolute(dir, remote_path); if (remote_is_dir(conn, dir) && file2 == NULL) { - printf("Changing to: %s\n", dir); + if (!quiet) + printf("Changing to: %s\n", dir); snprintf(cmd, sizeof cmd, "cd \"%s\"", dir); if (parse_dispatch_command(conn, cmd, &remote_path, 1) != 0) { - xfree(dir); - xfree(remote_path); - xfree(conn); + free(dir); + free(remote_path); + free(conn); return (-1); } } else { - if (file2 == NULL) - snprintf(cmd, sizeof cmd, "get %s", dir); - else - snprintf(cmd, sizeof cmd, "get %s %s", dir, - file2); - + /* XXX this is wrong wrt quoting */ + snprintf(cmd, sizeof cmd, "get%s %s%s%s", + global_aflag ? " -a" : "", dir, + file2 == NULL ? "" : " ", + file2 == NULL ? "" : file2); err = parse_dispatch_command(conn, cmd, &remote_path, 1); - xfree(dir); - xfree(remote_path); - xfree(conn); + free(dir); + free(remote_path); + free(conn); return (err); } - xfree(dir); + free(dir); } -#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF) - setvbuf(stdout, NULL, _IOLBF, 0); - setvbuf(infile, NULL, _IOLBF, 0); -#else setlinebuf(stdout); setlinebuf(infile); -#endif interactive = !batchmode && isatty(STDIN_FILENO); err = 0; @@ -1994,8 +2120,8 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2) if (err != 0) break; } - xfree(remote_path); - xfree(conn); + free(remote_path); + free(conn); #ifdef USE_LIBEDIT if (el != NULL) @@ -2069,7 +2195,7 @@ usage(void) extern char *__progname; fprintf(stderr, - "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n" + "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n" " [-D sftp_server_path] [-F ssh_config] " "[-i identity_file] [-l limit]\n" " [-o ssh_option] [-P port] [-R num_requests] " @@ -2102,6 +2228,7 @@ main(int argc, char **argv) /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); + setlocale(LC_CTYPE, ""); __progname = ssh_get_progname(argv[0]); memset(&args, '\0', sizeof(args)); @@ -2116,7 +2243,7 @@ main(int argc, char **argv) infile = stdin; while ((ch = getopt(argc, argv, - "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) { + "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) { switch (ch) { /* Passed through to ssh(1) */ case '4': @@ -2133,6 +2260,8 @@ main(int argc, char **argv) addargs(&args, "%s", optarg); break; case 'q': + ll = SYSLOG_LEVEL_ERROR; + quiet = 1; showprogress = 0; addargs(&args, "-%c", ch); break; @@ -2154,6 +2283,9 @@ main(int argc, char **argv) case '2': sshver = 2; break; + case 'a': + global_aflag = 1; + break; case 'B': copy_buffer_len = strtol(optarg, &cp, 10); if (copy_buffer_len == 0 || *cp != '\0') @@ -2168,9 +2300,12 @@ main(int argc, char **argv) (infile = fopen(optarg, "r")) == NULL) fatal("%s (%s).", strerror(errno), optarg); showprogress = 0; - batchmode = 1; + quiet = batchmode = 1; addargs(&args, "-obatchmode yes"); break; + case 'f': + global_fflag = 1; + break; case 'p': global_pflag = 1; break; @@ -2265,7 +2400,7 @@ main(int argc, char **argv) if (conn == NULL) fatal("Couldn't initialise connection to server"); - if (!batchmode) { + if (!quiet) { if (sftp_direct == NULL) fprintf(stderr, "Connected to %s.\n", host); else |