mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 09:27:59 +00:00
Add an SFTP server to the SSH server code.
Unlike the traditional Unix SSH server organisation, the SFTP server is built into the same process as all the rest of the code. sesschan.c spots a subsystem request for "sftp", and responds to it by instantiating an SftpServer object and swapping out its own vtable for one that talks to it. (I rather like the idea of an object swapping its own vtable for a different one in the middle of its lifetime! This is one of those tricks that would be absurdly hard to implement in a 'proper' OO language, but when you're doing vtables by hand in C, it's no more difficult than any other piece of ordinary pointer manipulation. As long as the methods in both vtables expect the same physical structure layout, it doesn't cause a problem.) The SftpServer object doesn't deal directly with SFTP packet formats; it implements the SFTP server logic in a more abstract way, by having a vtable method for each SFTP request type with an appropriate parameter list. It sends its replies by calling methods in another vtable called SftpReplyBuilder, which in the normal case will write an SFTP reply packet to send back to the client. So SftpServer can focus more or less completely on the details of a particular filesystem API - and hence, the implementation I've got lives in the unix source directory, and works directly with file descriptors and struct stat and the like. (One purpose of this abstraction layer is that I may well want to write a second dummy implementation, for test-suite purposes, with completely controllable behaviour, and now I have a handy place to plug it in in place of the live filesystem.) In between sesschan's parsing of the byte stream into SFTP packets and the SftpServer object, there's a layer in the new file sftpserver.c which does the actual packet decoding and encoding: each request packet is passed to that, which pulls the fields out of the request packet and calls the appropriate method of SftpServer. It also provides the default SftpReplyBuilder which makes the output packet. I've moved some code out of the previous SFTP client implementation - basic packet construction code, and in particular the BinarySink/ BinarySource marshalling fuinction for fxp_attrs - into sftpcommon.c, so that the two directions can share as much as possible.
This commit is contained in:
parent
1d323d5c80
commit
a081dd0a4c
6
Recipe
6
Recipe
@ -267,7 +267,7 @@ WINSSH = SSH winnoise wincapi winpgntc wingss winshare winnps winnpc
|
||||
UXSSH = SSH uxnoise uxagentc uxgss uxshare
|
||||
|
||||
# SFTP implementation (pscp, psftp).
|
||||
SFTP = sftp int64 logging cmdline
|
||||
SFTP = sftp sftpcommon int64 logging cmdline
|
||||
|
||||
# Miscellaneous objects appearing in all the utilities, or all the
|
||||
# network ones, or the Unix or Windows subsets of those in turn.
|
||||
@ -283,7 +283,7 @@ UXMISC = MISCNET UXMISCCOMMON uxproxy
|
||||
# SSH server.
|
||||
SSHSERVER = SSHCOMMON sshserver settings be_none logging ssh2kex-server
|
||||
+ ssh2userauth-server sshrsag sshprime ssh2connection-server
|
||||
+ sesschan int64 proxy cproxy ssh1login-server
|
||||
+ sesschan sftpcommon int64 sftpserver proxy cproxy ssh1login-server
|
||||
+ ssh1connection-server
|
||||
|
||||
# import.c and dependencies, for PuTTYgen-like utilities that have to
|
||||
@ -377,7 +377,7 @@ testbn : [UT] testbn sshbn MISC version CONF tree234 uxmisc uxnogtk
|
||||
testbn : [C] testbn sshbn MISC version CONF tree234 winmisc LIBS
|
||||
|
||||
uppity : [UT] uxserver SSHSERVER UXMISC uxsignal uxnoise uxgss uxnogtk
|
||||
+ uxpty ux_x11 uxagentsock
|
||||
+ uxpty uxsftpserver ux_x11 uxagentsock
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# On Windows, provide a means of removing local test binaries that we
|
||||
|
@ -160,7 +160,7 @@ AC_CHECK_LIB(X11, XOpenDisplay,
|
||||
[GTK_LIBS="-lX11 $GTK_LIBS"
|
||||
AC_DEFINE([HAVE_LIBX11],[],[Define if libX11.a is available])])
|
||||
|
||||
AC_CHECK_FUNCS([getaddrinfo posix_openpt ptsname setresuid strsignal updwtmpx])
|
||||
AC_CHECK_FUNCS([getaddrinfo posix_openpt ptsname setresuid strsignal updwtmpx fstatat dirfd])
|
||||
AC_CHECK_DECLS([CLOCK_MONOTONIC], [], [], [[#include <time.h>]])
|
||||
AC_SEARCH_LIBS([clock_gettime], [rt], [AC_DEFINE([HAVE_CLOCK_GETTIME],[],[Define if clock_gettime() is available])])
|
||||
|
||||
|
3
defs.h
3
defs.h
@ -61,6 +61,9 @@ typedef struct Frontend Frontend;
|
||||
|
||||
typedef struct Ssh Ssh;
|
||||
|
||||
typedef struct SftpServer SftpServer;
|
||||
typedef struct SftpServerVtable SftpServerVtable;
|
||||
|
||||
typedef struct Channel Channel;
|
||||
typedef struct SshChannel SshChannel;
|
||||
typedef struct mainchan mainchan;
|
||||
|
2
psftp.c
2
psftp.c
@ -420,7 +420,7 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart)
|
||||
if (restart) {
|
||||
file = open_existing_wfile(outfname, NULL);
|
||||
} else {
|
||||
file = open_new_file(outfname, GET_PERMISSIONS(attrs));
|
||||
file = open_new_file(outfname, GET_PERMISSIONS(attrs, -1));
|
||||
}
|
||||
|
||||
if (!file) {
|
||||
|
96
sesschan.c
96
sesschan.c
@ -11,12 +11,14 @@
|
||||
#include "ssh.h"
|
||||
#include "sshchan.h"
|
||||
#include "sshserver.h"
|
||||
#include "sftp.h"
|
||||
|
||||
typedef struct sesschan {
|
||||
SshChannel *c;
|
||||
|
||||
LogContext *parent_logctx, *child_logctx;
|
||||
Conf *conf;
|
||||
const SftpServerVtable *sftpserver_vt;
|
||||
|
||||
LogPolicy logpolicy;
|
||||
Seat seat;
|
||||
@ -39,6 +41,7 @@ typedef struct sesschan {
|
||||
Backend *backend;
|
||||
|
||||
bufchain subsys_input;
|
||||
SftpServer *sftpsrv;
|
||||
|
||||
Channel chan;
|
||||
} sesschan;
|
||||
@ -91,6 +94,35 @@ static const struct ChannelVtable sesschan_channelvt = {
|
||||
chan_no_request_response,
|
||||
};
|
||||
|
||||
static int sftp_chan_send(Channel *chan, int is_stderr, const void *, int);
|
||||
static void sftp_chan_send_eof(Channel *chan);
|
||||
static char *sftp_log_close_msg(Channel *chan);
|
||||
|
||||
static const struct ChannelVtable sftp_channelvt = {
|
||||
sesschan_free,
|
||||
chan_remotely_opened_confirmation,
|
||||
chan_remotely_opened_failure,
|
||||
sftp_chan_send,
|
||||
sftp_chan_send_eof,
|
||||
sesschan_set_input_wanted,
|
||||
sftp_log_close_msg,
|
||||
chan_default_want_close,
|
||||
chan_no_exit_status,
|
||||
chan_no_exit_signal,
|
||||
chan_no_exit_signal_numeric,
|
||||
chan_no_run_shell,
|
||||
chan_no_run_command,
|
||||
chan_no_run_subsystem,
|
||||
chan_no_enable_x11_forwarding,
|
||||
chan_no_enable_agent_forwarding,
|
||||
chan_no_allocate_pty,
|
||||
chan_no_set_env,
|
||||
chan_no_send_break,
|
||||
chan_no_send_signal,
|
||||
chan_no_change_window_size,
|
||||
chan_no_request_response,
|
||||
};
|
||||
|
||||
static void sesschan_eventlog(LogPolicy *lp, const char *event) {}
|
||||
static void sesschan_logging_error(LogPolicy *lp, const char *event) {}
|
||||
static int sesschan_askappend(
|
||||
@ -128,7 +160,8 @@ static const SeatVtable sesschan_seat_vt = {
|
||||
sesschan_get_window_pixel_size,
|
||||
};
|
||||
|
||||
Channel *sesschan_new(SshChannel *c, LogContext *logctx)
|
||||
Channel *sesschan_new(SshChannel *c, LogContext *logctx,
|
||||
const SftpServerVtable *sftpserver_vt)
|
||||
{
|
||||
sesschan *sess = snew(sesschan);
|
||||
memset(sess, 0, sizeof(sesschan));
|
||||
@ -150,6 +183,8 @@ Channel *sesschan_new(SshChannel *c, LogContext *logctx)
|
||||
sess->logpolicy.vt = &sesschan_logpolicy_vt;
|
||||
sess->child_logctx = log_init(&sess->logpolicy, sess->conf);
|
||||
|
||||
sess->sftpserver_vt = sftpserver_vt;
|
||||
|
||||
bufchain_init(&sess->subsys_input);
|
||||
|
||||
return &sess->chan;
|
||||
@ -165,6 +200,8 @@ static void sesschan_free(Channel *chan)
|
||||
if (sess->backend)
|
||||
backend_free(sess->backend);
|
||||
bufchain_clear(&sess->subsys_input);
|
||||
if (sess->sftpsrv)
|
||||
sftpsrv_free(sess->sftpsrv);
|
||||
for (i = 0; i < sess->n_x11_sockets; i++)
|
||||
sk_close(sess->x11_sockets[i]);
|
||||
if (sess->agentfwd_socket)
|
||||
@ -236,6 +273,15 @@ int sesschan_run_command(Channel *chan, ptrlen command)
|
||||
|
||||
int sesschan_run_subsystem(Channel *chan, ptrlen subsys)
|
||||
{
|
||||
sesschan *sess = container_of(chan, sesschan, chan);
|
||||
|
||||
if (ptrlen_eq_string(subsys, "sftp") && sess->sftpserver_vt) {
|
||||
sess->sftpsrv = sftpsrv_new(sess->sftpserver_vt);
|
||||
sess->chan.vt = &sftp_channelvt;
|
||||
logevent(sess->parent_logctx, "Starting built-in SFTP subsystem");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@ -557,3 +603,51 @@ static int sesschan_get_window_pixel_size(Seat *seat, int *width, int *height)
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------
|
||||
* Built-in SFTP subsystem.
|
||||
*/
|
||||
|
||||
static int sftp_chan_send(Channel *chan, int is_stderr,
|
||||
const void *data, int length)
|
||||
{
|
||||
sesschan *sess = container_of(chan, sesschan, chan);
|
||||
|
||||
bufchain_add(&sess->subsys_input, data, length);
|
||||
|
||||
while (bufchain_size(&sess->subsys_input) >= 4) {
|
||||
char lenbuf[4];
|
||||
unsigned pktlen;
|
||||
struct sftp_packet *pkt, *reply;
|
||||
|
||||
bufchain_fetch(&sess->subsys_input, lenbuf, 4);
|
||||
pktlen = GET_32BIT(lenbuf);
|
||||
|
||||
if (bufchain_size(&sess->subsys_input) - 4 < pktlen)
|
||||
break; /* wait for more data */
|
||||
|
||||
bufchain_consume(&sess->subsys_input, 4);
|
||||
pkt = sftp_recv_prepare(pktlen);
|
||||
bufchain_fetch_consume(&sess->subsys_input, pkt->data, pktlen);
|
||||
sftp_recv_finish(pkt);
|
||||
reply = sftp_handle_request(sess->sftpsrv, pkt);
|
||||
sftp_pkt_free(pkt);
|
||||
|
||||
sftp_send_prepare(reply);
|
||||
sshfwd_write(sess->c, reply->data, reply->length);
|
||||
sftp_pkt_free(reply);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sftp_chan_send_eof(Channel *chan)
|
||||
{
|
||||
sesschan *sess = container_of(chan, sesschan, chan);
|
||||
sshfwd_write_eof(sess->c);
|
||||
}
|
||||
|
||||
static char *sftp_log_close_msg(Channel *chan)
|
||||
{
|
||||
return dupstr("Session channel (SFTP) closed");
|
||||
}
|
||||
|
130
sftp.c
130
sftp.c
@ -13,138 +13,24 @@
|
||||
#include "tree234.h"
|
||||
#include "sftp.h"
|
||||
|
||||
struct sftp_packet {
|
||||
char *data;
|
||||
unsigned length, maxlen;
|
||||
unsigned savedpos;
|
||||
int type;
|
||||
BinarySink_IMPLEMENTATION;
|
||||
BinarySource_IMPLEMENTATION;
|
||||
};
|
||||
|
||||
static const char *fxp_error_message;
|
||||
static int fxp_errtype;
|
||||
|
||||
static void fxp_internal_error(const char *msg);
|
||||
|
||||
/* ----------------------------------------------------------------------
|
||||
* SFTP packet construction functions.
|
||||
*/
|
||||
static void sftp_pkt_BinarySink_write(
|
||||
BinarySink *bs, const void *data, size_t length)
|
||||
{
|
||||
struct sftp_packet *pkt = BinarySink_DOWNCAST(bs, struct sftp_packet);
|
||||
unsigned newlen;
|
||||
|
||||
assert(length <= 0xFFFFFFFFU - pkt->length);
|
||||
|
||||
newlen = pkt->length + length;
|
||||
if (pkt->maxlen < newlen) {
|
||||
pkt->maxlen = newlen * 5 / 4 + 256;
|
||||
pkt->data = sresize(pkt->data, pkt->maxlen, char);
|
||||
}
|
||||
|
||||
memcpy(pkt->data + pkt->length, data, length);
|
||||
pkt->length = newlen;
|
||||
}
|
||||
static struct sftp_packet *sftp_pkt_init(int pkt_type)
|
||||
{
|
||||
struct sftp_packet *pkt;
|
||||
pkt = snew(struct sftp_packet);
|
||||
pkt->data = NULL;
|
||||
pkt->savedpos = -1;
|
||||
pkt->length = 0;
|
||||
pkt->maxlen = 0;
|
||||
BinarySink_INIT(pkt, sftp_pkt_BinarySink_write);
|
||||
put_uint32(pkt, 0); /* length field will be filled in later */
|
||||
put_byte(pkt, pkt_type);
|
||||
return pkt;
|
||||
}
|
||||
|
||||
static void BinarySink_put_fxp_attrs(BinarySink *bs, struct fxp_attrs attrs)
|
||||
{
|
||||
put_uint32(bs, attrs.flags);
|
||||
if (attrs.flags & SSH_FILEXFER_ATTR_SIZE)
|
||||
put_uint64(bs, attrs.size);
|
||||
if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) {
|
||||
put_uint32(bs, attrs.uid);
|
||||
put_uint32(bs, attrs.gid);
|
||||
}
|
||||
if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
|
||||
put_uint32(bs, attrs.permissions);
|
||||
}
|
||||
if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) {
|
||||
put_uint32(bs, attrs.atime);
|
||||
put_uint32(bs, attrs.mtime);
|
||||
}
|
||||
if (attrs.flags & SSH_FILEXFER_ATTR_EXTENDED) {
|
||||
/*
|
||||
* We currently don't support sending any extended
|
||||
* attributes.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
static const struct fxp_attrs no_attrs = { 0 };
|
||||
|
||||
#define put_fxp_attrs(bs, attrs) \
|
||||
BinarySink_put_fxp_attrs(BinarySink_UPCAST(bs), attrs)
|
||||
|
||||
/* ----------------------------------------------------------------------
|
||||
* SFTP packet decode functions.
|
||||
* Client-specific parts of the send- and receive-packet system.
|
||||
*/
|
||||
|
||||
static int BinarySource_get_fxp_attrs(BinarySource *src,
|
||||
struct fxp_attrs *attrs)
|
||||
{
|
||||
attrs->flags = get_uint32(src);
|
||||
if (attrs->flags & SSH_FILEXFER_ATTR_SIZE)
|
||||
attrs->size = get_uint64(src);
|
||||
if (attrs->flags & SSH_FILEXFER_ATTR_UIDGID) {
|
||||
attrs->uid = get_uint32(src);
|
||||
attrs->gid = get_uint32(src);
|
||||
}
|
||||
if (attrs->flags & SSH_FILEXFER_ATTR_PERMISSIONS)
|
||||
attrs->permissions = get_uint32(src);
|
||||
if (attrs->flags & SSH_FILEXFER_ATTR_ACMODTIME) {
|
||||
attrs->atime = get_uint32(src);
|
||||
attrs->mtime = get_uint32(src);
|
||||
}
|
||||
if (attrs->flags & SSH_FILEXFER_ATTR_EXTENDED) {
|
||||
unsigned long count = get_uint32(src);
|
||||
while (count--) {
|
||||
/*
|
||||
* We should try to analyse these, if we ever find one
|
||||
* we recognise.
|
||||
*/
|
||||
get_string(src);
|
||||
get_string(src);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define get_fxp_attrs(bs, attrs) \
|
||||
BinarySource_get_fxp_attrs(BinarySource_UPCAST(bs), attrs)
|
||||
|
||||
static void sftp_pkt_free(struct sftp_packet *pkt)
|
||||
{
|
||||
if (pkt->data)
|
||||
sfree(pkt->data);
|
||||
sfree(pkt);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------
|
||||
* Send and receive packet functions.
|
||||
*/
|
||||
int sftp_send(struct sftp_packet *pkt)
|
||||
{
|
||||
int ret;
|
||||
PUT_32BIT(pkt->data, pkt->length - 4);
|
||||
sftp_send_prepare(pkt);
|
||||
ret = sftp_senddata(pkt->data, pkt->length);
|
||||
sftp_pkt_free(pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct sftp_packet *sftp_recv(void)
|
||||
{
|
||||
struct sftp_packet *pkt;
|
||||
@ -153,20 +39,14 @@ struct sftp_packet *sftp_recv(void)
|
||||
if (!sftp_recvdata(x, 4))
|
||||
return NULL;
|
||||
|
||||
pkt = snew(struct sftp_packet);
|
||||
pkt->savedpos = 0;
|
||||
pkt->length = pkt->maxlen = GET_32BIT(x);
|
||||
pkt->data = snewn(pkt->length, char);
|
||||
pkt = sftp_recv_prepare(GET_32BIT(x));
|
||||
|
||||
if (!sftp_recvdata(pkt->data, pkt->length)) {
|
||||
sftp_pkt_free(pkt);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BinarySource_INIT(pkt, pkt->data, pkt->length);
|
||||
pkt->type = get_byte(pkt);
|
||||
|
||||
if (get_err(pkt)) {
|
||||
if (!sftp_recv_finish(pkt)) {
|
||||
sftp_pkt_free(pkt);
|
||||
return NULL;
|
||||
}
|
||||
|
214
sftp.h
214
sftp.h
@ -86,6 +86,7 @@ struct fxp_attrs {
|
||||
unsigned long atime;
|
||||
unsigned long mtime;
|
||||
};
|
||||
extern const struct fxp_attrs no_attrs;
|
||||
|
||||
/*
|
||||
* Copy between the possibly-unused permissions field in an fxp_attrs
|
||||
@ -96,9 +97,9 @@ struct fxp_attrs {
|
||||
((attrs).flags |= SSH_FILEXFER_ATTR_PERMISSIONS, \
|
||||
(attrs).permissions = (perms)) : \
|
||||
((attrs).flags &= ~SSH_FILEXFER_ATTR_PERMISSIONS))
|
||||
#define GET_PERMISSIONS(attrs) \
|
||||
#define GET_PERMISSIONS(attrs, defaultperms) \
|
||||
((attrs).flags & SSH_FILEXFER_ATTR_PERMISSIONS ? \
|
||||
(attrs).permissions : -1)
|
||||
(attrs).permissions : defaultperms)
|
||||
|
||||
struct fxp_handle {
|
||||
char *hstring;
|
||||
@ -116,7 +117,47 @@ struct fxp_names {
|
||||
};
|
||||
|
||||
struct sftp_request;
|
||||
struct sftp_packet;
|
||||
|
||||
/*
|
||||
* Packet-manipulation functions.
|
||||
*/
|
||||
|
||||
struct sftp_packet {
|
||||
char *data;
|
||||
unsigned length, maxlen;
|
||||
unsigned savedpos;
|
||||
int type;
|
||||
BinarySink_IMPLEMENTATION;
|
||||
BinarySource_IMPLEMENTATION;
|
||||
};
|
||||
|
||||
/* When sending a packet, create it with sftp_pkt_init, then add
|
||||
* things to it by treating it as a BinarySink. When it's done, call
|
||||
* sftp_send_prepare, and then pkt->data and pkt->length describe its
|
||||
* wire format. */
|
||||
struct sftp_packet *sftp_pkt_init(int pkt_type);
|
||||
void sftp_send_prepare(struct sftp_packet *pkt);
|
||||
|
||||
/* When receiving a packet, create it with sftp_recv_prepare once you
|
||||
* decode its length from the first 4 bytes of wire data. Then write
|
||||
* that many bytes into pkt->data, and call sftp_recv_finish to set up
|
||||
* the type code and BinarySource. */
|
||||
struct sftp_packet *sftp_recv_prepare(unsigned length);
|
||||
int sftp_recv_finish(struct sftp_packet *pkt);
|
||||
|
||||
/* Either kind of packet can be freed afterwards with sftp_pkt_free. */
|
||||
void sftp_pkt_free(struct sftp_packet *pkt);
|
||||
|
||||
void BinarySink_put_fxp_attrs(BinarySink *bs, struct fxp_attrs attrs);
|
||||
int BinarySource_get_fxp_attrs(BinarySource *src, struct fxp_attrs *attrs);
|
||||
#define put_fxp_attrs(bs, attrs) \
|
||||
BinarySink_put_fxp_attrs(BinarySink_UPCAST(bs), attrs)
|
||||
#define get_fxp_attrs(bs, attrs) \
|
||||
BinarySource_get_fxp_attrs(BinarySource_UPCAST(bs), attrs)
|
||||
|
||||
/*
|
||||
* Error handling.
|
||||
*/
|
||||
|
||||
const char *fxp_error(void);
|
||||
int fxp_error_type(void);
|
||||
@ -269,3 +310,170 @@ int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin);
|
||||
int xfer_done(struct fxp_xfer *xfer);
|
||||
void xfer_set_error(struct fxp_xfer *xfer);
|
||||
void xfer_cleanup(struct fxp_xfer *xfer);
|
||||
|
||||
/*
|
||||
* Vtable for the platform-specific filesystem implementation that
|
||||
* answers requests in an SFTP server.
|
||||
*/
|
||||
typedef struct SftpReplyBuilder SftpReplyBuilder;
|
||||
struct SftpServer {
|
||||
const SftpServerVtable *vt;
|
||||
};
|
||||
struct SftpServerVtable {
|
||||
SftpServer *(*new)(const SftpServerVtable *vt);
|
||||
void (*free)(SftpServer *srv);
|
||||
|
||||
/*
|
||||
* Handle actual filesystem requests.
|
||||
*
|
||||
* Each of these functions replies by calling an appropiate
|
||||
* sftp_reply_foo() function on the given reply packet.
|
||||
*/
|
||||
|
||||
/* Should call fxp_reply_error or fxp_reply_simple_name */
|
||||
void (*realpath)(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen path);
|
||||
|
||||
/* Should call fxp_reply_error or fxp_reply_handle */
|
||||
void (*open)(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen path, unsigned flags, struct fxp_attrs attrs);
|
||||
|
||||
/* Should call fxp_reply_error or fxp_reply_handle */
|
||||
void (*opendir)(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen path);
|
||||
|
||||
/* Should call fxp_reply_error or fxp_reply_ok */
|
||||
void (*close)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle);
|
||||
|
||||
/* Should call fxp_reply_error or fxp_reply_ok */
|
||||
void (*mkdir)(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen path, struct fxp_attrs attrs);
|
||||
|
||||
/* Should call fxp_reply_error or fxp_reply_ok */
|
||||
void (*rmdir)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path);
|
||||
|
||||
/* Should call fxp_reply_error or fxp_reply_ok */
|
||||
void (*remove)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path);
|
||||
|
||||
/* Should call fxp_reply_error or fxp_reply_ok */
|
||||
void (*rename)(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen srcpath, ptrlen dstpath);
|
||||
|
||||
/* Should call fxp_reply_error or fxp_reply_attrs */
|
||||
void (*stat)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path,
|
||||
int follow_symlinks);
|
||||
|
||||
/* Should call fxp_reply_error or fxp_reply_attrs */
|
||||
void (*fstat)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle);
|
||||
|
||||
/* Should call fxp_reply_error or fxp_reply_ok */
|
||||
void (*setstat)(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen path, struct fxp_attrs attrs);
|
||||
|
||||
/* Should call fxp_reply_error or fxp_reply_ok */
|
||||
void (*fsetstat)(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen handle, struct fxp_attrs attrs);
|
||||
|
||||
/* Should call fxp_reply_error or fxp_reply_data */
|
||||
void (*read)(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen handle, uint64 offset, unsigned length);
|
||||
|
||||
/* Should call fxp_reply_error or fxp_reply_ok */
|
||||
void (*write)(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen handle, uint64 offset, ptrlen data);
|
||||
|
||||
/* Should call fxp_reply_error, or fxp_reply_name_count once and
|
||||
* then fxp_reply_full_name that many times */
|
||||
void (*readdir)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle,
|
||||
int max_entries, int omit_longname);
|
||||
};
|
||||
|
||||
#define sftpsrv_new(vt) \
|
||||
((vt)->new(vt))
|
||||
#define sftpsrv_free(srv) \
|
||||
((srv)->vt->free(srv))
|
||||
#define sftpsrv_realpath(srv, reply, path) \
|
||||
((srv)->vt->realpath(srv, reply, path))
|
||||
#define sftpsrv_open(srv, reply, path, flags, attrs) \
|
||||
((srv)->vt->open(srv, reply, path, flags, attrs))
|
||||
#define sftpsrv_opendir(srv, reply, path) \
|
||||
((srv)->vt->opendir(srv, reply, path))
|
||||
#define sftpsrv_close(srv, reply, handle) \
|
||||
((srv)->vt->close(srv, reply, handle))
|
||||
#define sftpsrv_mkdir(srv, reply, path, attrs) \
|
||||
((srv)->vt->mkdir(srv, reply, path, attrs))
|
||||
#define sftpsrv_rmdir(srv, reply, path) \
|
||||
((srv)->vt->rmdir(srv, reply, path))
|
||||
#define sftpsrv_remove(srv, reply, path) \
|
||||
((srv)->vt->remove(srv, reply, path))
|
||||
#define sftpsrv_rename(srv, reply, srcpath, dstpath) \
|
||||
((srv)->vt->rename(srv, reply, srcpath, dstpath))
|
||||
#define sftpsrv_stat(srv, reply, path, follow) \
|
||||
((srv)->vt->stat(srv, reply, path, follow))
|
||||
#define sftpsrv_fstat(srv, reply, handle) \
|
||||
((srv)->vt->fstat(srv, reply, handle))
|
||||
#define sftpsrv_setstat(srv, reply, path, attrs) \
|
||||
((srv)->vt->setstat(srv, reply, path, attrs))
|
||||
#define sftpsrv_fsetstat(srv, reply, handle, attrs) \
|
||||
((srv)->vt->fsetstat(srv, reply, handle, attrs))
|
||||
#define sftpsrv_read(srv, reply, handle, offset, length) \
|
||||
((srv)->vt->read(srv, reply, handle, offset, length))
|
||||
#define sftpsrv_write(srv, reply, handle, offset, data) \
|
||||
((srv)->vt->write(srv, reply, handle, offset, data))
|
||||
#define sftpsrv_readdir(srv, reply, handle, max, nolongname) \
|
||||
((srv)->vt->readdir(srv, reply, handle, max, nolongname))
|
||||
|
||||
typedef struct SftpReplyBuilderVtable SftpReplyBuilderVtable;
|
||||
struct SftpReplyBuilder {
|
||||
const SftpReplyBuilderVtable *vt;
|
||||
};
|
||||
struct SftpReplyBuilderVtable {
|
||||
void (*reply_ok)(SftpReplyBuilder *reply);
|
||||
void (*reply_error)(SftpReplyBuilder *reply, unsigned code,
|
||||
const char *msg);
|
||||
void (*reply_simple_name)(SftpReplyBuilder *reply, ptrlen name);
|
||||
void (*reply_name_count)(SftpReplyBuilder *reply, unsigned count);
|
||||
void (*reply_full_name)(SftpReplyBuilder *reply, ptrlen name,
|
||||
ptrlen longname, struct fxp_attrs attrs);
|
||||
void (*reply_handle)(SftpReplyBuilder *reply, ptrlen handle);
|
||||
void (*reply_data)(SftpReplyBuilder *reply, ptrlen data);
|
||||
void (*reply_attrs)(SftpReplyBuilder *reply, struct fxp_attrs attrs);
|
||||
};
|
||||
|
||||
#define fxp_reply_ok(reply) \
|
||||
((reply)->vt->reply_ok(reply))
|
||||
#define fxp_reply_error(reply, code, msg) \
|
||||
((reply)->vt->reply_error(reply, code, msg))
|
||||
#define fxp_reply_simple_name(reply, name) \
|
||||
((reply)->vt->reply_simple_name(reply, name))
|
||||
#define fxp_reply_name_count(reply, count) \
|
||||
((reply)->vt->reply_name_count(reply, count))
|
||||
#define fxp_reply_full_name(reply, name, longname, attrs) \
|
||||
((reply)->vt->reply_full_name(reply, name, longname, attrs))
|
||||
#define fxp_reply_handle(reply, handle) \
|
||||
((reply)->vt->reply_handle(reply, handle))
|
||||
#define fxp_reply_data(reply, data) \
|
||||
((reply)->vt->reply_data(reply, data))
|
||||
#define fxp_reply_attrs(reply, attrs) \
|
||||
((reply)->vt->reply_attrs(reply, attrs))
|
||||
|
||||
/*
|
||||
* The usual implementation of an SftpReplyBuilder, containing a
|
||||
* 'struct sftp_packet' which is assumed to be already initialised
|
||||
* before one of the above request methods is called.
|
||||
*/
|
||||
extern const struct SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt;
|
||||
typedef struct DefaultSftpReplyBuilder DefaultSftpReplyBuilder;
|
||||
struct DefaultSftpReplyBuilder {
|
||||
SftpReplyBuilder rb;
|
||||
struct sftp_packet *pkt;
|
||||
};
|
||||
|
||||
/*
|
||||
* The top-level function that handles an SFTP request, given an
|
||||
* implementation of the above SftpServer abstraction to do the actual
|
||||
* filesystem work. It handles all the marshalling and unmarshalling
|
||||
* of packets, and the copying of request ids into the responses.
|
||||
*/
|
||||
struct sftp_packet *sftp_handle_request(
|
||||
SftpServer *srv, struct sftp_packet *request);
|
||||
|
139
sftpcommon.c
Normal file
139
sftpcommon.c
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* sftpcommon.c: SFTP code shared between client and server.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "misc.h"
|
||||
#include "sftp.h"
|
||||
|
||||
static void sftp_pkt_BinarySink_write(
|
||||
BinarySink *bs, const void *data, size_t length)
|
||||
{
|
||||
struct sftp_packet *pkt = BinarySink_DOWNCAST(bs, struct sftp_packet);
|
||||
unsigned newlen;
|
||||
|
||||
assert(length <= 0xFFFFFFFFU - pkt->length);
|
||||
|
||||
newlen = pkt->length + length;
|
||||
if (pkt->maxlen < newlen) {
|
||||
pkt->maxlen = newlen * 5 / 4 + 256;
|
||||
pkt->data = sresize(pkt->data, pkt->maxlen, char);
|
||||
}
|
||||
|
||||
memcpy(pkt->data + pkt->length, data, length);
|
||||
pkt->length = newlen;
|
||||
}
|
||||
|
||||
struct sftp_packet *sftp_pkt_init(int type)
|
||||
{
|
||||
struct sftp_packet *pkt;
|
||||
pkt = snew(struct sftp_packet);
|
||||
pkt->data = NULL;
|
||||
pkt->savedpos = -1;
|
||||
pkt->length = 0;
|
||||
pkt->maxlen = 0;
|
||||
pkt->type = type;
|
||||
BinarySink_INIT(pkt, sftp_pkt_BinarySink_write);
|
||||
put_uint32(pkt, 0); /* length field will be filled in later */
|
||||
put_byte(pkt, 0); /* so will the type field */
|
||||
return pkt;
|
||||
}
|
||||
|
||||
void BinarySink_put_fxp_attrs(BinarySink *bs, struct fxp_attrs attrs)
|
||||
{
|
||||
put_uint32(bs, attrs.flags);
|
||||
if (attrs.flags & SSH_FILEXFER_ATTR_SIZE)
|
||||
put_uint64(bs, attrs.size);
|
||||
if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) {
|
||||
put_uint32(bs, attrs.uid);
|
||||
put_uint32(bs, attrs.gid);
|
||||
}
|
||||
if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
|
||||
put_uint32(bs, attrs.permissions);
|
||||
}
|
||||
if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) {
|
||||
put_uint32(bs, attrs.atime);
|
||||
put_uint32(bs, attrs.mtime);
|
||||
}
|
||||
if (attrs.flags & SSH_FILEXFER_ATTR_EXTENDED) {
|
||||
/*
|
||||
* We currently don't support sending any extended
|
||||
* attributes.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
const struct fxp_attrs no_attrs = { 0 };
|
||||
|
||||
#define put_fxp_attrs(bs, attrs) \
|
||||
BinarySink_put_fxp_attrs(BinarySink_UPCAST(bs), attrs)
|
||||
|
||||
int BinarySource_get_fxp_attrs(BinarySource *src, struct fxp_attrs *attrs)
|
||||
{
|
||||
attrs->flags = get_uint32(src);
|
||||
if (attrs->flags & SSH_FILEXFER_ATTR_SIZE)
|
||||
attrs->size = get_uint64(src);
|
||||
if (attrs->flags & SSH_FILEXFER_ATTR_UIDGID) {
|
||||
attrs->uid = get_uint32(src);
|
||||
attrs->gid = get_uint32(src);
|
||||
}
|
||||
if (attrs->flags & SSH_FILEXFER_ATTR_PERMISSIONS)
|
||||
attrs->permissions = get_uint32(src);
|
||||
if (attrs->flags & SSH_FILEXFER_ATTR_ACMODTIME) {
|
||||
attrs->atime = get_uint32(src);
|
||||
attrs->mtime = get_uint32(src);
|
||||
}
|
||||
if (attrs->flags & SSH_FILEXFER_ATTR_EXTENDED) {
|
||||
unsigned long count = get_uint32(src);
|
||||
while (count--) {
|
||||
/*
|
||||
* We should try to analyse these, if we ever find one
|
||||
* we recognise.
|
||||
*/
|
||||
get_string(src);
|
||||
get_string(src);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void sftp_pkt_free(struct sftp_packet *pkt)
|
||||
{
|
||||
if (pkt->data)
|
||||
sfree(pkt->data);
|
||||
sfree(pkt);
|
||||
}
|
||||
|
||||
void sftp_send_prepare(struct sftp_packet *pkt)
|
||||
{
|
||||
PUT_32BIT(pkt->data, pkt->length - 4);
|
||||
if (pkt->length >= 5) {
|
||||
/* Rewrite the type code, in case the caller changed its mind
|
||||
* about pkt->type since calling sftp_pkt_init */
|
||||
pkt->data[4] = pkt->type;
|
||||
}
|
||||
}
|
||||
|
||||
struct sftp_packet *sftp_recv_prepare(unsigned length)
|
||||
{
|
||||
struct sftp_packet *pkt;
|
||||
|
||||
pkt = snew(struct sftp_packet);
|
||||
pkt->savedpos = 0;
|
||||
pkt->length = pkt->maxlen = length;
|
||||
pkt->data = snewn(pkt->length, char);
|
||||
|
||||
return pkt;
|
||||
}
|
||||
|
||||
int sftp_recv_finish(struct sftp_packet *pkt)
|
||||
{
|
||||
BinarySource_INIT(pkt, pkt->data, pkt->length);
|
||||
pkt->type = get_byte(pkt);
|
||||
return !get_err(pkt);
|
||||
}
|
278
sftpserver.c
Normal file
278
sftpserver.c
Normal file
@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Implement the centralised parts of the server side of SFTP.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "putty.h"
|
||||
#include "ssh.h"
|
||||
#include "sftp.h"
|
||||
|
||||
struct sftp_packet *sftp_handle_request(
|
||||
SftpServer *srv, struct sftp_packet *req)
|
||||
{
|
||||
struct sftp_packet *reply;
|
||||
unsigned id;
|
||||
ptrlen path, dstpath, handle, data;
|
||||
uint64 offset;
|
||||
unsigned length;
|
||||
struct fxp_attrs attrs;
|
||||
DefaultSftpReplyBuilder dsrb;
|
||||
SftpReplyBuilder *rb;
|
||||
|
||||
if (req->type == SSH_FXP_INIT) {
|
||||
/*
|
||||
* Special case which doesn't have a request id at the start.
|
||||
*/
|
||||
reply = sftp_pkt_init(SSH_FXP_VERSION);
|
||||
/*
|
||||
* Since we support only the lowest protocol version, we don't
|
||||
* need to take the min of this and the client's version, or
|
||||
* even to bother reading the client version number out of the
|
||||
* input packet.
|
||||
*/
|
||||
put_uint32(reply, SFTP_PROTO_VERSION);
|
||||
return reply;
|
||||
}
|
||||
|
||||
/*
|
||||
* Centralise the request id handling. We'll overwrite the type
|
||||
* code of the output packet later.
|
||||
*/
|
||||
id = get_uint32(req);
|
||||
reply = sftp_pkt_init(0);
|
||||
put_uint32(reply, id);
|
||||
|
||||
dsrb.rb.vt = &DefaultSftpReplyBuilder_vt;
|
||||
dsrb.pkt = reply;
|
||||
rb = &dsrb.rb;
|
||||
|
||||
switch (req->type) {
|
||||
case SSH_FXP_REALPATH:
|
||||
path = get_string(req);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_realpath(srv, rb, path);
|
||||
break;
|
||||
|
||||
case SSH_FXP_OPEN:
|
||||
path = get_string(req);
|
||||
flags = get_uint32(req);
|
||||
get_fxp_attrs(req, &attrs);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
if ((flags & (SSH_FXF_READ|SSH_FXF_WRITE)) == 0) {
|
||||
fxp_reply_error(rb, SSH_FX_BAD_MESSAGE,
|
||||
"open without READ or WRITE flag");
|
||||
} else if ((flags & (SSH_FXF_CREAT|SSH_FXF_TRUNC)) == SSH_FXF_TRUNC) {
|
||||
fxp_reply_error(rb, SSH_FX_BAD_MESSAGE,
|
||||
"open with TRUNC but not CREAT");
|
||||
} else if ((flags & (SSH_FXF_CREAT|SSH_FXF_EXCL)) == SSH_FXF_EXCL) {
|
||||
fxp_reply_error(rb, SSH_FX_BAD_MESSAGE,
|
||||
"open with EXCL but not CREAT");
|
||||
} else {
|
||||
sftpsrv_open(srv, rb, path, flags, attrs);
|
||||
}
|
||||
break;
|
||||
|
||||
case SSH_FXP_OPENDIR:
|
||||
path = get_string(req);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_opendir(srv, rb, path);
|
||||
break;
|
||||
|
||||
case SSH_FXP_CLOSE:
|
||||
handle = get_string(req);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_close(srv, rb, handle);
|
||||
break;
|
||||
|
||||
case SSH_FXP_MKDIR:
|
||||
path = get_string(req);
|
||||
get_fxp_attrs(req, &attrs);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_mkdir(srv, rb, path, attrs);
|
||||
break;
|
||||
|
||||
case SSH_FXP_RMDIR:
|
||||
path = get_string(req);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_rmdir(srv, rb, path);
|
||||
break;
|
||||
|
||||
case SSH_FXP_REMOVE:
|
||||
path = get_string(req);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_remove(srv, rb, path);
|
||||
break;
|
||||
|
||||
case SSH_FXP_RENAME:
|
||||
path = get_string(req);
|
||||
dstpath = get_string(req);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_rename(srv, rb, path, dstpath);
|
||||
break;
|
||||
|
||||
case SSH_FXP_STAT:
|
||||
path = get_string(req);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_stat(srv, rb, path, TRUE);
|
||||
break;
|
||||
|
||||
case SSH_FXP_LSTAT:
|
||||
path = get_string(req);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_stat(srv, rb, path, FALSE);
|
||||
break;
|
||||
|
||||
case SSH_FXP_FSTAT:
|
||||
handle = get_string(req);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_fstat(srv, rb, handle);
|
||||
break;
|
||||
|
||||
case SSH_FXP_SETSTAT:
|
||||
path = get_string(req);
|
||||
get_fxp_attrs(req, &attrs);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_setstat(srv, rb, path, attrs);
|
||||
break;
|
||||
|
||||
case SSH_FXP_FSETSTAT:
|
||||
handle = get_string(req);
|
||||
get_fxp_attrs(req, &attrs);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_fsetstat(srv, rb, handle, attrs);
|
||||
break;
|
||||
|
||||
case SSH_FXP_READ:
|
||||
handle = get_string(req);
|
||||
offset = get_uint64(req);
|
||||
length = get_uint32(req);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_read(srv, rb, handle, offset, length);
|
||||
break;
|
||||
|
||||
case SSH_FXP_READDIR:
|
||||
handle = get_string(req);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_readdir(srv, rb, handle, INT_MAX, FALSE);
|
||||
break;
|
||||
|
||||
case SSH_FXP_WRITE:
|
||||
handle = get_string(req);
|
||||
offset = get_uint64(req);
|
||||
data = get_string(req);
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
sftpsrv_write(srv, rb, handle, offset, data);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (get_err(req))
|
||||
goto decode_error;
|
||||
fxp_reply_error(rb, SSH_FX_OP_UNSUPPORTED,
|
||||
"Unrecognised request type");
|
||||
break;
|
||||
|
||||
decode_error:
|
||||
fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, "Unable to decode request");
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
static void default_reply_ok(SftpReplyBuilder *reply)
|
||||
{
|
||||
DefaultSftpReplyBuilder *d =
|
||||
container_of(reply, DefaultSftpReplyBuilder, rb);
|
||||
d->pkt->type = SSH_FXP_STATUS;
|
||||
put_uint32(d->pkt, SSH_FX_OK);
|
||||
put_stringz(d->pkt, "");
|
||||
}
|
||||
|
||||
static void default_reply_error(
|
||||
SftpReplyBuilder *reply, unsigned code, const char *msg)
|
||||
{
|
||||
DefaultSftpReplyBuilder *d =
|
||||
container_of(reply, DefaultSftpReplyBuilder, rb);
|
||||
d->pkt->type = SSH_FXP_STATUS;
|
||||
put_uint32(d->pkt, code);
|
||||
put_stringz(d->pkt, msg);
|
||||
}
|
||||
|
||||
static void default_reply_name_count(SftpReplyBuilder *reply, unsigned count)
|
||||
{
|
||||
DefaultSftpReplyBuilder *d =
|
||||
container_of(reply, DefaultSftpReplyBuilder, rb);
|
||||
d->pkt->type = SSH_FXP_NAME;
|
||||
put_uint32(d->pkt, count);
|
||||
}
|
||||
|
||||
static void default_reply_full_name(SftpReplyBuilder *reply, ptrlen name,
|
||||
ptrlen longname, struct fxp_attrs attrs)
|
||||
{
|
||||
DefaultSftpReplyBuilder *d =
|
||||
container_of(reply, DefaultSftpReplyBuilder, rb);
|
||||
d->pkt->type = SSH_FXP_NAME;
|
||||
put_stringpl(d->pkt, name);
|
||||
put_stringpl(d->pkt, longname);
|
||||
put_fxp_attrs(d->pkt, attrs);
|
||||
}
|
||||
|
||||
static void default_reply_simple_name(SftpReplyBuilder *reply, ptrlen name)
|
||||
{
|
||||
fxp_reply_name_count(reply, 1);
|
||||
fxp_reply_full_name(reply, name, PTRLEN_LITERAL(""), no_attrs);
|
||||
}
|
||||
|
||||
static void default_reply_handle(SftpReplyBuilder *reply, ptrlen handle)
|
||||
{
|
||||
DefaultSftpReplyBuilder *d =
|
||||
container_of(reply, DefaultSftpReplyBuilder, rb);
|
||||
d->pkt->type = SSH_FXP_HANDLE;
|
||||
put_stringpl(d->pkt, handle);
|
||||
}
|
||||
|
||||
static void default_reply_data(SftpReplyBuilder *reply, ptrlen data)
|
||||
{
|
||||
DefaultSftpReplyBuilder *d =
|
||||
container_of(reply, DefaultSftpReplyBuilder, rb);
|
||||
d->pkt->type = SSH_FXP_DATA;
|
||||
put_stringpl(d->pkt, data);
|
||||
}
|
||||
|
||||
static void default_reply_attrs(
|
||||
SftpReplyBuilder *reply, struct fxp_attrs attrs)
|
||||
{
|
||||
DefaultSftpReplyBuilder *d =
|
||||
container_of(reply, DefaultSftpReplyBuilder, rb);
|
||||
d->pkt->type = SSH_FXP_ATTRS;
|
||||
put_fxp_attrs(d->pkt, attrs);
|
||||
}
|
||||
|
||||
const struct SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt = {
|
||||
default_reply_ok,
|
||||
default_reply_error,
|
||||
default_reply_simple_name,
|
||||
default_reply_name_count,
|
||||
default_reply_full_name,
|
||||
default_reply_handle,
|
||||
default_reply_data,
|
||||
default_reply_attrs,
|
||||
};
|
@ -50,7 +50,7 @@ void ssh1_connection_direction_specific_setup(
|
||||
if (!s->mainchan_chan) {
|
||||
s->mainchan_sc.vt = &ssh1sesschan_vtable;
|
||||
s->mainchan_sc.cl = &s->cl;
|
||||
s->mainchan_chan = sesschan_new(&s->mainchan_sc, s->ppl.logctx);
|
||||
s->mainchan_chan = sesschan_new(&s->mainchan_sc, s->ppl.logctx, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,13 +13,22 @@
|
||||
#include "ssh2connection.h"
|
||||
#include "sshserver.h"
|
||||
|
||||
void ssh2connection_server_configure(
|
||||
PacketProtocolLayer *ppl, const SftpServerVtable *sftpserver_vt)
|
||||
{
|
||||
struct ssh2_connection_state *s =
|
||||
container_of(ppl, struct ssh2_connection_state, ppl);
|
||||
s->sftpserver_vt = sftpserver_vt;
|
||||
}
|
||||
|
||||
static ChanopenResult chan_open_session(
|
||||
struct ssh2_connection_state *s, SshChannel *sc)
|
||||
{
|
||||
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
|
||||
|
||||
ppl_logevent(("Opened session channel"));
|
||||
CHANOPEN_RETURN_SUCCESS(sesschan_new(sc, s->ppl.logctx));
|
||||
CHANOPEN_RETURN_SUCCESS(sesschan_new(sc, s->ppl.logctx,
|
||||
s->sftpserver_vt));
|
||||
}
|
||||
|
||||
static ChanopenResult chan_open_direct_tcpip(
|
||||
|
@ -37,6 +37,8 @@ struct ssh2_connection_state {
|
||||
PortFwdManager *portfwdmgr;
|
||||
int portfwdmgr_configured;
|
||||
|
||||
const SftpServerVtable *sftpserver_vt;
|
||||
|
||||
/*
|
||||
* These store the list of global requests that we're waiting for
|
||||
* replies to. (REQUEST_FAILURE doesn't come with any indication
|
||||
|
@ -40,6 +40,7 @@ struct server {
|
||||
int nhostkeys;
|
||||
struct RSAKey *hostkey1;
|
||||
AuthPolicy *authpolicy;
|
||||
const SftpServerVtable *sftpserver_vt;
|
||||
|
||||
Seat seat;
|
||||
Ssh ssh;
|
||||
@ -210,7 +211,8 @@ static const PlugVtable ssh_server_plugvt = {
|
||||
|
||||
Plug *ssh_server_plug(
|
||||
Conf *conf, ssh_key *const *hostkeys, int nhostkeys,
|
||||
struct RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy)
|
||||
struct RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy,
|
||||
const SftpServerVtable *sftpserver_vt)
|
||||
{
|
||||
server *srv = snew(server);
|
||||
|
||||
@ -224,6 +226,7 @@ Plug *ssh_server_plug(
|
||||
srv->hostkeys = hostkeys;
|
||||
srv->hostkey1 = hostkey1;
|
||||
srv->authpolicy = authpolicy;
|
||||
srv->sftpserver_vt = sftpserver_vt;
|
||||
|
||||
srv->seat.vt = &server_seat_vt;
|
||||
|
||||
@ -412,6 +415,7 @@ static void server_got_ssh_version(struct ssh_version_receiver *rcv,
|
||||
connection_layer = ssh2_connection_new(
|
||||
&srv->ssh, NULL, FALSE, srv->conf,
|
||||
ssh_verstring_get_local(old_bpp), &srv->cl);
|
||||
ssh2connection_server_configure(connection_layer, srv->sftpserver_vt);
|
||||
server_connect_ppl(srv, connection_layer);
|
||||
|
||||
if (conf_get_int(srv->conf, CONF_ssh_no_userauth)) {
|
||||
|
@ -2,7 +2,8 @@ typedef struct AuthPolicy AuthPolicy;
|
||||
|
||||
Plug *ssh_server_plug(
|
||||
Conf *conf, ssh_key *const *hostkeys, int nhostkeys,
|
||||
struct RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy);
|
||||
struct RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy,
|
||||
const SftpServerVtable *sftpserver_vt);
|
||||
void ssh_server_start(Plug *plug, Socket *socket);
|
||||
|
||||
void server_instance_terminated(void);
|
||||
@ -37,11 +38,15 @@ PacketProtocolLayer *ssh2_userauth_server_new(
|
||||
void ssh2_userauth_server_set_transport_layer(
|
||||
PacketProtocolLayer *userauth, PacketProtocolLayer *transport);
|
||||
|
||||
void ssh2connection_server_configure(
|
||||
PacketProtocolLayer *ppl, const SftpServerVtable *sftpserver_vt);
|
||||
|
||||
PacketProtocolLayer *ssh1_login_server_new(
|
||||
PacketProtocolLayer *successor_layer, struct RSAKey *hostkey,
|
||||
AuthPolicy *authpolicy);
|
||||
|
||||
Channel *sesschan_new(SshChannel *c, LogContext *logctx);
|
||||
Channel *sesschan_new(SshChannel *c, LogContext *logctx,
|
||||
const SftpServerVtable *sftpserver_vt);
|
||||
|
||||
Backend *pty_backend_create(
|
||||
Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd,
|
||||
|
@ -251,6 +251,8 @@ static int longoptarg(const char *arg, const char *expected,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
extern const SftpServerVtable unix_live_sftpserver_vt;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int *fdlist;
|
||||
@ -427,7 +429,8 @@ int main(int argc, char **argv)
|
||||
|
||||
{
|
||||
Plug *plug = ssh_server_plug(
|
||||
conf, hostkeys, nhostkeys, hostkey1, &ap, server_logpolicy);
|
||||
conf, hostkeys, nhostkeys, hostkey1, &ap, server_logpolicy,
|
||||
&unix_live_sftpserver_vt);
|
||||
ssh_server_start(plug, make_fd_socket(0, 1, -1, plug));
|
||||
}
|
||||
|
||||
|
708
unix/uxsftpserver.c
Normal file
708
unix/uxsftpserver.c
Normal file
@ -0,0 +1,708 @@
|
||||
/*
|
||||
* Implement the SftpServer abstraction, in the 'live' form (i.e.
|
||||
* really operating on the Unix filesystem).
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
#include <dirent.h>
|
||||
#include <utime.h>
|
||||
|
||||
#include "putty.h"
|
||||
#include "ssh.h"
|
||||
#include "sftp.h"
|
||||
#include "tree234.h"
|
||||
|
||||
typedef struct UnixSftpServer UnixSftpServer;
|
||||
|
||||
struct UnixSftpServer {
|
||||
unsigned *fdseqs;
|
||||
int *fdsopen;
|
||||
int fdsize;
|
||||
|
||||
tree234 *dirhandles;
|
||||
int last_dirhandle_index;
|
||||
|
||||
char handlekey[8];
|
||||
|
||||
SftpServer srv;
|
||||
};
|
||||
|
||||
struct uss_dirhandle {
|
||||
int index;
|
||||
DIR *dp;
|
||||
};
|
||||
|
||||
#define USS_DIRHANDLE_SEQ (0xFFFFFFFFU)
|
||||
|
||||
static int uss_dirhandle_cmp(void *av, void *bv)
|
||||
{
|
||||
struct uss_dirhandle *a = (struct uss_dirhandle *)av;
|
||||
struct uss_dirhandle *b = (struct uss_dirhandle *)bv;
|
||||
if (a->index < b->index)
|
||||
return -1;
|
||||
if (a->index > b->index)
|
||||
return +1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SftpServer *uss_new(const SftpServerVtable *vt)
|
||||
{
|
||||
int i;
|
||||
UnixSftpServer *uss = snew(UnixSftpServer);
|
||||
|
||||
memset(uss, 0, sizeof(UnixSftpServer));
|
||||
|
||||
uss->dirhandles = newtree234(uss_dirhandle_cmp);
|
||||
uss->srv.vt = vt;
|
||||
|
||||
for (i = 0; i < lenof(uss->handlekey); i++)
|
||||
uss->handlekey[i] = random_byte();
|
||||
|
||||
return &uss->srv;
|
||||
}
|
||||
|
||||
static void uss_free(SftpServer *srv)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
struct uss_dirhandle *udh;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < uss->fdsize; i++)
|
||||
if (uss->fdsopen[i])
|
||||
close(i);
|
||||
sfree(uss->fdseqs);
|
||||
|
||||
while ((udh = delpos234(uss->dirhandles, 0)) != NULL) {
|
||||
closedir(udh->dp);
|
||||
sfree(udh);
|
||||
}
|
||||
|
||||
sfree(uss);
|
||||
}
|
||||
|
||||
static void uss_return_handle_raw(
|
||||
UnixSftpServer *uss, SftpReplyBuilder *reply, int index, unsigned seq)
|
||||
{
|
||||
unsigned char handlebuf[8];
|
||||
PUT_32BIT(handlebuf, index);
|
||||
PUT_32BIT(handlebuf + 4, seq);
|
||||
des_encrypt_xdmauth(uss->handlekey, handlebuf, 8);
|
||||
fxp_reply_handle(reply, make_ptrlen(handlebuf, 8));
|
||||
}
|
||||
|
||||
static int uss_decode_handle(
|
||||
UnixSftpServer *uss, ptrlen handle, int *index, unsigned *seq)
|
||||
{
|
||||
unsigned char handlebuf[8];
|
||||
|
||||
if (handle.len != 8)
|
||||
return FALSE;
|
||||
memcpy(handlebuf, handle.ptr, 8);
|
||||
des_decrypt_xdmauth(uss->handlekey, handlebuf, 8);
|
||||
*index = toint(GET_32BIT(handlebuf));
|
||||
*seq = GET_32BIT(handlebuf + 4);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void uss_return_new_handle(
|
||||
UnixSftpServer *uss, SftpReplyBuilder *reply, int fd)
|
||||
{
|
||||
assert(fd >= 0);
|
||||
if (fd >= uss->fdsize) {
|
||||
int old_size = uss->fdsize;
|
||||
uss->fdsize = fd * 5 / 4 + 32;
|
||||
uss->fdseqs = sresize(uss->fdseqs, uss->fdsize, unsigned);
|
||||
uss->fdsopen = sresize(uss->fdsopen, uss->fdsize, int);
|
||||
while (old_size < uss->fdsize) {
|
||||
uss->fdseqs[old_size] = 0;
|
||||
uss->fdsopen[old_size] = FALSE;
|
||||
old_size++;
|
||||
}
|
||||
}
|
||||
assert(!uss->fdsopen[fd]);
|
||||
uss->fdsopen[fd] = TRUE;
|
||||
if (++uss->fdseqs[fd] == USS_DIRHANDLE_SEQ)
|
||||
uss->fdseqs[fd] = 0;
|
||||
uss_return_handle_raw(uss, reply, fd, uss->fdseqs[fd]);
|
||||
}
|
||||
|
||||
static int uss_try_lookup_fd(UnixSftpServer *uss, ptrlen handle)
|
||||
{
|
||||
int fd;
|
||||
unsigned seq;
|
||||
if (!uss_decode_handle(uss, handle, &fd, &seq) ||
|
||||
fd < 0 || fd >= uss->fdsize ||
|
||||
!uss->fdsopen[fd] || uss->fdseqs[fd] != seq)
|
||||
return -1;
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int uss_lookup_fd(UnixSftpServer *uss, SftpReplyBuilder *reply,
|
||||
ptrlen handle)
|
||||
{
|
||||
int fd = uss_try_lookup_fd(uss, handle);
|
||||
if (fd < 0)
|
||||
fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle");
|
||||
return fd;
|
||||
}
|
||||
|
||||
static void uss_return_new_dirhandle(
|
||||
UnixSftpServer *uss, SftpReplyBuilder *reply, DIR *dp)
|
||||
{
|
||||
struct uss_dirhandle *udh = snew(struct uss_dirhandle);
|
||||
udh->index = uss->last_dirhandle_index++;
|
||||
udh->dp = dp;
|
||||
struct uss_dirhandle *added = add234(uss->dirhandles, udh);
|
||||
assert(added == udh);
|
||||
uss_return_handle_raw(uss, reply, udh->index, USS_DIRHANDLE_SEQ);
|
||||
}
|
||||
|
||||
static struct uss_dirhandle *uss_try_lookup_dirhandle(
|
||||
UnixSftpServer *uss, ptrlen handle)
|
||||
{
|
||||
struct uss_dirhandle key, *udh;
|
||||
unsigned seq;
|
||||
|
||||
if (!uss_decode_handle(uss, handle, &key.index, &seq) ||
|
||||
seq != USS_DIRHANDLE_SEQ ||
|
||||
(udh = find234(uss->dirhandles, &key, NULL)) == NULL)
|
||||
return NULL;
|
||||
|
||||
return udh;
|
||||
}
|
||||
|
||||
static struct uss_dirhandle *uss_lookup_dirhandle(
|
||||
UnixSftpServer *uss, SftpReplyBuilder *reply, ptrlen handle)
|
||||
{
|
||||
struct uss_dirhandle *udh = uss_try_lookup_dirhandle(uss, handle);
|
||||
if (!udh)
|
||||
fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle");
|
||||
return udh;
|
||||
}
|
||||
|
||||
static void uss_error(UnixSftpServer *uss, SftpReplyBuilder *reply)
|
||||
{
|
||||
unsigned code = SSH_FX_FAILURE;
|
||||
switch (errno) {
|
||||
case ENOENT:
|
||||
code = SSH_FX_NO_SUCH_FILE;
|
||||
break;
|
||||
case EPERM:
|
||||
code = SSH_FX_PERMISSION_DENIED;
|
||||
break;
|
||||
}
|
||||
fxp_reply_error(reply, code, strerror(errno));
|
||||
}
|
||||
|
||||
static void uss_realpath(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen path)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
|
||||
char *inpath = mkstr(path);
|
||||
char *outpath = realpath(inpath, NULL);
|
||||
free(inpath);
|
||||
|
||||
if (!outpath) {
|
||||
uss_error(uss, reply);
|
||||
} else {
|
||||
fxp_reply_simple_name(reply, ptrlen_from_asciz(outpath));
|
||||
free(outpath);
|
||||
}
|
||||
}
|
||||
|
||||
static void uss_open(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen path, unsigned flags, struct fxp_attrs attrs)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
|
||||
int openflags = 0;
|
||||
if (!((SSH_FXF_READ | SSH_FXF_WRITE) &~ flags))
|
||||
openflags |= O_RDWR;
|
||||
else if (flags & SSH_FXF_WRITE)
|
||||
openflags |= O_WRONLY;
|
||||
else if (flags & SSH_FXF_READ)
|
||||
openflags |= O_RDONLY;
|
||||
if (flags & SSH_FXF_APPEND)
|
||||
openflags |= O_APPEND;
|
||||
if (flags & SSH_FXF_CREAT)
|
||||
openflags |= O_CREAT;
|
||||
if (flags & SSH_FXF_TRUNC)
|
||||
openflags |= O_TRUNC;
|
||||
if (flags & SSH_FXF_EXCL)
|
||||
openflags |= O_EXCL;
|
||||
|
||||
char *pathstr = mkstr(path);
|
||||
int fd = open(pathstr, openflags, GET_PERMISSIONS(attrs, 0777));
|
||||
free(pathstr);
|
||||
|
||||
if (fd < 0) {
|
||||
uss_error(uss, reply);
|
||||
} else {
|
||||
uss_return_new_handle(uss, reply, fd);
|
||||
}
|
||||
}
|
||||
|
||||
static void uss_opendir(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen path)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
|
||||
char *pathstr = mkstr(path);
|
||||
DIR *dp = opendir(pathstr);
|
||||
free(pathstr);
|
||||
|
||||
if (!dp) {
|
||||
uss_error(uss, reply);
|
||||
} else {
|
||||
uss_return_new_dirhandle(uss, reply, dp);
|
||||
}
|
||||
}
|
||||
|
||||
static void uss_close(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen handle)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
int fd;
|
||||
struct uss_dirhandle *udh;
|
||||
|
||||
if ((udh = uss_try_lookup_dirhandle(uss, handle)) != NULL) {
|
||||
closedir(udh->dp);
|
||||
del234(uss->dirhandles, udh);
|
||||
sfree(udh);
|
||||
fxp_reply_ok(reply);
|
||||
} else if ((fd = uss_lookup_fd(uss, reply, handle)) >= 0) {
|
||||
close(fd);
|
||||
assert(0 <= fd && fd <= uss->fdsize);
|
||||
uss->fdsopen[fd] = FALSE;
|
||||
fxp_reply_ok(reply);
|
||||
}
|
||||
/* if both failed, uss_lookup_fd will have filled in an error response */
|
||||
}
|
||||
|
||||
static void uss_mkdir(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen path, struct fxp_attrs attrs)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
|
||||
char *pathstr = mkstr(path);
|
||||
int status = mkdir(pathstr, GET_PERMISSIONS(attrs, 0777));
|
||||
free(pathstr);
|
||||
|
||||
if (status < 0) {
|
||||
uss_error(uss, reply);
|
||||
} else {
|
||||
fxp_reply_ok(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static void uss_rmdir(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
|
||||
char *pathstr = mkstr(path);
|
||||
int status = rmdir(pathstr);
|
||||
free(pathstr);
|
||||
|
||||
if (status < 0) {
|
||||
uss_error(uss, reply);
|
||||
} else {
|
||||
fxp_reply_ok(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static void uss_remove(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen path)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
|
||||
char *pathstr = mkstr(path);
|
||||
int status = unlink(pathstr);
|
||||
free(pathstr);
|
||||
|
||||
if (status < 0) {
|
||||
uss_error(uss, reply);
|
||||
} else {
|
||||
fxp_reply_ok(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static void uss_rename(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen srcpath, ptrlen dstpath)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
|
||||
char *srcstr = mkstr(srcpath), *dststr = mkstr(dstpath);
|
||||
int status = rename(srcstr, dststr);
|
||||
free(srcstr);
|
||||
free(dststr);
|
||||
|
||||
if (status < 0) {
|
||||
uss_error(uss, reply);
|
||||
} else {
|
||||
fxp_reply_ok(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static uint64 uint64_from_off_t(off_t off)
|
||||
{
|
||||
return uint64_make((off >> 16) >> 16, (off & 0xFFFFFFFFU));
|
||||
}
|
||||
|
||||
static struct fxp_attrs uss_translate_struct_stat(const struct stat *st)
|
||||
{
|
||||
struct fxp_attrs attrs;
|
||||
|
||||
attrs.flags = (SSH_FILEXFER_ATTR_SIZE |
|
||||
SSH_FILEXFER_ATTR_PERMISSIONS |
|
||||
SSH_FILEXFER_ATTR_UIDGID |
|
||||
SSH_FILEXFER_ATTR_ACMODTIME);
|
||||
|
||||
attrs.size = uint64_from_off_t(st->st_size);
|
||||
attrs.permissions = st->st_mode;
|
||||
attrs.uid = st->st_uid;
|
||||
attrs.gid = st->st_gid;
|
||||
attrs.atime = st->st_atime;
|
||||
attrs.mtime = st->st_mtime;
|
||||
|
||||
return attrs;
|
||||
}
|
||||
|
||||
static void uss_reply_struct_stat(SftpReplyBuilder *reply,
|
||||
const struct stat *st)
|
||||
{
|
||||
fxp_reply_attrs(reply, uss_translate_struct_stat(st));
|
||||
}
|
||||
|
||||
static void uss_stat(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen path, int follow_symlinks)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
struct stat st;
|
||||
|
||||
char *pathstr = mkstr(path);
|
||||
int status = (follow_symlinks ? stat : lstat) (pathstr, &st);
|
||||
free(pathstr);
|
||||
|
||||
if (status < 0) {
|
||||
uss_error(uss, reply);
|
||||
} else {
|
||||
uss_reply_struct_stat(reply, &st);
|
||||
}
|
||||
}
|
||||
|
||||
static void uss_fstat(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen handle)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
struct stat st;
|
||||
int fd;
|
||||
|
||||
if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
|
||||
return;
|
||||
int status = fstat(fd, &st);
|
||||
|
||||
if (status < 0) {
|
||||
uss_error(uss, reply);
|
||||
} else {
|
||||
uss_reply_struct_stat(reply, &st);
|
||||
}
|
||||
}
|
||||
|
||||
static off_t uint64_to_off_t(uint64 u)
|
||||
{
|
||||
return ((((off_t)u.hi) << 16) << 16) | (off_t)u.lo;
|
||||
}
|
||||
|
||||
/*
|
||||
* The guts of setstat and fsetstat, macroised so that they can call
|
||||
* fchown(fd,...) or chown(path,...) depending on parameters.
|
||||
*/
|
||||
#define SETSTAT_GUTS(api_prefix, api_arg, attrs, success) do \
|
||||
{ \
|
||||
if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) \
|
||||
if (api_prefix(truncate)( \
|
||||
api_arg, uint64_to_off_t(attrs.size)) < 0) \
|
||||
success = FALSE; \
|
||||
if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) \
|
||||
if (api_prefix(chown)(api_arg, attrs.uid, attrs.gid) < 0) \
|
||||
success = FALSE; \
|
||||
if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) \
|
||||
if (api_prefix(chmod)(api_arg, attrs.permissions) < 0) \
|
||||
success = FALSE; \
|
||||
if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { \
|
||||
struct timeval tv[2]; \
|
||||
tv[0].tv_sec = attrs.atime; \
|
||||
tv[1].tv_sec = attrs.mtime; \
|
||||
tv[0].tv_usec = tv[1].tv_usec = 0; \
|
||||
if (api_prefix(utimes)(api_arg, tv) < 0) \
|
||||
success = FALSE; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define PATH_PREFIX(func) func
|
||||
#define FD_PREFIX(func) f ## func
|
||||
|
||||
static void uss_setstat(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen path, struct fxp_attrs attrs)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
|
||||
char *pathstr = mkstr(path);
|
||||
int success = TRUE;
|
||||
SETSTAT_GUTS(PATH_PREFIX, pathstr, attrs, success);
|
||||
free(pathstr);
|
||||
|
||||
if (!success) {
|
||||
uss_error(uss, reply);
|
||||
} else {
|
||||
fxp_reply_ok(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static void uss_fsetstat(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen handle, struct fxp_attrs attrs)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
int fd;
|
||||
|
||||
if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
|
||||
return;
|
||||
|
||||
int success = TRUE;
|
||||
SETSTAT_GUTS(FD_PREFIX, fd, attrs, success);
|
||||
|
||||
if (!success) {
|
||||
uss_error(uss, reply);
|
||||
} else {
|
||||
fxp_reply_ok(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static void uss_read(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen handle, uint64 offset, unsigned length)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
int fd;
|
||||
char *buf;
|
||||
|
||||
if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
|
||||
return;
|
||||
|
||||
if ((buf = malloc(length)) == NULL) {
|
||||
/* A rare case in which I bother to check malloc failure,
|
||||
* because in this case we can localise the problem easily by
|
||||
* turning it into a failure response from this one sftp
|
||||
* request */
|
||||
fxp_reply_error(reply, SSH_FX_FAILURE,
|
||||
"Out of memory for read buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
char *p = buf;
|
||||
|
||||
int status = lseek(fd, uint64_to_off_t(offset), SEEK_SET);
|
||||
if (status >= 0 || errno == ESPIPE) {
|
||||
int seekable = (status >= 0);
|
||||
while (length > 0) {
|
||||
status = read(fd, p, length);
|
||||
if (status <= 0)
|
||||
break;
|
||||
|
||||
unsigned bytes_read = status;
|
||||
assert(bytes_read <= length);
|
||||
length -= bytes_read;
|
||||
p += bytes_read;
|
||||
|
||||
if (!seekable) {
|
||||
/*
|
||||
* If the seek failed because the file is fundamentally
|
||||
* not a seekable kind of thing, abandon this loop after
|
||||
* one attempt, i.e. we just read whatever we could get
|
||||
* and we don't mind returning a short buffer.
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status < 0) {
|
||||
uss_error(uss, reply);
|
||||
} else if (p == buf) {
|
||||
fxp_reply_error(reply, SSH_FX_EOF, "End of file");
|
||||
} else {
|
||||
fxp_reply_data(reply, make_ptrlen(buf, p - buf));
|
||||
}
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void uss_write(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen handle, uint64 offset, ptrlen data)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
int fd;
|
||||
|
||||
if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
|
||||
return;
|
||||
|
||||
const char *p = data.ptr;
|
||||
unsigned length = data.len;
|
||||
|
||||
int status = lseek(fd, uint64_to_off_t(offset), SEEK_SET);
|
||||
if (status >= 0 || errno == ESPIPE) {
|
||||
|
||||
while (length > 0) {
|
||||
status = write(fd, p, length);
|
||||
assert(status != 0);
|
||||
if (status < 0)
|
||||
break;
|
||||
|
||||
unsigned bytes_written = status;
|
||||
assert(bytes_written <= length);
|
||||
length -= bytes_written;
|
||||
p += bytes_written;
|
||||
}
|
||||
}
|
||||
|
||||
if (status < 0) {
|
||||
uss_error(uss, reply);
|
||||
} else {
|
||||
fxp_reply_ok(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static void uss_readdir(SftpServer *srv, SftpReplyBuilder *reply,
|
||||
ptrlen handle, int max_entries, int omit_longname)
|
||||
{
|
||||
UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
|
||||
struct dirent *de;
|
||||
struct uss_dirhandle *udh;
|
||||
|
||||
if ((udh = uss_lookup_dirhandle(uss, reply, handle)) == NULL)
|
||||
return;
|
||||
|
||||
errno = 0;
|
||||
de = readdir(udh->dp);
|
||||
if (!de) {
|
||||
if (errno == 0) {
|
||||
fxp_reply_error(reply, SSH_FX_EOF, "End of directory");
|
||||
} else {
|
||||
uss_error(uss, reply);
|
||||
}
|
||||
} else {
|
||||
ptrlen longname = PTRLEN_LITERAL("");
|
||||
char *longnamebuf = NULL;
|
||||
struct fxp_attrs attrs = no_attrs;
|
||||
|
||||
#if defined HAVE_FSTATAT && defined HAVE_DIRFD
|
||||
struct stat st;
|
||||
if (!fstatat(dirfd(udh->dp), de->d_name, &st, AT_SYMLINK_NOFOLLOW)) {
|
||||
char perms[11], sizebuf[32], *uidbuf = NULL, *gidbuf = NULL;
|
||||
struct passwd *pwd;
|
||||
struct group *grp;
|
||||
const char *user, *group;
|
||||
struct tm tm;
|
||||
|
||||
attrs = uss_translate_struct_stat(&st);
|
||||
|
||||
if (!omit_longname) {
|
||||
|
||||
strcpy(perms, "----------");
|
||||
switch (st.st_mode & S_IFMT) {
|
||||
case S_IFBLK: perms[0] = 'b'; break;
|
||||
case S_IFCHR: perms[0] = 'c'; break;
|
||||
case S_IFDIR: perms[0] = 'd'; break;
|
||||
case S_IFIFO: perms[0] = 'p'; break;
|
||||
case S_IFLNK: perms[0] = 'l'; break;
|
||||
case S_IFSOCK: perms[0] = 's'; break;
|
||||
}
|
||||
if (st.st_mode & S_IRUSR)
|
||||
perms[1] = 'r';
|
||||
if (st.st_mode & S_IWUSR)
|
||||
perms[2] = 'w';
|
||||
if (st.st_mode & S_IXUSR)
|
||||
perms[3] = (st.st_mode & S_ISUID ? 's' : 'x');
|
||||
else
|
||||
perms[3] = (st.st_mode & S_ISUID ? 'S' : '-');
|
||||
if (st.st_mode & S_IRGRP)
|
||||
perms[4] = 'r';
|
||||
if (st.st_mode & S_IWGRP)
|
||||
perms[5] = 'w';
|
||||
if (st.st_mode & S_IXGRP)
|
||||
perms[6] = (st.st_mode & S_ISGID ? 's' : 'x');
|
||||
else
|
||||
perms[6] = (st.st_mode & S_ISGID ? 'S' : '-');
|
||||
if (st.st_mode & S_IROTH)
|
||||
perms[7] = 'r';
|
||||
if (st.st_mode & S_IWOTH)
|
||||
perms[8] = 'w';
|
||||
if (st.st_mode & S_IXOTH)
|
||||
perms[9] = 'x';
|
||||
|
||||
if ((pwd = getpwuid(st.st_uid)) != NULL)
|
||||
user = pwd->pw_name;
|
||||
else
|
||||
user = uidbuf = dupprintf("%u", (unsigned)st.st_uid);
|
||||
if ((grp = getgrgid(st.st_gid)) != NULL)
|
||||
group = grp->gr_name;
|
||||
else
|
||||
group = gidbuf = dupprintf("%u", (unsigned)st.st_gid);
|
||||
|
||||
uint64_decimal(uint64_from_off_t(st.st_size), sizebuf);
|
||||
|
||||
tm = *localtime(&st.st_mtime);
|
||||
|
||||
longnamebuf = dupprintf(
|
||||
"%s %3u %-8s %-8s %8s %.3s %2d %02d:%02d %s",
|
||||
perms, (unsigned)st.st_nlink, user, group, sizebuf,
|
||||
("JanFebMarAprMayJunJulAugSepOctNovDec" + 3*tm.tm_mon),
|
||||
tm.tm_mday, tm.tm_hour, tm.tm_min, de->d_name);
|
||||
longname = ptrlen_from_asciz(longnamebuf);
|
||||
|
||||
sfree(uidbuf);
|
||||
sfree(gidbuf);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* FIXME: be able to return more than one, in which case we
|
||||
* must also check max_entries */
|
||||
fxp_reply_name_count(reply, 1);
|
||||
fxp_reply_full_name(reply, ptrlen_from_asciz(de->d_name),
|
||||
longname, attrs);
|
||||
|
||||
sfree(longnamebuf);
|
||||
}
|
||||
}
|
||||
|
||||
const struct SftpServerVtable unix_live_sftpserver_vt = {
|
||||
uss_new,
|
||||
uss_free,
|
||||
uss_realpath,
|
||||
uss_open,
|
||||
uss_opendir,
|
||||
uss_close,
|
||||
uss_mkdir,
|
||||
uss_rmdir,
|
||||
uss_remove,
|
||||
uss_rename,
|
||||
uss_stat,
|
||||
uss_fstat,
|
||||
uss_setstat,
|
||||
uss_fsetstat,
|
||||
uss_read,
|
||||
uss_write,
|
||||
uss_readdir,
|
||||
};
|
Loading…
Reference in New Issue
Block a user