Merge branch 'vendor/OPENSSH'
[dragonfly.git] / crypto / openssh / sftp.c
index bae79f2..65613fd 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp.c,v 1.136 2012/06/22 14:36:33 dtucker Exp $ */
+/* $OpenBSD: sftp.c,v 1.164 2014/07/09 01:45:10 djm 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 or upload 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,35 @@ 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_REGET,
+       I_RENAME,
+       I_REPUT,
+       I_RM,
+       I_RMDIR,
+       I_SHELL,
+       I_SYMLINK,
+       I_VERSION,
+       I_PROGRESS,
+};
 
 struct CMD {
        const char *c;
@@ -188,7 +200,9 @@ 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  },
+       { "reput",      I_REPUT,        LOCAL   },
        { "rm",         I_RM,           REMOTE  },
        { "rmdir",      I_RMDIR,        REMOTE  },
        { "symlink",    I_SYMLINK,      REMOTE  },
@@ -219,7 +233,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 +251,8 @@ help(void)
            "                                   filesystem containing 'path'\n"
            "exit                               Quit sftp\n"
            "get [-Ppr] remote [local]          Download file\n"
+           "reget remote [local]               Resume download file\n"
+           "reput [local] remote               Resume upload 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 +326,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 +357,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 +373,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;
@@ -401,6 +423,30 @@ parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
        return optind;
 }
 
+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)
 {
@@ -481,6 +527,26 @@ parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
        return optind;
 }
 
+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)
 {
@@ -517,21 +583,25 @@ 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;
        glob_t g;
        char *filename, *tmp=NULL;
-       int i, err = 0;
+       int i, r, 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, GLOB_MARK, NULL, &g)) {
-               error("File \"%s\" not found.", abs_src);
+       if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
+               if (r == GLOB_NOSPACE) {
+                       error("Too many matches for \"%s\".", abs_src);
+               } else {
+                       error("File \"%s\" not found.", abs_src);
+               }
                err = -1;
                goto out;
        }
@@ -551,7 +621,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 +637,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 resume, int fflag)
 {
        char *tmp_dst = NULL;
        char *abs_dst = NULL;
@@ -632,11 +708,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 +728,30 @@ process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
                } else {
                        abs_dst = make_absolute(xstrdup(filename), pwd);
                }
-               xfree(tmp);
-
-               printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
+               free(tmp);
+
+                resume |= global_aflag;
+               if (!quiet && resume)
+                       printf("Resuming upload of %s to %s\n", g.gl_pathv[i], 
+                               abs_dst);
+               else if (!quiet && !resume)
+                       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, resume,
+                           fflag || global_fflag) == -1)
                                err = -1;
                } else {
                        if (do_upload(conn, g.gl_pathv[i], abs_dst,
-                           pflag || global_pflag) == -1)
+                           pflag || global_pflag, resume,
+                           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 +799,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 +825,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 +837,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 +849,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))
@@ -785,19 +866,23 @@ do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
 {
        char *fname, *lname;
        glob_t g;
-       int err;
+       int err, r;
        struct winsize ws;
        u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
 
        memset(&g, 0, sizeof(g));
 
-       if (remote_glob(conn, path,
+       if ((r = remote_glob(conn, path,
            GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
-           NULL, &g) ||
+           NULL, &g)) != 0 ||
            (g.gl_pathc && !g.gl_matchc)) {
                if (g.gl_pathc)
                        globfree(&g);
-               error("Can't ls: \"%s\" not found", path);
+               if (r == GLOB_NOSPACE) {
+                       error("Can't ls: Too many matches for \"%s\"", path);
+               } else {
+                       error("Can't ls: \"%s\" not found", path);
+               }
                return -1;
        }
 
@@ -838,7 +923,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) {
@@ -847,7 +932,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))
@@ -961,7 +1046,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.
@@ -991,7 +1076,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';
@@ -1006,7 +1095,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;
@@ -1112,8 +1201,10 @@ 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;
@@ -1125,9 +1216,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);
        }
@@ -1141,7 +1232,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;
@@ -1157,14 +1248,17 @@ 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_REPUT:
        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) {
@@ -1183,8 +1277,15 @@ parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
        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);
@@ -1202,6 +1303,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.",
@@ -1243,6 +1346,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;
@@ -1273,6 +1378,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");
@@ -1287,7 +1394,9 @@ 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;
@@ -1296,10 +1405,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));
@@ -1313,21 +1421,30 @@ 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_REPUT:
+               aflag = 1;
+               /* FALLTHROUGH */
        case I_PUT:
-               err = process_put(conn, path1, path2, *pwd, pflag, rflag);
+               err = process_put(conn, path1, path2, *pwd, pflag,
+                   rflag, aflag, 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;
@@ -1335,7 +1452,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;
@@ -1359,24 +1477,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:
@@ -1431,7 +1549,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;
@@ -1460,10 +1579,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);
@@ -1504,10 +1627,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)
@@ -1534,7 +1655,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)
@@ -1579,8 +1700,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;
@@ -1592,7 +1713,7 @@ complete_ambiguous(const char *word, char **list, size_t count)
                        tmp[matchlen] = '\0';
                        return tmp;
                }
-       } 
+       }
 
        return xstrdup(word);
 }
@@ -1612,26 +1733,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;
        }
 
@@ -1640,9 +1761,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);
@@ -1663,7 +1784,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;
@@ -1681,7 +1802,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;
        }
 
@@ -1694,23 +1815,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 */
@@ -1724,22 +1849,22 @@ complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
                if (tmp[tmplen] == '/')
                        pwdlen = tmplen + 1;    /* track last seen '/' */
        }
-       xfree(tmp);
+       free(tmp);
+       tmp = NULL;
 
-       if (g.gl_matchc == 0) 
+       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);
+       tmp = path_strip(tmp2, isabs ? NULL : remote_path);
+       free(tmp2);
 
        if (tmp == NULL)
                goto out;
@@ -1747,14 +1872,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 '"':
@@ -1762,6 +1900,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 "
@@ -1780,7 +1920,7 @@ complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
        lf = el_line(el);
        if (g.gl_matchc == 1) {
                i = 0;
-               if (!terminated)
+               if (!terminated && quote != '\0')
                        ins[i++] = quote;
                if (*(lf->cursor - 1) != '/' &&
                    (lastarg || *(lf->cursor) != ' '))
@@ -1789,7 +1929,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);
@@ -1800,8 +1940,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;
 
@@ -1815,7 +1956,7 @@ complete(EditLine *el, int ch)
        memcpy(line, lf->buffer, cursor);
        line[cursor] = '\0';
        argv = makeargv(line, &carg, 1, &quote, &terminated);
-       xfree(line);
+       free(line);
 
        /* Get all the arguments on the line */
        len = lf->lastchar - lf->buffer;
@@ -1827,7 +1968,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;
        }
 
@@ -1838,7 +1979,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 */
@@ -1851,11 +1992,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 */
@@ -1889,12 +2030,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 */
 
@@ -1907,30 +2055,30 @@ 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);
        }
 
        setlinebuf(stdout);
@@ -1988,8 +2136,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)
@@ -2063,7 +2211,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] "
@@ -2096,6 +2244,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));
@@ -2110,7 +2259,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':
@@ -2127,6 +2276,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;
@@ -2148,6 +2299,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')
@@ -2162,9 +2316,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;
@@ -2259,7 +2416,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