/* * @(#)SocksClient.java 0.3-3 06/05/2001 * * This file is part of the HTTPClient package * Copyright (C) 1996-2001 Ronald Tschalär * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307, USA * * For questions, suggestions, bug-reports, enhancement-requests etc. * I may be contacted at: * * ronald@innovation.ch * * The HTTPClient's home page is located at: * * http://www.innovation.ch/java/HTTPClient/ * */ package HTTPClient; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.ByteArrayOutputStream; import java.net.Socket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; /** * This class implements a SOCKS Client. Supports both versions 4 and 5. * GSSAPI however is not yet implemented. *

Usage is as follows: somewhere in the initialization code (and before * the first socket creation call) create a SocksClient instance. Then replace * each socket creation call * * sock = new Socket(host, port); * * with * * sock = socks_client.getSocket(host, port); * * (where socks_client is the above created SocksClient instance). * That's all. * * @version 0.3-3 06/05/2001 * @author Ronald Tschalär */ class SocksClient { /** the host the socks server sits on */ private String socks_host; /** the port the socks server listens on */ private int socks_port; /** the version of socks that the server handles */ private int socks_version; /** socks commands */ private final static byte CONNECT = 1, BIND = 2, UDP_ASS = 3; /** socks version 5 authentication methods */ private final static byte NO_AUTH = 0, GSSAPI = 1, USERPWD = 2, NO_ACC = (byte) 0xFF; /** socks version 5 address types */ private final static byte IP_V4 = 1, DMNAME = 3, IP_V6 = 4; // Constructors /** * Creates a new SOCKS Client using the specified host and port for * the server. Will try to establish the SOCKS version used when * establishing the first connection. * * @param host the host the SOCKS server is sitting on. * @param port the port the SOCKS server is listening on. */ SocksClient(String host, int port) { this.socks_host = host; this.socks_port = port; this.socks_version = -1; // as yet unknown } /** * Creates a new SOCKS Client using the specified host and port for * the server. * * @param host the host the SOCKS server is sitting on. * @param port the port the SOCKS server is listening on. * @param version the version the SOCKS server is using. * @exception SocksException if the version is invalid (Currently allowed * are: 4 and 5). */ SocksClient(String host, int port, int version) throws SocksException { this.socks_host = host; this.socks_port = port; if (version != 4 && version != 5) throw new SocksException("SOCKS Version not supported: "+version); this.socks_version = version; } // Methods /** * Initiates a connection to the socks server, does the startup * protocol and returns a socket ready for talking. * * @param host the host you wish to connect to * @param port the port you wish to connect to * @return a Socket with a connection via socks to the desired host/port * @exception IOException if any socket operation fails */ Socket getSocket(String host, int port) throws IOException { return getSocket(host, port, null, -1); } /** * Initiates a connection to the socks server, does the startup * protocol and returns a socket ready for talking. * * @param host the host you wish to connect to * @param port the port you wish to connect to * @param localAddr the local address to bind to * @param localPort the local port to bind to * @return a Socket with a connection via socks to the desired host/port * @exception IOException if any socket operation fails */ Socket getSocket(String host, int port, InetAddress localAddr, int localPort) throws IOException { Socket sock = null; try { Log.write(Log.SOCKS, "Socks: contacting server on " + socks_host + ":" + socks_port); // create socket and streams sock = connect(socks_host, socks_port, localAddr, localPort); InputStream inp = sock.getInputStream(); OutputStream out = sock.getOutputStream(); // setup connection depending on socks version switch (socks_version) { case 4: v4ProtExchg(inp, out, host, port); break; case 5: v5ProtExchg(inp, out, host, port); break; case -1: // Ok, let's try and figure it out try { v4ProtExchg(inp, out, host, port); socks_version = 4; } catch (SocksException se) { Log.write(Log.SOCKS, "Socks: V4 request failed: " + se.getMessage()); sock.close(); sock = connect(socks_host, socks_port, localAddr, localPort); inp = sock.getInputStream(); out = sock.getOutputStream(); v5ProtExchg(inp, out, host, port); socks_version = 5; } break; default: throw new Error("SocksClient internal error: unknown " + "version "+socks_version); } Log.write(Log.SOCKS, "Socks: connection established."); return sock; } catch (IOException ioe) { if (sock != null) { try { sock.close(); } catch (IOException ee) {} } throw ioe; } } /** * Connect to the host/port, trying all addresses assciated with that * host. * * @param host the host you wish to connect to * @param port the port you wish to connect to * @param localAddr the local address to bind to * @param localPort the local port to bind to * @return the Socket * @exception IOException if the connection could not be established */ private static final Socket connect(String host, int port, InetAddress localAddr, int localPort) throws IOException { InetAddress[] addr_list = InetAddress.getAllByName(host); for (int idx=0; idx> 8) & 0xff); // port buffer.write(port & 0xff); buffer.write(addr); // address buffer.write(user); // user if (v4A) { buffer.write(host.getBytes("8859_1")); // host name buffer.write(0); // terminating 0 } buffer.writeTo(out); // read response int version = inp.read(); if (version == -1) throw new SocksException("Connection refused by server"); else if (version == 4) // not all socks4 servers are correct... Log.write(Log.SOCKS, "Socks: Warning: received version 4 " + "instead of 0"); else if (version != 0) throw new SocksException("Received invalid version: " + version + "; expected: 0"); int sts = inp.read(); Log.write(Log.SOCKS, "Socks: Received response; version: " + version + "; status: " + sts); switch (sts) { case 90: // request granted break; case 91: // request rejected throw new SocksException("Connection request rejected"); case 92: // request rejected: can't connect to identd throw new SocksException("Connection request rejected: " + "can't connect to identd"); case 93: // request rejected: identd reports diff uid throw new SocksException("Connection request rejected: " + "identd reports different user-id " + "from "+ new String(user, 0, user.length-1)); default: // unknown status throw new SocksException("Connection request rejected: " + "unknown error " + sts); } byte[] skip = new byte[2+4]; // skip port + address int rcvd = 0, tot = 0; while (tot < skip.length && (rcvd = inp.read(skip, 0, skip.length-tot)) != -1) tot += rcvd; } /** * Does the protocol exchange for a version 5 SOCKS connection. * (rfc-1928) */ private void v5ProtExchg(InputStream inp, OutputStream out, String host, int port) throws SocksException, IOException { int version; ByteArrayOutputStream buffer = new ByteArrayOutputStream(100); Log.write(Log.SOCKS, "Socks: Beginning V5 Protocol Exchange for host " + host + ":" + port); // send version 5 verification methods Log.write(Log.SOCKS, "Socks: Sending authentication request; methods" + " No-Authentication, Username/Password"); buffer.reset(); buffer.write(5); // version buffer.write(2); // number of verification methods buffer.write(NO_AUTH); // method: no authentication buffer.write(USERPWD); // method: username/password //buffer.write(GSSAPI); // method: gssapi buffer.writeTo(out); // receive servers repsonse version = inp.read(); if (version == -1) throw new SocksException("Connection refused by server"); else if (version != 5) throw new SocksException("Received invalid version: " + version + "; expected: 5"); int method = inp.read(); Log.write(Log.SOCKS, "Socks: Received response; version: " + version + "; method: " + method); // enter sub-negotiation for authentication switch(method) { case NO_AUTH: break; case GSSAPI: negotiate_gssapi(inp, out); break; case USERPWD: negotiate_userpwd(inp, out); break; case NO_ACC: throw new SocksException("Server unwilling to accept any " + "standard authentication methods"); default: throw new SocksException("Cannot handle authentication method " + method); } // send version 5 request Log.write(Log.SOCKS, "Socks: Sending connect request"); buffer.reset(); buffer.write(5); // version buffer.write(CONNECT); // command buffer.write(0); // reserved - must be 0 buffer.write(DMNAME); // address type buffer.write(host.length() & 0xff); // address length buffer.write(host.getBytes("8859_1")); // address buffer.write((port >> 8) & 0xff); // port buffer.write(port & 0xff); buffer.writeTo(out); // read response version = inp.read(); if (version != 5) throw new SocksException("Received invalid version: " + version + "; expected: 5"); int sts = inp.read(); Log.write(Log.SOCKS, "Socks: Received response; version: " + version + "; status: " + sts); switch (sts) { case 0: // succeeded break; case 1: throw new SocksException("General SOCKS server failure"); case 2: throw new SocksException("Connection not allowed"); case 3: throw new SocksException("Network unreachable"); case 4: throw new SocksException("Host unreachable"); case 5: throw new SocksException("Connection refused"); case 6: throw new SocksException("TTL expired"); case 7: throw new SocksException("Command not supported"); case 8: throw new SocksException("Address type not supported"); default: throw new SocksException("Unknown reply received from server: " + sts); } inp.read(); // Reserved int atype = inp.read(), // address type alen; // address length switch(atype) { case IP_V6: alen = 16; break; case IP_V4: alen = 4; break; case DMNAME: alen = inp.read(); break; default: throw new SocksException("Invalid address type received from" + " server: "+atype); } byte[] skip = new byte[alen+2]; // skip address + port int rcvd = 0, tot = 0; while (tot < skip.length && (rcvd = inp.read(skip, 0, skip.length-tot)) != -1) tot += rcvd; } /** * Negotiates authentication using the gssapi protocol * (draft-ietf-aft-gssapi-02). * * NOTE: this is not implemented currently. Will have to wait till * Java provides the necessary access to the system routines. */ private void negotiate_gssapi(InputStream inp, OutputStream out) throws SocksException, IOException { throw new SocksException("GSSAPI authentication protocol not implemented"); } /** * Negotiates authentication using the username/password protocol * (rfc-1929). The username and password should previously have been * stored using the scheme "SOCKS5" and realm "USER/PASS"; e.g. * AuthorizationInfo.addAuthorization(socks_host, socks_port, "SOCKS5", * "USER/PASS", null, * { new NVPair(username, password) }); * */ private void negotiate_userpwd(InputStream inp, OutputStream out) throws SocksException, IOException { byte[] buffer; Log.write(Log.SOCKS, "Socks: Entering authorization subnegotiation" + "; method: Username/Password"); // get username/password AuthorizationInfo auth_info; try { auth_info = AuthorizationInfo.getAuthorization(socks_host, socks_port, "SOCKS5", "USER/PASS", null, null, true); } catch (AuthSchemeNotImplException atnie) { auth_info = null; } if (auth_info == null) throw new SocksException("No Authorization info for SOCKS found " + "(server requested username/password)."); NVPair[] unpw = auth_info.getParams(); if (unpw == null || unpw.length == 0) throw new SocksException("No Username/Password found in " + "authorization info for SOCKS."); String user_str = unpw[0].getName(); String pass_str = unpw[0].getValue(); // send them to server Log.write(Log.SOCKS, "Socks: Sending authorization request for user "+ user_str); byte[] utmp = user_str.getBytes(); byte[] ptmp = pass_str.getBytes(); buffer = new byte[1+1+utmp.length+1+ptmp.length]; buffer[0] = 1; // version 1 (subnegotiation) buffer[1] = (byte) utmp.length; // Username length System.arraycopy(utmp, 0, buffer, 2, utmp.length); // Username buffer[2+buffer[1]] = (byte) ptmp.length; // Password length System.arraycopy(ptmp, 0, buffer, 2+buffer[1]+1, ptmp.length); // Password out.write(buffer); // get reply int version = inp.read(); if (version != 1) throw new SocksException("Wrong version received in username/" + "password subnegotiation response: " + version + "; expected: 1"); int sts = inp.read(); if (sts != 0) throw new SocksException("Username/Password authentication " + "failed; status: "+sts); Log.write(Log.SOCKS, "Socks: Received response; version: " + version + "; status: " + sts); } /** * produces a string. * @return a string containing the host and port of the socks server */ public String toString() { return getClass().getName() + "[" + socks_host + ":" + socks_port + "]"; } }