📄 psftp.c
字号:
/* * psftp.c: (platform-independent) front end for PSFTP. */#include <stdio.h>#include <stdlib.h>#include <stdarg.h>#include <assert.h>#include <limits.h>#define PUTTY_DO_GLOBALS#include "putty.h"#include "psftp.h"#include "storage.h"#include "ssh.h"#include "sftp.h"#include "int64.h"/* * Since SFTP is a request-response oriented protocol, it requires * no buffer management: when we send data, we stop and wait for an * acknowledgement _anyway_, and so we can't possibly overfill our * send buffer. */static int psftp_connect(char *userhost, char *user, int portnumber);static int do_sftp_init(void);void do_sftp_cleanup();/* ---------------------------------------------------------------------- * sftp client state. */char *pwd, *homedir;static const Backend *back;static void *backhandle;static Config cfg;/* ---------------------------------------------------------------------- * Higher-level helper functions used in commands. *//* * 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). */char *canonify(char *name){ char *fullname, *canonname; struct sftp_packet *pktin; struct sftp_request *req, *rreq; if (name[0] == '/') { fullname = dupstr(name); } else { char *slash; if (pwd[strlen(pwd) - 1] == '/') slash = ""; else slash = "/"; fullname = dupcat(pwd, slash, name, NULL); } sftp_register(req = fxp_realpath_send(fullname)); rreq = sftp_find_request(pktin = sftp_recv()); assert(rreq == req); canonname = fxp_realpath_recv(pktin, rreq); if (canonname) { sfree(fullname); return canonname; } else { /* * Attempt number 2. Some FXP_REALPATH implementations * (glibc-based ones, in particular) require the _whole_ * path to point to something that exists, whereas others * (BSD-based) only require all but the last component to * exist. So if the first call failed, we should strip off * everything from the last slash onwards and try again, * then put the final component back on. * * Special cases: * * - if the last component is "/." or "/..", then we don't * bother trying this because there's no way it can work. * * - if the thing actually ends with a "/", we remove it * before we start. Except if the string is "/" itself * (although I can't see why we'd have got here if so, * because surely "/" would have worked the first * time?), in which case we don't bother. * * - if there's no slash in the string at all, give up in * confusion (we expect at least one because of the way * we constructed the string). */ int i; char *returnname; i = strlen(fullname); if (i > 2 && fullname[i - 1] == '/') fullname[--i] = '\0'; /* strip trailing / unless at pos 0 */ while (i > 0 && fullname[--i] != '/'); /* * Give up on special cases. */ if (fullname[i] != '/' || /* no slash at all */ !strcmp(fullname + i, "/.") || /* ends in /. */ !strcmp(fullname + i, "/..") || /* ends in /.. */ !strcmp(fullname, "/")) { return fullname; } /* * Now i points at the slash. Deal with the final special * case i==0 (ie the whole path was "/nonexistentfile"). */ fullname[i] = '\0'; /* separate the string */ if (i == 0) { sftp_register(req = fxp_realpath_send("/")); } else { sftp_register(req = fxp_realpath_send(fullname)); } rreq = sftp_find_request(pktin = sftp_recv()); assert(rreq == req); canonname = fxp_realpath_recv(pktin, rreq); if (!canonname) return fullname; /* even that failed; give up */ /* * We have a canonical name for all but the last path * component. Concatenate the last component and return. */ returnname = dupcat(canonname, canonname[strlen(canonname) - 1] == '/' ? "" : "/", fullname + i + 1, NULL); sfree(fullname); sfree(canonname); return returnname; }}/* * Return a pointer to the portion of str that comes after the last * slash (or backslash or colon, if `local' is TRUE). */static char *stripslashes(char *str, int local){ char *p; if (local) { p = strchr(str, ':'); if (p) str = p+1; } p = strrchr(str, '/'); if (p) str = p+1; if (local) { p = strrchr(str, '\\'); if (p) str = p+1; } return str;}/* ---------------------------------------------------------------------- * Actual sftp commands. */struct sftp_command { char **words; int nwords, wordssize; int (*obey) (struct sftp_command *); /* returns <0 to quit */};int sftp_cmd_null(struct sftp_command *cmd){ return 1; /* success */}int sftp_cmd_unknown(struct sftp_command *cmd){ printf("psftp: unknown command \"%s\"\n", cmd->words[0]); return 0; /* failure */}int sftp_cmd_quit(struct sftp_command *cmd){ return -1;}/* * 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; struct sftp_packet *pktin; struct sftp_request *req, *rreq; int i; if (back == NULL) { printf("psftp: not connected to a host; use \"open host.name\"\n"); return 0; } if (cmd->nwords < 2) dir = "."; else dir = cmd->words[1]; cdir = canonify(dir); if (!cdir) { printf("%s: %s\n", dir, fxp_error()); return 0; } printf("Listing directory %s\n", cdir); 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 == NULL) { printf("Unable to open %s: %s\n", dir, fxp_error()); } else { nnames = namesize = 0; ournames = NULL; while (1) { sftp_register(req = fxp_readdir_send(dirh)); rreq = sftp_find_request(pktin = sftp_recv()); assert(rreq == req); names = fxp_readdir_recv(pktin, rreq); if (names == NULL) { if (fxp_error_type() == SSH_FX_EOF) break; printf("Reading directory %s: %s\n", dir, fxp_error()); break; } 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++) ournames[nnames++] = fxp_dup_name(&names->names[i]); fxp_free_names(names); } sftp_register(req = fxp_close_send(dirh)); rreq = sftp_find_request(pktin = sftp_recv()); assert(rreq == req); fxp_close_recv(pktin, rreq); /* * 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); /* * And print them. */ for (i = 0; i < nnames; i++) { printf("%s\n", ournames[i]->longname); fxp_free_name(ournames[i]); } sfree(ournames); } sfree(cdir); return 1;}/* * Change directories. We do this by canonifying the new name, then * trying to OPENDIR it. Only if that succeeds do we set the new pwd. */int sftp_cmd_cd(struct sftp_command *cmd){ struct fxp_handle *dirh; struct sftp_packet *pktin; struct sftp_request *req, *rreq; char *dir; if (back == NULL) { printf("psftp: not connected to a host; use \"open host.name\"\n"); return 0; } if (cmd->nwords < 2) dir = dupstr(homedir); else dir = canonify(cmd->words[1]); if (!dir) { printf("%s: %s\n", dir, fxp_error()); return 0; } sftp_register(req = fxp_opendir_send(dir)); rreq = sftp_find_request(pktin = sftp_recv()); assert(rreq == req); dirh = fxp_opendir_recv(pktin, rreq); if (!dirh) { printf("Directory %s: %s\n", dir, fxp_error()); sfree(dir); return 0; } sftp_register(req = fxp_close_send(dirh)); rreq = sftp_find_request(pktin = sftp_recv()); assert(rreq == req); fxp_close_recv(pktin, rreq); sfree(pwd); pwd = dir; printf("Remote directory is now %s\n", pwd); return 1;}/* * Print current directory. Easy as pie. */int sftp_cmd_pwd(struct sftp_command *cmd){ if (back == NULL) { printf("psftp: not connected to a host; use \"open host.name\"\n"); return 0; } printf("Remote directory is %s\n", pwd); return 1;}/* * 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. */int sftp_general_get(struct sftp_command *cmd, int restart){ struct fxp_handle *fh; struct sftp_packet *pktin; struct sftp_request *req, *rreq; struct fxp_xfer *xfer; char *fname, *outfname; uint64 offset; FILE *fp; int ret; if (back == NULL) { printf("psftp: not connected to a host; use \"open host.name\"\n"); return 0; } if (cmd->nwords < 2) { printf("get: expects a filename\n"); return 0; } fname = canonify(cmd->words[1]); if (!fname) { printf("%s: %s\n", cmd->words[1], fxp_error()); return 0; } outfname = (cmd->nwords == 2 ? stripslashes(cmd->words[1], 0) : cmd->words[2]); sftp_register(req = fxp_open_send(fname, SSH_FXF_READ)); rreq = sftp_find_request(pktin = sftp_recv()); assert(rreq == req); fh = fxp_open_recv(pktin, rreq); if (!fh) { printf("%s: %s\n", fname, fxp_error()); sfree(fname); return 0; } if (restart) { fp = fopen(outfname, "rb+"); } else { fp = fopen(outfname, "wb"); } if (!fp) { printf("local: unable to open %s\n", outfname); sftp_register(req = fxp_close_send(fh)); rreq = sftp_find_request(pktin = sftp_recv()); assert(rreq == req); fxp_close_recv(pktin, rreq); sfree(fname); return 0; } if (restart) { long posn; fseek(fp, 0L, SEEK_END); posn = ftell(fp); printf("reget: restarting at file position %ld\n", posn); offset = uint64_make(0, posn); } else { offset = uint64_make(0, 0); } printf("remote:%s => local:%s\n", fname, outfname); /* * FIXME: we can use FXP_FSTAT here to get the file size, and * thus put up a progress bar. */ ret = 1; xfer = xfer_download_init(fh, offset); while (!xfer_done(xfer)) { void *vbuf; int ret, len; int wpos, wlen; xfer_download_queue(xfer); pktin = sftp_recv(); ret = xfer_download_gotpkt(xfer, pktin); if (ret < 0) { printf("error while reading: %s\n", fxp_error()); ret = 0; } while (xfer_download_data(xfer, &vbuf, &len)) { unsigned char *buf = (unsigned char *)vbuf; wpos = 0; while (wpos < len) { wlen = fwrite(buf + wpos, 1, len - wpos, fp); if (wlen <= 0) { printf("error while writing local file\n"); ret = 0; xfer_set_error(xfer); } wpos += wlen; } if (wpos < len) { /* we had an error */ ret = 0; xfer_set_error(xfer); } sfree(vbuf); } } xfer_cleanup(xfer); fclose(fp); sftp_register(req = fxp_close_send(fh)); rreq = sftp_find_request(pktin = sftp_recv()); assert(rreq == req); fxp_close_recv(pktin, rreq); sfree(fname); return ret;}int sftp_cmd_get(struct sftp_command *cmd){ return sftp_general_get(cmd, 0);}int sftp_cmd_reget(struct sftp_command *cmd){ return sftp_general_get(cmd, 1);}/* * 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. */int sftp_general_put(struct sftp_command *cmd, int restart){ struct fxp_handle *fh; struct fxp_xfer *xfer; char *fname, *origoutfname, *outfname; struct sftp_packet *pktin; struct sftp_request *req, *rreq; uint64 offset; FILE *fp; int ret, err, eof; if (back == NULL) { printf("psftp: not connected to a host; use \"open host.name\"\n"); return 0; } if (cmd->nwords < 2) { printf("put: expects a filename\n"); return 0; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -