001 /*
002 Copyright 2005 Mike Radin
003
004 This program is free software; you can redistribute it and/or modify
005 it under the terms of the GNU General Public License as published by
006 the Free Software Foundation; either version 1, or (at your option)
007 any later version.
008
009 This program is distributed in the hope that it will be useful,
010 but WITHOUT ANY WARRANTY; without even the implied warranty of
011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012 GNU General Public License for more details.
013
014 You should have received a copy of the GNU General Public License
015 along with this program; if not, write to the Free Software
016 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
017 */
018 package com.valhalla.jbother;
019
020 import java.io.InputStream;
021 import java.io.IOException;
022 import java.io.OutputStream;
023 import java.net.InetAddress;
024 import java.net.Socket;
025 import java.net.SocketException;
026 import javax.net.SocketFactory;
027
028 /**
029 *Allows connections to SOCKS4 and SOCKS5 proxy servers. This is not a generic implementation of either protocol, it was developed specifically for JBother and supports only a subset of features of the proxy protocols.
030 *
031 *@author Mike Radin - mike@projecttree.com
032 **/
033 public class ProxySocketFactory extends SocketFactory{
034 public static final int SOCKS4 = 1;
035 public static final int SOCKS5 = 2;
036
037 public static final int SOCKS5PASSWD = 2; // SOCKS5 password authentication
038
039 private String proxyhost;
040 private int proxyport;
041 private String proxyuser = "";
042 private String proxypasswd = "";
043 private int proxytype = SOCKS4;
044
045 public ProxySocketFactory(String host, int port){
046 proxyhost = host;
047 proxyport = port;
048 }
049 public ProxySocketFactory(String host, int port, int type, String user, String passwd){
050 proxyhost = host;
051 proxyport = port;
052 proxytype = type;
053 proxyuser = user;
054 proxypasswd = passwd;
055 }
056 public Socket createSocket(InetAddress host, int port) throws IOException{
057 return createProxySocket(host.getHostAddress(), port);
058 }
059 public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException{
060 return createProxySocket(address.getHostAddress(), port);
061 }
062 public Socket createSocket(String host, int port) throws IOException{
063 return createProxySocket(host, port);
064 }
065 public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException{
066 return createProxySocket(host, port);
067 }
068 private Socket createProxySocket(String host, int port) throws IOException{
069 Socket proxysocket = new Socket(proxyhost, proxyport);
070 proxysocket.setTcpNoDelay(true);
071
072 try{
073 InputStream is = proxysocket.getInputStream();
074 OutputStream os = proxysocket.getOutputStream();
075
076 if(proxytype == SOCKS4){
077 connectSOCKS4(is, os, host, port, proxyuser);
078 }else if(proxytype == SOCKS5){
079 int authtype = 0;
080 if(proxyuser != null && proxyuser.length() > 0){ authtype = querySOCKS5(is, os, new byte[] {0x0, 0x2}); }
081 else{ authtype = querySOCKS5(is, os, new byte[] {0x0}); }
082 if(authtype == SOCKS5PASSWD){ authenticateSOCKS5(is, os, authtype, proxyuser, proxypasswd); }
083 connectSOCKS5(is, os, host, port);
084 }else{
085 throw new IOException("ProxySocketFactory: unsupported proxy type");
086 }
087 }catch (SocketException e){
088 throw new IOException("Error communicating with SOCKS4 server " + proxyhost + ":" + proxyport + ", " + e.getMessage());
089 }catch(IOException e){
090 System.err.println(e);
091 throw e;
092 }
093 return proxysocket;
094 }
095 private void connectSOCKS4(InputStream is, OutputStream os, String host, int port, String user) throws IOException{
096 int i = ((user != null) ? user.length() : 0);
097 byte[] socks4msg = new byte[9 + i];
098 socks4msg[0] = 0x4;
099 socks4msg[1] = 0x1;
100 socks4msg[2] = (byte)(port >> 8);
101 socks4msg[3] = (byte)port;
102 System.arraycopy(InetAddress.getByName(host).getAddress(), 0, socks4msg, 4, 4);
103 if(user != null && user.length() > 0){ System.arraycopy(user.getBytes(), 0, socks4msg, 8, user.length()); }
104 socks4msg[8 + i] = (byte)0;
105
106 os.write(socks4msg);
107 os.flush();
108
109 byte[] socks4rep = new byte[8];
110 is.read(socks4rep, 0, 8);
111
112 if(socks4rep[0] != (byte)0){ throw new IOException("Invalid response from SOCKS4 server " + proxyhost + ":" + proxyport); }
113 if(socks4rep[1] != (byte)90){ throw new IOException("SOCKS4 server unable to connect, reason: " + socks4rep[1]); }
114
115 return;
116 }
117 private int querySOCKS5(InputStream is, OutputStream os, byte[] types) throws IOException{
118 byte[] socks5msg = new byte[2 + types.length];
119 socks5msg[0] = 0x5;
120 socks5msg[1] = (byte)types.length;
121 System.arraycopy(types, 0, socks5msg, 2, types.length);
122
123 os.write(socks5msg);
124 os.flush();
125
126 byte[] socks5rep = new byte[2];
127 is.read(socks5rep, 0, 2);
128
129 if(socks5rep[1] == 0xFF){ throw new IOException("SOCKS5: no acceptable authentication methods."); }
130
131 return (int)socks5rep[1];
132 }
133 private void authenticateSOCKS5(InputStream is, OutputStream os, int type, String user, String passwd) throws IOException{
134 int ulen = ((user != null) ? user.length(): 0);
135 int plen = ((passwd != null) ? passwd.length(): 0);
136 byte[] socks5msg = new byte[3 + ulen + plen];
137 socks5msg[0] = 0x1;
138 socks5msg[1] = (byte)ulen;
139 if(ulen > 0){ System.arraycopy(user.getBytes(), 0, socks5msg, 2, ulen); }
140 socks5msg[2 + ulen] = (byte)plen;
141 if(plen > 0){ System.arraycopy(passwd.getBytes(), 0, socks5msg, 3 + ulen, plen);}
142
143 os.write(socks5msg);
144 os.flush();
145
146 byte[] socks5rep = new byte[2];
147 is.read(socks5rep, 0, 2);
148
149 if(socks5rep[1] != 0x0){ throw new IOException("SOCKS5 authentication failed, reason: " + socks5rep[1]); }
150
151 return;
152 }
153 private void connectSOCKS5(InputStream is, OutputStream os, String host, int port) throws IOException{
154 byte[] socks5msg = new byte[10];
155 socks5msg[0] = 0x5;
156 socks5msg[1] = 0x1;
157 socks5msg[2] = 0x0;
158 socks5msg[3] = 0x1;
159 System.arraycopy(InetAddress.getByName(host).getAddress(), 0, socks5msg, 4, 4);
160 socks5msg[8] = (byte)(port >> 8);
161 socks5msg[9] = (byte)port;
162
163 os.write(socks5msg);
164 os.flush();
165
166 byte[] socks5rep = new byte[10];
167 is.read(socks5rep, 0, 10);
168
169 if(socks5rep[1] != 0x0){ throw new IOException("SOCKS5 server unable to connect, reason: " + socks5rep[1]); }
170
171 return;
172 }
173 }