diff --git a/go.mod b/go.mod index bdfc2dcfb..035c6ae51 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( github.com/mhale/smtpd v0.8.0 github.com/minio/sio v0.3.1 github.com/otiai10/copy v1.9.0 + github.com/peterverraedt/useros v0.1.5 github.com/pires/go-proxyproto v0.6.2 github.com/pkg/sftp v1.13.6-0.20230213180117-971c283182b6 github.com/pquerna/otp v1.4.0 @@ -121,6 +122,7 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/kr/fs v0.1.0 // indirect github.com/lestrrat-go/blackmagic v1.0.1 // indirect diff --git a/go.sum b/go.sum index efa0b694e..665ed0f7a 100644 --- a/go.sum +++ b/go.sum @@ -1411,6 +1411,9 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 h1:hgVxRoDDPtQE68PT4LFvNlPz2nBKd3OMlGKIQ69OmR4= +github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531/go.mod h1:fqTUQpVYBvhCNIsMXGl2GE9q6z94DIP6NtFKXCSTVbg= +github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d h1:J8tJzRyiddAFF65YVgxli+TyWBi0f79Sld6rJP6CBcY= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -1708,6 +1711,8 @@ github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9Cjg github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/peterverraedt/useros v0.1.5 h1:zwt96Q/3vm89gAUlgFzyfq3aiTVpGsvapIQqnxEphXA= +github.com/peterverraedt/useros v0.1.5/go.mod h1:JJp7q3r6tvRHC0QcQ3BifCSvQGQeeBxShAjdUS/yg1I= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8= github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= diff --git a/internal/common/actions_test.go b/internal/common/actions_test.go index 7a3a404d2..401b86694 100644 --- a/internal/common/actions_test.go +++ b/internal/common/actions_test.go @@ -262,7 +262,7 @@ func TestPreDeleteAction(t *testing.T) { } user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} - fs := vfs.NewOsFs("id", homeDir, "") + fs := vfs.NewOsFs("id", homeDir, "", 0, 0) c := NewBaseConnection("id", ProtocolSFTP, "", "", user) testfile := filepath.Join(user.HomeDir, "testfile") diff --git a/internal/common/common_test.go b/internal/common/common_test.go index 041d82107..8184e554d 100644 --- a/internal/common/common_test.go +++ b/internal/common/common_test.go @@ -830,7 +830,7 @@ func TestConnectionStatus(t *testing.T) { Username: username, }, } - fs := vfs.NewOsFs("", os.TempDir(), "") + fs := vfs.NewOsFs("", os.TempDir(), "", 0, 0) c1 := NewBaseConnection("id1", ProtocolSFTP, "", "", user) fakeConn1 := &fakeConnection{ BaseConnection: c1, diff --git a/internal/common/connection.go b/internal/common/connection.go index da9ad4f63..0876843f7 100644 --- a/internal/common/connection.go +++ b/internal/common/connection.go @@ -365,7 +365,6 @@ func (c *BaseConnection) CreateDir(virtualPath string, checkFilePatterns bool) e c.Log(logger.LevelError, "error creating dir: %q error: %+v", fsPath, err) return c.GetFsError(fs, err) } - vfs.SetPathPermissions(fs, fsPath, c.User.GetUID(), c.User.GetGID()) elapsed := time.Since(startTime).Nanoseconds() / 1000000 logger.CommandLog(mkdirLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", -1, @@ -764,7 +763,6 @@ func (c *BaseConnection) renameInternal(virtualSourcePath, virtualTargetPath str c.Log(logger.LevelError, "failed to rename %q -> %q: %+v", fsSourcePath, fsTargetPath, err) return c.GetFsError(fsSrc, err) } - vfs.SetPathPermissions(fsDst, fsTargetPath, c.User.GetUID(), c.User.GetGID()) elapsed := time.Since(startTime).Nanoseconds() / 1000000 c.updateQuotaAfterRename(fsDst, virtualSourcePath, virtualTargetPath, fsTargetPath, initialSize, files, size) //nolint:errcheck logger.CommandLog(renameLogSender, fsSourcePath, fsTargetPath, c.User.Username, "", c.ID, c.protocol, -1, -1, diff --git a/internal/common/connection_test.go b/internal/common/connection_test.go index 68e711cb8..447741db4 100644 --- a/internal/common/connection_test.go +++ b/internal/common/connection_test.go @@ -86,7 +86,7 @@ func (fs *MockOsFs) Walk(root string, walkFn filepath.WalkFunc) error { func newMockOsFs(hasVirtualFolders bool, connectionID, rootDir, name string, err error) vfs.Fs { return &MockOsFs{ - Fs: vfs.NewOsFs(connectionID, rootDir, ""), + Fs: vfs.NewOsFs(connectionID, rootDir, "", 0, 0), name: name, hasVirtualFolders: hasVirtualFolders, err: err, @@ -114,7 +114,7 @@ func TestRemoveErrors(t *testing.T) { } user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} - fs := vfs.NewOsFs("", os.TempDir(), "") + fs := vfs.NewOsFs("", os.TempDir(), "", 0, 0) conn := NewBaseConnection("", ProtocolFTP, "", "", user) err := conn.IsRemoveDirAllowed(fs, mappedPath, "/virtualpath1") if assert.Error(t, err) { @@ -159,7 +159,7 @@ func TestSetStatMode(t *testing.T) { } func TestRecursiveRenameWalkError(t *testing.T) { - fs := vfs.NewOsFs("", filepath.Clean(os.TempDir()), "") + fs := vfs.NewOsFs("", filepath.Clean(os.TempDir()), "", 0, 0) conn := NewBaseConnection("", ProtocolWebDAV, "", "", dataprovider.User{ BaseUser: sdk.BaseUser{ Permissions: map[string][]string{ @@ -193,7 +193,7 @@ func TestRecursiveRenameWalkError(t *testing.T) { } func TestCrossRenameFsErrors(t *testing.T) { - fs := vfs.NewOsFs("", os.TempDir(), "") + fs := vfs.NewOsFs("", os.TempDir(), "", 0, 0) conn := NewBaseConnection("", ProtocolWebDAV, "", "", dataprovider.User{}) res := conn.hasSpaceForCrossRename(fs, vfs.QuotaCheckResult{}, 1, "missingsource") assert.False(t, res) @@ -224,7 +224,7 @@ func TestRenameVirtualFolders(t *testing.T) { }, VirtualPath: vdir, }) - fs := vfs.NewOsFs("", os.TempDir(), "") + fs := vfs.NewOsFs("", os.TempDir(), "", 0, 0) conn := NewBaseConnection("", ProtocolFTP, "", "", u) res := conn.isRenamePermitted(fs, fs, "source", "target", vdir, "vdirtarget", nil) assert.False(t, res) @@ -376,7 +376,7 @@ func TestUpdateQuotaAfterRename(t *testing.T) { } func TestErrorsMapping(t *testing.T) { - fs := vfs.NewOsFs("", os.TempDir(), "") + fs := vfs.NewOsFs("", os.TempDir(), "", 0, 0) conn := NewBaseConnection("", ProtocolSFTP, "", "", dataprovider.User{BaseUser: sdk.BaseUser{HomeDir: os.TempDir()}}) osErrorsProtocols := []string{ProtocolWebDAV, ProtocolFTP, ProtocolHTTP, ProtocolHTTPShare, ProtocolDataRetention, ProtocolOIDC, protocolEventAction} diff --git a/internal/common/eventmanager.go b/internal/common/eventmanager.go index 7a640d4b1..f5269cf82 100644 --- a/internal/common/eventmanager.go +++ b/internal/common/eventmanager.go @@ -795,7 +795,6 @@ func getFileWriter(conn *BaseConnection, virtualPath string, expectedSize int64) if err != nil { return nil, numFiles, truncatedSize, nil, conn.GetFsError(fs, err) } - vfs.SetPathPermissions(fs, fsPath, conn.User.GetUID(), conn.User.GetGID()) if isFileOverwrite { if vfs.HasTruncateSupport(fs) || vfs.IsCryptOsFs(fs) { @@ -1927,7 +1926,7 @@ func executeFoldersQuotaResetRuleAction(conditions dataprovider.ConditionOptions BaseVirtualFolder: folder, VirtualPath: "/", } - numFiles, size, err := f.ScanQuota() + numFiles, size, err := f.ScanQuota(0, 0) QuotaScans.RemoveVFolderQuotaScan(folder.Name) if err != nil { eventManagerLog(logger.LevelError, "error scanning quota for folder %q: %v", folder.Name, err) diff --git a/internal/common/transfer_test.go b/internal/common/transfer_test.go index dd9b508f2..2315f7825 100644 --- a/internal/common/transfer_test.go +++ b/internal/common/transfer_test.go @@ -35,7 +35,7 @@ func TestTransferUpdateQuota(t *testing.T) { transfer := BaseTransfer{ Connection: conn, transferType: TransferUpload, - Fs: vfs.NewOsFs("", os.TempDir(), ""), + Fs: vfs.NewOsFs("", os.TempDir(), "", 0, 0), } transfer.BytesReceived.Store(123) errFake := errors.New("fake error") @@ -74,7 +74,7 @@ func TestTransferThrottling(t *testing.T) { DownloadBandwidth: 40, }, } - fs := vfs.NewOsFs("", os.TempDir(), "") + fs := vfs.NewOsFs("", os.TempDir(), "", 0, 0) testFileSize := int64(131072) wantedUploadElapsed := 1000 * (testFileSize / 1024) / u.UploadBandwidth wantedDownloadElapsed := 1000 * (testFileSize / 1024) / u.DownloadBandwidth @@ -106,7 +106,7 @@ func TestTransferThrottling(t *testing.T) { func TestRealPath(t *testing.T) { testFile := filepath.Join(os.TempDir(), "afile.txt") - fs := vfs.NewOsFs("123", os.TempDir(), "") + fs := vfs.NewOsFs("123", os.TempDir(), "", 0, 0) u := dataprovider.User{ BaseUser: sdk.BaseUser{ Username: "user", @@ -140,7 +140,7 @@ func TestRealPath(t *testing.T) { func TestTruncate(t *testing.T) { testFile := filepath.Join(os.TempDir(), "transfer_test_file") - fs := vfs.NewOsFs("123", os.TempDir(), "") + fs := vfs.NewOsFs("123", os.TempDir(), "", 0, 0) u := dataprovider.User{ BaseUser: sdk.BaseUser{ Username: "user", @@ -209,7 +209,7 @@ func TestTransferErrors(t *testing.T) { isCancelled = true } testFile := filepath.Join(os.TempDir(), "transfer_test_file") - fs := vfs.NewOsFs("id", os.TempDir(), "") + fs := vfs.NewOsFs("id", os.TempDir(), "", 0, 0) u := dataprovider.User{ BaseUser: sdk.BaseUser{ Username: "test", @@ -316,7 +316,7 @@ func TestFTPMode(t *testing.T) { transfer := BaseTransfer{ Connection: conn, transferType: TransferUpload, - Fs: vfs.NewOsFs("", os.TempDir(), ""), + Fs: vfs.NewOsFs("", os.TempDir(), "", 0, 0), } transfer.BytesReceived.Store(123) assert.Empty(t, transfer.ftpMode) @@ -394,7 +394,7 @@ func TestTransferQuota(t *testing.T) { conn := NewBaseConnection("", ProtocolSFTP, "", "", user) transfer := NewBaseTransfer(nil, conn, nil, "file.txt", "file.txt", "/transfer_test_file", TransferUpload, - 0, 0, 0, 0, true, vfs.NewOsFs("", os.TempDir(), ""), dataprovider.TransferQuota{}) + 0, 0, 0, 0, true, vfs.NewOsFs("", os.TempDir(), "", 0, 0), dataprovider.TransferQuota{}) err := transfer.CheckRead() assert.NoError(t, err) err = transfer.CheckWrite() @@ -448,7 +448,7 @@ func TestUploadOutsideHomeRenameError(t *testing.T) { transfer := BaseTransfer{ Connection: conn, transferType: TransferUpload, - Fs: vfs.NewOsFs("", filepath.Join(os.TempDir(), "home"), ""), + Fs: vfs.NewOsFs("", filepath.Join(os.TempDir(), "home"), "", 0, 0), } transfer.BytesReceived.Store(123) diff --git a/internal/dataprovider/user.go b/internal/dataprovider/user.go index 63e114f29..b113fdb5a 100644 --- a/internal/dataprovider/user.go +++ b/internal/dataprovider/user.go @@ -173,7 +173,7 @@ func (u *User) getRootFs(connectionID string) (fs vfs.Fs, err error) { case sdk.HTTPFilesystemProvider: return vfs.NewHTTPFs(connectionID, u.GetHomeDir(), "", u.FsConfig.HTTPConfig) default: - return vfs.NewOsFs(connectionID, u.GetHomeDir(), ""), nil + return vfs.NewOsFs(connectionID, u.GetHomeDir(), "", u.UID, u.GID), nil } } @@ -204,7 +204,6 @@ func (u *User) checkDirWithParents(virtualDirPath, connectionID string) error { if err != nil { return err } - vfs.SetPathPermissions(fs, fsPath, u.GetUID(), u.GetGID()) } else { return fmt.Errorf("unable to stat path %q: %w", vPath, err) } @@ -218,7 +217,7 @@ func (u *User) checkLocalHomeDir(connectionID string) { case sdk.LocalFilesystemProvider, sdk.CryptedFilesystemProvider: return default: - osFs := vfs.NewOsFs(connectionID, u.GetHomeDir(), "") + osFs := vfs.NewOsFs(connectionID, u.GetHomeDir(), "", u.UID, u.GID) osFs.CheckRootPath(u.Username, u.GetUID(), u.GetGID()) } } @@ -565,7 +564,7 @@ func (u *User) GetFilesystemForPath(virtualPath, connectionID string) (vfs.Fs, e } forbiddenSelfUsers = append(forbiddenSelfUsers, forbiddens...) } - fs, err := folder.GetFilesystem(connectionID, forbiddenSelfUsers) + fs, err := folder.GetFilesystem(connectionID, u.UID, u.GID, forbiddenSelfUsers) if err == nil { u.fsCache[folder.VirtualPath] = fs } @@ -617,7 +616,7 @@ func (u *User) CheckMetadataConsistency() error { } for idx := range u.VirtualFolders { v := &u.VirtualFolders[idx] - if err = v.CheckMetadataConsistency(); err != nil { + if err = v.CheckMetadataConsistency(u.UID, u.GID); err != nil { return err } } @@ -642,7 +641,7 @@ func (u *User) ScanQuota() (int, int64, error) { if !v.IsIncludedInUserQuota() { continue } - num, s, err := v.ScanQuota() + num, s, err := v.ScanQuota(u.UID, u.GID) if err != nil { return numFiles, size, err } diff --git a/internal/ftpd/handler.go b/internal/ftpd/handler.go index 2a9474646..a55fa2ab8 100644 --- a/internal/ftpd/handler.go +++ b/internal/ftpd/handler.go @@ -414,8 +414,6 @@ func (c *Connection) handleFTPUploadToNewFile(fs vfs.Fs, flags int, resolvedPath return nil, c.GetFsError(fs, err) } - vfs.SetPathPermissions(fs, filePath, c.User.GetUID(), c.User.GetGID()) - // we can get an error only for resume maxWriteSize, _ := c.GetMaxWriteSize(diskQuota, false, 0, fs.IsUploadResumeSupported()) @@ -496,8 +494,6 @@ func (c *Connection) handleFTPUploadToExistingFile(fs vfs.Fs, flags int, resolve } } - vfs.SetPathPermissions(fs, filePath, c.User.GetUID(), c.User.GetGID()) - baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, resolvedPath, filePath, requestPath, common.TransferUpload, minWriteOffset, initialSize, maxWriteSize, truncatedSize, false, fs, transferQuota) baseTransfer.SetFtpMode(c.getFTPMode()) diff --git a/internal/ftpd/internal_test.go b/internal/ftpd/internal_test.go index 769b4ca8c..c059cb0d9 100644 --- a/internal/ftpd/internal_test.go +++ b/internal/ftpd/internal_test.go @@ -398,7 +398,7 @@ func (fs MockOsFs) Rename(source, target string) (int, int64, error) { func newMockOsFs(err, statErr error, atomicUpload bool, connectionID, rootDir string) vfs.Fs { return &MockOsFs{ - Fs: vfs.NewOsFs(connectionID, rootDir, ""), + Fs: vfs.NewOsFs(connectionID, rootDir, "", 0, 0), err: err, statErr: statErr, isAtomicUploadSupported: atomicUpload, @@ -723,7 +723,7 @@ func TestUploadFileStatError(t *testing.T) { user.Permissions["/"] = []string{dataprovider.PermAny} mockCC := mockFTPClientContext{} connID := fmt.Sprintf("%v", mockCC.ID()) - fs := vfs.NewOsFs(connID, user.HomeDir, "") + fs := vfs.NewOsFs(connID, user.HomeDir, "", 0, 0) connection := &Connection{ BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, "", "", user), clientContext: mockCC, @@ -813,7 +813,7 @@ func TestUploadOverwriteErrors(t *testing.T) { _, 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) - fs = vfs.NewOsFs(connID, user.GetHomeDir(), "") + fs = vfs.NewOsFs(connID, user.GetHomeDir(), "", 0, 0) _, err = connection.handleFTPUploadToExistingFile(fs, 0, "missing1", "missing2", 0, "missing") assert.Error(t, err) } diff --git a/internal/httpd/api_quota.go b/internal/httpd/api_quota.go index e44224bad..075406817 100644 --- a/internal/httpd/api_quota.go +++ b/internal/httpd/api_quota.go @@ -260,7 +260,7 @@ func doFolderQuotaScan(folder vfs.BaseVirtualFolder) error { BaseVirtualFolder: folder, VirtualPath: "/", } - numFiles, size, err := f.ScanQuota() + numFiles, size, err := f.ScanQuota(0, 0) if err != nil { logger.Warn(logSender, "", "error scanning folder %q: %v", folder.Name, err) return err diff --git a/internal/httpd/handler.go b/internal/httpd/handler.go index 931effcb0..4fb4df587 100644 --- a/internal/httpd/handler.go +++ b/internal/httpd/handler.go @@ -229,8 +229,6 @@ func (c *Connection) handleUploadFile(fs vfs.Fs, resolvedPath, filePath, request } } - vfs.SetPathPermissions(fs, filePath, c.User.GetUID(), c.User.GetGID()) - baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, resolvedPath, filePath, requestPath, common.TransferUpload, 0, initialSize, maxWriteSize, truncatedSize, isNewFile, fs, transferQuota) return newHTTPDFile(baseTransfer, w, nil), nil diff --git a/internal/httpd/httpd_test.go b/internal/httpd/httpd_test.go index 0cfec07b0..731316e52 100644 --- a/internal/httpd/httpd_test.go +++ b/internal/httpd/httpd_test.go @@ -12012,7 +12012,7 @@ func TestWebClientMaxConnections(t *testing.T) { checkResponseCode(t, http.StatusOK, rr) // now add a fake connection - fs := vfs.NewOsFs("id", os.TempDir(), "") + fs := vfs.NewOsFs("id", os.TempDir(), "", 0, 0) connection := &httpd.Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolHTTP, "", "", user), } @@ -12203,7 +12203,7 @@ func TestMaxSessions(t *testing.T) { apiToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword) assert.NoError(t, err) // now add a fake connection - fs := vfs.NewOsFs("id", os.TempDir(), "") + fs := vfs.NewOsFs("id", os.TempDir(), "", 0, 0) connection := &httpd.Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolHTTP, "", "", user), } @@ -13312,7 +13312,7 @@ func TestShareMaxSessions(t *testing.T) { rr = executeRequest(req) checkResponseCode(t, http.StatusOK, rr) // add a fake connection - fs := vfs.NewOsFs("id", os.TempDir(), "") + fs := vfs.NewOsFs("id", os.TempDir(), "", 0, 0) connection := &httpd.Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolHTTP, "", "", user), } diff --git a/internal/sftpd/handler.go b/internal/sftpd/handler.go index af8659be0..290bb34f9 100644 --- a/internal/sftpd/handler.go +++ b/internal/sftpd/handler.go @@ -413,8 +413,6 @@ func (c *Connection) handleSFTPUploadToNewFile(fs vfs.Fs, pflags sftp.FileOpenFl return nil, c.GetFsError(fs, err) } - vfs.SetPathPermissions(fs, filePath, c.User.GetUID(), c.User.GetGID()) - // we can get an error only for resume maxWriteSize, _ := c.GetMaxWriteSize(diskQuota, false, 0, fs.IsUploadResumeSupported()) @@ -497,8 +495,6 @@ func (c *Connection) handleSFTPUploadToExistingFile(fs vfs.Fs, pflags sftp.FileO } } - vfs.SetPathPermissions(fs, filePath, c.User.GetUID(), c.User.GetGID()) - baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, resolvedPath, filePath, requestPath, common.TransferUpload, minWriteOffset, initialSize, maxWriteSize, truncatedSize, false, fs, transferQuota) t := newTransfer(baseTransfer, w, nil, errForRead) diff --git a/internal/sftpd/internal_test.go b/internal/sftpd/internal_test.go index f0a5733a4..1f556091e 100644 --- a/internal/sftpd/internal_test.go +++ b/internal/sftpd/internal_test.go @@ -149,7 +149,7 @@ func (fs MockOsFs) Rename(source, target string) (int, int64, error) { func newMockOsFs(err, statErr error, atomicUpload bool, connectionID, rootDir string) vfs.Fs { return &MockOsFs{ - Fs: vfs.NewOsFs(connectionID, rootDir, ""), + Fs: vfs.NewOsFs(connectionID, rootDir, "", 0, 0), err: err, statErr: statErr, isAtomicUploadSupported: atomicUpload, @@ -183,7 +183,7 @@ func TestUploadResumeInvalidOffset(t *testing.T) { Username: "testuser", }, } - fs := vfs.NewOsFs("", os.TempDir(), "") + fs := vfs.NewOsFs("", os.TempDir(), "", 0, 0) conn := common.NewBaseConnection("", common.ProtocolSFTP, "", "", user) baseTransfer := common.NewBaseTransfer(file, conn, nil, file.Name(), file.Name(), testfile, common.TransferUpload, 10, 0, 0, 0, false, fs, dataprovider.TransferQuota{}) @@ -214,7 +214,7 @@ func TestReadWriteErrors(t *testing.T) { Username: "testuser", }, } - fs := vfs.NewOsFs("", os.TempDir(), "") + fs := vfs.NewOsFs("", os.TempDir(), "", 0, 0) conn := common.NewBaseConnection("", common.ProtocolSFTP, "", "", user) baseTransfer := common.NewBaseTransfer(file, conn, nil, file.Name(), file.Name(), testfile, common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{}) @@ -288,7 +288,7 @@ func TestTransferCancelFn(t *testing.T) { Username: "testuser", }, } - fs := vfs.NewOsFs("", os.TempDir(), "") + fs := vfs.NewOsFs("", os.TempDir(), "", 0, 0) conn := common.NewBaseConnection("", common.ProtocolSFTP, "", "", user) baseTransfer := common.NewBaseTransfer(file, conn, cancelFn, file.Name(), file.Name(), testfile, common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{}) @@ -311,7 +311,7 @@ func TestTransferCancelFn(t *testing.T) { func TestUploadFiles(t *testing.T) { common.Config.UploadMode = common.UploadModeAtomic - fs := vfs.NewOsFs("123", os.TempDir(), "") + fs := vfs.NewOsFs("123", os.TempDir(), "", 0, 0) u := dataprovider.User{} c := Connection{ BaseConnection: common.NewBaseConnection("", common.ProtocolSFTP, "", "", u), @@ -1213,7 +1213,7 @@ func TestSCPParseUploadMessage(t *testing.T) { StdErrBuffer: bytes.NewBuffer(stdErrBuf), ReadError: nil, } - fs := vfs.NewOsFs("", os.TempDir(), "") + fs := vfs.NewOsFs("", os.TempDir(), "", 0, 0) connection := &Connection{ BaseConnection: common.NewBaseConnection("", common.ProtocolSFTP, "", "", dataprovider.User{ BaseUser: sdk.BaseUser{ @@ -1470,7 +1470,7 @@ func TestSCPRecursiveDownloadErrors(t *testing.T) { err := client.Close() assert.NoError(t, err) }() - fs := vfs.NewOsFs("123", os.TempDir(), "") + fs := vfs.NewOsFs("123", os.TempDir(), "", 0, 0) connection := &Connection{ BaseConnection: common.NewBaseConnection("", common.ProtocolSCP, "", "", dataprovider.User{ BaseUser: sdk.BaseUser{ @@ -1593,7 +1593,7 @@ func TestSCPDownloadFileData(t *testing.T) { ReadError: nil, WriteError: writeErr, } - fs := vfs.NewOsFs("", os.TempDir(), "") + fs := vfs.NewOsFs("", os.TempDir(), "", 0, 0) connection := &Connection{ BaseConnection: common.NewBaseConnection("", common.ProtocolSCP, "", "", dataprovider.User{BaseUser: sdk.BaseUser{HomeDir: os.TempDir()}}), channel: &mockSSHChannelReadErr, @@ -1645,7 +1645,7 @@ func TestSCPUploadFiledata(t *testing.T) { Username: "testuser", }, } - fs := vfs.NewOsFs("", os.TempDir(), "") + fs := vfs.NewOsFs("", os.TempDir(), "", 0, 0) connection := &Connection{ BaseConnection: common.NewBaseConnection("", common.ProtocolSCP, "", "", user), channel: &mockSSHChannel, @@ -1736,7 +1736,7 @@ func TestUploadError(t *testing.T) { Username: "testuser", }, } - fs := vfs.NewOsFs("", os.TempDir(), "") + fs := vfs.NewOsFs("", os.TempDir(), "", 0, 0) connection := &Connection{ BaseConnection: common.NewBaseConnection("", common.ProtocolSCP, "", "", user), } diff --git a/internal/sftpd/scp.go b/internal/sftpd/scp.go index d02184902..a93c22790 100644 --- a/internal/sftpd/scp.go +++ b/internal/sftpd/scp.go @@ -276,8 +276,6 @@ func (c *scpCommand) handleUploadFile(fs vfs.Fs, resolvedPath, filePath string, } } - vfs.SetPathPermissions(fs, filePath, c.connection.User.GetUID(), c.connection.User.GetGID()) - baseTransfer := common.NewBaseTransfer(file, c.connection.BaseConnection, cancelFn, resolvedPath, filePath, requestPath, common.TransferUpload, 0, initialSize, maxWriteSize, truncatedSize, isNewFile, fs, transferQuota) t := newTransfer(baseTransfer, w, nil, nil) @@ -689,7 +687,6 @@ func (c *scpCommand) createDir(fs vfs.Fs, dirPath string) error { c.sendErrorMessage(fs, err) return err } - vfs.SetPathPermissions(fs, dirPath, c.connection.User.GetUID(), c.connection.User.GetGID()) return err } diff --git a/internal/sftpd/sftpd_test.go b/internal/sftpd/sftpd_test.go index 2ec077f80..2cdd10995 100644 --- a/internal/sftpd/sftpd_test.go +++ b/internal/sftpd/sftpd_test.go @@ -8047,7 +8047,7 @@ func TestRootDirCommands(t *testing.T) { func TestRelativePaths(t *testing.T) { user := getTestUser(true) var path, rel string - filesystems := []vfs.Fs{vfs.NewOsFs("", user.GetHomeDir(), "")} + filesystems := []vfs.Fs{vfs.NewOsFs("", user.GetHomeDir(), "", 0, 0)} keyPrefix := strings.TrimPrefix(user.GetHomeDir(), "/") + "/" s3config := vfs.S3FsConfig{ BaseS3FsConfig: sdk.BaseS3FsConfig{ @@ -8112,7 +8112,7 @@ func TestResolvePaths(t *testing.T) { user := getTestUser(true) var path, resolved string var err error - filesystems := []vfs.Fs{vfs.NewOsFs("", user.GetHomeDir(), "")} + filesystems := []vfs.Fs{vfs.NewOsFs("", user.GetHomeDir(), "", 0, 0)} keyPrefix := strings.TrimPrefix(user.GetHomeDir(), "/") + "/" s3config := vfs.S3FsConfig{ BaseS3FsConfig: sdk.BaseS3FsConfig{ @@ -8175,8 +8175,8 @@ func TestVirtualRelativePaths(t *testing.T) { }) err := os.MkdirAll(mappedPath, os.ModePerm) assert.NoError(t, err) - fsRoot := vfs.NewOsFs("", user.GetHomeDir(), "") - fsVdir := vfs.NewOsFs("", mappedPath, vdirPath) + fsRoot := vfs.NewOsFs("", user.GetHomeDir(), "", 0, 0) + fsVdir := vfs.NewOsFs("", mappedPath, vdirPath, 0, 0) rel := fsVdir.GetRelativePath(mappedPath) assert.Equal(t, vdirPath, rel) rel = fsRoot.GetRelativePath(filepath.Join(mappedPath, "..")) diff --git a/internal/vfs/azblobfs.go b/internal/vfs/azblobfs.go index 5b0bb1a3b..a2c12a8e0 100644 --- a/internal/vfs/azblobfs.go +++ b/internal/vfs/azblobfs.go @@ -507,7 +507,7 @@ func (*AzureBlobFs) isBadRequestError(err error) bool { // CheckRootPath creates the specified local root directory if it does not exists func (fs *AzureBlobFs) CheckRootPath(username string, uid int, gid int) bool { // we need a local directory for temporary files - osFs := NewOsFs(fs.ConnectionID(), fs.localTempDir, "") + osFs := NewOsFs(fs.ConnectionID(), fs.localTempDir, "", uid, gid) return osFs.CheckRootPath(username, uid, gid) } diff --git a/internal/vfs/folder.go b/internal/vfs/folder.go index 4d131a6f5..e44559517 100644 --- a/internal/vfs/folder.go +++ b/internal/vfs/folder.go @@ -192,7 +192,7 @@ type VirtualFolder struct { } // GetFilesystem returns the filesystem for this folder -func (v *VirtualFolder) GetFilesystem(connectionID string, forbiddenSelfUsers []string) (Fs, error) { +func (v *VirtualFolder) GetFilesystem(connectionID string, uid, gid int, forbiddenSelfUsers []string) (Fs, error) { switch v.FsConfig.Provider { case sdk.S3FilesystemProvider: return NewS3Fs(connectionID, v.MappedPath, v.VirtualPath, v.FsConfig.S3Config) @@ -207,14 +207,14 @@ func (v *VirtualFolder) GetFilesystem(connectionID string, forbiddenSelfUsers [] case sdk.HTTPFilesystemProvider: return NewHTTPFs(connectionID, v.MappedPath, v.VirtualPath, v.FsConfig.HTTPConfig) default: - return NewOsFs(connectionID, v.MappedPath, v.VirtualPath), nil + return NewOsFs(connectionID, v.MappedPath, v.VirtualPath, uid, gid), nil } } // CheckMetadataConsistency checks the consistency between the metadata stored // in the configured metadata plugin and the filesystem -func (v *VirtualFolder) CheckMetadataConsistency() error { - fs, err := v.GetFilesystem(xid.New().String(), nil) +func (v *VirtualFolder) CheckMetadataConsistency(uid, gid int) error { + fs, err := v.GetFilesystem(xid.New().String(), uid, gid, nil) if err != nil { return err } @@ -224,11 +224,11 @@ func (v *VirtualFolder) CheckMetadataConsistency() error { } // ScanQuota scans the folder and returns the number of files and their size -func (v *VirtualFolder) ScanQuota() (int, int64, error) { +func (v *VirtualFolder) ScanQuota(uid, gid int) (int, int64, error) { if v.hasPathPlaceholder() { return 0, 0, errors.New("cannot scan quota: this folder has a path placeholder") } - fs, err := v.GetFilesystem(xid.New().String(), nil) + fs, err := v.GetFilesystem(xid.New().String(), uid, gid, nil) if err != nil { return 0, 0, err } diff --git a/internal/vfs/gcsfs.go b/internal/vfs/gcsfs.go index 965a54aab..ada571d85 100644 --- a/internal/vfs/gcsfs.go +++ b/internal/vfs/gcsfs.go @@ -462,7 +462,7 @@ func (*GCSFs) IsNotSupported(err error) bool { // CheckRootPath creates the specified local root directory if it does not exists func (fs *GCSFs) CheckRootPath(username string, uid int, gid int) bool { // we need a local directory for temporary files - osFs := NewOsFs(fs.ConnectionID(), fs.localTempDir, "") + osFs := NewOsFs(fs.ConnectionID(), fs.localTempDir, "", uid, gid) return osFs.CheckRootPath(username, uid, gid) } diff --git a/internal/vfs/httpfs.go b/internal/vfs/httpfs.go index 35f1df69f..5fb61e2bb 100644 --- a/internal/vfs/httpfs.go +++ b/internal/vfs/httpfs.go @@ -527,7 +527,7 @@ func (*HTTPFs) IsNotSupported(err error) bool { // CheckRootPath creates the specified local root directory if it does not exists func (fs *HTTPFs) CheckRootPath(username string, uid int, gid int) bool { // we need a local directory for temporary files - osFs := NewOsFs(fs.ConnectionID(), fs.localTempDir, "") + osFs := NewOsFs(fs.ConnectionID(), fs.localTempDir, "", uid, gid) return osFs.CheckRootPath(username, uid, gid) } diff --git a/internal/vfs/osfs.go b/internal/vfs/osfs.go index 90bdc87b2..d4070b995 100644 --- a/internal/vfs/osfs.go +++ b/internal/vfs/osfs.go @@ -28,6 +28,7 @@ import ( "github.com/eikenb/pipeat" fscopy "github.com/otiai10/copy" + "github.com/peterverraedt/useros" "github.com/pkg/sftp" "github.com/rs/xid" @@ -55,15 +56,18 @@ type OsFs struct { rootDir string // if not empty this fs is mouted as virtual folder in the specified path mountPath string + // useros wrapper for os operations + os useros.OS } // NewOsFs returns an OsFs object that allows to interact with local Os filesystem -func NewOsFs(connectionID, rootDir, mountPath string) Fs { +func NewOsFs(connectionID, rootDir, mountPath string, uid, gid int) Fs { return &OsFs{ name: osFsName, connectionID: connectionID, rootDir: rootDir, mountPath: getMountPath(mountPath), + os: osAsUser(uid, gid), } } @@ -79,17 +83,17 @@ func (fs *OsFs) ConnectionID() string { // Stat returns a FileInfo describing the named file func (fs *OsFs) Stat(name string) (os.FileInfo, error) { - return os.Stat(name) + return fs.os.Stat(name) } // Lstat returns a FileInfo describing the named file func (fs *OsFs) Lstat(name string) (os.FileInfo, error) { - return os.Lstat(name) + return fs.os.Lstat(name) } // Open opens the named file for reading -func (*OsFs) Open(name string, offset int64) (File, *pipeat.PipeReaderAt, func(), error) { - f, err := os.Open(name) +func (fs *OsFs) Open(name string, offset int64) (File, *pipeat.PipeReaderAt, func(), error) { + f, err := fs.os.Open(name) if err != nil { return nil, nil, nil, err } @@ -104,13 +108,13 @@ func (*OsFs) Open(name string, offset int64) (File, *pipeat.PipeReaderAt, func() } // Create creates or opens the named file for writing -func (*OsFs) Create(name string, flag int) (File, *PipeWriter, func(), error) { +func (fs *OsFs) Create(name string, flag int) (File, *PipeWriter, func(), error) { var err error - var f *os.File + var f useros.File if flag == 0 { - f, err = os.Create(name) + f, err = fs.os.Create(name) } else { - f, err = os.OpenFile(name, flag, 0666) + f, err = fs.os.OpenFile(name, flag, 0666) } return f, nil, nil, err } @@ -120,38 +124,55 @@ func (fs *OsFs) Rename(source, target string) (int, int64, error) { if source == target { return -1, -1, nil } - err := os.Rename(source, target) - if err != nil && isCrossDeviceError(err) { - fsLog(fs, logger.LevelError, "cross device error detected while renaming %q -> %q. Trying a copy and remove, this could take a long time", - source, target) - err = fscopy.Copy(source, target, fscopy.Options{ - OnSymlink: func(src string) fscopy.SymlinkAction { - return fscopy.Skip - }, - }) + err := fs.os.Rename(source, target) + if err == nil || !isCrossDeviceError(err) { + return -1, -1, err + } + + fsLog(fs, logger.LevelError, "cross device error detected while renaming %q -> %q. Trying a copy and remove, this could take a long time", + source, target) + + // First check whether we can copy + if err := fs.os.CurrentUser().CanWriteInode(target); err != nil { + return -1, -1, err + } + + if err := fs.os.Walk(source, func(path string, info os.FileInfo, err error) error { if err != nil { - fsLog(fs, logger.LevelError, "cross device copy error: %v", err) - return -1, -1, err + return err } - err = os.RemoveAll(source) + + return fs.os.CurrentUser().CanReadObject(path) + }); err != nil { return -1, -1, err } + + err = fscopy.Copy(source, target, fscopy.Options{ + OnSymlink: func(src string) fscopy.SymlinkAction { + return fscopy.Skip + }, + }) + if err != nil { + fsLog(fs, logger.LevelError, "cross device copy error: %v", err) + return -1, -1, err + } + err = fs.os.RemoveAll(source) return -1, -1, err } // Remove removes the named file or (empty) directory. -func (*OsFs) Remove(name string, isDir bool) error { - return os.Remove(name) +func (fs *OsFs) Remove(name string, isDir bool) error { + return fs.os.Remove(name) } // Mkdir creates a new directory with the specified name and default permissions -func (*OsFs) Mkdir(name string) error { - return os.Mkdir(name, os.ModePerm) +func (fs *OsFs) Mkdir(name string) error { + return fs.os.Mkdir(name, os.ModePerm) } // Symlink creates source as a symbolic link to target. -func (*OsFs) Symlink(source, target string) error { - return os.Symlink(source, target) +func (fs *OsFs) Symlink(source, target string) error { + return fs.os.Symlink(source, target) } // Readlink returns the destination of the named symbolic link @@ -159,7 +180,7 @@ func (*OsFs) Symlink(source, target string) error { func (fs *OsFs) Readlink(name string) (string, error) { // we don't have to follow multiple links: // https://github.com/openssh/openssh-portable/blob/7bf2eb958fbb551e7d61e75c176bb3200383285d/sftp-server.c#L1329 - resolved, err := os.Readlink(name) + resolved, err := fs.os.Readlink(name) if err != nil { return "", err } @@ -171,29 +192,29 @@ func (fs *OsFs) Readlink(name string) (string, error) { } // Chown changes the numeric uid and gid of the named file. -func (*OsFs) Chown(name string, uid int, gid int) error { - return os.Chown(name, uid, gid) +func (fs *OsFs) Chown(name string, uid int, gid int) error { + return fs.os.Chown(name, uid, gid) } // Chmod changes the mode of the named file to mode -func (*OsFs) Chmod(name string, mode os.FileMode) error { - return os.Chmod(name, mode) +func (fs *OsFs) Chmod(name string, mode os.FileMode) error { + return fs.os.Chmod(name, mode) } // Chtimes changes the access and modification times of the named file -func (*OsFs) Chtimes(name string, atime, mtime time.Time, isUploading bool) error { - return os.Chtimes(name, atime, mtime) +func (fs *OsFs) Chtimes(name string, atime, mtime time.Time, isUploading bool) error { + return fs.os.Chtimes(name, atime, mtime) } // Truncate changes the size of the named file -func (*OsFs) Truncate(name string, size int64) error { - return os.Truncate(name, size) +func (fs *OsFs) Truncate(name string, size int64) error { + return fs.os.Truncate(name, size) } // ReadDir reads the directory named by dirname and returns // a list of directory entries. -func (*OsFs) ReadDir(dirname string) ([]os.FileInfo, error) { - f, err := os.Open(dirname) +func (fs *OsFs) ReadDir(dirname string) ([]os.FileInfo, error) { + f, err := fs.os.Open(dirname) if err != nil { if isInvalidNameError(err) { err = os.ErrNotExist @@ -242,12 +263,13 @@ func (*OsFs) IsNotSupported(err error) bool { } // CheckRootPath creates the root directory if it does not exists +// This operation happens as the sftpgo user (e.g. root), we don't call setuid func (fs *OsFs) CheckRootPath(username string, uid int, gid int) bool { var err error - if _, err = fs.Stat(fs.rootDir); fs.IsNotExist(err) { + if _, err = os.Stat(fs.rootDir); os.IsNotExist(err) { err = os.MkdirAll(fs.rootDir, os.ModePerm) if err == nil { - SetPathPermissions(fs, fs.rootDir, uid, gid) + err = os.Chown(fs.rootDir, uid, gid) } else { fsLog(fs, logger.LevelError, "error creating root directory %q for user %q: %v", fs.rootDir, username, err) } @@ -295,8 +317,8 @@ func (fs *OsFs) GetRelativePath(name string) string { // Walk walks the file tree rooted at root, calling walkFn for each file or // directory in the tree, including root -func (*OsFs) Walk(root string, walkFn filepath.WalkFunc) error { - return filepath.Walk(root, walkFn) +func (fs *OsFs) Walk(root string, walkFn filepath.WalkFunc) error { + return fs.os.Walk(root, walkFn) } // Join joins any number of path elements into a single path @@ -313,7 +335,8 @@ func (fs *OsFs) ResolvePath(virtualPath string) (string, error) { virtualPath = strings.TrimPrefix(virtualPath, fs.mountPath) } r := filepath.Clean(filepath.Join(fs.rootDir, virtualPath)) - p, err := filepath.EvalSymlinks(r) + + p, err := fs.os.EvalSymlinks(r) if isInvalidNameError(err) { err = os.ErrNotExist } @@ -342,7 +365,7 @@ func (fs *OsFs) ResolvePath(virtualPath string) (string, error) { func (fs *OsFs) RealPath(p string) (string, error) { linksWalked := 0 for { - info, err := os.Lstat(p) + info, err := fs.os.Lstat(p) if err != nil { if errors.Is(err, os.ErrNotExist) { return fs.GetRelativePath(p), nil @@ -352,7 +375,7 @@ func (fs *OsFs) RealPath(p string) (string, error) { if info.Mode()&os.ModeSymlink == 0 { return fs.GetRelativePath(p), nil } - resolvedLink, err := os.Readlink(p) + resolvedLink, err := fs.os.Readlink(p) if err != nil { return "", err } @@ -377,7 +400,7 @@ func (fs *OsFs) GetDirSize(dirname string) (int, int64, error) { size := int64(0) isDir, err := isDirectory(fs, dirname) if err == nil && isDir { - err = filepath.Walk(dirname, func(_ string, info os.FileInfo, err error) error { + err = fs.os.Walk(dirname, func(_ string, info os.FileInfo, err error) error { if err != nil { return err } @@ -403,7 +426,7 @@ func (fs *OsFs) findNonexistentDirs(filePath string) ([]string, error) { results := []string{} cleanPath := filepath.Clean(filePath) parent := filepath.Dir(cleanPath) - _, err := os.Stat(parent) + _, err := fs.os.Stat(parent) for fs.IsNotExist(err) { results = append(results, parent) @@ -411,12 +434,12 @@ func (fs *OsFs) findNonexistentDirs(filePath string) ([]string, error) { if util.Contains(results, parent) { break } - _, err = os.Stat(parent) + _, err = fs.os.Stat(parent) } if err != nil { return results, err } - p, err := filepath.EvalSymlinks(parent) + p, err := fs.os.EvalSymlinks(parent) if err != nil { return results, err } @@ -440,11 +463,11 @@ func (fs *OsFs) findFirstExistingDir(path string) (string, error) { } else { parent = fs.rootDir } - p, err := filepath.EvalSymlinks(parent) + p, err := fs.os.EvalSymlinks(parent) if err != nil { return "", err } - fileInfo, err := os.Stat(p) + fileInfo, err := fs.os.Stat(p) if err != nil { return "", err } @@ -457,7 +480,7 @@ func (fs *OsFs) findFirstExistingDir(path string) (string, error) { func (fs *OsFs) isSubDir(sub string) error { // fs.rootDir must exist and it is already a validated absolute path - parent, err := filepath.EvalSymlinks(fs.rootDir) + parent, err := fs.os.EvalSymlinks(fs.rootDir) if err != nil { fsLog(fs, logger.LevelError, "invalid root path %q: %v", fs.rootDir, err) return err @@ -484,7 +507,7 @@ func (fs *OsFs) isSubDir(sub string) error { // GetMimeType returns the content type func (fs *OsFs) GetMimeType(name string) (string, error) { - f, err := os.OpenFile(name, os.O_RDONLY, 0) + f, err := fs.os.OpenFile(name, os.O_RDONLY, 0) if err != nil { return "", err } diff --git a/internal/vfs/osfs_fallback.go b/internal/vfs/osfs_fallback.go new file mode 100644 index 000000000..800514001 --- /dev/null +++ b/internal/vfs/osfs_fallback.go @@ -0,0 +1,10 @@ +//go:build !linux +// +build !linux + +package vfs + +import "github.com/peterverraedt/useros" + +func osAsUser(uid, gid int) useros.OS { + return useros.Default() +} diff --git a/internal/vfs/osfs_linux.go b/internal/vfs/osfs_linux.go new file mode 100644 index 000000000..cd13758f0 --- /dev/null +++ b/internal/vfs/osfs_linux.go @@ -0,0 +1,21 @@ +//go:build linux +// +build linux + +package vfs + +import ( + "syscall" + + "github.com/peterverraedt/useros" +) + +func osAsUser(uid, gid int) useros.OS { + if syscall.Geteuid() > 0 { + return useros.Default() + } + + return useros.User{ + UID: uid, + GID: gid, + }.OS() +} diff --git a/internal/vfs/s3fs.go b/internal/vfs/s3fs.go index 68dc4764d..404bec4ba 100644 --- a/internal/vfs/s3fs.go +++ b/internal/vfs/s3fs.go @@ -502,7 +502,7 @@ func (*S3Fs) IsNotSupported(err error) bool { // CheckRootPath creates the specified local root directory if it does not exists func (fs *S3Fs) CheckRootPath(username string, uid int, gid int) bool { // we need a local directory for temporary files - osFs := NewOsFs(fs.ConnectionID(), fs.localTempDir, "") + osFs := NewOsFs(fs.ConnectionID(), fs.localTempDir, "", uid, gid) return osFs.CheckRootPath(username, uid, gid) } diff --git a/internal/vfs/sftpfs.go b/internal/vfs/sftpfs.go index f4af6afac..312d92657 100644 --- a/internal/vfs/sftpfs.go +++ b/internal/vfs/sftpfs.go @@ -573,7 +573,7 @@ func (*SFTPFs) IsNotSupported(err error) bool { // CheckRootPath creates the specified local root directory if it does not exists func (fs *SFTPFs) CheckRootPath(username string, uid int, gid int) bool { // local directory for temporary files in buffer mode - osFs := NewOsFs(fs.ConnectionID(), fs.localTempDir, "") + osFs := NewOsFs(fs.ConnectionID(), fs.localTempDir, "", uid, gid) osFs.CheckRootPath(username, uid, gid) if fs.config.Prefix == "/" { return true diff --git a/internal/vfs/vfs.go b/internal/vfs/vfs.go index 1cb2cfd57..d52919fa2 100644 --- a/internal/vfs/vfs.go +++ b/internal/vfs/vfs.go @@ -23,7 +23,6 @@ import ( "os" "path" "path/filepath" - "runtime" "strings" "time" @@ -812,22 +811,6 @@ func IsLocalOrCryptoFs(fs Fs) bool { return IsLocalOsFs(fs) || IsCryptOsFs(fs) } -// SetPathPermissions calls fs.Chown. -// It does nothing for local filesystem on windows -func SetPathPermissions(fs Fs, path string, uid int, gid int) { - if uid == -1 && gid == -1 { - return - } - if IsLocalOsFs(fs) { - if runtime.GOOS == "windows" { - return - } - } - if err := fs.Chown(path, uid, gid); err != nil { - fsLog(fs, logger.LevelWarn, "error chowning path %v: %v", path, err) - } -} - func updateFileInfoModTime(storageID, objectPath string, info *FileInfo) (*FileInfo, error) { if !plugin.Handler.HasMetadater() { return info, nil diff --git a/internal/webdavd/handler.go b/internal/webdavd/handler.go index dd126e434..cc6af5b02 100644 --- a/internal/webdavd/handler.go +++ b/internal/webdavd/handler.go @@ -221,8 +221,6 @@ func (c *Connection) handleUploadToNewFile(fs vfs.Fs, resolvedPath, filePath, re return nil, c.GetFsError(fs, err) } - vfs.SetPathPermissions(fs, filePath, c.User.GetUID(), c.User.GetGID()) - // we can get an error only for resume maxWriteSize, _ := c.GetMaxWriteSize(diskQuota, false, 0, fs.IsUploadResumeSupported()) @@ -284,8 +282,6 @@ func (c *Connection) handleUploadToExistingFile(fs vfs.Fs, resolvedPath, filePat truncatedSize = fileSize } - vfs.SetPathPermissions(fs, filePath, c.User.GetUID(), c.User.GetGID()) - baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, resolvedPath, filePath, requestPath, common.TransferUpload, 0, initialSize, maxWriteSize, truncatedSize, false, fs, transferQuota) mtime := c.getModificationTime() diff --git a/internal/webdavd/internal_test.go b/internal/webdavd/internal_test.go index 8304b1eb9..d8fcf8233 100644 --- a/internal/webdavd/internal_test.go +++ b/internal/webdavd/internal_test.go @@ -326,7 +326,7 @@ func (fs *MockOsFs) GetMimeType(name string) (string, error) { func newMockOsFs(atomicUpload bool, connectionID, rootDir string, reader *pipeat.PipeReaderAt, err error) vfs.Fs { return &MockOsFs{ - Fs: vfs.NewOsFs(connectionID, rootDir, ""), + Fs: vfs.NewOsFs(connectionID, rootDir, "", 0, 0), isAtomicUploadSupported: atomicUpload, reader: reader, err: err, @@ -483,7 +483,7 @@ func TestResolvePathErrors(t *testing.T) { } user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} - fs := vfs.NewOsFs("connID", user.HomeDir, "") + fs := vfs.NewOsFs("connID", user.HomeDir, "", 0, 0) connection := &Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, "", "", user), } @@ -516,7 +516,7 @@ func TestResolvePathErrors(t *testing.T) { if runtime.GOOS != "windows" { user.HomeDir = filepath.Clean(os.TempDir()) connection.User = user - fs := vfs.NewOsFs("connID", connection.User.HomeDir, "") + fs := vfs.NewOsFs("connID", connection.User.HomeDir, "", 0, 0) subDir := "sub" testTxtFile := "file.txt" err = os.MkdirAll(filepath.Join(os.TempDir(), subDir, subDir), os.ModePerm) @@ -554,7 +554,7 @@ func TestFileAccessErrors(t *testing.T) { } user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} - fs := vfs.NewOsFs("connID", user.HomeDir, "") + fs := vfs.NewOsFs("connID", user.HomeDir, "", 0, 0) connection := &Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, "", "", user), } @@ -646,7 +646,7 @@ func TestContentType(t *testing.T) { } user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} - fs := vfs.NewOsFs("connID", user.HomeDir, "") + fs := vfs.NewOsFs("connID", user.HomeDir, "", 0, 0) connection := &Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, "", "", user), } @@ -673,7 +673,7 @@ func TestContentType(t *testing.T) { baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFilePath, testFile+".unknown1", common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{}) davFile = newWebDavFile(baseTransfer, nil, nil) - davFile.Fs = vfs.NewOsFs("id", user.HomeDir, "") + davFile.Fs = vfs.NewOsFs("id", user.HomeDir, "", 0, 0) fi, err = davFile.Stat() if assert.NoError(t, err) { ctype, err := fi.(*webDavFileInfo).ContentType(ctx) @@ -686,7 +686,7 @@ func TestContentType(t *testing.T) { baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFilePath, testFile, common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{}) davFile = newWebDavFile(baseTransfer, nil, nil) - davFile.Fs = vfs.NewOsFs("id", user.HomeDir, "") + davFile.Fs = vfs.NewOsFs("id", user.HomeDir, "", 0, 0) fi, err = davFile.Stat() if assert.NoError(t, err) { ctype, err := fi.(*webDavFileInfo).ContentType(ctx) @@ -701,7 +701,7 @@ func TestContentType(t *testing.T) { baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFilePath, testFile+".custom", common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{}) davFile = newWebDavFile(baseTransfer, nil, nil) - davFile.Fs = vfs.NewOsFs("id", user.HomeDir, "") + davFile.Fs = vfs.NewOsFs("id", user.HomeDir, "", 0, 0) fi, err = davFile.Stat() if assert.NoError(t, err) { ctype, err := fi.(*webDavFileInfo).ContentType(ctx) @@ -755,7 +755,7 @@ func TestTransferReadWriteErrors(t *testing.T) { } user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} - fs := vfs.NewOsFs("connID", user.HomeDir, "") + fs := vfs.NewOsFs("connID", user.HomeDir, "", 0, 0) connection := &Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, "", "", user), } @@ -911,7 +911,7 @@ func TestTransferSeek(t *testing.T) { assert.True(t, fs.IsNotExist(err)) davFile.Connection.RemoveTransfer(davFile.BaseTransfer) - fs = vfs.NewOsFs(fs.ConnectionID(), user.GetHomeDir(), "") + fs = vfs.NewOsFs(fs.ConnectionID(), user.GetHomeDir(), "", 0, 0) baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath+"1", testFilePath+"1", testFile, common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{AllowedTotalSize: 100}) davFile = newWebDavFile(baseTransfer, nil, nil) diff --git a/internal/webdavd/webdavd_test.go b/internal/webdavd/webdavd_test.go index 01109a3b7..57f0c4ca6 100644 --- a/internal/webdavd/webdavd_test.go +++ b/internal/webdavd/webdavd_test.go @@ -1497,7 +1497,7 @@ func TestMaxConnections(t *testing.T) { client := getWebDavClient(user, true, nil) assert.NoError(t, checkBasicFunc(client)) // now add a fake connection - fs := vfs.NewOsFs("id", os.TempDir(), "") + fs := vfs.NewOsFs("id", os.TempDir(), "", 0, 0) connection := &webdavd.Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, "", "", user), } @@ -1576,7 +1576,7 @@ func TestMaxSessions(t *testing.T) { client := getWebDavClient(user, false, nil) assert.NoError(t, checkBasicFunc(client)) // now add a fake connection - fs := vfs.NewOsFs("id", os.TempDir(), "") + fs := vfs.NewOsFs("id", os.TempDir(), "", 0, 0) connection := &webdavd.Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, "", "", user), }