| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 | 
							- // Copyright (C) 2019-2023 Nicola Murino
 
- //
 
- // This program is free software: you can redistribute it and/or modify
 
- // it under the terms of the GNU Affero General Public License as published
 
- // by the Free Software Foundation, version 3.
 
- //
 
- // This program is distributed in the hope that it will be useful,
 
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
 
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 
- // GNU Affero General Public License for more details.
 
- //
 
- // You should have received a copy of the GNU Affero General Public License
 
- // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
- package sftpd
 
- import (
 
- 	"io"
 
- 	"os"
 
- 	"path"
 
- 	"strings"
 
- 	"time"
 
- 	"github.com/pkg/sftp"
 
- 	"github.com/drakkan/sftpgo/v2/internal/vfs"
 
- )
 
- // Middleware defines the interface for SFTP middlewares
 
- type Middleware interface {
 
- 	sftp.FileReader
 
- 	sftp.FileWriter
 
- 	sftp.OpenFileWriter
 
- 	sftp.FileCmder
 
- 	sftp.StatVFSFileCmder
 
- 	sftp.FileLister
 
- 	sftp.LstatFileLister
 
- }
 
- type prefixMatch uint8
 
- const (
 
- 	pathContainsPrefix prefixMatch = iota
 
- 	pathIsPrefixParent
 
- 	pathDiverged
 
- 	methodList = "List"
 
- 	methodStat = "Stat"
 
- )
 
- type prefixMiddleware struct {
 
- 	prefix string
 
- 	next   Middleware
 
- }
 
- func newPrefixMiddleware(prefix string, next Middleware) Middleware {
 
- 	return &prefixMiddleware{
 
- 		prefix: prefix,
 
- 		next:   next,
 
- 	}
 
- }
 
- func (p *prefixMiddleware) Lstat(request *sftp.Request) (sftp.ListerAt, error) {
 
- 	switch getPrefixHierarchy(p.prefix, request.Filepath) {
 
- 	case pathContainsPrefix:
 
- 		request.Filepath, _ = p.removeFolderPrefix(request.Filepath)
 
- 		return p.next.Lstat(request)
 
- 	case pathIsPrefixParent:
 
- 		return listerAt([]os.FileInfo{
 
- 			vfs.NewFileInfo(request.Filepath, true, 0, time.Unix(0, 0), false),
 
- 		}), nil
 
- 	default:
 
- 		return nil, sftp.ErrSSHFxPermissionDenied
 
- 	}
 
- }
 
- func (p *prefixMiddleware) OpenFile(request *sftp.Request) (sftp.WriterAtReaderAt, error) {
 
- 	switch getPrefixHierarchy(p.prefix, request.Filepath) {
 
- 	case pathContainsPrefix:
 
- 		request.Filepath, _ = p.removeFolderPrefix(request.Filepath)
 
- 		return p.next.OpenFile(request)
 
- 	default:
 
- 		return nil, sftp.ErrSSHFxPermissionDenied
 
- 	}
 
- }
 
- func (p *prefixMiddleware) Filelist(request *sftp.Request) (sftp.ListerAt, error) {
 
- 	switch getPrefixHierarchy(p.prefix, request.Filepath) {
 
- 	case pathContainsPrefix:
 
- 		request.Filepath, _ = p.removeFolderPrefix(request.Filepath)
 
- 		return p.next.Filelist(request)
 
- 	case pathIsPrefixParent:
 
- 		switch request.Method {
 
- 		case methodList:
 
- 			modTime := time.Unix(0, 0)
 
- 			fileName := p.nextListFolder(request.Filepath)
 
- 			files := make([]os.FileInfo, 0, 3)
 
- 			files = append(files, vfs.NewFileInfo(".", true, 0, modTime, false))
 
- 			if request.Filepath != "/" {
 
- 				files = append(files, vfs.NewFileInfo("..", true, 0, modTime, false))
 
- 			}
 
- 			files = append(files, vfs.NewFileInfo(fileName, true, 0, modTime, false))
 
- 			return listerAt(files), nil
 
- 		case methodStat:
 
- 			return listerAt([]os.FileInfo{
 
- 				vfs.NewFileInfo(request.Filepath, true, 0, time.Unix(0, 0), false),
 
- 			}), nil
 
- 		default:
 
- 			return nil, sftp.ErrSSHFxOpUnsupported
 
- 		}
 
- 	default:
 
- 		return nil, sftp.ErrSSHFxPermissionDenied
 
- 	}
 
- }
 
- func (p *prefixMiddleware) Filewrite(request *sftp.Request) (io.WriterAt, error) {
 
- 	switch getPrefixHierarchy(p.prefix, request.Filepath) {
 
- 	case pathContainsPrefix:
 
- 		// forward to next handler
 
- 		request.Filepath, _ = p.removeFolderPrefix(request.Filepath)
 
- 		return p.next.Filewrite(request)
 
- 	default:
 
- 		return nil, sftp.ErrSSHFxPermissionDenied
 
- 	}
 
- }
 
- func (p *prefixMiddleware) Fileread(request *sftp.Request) (io.ReaderAt, error) {
 
- 	switch getPrefixHierarchy(p.prefix, request.Filepath) {
 
- 	case pathContainsPrefix:
 
- 		request.Filepath, _ = p.removeFolderPrefix(request.Filepath)
 
- 		return p.next.Fileread(request)
 
- 	default:
 
- 		return nil, sftp.ErrSSHFxPermissionDenied
 
- 	}
 
- }
 
