comparison src/ch/ethz/ssh2/SFTPv3Client.java @ 273:91a31873c42a ganymed

start conversion from trilead to ganymed
author Carl Byington <carl@five-ten-sg.com>
date Fri, 18 Jul 2014 11:21:46 -0700
parents
children d2b303406d63
comparison
equal deleted inserted replaced
272:ce2f4e397703 273:91a31873c42a
1 /*
2 * Copyright (c) 2006-2011 Christian Plattner. All rights reserved.
3 * Please refer to the LICENSE.txt for licensing details.
4 */
5 package ch.ethz.ssh2;
6
7 import java.io.IOException;
8 import java.util.ArrayList;
9 import java.util.List;
10
11 import ch.ethz.ssh2.log.Logger;
12 import ch.ethz.ssh2.packets.TypesReader;
13 import ch.ethz.ssh2.packets.TypesWriter;
14 import ch.ethz.ssh2.sftp.ErrorCodes;
15 import ch.ethz.ssh2.sftp.Packet;
16
17 /**
18 * A <code>SFTPv3Client</code> represents a SFTP (protocol version 3)
19 * client connection tunnelled over a SSH-2 connection. This is a very simple
20 * (synchronous) implementation.
21 * <p/>
22 * Basically, most methods in this class map directly to one of
23 * the packet types described in draft-ietf-secsh-filexfer-02.txt.
24 * <p/>
25 * Note: this is experimental code.
26 * <p/>
27 * Error handling: the methods of this class throw IOExceptions. However, unless
28 * there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will
29 * be thrown (a subclass of IOException). Therefore, you can implement more verbose
30 * behavior by checking if a thrown exception if of this type. If yes, then you
31 * can cast the exception and access detailed information about the failure.
32 * <p/>
33 * Notes about file names, directory names and paths, copy-pasted
34 * from the specs:
35 * <ul>
36 * <li>SFTP v3 represents file names as strings. File names are
37 * assumed to use the slash ('/') character as a directory separator.</li>
38 * <li>File names starting with a slash are "absolute", and are relative to
39 * the root of the file system. Names starting with any other character
40 * are relative to the user's default directory (home directory).</li>
41 * <li>Servers SHOULD interpret a path name component ".." as referring to
42 * the parent directory, and "." as referring to the current directory.
43 * If the server implementation limits access to certain parts of the
44 * file system, it must be extra careful in parsing file names when
45 * enforcing such restrictions. There have been numerous reported
46 * security bugs where a ".." in a path name has allowed access outside
47 * the intended area.</li>
48 * <li>An empty path name is valid, and it refers to the user's default
49 * directory (usually the user's home directory).</li>
50 * </ul>
51 * <p/>
52 * If you are still not tired then please go on and read the comment for
53 * {@link #setCharset(String)}.
54 *
55 * @author Christian Plattner, plattner@inf.ethz.ch
56 * @version $Id: SFTPv3Client.java 133 2014-04-14 12:26:29Z dkocher@sudo.ch $
57 */
58 public class SFTPv3Client extends AbstractSFTPClient {
59 private static final Logger log = Logger.getLogger(SFTPv3Client.class);
60
61 /**
62 * Open the file for reading.
63 */
64 public static final int SSH_FXF_READ = 0x00000001;
65 /**
66 * Open the file for writing. If both this and SSH_FXF_READ are
67 * specified, the file is opened for both reading and writing.
68 */
69 public static final int SSH_FXF_WRITE = 0x00000002;
70 /**
71 * Force all writes to append data at the end of the file.
72 */
73 public static final int SSH_FXF_APPEND = 0x00000004;
74 /**
75 * If this flag is specified, then a new file will be created if one
76 * does not alread exist (if O_TRUNC is specified, the new file will
77 * be truncated to zero length if it previously exists).
78 */
79 public static final int SSH_FXF_CREAT = 0x00000008;
80 /**
81 * Forces an existing file with the same name to be truncated to zero
82 * length when creating a file by specifying SSH_FXF_CREAT.
83 * SSH_FXF_CREAT MUST also be specified if this flag is used.
84 */
85 public static final int SSH_FXF_TRUNC = 0x00000010;
86 /**
87 * Causes the request to fail if the named file already exists.
88 */
89 public static final int SSH_FXF_EXCL = 0x00000020;
90
91 private PacketListener listener;
92
93 /**
94 * Create a SFTP v3 client.
95 *
96 * @param conn The underlying SSH-2 connection to be used.
97 * @throws IOException
98 */
99 public SFTPv3Client(Connection conn) throws IOException {
100 this(conn, new PacketListener() {
101 public void read(String packet) {
102 log.debug("Read packet " + packet);
103 }
104
105 public void write(String packet) {
106 log.debug("Write packet " + packet);
107 }
108 });
109 }
110
111 /**
112 * Create a SFTP v3 client.
113 *
114 * @param conn The underlying SSH-2 connection to be used.
115 * @throws IOException
116 */
117 public SFTPv3Client(Connection conn, PacketListener listener) throws IOException {
118 super(conn, 3, listener);
119 this.listener = listener;
120 }
121
122 @Override
123 public SFTPv3FileAttributes fstat(SFTPFileHandle handle) throws IOException {
124 int req_id = generateNextRequestID();
125
126 TypesWriter tw = new TypesWriter();
127 tw.writeString(handle.getHandle(), 0, handle.getHandle().length);
128
129 sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes());
130
131 byte[] resp = receiveMessage(34000);
132
133 TypesReader tr = new TypesReader(resp);
134
135 int t = tr.readByte();
136 listener.read(Packet.forName(t));
137
138 int rep_id = tr.readUINT32();
139 if(rep_id != req_id) {
140 throw new RequestMismatchException();
141 }
142
143 if(t == Packet.SSH_FXP_ATTRS) {
144 return new SFTPv3FileAttributes(tr);
145 }
146
147 if(t != Packet.SSH_FXP_STATUS) {
148 throw new PacketTypeException(t);
149 }
150
151 int errorCode = tr.readUINT32();
152 String errorMessage = tr.readString();
153 listener.read(errorMessage);
154 throw new SFTPException(errorMessage, errorCode);
155 }
156
157 private SFTPv3FileAttributes statBoth(String path, int statMethod) throws IOException {
158 int req_id = generateNextRequestID();
159
160 TypesWriter tw = new TypesWriter();
161 tw.writeString(path, this.getCharset());
162
163 sendMessage(statMethod, req_id, tw.getBytes());
164
165 byte[] resp = receiveMessage(34000);
166
167 TypesReader tr = new TypesReader(resp);
168
169 int t = tr.readByte();
170 listener.read(Packet.forName(t));
171
172 int rep_id = tr.readUINT32();
173 if(rep_id != req_id) {
174 throw new RequestMismatchException();
175 }
176
177 if(t == Packet.SSH_FXP_ATTRS) {
178 return new SFTPv3FileAttributes(tr);
179 }
180
181 if(t != Packet.SSH_FXP_STATUS) {
182 throw new PacketTypeException(t);
183 }
184
185 int errorCode = tr.readUINT32();
186 String errorMessage = tr.readString();
187 listener.read(errorMessage);
188 throw new SFTPException(errorMessage, errorCode);
189 }
190
191 @Override
192 public SFTPv3FileAttributes stat(String path) throws IOException {
193 return statBoth(path, Packet.SSH_FXP_STAT);
194 }
195
196 @Override
197 public SFTPv3FileAttributes lstat(String path) throws IOException {
198 return statBoth(path, Packet.SSH_FXP_LSTAT);
199 }
200
201
202 private List<SFTPv3DirectoryEntry> scanDirectory(byte[] handle) throws IOException {
203 List<SFTPv3DirectoryEntry> files = new ArrayList<SFTPv3DirectoryEntry>();
204
205 while(true) {
206 int req_id = generateNextRequestID();
207
208 TypesWriter tw = new TypesWriter();
209 tw.writeString(handle, 0, handle.length);
210
211 sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes());
212
213 byte[] resp = receiveMessage(34000);
214
215 TypesReader tr = new TypesReader(resp);
216
217 int t = tr.readByte();
218 listener.read(Packet.forName(t));
219
220 int rep_id = tr.readUINT32();
221 if(rep_id != req_id) {
222 throw new RequestMismatchException();
223 }
224
225 if(t == Packet.SSH_FXP_NAME) {
226 int count = tr.readUINT32();
227 if(log.isDebugEnabled()) {
228 log.debug(String.format("Parsing %d name entries", count));
229 }
230 while(count > 0) {
231 SFTPv3DirectoryEntry file = new SFTPv3DirectoryEntry();
232 file.filename = tr.readString(this.getCharset());
233 file.longEntry = tr.readString(this.getCharset());
234 listener.read(file.longEntry);
235 file.attributes = new SFTPv3FileAttributes(tr);
236 if(log.isDebugEnabled()) {
237 log.debug(String.format("Adding file %s", file));
238 }
239 files.add(file);
240 count--;
241 }
242 continue;
243 }
244
245 if(t != Packet.SSH_FXP_STATUS) {
246 throw new PacketTypeException(t);
247 }
248
249 int errorCode = tr.readUINT32();
250
251 if(errorCode == ErrorCodes.SSH_FX_EOF) {
252 return files;
253 }
254 String errorMessage = tr.readString();
255 listener.read(errorMessage);
256 throw new SFTPException(errorMessage, errorCode);
257 }
258 }
259
260 @Override
261 public final SFTPv3FileHandle openDirectory(String path) throws IOException {
262 int req_id = generateNextRequestID();
263
264 TypesWriter tw = new TypesWriter();
265 tw.writeString(path, this.getCharset());
266
267 sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes());
268
269 byte[] resp = receiveMessage(34000);
270
271 TypesReader tr = new TypesReader(resp);
272
273 int t = tr.readByte();
274 listener.read(Packet.forName(t));
275
276 int rep_id = tr.readUINT32();
277 if(rep_id != req_id) {
278 throw new RequestMismatchException();
279 }
280
281 if(t == Packet.SSH_FXP_HANDLE) {
282 return new SFTPv3FileHandle(this, tr.readByteString());
283 }
284
285 if(t != Packet.SSH_FXP_STATUS) {
286 throw new PacketTypeException(t);
287 }
288
289 int errorCode = tr.readUINT32();
290 String errorMessage = tr.readString();
291 listener.read(errorMessage);
292 throw new SFTPException(errorMessage, errorCode);
293 }
294
295 /**
296 * List the contents of a directory.
297 *
298 * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
299 * @return A Vector containing {@link SFTPv3DirectoryEntry} objects.
300 * @throws IOException
301 */
302 @Override
303 public List<SFTPv3DirectoryEntry> ls(String dirName) throws IOException {
304 SFTPv3FileHandle handle = openDirectory(dirName);
305 List<SFTPv3DirectoryEntry> result = scanDirectory(handle.getHandle());
306 closeFile(handle);
307 return result;
308 }
309
310 /**
311 * Open a file for reading.
312 *
313 * @param filename See the {@link SFTPv3Client comment} for the class for more details.
314 * @return a SFTPv3FileHandle handle
315 * @throws IOException
316 */
317 public SFTPv3FileHandle openFileRO(String filename) throws IOException {
318 return openFile(filename, SSH_FXF_READ, new SFTPv3FileAttributes());
319 }
320
321 /**
322 * Open a file for reading and writing.
323 *
324 * @param filename See the {@link SFTPv3Client comment} for the class for more details.
325 * @return a SFTPv3FileHandle handle
326 * @throws IOException
327 */
328 public SFTPv3FileHandle openFileRW(String filename) throws IOException {
329 return openFile(filename, SSH_FXF_READ | SSH_FXF_WRITE, new SFTPv3FileAttributes());
330 }
331
332 /**
333 * Open a file in append mode. The SFTP v3 draft says nothing but assuming normal POSIX
334 * behavior, all writes will be appendend to the end of the file, no matter which offset
335 * one specifies.
336 * <p/>
337 * A side note for the curious: OpenSSH does an lseek() to the specified writing offset before each write(),
338 * even for writes to files opened in O_APPEND mode. However, bear in mind that when working
339 * in the O_APPEND mode, each write() includes an implicit lseek() to the end of the file
340 * (well, this is what the newsgroups say).
341 *
342 * @param filename See the {@link SFTPv3Client comment} for the class for more details.
343 * @return a SFTPv3FileHandle handle
344 * @throws IOException
345 */
346 public SFTPv3FileHandle openFileRWAppend(String filename) throws IOException {
347 return openFile(filename, SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND, new SFTPv3FileAttributes());
348 }
349
350 /**
351 * Open a file in append mode. The SFTP v3 draft says nothing but assuming normal POSIX
352 * behavior, all writes will be appendend to the end of the file, no matter which offset
353 * one specifies.
354 * <p/>
355 * A side note for the curious: OpenSSH does an lseek() to the specified writing offset before each write(),
356 * even for writes to files opened in O_APPEND mode. However, bear in mind that when working
357 * in the O_APPEND mode, each write() includes an implicit lseek() to the end of the file
358 * (well, this is what the newsgroups say).
359 *
360 * @param filename See the {@link SFTPv3Client comment} for the class for more details.
361 * @return a SFTPv3FileHandle handle
362 * @throws IOException
363 */
364 public SFTPv3FileHandle openFileWAppend(String filename) throws IOException {
365 return openFile(filename, SSH_FXF_WRITE | SSH_FXF_APPEND, new SFTPv3FileAttributes());
366 }
367
368 @Override
369 public SFTPv3FileHandle createFile(String filename) throws IOException {
370 return createFile(filename, new SFTPv3FileAttributes());
371 }
372
373 @Override
374 public SFTPv3FileHandle createFile(String filename, SFTPFileAttributes attr) throws IOException {
375 return openFile(filename, SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE, attr);
376 }
377
378 /**
379 * Create a file (truncate it if it already exists) and open it for writing.
380 * Same as {@link #createFileTruncate(String, SFTPFileAttributes) createFileTruncate(filename, null)}.
381 *
382 * @param filename See the {@link SFTPv3Client comment} for the class for more details.
383 * @return a SFTPv3FileHandle handle
384 * @throws IOException
385 */
386 public SFTPv3FileHandle createFileTruncate(String filename) throws IOException {
387 return createFileTruncate(filename, new SFTPv3FileAttributes());
388 }
389
390 /**
391 * reate a file (truncate it if it already exists) and open it for writing.
392 * You can specify the default attributes of the file (the server may or may
393 * not respect your wishes).
394 *
395 * @param filename See the {@link SFTPv3Client comment} for the class for more details.
396 * @param attr may be <code>null</code> to use server defaults. Probably only
397 * the <code>uid</code>, <code>gid</code> and <code>permissions</code>
398 * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
399 * structure make sense. You need only to set those fields where you want
400 * to override the server's defaults.
401 * @return a SFTPv3FileHandle handle
402 * @throws IOException
403 */
404 public SFTPv3FileHandle createFileTruncate(String filename, SFTPFileAttributes attr) throws IOException {
405 return openFile(filename, SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_WRITE, attr);
406 }
407
408 @Override
409 public SFTPv3FileHandle openFile(String filename, int flags) throws IOException {
410 return openFile(filename, flags, new SFTPv3FileAttributes());
411 }
412
413 @Override
414 public SFTPv3FileHandle openFile(String filename, int flags, SFTPFileAttributes attr) throws IOException {
415 int req_id = generateNextRequestID();
416
417 TypesWriter tw = new TypesWriter();
418 tw.writeString(filename, this.getCharset());
419 tw.writeUINT32(flags);
420
421 tw.writeBytes(attr.toBytes());
422
423 sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes());
424
425 byte[] resp = receiveMessage(34000);
426
427 TypesReader tr = new TypesReader(resp);
428
429 int t = tr.readByte();
430 listener.read(Packet.forName(t));
431
432 int rep_id = tr.readUINT32();
433 if(rep_id != req_id) {
434 throw new RequestMismatchException();
435 }
436
437 if(t == Packet.SSH_FXP_HANDLE) {
438 return new SFTPv3FileHandle(this, tr.readByteString());
439 }
440
441 if(t != Packet.SSH_FXP_STATUS) {
442 throw new PacketTypeException(t);
443 }
444
445 int errorCode = tr.readUINT32();
446 String errorMessage = tr.readString();
447 listener.read(errorMessage);
448 throw new SFTPException(errorMessage, errorCode);
449 }
450
451 @Override
452 public void createSymlink(String src, String target) throws IOException {
453 int req_id = generateNextRequestID();
454
455 // Changed semantics of src and target. The bug is known on SFTP servers shipped with all
456 // versions of OpenSSH (Bug #861).
457 TypesWriter tw = new TypesWriter();
458 tw.writeString(target, this.getCharset());
459 tw.writeString(src, this.getCharset());
460
461 sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes());
462
463 expectStatusOKMessage(req_id);
464 }
465 }