From 4748e6f54d6ce78a4288a2beee3a06110ec1b9cc Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Mon, 31 Aug 2020 06:45:22 +0200 Subject: [PATCH] sftpd: handle read and write from the same handle (#158) Fixes #155 --- go.mod | 20 ++++++----- go.sum | 46 ++++++++++++++----------- sftpd/handler.go | 35 +++++++++++++++---- sftpd/internal_test.go | 77 ++++++++++++++++++++++++++++++++++-------- sftpd/scp.go | 4 +-- sftpd/sftpd_test.go | 45 ++++++++++++++++++++++++ sftpd/ssh_cmd.go | 6 ++-- sftpd/transfer.go | 34 +++++++++++++++++-- 8 files changed, 209 insertions(+), 58 deletions(-) diff --git a/go.mod b/go.mod index 0339fb7e..c8fca85b 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/drakkan/sftpgo go 1.13 require ( - cloud.google.com/go v0.64.0 // indirect - cloud.google.com/go/storage v1.10.0 + cloud.google.com/go v0.65.0 // indirect + cloud.google.com/go/storage v1.11.0 github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b - github.com/aws/aws-sdk-go v1.34.10 + github.com/aws/aws-sdk-go v1.34.13 github.com/eikenb/pipeat v0.0.0-20200430215831-470df5986b6d github.com/fclairamb/ftpserverlib v0.8.1-0.20200824203441-87a3864e6de5 github.com/fsnotify/fsnotify v1.4.9 // indirect @@ -17,14 +17,15 @@ require ( github.com/grandcat/zeroconf v1.0.0 github.com/jlaffaye/ftp v0.0.0-20200720194710-13949d38913e github.com/lib/pq v1.8.0 - github.com/mattn/go-sqlite3 v1.14.1 + github.com/magiconair/properties v1.8.2 // indirect + github.com/mattn/go-sqlite3 v1.14.2 github.com/miekg/dns v1.1.31 // indirect github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/nathanaelle/password/v2 v2.0.1 github.com/otiai10/copy v1.2.0 github.com/pelletier/go-toml v1.8.0 // indirect github.com/pires/go-proxyproto v0.1.3 - github.com/pkg/sftp v1.11.1-0.20200825160622-06ab92ee3917 + github.com/pkg/sftp v1.12.0 github.com/prometheus/client_golang v1.7.1 github.com/prometheus/common v0.13.0 // indirect github.com/rs/cors v1.7.1-0.20200626170627-8b4a00bd362b @@ -41,15 +42,18 @@ require ( go.etcd.io/bbolt v1.3.5 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/net v0.0.0-20200822124328-c89045814202 - golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 - golang.org/x/tools v0.0.0-20200823205832-c024452afbcd // indirect + golang.org/x/sys v0.0.0-20200828194041-157a740278f4 + golang.org/x/tools v0.0.0-20200828161849-5deb26317202 // indirect google.golang.org/api v0.30.0 - gopkg.in/ini.v1 v1.60.1 // indirect + google.golang.org/genproto v0.0.0-20200829155447-2bf3329a0021 // indirect + google.golang.org/grpc v1.31.1 // indirect + gopkg.in/ini.v1 v1.60.2 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) replace ( github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20200730125632-b21eac28818c + github.com/pkg/sftp => github.com/drakkan/sftp v0.0.0-20200830084022-ea67d57ce589 golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20200824205004-9f5ce89c1796 golang.org/x/net => github.com/drakkan/net v0.0.0-20200824204746-8b31adf087bf ) diff --git a/go.sum b/go.sum index e103f302..dbd53561 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,9 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.64.0 h1:xVP3LPvMjGT4J0a55y02Gw5y/dkY/rxGz58sfK1jqIo= cloud.google.com/go v0.64.0/go.mod h1:xfORb36jGvE+6EexW71nMEtL025s3x6xvuYUKM4JLv4= +cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -31,8 +32,9 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.11.0 h1:bSLyzhbGjLMYxCratCDRSSH7+xRGpNApTBmowDUFGLk= +cloud.google.com/go/storage v1.11.0/go.mod h1:/PAbprKS+5msVYogBmczjWalDXnQ9mr64yEq9YnyPeo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -60,8 +62,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.34.10 h1:VU78gcf/3wA4HNEDCHidK738l7K0Bals4SJnfnvXOtY= -github.com/aws/aws-sdk-go v1.34.10/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.34.13 h1:wwNWSUh4FGJxXVOVVNj2lWI8wTe5hK8sGWlK7ziEcgg= +github.com/aws/aws-sdk-go v1.34.13/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -109,6 +111,8 @@ github.com/drakkan/ftp v0.0.0-20200730125632-b21eac28818c h1:QSXIWohSNn0negBVSKE github.com/drakkan/ftp v0.0.0-20200730125632-b21eac28818c/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU= github.com/drakkan/net v0.0.0-20200824204746-8b31adf087bf h1:MbeUXErR+xQ1Yvk+E6wYBKvgK8nvDiXk00jNEyDRvE8= github.com/drakkan/net v0.0.0-20200824204746-8b31adf087bf/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +github.com/drakkan/sftp v0.0.0-20200830084022-ea67d57ce589 h1:iM/xecWdhSbPHaFnsnTsZg0trWuCFxQ9GUE7ZPstvko= +github.com/drakkan/sftp v0.0.0-20200830084022-ea67d57ce589/go.mod h1:fUqqXB5vEgVCZ131L+9say31RAri6aF6KDViawhxKK8= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= @@ -284,15 +288,16 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.2 h1:znVR8Q4g7/WlcvsxLBRWvo+vtFJUAbDn3w+Yak2xVMI= +github.com/magiconair/properties v1.8.2/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-sqlite3 v1.14.1 h1:AHx9Ra40wIzl+GelgX2X6AWxmT5tfxhI1PL0523HcSw= -github.com/mattn/go-sqlite3 v1.14.1/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/mattn/go-sqlite3 v1.14.2 h1:A2EQLwjYf/hfYaM20FVjs1UewCTTFR7RmjEHkLjldIA= +github.com/mattn/go-sqlite3 v1.14.2/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -364,9 +369,6 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pkg/sftp v1.11.1-0.20200825160622-06ab92ee3917 h1:NO+7wv5cAXYMhTpVX0e97zH349VVp8c2dB0d/SXfmkg= -github.com/pkg/sftp v1.11.1-0.20200825160622-06ab92ee3917/go.mod h1:i24A96cQ6ZvWut9G/Uv3LvC4u3VebGsBR5JFvPyChLc= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -576,10 +578,9 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= -golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200828194041-157a740278f4 h1:kCCpuwSAoYJPkNc6x0xT9yTtV4oKtARo4RGBQWOfg9E= +golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -639,7 +640,9 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200823205832-c024452afbcd/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200827163409-021d7c6f1ec3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -698,8 +701,11 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70 h1:wboULUXGF3c5qdUnKp+6gLAccE6PRpa/czkYvQ4UXv8= google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200827165113-ac2560b5e952/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200829155447-2bf3329a0021 h1:14sCoAL+O3izDMSeixcDn4kLi+JrAqQ42r8XD3oYePk= +google.golang.org/genproto v0.0.0-20200829155447-2bf3329a0021/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -716,8 +722,9 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1 h1:SfXqXS5hkufcdZ/mHtYCh53P2b+92WQq/DZcKLgsFRs= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -741,8 +748,8 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.60.1 h1:P5y5shSkb0CFe44qEeMBgn8JLow09MP17jlJHanke5g= -gopkg.in/ini.v1 v1.60.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.60.2 h1:7i8mqModL63zqi8nQn8Q3+0zvSCZy1AxhBgthKfi4WU= +gopkg.in/ini.v1 v1.60.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= @@ -755,9 +762,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/sftpd/handler.go b/sftpd/handler.go index c5b5f82f..b6dd4fc0 100644 --- a/sftpd/handler.go +++ b/sftpd/handler.go @@ -74,13 +74,22 @@ func (c *Connection) Fileread(request *sftp.Request) (io.ReaderAt, error) { baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, p, request.Filepath, common.TransferDownload, 0, 0, 0, false, c.Fs) - t := newTransfer(baseTransfer, nil, r) + t := newTransfer(baseTransfer, nil, r, nil) return t, nil } +// OpenFile implements OpenFileWriter interface +func (c *Connection) OpenFile(request *sftp.Request) (sftp.WriterAtReaderAt, error) { + return c.handleFilewrite(request) +} + // Filewrite handles the write actions for a file on the system. func (c *Connection) Filewrite(request *sftp.Request) (io.WriterAt, error) { + return c.handleFilewrite(request) +} + +func (c *Connection) handleFilewrite(request *sftp.Request) (sftp.WriterAtReaderAt, error) { c.UpdateLastActivity() if !c.User.IsFileAllowed(request.Filepath) { @@ -98,12 +107,24 @@ func (c *Connection) Filewrite(request *sftp.Request) (io.WriterAt, error) { filePath = c.Fs.GetAtomicUploadPath(p) } + var errForRead error + if !vfs.IsLocalOsFs(c.Fs) && request.Pflags().Read { + // read and write mode is only supported for local filesystem + errForRead = sftp.ErrSSHFxOpUnsupported + } + if !c.User.HasPerm(dataprovider.PermDownload, path.Dir(request.Filepath)) { + // we can try to read only for local fs here, see above. + // os.ErrPermission will become sftp.ErrSSHFxPermissionDenied when sent to + // the client + errForRead = os.ErrPermission + } + stat, statErr := c.Fs.Lstat(p) if (statErr == nil && stat.Mode()&os.ModeSymlink == os.ModeSymlink) || c.Fs.IsNotExist(statErr) { if !c.User.HasPerm(dataprovider.PermUpload, path.Dir(request.Filepath)) { return nil, sftp.ErrSSHFxPermissionDenied } - return c.handleSFTPUploadToNewFile(p, filePath, request.Filepath) + return c.handleSFTPUploadToNewFile(p, filePath, request.Filepath, errForRead) } if statErr != nil { @@ -121,7 +142,7 @@ func (c *Connection) Filewrite(request *sftp.Request) (io.WriterAt, error) { return nil, sftp.ErrSSHFxPermissionDenied } - return c.handleSFTPUploadToExistingFile(request.Pflags(), p, filePath, stat.Size(), request.Filepath) + return c.handleSFTPUploadToExistingFile(request.Pflags(), p, filePath, stat.Size(), request.Filepath, errForRead) } // Filecmd hander for basic SFTP system calls related to files, but not anything to do with reading @@ -272,7 +293,7 @@ func (c *Connection) handleSFTPRemove(filePath string, request *sftp.Request) er return c.RemoveFile(filePath, request.Filepath, fi) } -func (c *Connection) handleSFTPUploadToNewFile(resolvedPath, filePath, requestPath string) (io.WriterAt, error) { +func (c *Connection) handleSFTPUploadToNewFile(resolvedPath, filePath, requestPath string, errForRead error) (sftp.WriterAtReaderAt, error) { quotaResult := c.HasSpace(true, requestPath) if !quotaResult.HasSpace { c.Log(logger.LevelInfo, "denying file write due to quota limits") @@ -292,13 +313,13 @@ func (c *Connection) handleSFTPUploadToNewFile(resolvedPath, filePath, requestPa baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, resolvedPath, requestPath, common.TransferUpload, 0, 0, maxWriteSize, true, c.Fs) - t := newTransfer(baseTransfer, w, nil) + t := newTransfer(baseTransfer, w, nil, errForRead) return t, nil } func (c *Connection) handleSFTPUploadToExistingFile(pflags sftp.FileOpenFlags, resolvedPath, filePath string, - fileSize int64, requestPath string) (io.WriterAt, error) { + fileSize int64, requestPath string, errForRead error) (sftp.WriterAtReaderAt, error) { var err error quotaResult := c.HasSpace(false, requestPath) if !quotaResult.HasSpace { @@ -363,7 +384,7 @@ func (c *Connection) handleSFTPUploadToExistingFile(pflags sftp.FileOpenFlags, r baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, resolvedPath, requestPath, common.TransferUpload, minWriteOffset, initialSize, maxWriteSize, false, c.Fs) - t := newTransfer(baseTransfer, w, nil) + t := newTransfer(baseTransfer, w, nil, errForRead) return t, nil } diff --git a/sftpd/internal_test.go b/sftpd/internal_test.go index 79019892..94ad6ca4 100644 --- a/sftpd/internal_test.go +++ b/sftpd/internal_test.go @@ -16,6 +16,7 @@ import ( "github.com/eikenb/pipeat" "github.com/pkg/sftp" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" "github.com/drakkan/sftpgo/common" @@ -159,7 +160,7 @@ func TestUploadResumeInvalidOffset(t *testing.T) { fs := vfs.NewOsFs("", os.TempDir(), nil) conn := common.NewBaseConnection("", common.ProtocolSFTP, user, fs) baseTransfer := common.NewBaseTransfer(file, conn, nil, file.Name(), testfile, common.TransferUpload, 10, 0, 0, false, fs) - transfer := newTransfer(baseTransfer, nil, nil) + transfer := newTransfer(baseTransfer, nil, nil, nil) _, err = transfer.WriteAt([]byte("test"), 0) assert.Error(t, err, "upload with invalid offset must fail") if assert.Error(t, transfer.ErrTransfer) { @@ -187,7 +188,7 @@ func TestReadWriteErrors(t *testing.T) { fs := vfs.NewOsFs("", os.TempDir(), nil) conn := common.NewBaseConnection("", common.ProtocolSFTP, user, fs) baseTransfer := common.NewBaseTransfer(file, conn, nil, file.Name(), testfile, common.TransferDownload, 0, 0, 0, false, fs) - transfer := newTransfer(baseTransfer, nil, nil) + transfer := newTransfer(baseTransfer, nil, nil, nil) err = file.Close() assert.NoError(t, err) _, err = transfer.WriteAt([]byte("test"), 0) @@ -201,8 +202,8 @@ func TestReadWriteErrors(t *testing.T) { r, _, err := pipeat.Pipe() assert.NoError(t, err) baseTransfer = common.NewBaseTransfer(nil, conn, nil, file.Name(), testfile, common.TransferDownload, 0, 0, 0, false, fs) - transfer = newTransfer(baseTransfer, nil, r) - err = transfer.closeIO() + transfer = newTransfer(baseTransfer, nil, r, nil) + err = transfer.Close() assert.NoError(t, err) _, err = transfer.ReadAt(buf, 0) assert.Error(t, err, "reading from a closed pipe must fail") @@ -211,7 +212,7 @@ func TestReadWriteErrors(t *testing.T) { assert.NoError(t, err) pipeWriter := vfs.NewPipeWriter(w) baseTransfer = common.NewBaseTransfer(nil, conn, nil, file.Name(), testfile, common.TransferDownload, 0, 0, 0, false, fs) - transfer = newTransfer(baseTransfer, pipeWriter, nil) + transfer = newTransfer(baseTransfer, pipeWriter, nil, nil) err = r.Close() assert.NoError(t, err) @@ -224,9 +225,12 @@ func TestReadWriteErrors(t *testing.T) { assert.EqualError(t, err, errFake.Error()) _, err = transfer.WriteAt([]byte("test"), 0) assert.Error(t, err, "writing to closed pipe must fail") + err = transfer.BaseTransfer.Close() + assert.EqualError(t, err, errFake.Error()) err = os.Remove(testfile) assert.NoError(t, err) + assert.Len(t, conn.GetTransfers(), 0) } func TestUnsupportedListOP(t *testing.T) { @@ -254,7 +258,7 @@ func TestTransferCancelFn(t *testing.T) { fs := vfs.NewOsFs("", os.TempDir(), nil) conn := common.NewBaseConnection("", common.ProtocolSFTP, user, fs) baseTransfer := common.NewBaseTransfer(file, conn, cancelFn, file.Name(), testfile, common.TransferDownload, 0, 0, 0, false, fs) - transfer := newTransfer(baseTransfer, nil, nil) + transfer := newTransfer(baseTransfer, nil, nil, nil) errFake := errors.New("fake error, this will trigger cancelFn") transfer.TransferError(errFake) @@ -293,7 +297,7 @@ func TestMockFsErrors(t *testing.T) { flags.Write = true flags.Trunc = false flags.Append = true - _, err = c.handleSFTPUploadToExistingFile(flags, testfile, testfile, 0, "/testfile") + _, err = c.handleSFTPUploadToExistingFile(flags, testfile, testfile, 0, "/testfile", nil) assert.EqualError(t, err, sftp.ErrSSHFxOpUnsupported.Error()) fs = newMockOsFs(errFake, nil, false, "123", os.TempDir()) @@ -321,18 +325,18 @@ func TestUploadFiles(t *testing.T) { var flags sftp.FileOpenFlags flags.Write = true flags.Trunc = true - _, err := c.handleSFTPUploadToExistingFile(flags, "missing_path", "other_missing_path", 0, "/missing_path") + _, err := c.handleSFTPUploadToExistingFile(flags, "missing_path", "other_missing_path", 0, "/missing_path", nil) assert.Error(t, err, "upload to existing file must fail if one or both paths are invalid") common.Config.UploadMode = common.UploadModeStandard - _, err = c.handleSFTPUploadToExistingFile(flags, "missing_path", "other_missing_path", 0, "/missing_path") + _, err = c.handleSFTPUploadToExistingFile(flags, "missing_path", "other_missing_path", 0, "/missing_path", nil) assert.Error(t, err, "upload to existing file must fail if one or both paths are invalid") missingFile := "missing/relative/file.txt" if runtime.GOOS == osWindows { missingFile = "missing\\relative\\file.txt" } - _, err = c.handleSFTPUploadToNewFile(".", missingFile, "/missing") + _, err = c.handleSFTPUploadToNewFile(".", missingFile, "/missing", nil) assert.Error(t, err, "upload new file in missing path must fail") c.BaseConnection.Fs = newMockOsFs(nil, nil, false, "123", os.TempDir()) @@ -341,7 +345,7 @@ func TestUploadFiles(t *testing.T) { err = f.Close() assert.NoError(t, err) - tr, err := c.handleSFTPUploadToExistingFile(flags, f.Name(), f.Name(), 123, f.Name()) + tr, err := c.handleSFTPUploadToExistingFile(flags, f.Name(), f.Name(), 123, f.Name(), nil) if assert.NoError(t, err) { transfer := tr.(*transfer) transfers := c.GetTransfers() @@ -990,7 +994,7 @@ func TestSystemCommandErrors(t *testing.T) { sshCmd.connection.channel = &mockSSHChannel baseTransfer := common.NewBaseTransfer(nil, sshCmd.connection.BaseConnection, nil, "", "", common.TransferDownload, 0, 0, 0, false, fs) - transfer := newTransfer(baseTransfer, nil, nil) + transfer := newTransfer(baseTransfer, nil, nil, nil) destBuff := make([]byte, 65535) dst := bytes.NewBuffer(destBuff) _, err = transfer.copyFromReaderToWriter(dst, sshCmd.connection.channel) @@ -1542,7 +1546,7 @@ func TestSCPUploadFiledata(t *testing.T) { baseTransfer := common.NewBaseTransfer(file, scpCommand.connection.BaseConnection, nil, file.Name(), "/"+testfile, common.TransferDownload, 0, 0, 0, true, fs) - transfer := newTransfer(baseTransfer, nil, nil) + transfer := newTransfer(baseTransfer, nil, nil, nil) err = scpCommand.getUploadFileData(2, transfer) assert.Error(t, err, "upload must fail, we send a fake write error message") @@ -1574,7 +1578,7 @@ func TestSCPUploadFiledata(t *testing.T) { file, err = os.Create(testfile) assert.NoError(t, err) baseTransfer.File = file - transfer = newTransfer(baseTransfer, nil, nil) + transfer = newTransfer(baseTransfer, nil, nil, nil) transfer.Connection.AddTransfer(transfer) err = scpCommand.getUploadFileData(2, transfer) assert.Error(t, err, "upload must fail, we have not enough data to read") @@ -1626,7 +1630,7 @@ func TestUploadError(t *testing.T) { assert.NoError(t, err) baseTransfer := common.NewBaseTransfer(file, connection.BaseConnection, nil, testfile, testfile, common.TransferUpload, 0, 0, 0, true, fs) - transfer := newTransfer(baseTransfer, nil, nil) + transfer := newTransfer(baseTransfer, nil, nil, nil) errFake := errors.New("fake error") transfer.TransferError(errFake) @@ -1645,6 +1649,49 @@ func TestUploadError(t *testing.T) { common.Config.UploadMode = oldUploadMode } +func TestTransferFailingReader(t *testing.T) { + user := dataprovider.User{ + Username: "testuser", + } + user.Permissions = make(map[string][]string) + user.Permissions["/"] = []string{dataprovider.PermAny} + + fs := newMockOsFs(nil, nil, true, "", os.TempDir()) + connection := &Connection{ + BaseConnection: common.NewBaseConnection("", common.ProtocolSFTP, user, fs), + } + + request := sftp.NewRequest("Open", "afile.txt") + request.Flags = 27 // read,write,create,truncate + + transfer, err := connection.handleFilewrite(request) + require.NoError(t, err) + buf := make([]byte, 32) + _, err = transfer.ReadAt(buf, 0) + assert.EqualError(t, err, sftp.ErrSSHFxOpUnsupported.Error()) + if c, ok := transfer.(io.Closer); ok { + err = c.Close() + assert.EqualError(t, err, sftp.ErrSSHFxFailure.Error()) + } + + fsPath := filepath.Join(os.TempDir(), "afile.txt") + + r, _, err := pipeat.Pipe() + assert.NoError(t, err) + baseTransfer := common.NewBaseTransfer(nil, connection.BaseConnection, nil, fsPath, filepath.Base(fsPath), common.TransferUpload, 0, 0, 0, false, fs) + errRead := errors.New("read is not allowed") + tr := newTransfer(baseTransfer, nil, r, errRead) + _, err = tr.ReadAt(buf, 0) + assert.EqualError(t, err, errRead.Error()) + + err = tr.Close() + assert.EqualError(t, err, sftp.ErrSSHFxFailure.Error()) + + err = os.Remove(fsPath) + assert.NoError(t, err) + assert.Len(t, connection.GetTransfers(), 0) +} + func TestConnectionStatusStruct(t *testing.T) { var transfers []common.ConnectionTransfer transferUL := common.ConnectionTransfer{ diff --git a/sftpd/scp.go b/sftpd/scp.go index 304cd9f7..1a8c3407 100644 --- a/sftpd/scp.go +++ b/sftpd/scp.go @@ -227,7 +227,7 @@ func (c *scpCommand) handleUploadFile(resolvedPath, filePath string, sizeToRead baseTransfer := common.NewBaseTransfer(file, c.connection.BaseConnection, cancelFn, resolvedPath, requestPath, common.TransferUpload, 0, initialSize, maxWriteSize, isNewFile, c.connection.Fs) - t := newTransfer(baseTransfer, w, nil) + t := newTransfer(baseTransfer, w, nil, nil) return c.getUploadFileData(sizeToRead, t) } @@ -485,7 +485,7 @@ func (c *scpCommand) handleDownload(filePath string) error { baseTransfer := common.NewBaseTransfer(file, c.connection.BaseConnection, cancelFn, p, filePath, common.TransferDownload, 0, 0, 0, false, c.connection.Fs) - t := newTransfer(baseTransfer, nil, r) + t := newTransfer(baseTransfer, nil, r, nil) err = c.sendDownloadFileData(p, stat, t) // we need to call Close anyway and return close error if any and diff --git a/sftpd/sftpd_test.go b/sftpd/sftpd_test.go index 527ddfc2..d0f0d45f 100644 --- a/sftpd/sftpd_test.go +++ b/sftpd/sftpd_test.go @@ -359,6 +359,51 @@ func TestBasicSFTPHandling(t *testing.T) { assert.NoError(t, err) } +func TestOpenReadWrite(t *testing.T) { + usePubKey := false + u := getTestUser(usePubKey) + u.QuotaSize = 6553600 + user, _, err := httpd.AddUser(u, http.StatusOK) + assert.NoError(t, err) + err = os.RemoveAll(user.GetHomeDir()) + assert.NoError(t, err) + client, err := getSftpClient(user, usePubKey) + if assert.NoError(t, err) { + defer client.Close() + sftpFile, err := client.OpenFile(testFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC) + if assert.NoError(t, err) { + testData := []byte("test data") + n, err := sftpFile.Write(testData) + assert.NoError(t, err) + assert.Equal(t, len(testData), n) + buffer := make([]byte, 128) + n, err = sftpFile.ReadAt(buffer, 1) + assert.EqualError(t, err, io.EOF.Error()) + assert.Equal(t, len(testData)-1, n) + assert.Equal(t, testData[1:], buffer[:n]) + sftpFile.Close() + } + sftpFile, err = client.OpenFile(testFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC) + if assert.NoError(t, err) { + testData := []byte("new test data") + n, err := sftpFile.Write(testData) + assert.NoError(t, err) + assert.Equal(t, len(testData), n) + buffer := make([]byte, 128) + n, err = sftpFile.ReadAt(buffer, 1) + assert.EqualError(t, err, io.EOF.Error()) + assert.Equal(t, len(testData)-1, n) + assert.Equal(t, testData[1:], buffer[:n]) + sftpFile.Close() + sftpFile.Close() + } + } + _, err = httpd.RemoveUser(user, http.StatusOK) + assert.NoError(t, err) + err = os.RemoveAll(user.GetHomeDir()) + assert.NoError(t, err) +} + func TestConcurrency(t *testing.T) { usePubKey := true numLogins := 50 diff --git a/sftpd/ssh_cmd.go b/sftpd/ssh_cmd.go index 68145df2..fbd58b22 100644 --- a/sftpd/ssh_cmd.go +++ b/sftpd/ssh_cmd.go @@ -354,7 +354,7 @@ func (c *sshCommand) executeSystemCommand(command systemCommand) error { defer stdin.Close() baseTransfer := common.NewBaseTransfer(nil, c.connection.BaseConnection, nil, command.fsPath, sshDestPath, common.TransferUpload, 0, 0, remainingQuotaSize, false, c.connection.Fs) - transfer := newTransfer(baseTransfer, nil, nil) + transfer := newTransfer(baseTransfer, nil, nil, nil) w, e := transfer.copyFromReaderToWriter(stdin, c.connection.channel) c.connection.Log(logger.LevelDebug, "command: %#v, copy from remote command to sdtin ended, written: %v, "+ @@ -367,7 +367,7 @@ func (c *sshCommand) executeSystemCommand(command systemCommand) error { go func() { baseTransfer := common.NewBaseTransfer(nil, c.connection.BaseConnection, nil, command.fsPath, sshDestPath, common.TransferDownload, 0, 0, 0, false, c.connection.Fs) - transfer := newTransfer(baseTransfer, nil, nil) + transfer := newTransfer(baseTransfer, nil, nil, nil) w, e := transfer.copyFromReaderToWriter(c.connection.channel, stdout) c.connection.Log(logger.LevelDebug, "command: %#v, copy from sdtout to remote command ended, written: %v err: %v", @@ -381,7 +381,7 @@ func (c *sshCommand) executeSystemCommand(command systemCommand) error { go func() { baseTransfer := common.NewBaseTransfer(nil, c.connection.BaseConnection, nil, command.fsPath, sshDestPath, common.TransferDownload, 0, 0, 0, false, c.connection.Fs) - transfer := newTransfer(baseTransfer, nil, nil) + transfer := newTransfer(baseTransfer, nil, nil, nil) w, e := transfer.copyFromReaderToWriter(c.connection.channel.Stderr(), stderr) c.connection.Log(logger.LevelDebug, "command: %#v, copy from sdterr to remote command ended, written: %v err: %v", diff --git a/sftpd/transfer.go b/sftpd/transfer.go index 43b80771..110817d8 100644 --- a/sftpd/transfer.go +++ b/sftpd/transfer.go @@ -22,6 +22,19 @@ type readerAtCloser interface { io.Closer } +type failingReader struct { + innerReader readerAtCloser + errRead error +} + +func (r *failingReader) ReadAt(p []byte, off int64) (n int, err error) { + return 0, r.errRead +} + +func (r *failingReader) Close() error { + return r.innerReader.Close() +} + // transfer defines the transfer details. // It implements the io.ReaderAt and io.WriterAt interfaces to handle SFTP downloads and uploads type transfer struct { @@ -31,16 +44,31 @@ type transfer struct { isFinished bool } -func newTransfer(baseTransfer *common.BaseTransfer, pipeWriter *vfs.PipeWriter, pipeReader *pipeat.PipeReaderAt) *transfer { +func newTransfer(baseTransfer *common.BaseTransfer, pipeWriter *vfs.PipeWriter, pipeReader *pipeat.PipeReaderAt, + errForRead error) *transfer { var writer writerAtCloser var reader readerAtCloser if baseTransfer.File != nil { writer = baseTransfer.File - reader = baseTransfer.File + if errForRead == nil { + reader = baseTransfer.File + } else { + reader = &failingReader{ + innerReader: baseTransfer.File, + errRead: errForRead, + } + } } else if pipeWriter != nil { writer = pipeWriter } else if pipeReader != nil { - reader = pipeReader + if errForRead == nil { + reader = pipeReader + } else { + reader = &failingReader{ + innerReader: pipeReader, + errRead: errForRead, + } + } } return &transfer{ BaseTransfer: baseTransfer,