sftp.go

· erock's pastes · raw

expires: 2025-02-01

  1// Package sftp implements the SSH File Transfer Protocol as described in
  2// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
  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}