| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- 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
- }
|