1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
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
}
|