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}