| // 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 |
| sshFxUnlnownPrincipal = 16 |
| sshFxLockConflict = 17 |
| sshFxDitNotEmpty = 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 %v, got %v", u.want, u.got) |
| } |
| |
| func unimplementedSeekWhence(whence int) error { |
| return errors.Errorf("sftp: unimplemented seek whence %v", whence) |
| } |
| |
| func unexpectedCount(want, got uint32) error { |
| return errors.Errorf("sftp: unexpected count: want %v, got %v", 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: %v", 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 |
| } |