001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.lib.collab.util;
043:
044: import java.net.Socket;
045: import java.net.InetAddress;
046: import java.net.InetSocketAddress;
047: import java.net.SocketAddress;
048: import java.net.UnknownHostException;
049: import java.io.IOException;
050: import java.net.SocketException;
051: import java.io.InputStream;
052: import java.io.OutputStream;
053: import java.io.DataOutputStream;
054: import java.net.Inet6Address;
055:
056: import java.nio.*;
057: import java.nio.channels.*;
058:
059: /**
060: * A standalone SOCKS V5 SocketChannel provider.
061: *
062: * This code is an adaptation of a standalone socket SOCKS 5 client
063: * socket implementation written by Todd Fast and Matt Stevens.
064: * This copying was necessary in order to use SOCKS on a per-socket
065: * basis, as the JDK only allows SOCKS usage across the entire VM.
066: *
067: * @author Jacques Belissent
068: * @author Matt Stevens, matthew.stevens@sun.com
069: * @author Todd Fast, todd.fast@sun.com
070: */
071: public class Socks5SocketChannelAdaptor {
072:
073: static final byte PROTO_VERS4 = 4;
074: static final byte PROTO_VERS = 5;
075: static final int DEFAULT_PORT = 1080;
076:
077: static final byte NO_AUTH = 0;
078: static final byte GSSAPI = 1;
079: static final byte USER_PASSW = 2;
080: static final short NO_METHODS = 0xff;
081:
082: static final byte CONNECT = 1;
083: static final byte BIND = 2;
084: static final byte UDP_ASSOC = 3;
085:
086: static final byte IPV4 = 1;
087: static final byte DOMAIN_NAME = 3;
088: static final byte IPV6 = 4;
089:
090: static final byte REQUEST_OK = 0;
091: static final byte GENERAL_FAILURE = 1;
092: static final byte NOT_ALLOWED = 2;
093: static final byte NET_UNREACHABLE = 3;
094: static final byte HOST_UNREACHABLE = 4;
095: static final byte CONN_REFUSED = 5;
096: static final byte TTL_EXPIRED = 6;
097: static final byte CMD_NOT_SUPPORTED = 7;
098: static final byte ADDR_TYPE_NOT_SUP = 8;
099:
100: /**
101: * open a connection to a remote host via a SOCKS V5 proxy
102: * @param host remote host name
103: * @param host remote port
104: * @param socksHost SOCKS 5 proxy server hostname
105: * @param socksPort SOCKS 5 proxy server port
106: * @param username SOCKS authentication credential's identity
107: * @param password SOCKS authentication credential's password
108: */
109: public static SocketChannel open(String host, int port,
110: String socksHost, int socksPort, String username,
111: String password) throws UnknownHostException, IOException {
112: InetSocketAddress isa = new InetSocketAddress(socksHost,
113: socksPort <= 0 ? DEFAULT_PORT : socksPort);
114: Socks5SocketChannelAdaptor ssca = new Socks5SocketChannelAdaptor(
115: host, port, isa, username, password);
116: return ssca.getChannel();
117: }
118:
119: /**
120: * open a connection to a remote host via a SOCKS5 proxy
121: * @param host remote host name
122: * @param host remote port
123: * @param socksAddress SOCKS 5 proxy server IP address
124: * @param socksPort SOCKS 5 proxy server port
125: * @param username SOCKS authentication credential's identity
126: * @param password SOCKS authentication credential's password
127: */
128: public static SocketChannel open(InetAddress address, int port,
129: InetAddress socksAddress, int socksPort, String username,
130: String password) throws IOException {
131: InetSocketAddress isa = new InetSocketAddress(socksAddress,
132: socksPort <= 0 ? DEFAULT_PORT : socksPort);
133: Socks5SocketChannelAdaptor ssca = new Socks5SocketChannelAdaptor(
134: address, port, isa, username, password);
135: return ssca.getChannel();
136: }
137:
138: /**
139: * open a connection to a remote host via a SOCKS V5 proxy
140: * @param host remote host name
141: * @param host remote port
142: * @param socksProxychannel preconfigured socks proxy channel
143: * @param socksHost SOCKS 5 proxy server hostname
144: * @param socksPort SOCKS 5 proxy server port
145: * @param username SOCKS authentication credential's identity
146: * @param password SOCKS authentication credential's password
147: */
148: public static SocketChannel open(String host, int port,
149: SocketChannel socksProxyChannel, String socksHost,
150: int socksPort, String username, String password)
151: throws UnknownHostException, IOException {
152: InetSocketAddress isa = new InetSocketAddress(socksHost,
153: socksPort <= 0 ? DEFAULT_PORT : socksPort);
154: Socks5SocketChannelAdaptor ssca = new Socks5SocketChannelAdaptor(
155: host, port, socksProxyChannel, isa, username, password);
156: return ssca.getChannel();
157: }
158:
159: //////////////////////////////////////////////////////////////
160: //////// constructors modeled after java.net.Socket //////////
161: //////////////////////////////////////////////////////////////
162:
163: private Socks5SocketChannelAdaptor() throws IOException {
164: _channel = SocketChannel.open();
165: }
166:
167: private Socks5SocketChannelAdaptor(SocketAddress sa,
168: String username, String password) throws IOException {
169: _channel = SocketChannel.open();
170: // no connection at this point
171:
172: // remember SOCKS address for when we do "connect"
173: setProxyAddress(sa);
174: setUsername(username);
175: setPassword(password);
176: }
177:
178: private Socks5SocketChannelAdaptor(String host, int port,
179: SocketAddress sa, String username, String password)
180: throws IOException {
181: _channel = SocketChannel.open(sa);
182: // already connected by now
183:
184: setDestinationAddress(host, port);
185: setUsername(username);
186: setPassword(password);
187: finishConnect();
188: }
189:
190: private Socks5SocketChannelAdaptor(String host, int port,
191: SocketChannel socksProxyChannel, SocketAddress addr,
192: String username, String password) throws IOException {
193: _channel = socksProxyChannel;
194: _channel.connect(addr);
195: // already connected by now
196:
197: setDestinationAddress(host, port);
198: setUsername(username);
199: setPassword(password);
200: finishConnect();
201: }
202:
203: private Socks5SocketChannelAdaptor(InetAddress address, int port,
204: SocketAddress sa, String username, String password)
205: throws IOException {
206: _channel = SocketChannel.open(sa);
207: // already connected by now
208:
209: setDestinationAddress(address, port);
210: setUsername(username);
211: setPassword(password);
212: finishConnect();
213: }
214:
215: // not done
216: private Socks5SocketChannelAdaptor(String host, int port,
217: InetAddress localAddr, int localPort, SocketAddress sa,
218: String username, String password)
219: throws UnknownHostException, IOException {
220: _channel = SocketChannel.open(sa);
221: // already connected by now
222:
223: setDestinationAddress(host, port);
224: setUsername(username);
225: setPassword(password);
226: finishConnect();
227: }
228:
229: private Socks5SocketChannelAdaptor(InetAddress address, int port,
230: InetAddress localAddr, int localPort, SocketAddress sa,
231: String username, String password) throws IOException {
232: _channel = SocketChannel.open(sa);
233: // already connected by now
234:
235: setDestinationAddress(address, port);
236: setUsername(username);
237: setPassword(password);
238: finishConnect();
239: }
240:
241: private Socks5SocketChannelAdaptor(String host, int port,
242: boolean stream, SocketAddress sa, String username,
243: String password) throws IOException {
244: _channel = SocketChannel.open(sa);
245: // already connected by now
246:
247: setDestinationAddress(host, port);
248: setUsername(username);
249: setPassword(password);
250: finishConnect();
251: }
252:
253: private Socks5SocketChannelAdaptor(InetAddress address, int port,
254: boolean stream, SocketAddress sa, String username,
255: String password) throws IOException {
256: _channel = SocketChannel.open(sa);
257: // already connected by now
258:
259: setDestinationAddress(address, port);
260: setUsername(username);
261: setPassword(password);
262: finishConnect();
263: }
264:
265: /////////////////////////////////////////////////////////////////
266: ////////////////////// specializing /////////////////////////////
267: /////////////////////////////////////////////////////////////////
268: private boolean isClosed() {
269: return !_channel.isOpen();
270: }
271:
272: private InetAddress getInetAddress() {
273: if (_channel.socket().getInetAddress() != null
274: && getDestinationAddress() != null)
275: return getDestinationAddress().getAddress();
276: return null;
277: }
278:
279: private int getPort() {
280: int result = _channel.socket().getPort();
281: if (result > 0) {
282: if (getDestinationAddress() == null) {
283: // should not happen
284: result = -1;
285: } else {
286: result = getDestinationAddress().getPort();
287: }
288: }
289: return result;
290: }
291:
292: public String toString() {
293: if (_channel.isConnected()) {
294: return _channel.socket().toString() + " SOCKS[socksAddr="
295: + null + ", socksPort=" + null + "]";
296: }
297: return "Socket[unconnected]";
298: }
299:
300: private InetSocketAddress getProxyAddress() {
301: return socksProxyAddress;
302: }
303:
304: /**
305: * @param port will resolve to default SOCKS server port if <=0
306: */
307: private void setProxyAddress(String host, int port)
308: throws UnknownHostException, IOException {
309: if (_channel.isConnected() || isClosed())
310: throw new IllegalStateException(
311: "Socket is currently connected or has been closed");
312: if (null == host || host.trim().length() == 0)
313: throw new IllegalArgumentException(
314: "parameter 'host' may not be null or blank");
315: setProxyAddress(InetAddress.getByName(host),
316: port <= 0 ? DEFAULT_PORT : port);
317: }
318:
319: /**
320: * @param port will resolve to default SOCKS server port if <=0
321: */
322: private void setProxyAddress(InetAddress address, int port)
323: throws IOException {
324: if (_channel.isConnected() || isClosed())
325: throw new IllegalStateException(
326: "Socket is currently connected or has been closed");
327: if (null == address)
328: throw new IllegalArgumentException(
329: "parameter 'address' may not be null");
330: socksProxyAddress = new InetSocketAddress(address,
331: port <= 0 ? DEFAULT_PORT : port);
332: }
333:
334: private void setProxyAddress(InetSocketAddress address)
335: throws IOException {
336: if (_channel.isConnected() || isClosed())
337: throw new IllegalStateException(
338: "Socket is currently connected or has been closed");
339: if (null == address)
340: throw new IllegalArgumentException(
341: "parameter 'address' may not be null");
342: socksProxyAddress = address;
343: }
344:
345: private void setProxyAddress(SocketAddress address)
346: throws IOException {
347: if (address instanceof InetSocketAddress) {
348: setProxyAddress((InetSocketAddress) address);
349: } else {
350: throw new IllegalArgumentException(
351: "parameter 'address' must be an InetSocketAddress");
352: }
353: }
354:
355: private InetSocketAddress getDestinationAddress() {
356: return destinationAddress;
357: }
358:
359: private void setDestinationAddress(String host, int port)
360: throws UnknownHostException, IOException {
361: if (null == host || host.trim().length() == 0)
362: throw new IllegalArgumentException(
363: "parameter 'host' may not be null or blank");
364: setDestinationAddress(new InetSocketAddress(host,
365: port <= 0 ? DEFAULT_PORT : port));
366: }
367:
368: private void setDestinationAddress(InetAddress address, int port)
369: throws IOException {
370: if (null == address)
371: throw new IllegalArgumentException(
372: "parameter 'address' may not be null");
373: destinationAddress = new InetSocketAddress(address,
374: port <= 0 ? DEFAULT_PORT : port);
375: }
376:
377: private void setDestinationAddress(InetSocketAddress address) {
378: if (null == address)
379: throw new IllegalArgumentException(
380: "parameter 'address' may not be null");
381: destinationAddress = address;
382: }
383:
384: private String getUsername() {
385: return username;
386: }
387:
388: private void setUsername(String username) {
389: this .username = username;
390: }
391:
392: private String getPassword() {
393: return password;
394: }
395:
396: private void setPassword(String password) {
397: this .password = password;
398: }
399:
400: public SocketChannel getChannel() {
401: return _channel;
402: }
403:
404: private InetSocketAddress socksProxyAddress;
405: private InetSocketAddress destinationAddress;
406: private String username;
407: private String password;
408: private SocketChannel _channel = null;
409:
410: private int readSocksReply(InputStream in, byte[] data)
411: throws IOException {
412: int len = data.length;
413: int received = 0;
414: for (int attempts = 0; received < len && attempts < 3; attempts++) {
415: int count = in.read(data, received, len - received);
416: if (count < 0)
417: throw new SocketException(
418: "Malformed reply from SOCKS server");
419: received += count;
420: }
421: return received;
422: }
423:
424: private boolean authenticate(byte method, InputStream in,
425: DataOutputStream out) throws IOException {
426:
427: // No Authentication required. We're done then!
428: if (method == NO_AUTH)
429: return true;
430:
431: if (method == USER_PASSW) {
432: if (getUsername() == null)
433: return false;
434:
435: out.write((byte) 1);
436: out.write((byte) getUsername().length());
437: out.write(getUsername().getBytes());
438:
439: if (getPassword() != null) {
440: out.write((byte) getPassword().length());
441: out.write(getPassword().getBytes());
442: } else {
443: out.write((byte) 0);
444: }
445:
446: out.flush();
447:
448: byte[] data = new byte[2];
449: int i = readSocksReply(in, data);
450: if (i != 2 || data[1] != 0) {
451: /* RFC 1929 specifies that the connection MUST be closed if
452: authentication fails */
453: out.close();
454: //in.close();
455: _channel.close();
456: return false;
457: }
458: /* Authentication succeeded */
459: return true;
460: }
461: return false;
462: }
463:
464: // method was called 'connect'
465: private void finishConnect() throws IOException {
466: DataOutputStream out = new DataOutputStream(_channel.socket()
467: .getOutputStream());
468: InputStream in = _channel.socket().getInputStream();
469:
470: // This is SOCKS V5
471: out.write(PROTO_VERS);
472: out.write((byte) 2);
473: out.write(NO_AUTH);
474: out.write(USER_PASSW);
475: out.flush();
476: byte[] data = new byte[2];
477: int i = readSocksReply(in, data);
478: if (i != 2 || ((int) data[1]) == NO_METHODS)
479: throw new SocketException("SOCKS : No acceptable methods");
480: if (!authenticate(data[1], in, out))
481: throw new SocketException("SOCKS : authentication failed");
482:
483: out.write(PROTO_VERS);
484: out.write(CONNECT);
485: out.write((byte) 0);
486:
487: InetSocketAddress epoint = getDestinationAddress();
488:
489: /* Test for IPV4/IPV6/Unresolved */
490: if (epoint.isUnresolved()) {
491: out.write(DOMAIN_NAME);
492: out.write(epoint.getHostName().length());
493: out.write(epoint.getHostName().getBytes());
494: out.write((byte) ((epoint.getPort() >> 8) & 0xff));
495: out.write((byte) ((epoint.getPort() >> 0) & 0xff));
496: } else if (epoint.getAddress() instanceof Inet6Address) {
497: out.write(IPV6);
498: out.write(epoint.getAddress().getAddress());
499: out.write((byte) ((epoint.getPort() >> 8) & 0xff));
500: out.write((byte) ((epoint.getPort() >> 0) & 0xff));
501: } else {
502: out.write(IPV4);
503: out.write(epoint.getAddress().getAddress());
504: out.write((byte) ((epoint.getPort() >> 8) & 0xff));
505: out.write((byte) ((epoint.getPort() >> 0) & 0xff));
506: }
507: out.flush();
508: data = new byte[4];
509: i = readSocksReply(in, data);
510: if (i != 4)
511: throw new SocketException(
512: "Reply from SOCKS server has bad length");
513: SocketException ex = null;
514: int nport, len;
515: byte[] addr;
516: switch (data[1]) {
517: case REQUEST_OK:
518: // success!
519: switch (data[3]) {
520: case IPV4:
521: addr = new byte[4];
522: i = readSocksReply(in, addr);
523: if (i != 4)
524: throw new SocketException(
525: "Reply from SOCKS server badly formatted");
526: data = new byte[2];
527: i = readSocksReply(in, data);
528: if (i != 2)
529: throw new SocketException(
530: "Reply from SOCKS server badly formatted");
531: nport = ((int) data[0] & 0xff) << 8;
532: nport += ((int) data[1] & 0xff);
533: break;
534: case DOMAIN_NAME:
535: len = data[1];
536: byte[] host = new byte[len];
537: i = readSocksReply(in, host);
538: if (i != len)
539: throw new SocketException(
540: "Reply from SOCKS server badly formatted");
541: data = new byte[2];
542: i = readSocksReply(in, data);
543: if (i != 2)
544: throw new SocketException(
545: "Reply from SOCKS server badly formatted");
546: nport = ((int) data[0] & 0xff) << 8;
547: nport += ((int) data[1] & 0xff);
548: break;
549: case IPV6:
550: len = data[1];
551: addr = new byte[len];
552: i = readSocksReply(in, addr);
553: if (i != len)
554: throw new SocketException(
555: "Reply from SOCKS server badly formatted");
556: data = new byte[2];
557: i = readSocksReply(in, data);
558: if (i != 2)
559: throw new SocketException(
560: "Reply from SOCKS server badly formatted");
561: nport = ((int) data[0] & 0xff) << 8;
562: nport += ((int) data[1] & 0xff);
563: break;
564: default:
565: ex = new SocketException(
566: "Reply from SOCKS server contains wrong code");
567: break;
568: }
569: break;
570: case GENERAL_FAILURE:
571: ex = new SocketException("SOCKS server general failure");
572: break;
573: case NOT_ALLOWED:
574: ex = new SocketException(
575: "SOCKS: Connection not allowed by ruleset");
576: break;
577: case NET_UNREACHABLE:
578: ex = new SocketException("SOCKS: Network unreachable");
579: break;
580: case HOST_UNREACHABLE:
581: ex = new SocketException("SOCKS: Host unreachable");
582: break;
583: case CONN_REFUSED:
584: ex = new SocketException("SOCKS: Connection refused");
585: break;
586: case TTL_EXPIRED:
587: ex = new SocketException("SOCKS: TTL expired");
588: break;
589: case CMD_NOT_SUPPORTED:
590: ex = new SocketException("SOCKS: Command not supported");
591: break;
592: case ADDR_TYPE_NOT_SUP:
593: ex = new SocketException(
594: "SOCKS: address type not supported");
595: break;
596: }
597:
598: if (ex != null) {
599: //in.close();
600: out.close();
601: _channel.close();
602: throw ex;
603: }
604: }
605:
606: /////////////////////////////////////////////////////////////////
607: /////////////////////// test ////////////////////////////////////
608: /////////////////////////////////////////////////////////////////
609:
610: public static void main(String[] args) {
611: final String HTTP_GET = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>\r\n";
612: String sHostPort = "localhost";
613: String sProxyHostPort = "localhost";
614: String user = "foo";
615: String password = "bar";
616: if (args.length >= 1)
617: sHostPort = args[0];
618: if (args.length >= 2)
619: sProxyHostPort = args[1];
620: if (args.length == 4) {
621: user = args[2];
622: password = args[3];
623: }
624:
625: try {
626: HostPort server = new HostPort(sHostPort, 80);
627: HostPort proxy = new HostPort(sProxyHostPort, 1080);
628:
629: SocketChannel sc = Socks5SocketChannelAdaptor.open(server
630: .getHostName(), server.getPort(), proxy
631: .getHostName(), proxy.getPort(), user, password);
632:
633: System.out.println("Sending -> " + HTTP_GET);
634: ByteBuffer bb = ByteBuffer.wrap(HTTP_GET
635: .getBytes("US-ASCII"));
636: sc.write(bb);
637:
638: System.out.println("Receiving <-");
639: int len;
640: bb = ByteBuffer.allocate(1024);
641: byte[] b = new byte[1024];
642: do {
643: bb.rewind();
644: len = sc.read(bb);
645: System.out.println("Receiving <- " + len);
646: if (len > 0) {
647: bb.flip();
648: bb.get(b, 0, len);
649: System.out.print(new String(b, 0, len));
650: }
651: } while (len >= 0);
652:
653: sc.close();
654:
655: } catch (Exception e) {
656: e.printStackTrace();
657: }
658: }
659: }
|