1// Package sftp implements the SSH File Transfer Protocol as described in 2// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 3package sftp 4 5import ( 6 "fmt" 7) 8 9const ( 10 sshFxpInit = 1 11 sshFxpVersion = 2 12 sshFxpOpen = 3 13 sshFxpClose = 4 14 sshFxpRead = 5 15 sshFxpWrite = 6 16 sshFxpLstat = 7 17 sshFxpFstat = 8 18 sshFxpSetstat = 9 19 sshFxpFsetstat = 10 20 sshFxpOpendir = 11 21 sshFxpReaddir = 12 22 sshFxpRemove = 13 23 sshFxpMkdir = 14 24 sshFxpRmdir = 15 25 sshFxpRealpath = 16 26 sshFxpStat = 17 27 sshFxpRename = 18 28 sshFxpReadlink = 19 29 sshFxpSymlink = 20 30 sshFxpStatus = 101 31 sshFxpHandle = 102 32 sshFxpData = 103 33 sshFxpName = 104 34 sshFxpAttrs = 105 35 sshFxpExtended = 200 36 sshFxpExtendedReply = 201 37) 38 39const ( 40 sshFxOk = 0 41 sshFxEOF = 1 42 sshFxNoSuchFile = 2 43 sshFxPermissionDenied = 3 44 sshFxFailure = 4 45 sshFxBadMessage = 5 46 sshFxNoConnection = 6 47 sshFxConnectionLost = 7 48 sshFxOPUnsupported = 8 49 50 // see draft-ietf-secsh-filexfer-13 51 // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.1 52 sshFxInvalidHandle = 9 53 sshFxNoSuchPath = 10 54 sshFxFileAlreadyExists = 11 55 sshFxWriteProtect = 12 56 sshFxNoMedia = 13 57 sshFxNoSpaceOnFilesystem = 14 58 sshFxQuotaExceeded = 15 59 sshFxUnknownPrincipal = 16 60 sshFxLockConflict = 17 61 sshFxDirNotEmpty = 18 62 sshFxNotADirectory = 19 63 sshFxInvalidFilename = 20 64 sshFxLinkLoop = 21 65 sshFxCannotDelete = 22 66 sshFxInvalidParameter = 23 67 sshFxFileIsADirectory = 24 68 sshFxByteRangeLockConflict = 25 69 sshFxByteRangeLockRefused = 26 70 sshFxDeletePending = 27 71 sshFxFileCorrupt = 28 72 sshFxOwnerInvalid = 29 73 sshFxGroupInvalid = 30 74 sshFxNoMatchingByteRangeLock = 31 75) 76 77const ( 78 sshFxfRead = 0x00000001 79 sshFxfWrite = 0x00000002 80 sshFxfAppend = 0x00000004 81 sshFxfCreat = 0x00000008 82 sshFxfTrunc = 0x00000010 83 sshFxfExcl = 0x00000020 84) 85 86var ( 87 // supportedSFTPExtensions defines the supported extensions 88 supportedSFTPExtensions = []sshExtensionPair{ 89 {"hardlink@openssh.com", "1"}, 90 {"posix-rename@openssh.com", "1"}, 91 {"statvfs@openssh.com", "2"}, 92 } 93 sftpExtensions = supportedSFTPExtensions 94) 95 96type fxp uint8 97 98func (f fxp) String() string { 99 switch f { 100 case sshFxpInit: 101 return "SSH_FXP_INIT" 102 case sshFxpVersion: 103 return "SSH_FXP_VERSION" 104 case sshFxpOpen: 105 return "SSH_FXP_OPEN" 106 case sshFxpClose: 107 return "SSH_FXP_CLOSE" 108 case sshFxpRead: 109 return "SSH_FXP_READ" 110 case sshFxpWrite: 111 return "SSH_FXP_WRITE" 112 case sshFxpLstat: 113 return "SSH_FXP_LSTAT" 114 case sshFxpFstat: 115 return "SSH_FXP_FSTAT" 116 case sshFxpSetstat: 117 return "SSH_FXP_SETSTAT" 118 case sshFxpFsetstat: 119 return "SSH_FXP_FSETSTAT" 120 case sshFxpOpendir: 121 return "SSH_FXP_OPENDIR" 122 case sshFxpReaddir: 123 return "SSH_FXP_READDIR" 124 case sshFxpRemove: 125 return "SSH_FXP_REMOVE" 126 case sshFxpMkdir: 127 return "SSH_FXP_MKDIR" 128 case sshFxpRmdir: 129 return "SSH_FXP_RMDIR" 130 case sshFxpRealpath: 131 return "SSH_FXP_REALPATH" 132 case sshFxpStat: 133 return "SSH_FXP_STAT" 134 case sshFxpRename: 135 return "SSH_FXP_RENAME" 136 case sshFxpReadlink: 137 return "SSH_FXP_READLINK" 138 case sshFxpSymlink: 139 return "SSH_FXP_SYMLINK" 140 case sshFxpStatus: 141 return "SSH_FXP_STATUS" 142 case sshFxpHandle: 143 return "SSH_FXP_HANDLE" 144 case sshFxpData: 145 return "SSH_FXP_DATA" 146 case sshFxpName: 147 return "SSH_FXP_NAME" 148 case sshFxpAttrs: 149 return "SSH_FXP_ATTRS" 150 case sshFxpExtended: 151 return "SSH_FXP_EXTENDED" 152 case sshFxpExtendedReply: 153 return "SSH_FXP_EXTENDED_REPLY" 154 default: 155 return "unknown" 156 } 157} 158 159type fx uint8 160 161func (f fx) String() string { 162 switch f { 163 case sshFxOk: 164 return "SSH_FX_OK" 165 case sshFxEOF: 166 return "SSH_FX_EOF" 167 case sshFxNoSuchFile: 168 return "SSH_FX_NO_SUCH_FILE" 169 case sshFxPermissionDenied: 170 return "SSH_FX_PERMISSION_DENIED" 171 case sshFxFailure: 172 return "SSH_FX_FAILURE" 173 case sshFxBadMessage: 174 return "SSH_FX_BAD_MESSAGE" 175 case sshFxNoConnection: 176 return "SSH_FX_NO_CONNECTION" 177 case sshFxConnectionLost: 178 return "SSH_FX_CONNECTION_LOST" 179 case sshFxOPUnsupported: 180 return "SSH_FX_OP_UNSUPPORTED" 181 default: 182 return "unknown" 183 } 184} 185 186type unexpectedPacketErr struct { 187 want, got uint8 188} 189 190func (u *unexpectedPacketErr) Error() string { 191 return fmt.Sprintf("sftp: unexpected packet: want %v, got %v", fxp(u.want), fxp(u.got)) 192} 193 194func unimplementedPacketErr(u uint8) error { 195 return fmt.Errorf("sftp: unimplemented packet type: got %v", fxp(u)) 196} 197 198type unexpectedIDErr struct{ want, got uint32 } 199 200func (u *unexpectedIDErr) Error() string { 201 return fmt.Sprintf("sftp: unexpected id: want %d, got %d", u.want, u.got) 202} 203 204func unimplementedSeekWhence(whence int) error { 205 return fmt.Errorf("sftp: unimplemented seek whence %d", whence) 206} 207 208func unexpectedCount(want, got uint32) error { 209 return fmt.Errorf("sftp: unexpected count: want %d, got %d", want, got) 210} 211 212type unexpectedVersionErr struct{ want, got uint32 } 213 214func (u *unexpectedVersionErr) Error() string { 215 return fmt.Sprintf("sftp: unexpected server version: want %v, got %v", u.want, u.got) 216} 217 218// A StatusError is returned when an SFTP operation fails, and provides 219// additional information about the failure. 220type StatusError struct { 221 Code uint32 222 msg, lang string 223} 224 225func (s *StatusError) Error() string { 226 return fmt.Sprintf("sftp: %q (%v)", s.msg, fx(s.Code)) 227} 228 229// FxCode returns the error code typed to match against the exported codes 230func (s *StatusError) FxCode() fxerr { 231 return fxerr(s.Code) 232} 233 234func getSupportedExtensionByName(extensionName string) (sshExtensionPair, error) { 235 for _, supportedExtension := range supportedSFTPExtensions { 236 if supportedExtension.Name == extensionName { 237 return supportedExtension, nil 238 } 239 } 240 return sshExtensionPair{}, fmt.Errorf("unsupported extension: %s", extensionName) 241} 242 243// SetSFTPExtensions allows to customize the supported server extensions. 244// See the variable supportedSFTPExtensions for supported extensions. 245// This method accepts a slice of sshExtensionPair names for example 'hardlink@openssh.com'. 246// If an invalid extension is given an error will be returned and nothing will be changed 247func SetSFTPExtensions(extensions ...string) error { 248 tempExtensions := []sshExtensionPair{} 249 for _, extension := range extensions { 250 sftpExtension, err := getSupportedExtensionByName(extension) 251 if err != nil { 252 return err 253 } 254 tempExtensions = append(tempExtensions, sftpExtension) 255 } 256 sftpExtensions = tempExtensions 257 return nil 258} 259