X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/0e5de6f863bdaffaf36ce9ffe4d238bf1eb77975..e45da4d6928b5f35b9add90897665eaec43ac108:/sftp.c diff --git a/sftp.c b/sftp.c index 0bc68f05..ff3223ad 100644 --- a/sftp.c +++ b/sftp.c @@ -16,7 +16,13 @@ #include "includes.h" -RCSID("$OpenBSD: sftp.c,v 1.46 2004/05/19 12:17:33 djm Exp $"); +RCSID("$OpenBSD: sftp.c,v 1.68 2005/10/31 06:15:04 dtucker Exp $"); + +#ifdef USE_LIBEDIT +#include +#else +typedef void EditLine; +#endif #include "buffer.h" #include "xmalloc.h" @@ -49,21 +55,29 @@ int showprogress = 1; /* 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; + int remote_glob(struct sftp_conn *, const char *, int, int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */ -#ifdef HAVE___PROGNAME extern char *__progname; -#else -char *__progname; -#endif /* Separators for interactive commands */ #define WHITESPACE " \t\r\n" -/* Define what type of ls view (0 - multi-column) */ -#define LONG_VIEW 1 /* Full view ala ls -l */ -#define SHORT_VIEW 2 /* Single row view ala ls -1 */ +/* ls flags */ +#define LS_LONG_VIEW 0x01 /* Full view ala ls -l */ +#define LS_SHORT_VIEW 0x02 /* Single row view ala ls -1 */ +#define LS_NUMERIC_VIEW 0x04 /* Long view with numeric uid/gid */ +#define LS_NAME_SORT 0x08 /* Sort by name (default) */ +#define LS_TIME_SORT 0x10 /* Sort by mtime */ +#define LS_SIZE_SORT 0x20 /* Sort by file size */ +#define LS_REVERSE_SORT 0x40 /* Reverse sort order */ +#define LS_SHOW_ALL 0x80 /* Don't skip filenames starting with '.' */ + +#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW) +#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT) /* Commands for interactive mode */ #define I_CHDIR 1 @@ -136,8 +150,10 @@ int interactive_loop(int fd_in, int fd_out, char *file1, char *file2); static void killchild(int signo) { - if (sshpid > 1) + if (sshpid > 1) { kill(sshpid, SIGTERM); + waitpid(sshpid, NULL, 0); + } _exit(1); } @@ -146,9 +162,11 @@ static void cmd_interrupt(int signo) { const char msg[] = "\rInterrupt \n"; + int olderrno = errno; write(STDERR_FILENO, msg, sizeof(msg) - 1); interrupted = 1; + errno = olderrno; } static void @@ -248,7 +266,7 @@ path_strip(char *path, char *strip) return (xstrdup(path)); len = strlen(strip); - if (strip != NULL && strncmp(path, strip, len) == 0) { + if (strncmp(path, strip, len) == 0) { if (strip[len - 1] != '/' && path[len] == '/') len++; return (xstrdup(path + len)); @@ -275,13 +293,13 @@ path_append(char *p1, char *p2) static char * make_absolute(char *p, char *pwd) { - char *abs; + char *abs_str; /* Derelativise */ if (p && p[0] != '/') { - abs = path_append(pwd, p); + abs_str = path_append(pwd, p); xfree(p); - return(abs); + return(abs_str); } else return(p); } @@ -334,15 +352,41 @@ parse_ls_flags(const char **cpp, int *lflag) { const char *cp = *cpp; + /* Defaults */ + *lflag = LS_NAME_SORT; + /* Check for flags */ if (cp++[0] == '-') { - for(; strchr(WHITESPACE, *cp) == NULL; cp++) { + for (; strchr(WHITESPACE, *cp) == NULL; cp++) { switch (*cp) { case 'l': - *lflag = LONG_VIEW; + *lflag &= ~VIEW_FLAGS; + *lflag |= LS_LONG_VIEW; break; case '1': - *lflag = SHORT_VIEW; + *lflag &= ~VIEW_FLAGS; + *lflag |= LS_SHORT_VIEW; + break; + case 'n': + *lflag &= ~VIEW_FLAGS; + *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW; + break; + case 'S': + *lflag &= ~SORT_FLAGS; + *lflag |= LS_SIZE_SORT; + break; + case 't': + *lflag &= ~SORT_FLAGS; + *lflag |= LS_TIME_SORT; + break; + case 'r': + *lflag |= LS_REVERSE_SORT; + break; + case 'f': + *lflag &= ~SORT_FLAGS; + break; + case 'a': + *lflag |= LS_SHOW_ALL; break; default: error("Invalid flag -%c", *cp); @@ -360,7 +404,7 @@ get_pathname(const char **cpp, char **path) { const char *cp = *cpp, *end; char quot; - int i, j; + u_int i, j; cp += strspn(cp, WHITESPACE); if (!*cp) { @@ -390,7 +434,7 @@ get_pathname(const char **cpp, char **path) i++; if (cp[i] != '\'' && cp[i] != '\"' && cp[i] != '\\') { - error("Bad escaped character '\%c'", + error("Bad escaped character '\\%c'", cp[i]); goto fail; } @@ -603,28 +647,40 @@ sdirent_comp(const void *aa, const void *bb) { SFTP_DIRENT *a = *(SFTP_DIRENT **)aa; SFTP_DIRENT *b = *(SFTP_DIRENT **)bb; + int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1; + +#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1)) + if (sort_flag & LS_NAME_SORT) + return (rmul * strcmp(a->filename, b->filename)); + else if (sort_flag & LS_TIME_SORT) + return (rmul * NCMP(a->a.mtime, b->a.mtime)); + else if (sort_flag & LS_SIZE_SORT) + return (rmul * NCMP(a->a.size, b->a.size)); - return (strcmp(a->filename, b->filename)); + fatal("Unknown ls sort type"); } /* sftp ls.1 replacement for directories */ static int do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) { - int n, c = 1, colspace = 0, columns = 1; + int n; + u_int c = 1, colspace = 0, columns = 1; SFTP_DIRENT **d; if ((n = do_readdir(conn, path, &d)) != 0) return (n); - if (!(lflag & SHORT_VIEW)) { - int m = 0, width = 80; + if (!(lflag & LS_SHORT_VIEW)) { + u_int m = 0, width = 80; struct winsize ws; char *tmp; /* Count entries for sort and find longest filename */ - for (n = 0; d[n] != NULL; n++) - m = MAX(m, strlen(d[n]->filename)); + for (n = 0; d[n] != NULL; n++) { + if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL)) + m = MAX(m, strlen(d[n]->filename)); + } /* Add any subpath that also needs to be counted */ tmp = path_strip(path, strip_path); @@ -640,24 +696,35 @@ do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) colspace = MIN(colspace, width); } - qsort(d, n, sizeof(*d), sdirent_comp); + if (lflag & SORT_FLAGS) { + for (n = 0; d[n] != NULL; n++) + ; /* count entries */ + sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT); + qsort(d, n, sizeof(*d), sdirent_comp); + } for (n = 0; d[n] != NULL && !interrupted; n++) { char *tmp, *fname; + if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL)) + continue; + tmp = path_append(path, d[n]->filename); fname = path_strip(tmp, strip_path); xfree(tmp); - if (lflag & LONG_VIEW) { - char *lname; - struct stat sb; + if (lflag & LS_LONG_VIEW) { + if (lflag & LS_NUMERIC_VIEW) { + char *lname; + struct stat sb; - memset(&sb, 0, sizeof(sb)); - attrib_to_stat(&d[n]->a, &sb); - lname = ls_file(fname, &sb, 1); - printf("%s\n", lname); - xfree(lname); + memset(&sb, 0, sizeof(sb)); + attrib_to_stat(&d[n]->a, &sb); + lname = ls_file(fname, &sb, 1); + printf("%s\n", lname); + xfree(lname); + } else + printf("%s\n", d[n]->longname); } else { printf("%-*s", colspace, fname); if (c >= columns) { @@ -670,7 +737,7 @@ do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) xfree(fname); } - if (!(lflag & LONG_VIEW) && (c != 1)) + if (!(lflag & LS_LONG_VIEW) && (c != 1)) printf("\n"); free_sftp_dirents(d); @@ -683,13 +750,15 @@ do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, int lflag) { glob_t g; - int i, c = 1, colspace = 0, columns = 1; - Attrib *a; + u_int i, c = 1, colspace = 0, columns = 1; + Attrib *a = NULL; memset(&g, 0, sizeof(g)); if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE, - NULL, &g)) { + NULL, &g) || (g.gl_pathc && !g.gl_matchc)) { + if (g.gl_pathc) + globfree(&g); error("Can't ls: \"%s\" not found", path); return (-1); } @@ -698,24 +767,26 @@ do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, goto out; /* - * If the glob returns a single match, which is the same as the - * input glob, and it is a directory, then just list its contents + * If the glob returns a single match and it is a directory, + * then just list its contents. */ - if (g.gl_pathc == 1 && - strncmp(path, g.gl_pathv[0], strlen(g.gl_pathv[0]) - 1) == 0) { - if ((a = do_lstat(conn, path, 1)) == NULL) { + if (g.gl_matchc == 1) { + if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) { globfree(&g); return (-1); } if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && S_ISDIR(a->perm)) { + int err; + + err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag); globfree(&g); - return (do_ls_dir(conn, path, strip_path, lflag)); + return (err); } } - if (!(lflag & SHORT_VIEW)) { - int m = 0, width = 80; + if (!(lflag & LS_SHORT_VIEW)) { + u_int m = 0, width = 80; struct winsize ws; /* Count entries for sort and find longest filename */ @@ -730,12 +801,12 @@ do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, colspace = width / columns; } - for (i = 0; g.gl_pathv[i] && !interrupted; i++) { + for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) { char *fname; fname = path_strip(g.gl_pathv[i], strip_path); - if (lflag & LONG_VIEW) { + if (lflag & LS_LONG_VIEW) { char *lname; struct stat sb; @@ -747,7 +818,8 @@ do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, * that the server returns as well as the filenames. */ memset(&sb, 0, sizeof(sb)); - a = do_lstat(conn, g.gl_pathv[i], 1); + if (a == NULL) + a = do_lstat(conn, g.gl_pathv[i], 1); if (a != NULL) attrib_to_stat(a, &sb); lname = ls_file(fname, &sb, 1); @@ -764,7 +836,7 @@ do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, xfree(fname); } - if (!(lflag & LONG_VIEW) && (c != 1)) + if (!(lflag & LS_LONG_VIEW) && (c != 1)) printf("\n"); out: @@ -1152,6 +1224,14 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, return (0); } +#ifdef USE_LIBEDIT +static char * +prompt(EditLine *el) +{ + return ("sftp> "); +} +#endif + int interactive_loop(int fd_in, int fd_out, char *file1, char *file2) { @@ -1159,7 +1239,28 @@ interactive_loop(int fd_in, int fd_out, char *file1, char *file2) char *dir = NULL; char cmd[2048]; struct sftp_conn *conn; - int err; + int err, interactive; + EditLine *el = NULL; +#ifdef USE_LIBEDIT + History *hl = NULL; + HistEvent hev; + extern char *__progname; + + if (!batchmode && isatty(STDIN_FILENO)) { + if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL) + fatal("Couldn't initialise editline"); + if ((hl = history_init()) == NULL) + fatal("Couldn't initialise editline history"); + history(hl, &hev, H_SETSIZE, 100); + el_set(el, EL_HIST, history, hl); + + el_set(el, EL_PROMPT, prompt); + el_set(el, EL_EDITOR, "emacs"); + el_set(el, EL_TERMINAL, NULL); + el_set(el, EL_SIGNAL, 1); + el_source(el, NULL); + } +#endif /* USE_LIBEDIT */ conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests); if (conn == NULL) @@ -1176,8 +1277,11 @@ interactive_loop(int fd_in, int fd_out, char *file1, char *file2) 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, &pwd, 1) != 0) { + xfree(dir); + xfree(pwd); return (-1); + } } else { if (file2 == NULL) snprintf(cmd, sizeof cmd, "get %s", dir); @@ -1193,31 +1297,52 @@ interactive_loop(int fd_in, int fd_out, char *file1, char *file2) xfree(dir); } -#if HAVE_SETVBUF +#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF) setvbuf(stdout, NULL, _IOLBF, 0); setvbuf(infile, NULL, _IOLBF, 0); #else - setlinebuf(stdout); - setlinebuf(infile); + setlinebuf(stdout); + setlinebuf(infile); #endif + interactive = !batchmode && isatty(STDIN_FILENO); err = 0; for (;;) { char *cp; signal(SIGINT, SIG_IGN); - printf("sftp> "); + if (el == NULL) { + if (interactive) + printf("sftp> "); + if (fgets(cmd, sizeof(cmd), infile) == NULL) { + if (interactive) + printf("\n"); + break; + } + if (!interactive) { /* Echo command */ + printf("sftp> %s", cmd); + if (strlen(cmd) > 0 && + cmd[strlen(cmd) - 1] != '\n') + printf("\n"); + } + } else { +#ifdef USE_LIBEDIT + const char *line; + int count = 0; - /* XXX: use libedit */ - if (fgets(cmd, sizeof(cmd), infile) == NULL) { - printf("\n"); - break; + if ((line = el_gets(el, &count)) == NULL || count <= 0) { + printf("\n"); + break; + } + history(hl, &hev, H_ENTER, line); + if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) { + fprintf(stderr, "Error: input line too long\n"); + continue; + } +#endif /* USE_LIBEDIT */ } - if (batchmode) /* Echo command */ - printf("%s", cmd); - cp = strrchr(cmd, '\n'); if (cp) *cp = '\0'; @@ -1232,6 +1357,11 @@ interactive_loop(int fd_in, int fd_out, char *file1, char *file2) } xfree(pwd); +#ifdef USE_LIBEDIT + if (el != NULL) + el_end(el); +#endif /* USE_LIBEDIT */ + /* err == 1 signifies normal "quit" exit */ return (err >= 0 ? 0 : -1); } @@ -1265,7 +1395,7 @@ connect_to_server(char *path, char **args, int *in, int *out) if ((dup2(c_in, STDIN_FILENO) == -1) || (dup2(c_out, STDOUT_FILENO) == -1)) { fprintf(stderr, "dup2: %s\n", strerror(errno)); - exit(1); + _exit(1); } close(*in); close(*out); @@ -1274,14 +1404,14 @@ connect_to_server(char *path, char **args, int *in, int *out) /* * The underlying ssh is in the same process group, so we must - * ignore SIGINT if we want to gracefully abort commands, - * otherwise the signal will make it to the ssh process and + * ignore SIGINT if we want to gracefully abort commands, + * otherwise the signal will make it to the ssh process and * kill it too */ signal(SIGINT, SIG_IGN); - execv(path, args); + execvp(path, args); fprintf(stderr, "exec: %s: %s\n", path, strerror(errno)); - exit(1); + _exit(1); } signal(SIGTERM, killchild); @@ -1310,7 +1440,7 @@ int main(int argc, char **argv) { int in, out, ch, err; - char *host, *userhost, *cp, *file2; + char *host, *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; @@ -1319,6 +1449,9 @@ main(int argc, char **argv) extern int optind; extern char *optarg; + /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ + sanitise_stdfd(); + __progname = ssh_get_progname(argv[0]); args.list = NULL; addargs(&args, "ssh"); /* overwritten with ssh_program */ @@ -1361,11 +1494,12 @@ main(int argc, char **argv) fatal("Batch file already specified."); /* Allow "-" as stdin */ - if (strcmp(optarg, "-") != 0 && - (infile = fopen(optarg, "r")) == NULL) + if (strcmp(optarg, "-") != 0 && + (infile = fopen(optarg, "r")) == NULL) fatal("%s (%s).", strerror(errno), optarg); showprogress = 0; batchmode = 1; + addargs(&args, "-obatchmode yes"); break; case 'P': sftp_direct = optarg; @@ -1447,8 +1581,8 @@ main(int argc, char **argv) err = interactive_loop(in, out, file1, file2); #if !defined(USE_PIPES) - shutdown(in, SHUT_RDWR); - shutdown(out, SHUT_RDWR); + shutdown(in, SHUT_RDWR); + shutdown(out, SHUT_RDWR); #endif close(in);