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