From 3beee8350d7d97fa2a5b66816bf965157fda1d24 Mon Sep 17 00:00:00 2001 From: Shikhar Bhushan Date: Fri, 6 Aug 2010 00:02:21 +0100 Subject: [PATCH] support sftp versions < 3 --- src/main/java/net/schmizz/sshj/SSHClient.java | 16 ++- .../net/schmizz/sshj/sftp/PacketReader.java | 10 +- .../net/schmizz/sshj/sftp/PathHelper.java | 8 +- .../net/schmizz/sshj/sftp/RemoteResource.java | 2 +- .../java/net/schmizz/sshj/sftp/Response.java | 19 ++- .../net/schmizz/sshj/sftp/SFTPClient.java | 44 ++++--- .../net/schmizz/sshj/sftp/SFTPEngine.java | 118 ++++++++++-------- .../net/schmizz/sshj/sftp/SFTPException.java | 1 - .../schmizz/sshj/sftp/SFTPFileTransfer.java | 32 ++--- .../schmizz/sshj/sftp/StatefulSFTPClient.java | 6 +- 10 files changed, 148 insertions(+), 108 deletions(-) diff --git a/src/main/java/net/schmizz/sshj/SSHClient.java b/src/main/java/net/schmizz/sshj/SSHClient.java index e6e983f9..6ccdcf20 100644 --- a/src/main/java/net/schmizz/sshj/SSHClient.java +++ b/src/main/java/net/schmizz/sshj/SSHClient.java @@ -33,6 +33,7 @@ import net.schmizz.sshj.connection.channel.forwarded.RemotePortForwarder.Forward import net.schmizz.sshj.connection.channel.forwarded.X11Forwarder; import net.schmizz.sshj.connection.channel.forwarded.X11Forwarder.X11Channel; import net.schmizz.sshj.sftp.SFTPClient; +import net.schmizz.sshj.sftp.SFTPEngine; import net.schmizz.sshj.sftp.StatefulSFTPClient; import net.schmizz.sshj.transport.Transport; import net.schmizz.sshj.transport.TransportException; @@ -592,8 +593,21 @@ public class SSHClient */ public SFTPClient newSFTPClient() throws IOException { + return newSFTPClient(SFTPEngine.MAX_SUPPORTED_VERSION); + } + + /** + * @param version the protocol version to use + * + * @return Instantiated {@link SFTPClient} implementation. + * + * @throws IOException if there is an error starting the {@code sftp} subsystem + * @see StatefulSFTPClient + */ + public SFTPClient newSFTPClient(int version) + throws IOException { assert isConnected() && isAuthenticated(); - return new SFTPClient(this); + return new SFTPClient(new SFTPEngine(this, version).init()); } /** diff --git a/src/main/java/net/schmizz/sshj/sftp/PacketReader.java b/src/main/java/net/schmizz/sshj/sftp/PacketReader.java index c2837679..023ac0fb 100644 --- a/src/main/java/net/schmizz/sshj/sftp/PacketReader.java +++ b/src/main/java/net/schmizz/sshj/sftp/PacketReader.java @@ -34,9 +34,11 @@ public class PacketReader private final Map> futures = new ConcurrentHashMap>(); private final SFTPPacket packet = new SFTPPacket(); private final byte[] lenBuf = new byte[4]; + private final SFTPEngine engine; - public PacketReader(InputStream in) { - this.in = in; + public PacketReader(SFTPEngine engine) { + this.engine = engine; + this.in = engine.getSubsystem().getInputStream(); setName("sftp reader"); } @@ -78,7 +80,7 @@ public class PacketReader @Override public void run() { try { - while (true) { + while (!isInterrupted()) { readPacket(); handle(); } @@ -90,7 +92,7 @@ public class PacketReader public void handle() throws SFTPException { - Response resp = new Response(packet); + Response resp = new Response(packet, engine.getOperativeProtocolVersion()); Future future = futures.remove(resp.getRequestID()); log.debug("Received {} packet", resp.getType()); if (future == null) diff --git a/src/main/java/net/schmizz/sshj/sftp/PathHelper.java b/src/main/java/net/schmizz/sshj/sftp/PathHelper.java index 07686f90..f1f9ac76 100644 --- a/src/main/java/net/schmizz/sshj/sftp/PathHelper.java +++ b/src/main/java/net/schmizz/sshj/sftp/PathHelper.java @@ -19,11 +19,11 @@ import java.io.IOException; public class PathHelper { - private final SFTPEngine sftp; + private final SFTPEngine engine; private String dotDir; - public PathHelper(SFTPEngine sftp) { - this.sftp = sftp; + public PathHelper(SFTPEngine engine) { + this.engine = engine; } public PathComponents getComponents(String path) @@ -56,7 +56,7 @@ public class PathHelper { private String canon(String path) throws IOException { - return sftp.canonicalize(path); + return engine.canonicalize(path); } } \ No newline at end of file diff --git a/src/main/java/net/schmizz/sshj/sftp/RemoteResource.java b/src/main/java/net/schmizz/sshj/sftp/RemoteResource.java index d7cc9421..4014c8e7 100644 --- a/src/main/java/net/schmizz/sshj/sftp/RemoteResource.java +++ b/src/main/java/net/schmizz/sshj/sftp/RemoteResource.java @@ -21,7 +21,7 @@ import org.slf4j.LoggerFactory; import java.io.Closeable; import java.io.IOException; -abstract class RemoteResource +public abstract class RemoteResource implements Closeable { /** Logger */ diff --git a/src/main/java/net/schmizz/sshj/sftp/Response.java b/src/main/java/net/schmizz/sshj/sftp/Response.java index 71c956ca..99a6903c 100644 --- a/src/main/java/net/schmizz/sshj/sftp/Response.java +++ b/src/main/java/net/schmizz/sshj/sftp/Response.java @@ -47,15 +47,21 @@ public class Response } + private final int protocolVersion; private final PacketType type; private final long reqID; - public Response(Buffer pk) { + public Response(Buffer pk, int protocolVersion) { super(pk); + this.protocolVersion = protocolVersion; this.type = readType(); this.reqID = readLong(); } + public int getProtocolVersion() { + return protocolVersion; + } + public long getRequestID() { return reqID; } @@ -72,7 +78,7 @@ public class Response throws SFTPException { if (getType() != pt) if (getType() == PacketType.STATUS) - throw new SFTPException(readStatusCode(), readString()); + error(readStatusCode()); else throw new SFTPException("Unexpected packet " + getType()); return this; @@ -85,10 +91,15 @@ public class Response public Response ensureStatusIs(StatusCode acceptable) throws SFTPException { - StatusCode sc = readStatusCode(); + final StatusCode sc = readStatusCode(); if (sc != acceptable) - throw new SFTPException(sc, readString()); + error(sc); return this; } + private String error(StatusCode sc) + throws SFTPException { + throw new SFTPException(sc, protocolVersion < 3 ? sc.toString() : readString()); + } + } diff --git a/src/main/java/net/schmizz/sshj/sftp/SFTPClient.java b/src/main/java/net/schmizz/sshj/sftp/SFTPClient.java index 8c412a9e..3f4851be 100644 --- a/src/main/java/net/schmizz/sshj/sftp/SFTPClient.java +++ b/src/main/java/net/schmizz/sshj/sftp/SFTPClient.java @@ -15,7 +15,6 @@ */ package net.schmizz.sshj.sftp; -import net.schmizz.sshj.connection.channel.direct.SessionFactory; import net.schmizz.sshj.xfer.FilePermission; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,19 +33,18 @@ public class SFTPClient /** Logger */ protected final Logger log = LoggerFactory.getLogger(getClass()); - private final SFTPEngine sftp; + private final SFTPEngine engine; private final SFTPFileTransfer xfer; private final PathHelper pathHelper; - public SFTPClient(SessionFactory ssh) - throws IOException { - this.sftp = new SFTPEngine(ssh).init(); - this.pathHelper = new PathHelper(sftp); - this.xfer = new SFTPFileTransfer(sftp); + public SFTPClient(SFTPEngine engine) { + this.engine = engine; + this.pathHelper = new PathHelper(engine); + this.xfer = new SFTPFileTransfer(engine); } public SFTPEngine getSFTPEngine() { - return sftp; + return engine; } public SFTPFileTransfer getFileTansfer() { @@ -60,7 +58,7 @@ public class SFTPClient public List ls(String path, RemoteResourceFilter filter) throws IOException { - final RemoteDirectory dir = sftp.openDir(path); + final RemoteDirectory dir = engine.openDir(path); try { return dir.scan(filter); } finally { @@ -71,7 +69,7 @@ public class SFTPClient public RemoteFile open(String filename, Set mode, FileAttributes attrs) throws IOException { log.debug("Opening `{}`", filename); - return sftp.open(filename, mode, attrs); + return engine.open(filename, mode, attrs); } public RemoteFile open(String filename, Set mode) @@ -86,7 +84,7 @@ public class SFTPClient public void mkdir(String dirname) throws IOException { - sftp.makeDir(dirname); + engine.makeDir(dirname); } public void mkdirs(String path) @@ -111,7 +109,7 @@ public class SFTPClient public FileAttributes statExistence(String path) throws IOException { try { - return sftp.stat(path); + return engine.stat(path); } catch (SFTPException sftpe) { if (sftpe.getStatusCode() == Response.StatusCode.NO_SUCH_FILE) { return null; @@ -123,31 +121,31 @@ public class SFTPClient public void rename(String oldpath, String newpath) throws IOException { - sftp.rename(oldpath, newpath); + engine.rename(oldpath, newpath); } public void rm(String filename) throws IOException { - sftp.remove(filename); + engine.remove(filename); } public void rmdir(String dirname) throws IOException { - sftp.removeDir(dirname); + engine.removeDir(dirname); } public void symlink(String linkpath, String targetpath) throws IOException { - sftp.symlink(linkpath, targetpath); + engine.symlink(linkpath, targetpath); } public int version() { - return sftp.getOperativeProtocolVersion(); + return engine.getOperativeProtocolVersion(); } public void setattr(String path, FileAttributes attrs) throws IOException { - sftp.setAttributes(path, attrs); + engine.setAttributes(path, attrs); } public int uid(String path) @@ -187,17 +185,17 @@ public class SFTPClient public String readlink(String path) throws IOException { - return sftp.readLink(path); + return engine.readLink(path); } public FileAttributes stat(String path) throws IOException { - return sftp.stat(path); + return engine.stat(path); } public FileAttributes lstat(String path) throws IOException { - return sftp.lstat(path); + return engine.lstat(path); } public void chown(String path, int uid) @@ -222,7 +220,7 @@ public class SFTPClient public String canonicalize(String path) throws IOException { - return sftp.canonicalize(path); + return engine.canonicalize(path); } public long size(String path) @@ -243,7 +241,7 @@ public class SFTPClient @Override public void close() throws IOException { - sftp.close(); + engine.close(); } } diff --git a/src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java b/src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java index b1519750..b9900f54 100644 --- a/src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java +++ b/src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java @@ -33,37 +33,41 @@ import java.util.concurrent.TimeUnit; public class SFTPEngine implements Requester, Closeable { - /** Logger */ - private final Logger log = LoggerFactory.getLogger(getClass()); - - public static final int PROTOCOL_VERSION = 3; - + public static final int MAX_SUPPORTED_VERSION = 3; public static final int DEFAULT_TIMEOUT = 30; - private volatile int timeout = DEFAULT_TIMEOUT; + /** Logger */ + protected final Logger log = LoggerFactory.getLogger(getClass()); - private final Subsystem sub; - private final PacketReader reader; - private final OutputStream out; + protected volatile int timeout = DEFAULT_TIMEOUT; - private long reqID; - private int negotiatedVersion; - private final Map serverExtensions = new HashMap(); + protected final int clientVersion; + protected final Subsystem sub; + protected final PacketReader reader; + protected final OutputStream out; + + protected long reqID; + protected int operativeVersion; + protected final Map serverExtensions = new HashMap(); public SFTPEngine(SessionFactory ssh) throws SSHException { - sub = ssh.startSession().startSubsystem("sftp"); - out = sub.getOutputStream(); - reader = new PacketReader(sub.getInputStream()); + this(ssh, MAX_SUPPORTED_VERSION); } - public Subsystem getSubsystem() { - return sub; + public SFTPEngine(SessionFactory ssh, int clientVersion) + throws SSHException { + if (clientVersion > MAX_SUPPORTED_VERSION) + throw new SFTPException("Max. supported protocol version is: " + MAX_SUPPORTED_VERSION); + this.clientVersion = clientVersion; + sub = ssh.startSession().startSubsystem("sftp"); + out = sub.getOutputStream(); + reader = new PacketReader(this); } public SFTPEngine init() throws IOException { - transmit(new SFTPPacket(PacketType.INIT).putInt(PROTOCOL_VERSION)); + transmit(new SFTPPacket(PacketType.INIT).putInt(clientVersion)); final SFTPPacket response = reader.readPacket(); @@ -71,10 +75,10 @@ public class SFTPEngine if (type != PacketType.VERSION) throw new SFTPException("Expected INIT packet, received: " + type); - negotiatedVersion = response.readInt(); - log.info("Client version {}, server version {}", PROTOCOL_VERSION, negotiatedVersion); - if (negotiatedVersion < PROTOCOL_VERSION) - throw new SFTPException("Server reported protocol version: " + negotiatedVersion); + operativeVersion = response.readInt(); + log.info("Client version {}, server version {}", clientVersion, operativeVersion); + if (operativeVersion < clientVersion) + throw new SFTPException("Server reported protocol version: " + operativeVersion); while (response.available() > 0) serverExtensions.put(response.readString(), response.readString()); @@ -84,8 +88,16 @@ public class SFTPEngine return this; } + public Subsystem getSubsystem() { + return sub; + } + public int getOperativeProtocolVersion() { - return negotiatedVersion; + return operativeVersion; + } + + public Request newExtendedRequest(String reqName) { + return newRequest(PacketType.EXTENDED).putString(reqName); } @Override @@ -102,17 +114,6 @@ public class SFTPEngine return req.getResponseFuture().get(timeout, TimeUnit.SECONDS); } - private synchronized void transmit(SFTPPacket payload) - throws IOException { - final int len = payload.available(); - out.write((len >>> 24) & 0xff); - out.write((len >>> 16) & 0xff); - out.write((len >>> 8) & 0xff); - out.write(len & 0xff); - out.write(payload.array(), payload.rpos(), len); - out.flush(); - } - public RemoteFile open(String path, Set modes, FileAttributes fa) throws IOException { final String handle = doRequest( @@ -148,6 +149,8 @@ public class SFTPEngine public String readLink(String path) throws IOException { + if (operativeVersion < 3) + throw new SFTPException("READLINK is not supported in SFTPv" + operativeVersion); return readSingleName( doRequest( newRequest(PacketType.READLINK).putString(path) @@ -166,6 +169,8 @@ public class SFTPEngine public void symlink(String linkpath, String targetpath) throws IOException { + if (operativeVersion < 3) + throw new SFTPException("SYMLINK is not supported in SFTPv" + operativeVersion); doRequest( newRequest(PacketType.SYMLINK).putString(linkpath).putString(targetpath) ).ensureStatusPacketIsOK(); @@ -185,13 +190,6 @@ public class SFTPEngine ).ensureStatusIs(Response.StatusCode.OK); } - private FileAttributes stat(PacketType pt, String path) - throws IOException { - return doRequest(newRequest(pt).putString(path)) - .ensurePacketTypeIs(PacketType.ATTRS) - .readFileAttributes(); - } - public FileAttributes stat(String path) throws IOException { return stat(PacketType.STAT, path); @@ -204,6 +202,8 @@ public class SFTPEngine public void rename(String oldPath, String newPath) throws IOException { + if (operativeVersion < 1) + throw new SFTPException("RENAME is not supported in SFTPv" + operativeVersion); doRequest( newRequest(PacketType.RENAME).putString(oldPath).putString(newPath) ).ensureStatusPacketIsOK(); @@ -217,15 +217,6 @@ public class SFTPEngine )); } - private static String readSingleName(Response res) - throws IOException { - res.ensurePacketTypeIs(PacketType.NAME); - if (res.readInt() == 1) - return res.readString(); - else - throw new SFTPException("Unexpected data in " + res.getType() + " packet"); - } - public void setTimeout(int timeout) { this.timeout = timeout; } @@ -240,4 +231,31 @@ public class SFTPEngine sub.close(); } + protected FileAttributes stat(PacketType pt, String path) + throws IOException { + return doRequest(newRequest(pt).putString(path)) + .ensurePacketTypeIs(PacketType.ATTRS) + .readFileAttributes(); + } + + protected static String readSingleName(Response res) + throws IOException { + res.ensurePacketTypeIs(PacketType.NAME); + if (res.readInt() == 1) + return res.readString(); + else + throw new SFTPException("Unexpected data in " + res.getType() + " packet"); + } + + protected synchronized void transmit(SFTPPacket payload) + throws IOException { + final int len = payload.available(); + out.write((len >>> 24) & 0xff); + out.write((len >>> 16) & 0xff); + out.write((len >>> 8) & 0xff); + out.write(len & 0xff); + out.write(payload.array(), payload.rpos(), len); + out.flush(); + } + } diff --git a/src/main/java/net/schmizz/sshj/sftp/SFTPException.java b/src/main/java/net/schmizz/sshj/sftp/SFTPException.java index a3ffc05e..b3b4caf2 100644 --- a/src/main/java/net/schmizz/sshj/sftp/SFTPException.java +++ b/src/main/java/net/schmizz/sshj/sftp/SFTPException.java @@ -67,7 +67,6 @@ public class SFTPException public StatusCode getStatusCode() { return (sc == null) ? StatusCode.UNKNOWN : sc; - } public SFTPException(StatusCode sc, String msg) { diff --git a/src/main/java/net/schmizz/sshj/sftp/SFTPFileTransfer.java b/src/main/java/net/schmizz/sshj/sftp/SFTPFileTransfer.java index 491e247c..b535317f 100644 --- a/src/main/java/net/schmizz/sshj/sftp/SFTPFileTransfer.java +++ b/src/main/java/net/schmizz/sshj/sftp/SFTPFileTransfer.java @@ -33,7 +33,7 @@ public class SFTPFileTransfer extends AbstractFileTransfer implements FileTransfer { - private final SFTPEngine sftp; + private final SFTPEngine engine; private final PathHelper pathHelper; private volatile FileFilter uploadFilter = defaultLocalFilter; @@ -53,9 +53,9 @@ public class SFTPFileTransfer } }; - public SFTPFileTransfer(SFTPEngine sftp) { - this.sftp = sftp; - this.pathHelper = new PathHelper(sftp); + public SFTPFileTransfer(SFTPEngine engine) { + this.engine = engine; + this.pathHelper = new PathHelper(engine); } @Override @@ -68,7 +68,7 @@ public class SFTPFileTransfer public void download(String source, String dest) throws IOException { final PathComponents pathComponents = pathHelper.getComponents(source); - final FileAttributes attributes = sftp.stat(source); + final FileAttributes attributes = engine.stat(source); new Downloader().download(new RemoteResourceInfo(pathComponents, attributes), new File(dest)); } @@ -119,7 +119,7 @@ public class SFTPFileTransfer private File downloadDir(final RemoteResourceInfo remote, final File local) throws IOException { final File adjusted = FileTransferUtil.getTargetDirectory(local, remote.getName()); - final RemoteDirectory rd = sftp.openDir(remote.getPath()); + final RemoteDirectory rd = engine.openDir(remote.getPath()); try { for (RemoteResourceInfo rri : rd.scan(getDownloadFilter())) download(rri, new File(adjusted.getPath(), rri.getName())); @@ -132,11 +132,11 @@ public class SFTPFileTransfer private File downloadFile(final RemoteResourceInfo remote, final File local) throws IOException { final File adjusted = FileTransferUtil.getTargetFile(local, remote.getName()); - final RemoteFile rf = sftp.open(remote.getPath()); + final RemoteFile rf = engine.open(remote.getPath()); try { final FileOutputStream fos = new FileOutputStream(adjusted); try { - StreamCopier.copy(rf.getInputStream(), fos, sftp.getSubsystem() + StreamCopier.copy(rf.getInputStream(), fos, engine.getSubsystem() .getLocalMaxPacketSize(), false, listener); } finally { fos.close(); @@ -176,7 +176,7 @@ public class SFTPFileTransfer listener.finishedFile(); } else throw new IOException(local + " is not a file or directory"); - sftp.setAttributes(adjustedPath, getAttributes(local)); + engine.setAttributes(adjustedPath, getAttributes(local)); } private String uploadDir(File local, String remote) @@ -190,13 +190,13 @@ public class SFTPFileTransfer private String uploadFile(File local, String remote) throws IOException { final String adjusted = prepareFile(local, remote); - final RemoteFile rf = sftp.open(adjusted, EnumSet.of(OpenMode.WRITE, - OpenMode.CREAT, - OpenMode.TRUNC)); + final RemoteFile rf = engine.open(adjusted, EnumSet.of(OpenMode.WRITE, + OpenMode.CREAT, + OpenMode.TRUNC)); try { final FileInputStream fis = new FileInputStream(local); try { - final int bufSize = sftp.getSubsystem().getRemoteMaxPacketSize() - rf.getOutgoingPacketOverhead(); + final int bufSize = engine.getSubsystem().getRemoteMaxPacketSize() - rf.getOutgoingPacketOverhead(); StreamCopier.copy(fis, rf.getOutputStream(), bufSize, false, listener); } finally { fis.close(); @@ -211,11 +211,11 @@ public class SFTPFileTransfer throws IOException { final FileAttributes attrs; try { - attrs = sftp.stat(remote); + attrs = engine.stat(remote); } catch (SFTPException e) { if (e.getStatusCode() == StatusCode.NO_SUCH_FILE) { log.debug("probeDir: {} does not exist, creating", remote); - sftp.makeDir(remote); + engine.makeDir(remote); return remote; } else throw e; @@ -237,7 +237,7 @@ public class SFTPFileTransfer throws IOException { final FileAttributes attrs; try { - attrs = sftp.stat(remote); + attrs = engine.stat(remote); } catch (SFTPException e) { if (e.getStatusCode() == StatusCode.NO_SUCH_FILE) { log.debug("probeFile: {} does not exist", remote); diff --git a/src/main/java/net/schmizz/sshj/sftp/StatefulSFTPClient.java b/src/main/java/net/schmizz/sshj/sftp/StatefulSFTPClient.java index be1123a4..f6507d8c 100644 --- a/src/main/java/net/schmizz/sshj/sftp/StatefulSFTPClient.java +++ b/src/main/java/net/schmizz/sshj/sftp/StatefulSFTPClient.java @@ -15,8 +15,6 @@ */ package net.schmizz.sshj.sftp; -import net.schmizz.sshj.connection.channel.direct.SessionFactory; - import java.io.IOException; import java.util.List; import java.util.Set; @@ -26,9 +24,9 @@ public class StatefulSFTPClient private String cwd; - public StatefulSFTPClient(SessionFactory ssh) + public StatefulSFTPClient(SFTPEngine engine) throws IOException { - super(ssh); + super(engine); this.cwd = getSFTPEngine().canonicalize("."); log.info("Start dir = " + cwd); }