aboutsummaryrefslogtreecommitdiff
path: root/src/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java')
-rw-r--r--src/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java204
1 files changed, 204 insertions, 0 deletions
diff --git a/src/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java b/src/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java
new file mode 100644
index 0000000..664ea59
--- /dev/null
+++ b/src/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java
@@ -0,0 +1,204 @@
+/**
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smackx.bytestreams.socks5;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost;
+
+/**
+ * The SOCKS5 client class handles establishing a connection to a SOCKS5 proxy. Connecting to a
+ * SOCKS5 proxy requires authentication. This implementation only supports the no-authentication
+ * authentication method.
+ *
+ * @author Henning Staib
+ */
+class Socks5Client {
+
+ /* stream host containing network settings and name of the SOCKS5 proxy */
+ protected StreamHost streamHost;
+
+ /* SHA-1 digest identifying the SOCKS5 stream */
+ protected String digest;
+
+ /**
+ * Constructor for a SOCKS5 client.
+ *
+ * @param streamHost containing network settings of the SOCKS5 proxy
+ * @param digest identifying the SOCKS5 Bytestream
+ */
+ public Socks5Client(StreamHost streamHost, String digest) {
+ this.streamHost = streamHost;
+ this.digest = digest;
+ }
+
+ /**
+ * Returns the initialized socket that can be used to transfer data between peers via the SOCKS5
+ * proxy.
+ *
+ * @param timeout timeout to connect to SOCKS5 proxy in milliseconds
+ * @return socket the initialized socket
+ * @throws IOException if initializing the socket failed due to a network error
+ * @throws XMPPException if establishing connection to SOCKS5 proxy failed
+ * @throws TimeoutException if connecting to SOCKS5 proxy timed out
+ * @throws InterruptedException if the current thread was interrupted while waiting
+ */
+ public Socket getSocket(int timeout) throws IOException, XMPPException, InterruptedException,
+ TimeoutException {
+
+ // wrap connecting in future for timeout
+ FutureTask<Socket> futureTask = new FutureTask<Socket>(new Callable<Socket>() {
+
+ public Socket call() throws Exception {
+
+ // initialize socket
+ Socket socket = new Socket();
+ SocketAddress socketAddress = new InetSocketAddress(streamHost.getAddress(),
+ streamHost.getPort());
+ socket.connect(socketAddress);
+
+ // initialize connection to SOCKS5 proxy
+ if (!establish(socket)) {
+
+ // initialization failed, close socket
+ socket.close();
+ throw new XMPPException("establishing connection to SOCKS5 proxy failed");
+
+ }
+
+ return socket;
+ }
+
+ });
+ Thread executor = new Thread(futureTask);
+ executor.start();
+
+ // get connection to initiator with timeout
+ try {
+ return futureTask.get(timeout, TimeUnit.MILLISECONDS);
+ }
+ catch (ExecutionException e) {
+ Throwable cause = e.getCause();
+ if (cause != null) {
+ // case exceptions to comply with method signature
+ if (cause instanceof IOException) {
+ throw (IOException) cause;
+ }
+ if (cause instanceof XMPPException) {
+ throw (XMPPException) cause;
+ }
+ }
+
+ // throw generic IO exception if unexpected exception was thrown
+ throw new IOException("Error while connection to SOCKS5 proxy");
+ }
+
+ }
+
+ /**
+ * Initializes the connection to the SOCKS5 proxy by negotiating authentication method and
+ * requesting a stream for the given digest. Currently only the no-authentication method is
+ * supported by the Socks5Client.
+ * <p>
+ * Returns <code>true</code> if a stream could be established, otherwise <code>false</code>. If
+ * <code>false</code> is returned the given Socket should be closed.
+ *
+ * @param socket connected to a SOCKS5 proxy
+ * @return <code>true</code> if if a stream could be established, otherwise <code>false</code>.
+ * If <code>false</code> is returned the given Socket should be closed.
+ * @throws IOException if a network error occurred
+ */
+ protected boolean establish(Socket socket) throws IOException {
+
+ /*
+ * use DataInputStream/DataOutpuStream to assure read and write is completed in a single
+ * statement
+ */
+ DataInputStream in = new DataInputStream(socket.getInputStream());
+ DataOutputStream out = new DataOutputStream(socket.getOutputStream());
+
+ // authentication negotiation
+ byte[] cmd = new byte[3];
+
+ cmd[0] = (byte) 0x05; // protocol version 5
+ cmd[1] = (byte) 0x01; // number of authentication methods supported
+ cmd[2] = (byte) 0x00; // authentication method: no-authentication required
+
+ out.write(cmd);
+ out.flush();
+
+ byte[] response = new byte[2];
+ in.readFully(response);
+
+ // check if server responded with correct version and no-authentication method
+ if (response[0] != (byte) 0x05 || response[1] != (byte) 0x00) {
+ return false;
+ }
+
+ // request SOCKS5 connection with given address/digest
+ byte[] connectionRequest = createSocks5ConnectRequest();
+ out.write(connectionRequest);
+ out.flush();
+
+ // receive response
+ byte[] connectionResponse;
+ try {
+ connectionResponse = Socks5Utils.receiveSocks5Message(in);
+ }
+ catch (XMPPException e) {
+ return false; // server answered in an unsupported way
+ }
+
+ // verify response
+ connectionRequest[1] = (byte) 0x00; // set expected return status to 0
+ return Arrays.equals(connectionRequest, connectionResponse);
+ }
+
+ /**
+ * Returns a SOCKS5 connection request message. It contains the command "connect", the address
+ * type "domain" and the digest as address.
+ * <p>
+ * (see <a href="http://tools.ietf.org/html/rfc1928">RFC1928</a>)
+ *
+ * @return SOCKS5 connection request message
+ */
+ private byte[] createSocks5ConnectRequest() {
+ byte addr[] = this.digest.getBytes();
+
+ byte[] data = new byte[7 + addr.length];
+ data[0] = (byte) 0x05; // version (SOCKS5)
+ data[1] = (byte) 0x01; // command (1 - connect)
+ data[2] = (byte) 0x00; // reserved byte (always 0)
+ data[3] = (byte) 0x03; // address type (3 - domain name)
+ data[4] = (byte) addr.length; // address length
+ System.arraycopy(addr, 0, data, 5, addr.length); // address
+ data[data.length - 2] = (byte) 0; // address port (2 bytes always 0)
+ data[data.length - 1] = (byte) 0;
+
+ return data;
+ }
+
+}