/*
* Created on 08-Dec-2004
* Created by Paul Gardner
* Copyright (C) 2004, 2005, 2006 Aelitis, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/
package com.aelitis.azureus.core.proxy.socks.impl;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import org.gudy.azureus2.core3.logging.*;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.HostNameToIPResolver;
import org.gudy.azureus2.core3.util.HostNameToIPResolverListener;
import com.aelitis.azureus.core.proxy.*;
import com.aelitis.azureus.core.proxy.socks.*;
/**
* @author parg
*
*/
public class
AESocksProxyConnectionImpl
implements AESocksProxyConnection, AEProxyConnectionListener
{
private static final LogIDs LOGID = LogIDs.NET;
public static final boolean TRACE = false;
protected AESocksProxyImpl proxy;
protected AEProxyConnection connection;
protected boolean disable_dns_lookups;
protected SocketChannel source_channel;
protected int socks_version;
protected AESocksProxyPlugableConnection plugable_connection;
protected
AESocksProxyConnectionImpl(
AESocksProxyImpl _proxy,
AESocksProxyPlugableConnectionFactory _connection_factory,
AEProxyConnection _connection )
throws IOException
{
proxy = _proxy;
connection = _connection;
connection.addListener( this );
source_channel = connection.getSourceChannel();
try{
plugable_connection = _connection_factory.create( this );
if ( TRACE ){
Logger.log(new LogEvent(LOGID, "AESocksProxyProcessor: " + getName()));
}
}catch( AEProxyException e ){
throw( new IOException( e.getMessage()));
}
}
public AESocksProxy
getProxy()
{
return( proxy );
}
public void
setDelegate(
AESocksProxyPlugableConnection target )
{
plugable_connection = target;
}
protected String
getName()
{
String name = connection.getName() + ", ver = " + socks_version;
name += plugable_connection.getName();
return( name );
}
protected AEProxyState
getInitialState()
{
return( new proxyStateVersion());
}
public void
connectionClosed(
AEProxyConnection con )
{
try{
if ( plugable_connection != null ){
plugable_connection.close();
}
}catch( Throwable e ){
Debug.printStackTrace( e );
}
}
public boolean
isClosed()
{
return( connection.isClosed());
}
public AEProxyConnection
getConnection()
{
return( connection );
}
public void
disableDNSLookups()
{
disable_dns_lookups = true;
}
public void
enableDNSLookups()
{
disable_dns_lookups = false;
}
public void
close()
throws IOException
{
new proxyStateClose();
}
protected class
proxyStateVersion
extends AESocksProxyState
{
protected
proxyStateVersion()
{
super( AESocksProxyConnectionImpl.this );
connection.setReadState( this );
buffer = ByteBuffer.allocate(1);
}
protected boolean
readSupport(
SocketChannel sc )
throws IOException
{
int len = sc.read( buffer );
if ( len == 0 ){
return( false );
}else if ( len == -1 ){
throw( new IOException( "read channel shutdown" ));
}
if ( buffer.hasRemaining()){
return( true );
}
buffer.flip();
int version = buffer.get();
if ( version == 5 ){
new proxyStateV5MethodNumber();
}else if ( version == 4 ){
new proxyStateV4Request();
}else{
throw( new IOException( "Unsupported version " + version ));
}
return( true );
}
}
// V4
protected class
proxyStateV4Request
extends AESocksProxyState
{
boolean got_header;
protected int port;
protected byte[] address;
protected
proxyStateV4Request()
{
super( AESocksProxyConnectionImpl.this );
connection.setReadState( this );
buffer = ByteBuffer.allocate(7);
}
protected boolean
readSupport(
SocketChannel sc )
throws IOException
{
/*
+----+----+----+----+----+----+----+----+----+----+....+----+
| VN | CD | DSTPORT | DSTIP | USERID |NULL|
+----+----+----+----+----+----+----+----+----+----+....+----+
# of bytes: 1 1 2 4 variable 1
*/
int len = sc.read( buffer );
if ( len == 0 ){
return( false );
}else if ( len == -1 ){
throw( new IOException( "read channel shutdown" ));
}
if ( buffer.hasRemaining()){
return( true );
}
buffer.flip();
if ( got_header ){
if ( buffer.get() == (byte)0){
// end of play
if ( address[0] == 0 &&
address[1] == 0 &&
address[2] == 0 &&
address[3] != 0 ){
// socks 4a
new proxyStateV4aRequest( port );
}else{
socks_version = 4;
plugable_connection.connect(
new AESocksProxyAddressImpl( "", InetAddress.getByAddress( address ), port ));
}
}else{
// drop the user id byte
buffer.flip();
}
}else{
got_header = true;
byte command = buffer.get();
if ( command != 1 ){
throw( new IOException( "SocksV4: only CONNECT supported" ));
}
port = (((int)buffer.get() & 0xff) << 8 ) + ((int)buffer.get() & 0xff);
address = new byte[4];
for (int i=0;i<address.length;i++){
address[i] = buffer.get();
}
// prepare for user id
buffer = ByteBuffer.allocate(1);
}
return( true );
}
}
protected class
proxyStateV4aRequest
extends AESocksProxyState
{
protected String dns_address;
protected int port;
protected
proxyStateV4aRequest(
int _port )
{
super( AESocksProxyConnectionImpl.this );
port = _port;
dns_address = "";
connection.setReadState( this );
buffer = ByteBuffer.allocate(1);
}
protected boolean
readSupport(
final SocketChannel sc )
throws IOException
{
// dns name follows, null terminated
int len = sc.read( buffer );
if ( len == 0 ){
return( false );
}else if ( len == -1 ){
throw( new IOException( "read channel shutdown" ));
}
if ( buffer.hasRemaining()){
return( true );
}
buffer.flip();
byte data = buffer.get();
if ( data == 0 ){
if ( disable_dns_lookups ){
socks_version = 4;
plugable_connection.connect( new AESocksProxyAddressImpl( dns_address, null, port ));
}else{
final String f_dns_address = dns_address;
connection.cancelReadSelect( sc );
HostNameToIPResolver.addResolverRequest(
dns_address,
new HostNameToIPResolverListener()
{
public void
hostNameResolutionComplete(
InetAddress address )
{
try{
socks_version = 4;
plugable_connection.connect( new AESocksProxyAddressImpl( f_dns_address, address, port ));
// re-activate the read select suspended while resolving
connection.requestReadSelect( sc );
}catch ( IOException e ){
connection.failed(e);
}
}
});
}
}else{
dns_address += (char)data;
if ( dns_address.length() > 4096 ){
throw( new IOException( "DNS name too long" ));
}
// ready for next byte
buffer.flip();
}
return( true );
}
}
protected class
proxyStateV4Reply
extends AESocksProxyState
{
protected
proxyStateV4Reply()
throws IOException
{
super( AESocksProxyConnectionImpl.this );
/*
+----+----+----+----+----+----+----+----+
| VN | CD | DSTPORT | DSTIP |
+----+----+----+----+----+----+----+----+
# of bytes: 1 1 2 4
*/
connection.setWriteState( this );
byte[] addr = plugable_connection.getLocalAddress().getAddress();
int port = plugable_connection.getLocalPort();
buffer = ByteBuffer.wrap(
new byte[]{ (byte)0,(byte)90,
(byte)((port>>8)&0xff), (byte)(port&0xff),
addr[0],addr[1],addr[2],addr[3]});
write( source_channel );
}
protected boolean
writeSupport(
SocketChannel sc )
throws IOException
{
int len = sc.write( buffer );
if ( buffer.hasRemaining()){
connection.requestWriteSelect( sc );
}else{
plugable_connection.relayData();
}
return( len > 0 );
}
}
// V5
protected class
proxyStateV5MethodNumber
extends AESocksProxyState
{
protected
proxyStateV5MethodNumber()
{
super( AESocksProxyConnectionImpl.this );
connection.setReadState( this );
buffer = ByteBuffer.allocate(1);
}
protected boolean
readSupport(
SocketChannel sc )
throws IOException
{
int len = sc.read( buffer );
if ( len == 0 ){
return( false );
}else if ( len == -1 ){
throw( new IOException( "read channel shutdown" ));
}
if ( buffer.hasRemaining()){
return( true );
}
buffer.flip();
int num_methods = buffer.get();
new proxyStateV5Methods(num_methods);
return( true );
}
}
protected class
proxyStateV5Methods
extends AESocksProxyState
{
protected
proxyStateV5Methods(
int methods )
{
super( AESocksProxyConnectionImpl.this );
connection.setReadState( this );
buffer = ByteBuffer.allocate(methods);
}
protected boolean
readSupport(
SocketChannel sc )
throws IOException
{
int len = sc.read( buffer );
if ( len == 0 ){
return( false );
}else if ( len == -1 ){
throw( new IOException( "read channel shutdown" ));
}
if ( buffer.hasRemaining()){
return( true );
}
// we just ignore actual method values
new proxyStateV5MethodsReply();
return( true );
}
}
protected class
proxyStateV5MethodsReply
extends AESocksProxyState
{
protected
proxyStateV5MethodsReply()
throws IOException
{
super( AESocksProxyConnectionImpl.this );
new proxyStateV5Request();
connection.setWriteState( this );
buffer = ByteBuffer.wrap(new byte[]{(byte)5,(byte)0});
write( source_channel );
}
protected boolean
writeSupport(
SocketChannel sc )
throws IOException
{
int len = sc.write( buffer );
if ( buffer.hasRemaining()){
connection.requestWriteSelect( sc );
}
return( len > 0 );
}
}
/*
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
Where:
o VER protocol version: X'05'
o CMD
o CONNECT X'01'
o BIND X'02'
o UDP ASSOCIATE X'03'
o RSV RESERVED
o ATYP address type of following address
o IP V4 address: X'01'
o DOMAINNAME: X'03'
o IP V6 address: X'04'
o DST.ADDR desired destination address
o DST.PORT desired destination port in network octet
order
*/
protected class
proxyStateV5Request
extends AESocksProxyState
{
protected
proxyStateV5Request()
{
super( AESocksProxyConnectionImpl.this );
connection.setReadState( this );
buffer = ByteBuffer.allocate(4);
}
protected boolean
readSupport(
SocketChannel sc )
throws IOException
{
int len = sc.read( buffer );
if ( len == 0 ){
return( false );
}else if ( len == -1 ){
throw( new IOException( "read channel shutdown" ));
}
if ( buffer.hasRemaining()){
return( true );
}
buffer.flip();
buffer.get(); // version
int command = buffer.get();
buffer.get(); // reserved
int address_type = buffer.get();
if ( command != 1 ){
throw( new IOException( "V5: Only connect supported"));
}
if ( address_type == 1 ){
new proxyStateV5RequestIP();
}else if ( address_type == 3 ){
new proxyStateV5RequestDNS();
}else{
throw( new IOException( "V5: Unsupported address type" ));
}
return( true );
}
}
protected class
proxyStateV5RequestIP
extends AESocksProxyState
{
protected
proxyStateV5RequestIP()
{
super( AESocksProxyConnectionImpl.this );
connection.setReadState( this );
buffer = ByteBuffer.allocate(4);
}
protected boolean
readSupport(
SocketChannel sc )
throws IOException
{
int len = sc.read( buffer );
if ( len == 0 ){
return( false );
}else if ( len == -1 ){
throw( new IOException( "read channel shutdown" ));
}
if ( buffer.hasRemaining()){
return( true );
}
buffer.flip();
byte[] bytes = new byte[4];
buffer.get( bytes );
InetAddress inet_address = InetAddress.getByAddress( bytes );
new proxyStateV5RequestPort( "", inet_address );
return( true );
}
}
protected class
proxyStateV5RequestDNS
extends AESocksProxyState
{
boolean got_length = false;
protected
proxyStateV5RequestDNS()
{
super( AESocksProxyConnectionImpl.this );
connection.setReadState( this );
buffer = ByteBuffer.allocate(1);
}
protected boolean
readSupport(
final SocketChannel sc )
throws IOException
{
int len = sc.read( buffer );
if ( len == 0 ){
return( false );
}else if ( len == -1 ){
throw( new IOException( "read channel shutdown" ));
}
if ( buffer.hasRemaining()){
return( true );
}
buffer.flip();
if ( !got_length){
int length = ((int)buffer.get()) & 0xff;
buffer = ByteBuffer.allocate( length );
got_length = true;
}else{
String dns_address = "";
while( buffer.hasRemaining()){
dns_address += (char)buffer.get();
}
if ( disable_dns_lookups ){
new proxyStateV5RequestPort( dns_address, null );
}else{
final String f_dns_address = dns_address;
connection.cancelReadSelect( sc );
HostNameToIPResolver.addResolverRequest(
dns_address,
new HostNameToIPResolverListener()
{
public void
hostNameResolutionComplete(
InetAddress address )
{
new proxyStateV5RequestPort( f_dns_address, address);
connection.requestReadSelect( sc );
}
});
}
}
return( true );
}
}
protected class
proxyStateV5RequestPort
extends AESocksProxyState
{
protected String unresolved_address;
protected InetAddress address;
protected
proxyStateV5RequestPort(
String _unresolved_address,
InetAddress _address )
{
super( AESocksProxyConnectionImpl.this );
unresolved_address = _unresolved_address;
address = _address;
connection.setReadState( this );
buffer = ByteBuffer.allocate(2);
}
protected boolean
readSupport(
SocketChannel sc )
throws IOException
{
int len = sc.read( buffer );
if ( len == 0 ){
return( false );
}else if ( len == -1 ){
throw( new IOException( "read channel shutdown" ));
}
if ( buffer.hasRemaining()){
return( true );
}
buffer.flip();
int port = (((int)buffer.get() & 0xff) << 8 ) + ((int)buffer.get() & 0xff);
socks_version = 5;
plugable_connection.connect( new AESocksProxyAddressImpl( unresolved_address, address, port ));
return( true );
}
}
/*
+----+-----+-------+------+----------+----------+
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
Where:
o VER protocol version: X'05'
o REP Reply field:
o X'00' succeeded
o X'01' general SOCKS server failure
o X'02' connection not allowed by ruleset
o X'03' Network unreachable
o X'04' Host unreachable
o X'05' Connection refused
o X'06' TTL expired
o X'07' Command not supported
o X'08' Address type not supported
o X'09' to X'FF' unassigned
o RSV RESERVED
o ATYP address type of following address
o IP V4 address: X'01'
o DOMAINNAME: X'03'
o IP V6 address: X'04'
o BND.ADDR server bound address
o BND.PORT server bound port in network octet order
*/
protected class
proxyStateV5Reply
extends AESocksProxyState
{
protected
proxyStateV5Reply()
throws IOException
{
super( AESocksProxyConnectionImpl.this );
connection.setWriteState( this );
byte[] addr = plugable_connection.getLocalAddress().getAddress();
int port = plugable_connection.getLocalPort();
buffer = ByteBuffer.wrap(
new byte[]{(byte)5,(byte)0,(byte)0,(byte)1,
addr[0],addr[1],addr[2],addr[3],
(byte)((port>>8)&0xff), (byte)(port&0xff)});
write( source_channel );
}
protected boolean
writeSupport(
SocketChannel sc )
throws IOException
{
int len = sc.write( buffer );
if ( buffer.hasRemaining()){
connection.requestWriteSelect( sc );
}else{
plugable_connection.relayData();
}
return( len > 0 );
}
}
public void
connected()
throws IOException
{
if ( socks_version == 4 ){
new proxyStateV4Reply();
}else{
new proxyStateV5Reply();
}
}
protected class
proxyStateClose
extends AESocksProxyState
{
protected
proxyStateClose()
throws IOException
{
super( AESocksProxyConnectionImpl.this );
connection.close();
connection.setReadState( null);
connection.setWriteState( null);
connection.setConnectState( null);
}
}
}
|