HTTPClient.SocksClient Maven / Gradle / Ivy
Show all versions of grinder-httpclient Show documentation
/*
* @(#)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:
*
* [email protected]
*
* 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 + "]";
}
}