aboutsummaryrefslogtreecommitdiff
path: root/sftp/sftp.go
diff options
context:
space:
mode:
Diffstat (limited to 'sftp/sftp.go')
-rw-r--r--sftp/sftp.go258
1 files changed, 258 insertions, 0 deletions
diff --git a/sftp/sftp.go b/sftp/sftp.go
new file mode 100644
index 0000000..9a63c39
--- /dev/null
+++ b/sftp/sftp.go
@@ -0,0 +1,258 @@
+// 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
+}