diff --git a/go.mod b/go.mod index ff1bc967..c6f625b4 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,6 @@ require ( github.com/go-chi/jwtauth/v5 v5.3.1 github.com/go-chi/render v1.0.3 github.com/go-sql-driver/mysql v1.8.1 - github.com/golang/mock v1.6.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.6.0 github.com/hashicorp/go-hclog v1.6.3 diff --git a/go.sum b/go.sum index 6b9557cd..89e84c8a 100644 --- a/go.sum +++ b/go.sum @@ -175,8 +175,6 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -406,7 +404,6 @@ github.com/wneessen/go-mail v0.4.1 h1:m2rSg/sc8FZQCdtrV5M8ymHYOFrC6KJAQAIcgrXvqo github.com/wneessen/go-mail v0.4.1/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8= github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a h1:XfF01GyP+0eWCaVp0y6rNN+kFp7pt9Da4UUYrJ5XPWA= github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a/go.mod h1:aXb8yZQEWo1XHGMf1qQfnb83GR/EJ2EBlwtUgAaNBoE= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= @@ -438,7 +435,6 @@ golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSO golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= @@ -449,7 +445,6 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= @@ -461,7 +456,6 @@ golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= @@ -472,10 +466,7 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -494,7 +485,6 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= @@ -514,15 +504,12 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/api v0.176.1 h1:DJSXnV6An+NhJ1J+GWtoF2nHEuqB1VNoTfnIbjNvwD4= diff --git a/internal/config/config.go b/internal/config/config.go index 545a7742..e92bf13a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -269,7 +269,6 @@ func Init() { KeyboardInteractiveAuthentication: true, KeyboardInteractiveHook: "", PasswordAuthentication: true, - FolderPrefix: "", }, FTPD: ftpd.Configuration{ Bindings: []ftpd.Binding{defaultFTPDBinding}, @@ -2024,7 +2023,6 @@ func setViperDefaults() { viper.SetDefault("sftpd.keyboard_interactive_authentication", globalConf.SFTPD.KeyboardInteractiveAuthentication) viper.SetDefault("sftpd.keyboard_interactive_auth_hook", globalConf.SFTPD.KeyboardInteractiveHook) viper.SetDefault("sftpd.password_authentication", globalConf.SFTPD.PasswordAuthentication) - viper.SetDefault("sftpd.folder_prefix", globalConf.SFTPD.FolderPrefix) viper.SetDefault("ftpd.banner", globalConf.FTPD.Banner) viper.SetDefault("ftpd.banner_file", globalConf.FTPD.BannerFile) viper.SetDefault("ftpd.active_transfers_port_non_20", globalConf.FTPD.ActiveTransfersPortNon20) diff --git a/internal/sftpd/internal_test.go b/internal/sftpd/internal_test.go index 29a57ea0..c97a8ac1 100644 --- a/internal/sftpd/internal_test.go +++ b/internal/sftpd/internal_test.go @@ -2109,29 +2109,6 @@ func newFakeListener(err error) net.Listener { } } -func TestFolderPrefix(t *testing.T) { - c := Configuration{ - FolderPrefix: "files", - } - c.checkFolderPrefix() - assert.Equal(t, "/files", c.FolderPrefix) - c.FolderPrefix = "" - c.checkFolderPrefix() - assert.Empty(t, c.FolderPrefix) - c.FolderPrefix = "/" - c.checkFolderPrefix() - assert.Empty(t, c.FolderPrefix) - c.FolderPrefix = "/." - c.checkFolderPrefix() - assert.Empty(t, c.FolderPrefix) - c.FolderPrefix = "." - c.checkFolderPrefix() - assert.Empty(t, c.FolderPrefix) - c.FolderPrefix = ".." - c.checkFolderPrefix() - assert.Empty(t, c.FolderPrefix) -} - func TestLoadRevokedUserCertsFile(t *testing.T) { r := revokedCertificates{ certs: map[string]bool{}, diff --git a/internal/sftpd/middleware.go b/internal/sftpd/middleware.go deleted file mode 100644 index 82c7a5bf..00000000 --- a/internal/sftpd/middleware.go +++ /dev/null @@ -1,245 +0,0 @@ -// 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 . - -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 -} diff --git a/internal/sftpd/middleware_test.go b/internal/sftpd/middleware_test.go deleted file mode 100644 index 36c2e7f9..00000000 --- a/internal/sftpd/middleware_test.go +++ /dev/null @@ -1,370 +0,0 @@ -// 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 . - -package sftpd - -import ( - "testing" - - "github.com/golang/mock/gomock" - "github.com/pkg/sftp" - "github.com/stretchr/testify/suite" - - "github.com/drakkan/sftpgo/v2/internal/sftpd/mocks" -) - -type PrefixMiddlewareSuite struct { - suite.Suite - MockCtl *gomock.Controller -} - -func (Suite *PrefixMiddlewareSuite) BeforeTest(_, _ string) { - Suite.MockCtl = gomock.NewController(Suite.T()) -} - -func (Suite *PrefixMiddlewareSuite) AfterTest(_, _ string) { - Suite.MockCtl.Finish() -} - -func (Suite *PrefixMiddlewareSuite) TestFileWriter() { - prefix := prefixMiddleware{prefix: `/files`} - - // parent of prefix - WriterAt, err := prefix.Filewrite(&sftp.Request{Filepath: `/`}) - Suite.Nil(WriterAt) - Suite.Equal(sftp.ErrSSHFxPermissionDenied, err) - - // file path and prefix are unrelated - WriterAt, err = prefix.Filewrite(&sftp.Request{Filepath: `/random`}) - Suite.Nil(WriterAt) - Suite.Equal(sftp.ErrSSHFxPermissionDenied, err) - - // file path is sub path of configured prefix - // mocked returns are not import, just the call to the next file writer - mockedWriter := mocks.NewMockMiddleware(Suite.MockCtl) - mockedWriter.EXPECT(). - Filewrite(&sftp.Request{Filepath: `/data`}). - Return(nil, nil) - prefix.next = mockedWriter - WriterAt, err = prefix.Filewrite(&sftp.Request{Filepath: `/files/data`}) - Suite.Nil(err) - Suite.Nil(WriterAt) -} - -func (Suite *PrefixMiddlewareSuite) TestFileReader() { - middleware := prefixMiddleware{prefix: `/files`} - - // parent of prefix - ReaderAt, err := middleware.Fileread(&sftp.Request{Filepath: `/`}) - Suite.Nil(ReaderAt) - Suite.Equal(sftp.ErrSSHFxPermissionDenied, err) - - // file path and prefix are unrelated - ReaderAt, err = middleware.Fileread(&sftp.Request{Filepath: `/random`}) - Suite.Nil(ReaderAt) - Suite.Equal(sftp.ErrSSHFxPermissionDenied, err) - - // file path is sub path of configured prefix - // mocked returns are not import, just the call to the next file writer - mockedReader := mocks.NewMockMiddleware(Suite.MockCtl) - mockedReader.EXPECT(). - Fileread(&sftp.Request{Filepath: `/data`}). - Return(nil, nil) - middleware.next = mockedReader - ReaderAt, err = middleware.Fileread(&sftp.Request{Filepath: `/files/data`}) - Suite.Nil(err) - Suite.Nil(ReaderAt) -} - -func (Suite *PrefixMiddlewareSuite) TestOpenFile() { - middleware := prefixMiddleware{prefix: `/files`} - - ReadWriteAt, err := middleware.OpenFile(&sftp.Request{Filepath: `/`}) - Suite.Nil(ReadWriteAt) - Suite.Equal(sftp.ErrSSHFxPermissionDenied, err) - - // file path and prefix are unrelated - ReadWriteAt, err = middleware.OpenFile(&sftp.Request{Filepath: `/random`}) - Suite.Nil(ReadWriteAt) - Suite.Equal(sftp.ErrSSHFxPermissionDenied, err) - - var tests = []struct { - RequestPath string - NextPath string - }{ - // test normalization of various request paths - {RequestPath: `/files/data.csv`, NextPath: `/data.csv`}, - {RequestPath: `files/data.csv`, NextPath: `/data.csv`}, - {RequestPath: `//files/./data.csv`, NextPath: `/data.csv`}, - } - - for _, test := range tests { - OpenFileMock := mocks.NewMockMiddleware(Suite.MockCtl) - OpenFileMock.EXPECT(). - OpenFile(&sftp.Request{Filepath: test.NextPath}). - Return(nil, nil) - middleware.next = OpenFileMock - - ReadWriteAt, err = middleware.OpenFile(&sftp.Request{Filepath: test.RequestPath}) - Suite.Nil(ReadWriteAt) - Suite.Nil(err) - } -} - -func (Suite *PrefixMiddlewareSuite) TestStatVFS() { - prefix := prefixMiddleware{prefix: `/files`} - - // parent of prefix - res, err := prefix.StatVFS(&sftp.Request{Filepath: `/`}) - Suite.Nil(res) - Suite.Equal(sftp.ErrSSHFxPermissionDenied, err) - - // file path and prefix are unrelated - res, err = prefix.StatVFS(&sftp.Request{Filepath: `/random`}) - Suite.Nil(res) - Suite.Equal(sftp.ErrSSHFxPermissionDenied, err) - - // file path is sub path of configured prefix - // mocked returns are not import, just the call to the next file writer - statVFSMock := mocks.NewMockMiddleware(Suite.MockCtl) - statVFSMock.EXPECT(). - StatVFS(&sftp.Request{Filepath: `/data`}). - Return(nil, nil) - prefix.next = statVFSMock - res, err = prefix.StatVFS(&sftp.Request{Filepath: `/files/data`}) - Suite.Nil(err) - Suite.Nil(res) -} - -func (Suite *PrefixMiddlewareSuite) TestFileListForwarding() { - var tests = []struct { - Method string - FilePath string - FwdPath string - }{ - {Method: `List`, FilePath: `/files/data`, FwdPath: `/data`}, - {Method: `List`, FilePath: `/./files/data`, FwdPath: `/data`}, - {Method: `List`, FilePath: `files/data`, FwdPath: `/data`}, - } - - for _, test := range tests { - FileListMock := mocks.NewMockMiddleware(Suite.MockCtl) - FileListMock.EXPECT(). - Filelist(&sftp.Request{ - Method: test.Method, - Filepath: test.FwdPath, - }).Return(nil, nil) - - handlers := newPrefixMiddleware(`/files`, FileListMock) - ListerAt, err := handlers.Filelist(&sftp.Request{ - Method: test.Method, - Filepath: test.FilePath, - }) - Suite.Nil(ListerAt) - Suite.Nil(err) - } -} - -func (Suite *PrefixMiddlewareSuite) TestFileList() { - var tests = []struct { - Method string - FilePath string - ExpectedErr error - ExpectedPath string - ExpectedItems int - }{ - {Method: `List`, FilePath: `/random`, ExpectedErr: sftp.ErrSSHFxPermissionDenied, ExpectedItems: 0}, - {Method: `List`, FilePath: `/`, ExpectedPath: `files`, ExpectedItems: 2}, - {Method: `Stat`, FilePath: `/`, ExpectedPath: `/`, ExpectedItems: 1}, - {Method: `NotAnOp`, ExpectedErr: sftp.ErrSSHFxOpUnsupported}, - } - - for _, test := range tests { - middleware := prefixMiddleware{prefix: `/files`} - ListerAt, err := middleware.Filelist(&sftp.Request{ - Method: test.Method, - Filepath: test.FilePath, - }) - if test.ExpectedErr != nil { - Suite.Equal(test.ExpectedErr, err) - Suite.Nil(ListerAt) - } else { - Suite.Nil(err) - Suite.IsType(listerAt{}, ListerAt) - if directList, ok := ListerAt.(listerAt); ok { - Suite.Len(directList, test.ExpectedItems) - if test.ExpectedItems > 1 { - Suite.Equal(".", directList[0].Name()) - } - Suite.Equal(test.ExpectedPath, directList[test.ExpectedItems-1].Name()) - Suite.Equal(int64(0), directList[test.ExpectedItems-1].ModTime().Unix()) - Suite.True(directList[test.ExpectedItems-1].IsDir()) - } - } - } -} - -func (Suite *PrefixMiddlewareSuite) TestLstat() { - middleware := prefixMiddleware{prefix: `/files`} - ListerAt, err := middleware.Lstat(&sftp.Request{Filepath: `/`}) - Suite.Nil(err) - Suite.IsType(listerAt{}, ListerAt) - if directList, ok := ListerAt.(listerAt); ok { - Suite.Len(directList, 1) - Suite.Equal(`/`, directList[0].Name()) - Suite.Equal(int64(0), directList[0].ModTime().Unix()) - Suite.True(directList[0].IsDir()) - } - - middleware = prefixMiddleware{prefix: `/files`} - ListerAt, err = middleware.Lstat(&sftp.Request{Filepath: `/random`}) - Suite.Nil(ListerAt) - Suite.Equal(sftp.ErrSSHFxPermissionDenied, err) - - MockLstat := mocks.NewMockMiddleware(Suite.MockCtl) - MockLstat.EXPECT(). - Lstat(&sftp.Request{Filepath: "/data"}). - Return(nil, nil) - middleware = prefixMiddleware{prefix: `/files`} - middleware.next = MockLstat - - ListerAt, err = middleware.Lstat(&sftp.Request{Filepath: `/files/data`}) - Suite.Nil(err) - Suite.Nil(ListerAt) -} - -func (Suite *PrefixMiddlewareSuite) TestFileCmdForwarding() { - var tests = []struct { - Method string - FilePath string - TargetPath string - FwdFilePath string - FwdTargetPath string - }{ - {Method: `Rename`, FilePath: `/files/data.csv`, TargetPath: `/files/new-data.csv`, FwdFilePath: `/data.csv`, FwdTargetPath: `/new-data.csv`}, - {Method: `Rename`, FilePath: `files/data.csv`, TargetPath: `files/new-data.csv`, FwdFilePath: `/data.csv`, FwdTargetPath: `/new-data.csv`}, - {Method: `Symlink`, FilePath: `/./files/data.csv`, TargetPath: `files/new-data.csv`, FwdFilePath: `/data.csv`, FwdTargetPath: `/new-data.csv`}, - - {Method: `Setstat`, FilePath: `files/data.csv`, FwdFilePath: `/data.csv`}, - {Method: `Remove`, FilePath: `/./files/data.csv`, FwdFilePath: `/data.csv`}, - {Method: `Rmdir`, FilePath: `files/data`, FwdFilePath: `/data`}, - {Method: `Mkdir`, FilePath: `/./files/data`, FwdFilePath: `/data`}, - } - - for _, test := range tests { - FileCmdMock := mocks.NewMockMiddleware(Suite.MockCtl) - FileCmdMock.EXPECT(). - Filecmd(&sftp.Request{ - Method: test.Method, - Filepath: test.FwdFilePath, - Target: test.FwdTargetPath, - }).Return(nil) - - middleware := prefixMiddleware{ - prefix: `/files`, - next: FileCmdMock, - } - - Suite.Nil(middleware.Filecmd(&sftp.Request{ - Method: test.Method, - Filepath: test.FilePath, - Target: test.TargetPath, - })) - } -} - -func (Suite *PrefixMiddlewareSuite) TestFileCmdErrors() { - middleware := prefixMiddleware{prefix: `/files`} - - var tests = []struct { - Method string - RequestPath string - TargetPath string - ExpectedErr error - }{ - // two path methods - {Method: `Rename`, RequestPath: `/`, TargetPath: `/`, ExpectedErr: sftp.ErrSSHFxPermissionDenied}, - {Method: `Rename`, RequestPath: `/random`, TargetPath: `/`, ExpectedErr: sftp.ErrSSHFxPermissionDenied}, - {Method: `Rename`, RequestPath: `/random`, TargetPath: `/files`, ExpectedErr: sftp.ErrSSHFxPermissionDenied}, - {Method: `Symlink`, RequestPath: `/`, TargetPath: `/`, ExpectedErr: sftp.ErrSSHFxPermissionDenied}, - {Method: `Symlink`, RequestPath: `/random`, TargetPath: `/`, ExpectedErr: sftp.ErrSSHFxPermissionDenied}, - {Method: `Symlink`, RequestPath: `/random`, TargetPath: `/files`, ExpectedErr: sftp.ErrSSHFxPermissionDenied}, - - // single path methods - {Method: `Setstat`, RequestPath: `/`, ExpectedErr: sftp.ErrSSHFxPermissionDenied}, - {Method: `Setstat`, RequestPath: `/unrelated`, ExpectedErr: sftp.ErrSSHFxPermissionDenied}, - {Method: `Rmdir`, RequestPath: `/`, ExpectedErr: sftp.ErrSSHFxPermissionDenied}, - {Method: `Rmdir`, RequestPath: `/unrelated`, ExpectedErr: sftp.ErrSSHFxPermissionDenied}, - {Method: `Mkdir`, RequestPath: `/`, ExpectedErr: sftp.ErrSSHFxPermissionDenied}, - {Method: `Mkdir`, RequestPath: `/unrelated`, ExpectedErr: sftp.ErrSSHFxPermissionDenied}, - {Method: `Remove`, RequestPath: `/`, ExpectedErr: sftp.ErrSSHFxPermissionDenied}, - {Method: `Remove`, RequestPath: `/unrelated`, ExpectedErr: sftp.ErrSSHFxPermissionDenied}, - - {Method: `NotACmd`, ExpectedErr: sftp.ErrSSHFxOpUnsupported}, - } - - for _, test := range tests { - err := middleware.Filecmd(&sftp.Request{ - Method: test.Method, - Filepath: test.RequestPath, - Target: test.TargetPath, - }) - Suite.Equal(test.ExpectedErr, err) - } -} - -func (Suite *PrefixMiddlewareSuite) TestNextFolder() { - prefix := prefixMiddleware{prefix: `/files/data`} - Suite.Equal(`files`, prefix.nextListFolder(`/`)) - Suite.Equal(`files`, prefix.nextListFolder(``)) - Suite.Equal(`data`, prefix.nextListFolder(`/files`)) - Suite.Equal(`data`, prefix.nextListFolder(`files`)) - Suite.Equal(`data`, prefix.nextListFolder(`files/`)) - - prefix = prefixMiddleware{prefix: `files/data`} - Suite.Equal(`files`, prefix.nextListFolder(`/`)) - Suite.Equal(`files`, prefix.nextListFolder(``)) - Suite.Equal(`data`, prefix.nextListFolder(`/files`)) - Suite.Equal(`data`, prefix.nextListFolder(`files`)) - Suite.Equal(`data`, prefix.nextListFolder(`files/`)) -} - -func (Suite *PrefixMiddlewareSuite) TestContainsPrefix() { - prefix := prefixMiddleware{prefix: `/`} - Suite.True(prefix.containsPrefix(`/data`)) - Suite.True(prefix.containsPrefix(`/`)) - - prefix = prefixMiddleware{prefix: `/files`} - Suite.True(prefix.containsPrefix(`files`)) -} - -func (Suite *PrefixMiddlewareSuite) TestRemoveFolderPrefix() { - prefix := prefixMiddleware{prefix: `/`} - path, ok := prefix.removeFolderPrefix(`/files`) - Suite.Equal(`/files`, path) - Suite.True(ok) - - prefix = prefixMiddleware{prefix: `/files`} - path, ok = prefix.removeFolderPrefix(`files`) - Suite.Equal(`/`, path) - Suite.True(ok) - - path, ok = prefix.removeFolderPrefix(`/random`) - Suite.Equal(`/random`, path) - Suite.False(ok) -} - -func TestFolderPrefixSuite(t *testing.T) { - suite.Run(t, new(PrefixMiddlewareSuite)) -} diff --git a/internal/sftpd/mocks/middleware.go b/internal/sftpd/mocks/middleware.go deleted file mode 100644 index e0251436..00000000 --- a/internal/sftpd/mocks/middleware.go +++ /dev/null @@ -1,140 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: ../middleware.go - -// Package mocks is a generated GoMock package. -package mocks - -import ( - io "io" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - sftp "github.com/pkg/sftp" -) - -// MockMiddleware is a mock of Middleware interface. -type MockMiddleware struct { - ctrl *gomock.Controller - recorder *MockMiddlewareMockRecorder -} - -// MockMiddlewareMockRecorder is the mock recorder for MockMiddleware. -type MockMiddlewareMockRecorder struct { - mock *MockMiddleware -} - -// NewMockMiddleware creates a new mock instance. -func NewMockMiddleware(ctrl *gomock.Controller) *MockMiddleware { - mock := &MockMiddleware{ctrl: ctrl} - mock.recorder = &MockMiddlewareMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockMiddleware) EXPECT() *MockMiddlewareMockRecorder { - return m.recorder -} - -// Filecmd mocks base method. -func (m *MockMiddleware) Filecmd(arg0 *sftp.Request) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Filecmd", arg0) - ret0, _ := ret[0].(error) - return ret0 -} - -// Filecmd indicates an expected call of Filecmd. -func (mr *MockMiddlewareMockRecorder) Filecmd(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filecmd", reflect.TypeOf((*MockMiddleware)(nil).Filecmd), arg0) -} - -// Filelist mocks base method. -func (m *MockMiddleware) Filelist(arg0 *sftp.Request) (sftp.ListerAt, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Filelist", arg0) - ret0, _ := ret[0].(sftp.ListerAt) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Filelist indicates an expected call of Filelist. -func (mr *MockMiddlewareMockRecorder) Filelist(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filelist", reflect.TypeOf((*MockMiddleware)(nil).Filelist), arg0) -} - -// Fileread mocks base method. -func (m *MockMiddleware) Fileread(arg0 *sftp.Request) (io.ReaderAt, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Fileread", arg0) - ret0, _ := ret[0].(io.ReaderAt) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Fileread indicates an expected call of Fileread. -func (mr *MockMiddlewareMockRecorder) Fileread(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fileread", reflect.TypeOf((*MockMiddleware)(nil).Fileread), arg0) -} - -// Filewrite mocks base method. -func (m *MockMiddleware) Filewrite(arg0 *sftp.Request) (io.WriterAt, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Filewrite", arg0) - ret0, _ := ret[0].(io.WriterAt) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Filewrite indicates an expected call of Filewrite. -func (mr *MockMiddlewareMockRecorder) Filewrite(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filewrite", reflect.TypeOf((*MockMiddleware)(nil).Filewrite), arg0) -} - -// Lstat mocks base method. -func (m *MockMiddleware) Lstat(arg0 *sftp.Request) (sftp.ListerAt, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Lstat", arg0) - ret0, _ := ret[0].(sftp.ListerAt) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Lstat indicates an expected call of Lstat. -func (mr *MockMiddlewareMockRecorder) Lstat(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lstat", reflect.TypeOf((*MockMiddleware)(nil).Lstat), arg0) -} - -// OpenFile mocks base method. -func (m *MockMiddleware) OpenFile(arg0 *sftp.Request) (sftp.WriterAtReaderAt, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "OpenFile", arg0) - ret0, _ := ret[0].(sftp.WriterAtReaderAt) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// OpenFile indicates an expected call of OpenFile. -func (mr *MockMiddlewareMockRecorder) OpenFile(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenFile", reflect.TypeOf((*MockMiddleware)(nil).OpenFile), arg0) -} - -// StatVFS mocks base method. -func (m *MockMiddleware) StatVFS(arg0 *sftp.Request) (*sftp.StatVFS, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "StatVFS", arg0) - ret0, _ := ret[0].(*sftp.StatVFS) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// StatVFS indicates an expected call of StatVFS. -func (mr *MockMiddlewareMockRecorder) StatVFS(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatVFS", reflect.TypeOf((*MockMiddleware)(nil).StatVFS), arg0) -} diff --git a/internal/sftpd/server.go b/internal/sftpd/server.go index 6493895f..2f16dd52 100644 --- a/internal/sftpd/server.go +++ b/internal/sftpd/server.go @@ -24,7 +24,6 @@ import ( "io/fs" "net" "os" - "path" "path/filepath" "runtime/debug" "strings" @@ -180,14 +179,8 @@ type Configuration struct { KeyboardInteractiveHook string `json:"keyboard_interactive_auth_hook" mapstructure:"keyboard_interactive_auth_hook"` // PasswordAuthentication specifies whether password authentication is allowed. PasswordAuthentication bool `json:"password_authentication" mapstructure:"password_authentication"` - // Virtual root folder prefix to include in all file operations (ex: /files). - // The virtual paths used for per-directory permissions, file patterns etc. must not include the folder prefix. - // The prefix is only applied to SFTP requests, SCP and other SSH commands will be automatically disabled if - // you configure a prefix. - // This setting can help some migrations from OpenSSH. It is not recommended for general usage. - FolderPrefix string `json:"folder_prefix" mapstructure:"folder_prefix"` - certChecker *ssh.CertChecker - parsedUserCAKeys []ssh.PublicKey + certChecker *ssh.CertChecker + parsedUserCAKeys []ssh.PublicKey } type authenticationError struct { @@ -353,7 +346,6 @@ func (c *Configuration) Initialize(configDir string) error { c.configureKeyboardInteractiveAuth(serverConfig) c.configureLoginBanner(serverConfig, configDir) c.checkSSHCommands() - c.checkFolderPrefix() exitChannel := make(chan error, 1) serviceStatus.Bindings = nil @@ -665,7 +657,6 @@ func (c *Configuration) AcceptInboundConnection(conn net.Conn, config *ssh.Serve RemoteAddr: conn.RemoteAddr(), LocalAddr: conn.LocalAddr(), channel: channel, - folderPrefix: c.FolderPrefix, } go c.handleSftpConnection(channel, connection) } @@ -678,7 +669,6 @@ func (c *Configuration) AcceptInboundConnection(conn net.Conn, config *ssh.Serve RemoteAddr: conn.RemoteAddr(), LocalAddr: conn.LocalAddr(), channel: channel, - folderPrefix: c.FolderPrefix, } ok = processSSHCommand(req.Payload, &connection, c.EnabledSSHCommands) } @@ -718,17 +708,6 @@ func (c *Configuration) handleSftpConnection(channel ssh.Channel, connection *Co } func (c *Configuration) createHandlers(connection *Connection) sftp.Handlers { - if c.FolderPrefix != "" { - prefixMiddleware := newPrefixMiddleware(c.FolderPrefix, connection) - - return sftp.Handlers{ - FileGet: prefixMiddleware, - FilePut: prefixMiddleware, - FileCmd: prefixMiddleware, - FileList: prefixMiddleware, - } - } - return sftp.Handlers{ FileGet: connection, FilePut: connection, @@ -870,19 +849,6 @@ func (c *Configuration) checkSSHCommands() { logger.Debug(logSender, "", "enabled SSH commands %v", c.EnabledSSHCommands) } -func (c *Configuration) checkFolderPrefix() { - if c.FolderPrefix != "" { - c.FolderPrefix = path.Join("/", c.FolderPrefix) - if c.FolderPrefix == "/" { - c.FolderPrefix = "" - } - } - if c.FolderPrefix != "" { - c.EnabledSSHCommands = nil - logger.Debug(logSender, "", "folder prefix %q configured, SSH commands are disabled", c.FolderPrefix) - } -} - func (c *Configuration) generateDefaultHostKeys(configDir string) error { var err error defaultHostKeys := []string{defaultPrivateRSAKeyName, defaultPrivateECDSAKeyName, defaultPrivateEd25519KeyName} diff --git a/internal/sftpd/sftpd_test.go b/internal/sftpd/sftpd_test.go index 9b6ce13e..2f5645d4 100644 --- a/internal/sftpd/sftpd_test.go +++ b/internal/sftpd/sftpd_test.go @@ -320,7 +320,6 @@ func TestMain(m *testing.M) { }, } sftpdConf.PasswordAuthentication = true - sftpdConf.FolderPrefix = "/prefix/files" go func(cfg sftpd.Configuration) { logger.Debug(logSender, "", "initializing SFTP server with config %+v and proxy protocol %v", cfg, common.Config.ProxyProtocol) @@ -888,56 +887,6 @@ func TestStartDirectory(t *testing.T) { assert.NoError(t, err) } -func TestFolderPrefix(t *testing.T) { - usePubKey := true - u := getTestUser(usePubKey) - u.QuotaFiles = 1000 - user, _, err := httpdtest.AddUser(u, http.StatusCreated) - assert.NoError(t, err) - err = os.RemoveAll(user.GetHomeDir()) - assert.NoError(t, err) - conn, client, err := getSftpClientWithAddr(user, usePubKey, "127.0.0.1:2226") - if assert.NoError(t, err) { - defer conn.Close() - defer client.Close() - err = checkBasicSFTP(client) - assert.NoError(t, err) - _, err = client.Stat("path") - assert.ErrorIs(t, err, os.ErrPermission) - _, err = client.Stat("/prefix/path") - assert.ErrorIs(t, err, os.ErrPermission) - _, err = client.Stat("/prefix/files1") - assert.ErrorIs(t, err, os.ErrPermission) - contents, err := client.ReadDir("/") - if assert.NoError(t, err) { - if assert.Len(t, contents, 1) { - assert.Equal(t, "prefix", contents[0].Name()) - } - } - contents, err = client.ReadDir("/prefix") - if assert.NoError(t, err) { - if assert.Len(t, contents, 1) { - assert.Equal(t, "files", contents[0].Name()) - } - } - _, err = client.OpenFile(testFileName, os.O_WRONLY|os.O_CREATE) - assert.ErrorIs(t, err, os.ErrPermission) - _, err = client.OpenFile(testFileName, os.O_RDONLY) - assert.ErrorIs(t, err, os.ErrPermission) - - f, err := client.OpenFile(path.Join("prefix", "files", testFileName), os.O_WRONLY|os.O_CREATE) - assert.NoError(t, err) - _, err = f.Write([]byte("test")) - assert.NoError(t, err) - err = f.Close() - assert.NoError(t, err) - } - _, err = httpdtest.RemoveUser(user, http.StatusOK) - assert.NoError(t, err) - err = os.RemoveAll(user.GetHomeDir()) - assert.NoError(t, err) -} - func TestLoginNonExistentUser(t *testing.T) { usePubKey := true user := getTestUser(usePubKey)