📄 psftp.c
字号:
/*
* psftp.c: 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"
#include "FzSFtpIpc.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, int use_compression, int protocol);
static int do_sftp_init(void);
void do_sftp_cleanup();
/* ----------------------------------------------------------------------
* sftp client state.
*/
char *pwd, *homedir;
static 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 */
};
/*
* 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 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) {
FzSFtpIpc_Error("Not connected to a host");
return 0;
}
dir = ".";
cdir = canonify(dir);
if (!cdir) {
FzSFtpIpc_Error2("%s: %s", 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) {
FzSFtpIpc_Error2("Unable to open %s: %s", dir, fxp_error());
} else {
int num = 0;
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;
FzSFtpIpc_Error2("Reading directory %s: %s", 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++) {
if (strcmp(ournames[i]->filename, ".") && strcmp(ournames[i]->filename, "..") ) {
num++;
FzSFtpIpc_SendRequest(SFTP_DATAID_CTS_LIST, strlen(ournames[i]->longname)+1, ournames[i]->longname);
}
fxp_free_name(ournames[i]);
}
sfree(ournames);
FzSFtpIpc_Status1("Sucessfully received %d items", num);
FzSFtpIpc_SendRequest(SFTP_DATAID_CTS_LIST, 0, 0);
}
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(char *cd)
{
struct fxp_handle *dirh;
struct sftp_packet *pktin;
struct sftp_request *req, *rreq;
char *dir;
if (back == NULL) {
FzSFtpIpc_Error("Not connected to a host");
return 0;
}
dir = canonify(cd);
if (!dir) {
FzSFtpIpc_Error2("%s: %s", cd, 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) {
FzSFtpIpc_Error2("Directory %s: %s", 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;
FzSFtpIpc_Status1("Remote working directory is now %s", pwd)
FzSFtpIpc_SendRequest(SFTP_DATAID_CTS_CD, strlen(pwd)+1, pwd);
return 1;
}
/*
* Print current directory. Easy as pie.
*/
int sftp_cmd_pwd(struct sftp_command *cmd)
{
if (back == NULL) {
FzSFtpIpc_Error("Not connected to a host");
return 0;
}
FzSFtpIpc_Status1("Remote directory is %s", pwd);
FzSFtpIpc_SendRequest(SFTP_DATAID_CTS_PWD, strlen(pwd)+1, 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(char *file, char *outfname, int restart)
{
struct fxp_handle *fh;
struct sftp_packet *pktin;
struct sftp_request *req, *rreq;
struct fxp_xfer *xfer;
char *fname;
uint64 offset;
HANDLE fp;
int ret;
if (back == NULL) {
FzSFtpIpc_Error("Not connected to a host");
return 0;
}
fname = canonify(file);
if (!fname)
{
FzSFtpIpc_Error2("%s: %s", file, fxp_error());
return 0;
}
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) {
FzSFtpIpc_Error2("%s: %s", fname, fxp_error());
sfree(fname);
return 0;
}
if (restart)
fp = CreateFileA(outfname, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
else
fp = CreateFileA(outfname, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
if (fp==INVALID_HANDLE_VALUE)
{
FzSFtpIpc_Error1("Unable to open %s", 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 high=0;
LONG pos=SetFilePointer(fp, 0, &high, FILE_END);
char decbuf[30];
offset = uint64_make(high, pos);
uint64_decimal(offset, decbuf);
FzSFtpIpc_Status3("Downloading %s to %s, restarting at file position %s", fname, outfname, decbuf);
} else {
offset = uint64_make(0, 0);
FzSFtpIpc_Status2("Downloading %s to %s", 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) {
FzSFtpIpc_Error1("Error while reading: %s", fxp_error());
ret = 0;
}
while (xfer_download_data(xfer, &vbuf, &len)) {
unsigned char *buf = (unsigned char *)vbuf;
wpos = 0;
while (wpos < len) {
BOOL res = WriteFile(fp, buf + wpos, len - wpos, &wlen, NULL);
if (!res || 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);
}
else
FzSFtpIpc_SendRequest(SFTP_DATAID_CTS_TRANSFERSTATUS, 4, &len);
sfree(vbuf);
}
}
xfer_cleanup(xfer);
CloseHandle(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);
if (ret>0)
FzSFtpIpc_SendRequest(SFTP_DATAID_CTS_GET, 0, 0);
return ret;
}
/*
* 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(char *file, char *localfile, int restart)
{
struct fxp_handle *fh;
struct fxp_xfer *xfer;
char *outfname;
struct sftp_packet *pktin;
struct sftp_request *req, *rreq;
uint64 offset;
HANDLE fp;
int ret, err, eof;
if (back == NULL) {
FzSFtpIpc_Error("Not connected to a host");
return 0;
}
outfname = canonify(file);
if (!outfname) {
FzSFtpIpc_Error2("%s: %s", file, fxp_error());
return 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -