comparison src/com/trilead/ssh2/SFTPv3Client.java @ 0:0ce5cc452d02

initial version
author Carl Byington <carl@five-ten-sg.com>
date Thu, 22 May 2014 10:41:19 -0700
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:0ce5cc452d02
1
2 package com.trilead.ssh2;
3
4 import java.io.BufferedOutputStream;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.io.OutputStream;
8 import java.io.PrintStream;
9 import java.nio.charset.Charset;
10 import java.util.HashMap;
11 import java.util.Vector;
12
13 import com.trilead.ssh2.packets.TypesReader;
14 import com.trilead.ssh2.packets.TypesWriter;
15 import com.trilead.ssh2.sftp.AttribFlags;
16 import com.trilead.ssh2.sftp.ErrorCodes;
17 import com.trilead.ssh2.sftp.Packet;
18
19
20 /**
21 * A <code>SFTPv3Client</code> represents a SFTP (protocol version 3)
22 * client connection tunnelled over a SSH-2 connection. This is a very simple
23 * (synchronous) implementation.
24 * <p>
25 * Basically, most methods in this class map directly to one of
26 * the packet types described in draft-ietf-secsh-filexfer-02.txt.
27 * <p>
28 * Note: this is experimental code.
29 * <p>
30 * Error handling: the methods of this class throw IOExceptions. However, unless
31 * there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will
32 * be thrown (a subclass of IOException). Therefore, you can implement more verbose
33 * behavior by checking if a thrown exception if of this type. If yes, then you
34 * can cast the exception and access detailed information about the failure.
35 * <p>
36 * Notes about file names, directory names and paths, copy-pasted
37 * from the specs:
38 * <ul>
39 * <li>SFTP v3 represents file names as strings. File names are
40 * assumed to use the slash ('/') character as a directory separator.</li>
41 * <li>File names starting with a slash are "absolute", and are relative to
42 * the root of the file system. Names starting with any other character
43 * are relative to the user's default directory (home directory).</li>
44 * <li>Servers SHOULD interpret a path name component ".." as referring to
45 * the parent directory, and "." as referring to the current directory.
46 * If the server implementation limits access to certain parts of the
47 * file system, it must be extra careful in parsing file names when
48 * enforcing such restrictions. There have been numerous reported
49 * security bugs where a ".." in a path name has allowed access outside
50 * the intended area.</li>
51 * <li>An empty path name is valid, and it refers to the user's default
52 * directory (usually the user's home directory).</li>
53 * </ul>
54 * <p>
55 * If you are still not tired then please go on and read the comment for
56 * {@link #setCharset(String)}.
57 *
58 * @author Christian Plattner, plattner@trilead.com
59 * @version $Id: SFTPv3Client.java,v 1.3 2008/04/01 12:38:09 cplattne Exp $
60 */
61 public class SFTPv3Client {
62 final Connection conn;
63 final Session sess;
64 final PrintStream debug;
65
66 boolean flag_closed = false;
67
68 InputStream is;
69 OutputStream os;
70
71 int protocol_version = 0;
72 HashMap server_extensions = new HashMap();
73
74 int next_request_id = 1000;
75
76 String charsetName = null;
77
78 /**
79 * Create a SFTP v3 client.
80 *
81 * @param conn The underlying SSH-2 connection to be used.
82 * @param debug
83 * @throws IOException
84 *
85 * @deprecated this constructor (debug version) will disappear in the future,
86 * use {@link #SFTPv3Client(Connection)} instead.
87 */
88 @Deprecated
89 public SFTPv3Client(Connection conn, PrintStream debug) throws IOException {
90 if (conn == null)
91 throw new IllegalArgumentException("Cannot accept null argument!");
92
93 this.conn = conn;
94 this.debug = debug;
95
96 if (debug != null)
97 debug.println("Opening session and starting SFTP subsystem.");
98
99 sess = conn.openSession();
100 sess.startSubSystem("sftp");
101 is = sess.getStdout();
102 os = new BufferedOutputStream(sess.getStdin(), 2048);
103
104 if ((is == null) || (os == null))
105 throw new IOException("There is a problem with the streams of the underlying channel.");
106
107 init();
108 }
109
110 /**
111 * Create a SFTP v3 client.
112 *
113 * @param conn The underlying SSH-2 connection to be used.
114 * @throws IOException
115 */
116 public SFTPv3Client(Connection conn) throws IOException {
117 this(conn, null);
118 }
119
120 /**
121 * Set the charset used to convert between Java Unicode Strings and byte encodings
122 * used by the server for paths and file names. Unfortunately, the SFTP v3 draft
123 * says NOTHING about such conversions (well, with the exception of error messages
124 * which have to be in UTF-8). Newer drafts specify to use UTF-8 for file names
125 * (if I remember correctly). However, a quick test using OpenSSH serving a EXT-3
126 * filesystem has shown that UTF-8 seems to be a bad choice for SFTP v3 (tested with
127 * filenames containing german umlauts). "windows-1252" seems to work better for Europe.
128 * Luckily, "windows-1252" is the platform default in my case =).
129 * <p>
130 * If you don't set anything, then the platform default will be used (this is the default
131 * behavior).
132 *
133 * @see #getCharset()
134 *
135 * @param charset the name of the charset to be used or <code>null</code> to use the platform's
136 * default encoding.
137 * @throws IOException
138 */
139 public void setCharset(String charset) throws IOException {
140 if (charset == null) {
141 charsetName = charset;
142 return;
143 }
144
145 try {
146 Charset.forName(charset);
147 }
148 catch (Exception e) {
149 throw(IOException) new IOException("This charset is not supported").initCause(e);
150 }
151
152 charsetName = charset;
153 }
154
155 /**
156 * The currently used charset for filename encoding/decoding.
157 *
158 * @see #setCharset(String)
159 *
160 * @return The name of the charset (<code>null</code> if the platform's default charset is being used)
161 */
162 public String getCharset() {
163 return charsetName;
164 }
165
166 private final void checkHandleValidAndOpen(SFTPv3FileHandle handle) throws IOException {
167 if (handle.client != this)
168 throw new IOException("The file handle was created with another SFTPv3FileHandle instance.");
169
170 if (handle.isClosed == true)
171 throw new IOException("The file handle is closed.");
172 }
173
174 private final void sendMessage(int type, int requestId, byte[] msg, int off, int len) throws IOException {
175 int msglen = len + 1;
176
177 if (type != Packet.SSH_FXP_INIT)
178 msglen += 4;
179
180 os.write(msglen >> 24);
181 os.write(msglen >> 16);
182 os.write(msglen >> 8);
183 os.write(msglen);
184 os.write(type);
185
186 if (type != Packet.SSH_FXP_INIT) {
187 os.write(requestId >> 24);
188 os.write(requestId >> 16);
189 os.write(requestId >> 8);
190 os.write(requestId);
191 }
192
193 os.write(msg, off, len);
194 os.flush();
195 }
196
197 private final void sendMessage(int type, int requestId, byte[] msg) throws IOException {
198 sendMessage(type, requestId, msg, 0, msg.length);
199 }
200
201 private final void readBytes(byte[] buff, int pos, int len) throws IOException {
202 while (len > 0) {
203 int count = is.read(buff, pos, len);
204
205 if (count < 0)
206 throw new IOException("Unexpected end of sftp stream.");
207
208 if ((count == 0) || (count > len))
209 throw new IOException("Underlying stream implementation is bogus!");
210
211 len -= count;
212 pos += count;
213 }
214 }
215
216 /**
217 * Read a message and guarantee that the <b>contents</b> is not larger than
218 * <code>maxlen</code> bytes.
219 * <p>
220 * Note: receiveMessage(34000) actually means that the message may be up to 34004
221 * bytes (the length attribute preceeding the contents is 4 bytes).
222 *
223 * @param maxlen
224 * @return the message contents
225 * @throws IOException
226 */
227 private final byte[] receiveMessage(int maxlen) throws IOException {
228 byte[] msglen = new byte[4];
229 readBytes(msglen, 0, 4);
230 int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff));
231
232 if ((len > maxlen) || (len <= 0))
233 throw new IOException("Illegal sftp packet len: " + len);
234
235 byte[] msg = new byte[len];
236 readBytes(msg, 0, len);
237 return msg;
238 }
239
240 private final int generateNextRequestID() {
241 synchronized (this) {
242 return next_request_id++;
243 }
244 }
245
246 private final void closeHandle(byte[] handle) throws IOException {
247 int req_id = generateNextRequestID();
248 TypesWriter tw = new TypesWriter();
249 tw.writeString(handle, 0, handle.length);
250 sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes());
251 expectStatusOKMessage(req_id);
252 }
253
254 private SFTPv3FileAttributes readAttrs(TypesReader tr) throws IOException {
255 /*
256 * uint32 flags
257 * uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE
258 * uint32 uid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
259 * uint32 gid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
260 * uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
261 * uint32 atime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
262 * uint32 mtime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
263 * uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
264 * string extended_type
265 * string extended_data
266 * ... more extended data (extended_type - extended_data pairs),
267 * so that number of pairs equals extended_count
268 */
269 SFTPv3FileAttributes fa = new SFTPv3FileAttributes();
270 int flags = tr.readUINT32();
271
272 if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0) {
273 if (debug != null)
274 debug.println("SSH_FILEXFER_ATTR_SIZE");
275
276 fa.size = Long.valueOf(tr.readUINT64());
277 }
278
279 if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0) {
280 if (debug != null)
281 debug.println("SSH_FILEXFER_ATTR_V3_UIDGID");
282
283 fa.uid = Integer.valueOf(tr.readUINT32());
284 fa.gid = Integer.valueOf(tr.readUINT32());
285 }
286
287 if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
288 if (debug != null)
289 debug.println("SSH_FILEXFER_ATTR_PERMISSIONS");
290
291 fa.permissions = Integer.valueOf(tr.readUINT32());
292 }
293
294 if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0) {
295 if (debug != null)
296 debug.println("SSH_FILEXFER_ATTR_V3_ACMODTIME");
297
298 fa.atime = Long.valueOf((tr.readUINT32()) & 0xffffffffl);
299 fa.mtime = Long.valueOf((tr.readUINT32()) & 0xffffffffl);
300 }
301
302 if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0) {
303 int count = tr.readUINT32();
304
305 if (debug != null)
306 debug.println("SSH_FILEXFER_ATTR_EXTENDED (" + count + ")");
307
308 /* Read it anyway to detect corrupt packets */
309
310 while (count > 0) {
311 tr.readByteString();
312 tr.readByteString();
313 count--;
314 }
315 }
316
317 return fa;
318 }
319
320 /**
321 * Retrieve the file attributes of an open file.
322 *
323 * @param handle a SFTPv3FileHandle handle.
324 * @return a SFTPv3FileAttributes object.
325 * @throws IOException
326 */
327 public SFTPv3FileAttributes fstat(SFTPv3FileHandle handle) throws IOException {
328 checkHandleValidAndOpen(handle);
329 int req_id = generateNextRequestID();
330 TypesWriter tw = new TypesWriter();
331 tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
332
333 if (debug != null) {
334 debug.println("Sending SSH_FXP_FSTAT...");
335 debug.flush();
336 }
337
338 sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes());
339 byte[] resp = receiveMessage(34000);
340
341 if (debug != null) {
342 debug.println("Got REPLY.");
343 debug.flush();
344 }
345
346 TypesReader tr = new TypesReader(resp);
347 int t = tr.readByte();
348 int rep_id = tr.readUINT32();
349
350 if (rep_id != req_id)
351 throw new IOException("The server sent an invalid id field.");
352
353 if (t == Packet.SSH_FXP_ATTRS) {
354 return readAttrs(tr);
355 }
356
357 if (t != Packet.SSH_FXP_STATUS)
358 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
359
360 int errorCode = tr.readUINT32();
361 throw new SFTPException(tr.readString(), errorCode);
362 }
363
364 private SFTPv3FileAttributes statBoth(String path, int statMethod) throws IOException {
365 int req_id = generateNextRequestID();
366 TypesWriter tw = new TypesWriter();
367 tw.writeString(path, charsetName);
368
369 if (debug != null) {
370 debug.println("Sending SSH_FXP_STAT/SSH_FXP_LSTAT...");
371 debug.flush();
372 }
373
374 sendMessage(statMethod, req_id, tw.getBytes());
375 byte[] resp = receiveMessage(34000);
376
377 if (debug != null) {
378 debug.println("Got REPLY.");
379 debug.flush();
380 }
381
382 TypesReader tr = new TypesReader(resp);
383 int t = tr.readByte();
384 int rep_id = tr.readUINT32();
385
386 if (rep_id != req_id)
387 throw new IOException("The server sent an invalid id field.");
388
389 if (t == Packet.SSH_FXP_ATTRS) {
390 return readAttrs(tr);
391 }
392
393 if (t != Packet.SSH_FXP_STATUS)
394 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
395
396 int errorCode = tr.readUINT32();
397 throw new SFTPException(tr.readString(), errorCode);
398 }
399
400 /**
401 * Retrieve the file attributes of a file. This method
402 * follows symbolic links on the server.
403 *
404 * @see #lstat(String)
405 *
406 * @param path See the {@link SFTPv3Client comment} for the class for more details.
407 * @return a SFTPv3FileAttributes object.
408 * @throws IOException
409 */
410 public SFTPv3FileAttributes stat(String path) throws IOException {
411 return statBoth(path, Packet.SSH_FXP_STAT);
412 }
413
414 /**
415 * Retrieve the file attributes of a file. This method
416 * does NOT follow symbolic links on the server.
417 *
418 * @see #stat(String)
419 *
420 * @param path See the {@link SFTPv3Client comment} for the class for more details.
421 * @return a SFTPv3FileAttributes object.
422 * @throws IOException
423 */
424 public SFTPv3FileAttributes lstat(String path) throws IOException {
425 return statBoth(path, Packet.SSH_FXP_LSTAT);
426 }
427
428 /**
429 * Read the target of a symbolic link.
430 *
431 * @param path See the {@link SFTPv3Client comment} for the class for more details.
432 * @return The target of the link.
433 * @throws IOException
434 */
435 public String readLink(String path) throws IOException {
436 int req_id = generateNextRequestID();
437 TypesWriter tw = new TypesWriter();
438 tw.writeString(path, charsetName);
439
440 if (debug != null) {
441 debug.println("Sending SSH_FXP_READLINK...");
442 debug.flush();
443 }
444
445 sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes());
446 byte[] resp = receiveMessage(34000);
447
448 if (debug != null) {
449 debug.println("Got REPLY.");
450 debug.flush();
451 }
452
453 TypesReader tr = new TypesReader(resp);
454 int t = tr.readByte();
455 int rep_id = tr.readUINT32();
456
457 if (rep_id != req_id)
458 throw new IOException("The server sent an invalid id field.");
459
460 if (t == Packet.SSH_FXP_NAME) {
461 int count = tr.readUINT32();
462
463 if (count != 1)
464 throw new IOException("The server sent an invalid SSH_FXP_NAME packet.");
465
466 return tr.readString(charsetName);
467 }
468
469 if (t != Packet.SSH_FXP_STATUS)
470 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
471
472 int errorCode = tr.readUINT32();
473 throw new SFTPException(tr.readString(), errorCode);
474 }
475
476 private void expectStatusOKMessage(int id) throws IOException {
477 byte[] resp = receiveMessage(34000);
478
479 if (debug != null) {
480 debug.println("Got REPLY.");
481 debug.flush();
482 }
483
484 TypesReader tr = new TypesReader(resp);
485 int t = tr.readByte();
486 int rep_id = tr.readUINT32();
487
488 if (rep_id != id)
489 throw new IOException("The server sent an invalid id field.");
490
491 if (t != Packet.SSH_FXP_STATUS)
492 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
493
494 int errorCode = tr.readUINT32();
495
496 if (errorCode == ErrorCodes.SSH_FX_OK)
497 return;
498
499 throw new SFTPException(tr.readString(), errorCode);
500 }
501
502 /**
503 * Modify the attributes of a file. Used for operations such as changing
504 * the ownership, permissions or access times, as well as for truncating a file.
505 *
506 * @param path See the {@link SFTPv3Client comment} for the class for more details.
507 * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
508 * made to the attributes of the file. Empty fields will be ignored.
509 * @throws IOException
510 */
511 public void setstat(String path, SFTPv3FileAttributes attr) throws IOException {
512 int req_id = generateNextRequestID();
513 TypesWriter tw = new TypesWriter();
514 tw.writeString(path, charsetName);
515 tw.writeBytes(createAttrs(attr));
516
517 if (debug != null) {
518 debug.println("Sending SSH_FXP_SETSTAT...");
519 debug.flush();
520 }
521
522 sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes());
523 expectStatusOKMessage(req_id);
524 }
525
526 /**
527 * Modify the attributes of a file. Used for operations such as changing
528 * the ownership, permissions or access times, as well as for truncating a file.
529 *
530 * @param handle a SFTPv3FileHandle handle
531 * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
532 * made to the attributes of the file. Empty fields will be ignored.
533 * @throws IOException
534 */
535 public void fsetstat(SFTPv3FileHandle handle, SFTPv3FileAttributes attr) throws IOException {
536 checkHandleValidAndOpen(handle);
537 int req_id = generateNextRequestID();
538 TypesWriter tw = new TypesWriter();
539 tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
540 tw.writeBytes(createAttrs(attr));
541
542 if (debug != null) {
543 debug.println("Sending SSH_FXP_FSETSTAT...");
544 debug.flush();
545 }
546
547 sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes());
548 expectStatusOKMessage(req_id);
549 }
550
551 /**
552 * Create a symbolic link on the server. Creates a link "src" that points
553 * to "target".
554 *
555 * @param src See the {@link SFTPv3Client comment} for the class for more details.
556 * @param target See the {@link SFTPv3Client comment} for the class for more details.
557 * @throws IOException
558 */
559 public void createSymlink(String src, String target) throws IOException {
560 int req_id = generateNextRequestID();
561 /* Either I am too stupid to understand the SFTP draft
562 * or the OpenSSH guys changed the semantics of src and target.
563 */
564 TypesWriter tw = new TypesWriter();
565 tw.writeString(target, charsetName);
566 tw.writeString(src, charsetName);
567
568 if (debug != null) {
569 debug.println("Sending SSH_FXP_SYMLINK...");
570 debug.flush();
571 }
572
573 sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes());
574 expectStatusOKMessage(req_id);
575 }
576
577 /**
578 * Have the server canonicalize any given path name to an absolute path.
579 * This is useful for converting path names containing ".." components or
580 * relative pathnames without a leading slash into absolute paths.
581 *
582 * @param path See the {@link SFTPv3Client comment} for the class for more details.
583 * @return An absolute path.
584 * @throws IOException
585 */
586 public String canonicalPath(String path) throws IOException {
587 int req_id = generateNextRequestID();
588 TypesWriter tw = new TypesWriter();
589 tw.writeString(path, charsetName);
590
591 if (debug != null) {
592 debug.println("Sending SSH_FXP_REALPATH...");
593 debug.flush();
594 }
595
596 sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes());
597 byte[] resp = receiveMessage(34000);
598
599 if (debug != null) {
600 debug.println("Got REPLY.");
601 debug.flush();
602 }
603
604 TypesReader tr = new TypesReader(resp);
605 int t = tr.readByte();
606 int rep_id = tr.readUINT32();
607
608 if (rep_id != req_id)
609 throw new IOException("The server sent an invalid id field.");
610
611 if (t == Packet.SSH_FXP_NAME) {
612 int count = tr.readUINT32();
613
614 if (count != 1)
615 throw new IOException("The server sent an invalid SSH_FXP_NAME packet.");
616
617 return tr.readString(charsetName);
618 }
619
620 if (t != Packet.SSH_FXP_STATUS)
621 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
622
623 int errorCode = tr.readUINT32();
624 throw new SFTPException(tr.readString(), errorCode);
625 }
626
627 private final Vector scanDirectory(byte[] handle) throws IOException {
628 Vector files = new Vector();
629
630 while (true) {
631 int req_id = generateNextRequestID();
632 TypesWriter tw = new TypesWriter();
633 tw.writeString(handle, 0, handle.length);
634
635 if (debug != null) {
636 debug.println("Sending SSH_FXP_READDIR...");
637 debug.flush();
638 }
639
640 sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes());
641 /* Some servers send here a packet with size > 34000 */
642 /* To whom it may concern: please learn to read the specs. */
643 byte[] resp = receiveMessage(65536);
644
645 if (debug != null) {
646 debug.println("Got REPLY.");
647 debug.flush();
648 }
649
650 TypesReader tr = new TypesReader(resp);
651 int t = tr.readByte();
652 int rep_id = tr.readUINT32();
653
654 if (rep_id != req_id)
655 throw new IOException("The server sent an invalid id field.");
656
657 if (t == Packet.SSH_FXP_NAME) {
658 int count = tr.readUINT32();
659
660 if (debug != null)
661 debug.println("Parsing " + count + " name entries...");
662
663 while (count > 0) {
664 SFTPv3DirectoryEntry dirEnt = new SFTPv3DirectoryEntry();
665 dirEnt.filename = tr.readString(charsetName);
666 dirEnt.longEntry = tr.readString(charsetName);
667 dirEnt.attributes = readAttrs(tr);
668 files.addElement(dirEnt);
669
670 if (debug != null)
671 debug.println("File: '" + dirEnt.filename + "'");
672
673 count--;
674 }
675
676 continue;
677 }
678
679 if (t != Packet.SSH_FXP_STATUS)
680 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
681
682 int errorCode = tr.readUINT32();
683
684 if (errorCode == ErrorCodes.SSH_FX_EOF)
685 return files;
686
687 throw new SFTPException(tr.readString(), errorCode);
688 }
689 }
690
691 private final byte[] openDirectory(String path) throws IOException {
692 int req_id = generateNextRequestID();
693 TypesWriter tw = new TypesWriter();
694 tw.writeString(path, charsetName);
695
696 if (debug != null) {
697 debug.println("Sending SSH_FXP_OPENDIR...");
698 debug.flush();
699 }
700
701 sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes());
702 byte[] resp = receiveMessage(34000);
703 TypesReader tr = new TypesReader(resp);
704 int t = tr.readByte();
705 int rep_id = tr.readUINT32();
706
707 if (rep_id != req_id)
708 throw new IOException("The server sent an invalid id field.");
709
710 if (t == Packet.SSH_FXP_HANDLE) {
711 if (debug != null) {
712 debug.println("Got SSH_FXP_HANDLE.");
713 debug.flush();
714 }
715
716 byte[] handle = tr.readByteString();
717 return handle;
718 }
719
720 if (t != Packet.SSH_FXP_STATUS)
721 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
722
723 int errorCode = tr.readUINT32();
724 String errorMessage = tr.readString();
725 throw new SFTPException(errorMessage, errorCode);
726 }
727
728 private final String expandString(byte[] b, int off, int len) {
729 StringBuffer sb = new StringBuffer();
730
731 for (int i = 0; i < len; i++) {
732 int c = b[off + i] & 0xff;
733
734 if ((c >= 32) && (c <= 126)) {
735 sb.append((char) c);
736 }
737 else {
738 sb.append("{0x" + Integer.toHexString(c) + "}");
739 }
740 }
741
742 return sb.toString();
743 }
744
745 private void init() throws IOException {
746 /* Send SSH_FXP_INIT (version 3) */
747 final int client_version = 3;
748
749 if (debug != null)
750 debug.println("Sending SSH_FXP_INIT (" + client_version + ")...");
751
752 TypesWriter tw = new TypesWriter();
753 tw.writeUINT32(client_version);
754 sendMessage(Packet.SSH_FXP_INIT, 0, tw.getBytes());
755
756 /* Receive SSH_FXP_VERSION */
757
758 if (debug != null)
759 debug.println("Waiting for SSH_FXP_VERSION...");
760
761 TypesReader tr = new TypesReader(receiveMessage(34000)); /* Should be enough for any reasonable server */
762 int type = tr.readByte();
763
764 if (type != Packet.SSH_FXP_VERSION) {
765 throw new IOException("The server did not send a SSH_FXP_VERSION packet (got " + type + ")");
766 }
767
768 protocol_version = tr.readUINT32();
769
770 if (debug != null)
771 debug.println("SSH_FXP_VERSION: protocol_version = " + protocol_version);
772
773 if (protocol_version != 3)
774 throw new IOException("Server version " + protocol_version + " is currently not supported");
775
776 /* Read and save extensions (if any) for later use */
777
778 while (tr.remain() != 0) {
779 String name = tr.readString();
780 byte[] value = tr.readByteString();
781 server_extensions.put(name, value);
782
783 if (debug != null)
784 debug.println("SSH_FXP_VERSION: extension: " + name + " = '" + expandString(value, 0, value.length)
785 + "'");
786 }
787 }
788
789 /**
790 * Returns the negotiated SFTP protocol version between the client and the server.
791 *
792 * @return SFTP protocol version, i.e., "3".
793 *
794 */
795 public int getProtocolVersion() {
796 return protocol_version;
797 }
798
799 /**
800 * Close this SFTP session. NEVER forget to call this method to free up
801 * resources - even if you got an exception from one of the other methods.
802 * Sometimes these other methods may throw an exception, saying that the
803 * underlying channel is closed (this can happen, e.g., if the other server
804 * sent a close message.) However, as long as you have not called the
805 * <code>close()</code> method, you are likely wasting resources.
806 *
807 */
808 public void close() {
809 sess.close();
810 }
811
812 /**
813 * List the contents of a directory.
814 *
815 * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
816 * @return A Vector containing {@link SFTPv3DirectoryEntry} objects.
817 * @throws IOException
818 */
819 public Vector ls(String dirName) throws IOException {
820 byte[] handle = openDirectory(dirName);
821 Vector result = scanDirectory(handle);
822 closeHandle(handle);
823 return result;
824 }
825
826 /**
827 * Create a new directory.
828 *
829 * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
830 * @param posixPermissions the permissions for this directory, e.g., "0700" (remember that
831 * this is octal noation). The server will likely apply a umask.
832 *
833 * @throws IOException
834 */
835 public void mkdir(String dirName, int posixPermissions) throws IOException {
836 int req_id = generateNextRequestID();
837 TypesWriter tw = new TypesWriter();
838 tw.writeString(dirName, charsetName);
839 tw.writeUINT32(AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS);
840 tw.writeUINT32(posixPermissions);
841 sendMessage(Packet.SSH_FXP_MKDIR, req_id, tw.getBytes());
842 expectStatusOKMessage(req_id);
843 }
844
845 /**
846 * Remove a file.
847 *
848 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
849 * @throws IOException
850 */
851 public void rm(String fileName) throws IOException {
852 int req_id = generateNextRequestID();
853 TypesWriter tw = new TypesWriter();
854 tw.writeString(fileName, charsetName);
855 sendMessage(Packet.SSH_FXP_REMOVE, req_id, tw.getBytes());
856 expectStatusOKMessage(req_id);
857 }
858
859 /**
860 * Remove an empty directory.
861 *
862 * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
863 * @throws IOException
864 */
865 public void rmdir(String dirName) throws IOException {
866 int req_id = generateNextRequestID();
867 TypesWriter tw = new TypesWriter();
868 tw.writeString(dirName, charsetName);
869 sendMessage(Packet.SSH_FXP_RMDIR, req_id, tw.getBytes());
870 expectStatusOKMessage(req_id);
871 }
872
873 /**
874 * Move a file or directory.
875 *
876 * @param oldPath See the {@link SFTPv3Client comment} for the class for more details.
877 * @param newPath See the {@link SFTPv3Client comment} for the class for more details.
878 * @throws IOException
879 */
880 public void mv(String oldPath, String newPath) throws IOException {
881 int req_id = generateNextRequestID();
882 TypesWriter tw = new TypesWriter();
883 tw.writeString(oldPath, charsetName);
884 tw.writeString(newPath, charsetName);
885 sendMessage(Packet.SSH_FXP_RENAME, req_id, tw.getBytes());
886 expectStatusOKMessage(req_id);
887 }
888
889 /**
890 * Open a file for reading.
891 *
892 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
893 * @return a SFTPv3FileHandle handle
894 * @throws IOException
895 */
896 public SFTPv3FileHandle openFileRO(String fileName) throws IOException {
897 return openFile(fileName, 0x00000001, null); // SSH_FXF_READ
898 }
899
900 /**
901 * Open a file for reading and writing.
902 *
903 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
904 * @return a SFTPv3FileHandle handle
905 * @throws IOException
906 */
907 public SFTPv3FileHandle openFileRW(String fileName) throws IOException {
908 return openFile(fileName, 0x00000003, null); // SSH_FXF_READ | SSH_FXF_WRITE
909 }
910
911 // Append is broken (already in the specification, because there is no way to
912 // send a write operation (what offset to use??))
913 // public SFTPv3FileHandle openFileRWAppend(String fileName) throws IOException
914 // {
915 // return openFile(fileName, 0x00000007, null); // SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND
916 // }
917
918 /**
919 * Create a file and open it for reading and writing.
920 * Same as {@link #createFile(String, SFTPv3FileAttributes) createFile(fileName, null)}.
921 *
922 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
923 * @return a SFTPv3FileHandle handle
924 * @throws IOException
925 */
926 public SFTPv3FileHandle createFile(String fileName) throws IOException {
927 return createFile(fileName, null);
928 }
929
930 /**
931 * Create a file and open it for reading and writing.
932 * You can specify the default attributes of the file (the server may or may
933 * not respect your wishes).
934 *
935 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
936 * @param attr may be <code>null</code> to use server defaults. Probably only
937 * the <code>uid</code>, <code>gid</code> and <code>permissions</code>
938 * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
939 * structure make sense. You need only to set those fields where you want
940 * to override the server's defaults.
941 * @return a SFTPv3FileHandle handle
942 * @throws IOException
943 */
944 public SFTPv3FileHandle createFile(String fileName, SFTPv3FileAttributes attr) throws IOException {
945 return openFile(fileName, 0x00000008 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE
946 }
947
948 /**
949 * Create a file (truncate it if it already exists) and open it for reading and writing.
950 * Same as {@link #createFileTruncate(String, SFTPv3FileAttributes) createFileTruncate(fileName, null)}.
951 *
952 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
953 * @return a SFTPv3FileHandle handle
954 * @throws IOException
955 */
956 public SFTPv3FileHandle createFileTruncate(String fileName) throws IOException {
957 return createFileTruncate(fileName, null);
958 }
959
960 /**
961 * reate a file (truncate it if it already exists) and open it for reading and writing.
962 * You can specify the default attributes of the file (the server may or may
963 * not respect your wishes).
964 *
965 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
966 * @param attr may be <code>null</code> to use server defaults. Probably only
967 * the <code>uid</code>, <code>gid</code> and <code>permissions</code>
968 * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
969 * structure make sense. You need only to set those fields where you want
970 * to override the server's defaults.
971 * @return a SFTPv3FileHandle handle
972 * @throws IOException
973 */
974 public SFTPv3FileHandle createFileTruncate(String fileName, SFTPv3FileAttributes attr) throws IOException {
975 return openFile(fileName, 0x00000018 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_READ | SSH_FXF_WRITE
976 }
977
978 private byte[] createAttrs(SFTPv3FileAttributes attr) {
979 TypesWriter tw = new TypesWriter();
980 int attrFlags = 0;
981
982 if (attr == null) {
983 tw.writeUINT32(0);
984 }
985 else {
986 if (attr.size != null)
987 attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE;
988
989 if ((attr.uid != null) && (attr.gid != null))
990 attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID;
991
992 if (attr.permissions != null)
993 attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS;
994
995 if ((attr.atime != null) && (attr.mtime != null))
996 attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME;
997
998 tw.writeUINT32(attrFlags);
999
1000 if (attr.size != null)
1001 tw.writeUINT64(attr.size.longValue());
1002
1003 if ((attr.uid != null) && (attr.gid != null)) {
1004 tw.writeUINT32(attr.uid.intValue());
1005 tw.writeUINT32(attr.gid.intValue());
1006 }
1007
1008 if (attr.permissions != null)
1009 tw.writeUINT32(attr.permissions.intValue());
1010
1011 if ((attr.atime != null) && (attr.mtime != null)) {
1012 tw.writeUINT32(attr.atime.intValue());
1013 tw.writeUINT32(attr.mtime.intValue());
1014 }
1015 }
1016
1017 return tw.getBytes();
1018 }
1019
1020 private SFTPv3FileHandle openFile(String fileName, int flags, SFTPv3FileAttributes attr) throws IOException {
1021 int req_id = generateNextRequestID();
1022 TypesWriter tw = new TypesWriter();
1023 tw.writeString(fileName, charsetName);
1024 tw.writeUINT32(flags);
1025 tw.writeBytes(createAttrs(attr));
1026
1027 if (debug != null) {
1028 debug.println("Sending SSH_FXP_OPEN...");
1029 debug.flush();
1030 }
1031
1032 sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes());
1033 byte[] resp = receiveMessage(34000);
1034 TypesReader tr = new TypesReader(resp);
1035 int t = tr.readByte();
1036 int rep_id = tr.readUINT32();
1037
1038 if (rep_id != req_id)
1039 throw new IOException("The server sent an invalid id field.");
1040
1041 if (t == Packet.SSH_FXP_HANDLE) {
1042 if (debug != null) {
1043 debug.println("Got SSH_FXP_HANDLE.");
1044 debug.flush();
1045 }
1046
1047 return new SFTPv3FileHandle(this, tr.readByteString());
1048 }
1049
1050 if (t != Packet.SSH_FXP_STATUS)
1051 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
1052
1053 int errorCode = tr.readUINT32();
1054 String errorMessage = tr.readString();
1055 throw new SFTPException(errorMessage, errorCode);
1056 }
1057
1058 /**
1059 * Read bytes from a file. No more than 32768 bytes may be read at once.
1060 * Be aware that the semantics of read() are different than for Java streams.
1061 * <p>
1062 * <ul>
1063 * <li>The server will read as many bytes as it can from the file (up to <code>len</code>),
1064 * and return them.</li>
1065 * <li>If EOF is encountered before reading any data, <code>-1</code> is returned.
1066 * <li>If an error occurs, an exception is thrown</li>.
1067 * <li>For normal disk files, it is guaranteed that the server will return the specified
1068 * number of bytes, or up to end of file. For, e.g., device files this may return
1069 * fewer bytes than requested.</li>
1070 * </ul>
1071 *
1072 * @param handle a SFTPv3FileHandle handle
1073 * @param fileOffset offset (in bytes) in the file
1074 * @param dst the destination byte array
1075 * @param dstoff offset in the destination byte array
1076 * @param len how many bytes to read, 0 &lt; len &lt;= 32768 bytes
1077 * @return the number of bytes that could be read, may be less than requested if
1078 * the end of the file is reached, -1 is returned in case of <code>EOF</code>
1079 * @throws IOException
1080 */
1081 public int read(SFTPv3FileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException {
1082 checkHandleValidAndOpen(handle);
1083
1084 if ((len > 32768) || (len <= 0))
1085 throw new IllegalArgumentException("invalid len argument");
1086
1087 int req_id = generateNextRequestID();
1088 TypesWriter tw = new TypesWriter();
1089 tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
1090 tw.writeUINT64(fileOffset);
1091 tw.writeUINT32(len);
1092
1093 if (debug != null) {
1094 debug.println("Sending SSH_FXP_READ...");
1095 debug.flush();
1096 }
1097
1098 sendMessage(Packet.SSH_FXP_READ, req_id, tw.getBytes());
1099 byte[] resp = receiveMessage(34000);
1100 TypesReader tr = new TypesReader(resp);
1101 int t = tr.readByte();
1102 int rep_id = tr.readUINT32();
1103
1104 if (rep_id != req_id)
1105 throw new IOException("The server sent an invalid id field.");
1106
1107 if (t == Packet.SSH_FXP_DATA) {
1108 if (debug != null) {
1109 debug.println("Got SSH_FXP_DATA...");
1110 debug.flush();
1111 }
1112
1113 int readLen = tr.readUINT32();
1114
1115 if ((readLen < 0) || (readLen > len))
1116 throw new IOException("The server sent an invalid length field.");
1117
1118 tr.readBytes(dst, dstoff, readLen);
1119 return readLen;
1120 }
1121
1122 if (t != Packet.SSH_FXP_STATUS)
1123 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
1124
1125 int errorCode = tr.readUINT32();
1126
1127 if (errorCode == ErrorCodes.SSH_FX_EOF) {
1128 if (debug != null) {
1129 debug.println("Got SSH_FX_EOF.");
1130 debug.flush();
1131 }
1132
1133 return -1;
1134 }
1135
1136 String errorMessage = tr.readString();
1137 throw new SFTPException(errorMessage, errorCode);
1138 }
1139
1140 /**
1141 * Write bytes to a file. If <code>len</code> &gt; 32768, then the write operation will
1142 * be split into multiple writes.
1143 *
1144 * @param handle a SFTPv3FileHandle handle.
1145 * @param fileOffset offset (in bytes) in the file.
1146 * @param src the source byte array.
1147 * @param srcoff offset in the source byte array.
1148 * @param len how many bytes to write.
1149 * @throws IOException
1150 */
1151 public void write(SFTPv3FileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException {
1152 checkHandleValidAndOpen(handle);
1153
1154 while (len > 0) {
1155 int writeRequestLen = len;
1156
1157 if (writeRequestLen > 32768)
1158 writeRequestLen = 32768;
1159
1160 int req_id = generateNextRequestID();
1161 TypesWriter tw = new TypesWriter();
1162 tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
1163 tw.writeUINT64(fileOffset);
1164 tw.writeString(src, srcoff, writeRequestLen);
1165
1166 if (debug != null) {
1167 debug.println("Sending SSH_FXP_WRITE...");
1168 debug.flush();
1169 }
1170
1171 sendMessage(Packet.SSH_FXP_WRITE, req_id, tw.getBytes());
1172 fileOffset += writeRequestLen;
1173 srcoff += writeRequestLen;
1174 len -= writeRequestLen;
1175 byte[] resp = receiveMessage(34000);
1176 TypesReader tr = new TypesReader(resp);
1177 int t = tr.readByte();
1178 int rep_id = tr.readUINT32();
1179
1180 if (rep_id != req_id)
1181 throw new IOException("The server sent an invalid id field.");
1182
1183 if (t != Packet.SSH_FXP_STATUS)
1184 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
1185
1186 int errorCode = tr.readUINT32();
1187
1188 if (errorCode == ErrorCodes.SSH_FX_OK)
1189 continue;
1190
1191 String errorMessage = tr.readString();
1192 throw new SFTPException(errorMessage, errorCode);
1193 }
1194 }
1195
1196 /**
1197 * Close a file.
1198 *
1199 * @param handle a SFTPv3FileHandle handle
1200 * @throws IOException
1201 */
1202 public void closeFile(SFTPv3FileHandle handle) throws IOException {
1203 if (handle == null)
1204 throw new IllegalArgumentException("the handle argument may not be null");
1205
1206 try {
1207 if (handle.isClosed == false) {
1208 closeHandle(handle.fileHandle);
1209 }
1210 }
1211 finally {
1212 handle.isClosed = true;
1213 }
1214 }
1215 }