123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- // Copyright (C) 2019 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
- }
|