- func (p *prefixMiddleware) Filecmd(request *sftp.Request) error {
 
- 	switch request.Method {
 
- 	case "Rename", "Symlink":
 
- 		if getPrefixHierarchy(p.prefix, request.Filepath) == pathContainsPrefix &&
 
- 			getPrefixHierarchy(p.prefix, request.Target) == pathContainsPrefix {
 
- 			request.Filepath, _ = p.removeFolderPrefix(request.Filepath)
 
- 			request.Target, _ = p.removeFolderPrefix(request.Target)
 
- 			return p.next.Filecmd(request)
 
- 		}
 
- 		return sftp.ErrSSHFxPermissionDenied
 
- 	// commands have a source and destination (file path and target path)
 
- 	case "Setstat", "Rmdir", "Mkdir", "Remove":
 
- 		// commands just the file path
 
- 		if getPrefixHierarchy(p.prefix, request.Filepath) == pathContainsPrefix {
 
- 			request.Filepath, _ = p.removeFolderPrefix(request.Filepath)
 
- 			return p.next.Filecmd(request)
 
- 		}
 
- 		return sftp.ErrSSHFxPermissionDenied
 
- 	default:
 
- 		return sftp.ErrSSHFxOpUnsupported
 
- 	}
 
- }
 
- func (p *prefixMiddleware) StatVFS(request *sftp.Request) (*sftp.StatVFS, error) {
 
- 	switch getPrefixHierarchy(p.prefix, request.Filepath) {
 
- 	case pathContainsPrefix:
 
- 		// forward to next handler
 
- 		request.Filepath, _ = p.removeFolderPrefix(request.Filepath)
 
- 		return p.next.StatVFS(request)
 
- 	default:
 
- 		return nil, sftp.ErrSSHFxPermissionDenied
 
- 	}
 
- }
 
- func (p *prefixMiddleware) nextListFolder(requestPath string) string {
 
- 	cleanPath := path.Clean(`/` + requestPath)
 
- 	cleanPrefix := path.Clean(`/` + p.prefix)
 
- 	fileName := cleanPrefix[len(cleanPath):]
 
- 	fileName = strings.TrimLeft(fileName, `/`)
 
- 	slashIndex := strings.Index(fileName, `/`)
 
- 	if slashIndex > 0 {
 
- 		return fileName[0:slashIndex]
 
- 	}
 
- 	return fileName
 
- }
 
- func (p *prefixMiddleware) containsPrefix(virtualPath string) bool {
 
- 	if !path.IsAbs(virtualPath) {
 
- 		virtualPath = path.Clean(`/` + virtualPath)
 
- 	}
 
- 	if p.prefix == `/` || p.prefix == `` {
 
- 		return true
 
- 	} else if p.prefix == virtualPath {
 
- 		return true
 
- 	}
 
- 	return strings.HasPrefix(virtualPath, p.prefix+`/`)
 
- }
 
- func (p *prefixMiddleware) removeFolderPrefix(virtualPath string) (string, bool) {
 
- 	if p.prefix == `/` || p.prefix == `` {
 
- 		return virtualPath, true
 
- 	}
 
- 	virtualPath = path.Clean(`/` + virtualPath)
 
- 	if p.containsPrefix(virtualPath) {
 
- 		effectivePath := virtualPath[len(p.prefix):]
 
- 		if effectivePath == `` {
 
- 			effectivePath = `/`
 
- 		}
 
- 		return effectivePath, true
 
- 	}
 
- 	return virtualPath, false
 
- }
 
- func getPrefixHierarchy(prefix, virtualPath string) prefixMatch {
 
- 	prefixSplit := strings.Split(path.Clean(`/`+prefix), `/`)
 
- 	pathSplit := strings.Split(path.Clean(`/`+virtualPath), `/`)
 
- 	for {
 
- 		// stop if either slice is empty of the current head elements do not match
 
- 		if len(prefixSplit) == 0 || len(pathSplit) == 0 ||
 
- 			prefixSplit[0] != pathSplit[0] {
 
- 			break
 
- 		}
 
- 		prefixSplit = prefixSplit[1:]
 
- 		pathSplit = pathSplit[1:]
 
- 	}
 
- 	// The entire Prefix is included in Test Path
 
- 	// Example: Prefix (/files) with Test Path (/files/test.csv)
 
- 	if len(prefixSplit) == 0 ||
 
- 		(len(prefixSplit) == 1 && prefixSplit[0] == ``) {
 
- 		return pathContainsPrefix
 
- 	}
 
- 	// Test Path is part of the Prefix Hierarchy
 
- 	// Example: Prefix (/files) with Test Path (/)
 
- 	if len(pathSplit) == 0 ||
 
- 		(len(pathSplit) == 1 && pathSplit[0] == ``) {
 
- 		return pathIsPrefixParent
 
- 	}
 
- 	// Test Path is not with the Prefix Hierarchy
 
- 	// Example: Prefix (/files) with Test Path (/files2)
 
- 	return pathDiverged
 
- }
 
 
  |