github.com/pkg/sftp@v1.13.6/server.go (about)

     1  package sftp
     2  
     3  // sftp server counterpart
     4  
     5  import (
     6  	"encoding"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"strconv"
    14  	"sync"
    15  	"syscall"
    16  	"time"
    17  )
    18  
    19  const (
    20  	// SftpServerWorkerCount defines the number of workers for the SFTP server
    21  	SftpServerWorkerCount = 8
    22  )
    23  
    24  // Server is an SSH File Transfer Protocol (sftp) server.
    25  // This is intended to provide the sftp subsystem to an ssh server daemon.
    26  // This implementation currently supports most of sftp server protocol version 3,
    27  // as specified at https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
    28  type Server struct {
    29  	*serverConn
    30  	debugStream   io.Writer
    31  	readOnly      bool
    32  	pktMgr        *packetManager
    33  	openFiles     map[string]*os.File
    34  	openFilesLock sync.RWMutex
    35  	handleCount   int
    36  	workDir       string
    37  }
    38  
    39  func (svr *Server) nextHandle(f *os.File) string {
    40  	svr.openFilesLock.Lock()
    41  	defer svr.openFilesLock.Unlock()
    42  	svr.handleCount++
    43  	handle := strconv.Itoa(svr.handleCount)
    44  	svr.openFiles[handle] = f
    45  	return handle
    46  }
    47  
    48  func (svr *Server) closeHandle(handle string) error {
    49  	svr.openFilesLock.Lock()
    50  	defer svr.openFilesLock.Unlock()
    51  	if f, ok := svr.openFiles[handle]; ok {
    52  		delete(svr.openFiles, handle)
    53  		return f.Close()
    54  	}
    55  
    56  	return EBADF
    57  }
    58  
    59  func (svr *Server) getHandle(handle string) (*os.File, bool) {
    60  	svr.openFilesLock.RLock()
    61  	defer svr.openFilesLock.RUnlock()
    62  	f, ok := svr.openFiles[handle]
    63  	return f, ok
    64  }
    65  
    66  type serverRespondablePacket interface {
    67  	encoding.BinaryUnmarshaler
    68  	id() uint32
    69  	respond(svr *Server) responsePacket
    70  }
    71  
    72  // NewServer creates a new Server instance around the provided streams, serving
    73  // content from the root of the filesystem.  Optionally, ServerOption
    74  // functions may be specified to further configure the Server.
    75  //
    76  // A subsequent call to Serve() is required to begin serving files over SFTP.
    77  func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error) {
    78  	svrConn := &serverConn{
    79  		conn: conn{
    80  			Reader:      rwc,
    81  			WriteCloser: rwc,
    82  		},
    83  	}
    84  	s := &Server{
    85  		serverConn:  svrConn,
    86  		debugStream: ioutil.Discard,
    87  		pktMgr:      newPktMgr(svrConn),
    88  		openFiles:   make(map[string]*os.File),
    89  	}
    90  
    91  	for _, o := range options {
    92  		if err := o(s); err != nil {
    93  			return nil, err
    94  		}
    95  	}
    96  
    97  	return s, nil
    98  }
    99  
   100  // A ServerOption is a function which applies configuration to a Server.
   101  type ServerOption func(*Server) error
   102  
   103  // WithDebug enables Server debugging output to the supplied io.Writer.
   104  func WithDebug(w io.Writer) ServerOption {
   105  	return func(s *Server) error {
   106  		s.debugStream = w
   107  		return nil
   108  	}
   109  }
   110  
   111  // ReadOnly configures a Server to serve files in read-only mode.
   112  func ReadOnly() ServerOption {
   113  	return func(s *Server) error {
   114  		s.readOnly = true
   115  		return nil
   116  	}
   117  }
   118  
   119  // WithAllocator enable the allocator.
   120  // After processing a packet we keep in memory the allocated slices
   121  // and we reuse them for new packets.
   122  // The allocator is experimental
   123  func WithAllocator() ServerOption {
   124  	return func(s *Server) error {
   125  		alloc := newAllocator()
   126  		s.pktMgr.alloc = alloc
   127  		s.conn.alloc = alloc
   128  		return nil
   129  	}
   130  }
   131  
   132  // WithServerWorkingDirectory sets a working directory to use as base
   133  // for relative paths.
   134  // If unset the default is current working directory (os.Getwd).
   135  func WithServerWorkingDirectory(workDir string) ServerOption {
   136  	return func(s *Server) error {
   137  		s.workDir = cleanPath(workDir)
   138  		return nil
   139  	}
   140  }
   141  
   142  type rxPacket struct {
   143  	pktType  fxp
   144  	pktBytes []byte
   145  }
   146  
   147  // Up to N parallel servers
   148  func (svr *Server) sftpServerWorker(pktChan chan orderedRequest) error {
   149  	for pkt := range pktChan {
   150  		// readonly checks
   151  		readonly := true
   152  		switch pkt := pkt.requestPacket.(type) {
   153  		case notReadOnly:
   154  			readonly = false
   155  		case *sshFxpOpenPacket:
   156  			readonly = pkt.readonly()
   157  		case *sshFxpExtendedPacket:
   158  			readonly = pkt.readonly()
   159  		}
   160  
   161  		// If server is operating read-only and a write operation is requested,
   162  		// return permission denied
   163  		if !readonly && svr.readOnly {
   164  			svr.pktMgr.readyPacket(
   165  				svr.pktMgr.newOrderedResponse(statusFromError(pkt.id(), syscall.EPERM), pkt.orderID()),
   166  			)
   167  			continue
   168  		}
   169  
   170  		if err := handlePacket(svr, pkt); err != nil {
   171  			return err
   172  		}
   173  	}
   174  	return nil
   175  }
   176  
   177  func handlePacket(s *Server, p orderedRequest) error {
   178  	var rpkt responsePacket
   179  	orderID := p.orderID()
   180  	switch p := p.requestPacket.(type) {
   181  	case *sshFxInitPacket:
   182  		rpkt = &sshFxVersionPacket{
   183  			Version:    sftpProtocolVersion,
   184  			Extensions: sftpExtensions,
   185  		}
   186  	case *sshFxpStatPacket:
   187  		// stat the requested file
   188  		info, err := os.Stat(s.toLocalPath(p.Path))
   189  		rpkt = &sshFxpStatResponse{
   190  			ID:   p.ID,
   191  			info: info,
   192  		}
   193  		if err != nil {
   194  			rpkt = statusFromError(p.ID, err)
   195  		}
   196  	case *sshFxpLstatPacket:
   197  		// stat the requested file
   198  		info, err := os.Lstat(s.toLocalPath(p.Path))
   199  		rpkt = &sshFxpStatResponse{
   200  			ID:   p.ID,
   201  			info: info,
   202  		}
   203  		if err != nil {
   204  			rpkt = statusFromError(p.ID, err)
   205  		}
   206  	case *sshFxpFstatPacket:
   207  		f, ok := s.getHandle(p.Handle)
   208  		var err error = EBADF
   209  		var info os.FileInfo
   210  		if ok {
   211  			info, err = f.Stat()
   212  			rpkt = &sshFxpStatResponse{
   213  				ID:   p.ID,
   214  				info: info,
   215  			}
   216  		}
   217  		if err != nil {
   218  			rpkt = statusFromError(p.ID, err)
   219  		}
   220  	case *sshFxpMkdirPacket:
   221  		// TODO FIXME: ignore flags field
   222  		err := os.Mkdir(s.toLocalPath(p.Path), 0o755)
   223  		rpkt = statusFromError(p.ID, err)
   224  	case *sshFxpRmdirPacket:
   225  		err := os.Remove(s.toLocalPath(p.Path))
   226  		rpkt = statusFromError(p.ID, err)
   227  	case *sshFxpRemovePacket:
   228  		err := os.Remove(s.toLocalPath(p.Filename))
   229  		rpkt = statusFromError(p.ID, err)
   230  	case *sshFxpRenamePacket:
   231  		err := os.Rename(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath))
   232  		rpkt = statusFromError(p.ID, err)
   233  	case *sshFxpSymlinkPacket:
   234  		err := os.Symlink(s.toLocalPath(p.Targetpath), s.toLocalPath(p.Linkpath))
   235  		rpkt = statusFromError(p.ID, err)
   236  	case *sshFxpClosePacket:
   237  		rpkt = statusFromError(p.ID, s.closeHandle(p.Handle))
   238  	case *sshFxpReadlinkPacket:
   239  		f, err := os.Readlink(s.toLocalPath(p.Path))
   240  		rpkt = &sshFxpNamePacket{
   241  			ID: p.ID,
   242  			NameAttrs: []*sshFxpNameAttr{
   243  				{
   244  					Name:     f,
   245  					LongName: f,
   246  					Attrs:    emptyFileStat,
   247  				},
   248  			},
   249  		}
   250  		if err != nil {
   251  			rpkt = statusFromError(p.ID, err)
   252  		}
   253  	case *sshFxpRealpathPacket:
   254  		f, err := filepath.Abs(s.toLocalPath(p.Path))
   255  		f = cleanPath(f)
   256  		rpkt = &sshFxpNamePacket{
   257  			ID: p.ID,
   258  			NameAttrs: []*sshFxpNameAttr{
   259  				{
   260  					Name:     f,
   261  					LongName: f,
   262  					Attrs:    emptyFileStat,
   263  				},
   264  			},
   265  		}
   266  		if err != nil {
   267  			rpkt = statusFromError(p.ID, err)
   268  		}
   269  	case *sshFxpOpendirPacket:
   270  		lp := s.toLocalPath(p.Path)
   271  
   272  		if stat, err := os.Stat(lp); err != nil {
   273  			rpkt = statusFromError(p.ID, err)
   274  		} else if !stat.IsDir() {
   275  			rpkt = statusFromError(p.ID, &os.PathError{
   276  				Path: lp, Err: syscall.ENOTDIR,
   277  			})
   278  		} else {
   279  			rpkt = (&sshFxpOpenPacket{
   280  				ID:     p.ID,
   281  				Path:   p.Path,
   282  				Pflags: sshFxfRead,
   283  			}).respond(s)
   284  		}
   285  	case *sshFxpReadPacket:
   286  		var err error = EBADF
   287  		f, ok := s.getHandle(p.Handle)
   288  		if ok {
   289  			err = nil
   290  			data := p.getDataSlice(s.pktMgr.alloc, orderID)
   291  			n, _err := f.ReadAt(data, int64(p.Offset))
   292  			if _err != nil && (_err != io.EOF || n == 0) {
   293  				err = _err
   294  			}
   295  			rpkt = &sshFxpDataPacket{
   296  				ID:     p.ID,
   297  				Length: uint32(n),
   298  				Data:   data[:n],
   299  				// do not use data[:n:n] here to clamp the capacity, we allocated extra capacity above to avoid reallocations
   300  			}
   301  		}
   302  		if err != nil {
   303  			rpkt = statusFromError(p.ID, err)
   304  		}
   305  
   306  	case *sshFxpWritePacket:
   307  		f, ok := s.getHandle(p.Handle)
   308  		var err error = EBADF
   309  		if ok {
   310  			_, err = f.WriteAt(p.Data, int64(p.Offset))
   311  		}
   312  		rpkt = statusFromError(p.ID, err)
   313  	case *sshFxpExtendedPacket:
   314  		if p.SpecificPacket == nil {
   315  			rpkt = statusFromError(p.ID, ErrSSHFxOpUnsupported)
   316  		} else {
   317  			rpkt = p.respond(s)
   318  		}
   319  	case serverRespondablePacket:
   320  		rpkt = p.respond(s)
   321  	default:
   322  		return fmt.Errorf("unexpected packet type %T", p)
   323  	}
   324  
   325  	s.pktMgr.readyPacket(s.pktMgr.newOrderedResponse(rpkt, orderID))
   326  	return nil
   327  }
   328  
   329  // Serve serves SFTP connections until the streams stop or the SFTP subsystem
   330  // is stopped. It returns nil if the server exits cleanly.
   331  func (svr *Server) Serve() error {
   332  	defer func() {
   333  		if svr.pktMgr.alloc != nil {
   334  			svr.pktMgr.alloc.Free()
   335  		}
   336  	}()
   337  	var wg sync.WaitGroup
   338  	runWorker := func(ch chan orderedRequest) {
   339  		wg.Add(1)
   340  		go func() {
   341  			defer wg.Done()
   342  			if err := svr.sftpServerWorker(ch); err != nil {
   343  				svr.conn.Close() // shuts down recvPacket
   344  			}
   345  		}()
   346  	}
   347  	pktChan := svr.pktMgr.workerChan(runWorker)
   348  
   349  	var err error
   350  	var pkt requestPacket
   351  	var pktType uint8
   352  	var pktBytes []byte
   353  	for {
   354  		pktType, pktBytes, err = svr.serverConn.recvPacket(svr.pktMgr.getNextOrderID())
   355  		if err != nil {
   356  			// Check whether the connection terminated cleanly in-between packets.
   357  			if err == io.EOF {
   358  				err = nil
   359  			}
   360  			// we don't care about releasing allocated pages here, the server will quit and the allocator freed
   361  			break
   362  		}
   363  
   364  		pkt, err = makePacket(rxPacket{fxp(pktType), pktBytes})
   365  		if err != nil {
   366  			switch {
   367  			case errors.Is(err, errUnknownExtendedPacket):
   368  				//if err := svr.serverConn.sendError(pkt, ErrSshFxOpUnsupported); err != nil {
   369  				//	debug("failed to send err packet: %v", err)
   370  				//	svr.conn.Close() // shuts down recvPacket
   371  				//	break
   372  				//}
   373  			default:
   374  				debug("makePacket err: %v", err)
   375  				svr.conn.Close() // shuts down recvPacket
   376  				break
   377  			}
   378  		}
   379  
   380  		pktChan <- svr.pktMgr.newOrderedRequest(pkt)
   381  	}
   382  
   383  	close(pktChan) // shuts down sftpServerWorkers
   384  	wg.Wait()      // wait for all workers to exit
   385  
   386  	// close any still-open files
   387  	for handle, file := range svr.openFiles {
   388  		fmt.Fprintf(svr.debugStream, "sftp server file with handle %q left open: %v\n", handle, file.Name())
   389  		file.Close()
   390  	}
   391  	return err // error from recvPacket
   392  }
   393  
   394  type ider interface {
   395  	id() uint32
   396  }
   397  
   398  // The init packet has no ID, so we just return a zero-value ID
   399  func (p *sshFxInitPacket) id() uint32 { return 0 }
   400  
   401  type sshFxpStatResponse struct {
   402  	ID   uint32
   403  	info os.FileInfo
   404  }
   405  
   406  func (p *sshFxpStatResponse) marshalPacket() ([]byte, []byte, error) {
   407  	l := 4 + 1 + 4 // uint32(length) + byte(type) + uint32(id)
   408  
   409  	b := make([]byte, 4, l)
   410  	b = append(b, sshFxpAttrs)
   411  	b = marshalUint32(b, p.ID)
   412  
   413  	var payload []byte
   414  	payload = marshalFileInfo(payload, p.info)
   415  
   416  	return b, payload, nil
   417  }
   418  
   419  func (p *sshFxpStatResponse) MarshalBinary() ([]byte, error) {
   420  	header, payload, err := p.marshalPacket()
   421  	return append(header, payload...), err
   422  }
   423  
   424  var emptyFileStat = []interface{}{uint32(0)}
   425  
   426  func (p *sshFxpOpenPacket) readonly() bool {
   427  	return !p.hasPflags(sshFxfWrite)
   428  }
   429  
   430  func (p *sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
   431  	for _, f := range flags {
   432  		if p.Pflags&f == 0 {
   433  			return false
   434  		}
   435  	}
   436  	return true
   437  }
   438  
   439  func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket {
   440  	var osFlags int
   441  	if p.hasPflags(sshFxfRead, sshFxfWrite) {
   442  		osFlags |= os.O_RDWR
   443  	} else if p.hasPflags(sshFxfWrite) {
   444  		osFlags |= os.O_WRONLY
   445  	} else if p.hasPflags(sshFxfRead) {
   446  		osFlags |= os.O_RDONLY
   447  	} else {
   448  		// how are they opening?
   449  		return statusFromError(p.ID, syscall.EINVAL)
   450  	}
   451  
   452  	// Don't use O_APPEND flag as it conflicts with WriteAt.
   453  	// The sshFxfAppend flag is a no-op here as the client sends the offsets.
   454  
   455  	if p.hasPflags(sshFxfCreat) {
   456  		osFlags |= os.O_CREATE
   457  	}
   458  	if p.hasPflags(sshFxfTrunc) {
   459  		osFlags |= os.O_TRUNC
   460  	}
   461  	if p.hasPflags(sshFxfExcl) {
   462  		osFlags |= os.O_EXCL
   463  	}
   464  
   465  	f, err := os.OpenFile(svr.toLocalPath(p.Path), osFlags, 0o644)
   466  	if err != nil {
   467  		return statusFromError(p.ID, err)
   468  	}
   469  
   470  	handle := svr.nextHandle(f)
   471  	return &sshFxpHandlePacket{ID: p.ID, Handle: handle}
   472  }
   473  
   474  func (p *sshFxpReaddirPacket) respond(svr *Server) responsePacket {
   475  	f, ok := svr.getHandle(p.Handle)
   476  	if !ok {
   477  		return statusFromError(p.ID, EBADF)
   478  	}
   479  
   480  	dirents, err := f.Readdir(128)
   481  	if err != nil {
   482  		return statusFromError(p.ID, err)
   483  	}
   484  
   485  	idLookup := osIDLookup{}
   486  
   487  	ret := &sshFxpNamePacket{ID: p.ID}
   488  	for _, dirent := range dirents {
   489  		ret.NameAttrs = append(ret.NameAttrs, &sshFxpNameAttr{
   490  			Name:     dirent.Name(),
   491  			LongName: runLs(idLookup, dirent),
   492  			Attrs:    []interface{}{dirent},
   493  		})
   494  	}
   495  	return ret
   496  }
   497  
   498  func (p *sshFxpSetstatPacket) respond(svr *Server) responsePacket {
   499  	// additional unmarshalling is required for each possibility here
   500  	b := p.Attrs.([]byte)
   501  	var err error
   502  
   503  	p.Path = svr.toLocalPath(p.Path)
   504  
   505  	debug("setstat name \"%s\"", p.Path)
   506  	if (p.Flags & sshFileXferAttrSize) != 0 {
   507  		var size uint64
   508  		if size, b, err = unmarshalUint64Safe(b); err == nil {
   509  			err = os.Truncate(p.Path, int64(size))
   510  		}
   511  	}
   512  	if (p.Flags & sshFileXferAttrPermissions) != 0 {
   513  		var mode uint32
   514  		if mode, b, err = unmarshalUint32Safe(b); err == nil {
   515  			err = os.Chmod(p.Path, os.FileMode(mode))
   516  		}
   517  	}
   518  	if (p.Flags & sshFileXferAttrACmodTime) != 0 {
   519  		var atime uint32
   520  		var mtime uint32
   521  		if atime, b, err = unmarshalUint32Safe(b); err != nil {
   522  		} else if mtime, b, err = unmarshalUint32Safe(b); err != nil {
   523  		} else {
   524  			atimeT := time.Unix(int64(atime), 0)
   525  			mtimeT := time.Unix(int64(mtime), 0)
   526  			err = os.Chtimes(p.Path, atimeT, mtimeT)
   527  		}
   528  	}
   529  	if (p.Flags & sshFileXferAttrUIDGID) != 0 {
   530  		var uid uint32
   531  		var gid uint32
   532  		if uid, b, err = unmarshalUint32Safe(b); err != nil {
   533  		} else if gid, _, err = unmarshalUint32Safe(b); err != nil {
   534  		} else {
   535  			err = os.Chown(p.Path, int(uid), int(gid))
   536  		}
   537  	}
   538  
   539  	return statusFromError(p.ID, err)
   540  }
   541  
   542  func (p *sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
   543  	f, ok := svr.getHandle(p.Handle)
   544  	if !ok {
   545  		return statusFromError(p.ID, EBADF)
   546  	}
   547  
   548  	// additional unmarshalling is required for each possibility here
   549  	b := p.Attrs.([]byte)
   550  	var err error
   551  
   552  	debug("fsetstat name \"%s\"", f.Name())
   553  	if (p.Flags & sshFileXferAttrSize) != 0 {
   554  		var size uint64
   555  		if size, b, err = unmarshalUint64Safe(b); err == nil {
   556  			err = f.Truncate(int64(size))
   557  		}
   558  	}
   559  	if (p.Flags & sshFileXferAttrPermissions) != 0 {
   560  		var mode uint32
   561  		if mode, b, err = unmarshalUint32Safe(b); err == nil {
   562  			err = f.Chmod(os.FileMode(mode))
   563  		}
   564  	}
   565  	if (p.Flags & sshFileXferAttrACmodTime) != 0 {
   566  		var atime uint32
   567  		var mtime uint32
   568  		if atime, b, err = unmarshalUint32Safe(b); err != nil {
   569  		} else if mtime, b, err = unmarshalUint32Safe(b); err != nil {
   570  		} else {
   571  			atimeT := time.Unix(int64(atime), 0)
   572  			mtimeT := time.Unix(int64(mtime), 0)
   573  			err = os.Chtimes(f.Name(), atimeT, mtimeT)
   574  		}
   575  	}
   576  	if (p.Flags & sshFileXferAttrUIDGID) != 0 {
   577  		var uid uint32
   578  		var gid uint32
   579  		if uid, b, err = unmarshalUint32Safe(b); err != nil {
   580  		} else if gid, _, err = unmarshalUint32Safe(b); err != nil {
   581  		} else {
   582  			err = f.Chown(int(uid), int(gid))
   583  		}
   584  	}
   585  
   586  	return statusFromError(p.ID, err)
   587  }
   588  
   589  func statusFromError(id uint32, err error) *sshFxpStatusPacket {
   590  	ret := &sshFxpStatusPacket{
   591  		ID: id,
   592  		StatusError: StatusError{
   593  			// sshFXOk               = 0
   594  			// sshFXEOF              = 1
   595  			// sshFXNoSuchFile       = 2 ENOENT
   596  			// sshFXPermissionDenied = 3
   597  			// sshFXFailure          = 4
   598  			// sshFXBadMessage       = 5
   599  			// sshFXNoConnection     = 6
   600  			// sshFXConnectionLost   = 7
   601  			// sshFXOPUnsupported    = 8
   602  			Code: sshFxOk,
   603  		},
   604  	}
   605  	if err == nil {
   606  		return ret
   607  	}
   608  
   609  	debug("statusFromError: error is %T %#v", err, err)
   610  	ret.StatusError.Code = sshFxFailure
   611  	ret.StatusError.msg = err.Error()
   612  
   613  	if os.IsNotExist(err) {
   614  		ret.StatusError.Code = sshFxNoSuchFile
   615  		return ret
   616  	}
   617  	if code, ok := translateSyscallError(err); ok {
   618  		ret.StatusError.Code = code
   619  		return ret
   620  	}
   621  
   622  	if errors.Is(err, io.EOF) {
   623  		ret.StatusError.Code = sshFxEOF
   624  		return ret
   625  	}
   626  
   627  	var e fxerr
   628  	if errors.As(err, &e) {
   629  		ret.StatusError.Code = uint32(e)
   630  		return ret
   631  	}
   632  
   633  	return ret
   634  }