1package sftp
2
3// sftp server counterpart
4
5import (
6	"encoding"
7	"fmt"
8	"io"
9	"io/ioutil"
10	"os"
11	"path/filepath"
12	"strconv"
13	"sync"
14	"syscall"
15	"time"
16
17	"github.com/pkg/errors"
18)
19
20const (
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	pktChan       chan rxPacket
33	openFiles     map[string]*os.File
34	openFilesLock sync.RWMutex
35	handleCount   int
36	maxTxPacket   uint32
37}
38
39func (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
48func (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 syscall.EBADF
57}
58
59func (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
66type serverRespondablePacket interface {
67	encoding.BinaryUnmarshaler
68	id() uint32
69	respond(svr *Server) error
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.
77func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error) {
78	s := &Server{
79		serverConn: serverConn{
80			conn: conn{
81				Reader:      rwc,
82				WriteCloser: rwc,
83			},
84		},
85		debugStream: ioutil.Discard,
86		pktChan:     make(chan rxPacket, sftpServerWorkerCount),
87		openFiles:   make(map[string]*os.File),
88		maxTxPacket: 1 << 15,
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.
101type ServerOption func(*Server) error
102
103// WithDebug enables Server debugging output to the supplied io.Writer.
104func 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.
112func ReadOnly() ServerOption {
113	return func(s *Server) error {
114		s.readOnly = true
115		return nil
116	}
117}
118
119type rxPacket struct {
120	pktType  fxp
121	pktBytes []byte
122}
123
124// Up to N parallel servers
125func (svr *Server) sftpServerWorker() error {
126	for p := range svr.pktChan {
127		var pkt interface {
128			encoding.BinaryUnmarshaler
129			id() uint32
130		}
131		var readonly = true
132		switch p.pktType {
133		case ssh_FXP_INIT:
134			pkt = &sshFxInitPacket{}
135		case ssh_FXP_LSTAT:
136			pkt = &sshFxpLstatPacket{}
137		case ssh_FXP_OPEN:
138			pkt = &sshFxpOpenPacket{}
139			// readonly handled specially below
140		case ssh_FXP_CLOSE:
141			pkt = &sshFxpClosePacket{}
142		case ssh_FXP_READ:
143			pkt = &sshFxpReadPacket{}
144		case ssh_FXP_WRITE:
145			pkt = &sshFxpWritePacket{}
146			readonly = false
147		case ssh_FXP_FSTAT:
148			pkt = &sshFxpFstatPacket{}
149		case ssh_FXP_SETSTAT:
150			pkt = &sshFxpSetstatPacket{}
151			readonly = false
152		case ssh_FXP_FSETSTAT:
153			pkt = &sshFxpFsetstatPacket{}
154			readonly = false
155		case ssh_FXP_OPENDIR:
156			pkt = &sshFxpOpendirPacket{}
157		case ssh_FXP_READDIR:
158			pkt = &sshFxpReaddirPacket{}
159		case ssh_FXP_REMOVE:
160			pkt = &sshFxpRemovePacket{}
161			readonly = false
162		case ssh_FXP_MKDIR:
163			pkt = &sshFxpMkdirPacket{}
164			readonly = false
165		case ssh_FXP_RMDIR:
166			pkt = &sshFxpRmdirPacket{}
167			readonly = false
168		case ssh_FXP_REALPATH:
169			pkt = &sshFxpRealpathPacket{}
170		case ssh_FXP_STAT:
171			pkt = &sshFxpStatPacket{}
172		case ssh_FXP_RENAME:
173			pkt = &sshFxpRenamePacket{}
174			readonly = false
175		case ssh_FXP_READLINK:
176			pkt = &sshFxpReadlinkPacket{}
177		case ssh_FXP_SYMLINK:
178			pkt = &sshFxpSymlinkPacket{}
179			readonly = false
180		case ssh_FXP_EXTENDED:
181			pkt = &sshFxpExtendedPacket{}
182		default:
183			return errors.Errorf("unhandled packet type: %s", p.pktType)
184		}
185		if err := pkt.UnmarshalBinary(p.pktBytes); err != nil {
186			return err
187		}
188
189		// handle FXP_OPENDIR specially
190		switch pkt := pkt.(type) {
191		case *sshFxpOpenPacket:
192			readonly = pkt.readonly()
193		case *sshFxpExtendedPacket:
194			readonly = pkt.SpecificPacket.readonly()
195		}
196
197		// If server is operating read-only and a write operation is requested,
198		// return permission denied
199		if !readonly && svr.readOnly {
200			if err := svr.sendError(pkt, syscall.EPERM); err != nil {
201				return errors.Wrap(err, "failed to send read only packet response")
202			}
203			continue
204		}
205
206		if err := handlePacket(svr, pkt); err != nil {
207			return err
208		}
209	}
210	return nil
211}
212
213func handlePacket(s *Server, p interface{}) error {
214	switch p := p.(type) {
215	case *sshFxInitPacket:
216		return s.sendPacket(sshFxVersionPacket{sftpProtocolVersion, nil})
217	case *sshFxpStatPacket:
218		// stat the requested file
219		info, err := os.Stat(p.Path)
220		if err != nil {
221			return s.sendError(p, err)
222		}
223		return s.sendPacket(sshFxpStatResponse{
224			ID:   p.ID,
225			info: info,
226		})
227	case *sshFxpLstatPacket:
228		// stat the requested file
229		info, err := os.Lstat(p.Path)
230		if err != nil {
231			return s.sendError(p, err)
232		}
233		return s.sendPacket(sshFxpStatResponse{
234			ID:   p.ID,
235			info: info,
236		})
237	case *sshFxpFstatPacket:
238		f, ok := s.getHandle(p.Handle)
239		if !ok {
240			return s.sendError(p, syscall.EBADF)
241		}
242
243		info, err := f.Stat()
244		if err != nil {
245			return s.sendError(p, err)
246		}
247
248		return s.sendPacket(sshFxpStatResponse{
249			ID:   p.ID,
250			info: info,
251		})
252	case *sshFxpMkdirPacket:
253		// TODO FIXME: ignore flags field
254		err := os.Mkdir(p.Path, 0755)
255		return s.sendError(p, err)
256	case *sshFxpRmdirPacket:
257		err := os.Remove(p.Path)
258		return s.sendError(p, err)
259	case *sshFxpRemovePacket:
260		err := os.Remove(p.Filename)
261		return s.sendError(p, err)
262	case *sshFxpRenamePacket:
263		err := os.Rename(p.Oldpath, p.Newpath)
264		return s.sendError(p, err)
265	case *sshFxpSymlinkPacket:
266		err := os.Symlink(p.Targetpath, p.Linkpath)
267		return s.sendError(p, err)
268	case *sshFxpClosePacket:
269		return s.sendError(p, s.closeHandle(p.Handle))
270	case *sshFxpReadlinkPacket:
271		f, err := os.Readlink(p.Path)
272		if err != nil {
273			return s.sendError(p, err)
274		}
275
276		return s.sendPacket(sshFxpNamePacket{
277			ID: p.ID,
278			NameAttrs: []sshFxpNameAttr{{
279				Name:     f,
280				LongName: f,
281				Attrs:    emptyFileStat,
282			}},
283		})
284
285	case *sshFxpRealpathPacket:
286		f, err := filepath.Abs(p.Path)
287		if err != nil {
288			return s.sendError(p, err)
289		}
290		f = filepath.Clean(f)
291		return s.sendPacket(sshFxpNamePacket{
292			ID: p.ID,
293			NameAttrs: []sshFxpNameAttr{{
294				Name:     f,
295				LongName: f,
296				Attrs:    emptyFileStat,
297			}},
298		})
299	case *sshFxpOpendirPacket:
300		return sshFxpOpenPacket{
301			ID:     p.ID,
302			Path:   p.Path,
303			Pflags: ssh_FXF_READ,
304		}.respond(s)
305	case *sshFxpReadPacket:
306		f, ok := s.getHandle(p.Handle)
307		if !ok {
308			return s.sendError(p, syscall.EBADF)
309		}
310
311		data := make([]byte, clamp(p.Len, s.maxTxPacket))
312		n, err := f.ReadAt(data, int64(p.Offset))
313		if err != nil && (err != io.EOF || n == 0) {
314			return s.sendError(p, err)
315		}
316		return s.sendPacket(sshFxpDataPacket{
317			ID:     p.ID,
318			Length: uint32(n),
319			Data:   data[:n],
320		})
321	case *sshFxpWritePacket:
322		f, ok := s.getHandle(p.Handle)
323		if !ok {
324			return s.sendError(p, syscall.EBADF)
325		}
326
327		_, err := f.WriteAt(p.Data, int64(p.Offset))
328		return s.sendError(p, err)
329	case serverRespondablePacket:
330		err := p.respond(s)
331		return errors.Wrap(err, "pkt.respond failed")
332	default:
333		return errors.Errorf("unexpected packet type %T", p)
334	}
335}
336
337// Serve serves SFTP connections until the streams stop or the SFTP subsystem
338// is stopped.
339func (svr *Server) Serve() error {
340	var wg sync.WaitGroup
341	wg.Add(sftpServerWorkerCount)
342	for i := 0; i < sftpServerWorkerCount; i++ {
343		go func() {
344			defer wg.Done()
345			if err := svr.sftpServerWorker(); err != nil {
346				svr.conn.Close() // shuts down recvPacket
347			}
348		}()
349	}
350
351	var err error
352	var pktType uint8
353	var pktBytes []byte
354	for {
355		pktType, pktBytes, err = svr.recvPacket()
356		if err != nil {
357			break
358		}
359		svr.pktChan <- rxPacket{fxp(pktType), pktBytes}
360	}
361
362	close(svr.pktChan) // shuts down sftpServerWorkers
363	wg.Wait()          // wait for all workers to exit
364
365	// close any still-open files
366	for handle, file := range svr.openFiles {
367		fmt.Fprintf(svr.debugStream, "sftp server file with handle %q left open: %v\n", handle, file.Name())
368		file.Close()
369	}
370	return err // error from recvPacket
371}
372
373type id interface {
374	id() uint32
375}
376
377// The init packet has no ID, so we just return a zero-value ID
378func (p sshFxInitPacket) id() uint32 { return 0 }
379
380type sshFxpStatResponse struct {
381	ID   uint32
382	info os.FileInfo
383}
384
385func (p sshFxpStatResponse) MarshalBinary() ([]byte, error) {
386	b := []byte{ssh_FXP_ATTRS}
387	b = marshalUint32(b, p.ID)
388	b = marshalFileInfo(b, p.info)
389	return b, nil
390}
391
392var emptyFileStat = []interface{}{uint32(0)}
393
394func (p sshFxpOpenPacket) readonly() bool {
395	return !p.hasPflags(ssh_FXF_WRITE)
396}
397
398func (p sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
399	for _, f := range flags {
400		if p.Pflags&f == 0 {
401			return false
402		}
403	}
404	return true
405}
406
407func (p sshFxpOpenPacket) respond(svr *Server) error {
408	var osFlags int
409	if p.hasPflags(ssh_FXF_READ, ssh_FXF_WRITE) {
410		osFlags |= os.O_RDWR
411	} else if p.hasPflags(ssh_FXF_WRITE) {
412		osFlags |= os.O_WRONLY
413	} else if p.hasPflags(ssh_FXF_READ) {
414		osFlags |= os.O_RDONLY
415	} else {
416		// how are they opening?
417		return svr.sendError(p, syscall.EINVAL)
418	}
419
420	if p.hasPflags(ssh_FXF_APPEND) {
421		osFlags |= os.O_APPEND
422	}
423	if p.hasPflags(ssh_FXF_CREAT) {
424		osFlags |= os.O_CREATE
425	}
426	if p.hasPflags(ssh_FXF_TRUNC) {
427		osFlags |= os.O_TRUNC
428	}
429	if p.hasPflags(ssh_FXF_EXCL) {
430		osFlags |= os.O_EXCL
431	}
432
433	f, err := os.OpenFile(p.Path, osFlags, 0644)
434	if err != nil {
435		return svr.sendError(p, err)
436	}
437
438	handle := svr.nextHandle(f)
439	return svr.sendPacket(sshFxpHandlePacket{p.ID, handle})
440}
441
442func (p sshFxpReaddirPacket) respond(svr *Server) error {
443	f, ok := svr.getHandle(p.Handle)
444	if !ok {
445		return svr.sendError(p, syscall.EBADF)
446	}
447
448	dirname := f.Name()
449	dirents, err := f.Readdir(128)
450	if err != nil {
451		return svr.sendError(p, err)
452	}
453
454	ret := sshFxpNamePacket{ID: p.ID}
455	for _, dirent := range dirents {
456		ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{
457			Name:     dirent.Name(),
458			LongName: runLs(dirname, dirent),
459			Attrs:    []interface{}{dirent},
460		})
461	}
462	return svr.sendPacket(ret)
463}
464
465func (p sshFxpSetstatPacket) respond(svr *Server) error {
466	// additional unmarshalling is required for each possibility here
467	b := p.Attrs.([]byte)
468	var err error
469
470	debug("setstat name \"%s\"", p.Path)
471	if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 0 {
472		var size uint64
473		if size, b, err = unmarshalUint64Safe(b); err == nil {
474			err = os.Truncate(p.Path, int64(size))
475		}
476	}
477	if (p.Flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0 {
478		var mode uint32
479		if mode, b, err = unmarshalUint32Safe(b); err == nil {
480			err = os.Chmod(p.Path, os.FileMode(mode))
481		}
482	}
483	if (p.Flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0 {
484		var atime uint32
485		var mtime uint32
486		if atime, b, err = unmarshalUint32Safe(b); err != nil {
487		} else if mtime, b, err = unmarshalUint32Safe(b); err != nil {
488		} else {
489			atimeT := time.Unix(int64(atime), 0)
490			mtimeT := time.Unix(int64(mtime), 0)
491			err = os.Chtimes(p.Path, atimeT, mtimeT)
492		}
493	}
494	if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 {
495		var uid uint32
496		var gid uint32
497		if uid, b, err = unmarshalUint32Safe(b); err != nil {
498		} else if gid, b, err = unmarshalUint32Safe(b); err != nil {
499		} else {
500			err = os.Chown(p.Path, int(uid), int(gid))
501		}
502	}
503
504	return svr.sendError(p, err)
505}
506
507func (p sshFxpFsetstatPacket) respond(svr *Server) error {
508	f, ok := svr.getHandle(p.Handle)
509	if !ok {
510		return svr.sendError(p, syscall.EBADF)
511	}
512
513	// additional unmarshalling is required for each possibility here
514	b := p.Attrs.([]byte)
515	var err error
516
517	debug("fsetstat name \"%s\"", f.Name())
518	if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 0 {
519		var size uint64
520		if size, b, err = unmarshalUint64Safe(b); err == nil {
521			err = f.Truncate(int64(size))
522		}
523	}
524	if (p.Flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0 {
525		var mode uint32
526		if mode, b, err = unmarshalUint32Safe(b); err == nil {
527			err = f.Chmod(os.FileMode(mode))
528		}
529	}
530	if (p.Flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0 {
531		var atime uint32
532		var mtime uint32
533		if atime, b, err = unmarshalUint32Safe(b); err != nil {
534		} else if mtime, b, err = unmarshalUint32Safe(b); err != nil {
535		} else {
536			atimeT := time.Unix(int64(atime), 0)
537			mtimeT := time.Unix(int64(mtime), 0)
538			err = os.Chtimes(f.Name(), atimeT, mtimeT)
539		}
540	}
541	if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 {
542		var uid uint32
543		var gid uint32
544		if uid, b, err = unmarshalUint32Safe(b); err != nil {
545		} else if gid, b, err = unmarshalUint32Safe(b); err != nil {
546		} else {
547			err = f.Chown(int(uid), int(gid))
548		}
549	}
550
551	return svr.sendError(p, err)
552}
553
554// translateErrno translates a syscall error number to a SFTP error code.
555func translateErrno(errno syscall.Errno) uint32 {
556	switch errno {
557	case 0:
558		return ssh_FX_OK
559	case syscall.ENOENT:
560		return ssh_FX_NO_SUCH_FILE
561	case syscall.EPERM:
562		return ssh_FX_PERMISSION_DENIED
563	}
564
565	return ssh_FX_FAILURE
566}
567
568func statusFromError(p id, err error) sshFxpStatusPacket {
569	ret := sshFxpStatusPacket{
570		ID: p.id(),
571		StatusError: StatusError{
572			// ssh_FX_OK                = 0
573			// ssh_FX_EOF               = 1
574			// ssh_FX_NO_SUCH_FILE      = 2 ENOENT
575			// ssh_FX_PERMISSION_DENIED = 3
576			// ssh_FX_FAILURE           = 4
577			// ssh_FX_BAD_MESSAGE       = 5
578			// ssh_FX_NO_CONNECTION     = 6
579			// ssh_FX_CONNECTION_LOST   = 7
580			// ssh_FX_OP_UNSUPPORTED    = 8
581			Code: ssh_FX_OK,
582		},
583	}
584	if err != nil {
585		debug("statusFromError: error is %T %#v", err, err)
586		ret.StatusError.Code = ssh_FX_FAILURE
587		ret.StatusError.msg = err.Error()
588		if err == io.EOF {
589			ret.StatusError.Code = ssh_FX_EOF
590		} else if errno, ok := err.(syscall.Errno); ok {
591			ret.StatusError.Code = translateErrno(errno)
592		} else if pathError, ok := err.(*os.PathError); ok {
593			debug("statusFromError: error is %T %#v", pathError.Err, pathError.Err)
594			if errno, ok := pathError.Err.(syscall.Errno); ok {
595				ret.StatusError.Code = translateErrno(errno)
596			}
597		}
598	}
599	return ret
600}
601
602func clamp(v, max uint32) uint32 {
603	if v > max {
604		return max
605	}
606	return v
607}
608