sftpd: fix relative symlinks handling

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2022-09-30 19:23:54 +02:00
parent 1e21aa9453
commit 0e8c41bbd1
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
6 changed files with 101 additions and 24 deletions

7
go.mod
View file

@ -15,7 +15,7 @@ require (
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.33
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.19
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.1
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.2
github.com/aws/aws-sdk-go-v2/service/sts v1.16.19
github.com/cockroachdb/cockroach-go/v2 v2.2.16
github.com/coreos/go-oidc/v3 v3.4.0
@ -109,7 +109,7 @@ require (
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.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
github.com/googleapis/gax-go/v2 v2.5.1 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
@ -156,7 +156,7 @@ require (
golang.org/x/tools v0.1.12 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220927151529-dcaddaf36704 // indirect
google.golang.org/genproto v0.0.0-20220929141241-1ce7b20da813 // indirect
google.golang.org/grpc v1.49.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
@ -167,6 +167,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-20220930161944-e8c89afc13a7
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20220925175854-fd2128ac8ea8
golang.org/x/net => github.com/drakkan/net v0.0.0-20220925175748-018cd5f6a745
)

17
go.sum
View file

@ -190,8 +190,8 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11 h1:3/gm/JTX9bX8CpzTgIlrtYpB3EVBDxyg/GY/QdcIEZw=
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11/go.mod h1:fmgDANqTUCxciViKl9hb/zD5LFbvPINFRgWhDbR+vZo=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4/go.mod h1:PJc8s+lxyU8rrre0/4a0pn2wgwiDvOEzoOjcJUBr67o=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.1 h1:eMsEmvJR6zQ1lDi59RDtCc62x9fKs1kv2b8A8nPpWmY=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.1/go.mod h1:HEBBc70BYi5eUvxBqC3xXjU/04NO96X/XNUe5qhC7Bc=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.2 h1:3x1Qilin49XQ1rK6pDNAfG+DmCFPfB7Rrpl+FUDAR/0=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.2/go.mod h1:HEBBc70BYi5eUvxBqC3xXjU/04NO96X/XNUe5qhC7Bc=
github.com/aws/aws-sdk-go-v2/service/sns v1.17.4/go.mod h1:kElt+uCcXxcqFyc+bQqZPFD9DME/eC6oHBXvFzQ9Bcw=
github.com/aws/aws-sdk-go-v2/service/sqs v1.18.3/go.mod h1:skmQo0UPvsjsuYYSYMVmrPc1HWCbHUJyrCEp+ZaLzqM=
github.com/aws/aws-sdk-go-v2/service/ssm v1.24.1/go.mod h1:NR/xoKjdbRJ+qx0pMR4mI+N/H1I1ynHwXnO6FowXJc0=
@ -270,6 +270,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-20220925175748-018cd5f6a745 h1:Lgtp3izEEJFPxMLJfLWxc1htWNMZhK7EkBo0/dmEX4E=
github.com/drakkan/net v0.0.0-20220925175748-018cd5f6a745/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
github.com/drakkan/sftp v0.0.0-20220930161944-e8c89afc13a7 h1:Hj7AAfZ5yt9QuCxSQDllRygmL33xJ2sZLOmcyyOAdYU=
github.com/drakkan/sftp v0.0.0-20220930161944-e8c89afc13a7/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=
@ -441,8 +443,9 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/enterprise-certificate-proxy v0.1.0 h1:zO8WHNx/MYiAKJ3d5spxZXZE6KHmIQGQcAzwUzV7qQw=
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs=
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
@ -665,9 +668,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.6-0.20220831160757-628507938ec6 h1:hxLT9qX4jw+GjGuPA6XHtooT1+nf/hr5anQtACaXZmY=
github.com/pkg/sftp v1.13.6-0.20220831160757-628507938ec6/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=
@ -945,7 +945,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=
@ -1230,8 +1229,8 @@ google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/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-20220927151529-dcaddaf36704 h1:H1AcWFV69NFCMeBJ8nVLtv8uHZZ5Ozcgoq012hHEFuU=
google.golang.org/genproto v0.0.0-20220927151529-dcaddaf36704/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
google.golang.org/genproto v0.0.0-20220929141241-1ce7b20da813 h1:buul04Ikd79A5tP8nGhKEyMfr+/HplsO6nqSUapWZ/M=
google.golang.org/genproto v0.0.0-20220929141241-1ce7b20da813/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
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=

View file

@ -546,6 +546,13 @@ func (c *BaseConnection) Rename(virtualSourcePath, virtualTargetPath string) err
// CreateSymlink creates fsTargetPath as a symbolic link to fsSourcePath
func (c *BaseConnection) CreateSymlink(virtualSourcePath, virtualTargetPath string) error {
var relativePath string
if !path.IsAbs(virtualSourcePath) {
relativePath = virtualSourcePath
virtualSourcePath = path.Join(path.Dir(virtualTargetPath), relativePath)
c.Log(logger.LevelDebug, "link relative path %q resolved as %q, target path %q",
relativePath, virtualSourcePath, virtualTargetPath)
}
if c.isCrossFoldersRequest(virtualSourcePath, virtualTargetPath) {
c.Log(logger.LevelWarn, "cross folder symlink is not supported, src: %v dst: %v", virtualSourcePath, virtualTargetPath)
return c.GetOpUnsupportedError()
@ -579,6 +586,9 @@ func (c *BaseConnection) CreateSymlink(virtualSourcePath, virtualTargetPath stri
c.Log(logger.LevelError, "symlink target path %#v is not allowed", virtualTargetPath)
return c.GetPermissionDeniedError()
}
if relativePath != "" {
fsSourcePath = relativePath
}
if err := fs.Symlink(fsSourcePath, fsTargetPath); err != nil {
c.Log(logger.LevelError, "failed to create symlink %#v -> %#v: %+v", fsSourcePath, fsTargetPath, err)
return c.GetFsError(fs, err)

View file

@ -313,6 +313,71 @@ func TestBaseConnection(t *testing.T) {
assert.NoError(t, err)
}
func TestRelativeSymlinks(t *testing.T) {
u := getTestUser()
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
conn, client, err := getSftpClient(user)
if assert.NoError(t, err) {
defer conn.Close()
defer client.Close()
linkName := testFileName + "_link"
err = client.Symlink("non-existent-file", linkName)
assert.NoError(t, err)
err = client.Remove(linkName)
assert.NoError(t, err)
testDir := "sub"
err = client.Mkdir(testDir)
assert.NoError(t, err)
f, err := client.Create(path.Join(testDir, testFileName))
assert.NoError(t, err)
_, err = f.Write(testFileContent)
assert.NoError(t, err)
err = f.Close()
assert.NoError(t, err)
err = client.Symlink(path.Join(testDir, testFileName), linkName)
assert.NoError(t, err)
_, err = client.Stat(linkName)
assert.NoError(t, err)
p, err := client.ReadLink(linkName)
assert.NoError(t, err)
assert.Equal(t, path.Join("/", testDir, testFileName), p)
err = client.Remove(linkName)
assert.NoError(t, err)
err = client.Symlink(testFileName, path.Join(testDir, linkName))
assert.NoError(t, err)
_, err = client.Stat(path.Join(testDir, linkName))
assert.NoError(t, err)
p, err = client.ReadLink(path.Join(testDir, linkName))
assert.NoError(t, err)
assert.Equal(t, path.Join("/", testDir, testFileName), p)
f, err = client.Create(testFileName)
assert.NoError(t, err)
_, err = f.Write(testFileContent)
assert.NoError(t, err)
err = f.Close()
assert.NoError(t, err)
err = client.Symlink(testFileName, linkName)
assert.NoError(t, err)
_, err = client.Stat(linkName)
assert.NoError(t, err)
p, err = client.ReadLink(linkName)
assert.NoError(t, err)
assert.Equal(t, path.Join("/", testFileName), p)
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestCheckFsAfterUpdate(t *testing.T) {
u := getTestUser()
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
@ -2264,19 +2329,19 @@ func TestVirtualFoldersLink(t *testing.T) {
assert.NoError(t, err)
err = client.Symlink(path.Join(vdirPath2, testFileName), path.Join(vdirPath2, testDir, testFileName+".link"))
assert.NoError(t, err)
err = client.Symlink(testFileName, path.Join(vdirPath1, testFileName+".link1"))
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath1, testFileName+".link1"))
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
}
err = client.Symlink(testFileName, path.Join(vdirPath1, testDir, testFileName+".link1"))
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath1, testDir, testFileName+".link1"))
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
}
err = client.Symlink(testFileName, path.Join(vdirPath2, testFileName+".link1"))
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath2, testFileName+".link1"))
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
}
err = client.Symlink(testFileName, path.Join(vdirPath2, testDir, testFileName+".link1"))
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath2, testDir, testFileName+".link1"))
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
}

