KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > HTTPClient > SocksClient


1 /*
2  * @(#)SocksClient.java 0.3-2 18/06/1999
3  *
4  * This file is part of the HTTPClient package
5  * Copyright (C) 1996-1999 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  */

28
29 package HTTPClient;
30
31
32 import java.io.*;
33 import java.net.*;
34
35 /**
36  * This class implements a SOCKS Client. Supports both versions 4 and 5.
37  * GSSAPI however is not yet implemented.
38  * <P>Usage is as follows: somewhere in the initialization code (and before
39  * the first socket creation call) create a SocksClient instance. Then replace
40  * each socket creation call
41  *
42  * <code>sock = new Socket(host, port);</code>
43  *
44  * with
45  *
46  * <code>sock = socks_client.getSocket(host, port);</code>
47  *
48  * (where <var>socks_client</var> is the above created SocksClient instance).
49  * That's all.
50  *
51  * @version 0.3-2 18/06/1999
52  * @author Ronald Tschalär
53  */

54
55 class SocksClient implements GlobalConstants
56 {
57     /** the host the socks server sits on */
58     private String JavaDoc socks_host;
59
60     /** the port the socks server listens on */
61     private int socks_port;
62
63     /** the version of socks that the server handles */
64     private int socks_version;
65
66     /** socks commands */
67     private final static byte CONNECT = 1,
68                   BIND = 2,
69                   UDP_ASS = 3;
70
71     /** socks version 5 authentication methods */
72     private final static byte NO_AUTH = 0,
73                   GSSAPI = 1,
74                   USERPWD = 2,
75                   NO_ACC = (byte) 0xFF;
76
77     /** socks version 5 address types */
78     private final static byte IP_V4 = 1,
79                   DMNAME = 3,
80                   IP_V6 = 4;
81
82
83     // Constructors
84

85     /**
86      * Creates a new SOCKS Client using the specified host and port for
87      * the server. Will try to establish the SOCKS version used when
88      * establishing the first connection.
89      *
90      * @param host the host the SOCKS server is sitting on.
91      * @param port the port the SOCKS server is listening on.
92      */

93     SocksClient(String JavaDoc host, int port)
94     {
95     this.socks_host = host;
96     this.socks_port = port;
97     this.socks_version = -1; // as yet unknown
98
}
99
100     /**
101      * Creates a new SOCKS Client using the specified host and port for
102      * the server.
103      *
104      * @param host the host the SOCKS server is sitting on.
105      * @param port the port the SOCKS server is listening on.
106      * @param version the version the SOCKS server is using.
107      * @exception SocksException if the version is invalid (Currently allowed
108      * are: 4 and 5).
109      */

110     SocksClient(String JavaDoc host, int port, int version) throws SocksException
111     {
112     this.socks_host = host;
113     this.socks_port = port;
114
115     if (version != 4 && version != 5)
116         throw new SocksException("SOCKS Version not supported: "+version);
117     this.socks_version = version;
118     }
119
120
121     // Methods
122

123     /**
124      * Initiates a connection to the socks server, does the startup
125      * protocol and returns a socket ready for talking.
126      *
127      * @param host the host you wish to connect to
128      * @param port the port you wish to connect to
129      * @return a Socket with a connection via socks to the desired host/port
130      * @exception IOException if any socket operation fails
131      */

132     Socket getSocket(String JavaDoc host, int port) throws IOException
133     {
134     Socket sock = null;
135
136     try
137     {
138         if (DebugSocks)
139         System.err.println("Socks: contacting server on " +
140                     socks_host + ":" + socks_port);
141
142
143         // create socket and streams
144

145         sock = connect(socks_host, socks_port);
146         InputStream inp = sock.getInputStream();
147         OutputStream out = sock.getOutputStream();
148
149
150         // setup connection depending on socks version
151

152         switch (socks_version)
153         {
154         case 4:
155             v4ProtExchg(inp, out, host, port);
156             break;
157         case 5:
158             v5ProtExchg(inp, out, host, port);
159             break;
160         case -1:
161             // Ok, let's try and figure it out
162
try
163             {
164             v4ProtExchg(inp, out, host, port);
165             socks_version = 4;
166             }
167             catch (SocksException se)
168             {
169             if (DebugSocks)
170                 System.err.println("Socks: V4 request failed: " +
171                         se.getMessage());
172
173             sock.close();
174             sock = connect(socks_host, socks_port);
175             inp = sock.getInputStream();
176             out = sock.getOutputStream();
177
178             v5ProtExchg(inp, out, host, port);
179             socks_version = 5;
180             }
181             break;
182         default:
183             throw new Error JavaDoc("SocksClient internal error: unknown " +
184                     "version "+socks_version);
185         }
186
187         if (DebugSocks)
188         System.err.println("Socks: connection established.");
189
190         return sock;
191     }
192     catch (IOException ioe)
193     {
194         if (sock != null)
195         {
196         try { sock.close(); }
197         catch (IOException ee) {}
198         }
199
200         throw ioe;
201     }
202     }
203
204
205     /**
206      * Connect to the host/port, trying all addresses assciated with that
207      * host.
208      *
209      * @return the Socket
210      * @exception IOException if the connection could not be established
211      */

212     private static final Socket connect(String JavaDoc host, int port)
213         throws IOException
214     {
215     InetAddress[] addr_list = InetAddress.getAllByName(host);
216     for (int idx=0; idx<addr_list.length; idx++)
217     {
218         try
219         { return new Socket(addr_list[idx], port); }
220         catch (SocketException se)
221         {
222         if (idx < addr_list.length-1)
223             continue; // try next IP address
224
else
225             throw se; // none of them worked
226
}
227     }
228
229     return null; // never reached - just here to shut up the compiler
230
}
231
232
233     private boolean v4A = false; // SOCKS version 4A
234
private byte[] user = null;
235
236     /**
237      * Does the protocol exchange for a version 4 SOCKS connection.
238      */

239     private void v4ProtExchg(InputStream inp, OutputStream out, String JavaDoc host,
240                  int port)
241     throws SocksException, IOException
242     {
243     ByteArrayOutputStream buffer = new ByteArrayOutputStream(100);
244
245     if (DebugSocks)
246         System.err.println("Socks: Beginning V4 Protocol Exchange for host "
247                 + host + ":" + port);
248
249     // get ip addr and user name
250

251     byte[] addr = { 0, 0, 0, 42 };
252     if (!v4A)
253     {
254         try
255         { addr = InetAddress.getByName(host).getAddress(); }
256         // if we can't translate, let's try the server
257
catch (UnknownHostException uhe)
258         { v4A = true; }
259         catch (SecurityException JavaDoc se)
260         { v4A = true; }
261         if (DebugSocks)
262         if (v4A)
263             System.err.println("Socks: Switching to version 4A");
264     }
265
266     if (user == null) // I see no reason not to cache this
267
{
268         String JavaDoc user_str;
269         try
270         { user_str = System.getProperty("user.name", ""); }
271         catch (SecurityException JavaDoc se)
272         { user_str = ""; /* try it anyway */ }
273         user = new byte[user_str.length()+1];
274         user_str.getBytes(0, user_str.length(), user, 0);
275         user[user_str.length()] = 0; // 0-terminated string
276
}
277
278
279     // send version 4 request
280

281     if (DebugSocks)
282         System.err.println("Socks: Sending connect request for user " +
283                 new String JavaDoc(user, 0, 0, user.length-1));
284
285     buffer.reset();
286     buffer.write(4); // version
287
buffer.write(CONNECT); // command
288
buffer.write((port >> 8) & 0xff); // port
289
buffer.write(port & 0xff);
290     buffer.write(addr, 0, addr.length); // address
291
buffer.write(user, 0, user.length); // user
292
if (v4A)
293     {
294         byte[] host_buf = new byte[host.length()];
295         host.getBytes(0, host.length(), host_buf, 0);
296         buffer.write(host_buf, 0, host_buf.length); // host name
297
buffer.write(0); // terminating 0
298
}
299     buffer.writeTo(out);
300
301
302     // read response
303

304     int version = inp.read();
305     if (version == -1)
306         throw new SocksException("Connection refused by server");
307     else if (version == 4) // not all socks4 servers are correct...
308
if (DebugSocks)
309         System.err.println("Socks: Warning: received version 4 " +
310                     "instead of 0");
311     else if (version != 0)
312         throw new SocksException("Received invalid version: " + version +
313                      "; expected: 0");
314
315     int sts = inp.read();
316
317     if (DebugSocks)
318         System.err.println("Socks: Received response; version: " + version +
319                 "; status: " + sts);
320
321     switch (sts)
322     {
323         case 90: // request granted
324
break;
325         case 91: // request rejected
326
throw new SocksException("Connection request rejected");
327         case 92: // request rejected: can't connect to identd
328
throw new SocksException("Connection request rejected: " +
329                      "can't connect to identd");
330         case 93: // request rejected: identd reports diff uid
331
throw new SocksException("Connection request rejected: " +
332                      "identd reports different user-id " +
333                      "from "+
334                      new String JavaDoc(user, 0, 0, user.length-1));
335         default: // unknown status
336
throw new SocksException("Connection request rejected: " +
337                      "unknown error " + sts);
338     }
339
340     byte[] skip = new byte[2+4]; // skip port + address
341
int rcvd = 0,
342         tot = 0;
343     while (tot < skip.length &&
344         (rcvd = inp.read(skip, 0, skip.length-tot)) != -1)
345         tot += rcvd;
346     }
347
348
349     /**
350      * Does the protocol exchange for a version 5 SOCKS connection.
351      * (rfc-1928)
352      */

353     private void v5ProtExchg(InputStream inp, OutputStream out, String JavaDoc host,
354                  int port)
355     throws SocksException, IOException
356     {
357     int version;
358     ByteArrayOutputStream buffer = new ByteArrayOutputStream(100);
359
360     if (DebugSocks)
361         System.err.println("Socks: Beginning V5 Protocol Exchange for host "
362                 + host + ":" + port);
363
364     // send version 5 verification methods
365

366     if (DebugSocks)
367         System.err.println("Socks: Sending authentication request; methods"
368                 + " No-Authentication, Username/Password");
369
370     buffer.reset();
371     buffer.write(5); // version
372
buffer.write(2); // number of verification methods
373
buffer.write(NO_AUTH); // method: no authentication
374
buffer.write(USERPWD); // method: username/password
375
//buffer.write(GSSAPI); // method: gssapi
376
buffer.writeTo(out);
377
378
379     // receive servers repsonse
380

381     version = inp.read();
382     if (version == -1)
383         throw new SocksException("Connection refused by server");
384     else if (version != 5)
385         throw new SocksException("Received invalid version: " + version +
386                      "; expected: 5");
387
388     int method = inp.read();
389
390     if (DebugSocks)
391         System.err.println("Socks: Received response; version: " + version +
392                 "; method: " + method);
393
394
395     // enter sub-negotiation for authentication
396

397     switch(method)
398     {
399         case NO_AUTH:
400         break;
401         case GSSAPI:
402         negotiate_gssapi(inp, out);
403         break;
404         case USERPWD:
405         negotiate_userpwd(inp, out);
406         break;
407         case NO_ACC:
408         throw new SocksException("Server unwilling to accept any " +
409                      "standard authentication methods");
410         default:
411         throw new SocksException("Cannot handle authentication method "
412                      + method);
413     }
414
415
416     // send version 5 request
417

418     if (DebugSocks)
419         System.err.println("Socks: Sending connect request");
420
421     buffer.reset();
422     buffer.write(5); // version
423
buffer.write(CONNECT); // command
424
buffer.write(0); // reserved - must be 0
425
buffer.write(DMNAME); // address type
426
buffer.write(host.length() & 0xff); // address length
427
byte[] hname = new byte[host.length()];
428     host.getBytes(0, host.length(), hname, 0);
429     buffer.write(hname, 0, hname.length); // address
430
buffer.write((port >> 8) & 0xff); // port
431
buffer.write(port & 0xff);
432     buffer.writeTo(out);
433
434
435     // read response
436

437     version = inp.read();
438     if (version != 5)
439         throw new SocksException("Received invalid version: " + version +
440                      "; expected: 5");
441
442     int sts = inp.read();
443
444     if (DebugSocks)
445         System.err.println("Socks: Received response; version: " + version +
446                 "; status: " + sts);
447
448     switch (sts)
449     {
450         case 0: // succeeded
451
break;
452         case 1:
453         throw new SocksException("General SOCKS server failure");
454         case 2:
455         throw new SocksException("Connection not allowed");
456         case 3:
457         throw new SocksException("Network unreachable");
458         case 4:
459         throw new SocksException("Host unreachable");
460         case 5:
461         throw new SocksException("Connection refused");
462         case 6:
463         throw new SocksException("TTL expired");
464         case 7:
465         throw new SocksException("Command not supported");
466         case 8:
467         throw new SocksException("Address type not supported");
468         default:
469         throw new SocksException("Unknown reply received from server: "
470                      + sts);
471     }
472
473     inp.read(); // Reserved
474
int atype = inp.read(), // address type
475
alen; // address length
476
switch(atype)
477     {
478         case IP_V6:
479         alen = 16;
480         break;
481         case IP_V4:
482         alen = 4;
483         break;
484         case DMNAME:
485         alen = inp.read();
486         break;
487         default:
488         throw new SocksException("Invalid address type received from" +
489                      " server: "+atype);
490     }
491
492     byte[] skip = new byte[alen+2]; // skip address + port
493
int rcvd = 0,
494         tot = 0;
495     while (tot < skip.length &&
496         (rcvd = inp.read(skip, 0, skip.length-tot)) != -1)
497         tot += rcvd;
498     }
499
500
501     /**
502      * Negotiates authentication using the gssapi protocol
503      * (draft-ietf-aft-gssapi-02).
504      *
505      * NOTE: this is not implemented currently. Will have to wait till
506      * Java provides the necessary access to the system routines.
507      */

508     private void negotiate_gssapi(InputStream inp, OutputStream out)
509     throws SocksException, IOException
510     {
511     throw new
512         SocksException("GSSAPI authentication protocol not implemented");
513     }
514
515
516     /**
517      * Negotiates authentication using the username/password protocol
518      * (rfc-1929). The username and password should previously have been
519      * stored using the scheme "SOCKS5" and realm "USER/PASS"; e.g.
520      * AuthorizationInfo.addAuthorization(socks_host, socks_port, "SOCKS5",
521      * "USER/PASS", null,
522      * { new NVPair(username, password) });
523      *
524      */

525     private void negotiate_userpwd(InputStream inp, OutputStream out)
526     throws SocksException, IOException
527     {
528     byte[] buffer;
529
530
531     if (DebugSocks)
532         System.err.println("Socks: Entering authorization subnegotiation" +
533                 "; method: Username/Password");
534
535     // get username/password
536

537     AuthorizationInfo auth_info;
538     try
539     {
540         auth_info =
541         AuthorizationInfo.getAuthorization(socks_host, socks_port,
542                            "SOCKS5", "USER/PASS", true);
543     }
544     catch (AuthSchemeNotImplException atnie)
545         { auth_info = null; }
546
547     if (auth_info == null)
548         throw new SocksException("No Authorization info for SOCKS found " +
549                      "(server requested username/password).");
550
551     NVPair[] unpw = auth_info.getParams();
552     if (unpw == null || unpw.length == 0)
553         throw new SocksException("No Username/Password found in " +
554                      "authorization info for SOCKS.");
555
556     String JavaDoc user_str = unpw[0].getName();
557     String JavaDoc pass_str = unpw[0].getValue();
558
559
560     // send them to server
561

562     if (DebugSocks)
563         System.err.println("Socks: Sending authorization request for user "+
564                 user_str);
565
566     buffer = new byte[1+1+user_str.length()+1+pass_str.length()];
567     buffer[0] = 1; // version 1 (subnegotiation)
568
buffer[1] = (byte) user_str.length(); // Username length
569
user_str.getBytes(0, buffer[1], buffer, 2); // Username
570
buffer[2+buffer[1]] = (byte) pass_str.length(); // Password length
571
pass_str.getBytes(0, buffer[2+buffer[1]], buffer, 2+buffer[1]+1);// Password
572
out.write(buffer);
573
574
575     // get reply
576

577     int version = inp.read();
578     if (version != 1)
579         throw new SocksException("Wrong version received in username/" +
580                      "password subnegotiation response: " +
581                      version + "; expected: 1");
582
583     int sts = inp.read();
584     if (sts != 0)
585         throw new SocksException("Username/Password authentication " +
586                      "failed; status: "+sts);
587
588     if (DebugSocks)
589         System.err.println("Socks: Received response; version: " + version +
590                 "; status: " + sts);
591     }
592
593
594     /**
595      * produces a string.
596      * @return a string containing the host and port of the socks server
597      */

598     public String JavaDoc toString()
599     {
600     return getClass().getName() + "[" + socks_host + ":" + socks_port + "]";
601     }
602 }
603
604
Popular Tags