mirror of https://github.com/pkg/sftp.git
				
				
				
			
		
			
				
	
	
		
			261 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			261 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Package sftp implements the SSH File Transfer Protocol as described in
 | |
| // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
 | |
| package sftp
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 
 | |
| 	"github.com/pkg/errors"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	sshFxpInit          = 1
 | |
| 	sshFxpVersion       = 2
 | |
| 	sshFxpOpen          = 3
 | |
| 	sshFxpClose         = 4
 | |
| 	sshFxpRead          = 5
 | |
| 	sshFxpWrite         = 6
 | |
| 	sshFxpLstat         = 7
 | |
| 	sshFxpFstat         = 8
 | |
| 	sshFxpSetstat       = 9
 | |
| 	sshFxpFsetstat      = 10
 | |
| 	sshFxpOpendir       = 11
 | |
| 	sshFxpReaddir       = 12
 | |
| 	sshFxpRemove        = 13
 | |
| 	sshFxpMkdir         = 14
 | |
| 	sshFxpRmdir         = 15
 | |
| 	sshFxpRealpath      = 16
 | |
| 	sshFxpStat          = 17
 | |
| 	sshFxpRename        = 18
 | |
| 	sshFxpReadlink      = 19
 | |
| 	sshFxpSymlink       = 20
 | |
| 	sshFxpStatus        = 101
 | |
| 	sshFxpHandle        = 102
 | |
| 	sshFxpData          = 103
 | |
| 	sshFxpName          = 104
 | |
| 	sshFxpAttrs         = 105
 | |
| 	sshFxpExtended      = 200
 | |
| 	sshFxpExtendedReply = 201
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	sshFxOk               = 0
 | |
| 	sshFxEOF              = 1
 | |
| 	sshFxNoSuchFile       = 2
 | |
| 	sshFxPermissionDenied = 3
 | |
| 	sshFxFailure          = 4
 | |
| 	sshFxBadMessage       = 5
 | |
| 	sshFxNoConnection     = 6
 | |
| 	sshFxConnectionLost   = 7
 | |
| 	sshFxOPUnsupported    = 8
 | |
| 
 | |
| 	// see draft-ietf-secsh-filexfer-13
 | |
| 	// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.1
 | |
| 	sshFxInvalidHandle           = 9
 | |
| 	sshFxNoSuchPath              = 10
 | |
| 	sshFxFileAlreadyExists       = 11
 | |
| 	sshFxWriteProtect            = 12
 | |
| 	sshFxNoMedia                 = 13
 | |
| 	sshFxNoSpaceOnFilesystem     = 14
 | |
| 	sshFxQuotaExceeded           = 15
 | |
| 	sshFxUnknownPrincipal        = 16
 | |
| 	sshFxLockConflict            = 17
 | |
| 	sshFxDirNotEmpty             = 18
 | |
| 	sshFxNotADirectory           = 19
 | |
| 	sshFxInvalidFilename         = 20
 | |
| 	sshFxLinkLoop                = 21
 | |
| 	sshFxCannotDelete            = 22
 | |
| 	sshFxInvalidParameter        = 23
 | |
| 	sshFxFileIsADirectory        = 24
 | |
| 	sshFxByteRangeLockConflict   = 25
 | |
| 	sshFxByteRangeLockRefused    = 26
 | |
| 	sshFxDeletePending           = 27
 | |
| 	sshFxFileCorrupt             = 28
 | |
| 	sshFxOwnerInvalid            = 29
 | |
| 	sshFxGroupInvalid            = 30
 | |
| 	sshFxNoMatchingByteRangeLock = 31
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	sshFxfRead   = 0x00000001
 | |
| 	sshFxfWrite  = 0x00000002
 | |
| 	sshFxfAppend = 0x00000004
 | |
| 	sshFxfCreat  = 0x00000008
 | |
| 	sshFxfTrunc  = 0x00000010
 | |
| 	sshFxfExcl   = 0x00000020
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	// supportedSFTPExtensions defines the supported extensions
 | |
