259 lines
6.5 KiB
Go
259 lines
6.5 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"
|
||
|
)
|
||
|
|
||
|
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 fmt.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 fmt.Errorf("sftp: unimplemented seek whence %d", whence)
|
||
|
}
|
||
|
|
||
|
func unexpectedCount(want, got uint32) error {
|
||
|
return fmt.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{}, fmt.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
|
||
|
}
|