drakkan / sftpgo

@@ -66,7 +66,7 @@
Loading
66 66
		return nil, c.GetPermissionDeniedError()
67 67
	}
68 68
69 -
	fi, err := c.DoStat(name, mode)
69 +
	fi, err := c.DoStat(name, mode, true)
70 70
	if err != nil {
71 71
		return nil, err
72 72
	}
@@ -89,9 +89,9 @@
Loading
89 89
		return nil, c.GetPermissionDeniedError()
90 90
	}
91 91
92 -
	if !c.User.IsFileAllowed(name) {
92 +
	if ok, policy := c.User.IsFileAllowed(name); !ok {
93 93
		c.Log(logger.LevelWarn, "reading file %#v is not allowed", name)
94 -
		return nil, c.GetPermissionDeniedError()
94 +
		return nil, c.GetErrorForDeniedFile(policy)
95 95
	}
96 96
97 97
	fs, p, err := c.GetFsAndResolvedPath(name)
@@ -120,7 +120,7 @@
Loading
120 120
func (c *Connection) getFileWriter(name string) (io.WriteCloser, error) {
121 121
	c.UpdateLastActivity()
122 122
123 -
	if !c.User.IsFileAllowed(name) {
123 +
	if ok, _ := c.User.IsFileAllowed(name); !ok {
124 124
		c.Log(logger.LevelWarn, "writing file %#v is not allowed", name)
125 125
		return nil, c.GetPermissionDeniedError()
126 126
	}

@@ -62,7 +62,7 @@
Loading
62 62
	c.UpdateLastActivity()
63 63
64 64
	name = util.CleanPath(name)
65 -
	return c.CreateDir(name)
65 +
	return c.CreateDir(name, true)
66 66
}
67 67
68 68
// Rename renames a file or a directory
@@ -85,7 +85,7 @@
Loading
85 85
		return nil, c.GetPermissionDeniedError()
86 86
	}
87 87
88 -
	fi, err := c.DoStat(name, 0)
88 +
	fi, err := c.DoStat(name, 0, true)
89 89
	if err != nil {
90 90
		return nil, err
91 91
	}
@@ -156,7 +156,7 @@
Loading
156 156
}
157 157
158 158
func (c *Connection) putFile(fs vfs.Fs, fsPath, virtualPath string) (webdav.File, error) {
159 -
	if !c.User.IsFileAllowed(virtualPath) {
159 +
	if ok, _ := c.User.IsFileAllowed(virtualPath); !ok {
160 160
		c.Log(logger.LevelWarn, "writing file %#v is not allowed", virtualPath)
161 161
		return nil, c.GetPermissionDeniedError()
162 162
	}

@@ -161,7 +161,7 @@
Loading
161 161
			return c.sendErrorResponse(err)
162 162
		}
163 163
	} else if fi.Mode().IsRegular() {
164 -
		if !c.connection.User.IsFileAllowed(sshDestPath) {
164 +
		if ok, _ := c.connection.User.IsFileAllowed(sshDestPath); !ok {
165 165
			err := errors.New("unsupported copy destination: this file is not allowed")
166 166
			return c.sendErrorResponse(err)
167 167
		}
@@ -282,9 +282,9 @@
Loading
282 282
		response = fmt.Sprintf("%x  -\n", h.Sum(nil))
283 283
	} else {
284 284
		sshPath := c.getDestPath()
285 -
		if !c.connection.User.IsFileAllowed(sshPath) {
285 +
		if ok, policy := c.connection.User.IsFileAllowed(sshPath); !ok {
286 286
			c.connection.Log(logger.LevelInfo, "hash not allowed for file %#v", sshPath)
287 -
			return c.sendErrorResponse(c.connection.GetPermissionDeniedError())
287 +
			return c.sendErrorResponse(c.connection.GetErrorForDeniedFile(policy))
288 288
		}
289 289
		fs, fsPath, err := c.connection.GetFsAndResolvedPath(sshPath)
290 290
		if err != nil {

@@ -63,9 +63,9 @@
Loading
63 63
		return nil, sftp.ErrSSHFxPermissionDenied
64 64
	}
65 65
66 -
	if !c.User.IsFileAllowed(request.Filepath) {
66 +
	if ok, policy := c.User.IsFileAllowed(request.Filepath); !ok {
67 67
		c.Log(logger.LevelWarn, "reading file %#v is not allowed", request.Filepath)
68 -
		return nil, sftp.ErrSSHFxPermissionDenied
68 +
		return nil, c.GetErrorForDeniedFile(policy)
69 69
	}
70 70
71 71
	fs, p, err := c.GetFsAndResolvedPath(request.Filepath)
@@ -104,9 +104,9 @@
Loading
104 104
func (c *Connection) handleFilewrite(request *sftp.Request) (sftp.WriterAtReaderAt, error) {
105 105
	c.UpdateLastActivity()
106 106
107 -
	if !c.User.IsFileAllowed(request.Filepath) {
107 +
	if ok, _ := c.User.IsFileAllowed(request.Filepath); !ok {
108 108
		c.Log(logger.LevelWarn, "writing file %#v is not allowed", request.Filepath)
109 -
		return nil, sftp.ErrSSHFxPermissionDenied
109 +
		return nil, c.GetPermissionDeniedError()
110 110
	}
111 111
112 112
	fs, p, err := c.GetFsAndResolvedPath(request.Filepath)
@@ -175,7 +175,7 @@
Loading
175 175
	case "Rmdir":
176 176
		return c.RemoveDir(request.Filepath)
177 177
	case "Mkdir":
178 -
		err := c.CreateDir(request.Filepath)
178 +
		err := c.CreateDir(request.Filepath, true)
179 179
		if err != nil {
180 180
			return err
181 181
		}
@@ -214,7 +214,7 @@
Loading
214 214
			return nil, sftp.ErrSSHFxPermissionDenied
215 215
		}
216 216
217 -
		s, err := c.DoStat(request.Filepath, 0)
217 +
		s, err := c.DoStat(request.Filepath, 0, true)
218 218
		if err != nil {
219 219
			return nil, err
220 220
		}
@@ -255,7 +255,7 @@
Loading
255 255
		return nil, sftp.ErrSSHFxPermissionDenied
256 256
	}
257 257
258 -
	s, err := c.DoStat(request.Filepath, 1)
258 +
	s, err := c.DoStat(request.Filepath, 1, true)
259 259
	if err != nil {
260 260
		return nil, err
261 261
	}

@@ -797,11 +797,20 @@
Loading
797 797
	return result, nil
798 798
}
799 799
800 +
func getPatterDenyPolicyFromString(policy string) int {
801 +
	denyPolicy := sdk.DenyPolicyDefault
802 +
	if policy == "1" {
803 +
		denyPolicy = sdk.DenyPolicyHide
804 +
	}
805 +
	return denyPolicy
806 +
}
807 +
800 808
func getFilePatternsFromPostField(r *http.Request) []sdk.PatternsFilter {
801 809
	var result []sdk.PatternsFilter
802 810
803 811
	allowedPatterns := make(map[string][]string)
804 812
	deniedPatterns := make(map[string][]string)
813 +
	patternPolicies := make(map[string]string)
805 814
806 815
	for k := range r.Form {
807 816
		if strings.HasPrefix(k, "pattern_path") {
@@ -810,12 +819,16 @@
Loading
810 819
			filters := strings.TrimSpace(r.Form.Get(fmt.Sprintf("patterns%v", idx)))
811 820
			filters = strings.ReplaceAll(filters, " ", "")
812 821
			patternType := r.Form.Get(fmt.Sprintf("pattern_type%v", idx))
822 +
			patternPolicy := r.Form.Get(fmt.Sprintf("pattern_policy%v", idx))
813 823
			if p != "" && filters != "" {
814 824
				if patternType == "allowed" {
815 825
					allowedPatterns[p] = append(allowedPatterns[p], strings.Split(filters, ",")...)
816 826
				} else {
817 827
					deniedPatterns[p] = append(deniedPatterns[p], strings.Split(filters, ",")...)
818 828
				}
829 +
				if patternPolicy != "" && patternPolicy != "0" {
830 +
					patternPolicies[p] = patternPolicy
831 +
				}
819 832
			}
820 833
		}
821 834
	}
@@ -823,11 +836,12 @@
Loading
823 836
	for dirAllowed, allowPatterns := range allowedPatterns {
824 837
		filter := sdk.PatternsFilter{
825 838
			Path:            dirAllowed,
826 -
			AllowedPatterns: util.RemoveDuplicates(allowPatterns),
839 +
			AllowedPatterns: allowPatterns,
840 +
			DenyPolicy:      getPatterDenyPolicyFromString(patternPolicies[dirAllowed]),
827 841
		}
828 842
		for dirDenied, denPatterns := range deniedPatterns {
829 843
			if dirAllowed == dirDenied {
830 -
				filter.DeniedPatterns = util.RemoveDuplicates(denPatterns)
844 +
				filter.DeniedPatterns = denPatterns
831 845
				break
832 846
			}
833 847
		}
@@ -845,6 +859,7 @@
Loading
845 859
			result = append(result, sdk.PatternsFilter{
846 860
				Path:           dirDenied,
847 861
				DeniedPatterns: denPatterns,
862 +
				DenyPolicy:     getPatterDenyPolicyFromString(patternPolicies[dirDenied]),
848 863
			})
849 864
		}
850 865
	}

@@ -148,7 +148,7 @@
Loading
148 148
		return common.ErrPermissionDenied
149 149
	}
150 150
151 -
	info, err := c.connection.DoStat(dirPath, 1)
151 +
	info, err := c.connection.DoStat(dirPath, 1, true)
152 152
	if err == nil && info.IsDir() {
153 153
		return nil
154 154
	}
@@ -276,9 +276,9 @@
Loading
276 276
		return err
277 277
	}
278 278
279 -
	if !c.connection.User.IsFileAllowed(uploadFilePath) {
279 +
	if ok, _ := c.connection.User.IsFileAllowed(uploadFilePath); !ok {
280 280
		c.connection.Log(logger.LevelWarn, "writing file %#v is not allowed", uploadFilePath)
281 -
		c.sendErrorMessage(fs, common.ErrPermissionDenied)
281 +
		c.sendErrorMessage(fs, c.connection.GetPermissionDeniedError())
282 282
		return common.ErrPermissionDenied
283 283
	}
284 284
@@ -372,7 +372,7 @@
Loading
372 372
			c.sendErrorMessage(fs, err)
373 373
			return err
374 374
		}
375 -
		files = c.connection.User.AddVirtualDirs(files, fs.GetRelativePath(dirPath))
375 +
		files = c.connection.User.FilterListDir(files, fs.GetRelativePath(dirPath))
376 376
		var dirs []string
377 377
		for _, file := range files {
378 378
			filePath := fs.GetRelativePath(fs.Join(dirPath, file.Name()))
@@ -509,9 +509,9 @@
Loading
509 509
		return common.ErrPermissionDenied
510 510
	}
511 511
512 -
	if !c.connection.User.IsFileAllowed(filePath) {
512 +
	if ok, policy := c.connection.User.IsFileAllowed(filePath); !ok {
513 513
		c.connection.Log(logger.LevelWarn, "reading file %#v is not allowed", filePath)
514 -
		c.sendErrorMessage(fs, common.ErrPermissionDenied)
514 +
		c.sendErrorMessage(fs, c.connection.GetErrorForDeniedFile(policy))
515 515
		return common.ErrPermissionDenied
516 516
	}
517 517

@@ -143,9 +143,9 @@
Loading
143 143
			return 0, f.Connection.GetPermissionDeniedError()
144 144
		}
145 145
146 -
		if !f.Connection.User.IsFileAllowed(f.GetVirtualPath()) {
146 +
		if ok, policy := f.Connection.User.IsFileAllowed(f.GetVirtualPath()); !ok {
147 147
			f.Connection.Log(logger.LevelWarn, "reading file %#v is not allowed", f.GetVirtualPath())
148 -
			return 0, f.Connection.GetPermissionDeniedError()
148 +
			return 0, f.Connection.GetErrorForDeniedFile(policy)
149 149
		}
150 150
		err := common.ExecutePreAction(f.Connection, common.OperationPreDownload, f.GetFsPath(), f.GetVirtualPath(), 0, 0)
151 151
		if err != nil {

@@ -242,7 +242,7 @@
Loading
242 242
		c.Log(logger.LevelDebug, "error listing directory: %+v", err)
243 243
		return nil, c.GetFsError(fs, err)
244 244
	}
245 -
	return c.User.AddVirtualDirs(files, virtualPath), nil
245 +
	return c.User.FilterListDir(files, virtualPath), nil
246 246
}
247 247
248 248
// CheckParentDirs tries to create the specified directory and any missing parent dirs
@@ -254,7 +254,7 @@
Loading
254 254
	if fs.HasVirtualFolders() {
255 255
		return nil
256 256
	}
257 -
	if _, err := c.DoStat(virtualPath, 0); !c.IsNotExistError(err) {
257 +
	if _, err := c.DoStat(virtualPath, 0, false); !c.IsNotExistError(err) {
258 258
		return err
259 259
	}
260 260
	dirs := util.GetDirsForVirtualPath(virtualPath)
@@ -275,10 +275,15 @@
Loading
275 275
}
276 276
277 277
// CreateDir creates a new directory at the specified fsPath
278 -
func (c *BaseConnection) CreateDir(virtualPath string) error {
278 +
func (c *BaseConnection) CreateDir(virtualPath string, checkFilePatterns bool) error {
279 279
	if !c.User.HasPerm(dataprovider.PermCreateDirs, path.Dir(virtualPath)) {
280 280
		return c.GetPermissionDeniedError()
281 281
	}
282 +
	if checkFilePatterns {
283 +
		if ok, _ := c.User.IsFileAllowed(virtualPath); !ok {
284 +
			return c.GetPermissionDeniedError()
285 +
		}
286 +
	}
282 287
	if c.User.IsVirtualFolder(virtualPath) {
283 288
		c.Log(logger.LevelWarn, "mkdir not allowed %#v is a virtual folder", virtualPath)
284 289
		return c.GetPermissionDeniedError()
@@ -304,9 +309,9 @@
Loading
304 309
	if !c.User.HasAnyPerm([]string{dataprovider.PermDeleteFiles, dataprovider.PermDelete}, path.Dir(virtualPath)) {
305 310
		return c.GetPermissionDeniedError()
306 311
	}
307 -
	if !c.User.IsFileAllowed(virtualPath) {
312 +
	if ok, policy := c.User.IsFileAllowed(virtualPath); !ok {
308 313
		c.Log(logger.LevelDebug, "removing file %#v is not allowed", virtualPath)
309 -
		return c.GetPermissionDeniedError()
314 +
		return c.GetErrorForDeniedFile(policy)
310 315
	}
311 316
	return nil
312 317
}
@@ -368,6 +373,10 @@
Loading
368 373
	if !c.User.HasAnyPerm([]string{dataprovider.PermDeleteDirs, dataprovider.PermDelete}, path.Dir(virtualPath)) {
369 374
		return c.GetPermissionDeniedError()
370 375
	}
376 +
	if ok, policy := c.User.IsFileAllowed(virtualPath); !ok {
377 +
		c.Log(logger.LevelDebug, "removing directory %#v is not allowed", virtualPath)
378 +
		return c.GetErrorForDeniedFile(policy)
379 +
	}
371 380
	return nil
372 381
}
373 382
@@ -488,16 +497,25 @@
Loading
488 497
		return c.GetFsError(fs, err)
489 498
	}
490 499
	if fs.GetRelativePath(fsSourcePath) == "/" {
491 -
		c.Log(logger.LevelWarn, "symlinking root dir is not allowed")
500 +
		c.Log(logger.LevelError, "symlinking root dir is not allowed")
492 501
		return c.GetPermissionDeniedError()
493 502
	}
494 503
	if fs.GetRelativePath(fsTargetPath) == "/" {
495 -
		c.Log(logger.LevelWarn, "symlinking to root dir is not allowed")
504 +
		c.Log(logger.LevelError, "symlinking to root dir is not allowed")
496 505
		return c.GetPermissionDeniedError()
497 506
	}
498 507
	if !c.User.HasPerm(dataprovider.PermCreateSymlinks, path.Dir(virtualTargetPath)) {
499 508
		return c.GetPermissionDeniedError()
500 509
	}
510 +
	ok, policy := c.User.IsFileAllowed(virtualSourcePath)
511 +
	if !ok && policy == sdk.DenyPolicyHide {
512 +
		c.Log(logger.LevelError, "symlink source path %#v is not allowed", virtualSourcePath)
513 +
		return c.GetNotExistError()
514 +
	}
515 +
	if ok, _ = c.User.IsFileAllowed(virtualTargetPath); !ok {
516 +
		c.Log(logger.LevelError, "symlink target path %#v is not allowed", virtualTargetPath)
517 +
		return c.GetPermissionDeniedError()
518 +
	}
501 519
	if err := fs.Symlink(fsSourcePath, fsTargetPath); err != nil {
502 520
		c.Log(logger.LevelError, "failed to create symlink %#v -> %#v: %+v", fsSourcePath, fsTargetPath, err)
503 521
		return c.GetFsError(fs, err)
@@ -518,13 +536,19 @@
Loading
518 536
}
519 537
520 538
// DoStat execute a Stat if mode = 0, Lstat if mode = 1
521 -
func (c *BaseConnection) DoStat(virtualPath string, mode int) (os.FileInfo, error) {
539 +
func (c *BaseConnection) DoStat(virtualPath string, mode int, checkFilePatterns bool) (os.FileInfo, error) {
522 540
	// for some vfs we don't create intermediary folders so we cannot simply check
523 541
	// if virtualPath is a virtual folder
524 542
	vfolders := c.User.GetVirtualFoldersInPath(path.Dir(virtualPath))
525 543
	if _, ok := vfolders[virtualPath]; ok {
526 544
		return vfs.NewFileInfo(virtualPath, true, 0, time.Now(), false), nil
527 545
	}
546 +
	if checkFilePatterns {
547 +
		ok, policy := c.User.IsFileAllowed(virtualPath)
548 +
		if !ok && policy == sdk.DenyPolicyHide {
549 +
			return nil, c.GetNotExistError()
550 +
		}
551 +
	}
528 552
529 553
	var info os.FileInfo
530 554
@@ -549,9 +573,9 @@
Loading
549 573
}
550 574
551 575
func (c *BaseConnection) createDirIfMissing(name string) error {
552 -
	_, err := c.DoStat(name, 0)
576 +
	_, err := c.DoStat(name, 0, false)
553 577
	if c.IsNotExistError(err) {
554 -
		return c.CreateDir(name)
578 +
		return c.CreateDir(name, false)
555 579
	}
556 580
	return err
557 581
}
@@ -625,6 +649,9 @@
Loading
625 649
626 650
// SetStat set StatAttributes for the specified fsPath
627 651
func (c *BaseConnection) SetStat(virtualPath string, attributes *StatAttributes) error {
652 +
	if ok, policy := c.User.IsFileAllowed(virtualPath); !ok {
653 +
		return c.GetErrorForDeniedFile(policy)
654 +
	}
628 655
	fs, fsPath, err := c.GetFsAndResolvedPath(virtualPath)
629 656
	if err != nil {
630 657
		return err
@@ -799,12 +826,12 @@
Loading
799 826
		c.Log(logger.LevelWarn, "renaming a virtual folder is not allowed")
800 827
		return false
801 828
	}
802 -
	if !c.User.IsFileAllowed(virtualSourcePath) || !c.User.IsFileAllowed(virtualTargetPath) {
803 -
		if fi != nil && fi.Mode().IsRegular() {
804 -
			c.Log(logger.LevelDebug, "renaming file is not allowed, source: %#v target: %#v",
805 -
				virtualSourcePath, virtualTargetPath)
806 -
			return false
807 -
		}
829 +
	isSrcAllowed, _ := c.User.IsFileAllowed(virtualSourcePath)
830 +
	isDstAllowed, _ := c.User.IsFileAllowed(virtualTargetPath)
831 +
	if !isSrcAllowed || !isDstAllowed {
832 +
		c.Log(logger.LevelDebug, "renaming source: %#v to target: %#v not allowed", virtualSourcePath,
833 +
			virtualTargetPath)
834 +
		return false
808 835
	}
809 836
	return c.hasRenamePerms(virtualSourcePath, virtualTargetPath, fi)
810 837
}
@@ -1141,6 +1168,16 @@
Loading
1141 1168
	}
1142 1169
}
1143 1170
1171 +
// GetErrorForDeniedFile return permission denied or not exist error based on the specified policy
1172 +
func (c *BaseConnection) GetErrorForDeniedFile(policy int) error {
1173 +
	switch policy {
1174 +
	case sdk.DenyPolicyHide:
1175 +
		return c.GetNotExistError()
1176 +
	default:
1177 +
		return c.GetPermissionDeniedError()
1178 +
	}
1179 +
}
1180 +
1144 1181
// GetPermissionDeniedError returns an appropriate permission denied error for the connection protocol
1145 1182
func (c *BaseConnection) GetPermissionDeniedError() error {
1146 1183
	switch c.protocol {

@@ -83,7 +83,7 @@
Loading
83 83
func (c *Connection) Mkdir(name string, perm os.FileMode) error {
84 84
	c.UpdateLastActivity()
85 85
86 -
	return c.CreateDir(name)
86 +
	return c.CreateDir(name, true)
87 87
}
88 88
89 89
// MkdirAll is not implemented, we don't need it
@@ -145,7 +145,7 @@
Loading
145 145
		return nil, c.GetPermissionDeniedError()
146 146
	}
147 147
148 -
	fi, err := c.DoStat(name, 0)
148 +
	fi, err := c.DoStat(name, 0, true)
149 149
	if err != nil {
150 150
		return nil, err
151 151
	}
@@ -319,9 +319,9 @@
Loading
319 319
		return nil, c.GetPermissionDeniedError()
320 320
	}
321 321
322 -
	if !c.User.IsFileAllowed(ftpPath) {
322 +
	if ok, policy := c.User.IsFileAllowed(ftpPath); !ok {
323 323
		c.Log(logger.LevelWarn, "reading file %#v is not allowed", ftpPath)
324 -
		return nil, c.GetPermissionDeniedError()
324 +
		return nil, c.GetErrorForDeniedFile(policy)
325 325
	}
326 326
327 327
	if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreDownload, fsPath, ftpPath, 0, 0); err != nil {
@@ -344,7 +344,7 @@
Loading
344 344
}
345 345
346 346
func (c *Connection) uploadFile(fs vfs.Fs, fsPath, ftpPath string, flags int) (ftpserver.FileTransfer, error) {
347 -
	if !c.User.IsFileAllowed(ftpPath) {
347 +
	if ok, _ := c.User.IsFileAllowed(ftpPath); !ok {
348 348
		c.Log(logger.LevelWarn, "writing file %#v is not allowed", ftpPath)
349 349
		return nil, ftpserver.ErrFileNameNotAllowed
350 350
	}

@@ -92,7 +92,7 @@
Loading
92 92
			return
93 93
		}
94 94
	}
95 -
	err = connection.CreateDir(name)
95 +
	err = connection.CreateDir(name, true)
96 96
	if err != nil {
97 97
		sendAPIResponse(w, r, err, fmt.Sprintf("Unable to create directory %#v", name), getMappedStatusCode(err))
98 98
		return
Files Coverage
common 99.90%
config 100.00%
ftpd 100.00%
httpd 99.95%
mfa 100.00%
sftpd 98.52%
telemetry 100.00%
webdavd 100.00%
Project Totals (62 files) 99.71%
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading