2016-07-07 02:59:55 +08:00
|
|
|
package sftp
|
|
|
|
|
|
|
|
import (
|
2017-03-15 09:02:17 +08:00
|
|
|
"encoding"
|
2016-07-07 02:59:55 +08:00
|
|
|
"io"
|
2017-12-22 03:58:29 +08:00
|
|
|
"path"
|
2016-07-15 12:11:34 +08:00
|
|
|
"path/filepath"
|
2016-07-27 02:32:37 +08:00
|
|
|
"strconv"
|
2016-07-07 02:59:55 +08:00
|
|
|
"sync"
|
2016-07-09 03:38:35 +08:00
|
|
|
"syscall"
|
2016-07-30 02:22:07 +08:00
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2016-07-07 02:59:55 +08:00
|
|
|
)
|
|
|
|
|
2016-07-09 03:38:35 +08:00
|
|
|
var maxTxPacket uint32 = 1 << 15
|
2016-07-07 02:59:55 +08:00
|
|
|
|
2016-07-09 03:38:35 +08:00
|
|
|
type handleHandler func(string) string
|
2016-07-07 02:59:55 +08:00
|
|
|
|
2016-07-26 02:42:18 +08:00
|
|
|
// Handlers contains the 4 SFTP server request handlers.
|
2016-07-09 03:38:35 +08:00
|
|
|
type Handlers struct {
|
2016-07-09 05:13:03 +08:00
|
|
|
FileGet FileReader
|
|
|
|
FilePut FileWriter
|
|
|
|
FileCmd FileCmder
|
2017-07-28 09:22:11 +08:00
|
|
|
FileList FileLister
|
2016-07-09 03:38:35 +08:00
|
|
|
}
|
|
|
|
|
2016-07-26 02:42:18 +08:00
|
|
|
// RequestServer abstracts the sftp protocol with an http request-like protocol
|
2016-07-07 02:59:55 +08:00
|
|
|
type RequestServer struct {
|
2017-07-04 08:38:09 +08:00
|
|
|
*serverConn
|
2016-07-12 11:19:49 +08:00
|
|
|
Handlers Handlers
|
2017-08-21 06:23:55 +08:00
|
|
|
pktMgr *packetManager
|
2017-12-24 08:34:22 +08:00
|
|
|
openRequests map[string]*Request
|
2016-07-09 03:38:35 +08:00
|
|
|
openRequestLock sync.RWMutex
|
2016-07-27 02:32:37 +08:00
|
|
|
handleCount int
|
2016-07-07 02:59:55 +08:00
|
|
|
}
|
|
|
|
|
2016-07-26 02:42:18 +08:00
|
|
|
// NewRequestServer creates/allocates/returns new RequestServer.
|
|
|
|
// Normally there there will be one server per user-session.
|
|
|
|
func NewRequestServer(rwc io.ReadWriteCloser, h Handlers) *RequestServer {
|
2017-07-04 08:38:09 +08:00
|
|
|
svrConn := &serverConn{
|
2017-03-15 09:02:17 +08:00
|
|
|
conn: conn{
|
|
|
|
Reader: rwc,
|
|
|
|
WriteCloser: rwc,
|
2016-07-07 02:59:55 +08:00
|
|
|
},
|
2017-03-15 09:02:17 +08:00
|
|
|
}
|
|
|
|
return &RequestServer{
|
|
|
|
serverConn: svrConn,
|
2016-07-19 03:58:23 +08:00
|
|
|
Handlers: h,
|
2017-07-04 08:38:09 +08:00
|
|
|
pktMgr: newPktMgr(svrConn),
|
2017-12-24 08:34:22 +08:00
|
|
|
openRequests: make(map[string]*Request),
|
2016-07-07 02:59:55 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-24 08:34:22 +08:00
|
|
|
// New Open packet/Request
|
2017-08-21 08:03:15 +08:00
|
|
|
func (rs *RequestServer) nextRequest(r *Request) string {
|
2016-07-09 03:38:35 +08:00
|
|
|
rs.openRequestLock.Lock()
|
|
|
|
defer rs.openRequestLock.Unlock()
|
2016-07-27 02:32:37 +08:00
|
|
|
rs.handleCount++
|
|
|
|
handle := strconv.Itoa(rs.handleCount)
|
2017-12-24 08:34:22 +08:00
|
|
|
rs.openRequests[handle] = r
|
2016-07-27 02:32:37 +08:00
|
|
|
return handle
|
2016-07-09 03:38:35 +08:00
|
|
|
}
|
|
|
|
|
2017-12-24 08:34:22 +08:00
|
|
|
// Returns Request from openRequests, bool is false if it is missing
|
|
|
|
// If the method is different, save/return a new Request w/ that Method.
|
|
|
|
//
|
|
|
|
// The Requests in openRequests work essentially as open file descriptors that
|
|
|
|
// you can do different things with. What you are doing with it are denoted by
|
|
|
|
// the first packet of that type (read/write/etc). We create a new Request when
|
|
|
|
// it changes to set the request.Method attribute in a thread safe way.
|
|
|
|
func (rs *RequestServer) getRequest(handle, method string) (*Request, bool) {
|
2016-08-02 11:22:06 +08:00
|
|
|
rs.openRequestLock.RLock()
|
2016-07-09 03:38:35 +08:00
|
|
|
r, ok := rs.openRequests[handle]
|
2017-12-24 08:34:22 +08:00
|
|
|
rs.openRequestLock.RUnlock()
|
|
|
|
if !ok || r.Method == method {
|
|
|
|
return r, ok
|
|
|
|
}
|
|
|
|
// if we make it here we need to replace the request
|
|
|
|
rs.openRequestLock.Lock()
|
|
|
|
defer rs.openRequestLock.Unlock()
|
|
|
|
r, ok = rs.openRequests[handle]
|
|
|
|
if !ok || r.Method == method { // re-check needed b/c lock race
|
|
|
|
return r, ok
|
|
|
|
}
|
|
|
|
r = &Request{Method: method, Filepath: r.Filepath, state: r.state}
|
|
|
|
rs.openRequests[handle] = r
|
|
|
|
return r, ok
|
2016-07-09 03:38:35 +08:00
|
|
|
}
|
|
|
|
|
2017-12-13 02:05:18 +08:00
|
|
|
func (rs *RequestServer) closeRequest(handle string) error {
|
2016-07-09 03:38:35 +08:00
|
|
|
rs.openRequestLock.Lock()
|
|
|
|
defer rs.openRequestLock.Unlock()
|
2016-08-20 02:52:43 +08:00
|
|
|
if r, ok := rs.openRequests[handle]; ok {
|
2016-07-09 03:38:35 +08:00
|
|
|
delete(rs.openRequests, handle)
|
2017-12-13 02:05:18 +08:00
|
|
|
return r.close()
|
2016-07-09 03:38:35 +08:00
|
|
|
}
|
2017-12-13 02:05:18 +08:00
|
|
|
return syscall.EBADF
|
2016-07-09 03:38:35 +08:00
|
|
|
}
|
|
|
|
|
2016-07-26 02:42:18 +08:00
|
|
|
// Close the read/write/closer to trigger exiting the main server loop
|
2016-07-23 07:20:00 +08:00
|
|
|
func (rs *RequestServer) Close() error { return rs.conn.Close() }
|
2016-07-21 07:48:31 +08:00
|
|
|
|
2016-07-26 02:42:18 +08:00
|
|
|
// Serve requests for user session
|
2016-07-09 03:38:35 +08:00
|
|
|
func (rs *RequestServer) Serve() error {
|
2016-07-07 02:59:55 +08:00
|
|
|
var wg sync.WaitGroup
|
2017-07-04 08:53:55 +08:00
|
|
|
runWorker := func(ch requestChan) {
|
2017-04-24 05:27:25 +08:00
|
|
|
wg.Add(1)
|
2017-07-04 08:53:55 +08:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
if err := rs.packetWorker(ch); err != nil {
|
|
|
|
rs.conn.Close() // shuts down recvPacket
|
|
|
|
}
|
|
|
|
}()
|
2016-07-07 02:59:55 +08:00
|
|
|
}
|
2017-07-04 08:53:55 +08:00
|
|
|
pktChan := rs.pktMgr.workerChan(runWorker)
|
2016-07-07 02:59:55 +08:00
|
|
|
|
|
|
|
var err error
|
2017-03-15 09:02:17 +08:00
|
|
|
var pkt requestPacket
|
2016-07-07 02:59:55 +08:00
|
|
|
var pktType uint8
|
|
|
|
var pktBytes []byte
|
|
|
|
for {
|
2016-07-09 03:38:35 +08:00
|
|
|
pktType, pktBytes, err = rs.recvPacket()
|
2016-07-19 02:50:45 +08:00
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
2017-04-24 05:27:25 +08:00
|
|
|
|
2017-03-15 09:02:17 +08:00
|
|
|
pkt, err = makePacket(rxPacket{fxp(pktType), pktBytes})
|
2016-07-19 02:50:45 +08:00
|
|
|
if err != nil {
|
2017-03-15 09:02:17 +08:00
|
|
|
debug("makePacket err: %v", err)
|
|
|
|
rs.conn.Close() // shuts down recvPacket
|
2016-07-19 02:50:45 +08:00
|
|
|
break
|
|
|
|
}
|
2017-04-24 05:27:25 +08:00
|
|
|
|
|
|
|
pktChan <- pkt
|
2016-07-07 02:59:55 +08:00
|
|
|
}
|
2017-04-24 05:27:25 +08:00
|
|
|
|
|
|
|
close(pktChan) // shuts down sftpServerWorkers
|
|
|
|
wg.Wait() // wait for all workers to exit
|
2016-07-07 02:59:55 +08:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-04-24 05:27:25 +08:00
|
|
|
func (rs *RequestServer) packetWorker(pktChan chan requestPacket) error {
|
|
|
|
for pkt := range pktChan {
|
2016-07-26 02:52:07 +08:00
|
|
|
var rpkt responsePacket
|
2016-07-07 02:59:55 +08:00
|
|
|
switch pkt := pkt.(type) {
|
|
|
|
case *sshFxInitPacket:
|
2016-07-13 08:36:12 +08:00
|
|
|
rpkt = sshFxVersionPacket{sftpProtocolVersion, nil}
|
2016-07-12 11:19:49 +08:00
|
|
|
case *sshFxpClosePacket:
|
2016-07-30 02:22:07 +08:00
|
|
|
handle := pkt.getHandle()
|
2017-12-13 02:05:18 +08:00
|
|
|
rpkt = statusFromError(pkt, rs.closeRequest(handle))
|
2016-07-15 12:11:34 +08:00
|
|
|
case *sshFxpRealpathPacket:
|
2017-08-13 20:00:08 +08:00
|
|
|
rpkt = cleanPacketPath(pkt)
|
2016-07-13 10:31:46 +08:00
|
|
|
case isOpener:
|
2016-08-04 01:52:04 +08:00
|
|
|
handle := rs.nextRequest(requestFromPacket(pkt))
|
2016-07-13 10:31:46 +08:00
|
|
|
rpkt = sshFxpHandlePacket{pkt.id(), handle}
|
2017-04-25 10:42:51 +08:00
|
|
|
case *sshFxpFstatPacket:
|
|
|
|
handle := pkt.getHandle()
|
2017-12-24 08:34:22 +08:00
|
|
|
request, ok := rs.getRequest(handle, requestMethod(pkt))
|
2017-04-25 10:42:51 +08:00
|
|
|
if !ok {
|
|
|
|
rpkt = statusFromError(pkt, syscall.EBADF)
|
|
|
|
} else {
|
|
|
|
request = requestFromPacket(
|
|
|
|
&sshFxpStatPacket{ID: pkt.id(), Path: request.Filepath})
|
2017-08-23 10:24:14 +08:00
|
|
|
rpkt = request.call(rs.Handlers, pkt)
|
2017-04-25 10:42:51 +08:00
|
|
|
}
|
2017-04-25 12:53:56 +08:00
|
|
|
case *sshFxpFsetstatPacket:
|
|
|
|
handle := pkt.getHandle()
|
2017-12-24 08:34:22 +08:00
|
|
|
request, ok := rs.getRequest(handle, requestMethod(pkt))
|
2017-04-25 12:53:56 +08:00
|
|
|
if !ok {
|
|
|
|
rpkt = statusFromError(pkt, syscall.EBADF)
|
|
|
|
} else {
|
|
|
|
request = requestFromPacket(
|
|
|
|
&sshFxpSetstatPacket{ID: pkt.id(), Path: request.Filepath,
|
|
|
|
Flags: pkt.Flags, Attrs: pkt.Attrs,
|
|
|
|
})
|
2017-08-23 10:24:14 +08:00
|
|
|
rpkt = request.call(rs.Handlers, pkt)
|
2017-04-25 12:53:56 +08:00
|
|
|
}
|
2016-07-13 08:36:12 +08:00
|
|
|
case hasHandle:
|
2016-07-30 02:22:07 +08:00
|
|
|
handle := pkt.getHandle()
|
2017-12-24 08:34:22 +08:00
|
|
|
request, ok := rs.getRequest(handle, requestMethod(pkt))
|
|
|
|
if !ok {
|
2016-07-30 02:22:07 +08:00
|
|
|
rpkt = statusFromError(pkt, syscall.EBADF)
|
|
|
|
} else {
|
2017-08-23 10:24:14 +08:00
|
|
|
rpkt = request.call(rs.Handlers, pkt)
|
2016-07-30 02:22:07 +08:00
|
|
|
}
|
|
|
|
case hasPath:
|
2016-08-04 01:52:04 +08:00
|
|
|
request := requestFromPacket(pkt)
|
2017-08-23 10:24:14 +08:00
|
|
|
rpkt = request.call(rs.Handlers, pkt)
|
2016-07-30 02:22:07 +08:00
|
|
|
default:
|
|
|
|
return errors.Errorf("unexpected packet type %T", pkt)
|
2016-07-07 02:59:55 +08:00
|
|
|
}
|
2016-07-12 11:19:49 +08:00
|
|
|
|
2016-07-30 02:22:07 +08:00
|
|
|
err := rs.sendPacket(rpkt)
|
2016-07-19 02:50:45 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-07-07 02:59:55 +08:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2016-07-13 08:36:12 +08:00
|
|
|
|
2017-08-13 20:00:08 +08:00
|
|
|
func cleanPacketPath(pkt *sshFxpRealpathPacket) responsePacket {
|
|
|
|
path := cleanPath(pkt.getPath())
|
2016-07-15 12:11:34 +08:00
|
|
|
return &sshFxpNamePacket{
|
|
|
|
ID: pkt.id(),
|
|
|
|
NameAttrs: []sshFxpNameAttr{{
|
2017-08-13 20:00:08 +08:00
|
|
|
Name: path,
|
|
|
|
LongName: path,
|
2016-07-15 12:11:34 +08:00
|
|
|
Attrs: emptyFileStat,
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-22 03:58:29 +08:00
|
|
|
// Makes sure we have a clean POSIX (/) absolute path to work with
|
|
|
|
func cleanPath(p string) string {
|
|
|
|
p = filepath.ToSlash(p)
|
|
|
|
if !path.IsAbs(p) {
|
|
|
|
p = "/" + p
|
2017-08-13 20:00:08 +08:00
|
|
|
}
|
2017-12-22 03:58:29 +08:00
|
|
|
return path.Clean(p)
|
2017-08-13 20:00:08 +08:00
|
|
|
}
|
|
|
|
|
2017-03-15 09:02:17 +08:00
|
|
|
// Wrap underlying connection methods to use packetManager
|
2017-03-27 23:29:37 +08:00
|
|
|
func (rs *RequestServer) sendPacket(m encoding.BinaryMarshaler) error {
|
2017-03-15 09:02:17 +08:00
|
|
|
if pkt, ok := m.(responsePacket); ok {
|
|
|
|
rs.pktMgr.readyPacket(pkt)
|
|
|
|
} else {
|
|
|
|
return errors.Errorf("unexpected packet type %T", m)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-03-27 23:29:37 +08:00
|
|
|
func (rs *RequestServer) sendError(p ider, err error) error {
|
2017-03-15 09:02:17 +08:00
|
|
|
return rs.sendPacket(statusFromError(p, err))
|
|
|
|
}
|