/*
This file is part of leafdigital leafChat.
leafChat 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 3 of the License, or
(at your option) any later version.
leafChat 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 leafChat. If not, see <http://www.gnu.org/licenses/>.
Copyright 2011 Samuel Marshall.
*/
package com.leafdigital.net;
import java.io.*;
import java.net.*;
import leafchat.core.api.PluginContext;
/**
* Shared code in a socket for communicating with a SOCKS5 server.
*/
public abstract class SOCKS5Base extends Socket
{
protected InputStream input;
protected OutputStream output;
/**
* Constructs server connection, sets timeout to 10 seconds, and leaves it
* ready to send commands.
* @param context Context (for SOCKS details and logging)
* @throws IOException If there is any error
*/
public SOCKS5Base(PluginContext context) throws IOException
{
NetPlugin np=(NetPlugin)context.getPlugin();
String proxyHost=np.getSOCKSHost();
int proxyPort=np.getSOCKSPort();
String username=np.getSOCKSUsername(),password=np.getSOCKSPassword();
try
{
connect(new InetSocketAddress(proxyHost,proxyPort));
input=super.getInputStream();
output=super.getOutputStream();
}
catch(IOException e)
{
throw new SOCKS5Exception("Error connecting to proxy. Check proxy address and port are correct.",e);
}
setSoTimeout(10000);
boolean ok=false;
try
{
// Init and method select
output.write(5); // Version 5
output.write(2); // 2 methods
output.write(0); // No authentication
output.write(2); // Username/password
output.flush();
int version=input.read(); // Ignore
context.logDebug("Connected to SOCKS5 proxy "+proxyHost+":"+proxyPort+", version "+version);
int method=input.read();
if(method==255 || (method!=0 && method!=2))
{
throw new SOCKS5Exception("Proxy requires an authentication method leafChat does not support. leafChat cannot use this proxy.");
}
// Method-specific negotiation
if(method==2)
{
output.write(1);
output.write(username.length());
output.write(username.getBytes("ISO-8859-1"));
output.write(password.length());
output.write(password.getBytes("ISO-8859-1"));
output.flush();
input.read(); // Version
int status=input.read();
if(status!=0)
{
throw new SOCKS5Exception("Proxy did not accept username/password combination. Check these are correct.");
}
}
ok=true;
}
catch(SocketTimeoutException e)
{
throw new SOCKS5Exception("Timeout when communicating with proxy. Check proxy address and port are correct.",e);
}
catch(IOException e)
{
if(e instanceof SOCKS5Exception) throw e;
throw new SOCKS5Exception("Error communicating with proxy. Check proxy address and port are correct.",e);
}
finally
{
if(!ok) close();
}
}
@Override
public InputStream getInputStream()
{
return input;
}
@Override
public OutputStream getOutputStream()
{
return output;
}
/**
* Writes an address to the SOCKS connection in domain-name format (including
* sending the type field).
* @param host Host name
* @param port Port
* @throws IOException
*/
protected void writeAddress(String host,int port) throws IOException
{
output.write(3); // Domain name
// Actual name
output.write(host.length());
output.write(host.getBytes("ISO-8859-1"));
// Port in network byte order
int port1=port>>8,port2=port&0xff;
output.write(port1);
output.write(port2);
}
void readFully(byte[] buffer) throws IOException
{
int pos=0;
while(pos<buffer.length)
{
int read=input.read(buffer,pos,buffer.length-pos);
if(read<1) throw new IOException("Unexpected EOF");
pos+=read;
}
}
protected InetSocketAddress readAddress() throws IOException
{
// Address
int addressType=input.read();
InetAddress address;
switch(addressType)
{
case 1: // IPv4
byte[] ip4=new byte[4];
readFully(ip4);
address=Inet4Address.getByAddress(ip4);
break;
case 3: // Domain name
int size=input.read();
byte[] domain=new byte[size];
readFully(domain);
address=InetAddress.getByName(new String(domain,"US-ASCII"));
break;
case 4: // IPv6
byte[] ip6=new byte[16];
readFully(ip6);
address=Inet6Address.getByAddress(ip6);
break;
default:
throw new SOCKS5Exception("Unexpected address type in SOCKS server response: "+addressType);
}
// Port
int port=( input.read() << 8 ) | input.read();
return new InetSocketAddress(address,port);
}
}