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