X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/65857773290a7bacd6f5d84ab51a9c1228e61e99..055817455466c8eb60392f30bb7c689763962e17:/psftp.c diff --git a/psftp.c b/psftp.c index 8dcee0e2..ab076795 100644 --- a/psftp.c +++ b/psftp.c @@ -41,14 +41,6 @@ static Config cfg; */ /* - * Determine whether a string is entirely composed of dots. - */ -static int is_dots(char *str) -{ - return str[strspn(str, ".")] == '\0'; -} - -/* * Attempt to canonify a pathname starting from the pwd. If * canonification fails, at least fall back to returning a _valid_ * pathname (though it may be ugly, eg /home/simon/../foobar). @@ -200,8 +192,7 @@ static int bare_name_compare(const void *av, const void *bv) /* ---------------------------------------------------------------------- * The meat of the `get' and `put' commands. */ -int sftp_get_file(char *fname, char *outfname, int recurse, int restart, - char *wildcard) +int sftp_get_file(char *fname, char *outfname, int recurse, int restart) { struct fxp_handle *fh; struct sftp_packet *pktin; @@ -209,29 +200,25 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart, struct fxp_xfer *xfer; uint64 offset; FILE *fp; - int ret; + 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) { + if (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 */ + sftp_register(req = fxp_stat_send(fname)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_stat_recv(pktin, rreq, &attrs); - if (wildcard || - (result && - (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && - (attrs.permissions & 0040000))) { + if (result && + (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && + (attrs.permissions & 0040000)) { struct fxp_handle *dirhandle; int nnames, namesize; @@ -241,11 +228,9 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart, /* * First, attempt to create the destination directory, - * unless it already exists (or this is a wildcard - * run). + * unless it already exists. */ - if (!wildcard && - file_type(outfname) != FILE_TYPE_DIRECTORY && + if (file_type(outfname) != FILE_TYPE_DIRECTORY && !create_directory(outfname)) { printf("%s: Cannot create directory\n", outfname); return 0; @@ -291,10 +276,17 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart, ournames = sresize(ournames, namesize, struct fxp_name *); } for (i = 0; i < names->nnames; i++) - if (!is_dots(names->names[i].filename) && - (!wildcard || wc_match(wildcard, - names->names[i].filename))) - ournames[nnames++] = fxp_dup_name(&names->names[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); } sftp_register(req = fxp_close_send(dirhandle)); @@ -303,14 +295,6 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart, 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 @@ -361,8 +345,7 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart, ournames[i]->filename); else nextoutfname = dupstr(ournames[i]->filename); - ret = sftp_get_file(nextfname, nextoutfname, - recurse, restart, NULL); + ret = sftp_get_file(nextfname, nextoutfname, recurse, restart); restart = FALSE; /* after first partial file, do full */ sfree(nextoutfname); sfree(nextfname); @@ -442,7 +425,10 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart, ret = xfer_download_gotpkt(xfer, pktin); if (ret < 0) { - printf("error while reading: %s\n", fxp_error()); + if (!shown_err) { + printf("error while reading: %s\n", fxp_error()); + shown_err = TRUE; + } ret = 0; } @@ -480,8 +466,7 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart, return ret; } -int sftp_put_file(char *fname, char *outfname, int recurse, int restart, - char *wildcard) +int sftp_put_file(char *fname, char *outfname, int recurse, int restart) { struct fxp_handle *fh; struct fxp_xfer *xfer; @@ -496,7 +481,7 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart, * (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)) { + if (recurse && file_type(fname) == FILE_TYPE_DIRECTORY) { struct fxp_attrs attrs; int result; int nnames, namesize; @@ -504,28 +489,26 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart, DirHandle *dh; int i; - if (!wildcard) { - /* - * First, attempt to create the destination directory, - * unless it already exists. - */ - sftp_register(req = fxp_stat_send(outfname)); + /* + * 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_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); + result = fxp_mkdir_recv(pktin, rreq); - if (!result) { - printf("%s: create directory: %s\n", - outfname, fxp_error()); - return 0; - } + if (!result) { + printf("%s: create directory: %s\n", + outfname, fxp_error()); + return 0; } } @@ -534,43 +517,20 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart, */ 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); + dh = open_directory(fname); + if (!dh) { + printf("%s: unable to open directory\n", fname); + return 0; } - - /* - * A polite warning if nothing at all matched the - * wildcard. - */ - if (wildcard && !nnames) { - printf("%s: nothing matched\n", wildcard); + 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 @@ -619,8 +579,7 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart, else nextfname = dupstr(ournames[i]); nextoutfname = dupcat(outfname, "/", ournames[i], NULL); - ret = sftp_put_file(nextfname, nextoutfname, - recurse, restart, NULL); + ret = sftp_put_file(nextfname, nextoutfname, recurse, restart); restart = FALSE; /* after first partial file, do full */ sfree(nextoutfname); sfree(nextfname); @@ -743,6 +702,146 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart, } /* ---------------------------------------------------------------------- + * 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, *rreq; + 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); + + sftp_register(req = fxp_opendir_send(cdir)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + dirh = fxp_opendir_recv(pktin, rreq); + + 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, *rreq; + + while (1) { + if (swcm->names && swcm->namepos >= swcm->names->nnames) { + fxp_free_names(swcm->names); + swcm->names = NULL; + } + + if (!swcm->names) { + sftp_register(req = fxp_readdir_send(swcm->dirh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + swcm->names = fxp_readdir_recv(pktin, rreq); + + if (!swcm->names) { + if (fxp_error_type() != SSH_FX_EOF) + printf("%s: reading directory: %s\n", swcm->prefix, + fxp_error()); + 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[strlen(swcm->prefix)-1]=='/' ? "" : "/", + name->filename); + } +} + +void sftp_finish_wildcard_matching(SftpWildcardMatcher *swcm) +{ + struct sftp_packet *pktin; + struct sftp_request *req, *rreq; + + sftp_register(req = fxp_close_send(swcm->dirh)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + fxp_close_recv(pktin, rreq); + + if (swcm->names) + fxp_free_names(swcm->names); + + sfree(swcm->prefix); + sfree(swcm->wildcard); + + sfree(swcm); +} + +/* ---------------------------------------------------------------------- * Actual sftp commands. */ struct sftp_command { @@ -767,6 +866,23 @@ 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]. @@ -967,7 +1083,7 @@ int sftp_cmd_pwd(struct sftp_command *cmd) */ int sftp_general_get(struct sftp_command *cmd, int restart, int multiple) { - char *fname, *unwcfname, *origfname, *outfname; + char *fname, *unwcfname, *origfname, *origwfname, *outfname; int i, ret; int recurse = FALSE; @@ -985,29 +1101,48 @@ int sftp_general_get(struct sftp_command *cmd, int restart, int multiple) } else if (!strcmp(cmd->words[i], "-r")) { recurse = TRUE; } else { - printf("get: unrecognised option '%s'\n", cmd->words[i]); + printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]); return 0; } i++; } if (i >= cmd->nwords) { - printf("get: expects a filename\n"); + printf("%s: expects a filename\n", cmd->words[0]); return 0; } + ret = 1; do { - unwcfname = NULL; + SftpWildcardMatcher *swcm; + origfname = cmd->words[i++]; + unwcfname = snewn(strlen(origfname)+1, char); - if (multiple && - !wc_unescape(unwcfname = snewn(strlen(origfname)+1, char), - origfname)) { - ret = sftp_get_file(pwd, NULL, recurse, restart, origfname); + 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 { - fname = canonify(origfname); + origwfname = origfname; + swcm = NULL; + } + + while (origwfname) { + fname = canonify(origwfname); + if (!fname) { - printf("%s: %s\n", origfname, fxp_error()); + printf("%s: %s\n", origwfname, fxp_error()); sfree(unwcfname); return 0; } @@ -1015,13 +1150,22 @@ int sftp_general_get(struct sftp_command *cmd, int restart, int multiple) if (!multiple && i < cmd->nwords) outfname = cmd->words[i++]; else - outfname = stripslashes(origfname, 0); + outfname = stripslashes(origwfname, 0); - ret = sftp_get_file(fname, outfname, recurse, restart, NULL); + ret = sftp_get_file(fname, outfname, recurse, restart); sfree(fname); + + if (swcm) { + sfree(origwfname); + origwfname = sftp_wildcard_get_filename(swcm); + } else { + origwfname = NULL; + } } sfree(unwcfname); + if (swcm) + sftp_finish_wildcard_matching(swcm); if (!ret) return ret; @@ -1053,7 +1197,7 @@ int sftp_cmd_reget(struct sftp_command *cmd) */ int sftp_general_put(struct sftp_command *cmd, int restart, int multiple) { - char *fname, *origoutfname, *outfname; + char *fname, *wfname, *origoutfname, *outfname; int i, ret; int recurse = FALSE; @@ -1071,36 +1215,65 @@ int sftp_general_put(struct sftp_command *cmd, int restart, int multiple) } else if (!strcmp(cmd->words[i], "-r")) { recurse = TRUE; } else { - printf("put: unrecognised option '%s'\n", cmd->words[i]); + printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]); return 0; } i++; } if (i >= cmd->nwords) { - printf("put: expects a filename\n"); + printf("%s: expects a filename\n", cmd->words[0]); return 0; } + ret = 1; do { + WildcardMatcher *wcm; fname = cmd->words[i++]; if (multiple && test_wildcard(fname, FALSE) == WCTYPE_WILDCARD) { - ret = sftp_put_file(NULL, pwd, recurse, restart, fname); + 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; + } + + while (wfname) { if (!multiple && i < cmd->nwords) origoutfname = cmd->words[i++]; else - origoutfname = stripslashes(fname, 1); + origoutfname = stripslashes(wfname, 1); outfname = canonify(origoutfname); if (!outfname) { printf("%s: %s\n", origoutfname, fxp_error()); + if (wcm) { + sfree(wfname); + finish_wildcard_matching(wcm); + } return 0; } - ret = sftp_put_file(fname, outfname, recurse, restart, NULL); + ret = sftp_put_file(wfname, outfname, recurse, restart); sfree(outfname); + + if (wcm) { + sfree(wfname); + wfname = wildcard_get_filename(wcm); + } else { + wfname = NULL; + } } + + if (wcm) + finish_wildcard_matching(wcm); + if (!ret) return ret; @@ -1632,6 +1805,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", @@ -1642,10 +1823,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 }, { @@ -1653,10 +1836,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 }, { @@ -1687,10 +1871,11 @@ static struct sftp_cmd_lookup { }, { "mget", TRUE, "download multiple files at once", - " [ ... ]\n" + " [ -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", + " such as \"*.c\" to specify lots of files at once.\n" + " If -r specified, recursively fetch files and directories.\n", sftp_cmd_mget }, { @@ -1701,10 +1886,11 @@ static struct sftp_cmd_lookup { }, { "mput", TRUE, "upload multiple files at once", - " [ ... ]\n" + " [ -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", + " such as \"*.c\" to specify lots of files at once.\n" + " If -r specified, recursively store files and directories.\n", sftp_cmd_mput }, { @@ -1724,10 +1910,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 }, { @@ -1742,10 +1929,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 }, { @@ -1758,10 +1946,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 }, { @@ -2257,6 +2446,7 @@ 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(" -batch disable all interactive prompts\n"); @@ -2579,12 +2769,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;