2015-07-25 16:19:29 +08:00
|
|
|
package sftp
|
|
|
|
|
|
|
|
// sftp server counterpart
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
"syscall"
|
|
|
|
)
|
|
|
|
|
|
|
|
type FileSystem interface {
|
|
|
|
Lstat(p string) (os.FileInfo, error)
|
2015-07-26 10:07:33 +08:00
|
|
|
Mkdir(name string, perm os.FileMode) error
|
2015-07-25 16:19:29 +08:00
|
|
|
}
|
|
|
|
|
2015-07-26 16:32:19 +08:00
|
|
|
type FileSystemOpen interface {
|
|
|
|
FileSystem
|
|
|
|
OpenFile(name string, flag int, perm os.FileMode) (file *os.File, err error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type FileSystemSFTPOpen interface {
|
|
|
|
FileSystem
|
|
|
|
OpenFile(path string, f int) (*File, error) // sftp package has a strange OpenFile method with no perm
|
|
|
|
}
|
|
|
|
|
|
|
|
// common subset of os.File and sftp.File
|
|
|
|
type svrFile interface {
|
|
|
|
Chmod(mode os.FileMode) error
|
|
|
|
Chown(uid, gid int) error
|
|
|
|
Close() error
|
|
|
|
Read(b []byte) (int, error)
|
|
|
|
Seek(offset int64, whence int) (int64, error)
|
|
|
|
Stat() (os.FileInfo, error)
|
|
|
|
Truncate(size int64) error
|
|
|
|
Write(b []byte) (int, error)
|
|
|
|
// func (f *File) WriteTo(w io.Writer) (int64, error) // not in os
|
|
|
|
// func (f *File) ReadFrom(r io.Reader) (int64, error) // not in os
|
|
|
|
}
|
|
|
|
|
2015-07-25 16:19:29 +08:00
|
|
|
type nativeFs struct {
|
|
|
|
}
|
|
|
|
|
2015-07-26 10:07:33 +08:00
|
|
|
func (nfs *nativeFs) Lstat(p string) (os.FileInfo, error) { return os.Lstat(p) }
|
|
|
|
func (nfs *nativeFs) Mkdir(name string, perm os.FileMode) error { return os.Mkdir(name, perm) }
|
2015-07-26 16:32:19 +08:00
|
|
|
func (nfs *nativeFs) OpenFile(name string, flag int, perm os.FileMode) (file *os.File, err error) {
|
|
|
|
return os.OpenFile(name, flag, perm)
|
|
|
|
}
|
2015-07-25 16:19:29 +08:00
|
|
|
|
|
|
|
type Server struct {
|
|
|
|
in io.Reader
|
|
|
|
out io.Writer
|
2015-07-31 14:43:00 +08:00
|
|
|
debugStream io.Writer
|
|
|
|
debugLevel int
|
|
|
|
readOnly bool
|
2015-07-25 16:19:29 +08:00
|
|
|
rootDir string
|
|
|
|
lastId uint32
|
|
|
|
fs FileSystem
|
|
|
|
pktChan chan serverRespondablePacket
|
2015-07-26 16:32:19 +08:00
|
|
|
openFiles map[string]svrFile
|
|
|
|
openFilesLock *sync.RWMutex
|
|
|
|
handleCount int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (svr *Server) nextHandle(f svrFile) string {
|
|
|
|
svr.openFilesLock.Lock()
|
|
|
|
defer svr.openFilesLock.Unlock()
|
|
|
|
svr.handleCount++
|
|
|
|
handle := fmt.Sprintf("%d", svr.handleCount)
|
|
|
|
svr.openFiles[handle] = f
|
|
|
|
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()
|
|
|
|
} else {
|
|
|
|
return syscall.EBADF
|
|
|
|
}
|
2015-07-25 16:19:29 +08:00
|
|
|
}
|
|
|
|
|
2015-07-30 08:24:24 +08:00
|
|
|
func (svr *Server) getHandle(handle string) (svrFile, bool) {
|
|
|
|
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
|
|
|
|
respond(svr *Server) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creates a new server instance around the provided streams.
|
|
|
|
// A subsequent call to Run() is required.
|
2015-07-31 14:43:00 +08:00
|
|
|
func NewServer(in io.Reader, out io.Writer, debugStream io.Writer, debugLevel int, readOnly bool, rootDir string) (*Server, error) {
|
2015-07-25 16:19:29 +08:00
|
|
|
if rootDir == "" {
|
|
|
|
if wd, err := os.Getwd(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
rootDir = wd
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &Server{
|
|
|
|
in: in,
|
|
|
|
out: out,
|
2015-07-31 14:43:00 +08:00
|
|
|
debugStream: debugStream,
|
|
|
|
debugLevel: debugLevel,
|
|
|
|
readOnly: readOnly,
|
2015-07-25 16:19:29 +08:00
|
|
|
rootDir: rootDir,
|
|
|
|
fs: &nativeFs{},
|
|
|
|
pktChan: make(chan serverRespondablePacket, 4),
|
2015-07-26 16:32:19 +08:00
|
|
|
openFiles: map[string]svrFile{},
|
|
|
|
openFilesLock: &sync.RWMutex{},
|
2015-07-25 16:19:29 +08:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal a single logical packet from the secure channel
|
|
|
|
func (svr *Server) rxPackets() error {
|
|
|
|
defer close(svr.pktChan)
|
|
|
|
|
|
|
|
for {
|
|
|
|
pktType, pktBytes, err := recvPacket(svr.in)
|
|
|
|
if err == io.EOF {
|
|
|
|
return nil
|
|
|
|
} else if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "recvPacket error: %v\n", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if pkt, err := svr.decodePacket(fxp(pktType), pktBytes); err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "decodePacket error: %v\n", err)
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
svr.pktChan <- pkt
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (svr *Server) decodePacket(pktType fxp, pktBytes []byte) (serverRespondablePacket, error) {
|
|
|
|
//pktId, restBytes := unmarshalUint32(pktBytes[1:])
|
|
|
|
var pkt serverRespondablePacket = nil
|
|
|
|
switch pktType {
|
|
|
|
case ssh_FXP_INIT:
|
|
|
|
pkt = &sshFxInitPacket{}
|
|
|
|
case ssh_FXP_LSTAT:
|
|
|
|
pkt = &sshFxpLstatPacket{}
|
|
|
|
case ssh_FXP_VERSION:
|
|
|
|
case ssh_FXP_OPEN:
|
2015-07-26 16:32:19 +08:00
|
|
|
pkt = &sshFxpOpenPacket{}
|
2015-07-25 16:19:29 +08:00
|
|
|
case ssh_FXP_CLOSE:
|
2015-07-26 16:32:19 +08:00
|
|
|
pkt = &sshFxpClosePacket{}
|
2015-07-25 16:19:29 +08:00
|
|
|
case ssh_FXP_READ:
|
2015-07-30 08:24:24 +08:00
|
|
|
pkt = &sshFxpReadPacket{}
|
2015-07-25 16:19:29 +08:00
|
|
|
case ssh_FXP_WRITE:
|
2015-07-30 08:37:58 +08:00
|
|
|
pkt = &sshFxpWritePacket{}
|
2015-07-25 16:19:29 +08:00
|
|
|
case ssh_FXP_FSTAT:
|
2015-07-31 00:21:59 +08:00
|
|
|
pkt = &sshFxpFstatPacket{}
|
2015-07-25 16:19:29 +08:00
|
|
|
case ssh_FXP_SETSTAT:
|
|
|
|
case ssh_FXP_FSETSTAT:
|
|
|
|
case ssh_FXP_OPENDIR:
|
|
|
|
case ssh_FXP_READDIR:
|
|
|
|
case ssh_FXP_REMOVE:
|
|
|
|
case ssh_FXP_MKDIR:
|
2015-07-26 10:07:33 +08:00
|
|
|
pkt = &sshFxpMkdirPacket{}
|
2015-07-25 16:19:29 +08:00
|
|
|
case ssh_FXP_RMDIR:
|
|
|
|
case ssh_FXP_REALPATH:
|
|
|
|
case ssh_FXP_STAT:
|
|
|
|
case ssh_FXP_RENAME:
|
|
|
|
case ssh_FXP_READLINK:
|
|
|
|
case ssh_FXP_SYMLINK:
|
|
|
|
case ssh_FXP_STATUS:
|
|
|
|
case ssh_FXP_HANDLE:
|
|
|
|
case ssh_FXP_DATA:
|
|
|
|
case ssh_FXP_NAME:
|
|
|
|
case ssh_FXP_ATTRS:
|
|
|
|
case ssh_FXP_EXTENDED:
|
|
|
|
case ssh_FXP_EXTENDED_REPLY:
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
if pkt == nil {
|
|
|
|
return nil, fmt.Errorf("unhandled packet type: %s", pktType.String())
|
|
|
|
}
|
|
|
|
if err := pkt.UnmarshalBinary(pktBytes); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return pkt, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run this server until the streams stop or until the subsystem is stopped
|
|
|
|
func (svr *Server) Run() error {
|
|
|
|
go svr.rxPackets()
|
|
|
|
for pkt := range svr.pktChan {
|
|
|
|
fmt.Fprintf(os.Stderr, "pkt: %T %v\n", pkt, pkt)
|
|
|
|
pkt.respond(svr)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p sshFxInitPacket) respond(svr *Server) error {
|
|
|
|
return svr.sendPacket(sshFxVersionPacket{sftpProtocolVersion, nil})
|
|
|
|
}
|
|
|
|
|
2015-07-31 00:21:59 +08:00
|
|
|
type sshFxpStatReponse struct {
|
2015-07-25 16:19:29 +08:00
|
|
|
Id uint32
|
|
|
|
info os.FileInfo
|
|
|
|
}
|
|
|
|
|
2015-07-31 00:21:59 +08:00
|
|
|
func (p sshFxpStatReponse) MarshalBinary() ([]byte, error) {
|
2015-07-25 16:19:29 +08:00
|
|
|
b := []byte{ssh_FXP_ATTRS}
|
|
|
|
b = marshalUint32(b, p.Id)
|
|
|
|
b = marshalFileInfo(b, p.info)
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p sshFxpLstatPacket) respond(svr *Server) error {
|
|
|
|
// stat the requested file
|
|
|
|
if info, err := svr.fs.Lstat(p.Path); err != nil {
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, err))
|
|
|
|
} else {
|
2015-07-31 00:21:59 +08:00
|
|
|
return svr.sendPacket(sshFxpStatReponse{p.Id, info})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p sshFxpFstatPacket) respond(svr *Server) error {
|
|
|
|
if f, ok := svr.getHandle(p.Handle); !ok {
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, syscall.EBADF))
|
|
|
|
} else if osf, ok := f.(*os.File); ok {
|
|
|
|
if info, err := osf.Stat(); err != nil {
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, err))
|
|
|
|
} else {
|
|
|
|
return svr.sendPacket(sshFxpStatReponse{p.Id, info})
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// server error...
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, syscall.EBADF))
|
2015-07-25 16:19:29 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-26 10:07:33 +08:00
|
|
|
func (p sshFxpMkdirPacket) respond(svr *Server) error {
|
|
|
|
// ignore flags field
|
|
|
|
err := svr.fs.Mkdir(p.Path, 0755)
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, err))
|
|
|
|
}
|
|
|
|
|
2015-07-26 16:32:19 +08:00
|
|
|
func (p sshFxpOpenPacket) respond(svr *Server) error {
|
|
|
|
osFlags := 0
|
|
|
|
if p.Pflags&ssh_FXF_READ != 0 && p.Pflags&ssh_FXF_WRITE != 0 {
|
2015-07-31 14:43:00 +08:00
|
|
|
if svr.readOnly {
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, syscall.EPERM))
|
|
|
|
}
|
2015-07-26 16:32:19 +08:00
|
|
|
osFlags |= os.O_RDWR
|
|
|
|
} else if p.Pflags&ssh_FXF_WRITE != 0 {
|
2015-07-31 14:43:00 +08:00
|
|
|
if svr.readOnly {
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, syscall.EPERM))
|
|
|
|
}
|
2015-07-26 16:32:19 +08:00
|
|
|
osFlags |= os.O_WRONLY
|
2015-07-31 14:43:00 +08:00
|
|
|
} else if p.Pflags&ssh_FXF_READ != 0 {
|
|
|
|
osFlags |= os.O_RDONLY
|
|
|
|
} else {
|
|
|
|
// how are they opening?
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, syscall.EINVAL))
|
|
|
|
|
2015-07-26 16:32:19 +08:00
|
|
|
}
|
2015-07-31 14:43:00 +08:00
|
|
|
|
2015-07-26 16:32:19 +08:00
|
|
|
if p.Pflags&ssh_FXF_APPEND != 0 {
|
|
|
|
osFlags |= os.O_APPEND
|
|
|
|
}
|
|
|
|
if p.Pflags&ssh_FXF_CREAT != 0 {
|
|
|
|
osFlags |= os.O_CREATE
|
|
|
|
}
|
|
|
|
if p.Pflags&ssh_FXF_TRUNC != 0 {
|
|
|
|
osFlags |= os.O_TRUNC
|
|
|
|
}
|
|
|
|
if p.Pflags&ssh_FXF_EXCL != 0 {
|
|
|
|
osFlags |= os.O_EXCL
|
|
|
|
}
|
|
|
|
|
|
|
|
if fso, ok := svr.fs.(FileSystemOpen); ok {
|
|
|
|
if f, err := fso.OpenFile(p.Path, osFlags, 0644); err != nil {
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, err))
|
|
|
|
} else {
|
|
|
|
handle := svr.nextHandle(f)
|
|
|
|
return svr.sendPacket(sshFxpHandlePacket{p.Id, handle})
|
|
|
|
}
|
|
|
|
} else if sftpo, ok := svr.fs.(FileSystemSFTPOpen); ok {
|
|
|
|
if f, err := sftpo.OpenFile(p.Path, osFlags); err != nil {
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, err))
|
|
|
|
} else {
|
|
|
|
handle := svr.nextHandle(f)
|
|
|
|
return svr.sendPacket(sshFxpHandlePacket{p.Id, handle})
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, fmt.Errorf("unknown filesystem backend")))
|
|
|
|
}
|
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.sendPacket(statusFromError(p.Id, 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 {
|
|
|
|
if f, ok := svr.getHandle(p.Handle); !ok {
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, syscall.EBADF))
|
2015-07-30 08:37:58 +08:00
|
|
|
} else {
|
|
|
|
if p.Len > maxWritePacket {
|
|
|
|
p.Len = maxWritePacket
|
|
|
|
}
|
|
|
|
if osf, ok := f.(*os.File); ok {
|
|
|
|
debug("in readpacket server respond: len %d", p.Len)
|
|
|
|
ret := sshFxpDataPacket{Id: p.Id, Length: p.Len, Data: make([]byte, p.Len)}
|
|
|
|
if n, err := osf.ReadAt(ret.Data, int64(p.Offset)); err != nil && (err != io.EOF || n == 0) {
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, err))
|
|
|
|
} else {
|
|
|
|
ret.Length = uint32(n)
|
|
|
|
return svr.sendPacket(ret)
|
|
|
|
}
|
2015-07-30 08:24:24 +08:00
|
|
|
} else {
|
2015-07-30 08:37:58 +08:00
|
|
|
// server error...
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, syscall.EBADF))
|
2015-07-30 08:24:24 +08:00
|
|
|
}
|
2015-07-30 08:37:58 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p sshFxpWritePacket) respond(svr *Server) error {
|
|
|
|
if f, ok := svr.getHandle(p.Handle); !ok {
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, syscall.EBADF))
|
|
|
|
} else if osf, ok := f.(*os.File); ok {
|
|
|
|
_, err := osf.WriteAt(p.Data, int64(p.Offset))
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, err))
|
2015-07-30 08:24:24 +08:00
|
|
|
} else {
|
|
|
|
// server error...
|
|
|
|
return svr.sendPacket(statusFromError(p.Id, syscall.EBADF))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-31 14:43:00 +08:00
|
|
|
func errnoToSshErr(errno syscall.Errno) uint32 {
|
|
|
|
if errno == 0 {
|
|
|
|
return ssh_FX_OK
|
|
|
|
} else if errno == syscall.ENOENT {
|
|
|
|
return ssh_FX_NO_SUCH_FILE
|
|
|
|
} else if errno == syscall.EPERM {
|
|
|
|
return ssh_FX_PERMISSION_DENIED
|
|
|
|
} else {
|
|
|
|
return ssh_FX_FAILURE
|
|
|
|
}
|
|
|
|
|
|
|
|
return uint32(errno)
|
|
|
|
}
|
|
|
|
|
2015-07-25 16:19:29 +08:00
|
|
|
func statusFromError(id uint32, err error) sshFxpStatusPacket {
|
|
|
|
ret := sshFxpStatusPacket{
|
|
|
|
Id: id,
|
|
|
|
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,
|
|
|
|
msg: "",
|
2015-07-25 16:19:29 +08:00
|
|
|
lang: "",
|
|
|
|
},
|
|
|
|
}
|
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 {
|
|
|
|
ret.StatusError.Code = errnoToSshErr(errno)
|
|
|
|
} 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 {
|
2015-07-31 14:43:00 +08:00
|
|
|
ret.StatusError.Code = errnoToSshErr(errno)
|
2015-07-25 16:19:29 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|