From 4a44a7dfe16048187d8a0d491bf0ac9037ca0f91 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Wed, 27 Apr 2022 18:38:46 +0200 Subject: [PATCH] improved readlink handling Signed-off-by: Nicola Murino --- dataprovider/pgsql.go | 16 +++++++++ docs/sftpfs.md | 2 +- go.mod | 48 ++++++++++++------------- go.sum | 80 +++++++++++++++++++++++++----------------- sftpd/handler.go | 21 ++++++++--- sftpd/internal_test.go | 31 ++++++++++++++++ sftpd/sftpd_test.go | 66 ++++++++++++++++++++++++++++++++++ vfs/osfs.go | 12 +++++-- vfs/sftpfs.go | 41 +++++++++++++++++----- 9 files changed, 244 insertions(+), 73 deletions(-) diff --git a/dataprovider/pgsql.go b/dataprovider/pgsql.go index 2c028b73..ae280a33 100644 --- a/dataprovider/pgsql.go +++ b/dataprovider/pgsql.go @@ -611,6 +611,22 @@ func updatePGSQLDatabaseFrom15To16(dbHandle *sql.DB) error { sql := strings.ReplaceAll(pgsqlV16SQL, "{{users}}", sqlTableUsers) sql = strings.ReplaceAll(sql, "{{active_transfers}}", sqlTableActiveTransfers) sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix) + if config.Driver == CockroachDataProviderName { + // Cockroach does not allow to run this schema migration within a transaction + ctx, cancel := context.WithTimeout(context.Background(), longSQLQueryTimeout) + defer cancel() + + for _, q := range strings.Split(sql, ";") { + if strings.TrimSpace(q) == "" { + continue + } + _, err := dbHandle.ExecContext(ctx, q) + if err != nil { + return err + } + } + return sqlCommonUpdateDatabaseVersion(ctx, dbHandle, 16) + } return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 16) } diff --git a/docs/sftpfs.md b/docs/sftpfs.md index 006fad8a..604d9e2d 100644 --- a/docs/sftpfs.md +++ b/docs/sftpfs.md @@ -28,7 +28,7 @@ The password and the private key are stored as ciphertext according to your [KMS SHA256 fingerprints for remote server host keys are optional but highly recommended: if you provide one or more fingerprints the server host key will be verified against them and the connection will be denied if none of the fingerprints provided match that for the server host key. -Specifying a prefix you can restrict all operations to a given path within the remote SFTP server. +Specifying a prefix you can restrict all operations to a given path within the remote SFTP server. If you set a prefix make sure it is not inside a symlinked directory or it is a symlink itself. Buffering can be enabled by setting a buffer size (in MB) greater than 0. By enabling buffering, the reads and writes, from/to the remote SFTP server, are split in multiple concurrent requests and this allows data to be transferred at a faster rate, over high latency networks, by overlapping round-trip times. With buffering enabled, resuming uploads and trucate are not supported and a file cannot be opened for both reading and writing at the same time. 0 means disabled. diff --git a/go.mod b/go.mod index 27ffe520..911469f9 100644 --- a/go.mod +++ b/go.mod @@ -8,19 +8,19 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.0 github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 - github.com/aws/aws-sdk-go-v2 v1.16.2 - github.com/aws/aws-sdk-go-v2/config v1.15.3 - github.com/aws/aws-sdk-go-v2/credentials v1.11.2 - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.5 - github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.3 - github.com/aws/aws-sdk-go-v2/service/s3 v1.26.5 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.5 - github.com/aws/aws-sdk-go-v2/service/sts v1.16.3 + github.com/aws/aws-sdk-go-v2 v1.16.3 + github.com/aws/aws-sdk-go-v2/config v1.15.4 + github.com/aws/aws-sdk-go-v2/credentials v1.12.0 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.6 + github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.4 + github.com/aws/aws-sdk-go-v2/service/s3 v1.26.6 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.6 + github.com/aws/aws-sdk-go-v2/service/sts v1.16.4 github.com/cockroachdb/cockroach-go/v2 v2.2.8 github.com/coreos/go-oidc/v3 v3.1.0 github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 - github.com/fclairamb/ftpserverlib v0.17.1-0.20220317111420-26600d07c50e + github.com/fclairamb/ftpserverlib v0.18.0 github.com/fclairamb/go-log v0.3.0 github.com/go-chi/chi/v5 v5.0.8-0.20220103230436-7dbe9a0bd10f github.com/go-chi/jwtauth/v5 v5.0.2 @@ -34,7 +34,7 @@ require ( github.com/hashicorp/go-plugin v1.4.3 github.com/hashicorp/go-retryablehttp v0.7.1 github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126 - github.com/klauspost/compress v1.15.1 + github.com/klauspost/compress v1.15.2 github.com/lestrrat-go/jwx v1.2.23 github.com/lib/pq v1.10.5 github.com/lithammer/shortuuid/v3 v3.0.7 @@ -69,7 +69,7 @@ require ( golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 golang.org/x/time v0.0.0-20220411224347-583f2d630306 - google.golang.org/api v0.75.0 + google.golang.org/api v0.76.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) @@ -79,32 +79,32 @@ require ( cloud.google.com/go/iam v0.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.2 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.11.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.11.4 // indirect github.com/aws/smithy-go v1.11.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/fatih/color v1.13.0 // indirect - github.com/fsnotify/fsnotify v1.5.3 // indirect + 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.7 // 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.7 // indirect + github.com/google/go-cmp v0.5.8 // indirect github.com/googleapis/gax-go/v2 v2.3.0 // indirect github.com/googleapis/go-type-adapters v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -152,7 +152,7 @@ require ( golang.org/x/tools v0.1.10 // indirect golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731 // indirect + google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 // indirect google.golang.org/grpc v1.46.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect diff --git a/go.sum b/go.sum index ce46e848..15e5a502 100644 --- a/go.sum +++ b/go.sum @@ -135,51 +135,63 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go-v2 v1.16.2 h1:fqlCk6Iy3bnCumtrLz9r3mJ/2gUT0pJ0wLFVIdWh+JA= github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= +github.com/aws/aws-sdk-go-v2 v1.16.3 h1:0W1TSJ7O6OzwuEvIXAtJGvOeQ0SGAhcpxPN2/NK5EhM= +github.com/aws/aws-sdk-go-v2 v1.16.3/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM= -github.com/aws/aws-sdk-go-v2/config v1.15.3 h1:5AlQD0jhVXlGzwo+VORKiUuogkG7pQcLJNzIzK7eodw= github.com/aws/aws-sdk-go-v2/config v1.15.3/go.mod h1:9YL3v07Xc/ohTsxFXzan9ZpFpdTOFl4X65BAKYaz8jg= -github.com/aws/aws-sdk-go-v2/credentials v1.11.2 h1:RQQ5fzclAKJyY5TvF+fkjJEwzK4hnxQCLOu5JXzDmQo= +github.com/aws/aws-sdk-go-v2/config v1.15.4 h1:P4mesY1hYUxru4f9SU0XxNKXmzfxsD0FtMIPRBjkH7Q= +github.com/aws/aws-sdk-go-v2/config v1.15.4/go.mod h1:ZijHHh0xd/A+ZY53az0qzC5tT46kt4JVCePf2NX9Lk4= github.com/aws/aws-sdk-go-v2/credentials v1.11.2/go.mod h1:j8YsY9TXTm31k4eFhspiQicfXPLZ0gYXA50i4gxPE8g= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3 h1:LWPg5zjHV9oz/myQr4wMs0gi4CjnDN/ILmyZUFYXZsU= +github.com/aws/aws-sdk-go-v2/credentials v1.12.0 h1:4R/NqlcRFSkR0wxOhgHi+agGpbEr5qMCjn7VqUIJY+E= +github.com/aws/aws-sdk-go-v2/credentials v1.12.0/go.mod h1:9YWk7VW+eyKsoIL6/CljkTrNVWBSK9pkqOPUuijid4A= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3/go.mod h1:uk1vhHHERfSVCUnqSqz8O48LBYDSC+k6brng09jcMOk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4 h1:FP8gquGeGHHdfY6G5llaMQDF+HAf20VKc8opRwmjf04= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4/go.mod h1:u/s5/Z+ohUQOPXl00m2yJVyioWDECsbpXTQlaqSlufc= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3/go.mod h1:0dHuD2HZZSiwfJSy1FO5bX1hQ1TxVV1QXXjpn3XUE44= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.5 h1:lPo/NX1o4vkk2C7mHmB2FCf9Qp7KZNHrlzHxdP/yugw= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.5/go.mod h1:JNo9mEKrjnmDBc19z65TZmj1xG9PQHu2GOlApYk31DU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9 h1:onz/VaaxZ7Z4V+WIN9Txly9XLTmoOh1oJ8XcAC3pako= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.6 h1:6Q/ITRl/PoOAcbLkT3EOpch/6w9n/YNN6a/v+dfuBY8= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.6/go.mod h1:sj1vB2ZjQ1PQOWc89SyhEJs838UIpDcsa3HylyczQO0= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3 h1:9stUQR/u2KXU6HkFJYlqnZEjBnbgrVbG6I5HN09xZh0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10 h1:uFWgo6mGJI1n17nbcvSc6fxVuR3xLNqvXt12JCnEcT8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10/go.mod h1:F+EZtuIwjlv35kRJPyBGcsA4f7bnSoz15zOQ2lJq1Z4= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10 h1:by9P+oy3P/CwggN4ClnW2D4oL91QV7pBzBICi1chZvQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4 h1:cnsvEKSoHN4oAN7spMMr0zhEW2MHnhAVpmqQg8E6UcM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4/go.mod h1:8glyUqVIM4AmeenIsPo0oVh3+NUwnsQml2OFupfQW+0= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10/go.mod h1:8DcYQcz0+ZJaSxANlHIsbbi6S+zMwjwdDqwW3r9AzaE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.0 h1:cq+47u1zpHyH+PSkbBx1N9whx4TiM9m9ibimOPaNlBg= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.0/go.mod h1:Nf3QiqrNy2sj3Rku+9z4nN/bThI97gQmR7YxG3s+ez8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11 h1:6cZRymlLEIlDTEB0+5+An6Zj1CKt6rSE69tOmFeu1nk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11/go.mod h1:0MR+sS1b/yxsfAPvAESrw8NfwUoxMinDyw6EYR9BS2U= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.1 h1:C21IDZCm9Yu5xqjb3fKmxDoYvJXtw1DNlOmLZEIlY1M= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.1/go.mod h1:l/BbcfqDCT3hePawhy4ZRtewjtdkl6GWtd9/U+1penQ= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 h1:T4pFel53bkHjL2mMo+4DKE6r6AuoZnM0fg7k1/ratr4= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3 h1:I0dcwWitE752hVSMrsLCxqNQ+UdEp3nACx2bYNMQq+k= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3/go.mod h1:Seb8KNmD6kVTjwRjVEgOT5hPin6sq+v4C2ycJQDwuH8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3 h1:Gh1Gpyh01Yvn7ilO/b/hr01WgNpaszfbKMUgqM186xQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.4 h1:HXy33+dHXT6WYvnAtIvcQ7Zh4ppeAccz8ofi5bzsQ/A= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.4/go.mod h1:S8TVP66AAkMMdYYCNZGvrdEq9YRm+qLXjio4FqRnrEE= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3/go.mod h1:wlY6SVjuwvh3TVRpTqdy4I1JpBFLX4UGeKZdWntaocw= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3 h1:BKjwCJPnANbkwQ8vzSbaZDKawwagDubrH/z/c0X+kbQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4 h1:b16QW0XWl0jWjLABFc1A+uh145Oqv+xDcObNk0iQgUk= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4/go.mod h1:uKkN7qmSIsNJVyMtxNQoCEYMvFEXbOg9fwCJPdfp2u8= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3/go.mod h1:Bm/v2IaN6rZ+Op7zX+bOUMdL4fsrYZiD0dsjLhNKwZc= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.4 h1:RE/DlZLYrz1OOmq8F28IXHLksuuvlpzUbvJ+SESCZBI= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.4/go.mod h1:oudbsSdDtazNj47z1ut1n37re9hDsKpk2ZI3v7KSxq0= github.com/aws/aws-sdk-go-v2/service/kms v1.16.3/go.mod h1:QuiHPBqlOFCi4LqdSskYYAWpQlx3PKmohy+rE2F+o5g= -github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.3 h1:xqXHk4UDW7ii4MRciyLpY87yuZds0iymmgHt3h35xTE= -github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.3/go.mod h1:HT0cm2+NUCF33MdXjck554HC6VRgQ4q6JIlSqlYZ18Y= +github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.4 h1:qmHavnjRtgdH54nyG4iEk6ZCde9m2S++32INurhaNTk= +github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.4/go.mod h1:CloMDruFIVZJ8qv2OsY5ENIqzg5c0eeTciVVW3KHdvE= github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z4KJ7DtFFCx8fJD2a491Ak= -github.com/aws/aws-sdk-go-v2/service/s3 v1.26.5 h1:A3PuAUlh1u47WHcM68CDaG9ZWjK7ewePjDp+0dY9yv4= -github.com/aws/aws-sdk-go-v2/service/s3 v1.26.5/go.mod h1:qFKU5d+PAv+23bi9ZhtWeA+TmLUz7B/R59ZGXQ1Mmu4= +github.com/aws/aws-sdk-go-v2/service/s3 v1.26.6 h1:RyI53C9+8xxZ3zrllJwzZjI6/FePzxNv3pvh59Ir0aE= +github.com/aws/aws-sdk-go-v2/service/s3 v1.26.6/go.mod h1:FMXuMpmEOLQUDnQLMjsJ2jJGN7jpji1pQ59Kii+IM4U= 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.15.5 h1:AbC8yID67uGYOTiLzhssGA5Y0z5RkV8supmYzFQpsMw= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.5/go.mod h1:PJc8s+lxyU8rrre0/4a0pn2wgwiDvOEzoOjcJUBr67o= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.6 h1:m+mxqLIrGq7GJo5qw4rHn8BbUqHrvxvwFx54N1Pglvw= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.6/go.mod h1:Z+i6uqZgCOBXhNoEGoRm/ZaLsaJA9rGUAmkVKM/3+g4= 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= -github.com/aws/aws-sdk-go-v2/service/sso v1.11.3 h1:frW4ikGcxfAEDfmQqWgMLp+F1n4nRo9sF39OcIb5BkQ= github.com/aws/aws-sdk-go-v2/service/sso v1.11.3/go.mod h1:7UQ/e69kU7LDPtY40OyoHYgRmgfGM4mgsLYtcObdveU= -github.com/aws/aws-sdk-go-v2/service/sts v1.16.3 h1:cJGRyzCSVwZC7zZZ1xbx9m32UnrKydRYhOvcD1NYP9Q= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.4 h1:Uw5wBybFQ1UeA9ts0Y07gbv0ncZnIAyw858tDW0NP2o= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.4/go.mod h1:cPDwJwsP4Kff9mldCXAmddjJL6JGQqtA3Mzer2zyr88= github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4NxktD9rDGeKSVWnjTnwbx9b8= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.4 h1:+xtV90n3abQmgzk1pS++FdxZTrPEDgQng6e4/56WR2A= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.4/go.mod h1:lfSYenAXtavyX2A1LsViglqlG9eEFYxNryTZS5rn3QE= github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE= github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -221,8 +233,9 @@ github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -259,15 +272,15 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fclairamb/ftpserverlib v0.17.1-0.20220317111420-26600d07c50e h1:9HD2ZIYUP4r8j8mrUHAGDOmMkbHRAiyW/DqJFf8ztzU= -github.com/fclairamb/ftpserverlib v0.17.1-0.20220317111420-26600d07c50e/go.mod h1:DWF/Vler0n3k9w6FR+HTQG3kQeKgi9xzq4t2NiIADDM= +github.com/fclairamb/ftpserverlib v0.18.0 h1:q/uz7jVFMoGEMswnA+nbaKEC5mzxXJOmhPE/Q3r7VZI= +github.com/fclairamb/ftpserverlib v0.18.0/go.mod h1:QhLRiCajhPG/2WwGgcsAqmlaYXX8KziNXtSe1BlRH+k= github.com/fclairamb/go-log v0.3.0 h1:oSC7Zjt0FZIYC5xXahUUycKGkypSdr2srFPLsp7CLd0= github.com/fclairamb/go-log v0.3.0/go.mod h1:XG61EiPlAXnPDN8SA4N3zeA+GyBJmVOCCo12WORx/gA= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fsnotify/fsnotify v1.5.3 h1:vNFpj2z7YIbwh2bw7x35sqYpp2wfuq+pivKbWG09B8c= -github.com/fsnotify/fsnotify v1.5.3/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= @@ -373,8 +386,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk= github.com/google/go-replayers/httpreplay v1.1.1/go.mod h1:gN9GeLIs7l6NUoVaSSnv2RiqK1NiwAmD0MrKeC9IIks= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -526,8 +540,9 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 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 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.2 h1:3WH+AG7s2+T8o3nrM/8u2rdqUEcQhmga7smjrT41nAw= +github.com/klauspost/compress v1.15.2/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.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -1086,8 +1101,9 @@ google.golang.org/api v0.69.0/go.mod h1:boanBiw+h5c3s+tBPgEzLDRHfFLWV0qXxRHz3ws7 google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0 h1:0AYh/ae6l9TDUvIQrDw5QRpM100P6oHgD+o3dYHMzJg= google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.76.0 h1:UkZl25bR1FHNqtK/EKs3vCdpZtUO6gea3YElTwc8pQg= +google.golang.org/api v0.76.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1187,8 +1203,8 @@ google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731 h1:nquqdM9+ps0JZcIiI70+tqoaIFS5Ql4ZuK8UXnz3HfE= -google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 h1:G1IeWbjrqEq9ChWxEuRPJu6laA67+XgTFHVSAvepr38= +google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= diff --git a/sftpd/handler.go b/sftpd/handler.go index f8c2a76c..9f48b8cc 100644 --- a/sftpd/handler.go +++ b/sftpd/handler.go @@ -8,6 +8,7 @@ import ( "time" "github.com/pkg/sftp" + "github.com/sftpgo/sdk" "github.com/drakkan/sftpgo/v2/common" "github.com/drakkan/sftpgo/v2/dataprovider" @@ -226,8 +227,8 @@ func (c *Connection) Filelist(request *sftp.Request) (sftp.ListerAt, error) { return listerAt([]os.FileInfo{s}), nil case "Readlink": - if !c.User.HasPerm(dataprovider.PermListItems, path.Dir(request.Filepath)) { - return nil, sftp.ErrSSHFxPermissionDenied + if err := c.canReadLink(request.Filepath); err != nil { + return nil, err } fs, p, err := c.GetFsAndResolvedPath(request.Filepath) @@ -241,12 +242,11 @@ func (c *Connection) Filelist(request *sftp.Request) (sftp.ListerAt, error) { return nil, c.GetFsError(fs, err) } - if !c.User.HasPerm(dataprovider.PermListItems, path.Dir(s)) { - return nil, sftp.ErrSSHFxPermissionDenied + if err := c.canReadLink(s); err != nil { + return nil, err } return listerAt([]os.FileInfo{vfs.NewFileInfo(s, false, 0, time.Now(), true)}), nil - default: return nil, sftp.ErrSSHFxOpUnsupported } @@ -300,6 +300,17 @@ func (c *Connection) StatVFS(r *sftp.Request) (*sftp.StatVFS, error) { return c.getStatVFSFromQuotaResult(fs, p, quotaResult), nil } +func (c *Connection) canReadLink(name string) error { + if !c.User.HasPerm(dataprovider.PermListItems, path.Dir(name)) { + return sftp.ErrSSHFxPermissionDenied + } + ok, policy := c.User.IsFileAllowed(name) + if !ok && policy == sdk.DenyPolicyHide { + return sftp.ErrSSHFxNoSuchFile + } + return nil +} + func (c *Connection) handleSFTPSetstat(request *sftp.Request) error { attrs := common.StatAttributes{ Flags: 0, diff --git a/sftpd/internal_test.go b/sftpd/internal_test.go index d6c74e92..6278f0fb 100644 --- a/sftpd/internal_test.go +++ b/sftpd/internal_test.go @@ -2195,3 +2195,34 @@ func TestMaxUserSessions(t *testing.T) { common.Connections.Remove(connection.GetID()) assert.Len(t, common.Connections.GetStats(), 0) } + +func TestCanReadSymlink(t *testing.T) { + connection := &Connection{ + BaseConnection: common.NewBaseConnection(xid.New().String(), common.ProtocolSFTP, "", "", dataprovider.User{ + BaseUser: sdk.BaseUser{ + Username: "user_can_read_symlink", + HomeDir: filepath.Clean(os.TempDir()), + Permissions: map[string][]string{ + "/": {dataprovider.PermAny}, + "/sub": {dataprovider.PermUpload}, + }, + }, + Filters: dataprovider.UserFilters{ + BaseUserFilters: sdk.BaseUserFilters{ + FilePatterns: []sdk.PatternsFilter{ + { + Path: "/denied", + DeniedPatterns: []string{"*.txt"}, + DenyPolicy: sdk.DenyPolicyHide, + }, + }, + }, + }, + }), + } + err := connection.canReadLink("/sub/link") + assert.ErrorIs(t, err, sftp.ErrSSHFxPermissionDenied) + + err = connection.canReadLink("/denied/file.txt") + assert.ErrorIs(t, err, sftp.ErrSSHFxNoSuchFile) +} diff --git a/sftpd/sftpd_test.go b/sftpd/sftpd_test.go index 0bcb2769..10c218f9 100644 --- a/sftpd/sftpd_test.go +++ b/sftpd/sftpd_test.go @@ -536,6 +536,7 @@ func TestBasicSFTPFsHandling(t *testing.T) { testFilePath := filepath.Join(homeBasePath, testFileName) testFileSize := int64(65535) testLinkName := testFileName + ".link" + testLinkToLinkName := testLinkName + ".link" expectedQuotaSize := testFileSize expectedQuotaFiles := 1 err = createTestFile(testFilePath, testFileSize) @@ -563,6 +564,28 @@ func TestBasicSFTPFsHandling(t *testing.T) { if assert.NoError(t, err) { assert.Equal(t, path.Join("/", testFileName), val) } + linkDir := "linkDir" + err = client.Mkdir(linkDir) + assert.NoError(t, err) + linkToLinkPath := path.Join(linkDir, testLinkToLinkName) + err = client.Symlink(testLinkName, linkToLinkPath) + assert.NoError(t, err) + info, err = client.Lstat(linkToLinkPath) + if assert.NoError(t, err) { + assert.True(t, info.Mode()&os.ModeSymlink != 0) + } + info, err = client.Stat(linkToLinkPath) + if assert.NoError(t, err) { + assert.True(t, info.Mode()&os.ModeSymlink == 0) + } + val, err = client.ReadLink(linkToLinkPath) + if assert.NoError(t, err) { + assert.Equal(t, path.Join("/", testLinkName), val) + } + err = client.Remove(linkToLinkPath) + assert.NoError(t, err) + err = client.RemoveDirectory(linkDir) + assert.NoError(t, err) err = client.Remove(testFileName) assert.NoError(t, err) _, err = client.Lstat(testFileName) @@ -607,6 +630,49 @@ func TestBasicSFTPFsHandling(t *testing.T) { assert.NoError(t, err) } +func TestSFTPFsEscapeHomeDir(t *testing.T) { + usePubKey := true + baseUser, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated) + assert.NoError(t, err) + u := getTestSFTPUser(usePubKey) + sftpPrefix := "/prefix" + u.FsConfig.SFTPConfig.Prefix = sftpPrefix + user, _, 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() + err = checkBasicSFTP(client) + assert.NoError(t, err) + dirName := "dir" + linkName := "link" + err := client.Mkdir(dirName) + assert.NoError(t, err) + err = os.Symlink(baseUser.GetHomeDir(), filepath.Join(baseUser.GetHomeDir(), sftpPrefix, dirName, linkName)) + assert.NoError(t, err) + err = os.Symlink(filepath.Join(baseUser.GetHomeDir(), sftpPrefix, dirName, linkName), + filepath.Join(baseUser.GetHomeDir(), sftpPrefix, linkName)) + assert.NoError(t, err) + // 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.ReadDir(linkName) + assert.ErrorIs(t, err, os.ErrPermission) + _, err = client.ReadDir(path.Join(dirName, linkName)) + assert.ErrorIs(t, err, os.ErrPermission) + _, err = client.ReadDir("/") + assert.NoError(t, err) + } + + _, err = httpdtest.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + _, err = httpdtest.RemoveUser(baseUser, http.StatusOK) + assert.NoError(t, err) + err = os.RemoveAll(baseUser.GetHomeDir()) + assert.NoError(t, err) +} + func TestGroupSettingsOverride(t *testing.T) { usePubKey := true g := getTestGroup() diff --git a/vfs/osfs.go b/vfs/osfs.go index bc7d2323..95068252 100644 --- a/vfs/osfs.go +++ b/vfs/osfs.go @@ -136,11 +136,17 @@ func (*OsFs) Symlink(source, target string) error { // Readlink returns the destination of the named symbolic link // as absolute virtual path func (fs *OsFs) Readlink(name string) (string, error) { - p, err := os.Readlink(name) + // we don't have to follow multiple links: + // https://github.com/openssh/openssh-portable/blob/7bf2eb958fbb551e7d61e75c176bb3200383285d/sftp-server.c#L1329 + resolved, err := os.Readlink(name) if err != nil { - return p, err + return "", err } - return fs.GetRelativePath(p), err + resolved = filepath.Clean(resolved) + if !filepath.IsAbs(resolved) { + resolved = filepath.Join(filepath.Dir(name), resolved) + } + return fs.GetRelativePath(resolved), nil } // Chown changes the numeric uid and gid of the named file. diff --git a/vfs/sftpfs.go b/vfs/sftpfs.go index 86c00976..8ef72f6d 100644 --- a/vfs/sftpfs.go +++ b/vfs/sftpfs.go @@ -362,7 +362,16 @@ func (fs *SFTPFs) Readlink(name string) (string, error) { if err := fs.checkConnection(); err != nil { return "", err } - return fs.sftpClient.ReadLink(name) + resolved, err := fs.sftpClient.ReadLink(name) + if err != nil { + return resolved, err + } + resolved = path.Clean(resolved) + if !path.IsAbs(resolved) { + // we assume that multiple links are not followed + resolved = path.Join(path.Dir(name), resolved) + } + return fs.GetRelativePath(resolved), nil } // Chown changes the numeric uid and gid of the named file. @@ -577,14 +586,30 @@ func (fs *SFTPFs) ResolvePath(virtualPath string) (string, error) { // getRealPath returns the real remote path trying to resolve symbolic links if any func (fs *SFTPFs) getRealPath(name string) (string, error) { - info, err := fs.sftpClient.Lstat(name) - if err != nil { - return name, err + linksWalked := 0 + for { + info, err := fs.sftpClient.Lstat(name) + if err != nil { + return name, err + } + if info.Mode()&os.ModeSymlink == 0 { + return name, nil + } + resolvedLink, err := fs.sftpClient.ReadLink(name) + if err != nil { + return name, err + } + resolvedLink = path.Clean(resolvedLink) + if path.IsAbs(resolvedLink) { + name = resolvedLink + } else { + name = path.Join(path.Dir(name), resolvedLink) + } + linksWalked++ + if linksWalked > 10 { + return "", &pathResolutionError{err: "too many links"} + } } - if info.Mode()&os.ModeSymlink != 0 { - return fs.sftpClient.ReadLink(name) - } - return name, err } func (fs *SFTPFs) isSubDir(name string) error {