0001: package ch.ethz.ssh2;
0002:
0003: import java.io.BufferedOutputStream;
0004: import java.io.IOException;
0005: import java.io.InputStream;
0006: import java.io.OutputStream;
0007: import java.io.PrintStream;
0008: import java.nio.charset.Charset;
0009: import java.util.HashMap;
0010: import java.util.Vector;
0011:
0012: import ch.ethz.ssh2.packets.TypesReader;
0013: import ch.ethz.ssh2.packets.TypesWriter;
0014: import ch.ethz.ssh2.sftp.AttribFlags;
0015: import ch.ethz.ssh2.sftp.ErrorCodes;
0016: import ch.ethz.ssh2.sftp.Packet;
0017:
0018: /**
0019: * A <code>SFTPv3Client</code> represents a SFTP (protocol version 3)
0020: * client connection tunnelled over a SSH-2 connection. This is a very simple
0021: * (synchronous) implementation.
0022: * <p>
0023: * Basically, most methods in this class map directly to one of
0024: * the packet types described in draft-ietf-secsh-filexfer-02.txt.
0025: * <p>
0026: * Note: this is experimental code.
0027: * <p>
0028: * Error handling: the methods of this class throw IOExceptions. However, unless
0029: * there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will
0030: * be thrown (a subclass of IOException). Therefore, you can implement more verbose
0031: * behavior by checking if a thrown exception if of this type. If yes, then you
0032: * can cast the exception and access detailed information about the failure.
0033: * <p>
0034: * Notes about file names, directory names and paths, copy-pasted
0035: * from the specs:
0036: * <ul>
0037: * <li>SFTP v3 represents file names as strings. File names are
0038: * assumed to use the slash ('/') character as a directory separator.</li>
0039: * <li>File names starting with a slash are "absolute", and are relative to
0040: * the root of the file system. Names starting with any other character
0041: * are relative to the user's default directory (home directory).</li>
0042: * <li>Servers SHOULD interpret a path name component ".." as referring to
0043: * the parent directory, and "." as referring to the current directory.
0044: * If the server implementation limits access to certain parts of the
0045: * file system, it must be extra careful in parsing file names when
0046: * enforcing such restrictions. There have been numerous reported
0047: * security bugs where a ".." in a path name has allowed access outside
0048: * the intended area.</li>
0049: * <li>An empty path name is valid, and it refers to the user's default
0050: * directory (usually the user's home directory).</li>
0051: * </ul>
0052: * <p>
0053: * If you are still not tired then please go on and read the comment for
0054: * {@link #setCharset(String)}.
0055: *
0056: * @author Christian Plattner, plattner@inf.ethz.ch
0057: * @version $Id: SFTPv3Client.java,v 1.9 2006/09/20 12:51:37 cplattne Exp $
0058: */
0059: public class SFTPv3Client {
0060: final Connection conn;
0061: final Session sess;
0062: final PrintStream debug;
0063:
0064: boolean flag_closed = false;
0065:
0066: InputStream is;
0067: OutputStream os;
0068:
0069: int protocol_version = 0;
0070: HashMap server_extensions = new HashMap();
0071:
0072: int next_request_id = 1000;
0073:
0074: String charsetName = null;
0075:
0076: /**
0077: * Create a SFTP v3 client.
0078: *
0079: * @param conn The underlying SSH-2 connection to be used.
0080: * @param debug
0081: * @throws IOException
0082: *
0083: * @deprecated this constructor (debug version) will disappear in the future,
0084: * use {@link #SFTPv3Client(Connection)} instead.
0085: */
0086: public SFTPv3Client(Connection conn, PrintStream debug)
0087: throws IOException {
0088: if (conn == null)
0089: throw new IllegalArgumentException(
0090: "Cannot accept null argument!");
0091:
0092: this .conn = conn;
0093: this .debug = debug;
0094:
0095: if (debug != null)
0096: debug
0097: .println("Opening session and starting SFTP subsystem.");
0098:
0099: sess = conn.openSession();
0100: sess.startSubSystem("sftp");
0101:
0102: is = sess.getStdout();
0103: os = new BufferedOutputStream(sess.getStdin(), 2048);
0104:
0105: if ((is == null) || (os == null))
0106: throw new IOException(
0107: "There is a problem with the streams of the underlying channel.");
0108:
0109: init();
0110: }
0111:
0112: /**
0113: * Create a SFTP v3 client.
0114: *
0115: * @param conn The underlying SSH-2 connection to be used.
0116: * @throws IOException
0117: */
0118: public SFTPv3Client(Connection conn) throws IOException {
0119: this (conn, null);
0120: }
0121:
0122: /**
0123: * Set the charset used to convert between Java Unicode Strings and byte encodings
0124: * used by the server for paths and file names. Unfortunately, the SFTP v3 draft
0125: * says NOTHING about such conversions (well, with the exception of error messages
0126: * which have to be in UTF-8). Newer drafts specify to use UTF-8 for file names
0127: * (if I remember correctly). However, a quick test using OpenSSH serving a EXT-3
0128: * filesystem has shown that UTF-8 seems to be a bad choice for SFTP v3 (tested with
0129: * filenames containing german umlauts). "windows-1252" seems to work better for Europe.
0130: * Luckily, "windows-1252" is the platform default in my case =).
0131: * <p>
0132: * If you don't set anything, then the platform default will be used (this is the default
0133: * behavior).
0134: *
0135: * @see #getCharset()
0136: *
0137: * @param charset the name of the charset to be used or <code>null</code> to use the platform's
0138: * default encoding.
0139: * @throws IOException
0140: */
0141: public void setCharset(String charset) throws IOException {
0142: if (charset == null) {
0143: charsetName = charset;
0144: return;
0145: }
0146:
0147: try {
0148: Charset.forName(charset);
0149: } catch (Exception e) {
0150: throw (IOException) new IOException(
0151: "This charset is not supported").initCause(e);
0152: }
0153: charsetName = charset;
0154: }
0155:
0156: /**
0157: * The currently used charset for filename encoding/decoding.
0158: *
0159: * @see #setCharset(String)
0160: *
0161: * @return The name of the charset (<code>null</code> if the platform's default charset is being used)
0162: */
0163: public String getCharset() {
0164: return charsetName;
0165: }
0166:
0167: private final void checkHandleValidAndOpen(SFTPv3FileHandle handle)
0168: throws IOException {
0169: if (handle.client != this )
0170: throw new IOException(
0171: "The file handle was created with another SFTPv3FileHandle instance.");
0172:
0173: if (handle.isClosed == true)
0174: throw new IOException("The file handle is closed.");
0175: }
0176:
0177: private final void sendMessage(int type, int requestId, byte[] msg,
0178: int off, int len) throws IOException {
0179: int msglen = len + 1;
0180:
0181: if (type != Packet.SSH_FXP_INIT)
0182: msglen += 4;
0183:
0184: os.write(msglen >> 24);
0185: os.write(msglen >> 16);
0186: os.write(msglen >> 8);
0187: os.write(msglen);
0188: os.write(type);
0189:
0190: if (type != Packet.SSH_FXP_INIT) {
0191: os.write(requestId >> 24);
0192: os.write(requestId >> 16);
0193: os.write(requestId >> 8);
0194: os.write(requestId);
0195: }
0196:
0197: os.write(msg, off, len);
0198: os.flush();
0199: }
0200:
0201: private final void sendMessage(int type, int requestId, byte[] msg)
0202: throws IOException {
0203: sendMessage(type, requestId, msg, 0, msg.length);
0204: }
0205:
0206: private final void readBytes(byte[] buff, int pos, int len)
0207: throws IOException {
0208: while (len > 0) {
0209: int count = is.read(buff, pos, len);
0210: if (count < 0)
0211: throw new IOException("Unexpected end of sftp stream.");
0212: if ((count == 0) || (count > len))
0213: throw new IOException(
0214: "Underlying stream implementation is bogus!");
0215: len -= count;
0216: pos += count;
0217: }
0218: }
0219:
0220: /**
0221: * Read a message and guarantee that the <b>contents</b> is not larger than
0222: * <code>maxlen</code> bytes.
0223: * <p>
0224: * Note: receiveMessage(34000) actually means that the message may be up to 34004
0225: * bytes (the length attribute preceeding the contents is 4 bytes).
0226: *
0227: * @param maxlen
0228: * @return the message contents
0229: * @throws IOException
0230: */
0231: private final byte[] receiveMessage(int maxlen) throws IOException {
0232: byte[] msglen = new byte[4];
0233:
0234: readBytes(msglen, 0, 4);
0235:
0236: int len = (((msglen[0] & 0xff) << 24)
0237: | ((msglen[1] & 0xff) << 16)
0238: | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff));
0239:
0240: if ((len > maxlen) || (len <= 0))
0241: throw new IOException("Illegal sftp packet len: " + len);
0242:
0243: byte[] msg = new byte[len];
0244:
0245: readBytes(msg, 0, len);
0246:
0247: return msg;
0248: }
0249:
0250: private final int generateNextRequestID() {
0251: synchronized (this ) {
0252: return next_request_id++;
0253: }
0254: }
0255:
0256: private final void closeHandle(byte[] handle) throws IOException {
0257: int req_id = generateNextRequestID();
0258:
0259: TypesWriter tw = new TypesWriter();
0260: tw.writeString(handle, 0, handle.length);
0261:
0262: sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes());
0263:
0264: expectStatusOKMessage(req_id);
0265: }
0266:
0267: private SFTPv3FileAttributes readAttrs(TypesReader tr)
0268: throws IOException {
0269: /*
0270: * uint32 flags
0271: * uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE
0272: * uint32 uid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
0273: * uint32 gid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
0274: * uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
0275: * uint32 atime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
0276: * uint32 mtime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
0277: * uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
0278: * string extended_type
0279: * string extended_data
0280: * ... more extended data (extended_type - extended_data pairs),
0281: * so that number of pairs equals extended_count
0282: */
0283:
0284: SFTPv3FileAttributes fa = new SFTPv3FileAttributes();
0285:
0286: int flags = tr.readUINT32();
0287:
0288: if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0) {
0289: if (debug != null)
0290: debug.println("SSH_FILEXFER_ATTR_SIZE");
0291: fa.size = new Long(tr.readUINT64());
0292: }
0293:
0294: if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0) {
0295: if (debug != null)
0296: debug.println("SSH_FILEXFER_ATTR_V3_UIDGID");
0297: fa.uid = new Integer(tr.readUINT32());
0298: fa.gid = new Integer(tr.readUINT32());
0299: }
0300:
0301: if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
0302: if (debug != null)
0303: debug.println("SSH_FILEXFER_ATTR_PERMISSIONS");
0304: fa.permissions = new Integer(tr.readUINT32());
0305: }
0306:
0307: if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0) {
0308: if (debug != null)
0309: debug.println("SSH_FILEXFER_ATTR_V3_ACMODTIME");
0310: fa.atime = new Integer(tr.readUINT32());
0311: fa.mtime = new Integer(tr.readUINT32());
0312:
0313: }
0314:
0315: if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0) {
0316: int count = tr.readUINT32();
0317:
0318: if (debug != null)
0319: debug.println("SSH_FILEXFER_ATTR_EXTENDED (" + count
0320: + ")");
0321:
0322: /* Read it anyway to detect corrupt packets */
0323:
0324: while (count > 0) {
0325: tr.readByteString();
0326: tr.readByteString();
0327: count--;
0328: }
0329: }
0330:
0331: return fa;
0332: }
0333:
0334: /**
0335: * Retrieve the file attributes of an open file.
0336: *
0337: * @param handle a SFTPv3FileHandle handle.
0338: * @return a SFTPv3FileAttributes object.
0339: * @throws IOException
0340: */
0341: public SFTPv3FileAttributes fstat(SFTPv3FileHandle handle)
0342: throws IOException {
0343: checkHandleValidAndOpen(handle);
0344:
0345: int req_id = generateNextRequestID();
0346:
0347: TypesWriter tw = new TypesWriter();
0348: tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
0349:
0350: if (debug != null) {
0351: debug.println("Sending SSH_FXP_FSTAT...");
0352: debug.flush();
0353: }
0354:
0355: sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes());
0356:
0357: byte[] resp = receiveMessage(34000);
0358:
0359: if (debug != null) {
0360: debug.println("Got REPLY.");
0361: debug.flush();
0362: }
0363:
0364: TypesReader tr = new TypesReader(resp);
0365:
0366: int t = tr.readByte();
0367:
0368: int rep_id = tr.readUINT32();
0369: if (rep_id != req_id)
0370: throw new IOException(
0371: "The server sent an invalid id field.");
0372:
0373: if (t == Packet.SSH_FXP_ATTRS) {
0374: return readAttrs(tr);
0375: }
0376:
0377: if (t != Packet.SSH_FXP_STATUS)
0378: throw new IOException(
0379: "The SFTP server sent an unexpected packet type ("
0380: + t + ")");
0381:
0382: int errorCode = tr.readUINT32();
0383:
0384: throw new SFTPException(tr.readString(), errorCode);
0385: }
0386:
0387: private SFTPv3FileAttributes statBoth(String path, int statMethod)
0388: throws IOException {
0389: int req_id = generateNextRequestID();
0390:
0391: TypesWriter tw = new TypesWriter();
0392: tw.writeString(path, charsetName);
0393:
0394: if (debug != null) {
0395: debug.println("Sending SSH_FXP_STAT/SSH_FXP_LSTAT...");
0396: debug.flush();
0397: }
0398:
0399: sendMessage(statMethod, req_id, tw.getBytes());
0400:
0401: byte[] resp = receiveMessage(34000);
0402:
0403: if (debug != null) {
0404: debug.println("Got REPLY.");
0405: debug.flush();
0406: }
0407:
0408: TypesReader tr = new TypesReader(resp);
0409:
0410: int t = tr.readByte();
0411:
0412: int rep_id = tr.readUINT32();
0413: if (rep_id != req_id)
0414: throw new IOException(
0415: "The server sent an invalid id field.");
0416:
0417: if (t == Packet.SSH_FXP_ATTRS) {
0418: return readAttrs(tr);
0419: }
0420:
0421: if (t != Packet.SSH_FXP_STATUS)
0422: throw new IOException(
0423: "The SFTP server sent an unexpected packet type ("
0424: + t + ")");
0425:
0426: int errorCode = tr.readUINT32();
0427:
0428: throw new SFTPException(tr.readString(), errorCode);
0429: }
0430:
0431: /**
0432: * Retrieve the file attributes of a file. This method
0433: * follows symbolic links on the server.
0434: *
0435: * @see #lstat(String)
0436: *
0437: * @param path See the {@link SFTPv3Client comment} for the class for more details.
0438: * @return a SFTPv3FileAttributes object.
0439: * @throws IOException
0440: */
0441: public SFTPv3FileAttributes stat(String path) throws IOException {
0442: return statBoth(path, Packet.SSH_FXP_STAT);
0443: }
0444:
0445: /**
0446: * Retrieve the file attributes of a file. This method
0447: * does NOT follow symbolic links on the server.
0448: *
0449: * @see #stat(String)
0450: *
0451: * @param path See the {@link SFTPv3Client comment} for the class for more details.
0452: * @return a SFTPv3FileAttributes object.
0453: * @throws IOException
0454: */
0455: public SFTPv3FileAttributes lstat(String path) throws IOException {
0456: return statBoth(path, Packet.SSH_FXP_LSTAT);
0457: }
0458:
0459: /**
0460: * Read the target of a symbolic link.
0461: *
0462: * @param path See the {@link SFTPv3Client comment} for the class for more details.
0463: * @return The target of the link.
0464: * @throws IOException
0465: */
0466: public String readLink(String path) throws IOException {
0467: int req_id = generateNextRequestID();
0468:
0469: TypesWriter tw = new TypesWriter();
0470: tw.writeString(path, charsetName);
0471:
0472: if (debug != null) {
0473: debug.println("Sending SSH_FXP_READLINK...");
0474: debug.flush();
0475: }
0476:
0477: sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes());
0478:
0479: byte[] resp = receiveMessage(34000);
0480:
0481: if (debug != null) {
0482: debug.println("Got REPLY.");
0483: debug.flush();
0484: }
0485:
0486: TypesReader tr = new TypesReader(resp);
0487:
0488: int t = tr.readByte();
0489:
0490: int rep_id = tr.readUINT32();
0491: if (rep_id != req_id)
0492: throw new IOException(
0493: "The server sent an invalid id field.");
0494:
0495: if (t == Packet.SSH_FXP_NAME) {
0496: int count = tr.readUINT32();
0497:
0498: if (count != 1)
0499: throw new IOException(
0500: "The server sent an invalid SSH_FXP_NAME packet.");
0501:
0502: return tr.readString(charsetName);
0503: }
0504:
0505: if (t != Packet.SSH_FXP_STATUS)
0506: throw new IOException(
0507: "The SFTP server sent an unexpected packet type ("
0508: + t + ")");
0509:
0510: int errorCode = tr.readUINT32();
0511:
0512: throw new SFTPException(tr.readString(), errorCode);
0513: }
0514:
0515: private void expectStatusOKMessage(int id) throws IOException {
0516: byte[] resp = receiveMessage(34000);
0517:
0518: if (debug != null) {
0519: debug.println("Got REPLY.");
0520: debug.flush();
0521: }
0522:
0523: TypesReader tr = new TypesReader(resp);
0524:
0525: int t = tr.readByte();
0526:
0527: int rep_id = tr.readUINT32();
0528: if (rep_id != id)
0529: throw new IOException(
0530: "The server sent an invalid id field.");
0531:
0532: if (t != Packet.SSH_FXP_STATUS)
0533: throw new IOException(
0534: "The SFTP server sent an unexpected packet type ("
0535: + t + ")");
0536:
0537: int errorCode = tr.readUINT32();
0538:
0539: if (errorCode == ErrorCodes.SSH_FX_OK)
0540: return;
0541:
0542: throw new SFTPException(tr.readString(), errorCode);
0543: }
0544:
0545: /**
0546: * Modify the attributes of a file. Used for operations such as changing
0547: * the ownership, permissions or access times, as well as for truncating a file.
0548: *
0549: * @param path See the {@link SFTPv3Client comment} for the class for more details.
0550: * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
0551: * made to the attributes of the file. Empty fields will be ignored.
0552: * @throws IOException
0553: */
0554: public void setstat(String path, SFTPv3FileAttributes attr)
0555: throws IOException {
0556: int req_id = generateNextRequestID();
0557:
0558: TypesWriter tw = new TypesWriter();
0559: tw.writeString(path, charsetName);
0560: tw.writeBytes(createAttrs(attr));
0561:
0562: if (debug != null) {
0563: debug.println("Sending SSH_FXP_SETSTAT...");
0564: debug.flush();
0565: }
0566:
0567: sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes());
0568:
0569: expectStatusOKMessage(req_id);
0570: }
0571:
0572: /**
0573: * Modify the attributes of a file. Used for operations such as changing
0574: * the ownership, permissions or access times, as well as for truncating a file.
0575: *
0576: * @param handle a SFTPv3FileHandle handle
0577: * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
0578: * made to the attributes of the file. Empty fields will be ignored.
0579: * @throws IOException
0580: */
0581: public void fsetstat(SFTPv3FileHandle handle,
0582: SFTPv3FileAttributes attr) throws IOException {
0583: checkHandleValidAndOpen(handle);
0584:
0585: int req_id = generateNextRequestID();
0586:
0587: TypesWriter tw = new TypesWriter();
0588: tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
0589: tw.writeBytes(createAttrs(attr));
0590:
0591: if (debug != null) {
0592: debug.println("Sending SSH_FXP_FSETSTAT...");
0593: debug.flush();
0594: }
0595:
0596: sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes());
0597:
0598: expectStatusOKMessage(req_id);
0599: }
0600:
0601: /**
0602: * Create a symbolic link on the server. Creates a link "src" that points
0603: * to "target".
0604: *
0605: * @param src See the {@link SFTPv3Client comment} for the class for more details.
0606: * @param target See the {@link SFTPv3Client comment} for the class for more details.
0607: * @throws IOException
0608: */
0609: public void createSymlink(String src, String target)
0610: throws IOException {
0611: int req_id = generateNextRequestID();
0612:
0613: /* Either I am too stupid to understand the SFTP draft
0614: * or the OpenSSH guys changed the semantics of src and target.
0615: */
0616:
0617: TypesWriter tw = new TypesWriter();
0618: tw.writeString(target, charsetName);
0619: tw.writeString(src, charsetName);
0620:
0621: if (debug != null) {
0622: debug.println("Sending SSH_FXP_SYMLINK...");
0623: debug.flush();
0624: }
0625:
0626: sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes());
0627:
0628: expectStatusOKMessage(req_id);
0629: }
0630:
0631: /**
0632: * Have the server canonicalize any given path name to an absolute path.
0633: * This is useful for converting path names containing ".." components or
0634: * relative pathnames without a leading slash into absolute paths.
0635: *
0636: * @param path See the {@link SFTPv3Client comment} for the class for more details.
0637: * @return An absolute path.
0638: * @throws IOException
0639: */
0640: public String canonicalPath(String path) throws IOException {
0641: int req_id = generateNextRequestID();
0642:
0643: TypesWriter tw = new TypesWriter();
0644: tw.writeString(path, charsetName);
0645:
0646: if (debug != null) {
0647: debug.println("Sending SSH_FXP_REALPATH...");
0648: debug.flush();
0649: }
0650:
0651: sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes());
0652:
0653: byte[] resp = receiveMessage(34000);
0654:
0655: if (debug != null) {
0656: debug.println("Got REPLY.");
0657: debug.flush();
0658: }
0659:
0660: TypesReader tr = new TypesReader(resp);
0661:
0662: int t = tr.readByte();
0663:
0664: int rep_id = tr.readUINT32();
0665: if (rep_id != req_id)
0666: throw new IOException(
0667: "The server sent an invalid id field.");
0668:
0669: if (t == Packet.SSH_FXP_NAME) {
0670: int count = tr.readUINT32();
0671:
0672: if (count != 1)
0673: throw new IOException(
0674: "The server sent an invalid SSH_FXP_NAME packet.");
0675:
0676: return tr.readString(charsetName);
0677: }
0678:
0679: if (t != Packet.SSH_FXP_STATUS)
0680: throw new IOException(
0681: "The SFTP server sent an unexpected packet type ("
0682: + t + ")");
0683:
0684: int errorCode = tr.readUINT32();
0685:
0686: throw new SFTPException(tr.readString(), errorCode);
0687: }
0688:
0689: private final Vector scanDirectory(byte[] handle)
0690: throws IOException {
0691: Vector files = new Vector();
0692:
0693: while (true) {
0694: int req_id = generateNextRequestID();
0695:
0696: TypesWriter tw = new TypesWriter();
0697: tw.writeString(handle, 0, handle.length);
0698:
0699: if (debug != null) {
0700: debug.println("Sending SSH_FXP_READDIR...");
0701: debug.flush();
0702: }
0703:
0704: sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes());
0705:
0706: byte[] resp = receiveMessage(34000);
0707:
0708: if (debug != null) {
0709: debug.println("Got REPLY.");
0710: debug.flush();
0711: }
0712:
0713: TypesReader tr = new TypesReader(resp);
0714:
0715: int t = tr.readByte();
0716:
0717: int rep_id = tr.readUINT32();
0718: if (rep_id != req_id)
0719: throw new IOException(
0720: "The server sent an invalid id field.");
0721:
0722: if (t == Packet.SSH_FXP_NAME) {
0723: int count = tr.readUINT32();
0724:
0725: if (debug != null)
0726: debug.println("Parsing " + count
0727: + " name entries...");
0728:
0729: while (count > 0) {
0730: SFTPv3DirectoryEntry dirEnt = new SFTPv3DirectoryEntry();
0731:
0732: dirEnt.filename = tr.readString(charsetName);
0733: dirEnt.longEntry = tr.readString(charsetName);
0734:
0735: dirEnt.attributes = readAttrs(tr);
0736: files.addElement(dirEnt);
0737:
0738: if (debug != null)
0739: debug
0740: .println("File: '" + dirEnt.filename
0741: + "'");
0742: count--;
0743: }
0744: continue;
0745: }
0746:
0747: if (t != Packet.SSH_FXP_STATUS)
0748: throw new IOException(
0749: "The SFTP server sent an unexpected packet type ("
0750: + t + ")");
0751:
0752: int errorCode = tr.readUINT32();
0753:
0754: if (errorCode == ErrorCodes.SSH_FX_EOF)
0755: return files;
0756:
0757: throw new SFTPException(tr.readString(), errorCode);
0758: }
0759: }
0760:
0761: private final byte[] openDirectory(String path) throws IOException {
0762: int req_id = generateNextRequestID();
0763:
0764: TypesWriter tw = new TypesWriter();
0765: tw.writeString(path, charsetName);
0766:
0767: if (debug != null) {
0768: debug.println("Sending SSH_FXP_OPENDIR...");
0769: debug.flush();
0770: }
0771:
0772: sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes());
0773:
0774: byte[] resp = receiveMessage(34000);
0775:
0776: TypesReader tr = new TypesReader(resp);
0777:
0778: int t = tr.readByte();
0779:
0780: int rep_id = tr.readUINT32();
0781: if (rep_id != req_id)
0782: throw new IOException(
0783: "The server sent an invalid id field.");
0784:
0785: if (t == Packet.SSH_FXP_HANDLE) {
0786: if (debug != null) {
0787: debug.println("Got SSH_FXP_HANDLE.");
0788: debug.flush();
0789: }
0790:
0791: byte[] handle = tr.readByteString();
0792: return handle;
0793: }
0794:
0795: if (t != Packet.SSH_FXP_STATUS)
0796: throw new IOException(
0797: "The SFTP server sent an unexpected packet type ("
0798: + t + ")");
0799:
0800: int errorCode = tr.readUINT32();
0801: String errorMessage = tr.readString();
0802:
0803: throw new SFTPException(errorMessage, errorCode);
0804: }
0805:
0806: private final String expandString(byte[] b, int off, int len) {
0807: StringBuffer sb = new StringBuffer();
0808:
0809: for (int i = 0; i < len; i++) {
0810: int c = b[off + i] & 0xff;
0811:
0812: if ((c >= 32) && (c <= 126)) {
0813: sb.append((char) c);
0814: } else {
0815: sb.append("{0x" + Integer.toHexString(c) + "}");
0816: }
0817: }
0818:
0819: return sb.toString();
0820: }
0821:
0822: private void init() throws IOException {
0823: /* Send SSH_FXP_INIT (version 3) */
0824:
0825: final int client_version = 3;
0826:
0827: if (debug != null)
0828: debug.println("Sending SSH_FXP_INIT (" + client_version
0829: + ")...");
0830:
0831: TypesWriter tw = new TypesWriter();
0832: tw.writeUINT32(client_version);
0833: sendMessage(Packet.SSH_FXP_INIT, 0, tw.getBytes());
0834:
0835: /* Receive SSH_FXP_VERSION */
0836:
0837: if (debug != null)
0838: debug.println("Waiting for SSH_FXP_VERSION...");
0839:
0840: TypesReader tr = new TypesReader(receiveMessage(34000)); /* Should be enough for any reasonable server */
0841:
0842: int type = tr.readByte();
0843:
0844: if (type != Packet.SSH_FXP_VERSION) {
0845: throw new IOException(
0846: "The server did not send a SSH_FXP_VERSION packet (got "
0847: + type + ")");
0848: }
0849:
0850: protocol_version = tr.readUINT32();
0851:
0852: if (debug != null)
0853: debug.println("SSH_FXP_VERSION: protocol_version = "
0854: + protocol_version);
0855:
0856: if (protocol_version != 3)
0857: throw new IOException("Server version " + protocol_version
0858: + " is currently not supported");
0859:
0860: /* Read and save extensions (if any) for later use */
0861:
0862: while (tr.remain() != 0) {
0863: String name = tr.readString();
0864: byte[] value = tr.readByteString();
0865: server_extensions.put(name, value);
0866:
0867: if (debug != null)
0868: debug.println("SSH_FXP_VERSION: extension: " + name
0869: + " = '" + expandString(value, 0, value.length)
0870: + "'");
0871: }
0872: }
0873:
0874: /**
0875: * Returns the negotiated SFTP protocol version between the client and the server.
0876: *
0877: * @return SFTP protocol version, i.e., "3".
0878: *
0879: */
0880: public int getProtocolVersion() {
0881: return protocol_version;
0882: }
0883:
0884: /**
0885: * Close this SFTP session. NEVER forget to call this method to free up
0886: * resources - even if you got an exception from one of the other methods.
0887: * Sometimes these other methods may throw an exception, saying that the
0888: * underlying channel is closed (this can happen, e.g., if the other server
0889: * sent a close message.) However, as long as you have not called the
0890: * <code>close()</code> method, you are likely wasting resources.
0891: *
0892: */
0893: public void close() {
0894: sess.close();
0895: }
0896:
0897: /**
0898: * List the contents of a directory.
0899: *
0900: * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
0901: * @return A Vector containing {@link SFTPv3DirectoryEntry} objects.
0902: * @throws IOException
0903: */
0904: public Vector ls(String dirName) throws IOException {
0905: byte[] handle = openDirectory(dirName);
0906: Vector result = scanDirectory(handle);
0907: closeHandle(handle);
0908: return result;
0909: }
0910:
0911: /**
0912: * Create a new directory.
0913: *
0914: * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
0915: * @param posixPermissions the permissions for this directory, e.g., "0700". The server
0916: * will likely apply a umask.
0917: * @throws IOException
0918: */
0919: public void mkdir(String dirName, int posixPermissions)
0920: throws IOException {
0921: int req_id = generateNextRequestID();
0922:
0923: TypesWriter tw = new TypesWriter();
0924: tw.writeString(dirName, charsetName);
0925: tw.writeUINT32(AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS);
0926: tw.writeUINT32(posixPermissions);
0927:
0928: sendMessage(Packet.SSH_FXP_MKDIR, req_id, tw.getBytes());
0929:
0930: expectStatusOKMessage(req_id);
0931: }
0932:
0933: /**
0934: * Remove a file.
0935: *
0936: * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
0937: * @throws IOException
0938: */
0939: public void rm(String fileName) throws IOException {
0940: int req_id = generateNextRequestID();
0941:
0942: TypesWriter tw = new TypesWriter();
0943: tw.writeString(fileName, charsetName);
0944:
0945: sendMessage(Packet.SSH_FXP_REMOVE, req_id, tw.getBytes());
0946:
0947: expectStatusOKMessage(req_id);
0948: }
0949:
0950: /**
0951: * Remove an empty directory.
0952: *
0953: * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
0954: * @throws IOException
0955: */
0956: public void rmdir(String dirName) throws IOException {
0957: int req_id = generateNextRequestID();
0958:
0959: TypesWriter tw = new TypesWriter();
0960: tw.writeString(dirName, charsetName);
0961:
0962: sendMessage(Packet.SSH_FXP_RMDIR, req_id, tw.getBytes());
0963:
0964: expectStatusOKMessage(req_id);
0965: }
0966:
0967: /**
0968: * Move a file or directory.
0969: *
0970: * @param oldPath See the {@link SFTPv3Client comment} for the class for more details.
0971: * @param newPath See the {@link SFTPv3Client comment} for the class for more details.
0972: * @throws IOException
0973: */
0974: public void mv(String oldPath, String newPath) throws IOException {
0975: int req_id = generateNextRequestID();
0976:
0977: TypesWriter tw = new TypesWriter();
0978: tw.writeString(oldPath, charsetName);
0979: tw.writeString(newPath, charsetName);
0980:
0981: sendMessage(Packet.SSH_FXP_RENAME, req_id, tw.getBytes());
0982:
0983: expectStatusOKMessage(req_id);
0984: }
0985:
0986: /**
0987: * Open a file for reading.
0988: *
0989: * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
0990: * @return a SFTPv3FileHandle handle
0991: * @throws IOException
0992: */
0993: public SFTPv3FileHandle openFileRO(String fileName)
0994: throws IOException {
0995: return openFile(fileName, 0x00000001, null); // SSH_FXF_READ
0996: }
0997:
0998: /**
0999: * Open a file for reading and writing.
1000: *
1001: * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
1002: * @return a SFTPv3FileHandle handle
1003: * @throws IOException
1004: */
1005: public SFTPv3FileHandle openFileRW(String fileName)
1006: throws IOException {
1007: return openFile(fileName, 0x00000003, null); // SSH_FXF_READ | SSH_FXF_WRITE
1008: }
1009:
1010: // Append is broken (already in the specification, because there is no way to
1011: // send a write operation (what offset to use??))
1012: // public SFTPv3FileHandle openFileRWAppend(String fileName) throws IOException
1013: // {
1014: // return openFile(fileName, 0x00000007, null); // SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND
1015: // }
1016:
1017: /**
1018: * Create a file and open it for reading and writing.
1019: * Same as {@link #createFile(String, SFTPv3FileAttributes) createFile(fileName, null)}.
1020: *
1021: * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
1022: * @return a SFTPv3FileHandle handle
1023: * @throws IOException
1024: */
1025: public SFTPv3FileHandle createFile(String fileName)
1026: throws IOException {
1027: return createFile(fileName, null);
1028: }
1029:
1030: /**
1031: * Create a file and open it for reading and writing.
1032: * You can specify the default attributes of the file (the server may or may
1033: * not respect your wishes).
1034: *
1035: * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
1036: * @param attr may be <code>null</code> to use server defaults. Probably only
1037: * the <code>uid</code>, <code>gid</code> and <code>permissions</code>
1038: * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
1039: * structure make sense. You need only to set those fields where you want
1040: * to override the server's defaults.
1041: * @return a SFTPv3FileHandle handle
1042: * @throws IOException
1043: */
1044: public SFTPv3FileHandle createFile(String fileName,
1045: SFTPv3FileAttributes attr) throws IOException {
1046: return openFile(fileName, 0x00000008 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE
1047: }
1048:
1049: /**
1050: * Create a file (truncate it if it already exists) and open it for reading and writing.
1051: * Same as {@link #createFileTruncate(String, SFTPv3FileAttributes) createFileTruncate(fileName, null)}.
1052: *
1053: * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
1054: * @return a SFTPv3FileHandle handle
1055: * @throws IOException
1056: */
1057: public SFTPv3FileHandle createFileTruncate(String fileName)
1058: throws IOException {
1059: return createFileTruncate(fileName, null);
1060: }
1061:
1062: /**
1063: * reate a file (truncate it if it already exists) and open it for reading and writing.
1064: * You can specify the default attributes of the file (the server may or may
1065: * not respect your wishes).
1066: *
1067: * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
1068: * @param attr may be <code>null</code> to use server defaults. Probably only
1069: * the <code>uid</code>, <code>gid</code> and <code>permissions</code>
1070: * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
1071: * structure make sense. You need only to set those fields where you want
1072: * to override the server's defaults.
1073: * @return a SFTPv3FileHandle handle
1074: * @throws IOException
1075: */
1076: public SFTPv3FileHandle createFileTruncate(String fileName,
1077: SFTPv3FileAttributes attr) throws IOException {
1078: return openFile(fileName, 0x00000018 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_READ | SSH_FXF_WRITE
1079: }
1080:
1081: private byte[] createAttrs(SFTPv3FileAttributes attr) {
1082: TypesWriter tw = new TypesWriter();
1083:
1084: int attrFlags = 0;
1085:
1086: if (attr == null) {
1087: tw.writeUINT32(0);
1088: } else {
1089: if (attr.size != null)
1090: attrFlags = attrFlags
1091: | AttribFlags.SSH_FILEXFER_ATTR_SIZE;
1092:
1093: if ((attr.uid != null) && (attr.gid != null))
1094: attrFlags = attrFlags
1095: | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID;
1096:
1097: if (attr.permissions != null)
1098: attrFlags = attrFlags
1099: | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS;
1100:
1101: if ((attr.atime != null) && (attr.mtime != null))
1102: attrFlags = attrFlags
1103: | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME;
1104:
1105: tw.writeUINT32(attrFlags);
1106:
1107: if (attr.size != null)
1108: tw.writeUINT64(attr.size.longValue());
1109:
1110: if ((attr.uid != null) && (attr.gid != null)) {
1111: tw.writeUINT32(attr.uid.intValue());
1112: tw.writeUINT32(attr.gid.intValue());
1113: }
1114:
1115: if (attr.permissions != null)
1116: tw.writeUINT32(attr.permissions.intValue());
1117:
1118: if ((attr.atime != null) && (attr.mtime != null)) {
1119: tw.writeUINT32(attr.atime.intValue());
1120: tw.writeUINT32(attr.mtime.intValue());
1121: }
1122: }
1123:
1124: return tw.getBytes();
1125: }
1126:
1127: private SFTPv3FileHandle openFile(String fileName, int flags,
1128: SFTPv3FileAttributes attr) throws IOException {
1129: int req_id = generateNextRequestID();
1130:
1131: TypesWriter tw = new TypesWriter();
1132: tw.writeString(fileName, charsetName);
1133: tw.writeUINT32(flags);
1134: tw.writeBytes(createAttrs(attr));
1135:
1136: if (debug != null) {
1137: debug.println("Sending SSH_FXP_OPEN...");
1138: debug.flush();
1139: }
1140:
1141: sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes());
1142:
1143: byte[] resp = receiveMessage(34000);
1144:
1145: TypesReader tr = new TypesReader(resp);
1146:
1147: int t = tr.readByte();
1148:
1149: int rep_id = tr.readUINT32();
1150: if (rep_id != req_id)
1151: throw new IOException(
1152: "The server sent an invalid id field.");
1153:
1154: if (t == Packet.SSH_FXP_HANDLE) {
1155: if (debug != null) {
1156: debug.println("Got SSH_FXP_HANDLE.");
1157: debug.flush();
1158: }
1159:
1160: return new SFTPv3FileHandle(this , tr.readByteString());
1161: }
1162:
1163: if (t != Packet.SSH_FXP_STATUS)
1164: throw new IOException(
1165: "The SFTP server sent an unexpected packet type ("
1166: + t + ")");
1167:
1168: int errorCode = tr.readUINT32();
1169: String errorMessage = tr.readString();
1170:
1171: throw new SFTPException(errorMessage, errorCode);
1172: }
1173:
1174: /**
1175: * Read bytes from a file. No more than 32768 bytes may be read at once.
1176: * Be aware that the semantics of read() are different than for Java streams.
1177: * <p>
1178: * <ul>
1179: * <li>The server will read as many bytes as it can from the file (up to <code>len</code>),
1180: * and return them.</li>
1181: * <li>If EOF is encountered before reading any data, <code>-1</code> is returned.
1182: * <li>If an error occurs, an exception is thrown</li>.
1183: * <li>For normal disk files, it is guaranteed that the server will return the specified
1184: * number of bytes, or up to end of file. For, e.g., device files this may return
1185: * fewer bytes than requested.</li>
1186: * </ul>
1187: *
1188: * @param handle a SFTPv3FileHandle handle
1189: * @param fileOffset offset (in bytes) in the file
1190: * @param dst the destination byte array
1191: * @param dstoff offset in the destination byte array
1192: * @param len how many bytes to read, 0 < len <= 32768 bytes
1193: * @return the number of bytes that could be read, may be less than requested if
1194: * the end of the file is reached, -1 is returned in case of <code>EOF</code>
1195: * @throws IOException
1196: */
1197: public int read(SFTPv3FileHandle handle, long fileOffset,
1198: byte[] dst, int dstoff, int len) throws IOException {
1199: checkHandleValidAndOpen(handle);
1200:
1201: if ((len > 32768) || (len <= 0))
1202: throw new IllegalArgumentException("invalid len argument");
1203:
1204: int req_id = generateNextRequestID();
1205:
1206: TypesWriter tw = new TypesWriter();
1207: tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
1208: tw.writeUINT64(fileOffset);
1209: tw.writeUINT32(len);
1210:
1211: if (debug != null) {
1212: debug.println("Sending SSH_FXP_READ...");
1213: debug.flush();
1214: }
1215:
1216: sendMessage(Packet.SSH_FXP_READ, req_id, tw.getBytes());
1217:
1218: byte[] resp = receiveMessage(34000);
1219:
1220: TypesReader tr = new TypesReader(resp);
1221:
1222: int t = tr.readByte();
1223:
1224: int rep_id = tr.readUINT32();
1225: if (rep_id != req_id)
1226: throw new IOException(
1227: "The server sent an invalid id field.");
1228:
1229: if (t == Packet.SSH_FXP_DATA) {
1230: if (debug != null) {
1231: debug.println("Got SSH_FXP_DATA...");
1232: debug.flush();
1233: }
1234:
1235: int readLen = tr.readUINT32();
1236:
1237: if ((readLen < 0) || (readLen > len))
1238: throw new IOException(
1239: "The server sent an invalid length field.");
1240:
1241: tr.readBytes(dst, dstoff, readLen);
1242:
1243: return readLen;
1244: }
1245:
1246: if (t != Packet.SSH_FXP_STATUS)
1247: throw new IOException(
1248: "The SFTP server sent an unexpected packet type ("
1249: + t + ")");
1250:
1251: int errorCode = tr.readUINT32();
1252:
1253: if (errorCode == ErrorCodes.SSH_FX_EOF) {
1254: if (debug != null) {
1255: debug.println("Got SSH_FX_EOF.");
1256: debug.flush();
1257: }
1258:
1259: return -1;
1260: }
1261:
1262: String errorMessage = tr.readString();
1263:
1264: throw new SFTPException(errorMessage, errorCode);
1265: }
1266:
1267: /**
1268: * Write bytes to a file. If <code>len</code> > 32768, then the write operation will
1269: * be split into multiple writes.
1270: *
1271: * @param handle a SFTPv3FileHandle handle.
1272: * @param fileOffset offset (in bytes) in the file.
1273: * @param src the source byte array.
1274: * @param srcoff offset in the source byte array.
1275: * @param len how many bytes to write.
1276: * @throws IOException
1277: */
1278: public void write(SFTPv3FileHandle handle, long fileOffset,
1279: byte[] src, int srcoff, int len) throws IOException {
1280: checkHandleValidAndOpen(handle);
1281:
1282: if (len < 0)
1283:
1284: while (len > 0) {
1285: int writeRequestLen = len;
1286:
1287: if (writeRequestLen > 32768)
1288: writeRequestLen = 32768;
1289:
1290: int req_id = generateNextRequestID();
1291:
1292: TypesWriter tw = new TypesWriter();
1293: tw.writeString(handle.fileHandle, 0,
1294: handle.fileHandle.length);
1295: tw.writeUINT64(fileOffset);
1296: tw.writeString(src, srcoff, writeRequestLen);
1297:
1298: if (debug != null) {
1299: debug.println("Sending SSH_FXP_WRITE...");
1300: debug.flush();
1301: }
1302:
1303: sendMessage(Packet.SSH_FXP_WRITE, req_id, tw.getBytes());
1304:
1305: fileOffset += writeRequestLen;
1306:
1307: srcoff += writeRequestLen;
1308: len -= writeRequestLen;
1309:
1310: byte[] resp = receiveMessage(34000);
1311:
1312: TypesReader tr = new TypesReader(resp);
1313:
1314: int t = tr.readByte();
1315:
1316: int rep_id = tr.readUINT32();
1317: if (rep_id != req_id)
1318: throw new IOException(
1319: "The server sent an invalid id field.");
1320:
1321: if (t != Packet.SSH_FXP_STATUS)
1322: throw new IOException(
1323: "The SFTP server sent an unexpected packet type ("
1324: + t + ")");
1325:
1326: int errorCode = tr.readUINT32();
1327:
1328: if (errorCode == ErrorCodes.SSH_FX_OK)
1329: continue;
1330:
1331: String errorMessage = tr.readString();
1332:
1333: throw new SFTPException(errorMessage, errorCode);
1334: }
1335: }
1336:
1337: /**
1338: * Close a file.
1339: *
1340: * @param handle a SFTPv3FileHandle handle
1341: * @throws IOException
1342: */
1343: public void closeFile(SFTPv3FileHandle handle) throws IOException {
1344: if (handle == null)
1345: throw new IllegalArgumentException(
1346: "the handle argument may not be null");
1347:
1348: try {
1349: if (handle.isClosed == false) {
1350: closeHandle(handle.fileHandle);
1351: }
1352: } finally {
1353: handle.isClosed = true;
1354: }
1355: }
1356: }
|