diff --git a/README.md b/README.md index c43d359f..72e22e22 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,11 @@ Several storage backends are supported: local filesystem, encrypted local filesy ## Sponsors 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. + 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: diff --git a/common/connection.go b/common/connection.go index 09230d39..b8167714 100644 --- a/common/connection.go +++ b/common/connection.go @@ -482,7 +482,7 @@ func (c *BaseConnection) Rename(virtualSourcePath, virtualTargetPath string) err initialSize := int64(-1) if dstInfo, err := fsDst.Lstat(fsTargetPath); err == nil { 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) return c.GetOpUnsupportedError() } diff --git a/dataprovider/user.go b/dataprovider/user.go index 2b180299..a636aa02 100644 --- a/dataprovider/user.go +++ b/dataprovider/user.go @@ -198,7 +198,7 @@ func (u *User) checkDirWithParents(virtualDirPath, connectionID string) error { func (u *User) checkRootPath(connectionID string) error { fs, err := u.GetFilesystemForPath("/", connectionID) 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) } fs.CheckRootPath(u.Username, u.GetUID(), u.GetGID()) diff --git a/docs/service.md b/docs/service.md index 88c3383a..b3bd0859 100644 --- a/docs/service.md +++ b/docs/service.md @@ -6,9 +6,9 @@ Run the following instructions from the directory that contains the sftpgo binar ## 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. @@ -67,6 +67,10 @@ sudo /usr/bin/sftpgo gen man -d /usr/share/man/man1 ## 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. Here are some basic instructions to run SFTPGo as service, please run the following commands from the directory where you downloaded SFTPGo: diff --git a/go.mod b/go.mod index 0a221581..863558a3 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/hashicorp/go-plugin v1.4.4 github.com/hashicorp/go-retryablehttp v0.7.1 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/lib/pq v1.10.6 github.com/lithammer/shortuuid/v3 v3.0.7 @@ -53,12 +53,12 @@ require ( github.com/rs/zerolog v1.27.0 github.com/sftpgo/sdk v0.1.2-0.20220611083241-b653555f7f4d 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/viper v1.12.0 github.com/stretchr/testify v1.8.0 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/xhit/go-simple-mail/v2 v2.11.0 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/net v0.0.0-20220624214902-1bab6f366d9e 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 google.golang.org/api v0.87.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 @@ -103,7 +103,7 @@ require ( github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-ole/go-ole v1.2.6 // 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/protobuf v1.5.2 // 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/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // 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/russross/blackfriday/v2 v2.1.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/xerrors v0.0.0-20220609144429-65e65417b02f // 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/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.66.6 // indirect @@ -166,6 +166,7 @@ require ( replace ( 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/net => github.com/drakkan/net v0.0.0-20220628171916-78de6a2a21b0 ) diff --git a/go.sum b/go.sum index 79e9300d..3b61b6de 100644 --- a/go.sum +++ b/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/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/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/go.mod h1:kltMsfRMTHSFdMbK66XdS8mfMW77+FZA1fGY1xYMF84= 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/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.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE= -github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc= +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/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= 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/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.7 h1:7cgTQxJCU/vy+oP/E3B9RGbQTgbiVzIJWIKOLoAsPok= -github.com/klauspost/compress v1.15.7/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.8 h1:JahtItbkWjf2jzm/T+qgMxkP9EMHsqEUA6vCMGmXvhA= +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.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0= 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.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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.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.36.0 h1:78hJTing+BLYLjhXE+Z2BubeEymH5Lr0/Mt8FKkxxYo= -github.com/prometheus/common v0.36.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 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.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 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.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.9.0 h1:sFSLUHgxdnN32Qy38hK3QkYBFXZj9DKjVjCUCtD7juY= +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/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 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/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/unrolled/secure v1.11.0 h1:fjkKhD/MsQnlmz/au+MmFptCFNhvf5iv04ALkdCXRCI= -github.com/unrolled/secure v1.11.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= +github.com/unrolled/secure v1.12.0 h1:7k3jcgLwfjiKkhQde6VbQ3D4KDLtDBqDd/hs3PPANDY= +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/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ= 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-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-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-20210510120138-977fb7262007/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-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-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew= -golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +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-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= 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-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-20220712132514-bdd2acd4974d h1:YbuF5+kdiC516xIP60RvlHeFbY9sRDR73QsAGHpkeVw= -google.golang.org/genproto v0.0.0-20220712132514-bdd2acd4974d/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220715211116-798f69b842b9 h1:1aEQRgZ4Gks2SRAkLzIPpIszRazwVfjSFe1cKc+e0Jg= +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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/sftpd/handler.go b/sftpd/handler.go index ae847d3b..610ea4ea 100644 --- a/sftpd/handler.go +++ b/sftpd/handler.go @@ -268,6 +268,31 @@ func (c *Connection) Lstat(request *sftp.Request) (sftp.ListerAt, error) { 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 func (c *Connection) StatVFS(r *sftp.Request) (*sftp.StatVFS, error) { c.UpdateLastActivity() diff --git a/sftpd/server.go b/sftpd/server.go index c196ceeb..c2f0f80f 100644 --- a/sftpd/server.go +++ b/sftpd/server.go @@ -613,7 +613,7 @@ func (c *Configuration) handleSftpConnection(channel ssh.Channel, connection *Co sftp.WithStartDirectory(connection.User.Filters.StartDirectory)) defer server.Close() - if err := server.Serve(); err == io.EOF { + if err := server.Serve(); errors.Is(err, io.EOF) { exitStatus := sshSubsystemExitStatus{Status: uint32(0)} _, err = channel.SendRequest("exit-status", false, ssh.Marshal(&exitStatus)) connection.Log(logger.LevelInfo, "connection closed, sent exit status %+v error: %v", exitStatus, err) diff --git a/sftpd/sftpd_test.go b/sftpd/sftpd_test.go index 701b78d1..7c96d156 100644 --- a/sftpd/sftpd_test.go +++ b/sftpd/sftpd_test.go @@ -763,60 +763,70 @@ func TestStartDirectory(t *testing.T) { startDir := "/st@ rt/dir" u := getTestUser(usePubKey) u.Filters.StartDirectory = startDir - user, _, err := httpdtest.AddUser(u, http.StatusCreated) + localUser, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) - conn, client, err := getSftpClient(user, usePubKey) - if assert.NoError(t, err) { - defer conn.Close() - defer client.Close() + u = getTestSFTPUser(usePubKey) + u.Filters.StartDirectory = startDir + sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated) + 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() - assert.NoError(t, err) - assert.Equal(t, startDir, currentDir) + currentDir, err := client.Getwd() + assert.NoError(t, err) + assert.Equal(t, startDir, currentDir) - entries, err := client.ReadDir(".") - assert.NoError(t, err) - assert.Len(t, entries, 0) + entries, err := client.ReadDir(".") + assert.NoError(t, err) + assert.Len(t, entries, 0) - testFilePath := filepath.Join(homeBasePath, testFileName) - testFileSize := int64(65535) - err = createTestFile(testFilePath, testFileSize) - assert.NoError(t, err) - err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) - assert.NoError(t, err) - localDownloadPath := filepath.Join(homeBasePath, testDLFileName) - err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client) - assert.NoError(t, err) - _, err = client.Stat(testFileName) - assert.NoError(t, err) - err = client.Rename(testFileName, testFileName+"_rename") - assert.NoError(t, err) + testFilePath := filepath.Join(homeBasePath, testFileName) + testFileSize := int64(65535) + err = createTestFile(testFilePath, testFileSize) + assert.NoError(t, err) + err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + assert.NoError(t, err) + localDownloadPath := filepath.Join(homeBasePath, testDLFileName) + err = sftpDownloadFile(testFileName, localDownloadPath, testFileSize, client) + assert.NoError(t, err) + _, err = client.Stat(testFileName) + assert.NoError(t, err) + err = client.Rename(testFileName, testFileName+"_rename") + assert.NoError(t, err) - entries, err = client.ReadDir(".") - assert.NoError(t, err) - assert.Len(t, entries, 1) + entries, err = client.ReadDir(".") + assert.NoError(t, err) + assert.Len(t, entries, 1) - currentDir, err = client.RealPath("..") - assert.NoError(t, err) - assert.Equal(t, path.Dir(startDir), currentDir) + currentDir, err = client.RealPath("..") + assert.NoError(t, err) + assert.Equal(t, path.Dir(startDir), currentDir) - currentDir, err = client.RealPath("../..") - assert.NoError(t, err) - assert.Equal(t, "/", currentDir) + currentDir, err = client.RealPath("../..") + assert.NoError(t, err) + assert.Equal(t, "/", currentDir) - currentDir, err = client.RealPath("../../..") - assert.NoError(t, err) - assert.Equal(t, "/", currentDir) + currentDir, err = client.RealPath("../../..") + assert.NoError(t, err) + assert.Equal(t, "/", currentDir) - err = os.Remove(testFilePath) - assert.NoError(t, err) - err = os.Remove(localDownloadPath) - assert.NoError(t, err) + err = client.Remove(testFileName + "_rename") + assert.NoError(t, err) + + err = os.Remove(testFilePath) + assert.NoError(t, err) + err = os.Remove(localDownloadPath) + assert.NoError(t, err) + } } - - _, err = httpdtest.RemoveUser(user, http.StatusOK) + _, err = httpdtest.RemoveUser(sftpUser, http.StatusOK) 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) } @@ -1181,22 +1191,59 @@ func TestProxyProtocol(t *testing.T) { func TestRealPath(t *testing.T) { usePubKey := true u := getTestUser(usePubKey) - user, _, err := httpdtest.AddUser(u, http.StatusCreated) + localUser, _, err := httpdtest.AddUser(u, http.StatusCreated) assert.NoError(t, err) - conn, client, err := getSftpClient(user, usePubKey) - if assert.NoError(t, err) { - defer conn.Close() - defer client.Close() - 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) + u = getTestSFTPUser(usePubKey) + sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated) + 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() + + 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) - err = os.RemoveAll(user.GetHomeDir()) + _, err = httpdtest.RemoveUser(localUser, http.StatusOK) + assert.NoError(t, err) + err = os.RemoveAll(localUser.GetHomeDir()) assert.NoError(t, err) } @@ -7138,13 +7185,15 @@ func TestPermList(t *testing.T) { defer conn.Close() defer client.Close() _, 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") - 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") - 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") - 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) if assert.NoError(t, err) { _, err = f.Write([]byte("content")) diff --git a/templates/webadmin/adminsetup.html b/templates/webadmin/adminsetup.html index 21b87ea7..a143d5aa 100644 --- a/templates/webadmin/adminsetup.html +++ b/templates/webadmin/adminsetup.html @@ -72,7 +72,7 @@ {{if not .HideSupportLink}}