diff options
Diffstat (limited to 'weed/sftpd/sftp_permissions.go')
| -rw-r--r-- | weed/sftpd/sftp_permissions.go | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/weed/sftpd/sftp_permissions.go b/weed/sftpd/sftp_permissions.go new file mode 100644 index 000000000..1bddd91f2 --- /dev/null +++ b/weed/sftpd/sftp_permissions.go @@ -0,0 +1,238 @@ +package sftpd + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/seaweedfs/seaweedfs/weed/glog" + "github.com/seaweedfs/seaweedfs/weed/sftpd/user" +) + +// Permission constants for clarity and consistency +const ( + PermRead = "read" + PermWrite = "write" + PermExecute = "execute" + PermList = "list" + PermDelete = "delete" + PermMkdir = "mkdir" + PermTraverse = "traverse" + PermAll = "*" + PermAdmin = "admin" + PermReadWrite = "readwrite" +) + +// Entry represents a filesystem entry with attributes +type Entry struct { + IsDirectory bool + Attributes *EntryAttributes + IsSymlink bool // Added to track symlinks + Target string // For symlinks, stores the target path +} + +// EntryAttributes contains file attributes +type EntryAttributes struct { + Uid uint32 + Gid uint32 + FileMode uint32 + SymlinkTarget string +} + +// PermissionError represents a permission-related erro + +// CheckFilePermission verifies if a user has the required permission on a path +// It first checks if the path is in the user's home directory with explicit permissions. +// If not, it falls back to Unix permission checking followed by explicit permission checking. +// Parameters: +// - user: The user requesting access +// - path: The filesystem path to check +// - perm: The permission being requested (read, write, execute, etc.) +// +// Returns: +// - nil if permission is granted, error otherwise +func (fs *SftpServer) CheckFilePermission(path string, perm string) error { + + if fs.user == nil { + glog.V(0).Infof("permission denied. No user associated with the SftpServer.") + return os.ErrPermission + } + + // Special case for "create" or "write" permissions on non-existent paths + // Check parent directory permissions instead + entry, err := fs.getEntry(path) + if err != nil { + // If the path doesn't exist and we're checking for create/write/mkdir permission, + // check permissions on the parent directory instead + if err == os.ErrNotExist { + parentPath := filepath.Dir(path) + // Check if user can write to the parent directory + return fs.CheckFilePermission(parentPath, perm) + } + return fmt.Errorf("failed to get entry for path %s: %w", path, err) + } + + // Rest of the function remains the same... + // Handle symlinks by resolving them + if entry.Attributes.GetSymlinkTarget() != "" { + // Get the actual entry for the resolved path + entry, err = fs.getEntry(entry.Attributes.GetSymlinkTarget()) + if err != nil { + return fmt.Errorf("failed to get entry for resolved path %s: %w", entry.Attributes.SymlinkTarget, err) + } + } + + // Special case: root user always has permission + if fs.user.Username == "root" || fs.user.Uid == 0 { + return nil + } + + // Check if path is within user's home directory and has explicit permissions + if isPathInHomeDirectory(fs.user, path) { + // Check if user has explicit permissions for this path + if HasExplicitPermission(fs.user, path, perm, entry.IsDirectory) { + return nil + } + } else { + // For paths outside home directory or without explicit home permissions, + // check UNIX-style perms first + isOwner := fs.user.Uid == entry.Attributes.Uid + isGroup := fs.user.Gid == entry.Attributes.Gid + mode := os.FileMode(entry.Attributes.FileMode) + + if HasUnixPermission(isOwner, isGroup, mode, entry.IsDirectory, perm) { + return nil + } + + // Then check explicit ACLs + if HasExplicitPermission(fs.user, path, perm, entry.IsDirectory) { + return nil + } + } + glog.V(0).Infof("permission denied for user %s on path %s for permission %s", fs.user.Username, path, perm) + return os.ErrPermission +} + +// isPathInHomeDirectory checks if a path is in the user's home directory +func isPathInHomeDirectory(user *user.User, path string) bool { + return strings.HasPrefix(path, user.HomeDir) +} + +// HasUnixPermission checks if the user has the required Unix permission +// Uses bit masks for clarity and maintainability +func HasUnixPermission(isOwner, isGroup bool, fileMode os.FileMode, isDirectory bool, requiredPerm string) bool { + const ( + ownerRead = 0400 + ownerWrite = 0200 + ownerExec = 0100 + groupRead = 0040 + groupWrite = 0020 + groupExec = 0010 + otherRead = 0004 + otherWrite = 0002 + otherExec = 0001 + ) + + // Check read permission + hasRead := (isOwner && (fileMode&ownerRead != 0)) || + (isGroup && (fileMode&groupRead != 0)) || + (fileMode&otherRead != 0) + + // Check write permission + hasWrite := (isOwner && (fileMode&ownerWrite != 0)) || + (isGroup && (fileMode&groupWrite != 0)) || + (fileMode&otherWrite != 0) + + // Check execute permission + hasExec := (isOwner && (fileMode&ownerExec != 0)) || + (isGroup && (fileMode&groupExec != 0)) || + (fileMode&otherExec != 0) + + switch requiredPerm { + case PermRead: + return hasRead + case PermWrite: + return hasWrite + case PermExecute: + return hasExec + case PermList: + if isDirectory { + return hasRead && hasExec + } + return hasRead + case PermDelete: + return hasWrite + case PermMkdir: + return isDirectory && hasWrite + case PermTraverse: + return isDirectory && hasExec + case PermReadWrite: + return hasRead && hasWrite + case PermAll, PermAdmin: + return hasRead && hasWrite && hasExec + } + return false +} + +// HasExplicitPermission checks if the user has explicit permission from user config +func HasExplicitPermission(user *user.User, filepath, requiredPerm string, isDirectory bool) bool { + // Find the most specific permission that applies to this path + var bestMatch string + var perms []string + + for p, userPerms := range user.Permissions { + // Check if the path is either the permission path exactly or is under that path + if strings.HasPrefix(filepath, p) && len(p) > len(bestMatch) { + bestMatch = p + perms = userPerms + } + } + + // No matching permissions found + if bestMatch == "" { + return false + } + + // Check if user has admin role + if containsString(perms, PermAdmin) { + return true + } + + // If user has list permission and is requesting traverse/execute permission, grant it + if isDirectory && requiredPerm == PermExecute && containsString(perms, PermList) { + return true + } + + // Check if the required permission is in the list + for _, perm := range perms { + if perm == requiredPerm || perm == PermAll { + return true + } + + // Handle combined permissions + if perm == PermReadWrite && (requiredPerm == PermRead || requiredPerm == PermWrite) { + return true + } + + // Directory-specific permissions + if isDirectory && perm == PermList && requiredPerm == PermRead { + return true + } + if isDirectory && perm == PermTraverse && requiredPerm == PermExecute { + return true + } + } + + return false +} + +// Helper function to check if a string is in a slice +func containsString(slice []string, s string) bool { + for _, item := range slice { + if item == s { + return true + } + } + return false +} |
