Switch to using the dep tool and update all the dependencies

This commit is contained in:
Nick Craig-Wood 2017-05-11 15:39:54 +01:00
parent 5135ff73cb
commit 98c2d2c41b
5321 changed files with 4483201 additions and 5922 deletions

View file

@ -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
View file

@ -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
View 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
View file

@ -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 {

View 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")
}
}

View 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

File diff suppressed because it is too large Load diff

147
vendor/github.com/pkg/sftp/client_test.go generated vendored Normal file
View 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
View file

@ -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
View 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()
}

View 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))
}

View 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))
}

View 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)
}
}
}

View 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
View 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)
}
}
}

View 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))
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View file

@ -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
View 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
View 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
View 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()
}