extend virtual folders support to all storage backends

Fixes #241
This commit is contained in:
Nicola Murino
2021-03-21 19:15:47 +01:00
parent 0286da2356
commit d6dc3a507e
70 changed files with 6825 additions and 3740 deletions

View File

@@ -15,6 +15,7 @@ import (
"github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/httpdtest"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/vfs"
)
func TestBasicFTPHandlingCryptFs(t *testing.T) {
@@ -214,7 +215,7 @@ func TestResumeCryptFs(t *testing.T) {
func getTestUserWithCryptFs() dataprovider.User {
user := getTestUser()
user.FsConfig.Provider = dataprovider.CryptedFilesystemProvider
user.FsConfig.Provider = vfs.CryptedFilesystemProvider
user.FsConfig.CryptConfig.Passphrase = kms.NewPlainSecret("testPassphrase")
return user
}

View File

@@ -245,6 +245,10 @@ func TestMain(m *testing.M) {
if err != nil {
logger.ErrorToConsole("error creating banner file: %v", err)
}
// we run the test cases with UploadMode atomic and resume support. The non atomic code path
// simply does not execute some code so if it works in atomic mode will
// work in non atomic mode too
os.Setenv("SFTPGO_COMMON__UPLOAD_MODE", "2")
err = config.LoadConfig(configDir, "")
if err != nil {
logger.ErrorToConsole("error loading configuration: %v", err)
@@ -254,10 +258,6 @@ func TestMain(m *testing.M) {
logger.InfoToConsole("Starting FTPD tests, provider: %v", providerConf.Driver)
commonConf := config.GetCommonConfig()
// we run the test cases with UploadMode atomic and resume support. The non atomic code path
// simply does not execute some code so if it works in atomic mode will
// work in non atomic mode too
commonConf.UploadMode = 2
homeBasePath = os.TempDir()
if runtime.GOOS != osWindows {
commonConf.Actions.ExecuteOn = []string{"download", "upload", "rename", "delete"}
@@ -1274,7 +1274,7 @@ func TestLoginWithIPilters(t *testing.T) {
func TestLoginWithDatabaseCredentials(t *testing.T) {
u := getTestUser()
u.FsConfig.Provider = dataprovider.GCSFilesystemProvider
u.FsConfig.Provider = vfs.GCSFilesystemProvider
u.FsConfig.GCSConfig.Bucket = "test"
u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret(`{ "type": "service_account" }`)
@@ -1323,7 +1323,7 @@ func TestLoginWithDatabaseCredentials(t *testing.T) {
func TestLoginInvalidFs(t *testing.T) {
u := getTestUser()
u.FsConfig.Provider = dataprovider.GCSFilesystemProvider
u.FsConfig.Provider = vfs.GCSFilesystemProvider
u.FsConfig.GCSConfig.Bucket = "test"
u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("invalid JSON for credentials")
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
@@ -2153,7 +2153,7 @@ func TestClientCertificateAuth(t *testing.T) {
// TLS username is not enabled, mutual TLS should fail
_, err = getFTPClient(user, true, tlsConfig)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "Login method password is not allowed")
assert.Contains(t, err.Error(), "login method password is not allowed")
}
user.Filters.TLSUsername = dataprovider.TLSUsernameCN
@@ -2186,7 +2186,7 @@ func TestClientCertificateAuth(t *testing.T) {
assert.NoError(t, err)
_, err = getFTPClient(user, true, tlsConfig)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "Login method TLSCertificate+password is not allowed")
assert.Contains(t, err.Error(), "login method TLSCertificate+password is not allowed")
}
// disable FTP protocol
@@ -2196,7 +2196,7 @@ func TestClientCertificateAuth(t *testing.T) {
assert.NoError(t, err)
_, err = getFTPClient(user, true, tlsConfig)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "Protocol FTP is not allowed")
assert.Contains(t, err.Error(), "protocol FTP is not allowed")
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
@@ -2238,7 +2238,7 @@ func TestClientCertificateAndPwdAuth(t *testing.T) {
_, err = getFTPClient(user, true, nil)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "Login method password is not allowed")
assert.Contains(t, err.Error(), "login method password is not allowed")
}
user.Password = defaultPassword + "1"
_, err = getFTPClient(user, true, tlsConfig)
@@ -2405,6 +2405,111 @@ func TestPreLoginHookWithClientCert(t *testing.T) {
assert.NoError(t, err)
}
func TestNestedVirtualFolders(t *testing.T) {
u := getTestUser()
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
u = getTestSFTPUser()
mappedPathCrypt := filepath.Join(os.TempDir(), "crypt")
folderNameCrypt := filepath.Base(mappedPathCrypt)
vdirCryptPath := "/vdir/crypt"
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderNameCrypt,
FsConfig: vfs.Filesystem{
Provider: vfs.CryptedFilesystemProvider,
CryptConfig: vfs.CryptFsConfig{
Passphrase: kms.NewPlainSecret(defaultPassword),
},
},
MappedPath: mappedPathCrypt,
},
VirtualPath: vdirCryptPath,
})
mappedPath := filepath.Join(os.TempDir(), "local")
folderName := filepath.Base(mappedPath)
vdirPath := "/vdir/local"
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderName,
MappedPath: mappedPath,
},
VirtualPath: vdirPath,
})
mappedPathNested := filepath.Join(os.TempDir(), "nested")
folderNameNested := filepath.Base(mappedPathNested)
vdirNestedPath := "/vdir/crypt/nested"
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: folderNameNested,
MappedPath: mappedPathNested,
},
VirtualPath: vdirNestedPath,
QuotaFiles: -1,
QuotaSize: -1,
})
sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
client, err := getFTPClient(sftpUser, false, nil)
if assert.NoError(t, err) {
err = checkBasicFTP(client)
assert.NoError(t, err)
testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.NoError(t, err)
err = ftpDownloadFile(testFileName, localDownloadPath, testFileSize, client, 0)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, path.Join("/vdir", testFileName), testFileSize, client, 0)
assert.NoError(t, err)
err = ftpDownloadFile(path.Join("/vdir", testFileName), localDownloadPath, testFileSize, client, 0)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, path.Join(vdirPath, testFileName), testFileSize, client, 0)
assert.NoError(t, err)
err = ftpDownloadFile(path.Join(vdirPath, testFileName), localDownloadPath, testFileSize, client, 0)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, path.Join(vdirCryptPath, testFileName), testFileSize, client, 0)
assert.NoError(t, err)
err = ftpDownloadFile(path.Join(vdirCryptPath, testFileName), localDownloadPath, testFileSize, client, 0)
assert.NoError(t, err)
err = ftpUploadFile(testFilePath, path.Join(vdirNestedPath, testFileName), testFileSize, client, 0)
assert.NoError(t, err)
err = ftpDownloadFile(path.Join(vdirNestedPath, testFileName), localDownloadPath, testFileSize, client, 0)
assert.NoError(t, err)
err = client.Quit()
assert.NoError(t, err)
err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
}
_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameCrypt}, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameNested}, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(mappedPathCrypt)
assert.NoError(t, err)
err = os.RemoveAll(mappedPath)
assert.NoError(t, err)
err = os.RemoveAll(mappedPathNested)
assert.NoError(t, err)
err = os.RemoveAll(localUser.GetHomeDir())
assert.NoError(t, err)
assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 }, 1*time.Second, 50*time.Millisecond)
}
func checkBasicFTP(client *ftp.ServerConn) error {
_, err := client.CurrentDir()
if err != nil {
@@ -2562,7 +2667,7 @@ func getTestUser() dataprovider.User {
func getTestSFTPUser() dataprovider.User {
u := getTestUser()
u.Username = u.Username + "_sftp"
u.FsConfig.Provider = dataprovider.SFTPFilesystemProvider
u.FsConfig.Provider = vfs.SFTPFilesystemProvider
u.FsConfig.SFTPConfig.Endpoint = sftpServerAddr
u.FsConfig.SFTPConfig.Username = defaultUsername
u.FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)

View File

@@ -17,7 +17,7 @@ import (
)
var (
errNotImplemented = errors.New("Not implemented")
errNotImplemented = errors.New("not implemented")
errCOMBNotSupported = errors.New("COMB is not supported for this filesystem")
)
@@ -63,11 +63,7 @@ func (c *Connection) Create(name string) (afero.File, error) {
func (c *Connection) Mkdir(name string, perm os.FileMode) error {
c.UpdateLastActivity()
p, err := c.Fs.ResolvePath(name)
if err != nil {
return c.GetFsError(err)
}
return c.CreateDir(p, name)
return c.CreateDir(name)
}
// MkdirAll is not implemented, we don't need it
@@ -90,22 +86,22 @@ func (c *Connection) OpenFile(name string, flag int, perm os.FileMode) (afero.Fi
func (c *Connection) Remove(name string) error {
c.UpdateLastActivity()
p, err := c.Fs.ResolvePath(name)
fs, p, err := c.GetFsAndResolvedPath(name)
if err != nil {
return c.GetFsError(err)
return err
}
var fi os.FileInfo
if fi, err = c.Fs.Lstat(p); err != nil {
if fi, err = fs.Lstat(p); err != nil {
c.Log(logger.LevelWarn, "failed to remove a file %#v: stat error: %+v", p, err)
return c.GetFsError(err)
return c.GetFsError(fs, err)
}
if fi.IsDir() && fi.Mode()&os.ModeSymlink == 0 {
c.Log(logger.LevelDebug, "cannot remove %#v is not a file/symlink", p)
return c.GetGenericError(nil)
}
return c.RemoveFile(p, name, fi)
return c.RemoveFile(fs, p, name, fi)
}
// RemoveAll is not implemented, we don't need it
@@ -117,20 +113,10 @@ func (c *Connection) RemoveAll(path string) error {
func (c *Connection) Rename(oldname, newname string) error {
c.UpdateLastActivity()
p, err := c.Fs.ResolvePath(oldname)
if err != nil {
return c.GetFsError(err)
}
t, err := c.Fs.ResolvePath(newname)
if err != nil {
return c.GetFsError(err)
}
if err = c.BaseConnection.Rename(p, t, oldname, newname); err != nil {
if err := c.BaseConnection.Rename(oldname, newname); err != nil {
return err
}
vfs.SetPathPermissions(c.Fs, t, c.User.GetUID(), c.User.GetGID())
return nil
}
@@ -143,14 +129,10 @@ func (c *Connection) Stat(name string) (os.FileInfo, error) {
return nil, c.GetPermissionDeniedError()
}
p, err := c.Fs.ResolvePath(name)
fi, err := c.DoStat(name, 0)
if err != nil {
return nil, c.GetFsError(err)
}
fi, err := c.DoStat(p, 0)
if err != nil {
c.Log(logger.LevelDebug, "error running stat on path %#v: %+v", p, err)
return nil, c.GetFsError(err)
c.Log(logger.LevelDebug, "error running stat on path %#v: %+v", name, err)
return nil, err
}
return fi, nil
}
@@ -182,31 +164,23 @@ func (c *Connection) Chown(name string, uid, gid int) error {
func (c *Connection) Chmod(name string, mode os.FileMode) error {
c.UpdateLastActivity()
p, err := c.Fs.ResolvePath(name)
if err != nil {
return c.GetFsError(err)
}
attrs := common.StatAttributes{
Flags: common.StatAttrPerms,
Mode: mode,
}
return c.SetStat(p, name, &attrs)
return c.SetStat(name, &attrs)
}
// Chtimes changes the access and modification times of the named file
func (c *Connection) Chtimes(name string, atime time.Time, mtime time.Time) error {
c.UpdateLastActivity()
p, err := c.Fs.ResolvePath(name)
if err != nil {
return c.GetFsError(err)
}
attrs := common.StatAttributes{
Flags: common.StatAttrTimes,
Atime: atime,
Mtime: mtime,
}
return c.SetStat(p, name, &attrs)
return c.SetStat(name, &attrs)
}
// GetAvailableSpace implements ClientDriverExtensionAvailableSpace interface
@@ -224,14 +198,14 @@ func (c *Connection) GetAvailableSpace(dirName string) (int64, error) {
return c.User.Filters.MaxUploadFileSize, nil
}
p, err := c.Fs.ResolvePath(dirName)
fs, p, err := c.GetFsAndResolvedPath(dirName)
if err != nil {
return 0, c.GetFsError(err)
return 0, err
}
statVFS, err := c.Fs.GetAvailableDiskSize(p)
statVFS, err := fs.GetAvailableDiskSize(p)
if err != nil {
return 0, c.GetFsError(err)
return 0, c.GetFsError(fs, err)
}
return int64(statVFS.FreeSpace()), nil
}
@@ -281,61 +255,43 @@ func (c *Connection) AllocateSpace(size int) error {
func (c *Connection) RemoveDir(name string) error {
c.UpdateLastActivity()
p, err := c.Fs.ResolvePath(name)
if err != nil {
return c.GetFsError(err)
}
return c.BaseConnection.RemoveDir(p, name)
return c.BaseConnection.RemoveDir(name)
}
// Symlink implements ClientDriverExtensionSymlink
func (c *Connection) Symlink(oldname, newname string) error {
c.UpdateLastActivity()
p, err := c.Fs.ResolvePath(oldname)
if err != nil {
return c.GetFsError(err)
}
t, err := c.Fs.ResolvePath(newname)
if err != nil {
return c.GetFsError(err)
}
return c.BaseConnection.CreateSymlink(p, t, oldname, newname)
return c.BaseConnection.CreateSymlink(oldname, newname)
}
// ReadDir implements ClientDriverExtensionFilelist
func (c *Connection) ReadDir(name string) ([]os.FileInfo, error) {
c.UpdateLastActivity()
p, err := c.Fs.ResolvePath(name)
if err != nil {
return nil, c.GetFsError(err)
}
return c.ListDir(p, name)
return c.ListDir(name)
}
// GetHandle implements ClientDriverExtentionFileTransfer
func (c *Connection) GetHandle(name string, flags int, offset int64) (ftpserver.FileTransfer, error) {
c.UpdateLastActivity()
p, err := c.Fs.ResolvePath(name)
fs, p, err := c.GetFsAndResolvedPath(name)
if err != nil {
return nil, c.GetFsError(err)
return nil, err
}
if c.GetCommand() == "COMB" && !vfs.IsLocalOsFs(c.Fs) {
if c.GetCommand() == "COMB" && !vfs.IsLocalOsFs(fs) {
return nil, errCOMBNotSupported
}
if flags&os.O_WRONLY != 0 {
return c.uploadFile(p, name, flags)
return c.uploadFile(fs, p, name, flags)
}
return c.downloadFile(p, name, offset)
return c.downloadFile(fs, p, name, offset)
}
func (c *Connection) downloadFile(fsPath, ftpPath string, offset int64) (ftpserver.FileTransfer, error) {
func (c *Connection) downloadFile(fs vfs.Fs, fsPath, ftpPath string, offset int64) (ftpserver.FileTransfer, error) {
if !c.User.HasPerm(dataprovider.PermDownload, path.Dir(ftpPath)) {
return nil, c.GetPermissionDeniedError()
}
@@ -345,41 +301,41 @@ func (c *Connection) downloadFile(fsPath, ftpPath string, offset int64) (ftpserv
return nil, c.GetPermissionDeniedError()
}
file, r, cancelFn, err := c.Fs.Open(fsPath, offset)
file, r, cancelFn, err := fs.Open(fsPath, offset)
if err != nil {
c.Log(logger.LevelWarn, "could not open file %#v for reading: %+v", fsPath, err)
return nil, c.GetFsError(err)
return nil, c.GetFsError(fs, err)
}
baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, fsPath, ftpPath, common.TransferDownload,
0, 0, 0, false, c.Fs)
0, 0, 0, false, fs)
t := newTransfer(baseTransfer, nil, r, offset)
return t, nil
}
func (c *Connection) uploadFile(fsPath, ftpPath string, flags int) (ftpserver.FileTransfer, error) {
func (c *Connection) uploadFile(fs vfs.Fs, fsPath, ftpPath string, flags int) (ftpserver.FileTransfer, error) {
if !c.User.IsFileAllowed(ftpPath) {
c.Log(logger.LevelWarn, "writing file %#v is not allowed", ftpPath)
return nil, c.GetPermissionDeniedError()
}
filePath := fsPath
if common.Config.IsAtomicUploadEnabled() && c.Fs.IsAtomicUploadSupported() {
filePath = c.Fs.GetAtomicUploadPath(fsPath)
if common.Config.IsAtomicUploadEnabled() && fs.IsAtomicUploadSupported() {
filePath = fs.GetAtomicUploadPath(fsPath)
}
stat, statErr := c.Fs.Lstat(fsPath)
if (statErr == nil && stat.Mode()&os.ModeSymlink != 0) || c.Fs.IsNotExist(statErr) {
stat, statErr := fs.Lstat(fsPath)
if (statErr == nil && stat.Mode()&os.ModeSymlink != 0) || fs.IsNotExist(statErr) {
if !c.User.HasPerm(dataprovider.PermUpload, path.Dir(ftpPath)) {
return nil, c.GetPermissionDeniedError()
}
return c.handleFTPUploadToNewFile(fsPath, filePath, ftpPath)
return c.handleFTPUploadToNewFile(fs, fsPath, filePath, ftpPath)
}
if statErr != nil {
c.Log(logger.LevelError, "error performing file stat %#v: %+v", fsPath, statErr)
return nil, c.GetFsError(statErr)
return nil, c.GetFsError(fs, statErr)
}
// This happen if we upload a file that has the same name of an existing directory
@@ -392,34 +348,34 @@ func (c *Connection) uploadFile(fsPath, ftpPath string, flags int) (ftpserver.Fi
return nil, c.GetPermissionDeniedError()
}
return c.handleFTPUploadToExistingFile(flags, fsPath, filePath, stat.Size(), ftpPath)
return c.handleFTPUploadToExistingFile(fs, flags, fsPath, filePath, stat.Size(), ftpPath)
}
func (c *Connection) handleFTPUploadToNewFile(resolvedPath, filePath, requestPath string) (ftpserver.FileTransfer, error) {
func (c *Connection) handleFTPUploadToNewFile(fs vfs.Fs, resolvedPath, filePath, requestPath string) (ftpserver.FileTransfer, error) {
quotaResult := c.HasSpace(true, false, requestPath)
if !quotaResult.HasSpace {
c.Log(logger.LevelInfo, "denying file write due to quota limits")
return nil, common.ErrQuotaExceeded
}
file, w, cancelFn, err := c.Fs.Create(filePath, 0)
file, w, cancelFn, err := fs.Create(filePath, 0)
if err != nil {
c.Log(logger.LevelWarn, "error creating file %#v: %+v", resolvedPath, err)
return nil, c.GetFsError(err)
return nil, c.GetFsError(fs, err)
}
vfs.SetPathPermissions(c.Fs, filePath, c.User.GetUID(), c.User.GetGID())
vfs.SetPathPermissions(fs, filePath, c.User.GetUID(), c.User.GetGID())
// we can get an error only for resume
maxWriteSize, _ := c.GetMaxWriteSize(quotaResult, false, 0)
maxWriteSize, _ := c.GetMaxWriteSize(quotaResult, false, 0, fs.IsUploadResumeSupported())
baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, resolvedPath, requestPath,
common.TransferUpload, 0, 0, maxWriteSize, true, c.Fs)
common.TransferUpload, 0, 0, maxWriteSize, true, fs)
t := newTransfer(baseTransfer, w, nil, 0)
return t, nil
}
func (c *Connection) handleFTPUploadToExistingFile(flags int, resolvedPath, filePath string, fileSize int64,
func (c *Connection) handleFTPUploadToExistingFile(fs vfs.Fs, flags int, resolvedPath, filePath string, fileSize int64,
requestPath string) (ftpserver.FileTransfer, error) {
var err error
quotaResult := c.HasSpace(false, false, requestPath)
@@ -436,25 +392,25 @@ func (c *Connection) handleFTPUploadToExistingFile(flags int, resolvedPath, file
isResume := flags&os.O_TRUNC == 0
// if there is a size limit remaining size cannot be 0 here, since quotaResult.HasSpace
// will return false in this case and we deny the upload before
maxWriteSize, err := c.GetMaxWriteSize(quotaResult, isResume, fileSize)
maxWriteSize, err := c.GetMaxWriteSize(quotaResult, isResume, fileSize, fs.IsUploadResumeSupported())
if err != nil {
c.Log(logger.LevelDebug, "unable to get max write size: %v", err)
return nil, err
}
if common.Config.IsAtomicUploadEnabled() && c.Fs.IsAtomicUploadSupported() {
err = c.Fs.Rename(resolvedPath, filePath)
if common.Config.IsAtomicUploadEnabled() && fs.IsAtomicUploadSupported() {
err = fs.Rename(resolvedPath, filePath)
if err != nil {
c.Log(logger.LevelWarn, "error renaming existing file for atomic upload, source: %#v, dest: %#v, err: %+v",
resolvedPath, filePath, err)
return nil, c.GetFsError(err)
return nil, c.GetFsError(fs, err)
}
}
file, w, cancelFn, err := c.Fs.Create(filePath, flags)
file, w, cancelFn, err := fs.Create(filePath, flags)
if err != nil {
c.Log(logger.LevelWarn, "error opening existing file, flags: %v, source: %#v, err: %+v", flags, filePath, err)
return nil, c.GetFsError(err)
return nil, c.GetFsError(fs, err)
}
initialSize := int64(0)
@@ -462,12 +418,12 @@ func (c *Connection) handleFTPUploadToExistingFile(flags int, resolvedPath, file
c.Log(logger.LevelDebug, "upload resume requested, file path: %#v initial size: %v", filePath, fileSize)
minWriteOffset = fileSize
initialSize = fileSize
if vfs.IsSFTPFs(c.Fs) {
if vfs.IsSFTPFs(fs) {
// we need this since we don't allow resume with wrong offset, we should fix this in pkg/sftp
file.Seek(initialSize, io.SeekStart) //nolint:errcheck // for sftp seek cannot file, it simply set the offset
}
} else {
if vfs.IsLocalOrSFTPFs(c.Fs) {
if vfs.IsLocalOrSFTPFs(fs) {
vfolder, err := c.User.GetVirtualFolderForPath(path.Dir(requestPath))
if err == nil {
dataprovider.UpdateVirtualFolderQuota(&vfolder.BaseVirtualFolder, 0, -fileSize, false) //nolint:errcheck
@@ -482,10 +438,10 @@ func (c *Connection) handleFTPUploadToExistingFile(flags int, resolvedPath, file
}
}
vfs.SetPathPermissions(c.Fs, filePath, c.User.GetUID(), c.User.GetGID())
vfs.SetPathPermissions(fs, filePath, c.User.GetUID(), c.User.GetGID())
baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, resolvedPath, requestPath,
common.TransferUpload, minWriteOffset, initialSize, maxWriteSize, false, c.Fs)
common.TransferUpload, minWriteOffset, initialSize, maxWriteSize, false, fs)
t := newTransfer(baseTransfer, w, nil, 0)
return t, nil

View File

@@ -351,7 +351,7 @@ func (fs MockOsFs) Rename(source, target string) error {
func newMockOsFs(err, statErr error, atomicUpload bool, connectionID, rootDir string) vfs.Fs {
return &MockOsFs{
Fs: vfs.NewOsFs(connectionID, rootDir, nil),
Fs: vfs.NewOsFs(connectionID, rootDir, ""),
err: err,
statErr: statErr,
isAtomicUploadSupported: atomicUpload,
@@ -492,7 +492,7 @@ func TestClientVersion(t *testing.T) {
connID := fmt.Sprintf("2_%v", mockCC.ID())
user := dataprovider.User{}
connection := &Connection{
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, nil),
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user),
clientContext: mockCC,
}
common.Connections.Add(connection)
@@ -509,7 +509,7 @@ func TestDriverMethodsNotImplemented(t *testing.T) {
connID := fmt.Sprintf("2_%v", mockCC.ID())
user := dataprovider.User{}
connection := &Connection{
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, nil),
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user),
clientContext: mockCC,
}
_, err := connection.Create("")
@@ -533,9 +533,8 @@ func TestResolvePathErrors(t *testing.T) {
user.Permissions["/"] = []string{dataprovider.PermAny}
mockCC := mockFTPClientContext{}
connID := fmt.Sprintf("%v", mockCC.ID())
fs := vfs.NewOsFs(connID, user.HomeDir, nil)
connection := &Connection{
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, fs),
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user),
clientContext: mockCC,
}
err := connection.Mkdir("", os.ModePerm)
@@ -596,9 +595,9 @@ func TestUploadFileStatError(t *testing.T) {
user.Permissions["/"] = []string{dataprovider.PermAny}
mockCC := mockFTPClientContext{}
connID := fmt.Sprintf("%v", mockCC.ID())
fs := vfs.NewOsFs(connID, user.HomeDir, nil)
fs := vfs.NewOsFs(connID, user.HomeDir, "")
connection := &Connection{
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, fs),
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user),
clientContext: mockCC,
}
testFile := filepath.Join(user.HomeDir, "test", "testfile")
@@ -608,7 +607,7 @@ func TestUploadFileStatError(t *testing.T) {
assert.NoError(t, err)
err = os.Chmod(filepath.Dir(testFile), 0001)
assert.NoError(t, err)
_, err = connection.uploadFile(testFile, "test", 0)
_, err = connection.uploadFile(fs, testFile, "test", 0)
assert.Error(t, err)
err = os.Chmod(filepath.Dir(testFile), os.ModePerm)
assert.NoError(t, err)
@@ -625,9 +624,8 @@ func TestAVBLErrors(t *testing.T) {
user.Permissions["/"] = []string{dataprovider.PermAny}
mockCC := mockFTPClientContext{}
connID := fmt.Sprintf("%v", mockCC.ID())
fs := newMockOsFs(nil, nil, false, connID, user.GetHomeDir())
connection := &Connection{
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, fs),
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user),
clientContext: mockCC,
}
_, err := connection.GetAvailableSpace("/")
@@ -648,12 +646,12 @@ func TestUploadOverwriteErrors(t *testing.T) {
connID := fmt.Sprintf("%v", mockCC.ID())
fs := newMockOsFs(nil, nil, false, connID, user.GetHomeDir())
connection := &Connection{
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, fs),
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user),
clientContext: mockCC,
}
flags := 0
flags |= os.O_APPEND
_, err := connection.handleFTPUploadToExistingFile(flags, "", "", 0, "")
_, err := connection.handleFTPUploadToExistingFile(fs, flags, "", "", 0, "")
if assert.Error(t, err) {
assert.EqualError(t, err, common.ErrOpUnsupported.Error())
}
@@ -665,7 +663,7 @@ func TestUploadOverwriteErrors(t *testing.T) {
flags = 0
flags |= os.O_CREATE
flags |= os.O_TRUNC
tr, err := connection.handleFTPUploadToExistingFile(flags, f.Name(), f.Name(), 123, f.Name())
tr, err := connection.handleFTPUploadToExistingFile(fs, flags, f.Name(), f.Name(), 123, f.Name())
if assert.NoError(t, err) {
transfer := tr.(*transfer)
transfers := connection.GetTransfers()
@@ -680,11 +678,11 @@ func TestUploadOverwriteErrors(t *testing.T) {
err = os.Remove(f.Name())
assert.NoError(t, err)
_, err = connection.handleFTPUploadToExistingFile(os.O_TRUNC, filepath.Join(os.TempDir(), "sub", "file"),
_, err = connection.handleFTPUploadToExistingFile(fs, os.O_TRUNC, filepath.Join(os.TempDir(), "sub", "file"),
filepath.Join(os.TempDir(), "sub", "file1"), 0, "/sub/file1")
assert.Error(t, err)
connection.Fs = vfs.NewOsFs(connID, user.GetHomeDir(), nil)
_, err = connection.handleFTPUploadToExistingFile(0, "missing1", "missing2", 0, "missing")
fs = vfs.NewOsFs(connID, user.GetHomeDir(), "")
_, err = connection.handleFTPUploadToExistingFile(fs, 0, "missing1", "missing2", 0, "missing")
assert.Error(t, err)
}
@@ -702,7 +700,7 @@ func TestTransferErrors(t *testing.T) {
connID := fmt.Sprintf("%v", mockCC.ID())
fs := newMockOsFs(nil, nil, false, connID, user.GetHomeDir())
connection := &Connection{
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, fs),
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user),
clientContext: mockCC,
}
baseTransfer := common.NewBaseTransfer(file, connection.BaseConnection, nil, file.Name(), testfile, common.TransferDownload,

View File

@@ -145,7 +145,7 @@ func (s *Server) ClientConnected(cc ftpserver.ClientContext) (string, error) {
connID := fmt.Sprintf("%v_%v", s.ID, cc.ID())
user := dataprovider.User{}
connection := &Connection{
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, nil),
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user),
clientContext: cc,
}
common.Connections.Add(connection)
@@ -180,7 +180,6 @@ func (s *Server) AuthUser(cc ftpserver.ClientContext, username, password string)
if err != nil {
return nil, err
}
connection.Fs.CheckRootPath(connection.GetUsername(), user.GetUID(), user.GetGID())
connection.Log(logger.LevelInfo, "User id: %d, logged in with FTP, username: %#v, home_dir: %#v remote addr: %#v",
user.ID, user.Username, user.HomeDir, ipAddr)
dataprovider.UpdateLastLogin(&user) //nolint:errcheck
@@ -219,7 +218,6 @@ func (s *Server) VerifyConnection(cc ftpserver.ClientContext, user string, tlsCo
if err != nil {
return nil, err
}
connection.Fs.CheckRootPath(connection.GetUsername(), dbUser.GetUID(), dbUser.GetGID())
connection.Log(logger.LevelInfo, "User id: %d, logged in with FTP using a TLS certificate, username: %#v, home_dir: %#v remote addr: %#v",
dbUser.ID, dbUser.Username, dbUser.HomeDir, ipAddr)
dataprovider.UpdateLastLogin(&dbUser) //nolint:errcheck
@@ -295,11 +293,11 @@ func (s *Server) validateUser(user dataprovider.User, cc ftpserver.ClientContext
}
if utils.IsStringInSlice(common.ProtocolFTP, user.Filters.DeniedProtocols) {
logger.Debug(logSender, connectionID, "cannot login user %#v, protocol FTP is not allowed", user.Username)
return nil, fmt.Errorf("Protocol FTP is not allowed for user %#v", user.Username)
return nil, fmt.Errorf("protocol FTP is not allowed for user %#v", user.Username)
}
if !user.IsLoginMethodAllowed(loginMethod, nil) {
logger.Debug(logSender, connectionID, "cannot login user %#v, %v login method is not allowed", user.Username, loginMethod)
return nil, fmt.Errorf("Login method %v is not allowed for user %#v", loginMethod, user.Username)
return nil, fmt.Errorf("login method %v is not allowed for user %#v", loginMethod, user.Username)
}
if user.MaxSessions > 0 {
activeSessions := common.Connections.GetActiveSessions(user.Username)
@@ -309,27 +307,26 @@ func (s *Server) validateUser(user dataprovider.User, cc ftpserver.ClientContext
return nil, fmt.Errorf("too many open sessions: %v", activeSessions)
}
}
if dataprovider.GetQuotaTracking() > 0 && user.HasOverlappedMappedPaths() {
logger.Debug(logSender, connectionID, "cannot login user %#v, overlapping mapped folders are allowed only with quota tracking disabled",
user.Username)
return nil, errors.New("overlapping mapped folders are allowed only with quota tracking disabled")
}
remoteAddr := cc.RemoteAddr().String()
if !user.IsLoginFromAddrAllowed(remoteAddr) {
logger.Debug(logSender, connectionID, "cannot login user %#v, remote address is not allowed: %v", user.Username, remoteAddr)
return nil, fmt.Errorf("Login for user %#v is not allowed from this address: %v", user.Username, remoteAddr)
return nil, fmt.Errorf("login for user %#v is not allowed from this address: %v", user.Username, remoteAddr)
}
fs, err := user.GetFilesystem(connectionID)
err := user.CheckFsRoot(connectionID)
if err != nil {
errClose := user.CloseFs()
logger.Warn(logSender, connectionID, "unable to check fs root: %v close fs error: %v", err, errClose)
return nil, err
}
connection := &Connection{
BaseConnection: common.NewBaseConnection(fmt.Sprintf("%v_%v", s.ID, cc.ID()), common.ProtocolFTP, user, fs),
BaseConnection: common.NewBaseConnection(fmt.Sprintf("%v_%v", s.ID, cc.ID()), common.ProtocolFTP, user),
clientContext: cc,
}
err = common.Connections.Swap(connection)
if err != nil {
return nil, errors.New("Internal authentication error")
err = user.CloseFs()
logger.Warn(logSender, connectionID, "unable to swap connection, close fs error: %v", err)
return nil, errors.New("internal authentication error")
}
return connection, nil
}

View File

@@ -102,7 +102,7 @@ func (t *transfer) Close() error {
if errBaseClose != nil {
err = errBaseClose
}
return t.Connection.GetFsError(err)
return t.Connection.GetFsError(t.Fs, err)
}
func (t *transfer) closeIO() error {