001: /*
002: * @(#)SocksClient.java 0.3-2 18/06/1999
003: *
004: * This file is part of the HTTPClient package
005: * Copyright (C) 1996-1999 Ronald Tschalär
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free
019: * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
020: * MA 02111-1307, USA
021: *
022: * For questions, suggestions, bug-reports, enhancement-requests etc.
023: * I may be contacted at:
024: *
025: * ronald@innovation.ch
026: *
027: */
028:
029: package HTTPClient;
030:
031: import java.io.*;
032: import java.net.*;
033:
034: /**
035: * This class implements a SOCKS Client. Supports both versions 4 and 5.
036: * GSSAPI however is not yet implemented.
037: * <P>Usage is as follows: somewhere in the initialization code (and before
038: * the first socket creation call) create a SocksClient instance. Then replace
039: * each socket creation call
040: *
041: * <code>sock = new Socket(host, port);</code>
042: *
043: * with
044: *
045: * <code>sock = socks_client.getSocket(host, port);</code>
046: *
047: * (where <var>socks_client</var> is the above created SocksClient instance).
048: * That's all.
049: *
050: * @version 0.3-2 18/06/1999
051: * @author Ronald Tschalär
052: */
053:
054: class SocksClient implements GlobalConstants {
055: /** the host the socks server sits on */
056: private String socks_host;
057:
058: /** the port the socks server listens on */
059: private int socks_port;
060:
061: /** the version of socks that the server handles */
062: private int socks_version;
063:
064: /** socks commands */
065: private final static byte CONNECT = 1, BIND = 2, UDP_ASS = 3;
066:
067: /** socks version 5 authentication methods */
068: private final static byte NO_AUTH = 0, GSSAPI = 1, USERPWD = 2,
069: NO_ACC = (byte) 0xFF;
070:
071: /** socks version 5 address types */
072: private final static byte IP_V4 = 1, DMNAME = 3, IP_V6 = 4;
073:
074: // Constructors
075:
076: /**
077: * Creates a new SOCKS Client using the specified host and port for
078: * the server. Will try to establish the SOCKS version used when
079: * establishing the first connection.
080: *
081: * @param host the host the SOCKS server is sitting on.
082: * @param port the port the SOCKS server is listening on.
083: */
084: SocksClient(String host, int port) {
085: this .socks_host = host;
086: this .socks_port = port;
087: this .socks_version = -1; // as yet unknown
088: }
089:
090: /**
091: * Creates a new SOCKS Client using the specified host and port for
092: * the server.
093: *
094: * @param host the host the SOCKS server is sitting on.
095: * @param port the port the SOCKS server is listening on.
096: * @param version the version the SOCKS server is using.
097: * @exception SocksException if the version is invalid (Currently allowed
098: * are: 4 and 5).
099: */
100: SocksClient(String host, int port, int version)
101: throws SocksException {
102: this .socks_host = host;
103: this .socks_port = port;
104:
105: if (version != 4 && version != 5)
106: throw new SocksException("SOCKS Version not supported: "
107: + version);
108: this .socks_version = version;
109: }
110:
111: // Methods
112:
113: /**
114: * Initiates a connection to the socks server, does the startup
115: * protocol and returns a socket ready for talking.
116: *
117: * @param host the host you wish to connect to
118: * @param port the port you wish to connect to
119: * @return a Socket with a connection via socks to the desired host/port
120: * @exception IOException if any socket operation fails
121: */
122: Socket getSocket(String host, int port) throws IOException {
123: Socket sock = null;
124:
125: try {
126: if (DebugSocks)
127: System.err.println("Socks: contacting server on "
128: + socks_host + ":" + socks_port);
129:
130: // create socket and streams
131:
132: sock = connect(socks_host, socks_port);
133: InputStream inp = sock.getInputStream();
134: OutputStream out = sock.getOutputStream();
135:
136: // setup connection depending on socks version
137:
138: switch (socks_version) {
139: case 4:
140: v4ProtExchg(inp, out, host, port);
141: break;
142: case 5:
143: v5ProtExchg(inp, out, host, port);
144: break;
145: case -1:
146: // Ok, let's try and figure it out
147: try {
148: v4ProtExchg(inp, out, host, port);
149: socks_version = 4;
150: } catch (SocksException se) {
151: if (DebugSocks)
152: System.err.println("Socks: V4 request failed: "
153: + se.getMessage());
154:
155: sock.close();
156: sock = connect(socks_host, socks_port);
157: inp = sock.getInputStream();
158: out = sock.getOutputStream();
159:
160: v5ProtExchg(inp, out, host, port);
161: socks_version = 5;
162: }
163: break;
164: default:
165: throw new Error("SocksClient internal error: unknown "
166: + "version " + socks_version);
167: }
168:
169: if (DebugSocks)
170: System.err.println("Socks: connection established.");
171:
172: return sock;
173: } catch (IOException ioe) {
174: if (sock != null) {
175: try {
176: sock.close();
177: } catch (IOException ee) {
178: }
179: }
180:
181: throw ioe;
182: }
183: }
184:
185: /**
186: * Connect to the host/port, trying all addresses assciated with that
187: * host.
188: *
189: * @return the Socket
190: * @exception IOException if the connection could not be established
191: */
192: private static final Socket connect(String host, int port)
193: throws IOException {
194: InetAddress[] addr_list = InetAddress.getAllByName(host);
195: for (int idx = 0; idx < addr_list.length; idx++) {
196: try {
197: return new Socket(addr_list[idx], port);
198: } catch (SocketException se) {
199: if (idx < addr_list.length - 1)
200: continue; // try next IP address
201: else
202: throw se; // none of them worked
203: }
204: }
205:
206: return null; // never reached - just here to shut up the compiler
207: }
208:
209: private boolean v4A = false; // SOCKS version 4A
210: private byte[] user = null;
211:
212: /**
213: * Does the protocol exchange for a version 4 SOCKS connection.
214: */
215: private void v4ProtExchg(InputStream inp, OutputStream out,
216: String host, int port) throws SocksException, IOException {
217: ByteArrayOutputStream buffer = new ByteArrayOutputStream(100);
218:
219: if (DebugSocks)
220: System.err
221: .println("Socks: Beginning V4 Protocol Exchange for host "
222: + host + ":" + port);
223:
224: // get ip addr and user name
225:
226: byte[] addr = { 0, 0, 0, 42 };
227: if (!v4A) {
228: try {
229: addr = InetAddress.getByName(host).getAddress();
230: }
231: // if we can't translate, let's try the server
232: catch (UnknownHostException uhe) {
233: v4A = true;
234: } catch (SecurityException se) {
235: v4A = true;
236: }
237: if (DebugSocks)
238: if (v4A)
239: System.err
240: .println("Socks: Switching to version 4A");
241: }
242:
243: if (user == null) // I see no reason not to cache this
244: {
245: String user_str;
246: try {
247: user_str = System.getProperty("user.name", "");
248: } catch (SecurityException se) {
249: user_str = ""; /* try it anyway */
250: }
251: user = new byte[user_str.length() + 1];
252: user_str.getBytes(0, user_str.length(), user, 0);
253: user[user_str.length()] = 0; // 0-terminated string
254: }
255:
256: // send version 4 request
257:
258: if (DebugSocks)
259: System.err
260: .println("Socks: Sending connect request for user "
261: + new String(user, 0, 0, user.length - 1));
262:
263: buffer.reset();
264: buffer.write(4); // version
265: buffer.write(CONNECT); // command
266: buffer.write((port >> 8) & 0xff); // port
267: buffer.write(port & 0xff);
268: buffer.write(addr, 0, addr.length); // address
269: buffer.write(user, 0, user.length); // user
270: if (v4A) {
271: byte[] host_buf = new byte[host.length()];
272: host.getBytes(0, host.length(), host_buf, 0);
273: buffer.write(host_buf, 0, host_buf.length); // host name
274: buffer.write(0); // terminating 0
275: }
276: buffer.writeTo(out);
277:
278: // read response
279:
280: int version = inp.read();
281: if (version == -1)
282: throw new SocksException("Connection refused by server");
283: else if (version == 4) // not all socks4 servers are correct...
284: if (DebugSocks)
285: System.err
286: .println("Socks: Warning: received version 4 "
287: + "instead of 0");
288: else if (version != 0)
289: throw new SocksException("Received invalid version: "
290: + version + "; expected: 0");
291:
292: int sts = inp.read();
293:
294: if (DebugSocks)
295: System.err.println("Socks: Received response; version: "
296: + version + "; status: " + sts);
297:
298: switch (sts) {
299: case 90: // request granted
300: break;
301: case 91: // request rejected
302: throw new SocksException("Connection request rejected");
303: case 92: // request rejected: can't connect to identd
304: throw new SocksException("Connection request rejected: "
305: + "can't connect to identd");
306: case 93: // request rejected: identd reports diff uid
307: throw new SocksException("Connection request rejected: "
308: + "identd reports different user-id " + "from "
309: + new String(user, 0, 0, user.length - 1));
310: default: // unknown status
311: throw new SocksException("Connection request rejected: "
312: + "unknown error " + sts);
313: }
314:
315: byte[] skip = new byte[2 + 4]; // skip port + address
316: int rcvd = 0, tot = 0;
317: while (tot < skip.length
318: && (rcvd = inp.read(skip, 0, skip.length - tot)) != -1)
319: tot += rcvd;
320: }
321:
322: /**
323: * Does the protocol exchange for a version 5 SOCKS connection.
324: * (rfc-1928)
325: */
326: private void v5ProtExchg(InputStream inp, OutputStream out,
327: String host, int port) throws SocksException, IOException {
328: int version;
329: ByteArrayOutputStream buffer = new ByteArrayOutputStream(100);
330:
331: if (DebugSocks)
332: System.err
333: .println("Socks: Beginning V5 Protocol Exchange for host "
334: + host + ":" + port);
335:
336: // send version 5 verification methods
337:
338: if (DebugSocks)
339: System.err
340: .println("Socks: Sending authentication request; methods"
341: + " No-Authentication, Username/Password");
342:
343: buffer.reset();
344: buffer.write(5); // version
345: buffer.write(2); // number of verification methods
346: buffer.write(NO_AUTH); // method: no authentication
347: buffer.write(USERPWD); // method: username/password
348: //buffer.write(GSSAPI); // method: gssapi
349: buffer.writeTo(out);
350:
351: // receive servers repsonse
352:
353: version = inp.read();
354: if (version == -1)
355: throw new SocksException("Connection refused by server");
356: else if (version != 5)
357: throw new SocksException("Received invalid version: "
358: + version + "; expected: 5");
359:
360: int method = inp.read();
361:
362: if (DebugSocks)
363: System.err.println("Socks: Received response; version: "
364: + version + "; method: " + method);
365:
366: // enter sub-negotiation for authentication
367:
368: switch (method) {
369: case NO_AUTH:
370: break;
371: case GSSAPI:
372: negotiate_gssapi(inp, out);
373: break;
374: case USERPWD:
375: negotiate_userpwd(inp, out);
376: break;
377: case NO_ACC:
378: throw new SocksException("Server unwilling to accept any "
379: + "standard authentication methods");
380: default:
381: throw new SocksException(
382: "Cannot handle authentication method " + method);
383: }
384:
385: // send version 5 request
386:
387: if (DebugSocks)
388: System.err.println("Socks: Sending connect request");
389:
390: buffer.reset();
391: buffer.write(5); // version
392: buffer.write(CONNECT); // command
393: buffer.write(0); // reserved - must be 0
394: buffer.write(DMNAME); // address type
395: buffer.write(host.length() & 0xff); // address length
396: byte[] hname = new byte[host.length()];
397: host.getBytes(0, host.length(), hname, 0);
398: buffer.write(hname, 0, hname.length); // address
399: buffer.write((port >> 8) & 0xff); // port
400: buffer.write(port & 0xff);
401: buffer.writeTo(out);
402:
403: // read response
404:
405: version = inp.read();
406: if (version != 5)
407: throw new SocksException("Received invalid version: "
408: + version + "; expected: 5");
409:
410: int sts = inp.read();
411:
412: if (DebugSocks)
413: System.err.println("Socks: Received response; version: "
414: + version + "; status: " + sts);
415:
416: switch (sts) {
417: case 0: // succeeded
418: break;
419: case 1:
420: throw new SocksException("General SOCKS server failure");
421: case 2:
422: throw new SocksException("Connection not allowed");
423: case 3:
424: throw new SocksException("Network unreachable");
425: case 4:
426: throw new SocksException("Host unreachable");
427: case 5:
428: throw new SocksException("Connection refused");
429: case 6:
430: throw new SocksException("TTL expired");
431: case 7:
432: throw new SocksException("Command not supported");
433: case 8:
434: throw new SocksException("Address type not supported");
435: default:
436: throw new SocksException(
437: "Unknown reply received from server: " + sts);
438: }
439:
440: inp.read(); // Reserved
441: int atype = inp.read(), // address type
442: alen; // address length
443: switch (atype) {
444: case IP_V6:
445: alen = 16;
446: break;
447: case IP_V4:
448: alen = 4;
449: break;
450: case DMNAME:
451: alen = inp.read();
452: break;
453: default:
454: throw new SocksException(
455: "Invalid address type received from" + " server: "
456: + atype);
457: }
458:
459: byte[] skip = new byte[alen + 2]; // skip address + port
460: int rcvd = 0, tot = 0;
461: while (tot < skip.length
462: && (rcvd = inp.read(skip, 0, skip.length - tot)) != -1)
463: tot += rcvd;
464: }
465:
466: /**
467: * Negotiates authentication using the gssapi protocol
468: * (draft-ietf-aft-gssapi-02).
469: *
470: * NOTE: this is not implemented currently. Will have to wait till
471: * Java provides the necessary access to the system routines.
472: */
473: private void negotiate_gssapi(InputStream inp, OutputStream out)
474: throws SocksException, IOException {
475: throw new SocksException(
476: "GSSAPI authentication protocol not implemented");
477: }
478:
479: /**
480: * Negotiates authentication using the username/password protocol
481: * (rfc-1929). The username and password should previously have been
482: * stored using the scheme "SOCKS5" and realm "USER/PASS"; e.g.
483: * AuthorizationInfo.addAuthorization(socks_host, socks_port, "SOCKS5",
484: * "USER/PASS", null,
485: * { new NVPair(username, password) });
486: *
487: */
488: private void negotiate_userpwd(InputStream inp, OutputStream out)
489: throws SocksException, IOException {
490: byte[] buffer;
491:
492: if (DebugSocks)
493: System.err
494: .println("Socks: Entering authorization subnegotiation"
495: + "; method: Username/Password");
496:
497: // get username/password
498:
499: AuthorizationInfo auth_info;
500: try {
501: auth_info = AuthorizationInfo.getAuthorization(socks_host,
502: socks_port, "SOCKS5", "USER/PASS", true);
503: } catch (AuthSchemeNotImplException atnie) {
504: auth_info = null;
505: }
506:
507: if (auth_info == null)
508: throw new SocksException(
509: "No Authorization info for SOCKS found "
510: + "(server requested username/password).");
511:
512: NVPair[] unpw = auth_info.getParams();
513: if (unpw == null || unpw.length == 0)
514: throw new SocksException("No Username/Password found in "
515: + "authorization info for SOCKS.");
516:
517: String user_str = unpw[0].getName();
518: String pass_str = unpw[0].getValue();
519:
520: // send them to server
521:
522: if (DebugSocks)
523: System.err
524: .println("Socks: Sending authorization request for user "
525: + user_str);
526:
527: buffer = new byte[1 + 1 + user_str.length() + 1
528: + pass_str.length()];
529: buffer[0] = 1; // version 1 (subnegotiation)
530: buffer[1] = (byte) user_str.length(); // Username length
531: user_str.getBytes(0, buffer[1], buffer, 2); // Username
532: buffer[2 + buffer[1]] = (byte) pass_str.length(); // Password length
533: pass_str.getBytes(0, buffer[2 + buffer[1]], buffer,
534: 2 + buffer[1] + 1);// Password
535: out.write(buffer);
536:
537: // get reply
538:
539: int version = inp.read();
540: if (version != 1)
541: throw new SocksException(
542: "Wrong version received in username/"
543: + "password subnegotiation response: "
544: + version + "; expected: 1");
545:
546: int sts = inp.read();
547: if (sts != 0)
548: throw new SocksException(
549: "Username/Password authentication "
550: + "failed; status: " + sts);
551:
552: if (DebugSocks)
553: System.err.println("Socks: Received response; version: "
554: + version + "; status: " + sts);
555: }
556:
557: /**
558: * produces a string.
559: * @return a string containing the host and port of the socks server
560: */
561: public String toString() {
562: return getClass().getName() + "[" + socks_host + ":"
563: + socks_port + "]";
564: }
565: }
|