sftp/server.go

658 lines
16 KiB
Go
Raw Normal View History

2015-07-25 16:19:29 +08:00
package sftp
// sftp server counterpart
import (
"encoding"
"errors"
2015-07-25 16:19:29 +08:00
"fmt"
"io"
2024-01-06 20:04:19 +08:00
"io/fs"
"io/ioutil"
2015-07-25 16:19:29 +08:00
"os"
"path/filepath"
"strconv"
2015-07-25 16:19:29 +08:00
"sync"
"syscall"
2015-09-07 17:13:07 +08:00
"time"
2015-07-25 16:19:29 +08:00
)
const (
// SftpServerWorkerCount defines the number of workers for the SFTP server
SftpServerWorkerCount = 8
)
2024-01-07 19:49:09 +08:00
type file interface {
2024-01-06 20:04:19 +08:00
Stat() (os.FileInfo, error)
ReadAt(b []byte, off int64) (int, error)
WriteAt(b []byte, off int64) (int, error)
Readdir(int) ([]os.FileInfo, error)
Name() string
Truncate(int64) error
Chmod(mode fs.FileMode) error
Chown(uid, gid int) error
Close() error
}
// 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 https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
2015-07-25 16:19:29 +08:00
type Server struct {
*serverConn
2015-07-31 14:43:00 +08:00
debugStream io.Writer
readOnly bool
pktMgr *packetManager
2024-01-07 19:49:09 +08:00
openFiles map[string]file
openFilesLock sync.RWMutex
2015-07-26 16:32:19 +08:00
handleCount int
workDir string
2024-01-10 20:32:51 +08:00
winRoot bool
maxTxPacket uint32
2015-07-26 16:32:19 +08:00
}
2024-01-07 19:49:09 +08:00
func (svr *Server) nextHandle(f file) string {
2015-07-26 16:32:19 +08:00
svr.openFilesLock.Lock()
defer svr.openFilesLock.Unlock()
svr.handleCount++
handle := strconv.Itoa(svr.handleCount)
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()
}
return EBADF
2015-07-25 16:19:29 +08:00
}
2024-01-07 19:49:09 +08:00
func (svr *Server) getHandle(handle string) (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
id() uint32
respond(svr *Server) responsePacket
2015-07-25 16:19:29 +08:00
}
// NewServer creates a new Server instance around the provided streams, serving
// content from the root of the filesystem. Optionally, ServerOption
// functions may be specified to further configure the Server.
//
// A subsequent call to Serve() is required to begin serving files over SFTP.
func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error) {
svrConn := &serverConn{
2017-03-15 09:02:17 +08:00
conn: conn{
Reader: rwc,
WriteCloser: rwc,
},
2017-03-15 09:02:17 +08:00
}
s := &Server{
serverConn: svrConn,
debugStream: ioutil.Discard,
pktMgr: newPktMgr(svrConn),
2024-01-07 19:49:09 +08:00
openFiles: make(map[string]file),
maxTxPacket: defaultMaxTxPacket,
}
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.
func WithDebug(w io.Writer) ServerOption {
return func(s *Server) error {
s.debugStream = w
return nil
}
}
// ReadOnly configures a Server to serve files in read-only mode.
func ReadOnly() ServerOption {
return func(s *Server) error {
s.readOnly = true
return nil
}
2015-07-25 16:19:29 +08:00
}
2024-01-14 16:01:02 +08:00
// WindowsRootEnumeratesDrives configures a Server to serve a virtual '/' for windows that lists all drives
2024-01-11 17:16:37 +08:00
func WindowsRootEnumeratesDrives() ServerOption {
2024-01-10 20:32:51 +08:00
return func(s *Server) error {
s.winRoot = true
return nil
}
}
// 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
}
}
// WithServerWorkingDirectory sets a working directory to use as base
// for relative paths.
// If unset the default is current working directory (os.Getwd).
func WithServerWorkingDirectory(workDir string) ServerOption {
return func(s *Server) error {
s.workDir = cleanPath(workDir)
return nil
}
}
// WithMaxTxPacket sets the maximum size of the payload returned to the client,
// measured in bytes. The default value is 32768 bytes, and this option
// can only be used to increase it. Setting this option to a larger value
// should be safe, because the client decides the size of the requested payload.
//
// The default maximum packet size is 32768 bytes.
func WithMaxTxPacket(size uint32) ServerOption {
return func(s *Server) error {
if size < defaultMaxTxPacket {
return errors.New("size must be greater than or equal to 32768")
}
s.maxTxPacket = size
return nil
}
}
type rxPacket struct {
pktType fxp
pktBytes []byte
}
// Up to N parallel servers
func (svr *Server) sftpServerWorker(pktChan chan orderedRequest) error {
for pkt := range pktChan {
// readonly checks
readonly := true
switch pkt := pkt.requestPacket.(type) {
case notReadOnly:
readonly = false
case *sshFxpOpenPacket:
readonly = pkt.readonly()
case *sshFxpExtendedPacket:
readonly = pkt.readonly()
}
// If server is operating read-only and a write operation is requested,
// return permission denied
if !readonly && svr.readOnly {
svr.pktMgr.readyPacket(
svr.pktMgr.newOrderedResponse(statusFromError(pkt.id(), syscall.EPERM), pkt.orderID()),
)
continue
}
if err := handlePacket(svr, pkt); err != nil {
return err
}
2015-07-25 16:19:29 +08:00
}
return nil
2015-07-25 16:19:29 +08:00
}
func handlePacket(s *Server, p orderedRequest) error {
var rpkt responsePacket
orderID := p.orderID()
switch p := p.requestPacket.(type) {
case *sshFxInitPacket:
rpkt = &sshFxVersionPacket{
Version: sftpProtocolVersion,
Extensions: sftpExtensions,
}
case *sshFxpStatPacket:
// stat the requested file
info, err := os.Stat(s.toLocalPath(p.Path))
rpkt = &sshFxpStatResponse{
ID: p.ID,
info: info,
}
if err != nil {
rpkt = statusFromError(p.ID, err)
}
case *sshFxpLstatPacket:
// stat the requested file
2025-01-01 23:08:40 +08:00
info, err := s.lstat(s.toLocalPath(p.Path))
rpkt = &sshFxpStatResponse{
ID: p.ID,
info: info,
}
if err != nil {
rpkt = statusFromError(p.ID, err)
}
case *sshFxpFstatPacket:
f, ok := s.getHandle(p.Handle)
var err error = EBADF
var info os.FileInfo
if ok {
info, err = f.Stat()
rpkt = &sshFxpStatResponse{
ID: p.ID,
info: info,
}
}
if err != nil {
rpkt = statusFromError(p.ID, err)
}
case *sshFxpMkdirPacket:
// TODO FIXME: ignore flags field
err := os.Mkdir(s.toLocalPath(p.Path), 0o755)
rpkt = statusFromError(p.ID, err)
case *sshFxpRmdirPacket:
err := os.Remove(s.toLocalPath(p.Path))
rpkt = statusFromError(p.ID, err)
case *sshFxpRemovePacket:
err := os.Remove(s.toLocalPath(p.Filename))
rpkt = statusFromError(p.ID, err)
case *sshFxpRenamePacket:
err := os.Rename(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath))
rpkt = statusFromError(p.ID, err)
case *sshFxpSymlinkPacket:
err := os.Symlink(s.toLocalPath(p.Targetpath), s.toLocalPath(p.Linkpath))
rpkt = statusFromError(p.ID, err)
case *sshFxpClosePacket:
rpkt = statusFromError(p.ID, s.closeHandle(p.Handle))
case *sshFxpReadlinkPacket:
f, err := os.Readlink(s.toLocalPath(p.Path))
rpkt = &sshFxpNamePacket{
ID: p.ID,
NameAttrs: []*sshFxpNameAttr{
2021-02-23 05:29:35 +08:00
{
Name: f,
LongName: f,
Attrs: emptyFileStat,
},
},
}
if err != nil {
rpkt = statusFromError(p.ID, err)
}
case *sshFxpRealpathPacket:
f, err := filepath.Abs(s.toLocalPath(p.Path))
f = cleanPath(f)
2022-10-17 22:09:17 +08:00
rpkt = &sshFxpNamePacket{
ID: p.ID,
NameAttrs: []*sshFxpNameAttr{
{
Name: f,
LongName: f,
Attrs: emptyFileStat,
},
},
}
if err != nil {
rpkt = statusFromError(p.ID, err)
}
case *sshFxpOpendirPacket:
lp := s.toLocalPath(p.Path)
2025-01-01 23:08:40 +08:00
if stat, err := s.stat(lp); err != nil {
rpkt = statusFromError(p.ID, err)
} else if !stat.IsDir() {
rpkt = statusFromError(p.ID, &os.PathError{
Path: lp, Err: syscall.ENOTDIR,
})
} else {
rpkt = (&sshFxpOpenPacket{
ID: p.ID,
Path: p.Path,
Pflags: sshFxfRead,
}).respond(s)
}
case *sshFxpReadPacket:
var err error = EBADF
f, ok := s.getHandle(p.Handle)
if ok {
err = nil
data := p.getDataSlice(s.pktMgr.alloc, orderID, s.maxTxPacket)
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],
// do not use data[:n:n] here to clamp the capacity, we allocated extra capacity above to avoid reallocations
}
}
if err != nil {
rpkt = statusFromError(p.ID, err)
}
case *sshFxpWritePacket:
f, ok := s.getHandle(p.Handle)
var err error = EBADF
if ok {
_, err = f.WriteAt(p.Data, int64(p.Offset))
}
rpkt = statusFromError(p.ID, err)
case *sshFxpExtendedPacket:
if p.SpecificPacket == nil {
rpkt = statusFromError(p.ID, ErrSSHFxOpUnsupported)
} else {
rpkt = p.respond(s)
}
case serverRespondablePacket:
rpkt = p.respond(s)
default:
return fmt.Errorf("unexpected packet type %T", p)
}
s.pktMgr.readyPacket(s.pktMgr.newOrderedResponse(rpkt, orderID))
return nil
}
// Serve serves SFTP connections until the streams stop or the SFTP subsystem
// is stopped. It returns nil if the server exits cleanly.
func (svr *Server) Serve() error {
defer func() {
if svr.pktMgr.alloc != nil {
svr.pktMgr.alloc.Free()
}
}()
var wg sync.WaitGroup
runWorker := func(ch chan orderedRequest) {
wg.Add(1)
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
}
pktChan := svr.pktMgr.workerChan(runWorker)
var err error
2017-03-14 09:24:32 +08:00
var pkt requestPacket
var pktType fxp
var pktBytes []byte
for {
pktType, pktBytes, err = svr.serverConn.recvPacket(svr.pktMgr.getNextOrderID())
if err != nil {
// Check whether the connection terminated cleanly in-between packets.
if err == io.EOF {
err = nil
}
// we don't care about releasing allocated pages here, the server will quit and the allocator freed
break
}
pkt, err = makePacket(rxPacket{pktType, pktBytes})
if err != nil {
switch {
case errors.Is(err, errUnknownExtendedPacket):
//if err := svr.serverConn.sendError(pkt, ErrSshFxOpUnsupported); err != nil {
// debug("failed to send err packet: %v", err)
// svr.conn.Close() // shuts down recvPacket
// break
//}
default:
debug("makePacket err: %v", err)
svr.conn.Close() // shuts down recvPacket
break
}
}
pktChan <- svr.pktMgr.newOrderedRequest(pkt)
}
close(pktChan) // shuts down sftpServerWorkers
wg.Wait() // wait for all workers to exit
// close any still-open files
for handle, file := range svr.openFiles {
fmt.Fprintf(svr.debugStream, "sftp server file with handle %q left open: %v\n", handle, file.Name())
file.Close()
}
return err // error from recvPacket
2015-08-06 14:24:33 +08:00
}
type ider interface {
id() uint32
}
// The init packet has no ID, so we just return a zero-value ID
func (p *sshFxInitPacket) id() uint32 { return 0 }
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
}
func (p *sshFxpStatResponse) marshalPacket() ([]byte, []byte, error) {
l := 4 + 1 + 4 // uint32(length) + byte(type) + uint32(id)
b := make([]byte, 4, l)
b = append(b, sshFxpAttrs)
2016-01-05 05:15:21 +08:00
b = marshalUint32(b, p.ID)
var payload []byte
payload = marshalFileInfo(payload, p.info)
return b, payload, nil
}
func (p *sshFxpStatResponse) MarshalBinary() ([]byte, error) {
header, payload, err := p.marshalPacket()
return append(header, payload...), err
2015-07-25 16:19:29 +08:00
}
var emptyFileStat = []interface{}{uint32(0)}
func (p *sshFxpOpenPacket) readonly() bool {
return !p.hasPflags(sshFxfWrite)
}
func (p *sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
for _, f := range flags {
if p.Pflags&f == 0 {
return false
}
}
return true
}
func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket {
var osFlags int
if p.hasPflags(sshFxfRead, sshFxfWrite) {
2015-07-26 16:32:19 +08:00
osFlags |= os.O_RDWR
} else if p.hasPflags(sshFxfWrite) {
2015-07-26 16:32:19 +08:00
osFlags |= os.O_WRONLY
} else if p.hasPflags(sshFxfRead) {
2015-07-31 14:43:00 +08:00
osFlags |= os.O_RDONLY
} else {
// how are they opening?
return statusFromError(p.ID, syscall.EINVAL)
2015-07-26 16:32:19 +08:00
}
2015-07-31 14:43:00 +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.
if p.hasPflags(sshFxfCreat) {
2015-07-26 16:32:19 +08:00
osFlags |= os.O_CREATE
}
if p.hasPflags(sshFxfTrunc) {
2015-07-26 16:32:19 +08:00
osFlags |= os.O_TRUNC
}
if p.hasPflags(sshFxfExcl) {
2015-07-26 16:32:19 +08:00
osFlags |= os.O_EXCL
}
mode := os.FileMode(0o644)
2024-01-19 09:23:22 +08:00
// Like OpenSSH, we only handle permissions here, and only when the file is being created.
// Otherwise, the permissions are ignored.
2024-01-19 09:23:22 +08:00
if p.Flags&sshFileXferAttrPermissions != 0 {
fs, err := p.unmarshalFileStat(p.Flags)
if err != nil {
return statusFromError(p.ID, err)
}
mode = fs.FileMode() & os.ModePerm
}
2024-04-10 00:08:42 +08:00
f, err := svr.openfile(svr.toLocalPath(p.Path), osFlags, mode)
if err != nil {
return statusFromError(p.ID, err)
2015-07-26 16:32:19 +08:00
}
handle := svr.nextHandle(f)
return &sshFxpHandlePacket{ID: p.ID, Handle: handle}
2015-07-25 16:19:29 +08:00
}
func (p *sshFxpReaddirPacket) respond(svr *Server) responsePacket {
f, ok := svr.getHandle(p.Handle)
if !ok {
return statusFromError(p.ID, EBADF)
}
2015-08-01 06:46:13 +08:00
dirents, err := f.Readdir(128)
if err != nil {
return statusFromError(p.ID, err)
}
2015-08-01 06:46:13 +08:00
idLookup := osIDLookup{}
ret := &sshFxpNamePacket{ID: p.ID}
for _, dirent := range dirents {
ret.NameAttrs = append(ret.NameAttrs, &sshFxpNameAttr{
Name: dirent.Name(),
LongName: runLs(idLookup, dirent),
Attrs: []interface{}{dirent},
})
2015-08-01 06:46:13 +08:00
}
return ret
2015-08-01 06:46:13 +08:00
}
func (p *sshFxpSetstatPacket) respond(svr *Server) responsePacket {
path := svr.toLocalPath(p.Path)
debug("setstat name %q", path)
2024-01-19 09:23:22 +08:00
fs, err := p.unmarshalFileStat(p.Flags)
if err == nil && (p.Flags&sshFileXferAttrSize) != 0 {
2024-02-06 23:02:43 +08:00
err = os.Truncate(path, int64(fs.Size))
}
if err == nil && (p.Flags&sshFileXferAttrPermissions) != 0 {
2024-02-06 23:02:43 +08:00
err = os.Chmod(path, fs.FileMode())
}
if err == nil && (p.Flags&sshFileXferAttrUIDGID) != 0 {
2024-02-06 23:02:43 +08:00
err = os.Chown(path, int(fs.UID), int(fs.GID))
}
if err == nil && (p.Flags&sshFileXferAttrACmodTime) != 0 {
2024-02-06 23:02:43 +08:00
err = os.Chtimes(path, fs.AccessTime(), fs.ModTime())
2015-09-07 17:13:07 +08:00
}
return statusFromError(p.ID, err)
2015-09-07 17:13:07 +08:00
}
func (p *sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
f, ok := svr.getHandle(p.Handle)
if !ok {
return statusFromError(p.ID, EBADF)
}
path := f.Name()
debug("fsetstat name %q", path)
2024-01-19 09:23:22 +08:00
fs, err := p.unmarshalFileStat(p.Flags)
if err == nil && (p.Flags&sshFileXferAttrSize) != 0 {
2024-02-06 23:02:43 +08:00
err = f.Truncate(int64(fs.Size))
}
if err == nil && (p.Flags&sshFileXferAttrPermissions) != 0 {
2024-02-06 23:02:43 +08:00
err = f.Chmod(fs.FileMode())
}
if err == nil && (p.Flags&sshFileXferAttrUIDGID) != 0 {
2024-02-06 23:02:43 +08:00
err = f.Chown(int(fs.UID), int(fs.GID))
}
if err == nil && (p.Flags&sshFileXferAttrACmodTime) != 0 {
2024-02-06 23:02:43 +08:00
type chtimer interface {
Chtimes(atime, mtime time.Time) error
}
switch f := interface{}(f).(type) {
case chtimer:
// future-compatible, for when/if *os.File supports Chtimes.
err = f.Chtimes(fs.AccessTime(), fs.ModTime())
default:
err = os.Chtimes(path, fs.AccessTime(), fs.ModTime())
2015-09-07 17:13:07 +08:00
}
}
return statusFromError(p.ID, err)
2015-09-07 17:13:07 +08:00
}
func statusFromError(id uint32, err error) *sshFxpStatusPacket {
ret := &sshFxpStatusPacket{
ID: id,
2015-07-25 16:19:29 +08:00
StatusError: StatusError{
// 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)
ret.StatusError.Code = sshFxFailure
2018-01-26 09:29:36 +08:00
ret.StatusError.msg = err.Error()
if os.IsNotExist(err) {
ret.StatusError.Code = sshFxNoSuchFile
return ret
}
if code, ok := translateSyscallError(err); ok {
ret.StatusError.Code = code
return ret
}
if errors.Is(err, io.EOF) {
ret.StatusError.Code = sshFxEOF
return ret
}
var e fxerr
if errors.As(err, &e) {
ret.StatusError.Code = uint32(e)
return ret
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
}