aboutsummaryrefslogtreecommitdiff
path: root/src/org/jivesoftware/smack/proxy/Socks5ProxySocketFactory.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/jivesoftware/smack/proxy/Socks5ProxySocketFactory.java')
-rw-r--r--src/org/jivesoftware/smack/proxy/Socks5ProxySocketFactory.java375
1 files changed, 375 insertions, 0 deletions
diff --git a/src/org/jivesoftware/smack/proxy/Socks5ProxySocketFactory.java b/src/org/jivesoftware/smack/proxy/Socks5ProxySocketFactory.java
new file mode 100644
index 0000000..23ef623
--- /dev/null
+++ b/src/org/jivesoftware/smack/proxy/Socks5ProxySocketFactory.java
@@ -0,0 +1,375 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * 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.smack.proxy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import javax.net.SocketFactory;
+
+/**
+ * Socket factory for Socks5 proxy
+ *
+ * @author Atul Aggarwal
+ */
+public class Socks5ProxySocketFactory
+ extends SocketFactory
+{
+ private ProxyInfo proxy;
+
+ public Socks5ProxySocketFactory(ProxyInfo proxy)
+ {
+ this.proxy = proxy;
+ }
+
+ public Socket createSocket(String host, int port)
+ throws IOException, UnknownHostException
+ {
+ return socks5ProxifiedSocket(host,port);
+ }
+
+ public Socket createSocket(String host ,int port, InetAddress localHost,
+ int localPort)
+ throws IOException, UnknownHostException
+ {
+
+ return socks5ProxifiedSocket(host,port);
+
+ }
+
+ public Socket createSocket(InetAddress host, int port)
+ throws IOException
+ {
+
+ return socks5ProxifiedSocket(host.getHostAddress(),port);
+
+ }
+
+ public Socket createSocket( InetAddress address, int port,
+ InetAddress localAddress, int localPort)
+ throws IOException
+ {
+
+ return socks5ProxifiedSocket(address.getHostAddress(),port);
+
+ }
+
+ private Socket socks5ProxifiedSocket(String host, int port)
+ throws IOException
+ {
+ Socket socket = null;
+ InputStream in = null;
+ OutputStream out = null;
+ String proxy_host = proxy.getProxyAddress();
+ int proxy_port = proxy.getProxyPort();
+ String user = proxy.getProxyUsername();
+ String passwd = proxy.getProxyPassword();
+
+ try
+ {
+ socket=new Socket(proxy_host, proxy_port);
+ in=socket.getInputStream();
+ out=socket.getOutputStream();
+
+ socket.setTcpNoDelay(true);
+
+ byte[] buf=new byte[1024];
+ int index=0;
+
+/*
+ +----+----------+----------+
+ |VER | NMETHODS | METHODS |
+ +----+----------+----------+
+ | 1 | 1 | 1 to 255 |
+ +----+----------+----------+
+
+ The VER field is set to X'05' for this version of the protocol. The
+ NMETHODS field contains the number of method identifier octets that
+ appear in the METHODS field.
+
+ The values currently defined for METHOD are:
+
+ o X'00' NO AUTHENTICATION REQUIRED
+ o X'01' GSSAPI
+ o X'02' USERNAME/PASSWORD
+ o X'03' to X'7F' IANA ASSIGNED
+ o X'80' to X'FE' RESERVED FOR PRIVATE METHODS
+ o X'FF' NO ACCEPTABLE METHODS
+*/
+
+ buf[index++]=5;
+
+ buf[index++]=2;
+ buf[index++]=0; // NO AUTHENTICATION REQUIRED
+ buf[index++]=2; // USERNAME/PASSWORD
+
+ out.write(buf, 0, index);
+
+/*
+ The server selects from one of the methods given in METHODS, and
+ sends a METHOD selection message:
+
+ +----+--------+
+ |VER | METHOD |
+ +----+--------+
+ | 1 | 1 |
+ +----+--------+
+*/
+ //in.read(buf, 0, 2);
+ fill(in, buf, 2);
+
+ boolean check=false;
+ switch((buf[1])&0xff)
+ {
+ case 0: // NO AUTHENTICATION REQUIRED
+ check=true;
+ break;
+ case 2: // USERNAME/PASSWORD
+ if(user==null || passwd==null)
+ {
+ break;
+ }
+
+/*
+ Once the SOCKS V5 server has started, and the client has selected the
+ Username/Password Authentication protocol, the Username/Password
+ subnegotiation begins. This begins with the client producing a
+ Username/Password request:
+
+ +----+------+----------+------+----------+
+ |VER | ULEN | UNAME | PLEN | PASSWD |
+ +----+------+----------+------+----------+
+ | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
+ +----+------+----------+------+----------+
+
+ The VER field contains the current version of the subnegotiation,
+ which is X'01'. The ULEN field contains the length of the UNAME field
+ that follows. The UNAME field contains the username as known to the
+ source operating system. The PLEN field contains the length of the
+ PASSWD field that follows. The PASSWD field contains the password
+ association with the given UNAME.
+*/
+ index=0;
+ buf[index++]=1;
+ buf[index++]=(byte)(user.length());
+ System.arraycopy(user.getBytes(), 0, buf, index,
+ user.length());
+ index+=user.length();
+ buf[index++]=(byte)(passwd.length());
+ System.arraycopy(passwd.getBytes(), 0, buf, index,
+ passwd.length());
+ index+=passwd.length();
+
+ out.write(buf, 0, index);
+
+/*
+ The server verifies the supplied UNAME and PASSWD, and sends the
+ following response:
+
+ +----+--------+
+ |VER | STATUS |
+ +----+--------+
+ | 1 | 1 |
+ +----+--------+
+
+ A STATUS field of X'00' indicates success. If the server returns a
+ `failure' (STATUS value other than X'00') status, it MUST close the
+ connection.
+*/
+ //in.read(buf, 0, 2);
+ fill(in, buf, 2);
+ if(buf[1]==0)
+ {
+ check=true;
+ }
+ break;
+ default:
+ }
+
+ if(!check)
+ {
+ try
+ {
+ socket.close();
+ }
+ catch(Exception eee)
+ {
+ }
+ throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,
+ "fail in SOCKS5 proxy");
+ }
+
+/*
+ The SOCKS request is formed as follows:
+
+ +----+-----+-------+------+----------+----------+
+ |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+ +----+-----+-------+------+----------+----------+
+ | 1 | 1 | X'00' | 1 | Variable | 2 |
+ +----+-----+-------+------+----------+----------+
+
+ Where:
+
+ o VER protocol version: X'05'
+ o CMD
+ o CONNECT X'01'
+ o BIND X'02'
+ o UDP ASSOCIATE X'03'
+ o RSV RESERVED
+ o ATYP address type of following address
+ o IP V4 address: X'01'
+ o DOMAINNAME: X'03'
+ o IP V6 address: X'04'
+ o DST.ADDR desired destination address
+ o DST.PORT desired destination port in network octet
+ order
+*/
+
+ index=0;
+ buf[index++]=5;
+ buf[index++]=1; // CONNECT
+ buf[index++]=0;
+
+ byte[] hostb=host.getBytes();
+ int len=hostb.length;
+ buf[index++]=3; // DOMAINNAME
+ buf[index++]=(byte)(len);
+ System.arraycopy(hostb, 0, buf, index, len);
+ index+=len;
+ buf[index++]=(byte)(port>>>8);
+ buf[index++]=(byte)(port&0xff);
+
+ out.write(buf, 0, index);
+
+/*
+ The SOCKS request information is sent by the client as soon as it has
+ established a connection to the SOCKS server, and completed the
+ authentication negotiations. The server evaluates the request, and
+ returns a reply formed as follows:
+
+ +----+-----+-------+------+----------+----------+
+ |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+ +----+-----+-------+------+----------+----------+
+ | 1 | 1 | X'00' | 1 | Variable | 2 |
+ +----+-----+-------+------+----------+----------+
+
+ Where:
+
+ o VER protocol version: X'05'
+ o REP Reply field:
+ o X'00' succeeded
+ o X'01' general SOCKS server failure
+ o X'02' connection not allowed by ruleset
+ o X'03' Network unreachable
+ o X'04' Host unreachable
+ o X'05' Connection refused
+ o X'06' TTL expired
+ o X'07' Command not supported
+ o X'08' Address type not supported
+ o X'09' to X'FF' unassigned
+ o RSV RESERVED
+ o ATYP address type of following address
+ o IP V4 address: X'01'
+ o DOMAINNAME: X'03'
+ o IP V6 address: X'04'
+ o BND.ADDR server bound address
+ o BND.PORT server bound port in network octet order
+*/
+
+ //in.read(buf, 0, 4);
+ fill(in, buf, 4);
+
+ if(buf[1]!=0)
+ {
+ try
+ {
+ socket.close();
+ }
+ catch(Exception eee)
+ {
+ }
+ throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,
+ "server returns "+buf[1]);
+ }
+
+ switch(buf[3]&0xff)
+ {
+ case 1:
+ //in.read(buf, 0, 6);
+ fill(in, buf, 6);
+ break;
+ case 3:
+ //in.read(buf, 0, 1);
+ fill(in, buf, 1);
+ //in.read(buf, 0, buf[0]+2);
+ fill(in, buf, (buf[0]&0xff)+2);
+ break;
+ case 4:
+ //in.read(buf, 0, 18);
+ fill(in, buf, 18);
+ break;
+ default:
+ }
+ return socket;
+
+ }
+ catch(RuntimeException e)
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ try
+ {
+ if(socket!=null)
+ {
+ socket.close();
+ }
+ }
+ catch(Exception eee)
+ {
+ }
+ String message="ProxySOCKS5: "+e.toString();
+ if(e instanceof Throwable)
+ {
+ throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,message,
+ (Throwable)e);
+ }
+ throw new IOException(message);
+ }
+ }
+
+ private void fill(InputStream in, byte[] buf, int len)
+ throws IOException
+ {
+ int s=0;
+ while(s<len)
+ {
+ int i=in.read(buf, s, len-s);
+ if(i<=0)
+ {
+ throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, "stream " +
+ "is closed");
+ }
+ s+=i;
+ }
+ }
+}