forked from TrueCloudLab/rclone
Switch to using the dep tool and update all the dependencies
This commit is contained in:
parent
5135ff73cb
commit
98c2d2c41b
5321 changed files with 4483201 additions and 5922 deletions
12
vendor/github.com/pkg/sftp/.travis.yml
generated
vendored
12
vendor/github.com/pkg/sftp/.travis.yml
generated
vendored
|
@ -1,15 +1,23 @@
|
|||
language: go
|
||||
go_import_path: github.com/pkg/sftp
|
||||
|
||||
# current and previous stable releases, and tip
|
||||
go:
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- tip
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
go: 1.7.x
|
||||
- os: osx
|
||||
go: tip
|
||||
|
||||
sudo: false
|
||||
|
||||
addons:
|
||||
|
|
12
vendor/github.com/pkg/sftp/README.md
generated
vendored
12
vendor/github.com/pkg/sftp/README.md
generated
vendored
|
@ -36,13 +36,9 @@ issue.
|
|||
For API/code bugs, please include a small, self contained code example to
|
||||
reproduce the issue. For pull requests, remember test coverage.
|
||||
|
||||
We handle issues and pull requests with a 0 open philosophy. That means we will
|
||||
try to address the submission as soon as possible and will work toward a
|
||||
resolution. If progress can no longer be made (eg. unreproducible bug) or stops
|
||||
(eg. unresponsive submitter), we will close the bug.
|
||||
|
||||
Please remember that we don't have infinite time and so it is a good guideline
|
||||
that the less time it takes us to work with you on your idea/issue the greater
|
||||
the chance we will have the time to do it.
|
||||
We try to handle issues and pull requests with a 0 open philosophy. That means
|
||||
we will try to address the submission as soon as possible and will work toward
|
||||
a resolution. If progress can no longer be made (eg. unreproducible bug) or
|
||||
stops (eg. unresponsive submitter), we will close the bug.
|
||||
|
||||
Thanks.
|
||||
|
|
45
vendor/github.com/pkg/sftp/attrs_test.go
generated
vendored
Normal file
45
vendor/github.com/pkg/sftp/attrs_test.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ensure that attrs implemenst os.FileInfo
|
||||
var _ os.FileInfo = new(fileInfo)
|
||||
|
||||
var unmarshalAttrsTests = []struct {
|
||||
b []byte
|
||||
want *fileInfo
|
||||
rest []byte
|
||||
}{
|
||||
{marshal(nil, struct{ Flags uint32 }{}), &fileInfo{mtime: time.Unix(int64(0), 0)}, nil},
|
||||
{marshal(nil, struct {
|
||||
Flags uint32
|
||||
Size uint64
|
||||
}{ssh_FILEXFER_ATTR_SIZE, 20}), &fileInfo{size: 20, mtime: time.Unix(int64(0), 0)}, nil},
|
||||
{marshal(nil, struct {
|
||||
Flags uint32
|
||||
Size uint64
|
||||
Permissions uint32
|
||||
}{ssh_FILEXFER_ATTR_SIZE | ssh_FILEXFER_ATTR_PERMISSIONS, 20, 0644}), &fileInfo{size: 20, mode: os.FileMode(0644), mtime: time.Unix(int64(0), 0)}, nil},
|
||||
{marshal(nil, struct {
|
||||
Flags uint32
|
||||
Size uint64
|
||||
UID, GID, Permissions uint32
|
||||
}{ssh_FILEXFER_ATTR_SIZE | ssh_FILEXFER_ATTR_UIDGID | ssh_FILEXFER_ATTR_UIDGID | ssh_FILEXFER_ATTR_PERMISSIONS, 20, 1000, 1000, 0644}), &fileInfo{size: 20, mode: os.FileMode(0644), mtime: time.Unix(int64(0), 0)}, nil},
|
||||
}
|
||||
|
||||
func TestUnmarshalAttrs(t *testing.T) {
|
||||
for _, tt := range unmarshalAttrsTests {
|
||||
stat, rest := unmarshalAttrs(tt.b)
|
||||
got := fileInfoFromStat(stat, "")
|
||||
tt.want.sys = got.Sys()
|
||||
if !reflect.DeepEqual(got, tt.want) || !bytes.Equal(tt.rest, rest) {
|
||||
t.Errorf("unmarshalAttrs(%#v): want %#v, %#v, got: %#v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
262
vendor/github.com/pkg/sftp/client.go
generated
vendored
262
vendor/github.com/pkg/sftp/client.go
generated
vendored
|
@ -203,7 +203,7 @@ func (c *Client) opendir(path string) (string, error) {
|
|||
handle, _ := unmarshalString(data)
|
||||
return handle, nil
|
||||
case ssh_FXP_STATUS:
|
||||
return "", unmarshalStatus(id, data)
|
||||
return "", normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return "", unimplementedPacketErr(typ)
|
||||
}
|
||||
|
@ -284,7 +284,7 @@ func (c *Client) ReadLink(p string) (string, error) {
|
|||
filename, _ := unmarshalString(data) // ignore dummy attributes
|
||||
return filename, nil
|
||||
case ssh_FXP_STATUS:
|
||||
return "", unmarshalStatus(id, data)
|
||||
return "", normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return "", unimplementedPacketErr(typ)
|
||||
}
|
||||
|
@ -439,7 +439,7 @@ func (c *Client) fstat(handle string) (*FileStat, error) {
|
|||
attr, _ := unmarshalAttrs(data)
|
||||
return attr, nil
|
||||
case ssh_FXP_STATUS:
|
||||
return nil, unmarshalStatus(id, data)
|
||||
return nil, normaliseError(unmarshalStatus(id, data))
|
||||
default:
|
||||
return nil, unimplementedPacketErr(typ)
|
||||
}
|
||||
|
@ -690,43 +690,39 @@ func (f *File) Read(b []byte) (int, error) {
|
|||
if inFlight == 0 {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case res := <-ch:
|
||||
inFlight--
|
||||
if res.err != nil {
|
||||
firstErr = offsetErr{offset: 0, err: res.err}
|
||||
break
|
||||
}
|
||||
reqID, data := unmarshalUint32(res.data)
|
||||
req, ok := reqs[reqID]
|
||||
if !ok {
|
||||
firstErr = offsetErr{offset: 0, err: errors.Errorf("sid: %v not found", reqID)}
|
||||
break
|
||||
}
|
||||
delete(reqs, reqID)
|
||||
switch res.typ {
|
||||
case ssh_FXP_STATUS:
|
||||
if firstErr.err == nil || req.offset < firstErr.offset {
|
||||
firstErr = offsetErr{
|
||||
offset: req.offset,
|
||||
err: normaliseError(unmarshalStatus(reqID, res.data)),
|
||||
}
|
||||
break
|
||||
res := <-ch
|
||||
inFlight--
|
||||
if res.err != nil {
|
||||
firstErr = offsetErr{offset: 0, err: res.err}
|
||||
continue
|
||||
}
|
||||
reqID, data := unmarshalUint32(res.data)
|
||||
req, ok := reqs[reqID]
|
||||
if !ok {
|
||||
firstErr = offsetErr{offset: 0, err: errors.Errorf("sid: %v not found", reqID)}
|
||||
continue
|
||||
}
|
||||
delete(reqs, reqID)
|
||||
switch res.typ {
|
||||
case ssh_FXP_STATUS:
|
||||
if firstErr.err == nil || req.offset < firstErr.offset {
|
||||
firstErr = offsetErr{
|
||||
offset: req.offset,
|
||||
err: normaliseError(unmarshalStatus(reqID, res.data)),
|
||||
}
|
||||
case ssh_FXP_DATA:
|
||||
l, data := unmarshalUint32(data)
|
||||
n := copy(req.b, data[:l])
|
||||
read += n
|
||||
if n < len(req.b) {
|
||||
sendReq(req.b[l:], req.offset+uint64(l))
|
||||
}
|
||||
if desiredInFlight < maxConcurrentRequests {
|
||||
desiredInFlight++
|
||||
}
|
||||
default:
|
||||
firstErr = offsetErr{offset: 0, err: unimplementedPacketErr(res.typ)}
|
||||
break
|
||||
}
|
||||
case ssh_FXP_DATA:
|
||||
l, data := unmarshalUint32(data)
|
||||
n := copy(req.b, data[:l])
|
||||
read += n
|
||||
if n < len(req.b) {
|
||||
sendReq(req.b[l:], req.offset+uint64(l))
|
||||
}
|
||||
if desiredInFlight < maxConcurrentRequests {
|
||||
desiredInFlight++
|
||||
}
|
||||
default:
|
||||
firstErr = offsetErr{offset: 0, err: unimplementedPacketErr(res.typ)}
|
||||
}
|
||||
}
|
||||
// If the error is anything other than EOF, then there
|
||||
|
@ -796,79 +792,75 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
|
|||
}
|
||||
break
|
||||
}
|
||||
select {
|
||||
case res := <-ch:
|
||||
inFlight--
|
||||
if res.err != nil {
|
||||
firstErr = offsetErr{offset: 0, err: res.err}
|
||||
break
|
||||
res := <-ch
|
||||
inFlight--
|
||||
if res.err != nil {
|
||||
firstErr = offsetErr{offset: 0, err: res.err}
|
||||
continue
|
||||
}
|
||||
reqID, data := unmarshalUint32(res.data)
|
||||
req, ok := reqs[reqID]
|
||||
if !ok {
|
||||
firstErr = offsetErr{offset: 0, err: errors.Errorf("sid: %v not found", reqID)}
|
||||
continue
|
||||
}
|
||||
delete(reqs, reqID)
|
||||
switch res.typ {
|
||||
case ssh_FXP_STATUS:
|
||||
if firstErr.err == nil || req.offset < firstErr.offset {
|
||||
firstErr = offsetErr{offset: req.offset, err: normaliseError(unmarshalStatus(reqID, res.data))}
|
||||
}
|
||||
reqID, data := unmarshalUint32(res.data)
|
||||
req, ok := reqs[reqID]
|
||||
if !ok {
|
||||
firstErr = offsetErr{offset: 0, err: errors.Errorf("sid: %v not found", reqID)}
|
||||
break
|
||||
}
|
||||
delete(reqs, reqID)
|
||||
switch res.typ {
|
||||
case ssh_FXP_STATUS:
|
||||
if firstErr.err == nil || req.offset < firstErr.offset {
|
||||
firstErr = offsetErr{offset: req.offset, err: normaliseError(unmarshalStatus(reqID, res.data))}
|
||||
case ssh_FXP_DATA:
|
||||
l, data := unmarshalUint32(data)
|
||||
if req.offset == writeOffset {
|
||||
nbytes, err := w.Write(data)
|
||||
copied += int64(nbytes)
|
||||
if err != nil {
|
||||
// We will never receive another DATA with offset==writeOffset, so
|
||||
// the loop will drain inFlight and then exit.
|
||||
firstErr = offsetErr{offset: req.offset + uint64(nbytes), err: err}
|
||||
break
|
||||
}
|
||||
case ssh_FXP_DATA:
|
||||
l, data := unmarshalUint32(data)
|
||||
if req.offset == writeOffset {
|
||||
nbytes, err := w.Write(data)
|
||||
copied += int64(nbytes)
|
||||
if nbytes < int(l) {
|
||||
firstErr = offsetErr{offset: req.offset + uint64(nbytes), err: io.ErrShortWrite}
|
||||
break
|
||||
}
|
||||
switch {
|
||||
case offset > fileSize:
|
||||
desiredInFlight = 1
|
||||
case desiredInFlight < maxConcurrentRequests:
|
||||
desiredInFlight++
|
||||
}
|
||||
writeOffset += uint64(nbytes)
|
||||
for {
|
||||
pendingData, ok := pendingWrites[writeOffset]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
// Give go a chance to free the memory.
|
||||
delete(pendingWrites, writeOffset)
|
||||
nbytes, err := w.Write(pendingData)
|
||||
// Do not move writeOffset on error so subsequent iterations won't trigger
|
||||
// any writes.
|
||||
if err != nil {
|
||||
// We will never receive another DATA with offset==writeOffset, so
|
||||
// the loop will drain inFlight and then exit.
|
||||
firstErr = offsetErr{offset: req.offset + uint64(nbytes), err: err}
|
||||
firstErr = offsetErr{offset: writeOffset + uint64(nbytes), err: err}
|
||||
break
|
||||
}
|
||||
if nbytes < int(l) {
|
||||
firstErr = offsetErr{offset: req.offset + uint64(nbytes), err: io.ErrShortWrite}
|
||||
if nbytes < len(pendingData) {
|
||||
firstErr = offsetErr{offset: writeOffset + uint64(nbytes), err: io.ErrShortWrite}
|
||||
break
|
||||
}
|
||||
switch {
|
||||
case offset > fileSize:
|
||||
desiredInFlight = 1
|
||||
case desiredInFlight < maxConcurrentRequests:
|
||||
desiredInFlight++
|
||||
}
|
||||
writeOffset += uint64(nbytes)
|
||||
for {
|
||||
pendingData, ok := pendingWrites[writeOffset]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
// Give go a chance to free the memory.
|
||||
delete(pendingWrites, writeOffset)
|
||||
nbytes, err := w.Write(pendingData)
|
||||
// Do not move writeOffset on error so subsequent iterations won't trigger
|
||||
// any writes.
|
||||
if err != nil {
|
||||
firstErr = offsetErr{offset: writeOffset + uint64(nbytes), err: err}
|
||||
break
|
||||
}
|
||||
if nbytes < len(pendingData) {
|
||||
firstErr = offsetErr{offset: writeOffset + uint64(nbytes), err: io.ErrShortWrite}
|
||||
break
|
||||
}
|
||||
writeOffset += uint64(nbytes)
|
||||
}
|
||||
} else {
|
||||
// Don't write the data yet because
|
||||
// this response came in out of order
|
||||
// and we need to wait for responses
|
||||
// for earlier segments of the file.
|
||||
pendingWrites[req.offset] = data
|
||||
}
|
||||
default:
|
||||
firstErr = offsetErr{offset: 0, err: unimplementedPacketErr(res.typ)}
|
||||
break
|
||||
} else {
|
||||
// Don't write the data yet because
|
||||
// this response came in out of order
|
||||
// and we need to wait for responses
|
||||
// for earlier segments of the file.
|
||||
pendingWrites[req.offset] = data
|
||||
}
|
||||
default:
|
||||
firstErr = offsetErr{offset: 0, err: unimplementedPacketErr(res.typ)}
|
||||
}
|
||||
}
|
||||
if firstErr.err != io.EOF {
|
||||
|
@ -921,28 +913,25 @@ func (f *File) Write(b []byte) (int, error) {
|
|||
if inFlight == 0 {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case res := <-ch:
|
||||
inFlight--
|
||||
if res.err != nil {
|
||||
firstErr = res.err
|
||||
res := <-ch
|
||||
inFlight--
|
||||
if res.err != nil {
|
||||
firstErr = res.err
|
||||
continue
|
||||
}
|
||||
switch res.typ {
|
||||
case ssh_FXP_STATUS:
|
||||
id, _ := unmarshalUint32(res.data)
|
||||
err := normaliseError(unmarshalStatus(id, res.data))
|
||||
if err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
break
|
||||
}
|
||||
switch res.typ {
|
||||
case ssh_FXP_STATUS:
|
||||
id, _ := unmarshalUint32(res.data)
|
||||
err := normaliseError(unmarshalStatus(id, res.data))
|
||||
if err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
break
|
||||
}
|
||||
if desiredInFlight < maxConcurrentRequests {
|
||||
desiredInFlight++
|
||||
}
|
||||
default:
|
||||
firstErr = unimplementedPacketErr(res.typ)
|
||||
break
|
||||
if desiredInFlight < maxConcurrentRequests {
|
||||
desiredInFlight++
|
||||
}
|
||||
default:
|
||||
firstErr = unimplementedPacketErr(res.typ)
|
||||
}
|
||||
}
|
||||
// If error is non-nil, then there may be gaps in the data written to
|
||||
|
@ -988,28 +977,25 @@ func (f *File) ReadFrom(r io.Reader) (int64, error) {
|
|||
if inFlight == 0 {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case res := <-ch:
|
||||
inFlight--
|
||||
if res.err != nil {
|
||||
firstErr = res.err
|
||||
res := <-ch
|
||||
inFlight--
|
||||
if res.err != nil {
|
||||
firstErr = res.err
|
||||
continue
|
||||
}
|
||||
switch res.typ {
|
||||
case ssh_FXP_STATUS:
|
||||
id, _ := unmarshalUint32(res.data)
|
||||
err := normaliseError(unmarshalStatus(id, res.data))
|
||||
if err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
break
|
||||
}
|
||||
switch res.typ {
|
||||
case ssh_FXP_STATUS:
|
||||
id, _ := unmarshalUint32(res.data)
|
||||
err := normaliseError(unmarshalStatus(id, res.data))
|
||||
if err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
break
|
||||
}
|
||||
if desiredInFlight < maxConcurrentRequests {
|
||||
desiredInFlight++
|
||||
}
|
||||
default:
|
||||
firstErr = unimplementedPacketErr(res.typ)
|
||||
break
|
||||
if desiredInFlight < maxConcurrentRequests {
|
||||
desiredInFlight++
|
||||
}
|
||||
default:
|
||||
firstErr = unimplementedPacketErr(res.typ)
|
||||
}
|
||||
}
|
||||
if firstErr == io.EOF {
|
||||
|
|
42
vendor/github.com/pkg/sftp/client_integration_darwin_test.go
generated
vendored
Normal file
42
vendor/github.com/pkg/sftp/client_integration_darwin_test.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const sftpServer = "/usr/libexec/sftp-server"
|
||||
|
||||
func TestClientStatVFS(t *testing.T) {
|
||||
if *testServerImpl {
|
||||
t.Skipf("go server does not support FXP_EXTENDED")
|
||||
}
|
||||
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
vfs, err := sftp.StatVFS("/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// get system stats
|
||||
s := syscall.Statfs_t{}
|
||||
err = syscall.Statfs("/", &s)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// check some stats
|
||||
if vfs.Files != uint64(s.Files) {
|
||||
t.Fatal("fr_size does not match")
|
||||
}
|
||||
|
||||
if vfs.Bfree != uint64(s.Bfree) {
|
||||
t.Fatal("f_bsize does not match")
|
||||
}
|
||||
|
||||
if vfs.Favail != uint64(s.Ffree) {
|
||||
t.Fatal("f_namemax does not match")
|
||||
}
|
||||
}
|
42
vendor/github.com/pkg/sftp/client_integration_linux_test.go
generated
vendored
Normal file
42
vendor/github.com/pkg/sftp/client_integration_linux_test.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const sftpServer = "/usr/lib/openssh/sftp-server"
|
||||
|
||||
func TestClientStatVFS(t *testing.T) {
|
||||
if *testServerImpl {
|
||||
t.Skipf("go server does not support FXP_EXTENDED")
|
||||
}
|
||||
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
vfs, err := sftp.StatVFS("/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// get system stats
|
||||
s := syscall.Statfs_t{}
|
||||
err = syscall.Statfs("/", &s)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// check some stats
|
||||
if vfs.Frsize != uint64(s.Frsize) {
|
||||
t.Fatalf("fr_size does not match, expected: %v, got: %v", s.Frsize, vfs.Frsize)
|
||||
}
|
||||
|
||||
if vfs.Bsize != uint64(s.Bsize) {
|
||||
t.Fatalf("f_bsize does not match, expected: %v, got: %v", s.Bsize, vfs.Bsize)
|
||||
}
|
||||
|
||||
if vfs.Namemax != uint64(s.Namelen) {
|
||||
t.Fatalf("f_namemax does not match, expected: %v, got: %v", s.Namelen, vfs.Namemax)
|
||||
}
|
||||
}
|
2056
vendor/github.com/pkg/sftp/client_integration_test.go
generated
vendored
Normal file
2056
vendor/github.com/pkg/sftp/client_integration_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
147
vendor/github.com/pkg/sftp/client_test.go
generated
vendored
Normal file
147
vendor/github.com/pkg/sftp/client_test.go
generated
vendored
Normal file
|
@ -0,0 +1,147 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kr/fs"
|
||||
)
|
||||
|
||||
// assert that *Client implements fs.FileSystem
|
||||
var _ fs.FileSystem = new(Client)
|
||||
|
||||
// assert that *File implements io.ReadWriteCloser
|
||||
var _ io.ReadWriteCloser = new(File)
|
||||
|
||||
func TestNormaliseError(t *testing.T) {
|
||||
var (
|
||||
ok = &StatusError{Code: ssh_FX_OK}
|
||||
eof = &StatusError{Code: ssh_FX_EOF}
|
||||
fail = &StatusError{Code: ssh_FX_FAILURE}
|
||||
noSuchFile = &StatusError{Code: ssh_FX_NO_SUCH_FILE}
|
||||
foo = errors.New("foo")
|
||||
)
|
||||
|
||||
var tests = []struct {
|
||||
desc string
|
||||
err error
|
||||
want error
|
||||
}{
|
||||
{
|
||||
desc: "nil error",
|
||||
},
|
||||
{
|
||||
desc: "not *StatusError",
|
||||
err: foo,
|
||||
want: foo,
|
||||
},
|
||||
{
|
||||
desc: "*StatusError with ssh_FX_EOF",
|
||||
err: eof,
|
||||
want: io.EOF,
|
||||
},
|
||||
{
|
||||
desc: "*StatusError with ssh_FX_NO_SUCH_FILE",
|
||||
err: noSuchFile,
|
||||
want: os.ErrNotExist,
|
||||
},
|
||||
{
|
||||
desc: "*StatusError with ssh_FX_OK",
|
||||
err: ok,
|
||||
},
|
||||
{
|
||||
desc: "*StatusError with ssh_FX_FAILURE",
|
||||
err: fail,
|
||||
want: fail,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := normaliseError(tt.err)
|
||||
if got != tt.want {
|
||||
t.Errorf("normaliseError(%#v), test %q\n- want: %#v\n- got: %#v",
|
||||
tt.err, tt.desc, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var flagsTests = []struct {
|
||||
flags int
|
||||
want uint32
|
||||
}{
|
||||
{os.O_RDONLY, ssh_FXF_READ},
|
||||
{os.O_WRONLY, ssh_FXF_WRITE},
|
||||
{os.O_RDWR, ssh_FXF_READ | ssh_FXF_WRITE},
|
||||
{os.O_RDWR | os.O_CREATE | os.O_TRUNC, ssh_FXF_READ | ssh_FXF_WRITE | ssh_FXF_CREAT | ssh_FXF_TRUNC},
|
||||
{os.O_WRONLY | os.O_APPEND, ssh_FXF_WRITE | ssh_FXF_APPEND},
|
||||
}
|
||||
|
||||
func TestFlags(t *testing.T) {
|
||||
for i, tt := range flagsTests {
|
||||
got := flags(tt.flags)
|
||||
if got != tt.want {
|
||||
t.Errorf("test %v: flags(%x): want: %x, got: %x", i, tt.flags, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalStatus(t *testing.T) {
|
||||
requestID := uint32(1)
|
||||
|
||||
id := marshalUint32([]byte{}, requestID)
|
||||
idCode := marshalUint32(id, ssh_FX_FAILURE)
|
||||
idCodeMsg := marshalString(idCode, "err msg")
|
||||
idCodeMsgLang := marshalString(idCodeMsg, "lang tag")
|
||||
|
||||
var tests = []struct {
|
||||
desc string
|
||||
reqID uint32
|
||||
status []byte
|
||||
want error
|
||||
}{
|
||||
{
|
||||
desc: "well-formed status",
|
||||
reqID: 1,
|
||||
status: idCodeMsgLang,
|
||||
want: &StatusError{
|
||||
Code: ssh_FX_FAILURE,
|
||||
msg: "err msg",
|
||||
lang: "lang tag",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing error message and language tag",
|
||||
reqID: 1,
|
||||
status: idCode,
|
||||
want: &StatusError{
|
||||
Code: ssh_FX_FAILURE,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing language tag",
|
||||
reqID: 1,
|
||||
status: idCodeMsg,
|
||||
want: &StatusError{
|
||||
Code: ssh_FX_FAILURE,
|
||||
msg: "err msg",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "request identifier mismatch",
|
||||
reqID: 2,
|
||||
status: idCodeMsgLang,
|
||||
want: &unexpectedIDErr{2, requestID},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := unmarshalStatus(tt.reqID, tt.status)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("unmarshalStatus(%v, %v), test %q\n- want: %#v\n- got: %#v",
|
||||
requestID, tt.status, tt.desc, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
10
vendor/github.com/pkg/sftp/conn.go
generated
vendored
10
vendor/github.com/pkg/sftp/conn.go
generated
vendored
|
@ -56,9 +56,9 @@ func (c *clientConn) loop() {
|
|||
// appropriate channel.
|
||||
func (c *clientConn) recv() error {
|
||||
defer func() {
|
||||
c.Lock()
|
||||
c.conn.Lock()
|
||||
c.conn.Close()
|
||||
c.Unlock()
|
||||
c.conn.Unlock()
|
||||
}()
|
||||
for {
|
||||
typ, data, err := c.recvPacket()
|
||||
|
@ -102,11 +102,13 @@ func (c *clientConn) sendPacket(p idmarshaler) (byte, []byte, error) {
|
|||
func (c *clientConn) dispatchRequest(ch chan<- result, p idmarshaler) {
|
||||
c.Lock()
|
||||
c.inflight[p.id()] = ch
|
||||
c.Unlock()
|
||||
if err := c.conn.sendPacket(p); err != nil {
|
||||
c.Lock()
|
||||
delete(c.inflight, p.id())
|
||||
c.Unlock()
|
||||
ch <- result{err: err}
|
||||
}
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// broadcastErr sends an error to all goroutines waiting for a response.
|
||||
|
@ -126,6 +128,6 @@ type serverConn struct {
|
|||
conn
|
||||
}
|
||||
|
||||
func (s *serverConn) sendError(p id, err error) error {
|
||||
func (s *serverConn) sendError(p ider, err error) error {
|
||||
return s.sendPacket(statusFromError(p, err))
|
||||
}
|
||||
|
|
92
vendor/github.com/pkg/sftp/example_test.go
generated
vendored
Normal file
92
vendor/github.com/pkg/sftp/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
package sftp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
var conn *ssh.Client
|
||||
|
||||
// open an SFTP session over an existing ssh connection.
|
||||
sftp, err := sftp.NewClient(conn)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer sftp.Close()
|
||||
|
||||
// walk a directory
|
||||
w := sftp.Walk("/home/user")
|
||||
for w.Step() {
|
||||
if w.Err() != nil {
|
||||
continue
|
||||
}
|
||||
log.Println(w.Path())
|
||||
}
|
||||
|
||||
// leave your mark
|
||||
f, err := sftp.Create("hello.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if _, err := f.Write([]byte("Hello world!")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// check it's there
|
||||
fi, err := sftp.Lstat("hello.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println(fi)
|
||||
}
|
||||
|
||||
func ExampleNewClientPipe() {
|
||||
// Connect to a remote host and request the sftp subsystem via the 'ssh'
|
||||
// command. This assumes that passwordless login is correctly configured.
|
||||
cmd := exec.Command("ssh", "example.com", "-s", "sftp")
|
||||
|
||||
// send errors from ssh to stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// get stdin and stdout
|
||||
wr, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
rd, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// start the process
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cmd.Wait()
|
||||
|
||||
// open the SFTP session
|
||||
client, err := sftp.NewClientPipe(rd, wr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// read a directory
|
||||
list, err := client.ReadDir("/")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// print contents
|
||||
for _, item := range list {
|
||||
fmt.Println(item.Name())
|
||||
}
|
||||
|
||||
// close the connection
|
||||
client.Close()
|
||||
}
|
78
vendor/github.com/pkg/sftp/examples/buffered-read-benchmark/main.go
generated
vendored
Normal file
78
vendor/github.com/pkg/sftp/examples/buffered-read-benchmark/main.go
generated
vendored
Normal file
|
@ -0,0 +1,78 @@
|
|||
// buffered-read-benchmark benchmarks the peformance of reading
|
||||
// from /dev/zero on the server to a []byte on the client via io.Copy.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
var (
|
||||
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||
PORT = flag.Int("port", 22, "ssh server port")
|
||||
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||
SIZE = flag.Int("s", 1<<15, "set max packet size")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var auths []ssh.AuthMethod
|
||||
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||
|
||||
}
|
||||
if *PASS != "" {
|
||||
auths = append(auths, ssh.Password(*PASS))
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: *USER,
|
||||
Auth: auths,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||
conn, err := ssh.Dial("tcp", addr, &config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err := sftp.NewClient(conn, sftp.MaxPacket(*SIZE))
|
||||
if err != nil {
|
||||
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
r, err := c.Open("/dev/zero")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
const size = 1e9
|
||||
|
||||
log.Printf("reading %v bytes", size)
|
||||
t1 := time.Now()
|
||||
n, err := io.ReadFull(r, make([]byte, size))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if n != size {
|
||||
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||
}
|
||||
log.Printf("read %v bytes in %s", size, time.Since(t1))
|
||||
}
|
84
vendor/github.com/pkg/sftp/examples/buffered-write-benchmark/main.go
generated
vendored
Normal file
84
vendor/github.com/pkg/sftp/examples/buffered-write-benchmark/main.go
generated
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
// buffered-write-benchmark benchmarks the peformance of writing
|
||||
// a single large []byte on the client to /dev/null on the server via io.Copy.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
var (
|
||||
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||
PORT = flag.Int("port", 22, "ssh server port")
|
||||
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||
SIZE = flag.Int("s", 1<<15, "set max packet size")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var auths []ssh.AuthMethod
|
||||
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||
|
||||
}
|
||||
if *PASS != "" {
|
||||
auths = append(auths, ssh.Password(*PASS))
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: *USER,
|
||||
Auth: auths,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||
conn, err := ssh.Dial("tcp", addr, &config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err := sftp.NewClient(conn, sftp.MaxPacket(*SIZE))
|
||||
if err != nil {
|
||||
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
w, err := c.OpenFile("/dev/null", syscall.O_WRONLY)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
f, err := os.Open("/dev/zero")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
const size = 1e9
|
||||
|
||||
log.Printf("writing %v bytes", size)
|
||||
t1 := time.Now()
|
||||
n, err := w.Write(make([]byte, size))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if n != size {
|
||||
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||
}
|
||||
log.Printf("wrote %v bytes in %s", size, time.Since(t1))
|
||||
}
|
131
vendor/github.com/pkg/sftp/examples/request-server/main.go
generated
vendored
Normal file
131
vendor/github.com/pkg/sftp/examples/request-server/main.go
generated
vendored
Normal file
|
@ -0,0 +1,131 @@
|
|||
// An example SFTP server implementation using the golang SSH package.
|
||||
// Serves the whole filesystem visible to the user, and has a hard-coded username and password,
|
||||
// so not for real use!
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// Based on example server code from golang.org/x/crypto/ssh and server_standalone
|
||||
func main() {
|
||||
|
||||
var (
|
||||
readOnly bool
|
||||
debugStderr bool
|
||||
)
|
||||
|
||||
flag.BoolVar(&readOnly, "R", false, "read-only server")
|
||||
flag.BoolVar(&debugStderr, "e", false, "debug to stderr")
|
||||
flag.Parse()
|
||||
|
||||
debugStream := ioutil.Discard
|
||||
if debugStderr {
|
||||
debugStream = os.Stderr
|
||||
}
|
||||
|
||||
// An SSH server is represented by a ServerConfig, which holds
|
||||
// certificate details and handles authentication of ServerConns.
|
||||
config := &ssh.ServerConfig{
|
||||
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
// Should use constant-time compare (or better, salt+hash) in
|
||||
// a production setting.
|
||||
fmt.Fprintf(debugStream, "Login: %s\n", c.User())
|
||||
if c.User() == "testuser" && string(pass) == "tiger" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("password rejected for %q", c.User())
|
||||
},
|
||||
}
|
||||
|
||||
privateBytes, err := ioutil.ReadFile("id_rsa")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load private key", err)
|
||||
}
|
||||
|
||||
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to parse private key", err)
|
||||
}
|
||||
|
||||
config.AddHostKey(private)
|
||||
|
||||
// Once a ServerConfig has been configured, connections can be
|
||||
// accepted.
|
||||
listener, err := net.Listen("tcp", "0.0.0.0:2022")
|
||||
if err != nil {
|
||||
log.Fatal("failed to listen for connection", err)
|
||||
}
|
||||
fmt.Printf("Listening on %v\n", listener.Addr())
|
||||
|
||||
nConn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Fatal("failed to accept incoming connection", err)
|
||||
}
|
||||
|
||||
// Before use, a handshake must be performed on the incoming
|
||||
// net.Conn.
|
||||
_, chans, reqs, err := ssh.NewServerConn(nConn, config)
|
||||
if err != nil {
|
||||
log.Fatal("failed to handshake", err)
|
||||
}
|
||||
fmt.Fprintf(debugStream, "SSH server established\n")
|
||||
|
||||
// The incoming Request channel must be serviced.
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
// Service the incoming Channel channel.
|
||||
for newChannel := range chans {
|
||||
// Channels have a type, depending on the application level
|
||||
// protocol intended. In the case of an SFTP session, this is "subsystem"
|
||||
// with a payload string of "<length=4>sftp"
|
||||
fmt.Fprintf(debugStream, "Incoming channel: %s\n", newChannel.ChannelType())
|
||||
if newChannel.ChannelType() != "session" {
|
||||
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||
fmt.Fprintf(debugStream, "Unknown channel type: %s\n", newChannel.ChannelType())
|
||||
continue
|
||||
}
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
log.Fatal("could not accept channel.", err)
|
||||
}
|
||||
fmt.Fprintf(debugStream, "Channel accepted\n")
|
||||
|
||||
// Sessions have out-of-band requests such as "shell",
|
||||
// "pty-req" and "env". Here we handle only the
|
||||
// "subsystem" request.
|
||||
go func(in <-chan *ssh.Request) {
|
||||
for req := range in {
|
||||
fmt.Fprintf(debugStream, "Request: %v\n", req.Type)
|
||||
ok := false
|
||||
switch req.Type {
|
||||
case "subsystem":
|
||||
fmt.Fprintf(debugStream, "Subsystem: %s\n", req.Payload[4:])
|
||||
if string(req.Payload[4:]) == "sftp" {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(debugStream, " - accepted: %v\n", ok)
|
||||
req.Reply(ok, nil)
|
||||
}
|
||||
}(requests)
|
||||
|
||||
root := sftp.InMemHandler()
|
||||
server := sftp.NewRequestServer(channel, root)
|
||||
if err := server.Serve(); err == io.EOF {
|
||||
server.Close()
|
||||
log.Print("sftp client exited session.")
|
||||
} else if err != nil {
|
||||
log.Fatal("sftp server completed with error:", err)
|
||||
}
|
||||
}
|
||||
}
|
12
vendor/github.com/pkg/sftp/examples/sftp-server/README.md
generated
vendored
Normal file
12
vendor/github.com/pkg/sftp/examples/sftp-server/README.md
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
Example SFTP server implementation
|
||||
===
|
||||
|
||||
In order to use this example you will need an RSA key.
|
||||
|
||||
On linux-like systems with openssh installed, you can use the command:
|
||||
|
||||
```
|
||||
ssh-keygen -t rsa -f id_rsa
|
||||
```
|
||||
|
||||
Then you will be able to run the sftp-server command in the current directory.
|
147
vendor/github.com/pkg/sftp/examples/sftp-server/main.go
generated
vendored
Normal file
147
vendor/github.com/pkg/sftp/examples/sftp-server/main.go
generated
vendored
Normal file
|
@ -0,0 +1,147 @@
|
|||
// An example SFTP server implementation using the golang SSH package.
|
||||
// Serves the whole filesystem visible to the user, and has a hard-coded username and password,
|
||||
// so not for real use!
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// Based on example server code from golang.org/x/crypto/ssh and server_standalone
|
||||
func main() {
|
||||
|
||||
var (
|
||||
readOnly bool
|
||||
debugStderr bool
|
||||
)
|
||||
|
||||
flag.BoolVar(&readOnly, "R", false, "read-only server")
|
||||
flag.BoolVar(&debugStderr, "e", false, "debug to stderr")
|
||||
flag.Parse()
|
||||
|
||||
debugStream := ioutil.Discard
|
||||
if debugStderr {
|
||||
debugStream = os.Stderr
|
||||
}
|
||||
|
||||
// An SSH server is represented by a ServerConfig, which holds
|
||||
// certificate details and handles authentication of ServerConns.
|
||||
config := &ssh.ServerConfig{
|
||||
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
// Should use constant-time compare (or better, salt+hash) in
|
||||
// a production setting.
|
||||
fmt.Fprintf(debugStream, "Login: %s\n", c.User())
|
||||
if c.User() == "testuser" && string(pass) == "tiger" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("password rejected for %q", c.User())
|
||||
},
|
||||
}
|
||||
|
||||
privateBytes, err := ioutil.ReadFile("id_rsa")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load private key", err)
|
||||
}
|
||||
|
||||
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to parse private key", err)
|
||||
}
|
||||
|
||||
config.AddHostKey(private)
|
||||
|
||||
// Once a ServerConfig has been configured, connections can be
|
||||
// accepted.
|
||||
listener, err := net.Listen("tcp", "0.0.0.0:2022")
|
||||
if err != nil {
|
||||
log.Fatal("failed to listen for connection", err)
|
||||
}
|
||||
fmt.Printf("Listening on %v\n", listener.Addr())
|
||||
|
||||
nConn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Fatal("failed to accept incoming connection", err)
|
||||
}
|
||||
|
||||
// Before use, a handshake must be performed on the incoming
|
||||
// net.Conn.
|
||||
_, chans, reqs, err := ssh.NewServerConn(nConn, config)
|
||||
if err != nil {
|
||||
log.Fatal("failed to handshake", err)
|
||||
}
|
||||
fmt.Fprintf(debugStream, "SSH server established\n")
|
||||
|
||||
// The incoming Request channel must be serviced.
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
// Service the incoming Channel channel.
|
||||
for newChannel := range chans {
|
||||
// Channels have a type, depending on the application level
|
||||
// protocol intended. In the case of an SFTP session, this is "subsystem"
|
||||
// with a payload string of "<length=4>sftp"
|
||||
fmt.Fprintf(debugStream, "Incoming channel: %s\n", newChannel.ChannelType())
|
||||
if newChannel.ChannelType() != "session" {
|
||||
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||
fmt.Fprintf(debugStream, "Unknown channel type: %s\n", newChannel.ChannelType())
|
||||
continue
|
||||
}
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
log.Fatal("could not accept channel.", err)
|
||||
}
|
||||
fmt.Fprintf(debugStream, "Channel accepted\n")
|
||||
|
||||
// Sessions have out-of-band requests such as "shell",
|
||||
// "pty-req" and "env". Here we handle only the
|
||||
// "subsystem" request.
|
||||
go func(in <-chan *ssh.Request) {
|
||||
for req := range in {
|
||||
fmt.Fprintf(debugStream, "Request: %v\n", req.Type)
|
||||
ok := false
|
||||
switch req.Type {
|
||||
case "subsystem":
|
||||
fmt.Fprintf(debugStream, "Subsystem: %s\n", req.Payload[4:])
|
||||
if string(req.Payload[4:]) == "sftp" {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(debugStream, " - accepted: %v\n", ok)
|
||||
req.Reply(ok, nil)
|
||||
}
|
||||
}(requests)
|
||||
|
||||
serverOptions := []sftp.ServerOption{
|
||||
sftp.WithDebug(debugStream),
|
||||
}
|
||||
|
||||
if readOnly {
|
||||
serverOptions = append(serverOptions, sftp.ReadOnly())
|
||||
fmt.Fprintf(debugStream, "Read-only server\n")
|
||||
} else {
|
||||
fmt.Fprintf(debugStream, "Read write server\n")
|
||||
}
|
||||
|
||||
server, err := sftp.NewServer(
|
||||
channel,
|
||||
serverOptions...,
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := server.Serve(); err == io.EOF {
|
||||
server.Close()
|
||||
log.Print("sftp client exited session.")
|
||||
} else if err != nil {
|
||||
log.Fatal("sftp server completed with error:", err)
|
||||
}
|
||||
}
|
||||
}
|
85
vendor/github.com/pkg/sftp/examples/streaming-read-benchmark/main.go
generated
vendored
Normal file
85
vendor/github.com/pkg/sftp/examples/streaming-read-benchmark/main.go
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
|||
// streaming-read-benchmark benchmarks the peformance of reading
|
||||
// from /dev/zero on the server to /dev/null on the client via io.Copy.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
var (
|
||||
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||
PORT = flag.Int("port", 22, "ssh server port")
|
||||
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||
SIZE = flag.Int("s", 1<<15, "set max packet size")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var auths []ssh.AuthMethod
|
||||
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||
|
||||
}
|
||||
if *PASS != "" {
|
||||
auths = append(auths, ssh.Password(*PASS))
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: *USER,
|
||||
Auth: auths,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||
conn, err := ssh.Dial("tcp", addr, &config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err := sftp.NewClient(conn, sftp.MaxPacket(*SIZE))
|
||||
if err != nil {
|
||||
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
r, err := c.Open("/dev/zero")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
w, err := os.OpenFile("/dev/null", syscall.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
const size int64 = 1e9
|
||||
|
||||
log.Printf("reading %v bytes", size)
|
||||
t1 := time.Now()
|
||||
n, err := io.Copy(w, io.LimitReader(r, size))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if n != size {
|
||||
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||
}
|
||||
log.Printf("read %v bytes in %s", size, time.Since(t1))
|
||||
}
|
85
vendor/github.com/pkg/sftp/examples/streaming-write-benchmark/main.go
generated
vendored
Normal file
85
vendor/github.com/pkg/sftp/examples/streaming-write-benchmark/main.go
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
|||
// streaming-write-benchmark benchmarks the peformance of writing
|
||||
// from /dev/zero on the client to /dev/null on the server via io.Copy.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
var (
|
||||
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||
PORT = flag.Int("port", 22, "ssh server port")
|
||||
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||
SIZE = flag.Int("s", 1<<15, "set max packet size")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var auths []ssh.AuthMethod
|
||||
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||
|
||||
}
|
||||
if *PASS != "" {
|
||||
auths = append(auths, ssh.Password(*PASS))
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: *USER,
|
||||
Auth: auths,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||
conn, err := ssh.Dial("tcp", addr, &config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err := sftp.NewClient(conn, sftp.MaxPacket(*SIZE))
|
||||
if err != nil {
|
||||
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
w, err := c.OpenFile("/dev/null", syscall.O_WRONLY)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
f, err := os.Open("/dev/zero")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
const size int64 = 1e9
|
||||
|
||||
log.Printf("writing %v bytes", size)
|
||||
t1 := time.Now()
|
||||
n, err := io.Copy(w, io.LimitReader(f, size))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if n != size {
|
||||
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||
}
|
||||
log.Printf("wrote %v bytes in %s", size, time.Since(t1))
|
||||
}
|
345
vendor/github.com/pkg/sftp/match.go
generated
vendored
Normal file
345
vendor/github.com/pkg/sftp/match.go
generated
vendored
Normal file
|
@ -0,0 +1,345 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// ErrBadPattern indicates a globbing pattern was malformed.
|
||||
var ErrBadPattern = errors.New("syntax error in pattern")
|
||||
|
||||
// Unix separator
|
||||
const separator = "/"
|
||||
|
||||
// Match reports whether name matches the shell file name pattern.
|
||||
// The pattern syntax is:
|
||||
//
|
||||
// pattern:
|
||||
// { term }
|
||||
// term:
|
||||
// '*' matches any sequence of non-Separator characters
|
||||
// '?' matches any single non-Separator character
|
||||
// '[' [ '^' ] { character-range } ']'
|
||||
// character class (must be non-empty)
|
||||
// c matches character c (c != '*', '?', '\\', '[')
|
||||
// '\\' c matches character c
|
||||
//
|
||||
// character-range:
|
||||
// c matches character c (c != '\\', '-', ']')
|
||||
// '\\' c matches character c
|
||||
// lo '-' hi matches character c for lo <= c <= hi
|
||||
//
|
||||
// Match requires pattern to match all of name, not just a substring.
|
||||
// The only possible returned error is ErrBadPattern, when pattern
|
||||
// is malformed.
|
||||
//
|
||||
//
|
||||
func Match(pattern, name string) (matched bool, err error) {
|
||||
Pattern:
|
||||
for len(pattern) > 0 {
|
||||
var star bool
|
||||
var chunk string
|
||||
star, chunk, pattern = scanChunk(pattern)
|
||||
if star && chunk == "" {
|
||||
// Trailing * matches rest of string unless it has a /.
|
||||
return !strings.Contains(name, separator), nil
|
||||
}
|
||||
// Look for match at current position.
|
||||
t, ok, err := matchChunk(chunk, name)
|
||||
// if we're the last chunk, make sure we've exhausted the name
|
||||
// otherwise we'll give a false result even if we could still match
|
||||
// using the star
|
||||
if ok && (len(t) == 0 || len(pattern) > 0) {
|
||||
name = t
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if star {
|
||||
// Look for match skipping i+1 bytes.
|
||||
// Cannot skip /.
|
||||
for i := 0; i < len(name) && !isPathSeparator(name[i]); i++ {
|
||||
t, ok, err := matchChunk(chunk, name[i+1:])
|
||||
if ok {
|
||||
// if we're the last chunk, make sure we exhausted the name
|
||||
if len(pattern) == 0 && len(t) > 0 {
|
||||
continue
|
||||
}
|
||||
name = t
|
||||
continue Pattern
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return len(name) == 0, nil
|
||||
}
|
||||
|
||||
// detect if byte(char) is path separator
|
||||
func isPathSeparator(c byte) bool {
|
||||
return string(c) == "/"
|
||||
}
|
||||
|
||||
// scanChunk gets the next segment of pattern, which is a non-star string
|
||||
// possibly preceded by a star.
|
||||
func scanChunk(pattern string) (star bool, chunk, rest string) {
|
||||
for len(pattern) > 0 && pattern[0] == '*' {
|
||||
pattern = pattern[1:]
|
||||
star = true
|
||||
}
|
||||
inrange := false
|
||||
var i int
|
||||
Scan:
|
||||
for i = 0; i < len(pattern); i++ {
|
||||
switch pattern[i] {
|
||||
case '\\':
|
||||
|
||||
// error check handled in matchChunk: bad pattern.
|
||||
if i+1 < len(pattern) {
|
||||
i++
|
||||
}
|
||||
case '[':
|
||||
inrange = true
|
||||
case ']':
|
||||
inrange = false
|
||||
case '*':
|
||||
if !inrange {
|
||||
break Scan
|
||||
}
|
||||
}
|
||||
}
|
||||
return star, pattern[0:i], pattern[i:]
|
||||
}
|
||||
|
||||
// matchChunk checks whether chunk matches the beginning of s.
|
||||
// If so, it returns the remainder of s (after the match).
|
||||
// Chunk is all single-character operators: literals, char classes, and ?.
|
||||
func matchChunk(chunk, s string) (rest string, ok bool, err error) {
|
||||
for len(chunk) > 0 {
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
switch chunk[0] {
|
||||
case '[':
|
||||
// character class
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
s = s[n:]
|
||||
chunk = chunk[1:]
|
||||
// We can't end right after '[', we're expecting at least
|
||||
// a closing bracket and possibly a caret.
|
||||
if len(chunk) == 0 {
|
||||
err = ErrBadPattern
|
||||
return
|
||||
}
|
||||
// possibly negated
|
||||
negated := chunk[0] == '^'
|
||||
if negated {
|
||||
chunk = chunk[1:]
|
||||
}
|
||||
// parse all ranges
|
||||
match := false
|
||||
nrange := 0
|
||||
for {
|
||||
if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 {
|
||||
chunk = chunk[1:]
|
||||
break
|
||||
}
|
||||
var lo, hi rune
|
||||
if lo, chunk, err = getEsc(chunk); err != nil {
|
||||
return
|
||||
}
|
||||
hi = lo
|
||||
if chunk[0] == '-' {
|
||||
if hi, chunk, err = getEsc(chunk[1:]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if lo <= r && r <= hi {
|
||||
match = true
|
||||
}
|
||||
nrange++
|
||||
}
|
||||
if match == negated {
|
||||
return
|
||||
}
|
||||
|
||||
case '?':
|
||||
if isPathSeparator(s[0]) {
|
||||
return
|
||||
}
|
||||
_, n := utf8.DecodeRuneInString(s)
|
||||
s = s[n:]
|
||||
chunk = chunk[1:]
|
||||
|
||||
case '\\':
|
||||
chunk = chunk[1:]
|
||||
if len(chunk) == 0 {
|
||||
err = ErrBadPattern
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
if chunk[0] != s[0] {
|
||||
return
|
||||
}
|
||||
s = s[1:]
|
||||
chunk = chunk[1:]
|
||||
}
|
||||
}
|
||||
return s, true, nil
|
||||
}
|
||||
|
||||
// getEsc gets a possibly-escaped character from chunk, for a character class.
|
||||
func getEsc(chunk string) (r rune, nchunk string, err error) {
|
||||
if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' {
|
||||
err = ErrBadPattern
|
||||
return
|
||||
}
|
||||
if chunk[0] == '\\' {
|
||||
chunk = chunk[1:]
|
||||
if len(chunk) == 0 {
|
||||
err = ErrBadPattern
|
||||
return
|
||||
}
|
||||
}
|
||||
r, n := utf8.DecodeRuneInString(chunk)
|
||||
if r == utf8.RuneError && n == 1 {
|
||||
err = ErrBadPattern
|
||||
}
|
||||
nchunk = chunk[n:]
|
||||
if len(nchunk) == 0 {
|
||||
err = ErrBadPattern
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Split splits path immediately following the final Separator,
|
||||
// separating it into a directory and file name component.
|
||||
// If there is no Separator in path, Split returns an empty dir
|
||||
// and file set to path.
|
||||
// The returned values have the property that path = dir+file.
|
||||
func Split(path string) (dir, file string) {
|
||||
i := len(path) - 1
|
||||
for i >= 0 && !isPathSeparator(path[i]) {
|
||||
i--
|
||||
}
|
||||
return path[:i+1], path[i+1:]
|
||||
}
|
||||
|
||||
// Glob returns the names of all files matching pattern or nil
|
||||
// if there is no matching file. The syntax of patterns is the same
|
||||
// as in Match. The pattern may describe hierarchical names such as
|
||||
// /usr/*/bin/ed (assuming the Separator is '/').
|
||||
//
|
||||
// Glob ignores file system errors such as I/O errors reading directories.
|
||||
// The only possible returned error is ErrBadPattern, when pattern
|
||||
// is malformed.
|
||||
func (c *Client) Glob(pattern string) (matches []string, err error) {
|
||||
if !hasMeta(pattern) {
|
||||
file, err := c.Lstat(pattern)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
dir, _ := Split(pattern)
|
||||
dir = cleanGlobPath(dir)
|
||||
return []string{Join(dir, file.Name())}, nil
|
||||
}
|
||||
|
||||
dir, file := Split(pattern)
|
||||
dir = cleanGlobPath(dir)
|
||||
|
||||
if !hasMeta(dir) {
|
||||
return c.glob(dir, file, nil)
|
||||
}
|
||||
|
||||
// Prevent infinite recursion. See issue 15879.
|
||||
if dir == pattern {
|
||||
return nil, ErrBadPattern
|
||||
}
|
||||
|
||||
var m []string
|
||||
m, err = c.Glob(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, d := range m {
|
||||
matches, err = c.glob(d, file, matches)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// cleanGlobPath prepares path for glob matching.
|
||||
func cleanGlobPath(path string) string {
|
||||
switch path {
|
||||
case "":
|
||||
return "."
|
||||
case string(separator):
|
||||
// do nothing to the path
|
||||
return path
|
||||
default:
|
||||
return path[0 : len(path)-1] // chop off trailing separator
|
||||
}
|
||||
}
|
||||
|
||||
// glob searches for files matching pattern in the directory dir
|
||||
// and appends them to matches. If the directory cannot be
|
||||
// opened, it returns the existing matches. New matches are
|
||||
// added in lexicographical order.
|
||||
func (c *Client) glob(dir, pattern string, matches []string) (m []string, e error) {
|
||||
m = matches
|
||||
fi, err := c.Stat(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return
|
||||
}
|
||||
names, err := c.ReadDir(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//sort.Strings(names)
|
||||
|
||||
for _, n := range names {
|
||||
matched, err := Match(pattern, n.Name())
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
if matched {
|
||||
m = append(m, Join(dir, n.Name()))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Join joins any number of path elements into a single path, adding
|
||||
// a Separator if necessary.
|
||||
// all empty strings are ignored.
|
||||
func Join(elem ...string) string {
|
||||
return join(elem)
|
||||
}
|
||||
func join(elem []string) string {
|
||||
// If there's a bug here, fix the logic in ./path_plan9.go too.
|
||||
for i, e := range elem {
|
||||
if e != "" {
|
||||
return strings.Join(elem[i:], string(separator))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// hasMeta reports whether path contains any of the magic characters
|
||||
// recognized by Match.
|
||||
func hasMeta(path string) bool {
|
||||
// TODO(niemeyer): Should other magic characters be added here?
|
||||
return strings.ContainsAny(path, "*?[")
|
||||
}
|
5
vendor/github.com/pkg/sftp/other_test.go
generated
vendored
Normal file
5
vendor/github.com/pkg/sftp/other_test.go
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
// +build !linux,!darwin
|
||||
|
||||
package sftp
|
||||
|
||||
const sftpServer = "/usr/bin/false" // unsupported
|
156
vendor/github.com/pkg/sftp/packet-manager.go
generated
vendored
Normal file
156
vendor/github.com/pkg/sftp/packet-manager.go
generated
vendored
Normal file
|
@ -0,0 +1,156 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// The goal of the packetManager is to keep the outgoing packets in the same
|
||||
// order as the incoming. This is due to some sftp clients requiring this
|
||||
// behavior (eg. winscp).
|
||||
|
||||
type packetSender interface {
|
||||
sendPacket(encoding.BinaryMarshaler) error
|
||||
}
|
||||
|
||||
type packetManager struct {
|
||||
requests chan requestPacket
|
||||
responses chan responsePacket
|
||||
fini chan struct{}
|
||||
incoming requestPacketIDs
|
||||
outgoing responsePackets
|
||||
sender packetSender // connection object
|
||||
working *sync.WaitGroup
|
||||
}
|
||||
|
||||
func newPktMgr(sender packetSender) packetManager {
|
||||
s := packetManager{
|
||||
requests: make(chan requestPacket, sftpServerWorkerCount),
|
||||
responses: make(chan responsePacket, sftpServerWorkerCount),
|
||||
fini: make(chan struct{}),
|
||||
incoming: make([]uint32, 0, sftpServerWorkerCount),
|
||||
outgoing: make([]responsePacket, 0, sftpServerWorkerCount),
|
||||
sender: sender,
|
||||
working: &sync.WaitGroup{},
|
||||
}
|
||||
go s.controller()
|
||||
return s
|
||||
}
|
||||
|
||||
// register incoming packets to be handled
|
||||
// send id of 0 for packets without id
|
||||
func (s packetManager) incomingPacket(pkt requestPacket) {
|
||||
s.working.Add(1)
|
||||
s.requests <- pkt // buffer == sftpServerWorkerCount
|
||||
}
|
||||
|
||||
// register outgoing packets as being ready
|
||||
func (s packetManager) readyPacket(pkt responsePacket) {
|
||||
s.responses <- pkt
|
||||
s.working.Done()
|
||||
}
|
||||
|
||||
// shut down packetManager controller
|
||||
func (s packetManager) close() {
|
||||
// pause until current packets are processed
|
||||
s.working.Wait()
|
||||
close(s.fini)
|
||||
}
|
||||
|
||||
// Passed a worker function, returns a channel for incoming packets.
|
||||
// The goal is to process packets in the order they are received as is
|
||||
// requires by section 7 of the RFC, while maximizing throughput of file
|
||||
// transfers.
|
||||
func (s *packetManager) workerChan(worker func(requestChan)) requestChan {
|
||||
|
||||
rwChan := make(chan requestPacket, sftpServerWorkerCount)
|
||||
for i := 0; i < sftpServerWorkerCount; i++ {
|
||||
go worker(rwChan)
|
||||
}
|
||||
|
||||
cmdChan := make(chan requestPacket)
|
||||
go worker(cmdChan)
|
||||
|
||||
pktChan := make(chan requestPacket, sftpServerWorkerCount)
|
||||
go func() {
|
||||
// start with cmdChan
|
||||
curChan := cmdChan
|
||||
for pkt := range pktChan {
|
||||
// on file open packet, switch to rwChan
|
||||
switch pkt.(type) {
|
||||
case *sshFxpOpenPacket:
|
||||
curChan = rwChan
|
||||
// on file close packet, switch back to cmdChan
|
||||
// after waiting for any reads/writes to finish
|
||||
case *sshFxpClosePacket:
|
||||
// wait for rwChan to finish
|
||||
s.working.Wait()
|
||||
// stop using rwChan
|
||||
curChan = cmdChan
|
||||
}
|
||||
s.incomingPacket(pkt)
|
||||
curChan <- pkt
|
||||
}
|
||||
close(rwChan)
|
||||
close(cmdChan)
|
||||
s.close()
|
||||
}()
|
||||
|
||||
return pktChan
|
||||
}
|
||||
|
||||
// process packets
|
||||
func (s *packetManager) controller() {
|
||||
for {
|
||||
select {
|
||||
case pkt := <-s.requests:
|
||||
debug("incoming id: %v", pkt.id())
|
||||
s.incoming = append(s.incoming, pkt.id())
|
||||
if len(s.incoming) > 1 {
|
||||
s.incoming.Sort()
|
||||
}
|
||||
case pkt := <-s.responses:
|
||||
debug("outgoing pkt: %v", pkt.id())
|
||||
s.outgoing = append(s.outgoing, pkt)
|
||||
if len(s.outgoing) > 1 {
|
||||
s.outgoing.Sort()
|
||||
}
|
||||
case <-s.fini:
|
||||
return
|
||||
}
|
||||
s.maybeSendPackets()
|
||||
}
|
||||
}
|
||||
|
||||
// send as many packets as are ready
|
||||
func (s *packetManager) maybeSendPackets() {
|
||||
for {
|
||||
if len(s.outgoing) == 0 || len(s.incoming) == 0 {
|
||||
debug("break! -- outgoing: %v; incoming: %v",
|
||||
len(s.outgoing), len(s.incoming))
|
||||
break
|
||||
}
|
||||
out := s.outgoing[0]
|
||||
in := s.incoming[0]
|
||||
// debug("incoming: %v", s.incoming)
|
||||
// debug("outgoing: %v", outfilter(s.outgoing))
|
||||
if in == out.id() {
|
||||
s.sender.sendPacket(out)
|
||||
// pop off heads
|
||||
copy(s.incoming, s.incoming[1:]) // shift left
|
||||
s.incoming = s.incoming[:len(s.incoming)-1] // remove last
|
||||
copy(s.outgoing, s.outgoing[1:]) // shift left
|
||||
s.outgoing = s.outgoing[:len(s.outgoing)-1] // remove last
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func outfilter(o []responsePacket) []uint32 {
|
||||
res := make([]uint32, 0, len(o))
|
||||
for _, v := range o {
|
||||
res = append(res, v.id())
|
||||
}
|
||||
return res
|
||||
}
|
21
vendor/github.com/pkg/sftp/packet-manager_go1.8.go
generated
vendored
Normal file
21
vendor/github.com/pkg/sftp/packet-manager_go1.8.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// +build go1.8
|
||||
|
||||
package sftp
|
||||
|
||||
import "sort"
|
||||
|
||||
type responsePackets []responsePacket
|
||||
|
||||
func (r responsePackets) Sort() {
|
||||
sort.Slice(r, func(i, j int) bool {
|
||||
return r[i].id() < r[j].id()
|
||||
})
|
||||
}
|
||||
|
||||
type requestPacketIDs []uint32
|
||||
|
||||
func (r requestPacketIDs) Sort() {
|
||||
sort.Slice(r, func(i, j int) bool {
|
||||
return r[i] < r[j]
|
||||
})
|
||||
}
|
21
vendor/github.com/pkg/sftp/packet-manager_legacy.go
generated
vendored
Normal file
21
vendor/github.com/pkg/sftp/packet-manager_legacy.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// +build !go1.8
|
||||
|
||||
package sftp
|
||||
|
||||
import "sort"
|
||||
|
||||
// for sorting/ordering outgoing
|
||||
type responsePackets []responsePacket
|
||||
|
||||
func (r responsePackets) Len() int { return len(r) }
|
||||
func (r responsePackets) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||
func (r responsePackets) Less(i, j int) bool { return r[i].id() < r[j].id() }
|
||||
func (r responsePackets) Sort() { sort.Sort(r) }
|
||||
|
||||
// for sorting/ordering incoming
|
||||
type requestPacketIDs []uint32
|
||||
|
||||
func (r requestPacketIDs) Len() int { return len(r) }
|
||||
func (r requestPacketIDs) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||
func (r requestPacketIDs) Less(i, j int) bool { return r[i] < r[j] }
|
||||
func (r requestPacketIDs) Sort() { sort.Sort(r) }
|
138
vendor/github.com/pkg/sftp/packet-manager_test.go
generated
vendored
Normal file
138
vendor/github.com/pkg/sftp/packet-manager_test.go
generated
vendored
Normal file
|
@ -0,0 +1,138 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type _testSender struct {
|
||||
sent chan encoding.BinaryMarshaler
|
||||
}
|
||||
|
||||
func newTestSender() *_testSender {
|
||||
return &_testSender{make(chan encoding.BinaryMarshaler)}
|
||||
}
|
||||
|
||||
func (s _testSender) sendPacket(p encoding.BinaryMarshaler) error {
|
||||
s.sent <- p
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakepacket uint32
|
||||
|
||||
func (fakepacket) MarshalBinary() ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
func (fakepacket) UnmarshalBinary([]byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakepacket) id() uint32 {
|
||||
return uint32(f)
|
||||
}
|
||||
|
||||
type pair struct {
|
||||
in fakepacket
|
||||
out fakepacket
|
||||
}
|
||||
|
||||
// basic test
|
||||
var ttable1 = []pair{
|
||||
pair{fakepacket(0), fakepacket(0)},
|
||||
pair{fakepacket(1), fakepacket(1)},
|
||||
pair{fakepacket(2), fakepacket(2)},
|
||||
pair{fakepacket(3), fakepacket(3)},
|
||||
}
|
||||
|
||||
// outgoing packets out of order
|
||||
var ttable2 = []pair{
|
||||
pair{fakepacket(0), fakepacket(0)},
|
||||
pair{fakepacket(1), fakepacket(4)},
|
||||
pair{fakepacket(2), fakepacket(1)},
|
||||
pair{fakepacket(3), fakepacket(3)},
|
||||
pair{fakepacket(4), fakepacket(2)},
|
||||
}
|
||||
|
||||
// incoming packets out of order
|
||||
var ttable3 = []pair{
|
||||
pair{fakepacket(2), fakepacket(0)},
|
||||
pair{fakepacket(1), fakepacket(1)},
|
||||
pair{fakepacket(3), fakepacket(2)},
|
||||
pair{fakepacket(0), fakepacket(3)},
|
||||
}
|
||||
|
||||
var tables = [][]pair{ttable1, ttable2, ttable3}
|
||||
|
||||
func TestPacketManager(t *testing.T) {
|
||||
sender := newTestSender()
|
||||
s := newPktMgr(sender)
|
||||
|
||||
for i := range tables {
|
||||
table := tables[i]
|
||||
for _, p := range table {
|
||||
s.incomingPacket(p.in)
|
||||
}
|
||||
for _, p := range table {
|
||||
s.readyPacket(p.out)
|
||||
}
|
||||
for i := 0; i < len(table); i++ {
|
||||
pkt := <-sender.sent
|
||||
id := pkt.(fakepacket).id()
|
||||
assert.Equal(t, id, uint32(i))
|
||||
}
|
||||
}
|
||||
s.close()
|
||||
}
|
||||
|
||||
// Test what happens when the pool processes a close packet on a file that it
|
||||
// is still reading from.
|
||||
func TestCloseOutOfOrder(t *testing.T) {
|
||||
packets := []requestPacket{
|
||||
&sshFxpRemovePacket{ID: 0, Filename: "foo"},
|
||||
&sshFxpOpenPacket{ID: 1},
|
||||
&sshFxpWritePacket{ID: 2, Handle: "foo"},
|
||||
&sshFxpWritePacket{ID: 3, Handle: "foo"},
|
||||
&sshFxpWritePacket{ID: 4, Handle: "foo"},
|
||||
&sshFxpWritePacket{ID: 5, Handle: "foo"},
|
||||
&sshFxpClosePacket{ID: 6, Handle: "foo"},
|
||||
&sshFxpRemovePacket{ID: 7, Filename: "foo"},
|
||||
}
|
||||
|
||||
recvChan := make(chan requestPacket, len(packets)+1)
|
||||
sender := newTestSender()
|
||||
pktMgr := newPktMgr(sender)
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(packets))
|
||||
worker := func(ch requestChan) {
|
||||
for pkt := range ch {
|
||||
if _, ok := pkt.(*sshFxpWritePacket); ok {
|
||||
// sleep to cause writes to come after close/remove
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
pktMgr.working.Done()
|
||||
recvChan <- pkt
|
||||
wg.Done()
|
||||
}
|
||||
}
|
||||
pktChan := pktMgr.workerChan(worker)
|
||||
for _, p := range packets {
|
||||
pktChan <- p
|
||||
}
|
||||
wg.Wait()
|
||||
close(recvChan)
|
||||
received := []requestPacket{}
|
||||
for p := range recvChan {
|
||||
received = append(received, p)
|
||||
}
|
||||
if received[len(received)-2].id() != packets[len(packets)-2].id() {
|
||||
t.Fatal("Packets processed out of order1:", received, packets)
|
||||
}
|
||||
if received[len(received)-1].id() != packets[len(packets)-1].id() {
|
||||
t.Fatal("Packets processed out of order2:", received, packets)
|
||||
}
|
||||
}
|
141
vendor/github.com/pkg/sftp/packet-typing.go
generated
vendored
Normal file
141
vendor/github.com/pkg/sftp/packet-typing.go
generated
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// all incoming packets
|
||||
type requestPacket interface {
|
||||
encoding.BinaryUnmarshaler
|
||||
id() uint32
|
||||
}
|
||||
|
||||
type requestChan chan requestPacket
|
||||
|
||||
type responsePacket interface {
|
||||
encoding.BinaryMarshaler
|
||||
id() uint32
|
||||
}
|
||||
|
||||
// interfaces to group types
|
||||
type hasPath interface {
|
||||
requestPacket
|
||||
getPath() string
|
||||
}
|
||||
|
||||
type hasHandle interface {
|
||||
requestPacket
|
||||
getHandle() string
|
||||
}
|
||||
|
||||
type isOpener interface {
|
||||
hasPath
|
||||
isOpener()
|
||||
}
|
||||
|
||||
type notReadOnly interface {
|
||||
notReadOnly()
|
||||
}
|
||||
|
||||
//// define types by adding methods
|
||||
// hasPath
|
||||
func (p sshFxpLstatPacket) getPath() string { return p.Path }
|
||||
func (p sshFxpStatPacket) getPath() string { return p.Path }
|
||||
func (p sshFxpRmdirPacket) getPath() string { return p.Path }
|
||||
func (p sshFxpReadlinkPacket) getPath() string { return p.Path }
|
||||
func (p sshFxpRealpathPacket) getPath() string { return p.Path }
|
||||
func (p sshFxpMkdirPacket) getPath() string { return p.Path }
|
||||
func (p sshFxpSetstatPacket) getPath() string { return p.Path }
|
||||
func (p sshFxpStatvfsPacket) getPath() string { return p.Path }
|
||||
func (p sshFxpRemovePacket) getPath() string { return p.Filename }
|
||||
func (p sshFxpRenamePacket) getPath() string { return p.Oldpath }
|
||||
func (p sshFxpSymlinkPacket) getPath() string { return p.Targetpath }
|
||||
|
||||
// Openers implement hasPath and isOpener
|
||||
func (p sshFxpOpendirPacket) getPath() string { return p.Path }
|
||||
func (p sshFxpOpendirPacket) isOpener() {}
|
||||
func (p sshFxpOpenPacket) getPath() string { return p.Path }
|
||||
func (p sshFxpOpenPacket) isOpener() {}
|
||||
|
||||
// hasHandle
|
||||
func (p sshFxpFstatPacket) getHandle() string { return p.Handle }
|
||||
func (p sshFxpFsetstatPacket) getHandle() string { return p.Handle }
|
||||
func (p sshFxpReadPacket) getHandle() string { return p.Handle }
|
||||
func (p sshFxpWritePacket) getHandle() string { return p.Handle }
|
||||
func (p sshFxpReaddirPacket) getHandle() string { return p.Handle }
|
||||
|
||||
// notReadOnly
|
||||
func (p sshFxpWritePacket) notReadOnly() {}
|
||||
func (p sshFxpSetstatPacket) notReadOnly() {}
|
||||
func (p sshFxpFsetstatPacket) notReadOnly() {}
|
||||
func (p sshFxpRemovePacket) notReadOnly() {}
|
||||
func (p sshFxpMkdirPacket) notReadOnly() {}
|
||||
func (p sshFxpRmdirPacket) notReadOnly() {}
|
||||
func (p sshFxpRenamePacket) notReadOnly() {}
|
||||
func (p sshFxpSymlinkPacket) notReadOnly() {}
|
||||
|
||||
// this has a handle, but is only used for close
|
||||
func (p sshFxpClosePacket) getHandle() string { return p.Handle }
|
||||
|
||||
// some packets with ID are missing id()
|
||||
func (p sshFxpDataPacket) id() uint32 { return p.ID }
|
||||
func (p sshFxpStatusPacket) id() uint32 { return p.ID }
|
||||
func (p sshFxpStatResponse) id() uint32 { return p.ID }
|
||||
func (p sshFxpNamePacket) id() uint32 { return p.ID }
|
||||
func (p sshFxpHandlePacket) id() uint32 { return p.ID }
|
||||
func (p sshFxVersionPacket) id() uint32 { return 0 }
|
||||
|
||||
// take raw incoming packet data and build packet objects
|
||||
func makePacket(p rxPacket) (requestPacket, error) {
|
||||
var pkt requestPacket
|
||||
switch p.pktType {
|
||||
case ssh_FXP_INIT:
|
||||
pkt = &sshFxInitPacket{}
|
||||
case ssh_FXP_LSTAT:
|
||||
pkt = &sshFxpLstatPacket{}
|
||||
case ssh_FXP_OPEN:
|
||||
pkt = &sshFxpOpenPacket{}
|
||||
case ssh_FXP_CLOSE:
|
||||
pkt = &sshFxpClosePacket{}
|
||||
case ssh_FXP_READ:
|
||||
pkt = &sshFxpReadPacket{}
|
||||
case ssh_FXP_WRITE:
|
||||
pkt = &sshFxpWritePacket{}
|
||||
case ssh_FXP_FSTAT:
|
||||
pkt = &sshFxpFstatPacket{}
|
||||
case ssh_FXP_SETSTAT:
|
||||
pkt = &sshFxpSetstatPacket{}
|
||||
case ssh_FXP_FSETSTAT:
|
||||
pkt = &sshFxpFsetstatPacket{}
|
||||
case ssh_FXP_OPENDIR:
|
||||
pkt = &sshFxpOpendirPacket{}
|
||||
case ssh_FXP_READDIR:
|
||||
pkt = &sshFxpReaddirPacket{}
|
||||
case ssh_FXP_REMOVE:
|
||||
pkt = &sshFxpRemovePacket{}
|
||||
case ssh_FXP_MKDIR:
|
||||
pkt = &sshFxpMkdirPacket{}
|
||||
case ssh_FXP_RMDIR:
|
||||
pkt = &sshFxpRmdirPacket{}
|
||||
case ssh_FXP_REALPATH:
|
||||
pkt = &sshFxpRealpathPacket{}
|
||||
case ssh_FXP_STAT:
|
||||
pkt = &sshFxpStatPacket{}
|
||||
case ssh_FXP_RENAME:
|
||||
pkt = &sshFxpRenamePacket{}
|
||||
case ssh_FXP_READLINK:
|
||||
pkt = &sshFxpReadlinkPacket{}
|
||||
case ssh_FXP_SYMLINK:
|
||||
pkt = &sshFxpSymlinkPacket{}
|
||||
case ssh_FXP_EXTENDED:
|
||||
pkt = &sshFxpExtendedPacket{}
|
||||
default:
|
||||
return nil, errors.Errorf("unhandled packet type: %s", p.pktType)
|
||||
}
|
||||
if err := pkt.UnmarshalBinary(p.pktBytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pkt, nil
|
||||
}
|
3
vendor/github.com/pkg/sftp/packet.go
generated
vendored
3
vendor/github.com/pkg/sftp/packet.go
generated
vendored
|
@ -170,9 +170,6 @@ func unmarshalExtensionPair(b []byte) (extensionPair, []byte, error) {
|
|||
return ep, b, err
|
||||
}
|
||||
ep.Data, b, err = unmarshalStringSafe(b)
|
||||
if err != nil {
|
||||
return ep, b, err
|
||||
}
|
||||
return ep, b, err
|
||||
}
|
||||
|
||||
|
|
345
vendor/github.com/pkg/sftp/packet_test.go
generated
vendored
Normal file
345
vendor/github.com/pkg/sftp/packet_test.go
generated
vendored
Normal file
|
@ -0,0 +1,345 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var marshalUint32Tests = []struct {
|
||||
v uint32
|
||||
want []byte
|
||||
}{
|
||||
{1, []byte{0, 0, 0, 1}},
|
||||
{256, []byte{0, 0, 1, 0}},
|
||||
{^uint32(0), []byte{255, 255, 255, 255}},
|
||||
}
|
||||
|
||||
func TestMarshalUint32(t *testing.T) {
|
||||
for _, tt := range marshalUint32Tests {
|
||||
got := marshalUint32(nil, tt.v)
|
||||
if !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("marshalUint32(%d): want %v, got %v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var marshalUint64Tests = []struct {
|
||||
v uint64
|
||||
want []byte
|
||||
}{
|
||||
{1, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}},
|
||||
{256, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0}},
|
||||
{^uint64(0), []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
|
||||
{1 << 32, []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0}},
|
||||
}
|
||||
|
||||
func TestMarshalUint64(t *testing.T) {
|
||||
for _, tt := range marshalUint64Tests {
|
||||
got := marshalUint64(nil, tt.v)
|
||||
if !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("marshalUint64(%d): want %#v, got %#v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var marshalStringTests = []struct {
|
||||
v string
|
||||
want []byte
|
||||
}{
|
||||
{"", []byte{0, 0, 0, 0}},
|
||||
{"/foo", []byte{0x0, 0x0, 0x0, 0x4, 0x2f, 0x66, 0x6f, 0x6f}},
|
||||
}
|
||||
|
||||
func TestMarshalString(t *testing.T) {
|
||||
for _, tt := range marshalStringTests {
|
||||
got := marshalString(nil, tt.v)
|
||||
if !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("marshalString(%q): want %#v, got %#v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var marshalTests = []struct {
|
||||
v interface{}
|
||||
want []byte
|
||||
}{
|
||||
{uint8(1), []byte{1}},
|
||||
{byte(1), []byte{1}},
|
||||
{uint32(1), []byte{0, 0, 0, 1}},
|
||||
{uint64(1), []byte{0, 0, 0, 0, 0, 0, 0, 1}},
|
||||
{"foo", []byte{0x0, 0x0, 0x0, 0x3, 0x66, 0x6f, 0x6f}},
|
||||
{[]uint32{1, 2, 3, 4}, []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x4}},
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
for _, tt := range marshalTests {
|
||||
got := marshal(nil, tt.v)
|
||||
if !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("marshal(%v): want %#v, got %#v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var unmarshalUint32Tests = []struct {
|
||||
b []byte
|
||||
want uint32
|
||||
rest []byte
|
||||
}{
|
||||
{[]byte{0, 0, 0, 0}, 0, nil},
|
||||
{[]byte{0, 0, 1, 0}, 256, nil},
|
||||
{[]byte{255, 0, 0, 255}, 4278190335, nil},
|
||||
}
|
||||
|
||||
func TestUnmarshalUint32(t *testing.T) {
|
||||
for _, tt := range unmarshalUint32Tests {
|
||||
got, rest := unmarshalUint32(tt.b)
|
||||
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||
t.Errorf("unmarshalUint32(%v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var unmarshalUint64Tests = []struct {
|
||||
b []byte
|
||||
want uint64
|
||||
rest []byte
|
||||
}{
|
||||
{[]byte{0, 0, 0, 0, 0, 0, 0, 0}, 0, nil},
|
||||
{[]byte{0, 0, 0, 0, 0, 0, 1, 0}, 256, nil},
|
||||
{[]byte{255, 0, 0, 0, 0, 0, 0, 255}, 18374686479671623935, nil},
|
||||
}
|
||||
|
||||
func TestUnmarshalUint64(t *testing.T) {
|
||||
for _, tt := range unmarshalUint64Tests {
|
||||
got, rest := unmarshalUint64(tt.b)
|
||||
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||
t.Errorf("unmarshalUint64(%v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var unmarshalStringTests = []struct {
|
||||
b []byte
|
||||
want string
|
||||
rest []byte
|
||||
}{
|
||||
{marshalString(nil, ""), "", nil},
|
||||
{marshalString(nil, "blah"), "blah", nil},
|
||||
}
|
||||
|
||||
func TestUnmarshalString(t *testing.T) {
|
||||
for _, tt := range unmarshalStringTests {
|
||||
got, rest := unmarshalString(tt.b)
|
||||
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||
t.Errorf("unmarshalUint64(%v): want %q, %#v, got %q, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sendPacketTests = []struct {
|
||||
p encoding.BinaryMarshaler
|
||||
want []byte
|
||||
}{
|
||||
{sshFxInitPacket{
|
||||
Version: 3,
|
||||
Extensions: []extensionPair{
|
||||
{"posix-rename@openssh.com", "1"},
|
||||
},
|
||||
}, []byte{0x0, 0x0, 0x0, 0x26, 0x1, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x18, 0x70, 0x6f, 0x73, 0x69, 0x78, 0x2d, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x0, 0x0, 0x0, 0x1, 0x31}},
|
||||
|
||||
{sshFxpOpenPacket{
|
||||
ID: 1,
|
||||
Path: "/foo",
|
||||
Pflags: flags(os.O_RDONLY),
|
||||
}, []byte{0x0, 0x0, 0x0, 0x15, 0x3, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x4, 0x2f, 0x66, 0x6f, 0x6f, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0}},
|
||||
|
||||
{sshFxpWritePacket{
|
||||
ID: 124,
|
||||
Handle: "foo",
|
||||
Offset: 13,
|
||||
Length: uint32(len([]byte("bar"))),
|
||||
Data: []byte("bar"),
|
||||
}, []byte{0x0, 0x0, 0x0, 0x1b, 0x6, 0x0, 0x0, 0x0, 0x7c, 0x0, 0x0, 0x0, 0x3, 0x66, 0x6f, 0x6f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, 0x3, 0x62, 0x61, 0x72}},
|
||||
|
||||
{sshFxpSetstatPacket{
|
||||
ID: 31,
|
||||
Path: "/bar",
|
||||
Flags: flags(os.O_WRONLY),
|
||||
Attrs: struct {
|
||||
UID uint32
|
||||
GID uint32
|
||||
}{1000, 100},
|
||||
}, []byte{0x0, 0x0, 0x0, 0x19, 0x9, 0x0, 0x0, 0x0, 0x1f, 0x0, 0x0, 0x0, 0x4, 0x2f, 0x62, 0x61, 0x72, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x3, 0xe8, 0x0, 0x0, 0x0, 0x64}},
|
||||
}
|
||||
|
||||
func TestSendPacket(t *testing.T) {
|
||||
for _, tt := range sendPacketTests {
|
||||
var w bytes.Buffer
|
||||
sendPacket(&w, tt.p)
|
||||
if got := w.Bytes(); !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("sendPacket(%v): want %#v, got %#v", tt.p, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sp(p encoding.BinaryMarshaler) []byte {
|
||||
var w bytes.Buffer
|
||||
sendPacket(&w, p)
|
||||
return w.Bytes()
|
||||
}
|
||||
|
||||
var recvPacketTests = []struct {
|
||||
b []byte
|
||||
want uint8
|
||||
rest []byte
|
||||
}{
|
||||
{sp(sshFxInitPacket{
|
||||
Version: 3,
|
||||
Extensions: []extensionPair{
|
||||
{"posix-rename@openssh.com", "1"},
|
||||
},
|
||||
}), ssh_FXP_INIT, []byte{0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x18, 0x70, 0x6f, 0x73, 0x69, 0x78, 0x2d, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x0, 0x0, 0x0, 0x1, 0x31}},
|
||||
}
|
||||
|
||||
func TestRecvPacket(t *testing.T) {
|
||||
for _, tt := range recvPacketTests {
|
||||
r := bytes.NewReader(tt.b)
|
||||
got, rest, _ := recvPacket(r)
|
||||
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||
t.Errorf("recvPacket(%#v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHFxpOpenPacketreadonly(t *testing.T) {
|
||||
var tests = []struct {
|
||||
pflags uint32
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
pflags: ssh_FXF_READ,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
pflags: ssh_FXF_WRITE,
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
pflags: ssh_FXF_READ | ssh_FXF_WRITE,
|
||||
ok: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
p := &sshFxpOpenPacket{
|
||||
Pflags: tt.pflags,
|
||||
}
|
||||
|
||||
if want, got := tt.ok, p.readonly(); want != got {
|
||||
t.Errorf("unexpected value for p.readonly(): want: %v, got: %v",
|
||||
want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHFxpOpenPackethasPflags(t *testing.T) {
|
||||
var tests = []struct {
|
||||
desc string
|
||||
haveFlags uint32
|
||||
testFlags []uint32
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
desc: "have read, test against write",
|
||||
haveFlags: ssh_FXF_READ,
|
||||
testFlags: []uint32{ssh_FXF_WRITE},
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
desc: "have write, test against read",
|
||||
haveFlags: ssh_FXF_WRITE,
|
||||
testFlags: []uint32{ssh_FXF_READ},
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
desc: "have read+write, test against read",
|
||||
haveFlags: ssh_FXF_READ | ssh_FXF_WRITE,
|
||||
testFlags: []uint32{ssh_FXF_READ},
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
desc: "have read+write, test against write",
|
||||
haveFlags: ssh_FXF_READ | ssh_FXF_WRITE,
|
||||
testFlags: []uint32{ssh_FXF_WRITE},
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
desc: "have read+write, test against read+write",
|
||||
haveFlags: ssh_FXF_READ | ssh_FXF_WRITE,
|
||||
testFlags: []uint32{ssh_FXF_READ, ssh_FXF_WRITE},
|
||||
ok: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Log(tt.desc)
|
||||
|
||||
p := &sshFxpOpenPacket{
|
||||
Pflags: tt.haveFlags,
|
||||
}
|
||||
|
||||
if want, got := tt.ok, p.hasPflags(tt.testFlags...); want != got {
|
||||
t.Errorf("unexpected value for p.hasPflags(%#v): want: %v, got: %v",
|
||||
tt.testFlags, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalInit(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sp(sshFxInitPacket{
|
||||
Version: 3,
|
||||
Extensions: []extensionPair{
|
||||
{"posix-rename@openssh.com", "1"},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalOpen(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sp(sshFxpOpenPacket{
|
||||
ID: 1,
|
||||
Path: "/home/test/some/random/path",
|
||||
Pflags: flags(os.O_RDONLY),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalWriteWorstCase(b *testing.B) {
|
||||
data := make([]byte, 32*1024)
|
||||
for i := 0; i < b.N; i++ {
|
||||
sp(sshFxpWritePacket{
|
||||
ID: 1,
|
||||
Handle: "someopaquehandle",
|
||||
Offset: 0,
|
||||
Length: uint32(len(data)),
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalWrite1k(b *testing.B) {
|
||||
data := make([]byte, 1024)
|
||||
for i := 0; i < b.N; i++ {
|
||||
sp(sshFxpWritePacket{
|
||||
ID: 1,
|
||||
Handle: "someopaquehandle",
|
||||
Offset: 0,
|
||||
Length: uint32(len(data)),
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
}
|
220
vendor/github.com/pkg/sftp/request-example.go
generated
vendored
Normal file
220
vendor/github.com/pkg/sftp/request-example.go
generated
vendored
Normal file
|
@ -0,0 +1,220 @@
|
|||
package sftp
|
||||
|
||||
// This serves as an example of how to implement the request server handler as
|
||||
// well as a dummy backend for testing. It implements an in-memory backend that
|
||||
// works as a very simple filesystem with simple flat key-value lookup system.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// InMemHandler returns a Hanlders object with the test handlers
|
||||
func InMemHandler() Handlers {
|
||||
root := &root{
|
||||
files: make(map[string]*memFile),
|
||||
}
|
||||
root.memFile = newMemFile("/", true)
|
||||
return Handlers{root, root, root, root}
|
||||
}
|
||||
|
||||
// Handlers
|
||||
func (fs *root) Fileread(r Request) (io.ReaderAt, error) {
|
||||
fs.filesLock.Lock()
|
||||
defer fs.filesLock.Unlock()
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file.symlink != "" {
|
||||
file, err = fs.fetch(file.symlink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return file.ReaderAt()
|
||||
}
|
||||
|
||||
func (fs *root) Filewrite(r Request) (io.WriterAt, error) {
|
||||
fs.filesLock.Lock()
|
||||
defer fs.filesLock.Unlock()
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
if err == os.ErrNotExist {
|
||||
dir, err := fs.fetch(filepath.Dir(r.Filepath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !dir.isdir {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
file = newMemFile(r.Filepath, false)
|
||||
fs.files[r.Filepath] = file
|
||||
}
|
||||
return file.WriterAt()
|
||||
}
|
||||
|
||||
func (fs *root) Filecmd(r Request) error {
|
||||
fs.filesLock.Lock()
|
||||
defer fs.filesLock.Unlock()
|
||||
switch r.Method {
|
||||
case "Setstat":
|
||||
return nil
|
||||
case "Rename":
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := fs.files[r.Target]; ok {
|
||||
return &os.LinkError{Op: "rename", Old: r.Filepath, New: r.Target,
|
||||
Err: fmt.Errorf("dest file exists")}
|
||||
}
|
||||
fs.files[r.Target] = file
|
||||
delete(fs.files, r.Filepath)
|
||||
case "Rmdir", "Remove":
|
||||
_, err := fs.fetch(filepath.Dir(r.Filepath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(fs.files, r.Filepath)
|
||||
case "Mkdir":
|
||||
_, err := fs.fetch(filepath.Dir(r.Filepath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fs.files[r.Filepath] = newMemFile(r.Filepath, true)
|
||||
case "Symlink":
|
||||
_, err := fs.fetch(r.Filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
link := newMemFile(r.Target, false)
|
||||
link.symlink = r.Filepath
|
||||
fs.files[r.Target] = link
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *root) Fileinfo(r Request) ([]os.FileInfo, error) {
|
||||
fs.filesLock.Lock()
|
||||
defer fs.filesLock.Unlock()
|
||||
switch r.Method {
|
||||
case "List":
|
||||
list := []os.FileInfo{}
|
||||
for fn, fi := range fs.files {
|
||||
if filepath.Dir(fn) == r.Filepath {
|
||||
list = append(list, fi)
|
||||
}
|
||||
}
|
||||
return list, nil
|
||||
case "Stat":
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []os.FileInfo{file}, nil
|
||||
case "Readlink":
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file.symlink != "" {
|
||||
file, err = fs.fetch(file.symlink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return []os.FileInfo{file}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// In memory file-system-y thing that the Hanlders live on
|
||||
type root struct {
|
||||
*memFile
|
||||
files map[string]*memFile
|
||||
filesLock sync.Mutex
|
||||
}
|
||||
|
||||
func (fs *root) fetch(path string) (*memFile, error) {
|
||||
if path == "/" {
|
||||
return fs.memFile, nil
|
||||
}
|
||||
if file, ok := fs.files[path]; ok {
|
||||
return file, nil
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
// Implements os.FileInfo, Reader and Writer interfaces.
|
||||
// These are the 3 interfaces necessary for the Handlers.
|
||||
type memFile struct {
|
||||
name string
|
||||
modtime time.Time
|
||||
symlink string
|
||||
isdir bool
|
||||
content []byte
|
||||
contentLock sync.RWMutex
|
||||
}
|
||||
|
||||
// factory to make sure modtime is set
|
||||
func newMemFile(name string, isdir bool) *memFile {
|
||||
return &memFile{
|
||||
name: name,
|
||||
modtime: time.Now(),
|
||||
isdir: isdir,
|
||||
}
|
||||
}
|
||||
|
||||
// Have memFile fulfill os.FileInfo interface
|
||||
func (f *memFile) Name() string { return filepath.Base(f.name) }
|
||||
func (f *memFile) Size() int64 { return int64(len(f.content)) }
|
||||
func (f *memFile) Mode() os.FileMode {
|
||||
ret := os.FileMode(0644)
|
||||
if f.isdir {
|
||||
ret = os.FileMode(0755) | os.ModeDir
|
||||
}
|
||||
if f.symlink != "" {
|
||||
ret = os.FileMode(0777) | os.ModeSymlink
|
||||
}
|
||||
return ret
|
||||
}
|
||||
func (f *memFile) ModTime() time.Time { return f.modtime }
|
||||
func (f *memFile) IsDir() bool { return f.isdir }
|
||||
func (f *memFile) Sys() interface{} {
|
||||
return fakeFileInfoSys()
|
||||
}
|
||||
|
||||
// Read/Write
|
||||
func (f *memFile) ReaderAt() (io.ReaderAt, error) {
|
||||
if f.isdir {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
return bytes.NewReader(f.content), nil
|
||||
}
|
||||
|
||||
func (f *memFile) WriterAt() (io.WriterAt, error) {
|
||||
if f.isdir {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
func (f *memFile) WriteAt(p []byte, off int64) (int, error) {
|
||||
// fmt.Println(string(p), off)
|
||||
// mimic write delays, should be optional
|
||||
time.Sleep(time.Microsecond * time.Duration(len(p)))
|
||||
f.contentLock.Lock()
|
||||
defer f.contentLock.Unlock()
|
||||
plen := len(p) + int(off)
|
||||
if plen >= len(f.content) {
|
||||
nc := make([]byte, plen)
|
||||
copy(nc, f.content)
|
||||
f.content = nc
|
||||
}
|
||||
copy(f.content[off:], p)
|
||||
return len(p), nil
|
||||
}
|
30
vendor/github.com/pkg/sftp/request-interfaces.go
generated
vendored
Normal file
30
vendor/github.com/pkg/sftp/request-interfaces.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Interfaces are differentiated based on required returned values.
|
||||
// All input arguments are to be pulled from Request (the only arg).
|
||||
|
||||
// FileReader should return an io.Reader for the filepath
|
||||
type FileReader interface {
|
||||
Fileread(Request) (io.ReaderAt, error)
|
||||
}
|
||||
|
||||
// FileWriter should return an io.Writer for the filepath
|
||||
type FileWriter interface {
|
||||
Filewrite(Request) (io.WriterAt, error)
|
||||
}
|
||||
|
||||
// FileCmder should return an error (rename, remove, setstate, etc.)
|
||||
type FileCmder interface {
|
||||
Filecmd(Request) error
|
||||
}
|
||||
|
||||
// FileInfoer should return file listing info and errors (readdir, stat)
|
||||
// note stat requests would return a list of 1
|
||||
type FileInfoer interface {
|
||||
Fileinfo(Request) ([]os.FileInfo, error)
|
||||
}
|
48
vendor/github.com/pkg/sftp/request-readme.md
generated
vendored
Normal file
48
vendor/github.com/pkg/sftp/request-readme.md
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Request Based SFTP API
|
||||
|
||||
The request based API allows for custom backends in a way similar to the http
|
||||
package. In order to create a backend you need to implement 4 handler
|
||||
interfaces; one for reading, one for writing, one for misc commands and one for
|
||||
listing files. Each has 1 required method and in each case those methods take
|
||||
the Request as the only parameter and they each return something different.
|
||||
These 4 interfaces are enough to handle all the SFTP traffic in a simplified
|
||||
manner.
|
||||
|
||||
The Request structure has 5 public fields which you will deal with.
|
||||
|
||||
- Method (string) - string name of incoming call
|
||||
- Filepath (string) - path of file to act on
|
||||
- Attrs ([]byte) - byte string of file attribute data
|
||||
- Target (string) - target path for renames and sym-links
|
||||
|
||||
Below are the methods and a brief description of what they need to do.
|
||||
|
||||
### Fileread(*Request) (io.Reader, error)
|
||||
|
||||
Handler for "Get" method and returns an io.Reader for the file which the server
|
||||
then sends to the client.
|
||||
|
||||
### Filewrite(*Request) (io.Writer, error)
|
||||
|
||||
Handler for "Put" method and returns an io.Writer for the file which the server
|
||||
then writes the uploaded file to.
|
||||
|
||||
### Filecmd(*Request) error
|
||||
|
||||
Handles "SetStat", "Rename", "Rmdir", "Mkdir" and "Symlink" methods. Makes the
|
||||
appropriate changes and returns nil for success or an filesystem like error
|
||||
(eg. os.ErrNotExist).
|
||||
|
||||
### Fileinfo(*Request) ([]os.FileInfo, error)
|
||||
|
||||
Handles "List", "Stat", "Readlink" methods. Gathers/creates FileInfo structs
|
||||
with the data on the files and returns in a list (list of 1 for Stat and
|
||||
Readlink).
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
- Add support for API users to see trace/debugging info of what is going on
|
||||
inside SFTP server.
|
||||
- Consider adding support for SFTP file append only mode.
|
||||
|
231
vendor/github.com/pkg/sftp/request-server.go
generated
vendored
Normal file
231
vendor/github.com/pkg/sftp/request-server.go
generated
vendored
Normal file
|
@ -0,0 +1,231 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var maxTxPacket uint32 = 1 << 15
|
||||
|
||||
type handleHandler func(string) string
|
||||
|
||||
// Handlers contains the 4 SFTP server request handlers.
|
||||
type Handlers struct {
|
||||
FileGet FileReader
|
||||
FilePut FileWriter
|
||||
FileCmd FileCmder
|
||||
FileInfo FileInfoer
|
||||
}
|
||||
|
||||
// RequestServer abstracts the sftp protocol with an http request-like protocol
|
||||
type RequestServer struct {
|
||||
serverConn
|
||||
Handlers Handlers
|
||||
pktMgr packetManager
|
||||
openRequests map[string]Request
|
||||
openRequestLock sync.RWMutex
|
||||
handleCount int
|
||||
}
|
||||
|
||||
// NewRequestServer creates/allocates/returns new RequestServer.
|
||||
// Normally there there will be one server per user-session.
|
||||
func NewRequestServer(rwc io.ReadWriteCloser, h Handlers) *RequestServer {
|
||||
svrConn := serverConn{
|
||||
conn: conn{
|
||||
Reader: rwc,
|
||||
WriteCloser: rwc,
|
||||
},
|
||||
}
|
||||
return &RequestServer{
|
||||
serverConn: svrConn,
|
||||
Handlers: h,
|
||||
pktMgr: newPktMgr(&svrConn),
|
||||
openRequests: make(map[string]Request),
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *RequestServer) nextRequest(r Request) string {
|
||||
rs.openRequestLock.Lock()
|
||||
defer rs.openRequestLock.Unlock()
|
||||
rs.handleCount++
|
||||
handle := strconv.Itoa(rs.handleCount)
|
||||
rs.openRequests[handle] = r
|
||||
return handle
|
||||
}
|
||||
|
||||
func (rs *RequestServer) getRequest(handle string) (Request, bool) {
|
||||
rs.openRequestLock.RLock()
|
||||
defer rs.openRequestLock.RUnlock()
|
||||
r, ok := rs.openRequests[handle]
|
||||
return r, ok
|
||||
}
|
||||
|
||||
func (rs *RequestServer) closeRequest(handle string) {
|
||||
rs.openRequestLock.Lock()
|
||||
defer rs.openRequestLock.Unlock()
|
||||
if r, ok := rs.openRequests[handle]; ok {
|
||||
r.close()
|
||||
delete(rs.openRequests, handle)
|
||||
}
|
||||
}
|
||||
|
||||
// Close the read/write/closer to trigger exiting the main server loop
|
||||
func (rs *RequestServer) Close() error { return rs.conn.Close() }
|
||||
|
||||
// Serve requests for user session
|
||||
func (rs *RequestServer) Serve() error {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
workerFunc := func(ch requestChan) {
|
||||
wg.Add(1)
|
||||
defer wg.Done()
|
||||
if err := rs.packetWorker(ch); err != nil {
|
||||
rs.conn.Close() // shuts down recvPacket
|
||||
}
|
||||
}
|
||||
pktChan := rs.pktMgr.workerChan(workerFunc)
|
||||
|
||||
var err error
|
||||
var pkt requestPacket
|
||||
var pktType uint8
|
||||
var pktBytes []byte
|
||||
for {
|
||||
pktType, pktBytes, err = rs.recvPacket()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
pkt, err = makePacket(rxPacket{fxp(pktType), pktBytes})
|
||||
if err != nil {
|
||||
debug("makePacket err: %v", err)
|
||||
rs.conn.Close() // shuts down recvPacket
|
||||
break
|
||||
}
|
||||
|
||||
pktChan <- pkt
|
||||
}
|
||||
wg.Done()
|
||||
|
||||
close(pktChan) // shuts down sftpServerWorkers
|
||||
wg.Wait() // wait for all workers to exit
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (rs *RequestServer) packetWorker(pktChan chan requestPacket) error {
|
||||
for pkt := range pktChan {
|
||||
var rpkt responsePacket
|
||||
switch pkt := pkt.(type) {
|
||||
case *sshFxInitPacket:
|
||||
rpkt = sshFxVersionPacket{sftpProtocolVersion, nil}
|
||||
case *sshFxpClosePacket:
|
||||
handle := pkt.getHandle()
|
||||
rs.closeRequest(handle)
|
||||
rpkt = statusFromError(pkt, nil)
|
||||
case *sshFxpRealpathPacket:
|
||||
rpkt = cleanPath(pkt)
|
||||
case isOpener:
|
||||
handle := rs.nextRequest(requestFromPacket(pkt))
|
||||
rpkt = sshFxpHandlePacket{pkt.id(), handle}
|
||||
case *sshFxpFstatPacket:
|
||||
handle := pkt.getHandle()
|
||||
request, ok := rs.getRequest(handle)
|
||||
if !ok {
|
||||
rpkt = statusFromError(pkt, syscall.EBADF)
|
||||
} else {
|
||||
request = requestFromPacket(
|
||||
&sshFxpStatPacket{ID: pkt.id(), Path: request.Filepath})
|
||||
rpkt = rs.handle(request, pkt)
|
||||
}
|
||||
case *sshFxpFsetstatPacket:
|
||||
handle := pkt.getHandle()
|
||||
request, ok := rs.getRequest(handle)
|
||||
if !ok {
|
||||
rpkt = statusFromError(pkt, syscall.EBADF)
|
||||
} else {
|
||||
request = requestFromPacket(
|
||||
&sshFxpSetstatPacket{ID: pkt.id(), Path: request.Filepath,
|
||||
Flags: pkt.Flags, Attrs: pkt.Attrs,
|
||||
})
|
||||
rpkt = rs.handle(request, pkt)
|
||||
}
|
||||
case hasHandle:
|
||||
handle := pkt.getHandle()
|
||||
request, ok := rs.getRequest(handle)
|
||||
request.update(pkt)
|
||||
if !ok {
|
||||
rpkt = statusFromError(pkt, syscall.EBADF)
|
||||
} else {
|
||||
rpkt = rs.handle(request, pkt)
|
||||
}
|
||||
case hasPath:
|
||||
request := requestFromPacket(pkt)
|
||||
rpkt = rs.handle(request, pkt)
|
||||
default:
|
||||
return errors.Errorf("unexpected packet type %T", pkt)
|
||||
}
|
||||
|
||||
err := rs.sendPacket(rpkt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanPath(pkt *sshFxpRealpathPacket) responsePacket {
|
||||
path := pkt.getPath()
|
||||
if !filepath.IsAbs(path) {
|
||||
path = "/" + path
|
||||
} // all paths are absolute
|
||||
|
||||
cleaned_path := filepath.Clean(path)
|
||||
return &sshFxpNamePacket{
|
||||
ID: pkt.id(),
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
Name: cleaned_path,
|
||||
LongName: cleaned_path,
|
||||
Attrs: emptyFileStat,
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *RequestServer) handle(request Request, pkt requestPacket) responsePacket {
|
||||
// fmt.Println("Request Method: ", request.Method)
|
||||
rpkt, err := request.handle(rs.Handlers)
|
||||
if err != nil {
|
||||
err = errorAdapter(err)
|
||||
rpkt = statusFromError(pkt, err)
|
||||
}
|
||||
return rpkt
|
||||
}
|
||||
|
||||
// Wrap underlying connection methods to use packetManager
|
||||
func (rs *RequestServer) sendPacket(m encoding.BinaryMarshaler) error {
|
||||
if pkt, ok := m.(responsePacket); ok {
|
||||
rs.pktMgr.readyPacket(pkt)
|
||||
} else {
|
||||
return errors.Errorf("unexpected packet type %T", m)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rs *RequestServer) sendError(p ider, err error) error {
|
||||
return rs.sendPacket(statusFromError(p, err))
|
||||
}
|
||||
|
||||
// os.ErrNotExist should convert to ssh_FX_NO_SUCH_FILE, but is not recognized
|
||||
// by statusFromError. So we convert to syscall.ENOENT which it does.
|
||||
func errorAdapter(err error) error {
|
||||
if err == os.ErrNotExist {
|
||||
return syscall.ENOENT
|
||||
}
|
||||
return err
|
||||
}
|
330
vendor/github.com/pkg/sftp/request-server_test.go
generated
vendored
Normal file
330
vendor/github.com/pkg/sftp/request-server_test.go
generated
vendored
Normal file
|
@ -0,0 +1,330 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
type csPair struct {
|
||||
cli *Client
|
||||
svr *RequestServer
|
||||
}
|
||||
|
||||
// these must be closed in order, else client.Close will hang
|
||||
func (cs csPair) Close() {
|
||||
cs.svr.Close()
|
||||
cs.cli.Close()
|
||||
os.Remove(sock)
|
||||
}
|
||||
|
||||
func (cs csPair) testHandler() *root {
|
||||
return cs.svr.Handlers.FileGet.(*root)
|
||||
}
|
||||
|
||||
const sock = "/tmp/rstest.sock"
|
||||
|
||||
func clientRequestServerPair(t *testing.T) *csPair {
|
||||
ready := make(chan bool)
|
||||
os.Remove(sock) // either this or signal handling
|
||||
var server *RequestServer
|
||||
go func() {
|
||||
l, err := net.Listen("unix", sock)
|
||||
if err != nil {
|
||||
// neither assert nor t.Fatal reliably exit before Accept errors
|
||||
panic(err)
|
||||
}
|
||||
ready <- true
|
||||
fd, err := l.Accept()
|
||||
assert.Nil(t, err)
|
||||
handlers := InMemHandler()
|
||||
server = NewRequestServer(fd, handlers)
|
||||
server.Serve()
|
||||
}()
|
||||
<-ready
|
||||
defer os.Remove(sock)
|
||||
c, err := net.Dial("unix", sock)
|
||||
assert.Nil(t, err)
|
||||
client, err := NewClientPipe(c, c)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v\n", err)
|
||||
}
|
||||
return &csPair{client, server}
|
||||
}
|
||||
|
||||
// after adding logging, maybe check log to make sure packet handling
|
||||
// was split over more than one worker
|
||||
func TestRequestSplitWrite(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
w, err := p.cli.Create("/foo")
|
||||
assert.Nil(t, err)
|
||||
p.cli.maxPacket = 3 // force it to send in small chunks
|
||||
contents := "one two three four five six seven eight nine ten"
|
||||
w.Write([]byte(contents))
|
||||
w.Close()
|
||||
r := p.testHandler()
|
||||
f, _ := r.fetch("/foo")
|
||||
assert.Equal(t, contents, string(f.content))
|
||||
}
|
||||
|
||||
func TestRequestCache(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
foo := NewRequest("", "foo")
|
||||
bar := NewRequest("", "bar")
|
||||
fh := p.svr.nextRequest(foo)
|
||||
bh := p.svr.nextRequest(bar)
|
||||
assert.Len(t, p.svr.openRequests, 2)
|
||||
_foo, ok := p.svr.getRequest(fh)
|
||||
assert.Equal(t, foo, _foo)
|
||||
assert.True(t, ok)
|
||||
_, ok = p.svr.getRequest("zed")
|
||||
assert.False(t, ok)
|
||||
p.svr.closeRequest(fh)
|
||||
p.svr.closeRequest(bh)
|
||||
assert.Len(t, p.svr.openRequests, 0)
|
||||
}
|
||||
|
||||
func TestRequestCacheState(t *testing.T) {
|
||||
// test operation that uses open/close
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, p.svr.openRequests, 0)
|
||||
// test operation that doesn't open/close
|
||||
err = p.cli.Remove("/foo")
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, p.svr.openRequests, 0)
|
||||
}
|
||||
|
||||
func putTestFile(cli *Client, path, content string) (int, error) {
|
||||
w, err := cli.Create(path)
|
||||
if err == nil {
|
||||
defer w.Close()
|
||||
return w.Write([]byte(content))
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func TestRequestWrite(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
n, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 5, n)
|
||||
r := p.testHandler()
|
||||
f, err := r.fetch("/foo")
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, f.isdir)
|
||||
assert.Equal(t, f.content, []byte("hello"))
|
||||
}
|
||||
|
||||
// needs fail check
|
||||
func TestRequestFilename(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
r := p.testHandler()
|
||||
f, err := r.fetch("/foo")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, f.Name(), "foo")
|
||||
}
|
||||
|
||||
func TestRequestRead(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
rf, err := p.cli.Open("/foo")
|
||||
assert.Nil(t, err)
|
||||
defer rf.Close()
|
||||
contents := make([]byte, 5)
|
||||
n, err := rf.Read(contents)
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
assert.Equal(t, 5, n)
|
||||
assert.Equal(t, "hello", string(contents[0:5]))
|
||||
}
|
||||
|
||||
func TestRequestReadFail(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
rf, err := p.cli.Open("/foo")
|
||||
assert.Nil(t, err)
|
||||
contents := make([]byte, 5)
|
||||
n, err := rf.Read(contents)
|
||||
assert.Equal(t, n, 0)
|
||||
assert.Exactly(t, os.ErrNotExist, err)
|
||||
}
|
||||
|
||||
func TestRequestOpen(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
fh, err := p.cli.Open("foo")
|
||||
assert.Nil(t, err)
|
||||
err = fh.Close()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestRequestMkdir(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
err := p.cli.Mkdir("/foo")
|
||||
assert.Nil(t, err)
|
||||
r := p.testHandler()
|
||||
f, err := r.fetch("/foo")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, f.isdir)
|
||||
}
|
||||
|
||||
func TestRequestRemove(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
r := p.testHandler()
|
||||
_, err = r.fetch("/foo")
|
||||
assert.Nil(t, err)
|
||||
err = p.cli.Remove("/foo")
|
||||
assert.Nil(t, err)
|
||||
_, err = r.fetch("/foo")
|
||||
assert.Equal(t, err, os.ErrNotExist)
|
||||
}
|
||||
|
||||
func TestRequestRename(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
r := p.testHandler()
|
||||
_, err = r.fetch("/foo")
|
||||
assert.Nil(t, err)
|
||||
err = p.cli.Rename("/foo", "/bar")
|
||||
assert.Nil(t, err)
|
||||
_, err = r.fetch("/bar")
|
||||
assert.Nil(t, err)
|
||||
_, err = r.fetch("/foo")
|
||||
assert.Equal(t, err, os.ErrNotExist)
|
||||
}
|
||||
|
||||
func TestRequestRenameFail(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
_, err = putTestFile(p.cli, "/bar", "goodbye")
|
||||
assert.Nil(t, err)
|
||||
err = p.cli.Rename("/foo", "/bar")
|
||||
assert.IsType(t, &StatusError{}, err)
|
||||
}
|
||||
|
||||
func TestRequestStat(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
fi, err := p.cli.Stat("/foo")
|
||||
assert.Equal(t, fi.Name(), "foo")
|
||||
assert.Equal(t, fi.Size(), int64(5))
|
||||
assert.Equal(t, fi.Mode(), os.FileMode(0644))
|
||||
assert.NoError(t, testOsSys(fi.Sys()))
|
||||
}
|
||||
|
||||
// NOTE: Setstat is a noop in the request server tests, but we want to test
|
||||
// that is does nothing without crapping out.
|
||||
func TestRequestSetstat(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
mode := os.FileMode(0644)
|
||||
err = p.cli.Chmod("/foo", mode)
|
||||
assert.Nil(t, err)
|
||||
fi, err := p.cli.Stat("/foo")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, fi.Name(), "foo")
|
||||
assert.Equal(t, fi.Size(), int64(5))
|
||||
assert.Equal(t, fi.Mode(), os.FileMode(0644))
|
||||
assert.NoError(t, testOsSys(fi.Sys()))
|
||||
}
|
||||
|
||||
func TestRequestFstat(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
fp, err := p.cli.Open("/foo")
|
||||
assert.Nil(t, err)
|
||||
fi, err := fp.Stat()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, fi.Name(), "foo")
|
||||
assert.Equal(t, fi.Size(), int64(5))
|
||||
assert.Equal(t, fi.Mode(), os.FileMode(0644))
|
||||
assert.NoError(t, testOsSys(fi.Sys()))
|
||||
}
|
||||
|
||||
func TestRequestStatFail(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
fi, err := p.cli.Stat("/foo")
|
||||
assert.Nil(t, fi)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
}
|
||||
|
||||
func TestRequestSymlink(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
err = p.cli.Symlink("/foo", "/bar")
|
||||
assert.Nil(t, err)
|
||||
r := p.testHandler()
|
||||
fi, err := r.fetch("/bar")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, fi.Mode()&os.ModeSymlink == os.ModeSymlink)
|
||||
}
|
||||
|
||||
func TestRequestSymlinkFail(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
err := p.cli.Symlink("/foo", "/bar")
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
}
|
||||
|
||||
func TestRequestReadlink(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
err = p.cli.Symlink("/foo", "/bar")
|
||||
assert.Nil(t, err)
|
||||
rl, err := p.cli.ReadLink("/bar")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "foo", rl)
|
||||
}
|
||||
|
||||
func TestRequestReaddir(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
_, err = putTestFile(p.cli, "/bar", "goodbye")
|
||||
assert.Nil(t, err)
|
||||
di, err := p.cli.ReadDir("/")
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, di, 2)
|
||||
names := []string{di[0].Name(), di[1].Name()}
|
||||
sort.Strings(names)
|
||||
assert.Equal(t, []string{"bar", "foo"}, names)
|
||||
}
|
23
vendor/github.com/pkg/sftp/request-unix.go
generated
vendored
Normal file
23
vendor/github.com/pkg/sftp/request-unix.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
// +build !windows
|
||||
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func fakeFileInfoSys() interface{} {
|
||||
return &syscall.Stat_t{Uid: 65534, Gid: 65534}
|
||||
}
|
||||
|
||||
func testOsSys(sys interface{}) error {
|
||||
fstat := sys.(*FileStat)
|
||||
if fstat.UID != uint32(65534) {
|
||||
return errors.New("Uid failed to match.")
|
||||
}
|
||||
if fstat.GID != uint32(65534) {
|
||||
return errors.New("Gid failed to match:")
|
||||
}
|
||||
return nil
|
||||
}
|
303
vendor/github.com/pkg/sftp/request.go
generated
vendored
Normal file
303
vendor/github.com/pkg/sftp/request.go
generated
vendored
Normal file
|
@ -0,0 +1,303 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Request contains the data and state for the incoming service request.
|
||||
type Request struct {
|
||||
// Get, Put, Setstat, Stat, Rename, Remove
|
||||
// Rmdir, Mkdir, List, Readlink, Symlink
|
||||
Method string
|
||||
Filepath string
|
||||
Flags uint32
|
||||
Attrs []byte // convert to sub-struct
|
||||
Target string // for renames and sym-links
|
||||
// packet data
|
||||
pkt_id uint32
|
||||
packets chan packet_data
|
||||
// reader/writer/readdir from handlers
|
||||
stateLock *sync.RWMutex
|
||||
state *state
|
||||
}
|
||||
|
||||
type state struct {
|
||||
writerAt io.WriterAt
|
||||
readerAt io.ReaderAt
|
||||
endofdir bool // need to track when to send EOF for readdir
|
||||
}
|
||||
|
||||
type packet_data struct {
|
||||
id uint32
|
||||
data []byte
|
||||
length uint32
|
||||
offset int64
|
||||
}
|
||||
|
||||
// New Request initialized based on packet data
|
||||
func requestFromPacket(pkt hasPath) Request {
|
||||
method := requestMethod(pkt)
|
||||
request := NewRequest(method, pkt.getPath())
|
||||
request.pkt_id = pkt.id()
|
||||
switch p := pkt.(type) {
|
||||
case *sshFxpSetstatPacket:
|
||||
request.Flags = p.Flags
|
||||
request.Attrs = p.Attrs.([]byte)
|
||||
case *sshFxpRenamePacket:
|
||||
request.Target = filepath.Clean(p.Newpath)
|
||||
case *sshFxpSymlinkPacket:
|
||||
request.Target = filepath.Clean(p.Linkpath)
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
// NewRequest creates a new Request object.
|
||||
func NewRequest(method, path string) Request {
|
||||
request := Request{Method: method, Filepath: filepath.Clean(path)}
|
||||
request.packets = make(chan packet_data, sftpServerWorkerCount)
|
||||
request.state = &state{}
|
||||
request.stateLock = &sync.RWMutex{}
|
||||
return request
|
||||
}
|
||||
|
||||
// manage state
|
||||
func (r Request) setState(s interface{}) {
|
||||
r.stateLock.Lock()
|
||||
defer r.stateLock.Unlock()
|
||||
switch s := s.(type) {
|
||||
case io.WriterAt:
|
||||
r.state.writerAt = s
|
||||
case io.ReaderAt:
|
||||
r.state.readerAt = s
|
||||
case bool:
|
||||
r.state.endofdir = s
|
||||
}
|
||||
}
|
||||
|
||||
func (r Request) getWriter() io.WriterAt {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
return r.state.writerAt
|
||||
}
|
||||
|
||||
func (r Request) getReader() io.ReaderAt {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
return r.state.readerAt
|
||||
}
|
||||
|
||||
func (r Request) getEOD() bool {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
return r.state.endofdir
|
||||
}
|
||||
|
||||
// Close reader/writer if possible
|
||||
func (r Request) close() {
|
||||
rd := r.getReader()
|
||||
if c, ok := rd.(io.Closer); ok {
|
||||
c.Close()
|
||||
}
|
||||
wt := r.getWriter()
|
||||
if c, ok := wt.(io.Closer); ok {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// push packet_data into fifo
|
||||
func (r Request) pushPacket(pd packet_data) {
|
||||
r.packets <- pd
|
||||
}
|
||||
|
||||
// pop packet_data into fifo
|
||||
func (r *Request) popPacket() packet_data {
|
||||
return <-r.packets
|
||||
}
|
||||
|
||||
// called from worker to handle packet/request
|
||||
func (r Request) handle(handlers Handlers) (responsePacket, error) {
|
||||
var err error
|
||||
var rpkt responsePacket
|
||||
switch r.Method {
|
||||
case "Get":
|
||||
rpkt, err = fileget(handlers.FileGet, r)
|
||||
case "Put": // add "Append" to this to handle append only file writes
|
||||
rpkt, err = fileput(handlers.FilePut, r)
|
||||
case "Setstat", "Rename", "Rmdir", "Mkdir", "Symlink", "Remove":
|
||||
rpkt, err = filecmd(handlers.FileCmd, r)
|
||||
case "List", "Stat", "Readlink":
|
||||
rpkt, err = fileinfo(handlers.FileInfo, r)
|
||||
default:
|
||||
return rpkt, errors.Errorf("unexpected method: %s", r.Method)
|
||||
}
|
||||
return rpkt, err
|
||||
}
|
||||
|
||||
// wrap FileReader handler
|
||||
func fileget(h FileReader, r Request) (responsePacket, error) {
|
||||
var err error
|
||||
reader := r.getReader()
|
||||
if reader == nil {
|
||||
reader, err = h.Fileread(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.setState(reader)
|
||||
}
|
||||
|
||||
pd := r.popPacket()
|
||||
data := make([]byte, clamp(pd.length, maxTxPacket))
|
||||
n, err := reader.ReadAt(data, pd.offset)
|
||||
if err != nil && (err != io.EOF || n == 0) {
|
||||
return nil, err
|
||||
}
|
||||
return &sshFxpDataPacket{
|
||||
ID: pd.id,
|
||||
Length: uint32(n),
|
||||
Data: data[:n],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// wrap FileWriter handler
|
||||
func fileput(h FileWriter, r Request) (responsePacket, error) {
|
||||
var err error
|
||||
writer := r.getWriter()
|
||||
if writer == nil {
|
||||
writer, err = h.Filewrite(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.setState(writer)
|
||||
}
|
||||
|
||||
pd := r.popPacket()
|
||||
_, err = writer.WriteAt(pd.data, pd.offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sshFxpStatusPacket{
|
||||
ID: pd.id,
|
||||
StatusError: StatusError{
|
||||
Code: ssh_FX_OK,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
// wrap FileCmder handler
|
||||
func filecmd(h FileCmder, r Request) (responsePacket, error) {
|
||||
err := h.Filecmd(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sshFxpStatusPacket{
|
||||
ID: r.pkt_id,
|
||||
StatusError: StatusError{
|
||||
Code: ssh_FX_OK,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
// wrap FileInfoer handler
|
||||
func fileinfo(h FileInfoer, r Request) (responsePacket, error) {
|
||||
if r.getEOD() {
|
||||
return nil, io.EOF
|
||||
}
|
||||
finfo, err := h.Fileinfo(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case "List":
|
||||
pd := r.popPacket()
|
||||
dirname := path.Base(r.Filepath)
|
||||
ret := &sshFxpNamePacket{ID: pd.id}
|
||||
for _, fi := range finfo {
|
||||
ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{
|
||||
Name: fi.Name(),
|
||||
LongName: runLs(dirname, fi),
|
||||
Attrs: []interface{}{fi},
|
||||
})
|
||||
}
|
||||
r.setState(true)
|
||||
return ret, nil
|
||||
case "Stat":
|
||||
if len(finfo) == 0 {
|
||||
err = &os.PathError{Op: "stat", Path: r.Filepath,
|
||||
Err: syscall.ENOENT}
|
||||
return nil, err
|
||||
}
|
||||
return &sshFxpStatResponse{
|
||||
ID: r.pkt_id,
|
||||
info: finfo[0],
|
||||
}, nil
|
||||
case "Readlink":
|
||||
if len(finfo) == 0 {
|
||||
err = &os.PathError{Op: "readlink", Path: r.Filepath,
|
||||
Err: syscall.ENOENT}
|
||||
return nil, err
|
||||
}
|
||||
filename := finfo[0].Name()
|
||||
return &sshFxpNamePacket{
|
||||
ID: r.pkt_id,
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
Name: filename,
|
||||
LongName: filename,
|
||||
Attrs: emptyFileStat,
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// file data for additional read/write packets
|
||||
func (r *Request) update(p hasHandle) error {
|
||||
pd := packet_data{id: p.id()}
|
||||
switch p := p.(type) {
|
||||
case *sshFxpReadPacket:
|
||||
r.Method = "Get"
|
||||
pd.length = p.Len
|
||||
pd.offset = int64(p.Offset)
|
||||
case *sshFxpWritePacket:
|
||||
r.Method = "Put"
|
||||
pd.data = p.Data
|
||||
pd.length = p.Length
|
||||
pd.offset = int64(p.Offset)
|
||||
case *sshFxpReaddirPacket:
|
||||
r.Method = "List"
|
||||
default:
|
||||
return errors.Errorf("unexpected packet type %T", p)
|
||||
}
|
||||
r.pushPacket(pd)
|
||||
return nil
|
||||
}
|
||||
|
||||
// init attributes of request object from packet data
|
||||
func requestMethod(p hasPath) (method string) {
|
||||
switch p.(type) {
|
||||
case *sshFxpOpenPacket, *sshFxpOpendirPacket:
|
||||
method = "Open"
|
||||
case *sshFxpSetstatPacket:
|
||||
method = "Setstat"
|
||||
case *sshFxpRenamePacket:
|
||||
method = "Rename"
|
||||
case *sshFxpSymlinkPacket:
|
||||
method = "Symlink"
|
||||
case *sshFxpRemovePacket:
|
||||
method = "Remove"
|
||||
case *sshFxpStatPacket, *sshFxpLstatPacket:
|
||||
method = "Stat"
|
||||
case *sshFxpRmdirPacket:
|
||||
method = "Rmdir"
|
||||
case *sshFxpReadlinkPacket:
|
||||
method = "Readlink"
|
||||
case *sshFxpMkdirPacket:
|
||||
method = "Mkdir"
|
||||
}
|
||||
return method
|
||||
}
|
182
vendor/github.com/pkg/sftp/request_test.go
generated
vendored
Normal file
182
vendor/github.com/pkg/sftp/request_test.go
generated
vendored
Normal file
|
@ -0,0 +1,182 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testHandler struct {
|
||||
filecontents []byte // dummy contents
|
||||
output io.WriterAt // dummy file out
|
||||
err error // dummy error, should be file related
|
||||
}
|
||||
|
||||
func (t *testHandler) Fileread(r Request) (io.ReaderAt, error) {
|
||||
if t.err != nil {
|
||||
return nil, t.err
|
||||
}
|
||||
return bytes.NewReader(t.filecontents), nil
|
||||
}
|
||||
|
||||
func (t *testHandler) Filewrite(r Request) (io.WriterAt, error) {
|
||||
if t.err != nil {
|
||||
return nil, t.err
|
||||
}
|
||||
return io.WriterAt(t.output), nil
|
||||
}
|
||||
|
||||
func (t *testHandler) Filecmd(r Request) error {
|
||||
if t.err != nil {
|
||||
return t.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *testHandler) Fileinfo(r Request) ([]os.FileInfo, error) {
|
||||
if t.err != nil {
|
||||
return nil, t.err
|
||||
}
|
||||
f, err := os.Open(r.Filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []os.FileInfo{fi}, nil
|
||||
}
|
||||
|
||||
// make sure len(fakefile) == len(filecontents)
|
||||
type fakefile [10]byte
|
||||
|
||||
var filecontents = []byte("file-data.")
|
||||
|
||||
func testRequest(method string) Request {
|
||||
request := Request{
|
||||
Filepath: "./request_test.go",
|
||||
Method: method,
|
||||
Attrs: []byte("foo"),
|
||||
Target: "foo",
|
||||
packets: make(chan packet_data, sftpServerWorkerCount),
|
||||
state: &state{},
|
||||
stateLock: &sync.RWMutex{},
|
||||
}
|
||||
for _, p := range []packet_data{
|
||||
packet_data{id: 1, data: filecontents[:5], length: 5},
|
||||
packet_data{id: 2, data: filecontents[5:], length: 5, offset: 5}} {
|
||||
request.packets <- p
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
func (ff *fakefile) WriteAt(p []byte, off int64) (int, error) {
|
||||
n := copy(ff[off:], p)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (ff fakefile) string() string {
|
||||
b := make([]byte, len(ff))
|
||||
copy(b, ff[:])
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func newTestHandlers() Handlers {
|
||||
handler := &testHandler{
|
||||
filecontents: filecontents,
|
||||
output: &fakefile{},
|
||||
err: nil,
|
||||
}
|
||||
return Handlers{
|
||||
FileGet: handler,
|
||||
FilePut: handler,
|
||||
FileCmd: handler,
|
||||
FileInfo: handler,
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handlers) getOutString() string {
|
||||
handler := h.FilePut.(*testHandler)
|
||||
return handler.output.(*fakefile).string()
|
||||
}
|
||||
|
||||
var errTest = errors.New("test error")
|
||||
|
||||
func (h *Handlers) returnError() {
|
||||
handler := h.FilePut.(*testHandler)
|
||||
handler.err = errTest
|
||||
}
|
||||
|
||||
func statusOk(t *testing.T, p interface{}) {
|
||||
if pkt, ok := p.(*sshFxpStatusPacket); ok {
|
||||
assert.Equal(t, pkt.StatusError.Code, uint32(ssh_FX_OK))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestGet(t *testing.T) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest("Get")
|
||||
// req.length is 5, so we test reads in 5 byte chunks
|
||||
for i, txt := range []string{"file-", "data."} {
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
dpkt := pkt.(*sshFxpDataPacket)
|
||||
assert.Equal(t, dpkt.id(), uint32(i+1))
|
||||
assert.Equal(t, string(dpkt.Data), txt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestPut(t *testing.T) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest("Put")
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
statusOk(t, pkt)
|
||||
pkt, err = request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
statusOk(t, pkt)
|
||||
assert.Equal(t, "file-data.", handlers.getOutString())
|
||||
}
|
||||
|
||||
func TestRequestCmdr(t *testing.T) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest("Mkdir")
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
statusOk(t, pkt)
|
||||
|
||||
handlers.returnError()
|
||||
pkt, err = request.handle(handlers)
|
||||
assert.Nil(t, pkt)
|
||||
assert.Equal(t, err, errTest)
|
||||
}
|
||||
|
||||
func TestRequestInfoList(t *testing.T) { testInfoMethod(t, "List") }
|
||||
func TestRequestInfoReadlink(t *testing.T) { testInfoMethod(t, "Readlink") }
|
||||
func TestRequestInfoStat(t *testing.T) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest("Stat")
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
spkt, ok := pkt.(*sshFxpStatResponse)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, spkt.info.Name(), "request_test.go")
|
||||
}
|
||||
|
||||
func testInfoMethod(t *testing.T, method string) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest(method)
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
npkt, ok := pkt.(*sshFxpNamePacket)
|
||||
assert.True(t, ok)
|
||||
assert.IsType(t, sshFxpNameAttr{}, npkt.NameAttrs[0])
|
||||
assert.Equal(t, npkt.NameAttrs[0].Name, "request_test.go")
|
||||
}
|
11
vendor/github.com/pkg/sftp/request_windows.go
generated
vendored
Normal file
11
vendor/github.com/pkg/sftp/request_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
package sftp
|
||||
|
||||
import "syscall"
|
||||
|
||||
func fakeFileInfoSys() interface{} {
|
||||
return syscall.Win32FileAttributeData{}
|
||||
}
|
||||
|
||||
func testOsSys(sys interface{}) error {
|
||||
return nil
|
||||
}
|
140
vendor/github.com/pkg/sftp/server.go
generated
vendored
140
vendor/github.com/pkg/sftp/server.go
generated
vendored
|
@ -29,7 +29,7 @@ type Server struct {
|
|||
serverConn
|
||||
debugStream io.Writer
|
||||
readOnly bool
|
||||
pktChan chan rxPacket
|
||||
pktMgr packetManager
|
||||
openFiles map[string]*os.File
|
||||
openFilesLock sync.RWMutex
|
||||
handleCount int
|
||||
|
@ -75,15 +75,16 @@ type serverRespondablePacket interface {
|
|||
//
|
||||
// A subsequent call to Serve() is required to begin serving files over SFTP.
|
||||
func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error) {
|
||||
s := &Server{
|
||||
serverConn: serverConn{
|
||||
conn: conn{
|
||||
Reader: rwc,
|
||||
WriteCloser: rwc,
|
||||
},
|
||||
svrConn := serverConn{
|
||||
conn: conn{
|
||||
Reader: rwc,
|
||||
WriteCloser: rwc,
|
||||
},
|
||||
}
|
||||
s := &Server{
|
||||
serverConn: svrConn,
|
||||
debugStream: ioutil.Discard,
|
||||
pktChan: make(chan rxPacket, sftpServerWorkerCount),
|
||||
pktMgr: newPktMgr(&svrConn),
|
||||
openFiles: make(map[string]*os.File),
|
||||
maxTxPacket: 1 << 15,
|
||||
}
|
||||
|
@ -122,72 +123,14 @@ type rxPacket struct {
|
|||
}
|
||||
|
||||
// Up to N parallel servers
|
||||
func (svr *Server) sftpServerWorker() error {
|
||||
for p := range svr.pktChan {
|
||||
var pkt interface {
|
||||
encoding.BinaryUnmarshaler
|
||||
id() uint32
|
||||
}
|
||||
var readonly = true
|
||||
switch p.pktType {
|
||||
case ssh_FXP_INIT:
|
||||
pkt = &sshFxInitPacket{}
|
||||
case ssh_FXP_LSTAT:
|
||||
pkt = &sshFxpLstatPacket{}
|
||||
case ssh_FXP_OPEN:
|
||||
pkt = &sshFxpOpenPacket{}
|
||||
// readonly handled specially below
|
||||
case ssh_FXP_CLOSE:
|
||||
pkt = &sshFxpClosePacket{}
|
||||
case ssh_FXP_READ:
|
||||
pkt = &sshFxpReadPacket{}
|
||||
case ssh_FXP_WRITE:
|
||||
pkt = &sshFxpWritePacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_FSTAT:
|
||||
pkt = &sshFxpFstatPacket{}
|
||||
case ssh_FXP_SETSTAT:
|
||||
pkt = &sshFxpSetstatPacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_FSETSTAT:
|
||||
pkt = &sshFxpFsetstatPacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_OPENDIR:
|
||||
pkt = &sshFxpOpendirPacket{}
|
||||
case ssh_FXP_READDIR:
|
||||
pkt = &sshFxpReaddirPacket{}
|
||||
case ssh_FXP_REMOVE:
|
||||
pkt = &sshFxpRemovePacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_MKDIR:
|
||||
pkt = &sshFxpMkdirPacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_RMDIR:
|
||||
pkt = &sshFxpRmdirPacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_REALPATH:
|
||||
pkt = &sshFxpRealpathPacket{}
|
||||
case ssh_FXP_STAT:
|
||||
pkt = &sshFxpStatPacket{}
|
||||
case ssh_FXP_RENAME:
|
||||
pkt = &sshFxpRenamePacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_READLINK:
|
||||
pkt = &sshFxpReadlinkPacket{}
|
||||
case ssh_FXP_SYMLINK:
|
||||
pkt = &sshFxpSymlinkPacket{}
|
||||
readonly = false
|
||||
case ssh_FXP_EXTENDED:
|
||||
pkt = &sshFxpExtendedPacket{}
|
||||
default:
|
||||
return errors.Errorf("unhandled packet type: %s", p.pktType)
|
||||
}
|
||||
if err := pkt.UnmarshalBinary(p.pktBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
func (svr *Server) sftpServerWorker(pktChan chan requestPacket) error {
|
||||
for pkt := range pktChan {
|
||||
|
||||
// handle FXP_OPENDIR specially
|
||||
// readonly checks
|
||||
readonly := true
|
||||
switch pkt := pkt.(type) {
|
||||
case notReadOnly:
|
||||
readonly = false
|
||||
case *sshFxpOpenPacket:
|
||||
readonly = pkt.readonly()
|
||||
case *sshFxpExtendedPacket:
|
||||
|
@ -288,6 +231,7 @@ func handlePacket(s *Server, p interface{}) error {
|
|||
return s.sendError(p, err)
|
||||
}
|
||||
f = filepath.Clean(f)
|
||||
f = filepath.ToSlash(f) // make path more Unix like on windows servers
|
||||
return s.sendPacket(sshFxpNamePacket{
|
||||
ID: p.ID,
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
|
@ -338,17 +282,18 @@ func handlePacket(s *Server, p interface{}) error {
|
|||
// is stopped.
|
||||
func (svr *Server) Serve() error {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(sftpServerWorkerCount)
|
||||
for i := 0; i < sftpServerWorkerCount; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := svr.sftpServerWorker(); err != nil {
|
||||
svr.conn.Close() // shuts down recvPacket
|
||||
}
|
||||
}()
|
||||
wg.Add(1)
|
||||
workerFunc := func(ch requestChan) {
|
||||
wg.Add(1)
|
||||
defer wg.Done()
|
||||
if err := svr.sftpServerWorker(ch); err != nil {
|
||||
svr.conn.Close() // shuts down recvPacket
|
||||
}
|
||||
}
|
||||
pktChan := svr.pktMgr.workerChan(workerFunc)
|
||||
|
||||
var err error
|
||||
var pkt requestPacket
|
||||
var pktType uint8
|
||||
var pktBytes []byte
|
||||
for {
|
||||
|
@ -356,11 +301,20 @@ func (svr *Server) Serve() error {
|
|||
if err != nil {
|
||||
break
|
||||
}
|
||||
svr.pktChan <- rxPacket{fxp(pktType), pktBytes}
|
||||
}
|
||||
|
||||
close(svr.pktChan) // shuts down sftpServerWorkers
|
||||
wg.Wait() // wait for all workers to exit
|
||||
pkt, err = makePacket(rxPacket{fxp(pktType), pktBytes})
|
||||
if err != nil {
|
||||
debug("makePacket err: %v", err)
|
||||
svr.conn.Close() // shuts down recvPacket
|
||||
break
|
||||
}
|
||||
|
||||
pktChan <- pkt
|
||||
}
|
||||
wg.Done()
|
||||
|
||||
close(pktChan) // shuts down sftpServerWorkers
|
||||
wg.Wait() // wait for all workers to exit
|
||||
|
||||
// close any still-open files
|
||||
for handle, file := range svr.openFiles {
|
||||
|
@ -370,7 +324,21 @@ func (svr *Server) Serve() error {
|
|||
return err // error from recvPacket
|
||||
}
|
||||
|
||||
type id interface {
|
||||
// Wrap underlying connection methods to use packetManager
|
||||
func (svr *Server) sendPacket(m encoding.BinaryMarshaler) error {
|
||||
if pkt, ok := m.(responsePacket); ok {
|
||||
svr.pktMgr.readyPacket(pkt)
|
||||
} else {
|
||||
return errors.Errorf("unexpected packet type %T", m)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svr *Server) sendError(p ider, err error) error {
|
||||
return svr.sendPacket(statusFromError(p, err))
|
||||
}
|
||||
|
||||
type ider interface {
|
||||
id() uint32
|
||||
}
|
||||
|
||||
|
@ -565,7 +533,7 @@ func translateErrno(errno syscall.Errno) uint32 {
|
|||
return ssh_FX_FAILURE
|
||||
}
|
||||
|
||||
func statusFromError(p id, err error) sshFxpStatusPacket {
|
||||
func statusFromError(p ider, err error) sshFxpStatusPacket {
|
||||
ret := sshFxpStatusPacket{
|
||||
ID: p.id(),
|
||||
StatusError: StatusError{
|
||||
|
|
671
vendor/github.com/pkg/sftp/server_integration_test.go
generated
vendored
Normal file
671
vendor/github.com/pkg/sftp/server_integration_test.go
generated
vendored
Normal file
|
@ -0,0 +1,671 @@
|
|||
package sftp
|
||||
|
||||
// sftp server integration tests
|
||||
// enable with -integration
|
||||
// example invokation (darwin): gofmt -w `find . -name \*.go` && (cd server_standalone/ ; go build -tags debug) && go test -tags debug github.com/pkg/sftp -integration -v -sftp /usr/libexec/sftp-server -run ServerCompareSubsystems
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kr/fs"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
var testSftpClientBin = flag.String("sftp_client", "/usr/bin/sftp", "location of the sftp client binary")
|
||||
var sshServerDebugStream = ioutil.Discard
|
||||
var sftpServerDebugStream = ioutil.Discard
|
||||
var sftpClientDebugStream = ioutil.Discard
|
||||
|
||||
const (
|
||||
GOLANG_SFTP = true
|
||||
OPENSSH_SFTP = false
|
||||
)
|
||||
|
||||
var (
|
||||
hostPrivateKeySigner ssh.Signer
|
||||
privKey = []byte(`
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEArhp7SqFnXVZAgWREL9Ogs+miy4IU/m0vmdkoK6M97G9NX/Pj
|
||||
wf8I/3/ynxmcArbt8Rc4JgkjT2uxx/NqR0yN42N1PjO5Czu0dms1PSqcKIJdeUBV
|
||||
7gdrKSm9Co4d2vwfQp5mg47eG4w63pz7Drk9+VIyi9YiYH4bve7WnGDswn4ycvYZ
|
||||
slV5kKnjlfCdPig+g5P7yQYud0cDWVwyA0+kxvL6H3Ip+Fu8rLDZn4/P1WlFAIuc
|
||||
PAf4uEKDGGmC2URowi5eesYR7f6GN/HnBs2776laNlAVXZUmYTUfOGagwLsEkx8x
|
||||
XdNqntfbs2MOOoK+myJrNtcB9pCrM0H6um19uQIDAQABAoIBABkWr9WdVKvalgkP
|
||||
TdQmhu3mKRNyd1wCl+1voZ5IM9Ayac/98UAvZDiNU4Uhx52MhtVLJ0gz4Oa8+i16
|
||||
IkKMAZZW6ro/8dZwkBzQbieWUFJ2Fso2PyvB3etcnGU8/Yhk9IxBDzy+BbuqhYE2
|
||||
1ebVQtz+v1HvVZzaD11bYYm/Xd7Y28QREVfFen30Q/v3dv7dOteDE/RgDS8Czz7w
|
||||
jMW32Q8JL5grz7zPkMK39BLXsTcSYcaasT2ParROhGJZDmbgd3l33zKCVc1zcj9B
|
||||
SA47QljGd09Tys958WWHgtj2o7bp9v1Ufs4LnyKgzrB80WX1ovaSQKvd5THTLchO
|
||||
kLIhUAECgYEA2doGXy9wMBmTn/hjiVvggR1aKiBwUpnB87Hn5xCMgoECVhFZlT6l
|
||||
WmZe7R2klbtG1aYlw+y+uzHhoVDAJW9AUSV8qoDUwbRXvBVlp+In5wIqJ+VjfivK
|
||||
zgIfzomL5NvDz37cvPmzqIeySTowEfbQyq7CUQSoDtE9H97E2wWZhDkCgYEAzJdJ
|
||||
k+NSFoTkHhfD3L0xCDHpRV3gvaOeew8524fVtVUq53X8m91ng4AX1r74dCUYwwiF
|
||||
gqTtSSJfx2iH1xKnNq28M9uKg7wOrCKrRqNPnYUO3LehZEC7rwUr26z4iJDHjjoB
|
||||
uBcS7nw0LJ+0Zeg1IF+aIdZGV3MrAKnrzWPixYECgYBsffX6ZWebrMEmQ89eUtFF
|
||||
u9ZxcGI/4K8ErC7vlgBD5ffB4TYZ627xzFWuBLs4jmHCeNIJ9tct5rOVYN+wRO1k
|
||||
/CRPzYUnSqb+1jEgILL6istvvv+DkE+ZtNkeRMXUndWwel94BWsBnUKe0UmrSJ3G
|
||||
sq23J3iCmJW2T3z+DpXbkQKBgQCK+LUVDNPE0i42NsRnm+fDfkvLP7Kafpr3Umdl
|
||||
tMY474o+QYn+wg0/aPJIf9463rwMNyyhirBX/k57IIktUdFdtfPicd2MEGETElWv
|
||||
nN1GzYxD50Rs2f/jKisZhEwqT9YNyV9DkgDdGGdEbJNYqbv0qpwDIg8T9foe8E1p
|
||||
bdErgQKBgAt290I3L316cdxIQTkJh1DlScN/unFffITwu127WMr28Jt3mq3cZpuM
|
||||
Aecey/eEKCj+Rlas5NDYKsB18QIuAw+qqWyq0LAKLiAvP1965Rkc4PLScl3MgJtO
|
||||
QYa37FK0p8NcDeUuF86zXBVutwS5nJLchHhKfd590ks57OROtm29
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`)
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
hostPrivateKeySigner, err = ssh.ParsePrivateKey(privKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func keyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
permissions := &ssh.Permissions{
|
||||
CriticalOptions: map[string]string{},
|
||||
Extensions: map[string]string{},
|
||||
}
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func pwAuth(conn ssh.ConnMetadata, pw []byte) (*ssh.Permissions, error) {
|
||||
permissions := &ssh.Permissions{
|
||||
CriticalOptions: map[string]string{},
|
||||
Extensions: map[string]string{},
|
||||
}
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func basicServerConfig() *ssh.ServerConfig {
|
||||
config := ssh.ServerConfig{
|
||||
Config: ssh.Config{
|
||||
MACs: []string{"hmac-sha1"},
|
||||
},
|
||||
PasswordCallback: pwAuth,
|
||||
PublicKeyCallback: keyAuth,
|
||||
}
|
||||
config.AddHostKey(hostPrivateKeySigner)
|
||||
return &config
|
||||
}
|
||||
|
||||
type sshServer struct {
|
||||
useSubsystem bool
|
||||
conn net.Conn
|
||||
config *ssh.ServerConfig
|
||||
sshConn *ssh.ServerConn
|
||||
newChans <-chan ssh.NewChannel
|
||||
newReqs <-chan *ssh.Request
|
||||
}
|
||||
|
||||
func sshServerFromConn(conn net.Conn, useSubsystem bool, config *ssh.ServerConfig) (*sshServer, error) {
|
||||
// From a standard TCP connection to an encrypted SSH connection
|
||||
sshConn, newChans, newReqs, err := ssh.NewServerConn(conn, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svr := &sshServer{useSubsystem, conn, config, sshConn, newChans, newReqs}
|
||||
svr.listenChannels()
|
||||
return svr, nil
|
||||
}
|
||||
|
||||
func (svr *sshServer) Wait() error {
|
||||
return svr.sshConn.Wait()
|
||||
}
|
||||
|
||||
func (svr *sshServer) Close() error {
|
||||
return svr.sshConn.Close()
|
||||
}
|
||||
|
||||
func (svr *sshServer) listenChannels() {
|
||||
go func() {
|
||||
for chanReq := range svr.newChans {
|
||||
go svr.handleChanReq(chanReq)
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
for req := range svr.newReqs {
|
||||
go svr.handleReq(req)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (svr *sshServer) handleReq(req *ssh.Request) {
|
||||
switch req.Type {
|
||||
default:
|
||||
rejectRequest(req)
|
||||
}
|
||||
}
|
||||
|
||||
type sshChannelServer struct {
|
||||
svr *sshServer
|
||||
chanReq ssh.NewChannel
|
||||
ch ssh.Channel
|
||||
newReqs <-chan *ssh.Request
|
||||
}
|
||||
|
||||
type sshSessionChannelServer struct {
|
||||
*sshChannelServer
|
||||
env []string
|
||||
}
|
||||
|
||||
func (svr *sshServer) handleChanReq(chanReq ssh.NewChannel) {
|
||||
fmt.Fprintf(sshServerDebugStream, "channel request: %v, extra: '%v'\n", chanReq.ChannelType(), hex.EncodeToString(chanReq.ExtraData()))
|
||||
switch chanReq.ChannelType() {
|
||||
case "session":
|
||||
if ch, reqs, err := chanReq.Accept(); err != nil {
|
||||
fmt.Fprintf(sshServerDebugStream, "fail to accept channel request: %v\n", err)
|
||||
chanReq.Reject(ssh.ResourceShortage, "channel accept failure")
|
||||
} else {
|
||||
chsvr := &sshSessionChannelServer{
|
||||
sshChannelServer: &sshChannelServer{svr, chanReq, ch, reqs},
|
||||
env: append([]string{}, os.Environ()...),
|
||||
}
|
||||
chsvr.handle()
|
||||
}
|
||||
default:
|
||||
chanReq.Reject(ssh.UnknownChannelType, "channel type is not a session")
|
||||
}
|
||||
}
|
||||
|
||||
func (chsvr *sshSessionChannelServer) handle() {
|
||||
// should maybe do something here...
|
||||
go chsvr.handleReqs()
|
||||
}
|
||||
|
||||
func (chsvr *sshSessionChannelServer) handleReqs() {
|
||||
for req := range chsvr.newReqs {
|
||||
chsvr.handleReq(req)
|
||||
}
|
||||
fmt.Fprintf(sshServerDebugStream, "ssh server session channel complete\n")
|
||||
}
|
||||
|
||||
func (chsvr *sshSessionChannelServer) handleReq(req *ssh.Request) {
|
||||
switch req.Type {
|
||||
case "env":
|
||||
chsvr.handleEnv(req)
|
||||
case "subsystem":
|
||||
chsvr.handleSubsystem(req)
|
||||
default:
|
||||
rejectRequest(req)
|
||||
}
|
||||
}
|
||||
|
||||
func rejectRequest(req *ssh.Request) error {
|
||||
fmt.Fprintf(sshServerDebugStream, "ssh rejecting request, type: %s\n", req.Type)
|
||||
err := req.Reply(false, []byte{})
|
||||
if err != nil {
|
||||
fmt.Fprintf(sshServerDebugStream, "ssh request reply had error: %v\n", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func rejectRequestUnmarshalError(req *ssh.Request, s interface{}, err error) error {
|
||||
fmt.Fprintf(sshServerDebugStream, "ssh request unmarshaling error, type '%T': %v\n", s, err)
|
||||
rejectRequest(req)
|
||||
return err
|
||||
}
|
||||
|
||||
// env request form:
|
||||
type sshEnvRequest struct {
|
||||
Envvar string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (chsvr *sshSessionChannelServer) handleEnv(req *ssh.Request) error {
|
||||
envReq := &sshEnvRequest{}
|
||||
if err := ssh.Unmarshal(req.Payload, envReq); err != nil {
|
||||
return rejectRequestUnmarshalError(req, envReq, err)
|
||||
}
|
||||
req.Reply(true, nil)
|
||||
|
||||
found := false
|
||||
for i, envstr := range chsvr.env {
|
||||
if strings.HasPrefix(envstr, envReq.Envvar+"=") {
|
||||
found = true
|
||||
chsvr.env[i] = envReq.Envvar + "=" + envReq.Value
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
chsvr.env = append(chsvr.env, envReq.Envvar+"="+envReq.Value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Payload: int: command size, string: command
|
||||
type sshSubsystemRequest struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type sshSubsystemExitStatus struct {
|
||||
Status uint32
|
||||
}
|
||||
|
||||
func (chsvr *sshSessionChannelServer) handleSubsystem(req *ssh.Request) error {
|
||||
defer func() {
|
||||
err1 := chsvr.ch.CloseWrite()
|
||||
err2 := chsvr.ch.Close()
|
||||
fmt.Fprintf(sshServerDebugStream, "ssh server subsystem request complete, err: %v %v\n", err1, err2)
|
||||
}()
|
||||
|
||||
subsystemReq := &sshSubsystemRequest{}
|
||||
if err := ssh.Unmarshal(req.Payload, subsystemReq); err != nil {
|
||||
return rejectRequestUnmarshalError(req, subsystemReq, err)
|
||||
}
|
||||
|
||||
// reply to the ssh client
|
||||
|
||||
// no idea if this is actually correct spec-wise.
|
||||
// just enough for an sftp server to start.
|
||||
if subsystemReq.Name != "sftp" {
|
||||
return req.Reply(false, nil)
|
||||
}
|
||||
|
||||
req.Reply(true, nil)
|
||||
|
||||
if !chsvr.svr.useSubsystem {
|
||||
// use the openssh sftp server backend; this is to test the ssh code, not the sftp code,
|
||||
// or is used for comparison between our sftp subsystem and the openssh sftp subsystem
|
||||
cmd := exec.Command(*testSftp, "-e", "-l", "DEBUG") // log to stderr
|
||||
cmd.Stdin = chsvr.ch
|
||||
cmd.Stdout = chsvr.ch
|
||||
cmd.Stderr = sftpServerDebugStream
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
return cmd.Wait()
|
||||
}
|
||||
|
||||
sftpServer, err := NewServer(
|
||||
chsvr.ch,
|
||||
WithDebug(sftpServerDebugStream),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// wait for the session to close
|
||||
runErr := sftpServer.Serve()
|
||||
exitStatus := uint32(1)
|
||||
if runErr == nil {
|
||||
exitStatus = uint32(0)
|
||||
}
|
||||
|
||||
_, exitStatusErr := chsvr.ch.SendRequest("exit-status", false, ssh.Marshal(sshSubsystemExitStatus{exitStatus}))
|
||||
return exitStatusErr
|
||||
}
|
||||
|
||||
// starts an ssh server to test. returns: host string and port
|
||||
func testServer(t *testing.T, useSubsystem bool, readonly bool) (net.Listener, string, int) {
|
||||
if !*testIntegration {
|
||||
t.Skip("skipping intergration test")
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
host, portStr, err := net.SplitHostPort(listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
fmt.Fprintf(sshServerDebugStream, "ssh server socket closed: %v\n", err)
|
||||
break
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer conn.Close()
|
||||
sshSvr, err := sshServerFromConn(conn, useSubsystem, basicServerConfig())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
err = sshSvr.Wait()
|
||||
fmt.Fprintf(sshServerDebugStream, "ssh server finished, err: %v\n", err)
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
return listener, host, port
|
||||
}
|
||||
|
||||
func runSftpClient(t *testing.T, script string, path string, host string, port int) (string, error) {
|
||||
// if sftp client binary is unavailable, skip test
|
||||
if _, err := os.Stat(*testSftpClientBin); err != nil {
|
||||
t.Skip("sftp client binary unavailable")
|
||||
}
|
||||
args := []string{
|
||||
// "-vvvv",
|
||||
"-b", "-",
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
"-o", "LogLevel=ERROR",
|
||||
"-o", "UserKnownHostsFile /dev/null",
|
||||
"-P", fmt.Sprintf("%d", port), fmt.Sprintf("%s:%s", host, path),
|
||||
}
|
||||
cmd := exec.Command(*testSftpClientBin, args...)
|
||||
var stdout bytes.Buffer
|
||||
cmd.Stdin = bytes.NewBufferString(script)
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = sftpClientDebugStream
|
||||
if err := cmd.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
err := cmd.Wait()
|
||||
return string(stdout.Bytes()), err
|
||||
}
|
||||
|
||||
func TestServerCompareSubsystems(t *testing.T) {
|
||||
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
|
||||
listenerOp, hostOp, portOp := testServer(t, OPENSSH_SFTP, READONLY)
|
||||
defer listenerGo.Close()
|
||||
defer listenerOp.Close()
|
||||
|
||||
script := `
|
||||
ls /
|
||||
ls -l /
|
||||
ls /dev/
|
||||
ls -l /dev/
|
||||
ls -l /etc/
|
||||
ls -l /bin/
|
||||
ls -l /usr/bin/
|
||||
`
|
||||
outputGo, err := runSftpClient(t, script, "/", hostGo, portGo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
outputOp, err := runSftpClient(t, script, "/", hostOp, portOp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newlineRegex := regexp.MustCompile(`\r*\n`)
|
||||
spaceRegex := regexp.MustCompile(`\s+`)
|
||||
outputGoLines := newlineRegex.Split(outputGo, -1)
|
||||
outputOpLines := newlineRegex.Split(outputOp, -1)
|
||||
|
||||
for i, goLine := range outputGoLines {
|
||||
if i > len(outputOpLines) {
|
||||
t.Fatalf("output line count differs")
|
||||
}
|
||||
opLine := outputOpLines[i]
|
||||
bad := false
|
||||
if goLine != opLine {
|
||||
goWords := spaceRegex.Split(goLine, -1)
|
||||
opWords := spaceRegex.Split(opLine, -1)
|
||||
// allow words[2] and [3] to be different as these are users & groups
|
||||
// also allow words[1] to differ as the link count for directories like
|
||||
// proc is unstable during testing as processes are created/destroyed.
|
||||
for j, goWord := range goWords {
|
||||
if j > len(opWords) {
|
||||
bad = true
|
||||
}
|
||||
opWord := opWords[j]
|
||||
if goWord != opWord && j != 1 && j != 2 && j != 3 {
|
||||
bad = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bad {
|
||||
t.Errorf("outputs differ, go:\n%v\nopenssh:\n%v\n", goLine, opLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var rng = rand.New(rand.NewSource(time.Now().Unix()))
|
||||
|
||||
func randData(length int) []byte {
|
||||
data := make([]byte, length)
|
||||
for i := 0; i < length; i++ {
|
||||
data[i] = byte(rng.Uint32())
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func randName() string {
|
||||
return "sftp." + hex.EncodeToString(randData(16))
|
||||
}
|
||||
|
||||
func TestServerMkdirRmdir(t *testing.T) {
|
||||
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
|
||||
defer listenerGo.Close()
|
||||
|
||||
tmpDir := "/tmp/" + randName()
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// mkdir remote
|
||||
if _, err := runSftpClient(t, "mkdir "+tmpDir, "/", hostGo, portGo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// directory should now exist
|
||||
if _, err := os.Stat(tmpDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// now remove the directory
|
||||
if _, err := runSftpClient(t, "rmdir "+tmpDir, "/", hostGo, portGo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(tmpDir); err == nil {
|
||||
t.Fatal("should have error after deleting the directory")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerSymlink(t *testing.T) {
|
||||
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
|
||||
defer listenerGo.Close()
|
||||
|
||||
link := "/tmp/" + randName()
|
||||
defer os.RemoveAll(link)
|
||||
|
||||
// now create a symbolic link within the new directory
|
||||
if output, err := runSftpClient(t, "symlink /bin/sh "+link, "/", hostGo, portGo); err != nil {
|
||||
t.Fatalf("failed: %v %v", err, string(output))
|
||||
}
|
||||
|
||||
// symlink should now exist
|
||||
if stat, err := os.Lstat(link); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if (stat.Mode() & os.ModeSymlink) != os.ModeSymlink {
|
||||
t.Fatalf("is not a symlink: %v", stat.Mode())
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerPut(t *testing.T) {
|
||||
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
|
||||
defer listenerGo.Close()
|
||||
|
||||
tmpFileLocal := "/tmp/" + randName()
|
||||
tmpFileRemote := "/tmp/" + randName()
|
||||
defer os.RemoveAll(tmpFileLocal)
|
||||
defer os.RemoveAll(tmpFileRemote)
|
||||
|
||||
t.Logf("put: local %v remote %v", tmpFileLocal, tmpFileRemote)
|
||||
|
||||
// create a file with random contents. This will be the local file pushed to the server
|
||||
tmpFileLocalData := randData(10 * 1024 * 1024)
|
||||
if err := ioutil.WriteFile(tmpFileLocal, tmpFileLocalData, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// sftp the file to the server
|
||||
if output, err := runSftpClient(t, "put "+tmpFileLocal+" "+tmpFileRemote, "/", hostGo, portGo); err != nil {
|
||||
t.Fatalf("runSftpClient failed: %v, output\n%v\n", err, output)
|
||||
}
|
||||
|
||||
// tmpFile2 should now exist, with the same contents
|
||||
if tmpFileRemoteData, err := ioutil.ReadFile(tmpFileRemote); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if string(tmpFileLocalData) != string(tmpFileRemoteData) {
|
||||
t.Fatal("contents of file incorrect after put")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerGet(t *testing.T) {
|
||||
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
|
||||
defer listenerGo.Close()
|
||||
|
||||
tmpFileLocal := "/tmp/" + randName()
|
||||
tmpFileRemote := "/tmp/" + randName()
|
||||
defer os.RemoveAll(tmpFileLocal)
|
||||
defer os.RemoveAll(tmpFileRemote)
|
||||
|
||||
t.Logf("get: local %v remote %v", tmpFileLocal, tmpFileRemote)
|
||||
|
||||
// create a file with random contents. This will be the remote file pulled from the server
|
||||
tmpFileRemoteData := randData(10 * 1024 * 1024)
|
||||
if err := ioutil.WriteFile(tmpFileRemote, tmpFileRemoteData, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// sftp the file to the server
|
||||
if output, err := runSftpClient(t, "get "+tmpFileRemote+" "+tmpFileLocal, "/", hostGo, portGo); err != nil {
|
||||
t.Fatalf("runSftpClient failed: %v, output\n%v\n", err, output)
|
||||
}
|
||||
|
||||
// tmpFile2 should now exist, with the same contents
|
||||
if tmpFileLocalData, err := ioutil.ReadFile(tmpFileLocal); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if string(tmpFileLocalData) != string(tmpFileRemoteData) {
|
||||
t.Fatal("contents of file incorrect after put")
|
||||
}
|
||||
}
|
||||
|
||||
func compareDirectoriesRecursive(t *testing.T, aroot, broot string) {
|
||||
walker := fs.Walk(aroot)
|
||||
for walker.Step() {
|
||||
if err := walker.Err(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// find paths
|
||||
aPath := walker.Path()
|
||||
aRel, err := filepath.Rel(aroot, aPath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not find relative path for %v: %v", aPath, err)
|
||||
}
|
||||
bPath := path.Join(broot, aRel)
|
||||
|
||||
if aRel == "." {
|
||||
continue
|
||||
}
|
||||
|
||||
//t.Logf("comparing: %v a: %v b %v", aRel, aPath, bPath)
|
||||
|
||||
// if a is a link, the sftp recursive copy won't have copied it. ignore
|
||||
aLink, err := os.Lstat(aPath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not lstat %v: %v", aPath, err)
|
||||
}
|
||||
if aLink.Mode()&os.ModeSymlink != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// stat the files
|
||||
aFile, err := os.Stat(aPath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not stat %v: %v", aPath, err)
|
||||
}
|
||||
bFile, err := os.Stat(bPath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not stat %v: %v", bPath, err)
|
||||
}
|
||||
|
||||
// compare stats, with some leniency for the timestamp
|
||||
if aFile.Mode() != bFile.Mode() {
|
||||
t.Fatalf("modes different for %v: %v vs %v", aRel, aFile.Mode(), bFile.Mode())
|
||||
}
|
||||
if !aFile.IsDir() {
|
||||
if aFile.Size() != bFile.Size() {
|
||||
t.Fatalf("sizes different for %v: %v vs %v", aRel, aFile.Size(), bFile.Size())
|
||||
}
|
||||
}
|
||||
timeDiff := aFile.ModTime().Sub(bFile.ModTime())
|
||||
if timeDiff > time.Second || timeDiff < -time.Second {
|
||||
t.Fatalf("mtimes different for %v: %v vs %v", aRel, aFile.ModTime(), bFile.ModTime())
|
||||
}
|
||||
|
||||
// compare contents
|
||||
if !aFile.IsDir() {
|
||||
if aContents, err := ioutil.ReadFile(aPath); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if bContents, err := ioutil.ReadFile(bPath); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if string(aContents) != string(bContents) {
|
||||
t.Fatalf("contents different for %v", aRel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerPutRecursive(t *testing.T) {
|
||||
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
|
||||
defer listenerGo.Close()
|
||||
|
||||
dirLocal, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmpDirRemote := "/tmp/" + randName()
|
||||
defer os.RemoveAll(tmpDirRemote)
|
||||
|
||||
t.Logf("put recursive: local %v remote %v", dirLocal, tmpDirRemote)
|
||||
|
||||
// push this directory (source code etc) recursively to the server
|
||||
if output, err := runSftpClient(t, "mkdir "+tmpDirRemote+"\r\nput -r -P "+dirLocal+"/ "+tmpDirRemote+"/", "/", hostGo, portGo); err != nil {
|
||||
t.Fatalf("runSftpClient failed: %v, output\n%v\n", err, output)
|
||||
}
|
||||
|
||||
compareDirectoriesRecursive(t, dirLocal, path.Join(tmpDirRemote, path.Base(dirLocal)))
|
||||
}
|
||||
|
||||
func TestServerGetRecursive(t *testing.T) {
|
||||
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
|
||||
defer listenerGo.Close()
|
||||
|
||||
dirRemote, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmpDirLocal := "/tmp/" + randName()
|
||||
defer os.RemoveAll(tmpDirLocal)
|
||||
|
||||
t.Logf("get recursive: local %v remote %v", tmpDirLocal, dirRemote)
|
||||
|
||||
// pull this directory (source code etc) recursively from the server
|
||||
if output, err := runSftpClient(t, "lmkdir "+tmpDirLocal+"\r\nget -r -P "+dirRemote+"/ "+tmpDirLocal+"/", "/", hostGo, portGo); err != nil {
|
||||
t.Fatalf("runSftpClient failed: %v, output\n%v\n", err, output)
|
||||
}
|
||||
|
||||
compareDirectoriesRecursive(t, dirRemote, path.Join(tmpDirLocal, path.Base(dirRemote)))
|
||||
}
|
52
vendor/github.com/pkg/sftp/server_standalone/main.go
generated
vendored
Normal file
52
vendor/github.com/pkg/sftp/server_standalone/main.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
package main
|
||||
|
||||
// small wrapper around sftp server that allows it to be used as a separate process subsystem call by the ssh server.
|
||||
// in practice this will statically link; however this allows unit testing from the sftp client.
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
readOnly bool
|
||||
debugStderr bool
|
||||
debugLevel string
|
||||
options []sftp.ServerOption
|
||||
)
|
||||
|
||||
flag.BoolVar(&readOnly, "R", false, "read-only server")
|
||||
flag.BoolVar(&debugStderr, "e", false, "debug to stderr")
|
||||
flag.StringVar(&debugLevel, "l", "none", "debug level (ignored)")
|
||||
flag.Parse()
|
||||
|
||||
debugStream := ioutil.Discard
|
||||
if debugStderr {
|
||||
debugStream = os.Stderr
|
||||
}
|
||||
options = append(options, sftp.WithDebug(debugStream))
|
||||
|
||||
if readOnly {
|
||||
options = append(options, sftp.ReadOnly())
|
||||
}
|
||||
|
||||
svr, _ := sftp.NewServer(
|
||||
struct {
|
||||
io.Reader
|
||||
io.WriteCloser
|
||||
}{os.Stdin,
|
||||
os.Stdout,
|
||||
},
|
||||
options...,
|
||||
)
|
||||
if err := svr.Serve(); err != nil {
|
||||
fmt.Fprintf(debugStream, "sftp server completed with error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
95
vendor/github.com/pkg/sftp/server_test.go
generated
vendored
Normal file
95
vendor/github.com/pkg/sftp/server_test.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func clientServerPair(t *testing.T) (*Client, *Server) {
|
||||
cr, sw := io.Pipe()
|
||||
sr, cw := io.Pipe()
|
||||
server, err := NewServer(struct {
|
||||
io.Reader
|
||||
io.WriteCloser
|
||||
}{sr, sw})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go server.Serve()
|
||||
client, err := NewClientPipe(cr, cw)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v\n", err)
|
||||
}
|
||||
return client, server
|
||||
}
|
||||
|
||||
type sshFxpTestBadExtendedPacket struct {
|
||||
ID uint32
|
||||
Extension string
|
||||
Data string
|
||||
}
|
||||
|
||||
func (p sshFxpTestBadExtendedPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpTestBadExtendedPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 + 4 + // type(byte) + uint32 + uint32
|
||||
len(p.Extension) +
|
||||
len(p.Data)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_EXTENDED)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Extension)
|
||||
b = marshalString(b, p.Data)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// test that errors are sent back when we request an invalid extended packet operation
|
||||
func TestInvalidExtendedPacket(t *testing.T) {
|
||||
client, server := clientServerPair(t)
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
|
||||
badPacket := sshFxpTestBadExtendedPacket{client.nextID(), "thisDoesn'tExist", "foobar"}
|
||||
_, _, err := client.clientConn.sendPacket(badPacket)
|
||||
if err == nil {
|
||||
t.Fatal("expected error from bad packet")
|
||||
}
|
||||
|
||||
// try to stat a file; the client should have shut down.
|
||||
filePath := "/etc/passwd"
|
||||
_, err = client.Stat(filePath)
|
||||
if err == nil {
|
||||
t.Fatal("expected error from closed connection")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// test that server handles concurrent requests correctly
|
||||
func TestConcurrentRequests(t *testing.T) {
|
||||
client, server := clientServerPair(t)
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
|
||||
concurrency := 2
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(concurrency)
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
for j := 0; j < 1024; j++ {
|
||||
f, err := client.Open("/etc/passwd")
|
||||
if err != nil {
|
||||
t.Errorf("failed to open file: %v", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Errorf("failed t close file: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue