sftp realpath: resolve symlinks

Fixes #890

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2022-07-17 16:02:45 +02:00
parent e0ce2e2e8a
commit 55b47cf741
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
14 changed files with 244 additions and 100 deletions

View file

@ -14,8 +14,11 @@ Several storage backends are supported: local filesystem, encrypted local filesy
## Sponsors ## Sponsors
If you find SFTPGo useful please consider supporting this Open Source project. If you find SFTPGo useful please consider supporting this Open Source project.
Maintaing and evolving SFTPGo is a lot of work - easily the equivalent of a full time job - for me.
Maintaining and evolving SFTPGo is a lot of work - easily the equivalent of a full time job - for me.
I'd like to make SFTPGo into a sustainable long term project and would not like to introduce a dual licensing option and limit some features to the proprietary version only. I'd like to make SFTPGo into a sustainable long term project and would not like to introduce a dual licensing option and limit some features to the proprietary version only.
If you use SFTPGo, it is in your best interest to ensure that the project you rely on stays healthy and well maintained. If you use SFTPGo, it is in your best interest to ensure that the project you rely on stays healthy and well maintained.
This can only happen with your donations and [sponsorships](https://github.com/sponsors/drakkan) :heart: This can only happen with your donations and [sponsorships](https://github.com/sponsors/drakkan) :heart:

View file

@ -482,7 +482,7 @@ func (c *BaseConnection) Rename(virtualSourcePath, virtualTargetPath string) err
initialSize := int64(-1) initialSize := int64(-1)
if dstInfo, err := fsDst.Lstat(fsTargetPath); err == nil { if dstInfo, err := fsDst.Lstat(fsTargetPath); err == nil {
if dstInfo.IsDir() { if dstInfo.IsDir() {
c.Log(logger.LevelWarn, "attempted to rename %#v overwriting an existing directory %#v", c.Log(logger.LevelWarn, "attempted to rename %q overwriting an existing directory %q",
fsSourcePath, fsTargetPath) fsSourcePath, fsTargetPath)
return c.GetOpUnsupportedError() return c.GetOpUnsupportedError()
} }

View file

@ -198,7 +198,7 @@ func (u *User) checkDirWithParents(virtualDirPath, connectionID string) error {
func (u *User) checkRootPath(connectionID string) error { func (u *User) checkRootPath(connectionID string) error {
fs, err := u.GetFilesystemForPath("/", connectionID) fs, err := u.GetFilesystemForPath("/", connectionID)
if err != nil { if err != nil {
logger.Warn(logSender, connectionID, "could not create main filesystem for user %#v err: %v", u.Username, err) logger.Warn(logSender, connectionID, "could not create main filesystem for user %q err: %v", u.Username, err)
return fmt.Errorf("could not create root filesystem: %w", err) return fmt.Errorf("could not create root filesystem: %w", err)
} }
fs.CheckRootPath(u.Username, u.GetUID(), u.GetGID()) fs.CheckRootPath(u.Username, u.GetUID(), u.GetGID())

View file

@ -6,9 +6,9 @@ Run the following instructions from the directory that contains the sftpgo binar
## Linux ## Linux
The easiest way to run SFTPGo as a service is to download and install the pre-compiled deb/rpm package or use one of the Arch Linux PKGBUILDs we maintain. The easiest way to run SFTPGo as a service is to use the [deb/rpm repos](./repo.md) or download and install a pre-compiled deb/rpm package or use one of the Arch Linux PKGBUILDs we maintain.
This section describes the procedure to use if you prefer to build SFTPGo yourself or if you want to download and configure a pre-built release as tar. This section describes the manual procedure.
A `systemd` sample [service](../init/sftpgo.service "systemd service") can be found inside the source tree. A `systemd` sample [service](../init/sftpgo.service "systemd service") can be found inside the source tree.
@ -67,6 +67,10 @@ sudo /usr/bin/sftpgo gen man -d /usr/share/man/man1
## macOS ## macOS
The easiest way to run SFTPGo as a service is to install it from the Homebrew [Formula](https://formulae.brew.sh/formula/sftpgo).
This section describes the procedure to use if you prefer to build SFTPGo yourself or if you want to download and configure a pre-built release as tar.
For macOS, a `launchd` sample [service](../init/com.github.drakkan.sftpgo.plist "launchd plist") can be found inside the source tree. The `launchd` plist assumes that SFTPGo has `/usr/local/opt/sftpgo` as base directory. For macOS, a `launchd` sample [service](../init/com.github.drakkan.sftpgo.plist "launchd plist") can be found inside the source tree. The `launchd` plist assumes that SFTPGo has `/usr/local/opt/sftpgo` as base directory.
Here are some basic instructions to run SFTPGo as service, please run the following commands from the directory where you downloaded SFTPGo: Here are some basic instructions to run SFTPGo as service, please run the following commands from the directory where you downloaded SFTPGo:

15
go.mod
View file

@ -35,7 +35,7 @@ require (
github.com/hashicorp/go-plugin v1.4.4 github.com/hashicorp/go-plugin v1.4.4
github.com/hashicorp/go-retryablehttp v0.7.1 github.com/hashicorp/go-retryablehttp v0.7.1
github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126 github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126
github.com/klauspost/compress v1.15.7 github.com/klauspost/compress v1.15.8
github.com/lestrrat-go/jwx v1.2.25 github.com/lestrrat-go/jwx v1.2.25
github.com/lib/pq v1.10.6 github.com/lib/pq v1.10.6
github.com/lithammer/shortuuid/v3 v3.0.7 github.com/lithammer/shortuuid/v3 v3.0.7
@ -53,12 +53,12 @@ require (
github.com/rs/zerolog v1.27.0 github.com/rs/zerolog v1.27.0
github.com/sftpgo/sdk v0.1.2-0.20220611083241-b653555f7f4d github.com/sftpgo/sdk v0.1.2-0.20220611083241-b653555f7f4d
github.com/shirou/gopsutil/v3 v3.22.6 github.com/shirou/gopsutil/v3 v3.22.6
github.com/spf13/afero v1.8.2 github.com/spf13/afero v1.9.0
github.com/spf13/cobra v1.5.0 github.com/spf13/cobra v1.5.0
github.com/spf13/viper v1.12.0 github.com/spf13/viper v1.12.0
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.0
github.com/studio-b12/gowebdav v0.0.0-20220128162035-c7b1ff8a5e62 github.com/studio-b12/gowebdav v0.0.0-20220128162035-c7b1ff8a5e62
github.com/unrolled/secure v1.11.0 github.com/unrolled/secure v1.12.0
github.com/wagslane/go-password-validator v0.3.0 github.com/wagslane/go-password-validator v0.3.0
github.com/xhit/go-simple-mail/v2 v2.11.0 github.com/xhit/go-simple-mail/v2 v2.11.0
github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a
@ -68,7 +68,7 @@ require (
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
golang.org/x/oauth2 v0.0.0-20220630143837-2104d58473e0 golang.org/x/oauth2 v0.0.0-20220630143837-2104d58473e0
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 golang.org/x/time v0.0.0-20220609170525-579cf78fd858
google.golang.org/api v0.87.0 google.golang.org/api v0.87.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0
@ -103,7 +103,7 @@ require (
github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-test/deep v1.0.8 // indirect github.com/go-test/deep v1.0.8 // indirect
github.com/goccy/go-json v0.9.8 // indirect github.com/goccy/go-json v0.9.10 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.8 // indirect github.com/google/go-cmp v0.5.8 // indirect
@ -138,7 +138,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.36.0 // indirect github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect github.com/prometheus/procfs v0.7.3 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect
@ -155,7 +155,7 @@ require (
golang.org/x/tools v0.1.11 // indirect golang.org/x/tools v0.1.11 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220712132514-bdd2acd4974d // indirect google.golang.org/genproto v0.0.0-20220715211116-798f69b842b9 // indirect
google.golang.org/grpc v1.48.0 // indirect google.golang.org/grpc v1.48.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect gopkg.in/ini.v1 v1.66.6 // indirect
@ -166,6 +166,7 @@ require (
replace ( replace (
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
github.com/pkg/sftp => github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20220624105932-71c5dfcef1e8 golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20220624105932-71c5dfcef1e8
golang.org/x/net => github.com/drakkan/net v0.0.0-20220628171916-78de6a2a21b0 golang.org/x/net => github.com/drakkan/net v0.0.0-20220628171916-78de6a2a21b0
) )

34
go.sum
View file

@ -264,6 +264,8 @@ github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHP
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU= github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
github.com/drakkan/net v0.0.0-20220628171916-78de6a2a21b0 h1:VL3ucjcGkcuAEflNvFbNYHOTdAqgYOMRJBBRy7nQ/E4= github.com/drakkan/net v0.0.0-20220628171916-78de6a2a21b0 h1:VL3ucjcGkcuAEflNvFbNYHOTdAqgYOMRJBBRy7nQ/E4=
github.com/drakkan/net v0.0.0-20220628171916-78de6a2a21b0/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= github.com/drakkan/net v0.0.0-20220628171916-78de6a2a21b0/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d h1:kNk/KRhszPJASp7WvjagNW254aKK643Lu8/fr4/ukiM=
github.com/drakkan/sftp v0.0.0-20220716075551-51a5aa4e044d/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg=
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 h1:/ZshrfQzayqRSBDodmp3rhNCHJCff+utvgBuWRbiqu4= github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 h1:/ZshrfQzayqRSBDodmp3rhNCHJCff+utvgBuWRbiqu4=
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001/go.mod h1:kltMsfRMTHSFdMbK66XdS8mfMW77+FZA1fGY1xYMF84= github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001/go.mod h1:kltMsfRMTHSFdMbK66XdS8mfMW77+FZA1fGY1xYMF84=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -332,8 +334,8 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/goccy/go-json v0.7.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.7.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE= github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc=
github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
@ -540,8 +542,8 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.7 h1:7cgTQxJCU/vy+oP/E3B9RGbQTgbiVzIJWIKOLoAsPok= github.com/klauspost/compress v1.15.8 h1:JahtItbkWjf2jzm/T+qgMxkP9EMHsqEUA6vCMGmXvhA=
github.com/klauspost/compress v1.15.7/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.8/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0= github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
@ -654,9 +656,6 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go=
github.com/pkg/sftp v1.13.5/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
@ -681,8 +680,8 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.36.0 h1:78hJTing+BLYLjhXE+Z2BubeEymH5Lr0/Mt8FKkxxYo= github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.36.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
@ -720,8 +719,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.9.0 h1:sFSLUHgxdnN32Qy38hK3QkYBFXZj9DKjVjCUCtD7juY=
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/afero v1.9.0/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
@ -760,8 +759,8 @@ github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjM
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns= github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/unrolled/secure v1.11.0 h1:fjkKhD/MsQnlmz/au+MmFptCFNhvf5iv04ALkdCXRCI= github.com/unrolled/secure v1.12.0 h1:7k3jcgLwfjiKkhQde6VbQ3D4KDLtDBqDd/hs3PPANDY=
github.com/unrolled/secure v1.11.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= github.com/unrolled/secure v1.12.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I= github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I=
github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ= github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ=
github.com/xhit/go-simple-mail/v2 v2.11.0 h1:o/056V50zfkO3Mm5tVdo9rG3ryg4ZmJ2XW5GMinHfVs= github.com/xhit/go-simple-mail/v2 v2.11.0 h1:o/056V50zfkO3Mm5tVdo9rG3ryg4ZmJ2XW5GMinHfVs=
@ -935,7 +934,6 @@ golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -972,8 +970,8 @@ golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -1224,8 +1222,8 @@ google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljW
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220712132514-bdd2acd4974d h1:YbuF5+kdiC516xIP60RvlHeFbY9sRDR73QsAGHpkeVw= google.golang.org/genproto v0.0.0-20220715211116-798f69b842b9 h1:1aEQRgZ4Gks2SRAkLzIPpIszRazwVfjSFe1cKc+e0Jg=
google.golang.org/genproto v0.0.0-20220712132514-bdd2acd4974d/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220715211116-798f69b842b9/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=

View file

@ -268,6 +268,31 @@ func (c *Connection) Lstat(request *sftp.Request) (sftp.ListerAt, error) {
return listerAt([]os.FileInfo{s}), nil return listerAt([]os.FileInfo{s}), nil
} }
// RealPath implements the RealPathFileLister interface
func (c *Connection) RealPath(p string) (string, error) {
if !c.User.HasPerm(dataprovider.PermListItems, path.Dir(p)) {
return "", sftp.ErrSSHFxPermissionDenied
}
if c.User.Filters.StartDirectory == "" {
p = util.CleanPath(p)
} else {
p = util.CleanPathWithBase(c.User.Filters.StartDirectory, p)
}
fs, fsPath, err := c.GetFsAndResolvedPath(p)
if err != nil {
return "", err
}
if realPather, ok := fs.(vfs.FsRealPather); ok {
realPath, err := realPather.RealPath(fsPath)
if err != nil {
return "", c.GetFsError(fs, err)
}
return realPath, nil
}
return p, nil
}
// StatVFS implements StatVFSFileCmder interface // StatVFS implements StatVFSFileCmder interface
func (c *Connection) StatVFS(r *sftp.Request) (*sftp.StatVFS, error) { func (c *Connection) StatVFS(r *sftp.Request) (*sftp.StatVFS, error) {
c.UpdateLastActivity() c.UpdateLastActivity()

View file

@ -613,7 +613,7 @@ func (c *Configuration) handleSftpConnection(channel ssh.Channel, connection *Co
sftp.WithStartDirectory(connection.User.Filters.StartDirectory)) sftp.WithStartDirectory(connection.User.Filters.StartDirectory))
defer server.Close() defer server.Close()
if err := server.Serve(); err == io.EOF { if err := server.Serve(); errors.Is(err, io.EOF) {
exitStatus := sshSubsystemExitStatus{Status: uint32(0)} exitStatus := sshSubsystemExitStatus{Status: uint32(0)}
_, err = channel.SendRequest("exit-status", false, ssh.Marshal(&exitStatus)) _, err = channel.SendRequest("exit-status", false, ssh.Marshal(&exitStatus))
connection.Log(logger.LevelInfo, "connection closed, sent exit status %+v error: %v", exitStatus, err) connection.Log(logger.LevelInfo, "connection closed, sent exit status %+v error: %v", exitStatus, err)

View file

@ -763,60 +763,70 @@ func TestStartDirectory(t *testing.T) {
startDir := "/st@ rt/dir" startDir := "/st@ rt/dir"
u := getTestUser(usePubKey) u := getTestUser(usePubKey)
u.Filters.StartDirectory = startDir u.Filters.StartDirectory = startDir
user, _, err := httpdtest.AddUser(u, http.StatusCreated) localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err) assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey) u = getTestSFTPUser(usePubKey)
if assert.NoError(t, err) { u.Filters.StartDirectory = startDir
defer conn.Close() sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
defer client.Close() assert.NoError(t, err)
for _, user := range []dataprovider.User{localUser, sftpUser} {
conn, client, err := getSftpClient(user, usePubKey)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
currentDir, err := client.Getwd() currentDir, err := client.Getwd()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, startDir, currentDir) assert.Equal(t, startDir, currentDir)
entries, err := client.ReadDir(".") entries, err := client.ReadDir(".")
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, entries, 0) assert.Len(t, entries, 0)
testFilePath := filepath.Join(homeBasePath, testFileName) testFilePath := filepath.Join(homeBasePath, testFileName)
testFileSize := int64(65535) testFileSize := int64(65535)
err = createTestFile(testFilePath, testFileSize) err = createTestFile(testFilePath, testFileSize)
assert.NoError(t, err) assert.NoError(t, err)
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
assert.NoError(t, err) assert.NoError(t, err)
localDownloadPath := filepath.Join(homeBasePath, testDLFileName) localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client) err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client)
assert.NoError(t, err) assert.NoError(t, err)
_, err = client.Stat(testFileName) _, err = client.Stat(testFileName)
assert.NoError(t, err) assert.NoError(t, err)
err = client.Rename(testFileName, testFileName+"_rename") err = client.Rename(testFileName, testFileName+"_rename")
assert.NoError(t, err) assert.NoError(t, err)
entries, err = client.ReadDir(".") entries, err = client.ReadDir(".")
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, entries, 1) assert.Len(t, entries, 1)
currentDir, err = client.RealPath("..") currentDir, err = client.RealPath("..")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, path.Dir(startDir), currentDir) assert.Equal(t, path.Dir(startDir), currentDir)
currentDir, err = client.RealPath("../..") currentDir, err = client.RealPath("../..")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "/", currentDir) assert.Equal(t, "/", currentDir)
currentDir, err = client.RealPath("../../..") currentDir, err = client.RealPath("../../..")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "/", currentDir) assert.Equal(t, "/", currentDir)
err = os.Remove(testFilePath) err = client.Remove(testFileName + "_rename")
assert.NoError(t, err) assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err) err = os.Remove(testFilePath)
assert.NoError(t, err)
err = os.Remove(localDownloadPath)
assert.NoError(t, err)
}
} }
_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir()) _, err = httpdtest.RemoveUser(localUser, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(localUser.GetHomeDir())
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -1181,22 +1191,59 @@ func TestProxyProtocol(t *testing.T) {
func TestRealPath(t *testing.T) { func TestRealPath(t *testing.T) {
usePubKey := true usePubKey := true
u := getTestUser(usePubKey) u := getTestUser(usePubKey)
user, _, err := httpdtest.AddUser(u, http.StatusCreated) localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err) assert.NoError(t, err)
conn, client, err := getSftpClient(user, usePubKey) u = getTestSFTPUser(usePubKey)
if assert.NoError(t, err) { sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
defer conn.Close() assert.NoError(t, err)
defer client.Close() for _, user := range []dataprovider.User{localUser, sftpUser} {
p, err := client.RealPath("../..") conn, client, err := getSftpClient(user, usePubKey)
assert.NoError(t, err) if assert.NoError(t, err) {
assert.Equal(t, "/", p) defer conn.Close()
p, err = client.RealPath("../test") defer client.Close()
assert.NoError(t, err)
assert.Equal(t, "/test", p) p, err := client.RealPath("../..")
assert.NoError(t, err)
assert.Equal(t, "/", p)
p, err = client.RealPath("../test")
assert.NoError(t, err)
assert.Equal(t, "/test", p)
subdir := "testsubdir"
err = client.Mkdir(subdir)
assert.NoError(t, err)
linkName := testFileName + "_link"
err = client.Symlink(testFileName, path.Join(subdir, linkName))
assert.NoError(t, err)
p, err = client.RealPath(path.Join(subdir, linkName))
assert.NoError(t, err)
assert.Equal(t, path.Join("/", testFileName), p)
// an existing path
sftpFile, err := client.OpenFile(testFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC)
if assert.NoError(t, err) {
testData := []byte("hello world")
n, err := sftpFile.WriteAt(testData, 0)
assert.NoError(t, err)
assert.Equal(t, len(testData), n)
}
p, err = client.RealPath(path.Join(subdir, linkName))
assert.NoError(t, err)
assert.Equal(t, path.Join("/", testFileName), p)
// now a link outside the home dir
err = os.Symlink(filepath.Clean(os.TempDir()), filepath.Join(localUser.GetHomeDir(), subdir, "temp"))
assert.NoError(t, err)
_, err = client.RealPath(path.Join(subdir, "temp"))
assert.ErrorIs(t, err, os.ErrPermission)
err = os.Remove(filepath.Join(localUser.GetHomeDir(), subdir, "temp"))
assert.NoError(t, err)
err = os.RemoveAll(filepath.Join(localUser.GetHomeDir(), subdir))
assert.NoError(t, err)
}
} }
_, err = httpdtest.RemoveUser(user, http.StatusOK) _, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir()) _, err = httpdtest.RemoveUser(localUser, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(localUser.GetHomeDir())
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -7138,13 +7185,15 @@ func TestPermList(t *testing.T) {
defer conn.Close() defer conn.Close()
defer client.Close() defer client.Close()
_, err = client.ReadDir(".") _, err = client.ReadDir(".")
assert.Error(t, err, "read remote dir without permission should not succeed") assert.ErrorIs(t, err, os.ErrPermission, "read remote dir without permission should not succeed")
_, err = client.Stat("test_file") _, err = client.Stat("test_file")
assert.Error(t, err, "stat remote file without permission should not succeed") assert.ErrorIs(t, err, os.ErrPermission, "stat remote file without permission should not succeed")
_, err = client.Lstat("test_file") _, err = client.Lstat("test_file")
assert.Error(t, err, "lstat remote file without permission should not succeed") assert.ErrorIs(t, err, os.ErrPermission, "lstat remote file without permission should not succeed")
_, err = client.ReadLink("test_link") _, err = client.ReadLink("test_link")
assert.Error(t, err, "read remote link without permission on source dir should not succeed") assert.ErrorIs(t, err, os.ErrPermission, "read remote link without permission on source dir should not succeed")
_, err = client.RealPath(".")
assert.ErrorIs(t, err, os.ErrPermission, "real path without permission should not succeed")
f, err := client.Create(testFileName) f, err := client.Create(testFileName)
if assert.NoError(t, err) { if assert.NoError(t, err) {
_, err = f.Write([]byte("content")) _, err = f.Write([]byte("content"))

View file

@ -72,7 +72,7 @@
{{if not .HideSupportLink}} {{if not .HideSupportLink}}
<hr> <hr>
<div class="text-center"> <div class="text-center">
<a class="small" href="https://github.com/drakkan/sftpgo#sponsors">SFTPGo needs your help</a> <a class="small" href="https://github.com/drakkan/sftpgo#sponsors" target="_blank">SFTPGo needs your help</a>
</div> </div>
{{end}} {{end}}
</div> </div>

View file

@ -308,9 +308,15 @@ func GetDirsForVirtualPath(virtualPath string) []string {
// CleanPath returns a clean POSIX (/) absolute path to work with // CleanPath returns a clean POSIX (/) absolute path to work with
func CleanPath(p string) string { func CleanPath(p string) string {
return CleanPathWithBase("/", p)
}
// CleanPathWithBase returns a clean POSIX (/) absolute path to work with.
// The specified base will be used if the provided path is not absolute
func CleanPathWithBase(base, p string) string {
p = filepath.ToSlash(p) p = filepath.ToSlash(p)
if !path.IsAbs(p) { if !path.IsAbs(p) {
p = "/" + p p = path.Join(base, p)
} }
return path.Clean(p) return path.Clean(p)
} }

View file

@ -313,12 +313,44 @@ func (fs *OsFs) ResolvePath(virtualPath string) (string, error) {
err = fs.isSubDir(p) err = fs.isSubDir(p)
if err != nil { if err != nil {
fsLog(fs, logger.LevelError, "Invalid path resolution, dir %#v original path %#v resolved %#v err: %v", fsLog(fs, logger.LevelError, "Invalid path resolution, path %q original path %q resolved %q err: %v",
p, virtualPath, r, err) p, virtualPath, r, err)
} }
return r, err return r, err
} }
// RealPath implements the FsRealPather interface
func (fs *OsFs) RealPath(p string) (string, error) {
linksWalked := 0
for {
info, err := os.Lstat(p)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return fs.GetRelativePath(p), nil
}
return "", err
}
if info.Mode()&os.ModeSymlink == 0 {
return fs.GetRelativePath(p), nil
}
resolvedLink, err := os.Readlink(p)
if err != nil {
return "", err
}
resolvedLink = filepath.Clean(resolvedLink)
if filepath.IsAbs(resolvedLink) {
p = resolvedLink
} else {
p = filepath.Join(filepath.Dir(p), resolvedLink)
}
linksWalked++
if linksWalked > 10 {
fsLog(fs, logger.LevelError, "unable to get real path, too many links: %d", linksWalked)
return "", &pathResolutionError{err: "too many links"}
}
}
}
// GetDirSize returns the number of files and the size for a folder // GetDirSize returns the number of files and the size for a folder
// including any subfolders // including any subfolders
func (fs *OsFs) GetDirSize(dirname string) (int, int64, error) { func (fs *OsFs) GetDirSize(dirname string) (int, int64, error) {
@ -402,14 +434,14 @@ func (fs *OsFs) isSubDir(sub string) error {
// fs.rootDir must exist and it is already a validated absolute path // fs.rootDir must exist and it is already a validated absolute path
parent, err := filepath.EvalSymlinks(fs.rootDir) parent, err := filepath.EvalSymlinks(fs.rootDir)
if err != nil { if err != nil {
fsLog(fs, logger.LevelError, "invalid root path %#v: %v", fs.rootDir, err) fsLog(fs, logger.LevelError, "invalid root path %q: %v", fs.rootDir, err)
return err return err
} }
if parent == sub { if parent == sub {
return nil return nil
} }
if len(sub) < len(parent) { if len(sub) < len(parent) {
err = fmt.Errorf("path %#v is not inside %#v", sub, parent) err = fmt.Errorf("path %q is not inside %q", sub, parent)
return &pathResolutionError{err: err.Error()} return &pathResolutionError{err: err.Error()}
} }
separator := string(os.PathSeparator) separator := string(os.PathSeparator)
@ -419,7 +451,7 @@ func (fs *OsFs) isSubDir(sub string) error {
separator = "" separator = ""
} }
if !strings.HasPrefix(sub, parent+separator) { if !strings.HasPrefix(sub, parent+separator) {
err = fmt.Errorf("path %#v is not inside %#v", sub, parent) err = fmt.Errorf("path %q is not inside %q", sub, parent)
return &pathResolutionError{err: err.Error()} return &pathResolutionError{err: err.Error()}
} }
return nil return nil

View file

@ -628,6 +628,25 @@ func (fs *SFTPFs) ResolvePath(virtualPath string) (string, error) {
return fsPath, nil return fsPath, nil
} }
// RealPath implements the FsRealPather interface
func (fs *SFTPFs) RealPath(p string) (string, error) {
if err := fs.checkConnection(); err != nil {
return "", err
}
resolved, err := fs.sftpClient.RealPath(p)
if err != nil {
return "", err
}
if fs.config.Prefix != "/" {
if err := fs.isSubDir(resolved); err != nil {
fsLog(fs, logger.LevelError, "Invalid real path resolution, original path %q resolved %q err: %v",
p, resolved, err)
return "", err
}
}
return fs.GetRelativePath(resolved), nil
}
// getRealPath returns the real remote path trying to resolve symbolic links if any // getRealPath returns the real remote path trying to resolve symbolic links if any
func (fs *SFTPFs) getRealPath(name string) (string, error) { func (fs *SFTPFs) getRealPath(name string) (string, error) {
linksWalked := 0 linksWalked := 0
@ -641,7 +660,7 @@ func (fs *SFTPFs) getRealPath(name string) (string, error) {
} }
resolvedLink, err := fs.sftpClient.ReadLink(name) resolvedLink, err := fs.sftpClient.ReadLink(name)
if err != nil { if err != nil {
return name, err return name, fmt.Errorf("unable to resolve link to %q: %w", name, err)
} }
resolvedLink = path.Clean(resolvedLink) resolvedLink = path.Clean(resolvedLink)
if path.IsAbs(resolvedLink) { if path.IsAbs(resolvedLink) {
@ -651,6 +670,7 @@ func (fs *SFTPFs) getRealPath(name string) (string, error) {
} }
linksWalked++ linksWalked++
if linksWalked > 10 { if linksWalked > 10 {
fsLog(fs, logger.LevelError, "unable to get real path, too many links: %d", linksWalked)
return "", &pathResolutionError{err: "too many links"} return "", &pathResolutionError{err: "too many links"}
} }
} }
@ -661,11 +681,11 @@ func (fs *SFTPFs) isSubDir(name string) error {
return nil return nil
} }
if len(name) < len(fs.config.Prefix) { if len(name) < len(fs.config.Prefix) {
err := fmt.Errorf("path %#v is not inside: %#v", name, fs.config.Prefix) err := fmt.Errorf("path %q is not inside: %#v", name, fs.config.Prefix)
return &pathResolutionError{err: err.Error()} return &pathResolutionError{err: err.Error()}
} }
if !strings.HasPrefix(name, fs.config.Prefix+"/") { if !strings.HasPrefix(name, fs.config.Prefix+"/") {
err := fmt.Errorf("path %#v is not inside: %#v", name, fs.config.Prefix) err := fmt.Errorf("path %q is not inside: %#v", name, fs.config.Prefix)
return &pathResolutionError{err: err.Error()} return &pathResolutionError{err: err.Error()}
} }
return nil return nil

View file

@ -89,6 +89,12 @@ type Fs interface {
Close() error Close() error
} }
// FsRealPather is a Fs that implements the RealPath method.
type FsRealPather interface {
Fs
RealPath(p string) (string, error)
}
// fsMetadataChecker is a Fs that implements the getFileNamesInPrefix method. // fsMetadataChecker is a Fs that implements the getFileNamesInPrefix method.
// This interface is used to abstract metadata consistency checks // This interface is used to abstract metadata consistency checks
type fsMetadataChecker interface { type fsMetadataChecker interface {