2015-07-25 16:19:29 +08:00
|
|
|
package sftp
|
|
|
|
|
|
|
|
// sftp server counterpart
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2016-01-11 04:10:18 +08:00
|
|
|
"io/ioutil"
|
2015-07-25 16:19:29 +08:00
|
|
|
"os"
|
2015-08-06 03:57:28 +08:00
|
|
|
"path/filepath"
|
2016-01-08 06:27:37 +08:00
|
|
|
"strconv"
|
2015-07-25 16:19:29 +08:00
|
|
|
"sync"
|
|
|
|
"syscall"
|
2015-09-07 17:13:07 +08:00
|
|
|
"time"
|
2016-05-19 13:16:48 +08:00
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2015-07-25 16:19:29 +08:00
|
|
|
)
|
|
|
|
|
2015-09-07 14:55:15 +08:00
|
|
|
const (
|
2019-08-30 23:04:37 +08:00
|
|
|
// SftpServerWorkerCount defines the number of workers for the SFTP server
|
2017-08-13 20:00:08 +08:00
|
|
|
SftpServerWorkerCount = 8
|
2015-09-07 14:55:15 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
// Server is an SSH File Transfer Protocol (sftp) server.
|
|
|
|
// This is intended to provide the sftp subsystem to an ssh server daemon.
|
|
|
|
// This implementation currently supports most of sftp server protocol version 3,
|
|
|
|
// as specified at http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
|
2015-07-25 16:19:29 +08:00
|
|
|
type Server struct {
|
2017-07-04 08:38:09 +08:00
|
|
|
*serverConn
|
2015-07-31 14:43:00 +08:00
|
|
|
debugStream io.Writer
|
|
|
|
readOnly bool
|
2017-08-21 06:23:55 +08:00
|
|
|
pktMgr *packetManager
|
2015-09-08 13:50:46 +08:00
|
|
|
openFiles map[string]*os.File
|
2016-05-29 14:32:05 +08:00
|
|
|
openFilesLock sync.RWMutex
|
2015-07-26 16:32:19 +08:00
|
|
|
handleCount int
|
|
|
|
}
|
|
|
|
|
2015-09-08 13:50:46 +08:00
|
|
|
func (svr *Server) nextHandle(f *os.File) string {
|
2015-07-26 16:32:19 +08:00
|
|
|
svr.openFilesLock.Lock()
|
|
|
|
defer svr.openFilesLock.Unlock()
|
|
|
|
svr.handleCount++
|
2016-01-08 06:27:37 +08:00
|
|
|
handle := strconv.Itoa(svr.handleCount)
|
2015-09-08 13:50:46 +08:00
|
|
|
svr.openFiles[handle] = f
|
2015-07-26 16:32:19 +08:00
|
|
|
return handle
|
|
|
|
}
|
|
|
|
|
|
|
|
func (svr *Server) closeHandle(handle string) error {
|
|
|
|
svr.openFilesLock.Lock()
|
|
|
|
defer svr.openFilesLock.Unlock()
|
|
|
|
if f, ok := svr.openFiles[handle]; ok {
|
|
|
|
delete(svr.openFiles, handle)
|
|
|
|
return f.Close()
|
|
|
|
}
|
2015-12-31 01:56:52 +08:00
|
|
|
|
2020-09-11 00:11:47 +08:00
|
|
|
return EBADF
|
2015-07-25 16:19:29 +08:00
|
|
|
}
|
|
|
|
|
2015-09-08 13:50:46 +08:00
|
|
|
func (svr *Server) getHandle(handle string) (*os.File, bool) {
|
2015-07-30 08:24:24 +08:00
|
|
|
svr.openFilesLock.RLock()
|
|
|
|
defer svr.openFilesLock.RUnlock()
|
|
|
|
f, ok := svr.openFiles[handle]
|
|
|
|
return f, ok
|
|
|
|
}
|
|
|
|
|
2015-07-25 16:19:29 +08:00
|
|
|
type serverRespondablePacket interface {
|
|
|
|
encoding.BinaryUnmarshaler
|
2016-01-08 06:27:37 +08:00
|
|
|
id() uint32
|
2018-07-26 05:54:02 +08:00
|
|
|
respond(svr *Server) responsePacket
|
2015-07-25 16:19:29 +08:00
|
|
|
}
|
|
|
|
|
2016-01-11 04:10:18 +08:00
|
|
|
// NewServer creates a new Server instance around the provided streams, serving
|
2016-01-17 01:41:01 +08:00
|
|
|
// content from the root of the filesystem. Optionally, ServerOption
|
2016-01-11 04:10:18 +08:00
|
|
|
// functions may be specified to further configure the Server.
|
|
|
|
//
|
|
|
|
// A subsequent call to Serve() is required to begin serving files over SFTP.
|
2016-05-29 14:32:05 +08:00
|
|
|
func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error) {
|
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-06-15 09:50:02 +08:00
|
|
|
},
|
2017-03-15 09:02:17 +08:00
|
|
|
}
|
|
|
|
s := &Server{
|
|
|
|
serverConn: svrConn,
|
2016-05-29 14:32:05 +08:00
|
|
|
debugStream: ioutil.Discard,
|
2017-07-04 08:38:09 +08:00
|
|
|
pktMgr: newPktMgr(svrConn),
|
2016-05-29 14:32:05 +08:00
|
|
|
openFiles: make(map[string]*os.File),
|
2016-01-11 04:10:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, o := range options {
|
|
|
|
if err := o(s); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// A ServerOption is a function which applies configuration to a Server.
|
|
|
|
type ServerOption func(*Server) error
|
|
|
|
|
|
|
|
// WithDebug enables Server debugging output to the supplied io.Writer.
|
2016-01-12 01:21:15 +08:00
|
|
|
func WithDebug(w io.Writer) ServerOption {
|
2016-01-11 04:10:18 +08:00
|
|
|
return func(s *Server) error {
|
|
|
|
s.debugStream = w
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadOnly configures a Server to serve files in read-only mode.
|
2016-01-12 01:21:15 +08:00
|
|
|
func ReadOnly() ServerOption {
|
2016-01-11 04:10:18 +08:00
|
|
|
return func(s *Server) error {
|
|
|
|
s.readOnly = true
|
|
|
|
return nil
|
|
|
|
}
|
2015-07-25 16:19:29 +08:00
|
|
|
}
|
|
|
|
|
2020-03-18 16:36:07 +08:00
|
|
|
// WithAllocator enable the allocator.
|
|
|
|
// After processing a packet we keep in memory the allocated slices
|
|
|
|
// and we reuse them for new packets.
|
|
|
|
// The allocator is experimental
|
|
|
|
func WithAllocator() ServerOption {
|
|
|
|
return func(s *Server) error {
|
|
|
|
alloc := newAllocator()
|
|
|
|
s.pktMgr.alloc = alloc
|
|
|
|
s.conn.alloc = alloc
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-07 10:36:47 +08:00
|
|
|
type rxPacket struct {
|
|
|
|
pktType fxp
|
|
|
|
pktBytes []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
// Up to N parallel servers
|
2018-07-26 07:03:30 +08:00
|
|
|
func (svr *Server) sftpServerWorker(pktChan chan orderedRequest) error {
|
2017-03-26 08:07:47 +08:00
|
|
|
for pkt := range pktChan {
|
2017-03-14 06:54:55 +08:00
|
|
|
// readonly checks
|
2017-03-14 07:02:25 +08:00
|
|
|
readonly := true
|
2018-07-26 07:03:30 +08:00
|
|
|
switch pkt := pkt.requestPacket.(type) {
|
2017-03-14 06:54:55 +08:00
|
|
|
case notReadOnly:
|
|
|
|
readonly = false
|
2016-05-19 12:49:23 +08:00
|
|
|
case *sshFxpOpenPacket:
|
|
|
|
readonly = pkt.readonly()
|
2016-06-13 12:45:13 +08:00
|
|
|
case *sshFxpExtendedPacket:
|
2018-03-19 22:32:22 +08:00
|
|
|
readonly = pkt.readonly()
|
2016-05-19 12:49:23 +08:00
|
|
|
}
|
2015-12-31 01:56:52 +08:00
|
|
|
|
2016-01-08 06:27:37 +08:00
|
|
|
// If server is operating read-only and a write operation is requested,
|
|
|
|
// return permission denied
|
2016-05-19 12:49:23 +08:00
|
|
|
if !readonly && svr.readOnly {
|
2020-03-15 02:42:19 +08:00
|
|
|
svr.pktMgr.readyPacket(
|
2020-03-18 16:36:07 +08:00
|
|
|
svr.pktMgr.newOrderedResponse(statusFromError(pkt, syscall.EPERM), pkt.orderID()),
|
|
|
|
)
|
2016-01-08 06:27:37 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-06-15 18:47:20 +08:00
|
|
|
if err := handlePacket(svr, pkt); err != nil {
|
|
|
|
return err
|
2016-05-19 13:04:32 +08:00
|
|
|
}
|
2015-07-25 16:19:29 +08:00
|
|
|
}
|
2016-05-29 15:59:23 +08:00
|
|
|
return nil
|
2015-07-25 16:19:29 +08:00
|
|
|
}
|
|
|
|
|
2018-07-26 07:03:30 +08:00
|
|
|
func handlePacket(s *Server, p orderedRequest) error {
|
2018-07-26 05:54:02 +08:00
|
|
|
var rpkt responsePacket
|
2020-03-15 02:42:19 +08:00
|
|
|
orderID := p.orderID()
|
2018-07-26 07:03:30 +08:00
|
|
|
switch p := p.requestPacket.(type) {
|
2016-06-15 18:47:20 +08:00
|
|
|
case *sshFxInitPacket:
|
2019-05-25 03:23:18 +08:00
|
|
|
rpkt = sshFxVersionPacket{
|
2019-08-27 15:18:15 +08:00
|
|
|
Version: sftpProtocolVersion,
|
|
|
|
Extensions: sftpExtensions,
|
2019-05-25 03:23:18 +08:00
|
|
|
}
|
2016-06-15 18:47:20 +08:00
|
|
|
case *sshFxpStatPacket:
|
|
|
|
// stat the requested file
|
|
|
|
info, err := os.Stat(p.Path)
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = sshFxpStatResponse{
|
2016-06-15 18:47:20 +08:00
|
|
|
ID: p.ID,
|
|
|
|
info: info,
|
2018-07-26 05:54:02 +08:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
rpkt = statusFromError(p, err)
|
|
|
|
}
|
2016-06-15 18:47:20 +08:00
|
|
|
case *sshFxpLstatPacket:
|
|
|
|
// stat the requested file
|
|
|
|
info, err := os.Lstat(p.Path)
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = sshFxpStatResponse{
|
2016-06-15 18:47:20 +08:00
|
|
|
ID: p.ID,
|
|
|
|
info: info,
|
2018-07-26 05:54:02 +08:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
rpkt = statusFromError(p, err)
|
|
|
|
}
|
2016-06-15 18:47:20 +08:00
|
|
|
case *sshFxpFstatPacket:
|
|
|
|
f, ok := s.getHandle(p.Handle)
|
2020-09-11 00:11:47 +08:00
|
|
|
var err error = EBADF
|
2018-07-26 05:54:02 +08:00
|
|
|
var info os.FileInfo
|
|
|
|
if ok {
|
|
|
|
info, err = f.Stat()
|
|
|
|
rpkt = sshFxpStatResponse{
|
|
|
|
ID: p.ID,
|
|
|
|
info: info,
|
|
|
|
}
|
2016-06-15 18:47:20 +08:00
|
|
|
}
|
|
|
|
if err != nil {
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = statusFromError(p, err)
|
2016-06-15 18:47:20 +08:00
|
|
|
}
|
|
|
|
case *sshFxpMkdirPacket:
|
|
|
|
// TODO FIXME: ignore flags field
|
|
|
|
err := os.Mkdir(p.Path, 0755)
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = statusFromError(p, err)
|
2016-06-15 18:47:20 +08:00
|
|
|
case *sshFxpRmdirPacket:
|
|
|
|
err := os.Remove(p.Path)
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = statusFromError(p, err)
|
2016-06-15 18:47:20 +08:00
|
|
|
case *sshFxpRemovePacket:
|
|
|
|
err := os.Remove(p.Filename)
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = statusFromError(p, err)
|
2016-06-15 18:47:20 +08:00
|
|
|
case *sshFxpRenamePacket:
|
|
|
|
err := os.Rename(p.Oldpath, p.Newpath)
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = statusFromError(p, err)
|
2016-06-15 18:47:20 +08:00
|
|
|
case *sshFxpSymlinkPacket:
|
|
|
|
err := os.Symlink(p.Targetpath, p.Linkpath)
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = statusFromError(p, err)
|
2016-06-15 18:47:20 +08:00
|
|
|
case *sshFxpClosePacket:
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = statusFromError(p, s.closeHandle(p.Handle))
|
2016-06-15 18:47:20 +08:00
|
|
|
case *sshFxpReadlinkPacket:
|
|
|
|
f, err := os.Readlink(p.Path)
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = sshFxpNamePacket{
|
2016-06-15 18:47:20 +08:00
|
|
|
ID: p.ID,
|
|
|
|
NameAttrs: []sshFxpNameAttr{{
|
|
|
|
Name: f,
|
|
|
|
LongName: f,
|
|
|
|
Attrs: emptyFileStat,
|
|
|
|
}},
|
2018-07-26 05:54:02 +08:00
|
|
|
}
|
2016-06-15 18:47:20 +08:00
|
|
|
if err != nil {
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = statusFromError(p, err)
|
2016-06-15 18:47:20 +08:00
|
|
|
}
|
2018-07-26 05:54:02 +08:00
|
|
|
case *sshFxpRealpathPacket:
|
|
|
|
f, err := filepath.Abs(p.Path)
|
2017-08-13 20:00:08 +08:00
|
|
|
f = cleanPath(f)
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = sshFxpNamePacket{
|
2016-06-15 18:47:20 +08:00
|
|
|
ID: p.ID,
|
|
|
|
NameAttrs: []sshFxpNameAttr{{
|
|
|
|
Name: f,
|
|
|
|
LongName: f,
|
|
|
|
Attrs: emptyFileStat,
|
|
|
|
}},
|
2018-07-26 05:54:02 +08:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
rpkt = statusFromError(p, err)
|
|
|
|
}
|
2016-06-15 18:47:20 +08:00
|
|
|
case *sshFxpOpendirPacket:
|
2018-05-26 12:47:30 +08:00
|
|
|
if stat, err := os.Stat(p.Path); err != nil {
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = statusFromError(p, err)
|
2018-05-26 12:47:30 +08:00
|
|
|
} else if !stat.IsDir() {
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = statusFromError(p, &os.PathError{
|
2018-05-26 12:47:30 +08:00
|
|
|
Path: p.Path, Err: syscall.ENOTDIR})
|
2018-07-26 05:54:02 +08:00
|
|
|
} else {
|
|
|
|
rpkt = sshFxpOpenPacket{
|
|
|
|
ID: p.ID,
|
|
|
|
Path: p.Path,
|
2019-08-30 23:04:37 +08:00
|
|
|
Pflags: sshFxfRead,
|
2018-07-26 05:54:02 +08:00
|
|
|
}.respond(s)
|
2018-05-26 12:47:30 +08:00
|
|
|
}
|
2016-06-15 19:17:20 +08:00
|
|
|
case *sshFxpReadPacket:
|
2020-09-11 00:11:47 +08:00
|
|
|
var err error = EBADF
|
2016-06-15 19:17:20 +08:00
|
|
|
f, ok := s.getHandle(p.Handle)
|
2018-07-26 05:54:02 +08:00
|
|
|
if ok {
|
|
|
|
err = nil
|
2020-03-15 02:42:19 +08:00
|
|
|
data := p.getDataSlice(s.pktMgr.alloc, orderID)
|
2018-07-26 05:54:02 +08:00
|
|
|
n, _err := f.ReadAt(data, int64(p.Offset))
|
|
|
|
if _err != nil && (_err != io.EOF || n == 0) {
|
|
|
|
err = _err
|
|
|
|
}
|
|
|
|
rpkt = sshFxpDataPacket{
|
|
|
|
ID: p.ID,
|
|
|
|
Length: uint32(n),
|
|
|
|
Data: data[:n],
|
2020-03-10 22:35:56 +08:00
|
|
|
// do not use data[:n:n] here to clamp the capacity, we allocated extra capacity above to avoid reallocations
|
2018-07-26 05:54:02 +08:00
|
|
|
}
|
2016-06-15 19:17:20 +08:00
|
|
|
}
|
2018-07-26 05:54:02 +08:00
|
|
|
if err != nil {
|
|
|
|
rpkt = statusFromError(p, err)
|
2016-06-15 19:17:20 +08:00
|
|
|
}
|
2018-07-26 05:54:02 +08:00
|
|
|
|
2016-06-15 19:17:20 +08:00
|
|
|
case *sshFxpWritePacket:
|
|
|
|
f, ok := s.getHandle(p.Handle)
|
2020-09-11 00:11:47 +08:00
|
|
|
var err error = EBADF
|
2018-07-26 05:54:02 +08:00
|
|
|
if ok {
|
|
|
|
_, err = f.WriteAt(p.Data, int64(p.Offset))
|
2016-06-15 19:17:20 +08:00
|
|
|
}
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = statusFromError(p, err)
|
2019-08-09 21:48:13 +08:00
|
|
|
case *sshFxpExtendedPacket:
|
|
|
|
if p.SpecificPacket == nil {
|
2019-08-30 23:04:37 +08:00
|
|
|
rpkt = statusFromError(p, ErrSSHFxOpUnsupported)
|
2019-08-09 21:48:13 +08:00
|
|
|
} else {
|
|
|
|
rpkt = p.respond(s)
|
|
|
|
}
|
2016-06-15 18:47:20 +08:00
|
|
|
case serverRespondablePacket:
|
2018-07-26 05:54:02 +08:00
|
|
|
rpkt = p.respond(s)
|
2016-06-15 18:47:20 +08:00
|
|
|
default:
|
|
|
|
return errors.Errorf("unexpected packet type %T", p)
|
|
|
|
}
|
2018-07-26 05:54:02 +08:00
|
|
|
|
2020-03-15 02:42:19 +08:00
|
|
|
s.pktMgr.readyPacket(s.pktMgr.newOrderedResponse(rpkt, orderID))
|
2018-07-26 05:54:02 +08:00
|
|
|
return nil
|
2016-06-15 18:47:20 +08:00
|
|
|
}
|
|
|
|
|
2016-01-08 04:11:16 +08:00
|
|
|
// Serve serves SFTP connections until the streams stop or the SFTP subsystem
|
|
|
|
// is stopped.
|
2015-09-07 14:55:15 +08:00
|
|
|
func (svr *Server) Serve() error {
|
2020-03-18 16:36:07 +08:00
|
|
|
defer func() {
|
|
|
|
if svr.pktMgr.alloc != nil {
|
|
|
|
svr.pktMgr.alloc.Free()
|
|
|
|
}
|
|
|
|
}()
|
2016-05-29 15:59:23 +08:00
|
|
|
var wg sync.WaitGroup
|
2018-07-26 07:03:30 +08:00
|
|
|
runWorker := func(ch chan orderedRequest) {
|
2017-04-06 05:19:14 +08:00
|
|
|
wg.Add(1)
|
2017-07-04 08:53:55 +08:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
if err := svr.sftpServerWorker(ch); err != nil {
|
|
|
|
svr.conn.Close() // shuts down recvPacket
|
|
|
|
}
|
|
|
|
}()
|
2015-08-06 14:24:33 +08:00
|
|
|
}
|
2017-07-04 08:53:55 +08:00
|
|
|
pktChan := svr.pktMgr.workerChan(runWorker)
|
2016-05-29 15:59:23 +08:00
|
|
|
|
|
|
|
var err error
|
2017-03-14 09:24:32 +08:00
|
|
|
var pkt requestPacket
|
2016-06-15 18:04:25 +08:00
|
|
|
var pktType uint8
|
|
|
|
var pktBytes []byte
|
2016-05-29 15:59:23 +08:00
|
|
|
for {
|
2020-03-18 16:36:07 +08:00
|
|
|
pktType, pktBytes, err = svr.serverConn.recvPacket(svr.pktMgr.getNextOrderID())
|
2016-05-29 15:59:23 +08:00
|
|
|
if err != nil {
|
2020-03-15 02:42:19 +08:00
|
|
|
// we don't care about releasing allocated pages here, the server will quit and the allocator freed
|
2015-09-07 10:36:47 +08:00
|
|
|
break
|
|
|
|
}
|
2017-03-14 07:49:47 +08:00
|
|
|
|
|
|
|
pkt, err = makePacket(rxPacket{fxp(pktType), pktBytes})
|
|
|
|
if err != nil {
|
2018-03-19 22:32:22 +08:00
|
|
|
switch errors.Cause(err) {
|
|
|
|
case errUnknownExtendedPacket:
|
2019-08-09 21:48:13 +08:00
|
|
|
//if err := svr.serverConn.sendError(pkt, ErrSshFxOpUnsupported); err != nil {
|
|
|
|
// debug("failed to send err packet: %v", err)
|
|
|
|
// svr.conn.Close() // shuts down recvPacket
|
|
|
|
// break
|
|
|
|
//}
|
2018-03-19 22:32:22 +08:00
|
|
|
default:
|
|
|
|
debug("makePacket err: %v", err)
|
|
|
|
svr.conn.Close() // shuts down recvPacket
|
|
|
|
break
|
|
|
|
}
|
2017-03-14 07:49:47 +08:00
|
|
|
}
|
|
|
|
|
2018-07-26 07:03:30 +08:00
|
|
|
pktChan <- svr.pktMgr.newOrderedRequest(pkt)
|
2015-09-07 10:36:47 +08:00
|
|
|
}
|
2016-05-29 15:59:23 +08:00
|
|
|
|
2017-04-24 04:47:05 +08:00
|
|
|
close(pktChan) // shuts down sftpServerWorkers
|
|
|
|
wg.Wait() // wait for all workers to exit
|
2016-05-29 15:59:23 +08:00
|
|
|
|
2015-09-09 08:03:18 +08:00
|
|
|
// close any still-open files
|
|
|
|
for handle, file := range svr.openFiles {
|
2016-05-29 15:59:23 +08:00
|
|
|
fmt.Fprintf(svr.debugStream, "sftp server file with handle %q left open: %v\n", handle, file.Name())
|
2015-09-09 08:03:18 +08:00
|
|
|
file.Close()
|
|
|
|
}
|
2016-05-29 15:59:23 +08:00
|
|
|
return err // error from recvPacket
|
2015-08-06 14:24:33 +08:00
|
|
|
}
|
|
|
|
|
2017-03-14 08:52:53 +08:00
|
|
|
type ider interface {
|
2016-06-14 19:11:15 +08:00
|
|
|
id() uint32
|
|
|
|
}
|
|
|
|
|
2016-01-08 06:27:37 +08:00
|
|
|
// The init packet has no ID, so we just return a zero-value ID
|
2016-05-19 12:49:23 +08:00
|
|
|
func (p sshFxInitPacket) id() uint32 { return 0 }
|
2016-01-08 06:27:37 +08:00
|
|
|
|
2015-08-01 06:46:13 +08:00
|
|
|
type sshFxpStatResponse struct {
|
2016-01-05 05:15:21 +08:00
|
|
|
ID uint32
|
2015-07-25 16:19:29 +08:00
|
|
|
info os.FileInfo
|
|
|
|
}
|
|
|
|
|
2015-08-01 06:46:13 +08:00
|
|
|
func (p sshFxpStatResponse) MarshalBinary() ([]byte, error) {
|
2019-08-30 23:04:37 +08:00
|
|
|
b := []byte{sshFxpAttrs}
|
2016-01-05 05:15:21 +08:00
|
|
|
b = marshalUint32(b, p.ID)
|
2015-07-25 16:19:29 +08:00
|
|
|
b = marshalFileInfo(b, p.info)
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
2015-08-06 03:57:28 +08:00
|
|
|
var emptyFileStat = []interface{}{uint32(0)}
|
|
|
|
|
2016-01-08 06:27:37 +08:00
|
|
|
func (p sshFxpOpenPacket) readonly() bool {
|
2019-08-30 23:04:37 +08:00
|
|
|
return !p.hasPflags(sshFxfWrite)
|
2016-01-08 06:27:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
|
|
|
|
for _, f := range flags {
|
|
|
|
if p.Pflags&f == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2018-07-26 05:54:02 +08:00
|
|
|
func (p sshFxpOpenPacket) respond(svr *Server) responsePacket {
|
2015-12-31 01:56:52 +08:00
|
|
|
var osFlags int
|
2019-08-30 23:04:37 +08:00
|
|
|
if p.hasPflags(sshFxfRead, sshFxfWrite) {
|
2015-07-26 16:32:19 +08:00
|
|
|
osFlags |= os.O_RDWR
|
2019-08-30 23:04:37 +08:00
|
|
|
} else if p.hasPflags(sshFxfWrite) {
|
2015-07-26 16:32:19 +08:00
|
|
|
osFlags |= os.O_WRONLY
|
2019-08-30 23:04:37 +08:00
|
|
|
} else if p.hasPflags(sshFxfRead) {
|
2015-07-31 14:43:00 +08:00
|
|
|
osFlags |= os.O_RDONLY
|
|
|
|
} else {
|
|
|
|
// how are they opening?
|
2018-07-26 05:54:02 +08:00
|
|
|
return statusFromError(p, syscall.EINVAL)
|
2015-07-26 16:32:19 +08:00
|
|
|
}
|
2015-07-31 14:43:00 +08:00
|
|
|
|
2020-01-03 06:33:58 +08:00
|
|
|
// Don't use O_APPEND flag as it conflicts with WriteAt.
|
|
|
|
// The sshFxfAppend flag is a no-op here as the client sends the offsets.
|
|
|
|
|
2019-08-30 23:04:37 +08:00
|
|
|
if p.hasPflags(sshFxfCreat) {
|
2015-07-26 16:32:19 +08:00
|
|
|
osFlags |= os.O_CREATE
|
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
if p.hasPflags(sshFxfTrunc) {
|
2015-07-26 16:32:19 +08:00
|
|
|
osFlags |= os.O_TRUNC
|
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
if p.hasPflags(sshFxfExcl) {
|
2015-07-26 16:32:19 +08:00
|
|
|
osFlags |= os.O_EXCL
|
|
|
|
}
|
|
|
|
|
2015-12-31 01:56:52 +08:00
|
|
|
f, err := os.OpenFile(p.Path, osFlags, 0644)
|
|
|
|
if err != nil {
|
2018-07-26 05:54:02 +08:00
|
|
|
return statusFromError(p, err)
|
2015-07-26 16:32:19 +08:00
|
|
|
}
|
2015-12-31 01:56:52 +08:00
|
|
|
|
|
|
|
handle := svr.nextHandle(f)
|
2018-07-26 05:54:02 +08:00
|
|
|
return sshFxpHandlePacket{ID: p.id(), Handle: handle}
|
2015-07-25 16:19:29 +08:00
|
|
|
}
|
|
|
|
|
2018-07-26 05:54:02 +08:00
|
|
|
func (p sshFxpReaddirPacket) respond(svr *Server) responsePacket {
|
2015-12-31 01:56:52 +08:00
|
|
|
f, ok := svr.getHandle(p.Handle)
|
|
|
|
if !ok {
|
2020-09-11 00:11:47 +08:00
|
|
|
return statusFromError(p, EBADF)
|
2015-12-31 01:56:52 +08:00
|
|
|
}
|
2015-08-01 06:46:13 +08:00
|
|
|
|
2015-12-31 01:56:52 +08:00
|
|
|
dirname := f.Name()
|
|
|
|
dirents, err := f.Readdir(128)
|
|
|
|
if err != nil {
|
2018-07-26 05:54:02 +08:00
|
|
|
return statusFromError(p, err)
|
2015-12-31 01:56:52 +08:00
|
|
|
}
|
2015-08-01 06:46:13 +08:00
|
|
|
|
2016-01-05 05:15:21 +08:00
|
|
|
ret := sshFxpNamePacket{ID: p.ID}
|
2015-12-31 01:56:52 +08:00
|
|
|
for _, dirent := range dirents {
|
|
|
|
ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{
|
|
|
|
Name: dirent.Name(),
|
|
|
|
LongName: runLs(dirname, dirent),
|
|
|
|
Attrs: []interface{}{dirent},
|
|
|
|
})
|
2015-08-01 06:46:13 +08:00
|
|
|
}
|
2018-07-26 05:54:02 +08:00
|
|
|
return ret
|
2015-08-01 06:46:13 +08:00
|
|
|
}
|
|
|
|
|
2018-07-26 05:54:02 +08:00
|
|
|
func (p sshFxpSetstatPacket) respond(svr *Server) responsePacket {
|
2015-12-31 01:56:52 +08:00
|
|
|
// additional unmarshalling is required for each possibility here
|
|
|
|
b := p.Attrs.([]byte)
|
|
|
|
var err error
|
|
|
|
|
|
|
|
debug("setstat name \"%s\"", p.Path)
|
2019-08-30 23:04:37 +08:00
|
|
|
if (p.Flags & sshFileXferAttrSize) != 0 {
|
2015-12-31 01:56:52 +08:00
|
|
|
var size uint64
|
|
|
|
if size, b, err = unmarshalUint64Safe(b); err == nil {
|
|
|
|
err = os.Truncate(p.Path, int64(size))
|
2015-09-07 17:13:07 +08:00
|
|
|
}
|
2015-12-31 01:56:52 +08:00
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
if (p.Flags & sshFileXferAttrPermissions) != 0 {
|
2015-12-31 01:56:52 +08:00
|
|
|
var mode uint32
|
|
|
|
if mode, b, err = unmarshalUint32Safe(b); err == nil {
|
|
|
|
err = os.Chmod(p.Path, os.FileMode(mode))
|
2015-09-07 17:13:07 +08:00
|
|
|
}
|
2015-12-31 01:56:52 +08:00
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
if (p.Flags & sshFileXferAttrACmodTime) != 0 {
|
2015-12-31 01:56:52 +08:00
|
|
|
var atime uint32
|
|
|
|
var mtime uint32
|
|
|
|
if atime, b, err = unmarshalUint32Safe(b); err != nil {
|
|
|
|
} else if mtime, b, err = unmarshalUint32Safe(b); err != nil {
|
|
|
|
} else {
|
|
|
|
atimeT := time.Unix(int64(atime), 0)
|
|
|
|
mtimeT := time.Unix(int64(mtime), 0)
|
|
|
|
err = os.Chtimes(p.Path, atimeT, mtimeT)
|
2015-09-07 17:13:07 +08:00
|
|
|
}
|
2015-12-31 01:56:52 +08:00
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
if (p.Flags & sshFileXferAttrUIDGID) != 0 {
|
2015-12-31 01:56:52 +08:00
|
|
|
var uid uint32
|
|
|
|
var gid uint32
|
|
|
|
if uid, b, err = unmarshalUint32Safe(b); err != nil {
|
2018-02-16 03:00:22 +08:00
|
|
|
} else if gid, _, err = unmarshalUint32Safe(b); err != nil {
|
2015-12-31 01:56:52 +08:00
|
|
|
} else {
|
|
|
|
err = os.Chown(p.Path, int(uid), int(gid))
|
2015-09-07 17:13:07 +08:00
|
|
|
}
|
|
|
|
}
|
2015-12-31 01:56:52 +08:00
|
|
|
|
2018-07-26 05:54:02 +08:00
|
|
|
return statusFromError(p, err)
|
2015-09-07 17:13:07 +08:00
|
|
|
}
|
|
|
|
|
2018-07-26 05:54:02 +08:00
|
|
|
func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
|
2015-12-31 01:56:52 +08:00
|
|
|
f, ok := svr.getHandle(p.Handle)
|
|
|
|
if !ok {
|
2020-09-11 00:11:47 +08:00
|
|
|
return statusFromError(p, EBADF)
|
2015-12-31 01:56:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// additional unmarshalling is required for each possibility here
|
|
|
|
b := p.Attrs.([]byte)
|
|
|
|
var err error
|
|
|
|
|
|
|
|
debug("fsetstat name \"%s\"", f.Name())
|
2019-08-30 23:04:37 +08:00
|
|
|
if (p.Flags & sshFileXferAttrSize) != 0 {
|
2015-12-31 01:56:52 +08:00
|
|
|
var size uint64
|
|
|
|
if size, b, err = unmarshalUint64Safe(b); err == nil {
|
|
|
|
err = f.Truncate(int64(size))
|
2015-09-07 17:13:07 +08:00
|
|
|
}
|
2015-12-31 01:56:52 +08:00
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
if (p.Flags & sshFileXferAttrPermissions) != 0 {
|
2015-12-31 01:56:52 +08:00
|
|
|
var mode uint32
|
|
|
|
if mode, b, err = unmarshalUint32Safe(b); err == nil {
|
|
|
|
err = f.Chmod(os.FileMode(mode))
|
2015-09-07 17:13:07 +08:00
|
|
|
}
|
2015-12-31 01:56:52 +08:00
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
if (p.Flags & sshFileXferAttrACmodTime) != 0 {
|
2015-12-31 01:56:52 +08:00
|
|
|
var atime uint32
|
|
|
|
var mtime uint32
|
|
|
|
if atime, b, err = unmarshalUint32Safe(b); err != nil {
|
|
|
|
} else if mtime, b, err = unmarshalUint32Safe(b); err != nil {
|
|
|
|
} else {
|
|
|
|
atimeT := time.Unix(int64(atime), 0)
|
|
|
|
mtimeT := time.Unix(int64(mtime), 0)
|
|
|
|
err = os.Chtimes(f.Name(), atimeT, mtimeT)
|
2015-09-07 17:13:07 +08:00
|
|
|
}
|
2015-12-31 01:56:52 +08:00
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
if (p.Flags & sshFileXferAttrUIDGID) != 0 {
|
2015-12-31 01:56:52 +08:00
|
|
|
var uid uint32
|
|
|
|
var gid uint32
|
|
|
|
if uid, b, err = unmarshalUint32Safe(b); err != nil {
|
2018-02-16 03:00:22 +08:00
|
|
|
} else if gid, _, err = unmarshalUint32Safe(b); err != nil {
|
2015-12-31 01:56:52 +08:00
|
|
|
} else {
|
|
|
|
err = f.Chown(int(uid), int(gid))
|
2015-09-07 17:13:07 +08:00
|
|
|
}
|
|
|
|
}
|
2015-12-31 01:56:52 +08:00
|
|
|
|
2018-07-26 05:54:02 +08:00
|
|
|
return statusFromError(p, err)
|
2015-09-07 17:13:07 +08:00
|
|
|
}
|
|
|
|
|
2017-03-14 08:52:53 +08:00
|
|
|
func statusFromError(p ider, err error) sshFxpStatusPacket {
|
2015-07-25 16:19:29 +08:00
|
|
|
ret := sshFxpStatusPacket{
|
2016-06-14 19:11:15 +08:00
|
|
|
ID: p.id(),
|
2015-07-25 16:19:29 +08:00
|
|
|
StatusError: StatusError{
|
2019-08-30 23:04:37 +08:00
|
|
|
// sshFXOk = 0
|
|
|
|
// sshFXEOF = 1
|
|
|
|
// sshFXNoSuchFile = 2 ENOENT
|
|
|
|
// sshFXPermissionDenied = 3
|
|
|
|
// sshFXFailure = 4
|
|
|
|
// sshFXBadMessage = 5
|
|
|
|
// sshFXNoConnection = 6
|
|
|
|
// sshFXConnectionLost = 7
|
|
|
|
// sshFXOPUnsupported = 8
|
|
|
|
Code: sshFxOk,
|
2015-07-25 16:19:29 +08:00
|
|
|
},
|
|
|
|
}
|
2018-01-26 09:29:36 +08:00
|
|
|
if err == nil {
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
debug("statusFromError: error is %T %#v", err, err)
|
2019-08-30 23:04:37 +08:00
|
|
|
ret.StatusError.Code = sshFxFailure
|
2018-01-26 09:29:36 +08:00
|
|
|
ret.StatusError.msg = err.Error()
|
|
|
|
|
2020-09-14 22:58:12 +08:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
ret.StatusError.Code = sshFxNoSuchFile
|
|
|
|
return ret
|
|
|
|
}
|
2020-09-11 00:11:47 +08:00
|
|
|
if code, ok := translateSyscallError(err); ok {
|
|
|
|
ret.StatusError.Code = code
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2018-01-26 09:29:36 +08:00
|
|
|
switch e := err.(type) {
|
2018-01-27 09:26:44 +08:00
|
|
|
case fxerr:
|
|
|
|
ret.StatusError.Code = uint32(e)
|
2018-01-26 09:29:36 +08:00
|
|
|
default:
|
2020-09-14 22:58:12 +08:00
|
|
|
if e == io.EOF {
|
2019-08-30 23:04:37 +08:00
|
|
|
ret.StatusError.Code = sshFxEOF
|
2015-07-25 16:19:29 +08:00
|
|
|
}
|
|
|
|
}
|
2018-01-26 09:29:36 +08:00
|
|
|
|
2015-07-25 16:19:29 +08:00
|
|
|
return ret
|
|
|
|
}
|
2016-06-15 19:17:20 +08:00
|
|
|
|
|
|
|
func clamp(v, max uint32) uint32 {
|
|
|
|
if v > max {
|
|
|
|
return max
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
2017-08-10 13:34:48 +08:00
|
|
|
|
|
|
|
func runLsTypeWord(dirent os.FileInfo) string {
|
|
|
|
// find first character, the type char
|
|
|
|
// b Block special file.
|
|
|
|
// c Character special file.
|
|
|
|
// d Directory.
|
|
|
|
// l Symbolic link.
|
|
|
|
// s Socket link.
|
|
|
|
// p FIFO.
|
|
|
|
// - Regular file.
|
|
|
|
tc := '-'
|
|
|
|
mode := dirent.Mode()
|
|
|
|
if (mode & os.ModeDir) != 0 {
|
|
|
|
tc = 'd'
|
|
|
|
} else if (mode & os.ModeDevice) != 0 {
|
|
|
|
tc = 'b'
|
|
|
|
if (mode & os.ModeCharDevice) != 0 {
|
|
|
|
tc = 'c'
|
|
|
|
}
|
|
|
|
} else if (mode & os.ModeSymlink) != 0 {
|
|
|
|
tc = 'l'
|
|
|
|
} else if (mode & os.ModeSocket) != 0 {
|
|
|
|
tc = 's'
|
|
|
|
} else if (mode & os.ModeNamedPipe) != 0 {
|
|
|
|
tc = 'p'
|
|
|
|
}
|
|
|
|
|
|
|
|
// owner
|
|
|
|
orc := '-'
|
|
|
|
if (mode & 0400) != 0 {
|
|
|
|
orc = 'r'
|
|
|
|
}
|
|
|
|
owc := '-'
|
|
|
|
if (mode & 0200) != 0 {
|
|
|
|
owc = 'w'
|
|
|
|
}
|
|
|
|
oxc := '-'
|
|
|
|
ox := (mode & 0100) != 0
|
|
|
|
setuid := (mode & os.ModeSetuid) != 0
|
|
|
|
if ox && setuid {
|
|
|
|
oxc = 's'
|
|
|
|
} else if setuid {
|
|
|
|
oxc = 'S'
|
|
|
|
} else if ox {
|
|
|
|
oxc = 'x'
|
|
|
|
}
|
|
|
|
|
|
|
|
// group
|
|
|
|
grc := '-'
|
|
|
|
if (mode & 040) != 0 {
|
|
|
|
grc = 'r'
|
|
|
|
}
|
|
|
|
gwc := '-'
|
|
|
|
if (mode & 020) != 0 {
|
|
|
|
gwc = 'w'
|
|
|
|
}
|
|
|
|
gxc := '-'
|
|
|
|
gx := (mode & 010) != 0
|
|
|
|
setgid := (mode & os.ModeSetgid) != 0
|
|
|
|
if gx && setgid {
|
|
|
|
gxc = 's'
|
|
|
|
} else if setgid {
|
|
|
|
gxc = 'S'
|
|
|
|
} else if gx {
|
|
|
|
gxc = 'x'
|
|
|
|
}
|
|
|
|
|
|
|
|
// all / others
|
|
|
|
arc := '-'
|
|
|
|
if (mode & 04) != 0 {
|
|
|
|
arc = 'r'
|
|
|
|
}
|
|
|
|
awc := '-'
|
|
|
|
if (mode & 02) != 0 {
|
|
|
|
awc = 'w'
|
|
|
|
}
|
|
|
|
axc := '-'
|
|
|
|
ax := (mode & 01) != 0
|
|
|
|
sticky := (mode & os.ModeSticky) != 0
|
|
|
|
if ax && sticky {
|
|
|
|
axc = 't'
|
|
|
|
} else if sticky {
|
|
|
|
axc = 'T'
|
|
|
|
} else if ax {
|
|
|
|
axc = 'x'
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%c%c%c%c%c%c%c%c%c%c", tc, orc, owc, oxc, grc, gwc, gxc, arc, awc, axc)
|
|
|
|
}
|