1348 lines · 35.6 KB · Go
RawDownload
package sftp import (	"bytes"	"encoding/binary"	"io"	"os"	"path"	"sync/atomic"	"syscall"	"time" 	"github.com/kr/fs"	"github.com/pkg/errors"	"golang.org/x/crypto/ssh") var (	// ErrInternalInconsistency indicates the packets sent and the data queued to be	// written to the file don't match up. It is an unusual error and usually is	// caused by bad behavior server side or connection issues. The error is	// limited in scope to the call where it happened, the client object is still	// OK to use as long as the connection is still open.	ErrInternalInconsistency = errors.New("internal inconsistency")	// InternalInconsistency alias for ErrInternalInconsistency.	//	// Deprecated: please use ErrInternalInconsistency	InternalInconsistency = ErrInternalInconsistency) // A ClientOption is a function which applies configuration to a Client.type ClientOption func(*Client) error // MaxPacketChecked sets the maximum size of the payload, measured in bytes.// This option only accepts sizes servers should support, ie. <= 32768 bytes.//// If you get the error "failed to send packet header: EOF" when copying a// large file, try lowering this number.//// The default packet size is 32768 bytes.func MaxPacketChecked(size int) ClientOption {	return func(c *Client) error {		if size < 1 {			return errors.Errorf("size must be greater or equal to 1")		}		if size > 32768 {			return errors.Errorf("sizes larger than 32KB might not work with all servers")		}		c.maxPacket = size		return nil	}} // UseFstat sets whether to use Fstat or Stat when File.WriteTo is called// (usually when copying files).// Some servers limit the amount of open files and calling Stat after opening// the file will throw an error From the server. Setting this flag will call// Fstat instead of Stat which is suppose to be called on an open file handle.//// It has been found that that with IBM Sterling SFTP servers which have// "extractability" level set to 1 which means only 1 file can be opened at// any given time.//// If the server you are working with still has an issue with both Stat and// Fstat calls you can always open a file and read it until the end.//// Another reason to read the file until its end and Fstat doesn't work is// that in some servers, reading a full file will automatically delete the// file as some of these mainframes map the file to a message in a queue.// Once the file has been read it will get deleted.func UseFstat(value bool) ClientOption {	return func(c *Client) error {		c.useFstat = value		return nil	}} // MaxPacketUnchecked sets the maximum size of the payload, measured in bytes.// It accepts sizes larger than the 32768 bytes all servers should support.// Only use a setting higher than 32768 if your application always connects to// the same server or after sufficiently broad testing.//// If you get the error "failed to send packet header: EOF" when copying a// large file, try lowering this number.//// The default packet size is 32768 bytes.func MaxPacketUnchecked(size int) ClientOption {	return func(c *Client) error {		if size < 1 {			return errors.Errorf("size must be greater or equal to 1")		}		c.maxPacket = size		return nil	}} // MaxPacket sets the maximum size of the payload, measured in bytes.// This option only accepts sizes servers should support, ie. <= 32768 bytes.// This is a synonym for MaxPacketChecked that provides backward compatibility.//// If you get the error "failed to send packet header: EOF" when copying a// large file, try lowering this number.//// The default packet size is 32768 bytes.func MaxPacket(size int) ClientOption {	return MaxPacketChecked(size)} // MaxConcurrentRequestsPerFile sets the maximum concurrent requests allowed for a single file.//// The default maximum concurrent requests is 64.func MaxConcurrentRequestsPerFile(n int) ClientOption {	return func(c *Client) error {		if n < 1 {			return errors.Errorf("n must be greater or equal to 1")		}		c.maxConcurrentRequests = n		return nil	}} // NewClient creates a new SFTP client on conn, using zero or more option// functions.func NewClient(conn *ssh.Client, opts ...ClientOption) (*Client, error) {	s, err := conn.NewSession()	if err != nil {		return nil, err	}	if err := s.RequestSubsystem("sftp"); err != nil {		return nil, err	}	pw, err := s.StdinPipe()	if err != nil {		return nil, err	}	pr, err := s.StdoutPipe()	if err != nil {		return nil, err	} 	return NewClientPipe(pr, pw, opts...)} // NewClientPipe creates a new SFTP client given a Reader and a WriteCloser.// This can be used for connecting to an SFTP server over TCP/TLS or by using// the system's ssh client program (e.g. via exec.Command).func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...ClientOption) (*Client, error) {	sftp := &Client{		clientConn: clientConn{			conn: conn{				Reader:      rd,				WriteCloser: wr,			},			inflight: make(map[uint32]chan<- result),			closed:   make(chan struct{}),		},		maxPacket:             1 << 15,		maxConcurrentRequests: 64,	}	if err := sftp.applyOptions(opts...); err != nil {		wr.Close()		return nil, err	}	if err := sftp.sendInit(); err != nil {		wr.Close()		return nil, err	}	if err := sftp.recvVersion(); err != nil {		wr.Close()		return nil, err	}	sftp.clientConn.wg.Add(1)	go sftp.loop()	return sftp, nil} // Client represents an SFTP session on a *ssh.ClientConn SSH connection.// Multiple Clients can be active on a single SSH connection, and a Client// may be called concurrently from multiple Goroutines.//// Client implements the github.com/kr/fs.FileSystem interface.type Client struct {	clientConn 	maxPacket             int // max packet size read or written.	nextid                uint32	maxConcurrentRequests int	useFstat              bool} // Create creates the named file mode 0666 (before umask), truncating it if it// already exists. If successful, methods on the returned File can be used for// I/O; the associated file descriptor has mode O_RDWR. If you need more// control over the flags/mode used to open the file see client.OpenFile.//// Note that some SFTP servers (eg. AWS Transfer) do not support opening files// read/write at the same time. For those services you will need to use// `client.OpenFile(os.O_WRONLY|os.O_CREATE|os.O_TRUNC)`.func (c *Client) Create(path string) (*File, error) {	return c.open(path, flags(os.O_RDWR|os.O_CREATE|os.O_TRUNC))} const sftpProtocolVersion = 3 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 func (c *Client) sendInit() error {	return c.clientConn.conn.sendPacket(sshFxInitPacket{		Version: sftpProtocolVersion, // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02	})} // returns the next value of c.nextidfunc (c *Client) nextID() uint32 {	return atomic.AddUint32(&c.nextid, 1)} func (c *Client) recvVersion() error {	typ, data, err := c.recvPacket()	if err != nil {		return err	}	if typ != sshFxpVersion {		return &unexpectedPacketErr{sshFxpVersion, typ}	} 	version, _ := unmarshalUint32(data)	if version != sftpProtocolVersion {		return &unexpectedVersionErr{sftpProtocolVersion, version}	} 	return nil} // Walk returns a new Walker rooted at root.func (c *Client) Walk(root string) *fs.Walker {	return fs.WalkFS(root, c)} // ReadDir reads the directory named by dirname and returns a list of// directory entries.func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {	handle, err := c.opendir(p)	if err != nil {		return nil, err	}	defer c.close(handle) // this has to defer earlier than the lock below	var attrs []os.FileInfo	var done = false	for !done {		id := c.nextID()		typ, data, err1 := c.sendPacket(sshFxpReaddirPacket{			ID:     id,			Handle: handle,		})		if err1 != nil {			err = err1			done = true			break		}		switch typ {		case sshFxpName:			sid, data := unmarshalUint32(data)			if sid != id {				return nil, &unexpectedIDErr{id, sid}			}			count, data := unmarshalUint32(data)			for i := uint32(0); i < count; i++ {				var filename string				filename, data = unmarshalString(data)				_, data = unmarshalString(data) // discard longname				var attr *FileStat				attr, data = unmarshalAttrs(data)				if filename == "." || filename == ".." {					continue				}				attrs = append(attrs, fileInfoFromStat(attr, path.Base(filename)))			}		case sshFxpStatus:			// TODO(dfc) scope warning!			err = normaliseError(unmarshalStatus(id, data))			done = true		default:			return nil, unimplementedPacketErr(typ)		}	}	if err == io.EOF {		err = nil	}	return attrs, err} func (c *Client) opendir(path string) (string, error) {	id := c.nextID()	typ, data, err := c.sendPacket(sshFxpOpendirPacket{		ID:   id,		Path: path,	})	if err != nil {		return "", err	}	switch typ {	case sshFxpHandle:		sid, data := unmarshalUint32(data)		if sid != id {			return "", &unexpectedIDErr{id, sid}		}		handle, _ := unmarshalString(data)		return handle, nil	case sshFxpStatus:		return "", normaliseError(unmarshalStatus(id, data))	default:		return "", unimplementedPacketErr(typ)	}} // Stat returns a FileInfo structure describing the file specified by path 'p'.// If 'p' is a symbolic link, the returned FileInfo structure describes the referent file.func (c *Client) Stat(p string) (os.FileInfo, error) {	id := c.nextID()	typ, data, err := c.sendPacket(sshFxpStatPacket{		ID:   id,		Path: p,	})	if err != nil {		return nil, err	}	switch typ {	case sshFxpAttrs:		sid, data := unmarshalUint32(data)		if sid != id {			return nil, &unexpectedIDErr{id, sid}		}		attr, _ := unmarshalAttrs(data)		return fileInfoFromStat(attr, path.Base(p)), nil	case sshFxpStatus:		return nil, normaliseError(unmarshalStatus(id, data))	default:		return nil, unimplementedPacketErr(typ)	}} // Lstat returns a FileInfo structure describing the file specified by path 'p'.// If 'p' is a symbolic link, the returned FileInfo structure describes the symbolic link.func (c *Client) Lstat(p string) (os.FileInfo, error) {	id := c.nextID()	typ, data, err := c.sendPacket(sshFxpLstatPacket{		ID:   id,		Path: p,	})	if err != nil {		return nil, err	}	switch typ {	case sshFxpAttrs:		sid, data := unmarshalUint32(data)		if sid != id {			return nil, &unexpectedIDErr{id, sid}		}		attr, _ := unmarshalAttrs(data)		return fileInfoFromStat(attr, path.Base(p)), nil	case sshFxpStatus:		return nil, normaliseError(unmarshalStatus(id, data))	default:		return nil, unimplementedPacketErr(typ)	}} // ReadLink reads the target of a symbolic link.func (c *Client) ReadLink(p string) (string, error) {	id := c.nextID()	typ, data, err := c.sendPacket(sshFxpReadlinkPacket{		ID:   id,		Path: p,	})	if err != nil {		return "", err	}	switch typ {	case sshFxpName:		sid, data := unmarshalUint32(data)		if sid != id {			return "", &unexpectedIDErr{id, sid}		}		count, data := unmarshalUint32(data)		if count != 1 {			return "", unexpectedCount(1, count)		}		filename, _ := unmarshalString(data) // ignore dummy attributes		return filename, nil	case sshFxpStatus:		return "", normaliseError(unmarshalStatus(id, data))	default:		return "", unimplementedPacketErr(typ)	}} // Link creates a hard link at 'newname', pointing at the same inode as 'oldname'func (c *Client) Link(oldname, newname string) error {	id := c.nextID()	typ, data, err := c.sendPacket(sshFxpHardlinkPacket{		ID:      id,		Oldpath: oldname,		Newpath: newname,	})	if err != nil {		return err	}	switch typ {	case sshFxpStatus:		return normaliseError(unmarshalStatus(id, data))	default:		return unimplementedPacketErr(typ)	}} // Symlink creates a symbolic link at 'newname', pointing at target 'oldname'func (c *Client) Symlink(oldname, newname string) error {	id := c.nextID()	typ, data, err := c.sendPacket(sshFxpSymlinkPacket{		ID:         id,		Linkpath:   newname,		Targetpath: oldname,	})	if err != nil {		return err	}	switch typ {	case sshFxpStatus:		return normaliseError(unmarshalStatus(id, data))	default:		return unimplementedPacketErr(typ)	}} // setstat is a convience wrapper to allow for changing of various parts of the file descriptor.func (c *Client) setstat(path string, flags uint32, attrs interface{}) error {	id := c.nextID()	typ, data, err := c.sendPacket(sshFxpSetstatPacket{		ID:    id,		Path:  path,		Flags: flags,		Attrs: attrs,	})	if err != nil {		return err	}	switch typ {	case sshFxpStatus:		return normaliseError(unmarshalStatus(id, data))	default:		return unimplementedPacketErr(typ)	}} // Chtimes changes the access and modification times of the named file.func (c *Client) Chtimes(path string, atime time.Time, mtime time.Time) error {	type times struct {		Atime uint32		Mtime uint32	}	attrs := times{uint32(atime.Unix()), uint32(mtime.Unix())}	return c.setstat(path, sshFileXferAttrACmodTime, attrs)} // Chown changes the user and group owners of the named file.func (c *Client) Chown(path string, uid, gid int) error {	type owner struct {		UID uint32		GID uint32	}	attrs := owner{uint32(uid), uint32(gid)}	return c.setstat(path, sshFileXferAttrUIDGID, attrs)} // Chmod changes the permissions of the named file.func (c *Client) Chmod(path string, mode os.FileMode) error {	return c.setstat(path, sshFileXferAttrPermissions, uint32(mode))} // Truncate sets the size of the named file. Although it may be safely assumed// that if the size is less than its current size it will be truncated to fit,// the SFTP protocol does not specify what behavior the server should do when setting// size greater than the current size.func (c *Client) Truncate(path string, size int64) error {	return c.setstat(path, sshFileXferAttrSize, uint64(size))} // Open opens the named file for reading. If successful, methods on the// returned file can be used for reading; the associated file descriptor// has mode O_RDONLY.func (c *Client) Open(path string) (*File, error) {	return c.open(path, flags(os.O_RDONLY))} // OpenFile is the generalized open call; most users will use Open or// Create instead. It opens the named file with specified flag (O_RDONLY// etc.). If successful, methods on the returned File can be used for I/O.func (c *Client) OpenFile(path string, f int) (*File, error) {	return c.open(path, flags(f))} 

Lines 1–500 of 1348.