package sftp

// sftp server counterpart

import (
	
	
	
	
	
	
	
	
	
	
	
)

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 https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
type Server struct {
	*serverConn
	debugStream   io.Writer
	readOnly      bool
	pktMgr        *packetManager
	openFiles     map[string]*os.File
	openFilesLock sync.RWMutex
	handleCount   int
	workDir       string
}

func ( *Server) ( *os.File) string {
	.openFilesLock.Lock()
	defer .openFilesLock.Unlock()
	.handleCount++
	 := strconv.Itoa(.handleCount)
	.openFiles[] = 
	return 
}

func ( *Server) ( string) error {
	.openFilesLock.Lock()
	defer .openFilesLock.Unlock()
	if ,  := .openFiles[];  {
		delete(.openFiles, )
		return .Close()
	}

	return EBADF
}

func ( *Server) ( string) (*os.File, bool) {
	.openFilesLock.RLock()
	defer .openFilesLock.RUnlock()
	,  := .openFiles[]
	return , 
}

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 ( io.ReadWriteCloser,  ...ServerOption) (*Server, error) {
	 := &serverConn{
		conn: conn{
			Reader:      ,
			WriteCloser: ,
		},
	}
	 := &Server{
		serverConn:  ,
		debugStream: ioutil.Discard,
		pktMgr:      newPktMgr(),
		openFiles:   make(map[string]*os.File),
	}

	for ,  := range  {
		if  := ();  != nil {
			return nil, 
		}
	}

	return , 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 ( io.Writer) ServerOption {
	return func( *Server) error {
		.debugStream = 
		return nil
	}
}

// ReadOnly configures a Server to serve files in read-only mode.
func () ServerOption {
	return func( *Server) error {
		.readOnly = true
		return nil
	}
}

// WithAllocator enable the allocator.
// After processing a packet we keep in memory the allocated slices
// and we reuse them for new packets.
// The allocator is experimental
func () ServerOption {
	return func( *Server) error {
		 := newAllocator()
		.pktMgr.alloc = 
		.conn.alloc = 
		return nil
	}
}

// WithServerWorkingDirectory sets a working directory to use as base
// for relative paths.
// If unset the default is current working directory (os.Getwd).
func ( string) ServerOption {
	return func( *Server) error {
		.workDir = cleanPath()
		return nil
	}
}

type rxPacket struct {
	pktType  fxp
	pktBytes []byte
}

// Up to N parallel servers
func ( *Server) ( chan orderedRequest) error {
	for  := range  {
		// readonly checks
		 := true
		switch pkt := .requestPacket.(type) {
		case notReadOnly:
			 = false
		case *sshFxpOpenPacket:
			 = .readonly()
		case *sshFxpExtendedPacket:
			 = .readonly()
		}

		// If server is operating read-only and a write operation is requested,
		// return permission denied
		if ! && .readOnly {
			.pktMgr.readyPacket(
				.pktMgr.newOrderedResponse(statusFromError(.id(), syscall.EPERM), .orderID()),
			)
			continue
		}

		if  := handlePacket(, );  != nil {
			return 
		}
	}
	return nil
}

func handlePacket( *Server,  orderedRequest) error {
	var  responsePacket
	 := .orderID()
	switch p := .requestPacket.(type) {
	case *sshFxInitPacket:
		 = &sshFxVersionPacket{
			Version:    sftpProtocolVersion,
			Extensions: sftpExtensions,
		}
	case *sshFxpStatPacket:
		// stat the requested file
		,  := os.Stat(.toLocalPath(.Path))
		 = &sshFxpStatResponse{
			ID:   .ID,
			info: ,
		}
		if  != nil {
			 = statusFromError(.ID, )
		}
	case *sshFxpLstatPacket:
		// stat the requested file
		,  := os.Lstat(.toLocalPath(.Path))
		 = &sshFxpStatResponse{
			ID:   .ID,
			info: ,
		}
		if  != nil {
			 = statusFromError(.ID, )
		}
	case *sshFxpFstatPacket:
		,  := .getHandle(.Handle)
		var  error = EBADF
		var  os.FileInfo
		if  {
			,  = .Stat()
			 = &sshFxpStatResponse{
				ID:   .ID,
				info: ,
			}
		}
		if  != nil {
			 = statusFromError(.ID, )
		}
	case *sshFxpMkdirPacket:
		// TODO FIXME: ignore flags field
		 := os.Mkdir(.toLocalPath(.Path), 0o755)
		 = statusFromError(.ID, )
	case *sshFxpRmdirPacket:
		 := os.Remove(.toLocalPath(.Path))
		 = statusFromError(.ID, )
	case *sshFxpRemovePacket:
		 := os.Remove(.toLocalPath(.Filename))
		 = statusFromError(.ID, )
	case *sshFxpRenamePacket:
		 := os.Rename(.toLocalPath(.Oldpath), .toLocalPath(.Newpath))
		 = statusFromError(.ID, )
	case *sshFxpSymlinkPacket:
		 := os.Symlink(.toLocalPath(.Targetpath), .toLocalPath(.Linkpath))
		 = statusFromError(.ID, )
	case *sshFxpClosePacket:
		 = statusFromError(.ID, .closeHandle(.Handle))
	case *sshFxpReadlinkPacket:
		,  := os.Readlink(.toLocalPath(.Path))
		 = &sshFxpNamePacket{
			ID: .ID,
			NameAttrs: []*sshFxpNameAttr{
				{
					Name:     ,
					LongName: ,
					Attrs:    emptyFileStat,
				},
			},
		}
		if  != nil {
			 = statusFromError(.ID, )
		}
	case *sshFxpRealpathPacket:
		,  := filepath.Abs(.toLocalPath(.Path))
		 = cleanPath()
		 = &sshFxpNamePacket{
			ID: .ID,
			NameAttrs: []*sshFxpNameAttr{
				{
					Name:     ,
					LongName: ,
					Attrs:    emptyFileStat,
				},
			},
		}
		if  != nil {
			 = statusFromError(.ID, )
		}
	case *sshFxpOpendirPacket:
		 := .toLocalPath(.Path)

		if ,  := os.Stat();  != nil {
			 = statusFromError(.ID, )
		} else if !.IsDir() {
			 = statusFromError(.ID, &os.PathError{
				Path: , Err: syscall.ENOTDIR,
			})
		} else {
			 = (&sshFxpOpenPacket{
				ID:     .ID,
				Path:   .Path,
				Pflags: sshFxfRead,
			}).respond()
		}
	case *sshFxpReadPacket:
		var  error = EBADF
		,  := .getHandle(.Handle)
		if  {
			 = nil
			 := .getDataSlice(.pktMgr.alloc, )
			,  := .ReadAt(, int64(.Offset))
			if  != nil && ( != io.EOF ||  == 0) {
				 = 
			}
			 = &sshFxpDataPacket{
				ID:     .ID,
				Length: uint32(),
				Data:   [:],
				// do not use data[:n:n] here to clamp the capacity, we allocated extra capacity above to avoid reallocations
			}
		}
		if  != nil {
			 = statusFromError(.ID, )
		}

	case *sshFxpWritePacket:
		,  := .getHandle(.Handle)
		var  error = EBADF
		if  {
			_,  = .WriteAt(.Data, int64(.Offset))
		}
		 = statusFromError(.ID, )
	case *sshFxpExtendedPacket:
		if .SpecificPacket == nil {
			 = statusFromError(.ID, ErrSSHFxOpUnsupported)
		} else {
			 = .respond()
		}
	case serverRespondablePacket:
		 = .respond()
	default:
		return fmt.Errorf("unexpected packet type %T", )
	}

	.pktMgr.readyPacket(.pktMgr.newOrderedResponse(, ))
	return nil
}

// Serve serves SFTP connections until the streams stop or the SFTP subsystem
// is stopped. It returns nil if the server exits cleanly.
func ( *Server) () error {
	defer func() {
		if .pktMgr.alloc != nil {
			.pktMgr.alloc.Free()
		}
	}()
	var  sync.WaitGroup
	 := func( chan orderedRequest) {
		.Add(1)
		go func() {
			defer .Done()
			if  := .sftpServerWorker();  != nil {
				.conn.Close() // shuts down recvPacket
			}
		}()
	}
	 := .pktMgr.workerChan()

	var  error
	var  requestPacket
	var  uint8
	var  []byte
	for {
		, ,  = .serverConn.recvPacket(.pktMgr.getNextOrderID())
		if  != nil {
			// Check whether the connection terminated cleanly in-between packets.
			if  == io.EOF {
				 = nil
			}
			// we don't care about releasing allocated pages here, the server will quit and the allocator freed
			break
		}

		,  = makePacket(rxPacket{fxp(), })
		if  != nil {
			switch {
			case errors.Is(, 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", )
				.conn.Close() // shuts down recvPacket
				break
			}
		}

		 <- .pktMgr.newOrderedRequest()
	}

	close() // shuts down sftpServerWorkers
	.Wait()      // wait for all workers to exit

	// close any still-open files
	for ,  := range .openFiles {
		fmt.Fprintf(.debugStream, "sftp server file with handle %q left open: %v\n", , .Name())
		.Close()
	}
	return  // error from recvPacket
}

type ider interface {
	id() uint32
}

// The init packet has no ID, so we just return a zero-value ID
func ( *sshFxInitPacket) () uint32 { return 0 }

type sshFxpStatResponse struct {
	ID   uint32
	info os.FileInfo
}

func ( *sshFxpStatResponse) () ([]byte, []byte, error) {
	 := 4 + 1 + 4 // uint32(length) + byte(type) + uint32(id)

	 := make([]byte, 4, )
	 = append(, sshFxpAttrs)
	 = marshalUint32(, .ID)

	var  []byte
	 = marshalFileInfo(, .info)

	return , , nil
}

func ( *sshFxpStatResponse) () ([]byte, error) {
	, ,  := .marshalPacket()
	return append(, ...), 
}

var emptyFileStat = []interface{}{uint32(0)}

func ( *sshFxpOpenPacket) () bool {
	return !.hasPflags(sshFxfWrite)
}

func ( *sshFxpOpenPacket) ( ...uint32) bool {
	for ,  := range  {
		if .Pflags& == 0 {
			return false
		}
	}
	return true
}

func ( *sshFxpOpenPacket) ( *Server) responsePacket {
	var  int
	if .hasPflags(sshFxfRead, sshFxfWrite) {
		 |= os.O_RDWR
	} else if .hasPflags(sshFxfWrite) {
		 |= os.O_WRONLY
	} else if .hasPflags(sshFxfRead) {
		 |= os.O_RDONLY
	} else {
		// how are they opening?
		return statusFromError(.ID, 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 .hasPflags(sshFxfCreat) {
		 |= os.O_CREATE
	}
	if .hasPflags(sshFxfTrunc) {
		 |= os.O_TRUNC
	}
	if .hasPflags(sshFxfExcl) {
		 |= os.O_EXCL
	}

	,  := os.OpenFile(.toLocalPath(.Path), , 0o644)
	if  != nil {
		return statusFromError(.ID, )
	}

	 := .nextHandle()
	return &sshFxpHandlePacket{ID: .ID, Handle: }
}

func ( *sshFxpReaddirPacket) ( *Server) responsePacket {
	,  := .getHandle(.Handle)
	if ! {
		return statusFromError(.ID, EBADF)
	}

	,  := .Readdir(128)
	if  != nil {
		return statusFromError(.ID, )
	}

	 := osIDLookup{}

	 := &sshFxpNamePacket{ID: .ID}
	for ,  := range  {
		.NameAttrs = append(.NameAttrs, &sshFxpNameAttr{
			Name:     .Name(),
			LongName: runLs(, ),
			Attrs:    []interface{}{},
		})
	}
	return 
}

func ( *sshFxpSetstatPacket) ( *Server) responsePacket {
	// additional unmarshalling is required for each possibility here
	 := .Attrs.([]byte)
	var  error

	.Path = .toLocalPath(.Path)

	debug("setstat name \"%s\"", .Path)
	if (.Flags & sshFileXferAttrSize) != 0 {
		var  uint64
		if , ,  = unmarshalUint64Safe();  == nil {
			 = os.Truncate(.Path, int64())
		}
	}
	if (.Flags & sshFileXferAttrPermissions) != 0 {
		var  uint32
		if , ,  = unmarshalUint32Safe();  == nil {
			 = os.Chmod(.Path, os.FileMode())
		}
	}
	if (.Flags & sshFileXferAttrACmodTime) != 0 {
		var  uint32
		var  uint32
		if , ,  = unmarshalUint32Safe();  != nil {
		} else if , ,  = unmarshalUint32Safe();  != nil {
		} else {
			 := time.Unix(int64(), 0)
			 := time.Unix(int64(), 0)
			 = os.Chtimes(.Path, , )
		}
	}
	if (.Flags & sshFileXferAttrUIDGID) != 0 {
		var  uint32
		var  uint32
		if , ,  = unmarshalUint32Safe();  != nil {
		} else if , _,  = unmarshalUint32Safe();  != nil {
		} else {
			 = os.Chown(.Path, int(), int())
		}
	}

	return statusFromError(.ID, )
}

func ( *sshFxpFsetstatPacket) ( *Server) responsePacket {
	,  := .getHandle(.Handle)
	if ! {
		return statusFromError(.ID, EBADF)
	}

	// additional unmarshalling is required for each possibility here
	 := .Attrs.([]byte)
	var  error

	debug("fsetstat name \"%s\"", .Name())
	if (.Flags & sshFileXferAttrSize) != 0 {
		var  uint64
		if , ,  = unmarshalUint64Safe();  == nil {
			 = .Truncate(int64())
		}
	}
	if (.Flags & sshFileXferAttrPermissions) != 0 {
		var  uint32
		if , ,  = unmarshalUint32Safe();  == nil {
			 = .Chmod(os.FileMode())
		}
	}
	if (.Flags & sshFileXferAttrACmodTime) != 0 {
		var  uint32
		var  uint32
		if , ,  = unmarshalUint32Safe();  != nil {
		} else if , ,  = unmarshalUint32Safe();  != nil {
		} else {
			 := time.Unix(int64(), 0)
			 := time.Unix(int64(), 0)
			 = os.Chtimes(.Name(), , )
		}
	}
	if (.Flags & sshFileXferAttrUIDGID) != 0 {
		var  uint32
		var  uint32
		if , ,  = unmarshalUint32Safe();  != nil {
		} else if , _,  = unmarshalUint32Safe();  != nil {
		} else {
			 = .Chown(int(), int())
		}
	}

	return statusFromError(.ID, )
}

func statusFromError( uint32,  error) *sshFxpStatusPacket {
	 := &sshFxpStatusPacket{
		ID: ,
		StatusError: StatusError{
			// sshFXOk               = 0
			// sshFXEOF              = 1
			// sshFXNoSuchFile       = 2 ENOENT
			// sshFXPermissionDenied = 3
			// sshFXFailure          = 4
			// sshFXBadMessage       = 5
			// sshFXNoConnection     = 6
			// sshFXConnectionLost   = 7
			// sshFXOPUnsupported    = 8
			Code: sshFxOk,
		},
	}
	if  == nil {
		return 
	}

	debug("statusFromError: error is %T %#v", , )
	.StatusError.Code = sshFxFailure
	.StatusError.msg = .Error()

	if os.IsNotExist() {
		.StatusError.Code = sshFxNoSuchFile
		return 
	}
	if ,  := translateSyscallError();  {
		.StatusError.Code = 
		return 
	}

	if errors.Is(, io.EOF) {
		.StatusError.Code = sshFxEOF
		return 
	}

	var  fxerr
	if errors.As(, &) {
		.StatusError.Code = uint32()
		return 
	}

	return 
}