X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/f4ff9455ce1b9e6ccc9c580966c3a2a002bc20d1..479fe1ba750b1cda0ad3a159f2727619555436b0:/psftp.c diff --git a/psftp.c b/psftp.c index b134c503..0eedf9d2 100644 --- a/psftp.c +++ b/psftp.c @@ -168,6 +168,584 @@ static char *stripslashes(char *str, int local) return str; } +/* + * qsort comparison routine for fxp_name structures. Sorts by real + * file name. + */ +static int sftp_name_compare(const void *av, const void *bv) +{ + const struct fxp_name *const *a = (const struct fxp_name *const *) av; + const struct fxp_name *const *b = (const struct fxp_name *const *) bv; + return strcmp((*a)->filename, (*b)->filename); +} + +/* + * Likewise, but for a bare char *. + */ +static int bare_name_compare(const void *av, const void *bv) +{ + const char **a = (const char **) av; + const char **b = (const char **) bv; + return strcmp(*a, *b); +} + +/* ---------------------------------------------------------------------- + * The meat of the `get' and `put' commands. + */ +int sftp_get_file(char *fname, char *outfname, int recurse, int restart, + char *wildcard) +{ + struct fxp_handle *fh; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + struct fxp_xfer *xfer; + uint64 offset; + FILE *fp; + int ret, shown_err = FALSE; + + /* + * In recursive mode, see if we're dealing with a directory. + * (If we're not in recursive mode, we need not even check: the + * subsequent FXP_OPEN will return a usable error message.) + */ + if (wildcard || recurse) { + struct fxp_attrs attrs; + int result; + + if (!wildcard) { + sftp_register(req = fxp_stat_send(fname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_stat_recv(pktin, rreq, &attrs); + } else + result = 0; /* placate optimisers */ + + if (wildcard || + (result && + (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && + (attrs.permissions & 0040000))) { + + struct fxp_handle *dirhandle; + int nnames, namesize; + struct fxp_name **ournames; + struct fxp_names *names; + int i; + + /* + * First, attempt to create the destination directory, + * unless it already exists (or this is a wildcard + * run). + */ + if (!wildcard && + file_type(outfname) != FILE_TYPE_DIRECTORY && + !create_directory(outfname)) { + printf("%s: Cannot create directory\n", outfname); + return 0; + } + + /* + * Now get the list of filenames in the remote + * directory. + */ + sftp_register(req = fxp_opendir_send(fname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + dirhandle = fxp_opendir_recv(pktin, rreq); + + if (!dirhandle) { + printf("%s: unable to open directory: %s\n", + fname, fxp_error()); + return 0; + } + nnames = namesize = 0; + ournames = NULL; + while (1) { + int i; + + sftp_register(req = fxp_readdir_send(dirhandle)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + names = fxp_readdir_recv(pktin, rreq); + + if (names == NULL) { + if (fxp_error_type() == SSH_FX_EOF) + break; + printf("%s: reading directory: %s\n", fname, fxp_error()); + sfree(ournames); + return 0; + } + if (names->nnames == 0) { + fxp_free_names(names); + break; + } + if (nnames + names->nnames >= namesize) { + namesize += names->nnames + 128; + ournames = sresize(ournames, namesize, struct fxp_name *); + } + for (i = 0; i < names->nnames; i++) + if (strcmp(names->names[i].filename, ".") && + strcmp(names->names[i].filename, "..") && + (!wildcard || wc_match(wildcard, + names->names[i].filename))) { + if (!vet_filename(names->names[i].filename)) { + printf("ignoring potentially dangerous server-" + "supplied filename '%s'\n", + names->names[i].filename); + } else { + ournames[nnames++] = + fxp_dup_name(&names->names[i]); + } + } + fxp_free_names(names); + } + sftp_register(req = fxp_close_send(dirhandle)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + /* + * A polite warning if nothing at all matched the + * wildcard. + */ + if (wildcard && !nnames) { + printf("%s: nothing matched\n", wildcard); + } + + /* + * Sort the names into a clear order. This ought to + * make things more predictable when we're doing a + * reget of the same directory, just in case two + * readdirs on the same remote directory return a + * different order. + */ + qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare); + + /* + * If we're in restart mode, find the last filename on + * this list that already exists. We may have to do a + * reget on _that_ file, but shouldn't have to do + * anything on the previous files. + * + * If none of them exists, of course, we start at 0. + */ + i = 0; + while (i < nnames) { + char *nextoutfname; + int ret; + if (outfname) + nextoutfname = dir_file_cat(outfname, + ournames[i]->filename); + else + nextoutfname = dupstr(ournames[i]->filename); + ret = (file_type(nextoutfname) == FILE_TYPE_NONEXISTENT); + sfree(nextoutfname); + if (ret) + break; + i++; + } + if (i > 0) + i--; + + /* + * Now we're ready to recurse. Starting at ournames[i] + * and continuing on to the end of the list, we + * construct a new source and target file name, and + * call sftp_get_file again. + */ + for (; i < nnames; i++) { + char *nextfname, *nextoutfname; + int ret; + + nextfname = dupcat(fname, "/", ournames[i]->filename, NULL); + if (outfname) + nextoutfname = dir_file_cat(outfname, + ournames[i]->filename); + else + nextoutfname = dupstr(ournames[i]->filename); + ret = sftp_get_file(nextfname, nextoutfname, + recurse, restart, NULL); + restart = FALSE; /* after first partial file, do full */ + sfree(nextoutfname); + sfree(nextfname); + if (!ret) { + for (i = 0; i < nnames; i++) { + fxp_free_name(ournames[i]); + } + sfree(ournames); + return 0; + } + } + + /* + * Done this recursion level. Free everything. + */ + for (i = 0; i < nnames; i++) { + fxp_free_name(ournames[i]); + } + sfree(ournames); + + return 1; + } + } + + sftp_register(req = fxp_open_send(fname, SSH_FXF_READ)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fh = fxp_open_recv(pktin, rreq); + + if (!fh) { + printf("%s: %s\n", fname, fxp_error()); + return 0; + } + + if (restart) { + fp = fopen(outfname, "rb+"); + } else { + fp = fopen(outfname, "wb"); + } + + if (!fp) { + printf("local: unable to open %s\n", outfname); + + sftp_register(req = fxp_close_send(fh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + return 0; + } + + if (restart) { + long posn; + fseek(fp, 0L, SEEK_END); + posn = ftell(fp); + printf("reget: restarting at file position %ld\n", posn); + offset = uint64_make(0, posn); + } else { + offset = uint64_make(0, 0); + } + + printf("remote:%s => local:%s\n", fname, outfname); + + /* + * FIXME: we can use FXP_FSTAT here to get the file size, and + * thus put up a progress bar. + */ + ret = 1; + xfer = xfer_download_init(fh, offset); + while (!xfer_done(xfer)) { + void *vbuf; + int ret, len; + int wpos, wlen; + + xfer_download_queue(xfer); + pktin = sftp_recv(); + ret = xfer_download_gotpkt(xfer, pktin); + + if (ret < 0) { + if (!shown_err) { + printf("error while reading: %s\n", fxp_error()); + shown_err = TRUE; + } + ret = 0; + } + + while (xfer_download_data(xfer, &vbuf, &len)) { + unsigned char *buf = (unsigned char *)vbuf; + + wpos = 0; + while (wpos < len) { + wlen = fwrite(buf + wpos, 1, len - wpos, fp); + if (wlen <= 0) { + printf("error while writing local file\n"); + ret = 0; + xfer_set_error(xfer); + } + wpos += wlen; + } + if (wpos < len) { /* we had an error */ + ret = 0; + xfer_set_error(xfer); + } + + sfree(vbuf); + } + } + + xfer_cleanup(xfer); + + fclose(fp); + + sftp_register(req = fxp_close_send(fh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + return ret; +} + +int sftp_put_file(char *fname, char *outfname, int recurse, int restart, + char *wildcard) +{ + struct fxp_handle *fh; + struct fxp_xfer *xfer; + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + uint64 offset; + FILE *fp; + int ret, err, eof; + + /* + * In recursive mode, see if we're dealing with a directory. + * (If we're not in recursive mode, we need not even check: the + * subsequent fopen will return an error message.) + */ + if (wildcard || (recurse && file_type(fname) == FILE_TYPE_DIRECTORY)) { + struct fxp_attrs attrs; + int result; + int nnames, namesize; + char *name, **ournames; + DirHandle *dh; + int i; + + if (!wildcard) { + /* + * First, attempt to create the destination directory, + * unless it already exists. + */ + sftp_register(req = fxp_stat_send(outfname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_stat_recv(pktin, rreq, &attrs); + if (!result || + !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) || + !(attrs.permissions & 0040000)) { + sftp_register(req = fxp_mkdir_send(outfname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_mkdir_recv(pktin, rreq); + + if (!result) { + printf("%s: create directory: %s\n", + outfname, fxp_error()); + return 0; + } + } + } + + /* + * Now get the list of filenames in the local directory. + */ + nnames = namesize = 0; + ournames = NULL; + if (wildcard) { + WildcardMatcher *wcm; + + wcm = begin_wildcard_matching(wildcard); + if (wcm) { + while ((name = wildcard_get_filename(wcm)) != NULL) { + if (nnames >= namesize) { + namesize += 128; + ournames = sresize(ournames, namesize, char *); + } + ournames[nnames++] = name; + } + finish_wildcard_matching(wcm); + } + } else { + dh = open_directory(fname); + if (!dh) { + printf("%s: unable to open directory\n", fname); + return 0; + } + while ((name = read_filename(dh)) != NULL) { + if (nnames >= namesize) { + namesize += 128; + ournames = sresize(ournames, namesize, char *); + } + ournames[nnames++] = name; + } + close_directory(dh); + } + + /* + * A polite warning if nothing at all matched the + * wildcard. + */ + if (wildcard && !nnames) { + printf("%s: nothing matched\n", wildcard); + } + + /* + * Sort the names into a clear order. This ought to make + * things more predictable when we're doing a reput of the + * same directory, just in case two readdirs on the same + * local directory return a different order. + */ + qsort(ournames, nnames, sizeof(*ournames), bare_name_compare); + + /* + * If we're in restart mode, find the last filename on this + * list that already exists. We may have to do a reput on + * _that_ file, but shouldn't have to do anything on the + * previous files. + * + * If none of them exists, of course, we start at 0. + */ + i = 0; + while (i < nnames) { + char *nextoutfname; + nextoutfname = dupcat(outfname, "/", ournames[i], NULL); + sftp_register(req = fxp_stat_send(nextoutfname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_stat_recv(pktin, rreq, &attrs); + sfree(nextoutfname); + if (!result) + break; + i++; + } + if (i > 0) + i--; + + /* + * Now we're ready to recurse. Starting at ournames[i] + * and continuing on to the end of the list, we + * construct a new source and target file name, and + * call sftp_put_file again. + */ + for (; i < nnames; i++) { + char *nextfname, *nextoutfname; + int ret; + + if (fname) + nextfname = dir_file_cat(fname, ournames[i]); + else + nextfname = dupstr(ournames[i]); + nextoutfname = dupcat(outfname, "/", ournames[i], NULL); + ret = sftp_put_file(nextfname, nextoutfname, + recurse, restart, NULL); + restart = FALSE; /* after first partial file, do full */ + sfree(nextoutfname); + sfree(nextfname); + if (!ret) { + for (i = 0; i < nnames; i++) { + sfree(ournames[i]); + } + sfree(ournames); + return 0; + } + } + + /* + * Done this recursion level. Free everything. + */ + for (i = 0; i < nnames; i++) { + sfree(ournames[i]); + } + sfree(ournames); + + return 1; + } + + fp = fopen(fname, "rb"); + if (!fp) { + printf("local: unable to open %s\n", fname); + return 0; + } + if (restart) { + sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE)); + } else { + sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE | + SSH_FXF_CREAT | SSH_FXF_TRUNC)); + } + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fh = fxp_open_recv(pktin, rreq); + + if (!fh) { + printf("%s: %s\n", outfname, fxp_error()); + return 0; + } + + if (restart) { + char decbuf[30]; + struct fxp_attrs attrs; + int ret; + + sftp_register(req = fxp_fstat_send(fh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + ret = fxp_fstat_recv(pktin, rreq, &attrs); + + if (!ret) { + printf("read size of %s: %s\n", outfname, fxp_error()); + return 0; + } + if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) { + printf("read size of %s: size was not given\n", outfname); + return 0; + } + offset = attrs.size; + uint64_decimal(offset, decbuf); + printf("reput: restarting at file position %s\n", decbuf); + if (uint64_compare(offset, uint64_make(0, LONG_MAX)) > 0) { + printf("reput: remote file is larger than we can deal with\n"); + return 0; + } + if (fseek(fp, offset.lo, SEEK_SET) != 0) + fseek(fp, 0, SEEK_END); /* *shrug* */ + } else { + offset = uint64_make(0, 0); + } + + printf("local:%s => remote:%s\n", fname, outfname); + + /* + * FIXME: we can use FXP_FSTAT here to get the file size, and + * thus put up a progress bar. + */ + ret = 1; + xfer = xfer_upload_init(fh, offset); + err = eof = 0; + while ((!err && !eof) || !xfer_done(xfer)) { + char buffer[4096]; + int len, ret; + + while (xfer_upload_ready(xfer) && !err && !eof) { + len = fread(buffer, 1, sizeof(buffer), fp); + if (len == -1) { + printf("error while reading local file\n"); + err = 1; + } else if (len == 0) { + eof = 1; + } else { + xfer_upload_data(xfer, buffer, len); + } + } + + if (!xfer_done(xfer)) { + pktin = sftp_recv(); + ret = xfer_upload_gotpkt(xfer, pktin); + if (!ret) { + printf("error while writing: %s\n", fxp_error()); + err = 1; + } + } + } + + xfer_cleanup(xfer); + + sftp_register(req = fxp_close_send(fh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + fclose(fp); + + return ret; +} + /* ---------------------------------------------------------------------- * Actual sftp commands. */ @@ -193,23 +771,34 @@ int sftp_cmd_quit(struct sftp_command *cmd) return -1; } +int sftp_cmd_close(struct sftp_command *cmd) +{ + if (back == NULL) { + printf("psftp: not connected to a host; use \"open host.name\"\n"); + return 0; + } + + if (back != NULL && back->socket(backhandle) != NULL) { + char ch; + back->special(backhandle, TS_EOF); + sftp_recvdata(&ch, 1); + } + do_sftp_cleanup(); + + return 0; +} + /* * List a directory. If no arguments are given, list pwd; otherwise * list the directory given in words[1]. */ -static int sftp_ls_compare(const void *av, const void *bv) -{ - const struct fxp_name *const *a = (const struct fxp_name *const *) av; - const struct fxp_name *const *b = (const struct fxp_name *const *) bv; - return strcmp((*a)->filename, (*b)->filename); -} int sftp_cmd_ls(struct sftp_command *cmd) { struct fxp_handle *dirh; struct fxp_names *names; struct fxp_name **ournames; int nnames, namesize; - char *dir, *cdir; + char *dir, *cdir, *unwcdir, *wildcard; struct sftp_packet *pktin; struct sftp_request *req, *rreq; int i; @@ -224,9 +813,35 @@ int sftp_cmd_ls(struct sftp_command *cmd) else dir = cmd->words[1]; + unwcdir = snewn(1 + strlen(dir), char); + if (wc_unescape(unwcdir, dir)) { + dir = unwcdir; + wildcard = NULL; + } else { + char *tmpdir; + int len, check; + + wildcard = stripslashes(dir, 0); + unwcdir = dupstr(dir); + len = wildcard - dir; + unwcdir[len] = '\0'; + if (len > 0 && unwcdir[len-1] == '/') + unwcdir[len-1] = '\0'; + tmpdir = snewn(1 + len, char); + check = wc_unescape(tmpdir, unwcdir); + sfree(tmpdir); + if (!check) { + printf("Multiple-level wildcards are not supported\n"); + sfree(unwcdir); + return 0; + } + dir = unwcdir; + } + cdir = canonify(dir); if (!cdir) { printf("%s: %s\n", dir, fxp_error()); + sfree(unwcdir); return 0; } @@ -267,7 +882,8 @@ int sftp_cmd_ls(struct sftp_command *cmd) } for (i = 0; i < names->nnames; i++) - ournames[nnames++] = fxp_dup_name(&names->names[i]); + if (!wildcard || wc_match(wildcard, names->names[i].filename)) + ournames[nnames++] = fxp_dup_name(&names->names[i]); fxp_free_names(names); } @@ -280,7 +896,7 @@ int sftp_cmd_ls(struct sftp_command *cmd) * Now we have our filenames. Sort them by actual file * name, and then output the longname parts. */ - qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare); + qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare); /* * And print them. @@ -293,6 +909,7 @@ int sftp_cmd_ls(struct sftp_command *cmd) } sfree(cdir); + sfree(unwcdir); return 1; } @@ -361,291 +978,168 @@ int sftp_cmd_pwd(struct sftp_command *cmd) } /* - * Get a file and save it at the local end. We have two very - * similar commands here: `get' and `reget', which differ in that - * `reget' checks for the existence of the destination file and - * starts from where a previous aborted transfer left off. + * Get a file and save it at the local end. We have three very + * similar commands here. The basic one is `get'; `reget' differs + * in that it checks for the existence of the destination file and + * starts from where a previous aborted transfer left off; `mget' + * differs in that it interprets all its arguments as files to + * transfer (never as a different local name for a remote file) and + * can handle wildcards. */ -int sftp_general_get(struct sftp_command *cmd, int restart) +int sftp_general_get(struct sftp_command *cmd, int restart, int multiple) { - struct fxp_handle *fh; - struct sftp_packet *pktin; - struct sftp_request *req, *rreq; - struct fxp_xfer *xfer; - char *fname, *outfname; - uint64 offset; - FILE *fp; - int ret; + char *fname, *unwcfname, *origfname, *outfname; + int i, ret; + int recurse = FALSE; if (back == NULL) { printf("psftp: not connected to a host; use \"open host.name\"\n"); return 0; } - if (cmd->nwords < 2) { - printf("get: expects a filename\n"); - return 0; - } - - fname = canonify(cmd->words[1]); - if (!fname) { - printf("%s: %s\n", cmd->words[1], fxp_error()); - return 0; - } - outfname = (cmd->nwords == 2 ? - stripslashes(cmd->words[1], 0) : cmd->words[2]); - - sftp_register(req = fxp_open_send(fname, SSH_FXF_READ)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fh = fxp_open_recv(pktin, rreq); - - if (!fh) { - printf("%s: %s\n", fname, fxp_error()); - sfree(fname); - return 0; - } - - if (restart) { - fp = fopen(outfname, "rb+"); - } else { - fp = fopen(outfname, "wb"); + i = 1; + while (i < cmd->nwords && cmd->words[i][0] == '-') { + if (!strcmp(cmd->words[i], "--")) { + /* finish processing options */ + i++; + break; + } else if (!strcmp(cmd->words[i], "-r")) { + recurse = TRUE; + } else { + printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]); + return 0; + } + i++; } - if (!fp) { - printf("local: unable to open %s\n", outfname); - - sftp_register(req = fxp_close_send(fh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); - - sfree(fname); + if (i >= cmd->nwords) { + printf("%s: expects a filename\n", cmd->words[0]); return 0; } - if (restart) { - long posn; - fseek(fp, 0L, SEEK_END); - posn = ftell(fp); - printf("reget: restarting at file position %ld\n", posn); - offset = uint64_make(0, posn); - } else { - offset = uint64_make(0, 0); - } - - printf("remote:%s => local:%s\n", fname, outfname); - - /* - * FIXME: we can use FXP_FSTAT here to get the file size, and - * thus put up a progress bar. - */ - ret = 1; - xfer = xfer_download_init(fh, offset); - while (!xfer_done(xfer)) { - void *vbuf; - int ret, len; - int wpos, wlen; - - xfer_download_queue(xfer); - pktin = sftp_recv(); - ret = xfer_download_gotpkt(xfer, pktin); - - if (ret < 0) { - printf("error while reading: %s\n", fxp_error()); - ret = 0; - } - - while (xfer_download_data(xfer, &vbuf, &len)) { - unsigned char *buf = (unsigned char *)vbuf; + do { + unwcfname = NULL; + origfname = cmd->words[i++]; - wpos = 0; - while (wpos < len) { - wlen = fwrite(buf + wpos, 1, len - wpos, fp); - if (wlen <= 0) { - printf("error while writing local file\n"); - ret = 0; - xfer_set_error(xfer); - } - wpos += wlen; - } - if (wpos < len) { /* we had an error */ - ret = 0; - xfer_set_error(xfer); + if (multiple && + !wc_unescape(unwcfname = snewn(strlen(origfname)+1, char), + origfname)) { + ret = sftp_get_file(pwd, NULL, recurse, restart, origfname); + } else { + fname = canonify(origfname); + if (!fname) { + printf("%s: %s\n", origfname, fxp_error()); + sfree(unwcfname); + return 0; } - sfree(vbuf); - } - } - - xfer_cleanup(xfer); + if (!multiple && i < cmd->nwords) + outfname = cmd->words[i++]; + else + outfname = stripslashes(origfname, 0); - fclose(fp); + ret = sftp_get_file(fname, outfname, recurse, restart, NULL); - sftp_register(req = fxp_close_send(fh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); + sfree(fname); + } + sfree(unwcfname); + if (!ret) + return ret; - sfree(fname); + } while (multiple && i < cmd->nwords); return ret; } int sftp_cmd_get(struct sftp_command *cmd) { - return sftp_general_get(cmd, 0); + return sftp_general_get(cmd, 0, 0); +} +int sftp_cmd_mget(struct sftp_command *cmd) +{ + return sftp_general_get(cmd, 0, 1); } int sftp_cmd_reget(struct sftp_command *cmd) { - return sftp_general_get(cmd, 1); + return sftp_general_get(cmd, 1, 0); } /* - * Send a file and store it at the remote end. We have two very - * similar commands here: `put' and `reput', which differ in that - * `reput' checks for the existence of the destination file and - * starts from where a previous aborted transfer left off. + * Send a file and store it at the remote end. We have three very + * similar commands here. The basic one is `put'; `reput' differs + * in that it checks for the existence of the destination file and + * starts from where a previous aborted transfer left off; `mput' + * differs in that it interprets all its arguments as files to + * transfer (never as a different remote name for a local file) and + * can handle wildcards. */ -int sftp_general_put(struct sftp_command *cmd, int restart) +int sftp_general_put(struct sftp_command *cmd, int restart, int multiple) { - struct fxp_handle *fh; - struct fxp_xfer *xfer; char *fname, *origoutfname, *outfname; - struct sftp_packet *pktin; - struct sftp_request *req, *rreq; - uint64 offset; - FILE *fp; - int ret, err, eof; + int i, ret; + int recurse = FALSE; if (back == NULL) { printf("psftp: not connected to a host; use \"open host.name\"\n"); return 0; } - if (cmd->nwords < 2) { - printf("put: expects a filename\n"); - return 0; - } - - fname = cmd->words[1]; - origoutfname = (cmd->nwords == 2 ? - stripslashes(cmd->words[1], 1) : cmd->words[2]); - outfname = canonify(origoutfname); - if (!outfname) { - printf("%s: %s\n", origoutfname, fxp_error()); - return 0; - } - - fp = fopen(fname, "rb"); - if (!fp) { - printf("local: unable to open %s\n", fname); - sfree(outfname); - return 0; - } - if (restart) { - sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE)); - } else { - sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE | - SSH_FXF_CREAT | SSH_FXF_TRUNC)); - } - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fh = fxp_open_recv(pktin, rreq); - - if (!fh) { - printf("%s: %s\n", outfname, fxp_error()); - sfree(outfname); - return 0; - } - - if (restart) { - char decbuf[30]; - struct fxp_attrs attrs; - int ret; - - sftp_register(req = fxp_fstat_send(fh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - ret = fxp_fstat_recv(pktin, rreq, &attrs); - - if (!ret) { - printf("read size of %s: %s\n", outfname, fxp_error()); - sfree(outfname); - return 0; - } - if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) { - printf("read size of %s: size was not given\n", outfname); - sfree(outfname); - return 0; - } - offset = attrs.size; - uint64_decimal(offset, decbuf); - printf("reput: restarting at file position %s\n", decbuf); - if (uint64_compare(offset, uint64_make(0, LONG_MAX)) > 0) { - printf("reput: remote file is larger than we can deal with\n"); - sfree(outfname); + i = 1; + while (i < cmd->nwords && cmd->words[i][0] == '-') { + if (!strcmp(cmd->words[i], "--")) { + /* finish processing options */ + i++; + break; + } else if (!strcmp(cmd->words[i], "-r")) { + recurse = TRUE; + } else { + printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]); return 0; } - if (fseek(fp, offset.lo, SEEK_SET) != 0) - fseek(fp, 0, SEEK_END); /* *shrug* */ - } else { - offset = uint64_make(0, 0); + i++; } - printf("local:%s => remote:%s\n", fname, outfname); - - /* - * FIXME: we can use FXP_FSTAT here to get the file size, and - * thus put up a progress bar. - */ - ret = 1; - xfer = xfer_upload_init(fh, offset); - err = eof = 0; - while ((!err && !eof) || !xfer_done(xfer)) { - char buffer[4096]; - int len, ret; + if (i >= cmd->nwords) { + printf("%s: expects a filename\n", cmd->words[0]); + return 0; + } - while (xfer_upload_ready(xfer) && !err && !eof) { - len = fread(buffer, 1, sizeof(buffer), fp); - if (len == -1) { - printf("error while reading local file\n"); - err = 1; - } else if (len == 0) { - eof = 1; - } else { - xfer_upload_data(xfer, buffer, len); - } - } + do { + fname = cmd->words[i++]; - if (!xfer_done(xfer)) { - pktin = sftp_recv(); - ret = xfer_upload_gotpkt(xfer, pktin); - if (!ret) { - printf("error while writing: %s\n", fxp_error()); - err = 1; + if (multiple && test_wildcard(fname, FALSE) == WCTYPE_WILDCARD) { + ret = sftp_put_file(NULL, pwd, recurse, restart, fname); + } else { + if (!multiple && i < cmd->nwords) + origoutfname = cmd->words[i++]; + else + origoutfname = stripslashes(fname, 1); + + outfname = canonify(origoutfname); + if (!outfname) { + printf("%s: %s\n", origoutfname, fxp_error()); + return 0; } + ret = sftp_put_file(fname, outfname, recurse, restart, NULL); + sfree(outfname); } - } - - xfer_cleanup(xfer); - - sftp_register(req = fxp_close_send(fh)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - fxp_close_recv(pktin, rreq); + if (!ret) + return ret; - fclose(fp); - sfree(outfname); + } while (multiple && i < cmd->nwords); return ret; } int sftp_cmd_put(struct sftp_command *cmd) { - return sftp_general_put(cmd, 0); + return sftp_general_put(cmd, 0, 0); +} +int sftp_cmd_mput(struct sftp_command *cmd) +{ + return sftp_general_put(cmd, 0, 1); } int sftp_cmd_reput(struct sftp_command *cmd) { - return sftp_general_put(cmd, 1); + return sftp_general_put(cmd, 1, 0); } int sftp_cmd_mkdir(struct sftp_command *cmd) @@ -1159,6 +1653,14 @@ static struct sftp_cmd_lookup { sftp_cmd_chmod }, { + "close", TRUE, "finish your SFTP session but do not quit PSFTP", + "\n" + " Terminates your SFTP session, but does not quit the PSFTP\n" + " program. You can then use \"open\" to start another SFTP\n" + " session, to the same server or to a different one.\n", + sftp_cmd_close + }, + { "del", TRUE, "delete a file", " \n" " Delete a file.\n", @@ -1169,10 +1671,12 @@ static struct sftp_cmd_lookup { }, { "dir", TRUE, "list contents of a remote directory", - " [ ]\n" + " [ ]/[ ]\n" " List the contents of a specified directory on the server.\n" " If is not given, the current working directory\n" - " will be listed.\n", + " is assumed.\n" + " If is given, it is treated as a set of files to\n" + " list; otherwise, all files are listed.\n", sftp_cmd_ls }, { @@ -1180,10 +1684,11 @@ static struct sftp_cmd_lookup { }, { "get", TRUE, "download a file from the server to your local machine", - " [ ]\n" + " [ -r ] [ -- ] [ ]\n" " Downloads a file on the server and stores it locally under\n" " the same name, or under a different one if you supply the\n" - " argument .\n", + " argument .\n" + " If -r specified, recursively fetch a directory.\n", sftp_cmd_get }, { @@ -1213,12 +1718,30 @@ static struct sftp_cmd_lookup { sftp_cmd_ls }, { + "mget", TRUE, "download multiple files at once", + " [ -r ] [ -- ] [ ... ]\n" + " Downloads many files from the server, storing each one under\n" + " the same name it has on the server side. You can use wildcards\n" + " such as \"*.c\" to specify lots of files at once.\n" + " If -r specified, recursively fetch files and directories.\n", + sftp_cmd_mget + }, + { "mkdir", TRUE, "create a directory on the remote server", " \n" " Creates a directory with the given name on the server.\n", sftp_cmd_mkdir }, { + "mput", TRUE, "upload multiple files at once", + " [ ... ]\n" + " Uploads many files to the server, storing each one under the\n" + " same name it has on the client side. You can use wildcards\n" + " such as \"*.c\" to specify lots of files at once.\n" + " If -r specified, recursively store files and directories.\n", + sftp_cmd_mput + }, + { "mv", TRUE, "move or rename a file on the remote server", " \n" " Moves or renames the file on the server,\n" @@ -1235,10 +1758,11 @@ static struct sftp_cmd_lookup { }, { "put", TRUE, "upload a file from your local machine to the server", - " [ ]\n" + " [ -r ] [ -- ] [ ]\n" " Uploads a file to the server and stores it there under\n" " the same name, or under a different one if you supply the\n" - " argument .\n", + " argument .\n" + " If -r specified, recursively store a directory.\n", sftp_cmd_put }, { @@ -1253,10 +1777,11 @@ static struct sftp_cmd_lookup { }, { "reget", TRUE, "continue downloading a file", - " [ ]\n" + " [ -r ] [ -- ] [ ]\n" " Works exactly like the \"get\" command, but the local file\n" " must already exist. The download will begin at the end of the\n" - " file. This is for resuming a download that was interrupted.\n", + " file. This is for resuming a download that was interrupted.\n" + " If -r specified, resume interrupted \"get -r\".\n", sftp_cmd_reget }, { @@ -1269,10 +1794,11 @@ static struct sftp_cmd_lookup { }, { "reput", TRUE, "continue uploading a file", - " [ ]\n" + " [ -r ] [ -- ] [ ]\n" " Works exactly like the \"put\" command, but the remote file\n" " must already exist. The upload will begin at the end of the\n" - " file. This is for resuming an upload that was interrupted.\n", + " file. This is for resuming an upload that was interrupted.\n" + " If -r specified, resume interrupted \"put -r\".\n", sftp_cmd_reput }, { @@ -1361,45 +1887,34 @@ static int sftp_cmd_help(struct sftp_command *cmd) struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) { char *line; - int linelen, linesize; struct sftp_command *cmd; char *p, *q, *r; int quoting; - if ((mode == 0) || (modeflags & 1)) { - printf("psftp> "); - } - fflush(stdout); - cmd = snew(struct sftp_command); cmd->words = NULL; cmd->nwords = 0; cmd->wordssize = 0; line = NULL; - linesize = linelen = 0; - while (1) { - int len; - char *ret; - - linesize += 512; - line = sresize(line, linesize, char); - ret = fgets(line + linelen, linesize - linelen, fp); - - if (!ret || (linelen == 0 && line[0] == '\0')) { - cmd->obey = sftp_cmd_quit; - if ((mode == 0) || (modeflags & 1)) - printf("quit\n"); - return cmd; /* eof */ - } - len = linelen + strlen(line + linelen); - linelen += len; - if (line[linelen - 1] == '\n') { - linelen--; - line[linelen] = '\0'; - break; - } + + if (fp) { + if (modeflags & 1) + printf("psftp> "); + line = fgetline(fp); + } else { + line = ssh_sftp_get_cmdline("psftp> ", back == NULL); + } + + if (!line || !*line) { + cmd->obey = sftp_cmd_quit; + if ((mode == 0) || (modeflags & 1)) + printf("quit\n"); + return cmd; /* eof */ } + + line[strcspn(line, "\r\n")] = '\0'; + if (modeflags & 1) { printf("%s\n", line); } @@ -1464,7 +1979,8 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) } } - sfree(line); + sfree(line); + /* * Now parse the first word and assign a function. */ @@ -1525,6 +2041,8 @@ void do_sftp_cleanup() sftp_recvdata(&ch, 1); back->free(backhandle); sftp_cleanup_request(); + back = NULL; + backhandle = NULL; } if (pwd) { sfree(pwd); @@ -1551,7 +2069,7 @@ void do_sftp(int mode, int modeflags, char *batchfile) */ while (1) { struct sftp_command *cmd; - cmd = sftp_getcmd(stdin, 0, 0); + cmd = sftp_getcmd(NULL, 0, 0); if (!cmd) break; ret = cmd->obey(cmd); @@ -1812,11 +2330,27 @@ static int psftp_connect(char *userhost, char *user, int portnumber) user = userhost; } - /* Try to load settings for this host */ - do_defaults(host, &cfg); - if (cfg.host[0] == '\0') { - /* No settings for this host; use defaults */ - do_defaults(NULL, &cfg); + /* + * If we haven't loaded session details already (e.g., from -load), + * try looking for a session called "host". + */ + if (!loaded_session) { + /* Try to load settings for `host' into a temporary config */ + Config cfg2; + cfg2.host[0] = '\0'; + do_defaults(host, &cfg2); + if (cfg2.host[0] != '\0') { + /* Settings present and include hostname */ + /* Re-load data into the real config. */ + do_defaults(host, &cfg); + } else { + /* Session doesn't exist or mention a hostname. */ + /* Use `host' as a bare hostname. */ + strncpy(cfg.host, host, sizeof(cfg.host) - 1); + cfg.host[sizeof(cfg.host) - 1] = '\0'; + } + } else { + /* Patch in hostname `host' to session details. */ strncpy(cfg.host, host, sizeof(cfg.host) - 1); cfg.host[sizeof(cfg.host) - 1] = '\0'; } @@ -1831,6 +2365,15 @@ static int psftp_connect(char *userhost, char *user, int portnumber) } /* + * If saved session / Default Settings says SSH-1 (`1 only' or `1'), + * then change it to SSH-2, on the grounds that that's more likely to + * work for SFTP. (Can be overridden with `-1' option.) + * But if it says `2 only' or `2', respect which. + */ + if (cfg.sshprot != 2 && cfg.sshprot != 3) + cfg.sshprot = 2; + + /* * Enact command-line overrides. */ cmdline_run_saved(&cfg); @@ -1845,7 +2388,7 @@ static int psftp_connect(char *userhost, char *user, int portnumber) /* See if host is of the form user@host */ if (cfg.host[0] != '\0') { - char *atsign = strchr(cfg.host, '@'); + char *atsign = strrchr(cfg.host, '@'); /* Make sure we're not overflowing the user field */ if (atsign) { if (atsign - cfg.host < sizeof cfg.username) { @@ -1882,10 +2425,9 @@ static int psftp_connect(char *userhost, char *user, int portnumber) cfg.username[sizeof(cfg.username) - 1] = '\0'; } if (!cfg.username[0]) { - printf("login as: "); - fflush(stdout); - if (!fgets(cfg.username, sizeof(cfg.username), stdin)) { - fprintf(stderr, "psftp: aborting\n"); + if (!console_get_line("login as: ", + cfg.username, sizeof(cfg.username), FALSE)) { + fprintf(stderr, "psftp: no username, aborting\n"); cleanup_exit(1); } else { int len = strlen(cfg.username); @@ -1897,9 +2439,6 @@ static int psftp_connect(char *userhost, char *user, int portnumber) if (portnumber) cfg.port = portnumber; - /* SFTP uses SSH2 by default always */ - cfg.sshprot = 2; - /* * Disable scary things which shouldn't be enabled for simple * things like SCP and SFTP: agent forwarding, port forwarding, @@ -1939,7 +2478,8 @@ static int psftp_connect(char *userhost, char *user, int portnumber) back = &ssh_backend; - err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost,0); + err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost, + 0, cfg.tcp_keepalives); if (err != NULL) { fprintf(stderr, "ssh_init: %s\n", err); return 1; @@ -1995,6 +2535,10 @@ int psftp_main(int argc, char *argv[]) userhost = user = NULL; + /* Load Default Settings before doing anything else. */ + do_defaults(NULL, &cfg); + loaded_session = FALSE; + errors = 0; for (i = 1; i < argc; i++) { int ret; @@ -2040,6 +2584,15 @@ int psftp_main(int argc, char *argv[]) back = NULL; /* + * If the loaded session provides a hostname, and a hostname has not + * otherwise been specified, pop it in `userhost' so that + * `psftp -load sessname' is sufficient to start a session. + */ + if (!userhost && cfg.host[0] != '\0') { + userhost = dupstr(cfg.host); + } + + /* * If a user@host string has already been provided, connect to * it now. */ @@ -2063,12 +2616,10 @@ int psftp_main(int argc, char *argv[]) back->special(backhandle, TS_EOF); sftp_recvdata(&ch, 1); } + do_sftp_cleanup(); random_save_seed(); cmdline_cleanup(); console_provide_logctx(NULL); - do_sftp_cleanup(); - backhandle = NULL; - back = NULL; sk_cleanup(); return 0;