689 lines · 15.5 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 defines the number of workers for the SFTP server	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) responsePacket} // 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 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.sendPacket(orderedResponse{				responsePacket: statusFromError(pkt, syscall.EPERM),				orderid:        pkt.orderID()})			continue		} 		if err := handlePacket(svr, pkt); err != nil {			return err		}	}	return nil} func handlePacket(s *Server, p orderedRequest) error {	var rpkt responsePacket	switch p := p.requestPacket.(type) {	case *sshFxInitPacket:		rpkt = sshFxVersionPacket{			Version:    sftpProtocolVersion,			Extensions: sftpExtensions,		}	case *sshFxpStatPacket:		// stat the requested file		info, err := os.Stat(p.Path)		rpkt = sshFxpStatResponse{			ID:   p.ID,			info: info,		}		if err != nil {			rpkt = statusFromError(p, err)		}	case *sshFxpLstatPacket:		// stat the requested file		info, err := os.Lstat(p.Path)		rpkt = sshFxpStatResponse{			ID:   p.ID,			info: info,		}		if err != nil {			rpkt = statusFromError(p, err)		}	case *sshFxpFstatPacket:		f, ok := s.getHandle(p.Handle)		var err error = syscall.EBADF		var info os.FileInfo		if ok {			info, err = f.Stat()			rpkt = sshFxpStatResponse{				ID:   p.ID,				info: info,			}		}		if err != nil {			rpkt = statusFromError(p, err)		}	case *sshFxpMkdirPacket:		// TODO FIXME: ignore flags field		err := os.Mkdir(p.Path, 0755)		rpkt = statusFromError(p, err)	case *sshFxpRmdirPacket:		err := os.Remove(p.Path)		rpkt = statusFromError(p, err)	case *sshFxpRemovePacket:		err := os.Remove(p.Filename)		rpkt = statusFromError(p, err)	case *sshFxpRenamePacket:		err := os.Rename(p.Oldpath, p.Newpath)		rpkt = statusFromError(p, err)	case *sshFxpSymlinkPacket:		err := os.Symlink(p.Targetpath, p.Linkpath)		rpkt = statusFromError(p, err)	case *sshFxpClosePacket:		rpkt = statusFromError(p, s.closeHandle(p.Handle))	case *sshFxpReadlinkPacket:		f, err := os.Readlink(p.Path)		rpkt = sshFxpNamePacket{			ID: p.ID,			NameAttrs: []sshFxpNameAttr{{				Name:     f,				LongName: f,				Attrs:    emptyFileStat,			}},		}		if err != nil {			rpkt = statusFromError(p, err)		}	case *sshFxpRealpathPacket:		f, err := filepath.Abs(p.Path)		f = cleanPath(f)		rpkt = sshFxpNamePacket{			ID: p.ID,			NameAttrs: []sshFxpNameAttr{{				Name:     f,				LongName: f,				Attrs:    emptyFileStat,			}},		}		if err != nil {			rpkt = statusFromError(p, err)		}	case *sshFxpOpendirPacket:		if stat, err := os.Stat(p.Path); err != nil {			rpkt = statusFromError(p, err)		} else if !stat.IsDir() {			rpkt = statusFromError(p, &os.PathError{				Path: p.Path, Err: syscall.ENOTDIR})		} else {			rpkt = sshFxpOpenPacket{				ID:     p.ID,				Path:   p.Path,				Pflags: sshFxfRead,			}.respond(s)		}	case *sshFxpReadPacket:		var err error = syscall.EBADF		f, ok := s.getHandle(p.Handle)		if ok {			err = nil			data := make([]byte, clamp(p.Len, 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],			}		}		if err != nil {			rpkt = statusFromError(p, err)		} 	case *sshFxpWritePacket:		f, ok := s.getHandle(p.Handle)		var err error = syscall.EBADF		if ok {			_, err = f.WriteAt(p.Data, int64(p.Offset))		}		rpkt = statusFromError(p, err)	case *sshFxpExtendedPacket:		if p.SpecificPacket == nil {			rpkt = statusFromError(p, ErrSSHFxOpUnsupported)		} else {			rpkt = p.respond(s)		}	case serverRespondablePacket:		rpkt = p.respond(s)	default:		return errors.Errorf("unexpected packet type %T", p)	} 	s.pktMgr.readyPacket(s.pktMgr.newOrderedResponse(rpkt, p.orderID()))	return nil} // 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 chan orderedRequest) {		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 <- 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} 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{sshFxpAttrs}	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(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) {		osFlags |= os.O_RDWR	} else if p.hasPflags(sshFxfWrite) {		osFlags |= os.O_WRONLY	} else if p.hasPflags(sshFxfRead) {		osFlags |= os.O_RDONLY	} else {		// how are they opening?		return statusFromError(p, syscall.EINVAL)	} 	// 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) {		osFlags |= os.O_CREATE	}	if p.hasPflags(sshFxfTrunc) {		osFlags |= os.O_TRUNC	}	if p.hasPflags(sshFxfExcl) {		osFlags |= os.O_EXCL	} 	f, err := os.OpenFile(p.Path, osFlags, 0644)	if err != nil {		return statusFromError(p, err)	} 	handle := svr.nextHandle(f)	return sshFxpHandlePacket{ID: p.id(), Handle: handle}} func (p sshFxpReaddirPacket) respond(svr *Server) responsePacket {	f, ok := svr.getHandle(p.Handle)	if !ok {		return statusFromError(p, syscall.EBADF)	} 	dirname := f.Name()	dirents, err := f.Readdir(128)	if err != nil {		return statusFromError(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 ret} func (p sshFxpSetstatPacket) respond(svr *Server) responsePacket {	// additional unmarshalling is required for each possibility here	b := p.Attrs.([]byte)	var err error 	debug("setstat name \"%s\"", p.Path)	if (p.Flags & sshFileXferAttrSize) != 0 {		var size uint64		if size, b, err = unmarshalUint64Safe(b); err == nil {			err = os.Truncate(p.Path, int64(size))		}	}	if (p.Flags & sshFileXferAttrPermissions) != 0 {		var mode uint32		if mode, b, err = unmarshalUint32Safe(b); err == nil {			err = os.Chmod(p.Path, os.FileMode(mode))		}	}	if (p.Flags & sshFileXferAttrACmodTime) != 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 & sshFileXferAttrUIDGID) != 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 statusFromError(p, err)} func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {	f, ok := svr.getHandle(p.Handle)	if !ok {		return statusFromError(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 & sshFileXferAttrSize) != 0 {		var size uint64

Lines 1–500 of 689.