diff options
Diffstat (limited to 'src/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSession.java')
-rw-r--r-- | src/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSession.java | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/src/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSession.java b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSession.java new file mode 100644 index 0000000..25aadc4 --- /dev/null +++ b/src/com/isode/stroke/filetransfer/SOCKS5BytestreamClientSession.java @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.filetransfer; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.base.SafeByteArray; +import com.isode.stroke.network.Connection; +import com.isode.stroke.network.HostAddressPort; +import com.isode.stroke.network.Timer; +import com.isode.stroke.network.TimerFactory; +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.signals.Slot1; +import com.isode.stroke.signals.Signal1; +import com.isode.stroke.signals.Slot; +import com.isode.stroke.stringcodecs.Hexify; +import java.util.logging.Logger; + +/** + * A session which has been connected to a SOCKS5 server (requester). + * + */ +public class SOCKS5BytestreamClientSession { + + public enum State { + Initial(0), + Hello(1), + Authenticating(2), + Ready(3), + Writing(4), + Reading(5), + Finished(6); + private State(int x) { + description = x; + } + public int description; + }; + + private Connection connection; + private HostAddressPort addressPort; + private String destination; // hexify(SHA1(sessionID + requester + target)) + + private State state; + + private ByteArray unprocessedData; + private ByteArray authenticateAddress; + + private int chunkSize; + private WriteBytestream writeBytestream; + private ReadBytestream readBytestream; + + private Timer weFailedTimeout; + + private SignalConnection connectFinishedConnection; + private SignalConnection dataWrittenConnection; + private SignalConnection dataReadConnection; + private SignalConnection disconnectedConnection; + private Logger logger_ = Logger.getLogger(this.getClass().getName()); + + public SOCKS5BytestreamClientSession(Connection connection, final HostAddressPort addressPort, final String destination, TimerFactory timerFactory) { + this.connection = connection; + this.addressPort = addressPort; + this.destination = destination; + this.state = State.Initial; + this.chunkSize = 131072; + weFailedTimeout = timerFactory.createTimer(3000); + weFailedTimeout.onTick.connect(new Slot() { + @Override + public void call() { + handleWeFailedTimeout(); + } + }); + } + + public void start() { + assert(state == State.Initial); + logger_.fine("Trying to connect via TCP to " + addressPort.toString() + ".\n"); + weFailedTimeout.start(); + connectFinishedConnection = connection.onConnectFinished.connect(new Slot1<Boolean>() { + @Override + public void call(Boolean b) { + handleConnectFinished(b); + } + }); + connection.connect(addressPort); + } + + public void stop() { + logger_.fine("\n"); + if (state.description < State.Ready.description) { + weFailedTimeout.stop(); + } + if (state == State.Finished) { + return; + } + closeConnection(); + readBytestream = null; + state = State.Finished; + } + + public void startReceiving(WriteBytestream writeStream) { + if (state == State.Ready) { + state = State.Reading; + writeBytestream = writeStream; + writeBytestream.write(unprocessedData); + //onBytesReceived(unprocessedData.size()); + unprocessedData.clear(); + } else { + logger_.fine("Session isn't ready for transfer yet!\n"); + } + } + + public void startSending(ReadBytestream readStream) { + if (state == State.Ready) { + state = State.Writing; + readBytestream = readStream; + dataWrittenConnection = connection.onDataWritten.connect(new Slot() { + @Override + public void call() { + sendData(); + } + }); + sendData(); + } else { + logger_.fine("Session isn't ready for transfer yet!\n"); + } + } + + public HostAddressPort getAddressPort() { + return addressPort; + } + + public Signal1<Boolean /*error*/> onSessionReady = new Signal1<Boolean>(); + + public Signal1<FileTransferError> onFinished = new Signal1<FileTransferError>(); + public Signal1<Integer> onBytesSent = new Signal1<Integer>(); + //public boost::signal<void (size_t)> onBytesReceived; + + private void process() { + logger_.fine("unprocessedData.size(): " + unprocessedData.getSize() + "\n"); + ByteArray bndAddress = new ByteArray(); + switch(state) { + case Initial: + hello(); + break; + case Hello: + if (unprocessedData.getSize() > 1) { + char version = (char)unprocessedData.getData()[0]; + char authMethod = (char)unprocessedData.getData()[1]; + if (version != 5 || authMethod != 0) { + // signal failure to upper level + finish(true); + return; + } + unprocessedData.clear(); + authenticate(); + } + break; + case Authenticating: + if (unprocessedData.getSize() < 5) { + // need more data to start progressing + break; + } + if (unprocessedData.getData()[0] != (byte)0x05) { + // wrong version + // disconnect & signal failure + finish(true); + break; + } + if (unprocessedData.getData()[1] != (byte)0x00) { + // no success + // disconnect & signal failure + finish(true); + break; + } + if (unprocessedData.getData()[3] != (byte)0x03) { + // we expect x'03' = DOMAINNAME here + // disconnect & signal failure + finish(true); + break; + } + if (((int)unprocessedData.getData()[4]) + 1 > unprocessedData.getSize() + 5) { + // complete domainname and port not available yet + break; + } + //-----bndAddress = new ByteArray(&vecptr(unprocessedData)[5], unprocessedData[4]); + if (unprocessedData.getData()[unprocessedData.getData()[4] + 5] != 0 && new ByteArray(destination).equals(bndAddress)) { + // we expect a 0 as port + // disconnect and fail + finish(true); + } + unprocessedData.clear(); + state = State.Ready; + logger_.fine("session ready\n"); + // issue ready signal so the bytestream can be used for reading or writing + weFailedTimeout.stop(); + onSessionReady.emit(false); + break; + case Ready: + logger_.fine("Received further data in Ready state.\n"); + break; + case Reading: + case Writing: + case Finished: + logger_.fine("Unexpected receive of data. Current state: " + state + "\n"); + logger_.fine("Data: " + Hexify.hexify(unprocessedData) + "\n"); + unprocessedData.clear(); + //assert(false); + } + } + + private void hello() { + // Version 5, 1 auth method, No authentication + final SafeByteArray hello = new SafeByteArray(new byte[]{0x05, 0x01, 0x00}); + connection.write(hello); + state = State.Hello; + } + + private void authenticate() { + logger_.fine("\n"); + SafeByteArray header = new SafeByteArray(new byte[]{0x05, 0x01, 0x00, 0x03}); + SafeByteArray message = header; + String destinationlength = Integer.toString(destination.length()); + message.append(new SafeByteArray(destinationlength)); + authenticateAddress = new ByteArray(destination); + message.append(authenticateAddress); + message.append(new SafeByteArray(new byte[]{0x00, 0x00})); // 2 byte for port + connection.write(message); + state = State.Authenticating; + } + + private void handleConnectFinished(boolean error) { + connectFinishedConnection.disconnect(); + if (error) { + logger_.fine("Failed to connect via TCP to " + addressPort.toString() + "." + "\n"); + finish(true); + } else { + logger_.fine("Successfully connected via TCP" + addressPort.toString() + "." + "\n"); + disconnectedConnection = connection.onDisconnected.connect(new Slot1<Connection.Error>() { + @Override + public void call(Connection.Error e) { + handleDisconnected(e); + } + }); + dataReadConnection = connection.onDataRead.connect(new Slot1<SafeByteArray>() { + @Override + public void call(SafeByteArray b) { + handleDataRead(b); + } + }); + weFailedTimeout.stop(); + weFailedTimeout.start(); + process(); + } + } + + private void handleDataRead(SafeByteArray data) { + logger_.fine("state: " + state + " data.size() = " + data.getSize() + "\n"); + if (state != State.Reading) { + unprocessedData.append(data); + process(); + } + else { + //---------writeBytestream.write(new ByteArray(vecptr(*data), data.size())); + //onBytesReceived(data.size()); + } + } + + private void handleDisconnected(final Connection.Error error) { + logger_.fine((error != null ? (error == Connection.Error.ReadError ? "Read Error" : "Write Error") : "No Error") + "\n"); + if (error != null) { + finish(true); + } + } + + private void handleWeFailedTimeout() { + logger_.fine("Failed due to timeout!\n"); + finish(true); + } + + private void finish(boolean error) { + logger_.fine("\n"); + if (state.description < State.Ready.description) { + weFailedTimeout.stop(); } + closeConnection(); + readBytestream = null; + if (State.Initial.equals(state) || State.Hello.equals(state) || State.Authenticating.equals(state)) { + onSessionReady.emit(true); + } + else { + state = State.Finished; + if (error) { + onFinished.emit(new FileTransferError(FileTransferError.Type.ReadError)); + } else { + onFinished.emit(null); + } + } + } + + private void sendData() { + if (!readBytestream.isFinished()) { + //try { + ByteArray dataToSend = readBytestream.read((int)(chunkSize)); + connection.write(new SafeByteArray(dataToSend)); + onBytesSent.emit(dataToSend.getSize()); + //} + //catch (BytestreamException e) { + // finish(true); + //} + } + else { + finish(false); + } + } + + private void closeConnection() { + connectFinishedConnection.disconnect(); + dataWrittenConnection.disconnect(); + dataReadConnection.disconnect(); + disconnectedConnection.disconnect(); + connection.disconnect(); + } +}
\ No newline at end of file |