diff --git a/Recipe b/Recipe index 72b899b6..309087d6 100644 --- a/Recipe +++ b/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 diff --git a/configure.ac b/configure.ac index 2c6484ca..a09d6417 100644 --- a/configure.ac +++ b/configure.ac @@ -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 ]]) AC_SEARCH_LIBS([clock_gettime], [rt], [AC_DEFINE([HAVE_CLOCK_GETTIME],[],[Define if clock_gettime() is available])]) diff --git a/defs.h b/defs.h index ed934a13..bf257117 100644 --- a/defs.h +++ b/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; diff --git a/psftp.c b/psftp.c index bee62b7d..8ccbb503 100644 --- a/psftp.c +++ b/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) { diff --git a/sesschan.c b/sesschan.c index 577c5feb..46116466 100644 --- a/sesschan.c +++ b/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"); +} diff --git a/sftp.c b/sftp.c index 9aa4a7db..98b8a6e8 100644 --- a/sftp.c +++ b/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; } diff --git a/sftp.h b/sftp.h index 65c8a44d..636c42d6 100644 --- a/sftp.h +++ b/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); diff --git a/sftpcommon.c b/sftpcommon.c new file mode 100644 index 00000000..5becf3ce --- /dev/null +++ b/sftpcommon.c @@ -0,0 +1,139 @@ +/* + * sftpcommon.c: SFTP code shared between client and server. + */ + +#include +#include +#include +#include +#include + +#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); +} diff --git a/sftpserver.c b/sftpserver.c new file mode 100644 index 00000000..4324137e --- /dev/null +++ b/sftpserver.c @@ -0,0 +1,278 @@ +/* + * Implement the centralised parts of the server side of SFTP. + */ + +#include +#include +#include + +#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, +}; diff --git a/ssh1connection-server.c b/ssh1connection-server.c index 01ea6f8d..330b9058 100644 --- a/ssh1connection-server.c +++ b/ssh1connection-server.c @@ -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); } } diff --git a/ssh2connection-server.c b/ssh2connection-server.c index da4b60cf..7691ffb1 100644 --- a/ssh2connection-server.c +++ b/ssh2connection-server.c @@ -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( diff --git a/ssh2connection.h b/ssh2connection.h index a441127e..058efb57 100644 --- a/ssh2connection.h +++ b/ssh2connection.h @@ -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 diff --git a/sshserver.c b/sshserver.c index eaa22ca1..9c3963e1 100644 --- a/sshserver.c +++ b/sshserver.c @@ -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)) { diff --git a/sshserver.h b/sshserver.h index e4d76df8..fa0ee87e 100644 --- a/sshserver.h +++ b/sshserver.h @@ -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, diff --git a/unix/uxserver.c b/unix/uxserver.c index 4c1ca982..b5e08404 100644 --- a/unix/uxserver.c +++ b/unix/uxserver.c @@ -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)); } diff --git a/unix/uxsftpserver.c b/unix/uxsftpserver.c new file mode 100644 index 00000000..ddc84665 --- /dev/null +++ b/unix/uxsftpserver.c @@ -0,0 +1,708 @@ +/* + * Implement the SftpServer abstraction, in the 'live' form (i.e. + * really operating on the Unix filesystem). + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, +};