X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/86916870d36cb70e7ef0ea760e75a6ae8b2d83a5..HEAD:/psftp.c diff --git a/psftp.c b/psftp.c index f8d1f799..8ed2343a 100644 --- a/psftp.c +++ b/psftp.c @@ -1,9 +1,7 @@ /* - * psftp.c: front end for PSFTP. + * psftp.c: (platform-independent) front end for PSFTP. */ -#include - #include #include #include @@ -12,11 +10,14 @@ #define PUTTY_DO_GLOBALS #include "putty.h" +#include "psftp.h" #include "storage.h" #include "ssh.h" #include "sftp.h" #include "int64.h" +const char *const appname = "PSFTP"; + /* * Since SFTP is a request-response oriented protocol, it requires * no buffer management: when we send data, we stop and wait for an @@ -26,6 +27,7 @@ static int psftp_connect(char *userhost, char *user, int portnumber); static int do_sftp_init(void); +void do_sftp_cleanup(); /* ---------------------------------------------------------------------- * sftp client state. @@ -34,6 +36,28 @@ static int do_sftp_init(void); char *pwd, *homedir; static Backend *back; static void *backhandle; +static Conf *conf; +int sent_eof = FALSE; + +/* ---------------------------------------------------------------------- + * Manage sending requests and waiting for replies. + */ +struct sftp_packet *sftp_wait_for_reply(struct sftp_request *req) +{ + struct sftp_packet *pktin; + struct sftp_request *rreq; + + sftp_register(req); + pktin = sftp_recv(); + if (pktin == NULL) + connection_fatal(NULL, "did not receive SFTP response packet " + "from server"); + rreq = sftp_find_request(pktin); + if (rreq != req) + connection_fatal(NULL, "unable to understand SFTP response packet " + "from server: %s", fxp_error()); + return pktin; +} /* ---------------------------------------------------------------------- * Higher-level helper functions used in commands. @@ -47,6 +71,8 @@ static void *backhandle; char *canonify(char *name) { char *fullname, *canonname; + struct sftp_packet *pktin; + struct sftp_request *req; if (name[0] == '/') { fullname = dupstr(name); @@ -59,7 +85,9 @@ char *canonify(char *name) fullname = dupcat(pwd, slash, name, NULL); } - canonname = fxp_realpath(fullname); + req = fxp_realpath_send(fullname); + pktin = sftp_wait_for_reply(req); + canonname = fxp_realpath_recv(pktin, req); if (canonname) { sfree(fullname); @@ -114,13 +142,19 @@ char *canonify(char *name) */ fullname[i] = '\0'; /* separate the string */ if (i == 0) { - canonname = fxp_realpath("/"); + req = fxp_realpath_send("/"); } else { - canonname = fxp_realpath(fullname); + req = fxp_realpath_send(fullname); } + pktin = sftp_wait_for_reply(req); + canonname = fxp_realpath_recv(pktin, req); - if (!canonname) - return fullname; /* even that failed; give up */ + if (!canonname) { + /* Even that failed. Restore our best guess at the + * constructed filename and give up */ + fullname[i] = '/'; /* restore slash and last component */ + return fullname; + } /* * We have a canonical name for all but the last path @@ -159,6 +193,772 @@ 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); +} + +static void not_connected(void) +{ + printf("psftp: not connected to a host; use \"open host.name\"\n"); +} + +/* ---------------------------------------------------------------------- + * The meat of the `get' and `put' commands. + */ +int sftp_get_file(char *fname, char *outfname, int recurse, int restart) +{ + struct fxp_handle *fh; + struct sftp_packet *pktin; + struct sftp_request *req; + struct fxp_xfer *xfer; + uint64 offset; + WFile *file; + int ret, shown_err = FALSE; + struct fxp_attrs attrs; + + /* + * 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 (recurse) { + int result; + + req = fxp_stat_send(fname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &attrs); + + if (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. + */ + if (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. + */ + req = fxp_opendir_send(fname); + pktin = sftp_wait_for_reply(req); + dirhandle = fxp_opendir_recv(pktin, req); + + if (!dirhandle) { + printf("%s: unable to open directory: %s\n", + fname, fxp_error()); + return 0; + } + nnames = namesize = 0; + ournames = NULL; + while (1) { + int i; + + req = fxp_readdir_send(dirhandle); + pktin = sftp_wait_for_reply(req); + names = fxp_readdir_recv(pktin, req); + + if (names == NULL) { + if (fxp_error_type() == SSH_FX_EOF) + break; + printf("%s: reading directory: %s\n", fname, fxp_error()); + + req = fxp_close_send(dirhandle); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + 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, "..")) { + 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); + } + req = fxp_close_send(dirhandle); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + /* + * 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. + */ + if (nnames > 0) + 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; + if (restart) { + while (i < nnames) { + char *nextoutfname; + int ret; + nextoutfname = dir_file_cat(outfname, + 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); + nextoutfname = dir_file_cat(outfname, ournames[i]->filename); + ret = sftp_get_file(nextfname, nextoutfname, recurse, restart); + 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; + } + } + + req = fxp_stat_send(fname); + pktin = sftp_wait_for_reply(req); + if (!fxp_stat_recv(pktin, req, &attrs)) + attrs.flags = 0; + + req = fxp_open_send(fname, SSH_FXF_READ, NULL); + pktin = sftp_wait_for_reply(req); + fh = fxp_open_recv(pktin, req); + + if (!fh) { + printf("%s: open for read: %s\n", fname, fxp_error()); + return 0; + } + + if (restart) { + file = open_existing_wfile(outfname, NULL); + } else { + file = open_new_file(outfname, GET_PERMISSIONS(attrs)); + } + + if (!file) { + printf("local: unable to open %s\n", outfname); + + req = fxp_close_send(fh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + return 0; + } + + if (restart) { + char decbuf[30]; + if (seek_file(file, uint64_make(0,0) , FROM_END) == -1) { + close_wfile(file); + printf("reget: cannot restart %s - file too large\n", + outfname); + req = fxp_close_send(fh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + return 0; + } + + offset = get_file_posn(file); + uint64_decimal(offset, decbuf); + printf("reget: restarting at file position %s\n", decbuf); + } 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; + } + if (ret == INT_MIN) /* pktin not even freed */ + sfree(pktin); + ret = 0; + } + + while (xfer_download_data(xfer, &vbuf, &len)) { + unsigned char *buf = (unsigned char *)vbuf; + + wpos = 0; + while (wpos < len) { + wlen = write_to_file(file, buf + wpos, len - wpos); + if (wlen <= 0) { + printf("error while writing local file\n"); + ret = 0; + xfer_set_error(xfer); + break; + } + wpos += wlen; + } + if (wpos < len) { /* we had an error */ + ret = 0; + xfer_set_error(xfer); + } + + sfree(vbuf); + } + } + + xfer_cleanup(xfer); + + close_wfile(file); + + req = fxp_close_send(fh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + return ret; +} + +int sftp_put_file(char *fname, char *outfname, int recurse, int restart) +{ + struct fxp_handle *fh; + struct fxp_xfer *xfer; + struct sftp_packet *pktin; + struct sftp_request *req; + uint64 offset; + RFile *file; + int ret, err, eof; + struct fxp_attrs attrs; + long permissions; + + /* + * 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 (recurse && file_type(fname) == FILE_TYPE_DIRECTORY) { + int result; + int nnames, namesize; + char *name, **ournames; + DirHandle *dh; + int i; + + /* + * First, attempt to create the destination directory, + * unless it already exists. + */ + req = fxp_stat_send(outfname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &attrs); + if (!result || + !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) || + !(attrs.permissions & 0040000)) { + req = fxp_mkdir_send(outfname); + pktin = sftp_wait_for_reply(req); + result = fxp_mkdir_recv(pktin, req); + + 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; + + 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); + + /* + * 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. + */ + if (nnames > 0) + 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; + if (restart) { + while (i < nnames) { + char *nextoutfname; + nextoutfname = dupcat(outfname, "/", ournames[i], NULL); + req = fxp_stat_send(nextoutfname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &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; + + nextfname = dir_file_cat(fname, ournames[i]); + nextoutfname = dupcat(outfname, "/", ournames[i], NULL); + ret = sftp_put_file(nextfname, nextoutfname, recurse, restart); + 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; + } + + file = open_existing_file(fname, NULL, NULL, NULL, &permissions); + if (!file) { + printf("local: unable to open %s\n", fname); + return 0; + } + attrs.flags = 0; + PUT_PERMISSIONS(attrs, permissions); + if (restart) { + req = fxp_open_send(outfname, SSH_FXF_WRITE, &attrs); + } else { + req = fxp_open_send(outfname, + SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC, + &attrs); + } + pktin = sftp_wait_for_reply(req); + fh = fxp_open_recv(pktin, req); + + if (!fh) { + close_rfile(file); + printf("%s: open for write: %s\n", outfname, fxp_error()); + return 0; + } + + if (restart) { + char decbuf[30]; + struct fxp_attrs attrs; + int ret; + + req = fxp_fstat_send(fh); + pktin = sftp_wait_for_reply(req); + ret = fxp_fstat_recv(pktin, req, &attrs); + + if (!ret) { + close_rfile(file); + printf("read size of %s: %s\n", outfname, fxp_error()); + return 0; + } + if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) { + close_rfile(file); + 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 (seek_file((WFile *)file, offset, FROM_START) != 0) + seek_file((WFile *)file, uint64_make(0,0), FROM_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 = read_from_file(file, buffer, sizeof(buffer)); + 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 <= 0) { + if (ret == INT_MIN) /* pktin not even freed */ + sfree(pktin); + if (!err) { + printf("error while writing: %s\n", fxp_error()); + err = 1; + } + } + } + } + + xfer_cleanup(xfer); + + req = fxp_close_send(fh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + close_rfile(file); + + return ret; +} + +/* ---------------------------------------------------------------------- + * A remote wildcard matcher, providing a similar interface to the + * local one in psftp.h. + */ + +typedef struct SftpWildcardMatcher { + struct fxp_handle *dirh; + struct fxp_names *names; + int namepos; + char *wildcard, *prefix; +} SftpWildcardMatcher; + +SftpWildcardMatcher *sftp_begin_wildcard_matching(char *name) +{ + struct sftp_packet *pktin; + struct sftp_request *req; + char *wildcard; + char *unwcdir, *tmpdir, *cdir; + int len, check; + SftpWildcardMatcher *swcm; + struct fxp_handle *dirh; + + /* + * We don't handle multi-level wildcards; so we expect to find + * a fully specified directory part, followed by a wildcard + * after that. + */ + wildcard = stripslashes(name, 0); + + unwcdir = dupstr(name); + len = wildcard - name; + 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 NULL; + } + + cdir = canonify(unwcdir); + + req = fxp_opendir_send(cdir); + pktin = sftp_wait_for_reply(req); + dirh = fxp_opendir_recv(pktin, req); + + if (dirh) { + swcm = snew(SftpWildcardMatcher); + swcm->dirh = dirh; + swcm->names = NULL; + swcm->wildcard = dupstr(wildcard); + swcm->prefix = unwcdir; + } else { + printf("Unable to open %s: %s\n", cdir, fxp_error()); + swcm = NULL; + sfree(unwcdir); + } + + sfree(cdir); + + return swcm; +} + +char *sftp_wildcard_get_filename(SftpWildcardMatcher *swcm) +{ + struct fxp_name *name; + struct sftp_packet *pktin; + struct sftp_request *req; + + while (1) { + if (swcm->names && swcm->namepos >= swcm->names->nnames) { + fxp_free_names(swcm->names); + swcm->names = NULL; + } + + if (!swcm->names) { + req = fxp_readdir_send(swcm->dirh); + pktin = sftp_wait_for_reply(req); + swcm->names = fxp_readdir_recv(pktin, req); + + if (!swcm->names) { + if (fxp_error_type() != SSH_FX_EOF) + printf("%s: reading directory: %s\n", swcm->prefix, + fxp_error()); + return NULL; + } else if (swcm->names->nnames == 0) { + /* + * Another failure mode which we treat as EOF is if + * the server reports success from FXP_READDIR but + * returns no actual names. This is unusual, since + * from most servers you'd expect at least "." and + * "..", but there's nothing forbidding a server from + * omitting those if it wants to. + */ + return NULL; + } + + swcm->namepos = 0; + } + + assert(swcm->names && swcm->namepos < swcm->names->nnames); + + name = &swcm->names->names[swcm->namepos++]; + + if (!strcmp(name->filename, ".") || !strcmp(name->filename, "..")) + continue; /* expected bad filenames */ + + if (!vet_filename(name->filename)) { + printf("ignoring potentially dangerous server-" + "supplied filename '%s'\n", name->filename); + continue; /* unexpected bad filename */ + } + + if (!wc_match(swcm->wildcard, name->filename)) + continue; /* doesn't match the wildcard */ + + /* + * We have a working filename. Return it. + */ + return dupprintf("%s%s%s", swcm->prefix, + (!swcm->prefix[0] || + swcm->prefix[strlen(swcm->prefix)-1]=='/' ? + "" : "/"), + name->filename); + } +} + +void sftp_finish_wildcard_matching(SftpWildcardMatcher *swcm) +{ + struct sftp_packet *pktin; + struct sftp_request *req; + + req = fxp_close_send(swcm->dirh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); + + if (swcm->names) + fxp_free_names(swcm->names); + + sfree(swcm->prefix); + sfree(swcm->wildcard); + + sfree(swcm); +} + +/* + * General function to match a potential wildcard in a filename + * argument and iterate over every matching file. Used in several + * PSFTP commands (rmdir, rm, chmod, mv). + */ +int wildcard_iterate(char *filename, int (*func)(void *, char *), void *ctx) +{ + char *unwcfname, *newname, *cname; + int is_wc, ret; + + unwcfname = snewn(strlen(filename)+1, char); + is_wc = !wc_unescape(unwcfname, filename); + + if (is_wc) { + SftpWildcardMatcher *swcm = sftp_begin_wildcard_matching(filename); + int matched = FALSE; + sfree(unwcfname); + + if (!swcm) + return 0; + + ret = 1; + + while ( (newname = sftp_wildcard_get_filename(swcm)) != NULL ) { + cname = canonify(newname); + if (!cname) { + printf("%s: canonify: %s\n", newname, fxp_error()); + ret = 0; + } + sfree(newname); + matched = TRUE; + ret &= func(ctx, cname); + sfree(cname); + } + + if (!matched) { + /* Politely warn the user that nothing matched. */ + printf("%s: nothing matched\n", filename); + } + + sftp_finish_wildcard_matching(swcm); + } else { + cname = canonify(unwcfname); + if (!cname) { + printf("%s: canonify: %s\n", filename, fxp_error()); + ret = 0; + } + ret = func(ctx, cname); + sfree(cname); + sfree(unwcfname); + } + + return ret; +} + +/* + * Handy helper function. + */ +int is_wildcard(char *name) +{ + char *unwcfname = snewn(strlen(name)+1, char); + int is_wc = !wc_unescape(unwcfname, name); + sfree(unwcfname); + return is_wc; +} + /* ---------------------------------------------------------------------- * Actual sftp commands. */ @@ -184,27 +984,41 @@ int sftp_cmd_quit(struct sftp_command *cmd) return -1; } +int sftp_cmd_close(struct sftp_command *cmd) +{ + if (back == NULL) { + not_connected(); + return 0; + } + + if (back != NULL && back->connected(backhandle)) { + char ch; + back->special(backhandle, TS_EOF); + sent_eof = TRUE; + 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; int i; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -213,15 +1027,45 @@ 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; + + sfree(unwcdir); + 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()); + printf("%s: canonify: %s\n", dir, fxp_error()); + sfree(unwcdir); return 0; } printf("Listing directory %s\n", cdir); - dirh = fxp_opendir(cdir); + req = fxp_opendir_send(cdir); + pktin = sftp_wait_for_reply(req); + dirh = fxp_opendir_recv(pktin, req); + if (dirh == NULL) { printf("Unable to open %s: %s\n", dir, fxp_error()); } else { @@ -230,7 +1074,10 @@ int sftp_cmd_ls(struct sftp_command *cmd) while (1) { - names = fxp_readdir(dirh); + req = fxp_readdir_send(dirh); + pktin = sftp_wait_for_reply(req); + names = fxp_readdir_recv(pktin, req); + if (names == NULL) { if (fxp_error_type() == SSH_FX_EOF) break; @@ -244,22 +1091,25 @@ int sftp_cmd_ls(struct sftp_command *cmd) if (nnames + names->nnames >= namesize) { namesize += names->nnames + 128; - ournames = - srealloc(ournames, namesize * sizeof(*ournames)); + ournames = sresize(ournames, namesize, struct fxp_name *); } 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); } - fxp_close(dirh); + req = fxp_close_send(dirh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); /* * 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); + if (nnames > 0) + qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare); /* * And print them. @@ -272,6 +1122,7 @@ int sftp_cmd_ls(struct sftp_command *cmd) } sfree(cdir); + sfree(unwcdir); return 1; } @@ -283,10 +1134,12 @@ int sftp_cmd_ls(struct sftp_command *cmd) int sftp_cmd_cd(struct sftp_command *cmd) { struct fxp_handle *dirh; + struct sftp_packet *pktin; + struct sftp_request *req; char *dir; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -296,18 +1149,23 @@ int sftp_cmd_cd(struct sftp_command *cmd) dir = canonify(cmd->words[1]); if (!dir) { - printf("%s: %s\n", dir, fxp_error()); + printf("%s: canonify: %s\n", dir, fxp_error()); return 0; } - dirh = fxp_opendir(dir); + req = fxp_opendir_send(dir); + pktin = sftp_wait_for_reply(req); + dirh = fxp_opendir_recv(pktin, req); + if (!dirh) { printf("Directory %s: %s\n", dir, fxp_error()); sfree(dir); return 0; } - fxp_close(dirh); + req = fxp_close_send(dirh); + pktin = sftp_wait_for_reply(req); + fxp_close_recv(pktin, req); sfree(pwd); pwd = dir; @@ -322,7 +1180,7 @@ int sftp_cmd_cd(struct sftp_command *cmd) int sftp_cmd_pwd(struct sftp_command *cmd) { if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -331,248 +1189,239 @@ 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; - char *fname, *outfname; - uint64 offset; - FILE *fp; - int ret; + char *fname, *unwcfname, *origfname, *origwfname, *outfname; + int i, ret; + int recurse = FALSE; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } - if (cmd->nwords < 2) { - printf("get: expects a filename\n"); - return 0; + 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++; } - fname = canonify(cmd->words[1]); - if (!fname) { - printf("%s: %s\n", cmd->words[1], fxp_error()); + if (i >= cmd->nwords) { + printf("%s: expects a filename\n", cmd->words[0]); return 0; } - outfname = (cmd->nwords == 2 ? - stripslashes(cmd->words[1], 0) : cmd->words[2]); - fh = fxp_open(fname, SSH_FXF_READ); - if (!fh) { - printf("%s: %s\n", fname, fxp_error()); - sfree(fname); - return 0; - } + ret = 1; + do { + SftpWildcardMatcher *swcm; - if (restart) { - fp = fopen(outfname, "rb+"); - } else { - fp = fopen(outfname, "wb"); - } + origfname = cmd->words[i++]; + unwcfname = snewn(strlen(origfname)+1, char); - if (!fp) { - printf("local: unable to open %s\n", outfname); - fxp_close(fh); - sfree(fname); - return 0; - } + if (multiple && !wc_unescape(unwcfname, origfname)) { + swcm = sftp_begin_wildcard_matching(origfname); + if (!swcm) { + sfree(unwcfname); + continue; + } + origwfname = sftp_wildcard_get_filename(swcm); + if (!origwfname) { + /* Politely warn the user that nothing matched. */ + printf("%s: nothing matched\n", origfname); + sftp_finish_wildcard_matching(swcm); + sfree(unwcfname); + continue; + } + } else { + origwfname = origfname; + swcm = NULL; + } - 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); - } + while (origwfname) { + fname = canonify(origwfname); - printf("remote:%s => local:%s\n", fname, outfname); + if (!fname) { + sftp_finish_wildcard_matching(swcm); + printf("%s: canonify: %s\n", origwfname, fxp_error()); + sfree(origwfname); + sfree(unwcfname); + return 0; + } - /* - * FIXME: we can use FXP_FSTAT here to get the file size, and - * thus put up a progress bar. - */ - ret = 1; - while (1) { - char buffer[4096]; - int len; - int wpos, wlen; + if (!multiple && i < cmd->nwords) + outfname = cmd->words[i++]; + else + outfname = stripslashes(origwfname, 0); - len = fxp_read(fh, buffer, offset, sizeof(buffer)); - if ((len == -1 && fxp_error_type() == SSH_FX_EOF) || len == 0) - break; - if (len == -1) { - printf("error while reading: %s\n", fxp_error()); - ret = 0; - break; - } + ret = sftp_get_file(fname, outfname, recurse, restart); - wpos = 0; - while (wpos < len) { - wlen = fwrite(buffer, 1, len - wpos, fp); - if (wlen <= 0) { - printf("error while writing local file\n"); - ret = 0; - break; + sfree(fname); + + if (swcm) { + sfree(origwfname); + origwfname = sftp_wildcard_get_filename(swcm); + } else { + origwfname = NULL; } - wpos += wlen; - } - if (wpos < len) { /* we had an error */ - ret = 0; - break; } - offset = uint64_add32(offset, len); - } + sfree(unwcfname); + if (swcm) + sftp_finish_wildcard_matching(swcm); + if (!ret) + return ret; - fclose(fp); - fxp_close(fh); - 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; - char *fname, *origoutfname, *outfname; - uint64 offset; - FILE *fp; - int ret; + char *fname, *wfname, *origoutfname, *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("put: expects a filename\n"); + not_connected(); 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; + 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++; } - fp = fopen(fname, "rb"); - if (!fp) { - printf("local: unable to open %s\n", fname); - sfree(outfname); - return 0; - } - if (restart) { - fh = fxp_open(outfname, - SSH_FXF_WRITE); - } else { - fh = fxp_open(outfname, - SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC); - } - if (!fh) { - printf("%s: %s\n", outfname, fxp_error()); - sfree(outfname); + if (i >= cmd->nwords) { + printf("%s: expects a filename\n", cmd->words[0]); return 0; } - if (restart) { - char decbuf[30]; - struct fxp_attrs attrs; - if (!fxp_fstat(fh, &attrs)) { - 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; + ret = 1; + do { + WildcardMatcher *wcm; + fname = cmd->words[i++]; + + if (multiple && test_wildcard(fname, FALSE) == WCTYPE_WILDCARD) { + wcm = begin_wildcard_matching(fname); + wfname = wildcard_get_filename(wcm); + if (!wfname) { + /* Politely warn the user that nothing matched. */ + printf("%s: nothing matched\n", fname); + finish_wildcard_matching(wcm); + continue; + } + } else { + wfname = fname; + wcm = NULL; } - 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"); + + while (wfname) { + if (!multiple && i < cmd->nwords) + origoutfname = cmd->words[i++]; + else + origoutfname = stripslashes(wfname, 1); + + outfname = canonify(origoutfname); + if (!outfname) { + printf("%s: canonify: %s\n", origoutfname, fxp_error()); + if (wcm) { + sfree(wfname); + finish_wildcard_matching(wcm); + } + return 0; + } + ret = sftp_put_file(wfname, outfname, recurse, restart); sfree(outfname); - 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); + if (wcm) { + sfree(wfname); + wfname = wildcard_get_filename(wcm); + } else { + wfname = NULL; + } + } - /* - * FIXME: we can use FXP_FSTAT here to get the file size, and - * thus put up a progress bar. - */ - ret = 1; - while (1) { - char buffer[4096]; - int len; + if (wcm) + finish_wildcard_matching(wcm); - len = fread(buffer, 1, sizeof(buffer), fp); - if (len == -1) { - printf("error while reading local file\n"); - ret = 0; - break; - } else if (len == 0) { - break; - } - if (!fxp_write(fh, buffer, offset, len)) { - printf("error while writing: %s\n", fxp_error()); - ret = 0; - break; - } - offset = uint64_add32(offset, len); - } + if (!ret) + return ret; - fxp_close(fh); - 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) { char *dir; + struct sftp_packet *pktin; + struct sftp_request *req; int result; + int i, ret; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -581,30 +1430,56 @@ int sftp_cmd_mkdir(struct sftp_command *cmd) return 0; } - dir = canonify(cmd->words[1]); - if (!dir) { - printf("%s: %s\n", dir, fxp_error()); - return 0; + ret = 1; + for (i = 1; i < cmd->nwords; i++) { + dir = canonify(cmd->words[i]); + if (!dir) { + printf("%s: canonify: %s\n", dir, fxp_error()); + return 0; + } + + req = fxp_mkdir_send(dir); + pktin = sftp_wait_for_reply(req); + result = fxp_mkdir_recv(pktin, req); + + if (!result) { + printf("mkdir %s: %s\n", dir, fxp_error()); + ret = 0; + } else + printf("mkdir %s: OK\n", dir); + + sfree(dir); } - result = fxp_mkdir(dir); + return ret; +} + +static int sftp_action_rmdir(void *vctx, char *dir) +{ + struct sftp_packet *pktin; + struct sftp_request *req; + int result; + + req = fxp_rmdir_send(dir); + pktin = sftp_wait_for_reply(req); + result = fxp_rmdir_recv(pktin, req); + if (!result) { - printf("mkdir %s: %s\n", dir, fxp_error()); - sfree(dir); + printf("rmdir %s: %s\n", dir, fxp_error()); return 0; } - sfree(dir); + printf("rmdir %s: OK\n", dir); + return 1; } int sftp_cmd_rmdir(struct sftp_command *cmd) { - char *dir; - int result; + int i, ret; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -613,30 +1488,39 @@ int sftp_cmd_rmdir(struct sftp_command *cmd) return 0; } - dir = canonify(cmd->words[1]); - if (!dir) { - printf("%s: %s\n", dir, fxp_error()); - return 0; - } + ret = 1; + for (i = 1; i < cmd->nwords; i++) + ret &= wildcard_iterate(cmd->words[i], sftp_action_rmdir, NULL); + + return ret; +} + +static int sftp_action_rm(void *vctx, char *fname) +{ + struct sftp_packet *pktin; + struct sftp_request *req; + int result; + + req = fxp_remove_send(fname); + pktin = sftp_wait_for_reply(req); + result = fxp_remove_recv(pktin, req); - result = fxp_rmdir(dir); if (!result) { - printf("rmdir %s: %s\n", dir, fxp_error()); - sfree(dir); + printf("rm %s: %s\n", fname, fxp_error()); return 0; } - sfree(dir); + printf("rm %s: OK\n", fname); + return 1; } int sftp_cmd_rm(struct sftp_command *cmd) { - char *fname; - int result; + int i, ret; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -645,30 +1529,91 @@ int sftp_cmd_rm(struct sftp_command *cmd) return 0; } - fname = canonify(cmd->words[1]); - if (!fname) { - printf("%s: %s\n", fname, fxp_error()); - return 0; + ret = 1; + for (i = 1; i < cmd->nwords; i++) + ret &= wildcard_iterate(cmd->words[i], sftp_action_rm, NULL); + + return ret; +} + +static int check_is_dir(char *dstfname) +{ + struct sftp_packet *pktin; + struct sftp_request *req; + struct fxp_attrs attrs; + int result; + + req = fxp_stat_send(dstfname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &attrs); + + if (result && + (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && + (attrs.permissions & 0040000)) + return TRUE; + else + return FALSE; +} + +struct sftp_context_mv { + char *dstfname; + int dest_is_dir; +}; + +static int sftp_action_mv(void *vctx, char *srcfname) +{ + struct sftp_context_mv *ctx = (struct sftp_context_mv *)vctx; + struct sftp_packet *pktin; + struct sftp_request *req; + const char *error; + char *finalfname, *newcanon = NULL; + int ret, result; + + if (ctx->dest_is_dir) { + char *p; + char *newname; + + p = srcfname + strlen(srcfname); + while (p > srcfname && p[-1] != '/') p--; + newname = dupcat(ctx->dstfname, "/", p, NULL); + newcanon = canonify(newname); + if (!newcanon) { + printf("%s: canonify: %s\n", newname, fxp_error()); + sfree(newname); + return 0; + } + sfree(newname); + + finalfname = newcanon; + } else { + finalfname = ctx->dstfname; } - result = fxp_remove(fname); - if (!result) { - printf("rm %s: %s\n", fname, fxp_error()); - sfree(fname); - return 0; + req = fxp_rename_send(srcfname, finalfname); + pktin = sftp_wait_for_reply(req); + result = fxp_rename_recv(pktin, req); + + error = result ? NULL : fxp_error(); + + if (error) { + printf("mv %s %s: %s\n", srcfname, finalfname, error); + ret = 0; + } else { + printf("%s -> %s\n", srcfname, finalfname); + ret = 1; } - sfree(fname); - return 1; + sfree(newcanon); + return ret; } int sftp_cmd_mv(struct sftp_command *cmd) { - char *srcfname, *dstfname; - int result; + struct sftp_context_mv actx, *ctx = &actx; + int i, ret; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -676,71 +1621,91 @@ int sftp_cmd_mv(struct sftp_command *cmd) printf("mv: expects two filenames\n"); return 0; } - srcfname = canonify(cmd->words[1]); - if (!srcfname) { - printf("%s: %s\n", srcfname, fxp_error()); + + ctx->dstfname = canonify(cmd->words[cmd->nwords-1]); + if (!ctx->dstfname) { + printf("%s: canonify: %s\n", ctx->dstfname, fxp_error()); + return 0; + } + + /* + * If there's more than one source argument, or one source + * argument which is a wildcard, we _require_ that the + * destination is a directory. + */ + ctx->dest_is_dir = check_is_dir(ctx->dstfname); + if ((cmd->nwords > 3 || is_wildcard(cmd->words[1])) && !ctx->dest_is_dir) { + printf("mv: multiple or wildcard arguments require the destination" + " to be a directory\n"); + sfree(ctx->dstfname); return 0; } - dstfname = canonify(cmd->words[2]); - if (!dstfname) { - printf("%s: %s\n", dstfname, fxp_error()); + /* + * Now iterate over the source arguments. + */ + ret = 1; + for (i = 1; i < cmd->nwords-1; i++) + ret &= wildcard_iterate(cmd->words[i], sftp_action_mv, ctx); + + sfree(ctx->dstfname); + return ret; +} + +struct sftp_context_chmod { + unsigned attrs_clr, attrs_xor; +}; + +static int sftp_action_chmod(void *vctx, char *fname) +{ + struct fxp_attrs attrs; + struct sftp_packet *pktin; + struct sftp_request *req; + int result; + unsigned oldperms, newperms; + struct sftp_context_chmod *ctx = (struct sftp_context_chmod *)vctx; + + req = fxp_stat_send(fname); + pktin = sftp_wait_for_reply(req); + result = fxp_stat_recv(pktin, req, &attrs); + + if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) { + printf("get attrs for %s: %s\n", fname, + result ? "file permissions not provided" : fxp_error()); return 0; } - result = fxp_rename(srcfname, dstfname); - if (!result) { - char const *error = fxp_error(); - struct fxp_attrs attrs; + attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */ + oldperms = attrs.permissions & 07777; + attrs.permissions &= ~ctx->attrs_clr; + attrs.permissions ^= ctx->attrs_xor; + newperms = attrs.permissions & 07777; - /* - * The move might have failed because dstfname pointed at a - * directory. We check this possibility now: if dstfname - * _is_ a directory, we re-attempt the move by appending - * the basename of srcfname to dstfname. - */ - result = fxp_stat(dstfname, &attrs); - if (result && - (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && - (attrs.permissions & 0040000)) { - char *p; - char *newname, *newcanon; - printf("(destination %s is a directory)\n", dstfname); - p = srcfname + strlen(srcfname); - while (p > srcfname && p[-1] != '/') p--; - newname = dupcat(dstfname, "/", p, NULL); - newcanon = canonify(newname); - sfree(newname); - if (newcanon) { - sfree(dstfname); - dstfname = newcanon; - result = fxp_rename(srcfname, dstfname); - error = result ? NULL : fxp_error(); - } - } - if (error) { - printf("mv %s %s: %s\n", srcfname, dstfname, error); - sfree(srcfname); - sfree(dstfname); - return 0; - } + if (oldperms == newperms) + return 1; /* no need to do anything! */ + + req = fxp_setstat_send(fname, attrs); + pktin = sftp_wait_for_reply(req); + result = fxp_setstat_recv(pktin, req); + + if (!result) { + printf("set attrs for %s: %s\n", fname, fxp_error()); + return 0; } - printf("%s -> %s\n", srcfname, dstfname); - sfree(srcfname); - sfree(dstfname); + printf("%s: %04o -> %04o\n", fname, oldperms, newperms); + return 1; } int sftp_cmd_chmod(struct sftp_command *cmd) { - char *fname, *mode; - int result; - struct fxp_attrs attrs; - unsigned attrs_clr, attrs_xor, oldperms, newperms; + char *mode; + int i, ret; + struct sftp_context_chmod actx, *ctx = &actx; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -760,7 +1725,7 @@ int sftp_cmd_chmod(struct sftp_command *cmd) * Additionally, the s attribute may not be specified for any * [ugoa] specifications other than exactly u or exactly g. */ - attrs_clr = attrs_xor = 0; + ctx->attrs_clr = ctx->attrs_xor = 0; mode = cmd->words[1]; if (mode[0] >= '0' && mode[0] <= '9') { if (mode[strspn(mode, "01234567")]) { @@ -768,9 +1733,9 @@ int sftp_cmd_chmod(struct sftp_command *cmd) " contain digits 0-7 only\n"); return 0; } - attrs_clr = 07777; - sscanf(mode, "%o", &attrs_xor); - attrs_xor &= attrs_clr; + ctx->attrs_clr = 07777; + sscanf(mode, "%o", &ctx->attrs_xor); + ctx->attrs_xor &= ctx->attrs_clr; } else { while (*mode) { char *modebegin = mode; @@ -788,20 +1753,20 @@ int sftp_cmd_chmod(struct sftp_command *cmd) default: printf("chmod: file mode '%.*s' contains unrecognised" " user/group/other specifier '%c'\n", - strcspn(modebegin, ","), modebegin, *mode); + (int)strcspn(modebegin, ","), modebegin, *mode); return 0; } mode++; } if (!*mode || *mode == ',') { printf("chmod: file mode '%.*s' is incomplete\n", - strcspn(modebegin, ","), modebegin); + (int)strcspn(modebegin, ","), modebegin); return 0; } action = *mode++; if (!*mode || *mode == ',') { printf("chmod: file mode '%.*s' is incomplete\n", - strcspn(modebegin, ","), modebegin); + (int)strcspn(modebegin, ","), modebegin); return 0; } perms = 0; @@ -816,7 +1781,7 @@ int sftp_cmd_chmod(struct sftp_command *cmd) (subset & 06777) != 02070) { printf("chmod: file mode '%.*s': set[ug]id bit should" " be used with exactly one of u or g only\n", - strcspn(modebegin, ","), modebegin); + (int)strcspn(modebegin, ","), modebegin); return 0; } perms |= 06000; @@ -824,7 +1789,7 @@ int sftp_cmd_chmod(struct sftp_command *cmd) default: printf("chmod: file mode '%.*s' contains unrecognised" " permission specifier '%c'\n", - strcspn(modebegin, ","), modebegin, *mode); + (int)strcspn(modebegin, ","), modebegin, *mode); return 0; } mode++; @@ -832,64 +1797,39 @@ int sftp_cmd_chmod(struct sftp_command *cmd) if (!(subset & 06777) && (perms &~ subset)) { printf("chmod: file mode '%.*s' contains no user/group/other" " specifier and permissions other than 't' \n", - strcspn(modebegin, ","), modebegin); + (int)strcspn(modebegin, ","), modebegin); return 0; } perms &= subset; switch (action) { case '+': - attrs_clr |= perms; - attrs_xor |= perms; + ctx->attrs_clr |= perms; + ctx->attrs_xor |= perms; break; case '-': - attrs_clr |= perms; - attrs_xor &= ~perms; + ctx->attrs_clr |= perms; + ctx->attrs_xor &= ~perms; break; case '=': - attrs_clr |= subset; - attrs_xor |= perms; + ctx->attrs_clr |= subset; + ctx->attrs_xor |= perms; break; } if (*mode) mode++; /* eat comma */ } } - fname = canonify(cmd->words[2]); - if (!fname) { - printf("%s: %s\n", fname, fxp_error()); - return 0; - } - - result = fxp_stat(fname, &attrs); - if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) { - printf("get attrs for %s: %s\n", fname, - result ? "file permissions not provided" : fxp_error()); - sfree(fname); - return 0; - } - - attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */ - oldperms = attrs.permissions & 07777; - attrs.permissions &= ~attrs_clr; - attrs.permissions ^= attrs_xor; - newperms = attrs.permissions & 07777; - - result = fxp_setstat(fname, attrs); - - if (!result) { - printf("set attrs for %s: %s\n", fname, fxp_error()); - sfree(fname); - return 0; - } - - printf("%s: %04o -> %04o\n", fname, oldperms, newperms); + ret = 1; + for (i = 2; i < cmd->nwords; i++) + ret &= wildcard_iterate(cmd->words[i], sftp_action_chmod, ctx); - sfree(fname); - return 1; + return ret; } static int sftp_cmd_open(struct sftp_command *cmd) { + int portnumber; + if (back != NULL) { printf("psftp: already connected\n"); return 0; @@ -900,7 +1840,16 @@ static int sftp_cmd_open(struct sftp_command *cmd) return 0; } - if (psftp_connect(cmd->words[1], NULL, 0)) { + if (cmd->nwords > 2) { + portnumber = atoi(cmd->words[2]); + if (portnumber == 0) { + printf("open: invalid port number\n"); + return 0; + } + } else + portnumber = 0; + + if (psftp_connect(cmd->words[1], NULL, portnumber)) { back = NULL; /* connection is already closed */ return -1; /* this is fatal */ } @@ -910,34 +1859,21 @@ static int sftp_cmd_open(struct sftp_command *cmd) static int sftp_cmd_lcd(struct sftp_command *cmd) { - char *currdir; - int len; + char *currdir, *errmsg; if (cmd->nwords < 2) { printf("lcd: expects a local directory name\n"); return 0; } - if (!SetCurrentDirectory(cmd->words[1])) { - LPVOID message; - int i; - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR)&message, 0, NULL); - i = strcspn((char *)message, "\n"); - printf("lcd: unable to change directory: %.*s\n", i, (LPCTSTR)message); - LocalFree(message); + errmsg = psftp_lcd(cmd->words[1]); + if (errmsg) { + printf("lcd: unable to change directory: %s\n", errmsg); + sfree(errmsg); return 0; } - currdir = smalloc(256); - len = GetCurrentDirectory(256, currdir); - if (len > 256) - currdir = srealloc(currdir, len); - GetCurrentDirectory(len, currdir); + currdir = psftp_getcwd(); printf("New local directory is %s\n", currdir); sfree(currdir); @@ -947,13 +1883,8 @@ static int sftp_cmd_lcd(struct sftp_command *cmd) static int sftp_cmd_lpwd(struct sftp_command *cmd) { char *currdir; - int len; - currdir = smalloc(256); - len = GetCurrentDirectory(256, currdir); - if (len > 256) - currdir = srealloc(currdir, len); - GetCurrentDirectory(len, currdir); + currdir = psftp_getcwd(); printf("Current local directory is %s\n", currdir); sfree(currdir); @@ -994,9 +1925,10 @@ static struct sftp_cmd_lookup { * in ASCII order. */ { - "!", TRUE, "run a local Windows command", + "!", TRUE, "run a local command", "\n" - " Runs a local Windows command. For example, \"!del myfile\".\n", + /* FIXME: this example is crap for non-Windows. */ + " Runs a local command. For example, \"!del myfile\".\n", sftp_cmd_pling }, { @@ -1007,7 +1939,7 @@ static struct sftp_cmd_lookup { }, { "cd", TRUE, "change your remote working directory", - " [ ]\n" + " [ ]\n" " Change the remote working directory for your SFTP session.\n" " If a new working directory is not supplied, you will be\n" " returned to your home directory.\n", @@ -1015,10 +1947,11 @@ static struct sftp_cmd_lookup { }, { "chmod", TRUE, "change file permissions and modes", - " ( | ) \n" - " Change the file permissions on a file or directory.\n" - " can be any octal Unix permission specifier.\n" - " Alternatively, can include:\n" + " [ ... ]\n" + " Change the file permissions on one or more remote files or\n" + " directories.\n" + " can be any octal Unix permission specifier.\n" + " Alternatively, can include the following modifiers:\n" " u+r make file readable by owning user\n" " u+w make file writable by owning user\n" " u+x make file executable by owning user\n" @@ -1041,20 +1974,30 @@ static struct sftp_cmd_lookup { sftp_cmd_chmod }, { - "del", TRUE, "delete a file", - " \n" - " Delete a file.\n", + "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 files on the remote server", + " [ ... ]\n" + " Delete a file or files from the server.\n", sftp_cmd_rm }, { "delete", FALSE, "del", NULL, sftp_cmd_rm }, { - "dir", TRUE, "list contents of a remote directory", - " [ ]\n" + "dir", TRUE, "list remote files", + " [ ]/[ ]\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 }, { @@ -1062,10 +2005,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 }, { @@ -1095,32 +2039,55 @@ static struct sftp_cmd_lookup { sftp_cmd_ls }, { - "mkdir", TRUE, "create a directory on the remote server", - " \n" - " Creates a directory with the given name on the server.\n", + "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 directories on the remote server", + " [ ... ]\n" + " Creates directories with the given names on the server.\n", sftp_cmd_mkdir }, { - "mv", TRUE, "move or rename a file on the remote server", - " \n" - " Moves or renames the file on the server,\n" - " so that it is accessible under the name .\n", + "mput", TRUE, "upload multiple files at once", + " [ -r ] [ -- ] [ ... ]\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 file(s) on the remote server", + " [ ... ] \n" + " Moves or renames (s) on the server to ,\n" + " also on the server.\n" + " If specifies an existing directory, then \n" + " may be a wildcard, and multiple s may be given; all\n" + " source files are moved into .\n" + " Otherwise, must specify a single file, which is moved\n" + " or renamed so that it is accessible under the name .\n", sftp_cmd_mv }, { "open", TRUE, "connect to a host", - " [@]\n" + " [@] []\n" " Establishes an SFTP connection to a given host. Only usable\n" - " when you did not already specify a host name on the command\n" - " line.\n", + " when you are not already connected to a server.\n", sftp_cmd_open }, { "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 }, { @@ -1134,11 +2101,12 @@ static struct sftp_cmd_lookup { sftp_cmd_quit }, { - "reget", TRUE, "continue downloading a file", - " [ ]\n" + "reget", TRUE, "continue downloading files", + " [ -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 }, { @@ -1150,11 +2118,12 @@ static struct sftp_cmd_lookup { sftp_cmd_mv }, { - "reput", TRUE, "continue uploading a file", - " [ ]\n" + "reput", TRUE, "continue uploading files", + " [ -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 }, { @@ -1162,10 +2131,11 @@ static struct sftp_cmd_lookup { sftp_cmd_rm }, { - "rmdir", TRUE, "remove a directory on the remote server", - " \n" + "rmdir", TRUE, "remove directories on the remote server", + " [ ... ]\n" " Removes the directory with the given name on the server.\n" - " The directory will not be removed unless it is empty.\n", + " The directory will not be removed unless it is empty.\n" + " Wildcards may be used to specify multiple directories.\n", sftp_cmd_rmdir } }; @@ -1243,45 +2213,35 @@ 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 = smalloc(sizeof(struct sftp_command)); + 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 = srealloc(line, linesize); - 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"); + sfree(line); + return cmd; /* eof */ } + + line[strcspn(line, "\r\n")] = '\0'; + if (modeflags & 1) { printf("%s\n", line); } @@ -1297,9 +2257,14 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) * containing everything else on the line. */ cmd->nwords = cmd->wordssize = 2; - cmd->words = srealloc(cmd->words, cmd->wordssize * sizeof(char *)); - cmd->words[0] = "!"; - cmd->words[1] = p+1; + cmd->words = sresize(cmd->words, cmd->wordssize, char *); + cmd->words[0] = dupstr("!"); + cmd->words[1] = dupstr(p+1); + } else if (*p == '#') { + /* + * Special case: comment. Entire line is ignored. + */ + cmd->nwords = cmd->wordssize = 0; } else { /* @@ -1318,10 +2283,13 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) * >this has "quotes" in< * >and"this"< */ - while (*p) { + while (1) { /* skip whitespace */ while (*p && (*p == ' ' || *p == '\t')) p++; + /* terminate loop */ + if (!*p) + break; /* mark start of word */ q = r = p; /* q sits at start, r writes word */ quoting = 0; @@ -1340,13 +2308,14 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) *r = '\0'; if (cmd->nwords >= cmd->wordssize) { cmd->wordssize = cmd->nwords + 16; - cmd->words = - srealloc(cmd->words, cmd->wordssize * sizeof(char *)); + cmd->words = sresize(cmd->words, cmd->wordssize, char *); } - cmd->words[cmd->nwords++] = q; + cmd->words[cmd->nwords++] = dupstr(q); } } + sfree(line); + /* * Now parse the first word and assign a function. */ @@ -1367,6 +2336,9 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) static int do_sftp_init(void) { + struct sftp_packet *pktin; + struct sftp_request *req; + /* * Do protocol initialisation. */ @@ -1379,7 +2351,10 @@ static int do_sftp_init(void) /* * Find out where our home directory is. */ - homedir = fxp_realpath("."); + req = fxp_realpath_send("."); + pktin = sftp_wait_for_reply(req); + homedir = fxp_realpath_recv(pktin, req); + if (!homedir) { fprintf(stderr, "Warning: failed to resolve home directory: %s\n", @@ -1392,6 +2367,28 @@ static int do_sftp_init(void) return 0; } +void do_sftp_cleanup() +{ + char ch; + if (back) { + back->special(backhandle, TS_EOF); + sent_eof = TRUE; + sftp_recvdata(&ch, 1); + back->free(backhandle); + sftp_cleanup_request(); + back = NULL; + backhandle = NULL; + } + if (pwd) { + sfree(pwd); + pwd = NULL; + } + if (homedir) { + sfree(homedir); + homedir = NULL; + } +} + void do_sftp(int mode, int modeflags, char *batchfile) { FILE *fp; @@ -1407,10 +2404,18 @@ 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; - if (cmd->obey(cmd) < 0) + ret = cmd->obey(cmd); + if (cmd->words) { + int i; + for(i = 0; i < cmd->nwords; i++) + sfree(cmd->words[i]); + sfree(cmd->words); + } + sfree(cmd); + if (ret < 0) break; } } else { @@ -1474,6 +2479,18 @@ void modalfatalbox(char *fmt, ...) cleanup_exit(1); } +void nonfatal(char *fmt, ...) +{ + char *str, *str2; + va_list ap; + va_start(ap, fmt); + str = dupvprintf(fmt, ap); + str2 = dupcat("Error: ", str, "\n", NULL); + sfree(str); + va_end(ap); + fputs(str2, stderr); + sfree(str2); +} void connection_fatal(void *frontend, char *fmt, ...) { char *str, *str2; @@ -1501,18 +2518,14 @@ void ldisc_send(void *handle, char *buf, int len, int interactive) } /* - * Be told what socket we're supposed to be using. + * In psftp, all agent requests should be synchronous, so this is a + * never-called stub. */ -static SOCKET sftp_ssh_socket; -char *do_select(SOCKET skt, int startup) +void agent_schedule_callback(void (*callback)(void *, void *, int), + void *callback_ctx, void *data, int len) { - if (startup) - sftp_ssh_socket = skt; - else - sftp_ssh_socket = INVALID_SOCKET; - return NULL; + assert(!"We shouldn't be here"); } -extern int select_result(WPARAM, LPARAM); /* * Receive a block of data from the SSH link. Block until all data @@ -1527,19 +2540,19 @@ static unsigned char *outptr; /* where to put the data */ static unsigned outlen; /* how much data required */ static unsigned char *pending = NULL; /* any spare data */ static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */ -int from_backend(void *frontend, int is_stderr, char *data, int datalen) +int from_backend(void *frontend, int is_stderr, const char *data, int datalen) { unsigned char *p = (unsigned char *) data; unsigned len = (unsigned) datalen; - assert(len > 0); - /* * stderr data is just spouted to local stderr and otherwise * ignored. */ if (is_stderr) { - fwrite(data, 1, len, stderr); + if (len > 0) + if (fwrite(data, 1, len, stderr) < len) + /* oh well */; return 0; } @@ -1549,7 +2562,7 @@ int from_backend(void *frontend, int is_stderr, char *data, int datalen) if (!outptr) return 0; - if (outlen > 0) { + if ((outlen > 0) && (len > 0)) { unsigned used = outlen; if (used > len) used = len; @@ -1563,10 +2576,7 @@ int from_backend(void *frontend, int is_stderr, char *data, int datalen) if (len > 0) { if (pendsize < pendlen + len) { pendsize = pendlen + len + 4096; - pending = (pending ? srealloc(pending, pendsize) : - smalloc(pendsize)); - if (!pending) - fatalbox("Out of memory"); + pending = sresize(pending, pendsize, unsigned char); } memcpy(pending + pendlen, p, len); pendlen += len; @@ -1574,6 +2584,28 @@ int from_backend(void *frontend, int is_stderr, char *data, int datalen) return 0; } +int from_backend_untrusted(void *frontend_handle, const char *data, int len) +{ + /* + * No "untrusted" output should get here (the way the code is + * currently, it's all diverted by FLAG_STDERR). + */ + assert(!"Unexpected call to from_backend_untrusted()"); + return 0; /* not reached */ +} +int from_backend_eof(void *frontend) +{ + /* + * We expect to be the party deciding when to close the + * connection, so if we see EOF before we sent it ourselves, we + * should panic. + */ + if (!sent_eof) { + connection_fatal(frontend, + "Received unexpected end-of-file from SFTP server"); + } + return FALSE; +} int sftp_recvdata(char *buf, int len) { outptr = (unsigned char *) buf; @@ -1602,68 +2634,29 @@ int sftp_recvdata(char *buf, int len) } while (outlen > 0) { - fd_set readfds; - - FD_ZERO(&readfds); - FD_SET(sftp_ssh_socket, &readfds); - if (select(1, &readfds, NULL, NULL, NULL) < 0) + if (back->exitcode(backhandle) >= 0 || ssh_sftp_loop_iteration() < 0) return 0; /* doom */ - select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ); } return 1; } int sftp_senddata(char *buf, int len) { - back->send(backhandle, (unsigned char *) buf, len); + back->send(backhandle, buf, len); return 1; } /* - * Loop through the ssh connection and authentication process. - */ -static void ssh_sftp_init(void) -{ - if (sftp_ssh_socket == INVALID_SOCKET) - return; - while (!back->sendok(backhandle)) { - fd_set readfds; - FD_ZERO(&readfds); - FD_SET(sftp_ssh_socket, &readfds); - if (select(1, &readfds, NULL, NULL, NULL) < 0) - return; /* doom */ - select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ); - } -} - -/* - * Initialize the Win$ock driver. - */ -static void init_winsock(void) -{ - WORD winsock_ver; - WSADATA wsadata; - - winsock_ver = MAKEWORD(1, 1); - if (WSAStartup(winsock_ver, &wsadata)) { - fprintf(stderr, "Unable to initialise WinSock"); - cleanup_exit(1); - } - if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) { - fprintf(stderr, "WinSock version is incompatible with 1.1"); - cleanup_exit(1); - } -} - -/* * Short description of parameters. */ static void usage(void) { printf("PuTTY Secure File Transfer (SFTP) client\n"); printf("%s\n", ver); - printf("Usage: psftp [options] user@host\n"); + printf("Usage: psftp [options] [user@]host\n"); printf("Options:\n"); + printf(" -V print version information and exit\n"); + printf(" -pgpfp print PGP key fingerprints and exit\n"); printf(" -b file use specified batchfile\n"); printf(" -bc output batchfile commands\n"); printf(" -be don't stop batchfile processing if errors\n"); @@ -1673,19 +2666,29 @@ static void usage(void) printf(" -P port connect to specified port\n"); printf(" -pw passw login with specified password\n"); printf(" -1 -2 force use of particular SSH protocol version\n"); + printf(" -4 -6 force use of IPv4 or IPv6\n"); printf(" -C enable compression\n"); printf(" -i key private key file for authentication\n"); + printf(" -noagent disable use of Pageant\n"); + printf(" -agent enable use of Pageant\n"); printf(" -batch disable all interactive prompts\n"); cleanup_exit(1); } +static void version(void) +{ + printf("psftp: %s\n", ver); + cleanup_exit(1); +} + /* * Connect to a host. */ static int psftp_connect(char *userhost, char *user, int portnumber) { char *host, *realhost; - char *err; + const char *err; + void *logctx; /* Separate host and username */ host = userhost; @@ -1701,110 +2704,124 @@ 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); - strncpy(cfg.host, host, sizeof(cfg.host) - 1); - cfg.host[sizeof(cfg.host) - 1] = '\0'; + /* + * 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 */ + Conf *conf2 = conf_new(); + conf_set_str(conf2, CONF_host, ""); + do_defaults(host, conf2); + if (conf_get_str(conf2, CONF_host)[0] != '\0') { + /* Settings present and include hostname */ + /* Re-load data into the real config. */ + do_defaults(host, conf); + } else { + /* Session doesn't exist or mention a hostname. */ + /* Use `host' as a bare hostname. */ + conf_set_str(conf, CONF_host, host); + } + conf_free(conf2); + } else { + /* Patch in hostname `host' to session details. */ + conf_set_str(conf, CONF_host, host); } /* * Force use of SSH. (If they got the protocol wrong we assume the * port is useless too.) */ - if (cfg.protocol != PROT_SSH) { - cfg.protocol = PROT_SSH; - cfg.port = 22; + if (conf_get_int(conf, CONF_protocol) != PROT_SSH) { + conf_set_int(conf, CONF_protocol, PROT_SSH); + conf_set_int(conf, CONF_port, 22); } /* - * Enact command-line overrides. - */ - cmdline_run_saved(&cfg); - - /* - * Trim leading whitespace off the hostname if it's there. + * 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. */ - { - int space = strspn(cfg.host, " \t"); - memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space); - } - - /* See if host is of the form user@host */ - if (cfg.host[0] != '\0') { - char *atsign = strchr(cfg.host, '@'); - /* Make sure we're not overflowing the user field */ - if (atsign) { - if (atsign - cfg.host < sizeof cfg.username) { - strncpy(cfg.username, cfg.host, atsign - cfg.host); - cfg.username[atsign - cfg.host] = '\0'; - } - memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1)); - } - } + if ((conf_get_int(conf, CONF_sshprot) & ~1) != 2) /* is it 2 or 3? */ + conf_set_int(conf, CONF_sshprot, 2); /* - * Trim a colon suffix off the hostname if it's there. + * Enact command-line overrides. */ - cfg.host[strcspn(cfg.host, ":")] = '\0'; + cmdline_run_saved(conf); /* - * Remove any remaining whitespace from the hostname. + * Muck about with the hostname in various ways. */ { - int p1 = 0, p2 = 0; - while (cfg.host[p2] != '\0') { - if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') { - cfg.host[p1] = cfg.host[p2]; - p1++; + char *hostbuf = dupstr(conf_get_str(conf, CONF_host)); + char *host = hostbuf; + char *p, *q; + + /* + * Trim leading whitespace. + */ + host += strspn(host, " \t"); + + /* + * See if host is of the form user@host, and separate out + * the username if so. + */ + if (host[0] != '\0') { + char *atsign = strrchr(host, '@'); + if (atsign) { + *atsign = '\0'; + conf_set_str(conf, CONF_username, host); + host = atsign + 1; } - p2++; } - cfg.host[p1] = '\0'; + + /* + * Remove any remaining whitespace. + */ + p = hostbuf; + q = host; + while (*q) { + if (*q != ' ' && *q != '\t') + *p++ = *q; + q++; + } + *p = '\0'; + + conf_set_str(conf, CONF_host, hostbuf); + sfree(hostbuf); } /* Set username */ if (user != NULL && user[0] != '\0') { - strncpy(cfg.username, user, sizeof(cfg.username) - 1); - 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"); - cleanup_exit(1); - } else { - int len = strlen(cfg.username); - if (cfg.username[len - 1] == '\n') - cfg.username[len - 1] = '\0'; - } + conf_set_str(conf, CONF_username, user); } if (portnumber) - cfg.port = portnumber; - - /* SFTP uses SSH2 by default always */ - cfg.sshprot = 2; + conf_set_int(conf, CONF_port, portnumber); /* * Disable scary things which shouldn't be enabled for simple * things like SCP and SFTP: agent forwarding, port forwarding, * X forwarding. */ - cfg.x11_forward = 0; - cfg.agentfwd = 0; - cfg.portfwd[0] = cfg.portfwd[1] = '\0'; + conf_set_int(conf, CONF_x11_forward, 0); + conf_set_int(conf, CONF_agentfwd, 0); + conf_set_int(conf, CONF_ssh_simple, TRUE); + { + char *key; + while ((key = conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) != NULL) + conf_del_str_str(conf, CONF_portfwd, key); + } /* Set up subsystem name. */ - strcpy(cfg.remote_cmd, "sftp"); - cfg.ssh_subsys = TRUE; - cfg.nopty = TRUE; + conf_set_str(conf, CONF_remote_cmd, "sftp"); + conf_set_int(conf, CONF_ssh_subsys, TRUE); + conf_set_int(conf, CONF_nopty, TRUE); /* - * Set up fallback option, for SSH1 servers or servers with the + * Set up fallback option, for SSH-1 servers or servers with the * sftp subsystem not enabled but the server binary installed * in the usual place. We only support fallback on Unix * systems, and we use a kludgy piece of shellery which should @@ -1820,24 +2837,40 @@ static int psftp_connect(char *userhost, char *user, int portnumber) * obvious pathnames and then give up, and when it does give up * it will print the preferred pathname in the error messages. */ - cfg.remote_cmd_ptr2 = - "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" - "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" - "exec sftp-server"; - cfg.ssh_subsys2 = FALSE; + conf_set_str(conf, CONF_remote_cmd2, + "test -x /usr/lib/sftp-server &&" + " exec /usr/lib/sftp-server\n" + "test -x /usr/local/lib/sftp-server &&" + " exec /usr/local/lib/sftp-server\n" + "exec sftp-server"); + conf_set_int(conf, CONF_ssh_subsys2, FALSE); back = &ssh_backend; - err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost,0); + err = back->init(NULL, &backhandle, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, 0, + conf_get_int(conf, CONF_tcp_keepalives)); if (err != NULL) { fprintf(stderr, "ssh_init: %s\n", err); return 1; } - logctx = log_init(NULL); + logctx = log_init(NULL, conf); back->provide_logctx(backhandle, logctx); - ssh_sftp_init(); + console_provide_logctx(logctx); + while (!back->sendok(backhandle)) { + if (back->exitcode(backhandle) >= 0) + return 1; + if (ssh_sftp_loop_iteration() < 0) { + fprintf(stderr, "ssh_init: error during SSH connection setup\n"); + return 1; + } + } if (verbose && realhost != NULL) printf("Connected to %s\n", realhost); + if (realhost != NULL) + sfree(realhost); return 0; } @@ -1855,7 +2888,7 @@ void cmdline_error(char *p, ...) /* * Main program. Parse arguments etc. */ -int main(int argc, char *argv[]) +int psftp_main(int argc, char *argv[]) { int i; int portnumber = 0; @@ -1863,17 +2896,22 @@ int main(int argc, char *argv[]) int mode = 0; int modeflags = 0; char *batchfile = NULL; - int errors = 0; - flags = FLAG_STDERR | FLAG_INTERACTIVE; + flags = FLAG_STDERR | FLAG_INTERACTIVE +#ifdef FLAG_SYNCAGENT + | FLAG_SYNCAGENT +#endif + ; cmdline_tooltype = TOOLTYPE_FILETRANSFER; - ssh_get_line = &console_get_line; - init_winsock(); sk_init(); userhost = user = NULL; - errors = 0; + /* Load Default Settings before doing anything else. */ + conf = conf_new(); + do_defaults(NULL, conf); + loaded_session = FALSE; + for (i = 1; i < argc; i++) { int ret; if (argv[i][0] != '-') { @@ -1883,7 +2921,7 @@ int main(int argc, char *argv[]) userhost = dupstr(argv[i]); continue; } - ret = cmdline_process_param(argv[i], i+1socket(backhandle) != NULL) { + if (back != NULL && back->connected(backhandle)) { char ch; back->special(backhandle, TS_EOF); + sent_eof = TRUE; sftp_recvdata(&ch, 1); } - WSACleanup(); + do_sftp_cleanup(); random_save_seed(); + cmdline_cleanup(); + console_provide_logctx(NULL); + sk_cleanup(); return 0; }