mirror of https://github.com/pkg/sftp.git
153 lines
3.5 KiB
Go
153 lines
3.5 KiB
Go
package sftp
|
|
|
|
import (
|
|
"io"
|
|
"io/ioutil"
|
|
"sync"
|
|
"syscall"
|
|
)
|
|
|
|
// Server takes the dataHandler and openHandler as arguments
|
|
// starts up packet handlers
|
|
// packet handlers convert packets to datas
|
|
// call dataHandler with data
|
|
// is done with packet/data
|
|
//
|
|
// dataHandler should call Handler() on data to process data and
|
|
// reply to client
|
|
//
|
|
// tricky bit about reading/writing spinning up workers to handle all packets
|
|
|
|
// datas using Id for switch
|
|
// + only 1 type + const
|
|
// - duplicates sftp prot Id
|
|
|
|
// datas using data-type for switch
|
|
// + types as types
|
|
// + type.Handle could enforce type of arg
|
|
// - requires dummy interface only for typing
|
|
|
|
var maxTxPacket uint32 = 1 << 15
|
|
|
|
type handleHandler func(string) string
|
|
|
|
type Handlers struct {
|
|
FileGet FileReader
|
|
FilePut FileWriter
|
|
FileCmd FileCmder
|
|
FileInfo FileInfoer
|
|
}
|
|
|
|
// Server that abstracts the sftp protocol for a http request-like protocol
|
|
type RequestServer struct {
|
|
Handlers *Handlers
|
|
serverConn
|
|
debugStream io.Writer
|
|
pktChan chan rxPacket
|
|
openRequests map[string]*Request
|
|
openRequestLock sync.RWMutex
|
|
}
|
|
|
|
// simple factory function
|
|
// one server per user-session
|
|
func NewRequestServer(rwc io.ReadWriteCloser) (*RequestServer, error) {
|
|
s := &RequestServer{
|
|
serverConn: serverConn{
|
|
conn: conn{
|
|
Reader: rwc,
|
|
WriteCloser: rwc,
|
|
},
|
|
},
|
|
debugStream: ioutil.Discard,
|
|
pktChan: make(chan rxPacket, sftpServerWorkerCount),
|
|
openRequests: make(map[string]*Request),
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
func (rs *RequestServer) nextRequest(r *Request) string {
|
|
rs.openRequestLock.Lock()
|
|
defer rs.openRequestLock.Unlock()
|
|
rs.openRequests[r.Filepath] = r
|
|
return r.Filepath
|
|
}
|
|
|
|
func (rs *RequestServer) getRequest(handle string) (*Request, bool) {
|
|
rs.openRequestLock.Lock()
|
|
defer rs.openRequestLock.Unlock()
|
|
r, ok := rs.openRequests[handle]
|
|
return r, ok
|
|
}
|
|
|
|
func (rs *RequestServer) closeRequest(handle string) {
|
|
rs.openRequestLock.Lock()
|
|
defer rs.openRequestLock.Unlock()
|
|
if _, ok := rs.openRequests[handle]; ok {
|
|
delete(rs.openRequests, handle)
|
|
// Do Requests need cleanup?
|
|
}
|
|
}
|
|
|
|
// start serving requests from user session
|
|
func (rs *RequestServer) Serve() error {
|
|
var wg sync.WaitGroup
|
|
wg.Add(sftpServerWorkerCount)
|
|
for i := 0; i < sftpServerWorkerCount; i++ {
|
|
go func() {
|
|
defer wg.Done()
|
|
if err := rs.packetWorker(); err != nil {
|
|
rs.conn.Close() // shuts down recvPacket
|
|
}
|
|
}()
|
|
}
|
|
|
|
var err error
|
|
var pktType uint8
|
|
var pktBytes []byte
|
|
for {
|
|
pktType, pktBytes, err = rs.recvPacket()
|
|
if err != nil { break }
|
|
rs.pktChan <- rxPacket{fxp(pktType), pktBytes}
|
|
}
|
|
|
|
close(rs.pktChan) // shuts down sftpServerWorkers
|
|
wg.Wait() // wait for all workers to exit
|
|
return err
|
|
}
|
|
|
|
// make packet
|
|
// handle special cases
|
|
// convert to request
|
|
// call RequestHandler
|
|
// send feedback
|
|
func (rs *RequestServer) packetWorker() error {
|
|
for p := range rs.pktChan {
|
|
pkt, err := makePacket(p)
|
|
if err != nil { return err }
|
|
|
|
// handle packet specific pre-processing
|
|
var handle string
|
|
switch pkt := pkt.(type) {
|
|
case *sshFxInitPacket:
|
|
err := rs.sendPacket(sshFxVersionPacket{sftpProtocolVersion, nil})
|
|
if err != nil { return err }
|
|
continue
|
|
case *sshFxpClosePacket:
|
|
handle = pkt.getHandle()
|
|
rs.closeRequest(handle)
|
|
err := rs.sendError(pkt, nil)
|
|
if err != nil { return err }
|
|
continue
|
|
case hasPath:
|
|
handle = rs.nextRequest(newRequest(pkt.getPath(), rs))
|
|
case hasHandle:
|
|
handle = pkt.getHandle()
|
|
}
|
|
request, ok := rs.getRequest(handle)
|
|
if !ok { return rs.sendError(pkt, syscall.EBADF) }
|
|
request.pktChan <- pkt
|
|
}
|
|
return nil
|
|
}
|