Pārlūkot izejas kodu

fix a potential race condition for pre-login and ext auth
hooks

doing something like this:

err = provider.updateUser(u)
...
return provider.userExists(username)

could be racy if another update happen before

provider.userExists(username)

also pass a pointer to updateUser so if the user is modified inside
"validateUser" we can just return the modified user without do a new
query

Nicola Murino 4 gadi atpakaļ
vecāks
revīzija
daac90c4e1

+ 2 - 2
cmd/startsubsys.go

@@ -96,7 +96,7 @@ Command-line flags should be specified in the Subsystem declaration.
 				if user.HomeDir != filepath.Clean(homedir) && !preserveHomeDir {
 				if user.HomeDir != filepath.Clean(homedir) && !preserveHomeDir {
 					// update the user
 					// update the user
 					user.HomeDir = filepath.Clean(homedir)
 					user.HomeDir = filepath.Clean(homedir)
-					err = dataprovider.UpdateUser(user)
+					err = dataprovider.UpdateUser(&user)
 					if err != nil {
 					if err != nil {
 						logger.Error(logSender, connectionID, "unable to update user %#v: %v", username, err)
 						logger.Error(logSender, connectionID, "unable to update user %#v: %v", username, err)
 						os.Exit(1)
 						os.Exit(1)
@@ -113,7 +113,7 @@ Command-line flags should be specified in the Subsystem declaration.
 				user.Password = connectionID
 				user.Password = connectionID
 				user.Permissions = make(map[string][]string)
 				user.Permissions = make(map[string][]string)
 				user.Permissions["/"] = []string{dataprovider.PermAny}
 				user.Permissions["/"] = []string{dataprovider.PermAny}
-				err = dataprovider.AddUser(user)
+				err = dataprovider.AddUser(&user)
 				if err != nil {
 				if err != nil {
 					logger.Error(logSender, connectionID, "unable to add user %#v: %v", username, err)
 					logger.Error(logSender, connectionID, "unable to add user %#v: %v", username, err)
 					os.Exit(1)
 					os.Exit(1)

+ 2 - 2
common/common.go

@@ -364,7 +364,7 @@ func (c *Configuration) GetProxyListener(listener net.Listener) (*proxyproto.Lis
 
 
 // ExecutePostConnectHook executes the post connect hook if defined
 // ExecutePostConnectHook executes the post connect hook if defined
 func (c *Configuration) ExecutePostConnectHook(ipAddr, protocol string) error {
 func (c *Configuration) ExecutePostConnectHook(ipAddr, protocol string) error {
-	if len(c.PostConnectHook) == 0 {
+	if c.PostConnectHook == "" {
 		return nil
 		return nil
 	}
 	}
 	if strings.HasPrefix(c.PostConnectHook, "http") {
 	if strings.HasPrefix(c.PostConnectHook, "http") {
@@ -594,7 +594,7 @@ func (conns *ActiveConnections) checkIdles() {
 
 
 	for _, c := range conns.connections {
 	for _, c := range conns.connections {
 		idleTime := time.Since(c.GetLastActivity())
 		idleTime := time.Since(c.GetLastActivity())
-		isUnauthenticatedFTPUser := (c.GetProtocol() == ProtocolFTP && len(c.GetUsername()) == 0)
+		isUnauthenticatedFTPUser := (c.GetProtocol() == ProtocolFTP && c.GetUsername() == "")
 
 
 		if idleTime > Config.idleTimeoutAsDuration || (isUnauthenticatedFTPUser && idleTime > Config.idleLoginTimeout) {
 		if idleTime > Config.idleTimeoutAsDuration || (isUnauthenticatedFTPUser && idleTime > Config.idleLoginTimeout) {
 			defer func(conn ActiveConnection, isFTPNoAuth bool) {
 			defer func(conn ActiveConnection, isFTPNoAuth bool) {

+ 8 - 8
common/connection_test.go

@@ -1031,7 +1031,7 @@ func TestHasSpace(t *testing.T) {
 
 
 	user.VirtualFolders[0].QuotaFiles = 0
 	user.VirtualFolders[0].QuotaFiles = 0
 	user.VirtualFolders[0].QuotaSize = 0
 	user.VirtualFolders[0].QuotaSize = 0
-	err = dataprovider.AddUser(user)
+	err = dataprovider.AddUser(&user)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	user, err = dataprovider.UserExists(user.Username)
 	user, err = dataprovider.UserExists(user.Username)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
@@ -1041,7 +1041,7 @@ func TestHasSpace(t *testing.T) {
 
 
 	user.VirtualFolders[0].QuotaFiles = 10
 	user.VirtualFolders[0].QuotaFiles = 10
 	user.VirtualFolders[0].QuotaSize = 1048576
 	user.VirtualFolders[0].QuotaSize = 1048576
-	err = dataprovider.UpdateUser(user)
+	err = dataprovider.UpdateUser(&user)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	c.User = user
 	c.User = user
 	quotaResult = c.HasSpace(true, "/vdir/file1")
 	quotaResult = c.HasSpace(true, "/vdir/file1")
@@ -1057,10 +1057,10 @@ func TestHasSpace(t *testing.T) {
 	quotaResult = c.HasSpace(true, "/vdir/file1")
 	quotaResult = c.HasSpace(true, "/vdir/file1")
 	assert.False(t, quotaResult.HasSpace)
 	assert.False(t, quotaResult.HasSpace)
 
 
-	err = dataprovider.DeleteUser(user)
+	err = dataprovider.DeleteUser(&user)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
-	err = dataprovider.DeleteFolder(folder)
+	err = dataprovider.DeleteFolder(&folder)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 }
 }
 
 
@@ -1091,7 +1091,7 @@ func TestUpdateQuotaMoveVFolders(t *testing.T) {
 		QuotaFiles:  -1,
 		QuotaFiles:  -1,
 		QuotaSize:   -1,
 		QuotaSize:   -1,
 	})
 	})
-	err := dataprovider.AddUser(user)
+	err := dataprovider.AddUser(&user)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	user, err = dataprovider.UserExists(user.Username)
 	user, err = dataprovider.UserExists(user.Username)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
@@ -1148,11 +1148,11 @@ func TestUpdateQuotaMoveVFolders(t *testing.T) {
 	assert.Equal(t, 1, user.UsedQuotaFiles)
 	assert.Equal(t, 1, user.UsedQuotaFiles)
 	assert.Equal(t, int64(100), user.UsedQuotaSize)
 	assert.Equal(t, int64(100), user.UsedQuotaSize)
 
 
-	err = dataprovider.DeleteUser(user)
+	err = dataprovider.DeleteUser(&user)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
-	err = dataprovider.DeleteFolder(folder1)
+	err = dataprovider.DeleteFolder(&folder1)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
-	err = dataprovider.DeleteFolder(folder2)
+	err = dataprovider.DeleteFolder(&folder2)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 }
 }
 
 

+ 4 - 8
common/tlsutils.go

@@ -171,19 +171,15 @@ func (m *CertManager) LoadRootCAs() error {
 	return nil
 	return nil
 }
 }
 
 
-// SetCACertificates sets the root CA authorities file paths
+// SetCACertificates sets the root CA authorities file paths.
+// This should not be changed at runtime
 func (m *CertManager) SetCACertificates(caCertificates []string) {
 func (m *CertManager) SetCACertificates(caCertificates []string) {
-	m.Lock()
-	defer m.Unlock()
-
 	m.caCertificates = caCertificates
 	m.caCertificates = caCertificates
 }
 }
 
 
-// SetCARevocationLists sets the CA revocation lists file paths
+// SetCARevocationLists sets the CA revocation lists file paths.
+// This should not be changed at runtime
 func (m *CertManager) SetCARevocationLists(caRevocationLists []string) {
 func (m *CertManager) SetCARevocationLists(caRevocationLists []string) {
-	m.Lock()
-	defer m.Unlock()
-
 	m.caRevocationLists = caRevocationLists
 	m.caRevocationLists = caRevocationLists
 }
 }
 
 

+ 17 - 16
dataprovider/bolt.go

@@ -101,7 +101,7 @@ func (p BoltProvider) checkAvailability() error {
 
 
 func (p BoltProvider) validateUserAndPass(username, password, ip, protocol string) (User, error) {
 func (p BoltProvider) validateUserAndPass(username, password, ip, protocol string) (User, error) {
 	var user User
 	var user User
-	if len(password) == 0 {
+	if password == "" {
 		return user, errors.New("Credentials cannot be null or empty")
 		return user, errors.New("Credentials cannot be null or empty")
 	}
 	}
 	user, err := p.userExists(username)
 	user, err := p.userExists(username)
@@ -246,8 +246,8 @@ func (p BoltProvider) userExists(username string) (User, error) {
 	return user, err
 	return user, err
 }
 }
 
 
-func (p BoltProvider) addUser(user User) error {
-	err := validateUser(&user)
+func (p BoltProvider) addUser(user *User) error {
+	err := validateUser(user)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -291,8 +291,8 @@ func (p BoltProvider) addUser(user User) error {
 	})
 	})
 }
 }
 
 
-func (p BoltProvider) updateUser(user User) error {
-	err := validateUser(&user)
+func (p BoltProvider) updateUser(user *User) error {
+	err := validateUser(user)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -315,7 +315,7 @@ func (p BoltProvider) updateUser(user User) error {
 			return err
 			return err
 		}
 		}
 		for _, folder := range oldUser.VirtualFolders {
 		for _, folder := range oldUser.VirtualFolders {
-			err = removeUserFromFolderMapping(folder, oldUser, folderBucket)
+			err = removeUserFromFolderMapping(folder, &oldUser, folderBucket)
 			if err != nil {
 			if err != nil {
 				return err
 				return err
 			}
 			}
@@ -339,7 +339,7 @@ func (p BoltProvider) updateUser(user User) error {
 	})
 	})
 }
 }
 
 
-func (p BoltProvider) deleteUser(user User) error {
+func (p BoltProvider) deleteUser(user *User) error {
 	return p.dbHandle.Update(func(tx *bolt.Tx) error {
 	return p.dbHandle.Update(func(tx *bolt.Tx) error {
 		bucket, idxBucket, err := getBuckets(tx)
 		bucket, idxBucket, err := getBuckets(tx)
 		if err != nil {
 		if err != nil {
@@ -567,8 +567,8 @@ func (p BoltProvider) getFolderByPath(name string) (vfs.BaseVirtualFolder, error
 	return folder, err
 	return folder, err
 }
 }
 
 
-func (p BoltProvider) addFolder(folder vfs.BaseVirtualFolder) error {
-	err := validateFolder(&folder)
+func (p BoltProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
+	err := validateFolder(folder)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -580,12 +580,12 @@ func (p BoltProvider) addFolder(folder vfs.BaseVirtualFolder) error {
 		if f := bucket.Get([]byte(folder.MappedPath)); f != nil {
 		if f := bucket.Get([]byte(folder.MappedPath)); f != nil {
 			return fmt.Errorf("folder %v already exists", folder.MappedPath)
 			return fmt.Errorf("folder %v already exists", folder.MappedPath)
 		}
 		}
-		_, err = addFolderInternal(folder, bucket)
+		_, err = addFolderInternal(*folder, bucket)
 		return err
 		return err
 	})
 	})
 }
 }
 
 
-func (p BoltProvider) deleteFolder(folder vfs.BaseVirtualFolder) error {
+func (p BoltProvider) deleteFolder(folder *vfs.BaseVirtualFolder) error {
 	return p.dbHandle.Update(func(tx *bolt.Tx) error {
 	return p.dbHandle.Update(func(tx *bolt.Tx) error {
 		bucket, err := getFolderBucket(tx)
 		bucket, err := getFolderBucket(tx)
 		if err != nil {
 		if err != nil {
@@ -816,7 +816,7 @@ func addFolderInternal(folder vfs.BaseVirtualFolder, bucket *bolt.Bucket) (vfs.B
 	return folder, err
 	return folder, err
 }
 }
 
 
-func addUserToFolderMapping(folder vfs.VirtualFolder, user User, bucket *bolt.Bucket) error {
+func addUserToFolderMapping(folder vfs.VirtualFolder, user *User, bucket *bolt.Bucket) error {
 	var baseFolder vfs.BaseVirtualFolder
 	var baseFolder vfs.BaseVirtualFolder
 	var err error
 	var err error
 	if f := bucket.Get([]byte(folder.MappedPath)); f == nil {
 	if f := bucket.Get([]byte(folder.MappedPath)); f == nil {
@@ -842,7 +842,7 @@ func addUserToFolderMapping(folder vfs.VirtualFolder, user User, bucket *bolt.Bu
 	return err
 	return err
 }
 }
 
 
-func removeUserFromFolderMapping(folder vfs.VirtualFolder, user User, bucket *bolt.Bucket) error {
+func removeUserFromFolderMapping(folder vfs.VirtualFolder, user *User, bucket *bolt.Bucket) error {
 	var f []byte
 	var f []byte
 	if f = bucket.Get([]byte(folder.MappedPath)); f == nil {
 	if f = bucket.Get([]byte(folder.MappedPath)); f == nil {
 		// the folder does not exists so there is no associated user
 		// the folder does not exists so there is no associated user
@@ -940,7 +940,7 @@ func updateDatabaseFrom1To2(dbHandle *bolt.DB) error {
 			return err
 			return err
 		}
 		}
 		user.Status = 1
 		user.Status = 1
-		err = provider.updateUser(user)
+		err = provider.updateUser(&user)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -994,7 +994,8 @@ func updateDatabaseFrom2To3(dbHandle *bolt.DB) error {
 	}
 	}
 
 
 	for _, user := range users {
 	for _, user := range users {
-		err = provider.updateUser(user)
+		user := user
+		err = provider.updateUser(&user)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -1053,7 +1054,7 @@ func updateDatabaseFrom3To4(dbHandle *bolt.DB) error {
 			}
 			}
 		}
 		}
 		user.VirtualFolders = folders
 		user.VirtualFolders = folders
-		err = provider.updateUser(user)
+		err = provider.updateUser(&user)
 		providerLog(logger.LevelInfo, "number of virtual folders to restore %v, user %#v, error: %v", len(user.VirtualFolders),
 		providerLog(logger.LevelInfo, "number of virtual folders to restore %v, user %#v, error: %v", len(user.VirtualFolders),
 			user.Username, err)
 			user.Username, err)
 		if err != nil {
 		if err != nil {

+ 41 - 38
dataprovider/dataprovider.go

@@ -367,17 +367,17 @@ type Provider interface {
 	updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error
 	updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error
 	getUsedQuota(username string) (int, int64, error)
 	getUsedQuota(username string) (int, int64, error)
 	userExists(username string) (User, error)
 	userExists(username string) (User, error)
-	addUser(user User) error
-	updateUser(user User) error
-	deleteUser(user User) error
+	addUser(user *User) error
+	updateUser(user *User) error
+	deleteUser(user *User) error
 	getUsers(limit int, offset int, order string, username string) ([]User, error)
 	getUsers(limit int, offset int, order string, username string) ([]User, error)
 	dumpUsers() ([]User, error)
 	dumpUsers() ([]User, error)
 	getUserByID(ID int64) (User, error)
 	getUserByID(ID int64) (User, error)
 	updateLastLogin(username string) error
 	updateLastLogin(username string) error
 	getFolders(limit, offset int, order, folderPath string) ([]vfs.BaseVirtualFolder, error)
 	getFolders(limit, offset int, order, folderPath string) ([]vfs.BaseVirtualFolder, error)
 	getFolderByPath(mappedPath string) (vfs.BaseVirtualFolder, error)
 	getFolderByPath(mappedPath string) (vfs.BaseVirtualFolder, error)
-	addFolder(folder vfs.BaseVirtualFolder) error
-	deleteFolder(folder vfs.BaseVirtualFolder) error
+	addFolder(folder *vfs.BaseVirtualFolder) error
+	deleteFolder(folder *vfs.BaseVirtualFolder) error
 	updateFolderQuota(mappedPath string, filesAdd int, sizeAdd int64, reset bool) error
 	updateFolderQuota(mappedPath string, filesAdd int, sizeAdd int64, reset bool) error
 	getUsedFolderQuota(mappedPath string) (int, int64, error)
 	getUsedFolderQuota(mappedPath string) (int, int64, error)
 	dumpFolders() ([]vfs.BaseVirtualFolder, error)
 	dumpFolders() ([]vfs.BaseVirtualFolder, error)
@@ -439,16 +439,16 @@ func Initialize(cnf Config, basePath string) error {
 
 
 func validateHooks() error {
 func validateHooks() error {
 	var hooks []string
 	var hooks []string
-	if len(config.PreLoginHook) > 0 && !strings.HasPrefix(config.PreLoginHook, "http") {
+	if config.PreLoginHook != "" && !strings.HasPrefix(config.PreLoginHook, "http") {
 		hooks = append(hooks, config.PreLoginHook)
 		hooks = append(hooks, config.PreLoginHook)
 	}
 	}
-	if len(config.ExternalAuthHook) > 0 && !strings.HasPrefix(config.ExternalAuthHook, "http") {
+	if config.ExternalAuthHook != "" && !strings.HasPrefix(config.ExternalAuthHook, "http") {
 		hooks = append(hooks, config.ExternalAuthHook)
 		hooks = append(hooks, config.ExternalAuthHook)
 	}
 	}
-	if len(config.PostLoginHook) > 0 && !strings.HasPrefix(config.PostLoginHook, "http") {
+	if config.PostLoginHook != "" && !strings.HasPrefix(config.PostLoginHook, "http") {
 		hooks = append(hooks, config.PostLoginHook)
 		hooks = append(hooks, config.PostLoginHook)
 	}
 	}
-	if len(config.CheckPasswordHook) > 0 && !strings.HasPrefix(config.CheckPasswordHook, "http") {
+	if config.CheckPasswordHook != "" && !strings.HasPrefix(config.CheckPasswordHook, "http") {
 		hooks = append(hooks, config.CheckPasswordHook)
 		hooks = append(hooks, config.CheckPasswordHook)
 	}
 	}
 
 
@@ -527,14 +527,14 @@ func RevertDatabase(cnf Config, basePath string, targetVersion int) error {
 
 
 // CheckUserAndPass retrieves the SFTP user with the given username and password if a match is found or an error
 // CheckUserAndPass retrieves the SFTP user with the given username and password if a match is found or an error
 func CheckUserAndPass(username, password, ip, protocol string) (User, error) {
 func CheckUserAndPass(username, password, ip, protocol string) (User, error) {
-	if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&1 != 0) {
+	if config.ExternalAuthHook != "" && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&1 != 0) {
 		user, err := doExternalAuth(username, password, nil, "", ip, protocol)
 		user, err := doExternalAuth(username, password, nil, "", ip, protocol)
 		if err != nil {
 		if err != nil {
 			return user, err
 			return user, err
 		}
 		}
 		return checkUserAndPass(user, password, ip, protocol)
 		return checkUserAndPass(user, password, ip, protocol)
 	}
 	}
-	if len(config.PreLoginHook) > 0 {
+	if config.PreLoginHook != "" {
 		user, err := executePreLoginHook(username, LoginMethodPassword, ip, protocol)
 		user, err := executePreLoginHook(username, LoginMethodPassword, ip, protocol)
 		if err != nil {
 		if err != nil {
 			return user, err
 			return user, err
@@ -546,14 +546,14 @@ func CheckUserAndPass(username, password, ip, protocol string) (User, error) {
 
 
 // CheckUserAndPubKey retrieves the SFTP user with the given username and public key if a match is found or an error
 // CheckUserAndPubKey retrieves the SFTP user with the given username and public key if a match is found or an error
 func CheckUserAndPubKey(username string, pubKey []byte, ip, protocol string) (User, string, error) {
 func CheckUserAndPubKey(username string, pubKey []byte, ip, protocol string) (User, string, error) {
-	if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&2 != 0) {
+	if config.ExternalAuthHook != "" && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&2 != 0) {
 		user, err := doExternalAuth(username, "", pubKey, "", ip, protocol)
 		user, err := doExternalAuth(username, "", pubKey, "", ip, protocol)
 		if err != nil {
 		if err != nil {
 			return user, "", err
 			return user, "", err
 		}
 		}
 		return checkUserAndPubKey(user, pubKey)
 		return checkUserAndPubKey(user, pubKey)
 	}
 	}
-	if len(config.PreLoginHook) > 0 {
+	if config.PreLoginHook != "" {
 		user, err := executePreLoginHook(username, SSHLoginMethodPublicKey, ip, protocol)
 		user, err := executePreLoginHook(username, SSHLoginMethodPublicKey, ip, protocol)
 		if err != nil {
 		if err != nil {
 			return user, "", err
 			return user, "", err
@@ -568,9 +568,9 @@ func CheckUserAndPubKey(username string, pubKey []byte, ip, protocol string) (Us
 func CheckKeyboardInteractiveAuth(username, authHook string, client ssh.KeyboardInteractiveChallenge, ip, protocol string) (User, error) {
 func CheckKeyboardInteractiveAuth(username, authHook string, client ssh.KeyboardInteractiveChallenge, ip, protocol string) (User, error) {
 	var user User
 	var user User
 	var err error
 	var err error
-	if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&4 != 0) {
+	if config.ExternalAuthHook != "" && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&4 != 0) {
 		user, err = doExternalAuth(username, "", nil, "1", ip, protocol)
 		user, err = doExternalAuth(username, "", nil, "1", ip, protocol)
-	} else if len(config.PreLoginHook) > 0 {
+	} else if config.PreLoginHook != "" {
 		user, err = executePreLoginHook(username, SSHLoginMethodKeyboardInteractive, ip, protocol)
 		user, err = executePreLoginHook(username, SSHLoginMethodKeyboardInteractive, ip, protocol)
 	} else {
 	} else {
 		user, err = provider.userExists(username)
 		user, err = provider.userExists(username)
@@ -653,41 +653,41 @@ func UserExists(username string) (User, error) {
 
 
 // AddUser adds a new SFTPGo user.
 // AddUser adds a new SFTPGo user.
 // ManageUsers configuration must be set to 1 to enable this method
 // ManageUsers configuration must be set to 1 to enable this method
-func AddUser(user User) error {
+func AddUser(user *User) error {
 	if config.ManageUsers == 0 {
 	if config.ManageUsers == 0 {
 		return &MethodDisabledError{err: manageUsersDisabledError}
 		return &MethodDisabledError{err: manageUsersDisabledError}
 	}
 	}
 	err := provider.addUser(user)
 	err := provider.addUser(user)
 	if err == nil {
 	if err == nil {
-		go executeAction(operationAdd, user)
+		go executeAction(operationAdd, *user)
 	}
 	}
 	return err
 	return err
 }
 }
 
 
 // UpdateUser updates an existing SFTPGo user.
 // UpdateUser updates an existing SFTPGo user.
 // ManageUsers configuration must be set to 1 to enable this method
 // ManageUsers configuration must be set to 1 to enable this method
-func UpdateUser(user User) error {
+func UpdateUser(user *User) error {
 	if config.ManageUsers == 0 {
 	if config.ManageUsers == 0 {
 		return &MethodDisabledError{err: manageUsersDisabledError}
 		return &MethodDisabledError{err: manageUsersDisabledError}
 	}
 	}
 	err := provider.updateUser(user)
 	err := provider.updateUser(user)
 	if err == nil {
 	if err == nil {
 		RemoveCachedWebDAVUser(user.Username)
 		RemoveCachedWebDAVUser(user.Username)
-		go executeAction(operationUpdate, user)
+		go executeAction(operationUpdate, *user)
 	}
 	}
 	return err
 	return err
 }
 }
 
 
 // DeleteUser deletes an existing SFTPGo user.
 // DeleteUser deletes an existing SFTPGo user.
 // ManageUsers configuration must be set to 1 to enable this method
 // ManageUsers configuration must be set to 1 to enable this method
-func DeleteUser(user User) error {
+func DeleteUser(user *User) error {
 	if config.ManageUsers == 0 {
 	if config.ManageUsers == 0 {
 		return &MethodDisabledError{err: manageUsersDisabledError}
 		return &MethodDisabledError{err: manageUsersDisabledError}
 	}
 	}
 	err := provider.deleteUser(user)
 	err := provider.deleteUser(user)
 	if err == nil {
 	if err == nil {
 		RemoveCachedWebDAVUser(user.Username)
 		RemoveCachedWebDAVUser(user.Username)
-		go executeAction(operationDelete, user)
+		go executeAction(operationDelete, *user)
 	}
 	}
 	return err
 	return err
 }
 }
@@ -711,7 +711,7 @@ func GetUserByID(ID int64) (User, error) {
 
 
 // AddFolder adds a new virtual folder.
 // AddFolder adds a new virtual folder.
 // ManageUsers configuration must be set to 1 to enable this method
 // ManageUsers configuration must be set to 1 to enable this method
-func AddFolder(folder vfs.BaseVirtualFolder) error {
+func AddFolder(folder *vfs.BaseVirtualFolder) error {
 	if config.ManageUsers == 0 {
 	if config.ManageUsers == 0 {
 		return &MethodDisabledError{err: manageUsersDisabledError}
 		return &MethodDisabledError{err: manageUsersDisabledError}
 	}
 	}
@@ -720,7 +720,7 @@ func AddFolder(folder vfs.BaseVirtualFolder) error {
 
 
 // DeleteFolder deletes an existing folder.
 // DeleteFolder deletes an existing folder.
 // ManageUsers configuration must be set to 1 to enable this method
 // ManageUsers configuration must be set to 1 to enable this method
-func DeleteFolder(folder vfs.BaseVirtualFolder) error {
+func DeleteFolder(folder *vfs.BaseVirtualFolder) error {
 	if config.ManageUsers == 0 {
 	if config.ManageUsers == 0 {
 		return &MethodDisabledError{err: manageUsersDisabledError}
 		return &MethodDisabledError{err: manageUsersDisabledError}
 	}
 	}
@@ -1303,7 +1303,7 @@ func validateUser(user *User) error {
 	return nil
 	return nil
 }
 }
 
 
-func checkLoginConditions(user User) error {
+func checkLoginConditions(user *User) error {
 	if user.Status < 1 {
 	if user.Status < 1 {
 		return fmt.Errorf("user %#v is disabled", user.Username)
 		return fmt.Errorf("user %#v is disabled", user.Username)
 	}
 	}
@@ -1344,11 +1344,11 @@ func isPasswordOK(user *User, password string) (bool, error) {
 }
 }
 
 
 func checkUserAndPass(user User, password, ip, protocol string) (User, error) {
 func checkUserAndPass(user User, password, ip, protocol string) (User, error) {
-	err := checkLoginConditions(user)
+	err := checkLoginConditions(&user)
 	if err != nil {
 	if err != nil {
 		return user, err
 		return user, err
 	}
 	}
-	if len(user.Password) == 0 {
+	if user.Password == "" {
 		return user, errors.New("Credentials cannot be null or empty")
 		return user, errors.New("Credentials cannot be null or empty")
 	}
 	}
 	hookResponse, err := executeCheckPasswordHook(user.Username, password, ip, protocol)
 	hookResponse, err := executeCheckPasswordHook(user.Username, password, ip, protocol)
@@ -1378,7 +1378,7 @@ func checkUserAndPass(user User, password, ip, protocol string) (User, error) {
 }
 }
 
 
 func checkUserAndPubKey(user User, pubKey []byte) (User, string, error) {
 func checkUserAndPubKey(user User, pubKey []byte) (User, string, error) {
-	err := checkLoginConditions(user)
+	err := checkLoginConditions(&user)
 	if err != nil {
 	if err != nil {
 		return user, "", err
 		return user, "", err
 	}
 	}
@@ -1731,7 +1731,7 @@ func doKeyboardInteractiveAuth(user User, authHook string, client ssh.KeyboardIn
 	if authResult != 1 {
 	if authResult != 1 {
 		return user, fmt.Errorf("keyboard interactive auth failed, result: %v", authResult)
 		return user, fmt.Errorf("keyboard interactive auth failed, result: %v", authResult)
 	}
 	}
-	err = checkLoginConditions(user)
+	err = checkLoginConditions(&user)
 	if err != nil {
 	if err != nil {
 		return user, err
 		return user, err
 	}
 	}
@@ -1739,7 +1739,7 @@ func doKeyboardInteractiveAuth(user User, authHook string, client ssh.KeyboardIn
 }
 }
 
 
 func isCheckPasswordHookDefined(protocol string) bool {
 func isCheckPasswordHookDefined(protocol string) bool {
-	if len(config.CheckPasswordHook) == 0 {
+	if config.CheckPasswordHook == "" {
 		return false
 		return false
 	}
 	}
 	if config.CheckPasswordScope == 0 {
 	if config.CheckPasswordScope == 0 {
@@ -1900,20 +1900,23 @@ func executePreLoginHook(username, loginMethod, ip, protocol string) (User, erro
 	u.LastQuotaUpdate = userLastQuotaUpdate
 	u.LastQuotaUpdate = userLastQuotaUpdate
 	u.LastLogin = userLastLogin
 	u.LastLogin = userLastLogin
 	if userID == 0 {
 	if userID == 0 {
-		err = provider.addUser(u)
+		err = provider.addUser(&u)
 	} else {
 	} else {
-		err = provider.updateUser(u)
+		err = provider.updateUser(&u)
 	}
 	}
 	if err != nil {
 	if err != nil {
 		return u, err
 		return u, err
 	}
 	}
 	providerLog(logger.LevelDebug, "user %#v added/updated from pre-login hook response, id: %v", username, userID)
 	providerLog(logger.LevelDebug, "user %#v added/updated from pre-login hook response, id: %v", username, userID)
-	return provider.userExists(username)
+	if userID == 0 {
+		return provider.userExists(username)
+	}
+	return u, nil
 }
 }
 
 
 // ExecutePostLoginHook executes the post login hook if defined
 // ExecutePostLoginHook executes the post login hook if defined
 func ExecutePostLoginHook(username, loginMethod, ip, protocol string, err error) {
 func ExecutePostLoginHook(username, loginMethod, ip, protocol string, err error) {
-	if len(config.PostLoginHook) == 0 {
+	if config.PostLoginHook == "" {
 		return
 		return
 	}
 	}
 	if config.PostLoginScope == 1 && err == nil {
 	if config.PostLoginScope == 1 && err == nil {
@@ -2047,7 +2050,7 @@ func doExternalAuth(username, password string, pubKey []byte, keyboardInteractiv
 	if len(pkey) > 0 && !utils.IsStringPrefixInSlice(pkey, user.PublicKeys) {
 	if len(pkey) > 0 && !utils.IsStringPrefixInSlice(pkey, user.PublicKeys) {
 		user.PublicKeys = append(user.PublicKeys, pkey)
 		user.PublicKeys = append(user.PublicKeys, pkey)
 	}
 	}
-	// some users want to map multiple login usernames with a single SGTPGo account
+	// some users want to map multiple login usernames with a single SFTPGo account
 	// for example an SFTP user logins using "user1" or "user2" and the external auth
 	// for example an SFTP user logins using "user1" or "user2" and the external auth
 	// returns "user" in both cases, so we use the username returned from
 	// returns "user" in both cases, so we use the username returned from
 	// external auth and not the one used to login
 	// external auth and not the one used to login
@@ -2058,10 +2061,10 @@ func doExternalAuth(username, password string, pubKey []byte, keyboardInteractiv
 		user.UsedQuotaFiles = u.UsedQuotaFiles
 		user.UsedQuotaFiles = u.UsedQuotaFiles
 		user.LastQuotaUpdate = u.LastQuotaUpdate
 		user.LastQuotaUpdate = u.LastQuotaUpdate
 		user.LastLogin = u.LastLogin
 		user.LastLogin = u.LastLogin
-		err = provider.updateUser(user)
-	} else {
-		err = provider.addUser(user)
+		err = provider.updateUser(&user)
+		return user, err
 	}
 	}
+	err = provider.addUser(&user)
 	if err != nil {
 	if err != nil {
 		return user, err
 		return user, err
 	}
 	}
@@ -2174,7 +2177,7 @@ func CacheWebDAVUser(cachedUser *CachedUser, maxSize int) {
 
 
 		webDAVUsersCache.Range(func(k, v interface{}) bool {
 		webDAVUsersCache.Range(func(k, v interface{}) bool {
 			cacheSize++
 			cacheSize++
-			if len(userToRemove) == 0 {
+			if userToRemove == "" {
 				userToRemove = k.(string)
 				userToRemove = k.(string)
 				expirationTime = v.(*CachedUser).Expiration
 				expirationTime = v.(*CachedUser).Expiration
 				return true
 				return true

+ 20 - 17
dataprovider/memory.go

@@ -88,7 +88,7 @@ func (p MemoryProvider) close() error {
 
 
 func (p MemoryProvider) validateUserAndPass(username, password, ip, protocol string) (User, error) {
 func (p MemoryProvider) validateUserAndPass(username, password, ip, protocol string) (User, error) {
 	var user User
 	var user User
-	if len(password) == 0 {
+	if password == "" {
 		return user, errors.New("Credentials cannot be null or empty")
 		return user, errors.New("Credentials cannot be null or empty")
 	}
 	}
 	user, err := p.userExists(username)
 	user, err := p.userExists(username)
@@ -178,13 +178,13 @@ func (p MemoryProvider) getUsedQuota(username string) (int, int64, error) {
 	return user.UsedQuotaFiles, user.UsedQuotaSize, err
 	return user.UsedQuotaFiles, user.UsedQuotaSize, err
 }
 }
 
 
-func (p MemoryProvider) addUser(user User) error {
+func (p MemoryProvider) addUser(user *User) error {
 	p.dbHandle.Lock()
 	p.dbHandle.Lock()
 	defer p.dbHandle.Unlock()
 	defer p.dbHandle.Unlock()
 	if p.dbHandle.isClosed {
 	if p.dbHandle.isClosed {
 		return errMemoryProviderClosed
 		return errMemoryProviderClosed
 	}
 	}
-	err := validateUser(&user)
+	err := validateUser(user)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -198,20 +198,20 @@ func (p MemoryProvider) addUser(user User) error {
 	user.UsedQuotaFiles = 0
 	user.UsedQuotaFiles = 0
 	user.LastLogin = 0
 	user.LastLogin = 0
 	user.VirtualFolders = p.joinVirtualFoldersFields(user)
 	user.VirtualFolders = p.joinVirtualFoldersFields(user)
-	p.dbHandle.users[user.Username] = user
+	p.dbHandle.users[user.Username] = user.getACopy()
 	p.dbHandle.usersIdx[user.ID] = user.Username
 	p.dbHandle.usersIdx[user.ID] = user.Username
 	p.dbHandle.usernames = append(p.dbHandle.usernames, user.Username)
 	p.dbHandle.usernames = append(p.dbHandle.usernames, user.Username)
 	sort.Strings(p.dbHandle.usernames)
 	sort.Strings(p.dbHandle.usernames)
 	return nil
 	return nil
 }
 }
 
 
-func (p MemoryProvider) updateUser(user User) error {
+func (p MemoryProvider) updateUser(user *User) error {
 	p.dbHandle.Lock()
 	p.dbHandle.Lock()
 	defer p.dbHandle.Unlock()
 	defer p.dbHandle.Unlock()
 	if p.dbHandle.isClosed {
 	if p.dbHandle.isClosed {
 		return errMemoryProviderClosed
 		return errMemoryProviderClosed
 	}
 	}
-	err := validateUser(&user)
+	err := validateUser(user)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -227,11 +227,12 @@ func (p MemoryProvider) updateUser(user User) error {
 	user.UsedQuotaSize = u.UsedQuotaSize
 	user.UsedQuotaSize = u.UsedQuotaSize
 	user.UsedQuotaFiles = u.UsedQuotaFiles
 	user.UsedQuotaFiles = u.UsedQuotaFiles
 	user.LastLogin = u.LastLogin
 	user.LastLogin = u.LastLogin
-	p.dbHandle.users[user.Username] = user
+	// pre-login and external auth hook will use the passed *user so save a copy
+	p.dbHandle.users[user.Username] = user.getACopy()
 	return nil
 	return nil
 }
 }
 
 
-func (p MemoryProvider) deleteUser(user User) error {
+func (p MemoryProvider) deleteUser(user *User) error {
 	p.dbHandle.Lock()
 	p.dbHandle.Lock()
 	defer p.dbHandle.Unlock()
 	defer p.dbHandle.Unlock()
 	if p.dbHandle.isClosed {
 	if p.dbHandle.isClosed {
@@ -247,7 +248,7 @@ func (p MemoryProvider) deleteUser(user User) error {
 	delete(p.dbHandle.users, user.Username)
 	delete(p.dbHandle.users, user.Username)
 	delete(p.dbHandle.usersIdx, user.ID)
 	delete(p.dbHandle.usersIdx, user.ID)
 	// this could be more efficient
 	// this could be more efficient
-	p.dbHandle.usernames = []string{}
+	p.dbHandle.usernames = make([]string, 0, len(p.dbHandle.users))
 	for username := range p.dbHandle.users {
 	for username := range p.dbHandle.users {
 		p.dbHandle.usernames = append(p.dbHandle.usernames, username)
 		p.dbHandle.usernames = append(p.dbHandle.usernames, username)
 	}
 	}
@@ -396,7 +397,7 @@ func (p MemoryProvider) getUsedFolderQuota(mappedPath string) (int, int64, error
 	return folder.UsedQuotaFiles, folder.UsedQuotaSize, err
 	return folder.UsedQuotaFiles, folder.UsedQuotaSize, err
 }
 }
 
 
-func (p MemoryProvider) joinVirtualFoldersFields(user User) []vfs.VirtualFolder {
+func (p MemoryProvider) joinVirtualFoldersFields(user *User) []vfs.VirtualFolder {
 	var folders []vfs.VirtualFolder
 	var folders []vfs.VirtualFolder
 	for _, folder := range user.VirtualFolders {
 	for _, folder := range user.VirtualFolders {
 		f, err := p.addOrGetFolderInternal(folder.MappedPath, user.Username, folder.UsedQuotaSize, folder.UsedQuotaFiles,
 		f, err := p.addOrGetFolderInternal(folder.MappedPath, user.Username, folder.UsedQuotaSize, folder.UsedQuotaFiles,
@@ -522,13 +523,13 @@ func (p MemoryProvider) getFolderByPath(mappedPath string) (vfs.BaseVirtualFolde
 	return p.folderExistsInternal(mappedPath)
 	return p.folderExistsInternal(mappedPath)
 }
 }
 
 
-func (p MemoryProvider) addFolder(folder vfs.BaseVirtualFolder) error {
+func (p MemoryProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
 	p.dbHandle.Lock()
 	p.dbHandle.Lock()
 	defer p.dbHandle.Unlock()
 	defer p.dbHandle.Unlock()
 	if p.dbHandle.isClosed {
 	if p.dbHandle.isClosed {
 		return errMemoryProviderClosed
 		return errMemoryProviderClosed
 	}
 	}
-	err := validateFolder(&folder)
+	err := validateFolder(folder)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -537,13 +538,13 @@ func (p MemoryProvider) addFolder(folder vfs.BaseVirtualFolder) error {
 		return fmt.Errorf("folder %#v already exists", folder.MappedPath)
 		return fmt.Errorf("folder %#v already exists", folder.MappedPath)
 	}
 	}
 	folder.ID = p.getNextFolderID()
 	folder.ID = p.getNextFolderID()
-	p.dbHandle.vfolders[folder.MappedPath] = folder
+	p.dbHandle.vfolders[folder.MappedPath] = *folder
 	p.dbHandle.vfoldersPaths = append(p.dbHandle.vfoldersPaths, folder.MappedPath)
 	p.dbHandle.vfoldersPaths = append(p.dbHandle.vfoldersPaths, folder.MappedPath)
 	sort.Strings(p.dbHandle.vfoldersPaths)
 	sort.Strings(p.dbHandle.vfoldersPaths)
 	return nil
 	return nil
 }
 }
 
 
-func (p MemoryProvider) deleteFolder(folder vfs.BaseVirtualFolder) error {
+func (p MemoryProvider) deleteFolder(folder *vfs.BaseVirtualFolder) error {
 	p.dbHandle.Lock()
 	p.dbHandle.Lock()
 	defer p.dbHandle.Unlock()
 	defer p.dbHandle.Unlock()
 	if p.dbHandle.isClosed {
 	if p.dbHandle.isClosed {
@@ -644,8 +645,9 @@ func (p MemoryProvider) reloadConfig() error {
 			logger.Debug(logSender, "", "folder %#v already exists, restore not needed", folder.MappedPath)
 			logger.Debug(logSender, "", "folder %#v already exists, restore not needed", folder.MappedPath)
 			continue
 			continue
 		}
 		}
+		folder := folder // pin
 		folder.Users = nil
 		folder.Users = nil
-		err = p.addFolder(folder)
+		err = p.addFolder(&folder)
 		if err != nil {
 		if err != nil {
 			providerLog(logger.LevelWarn, "error adding folder %#v: %v", folder.MappedPath, err)
 			providerLog(logger.LevelWarn, "error adding folder %#v: %v", folder.MappedPath, err)
 			return err
 			return err
@@ -653,15 +655,16 @@ func (p MemoryProvider) reloadConfig() error {
 	}
 	}
 	for _, user := range dump.Users {
 	for _, user := range dump.Users {
 		u, err := p.userExists(user.Username)
 		u, err := p.userExists(user.Username)
+		user := user // pin
 		if err == nil {
 		if err == nil {
 			user.ID = u.ID
 			user.ID = u.ID
-			err = p.updateUser(user)
+			err = p.updateUser(&user)
 			if err != nil {
 			if err != nil {
 				providerLog(logger.LevelWarn, "error updating user %#v: %v", user.Username, err)
 				providerLog(logger.LevelWarn, "error updating user %#v: %v", user.Username, err)
 				return err
 				return err
 			}
 			}
 		} else {
 		} else {
-			err = p.addUser(user)
+			err = p.addUser(&user)
 			if err != nil {
 			if err != nil {
 				providerLog(logger.LevelWarn, "error adding user %#v: %v", user.Username, err)
 				providerLog(logger.LevelWarn, "error adding user %#v: %v", user.Username, err)
 				return err
 				return err

+ 6 - 6
dataprovider/mysql.go

@@ -74,7 +74,7 @@ func initializeMySQLProvider() error {
 }
 }
 func getMySQLConnectionString(redactedPwd bool) string {
 func getMySQLConnectionString(redactedPwd bool) string {
 	var connectionString string
 	var connectionString string
-	if len(config.ConnectionString) == 0 {
+	if config.ConnectionString == "" {
 		password := config.Password
 		password := config.Password
 		if redactedPwd {
 		if redactedPwd {
 			password = "[redacted]"
 			password = "[redacted]"
@@ -119,15 +119,15 @@ func (p MySQLProvider) userExists(username string) (User, error) {
 	return sqlCommonCheckUserExists(username, p.dbHandle)
 	return sqlCommonCheckUserExists(username, p.dbHandle)
 }
 }
 
 
-func (p MySQLProvider) addUser(user User) error {
+func (p MySQLProvider) addUser(user *User) error {
 	return sqlCommonAddUser(user, p.dbHandle)
 	return sqlCommonAddUser(user, p.dbHandle)
 }
 }
 
 
-func (p MySQLProvider) updateUser(user User) error {
+func (p MySQLProvider) updateUser(user *User) error {
 	return sqlCommonUpdateUser(user, p.dbHandle)
 	return sqlCommonUpdateUser(user, p.dbHandle)
 }
 }
 
 
-func (p MySQLProvider) deleteUser(user User) error {
+func (p MySQLProvider) deleteUser(user *User) error {
 	return sqlCommonDeleteUser(user, p.dbHandle)
 	return sqlCommonDeleteUser(user, p.dbHandle)
 }
 }
 
 
@@ -153,11 +153,11 @@ func (p MySQLProvider) getFolderByPath(mappedPath string) (vfs.BaseVirtualFolder
 	return sqlCommonCheckFolderExists(ctx, mappedPath, p.dbHandle)
 	return sqlCommonCheckFolderExists(ctx, mappedPath, p.dbHandle)
 }
 }
 
 
-func (p MySQLProvider) addFolder(folder vfs.BaseVirtualFolder) error {
+func (p MySQLProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
 	return sqlCommonAddFolder(folder, p.dbHandle)
 	return sqlCommonAddFolder(folder, p.dbHandle)
 }
 }
 
 
-func (p MySQLProvider) deleteFolder(folder vfs.BaseVirtualFolder) error {
+func (p MySQLProvider) deleteFolder(folder *vfs.BaseVirtualFolder) error {
 	return sqlCommonDeleteFolder(folder, p.dbHandle)
 	return sqlCommonDeleteFolder(folder, p.dbHandle)
 }
 }
 
 

+ 6 - 6
dataprovider/pgsql.go

@@ -75,7 +75,7 @@ func initializePGSQLProvider() error {
 
 
 func getPGSQLConnectionString(redactedPwd bool) string {
 func getPGSQLConnectionString(redactedPwd bool) string {
 	var connectionString string
 	var connectionString string
-	if len(config.ConnectionString) == 0 {
+	if config.ConnectionString == "" {
 		password := config.Password
 		password := config.Password
 		if redactedPwd {
 		if redactedPwd {
 			password = "[redacted]"
 			password = "[redacted]"
@@ -120,15 +120,15 @@ func (p PGSQLProvider) userExists(username string) (User, error) {
 	return sqlCommonCheckUserExists(username, p.dbHandle)
 	return sqlCommonCheckUserExists(username, p.dbHandle)
 }
 }
 
 
-func (p PGSQLProvider) addUser(user User) error {
+func (p PGSQLProvider) addUser(user *User) error {
 	return sqlCommonAddUser(user, p.dbHandle)
 	return sqlCommonAddUser(user, p.dbHandle)
 }
 }
 
 
-func (p PGSQLProvider) updateUser(user User) error {
+func (p PGSQLProvider) updateUser(user *User) error {
 	return sqlCommonUpdateUser(user, p.dbHandle)
 	return sqlCommonUpdateUser(user, p.dbHandle)
 }
 }
 
 
-func (p PGSQLProvider) deleteUser(user User) error {
+func (p PGSQLProvider) deleteUser(user *User) error {
 	return sqlCommonDeleteUser(user, p.dbHandle)
 	return sqlCommonDeleteUser(user, p.dbHandle)
 }
 }
 
 
@@ -154,11 +154,11 @@ func (p PGSQLProvider) getFolderByPath(mappedPath string) (vfs.BaseVirtualFolder
 	return sqlCommonCheckFolderExists(ctx, mappedPath, p.dbHandle)
 	return sqlCommonCheckFolderExists(ctx, mappedPath, p.dbHandle)
 }
 }
 
 
-func (p PGSQLProvider) addFolder(folder vfs.BaseVirtualFolder) error {
+func (p PGSQLProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
 	return sqlCommonAddFolder(folder, p.dbHandle)
 	return sqlCommonAddFolder(folder, p.dbHandle)
 }
 }
 
 
-func (p PGSQLProvider) deleteFolder(folder vfs.BaseVirtualFolder) error {
+func (p PGSQLProvider) deleteFolder(folder *vfs.BaseVirtualFolder) error {
 	return sqlCommonDeleteFolder(folder, p.dbHandle)
 	return sqlCommonDeleteFolder(folder, p.dbHandle)
 }
 }
 
 

+ 16 - 16
dataprovider/sqlcommon.go

@@ -48,7 +48,7 @@ func getUserByUsername(username string, dbHandle sqlQuerier) (User, error) {
 
 
 func sqlCommonValidateUserAndPass(username, password, ip, protocol string, dbHandle *sql.DB) (User, error) {
 func sqlCommonValidateUserAndPass(username, password, ip, protocol string, dbHandle *sql.DB) (User, error) {
 	var user User
 	var user User
-	if len(password) == 0 {
+	if password == "" {
 		return user, errors.New("Credentials cannot be null or empty")
 		return user, errors.New("Credentials cannot be null or empty")
 	}
 	}
 	user, err := getUserByUsername(username, dbHandle)
 	user, err := getUserByUsername(username, dbHandle)
@@ -177,8 +177,8 @@ func sqlCommonCheckUserExists(username string, dbHandle *sql.DB) (User, error) {
 	return getUserWithVirtualFolders(user, dbHandle)
 	return getUserWithVirtualFolders(user, dbHandle)
 }
 }
 
 
-func sqlCommonAddUser(user User, dbHandle *sql.DB) error {
-	err := validateUser(&user)
+func sqlCommonAddUser(user *User, dbHandle *sql.DB) error {
+	err := validateUser(user)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -231,8 +231,8 @@ func sqlCommonAddUser(user User, dbHandle *sql.DB) error {
 	return tx.Commit()
 	return tx.Commit()
 }
 }
 
 
-func sqlCommonUpdateUser(user User, dbHandle *sql.DB) error {
-	err := validateUser(&user)
+func sqlCommonUpdateUser(user *User, dbHandle *sql.DB) error {
+	err := validateUser(user)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -285,7 +285,7 @@ func sqlCommonUpdateUser(user User, dbHandle *sql.DB) error {
 	return tx.Commit()
 	return tx.Commit()
 }
 }
 
 
-func sqlCommonDeleteUser(user User, dbHandle *sql.DB) error {
+func sqlCommonDeleteUser(user *User, dbHandle *sql.DB) error {
 	ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
 	ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
 	defer cancel()
 	defer cancel()
 	q := getDeleteUserQuery()
 	q := getDeleteUserQuery()
@@ -470,7 +470,7 @@ func sqlCommonCheckFolderExists(ctx context.Context, name string, dbHandle sqlQu
 func sqlCommonAddOrGetFolder(ctx context.Context, name string, usedQuotaSize int64, usedQuotaFiles int, lastQuotaUpdate int64, dbHandle sqlQuerier) (vfs.BaseVirtualFolder, error) {
 func sqlCommonAddOrGetFolder(ctx context.Context, name string, usedQuotaSize int64, usedQuotaFiles int, lastQuotaUpdate int64, dbHandle sqlQuerier) (vfs.BaseVirtualFolder, error) {
 	folder, err := sqlCommonCheckFolderExists(ctx, name, dbHandle)
 	folder, err := sqlCommonCheckFolderExists(ctx, name, dbHandle)
 	if _, ok := err.(*RecordNotFoundError); ok {
 	if _, ok := err.(*RecordNotFoundError); ok {
-		f := vfs.BaseVirtualFolder{
+		f := &vfs.BaseVirtualFolder{
 			MappedPath:      name,
 			MappedPath:      name,
 			UsedQuotaSize:   usedQuotaSize,
 			UsedQuotaSize:   usedQuotaSize,
 			UsedQuotaFiles:  usedQuotaFiles,
 			UsedQuotaFiles:  usedQuotaFiles,
@@ -485,8 +485,8 @@ func sqlCommonAddOrGetFolder(ctx context.Context, name string, usedQuotaSize int
 	return folder, err
 	return folder, err
 }
 }
 
 
-func sqlCommonAddFolder(folder vfs.BaseVirtualFolder, dbHandle sqlQuerier) error {
-	err := validateFolder(&folder)
+func sqlCommonAddFolder(folder *vfs.BaseVirtualFolder, dbHandle sqlQuerier) error {
+	err := validateFolder(folder)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -503,7 +503,7 @@ func sqlCommonAddFolder(folder vfs.BaseVirtualFolder, dbHandle sqlQuerier) error
 	return err
 	return err
 }
 }
 
 
-func sqlCommonDeleteFolder(folder vfs.BaseVirtualFolder, dbHandle sqlQuerier) error {
+func sqlCommonDeleteFolder(folder *vfs.BaseVirtualFolder, dbHandle sqlQuerier) error {
 	ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
 	ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
 	defer cancel()
 	defer cancel()
 	q := getDeleteFolderQuery()
 	q := getDeleteFolderQuery()
@@ -585,7 +585,7 @@ func sqlCommonGetFolders(limit, offset int, order, folderPath string, dbHandle s
 	return getVirtualFoldersWithUsers(folders, dbHandle)
 	return getVirtualFoldersWithUsers(folders, dbHandle)
 }
 }
 
 
-func sqlCommonClearFolderMapping(ctx context.Context, user User, dbHandle sqlQuerier) error {
+func sqlCommonClearFolderMapping(ctx context.Context, user *User, dbHandle sqlQuerier) error {
 	q := getClearFolderMappingQuery()
 	q := getClearFolderMappingQuery()
 	stmt, err := dbHandle.PrepareContext(ctx, q)
 	stmt, err := dbHandle.PrepareContext(ctx, q)
 	if err != nil {
 	if err != nil {
@@ -597,7 +597,7 @@ func sqlCommonClearFolderMapping(ctx context.Context, user User, dbHandle sqlQue
 	return err
 	return err
 }
 }
 
 
-func sqlCommonAddFolderMapping(ctx context.Context, user User, folder vfs.VirtualFolder, dbHandle sqlQuerier) error {
+func sqlCommonAddFolderMapping(ctx context.Context, user *User, folder vfs.VirtualFolder, dbHandle sqlQuerier) error {
 	q := getAddFolderMappingQuery()
 	q := getAddFolderMappingQuery()
 	stmt, err := dbHandle.PrepareContext(ctx, q)
 	stmt, err := dbHandle.PrepareContext(ctx, q)
 	if err != nil {
 	if err != nil {
@@ -609,7 +609,7 @@ func sqlCommonAddFolderMapping(ctx context.Context, user User, folder vfs.Virtua
 	return err
 	return err
 }
 }
 
 
-func generateVirtualFoldersMapping(ctx context.Context, user User, dbHandle sqlQuerier) error {
+func generateVirtualFoldersMapping(ctx context.Context, user *User, dbHandle sqlQuerier) error {
 	err := sqlCommonClearFolderMapping(ctx, user, dbHandle)
 	err := sqlCommonClearFolderMapping(ctx, user, dbHandle)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -813,7 +813,7 @@ func sqlCommonExecSQLAndUpdateDBVersion(dbHandle *sql.DB, sql []string, newVersi
 		return err
 		return err
 	}
 	}
 	for _, q := range sql {
 	for _, q := range sql {
-		if len(strings.TrimSpace(q)) == 0 {
+		if strings.TrimSpace(q) == "" {
 			continue
 			continue
 		}
 		}
 		_, err = tx.ExecContext(ctx, q)
 		_, err = tx.ExecContext(ctx, q)
@@ -892,7 +892,7 @@ func sqlCommonRestoreCompatVirtualFolders(ctx context.Context, users []userCompa
 				QuotaSize:         quotaSize,
 				QuotaSize:         quotaSize,
 				QuotaFiles:        quotaFiles,
 				QuotaFiles:        quotaFiles,
 			}
 			}
-			err = sqlCommonAddFolderMapping(ctx, u, f, dbHandle)
+			err = sqlCommonAddFolderMapping(ctx, &u, f, dbHandle)
 			if err != nil {
 			if err != nil {
 				providerLog(logger.LevelWarn, "error adding virtual folder mapping for user %#v: %v", user.Username, err)
 				providerLog(logger.LevelWarn, "error adding virtual folder mapping for user %#v: %v", user.Username, err)
 				return foldersToScan, err
 				return foldersToScan, err
@@ -923,7 +923,7 @@ func sqlCommonUpdateDatabaseFrom3To4(sqlV4 string, dbHandle *sql.DB) error {
 		return err
 		return err
 	}
 	}
 	for _, q := range strings.Split(sql, ";") {
 	for _, q := range strings.Split(sql, ";") {
-		if len(strings.TrimSpace(q)) == 0 {
+		if strings.TrimSpace(q) == "" {
 			continue
 			continue
 		}
 		}
 		_, err = tx.ExecContext(ctx, q)
 		_, err = tx.ExecContext(ctx, q)

+ 6 - 6
dataprovider/sqlite.go

@@ -93,7 +93,7 @@ func initializeSQLiteProvider(basePath string) error {
 	var err error
 	var err error
 	var connectionString string
 	var connectionString string
 	logSender = fmt.Sprintf("dataprovider_%v", SQLiteDataProviderName)
 	logSender = fmt.Sprintf("dataprovider_%v", SQLiteDataProviderName)
-	if len(config.ConnectionString) == 0 {
+	if config.ConnectionString == "" {
 		dbPath := config.Name
 		dbPath := config.Name
 		if !utils.IsFileInputValid(dbPath) {
 		if !utils.IsFileInputValid(dbPath) {
 			return fmt.Errorf("Invalid database path: %#v", dbPath)
 			return fmt.Errorf("Invalid database path: %#v", dbPath)
@@ -149,15 +149,15 @@ func (p SQLiteProvider) userExists(username string) (User, error) {
 	return sqlCommonCheckUserExists(username, p.dbHandle)
 	return sqlCommonCheckUserExists(username, p.dbHandle)
 }
 }
 
 
-func (p SQLiteProvider) addUser(user User) error {
+func (p SQLiteProvider) addUser(user *User) error {
 	return sqlCommonAddUser(user, p.dbHandle)
 	return sqlCommonAddUser(user, p.dbHandle)
 }
 }
 
 
-func (p SQLiteProvider) updateUser(user User) error {
+func (p SQLiteProvider) updateUser(user *User) error {
 	return sqlCommonUpdateUser(user, p.dbHandle)
 	return sqlCommonUpdateUser(user, p.dbHandle)
 }
 }
 
 
-func (p SQLiteProvider) deleteUser(user User) error {
+func (p SQLiteProvider) deleteUser(user *User) error {
 	return sqlCommonDeleteUser(user, p.dbHandle)
 	return sqlCommonDeleteUser(user, p.dbHandle)
 }
 }
 
 
@@ -183,11 +183,11 @@ func (p SQLiteProvider) getFolderByPath(mappedPath string) (vfs.BaseVirtualFolde
 	return sqlCommonCheckFolderExists(ctx, mappedPath, p.dbHandle)
 	return sqlCommonCheckFolderExists(ctx, mappedPath, p.dbHandle)
 }
 }
 
 
-func (p SQLiteProvider) addFolder(folder vfs.BaseVirtualFolder) error {
+func (p SQLiteProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
 	return sqlCommonAddFolder(folder, p.dbHandle)
 	return sqlCommonAddFolder(folder, p.dbHandle)
 }
 }
 
 
-func (p SQLiteProvider) deleteFolder(folder vfs.BaseVirtualFolder) error {
+func (p SQLiteProvider) deleteFolder(folder *vfs.BaseVirtualFolder) error {
 	return sqlCommonDeleteFolder(folder, p.dbHandle)
 	return sqlCommonDeleteFolder(folder, p.dbHandle)
 }
 }
 
 

+ 3 - 3
httpd/api_folder.go

@@ -63,7 +63,7 @@ func addFolder(w http.ResponseWriter, r *http.Request) {
 		sendAPIResponse(w, r, err, "", http.StatusBadRequest)
 		sendAPIResponse(w, r, err, "", http.StatusBadRequest)
 		return
 		return
 	}
 	}
-	err = dataprovider.AddFolder(folder)
+	err = dataprovider.AddFolder(&folder)
 	if err == nil {
 	if err == nil {
 		folder, err = dataprovider.GetFolderByPath(folder.MappedPath)
 		folder, err = dataprovider.GetFolderByPath(folder.MappedPath)
 		if err == nil {
 		if err == nil {
@@ -81,7 +81,7 @@ func deleteFolderByPath(w http.ResponseWriter, r *http.Request) {
 	if _, ok := r.URL.Query()["folder_path"]; ok {
 	if _, ok := r.URL.Query()["folder_path"]; ok {
 		folderPath = r.URL.Query().Get("folder_path")
 		folderPath = r.URL.Query().Get("folder_path")
 	}
 	}
-	if len(folderPath) == 0 {
+	if folderPath == "" {
 		err := errors.New("a non-empty folder path is required")
 		err := errors.New("a non-empty folder path is required")
 		sendAPIResponse(w, r, err, "", http.StatusBadRequest)
 		sendAPIResponse(w, r, err, "", http.StatusBadRequest)
 		return
 		return
@@ -92,7 +92,7 @@ func deleteFolderByPath(w http.ResponseWriter, r *http.Request) {
 		sendAPIResponse(w, r, err, "", getRespStatus(err))
 		sendAPIResponse(w, r, err, "", getRespStatus(err))
 		return
 		return
 	}
 	}
-	err = dataprovider.DeleteFolder(folder)
+	err = dataprovider.DeleteFolder(&folder)
 	if err != nil {
 	if err != nil {
 		sendAPIResponse(w, r, err, "", http.StatusInternalServerError)
 		sendAPIResponse(w, r, err, "", http.StatusInternalServerError)
 	} else {
 	} else {

+ 6 - 4
httpd/api_maintenance.go

@@ -25,7 +25,7 @@ func dumpData(w http.ResponseWriter, r *http.Request) {
 	if _, ok := r.URL.Query()["indent"]; ok {
 	if _, ok := r.URL.Query()["indent"]; ok {
 		indent = strings.TrimSpace(r.URL.Query().Get("indent"))
 		indent = strings.TrimSpace(r.URL.Query().Get("indent"))
 	}
 	}
-	if len(outputFile) == 0 {
+	if outputFile == "" {
 		sendAPIResponse(w, r, errors.New("Invalid or missing output_file"), "", http.StatusBadRequest)
 		sendAPIResponse(w, r, errors.New("Invalid or missing output_file"), "", http.StatusBadRequest)
 		return
 		return
 	}
 	}
@@ -147,8 +147,9 @@ func RestoreFolders(folders []vfs.BaseVirtualFolder, inputFile string, scanQuota
 			logger.Debug(logSender, "", "folder %#v already exists, restore not needed", folder.MappedPath)
 			logger.Debug(logSender, "", "folder %#v already exists, restore not needed", folder.MappedPath)
 			continue
 			continue
 		}
 		}
+		folder := folder // pin
 		folder.Users = nil
 		folder.Users = nil
-		err = dataprovider.AddFolder(folder)
+		err = dataprovider.AddFolder(&folder)
 		logger.Debug(logSender, "", "adding new folder: %+v, dump file: %#v, error: %v", folder, inputFile, err)
 		logger.Debug(logSender, "", "adding new folder: %+v, dump file: %#v, error: %v", folder, inputFile, err)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -166,6 +167,7 @@ func RestoreFolders(folders []vfs.BaseVirtualFolder, inputFile string, scanQuota
 // RestoreUsers restores the specified users
 // RestoreUsers restores the specified users
 func RestoreUsers(users []dataprovider.User, inputFile string, mode, scanQuota int) error {
 func RestoreUsers(users []dataprovider.User, inputFile string, mode, scanQuota int) error {
 	for _, user := range users {
 	for _, user := range users {
+		user := user // pin
 		u, err := dataprovider.UserExists(user.Username)
 		u, err := dataprovider.UserExists(user.Username)
 		if err == nil {
 		if err == nil {
 			if mode == 1 {
 			if mode == 1 {
@@ -173,14 +175,14 @@ func RestoreUsers(users []dataprovider.User, inputFile string, mode, scanQuota i
 				continue
 				continue
 			}
 			}
 			user.ID = u.ID
 			user.ID = u.ID
-			err = dataprovider.UpdateUser(user)
+			err = dataprovider.UpdateUser(&user)
 			user.Password = "[redacted]"
 			user.Password = "[redacted]"
 			logger.Debug(logSender, "", "restoring existing user: %+v, dump file: %#v, error: %v", user, inputFile, err)
 			logger.Debug(logSender, "", "restoring existing user: %+v, dump file: %#v, error: %v", user, inputFile, err)
 			if mode == 2 && err == nil {
 			if mode == 2 && err == nil {
 				disconnectUser(user.Username)
 				disconnectUser(user.Username)
 			}
 			}
 		} else {
 		} else {
-			err = dataprovider.AddUser(user)
+			err = dataprovider.AddUser(&user)
 			user.Password = "[redacted]"
 			user.Password = "[redacted]"
 			logger.Debug(logSender, "", "adding new user: %+v, dump file: %#v, error: %v", user, inputFile, err)
 			logger.Debug(logSender, "", "adding new user: %+v, dump file: %#v, error: %v", user, inputFile, err)
 		}
 		}

+ 3 - 3
httpd/api_user.go

@@ -115,7 +115,7 @@ func addUser(w http.ResponseWriter, r *http.Request) {
 			return
 			return
 		}
 		}
 	}
 	}
-	err = dataprovider.AddUser(user)
+	err = dataprovider.AddUser(&user)
 	if err == nil {
 	if err == nil {
 		user, err = dataprovider.UserExists(user.Username)
 		user, err = dataprovider.UserExists(user.Username)
 		if err == nil {
 		if err == nil {
@@ -181,7 +181,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
 		sendAPIResponse(w, r, err, "user ID in request body does not match user ID in path parameter", http.StatusBadRequest)
 		sendAPIResponse(w, r, err, "user ID in request body does not match user ID in path parameter", http.StatusBadRequest)
 		return
 		return
 	}
 	}
-	err = dataprovider.UpdateUser(user)
+	err = dataprovider.UpdateUser(&user)
 	if err != nil {
 	if err != nil {
 		sendAPIResponse(w, r, err, "", getRespStatus(err))
 		sendAPIResponse(w, r, err, "", getRespStatus(err))
 	} else {
 	} else {
@@ -204,7 +204,7 @@ func deleteUser(w http.ResponseWriter, r *http.Request) {
 		sendAPIResponse(w, r, err, "", getRespStatus(err))
 		sendAPIResponse(w, r, err, "", getRespStatus(err))
 		return
 		return
 	}
 	}
-	err = dataprovider.DeleteUser(user)
+	err = dataprovider.DeleteUser(&user)
 	if err != nil {
 	if err != nil {
 		sendAPIResponse(w, r, err, "", http.StatusInternalServerError)
 		sendAPIResponse(w, r, err, "", http.StatusInternalServerError)
 	} else {
 	} else {

+ 2 - 2
httpd/httpd.go

@@ -129,10 +129,10 @@ func (c Conf) Initialize(configDir string) error {
 	staticFilesPath := getConfigPath(c.StaticFilesPath, configDir)
 	staticFilesPath := getConfigPath(c.StaticFilesPath, configDir)
 	templatesPath := getConfigPath(c.TemplatesPath, configDir)
 	templatesPath := getConfigPath(c.TemplatesPath, configDir)
 	enableWebAdmin := len(staticFilesPath) > 0 || len(templatesPath) > 0
 	enableWebAdmin := len(staticFilesPath) > 0 || len(templatesPath) > 0
-	if len(backupsPath) == 0 {
+	if backupsPath == "" {
 		return fmt.Errorf("Required directory is invalid, backup path %#v", backupsPath)
 		return fmt.Errorf("Required directory is invalid, backup path %#v", backupsPath)
 	}
 	}
-	if enableWebAdmin && (len(staticFilesPath) == 0 || len(templatesPath) == 0) {
+	if enableWebAdmin && (staticFilesPath == "" || templatesPath == "") {
 		return fmt.Errorf("Required directory is invalid, static file path: %#v template path: %#v",
 		return fmt.Errorf("Required directory is invalid, static file path: %#v template path: %#v",
 			staticFilesPath, templatesPath)
 			staticFilesPath, templatesPath)
 	}
 	}

+ 1 - 1
httpd/httpd_test.go

@@ -3430,7 +3430,7 @@ func TestRenderWebCloneUserMock(t *testing.T) {
 		assert.NoError(t, err)
 		assert.NoError(t, err)
 		user.FsConfig.CryptConfig.Passphrase.SetStatus(kms.SecretStatusAWS)
 		user.FsConfig.CryptConfig.Passphrase.SetStatus(kms.SecretStatusAWS)
 		user.Password = defaultPassword
 		user.Password = defaultPassword
-		err = dataprovider.UpdateUser(user)
+		err = dataprovider.UpdateUser(&user)
 		assert.NoError(t, err)
 		assert.NoError(t, err)
 
 
 		req, err = http.NewRequest(http.MethodGet, webUserPath+fmt.Sprintf("?cloneFromId=%v", user.ID), nil)
 		req, err = http.NewRequest(http.MethodGet, webUserPath+fmt.Sprintf("?cloneFromId=%v", user.ID), nil)

+ 3 - 3
httpd/web.go

@@ -727,7 +727,7 @@ func handleWebAddUserPost(w http.ResponseWriter, r *http.Request) {
 		renderAddUserPage(w, user, err.Error())
 		renderAddUserPage(w, user, err.Error())
 		return
 		return
 	}
 	}
-	err = dataprovider.AddUser(user)
+	err = dataprovider.AddUser(&user)
 	if err == nil {
 	if err == nil {
 		http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
 		http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
 	} else {
 	} else {
@@ -764,7 +764,7 @@ func handleWebUpdateUserPost(w http.ResponseWriter, r *http.Request) {
 		user.FsConfig.GCSConfig.Credentials, user.FsConfig.CryptConfig.Passphrase, user.FsConfig.SFTPConfig.Password,
 		user.FsConfig.GCSConfig.Credentials, user.FsConfig.CryptConfig.Passphrase, user.FsConfig.SFTPConfig.Password,
 		user.FsConfig.SFTPConfig.PrivateKey)
 		user.FsConfig.SFTPConfig.PrivateKey)
 
 
-	err = dataprovider.UpdateUser(updatedUser)
+	err = dataprovider.UpdateUser(&updatedUser)
 	if err == nil {
 	if err == nil {
 		if len(r.Form.Get("disconnect")) > 0 {
 		if len(r.Form.Get("disconnect")) > 0 {
 			disconnectUser(user.Username)
 			disconnectUser(user.Username)
@@ -806,7 +806,7 @@ func handleWebAddFolderPost(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	folder.MappedPath = r.Form.Get("mapped_path")
 	folder.MappedPath = r.Form.Get("mapped_path")
 
 
-	err = dataprovider.AddFolder(folder)
+	err = dataprovider.AddFolder(&folder)
 	if err == nil {
 	if err == nil {
 		http.Redirect(w, r, webFoldersPath, http.StatusSeeOther)
 		http.Redirect(w, r, webFoldersPath, http.StatusSeeOther)
 	} else {
 	} else {

+ 1 - 2
main.go

@@ -1,6 +1,5 @@
 // Fully featured and highly configurable SFTP server with optional
 // Fully featured and highly configurable SFTP server with optional
-// FTP/S and WebDAV support. It can serve local filesystem, S3 or
-// Google Cloud Storage.
+// FTP/S and WebDAV support.
 // For more details about features, installation, configuration and usage
 // For more details about features, installation, configuration and usage
 // please refer to the README inside the source tree:
 // please refer to the README inside the source tree:
 // https://github.com/drakkan/sftpgo/blob/master/README.md
 // https://github.com/drakkan/sftpgo/blob/master/README.md

+ 1 - 1
pkgs/build.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 #!/bin/bash
 
 
-NFPM_VERSION=2.2.1
+NFPM_VERSION=2.2.2
 NFPM_ARCH=${NFPM_ARCH:-amd64}
 NFPM_ARCH=${NFPM_ARCH:-amd64}
 if [ -z ${SFTPGO_VERSION} ]
 if [ -z ${SFTPGO_VERSION} ]
 then
 then

+ 1 - 1
service/service.go

@@ -106,7 +106,7 @@ func (s *Service) Start() error {
 
 
 	if s.PortableMode == 1 {
 	if s.PortableMode == 1 {
 		// create the user for portable mode
 		// create the user for portable mode
-		err = dataprovider.AddUser(s.PortableUser)
+		err = dataprovider.AddUser(&s.PortableUser)
 		if err != nil {
 		if err != nil {
 			logger.ErrorToConsole("error adding portable user: %v", err)
 			logger.ErrorToConsole("error adding portable user: %v", err)
 			return err
 			return err

+ 1 - 1
service/service_portable.go

@@ -245,7 +245,7 @@ func (s *Service) configurePortableUser() string {
 	if len(s.PortableUser.Password) > 0 {
 	if len(s.PortableUser.Password) > 0 {
 		printablePassword = "[redacted]"
 		printablePassword = "[redacted]"
 	}
 	}
-	if len(s.PortableUser.PublicKeys) == 0 && len(s.PortableUser.Password) == 0 {
+	if len(s.PortableUser.PublicKeys) == 0 && s.PortableUser.Password == "" {
 		var b strings.Builder
 		var b strings.Builder
 		for i := 0; i < 8; i++ {
 		for i := 0; i < 8; i++ {
 			b.WriteRune(chars[rand.Intn(len(chars))])
 			b.WriteRune(chars[rand.Intn(len(chars))])

+ 1 - 1
sftpd/scp.go

@@ -668,7 +668,7 @@ func (c *scpCommand) parseUploadMessage(command string) (int64, string, error) {
 			return size, name, err
 			return size, name, err
 		}
 		}
 		name = parts[2]
 		name = parts[2]
-		if len(name) == 0 {
+		if name == "" {
 			err = fmt.Errorf("error getting name from upload message, cannot be empty")
 			err = fmt.Errorf("error getting name from upload message, cannot be empty")
 			c.connection.Log(logger.LevelWarn, "error: %v", err)
 			c.connection.Log(logger.LevelWarn, "error: %v", err)
 			c.sendErrorMessage(err)
 			c.sendErrorMessage(err)

+ 1 - 1
sftpd/server.go

@@ -323,7 +323,7 @@ func (c *Configuration) configureLoginBanner(serverConfig *ssh.ServerConfig, con
 }
 }
 
 
 func (c *Configuration) configureKeyboardInteractiveAuth(serverConfig *ssh.ServerConfig) {
 func (c *Configuration) configureKeyboardInteractiveAuth(serverConfig *ssh.ServerConfig) {
-	if len(c.KeyboardInteractiveHook) == 0 {
+	if c.KeyboardInteractiveHook == "" {
 		return
 		return
 	}
 	}
 	if !strings.HasPrefix(c.KeyboardInteractiveHook, "http") {
 	if !strings.HasPrefix(c.KeyboardInteractiveHook, "http") {

+ 2 - 2
sftpd/ssh_cmd.go

@@ -537,7 +537,7 @@ func (c *sshCommand) getCopyPaths() (string, string, error) {
 	if strings.HasSuffix(sshDestPath, "/") {
 	if strings.HasSuffix(sshDestPath, "/") {
 		sshDestPath = path.Join(sshDestPath, path.Base(sshSourcePath))
 		sshDestPath = path.Join(sshDestPath, path.Base(sshSourcePath))
 	}
 	}
-	if len(sshSourcePath) == 0 || len(sshDestPath) == 0 || len(c.args) != 2 {
+	if sshSourcePath == "" || sshDestPath == "" || len(c.args) != 2 {
 		err := errors.New("usage sftpgo-copy <source dir path> <destination dir path>")
 		err := errors.New("usage sftpgo-copy <source dir path> <destination dir path>")
 		return "", "", err
 		return "", "", err
 	}
 	}
@@ -606,7 +606,7 @@ func (c *sshCommand) checkCopyPermissions(fsSourcePath, fsDestPath, sshSourcePat
 
 
 func (c *sshCommand) getRemovePath() (string, error) {
 func (c *sshCommand) getRemovePath() (string, error) {
 	sshDestPath := c.getDestPath()
 	sshDestPath := c.getDestPath()
-	if len(sshDestPath) == 0 || len(c.args) != 1 {
+	if sshDestPath == "" || len(c.args) != 1 {
 		err := errors.New("usage sftpgo-remove <destination path>")
 		err := errors.New("usage sftpgo-remove <destination path>")
 		return "", err
 		return "", err
 	}
 	}

+ 12 - 12
webdavd/internal_test.go

@@ -896,7 +896,7 @@ func TestBasicUsersCache(t *testing.T) {
 	}
 	}
 	u.Permissions = make(map[string][]string)
 	u.Permissions = make(map[string][]string)
 	u.Permissions["/"] = []string{dataprovider.PermAny}
 	u.Permissions["/"] = []string{dataprovider.PermAny}
-	err := dataprovider.AddUser(u)
+	err := dataprovider.AddUser(&u)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	user, err := dataprovider.UserExists(u.Username)
 	user, err := dataprovider.UserExists(u.Username)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
@@ -969,7 +969,7 @@ func TestBasicUsersCache(t *testing.T) {
 		assert.False(t, cachedUser.IsExpired())
 		assert.False(t, cachedUser.IsExpired())
 	}
 	}
 	// cache is invalidated after a user modification
 	// cache is invalidated after a user modification
-	err = dataprovider.UpdateUser(user)
+	err = dataprovider.UpdateUser(&user)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	_, ok = dataprovider.GetCachedWebDAVUser(username)
 	_, ok = dataprovider.GetCachedWebDAVUser(username)
 	assert.False(t, ok)
 	assert.False(t, ok)
@@ -980,7 +980,7 @@ func TestBasicUsersCache(t *testing.T) {
 	_, ok = dataprovider.GetCachedWebDAVUser(username)
 	_, ok = dataprovider.GetCachedWebDAVUser(username)
 	assert.True(t, ok)
 	assert.True(t, ok)
 	// cache is invalidated after user deletion
 	// cache is invalidated after user deletion
-	err = dataprovider.DeleteUser(user)
+	err = dataprovider.DeleteUser(&user)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	_, ok = dataprovider.GetCachedWebDAVUser(username)
 	_, ok = dataprovider.GetCachedWebDAVUser(username)
 	assert.False(t, ok)
 	assert.False(t, ok)
@@ -1001,25 +1001,25 @@ func TestUsersCacheSizeAndExpiration(t *testing.T) {
 	u.Password = password + "1"
 	u.Password = password + "1"
 	u.Permissions = make(map[string][]string)
 	u.Permissions = make(map[string][]string)
 	u.Permissions["/"] = []string{dataprovider.PermAny}
 	u.Permissions["/"] = []string{dataprovider.PermAny}
-	err := dataprovider.AddUser(u)
+	err := dataprovider.AddUser(&u)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	user1, err := dataprovider.UserExists(u.Username)
 	user1, err := dataprovider.UserExists(u.Username)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	u.Username = username + "2"
 	u.Username = username + "2"
 	u.Password = password + "2"
 	u.Password = password + "2"
-	err = dataprovider.AddUser(u)
+	err = dataprovider.AddUser(&u)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	user2, err := dataprovider.UserExists(u.Username)
 	user2, err := dataprovider.UserExists(u.Username)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	u.Username = username + "3"
 	u.Username = username + "3"
 	u.Password = password + "3"
 	u.Password = password + "3"
-	err = dataprovider.AddUser(u)
+	err = dataprovider.AddUser(&u)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	user3, err := dataprovider.UserExists(u.Username)
 	user3, err := dataprovider.UserExists(u.Username)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	u.Username = username + "4"
 	u.Username = username + "4"
 	u.Password = password + "4"
 	u.Password = password + "4"
-	err = dataprovider.AddUser(u)
+	err = dataprovider.AddUser(&u)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	user4, err := dataprovider.UserExists(u.Username)
 	user4, err := dataprovider.UserExists(u.Username)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
@@ -1137,7 +1137,7 @@ func TestUsersCacheSizeAndExpiration(t *testing.T) {
 	assert.True(t, ok)
 	assert.True(t, ok)
 
 
 	// now remove user1 after an update
 	// now remove user1 after an update
-	err = dataprovider.UpdateUser(user1)
+	err = dataprovider.UpdateUser(&user1)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	_, ok = dataprovider.GetCachedWebDAVUser(user1.Username)
 	_, ok = dataprovider.GetCachedWebDAVUser(user1.Username)
 	assert.False(t, ok)
 	assert.False(t, ok)
@@ -1164,13 +1164,13 @@ func TestUsersCacheSizeAndExpiration(t *testing.T) {
 	_, ok = dataprovider.GetCachedWebDAVUser(user4.Username)
 	_, ok = dataprovider.GetCachedWebDAVUser(user4.Username)
 	assert.True(t, ok)
 	assert.True(t, ok)
 
 
-	err = dataprovider.DeleteUser(user1)
+	err = dataprovider.DeleteUser(&user1)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
-	err = dataprovider.DeleteUser(user2)
+	err = dataprovider.DeleteUser(&user2)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
-	err = dataprovider.DeleteUser(user3)
+	err = dataprovider.DeleteUser(&user3)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
-	err = dataprovider.DeleteUser(user4)
+	err = dataprovider.DeleteUser(&user4)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
 	err = os.RemoveAll(u.GetHomeDir())
 	err = os.RemoveAll(u.GetHomeDir())

+ 1 - 1
webdavd/server.go

@@ -207,7 +207,7 @@ func (s *webDavServer) authenticate(r *http.Request, ip string) (dataprovider.Us
 		if cachedUser.IsExpired() {
 		if cachedUser.IsExpired() {
 			dataprovider.RemoveCachedWebDAVUser(username)
 			dataprovider.RemoveCachedWebDAVUser(username)
 		} else {
 		} else {
-			if len(password) > 0 && cachedUser.Password == password {
+			if password != "" && cachedUser.Password == password {
 				return cachedUser.User, true, cachedUser.LockSystem, nil
 				return cachedUser.User, true, cachedUser.LockSystem, nil
 			}
 			}
 			updateLoginMetrics(username, ip, dataprovider.ErrInvalidCredentials)
 			updateLoginMetrics(username, ip, dataprovider.ErrInvalidCredentials)