View file

@ -607,7 +607,7 @@ func TestBasicSFTPFsHandling(t *testing.T) {
err = client.Mkdir(linkDir)
assert.NoError(t, err)
linkToLinkPath := path.Join(linkDir, testLinkToLinkName)
err = client.Symlink(testLinkName, linkToLinkPath)
err = client.Symlink(path.Join("/", testLinkName), linkToLinkPath)
assert.NoError(t, err)
info, err = client.Lstat(linkToLinkPath)
if assert.NoError(t, err) {
@ -733,6 +733,8 @@ func TestSFTPFsEscapeHomeDir(t *testing.T) {
// linkName points to a link inside the home dir and this link points to a dir outside the home dir
_, err = client.ReadLink(linkName)
assert.ErrorIs(t, err, os.ErrPermission)
_, err = client.RealPath(linkName)
assert.ErrorIs(t, err, os.ErrPermission)
_, err = client.ReadDir(linkName)
assert.ErrorIs(t, err, os.ErrPermission)
_, err = client.ReadDir(path.Join(dirName, linkName))
@ -1233,7 +1235,7 @@ func TestRealPath(t *testing.T) {
err = client.Mkdir(subdir)
assert.NoError(t, err)
linkName := testFileName + "_link"
err = client.Symlink(testFileName, path.Join(subdir, linkName))
err = client.Symlink(path.Join("/", testFileName), path.Join(subdir, linkName))
assert.NoError(t, err)
p, err = client.RealPath(path.Join(subdir, linkName))
assert.NoError(t, err)
@ -6890,13 +6892,13 @@ func TestVirtualFoldersLink(t *testing.T) {
assert.NoError(t, err)
err = client.Symlink(path.Join(vdirPath2, testFileName), path.Join(vdirPath2, testDir, testFileName+".link"))
assert.NoError(t, err)
err = client.Symlink(testFileName, path.Join(vdirPath1, testFileName+".link1"))
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath1, testFileName+".link1"))
assert.Error(t, err)
err = client.Symlink(testFileName, path.Join(vdirPath1, testDir, testFileName+".link1"))
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath1, testDir, testFileName+".link1"))
assert.Error(t, err)
err = client.Symlink(testFileName, path.Join(vdirPath2, testFileName+".link1"))
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath2, testFileName+".link1"))
assert.Error(t, err)
err = client.Symlink(testFileName, path.Join(vdirPath2, testDir, testFileName+".link1"))
err = client.Symlink(path.Join("/", testFileName), path.Join(vdirPath2, testDir, testFileName+".link1"))
assert.Error(t, err)
err = client.Symlink(path.Join(vdirPath1, testFileName), testFileName+".link1")
assert.Error(t, err)
@ -7355,7 +7357,7 @@ func TestPermList(t *testing.T) {
}
err = client.Mkdir("sub")
assert.NoError(t, err)
err = client.Symlink(testFileName, path.Join("/sub", testFileName))
err = client.Symlink(path.Join("/", testFileName), path.Join("/sub", testFileName))
assert.NoError(t, err)
_, err = client.ReadLink(path.Join("/sub", testFileName))
assert.Error(t, err, "read remote link without permission on targe dir should not succeed")

View file

@ -1,6 +1,6 @@
#!/bin/bash
NFPM_VERSION=2.19.1
NFPM_VERSION=2.19.2
NFPM_ARCH=${NFPM_ARCH:-amd64}
if [ -z ${SFTPGO_VERSION} ]
then