690 lines · 15.4 KB · Go
RawDownload
package sftp // sftp server counterpart import (	"encoding"	"fmt"	"io"	"io/ioutil"	"os"	"path/filepath"	"strconv"	"sync"	"syscall"	"time" 	"github.com/pkg/errors") 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-02type Server struct {	*serverConn	debugStream   io.Writer	readOnly      bool	pktMgr        *packetManager	openFiles     map[string]*os.File	openFilesLock sync.RWMutex	handleCount   int	maxTxPacket   uint32} func (svr *Server) nextHandle(f *os.File) string {	svr.openFilesLock.Lock()	defer svr.openFilesLock.Unlock()	svr.handleCount++	handle := strconv.Itoa(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()	} 	return syscall.EBADF} func (svr *Server) getHandle(handle string) (*os.File, bool) {	svr.openFilesLock.RLock()	defer svr.openFilesLock.RUnlock()	f, ok := svr.openFiles[handle]	return f, ok} type serverRespondablePacket interface {	encoding.BinaryUnmarshaler	id() uint32	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) {	svrConn := &serverConn{		conn: conn{			Reader:      rwc,			WriteCloser: rwc,		},	}	s := &Server{		serverConn:  svrConn,		debugStream: ioutil.Discard,		pktMgr:      newPktMgr(svrConn),		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	}} type rxPacket struct {	pktType  fxp	pktBytes []byte} // Up to N parallel serversfunc (svr *Server) sftpServerWorker(pktChan chan requestPacket) error {	for pkt := range pktChan { 		// readonly checks		readonly := true		switch pkt := pkt.(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 {			if err := svr.sendError(pkt, syscall.EPERM); err != nil {				return errors.Wrap(err, "failed to send read only packet response")			}			continue		} 		if err := handlePacket(svr, pkt); err != nil {			return err		}	}	return nil} func handlePacket(s *Server, p interface{}) error {	switch p := p.(type) {	case *sshFxInitPacket:		return s.sendPacket(sshFxVersionPacket{sftpProtocolVersion, nil})	case *sshFxpStatPacket:		// stat the requested file		info, err := os.Stat(p.Path)		if err != nil {			return s.sendError(p, err)		}		return s.sendPacket(sshFxpStatResponse{			ID:   p.ID,			info: info,		})	case *sshFxpLstatPacket:		// stat the requested file		info, err := os.Lstat(p.Path)		if err != nil {			return s.sendError(p, err)		}		return s.sendPacket(sshFxpStatResponse{			ID:   p.ID,			info: info,		})	case *sshFxpFstatPacket:		f, ok := s.getHandle(p.Handle)		if !ok {			return s.sendError(p, syscall.EBADF)		} 		info, err := f.Stat()		if err != nil {			return s.sendError(p, err)		} 		return s.sendPacket(sshFxpStatResponse{			ID:   p.ID,			info: info,		})	case *sshFxpMkdirPacket:		// TODO FIXME: ignore flags field		err := os.Mkdir(p.Path, 0755)		return s.sendError(p, err)	case *sshFxpRmdirPacket:		err := os.Remove(p.Path)		return s.sendError(p, err)	case *sshFxpRemovePacket:		err := os.Remove(p.Filename)		return s.sendError(p, err)	case *sshFxpRenamePacket:		err := os.Rename(p.Oldpath, p.Newpath)		return s.sendError(p, err)	case *sshFxpSymlinkPacket:		err := os.Symlink(p.Targetpath, p.Linkpath)		return s.sendError(p, err)	case *sshFxpClosePacket:		return s.sendError(p, s.closeHandle(p.Handle))	case *sshFxpReadlinkPacket:		f, err := os.Readlink(p.Path)		if err != nil {			return s.sendError(p, err)		} 		return s.sendPacket(sshFxpNamePacket{			ID: p.ID,			NameAttrs: []sshFxpNameAttr{{				Name:     f,				LongName: f,				Attrs:    emptyFileStat,			}},		}) 	case *sshFxpRealpathPacket:		f, err := filepath.Abs(p.Path)		if err != nil {			return s.sendError(p, err)		}		f = cleanPath(f)		return s.sendPacket(sshFxpNamePacket{			ID: p.ID,			NameAttrs: []sshFxpNameAttr{{				Name:     f,				LongName: f,				Attrs:    emptyFileStat,			}},		})	case *sshFxpOpendirPacket:		if stat, err := os.Stat(p.Path); err != nil {			return s.sendError(p, err)		} else if !stat.IsDir() {			return s.sendError(p, &os.PathError{				Path: p.Path, Err: syscall.ENOTDIR})		}		return sshFxpOpenPacket{			ID:     p.ID,			Path:   p.Path,			Pflags: ssh_FXF_READ,		}.respond(s)	case *sshFxpReadPacket:		f, ok := s.getHandle(p.Handle)		if !ok {			return s.sendError(p, syscall.EBADF)		} 		data := make([]byte, clamp(p.Len, s.maxTxPacket))		n, err := f.ReadAt(data, int64(p.Offset))		if err != nil && (err != io.EOF || n == 0) {			return s.sendError(p, err)		}		return s.sendPacket(sshFxpDataPacket{			ID:     p.ID,			Length: uint32(n),			Data:   data[:n],		})	case *sshFxpWritePacket:		f, ok := s.getHandle(p.Handle)		if !ok {			return s.sendError(p, syscall.EBADF)		} 		_, err := f.WriteAt(p.Data, int64(p.Offset))		return s.sendError(p, err)	case serverRespondablePacket:		err := p.respond(s)		return errors.Wrap(err, "pkt.respond failed")	default:		return errors.Errorf("unexpected packet type %T", p)	}} // Serve serves SFTP connections until the streams stop or the SFTP subsystem// is stopped.func (svr *Server) Serve() error {	var wg sync.WaitGroup	runWorker := func(ch requestChan) {		wg.Add(1)		go func() {			defer wg.Done()			if err := svr.sftpServerWorker(ch); err != nil {				svr.conn.Close() // shuts down recvPacket			}		}()	}	pktChan := svr.pktMgr.workerChan(runWorker) 	var err error	var pkt requestPacket	var pktType uint8	var pktBytes []byte	for {		pktType, pktBytes, err = svr.recvPacket()		if err != nil {			break		} 		pkt, err = makePacket(rxPacket{fxp(pktType), pktBytes})		if err != nil {			switch errors.Cause(err) {			case 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 <- 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} // Wrap underlying connection methods to use packetManagerfunc (svr *Server) sendPacket(m encoding.BinaryMarshaler) error {	if pkt, ok := m.(responsePacket); ok {		svr.pktMgr.readyPacket(pkt)	} else {		return errors.Errorf("unexpected packet type %T", m)	}	return nil} func (svr *Server) sendError(p ider, err error) error {	return svr.sendPacket(statusFromError(p, err))} type ider interface {	id() uint32} // The init packet has no ID, so we just return a zero-value IDfunc (p sshFxInitPacket) id() uint32 { return 0 } type sshFxpStatResponse struct {	ID   uint32	info os.FileInfo} func (p sshFxpStatResponse) MarshalBinary() ([]byte, error) {	b := []byte{ssh_FXP_ATTRS}	b = marshalUint32(b, p.ID)	b = marshalFileInfo(b, p.info)	return b, nil} var emptyFileStat = []interface{}{uint32(0)} 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} func (p sshFxpOpenPacket) respond(svr *Server) error {	var osFlags int	if p.hasPflags(ssh_FXF_READ, ssh_FXF_WRITE) {		osFlags |= os.O_RDWR	} else if p.hasPflags(ssh_FXF_WRITE) {		osFlags |= os.O_WRONLY	} else if p.hasPflags(ssh_FXF_READ) {		osFlags |= os.O_RDONLY	} else {		// how are they opening?		return svr.sendError(p, syscall.EINVAL)	} 	if p.hasPflags(ssh_FXF_APPEND) {		osFlags |= os.O_APPEND	}	if p.hasPflags(ssh_FXF_CREAT) {		osFlags |= os.O_CREATE	}	if p.hasPflags(ssh_FXF_TRUNC) {		osFlags |= os.O_TRUNC	}	if p.hasPflags(ssh_FXF_EXCL) {		osFlags |= os.O_EXCL	} 	f, err := os.OpenFile(p.Path, osFlags, 0644)	if err != nil {		return svr.sendError(p, err)	} 	handle := svr.nextHandle(f)	return svr.sendPacket(sshFxpHandlePacket{p.ID, handle})} func (p sshFxpReaddirPacket) respond(svr *Server) error {	f, ok := svr.getHandle(p.Handle)	if !ok {		return svr.sendError(p, syscall.EBADF)	} 	dirname := f.Name()	dirents, err := f.Readdir(128)	if err != nil {		return svr.sendError(p, err)	} 	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},		})	}	return svr.sendPacket(ret)} 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))		}	}	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))		}	}	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)		}	}	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, _, err = unmarshalUint32Safe(b); err != nil {		} else {			err = os.Chown(p.Path, int(uid), int(gid))		}	} 	return svr.sendError(p, err)} 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 {

Lines 1–500 of 690.