| 	supportedSFTPExtensions = []sshExtensionPair{
 | |
| 		{"hardlink@openssh.com", "1"},
 | |
| 		{"posix-rename@openssh.com", "1"},
 | |
| 		{"statvfs@openssh.com", "2"},
 | |
| 	}
 | |
| 	sftpExtensions = supportedSFTPExtensions
 | |
| )
 | |
| 
 | |
| type fxp uint8
 | |
| 
 | |
| func (f fxp) String() string {
 | |
| 	switch f {
 | |
| 	case sshFxpInit:
 | |
| 		return "SSH_FXP_INIT"
 | |
| 	case sshFxpVersion:
 | |
| 		return "SSH_FXP_VERSION"
 | |
| 	case sshFxpOpen:
 | |
| 		return "SSH_FXP_OPEN"
 | |
| 	case sshFxpClose:
 | |
| 		return "SSH_FXP_CLOSE"
 | |
| 	case sshFxpRead:
 | |
| 		return "SSH_FXP_READ"
 | |
| 	case sshFxpWrite:
 | |
| 		return "SSH_FXP_WRITE"
 | |
| 	case sshFxpLstat:
 | |
| 		return "SSH_FXP_LSTAT"
 | |
| 	case sshFxpFstat:
 | |
| 		return "SSH_FXP_FSTAT"
 | |
| 	case sshFxpSetstat:
 | |
| 		return "SSH_FXP_SETSTAT"
 | |
| 	case sshFxpFsetstat:
 | |
| 		return "SSH_FXP_FSETSTAT"
 | |
| 	case sshFxpOpendir:
 | |
| 		return "SSH_FXP_OPENDIR"
 | |
| 	case sshFxpReaddir:
 | |
| 		return "SSH_FXP_READDIR"
 | |
| 	case sshFxpRemove:
 | |
| 		return "SSH_FXP_REMOVE"
 | |
| 	case sshFxpMkdir:
 | |
| 		return "SSH_FXP_MKDIR"
 | |
| 	case sshFxpRmdir:
 | |
| 		return "SSH_FXP_RMDIR"
 | |
| 	case sshFxpRealpath:
 | |
| 		return "SSH_FXP_REALPATH"
 | |
| 	case sshFxpStat:
 | |
| 		return "SSH_FXP_STAT"
 | |
| 	case sshFxpRename:
 | |
| 		return "SSH_FXP_RENAME"
 | |
| 	case sshFxpReadlink:
 | |
| 		return "SSH_FXP_READLINK"
 | |
| 	case sshFxpSymlink:
 | |
| 		return "SSH_FXP_SYMLINK"
 | |
| 	case sshFxpStatus:
 | |
| 		return "SSH_FXP_STATUS"
 | |
| 	case sshFxpHandle:
 | |
| 		return "SSH_FXP_HANDLE"
 | |
| 	case sshFxpData:
 | |
| 		return "SSH_FXP_DATA"
 | |
| 	case sshFxpName:
 | |
| 		return "SSH_FXP_NAME"
 | |
| 	case sshFxpAttrs:
 | |
| 		return "SSH_FXP_ATTRS"
 | |
| 	case sshFxpExtended:
 | |
| 		return "SSH_FXP_EXTENDED"
 | |
| 	case sshFxpExtendedReply:
 | |
| 		return "SSH_FXP_EXTENDED_REPLY"
 | |
| 	default:
 | |
| 		return "unknown"
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type fx uint8
 | |
| 
 | |
| func (f fx) String() string {
 | |
| 	switch f {
 | |
| 	case sshFxOk:
 | |
| 		return "SSH_FX_OK"
 | |
| 	case sshFxEOF:
 | |
| 		return "SSH_FX_EOF"
 | |
| 	case sshFxNoSuchFile:
 | |
| 		return "SSH_FX_NO_SUCH_FILE"
 | |
| 	case sshFxPermissionDenied:
 | |
| 		return "SSH_FX_PERMISSION_DENIED"
 | |
| 	case sshFxFailure:
 | |
| 		return "SSH_FX_FAILURE"
 | |
| 	case sshFxBadMessage:
 | |
| 		return "SSH_FX_BAD_MESSAGE"
 | |
| 	case sshFxNoConnection:
 | |
| 		return "SSH_FX_NO_CONNECTION"
 | |
| 	case sshFxConnectionLost:
 | |
| 		return "SSH_FX_CONNECTION_LOST"
 | |
| 	case sshFxOPUnsupported:
 | |
| 		return "SSH_FX_OP_UNSUPPORTED"
 | |
| 	default:
 | |
| 		return "unknown"
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type unexpectedPacketErr struct {
 | |
| 	want, got uint8
 | |
| }
 | |
| 
 | |
| func (u *unexpectedPacketErr) Error() string {
 | |
| 	return fmt.Sprintf("sftp: unexpected packet: want %v, got %v", fxp(u.want), fxp(u.got))
 | |
| }
 | |
| 
 | |
| func unimplementedPacketErr(u uint8) error {
 | |
| 	return errors.Errorf("sftp: unimplemented packet type: got %v", fxp(u))
 | |
| }
 | |
| 
 | |
| type unexpectedIDErr struct{ want, got uint32 }
 | |
| 
 | |
| func (u *unexpectedIDErr) Error() string {
 | |
| 	return fmt.Sprintf("sftp: unexpected id: want %d, got %d", u.want, u.got)
 | |
| }
 | |
| 
 | |
| func unimplementedSeekWhence(whence int) error {
 | |
| 	return errors.Errorf("sftp: unimplemented seek whence %d", whence)
 | |
| }
 | |
| 
 | |
| func unexpectedCount(want, got uint32) error {
 | |
| 	return errors.Errorf("sftp: unexpected count: want %d, got %d", want, got)
 | |
| }
 | |
| 
 | |
| type unexpectedVersionErr struct{ want, got uint32 }
 | |
| 
 | |
| func (u *unexpectedVersionErr) Error() string {
 | |
| 	return fmt.Sprintf("sftp: unexpected server version: want %v, got %v", u.want, u.got)
 | |
| }
 | |
| 
 | |
| // A StatusError is returned when an SFTP operation fails, and provides
 | |
| // additional information about the failure.
 | |
| type StatusError struct {
 | |
| 	Code      uint32
 | |
| 	msg, lang string
 | |
| }
 | |
| 
 | |
| func (s *StatusError) Error() string {
 | |
| 	return fmt.Sprintf("sftp: %q (%v)", s.msg, fx(s.Code))
 | |
| }
 | |
| 
 | |
| // FxCode returns the error code typed to match against the exported codes
 | |
| func (s *StatusError) FxCode() fxerr {
 | |
| 	return fxerr(s.Code)
 | |
| }
 | |
| 
 | |
| func getSupportedExtensionByName(extensionName string) (sshExtensionPair, error) {
 | |
| 	for _, supportedExtension := range supportedSFTPExtensions {
 | |
| 		if supportedExtension.Name == extensionName {
 | |
| 			return supportedExtension, nil
 | |
| 		}
 | |
| 	}
 | |
| 	return sshExtensionPair{}, errors.Errorf("unsupported extension: %s", extensionName)
 | |
| }
 | |
| 
 | |
| // SetSFTPExtensions allows to customize the supported server extensions.
 | |
| // See the variable supportedSFTPExtensions for supported extensions.
 | |
| // This method accepts a slice of sshExtensionPair names for example 'hardlink@openssh.com'.
 | |
| // If an invalid extension is given an error will be returned and nothing will be changed
 | |
| func SetSFTPExtensions(extensions ...string) error {
 | |
| 	tempExtensions := []sshExtensionPair{}
 | |
| 	for _, extension := range extensions {
 | |
| 		sftpExtension, err := getSupportedExtensionByName(extension)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		tempExtensions = append(tempExtensions, sftpExtension)
 | |
| 	}
 | |
| 	sftpExtensions = tempExtensions
 | |
| 	return nil
 | |
| }
 |