/*
* This file is part of the OWASP Proxy, a free intercepting proxy library.
* Copyright (C) 2008-2010 Rogan Dawes <rogan@dawes.za.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to:
* The Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
package org.owasp.proxy.socks;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.net.Socket;
import org.owasp.proxy.socks.impl.ProxyMessage;
import org.owasp.proxy.socks.impl.ServerAuthenticator;
import org.owasp.proxy.socks.impl.ServerAuthenticatorNone;
import org.owasp.proxy.socks.impl.Socks4Message;
import org.owasp.proxy.socks.impl.Socks5Message;
import org.owasp.proxy.socks.impl.SocksConstants;
import org.owasp.proxy.socks.impl.SocksException;
public class SocksProtocolHandler {
private Socket socket;
private InputStream in;
private OutputStream out;
private ProxyMessage msg;
private ServerAuthenticator auth;
public SocksProtocolHandler(Socket accept, ServerAuthenticator auth) {
this.socket = accept;
if (auth == null) {
this.auth = new ServerAuthenticatorNone();
} else {
this.auth = auth;
}
}
public InetSocketAddress handleConnectRequest() throws IOException {
try {
return startSession();
} catch (IOException ioe) {
handleException(ioe);
throw ioe;
}
}
private InetSocketAddress startSession() throws IOException {
auth = auth.startSession(socket);
if (auth == null) { // Authentication failed
throw new SocksException(SocksConstants.SOCKS_AUTH_FAILURE);
}
in = auth.getInputStream();
out = auth.getOutputStream();
msg = readMsg(in);
return handleRequest(msg);
}
private void handleException(IOException ioe) {
// If we couldn't read the request, return;
if (msg == null)
return;
int error_code = SocksConstants.SOCKS_FAILURE;
if (ioe instanceof SocksException)
error_code = ((SocksException) ioe).getErrorCode();
else if (ioe instanceof NoRouteToHostException)
error_code = SocksConstants.SOCKS_HOST_UNREACHABLE;
else if (ioe instanceof ConnectException)
error_code = SocksConstants.SOCKS_CONNECTION_REFUSED;
else if (ioe instanceof InterruptedIOException)
error_code = SocksConstants.SOCKS_TTL_EXPIRE;
if (error_code > SocksConstants.SOCKS_ADDR_NOT_SUPPORTED
|| error_code < 0) {
error_code = SocksConstants.SOCKS_FAILURE;
}
sendErrorMessage(error_code);
}
private void sendErrorMessage(int error_code) {
ProxyMessage err_msg;
if (msg instanceof Socks4Message)
err_msg = new Socks4Message(Socks4Message.REPLY_REJECTED);
else
err_msg = new Socks5Message(error_code);
try {
err_msg.write(out);
} catch (IOException ioe) {
}
}
private ProxyMessage readMsg(InputStream in) throws IOException {
PushbackInputStream push_in;
if (in instanceof PushbackInputStream)
push_in = (PushbackInputStream) in;
else
push_in = new PushbackInputStream(in);
int version = push_in.read();
push_in.unread(version);
ProxyMessage msg;
if (version == 5) {
msg = new Socks5Message(push_in, false);
} else if (version == 4) {
msg = new Socks4Message(push_in, false);
} else {
throw new SocksException(SocksConstants.SOCKS_FAILURE);
}
return msg;
}
private InetSocketAddress handleRequest(ProxyMessage msg)
throws IOException {
if (!auth.checkRequest(msg))
throw new SocksException(SocksConstants.SOCKS_FAILURE);
if (msg.ip == null && !(msg instanceof Socks5Message))
// Socks5 allows specifying a host name
throw new SocksException(SocksConstants.SOCKS_FAILURE);
switch (msg.command) {
case SocksConstants.SOCKS_CMD_CONNECT:
return onConnect(msg);
default:
throw new SocksException(SocksConstants.SOCKS_CMD_NOT_SUPPORTED);
}
}
private InetSocketAddress onConnect(ProxyMessage msg) throws IOException {
ProxyMessage response = null;
if (msg instanceof Socks5Message) {
response = new Socks5Message(SocksConstants.SOCKS_SUCCESS, msg.ip,
msg.port);
} else {
response = new Socks4Message(Socks4Message.REPLY_OK, msg.ip,
msg.port);
}
response.write(out);
return new InetSocketAddress(msg.host, msg.port);
}
}