View Javadoc

1   /*
2    * @(#)SocksClient.java					0.3-3 06/05/2001
3    *
4    *  This file is part of the HTTPClient package
5    *  Copyright (C) 1996-2001 Ronald Tschalär
6    *
7    *  This library is free software; you can redistribute it and/or
8    *  modify it under the terms of the GNU Lesser General Public
9    *  License as published by the Free Software Foundation; either
10   *  version 2 of the License, or (at your option) any later version.
11   *
12   *  This library is distributed in the hope that it will be useful,
13   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   *  Lesser General Public License for more details.
16   *
17   *  You should have received a copy of the GNU Lesser General Public
18   *  License along with this library; if not, write to the Free
19   *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
20   *  MA 02111-1307, USA
21   *
22   *  For questions, suggestions, bug-reports, enhancement-requests etc.
23   *  I may be contacted at:
24   *
25   *  ronald@innovation.ch
26   *
27   *  The HTTPClient's home page is located at:
28   *
29   *  http://www.innovation.ch/java/HTTPClient/ 
30   *
31   */
32  
33  package HTTPClient;
34  
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.io.OutputStream;
38  import java.io.ByteArrayOutputStream;
39  import java.net.Socket;
40  import java.net.InetAddress;
41  import java.net.SocketException;
42  import java.net.UnknownHostException;
43  
44  /**
45   * This class implements a SOCKS Client. Supports both versions 4 and 5.
46   * GSSAPI however is not yet implemented.
47   * <P>Usage is as follows: somewhere in the initialization code (and before
48   * the first socket creation call) create a SocksClient instance. Then replace
49   * each socket creation call
50   *
51   *     <code>sock = new Socket(host, port);</code>
52   *
53   * with
54   *
55   *     <code>sock = socks_client.getSocket(host, port);</code>
56   *
57   * (where <var>socks_client</var> is the above created SocksClient instance).
58   * That's all.
59   *
60   * @version	0.3-3  06/05/2001
61   * @author	Ronald Tschalär
62   */
63  class SocksClient
64  {
65      /** the host the socks server sits on */
66      private String socks_host;
67  
68      /** the port the socks server listens on */
69      private int    socks_port;
70  
71      /** the version of socks that the server handles */
72      private int    socks_version;
73  
74      /** socks commands */
75      private final static byte CONNECT = 1,
76  			      BIND    = 2,
77  			      UDP_ASS = 3;
78  
79      /** socks version 5 authentication methods */
80      private final static byte NO_AUTH = 0,
81  			      GSSAPI  = 1,
82  			      USERPWD = 2,
83  			      NO_ACC  = (byte) 0xFF;
84  
85      /** socks version 5 address types */
86      private final static byte IP_V4   = 1,
87  			      DMNAME  = 3,
88  			      IP_V6   = 4;
89  
90  
91      // Constructors
92  
93      /**
94       * Creates a new SOCKS Client using the specified host and port for
95       * the server. Will try to establish the SOCKS version used when
96       * establishing the first connection.
97       *
98       * @param host  the host the SOCKS server is sitting on.
99       * @param port  the port the SOCKS server is listening on.
100      */
101     SocksClient(String host, int port)
102     {
103 	this.socks_host    = host;
104 	this.socks_port    = port;
105 	this.socks_version = -1;	// as yet unknown
106     }
107 
108     /**
109      * Creates a new SOCKS Client using the specified host and port for
110      * the server.
111      *
112      * @param host     the host the SOCKS server is sitting on.
113      * @param port     the port the SOCKS server is listening on.
114      * @param version  the version the SOCKS server is using.
115      * @exception SocksException if the version is invalid (Currently allowed
116      *                           are: 4 and 5).
117      */
118     SocksClient(String host, int port, int version)  throws SocksException
119     {
120 	this.socks_host    = host;
121 	this.socks_port    = port;
122 
123 	if (version != 4  &&  version != 5)
124 	    throw new SocksException("SOCKS Version not supported: "+version);
125 	this.socks_version = version;
126     }
127 
128 
129     // Methods
130 
131     /**
132      * Initiates a connection to the socks server, does the startup
133      * protocol and returns a socket ready for talking.
134      *
135      * @param host  the host you wish to connect to
136      * @param port  the port you wish to connect to
137      * @return a Socket with a connection via socks to the desired host/port
138      * @exception IOException if any socket operation fails
139      */
140     Socket getSocket(String host, int port)  throws IOException
141     {
142 	return getSocket(host, port, null, -1);
143     }
144 
145     /**
146      * Initiates a connection to the socks server, does the startup
147      * protocol and returns a socket ready for talking.
148      *
149      * @param host      the host you wish to connect to
150      * @param port      the port you wish to connect to
151      * @param localAddr the local address to bind to
152      * @param localPort the local port to bind to
153      * @return a Socket with a connection via socks to the desired host/port
154      * @exception IOException if any socket operation fails
155      */
156     Socket getSocket(String host, int port, InetAddress localAddr,
157 		     int localPort)  throws IOException
158     {
159 	Socket sock = null;
160 
161 	try
162 	{
163 	    Log.write(Log.SOCKS, "Socks: contacting server on " +
164 				 socks_host + ":" + socks_port);
165 
166 
167 	    // create socket and streams
168 
169 	    sock = connect(socks_host, socks_port, localAddr, localPort);
170 	    InputStream  inp = sock.getInputStream();
171 	    OutputStream out = sock.getOutputStream();
172 
173 
174 	    // setup connection depending on socks version
175 
176 	    switch (socks_version)
177 	    {
178 		case 4:
179 		    v4ProtExchg(inp, out, host, port);
180 		    break;
181 		case 5:
182 		    v5ProtExchg(inp, out, host, port);
183 		    break;
184 		case -1:
185 		    // Ok, let's try and figure it out
186 		    try
187 		    {
188 			v4ProtExchg(inp, out, host, port);
189 			socks_version = 4;
190 		    }
191 		    catch (SocksException se)
192 		    {
193 			Log.write(Log.SOCKS, "Socks: V4 request failed: " +
194 					     se.getMessage());
195 
196 			sock.close();
197 			sock = connect(socks_host, socks_port, localAddr,
198 				       localPort);
199 			inp = sock.getInputStream();
200 			out = sock.getOutputStream();
201 
202 			v5ProtExchg(inp, out, host, port);
203 			socks_version = 5;
204 		    }
205 		    break;
206 		default:
207 		    throw new Error("SocksClient internal error: unknown " +
208 				    "version "+socks_version);
209 	    }
210 
211 	    Log.write(Log.SOCKS, "Socks: connection established.");
212 
213 	    return sock;
214 	}
215 	catch (IOException ioe)
216 	{
217 	    if (sock != null)
218 	    {
219 		try { sock.close(); }
220 		catch (IOException ee) {}
221 	    }
222 
223 	    throw ioe;
224 	}
225     }
226 
227 
228     /**
229      * Connect to the host/port, trying all addresses assciated with that
230      * host.
231      *
232      * @param host      the host you wish to connect to
233      * @param port      the port you wish to connect to
234      * @param localAddr the local address to bind to
235      * @param localPort the local port to bind to
236      * @return the Socket
237      * @exception IOException if the connection could not be established
238      */
239     private static final Socket connect(String host, int port,
240 					InetAddress localAddr, int localPort)
241 	    throws IOException
242     {
243 	InetAddress[] addr_list = InetAddress.getAllByName(host);
244 	for (int idx=0; idx<addr_list.length; idx++)
245 	{
246 	    try
247 	    {
248 		if (localAddr == null)
249 		    return new Socket(addr_list[idx], port);
250 		else
251 		    return new Socket(addr_list[idx], port, localAddr, localPort);
252 	    }
253 	    catch (SocketException se)
254 	    {
255 		if (idx < addr_list.length-1)
256 		    continue;	// try next IP address
257 		else
258 		    throw se;	// none of them worked
259 	    }
260 	}
261 
262 	return null;	// never reached - just here to shut up the compiler
263     }
264 
265 
266     private boolean v4A  = false;	// SOCKS version 4A
267     private byte[]  user = null;
268 
269     /**
270      * Does the protocol exchange for a version 4 SOCKS connection.
271      */
272     private void v4ProtExchg(InputStream inp, OutputStream out, String host,
273 			     int port)
274 	throws SocksException, IOException
275     {
276 	ByteArrayOutputStream buffer = new ByteArrayOutputStream(100);
277 
278 	Log.write(Log.SOCKS, "Socks: Beginning V4 Protocol Exchange for host "
279 			     + host + ":" + port);
280 
281 	// get ip addr and user name
282 
283 	byte[] addr = { 0, 0, 0, 42 };
284 	if (!v4A)
285 	{
286 	    try
287 		{ addr = InetAddress.getByName(host).getAddress(); }
288 	    // if we can't translate, let's try the server
289 	    catch (UnknownHostException uhe)
290 		{ v4A = true; }
291 	    catch (SecurityException se)
292 		{ v4A = true; }
293 	    if (v4A)
294 		Log.write(Log.SOCKS, "Socks: Switching to version 4A");
295 	}
296 
297 	if (user == null)	// I see no reason not to cache this
298 	{
299 	    String user_str;
300 	    try
301 		{ user_str = System.getProperty("user.name", ""); }
302 	    catch (SecurityException se)
303 		{ user_str = "";	/* try it anyway */ }
304 	    byte[] tmp = user_str.getBytes();
305 	    user = new byte[tmp.length+1];
306 	    System.arraycopy(tmp, 0, user, 0, tmp.length);
307 	    user[user_str.length()] = 0;	// 0-terminated string
308 	}
309 
310 
311 	// send version 4 request
312 
313 	Log.write(Log.SOCKS, "Socks: Sending connect request for user " +
314 			     new String(user, 0, user.length-1));
315 
316 	buffer.reset();
317 	buffer.write(4);				// version
318 	buffer.write(CONNECT);				// command
319 	buffer.write((port >> 8) & 0xff);		// port
320 	buffer.write(port & 0xff);
321 	buffer.write(addr);				// address
322 	buffer.write(user);				// user
323 	if (v4A)
324 	{
325 	    buffer.write(host.getBytes("8859_1"));	// host name
326 	    buffer.write(0);				// terminating 0
327 	}
328 	buffer.writeTo(out);
329 
330 
331 	// read response
332 
333 	int version = inp.read();
334 	if (version == -1)
335 	    throw new SocksException("Connection refused by server");
336 	else if (version == 4)	// not all socks4 servers are correct...
337 	    Log.write(Log.SOCKS, "Socks: Warning: received version 4 " +
338 				 "instead of 0");
339 	else if (version != 0)
340 	    throw new SocksException("Received invalid version: " + version +
341 				     "; expected: 0");
342 
343 	int sts = inp.read();
344 
345 	Log.write(Log.SOCKS, "Socks: Received response; version: " + version +
346 			     "; status: " + sts);
347 
348 	switch (sts)
349 	{
350 	    case 90:	// request granted
351 		break;
352 	    case 91:	// request rejected
353 		throw new SocksException("Connection request rejected");
354 	    case 92:	// request rejected: can't connect to identd
355 		throw new SocksException("Connection request rejected: " +
356 					 "can't connect to identd");
357 	    case 93:	// request rejected: identd reports diff uid
358 		throw new SocksException("Connection request rejected: " +
359 					 "identd reports different user-id " +
360 					 "from "+
361 					 new String(user, 0, user.length-1));
362 	    default:	// unknown status
363 		throw new SocksException("Connection request rejected: " +
364 					 "unknown error " + sts);
365 	}
366 
367 	byte[] skip = new byte[2+4];		// skip port + address
368 	int rcvd = 0,
369 	    tot  = 0;
370 	while (tot < skip.length  &&
371 		(rcvd = inp.read(skip, 0, skip.length-tot)) != -1)
372 	    tot += rcvd;
373     }
374 
375 
376     /**
377      * Does the protocol exchange for a version 5 SOCKS connection.
378      * (rfc-1928)
379      */
380     private void v5ProtExchg(InputStream inp, OutputStream out, String host,
381 			     int port)
382 	throws SocksException, IOException
383     {
384 	int                   version;
385 	ByteArrayOutputStream buffer = new ByteArrayOutputStream(100);
386 
387 	Log.write(Log.SOCKS, "Socks: Beginning V5 Protocol Exchange for host "
388 			     + host + ":" + port);
389 
390 	// send version 5 verification methods
391 
392 	Log.write(Log.SOCKS, "Socks: Sending authentication request; methods"
393 			     + " No-Authentication, Username/Password");
394 
395 	buffer.reset();
396 	buffer.write(5);		// version
397 	buffer.write(2);		// number of verification methods
398 	buffer.write(NO_AUTH);		// method: no authentication
399 	buffer.write(USERPWD);		// method: username/password
400 	//buffer.write(GSSAPI);		// method: gssapi
401 	buffer.writeTo(out);
402 
403 
404 	// receive servers repsonse
405 
406 	version = inp.read();
407 	if (version == -1)
408 	    throw new SocksException("Connection refused by server");
409 	else if (version != 5)
410 	    throw new SocksException("Received invalid version: " + version +
411 				     "; expected: 5");
412 
413 	int method = inp.read();
414 
415 	Log.write(Log.SOCKS, "Socks: Received response; version: " + version +
416 			     "; method: " + method);
417 
418 
419 	// enter sub-negotiation for authentication
420 
421 	switch(method)
422 	{
423 	    case NO_AUTH:
424 		break;
425 	    case GSSAPI:
426 		negotiate_gssapi(inp, out);
427 		break;
428 	    case USERPWD:
429 		negotiate_userpwd(inp, out);
430 		break;
431 	    case NO_ACC:
432 		throw new SocksException("Server unwilling to accept any " +
433 					 "standard authentication methods");
434 	    default:
435 		throw new SocksException("Cannot handle authentication method "
436 					 + method);
437 	}
438 
439 
440 	// send version 5 request
441 
442 	Log.write(Log.SOCKS, "Socks: Sending connect request");
443 
444 	buffer.reset();
445 	buffer.write(5);				// version
446 	buffer.write(CONNECT);				// command
447 	buffer.write(0);				// reserved - must be 0
448 	buffer.write(DMNAME);				// address type
449 	buffer.write(host.length() & 0xff);		// address length
450 	buffer.write(host.getBytes("8859_1"));		// address
451 	buffer.write((port >> 8) & 0xff);		// port
452 	buffer.write(port & 0xff);
453 	buffer.writeTo(out);
454 
455 
456 	// read response
457 
458 	version = inp.read();
459 	if (version != 5)
460 	    throw new SocksException("Received invalid version: " + version +
461 				     "; expected: 5");
462 
463 	int sts = inp.read();
464 
465 	Log.write(Log.SOCKS, "Socks: Received response; version: " + version +
466 			     "; status: " + sts);
467 
468 	switch (sts)
469 	{
470 	    case 0:	// succeeded
471 		break;
472 	    case 1:
473 		throw new SocksException("General SOCKS server failure");
474 	    case 2:
475 		throw new SocksException("Connection not allowed");
476 	    case 3:
477 		throw new SocksException("Network unreachable");
478 	    case 4:
479 		throw new SocksException("Host unreachable");
480 	    case 5:
481 		throw new SocksException("Connection refused");
482 	    case 6:
483 		throw new SocksException("TTL expired");
484 	    case 7:
485 		throw new SocksException("Command not supported");
486 	    case 8:
487 		throw new SocksException("Address type not supported");
488 	    default:
489 		throw new SocksException("Unknown reply received from server: "
490 					 + sts);
491 	}
492 
493 	inp.read();			// Reserved
494 	int atype = inp.read(),		// address type
495 	    alen;			// address length
496 	switch(atype)
497 	{
498 	    case IP_V6:
499 		alen = 16;
500 		break;
501 	    case IP_V4:
502 		alen = 4;
503 		break;
504 	    case DMNAME:
505 		alen = inp.read();
506 		break;
507 	    default:
508 		throw new SocksException("Invalid address type received from" +
509 					 " server: "+atype);
510 	}
511 
512 	byte[] skip = new byte[alen+2];		// skip address + port
513 	int rcvd = 0,
514 	    tot  = 0;
515 	while (tot < skip.length  &&
516 		(rcvd = inp.read(skip, 0, skip.length-tot)) != -1)
517 	    tot += rcvd;
518     }
519 
520 
521     /**
522      * Negotiates authentication using the gssapi protocol
523      * (draft-ietf-aft-gssapi-02).
524      *
525      * NOTE: this is not implemented currently. Will have to wait till
526      *       Java provides the necessary access to the system routines.
527      */
528     private void negotiate_gssapi(InputStream inp, OutputStream out)
529 	throws SocksException, IOException
530     {
531 	throw new
532 	    SocksException("GSSAPI authentication protocol not implemented");
533     }
534 
535 
536     /**
537      * Negotiates authentication using the username/password protocol
538      * (rfc-1929). The username and password should previously have been
539      * stored using the scheme "SOCKS5" and realm "USER/PASS"; e.g.
540      * AuthorizationInfo.addAuthorization(socks_host, socks_port, "SOCKS5",
541      *					  "USER/PASS", null,
542      *					  { new NVPair(username, password) });
543      *
544      */
545     private void negotiate_userpwd(InputStream inp, OutputStream out)
546 	throws SocksException, IOException
547     {
548 	byte[] buffer;
549 
550 
551 	Log.write(Log.SOCKS, "Socks: Entering authorization subnegotiation" +
552 			     "; method: Username/Password");
553 
554 	// get username/password
555 
556 	AuthorizationInfo auth_info;
557 	try
558 	{
559 	    auth_info =
560 		AuthorizationInfo.getAuthorization(socks_host, socks_port,
561 						   "SOCKS5", "USER/PASS",
562 						   null, null, true);
563 	}
564 	catch (AuthSchemeNotImplException atnie)
565 	    { auth_info = null; }
566 
567 	if (auth_info == null)
568 	    throw new SocksException("No Authorization info for SOCKS found " +
569 				     "(server requested username/password).");
570 
571 	NVPair[] unpw = auth_info.getParams();
572 	if (unpw == null  ||  unpw.length == 0)
573 	    throw new SocksException("No Username/Password found in " +
574 				     "authorization info for SOCKS.");
575 
576 	String user_str = unpw[0].getName();
577 	String pass_str = unpw[0].getValue();
578 
579 
580 	// send them to server
581 
582 	Log.write(Log.SOCKS, "Socks: Sending authorization request for user "+
583 			     user_str);
584 
585 	byte[] utmp = user_str.getBytes();
586 	byte[] ptmp = pass_str.getBytes();
587 	buffer = new byte[1+1+utmp.length+1+ptmp.length];
588 	buffer[0] = 1;				// version 1 (subnegotiation)
589 	buffer[1] = (byte) utmp.length;				// Username length
590 	System.arraycopy(utmp, 0, buffer, 2, utmp.length);	// Username
591 	buffer[2+buffer[1]] = (byte) ptmp.length;		// Password length
592 	System.arraycopy(ptmp, 0, buffer, 2+buffer[1]+1, ptmp.length);	// Password
593 	out.write(buffer);
594 
595 
596 	// get reply
597 
598 	int version = inp.read();
599 	if (version != 1)
600 	    throw new SocksException("Wrong version received in username/" +
601 				     "password subnegotiation response: " +
602 				     version + "; expected: 1");
603 
604 	int sts = inp.read();
605 	if (sts != 0)
606 	    throw new SocksException("Username/Password authentication " +
607 				     "failed; status: "+sts);
608 
609 	Log.write(Log.SOCKS, "Socks: Received response; version: " + version +
610 			     "; status: " + sts);
611     }
612 
613 
614     /**
615      * produces a string.
616      * @return a string containing the host and port of the socks server
617      */
618     public String toString()
619     {
620 	return getClass().getName() + "[" + socks_host + ":" + socks_port + "]";
621     }
622 }