mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-21 23:20:24 +00:00
sftp realpath: resolve symlinks
Fixes #890 Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
e0ce2e2e8a
commit
55b47cf741
14 changed files with 244 additions and 100 deletions
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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
15
go.mod
|
@ -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
34
go.sum
|
@ -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=
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
40
vfs/osfs.go
40
vfs/osfs.go
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue