2016-07-07 02:59:55 +08:00
|
|
|
package sftp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
2016-07-23 11:30:33 +08:00
|
|
|
"os"
|
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
|
|
|
|
FileInfo FileInfoer
|
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 {
|
|
|
|
serverConn
|
2016-07-12 11:19:49 +08:00
|
|
|
Handlers Handlers
|
|
|
|
pktChan chan packet
|
2016-07-09 03:38:35 +08:00
|
|
|
openRequests map[string]*Request
|
|
|
|
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 {
|
|
|
|
return &RequestServer{
|
2016-07-07 02:59:55 +08:00
|
|
|
serverConn: serverConn{
|
|
|
|
conn: conn{
|
|
|
|
Reader: rwc,
|
|
|
|
WriteCloser: rwc,
|
|
|
|
},
|
|
|
|
},
|
2016-07-19 03:58:23 +08:00
|
|
|
Handlers: h,
|
2016-07-12 11:19:49 +08:00
|
|
|
pktChan: make(chan packet, sftpServerWorkerCount),
|
2016-07-09 03:38:35 +08:00
|
|
|
openRequests: make(map[string]*Request),
|
2016-07-07 02:59:55 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-09 03:38:35 +08:00
|
|
|
func (rs *RequestServer) nextRequest(r *Request) string {
|
|
|
|
rs.openRequestLock.Lock()
|
|
|
|
defer rs.openRequestLock.Unlock()
|
2016-07-27 02:32:37 +08:00
|
|
|
rs.handleCount++
|
|
|
|
handle := strconv.Itoa(rs.handleCount)
|
|
|
|
rs.openRequests[handle] = r
|
|
|
|
return handle
|
2016-07-09 03:38:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (rs *RequestServer) getRequest(handle string) (*Request, bool) {
|
2016-08-02 11:22:06 +08:00
|
|
|
rs.openRequestLock.RLock()
|
|
|
|
defer rs.openRequestLock.RUnlock()
|
2016-07-09 03:38:35 +08:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
wg.Add(sftpServerWorkerCount)
|
|
|
|
for i := 0; i < sftpServerWorkerCount; i++ {
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2016-07-09 03:38:35 +08:00
|
|
|
if err := rs.packetWorker(); err != nil {
|
|
|
|
rs.conn.Close() // shuts down recvPacket
|
2016-07-07 02:59:55 +08:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
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
|
|
|
|
}
|
2016-07-12 11:19:49 +08:00
|
|
|
pkt, err := makePacket(rxPacket{fxp(pktType), pktBytes})
|
2016-07-19 02:50:45 +08:00
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
2016-07-12 11:19:49 +08:00
|
|
|
rs.pktChan <- pkt
|
2016-07-07 02:59:55 +08:00
|
|
|
}
|
|
|
|
|
2016-07-09 03:38:35 +08:00
|
|
|
close(rs.pktChan) // shuts down sftpServerWorkers
|
|
|
|
wg.Wait() // wait for all workers to exit
|
2016-07-07 02:59:55 +08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-07-09 03:38:35 +08:00
|
|
|
func (rs *RequestServer) packetWorker() error {
|
2016-07-12 11:19:49 +08:00
|
|
|
for pkt := range rs.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()
|
2016-07-12 11:19:49 +08:00
|
|
|
rs.closeRequest(handle)
|
2016-07-13 08:36:12 +08:00
|
|
|
rpkt = statusFromError(pkt, nil)
|
2016-07-15 12:11:34 +08:00
|
|
|
case *sshFxpRealpathPacket:
|
|
|
|
rpkt = cleanPath(pkt)
|
2016-07-13 10:31:46 +08:00
|
|
|
case isOpener:
|
2016-07-30 02:22:07 +08:00
|
|
|
handle := rs.nextRequest(newRequest(pkt.getPath()))
|
2016-07-13 10:31:46 +08:00
|
|
|
rpkt = sshFxpHandlePacket{pkt.id(), handle}
|
2016-07-13 08:36:12 +08:00
|
|
|
case hasHandle:
|
2016-07-30 02:22:07 +08:00
|
|
|
handle := pkt.getHandle()
|
|
|
|
request, ok := rs.getRequest(handle)
|
|
|
|
if !ok {
|
|
|
|
rpkt = statusFromError(pkt, syscall.EBADF)
|
|
|
|
} else {
|
|
|
|
rpkt = rs.handle(request, pkt)
|
|
|
|
}
|
|
|
|
case hasPath:
|
|
|
|
request := newRequest(pkt.getPath())
|
|
|
|
rpkt = rs.handle(request, pkt)
|
|
|
|
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
|
|
|
|
2016-07-26 02:52:07 +08:00
|
|
|
func cleanPath(pkt *sshFxpRealpathPacket) responsePacket {
|
2016-07-19 03:40:41 +08:00
|
|
|
path := pkt.getPath()
|
|
|
|
if !filepath.IsAbs(path) {
|
2016-07-23 07:20:00 +08:00
|
|
|
path = "/" + path
|
|
|
|
} // all paths are absolute
|
|
|
|
|
2016-07-19 03:40:41 +08:00
|
|
|
cleaned_path := filepath.Clean(path)
|
2016-07-15 12:11:34 +08:00
|
|
|
return &sshFxpNamePacket{
|
|
|
|
ID: pkt.id(),
|
|
|
|
NameAttrs: []sshFxpNameAttr{{
|
|
|
|
Name: cleaned_path,
|
|
|
|
LongName: cleaned_path,
|
|
|
|
Attrs: emptyFileStat,
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-30 02:22:07 +08:00
|
|
|
func (rs *RequestServer) handle(request *Request, pkt packet) responsePacket {
|
|
|
|
// called here to keep packet handling out of request for testing
|
|
|
|
request.populate(pkt)
|
|
|
|
// fmt.Println("Request Method: ", request.Method)
|
|
|
|
rpkt, err := request.handle(rs.Handlers)
|
|
|
|
if err != nil {
|
|
|
|
err = errorAdapter(err)
|
|
|
|
rpkt = statusFromError(pkt, err)
|
2016-07-19 02:50:45 +08:00
|
|
|
}
|
2016-07-13 08:36:12 +08:00
|
|
|
return rpkt
|
|
|
|
}
|
2016-07-23 11:30:33 +08:00
|
|
|
|
|
|
|
// os.ErrNotExist should convert to ssh_FX_NO_SUCH_FILE, but is not recognized
|
|
|
|
// by statusFromError. So we convert to syscall.ENOENT which it does.
|
|
|
|
func errorAdapter(err error) error {
|
|
|
|
if err == os.ErrNotExist {
|
|
|
|
return syscall.ENOENT
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|