sftp/server.go

631 lines
14 KiB
Go
Raw Normal View History

2015-07-25 16:19:29 +08:00
package sftp
// sftp server counterpart
import (
"encoding"
"fmt"
"io"
"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"
"github.com/pkg/errors"
2015-07-25 16:19:29 +08:00
)
const (
sftpServerWorkerCount = 8
)
// 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 {
conn
2015-07-31 14:43:00 +08:00
debugStream io.Writer
readOnly bool
pktChan chan rxPacket
openFiles map[string]*os.File
openFilesLock sync.RWMutex
2015-07-26 16:32:19 +08:00
handleCount int
2015-08-05 12:11:01 +08:00
maxTxPacket uint32
2015-07-26 16:32:19 +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++
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 syscall.EBADF
2015-07-25 16:19:29 +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
id() uint32
2015-07-25 16:19:29 +08:00
respond(svr *Server) error
}
// 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) {
s := &Server{
conn: conn{
Reader: rwc,
WriteCloser: rwc,
},
debugStream: ioutil.Discard,
pktChan: make(chan rxPacket, sftpServerWorkerCount),
openFiles: make(map[string]*os.File),
maxTxPacket: 1 << 15,
}
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
}
type rxPacket struct {
pktType fxp
pktBytes []byte
}
// Up to N parallel servers
func (svr *Server) sftpServerWorker() error {
for p := range svr.pktChan {
var pkt serverRespondablePacket
var readonly = true
switch p.pktType {
case ssh_FXP_INIT:
pkt = &sshFxInitPacket{}
case ssh_FXP_LSTAT:
pkt = &sshFxpLstatPacket{}
case ssh_FXP_OPEN:
pkt = &sshFxpOpenPacket{}
// readonly handled specially below
case ssh_FXP_CLOSE:
pkt = &sshFxpClosePacket{}
case ssh_FXP_READ:
pkt = &sshFxpReadPacket{}
case ssh_FXP_WRITE:
pkt = &sshFxpWritePacket{}
readonly = false
case ssh_FXP_FSTAT:
pkt = &sshFxpFstatPacket{}
case ssh_FXP_SETSTAT:
pkt = &sshFxpSetstatPacket{}
readonly = false
case ssh_FXP_FSETSTAT:
pkt = &sshFxpFsetstatPacket{}
readonly = false
case ssh_FXP_OPENDIR:
pkt = &sshFxpOpendirPacket{}
case ssh_FXP_READDIR:
pkt = &sshFxpReaddirPacket{}
case ssh_FXP_REMOVE:
pkt = &sshFxpRemovePacket{}
readonly = false
case ssh_FXP_MKDIR:
pkt = &sshFxpMkdirPacket{}
readonly = false
case ssh_FXP_RMDIR:
pkt = &sshFxpRmdirPacket{}
readonly = false
case ssh_FXP_REALPATH:
pkt = &sshFxpRealpathPacket{}
case ssh_FXP_STAT:
pkt = &sshFxpStatPacket{}
case ssh_FXP_RENAME:
pkt = &sshFxpRenamePacket{}
readonly = false
case ssh_FXP_READLINK:
pkt = &sshFxpReadlinkPacket{}
case ssh_FXP_SYMLINK:
pkt = &sshFxpSymlinkPacket{}
readonly = false
case ssh_FXP_EXTENDED:
pkt = &sshFxpExtendedPacket{}
default:
return errors.Errorf("unhandled packet type: %s", p.pktType)
2015-07-25 16:19:29 +08:00
}
if err := pkt.UnmarshalBinary(p.pktBytes); err != nil {
return err
}
// handle SFP_OPENDIR specially
switch pkt := pkt.(type) {
case *sshFxpOpenPacket:
readonly = pkt.readonly()
case *sshFxpExtendedPacket:
readonly = pkt.SpecificPacket.readonly()
}
// If server is operating read-only and a write operation is requested,
// return permission denied
if !readonly && svr.readOnly {
if err := svr.sendError(pkt, syscall.EPERM); err != nil {
return errors.Wrap(err, "failed to send read only packet response")
}
continue
}
if err := pkt.respond(svr); err != nil {
return errors.Wrap(err, "pkt.respond failed")
}
2015-07-25 16:19:29 +08:00
}
return nil
2015-07-25 16:19:29 +08:00
}
// Serve serves SFTP connections until the streams stop or the SFTP subsystem
// is stopped.
func (svr *Server) Serve() error {
var wg sync.WaitGroup
wg.Add(sftpServerWorkerCount)
for i := 0; i < sftpServerWorkerCount; i++ {
go func() {
defer wg.Done()
if err := svr.sftpServerWorker(); err != nil {
svr.conn.Close() // shuts down recvPacket
}
}()
2015-08-06 14:24:33 +08:00
}
var err error
var pktType uint8
var pktBytes []byte
for {
pktType, pktBytes, err = svr.recvPacket()
if err != nil {
break
}
svr.pktChan <- rxPacket{fxp(pktType), pktBytes}
}
close(svr.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 id interface {
id() uint32
}
func (s *Server) sendError(p id, err error) error {
return s.sendPacket(statusFromError(p, err))
}
2015-07-25 16:19:29 +08:00
func (p sshFxInitPacket) respond(svr *Server) error {
return svr.sendPacket(sshFxVersionPacket{sftpProtocolVersion, nil})
}
// 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
}
2015-08-01 06:46:13 +08:00
func (p sshFxpStatResponse) MarshalBinary() ([]byte, error) {
2015-07-25 16:19:29 +08:00
b := []byte{ssh_FXP_ATTRS}
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
}
func (p sshFxpLstatPacket) respond(svr *Server) error {
// stat the requested file
info, err := os.Lstat(p.Path)
if err != nil {
return svr.sendError(p, err)
2015-07-31 00:21:59 +08:00
}
return svr.sendPacket(sshFxpStatResponse{
2016-01-05 05:15:21 +08:00
ID: p.ID,
info: info,
})
2015-07-31 00:21:59 +08:00
}
func (p sshFxpStatPacket) respond(svr *Server) error {
// stat the requested file
info, err := os.Stat(p.Path)
if err != nil {
return svr.sendError(p, err)
}
return svr.sendPacket(sshFxpStatResponse{
2016-01-05 05:15:21 +08:00
ID: p.ID,
info: info,
})
}
2015-07-31 00:21:59 +08:00
func (p sshFxpFstatPacket) respond(svr *Server) error {
f, ok := svr.getHandle(p.Handle)
if !ok {
return svr.sendError(p, syscall.EBADF)
}
info, err := f.Stat()
if err != nil {
return svr.sendError(p, err)
2015-07-25 16:19:29 +08:00
}
return svr.sendPacket(sshFxpStatResponse{
2016-01-05 05:15:21 +08:00
ID: p.ID,
info: info,
})
2015-07-25 16:19:29 +08:00
}
2015-07-26 10:07:33 +08:00
func (p sshFxpMkdirPacket) respond(svr *Server) error {
2015-08-01 06:46:13 +08:00
// TODO FIXME: ignore flags field
err := os.Mkdir(p.Path, 0755)
return svr.sendError(p, err)
2015-08-01 06:46:13 +08:00
}
2015-09-07 16:05:16 +08:00
func (p sshFxpRmdirPacket) respond(svr *Server) error {
err := os.Remove(p.Path)
return svr.sendError(p, err)
2015-09-07 16:05:16 +08:00
}
2015-08-01 06:46:13 +08:00
func (p sshFxpRemovePacket) respond(svr *Server) error {
err := os.Remove(p.Filename)
return svr.sendError(p, err)
2015-07-26 10:07:33 +08:00
}
2015-08-01 06:46:13 +08:00
func (p sshFxpRenamePacket) respond(svr *Server) error {
err := os.Rename(p.Oldpath, p.Newpath)
return svr.sendError(p, err)
2015-08-01 06:46:13 +08:00
}
2015-09-07 16:05:16 +08:00
func (p sshFxpSymlinkPacket) respond(svr *Server) error {
err := os.Symlink(p.Targetpath, p.Linkpath)
return svr.sendError(p, err)
2015-09-07 16:05:16 +08:00
}
var emptyFileStat = []interface{}{uint32(0)}
2015-08-01 06:46:13 +08:00
func (p sshFxpReadlinkPacket) respond(svr *Server) error {
f, err := os.Readlink(p.Path)
if err != nil {
return svr.sendError(p, err)
}
return svr.sendPacket(sshFxpNamePacket{
2016-01-05 05:15:21 +08:00
ID: p.ID,
NameAttrs: []sshFxpNameAttr{{
Name: f,
LongName: f,
Attrs: emptyFileStat,
}},
})
}
func (p sshFxpRealpathPacket) respond(svr *Server) error {
f, err := filepath.Abs(p.Path)
if err != nil {
return svr.sendError(p, err)
2015-08-01 06:46:13 +08:00
}
f = filepath.Clean(f)
return svr.sendPacket(sshFxpNamePacket{
2016-01-05 05:15:21 +08:00
ID: p.ID,
NameAttrs: []sshFxpNameAttr{{
Name: f,
LongName: f,
Attrs: emptyFileStat,
}},
})
2015-08-01 06:46:13 +08:00
}
func (p sshFxpOpendirPacket) respond(svr *Server) error {
return sshFxpOpenPacket{
2016-01-05 05:15:21 +08:00
ID: p.ID,
Path: p.Path,
Pflags: ssh_FXF_READ,
}.respond(svr)
2015-08-01 06:46:13 +08:00
}
func (p sshFxpOpenPacket) readonly() bool {
return !p.hasPflags(ssh_FXF_WRITE)
}
func (p sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
for _, f := range flags {
if p.Pflags&f == 0 {
return false
}
}
return true
}
2015-07-26 16:32:19 +08:00
func (p sshFxpOpenPacket) respond(svr *Server) error {
var osFlags int
if p.hasPflags(ssh_FXF_READ, ssh_FXF_WRITE) {
2015-07-26 16:32:19 +08:00
osFlags |= os.O_RDWR
} else if p.hasPflags(ssh_FXF_WRITE) {
2015-07-26 16:32:19 +08:00
osFlags |= os.O_WRONLY
} else if p.hasPflags(ssh_FXF_READ) {
2015-07-31 14:43:00 +08:00
osFlags |= os.O_RDONLY
} else {
// how are they opening?
return svr.sendError(p, syscall.EINVAL)
2015-07-26 16:32:19 +08:00
}
2015-07-31 14:43:00 +08:00
if p.hasPflags(ssh_FXF_APPEND) {
2015-07-26 16:32:19 +08:00
osFlags |= os.O_APPEND
}
if p.hasPflags(ssh_FXF_CREAT) {
2015-07-26 16:32:19 +08:00
osFlags |= os.O_CREATE
}
if p.hasPflags(ssh_FXF_TRUNC) {
2015-07-26 16:32:19 +08:00
osFlags |= os.O_TRUNC
}
if p.hasPflags(ssh_FXF_EXCL) {
2015-07-26 16:32:19 +08:00
osFlags |= os.O_EXCL
}
f, err := os.OpenFile(p.Path, osFlags, 0644)
if err != nil {
return svr.sendError(p, err)
2015-07-26 16:32:19 +08:00
}
handle := svr.nextHandle(f)
2016-01-05 05:15:21 +08:00
return svr.sendPacket(sshFxpHandlePacket{p.ID, handle})
2015-07-25 16:19:29 +08:00
}
2015-07-26 16:32:19 +08:00
func (p sshFxpClosePacket) respond(svr *Server) error {
return svr.sendError(p, svr.closeHandle(p.Handle))
2015-07-25 16:19:29 +08:00
}
2015-07-30 08:24:24 +08:00
func (p sshFxpReadPacket) respond(svr *Server) error {
f, ok := svr.getHandle(p.Handle)
if !ok {
return svr.sendError(p, syscall.EBADF)
2015-07-30 08:37:58 +08:00
}
if p.Len > svr.maxTxPacket {
p.Len = svr.maxTxPacket
}
ret := sshFxpDataPacket{
2016-01-05 05:15:21 +08:00
ID: p.ID,
Length: p.Len,
Data: make([]byte, p.Len),
}
n, err := f.ReadAt(ret.Data, int64(p.Offset))
if err != nil && (err != io.EOF || n == 0) {
return svr.sendError(p, err)
}
ret.Length = uint32(n)
return svr.sendPacket(ret)
2015-07-30 08:37:58 +08:00
}
func (p sshFxpWritePacket) respond(svr *Server) error {
f, ok := svr.getHandle(p.Handle)
if !ok {
return svr.sendError(p, syscall.EBADF)
2015-07-30 08:24:24 +08:00
}
_, err := f.WriteAt(p.Data, int64(p.Offset))
return svr.sendError(p, err)
2015-07-30 08:24:24 +08:00
}
2015-08-01 06:46:13 +08:00
func (p sshFxpReaddirPacket) respond(svr *Server) error {
f, ok := svr.getHandle(p.Handle)
if !ok {
return svr.sendError(p, syscall.EBADF)
}
2015-08-01 06:46:13 +08:00
dirname := f.Name()
dirents, err := f.Readdir(128)
if err != nil {
return svr.sendError(p, err)
}
2015-08-01 06:46:13 +08:00
2016-01-05 05:15:21 +08:00
ret := sshFxpNamePacket{ID: p.ID}
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
}
return svr.sendPacket(ret)
2015-08-01 06:46:13 +08:00
}
func (p sshFxpSetstatPacket) respond(svr *Server) error {
// additional unmarshalling is required for each possibility here
b := p.Attrs.([]byte)
var err error
debug("setstat name \"%s\"", p.Path)
if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 0 {
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
}
}
if (p.Flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0 {
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
}
}
if (p.Flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0 {
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
}
}
if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 {
var uid uint32
var gid uint32
if uid, b, err = unmarshalUint32Safe(b); err != nil {
} else if gid, b, err = unmarshalUint32Safe(b); err != nil {
} else {
err = os.Chown(p.Path, int(uid), int(gid))
2015-09-07 17:13:07 +08:00
}
}
return svr.sendError(p, err)
2015-09-07 17:13:07 +08:00
}
func (p sshFxpFsetstatPacket) respond(svr *Server) error {
f, ok := svr.getHandle(p.Handle)
if !ok {
return svr.sendError(p, syscall.EBADF)
}
// additional unmarshalling is required for each possibility here
b := p.Attrs.([]byte)
var err error
debug("fsetstat name \"%s\"", f.Name())
if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 0 {
var size uint64
if size, b, err = unmarshalUint64Safe(b); err == nil {
err = f.Truncate(int64(size))
2015-09-07 17:13:07 +08:00
}
}
if (p.Flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0 {
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
}
}
if (p.Flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0 {
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
}
}
if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 {
var uid uint32
var gid uint32
if uid, b, err = unmarshalUint32Safe(b); err != nil {
} else if gid, b, err = unmarshalUint32Safe(b); err != nil {
} else {
err = f.Chown(int(uid), int(gid))
2015-09-07 17:13:07 +08:00
}
}
return svr.sendError(p, err)
2015-09-07 17:13:07 +08:00
}
2016-01-05 05:15:21 +08:00
// translateErrno translates a syscall error number to a SFTP error code.
func translateErrno(errno syscall.Errno) uint32 {
switch errno {
case 0:
2015-07-31 14:43:00 +08:00
return ssh_FX_OK
case syscall.ENOENT:
2015-07-31 14:43:00 +08:00
return ssh_FX_NO_SUCH_FILE
case syscall.EPERM:
2015-07-31 14:43:00 +08:00
return ssh_FX_PERMISSION_DENIED
}
return ssh_FX_FAILURE
2015-07-31 14:43:00 +08:00
}
func statusFromError(p id, err error) sshFxpStatusPacket {
2015-07-25 16:19:29 +08:00
ret := sshFxpStatusPacket{
ID: p.id(),
2015-07-25 16:19:29 +08:00
StatusError: StatusError{
// ssh_FX_OK = 0
// ssh_FX_EOF = 1
// ssh_FX_NO_SUCH_FILE = 2 ENOENT
// ssh_FX_PERMISSION_DENIED = 3
// ssh_FX_FAILURE = 4
// ssh_FX_BAD_MESSAGE = 5
// ssh_FX_NO_CONNECTION = 6
// ssh_FX_CONNECTION_LOST = 7
// ssh_FX_OP_UNSUPPORTED = 8
2015-07-26 10:07:33 +08:00
Code: ssh_FX_OK,
2015-07-25 16:19:29 +08:00
},
}
2015-07-26 10:07:33 +08:00
if err != nil {
debug("statusFromError: error is %T %#v", err, err)
ret.StatusError.Code = ssh_FX_FAILURE
ret.StatusError.msg = err.Error()
if err == io.EOF {
ret.StatusError.Code = ssh_FX_EOF
2015-07-31 14:43:00 +08:00
} else if errno, ok := err.(syscall.Errno); ok {
2016-01-05 05:15:21 +08:00
ret.StatusError.Code = translateErrno(errno)
2015-07-31 14:43:00 +08:00
} else if pathError, ok := err.(*os.PathError); ok {
2015-07-26 10:07:33 +08:00
debug("statusFromError: error is %T %#v", pathError.Err, pathError.Err)
if errno, ok := pathError.Err.(syscall.Errno); ok {
2016-01-05 05:15:21 +08:00
ret.StatusError.Code = translateErrno(errno)
2015-07-25 16:19:29 +08:00
}
}
}
return ret
}