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    }