mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-07 23:00:55 +03:00
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
152
ftpd/handler.go
152
ftpd/handler.go
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user